smu.c
来自「linux 内核源代码」· C语言 代码 · 共 1,301 行 · 第 1/2 页
C
1,301 行
* for power management. * For now, we don't power manage machines with an SMU chip, * I'm a bit too far from figuring out how that works with those * new chipsets, but that will come back and bite us */ of_register_platform_driver(&smu_of_platform_driver); return 0;}device_initcall(smu_init_sysfs);struct of_device *smu_get_ofdev(void){ if (!smu) return NULL; return smu->of_dev;}EXPORT_SYMBOL_GPL(smu_get_ofdev);/* * i2c interface */static void smu_i2c_complete_command(struct smu_i2c_cmd *cmd, int fail){ void (*done)(struct smu_i2c_cmd *cmd, void *misc) = cmd->done; void *misc = cmd->misc; unsigned long flags; /* Check for read case */ if (!fail && cmd->read) { if (cmd->pdata[0] < 1) fail = 1; else memcpy(cmd->info.data, &cmd->pdata[1], cmd->info.datalen); } DPRINTK("SMU: completing, success: %d\n", !fail); /* Update status and mark no pending i2c command with lock * held so nobody comes in while we dequeue an eventual * pending next i2c command */ spin_lock_irqsave(&smu->lock, flags); smu->cmd_i2c_cur = NULL; wmb(); cmd->status = fail ? -EIO : 0; /* Is there another i2c command waiting ? */ if (!list_empty(&smu->cmd_i2c_list)) { struct smu_i2c_cmd *newcmd; /* Fetch it, new current, remove from list */ newcmd = list_entry(smu->cmd_i2c_list.next, struct smu_i2c_cmd, link); smu->cmd_i2c_cur = newcmd; list_del(&cmd->link); /* Queue with low level smu */ list_add_tail(&cmd->scmd.link, &smu->cmd_list); if (smu->cmd_cur == NULL) smu_start_cmd(); } spin_unlock_irqrestore(&smu->lock, flags); /* Call command completion handler if any */ if (done) done(cmd, misc);}static void smu_i2c_retry(unsigned long data){ struct smu_i2c_cmd *cmd = smu->cmd_i2c_cur; DPRINTK("SMU: i2c failure, requeuing...\n"); /* requeue command simply by resetting reply_len */ cmd->pdata[0] = 0xff; cmd->scmd.reply_len = sizeof(cmd->pdata); smu_queue_cmd(&cmd->scmd);}static void smu_i2c_low_completion(struct smu_cmd *scmd, void *misc){ struct smu_i2c_cmd *cmd = misc; int fail = 0; DPRINTK("SMU: i2c compl. stage=%d status=%x pdata[0]=%x rlen: %x\n", cmd->stage, scmd->status, cmd->pdata[0], scmd->reply_len); /* Check for possible status */ if (scmd->status < 0) fail = 1; else if (cmd->read) { if (cmd->stage == 0) fail = cmd->pdata[0] != 0; else fail = cmd->pdata[0] >= 0x80; } else { fail = cmd->pdata[0] != 0; } /* Handle failures by requeuing command, after 5ms interval */ if (fail && --cmd->retries > 0) { DPRINTK("SMU: i2c failure, starting timer...\n"); BUG_ON(cmd != smu->cmd_i2c_cur); if (!smu_irq_inited) { mdelay(5); smu_i2c_retry(0); return; } mod_timer(&smu->i2c_timer, jiffies + msecs_to_jiffies(5)); return; } /* If failure or stage 1, command is complete */ if (fail || cmd->stage != 0) { smu_i2c_complete_command(cmd, fail); return; } DPRINTK("SMU: going to stage 1\n"); /* Ok, initial command complete, now poll status */ scmd->reply_buf = cmd->pdata; scmd->reply_len = sizeof(cmd->pdata); scmd->data_buf = cmd->pdata; scmd->data_len = 1; cmd->pdata[0] = 0; cmd->stage = 1; cmd->retries = 20; smu_queue_cmd(scmd);}int smu_queue_i2c(struct smu_i2c_cmd *cmd){ unsigned long flags; if (smu == NULL) return -ENODEV; /* Fill most fields of scmd */ cmd->scmd.cmd = SMU_CMD_I2C_COMMAND; cmd->scmd.done = smu_i2c_low_completion; cmd->scmd.misc = cmd; cmd->scmd.reply_buf = cmd->pdata; cmd->scmd.reply_len = sizeof(cmd->pdata); cmd->scmd.data_buf = (u8 *)(char *)&cmd->info; cmd->scmd.status = 1; cmd->stage = 0; cmd->pdata[0] = 0xff; cmd->retries = 20; cmd->status = 1; /* Check transfer type, sanitize some "info" fields * based on transfer type and do more checking */ cmd->info.caddr = cmd->info.devaddr; cmd->read = cmd->info.devaddr & 0x01; switch(cmd->info.type) { case SMU_I2C_TRANSFER_SIMPLE: memset(&cmd->info.sublen, 0, 4); break; case SMU_I2C_TRANSFER_COMBINED: cmd->info.devaddr &= 0xfe; case SMU_I2C_TRANSFER_STDSUB: if (cmd->info.sublen > 3) return -EINVAL; break; default: return -EINVAL; } /* Finish setting up command based on transfer direction */ if (cmd->read) { if (cmd->info.datalen > SMU_I2C_READ_MAX) return -EINVAL; memset(cmd->info.data, 0xff, cmd->info.datalen); cmd->scmd.data_len = 9; } else { if (cmd->info.datalen > SMU_I2C_WRITE_MAX) return -EINVAL; cmd->scmd.data_len = 9 + cmd->info.datalen; } DPRINTK("SMU: i2c enqueuing command\n"); DPRINTK("SMU: %s, len=%d bus=%x addr=%x sub0=%x type=%x\n", cmd->read ? "read" : "write", cmd->info.datalen, cmd->info.bus, cmd->info.caddr, cmd->info.subaddr[0], cmd->info.type); /* Enqueue command in i2c list, and if empty, enqueue also in * main command list */ spin_lock_irqsave(&smu->lock, flags); if (smu->cmd_i2c_cur == NULL) { smu->cmd_i2c_cur = cmd; list_add_tail(&cmd->scmd.link, &smu->cmd_list); if (smu->cmd_cur == NULL) smu_start_cmd(); } else list_add_tail(&cmd->link, &smu->cmd_i2c_list); spin_unlock_irqrestore(&smu->lock, flags); return 0;}/* * Handling of "partitions" */static int smu_read_datablock(u8 *dest, unsigned int addr, unsigned int len){ DECLARE_COMPLETION_ONSTACK(comp); unsigned int chunk; struct smu_cmd cmd; int rc; u8 params[8]; /* We currently use a chunk size of 0xe. We could check the * SMU firmware version and use bigger sizes though */ chunk = 0xe; while (len) { unsigned int clen = min(len, chunk); cmd.cmd = SMU_CMD_MISC_ee_COMMAND; cmd.data_len = 7; cmd.data_buf = params; cmd.reply_len = chunk; cmd.reply_buf = dest; cmd.done = smu_done_complete; cmd.misc = ∁ params[0] = SMU_CMD_MISC_ee_GET_DATABLOCK_REC; params[1] = 0x4; *((u32 *)¶ms[2]) = addr; params[6] = clen; rc = smu_queue_cmd(&cmd); if (rc) return rc; wait_for_completion(&comp); if (cmd.status != 0) return rc; if (cmd.reply_len != clen) { printk(KERN_DEBUG "SMU: short read in " "smu_read_datablock, got: %d, want: %d\n", cmd.reply_len, clen); return -EIO; } len -= clen; addr += clen; dest += clen; } return 0;}static struct smu_sdbp_header *smu_create_sdb_partition(int id){ DECLARE_COMPLETION_ONSTACK(comp); struct smu_simple_cmd cmd; unsigned int addr, len, tlen; struct smu_sdbp_header *hdr; struct property *prop; /* First query the partition info */ DPRINTK("SMU: Query partition infos ... (irq=%d)\n", smu->db_irq); smu_queue_simple(&cmd, SMU_CMD_PARTITION_COMMAND, 2, smu_done_complete, &comp, SMU_CMD_PARTITION_LATEST, id); wait_for_completion(&comp); DPRINTK("SMU: done, status: %d, reply_len: %d\n", cmd.cmd.status, cmd.cmd.reply_len); /* Partition doesn't exist (or other error) */ if (cmd.cmd.status != 0 || cmd.cmd.reply_len != 6) return NULL; /* Fetch address and length from reply */ addr = *((u16 *)cmd.buffer); len = cmd.buffer[3] << 2; /* Calucluate total length to allocate, including the 17 bytes * for "sdb-partition-XX" that we append at the end of the buffer */ tlen = sizeof(struct property) + len + 18; prop = kzalloc(tlen, GFP_KERNEL); if (prop == NULL) return NULL; hdr = (struct smu_sdbp_header *)(prop + 1); prop->name = ((char *)prop) + tlen - 18; sprintf(prop->name, "sdb-partition-%02x", id); prop->length = len; prop->value = hdr; prop->next = NULL; /* Read the datablock */ if (smu_read_datablock((u8 *)hdr, addr, len)) { printk(KERN_DEBUG "SMU: datablock read failed while reading " "partition %02x !\n", id); goto failure; } /* Got it, check a few things and create the property */ if (hdr->id != id) { printk(KERN_DEBUG "SMU: Reading partition %02x and got " "%02x !\n", id, hdr->id); goto failure; } if (prom_add_property(smu->of_node, prop)) { printk(KERN_DEBUG "SMU: Failed creating sdb-partition-%02x " "property !\n", id); goto failure; } return hdr; failure: kfree(prop); return NULL;}/* Note: Only allowed to return error code in pointers (using ERR_PTR) * when interruptible is 1 */const struct smu_sdbp_header *__smu_get_sdb_partition(int id, unsigned int *size, int interruptible){ char pname[32]; const struct smu_sdbp_header *part; if (!smu) return NULL; sprintf(pname, "sdb-partition-%02x", id); DPRINTK("smu_get_sdb_partition(%02x)\n", id); if (interruptible) { int rc; rc = mutex_lock_interruptible(&smu_part_access); if (rc) return ERR_PTR(rc); } else mutex_lock(&smu_part_access); part = of_get_property(smu->of_node, pname, size); if (part == NULL) { DPRINTK("trying to extract from SMU ...\n"); part = smu_create_sdb_partition(id); if (part != NULL && size) *size = part->len << 2; } mutex_unlock(&smu_part_access); return part;}const struct smu_sdbp_header *smu_get_sdb_partition(int id, unsigned int *size){ return __smu_get_sdb_partition(id, size, 0);}EXPORT_SYMBOL(smu_get_sdb_partition);/* * Userland driver interface */static LIST_HEAD(smu_clist);static DEFINE_SPINLOCK(smu_clist_lock);enum smu_file_mode { smu_file_commands, smu_file_events, smu_file_closing};struct smu_private{ struct list_head list; enum smu_file_mode mode; int busy; struct smu_cmd cmd; spinlock_t lock; wait_queue_head_t wait; u8 buffer[SMU_MAX_DATA];};static int smu_open(struct inode *inode, struct file *file){ struct smu_private *pp; unsigned long flags; pp = kzalloc(sizeof(struct smu_private), GFP_KERNEL); if (pp == 0) return -ENOMEM; spin_lock_init(&pp->lock); pp->mode = smu_file_commands; init_waitqueue_head(&pp->wait); spin_lock_irqsave(&smu_clist_lock, flags); list_add(&pp->list, &smu_clist); spin_unlock_irqrestore(&smu_clist_lock, flags); file->private_data = pp; return 0;}static void smu_user_cmd_done(struct smu_cmd *cmd, void *misc){ struct smu_private *pp = misc; wake_up_all(&pp->wait);}static ssize_t smu_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos){ struct smu_private *pp = file->private_data; unsigned long flags; struct smu_user_cmd_hdr hdr; int rc = 0; if (pp->busy) return -EBUSY; else if (copy_from_user(&hdr, buf, sizeof(hdr))) return -EFAULT; else if (hdr.cmdtype == SMU_CMDTYPE_WANTS_EVENTS) { pp->mode = smu_file_events; return 0; } else if (hdr.cmdtype == SMU_CMDTYPE_GET_PARTITION) { const struct smu_sdbp_header *part; part = __smu_get_sdb_partition(hdr.cmd, NULL, 1); if (part == NULL) return -EINVAL; else if (IS_ERR(part)) return PTR_ERR(part); return 0; } else if (hdr.cmdtype != SMU_CMDTYPE_SMU) return -EINVAL; else if (pp->mode != smu_file_commands) return -EBADFD; else if (hdr.data_len > SMU_MAX_DATA) return -EINVAL; spin_lock_irqsave(&pp->lock, flags); if (pp->busy) { spin_unlock_irqrestore(&pp->lock, flags); return -EBUSY; } pp->busy = 1; pp->cmd.status = 1; spin_unlock_irqrestore(&pp->lock, flags); if (copy_from_user(pp->buffer, buf + sizeof(hdr), hdr.data_len)) { pp->busy = 0; return -EFAULT; } pp->cmd.cmd = hdr.cmd; pp->cmd.data_len = hdr.data_len; pp->cmd.reply_len = SMU_MAX_DATA; pp->cmd.data_buf = pp->buffer; pp->cmd.reply_buf = pp->buffer; pp->cmd.done = smu_user_cmd_done; pp->cmd.misc = pp; rc = smu_queue_cmd(&pp->cmd); if (rc < 0) return rc; return count;}static ssize_t smu_read_command(struct file *file, struct smu_private *pp, char __user *buf, size_t count){ DECLARE_WAITQUEUE(wait, current); struct smu_user_reply_hdr hdr; unsigned long flags; int size, rc = 0; if (!pp->busy) return 0; if (count < sizeof(struct smu_user_reply_hdr)) return -EOVERFLOW; spin_lock_irqsave(&pp->lock, flags); if (pp->cmd.status == 1) { if (file->f_flags & O_NONBLOCK) return -EAGAIN; add_wait_queue(&pp->wait, &wait); for (;;) { set_current_state(TASK_INTERRUPTIBLE); rc = 0; if (pp->cmd.status != 1) break; rc = -ERESTARTSYS; if (signal_pending(current)) break; spin_unlock_irqrestore(&pp->lock, flags); schedule(); spin_lock_irqsave(&pp->lock, flags); } set_current_state(TASK_RUNNING); remove_wait_queue(&pp->wait, &wait); } spin_unlock_irqrestore(&pp->lock, flags); if (rc) return rc; if (pp->cmd.status != 0) pp->cmd.reply_len = 0; size = sizeof(hdr) + pp->cmd.reply_len; if (count < size) size = count; rc = size; hdr.status = pp->cmd.status; hdr.reply_len = pp->cmd.reply_len; if (copy_to_user(buf, &hdr, sizeof(hdr))) return -EFAULT; size -= sizeof(hdr); if (size && copy_to_user(buf + sizeof(hdr), pp->buffer, size)) return -EFAULT; pp->busy = 0; return rc;}static ssize_t smu_read_events(struct file *file, struct smu_private *pp, char __user *buf, size_t count){ /* Not implemented */ msleep_interruptible(1000); return 0;}static ssize_t smu_read(struct file *file, char __user *buf, size_t count, loff_t *ppos){ struct smu_private *pp = file->private_data; if (pp->mode == smu_file_commands) return smu_read_command(file, pp, buf, count); if (pp->mode == smu_file_events) return smu_read_events(file, pp, buf, count); return -EBADFD;}static unsigned int smu_fpoll(struct file *file, poll_table *wait){ struct smu_private *pp = file->private_data; unsigned int mask = 0; unsigned long flags; if (pp == 0) return 0; if (pp->mode == smu_file_commands) { poll_wait(file, &pp->wait, wait); spin_lock_irqsave(&pp->lock, flags); if (pp->busy && pp->cmd.status != 1) mask |= POLLIN; spin_unlock_irqrestore(&pp->lock, flags); } if (pp->mode == smu_file_events) { /* Not yet implemented */ } return mask;}static int smu_release(struct inode *inode, struct file *file){ struct smu_private *pp = file->private_data; unsigned long flags; unsigned int busy; if (pp == 0) return 0; file->private_data = NULL; /* Mark file as closing to avoid races with new request */ spin_lock_irqsave(&pp->lock, flags); pp->mode = smu_file_closing; busy = pp->busy; /* Wait for any pending request to complete */ if (busy && pp->cmd.status == 1) { DECLARE_WAITQUEUE(wait, current); add_wait_queue(&pp->wait, &wait); for (;;) { set_current_state(TASK_UNINTERRUPTIBLE); if (pp->cmd.status != 1) break; spin_unlock_irqrestore(&pp->lock, flags); schedule(); spin_lock_irqsave(&pp->lock, flags); } set_current_state(TASK_RUNNING); remove_wait_queue(&pp->wait, &wait); } spin_unlock_irqrestore(&pp->lock, flags); spin_lock_irqsave(&smu_clist_lock, flags); list_del(&pp->list); spin_unlock_irqrestore(&smu_clist_lock, flags); kfree(pp); return 0;}static const struct file_operations smu_device_fops = { .llseek = no_llseek, .read = smu_read, .write = smu_write, .poll = smu_fpoll, .open = smu_open, .release = smu_release,};static struct miscdevice pmu_device = { MISC_DYNAMIC_MINOR, "smu", &smu_device_fops};static int smu_device_init(void){ if (!smu) return -ENODEV; if (misc_register(&pmu_device) < 0) printk(KERN_ERR "via-pmu: cannot register misc device.\n"); return 0;}device_initcall(smu_device_init);
⌨️ 快捷键说明
复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?