46ae3a601b
git-svn-id: http://svn.sabayonlinux.org/overlay@1704 d7aec97c-591d-0410-af39-a8856400b30a
4160 lines
131 KiB
Diff
4160 lines
131 KiB
Diff
Index: linux-2.6.23-rc6/Documentation/block/disk-protection.txt
|
|
===================================================================
|
|
--- /dev/null
|
|
+++ linux-2.6.23-rc6/Documentation/block/disk-protection.txt
|
|
@@ -0,0 +1,79 @@
|
|
+Hard disk protection
|
|
+====================
|
|
+
|
|
+
|
|
+Intro
|
|
+-----
|
|
+ATA/ATAPI-7 specifies the IDLE IMMEDIATE command with UNLOAD FEATURE.
|
|
+Issuing this command should cause the drive to switch to idle mode and
|
|
+unload disk heads. This feature is being used in modern laptops in
|
|
+conjunction with accelerometers and appropriate software to implement
|
|
+a shock protection facility. The idea is to stop all I/O operations on
|
|
+the internal hard drive and park its heads on the ramp when critical
|
|
+situations are anticipated. The desire to have such a feature
|
|
+available on GNU/Linux systems has been the original motivation to
|
|
+implement a generic disk parking interface in the Linux kernel.
|
|
+
|
|
+
|
|
+The interface
|
|
+-------------
|
|
+The interface works as follows: Writing an integer value to
|
|
+/sys/block/*/queue/protect will park the respective drive and freeze
|
|
+the block layer queue for the specified number of seconds. When the
|
|
+timeout expires and no further disk park request has been issued in
|
|
+the meantime, the queue is unfrozen and accumulated I/O operations are
|
|
+performed.
|
|
+
|
|
+IMPORTANT NOTE:
|
|
+Not all ATA drives implement IDLE IMMEDIATE with UNLOAD FEATURE and
|
|
+quite a few of those that do so, don't report this capability as
|
|
+described in the specs. When a disk park has been requested through
|
|
+sysfs as described above, the kernel will try to determine if the
|
|
+drive supports the UNLOAD FEATURE by default. The kernel will only
|
|
+rely on the IDLE IMMEDIATE with UNLOAD FEATURE command if it is
|
|
+convinced that this command is actually supported by the disk drive;
|
|
+otherwise, it will fall back to STANDBY IMMEDIATE. Resuming from the
|
|
+latter will take much longer and it is generally more likely to have a
|
|
+negative impact on the drive's lifetime due to the inclease of spin
|
|
+down and up cycles. If you want to use this interface in a shock
|
|
+protection framework and you know that your drive does indeed support
|
|
+the IDLE IMMEDIATE with UNLOAD FEATURE command despite not saying so,
|
|
+you can force the kernel to issue that command by doing the following
|
|
+on the command line:
|
|
+# echo -n unload > /sys/block/sda/queue/protect_method
|
|
+(replace sda by the drive identifier as appropriate).
|
|
+
|
|
+/sys/block/*/queue/protect_method accepts auto, unload and standby
|
|
+respectively. Reading from protect_method shows the available options
|
|
+surrounding the active one with brackets. When auto is active, this
|
|
+will change to whatever the kernel sees fit after the next disk park
|
|
+command has been issued.
|
|
+
|
|
+
|
|
+References
|
|
+----------
|
|
+
|
|
+There are several laptops from different brands featuring shock
|
|
+protection capabilities. As manufacturers have refused to support open
|
|
+source development of the required software components so far, Linux
|
|
+support for shock protection varies considerably between different
|
|
+hardware implementations. Ideally, this section should contain a list
|
|
+of poiters at different projects aiming at an implementation of shock
|
|
+protection on different systeems. Unfortunately, I only know of a
|
|
+single project which, although still considered experimental, is fit
|
|
+for use. Please feel free to add projects that have been the victims
|
|
+of my ignorance.
|
|
+
|
|
+- http://www.thinkwiki.org/wiki/HDAPS
|
|
+ See this page for information about Linux support of the hard disk
|
|
+ active protection syystem as implemented in IBM/Lenovo Thinkpads.
|
|
+
|
|
+
|
|
+CREDITS
|
|
+-------
|
|
+
|
|
+The patch to implement the interface described in this file has
|
|
+originally been published by Jon Escombe <lists@dresco.co.uk>.
|
|
+
|
|
+
|
|
+05 Dec 2006, Elias Oltmanns <eo@nebensachen.de>
|
|
Index: linux-2.6.23-rc6/block/ll_rw_blk.c
|
|
===================================================================
|
|
--- linux-2.6.23-rc6.orig/block/ll_rw_blk.c
|
|
+++ linux-2.6.23-rc6/block/ll_rw_blk.c
|
|
@@ -38,10 +38,14 @@
|
|
|
|
static void blk_unplug_work(struct work_struct *work);
|
|
static void blk_unplug_timeout(unsigned long data);
|
|
+static void blk_unfreeze_work(struct work_struct *work);
|
|
+static void blk_unfreeze_timeout(unsigned long data);
|
|
static void drive_stat_acct(struct request *rq, int nr_sectors, int new_io);
|
|
static void init_request_from_bio(struct request *req, struct bio *bio);
|
|
static int __make_request(struct request_queue *q, struct bio *bio);
|
|
static struct io_context *current_io_context(gfp_t gfp_flags, int node);
|
|
+static int blk_protect_register(struct request_queue *q);
|
|
+static void blk_protect_unregister(struct request_queue *q);
|
|
|
|
/*
|
|
* For the allocated request tables
|
|
@@ -227,6 +231,16 @@ void blk_queue_make_request(struct reque
|
|
q->unplug_timer.function = blk_unplug_timeout;
|
|
q->unplug_timer.data = (unsigned long)q;
|
|
|
|
+ q->max_unfreeze = 30;
|
|
+
|
|
+ INIT_WORK(&q->unfreeze_work, blk_unfreeze_work);
|
|
+
|
|
+ q->unfreeze_timer.function = blk_unfreeze_timeout;
|
|
+ q->unfreeze_timer.data = (unsigned long)q;
|
|
+
|
|
+ /* Set protect_method to auto detection initially */
|
|
+ q->protect_method = 2;
|
|
+
|
|
/*
|
|
* by default assume old behaviour and bounce for any highmem page
|
|
*/
|
|
@@ -318,6 +332,18 @@ void blk_queue_issue_flush_fn(struct req
|
|
|
|
EXPORT_SYMBOL(blk_queue_issue_flush_fn);
|
|
|
|
+void blk_queue_issue_protect_fn(struct request_queue *q, issue_protect_fn *ipf)
|
|
+{
|
|
+ q->issue_protect_fn = ipf;
|
|
+}
|
|
+EXPORT_SYMBOL(blk_queue_issue_protect_fn);
|
|
+
|
|
+void blk_queue_issue_unprotect_fn(struct request_queue *q, issue_unprotect_fn *iuf)
|
|
+{
|
|
+ q->issue_unprotect_fn = iuf;
|
|
+}
|
|
+EXPORT_SYMBOL(blk_queue_issue_unprotect_fn);
|
|
+
|
|
/*
|
|
* Cache flushing for ordered writes handling
|
|
*/
|
|
@@ -1840,6 +1866,7 @@ struct request_queue *blk_alloc_queue_no
|
|
return NULL;
|
|
|
|
init_timer(&q->unplug_timer);
|
|
+ init_timer(&q->unfreeze_timer);
|
|
|
|
snprintf(q->kobj.name, KOBJ_NAME_LEN, "%s", "queue");
|
|
q->kobj.ktype = &queue_ktype;
|
|
@@ -4105,6 +4132,7 @@ int blk_register_queue(struct gendisk *d
|
|
return ret;
|
|
}
|
|
|
|
+ blk_protect_register(q);
|
|
return 0;
|
|
}
|
|
|
|
@@ -4113,6 +4141,7 @@ void blk_unregister_queue(struct gendisk
|
|
struct request_queue *q = disk->queue;
|
|
|
|
if (q && q->request_fn) {
|
|
+ blk_protect_unregister(q);
|
|
elv_unregister_queue(q);
|
|
|
|
kobject_uevent(&q->kobj, KOBJ_REMOVE);
|
|
@@ -4120,3 +4149,198 @@ void blk_unregister_queue(struct gendisk
|
|
kobject_put(&disk->kobj);
|
|
}
|
|
}
|
|
+
|
|
+/*
|
|
+ * Issue lower level unprotect function if no timers are pending.
|
|
+ */
|
|
+static void blk_unfreeze_work(struct work_struct *work)
|
|
+{
|
|
+ struct request_queue *q = container_of(work, struct request_queue, unfreeze_work);
|
|
+ int pending;
|
|
+ unsigned long flags;
|
|
+
|
|
+ spin_lock_irqsave(q->queue_lock, flags);
|
|
+ pending = timer_pending(&q->unfreeze_timer);
|
|
+ spin_unlock_irqrestore(q->queue_lock, flags);
|
|
+ if (!pending)
|
|
+ q->issue_unprotect_fn(q);
|
|
+}
|
|
+
|
|
+/*
|
|
+ * Called when the queue freeze timeout expires...
|
|
+ */
|
|
+static void blk_unfreeze_timeout(unsigned long data)
|
|
+{
|
|
+ struct request_queue *q = (struct request_queue *) data;
|
|
+
|
|
+ kblockd_schedule_work(&q->unfreeze_work);
|
|
+}
|
|
+
|
|
+/*
|
|
+ * The lower level driver parks and freezes the queue, and this block layer
|
|
+ * function sets up the freeze timeout timer on return. If the queue is
|
|
+ * already frozen then this is called to extend the timer...
|
|
+ */
|
|
+void blk_freeze_queue(struct request_queue *q, int seconds)
|
|
+{
|
|
+ /* Don't accept arbitrarily long freezes */
|
|
+ if (seconds >= q->max_unfreeze)
|
|
+ seconds = q->max_unfreeze;
|
|
+ /* set/reset the timer */
|
|
+ mod_timer(&q->unfreeze_timer, msecs_to_jiffies(seconds*1000) + jiffies);
|
|
+}
|
|
+
|
|
+/*
|
|
+ * When reading the 'protect' attribute, we return seconds remaining
|
|
+ * before unfreeze timeout expires
|
|
+ */
|
|
+static ssize_t queue_protect_show(struct request_queue *q, char *page)
|
|
+{
|
|
+ unsigned int seconds = 0;
|
|
+
|
|
+ spin_lock_irq(q->queue_lock);
|
|
+ if (blk_queue_stopped(q) && timer_pending(&q->unfreeze_timer))
|
|
+ /*
|
|
+ * Adding 1 in order to guarantee nonzero value until timer
|
|
+ * has actually expired.
|
|
+ */
|
|
+ seconds = jiffies_to_msecs(q->unfreeze_timer.expires
|
|
+ - jiffies) / 1000 + 1;
|
|
+ spin_unlock_irq(q->queue_lock);
|
|
+ return queue_var_show(seconds, (page));
|
|
+}
|
|
+
|
|
+/*
|
|
+ * When writing the 'protect' attribute, input is the number of seconds
|
|
+ * to freeze the queue for. We call a lower level helper function to
|
|
+ * park the heads and freeze/block the queue, then we make a block layer
|
|
+ * call to setup the thaw timeout. If input is 0, then we thaw the queue.
|
|
+ */
|
|
+static ssize_t queue_protect_store(struct request_queue *q,
|
|
+ const char *page, size_t count)
|
|
+{
|
|
+ unsigned long freeze = 0;
|
|
+
|
|
+ queue_var_store(&freeze, page, count);
|
|
+
|
|
+ if (freeze>0) {
|
|
+ /* Park and freeze */
|
|
+ if (!blk_queue_stopped(q))
|
|
+ q->issue_protect_fn(q);
|
|
+ /* set / reset the thaw timer */
|
|
+ spin_lock_irq(q->queue_lock);
|
|
+ blk_freeze_queue(q, freeze);
|
|
+ spin_unlock_irq(q->queue_lock);
|
|
+ } else {
|
|
+ spin_lock_irq(q->queue_lock);
|
|
+ freeze = del_timer(&q->unfreeze_timer);
|
|
+ spin_unlock_irq(q->queue_lock);
|
|
+ if (freeze)
|
|
+ q->issue_unprotect_fn(q);
|
|
+ }
|
|
+
|
|
+ return count;
|
|
+}
|
|
+
|
|
+static ssize_t
|
|
+queue_str_show(char *page, char *str, int status)
|
|
+{
|
|
+ ssize_t len;
|
|
+
|
|
+ if (status & 1)
|
|
+ len = sprintf(page, "[%s]", str);
|
|
+ else
|
|
+ len = sprintf(page, "%s", str);
|
|
+ if (status & 2)
|
|
+ len += sprintf(page+len, "\n");
|
|
+ else
|
|
+ len += sprintf(page+len, " ");
|
|
+ return len;
|
|
+}
|
|
+
|
|
+/*
|
|
+ * Returns current protect_method.
|
|
+ */
|
|
+static ssize_t queue_protect_method_show(struct request_queue *q, char *page)
|
|
+{
|
|
+ int len = 0;
|
|
+ int unload = q->protect_method;
|
|
+
|
|
+ len += queue_str_show(page+len, "auto", (unload & 2) >> 1);
|
|
+ len += queue_str_show(page+len, "unload", unload & 1);
|
|
+ len += queue_str_show(page+len, "standby", !unload ? 3 : 2);
|
|
+ return len;
|
|
+}
|
|
+
|
|
+/*
|
|
+ * Stores the device protect method.
|
|
+ */
|
|
+static ssize_t queue_protect_method_store(struct request_queue *q,
|
|
+ const char *page, size_t count)
|
|
+{
|
|
+ spin_lock_irq(q->queue_lock);
|
|
+ if (!strcmp(page, "auto") || !strcmp(page, "auto\n"))
|
|
+ q->protect_method = 2;
|
|
+ else if (!strcmp(page, "unload") || !strcmp(page, "unload\n"))
|
|
+ q->protect_method = 1;
|
|
+ else if (!strcmp(page, "standby") || !strcmp(page, "standby\n"))
|
|
+ q->protect_method = 0;
|
|
+ else {
|
|
+ spin_unlock_irq(q->queue_lock);
|
|
+ return -EINVAL;
|
|
+ }
|
|
+ spin_unlock_irq(q->queue_lock);
|
|
+ return count;
|
|
+}
|
|
+
|
|
+static struct queue_sysfs_entry queue_protect_entry = {
|
|
+ .attr = { .name = "protect", .mode = S_IRUGO | S_IWUSR },
|
|
+ .show = queue_protect_show,
|
|
+ .store = queue_protect_store,
|
|
+};
|
|
+static struct queue_sysfs_entry queue_protect_method_entry = {
|
|
+ .attr = { .name = "protect_method", .mode = S_IRUGO | S_IWUSR },
|
|
+ .show = queue_protect_method_show,
|
|
+ .store = queue_protect_method_store,
|
|
+};
|
|
+
|
|
+static int blk_protect_register(struct request_queue *q)
|
|
+{
|
|
+ int error = 0;
|
|
+
|
|
+ /* check that the lower level driver has a protect handler */
|
|
+ if (!q->issue_protect_fn)
|
|
+ return 1;
|
|
+
|
|
+ /* create the attributes */
|
|
+ error = sysfs_create_file(&q->kobj, &queue_protect_entry.attr);
|
|
+ if (error) {
|
|
+ printk(KERN_ERR
|
|
+ "blk_protect_register(): failed to create protect queue attribute!\n");
|
|
+ return error;
|
|
+ }
|
|
+ kobject_get(&q->kobj);
|
|
+
|
|
+ error = sysfs_create_file(&q->kobj, &queue_protect_method_entry.attr);
|
|
+ if (error) {
|
|
+ printk(KERN_ERR
|
|
+ "blk_protect_register(): failed to create protect_method attribute!\n");
|
|
+ return error;
|
|
+ }
|
|
+ kobject_get(&q->kobj);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static void blk_protect_unregister(struct request_queue *q)
|
|
+{
|
|
+ /* check that the lower level driver has a protect handler */
|
|
+ if (!q->issue_protect_fn)
|
|
+ return;
|
|
+
|
|
+ /* remove the attributes */
|
|
+ sysfs_remove_file(&q->kobj, &queue_protect_method_entry.attr);
|
|
+ kobject_put(&q->kobj);
|
|
+ sysfs_remove_file(&q->kobj, &queue_protect_entry.attr);
|
|
+ kobject_put(&q->kobj);
|
|
+}
|
|
Index: linux-2.6.23-rc6/drivers/ata/libata-scsi.c
|
|
===================================================================
|
|
--- linux-2.6.23-rc6.orig/drivers/ata/libata-scsi.c
|
|
+++ linux-2.6.23-rc6/drivers/ata/libata-scsi.c
|
|
@@ -781,6 +781,33 @@ static void ata_scsi_dev_config(struct s
|
|
}
|
|
}
|
|
|
|
+extern int scsi_protect_queue(struct request_queue *q, int unload);
|
|
+extern int scsi_unprotect_queue(struct request_queue *q);
|
|
+
|
|
+static int ata_scsi_issue_protect_fn(struct request_queue *q)
|
|
+{
|
|
+ struct scsi_device *sdev = q->queuedata;
|
|
+ struct ata_port *ap = (struct ata_port *) &sdev->host->hostdata[0];
|
|
+ struct ata_device *dev = &ap->device[sdev->id];
|
|
+ int unload = q->protect_method;
|
|
+ unsigned long flags;
|
|
+
|
|
+ if (unload == 2) {
|
|
+ unload = ata_id_has_unload(dev->id) ? 1 : 0;
|
|
+ spin_lock_irqsave(q->queue_lock, flags);
|
|
+ q->protect_method = unload;
|
|
+ spin_unlock_irqrestore(q->queue_lock, flags);
|
|
+ }
|
|
+
|
|
+ /* call scsi_protect_queue, requesting either unload or standby */
|
|
+ return scsi_protect_queue(q, unload);
|
|
+}
|
|
+
|
|
+static int ata_scsi_issue_unprotect_fn(struct request_queue *q)
|
|
+{
|
|
+ return scsi_unprotect_queue(q);
|
|
+}
|
|
+
|
|
/**
|
|
* ata_scsi_slave_config - Set SCSI device attributes
|
|
* @sdev: SCSI device to examine
|
|
@@ -806,6 +833,10 @@ int ata_scsi_slave_config(struct scsi_de
|
|
|
|
if (dev)
|
|
ata_scsi_dev_config(sdev, dev);
|
|
+ blk_queue_issue_protect_fn(sdev->request_queue,
|
|
+ ata_scsi_issue_protect_fn);
|
|
+ blk_queue_issue_unprotect_fn(sdev->request_queue,
|
|
+ ata_scsi_issue_unprotect_fn);
|
|
|
|
return 0; /* scsi layer doesn't check return value, sigh */
|
|
}
|
|
Index: linux-2.6.23-rc6/drivers/ide/ide-disk.c
|
|
===================================================================
|
|
--- linux-2.6.23-rc6.orig/drivers/ide/ide-disk.c
|
|
+++ linux-2.6.23-rc6/drivers/ide/ide-disk.c
|
|
@@ -742,6 +742,145 @@ static int idedisk_issue_flush(struct re
|
|
}
|
|
|
|
/*
|
|
+ * todo:
|
|
+ * - we freeze the queue regardless of success and rely on the
|
|
+ * ide_protect_queue function to thaw immediately if the command
|
|
+ * failed (to be consistent with the libata handler)... should
|
|
+ * we also inspect here?
|
|
+ */
|
|
+void ide_end_protect_rq(struct request *rq, int error)
|
|
+{
|
|
+ struct completion *waiting = rq->end_io_data;
|
|
+
|
|
+ rq->end_io_data = NULL;
|
|
+ /* spin lock already accquired */
|
|
+ if (!blk_queue_stopped(rq->q))
|
|
+ blk_stop_queue(rq->q);
|
|
+
|
|
+ complete(waiting);
|
|
+}
|
|
+
|
|
+int ide_unprotect_queue(struct request_queue *q)
|
|
+{
|
|
+ struct request rq;
|
|
+ unsigned long flags;
|
|
+ int pending = 0, rc = 0;
|
|
+ ide_drive_t *drive = q->queuedata;
|
|
+ u8 args[7], *argbuf = args;
|
|
+
|
|
+ if (!blk_queue_stopped(q))
|
|
+ return -EIO;
|
|
+
|
|
+ /* Are there any pending jobs on the queue? */
|
|
+ pending = ((q->rq.count[READ] > 0) || (q->rq.count[WRITE] > 0)) ? 1 : 0;
|
|
+
|
|
+ spin_lock_irqsave(q->queue_lock, flags);
|
|
+ blk_start_queue(q);
|
|
+ spin_unlock_irqrestore(q->queue_lock, flags);
|
|
+
|
|
+ /* The unload feature of the IDLE_IMMEDIATE command
|
|
+ temporarily disables HD power management from spinning down
|
|
+ the disk. Any other command will reenable HD pm, so, if
|
|
+ there are no pending jobs on the queue, another
|
|
+ CHECK_POWER_MODE1 command without the unload feature should do
|
|
+ just fine. */
|
|
+ if (!pending) {
|
|
+ printk(KERN_DEBUG "ide_unprotect_queue(): No pending I/O, re-enabling power management..\n");
|
|
+ memset(args, 0, sizeof(args));
|
|
+ argbuf[0] = 0xe5; /* CHECK_POWER_MODE1 */
|
|
+ ide_init_drive_cmd(&rq);
|
|
+ rq.cmd_type = REQ_TYPE_ATA_TASK;
|
|
+ rq.buffer = argbuf;
|
|
+ rc = ide_do_drive_cmd(drive, &rq, ide_head_wait);
|
|
+ }
|
|
+
|
|
+ return rc;
|
|
+}
|
|
+
|
|
+int ide_protect_queue(struct request_queue *q, int unload)
|
|
+{
|
|
+ ide_drive_t *drive = q->queuedata;
|
|
+ struct request rq;
|
|
+ u8 args[7], *argbuf = args;
|
|
+ int ret = 0;
|
|
+ DECLARE_COMPLETION(wait);
|
|
+
|
|
+ memset(&rq, 0, sizeof(rq));
|
|
+ memset(args, 0, sizeof(args));
|
|
+
|
|
+ if (blk_queue_stopped(q))
|
|
+ return -EIO;
|
|
+
|
|
+ if (unload) {
|
|
+ argbuf[0] = 0xe1;
|
|
+ argbuf[1] = 0x44;
|
|
+ argbuf[3] = 0x4c;
|
|
+ argbuf[4] = 0x4e;
|
|
+ argbuf[5] = 0x55;
|
|
+ } else
|
|
+ argbuf[0] = 0xe0;
|
|
+
|
|
+ /* Issue the park command & freeze */
|
|
+ ide_init_drive_cmd(&rq);
|
|
+
|
|
+ rq.cmd_type = REQ_TYPE_ATA_TASK;
|
|
+ rq.buffer = argbuf;
|
|
+ rq.end_io_data = &wait;
|
|
+ rq.end_io = ide_end_protect_rq;
|
|
+
|
|
+ ret = ide_do_drive_cmd(drive, &rq, ide_next);
|
|
+ wait_for_completion(&wait);
|
|
+
|
|
+ if (ret)
|
|
+ {
|
|
+ printk(KERN_DEBUG "ide_protect_queue(): Warning: head NOT parked!..\n");
|
|
+ ide_unprotect_queue(q);
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ if (unload) {
|
|
+ if (args[3] == 0xc4)
|
|
+ printk(KERN_DEBUG "ide_protect_queue(): head parked..\n");
|
|
+ else {
|
|
+ /* error parking the head */
|
|
+ printk(KERN_DEBUG "ide_protect_queue(): head NOT parked!..\n");
|
|
+ ret = -EIO;
|
|
+ ide_unprotect_queue(q);
|
|
+ }
|
|
+ } else
|
|
+ printk(KERN_DEBUG "ide_protect_queue(): head park not requested, used standby!..\n");
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+int idedisk_issue_protect_fn(struct request_queue *q)
|
|
+{
|
|
+ ide_drive_t *drive = q->queuedata;
|
|
+ int unload = q->protect_method;
|
|
+ unsigned long flags;
|
|
+
|
|
+ /*
|
|
+ * Check capability of the device -
|
|
+ * - if "idle immediate with unload" is supported we use that, else
|
|
+ * we use "standby immediate" and live with spinning down the drive..
|
|
+ * (Word 84, bit 13 of IDENTIFY DEVICE data)
|
|
+ */
|
|
+ if (unload == 2) {
|
|
+ unload = drive->id->cfsse & (1 << 13) ? 1 : 0;
|
|
+ spin_lock_irqsave(q->queue_lock, flags);
|
|
+ q->protect_method = unload;
|
|
+ spin_unlock_irqrestore(q->queue_lock, flags);
|
|
+ }
|
|
+
|
|
+ return ide_protect_queue(q, unload);
|
|
+}
|
|
+
|
|
+int idedisk_issue_unprotect_fn(struct request_queue *q)
|
|
+{
|
|
+ return ide_unprotect_queue(q);
|
|
+}
|
|
+
|
|
+/*
|
|
* This is tightly woven into the driver->do_special can not touch.
|
|
* DON'T do it again until a total personality rewrite is committed.
|
|
*/
|
|
@@ -1016,6 +1155,9 @@ static void idedisk_setup (ide_drive_t *
|
|
drive->wcache = 1;
|
|
|
|
write_cache(drive, 1);
|
|
+
|
|
+ blk_queue_issue_protect_fn(drive->queue, idedisk_issue_protect_fn);
|
|
+ blk_queue_issue_unprotect_fn(drive->queue, idedisk_issue_unprotect_fn);
|
|
}
|
|
|
|
static void ide_cacheflush_p(ide_drive_t *drive)
|
|
Index: linux-2.6.23-rc6/drivers/ide/ide-io.c
|
|
===================================================================
|
|
--- linux-2.6.23-rc6.orig/drivers/ide/ide-io.c
|
|
+++ linux-2.6.23-rc6/drivers/ide/ide-io.c
|
|
@@ -1276,6 +1276,17 @@ static void ide_do_request (ide_hwgroup_
|
|
}
|
|
|
|
/*
|
|
+ * Don't accept a request when the queue is stopped (unless we
|
|
+ * are resuming from suspend). Prevents existing queue entries
|
|
+ * being processed after queue is stopped by the hard disk
|
|
+ * protection mechanism...
|
|
+ */
|
|
+ if (test_bit(QUEUE_FLAG_STOPPED, &drive->queue->queue_flags) && !blk_pm_resume_request(rq)) {
|
|
+ hwgroup->busy = 0;
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ /*
|
|
* Sanity: don't accept a request that isn't a PM request
|
|
* if we are currently power managed. This is very important as
|
|
* blk_stop_queue() doesn't prevent the elv_next_request()
|
|
@@ -1773,6 +1784,9 @@ int ide_do_drive_cmd (ide_drive_t *drive
|
|
where = ELEVATOR_INSERT_FRONT;
|
|
rq->cmd_flags |= REQ_PREEMPT;
|
|
}
|
|
+ if (action == ide_next)
|
|
+ where = ELEVATOR_INSERT_FRONT;
|
|
+
|
|
__elv_add_request(drive->queue, rq, where, 0);
|
|
ide_do_request(hwgroup, IDE_NO_IRQ);
|
|
spin_unlock_irqrestore(&ide_lock, flags);
|
|
Index: linux-2.6.23-rc6/drivers/scsi/scsi_lib.c
|
|
===================================================================
|
|
--- linux-2.6.23-rc6.orig/drivers/scsi/scsi_lib.c
|
|
+++ linux-2.6.23-rc6/drivers/scsi/scsi_lib.c
|
|
@@ -2273,3 +2273,166 @@ void scsi_kunmap_atomic_sg(void *virt)
|
|
kunmap_atomic(virt, KM_BIO_SRC_IRQ);
|
|
}
|
|
EXPORT_SYMBOL(scsi_kunmap_atomic_sg);
|
|
+
|
|
+/*
|
|
+ * Structure required for synchronous io completion after queue freezing
|
|
+ */
|
|
+struct scsi_protect_io_context_sync {
|
|
+ struct scsi_device *sdev;
|
|
+ int result;
|
|
+ char *sense;
|
|
+ struct completion *waiting;
|
|
+};
|
|
+
|
|
+/*
|
|
+ * scsi_protect_wait_done()
|
|
+ * Command completion handler for scsi_protect_queue().
|
|
+ *
|
|
+ * Unable to call scsi_internal_device_block() as
|
|
+ * scsi_end_request() already has the spinlock. So,
|
|
+ * we put the necessary functionality inline.
|
|
+ *
|
|
+ * todo:
|
|
+ * - we block the queue regardless of success and rely on the
|
|
+ * scsi_protect_queue function to unblock if the command
|
|
+ * failed... should we also inspect here?
|
|
+ */
|
|
+static void scsi_protect_wait_done(void *data, char *sense, int result, int resid)
|
|
+{
|
|
+ struct scsi_protect_io_context_sync *siocs = data;
|
|
+ struct completion *waiting = siocs->waiting;
|
|
+ struct request_queue *q = siocs->sdev->request_queue;
|
|
+
|
|
+ siocs->waiting = NULL;
|
|
+ siocs->result = result;
|
|
+ memcpy(siocs->sense, sense, SCSI_SENSE_BUFFERSIZE);
|
|
+
|
|
+ if (!scsi_device_set_state(siocs->sdev, SDEV_BLOCK))
|
|
+ blk_stop_queue(q);
|
|
+
|
|
+ complete(waiting);
|
|
+}
|
|
+
|
|
+/*
|
|
+ * scsi_unprotect_queue()
|
|
+ * - release the queue that was previously blocked
|
|
+ */
|
|
+int scsi_unprotect_queue(struct request_queue *q)
|
|
+{
|
|
+ struct scsi_device *sdev = q->queuedata;
|
|
+ int rc = 0, pending = 0;
|
|
+ u8 scsi_cmd[MAX_COMMAND_SIZE];
|
|
+ struct scsi_sense_hdr sshdr;
|
|
+
|
|
+ if (sdev->sdev_state != SDEV_BLOCK)
|
|
+ return -ENXIO;
|
|
+
|
|
+ /* Are there any pending jobs on the queue? */
|
|
+ pending = ((q->rq.count[READ] > 0) || (q->rq.count[WRITE] > 0)) ? 1 : 0;
|
|
+
|
|
+ rc = scsi_internal_device_unblock(sdev);
|
|
+ if (rc)
|
|
+ return rc;
|
|
+
|
|
+ if (!pending) {
|
|
+ printk(KERN_DEBUG "scsi_unprotect_queue(): No pending I/O, re-enabling power management..\n");
|
|
+
|
|
+ memset(scsi_cmd, 0, sizeof(scsi_cmd));
|
|
+ scsi_cmd[0] = ATA_16;
|
|
+ scsi_cmd[1] = (3 << 1); /* Non-data */
|
|
+ /* scsi_cmd[2] is already 0 -- no off.line, cc, or data xfer */
|
|
+ scsi_cmd[14] = 0xe5; /* CHECK_POWER_MODE1 */
|
|
+
|
|
+ /* Good values for timeout and retries? Values below
|
|
+ from scsi_ioctl_send_command() for default case... */
|
|
+ if (scsi_execute_req(sdev, scsi_cmd, DMA_NONE, NULL, 0, &sshdr,
|
|
+ (10*HZ), 5))
|
|
+ rc = -EIO;
|
|
+ }
|
|
+ return rc;
|
|
+}
|
|
+EXPORT_SYMBOL_GPL(scsi_unprotect_queue);
|
|
+
|
|
+/*
|
|
+ * scsi_protect_queue()
|
|
+ * - build and issue the park/standby command..
|
|
+ * - queue is blocked during command completion handler
|
|
+ */
|
|
+int scsi_protect_queue(struct request_queue *q, int unload)
|
|
+{
|
|
+ struct scsi_protect_io_context_sync siocs;
|
|
+ struct scsi_device *sdev = q->queuedata;
|
|
+ int rc = 0;
|
|
+ u8 args[7];
|
|
+ u8 scsi_cmd[MAX_COMMAND_SIZE];
|
|
+ unsigned char sense[SCSI_SENSE_BUFFERSIZE];
|
|
+ unsigned char *desc;
|
|
+ DECLARE_COMPLETION_ONSTACK(wait);
|
|
+
|
|
+ if (sdev->sdev_state != SDEV_RUNNING)
|
|
+ return -ENXIO;
|
|
+
|
|
+ memset(args, 0, sizeof(args));
|
|
+ memset(sense, 0, sizeof(sense));
|
|
+
|
|
+ if (unload) {
|
|
+ args[0] = 0xe1;
|
|
+ args[1] = 0x44;
|
|
+ args[3] = 0x4c;
|
|
+ args[4] = 0x4e;
|
|
+ args[5] = 0x55;
|
|
+ } else
|
|
+ args[0] = 0xe0;
|
|
+
|
|
+ memset(scsi_cmd, 0, sizeof(scsi_cmd));
|
|
+ scsi_cmd[0] = ATA_16;
|
|
+ scsi_cmd[1] = (3 << 1); /* Non-data */
|
|
+ scsi_cmd[2] = 0x20; /* no off.line, or data xfer, request cc */
|
|
+ scsi_cmd[4] = args[1];
|
|
+ scsi_cmd[6] = args[2];
|
|
+ scsi_cmd[8] = args[3];
|
|
+ scsi_cmd[10] = args[4];
|
|
+ scsi_cmd[12] = args[5];
|
|
+ scsi_cmd[14] = args[0];
|
|
+ siocs.sdev = sdev;
|
|
+ siocs.sense = sense;
|
|
+ siocs.waiting = &wait;
|
|
+
|
|
+ scsi_execute_async(sdev, scsi_cmd, COMMAND_SIZE(scsi_cmd[0]),
|
|
+ DMA_NONE, NULL, 0, 0, (10*HZ), 5,
|
|
+ &siocs, &scsi_protect_wait_done, GFP_NOWAIT);
|
|
+ wait_for_completion(&wait);
|
|
+
|
|
+ if (siocs.result != ((DRIVER_SENSE << 24) + SAM_STAT_CHECK_CONDITION)) {
|
|
+ printk(KERN_DEBUG "scsi_protect_queue(): head NOT parked!..\n");
|
|
+ scsi_unprotect_queue(q); /* just in case we still managed to block */
|
|
+ rc = -EIO;
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ desc = sense + 8;
|
|
+
|
|
+ /* Retrieve data from check condition */
|
|
+ args[1] = desc[3];
|
|
+ args[2] = desc[5];
|
|
+ args[3] = desc[7];
|
|
+ args[4] = desc[9];
|
|
+ args[5] = desc[11];
|
|
+ args[0] = desc[13];
|
|
+
|
|
+ if (unload) {
|
|
+ if (args[3] == 0xc4)
|
|
+ printk(KERN_DEBUG "scsi_protect_queue(): head parked..\n");
|
|
+ else {
|
|
+ /* error parking the head */
|
|
+ printk(KERN_DEBUG "scsi_protect_queue(): head NOT parked!..\n");
|
|
+ rc = -EIO;
|
|
+ scsi_unprotect_queue(q);
|
|
+ }
|
|
+ } else
|
|
+ printk(KERN_DEBUG "scsi_protect_queue(): head park not requested, used standby!..\n");
|
|
+
|
|
+out:
|
|
+ return rc;
|
|
+}
|
|
+EXPORT_SYMBOL_GPL(scsi_protect_queue);
|
|
Index: linux-2.6.23-rc6/include/linux/ata.h
|
|
===================================================================
|
|
--- linux-2.6.23-rc6.orig/include/linux/ata.h
|
|
+++ linux-2.6.23-rc6/include/linux/ata.h
|
|
@@ -344,6 +344,7 @@ struct ata_taskfile {
|
|
#define ata_id_rahead_enabled(id) ((id)[85] & (1 << 6))
|
|
#define ata_id_wcache_enabled(id) ((id)[85] & (1 << 5))
|
|
#define ata_id_hpa_enabled(id) ((id)[85] & (1 << 10))
|
|
+#define ata_id_has_unload(id) ((id)[84] & (1 << 13))
|
|
#define ata_id_has_fua(id) ((id)[84] & (1 << 6))
|
|
#define ata_id_has_flush(id) ((id)[83] & (1 << 12))
|
|
#define ata_id_has_flush_ext(id) ((id)[83] & (1 << 13))
|
|
Index: linux-2.6.23-rc6/include/linux/blkdev.h
|
|
===================================================================
|
|
--- linux-2.6.23-rc6.orig/include/linux/blkdev.h
|
|
+++ linux-2.6.23-rc6/include/linux/blkdev.h
|
|
@@ -347,6 +347,8 @@ typedef int (merge_bvec_fn) (struct requ
|
|
typedef int (issue_flush_fn) (struct request_queue *, struct gendisk *, sector_t *);
|
|
typedef void (prepare_flush_fn) (struct request_queue *, struct request *);
|
|
typedef void (softirq_done_fn)(struct request *);
|
|
+typedef int (issue_protect_fn) (struct request_queue *);
|
|
+typedef int (issue_unprotect_fn) (struct request_queue *);
|
|
|
|
enum blk_queue_state {
|
|
Queue_down,
|
|
@@ -385,6 +387,8 @@ struct request_queue
|
|
issue_flush_fn *issue_flush_fn;
|
|
prepare_flush_fn *prepare_flush_fn;
|
|
softirq_done_fn *softirq_done_fn;
|
|
+ issue_protect_fn *issue_protect_fn;
|
|
+ issue_unprotect_fn *issue_unprotect_fn;
|
|
|
|
/*
|
|
* Dispatch queue sorting
|
|
@@ -400,6 +404,14 @@ struct request_queue
|
|
unsigned long unplug_delay; /* After this many jiffies */
|
|
struct work_struct unplug_work;
|
|
|
|
+ /*
|
|
+ * Auto-unfreeze state
|
|
+ */
|
|
+ struct timer_list unfreeze_timer;
|
|
+ int max_unfreeze; /* At most this many seconds */
|
|
+ struct work_struct unfreeze_work;
|
|
+ int protect_method;
|
|
+
|
|
struct backing_dev_info backing_dev_info;
|
|
|
|
/*
|
|
@@ -774,6 +786,8 @@ extern int blk_do_ordered(struct request
|
|
extern unsigned blk_ordered_cur_seq(struct request_queue *);
|
|
extern unsigned blk_ordered_req_seq(struct request *);
|
|
extern void blk_ordered_complete_seq(struct request_queue *, unsigned, int);
|
|
+extern void blk_queue_issue_protect_fn(struct request_queue *, issue_protect_fn *);
|
|
+extern void blk_queue_issue_unprotect_fn(struct request_queue *, issue_unprotect_fn *);
|
|
|
|
extern int blk_rq_map_sg(struct request_queue *, struct request *, struct scatterlist *);
|
|
extern void blk_dump_rq_flags(struct request *, char *);
|
|
Index: linux-2.6.23-rc6/include/linux/ide.h
|
|
===================================================================
|
|
--- linux-2.6.23-rc6.orig/include/linux/ide.h
|
|
+++ linux-2.6.23-rc6/include/linux/ide.h
|
|
@@ -1110,6 +1110,7 @@ extern u64 ide_get_error_location(ide_dr
|
|
*/
|
|
typedef enum {
|
|
ide_wait, /* insert rq at end of list, and wait for it */
|
|
+ ide_next, /* insert rq immediately after current request */
|
|
ide_preempt, /* insert rq in front of current request */
|
|
ide_head_wait, /* insert rq in front of current request and wait for it */
|
|
ide_end /* insert rq at end of list, but don't wait for it */
|
|
Index: linux-2.6.23-rc6/drivers/input/input.c
|
|
===================================================================
|
|
--- linux-2.6.23-rc6.orig/drivers/input/input.c
|
|
+++ linux-2.6.23-rc6/drivers/input/input.c
|
|
@@ -71,7 +71,7 @@ void input_event(struct input_dev *dev,
|
|
|
|
case EV_KEY:
|
|
|
|
- if (code > KEY_MAX || !test_bit(code, dev->keybit) || !!test_bit(code, dev->key) == value)
|
|
+ if (code > KEY_MAX || !!test_bit(code, dev->key) == value)
|
|
return;
|
|
|
|
if (value == 2)
|
|
Index: linux-2.6.23-rc6/arch/i386/boot/compressed/misc.c
|
|
===================================================================
|
|
--- linux-2.6.23-rc6.orig/arch/i386/boot/compressed/misc.c
|
|
+++ linux-2.6.23-rc6/arch/i386/boot/compressed/misc.c
|
|
@@ -372,8 +372,8 @@ asmlinkage void decompress_kernel(void *
|
|
#endif
|
|
|
|
makecrc();
|
|
- putstr("Uncompressing Linux... ");
|
|
+ //putstr("Uncompressing Linux... ");
|
|
gunzip();
|
|
- putstr("Ok, booting the kernel.\n");
|
|
+ //putstr("Ok, booting the kernel.\n");
|
|
return;
|
|
}
|
|
Index: linux-2.6.23-rc6/Documentation/tp_smapi.txt
|
|
===================================================================
|
|
--- /dev/null
|
|
+++ linux-2.6.23-rc6/Documentation/tp_smapi.txt
|
|
@@ -0,0 +1,190 @@
|
|
+tp_smapi version 0.32
|
|
+IBM ThinkPad hardware functions driver
|
|
+
|
|
+Author: Shem Multinymous <multinymous@gmail.com>
|
|
+Project: http://sourceforge.net/projects/tpctl
|
|
+Wiki: http://thinkwiki.org/wiki/tp_smapi
|
|
+List: linux-thinkpad@linux-thinkpad.org
|
|
+ (http://mailman.linux-thinkpad.org/mailman/listinfo/linux-thinkpad)
|
|
+
|
|
+Description
|
|
+-----------
|
|
+
|
|
+ThinkPad laptops include a proprietary interface called SMAPI BIOS
|
|
+(System Management Application Program Interface) which provides some
|
|
+hardware control functionality that is not accessible by other means.
|
|
+
|
|
+This driver exposes some features of the SMAPI BIOS through a sysfs
|
|
+interface. It is suitable for newer models, on which SMAPI is invoked
|
|
+through IO port writes. Older models use a different SMAPI interface;
|
|
+for those, try the "thinkpad" module from the "tpctl" package.
|
|
+
|
|
+WARNING:
|
|
+This driver uses undocumented features and direct hardware access.
|
|
+It thus cannot be guaranteed to work, and may cause arbitrary damage
|
|
+(especially on models it wasn't tested on).
|
|
+
|
|
+
|
|
+Module parameters
|
|
+-----------------
|
|
+
|
|
+tp_smapi module:
|
|
+ debug=1 enables verbose dmesg output.
|
|
+
|
|
+
|
|
+Usage
|
|
+-----
|
|
+
|
|
+Control of battery charging thresholds (in percents of current full charge
|
|
+capacity):
|
|
+
|
|
+# echo 40 > /sys/devices/platform/smapi/BAT0/start_charge_thresh
|
|
+# echo 70 > /sys/devices/platform/smapi/BAT0/stop_charge_thresh
|
|
+# cat /sys/devices/platform/smapi/BAT0/*_charge_thresh
|
|
+
|
|
+ (This is useful since Li-Ion batteries wear out much faster at very
|
|
+ high or low charge levels. The driver will also keeps the thresholds
|
|
+ across suspend-to-disk with AC disconnected; this isn't done
|
|
+ automatically by the hardware.)
|
|
+
|
|
+Inhibiting battery charging for 17 minutes (overrides thresholds):
|
|
+
|
|
+# echo 17 > /sys/devices/platform/smapi/BAT0/inhibit_charge_minutes
|
|
+# echo 0 > /sys/devices/platform/smapi/BAT0/inhibit_charge_minutes # stop
|
|
+# cat /sys/devices/platform/smapi/BAT0/inhibit_charge_minutes
|
|
+
|
|
+ (This can be used to control which battery is charged when using an
|
|
+ Ultrabay battery.)
|
|
+
|
|
+Forcing battery discharging even if AC power available:
|
|
+
|
|
+# echo 1 > /sys/devices/platform/smapi/BAT0/force_discharge # start discharge
|
|
+# echo 0 > /sys/devices/platform/smapi/BAT0/force_discharge # stop discharge
|
|
+# cat /sys/devices/platform/smapi/BAT0/force_discharge
|
|
+
|
|
+ (This can be used to control which battery is discharged when using an
|
|
+ Ultrabay battery.)
|
|
+
|
|
+Misc read-only battery status (see note about HDAPS below):
|
|
+
|
|
+# cat /sys/devices/platform/smapi/BAT0/installed
|
|
+# cat /sys/devices/platform/smapi/BAT0/state # idle/charging/discharging
|
|
+# cat /sys/devices/platform/smapi/BAT0/cycle_count
|
|
+# cat /sys/devices/platform/smapi/BAT0/current_now # instantaneous current
|
|
+# cat /sys/devices/platform/smapi/BAT0/current_avg # last minute average
|
|
+# cat /sys/devices/platform/smapi/BAT0/power_now # instantaneous power
|
|
+# cat /sys/devices/platform/smapi/BAT0/power_avg # last minute average
|
|
+# cat /sys/devices/platform/smapi/BAT0/last_full_capacity
|
|
+# cat /sys/devices/platform/smapi/BAT0/remaining_percent
|
|
+# cat /sys/devices/platform/smapi/BAT0/remaining_running_time
|
|
+# cat /sys/devices/platform/smapi/BAT0/remaining_charging_time
|
|
+# cat /sys/devices/platform/smapi/BAT0/remaining_capacity
|
|
+# cat /sys/devices/platform/smapi/BAT0/design_capacity
|
|
+# cat /sys/devices/platform/smapi/BAT0/voltage
|
|
+# cat /sys/devices/platform/smapi/BAT0/design_voltage
|
|
+# cat /sys/devices/platform/smapi/BAT0/manufacturer
|
|
+# cat /sys/devices/platform/smapi/BAT0/model
|
|
+# cat /sys/devices/platform/smapi/BAT0/barcoding
|
|
+# cat /sys/devices/platform/smapi/BAT0/chemistry
|
|
+# cat /sys/devices/platform/smapi/BAT0/serial
|
|
+# cat /sys/devices/platform/smapi/BAT0/manufacture_date
|
|
+# cat /sys/devices/platform/smapi/BAT0/first_use_date
|
|
+# cat /sys/devices/platform/smapi/BAT0/temperature # in milli-Celsius
|
|
+# cat /sys/devices/platform/smapi/ac_connected
|
|
+
|
|
+
|
|
+You can also get a hex dump of the raw status data, which contains additional data
|
|
+now in the above (if you can figure it out). Some unused values are autodetected
|
|
+and replaced by "--":
|
|
+
|
|
+# cat /sys/devices/platform/smapi/BAT0/dump
|
|
+
|
|
+In all of the above, replace BAT0 with BAT1 to address the 2nd battery.
|
|
+
|
|
+
|
|
+Controlling PCI bus power saving:
|
|
+
|
|
+# cat /sys/devices/platform/smapi/enable_pci_power_saving_on_boot
|
|
+# echo 1 > /sys/devices/platform/smapi/enable_pci_power_saving_on_boot # on
|
|
+# echo 0 > /sys/devices/platform/smapi/enable_pci_power_saving_on_boot # off
|
|
+
|
|
+This controls the "PCI bus power saving" option in the BIOS, and takes
|
|
+effect at the next boot. On ThinkPad T43, this setting is stored in bit 0x40
|
|
+of NVRAM byte 0x39, and turning it off increases idle power consumption
|
|
+by about 350mW. Out-of-the-box default is 1.
|
|
+
|
|
+
|
|
+Raw SMAPI calls:
|
|
+
|
|
+/sys/devices/platform/smapi/smapi_request
|
|
+This performs raw SMAPI calls. It uses a bad interface that cannot handle
|
|
+multiple simultaneous access. Don't touch it, it's for development only.
|
|
+If you did touch it, you would so something like
|
|
+# echo '211a 100 0 0' > /sys/devices/platform/smapi/smapi_request
|
|
+# cat /sys/devices/platform/smapi/smapi_request
|
|
+and notice that in the output "211a 34b b2 0 0 0 'OK'", the "4b" in the 2nd
|
|
+value, converted to decimal is 75: the current charge stop threshold.
|
|
+
|
|
+
|
|
+Model-specific status
|
|
+---------------------
|
|
+
|
|
+Works (at least partially) on the following ThinkPad model:
|
|
+* A30
|
|
+* G41
|
|
+* R40, R50p, R51, R52
|
|
+* T23, T40, T40p, T41, T41p, T42, T42p, T43, T43p, T60
|
|
+* X24, X31, X32, X40, X41, X60
|
|
+* Z60t, Z61m
|
|
+
|
|
+Not all functions are available on all models; for detailed status, see:
|
|
+ http://thinkwiki.org/wiki/tp_smapi
|
|
+
|
|
+Please report success/failure by e-mail or on the Wiki.
|
|
+If you get a "not implemented" or "not supported" message, your laptop
|
|
+probably just can't do that (at least not via the SMAPI BIOS).
|
|
+For negative reports, follow the bug reporting guidelines below.
|
|
+If you send me the necessary technical data (i.e., SMAPI function
|
|
+interfaces), I will support additional models.
|
|
+
|
|
+
|
|
+Bug reporting
|
|
+-------------
|
|
+
|
|
+Mail <multinymous@gmail.com>. Please include:
|
|
+* Details about your model,
|
|
+* Relevant "dmesg" output. Make sure thinkpad_ec and tp_smapi are loaded with
|
|
+ the "debug=1" parameter (e.g., use "make load HDAPS=1 DEBUG=1").
|
|
+* Output of "dmidecode | grep -C5 Product"
|
|
+* Does the failed functionality works under Windows?
|
|
+
|
|
+
|
|
+More about SMAPI
|
|
+----------------
|
|
+
|
|
+For hints about what may be possible via the SMAPI BIOS and how, see:
|
|
+
|
|
+* IBM Technical Reference Manual for the ThinkPad 770
|
|
+ (http://www-307.ibm.com/pc/support/site.wss/document.do?lndocid=PFAN-3TUQQD)
|
|
+* Exported symbols in PWRMGRIF.DLL or TPPWRW32.DLL (e.g., use "objdump -x").
|
|
+* drivers/char/mwave/smapi.c in the Linux kernel tree.*
|
|
+* The "thinkpad" SMAPI module (http://tpctl.sourceforge.net).
|
|
+* The SMAPI_* constants in tp_smapi.c (some of these are presently unused).
|
|
+
|
|
+Note that in the above Technical Reference and in the "thinkpad" module,
|
|
+SMAPI is invoked through a function call to some physical address. However,
|
|
+the interface used by tp_smapi and the above mwave drive, and apparently
|
|
+required by newer ThinkPad, is different: you set the parameters up in the
|
|
+CPU's registers and write to ports 0xB2 (the APM control port) and 0x4F; this
|
|
+triggers an SMI (System Management Interrupt), causing the CPU to enter
|
|
+SMM (System Management Mode) and run the BIOS firmware; the results are
|
|
+returned in the CPU's registers. It is not clear what is the relation between
|
|
+the two variants of SMAPI, though the assignment of error codes seems to be
|
|
+similar.
|
|
+
|
|
+In addition, the embedded controller on ThinkPad laptops has a non-standard
|
|
+interface at IO ports 0x1600-0x161F (mapped to LCP channel 3 of the H8S chip).
|
|
+The interface provides various system management services (currently known:
|
|
+battery information and accelerometer readouts). For more information see the
|
|
+thinkpad_ec modul and the H8S hardware documentation:
|
|
+http://documentation.renesas.com/eng/products/mpumcu/rej09b0300_2140bhm.pdf
|
|
Index: linux-2.6.23-rc6/drivers/hwmon/hdaps.c
|
|
===================================================================
|
|
--- linux-2.6.23-rc6.orig/drivers/hwmon/hdaps.c
|
|
+++ linux-2.6.23-rc6/drivers/hwmon/hdaps.c
|
|
@@ -30,262 +30,383 @@
|
|
#include <linux/platform_device.h>
|
|
#include <linux/input.h>
|
|
#include <linux/kernel.h>
|
|
-#include <linux/mutex.h>
|
|
#include <linux/module.h>
|
|
#include <linux/timer.h>
|
|
#include <linux/dmi.h>
|
|
#include <linux/jiffies.h>
|
|
+#include <linux/thinkpad_ec.h>
|
|
+#include <linux/pci_ids.h>
|
|
|
|
-#include <asm/io.h>
|
|
-
|
|
-#define HDAPS_LOW_PORT 0x1600 /* first port used by hdaps */
|
|
-#define HDAPS_NR_PORTS 0x30 /* number of ports: 0x1600 - 0x162f */
|
|
-
|
|
-#define HDAPS_PORT_STATE 0x1611 /* device state */
|
|
-#define HDAPS_PORT_YPOS 0x1612 /* y-axis position */
|
|
-#define HDAPS_PORT_XPOS 0x1614 /* x-axis position */
|
|
-#define HDAPS_PORT_TEMP1 0x1616 /* device temperature, in Celsius */
|
|
-#define HDAPS_PORT_YVAR 0x1617 /* y-axis variance (what is this?) */
|
|
-#define HDAPS_PORT_XVAR 0x1619 /* x-axis variance (what is this?) */
|
|
-#define HDAPS_PORT_TEMP2 0x161b /* device temperature (again?) */
|
|
-#define HDAPS_PORT_UNKNOWN 0x161c /* what is this? */
|
|
-#define HDAPS_PORT_KMACT 0x161d /* keyboard or mouse activity */
|
|
-
|
|
-#define STATE_FRESH 0x50 /* accelerometer data is fresh */
|
|
+/* Embedded controller accelerometer read command and its result: */
|
|
+static const struct thinkpad_ec_row ec_accel_args =
|
|
+ { .mask=0x0001, .val={0x11} };
|
|
+#define EC_ACCEL_IDX_READOUTS 0x1 /* readouts included in this read */
|
|
+ /* First readout, if READOUTS>=1: */
|
|
+#define EC_ACCEL_IDX_YPOS1 0x2 /* y-axis position word */
|
|
+#define EC_ACCEL_IDX_XPOS1 0x4 /* x-axis position word */
|
|
+#define EC_ACCEL_IDX_TEMP1 0x6 /* device temperature in Celsius */
|
|
+ /* Second readout, if READOUTS>=2: */
|
|
+#define EC_ACCEL_IDX_XPOS2 0x7 /* y-axis position word */
|
|
+#define EC_ACCEL_IDX_YPOS2 0x9 /* x-axis pisition word */
|
|
+#define EC_ACCEL_IDX_TEMP2 0xb /* device temperature in Celsius */
|
|
+#define EC_ACCEL_IDX_QUEUED 0xc /* Number of queued readouts left */
|
|
+#define EC_ACCEL_IDX_KMACT 0xd /* keyboard or mouse activity */
|
|
+#define EC_ACCEL_IDX_RETVAL 0xf /* command return value, good=0x00 */
|
|
|
|
#define KEYBD_MASK 0x20 /* set if keyboard activity */
|
|
#define MOUSE_MASK 0x40 /* set if mouse activity */
|
|
-#define KEYBD_ISSET(n) (!! (n & KEYBD_MASK)) /* keyboard used? */
|
|
-#define MOUSE_ISSET(n) (!! (n & MOUSE_MASK)) /* mouse used? */
|
|
|
|
-#define INIT_TIMEOUT_MSECS 4000 /* wait up to 4s for device init ... */
|
|
-#define INIT_WAIT_MSECS 200 /* ... in 200ms increments */
|
|
+#define READ_TIMEOUT_MSECS 100 /* wait this long for device read */
|
|
+#define RETRY_MSECS 3 /* retry delay */
|
|
|
|
-#define HDAPS_POLL_PERIOD (HZ/20) /* poll for input every 1/20s */
|
|
#define HDAPS_INPUT_FUZZ 4 /* input event threshold */
|
|
#define HDAPS_INPUT_FLAT 4
|
|
+#define KMACT_REMEMBER_PERIOD (HZ/10) /* keyboard/mouse persistance */
|
|
+
|
|
+/* Input IDs */
|
|
+#define HDAPS_INPUT_VENDOR PCI_VENDOR_ID_IBM
|
|
+#define HDAPS_INPUT_PRODUCT 0x5054 /* "TP", shared with thinkpad_acpi */
|
|
+#define HDAPS_INPUT_JS_VERSION 0x6801 /* Joystick emulation input device */
|
|
+#define HDAPS_INPUT_RAW_VERSION 0x4801 /* Raw accelerometer input device */
|
|
|
|
static struct timer_list hdaps_timer;
|
|
static struct platform_device *pdev;
|
|
-static struct input_dev *hdaps_idev;
|
|
+static struct input_dev *hdaps_idev; /* joystick-like device with fuzz */
|
|
+static struct input_dev *hdaps_idev_raw; /* raw hdaps sensor readouts */
|
|
static unsigned int hdaps_invert;
|
|
-static u8 km_activity;
|
|
-static int rest_x;
|
|
-static int rest_y;
|
|
+static int needs_calibration;
|
|
|
|
-static DEFINE_MUTEX(hdaps_mtx);
|
|
+/* Configuration: */
|
|
+static int sampling_rate = 50; /* Sampling rate */
|
|
+static int oversampling_ratio = 5; /* Ratio between our sampling rate and
|
|
+ * EC accelerometer sampling rate */
|
|
+static int running_avg_filter_order = 2; /* EC running average filter order */
|
|
+static int fake_data_mode; /* Enable EC fake data mode? */
|
|
+
|
|
+/* Latest state readout: */
|
|
+static int pos_x, pos_y; /* position */
|
|
+static int temperature; /* temperature */
|
|
+static int stale_readout = 1; /* last read invalid */
|
|
+static int rest_x, rest_y; /* calibrated rest position */
|
|
+
|
|
+/* Last time we saw keyboard and mouse activity: */
|
|
+static u64 last_keyboard_jiffies = INITIAL_JIFFIES;
|
|
+static u64 last_mouse_jiffies = INITIAL_JIFFIES;
|
|
+static u64 last_update_jiffies = INITIAL_JIFFIES;
|
|
+
|
|
+/* input device use count */
|
|
+static int hdaps_users;
|
|
+static DEFINE_MUTEX(hdaps_users_mtx);
|
|
|
|
-/*
|
|
- * __get_latch - Get the value from a given port. Callers must hold hdaps_mtx.
|
|
- */
|
|
-static inline u8 __get_latch(u16 port)
|
|
+/* Some models require an axis transformation to the standard reprsentation */
|
|
+static void transform_axes(int *x, int *y)
|
|
{
|
|
- return inb(port) & 0xff;
|
|
+ if (hdaps_invert) {
|
|
+ *x = -*x;
|
|
+ *y = -*y;
|
|
+ }
|
|
}
|
|
|
|
-/*
|
|
- * __check_latch - Check a port latch for a given value. Returns zero if the
|
|
- * port contains the given value. Callers must hold hdaps_mtx.
|
|
+/**
|
|
+ * __hdaps_update - query current state, with locks already acquired
|
|
+ * @fast: if nonzero, do one quick attempt without retries.
|
|
+ *
|
|
+ * Query current accelerometer state and update global state variables.
|
|
+ * Also prefetches the next query. Caller must hold controller lock.
|
|
*/
|
|
-static inline int __check_latch(u16 port, u8 val)
|
|
+static int __hdaps_update(int fast)
|
|
{
|
|
- if (__get_latch(port) == val)
|
|
- return 0;
|
|
- return -EINVAL;
|
|
+ /* Read data: */
|
|
+ struct thinkpad_ec_row data;
|
|
+ int ret;
|
|
+
|
|
+ data.mask = (1 << EC_ACCEL_IDX_READOUTS) | (1 << EC_ACCEL_IDX_KMACT) |
|
|
+ (3 << EC_ACCEL_IDX_YPOS1) | (3 << EC_ACCEL_IDX_XPOS1) |
|
|
+ (1 << EC_ACCEL_IDX_TEMP1) | (1 << EC_ACCEL_IDX_RETVAL);
|
|
+ if (fast)
|
|
+ ret = thinkpad_ec_try_read_row(&ec_accel_args, &data);
|
|
+ else
|
|
+ ret = thinkpad_ec_read_row(&ec_accel_args, &data);
|
|
+ thinkpad_ec_prefetch_row(&ec_accel_args); /* Prefetch even if error */
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ /* Check status: */
|
|
+ if (data.val[EC_ACCEL_IDX_RETVAL] != 0x00) {
|
|
+ printk(KERN_WARNING "hdaps: read RETVAL=0x%02x\n",
|
|
+ data.val[EC_ACCEL_IDX_RETVAL]);
|
|
+ return -EIO;
|
|
+ }
|
|
+
|
|
+ if (data.val[EC_ACCEL_IDX_READOUTS] < 1)
|
|
+ return -EBUSY; /* no pending readout, try again later */
|
|
+
|
|
+ /* Parse position data: */
|
|
+ pos_x = *(s16*)(data.val+EC_ACCEL_IDX_XPOS1);
|
|
+ pos_y = *(s16*)(data.val+EC_ACCEL_IDX_YPOS1);
|
|
+ transform_axes(&pos_x, &pos_y);
|
|
+
|
|
+ /* Keyboard and mouse activity status is cleared as soon as it's read,
|
|
+ * so applications will eat each other's events. Thus we remember any
|
|
+ * event for KMACT_REMEMBER_PERIOD jiffies.
|
|
+ */
|
|
+ if (data.val[EC_ACCEL_IDX_KMACT] & KEYBD_MASK)
|
|
+ last_keyboard_jiffies = get_jiffies_64();
|
|
+ if (data.val[EC_ACCEL_IDX_KMACT] & MOUSE_MASK)
|
|
+ last_mouse_jiffies = get_jiffies_64();
|
|
+
|
|
+ temperature = data.val[EC_ACCEL_IDX_TEMP1];
|
|
+
|
|
+ last_update_jiffies = get_jiffies_64();
|
|
+ stale_readout = 0;
|
|
+ if (needs_calibration) {
|
|
+ rest_x = pos_x;
|
|
+ rest_y = pos_y;
|
|
+ needs_calibration = 0;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
}
|
|
|
|
-/*
|
|
- * __wait_latch - Wait up to 100us for a port latch to get a certain value,
|
|
- * returning zero if the value is obtained. Callers must hold hdaps_mtx.
|
|
+/**
|
|
+ * hdaps_update - acquire locks and query current state
|
|
+ *
|
|
+ * Query current accelerometer state and update global state variables.
|
|
+ * Also prefetches the next query.
|
|
+ * Retries until timeout if the accelerometer is not in ready status (common).
|
|
+ * Does its own locking.
|
|
*/
|
|
-static int __wait_latch(u16 port, u8 val)
|
|
+static int hdaps_update(void)
|
|
{
|
|
- unsigned int i;
|
|
+ u64 age = get_jiffies_64() - last_update_jiffies;
|
|
+ int total, ret;
|
|
|
|
- for (i = 0; i < 20; i++) {
|
|
- if (!__check_latch(port, val))
|
|
+ if (!stale_readout && age < (9*HZ)/(10*sampling_rate))
|
|
+ return 0; /* already updated recently */
|
|
+ for (total=0; total<READ_TIMEOUT_MSECS; total+=RETRY_MSECS) {
|
|
+ ret = thinkpad_ec_lock();
|
|
+ if (ret)
|
|
+ return ret;
|
|
+ ret = __hdaps_update(0);
|
|
+ thinkpad_ec_unlock();
|
|
+
|
|
+ if (!ret)
|
|
return 0;
|
|
- udelay(5);
|
|
+ if (ret != -EBUSY)
|
|
+ break;
|
|
+ msleep(RETRY_MSECS);
|
|
}
|
|
-
|
|
- return -EIO;
|
|
+ return ret;
|
|
}
|
|
|
|
-/*
|
|
- * __device_refresh - request a refresh from the accelerometer. Does not wait
|
|
- * for refresh to complete. Callers must hold hdaps_mtx.
|
|
+/**
|
|
+ * hdaps_set_power - enable or disable power to the accelerometer.
|
|
+ * Returns zero on success and negative error code on failure. Can sleep.
|
|
*/
|
|
-static void __device_refresh(void)
|
|
+static int hdaps_set_power(int on)
|
|
{
|
|
- udelay(200);
|
|
- if (inb(0x1604) != STATE_FRESH) {
|
|
- outb(0x11, 0x1610);
|
|
- outb(0x01, 0x161f);
|
|
- }
|
|
+ struct thinkpad_ec_row args =
|
|
+ { .mask=0x0003, .val={0x14, on?0x01:0x00} };
|
|
+ struct thinkpad_ec_row data = { .mask = 0x8000 };
|
|
+ int ret = thinkpad_ec_read_row(&args, &data);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+ if (data.val[0xF]!=0x00)
|
|
+ return -EIO;
|
|
+ return 0;
|
|
}
|
|
|
|
-/*
|
|
- * __device_refresh_sync - request a synchronous refresh from the
|
|
- * accelerometer. We wait for the refresh to complete. Returns zero if
|
|
- * successful and nonzero on error. Callers must hold hdaps_mtx.
|
|
+/**
|
|
+ * hdaps_set_fake_data_mode - enable or disable EC test mode
|
|
+ * EC test mode fakes accelerometer data using an incrementing counter.
|
|
+ * Returns zero on success and negative error code on failure. Can sleep.
|
|
*/
|
|
-static int __device_refresh_sync(void)
|
|
+static int hdaps_set_fake_data_mode(int on)
|
|
{
|
|
- __device_refresh();
|
|
- return __wait_latch(0x1604, STATE_FRESH);
|
|
+ struct thinkpad_ec_row args =
|
|
+ { .mask=0x0007, .val={0x17, 0x83, on?0x01:0x00} };
|
|
+ struct thinkpad_ec_row data = { .mask = 0x8000 };
|
|
+ int ret = thinkpad_ec_read_row(&args, &data);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+ if (data.val[0xF]!=0x00) {
|
|
+ printk(KERN_WARNING "failed setting hdaps fake data to %d\n",
|
|
+ on);
|
|
+ return -EIO;
|
|
+ }
|
|
+ printk(KERN_DEBUG "hdaps: fake_data_mode set to %d\n", on);
|
|
+ return 0;
|
|
}
|
|
|
|
-/*
|
|
- * __device_complete - indicate to the accelerometer that we are done reading
|
|
- * data, and then initiate an async refresh. Callers must hold hdaps_mtx.
|
|
+/**
|
|
+ * hdaps_set_ec_config - set accelerometer parameters.
|
|
+ * @ec_rate: embedded controller sampling rate
|
|
+ * @order: embedded controller running average filter order
|
|
+ * (Normally we have @ec_rate = sampling_rate * oversampling_ratio.)
|
|
+ * Returns zero on success and negative error code on failure. Can sleep.
|
|
*/
|
|
-static inline void __device_complete(void)
|
|
+static int hdaps_set_ec_config(int ec_rate, int order)
|
|
{
|
|
- inb(0x161f);
|
|
- inb(0x1604);
|
|
- __device_refresh();
|
|
+ struct thinkpad_ec_row args = { .mask=0x000F,
|
|
+ .val={0x10, (u8)ec_rate, (u8)(ec_rate>>8), order} };
|
|
+ struct thinkpad_ec_row data = { .mask = 0x8000 };
|
|
+ int ret = thinkpad_ec_read_row(&args, &data);
|
|
+ printk(KERN_DEBUG "hdaps: setting ec_rate=%d, filter_order=%d\n",
|
|
+ ec_rate, order);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+ if (data.val[0xF]==0x03) {
|
|
+ printk(KERN_WARNING "hdaps: config param out of range\n");
|
|
+ return -EINVAL;
|
|
+ }
|
|
+ if (data.val[0xF]==0x06) {
|
|
+ printk(KERN_WARNING "hdaps: config change already pending\n");
|
|
+ return -EBUSY;
|
|
+ }
|
|
+ if (data.val[0xF]!=0x00) {
|
|
+ printk(KERN_WARNING "hdaps: config change error, ret=%d\n",
|
|
+ data.val[0xF]);
|
|
+ return -EIO;
|
|
+ }
|
|
+ return 0;
|
|
}
|
|
|
|
-/*
|
|
- * hdaps_readb_one - reads a byte from a single I/O port, placing the value in
|
|
- * the given pointer. Returns zero on success or a negative error on failure.
|
|
- * Can sleep.
|
|
+/**
|
|
+ * hdaps_get_ec_config - get accelerometer parameters.
|
|
+ * @ec_rate: embedded controller sampling rate
|
|
+ * @order: embedded controller running average filter order
|
|
+ * Returns zero on success and negative error code on failure. Can sleep.
|
|
*/
|
|
-static int hdaps_readb_one(unsigned int port, u8 *val)
|
|
+static int hdaps_get_ec_config(int *ec_rate, int *order)
|
|
{
|
|
- int ret;
|
|
-
|
|
- mutex_lock(&hdaps_mtx);
|
|
-
|
|
- /* do a sync refresh -- we need to be sure that we read fresh data */
|
|
- ret = __device_refresh_sync();
|
|
+ const struct thinkpad_ec_row args =
|
|
+ { .mask=0x0003, .val={0x17, 0x82} };
|
|
+ struct thinkpad_ec_row data = { .mask = 0x801F };
|
|
+ int ret = thinkpad_ec_read_row(&args, &data);
|
|
if (ret)
|
|
- goto out;
|
|
-
|
|
- *val = inb(port);
|
|
- __device_complete();
|
|
-
|
|
-out:
|
|
- mutex_unlock(&hdaps_mtx);
|
|
- return ret;
|
|
+ return ret;
|
|
+ if (data.val[0xF]!=0x00)
|
|
+ return -EIO;
|
|
+ if (!(data.val[0x1] & 0x01))
|
|
+ return -ENXIO; /* accelerometer polling not enabled */
|
|
+ if (data.val[0x1] & 0x02)
|
|
+ return -EBUSY; /* config change in progress, retry later */
|
|
+ *ec_rate = data.val[0x2] | ((int)(data.val[0x3]) << 8);
|
|
+ *order = data.val[0x4];
|
|
+ return 0;
|
|
}
|
|
|
|
-/* __hdaps_read_pair - internal lockless helper for hdaps_read_pair(). */
|
|
-static int __hdaps_read_pair(unsigned int port1, unsigned int port2,
|
|
- int *x, int *y)
|
|
+/**
|
|
+ * hdaps_get_ec_mode - get EC accelerometer mode
|
|
+ * Returns zero on success and negative error code on failure. Can sleep.
|
|
+ */
|
|
+static int hdaps_get_ec_mode(u8 *mode)
|
|
{
|
|
- /* do a sync refresh -- we need to be sure that we read fresh data */
|
|
- if (__device_refresh_sync())
|
|
+ const struct thinkpad_ec_row args = { .mask=0x0001, .val={0x13} };
|
|
+ struct thinkpad_ec_row data = { .mask = 0x8002 };
|
|
+ int ret = thinkpad_ec_read_row(&args, &data);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+ if (data.val[0xF]!=0x00) {
|
|
+ printk(KERN_WARNING
|
|
+ "accelerometer not implemented (0x%02x)\n",
|
|
+ data.val[0xF]);
|
|
return -EIO;
|
|
-
|
|
- *y = inw(port2);
|
|
- *x = inw(port1);
|
|
- km_activity = inb(HDAPS_PORT_KMACT);
|
|
- __device_complete();
|
|
-
|
|
- /* if hdaps_invert is set, negate the two values */
|
|
- if (hdaps_invert) {
|
|
- *x = -*x;
|
|
- *y = -*y;
|
|
}
|
|
-
|
|
+ *mode = data.val[0x1];
|
|
return 0;
|
|
}
|
|
|
|
-/*
|
|
- * hdaps_read_pair - reads the values from a pair of ports, placing the values
|
|
- * in the given pointers. Returns zero on success. Can sleep.
|
|
+/**
|
|
+ * hdaps_check_ec - checks something about the EC.
|
|
+ * Follows the clean-room spec for HDAPS; we don't know what it means.
|
|
+ * Returns zero on success and negative error code on failure. Can sleep.
|
|
*/
|
|
-static int hdaps_read_pair(unsigned int port1, unsigned int port2,
|
|
- int *val1, int *val2)
|
|
+static int hdaps_check_ec(void)
|
|
{
|
|
- int ret;
|
|
-
|
|
- mutex_lock(&hdaps_mtx);
|
|
- ret = __hdaps_read_pair(port1, port2, val1, val2);
|
|
- mutex_unlock(&hdaps_mtx);
|
|
-
|
|
- return ret;
|
|
+ const struct thinkpad_ec_row args =
|
|
+ { .mask=0x0003, .val={0x17, 0x81} };
|
|
+ struct thinkpad_ec_row data = { .mask = 0x800E };
|
|
+ int ret = thinkpad_ec_read_row(&args, &data);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+ if (data.val[0x1]!=0x00 || data.val[0x2]!=0x60 ||
|
|
+ data.val[0x3]!=0x00 || data.val[0xF]!=0x00)
|
|
+ return -EIO;
|
|
+ return 0;
|
|
}
|
|
|
|
-/*
|
|
- * hdaps_device_init - initialize the accelerometer. Returns zero on success
|
|
- * and negative error code on failure. Can sleep.
|
|
+/**
|
|
+ * hdaps_device_init - initialize the accelerometer.
|
|
+ *
|
|
+ * Call several embedded controller functions to test and initialize the
|
|
+ * accelerometer.
|
|
+ * Returns zero on success and negative error code on failure. Can sleep.
|
|
*/
|
|
+#define ABORT_INIT(msg) printk(KERN_ERR "hdaps init failed at: %s\n", msg)
|
|
static int hdaps_device_init(void)
|
|
{
|
|
- int total, ret = -ENXIO;
|
|
+ int ret;
|
|
+ u8 mode;
|
|
|
|
- mutex_lock(&hdaps_mtx);
|
|
+ ret = thinkpad_ec_lock();
|
|
+ if (ret)
|
|
+ return ret;
|
|
|
|
- outb(0x13, 0x1610);
|
|
- outb(0x01, 0x161f);
|
|
- if (__wait_latch(0x161f, 0x00))
|
|
- goto out;
|
|
+ if (hdaps_get_ec_mode(&mode))
|
|
+ { ABORT_INIT("hdaps_get_ec_mode failed"); goto bad; }
|
|
|
|
- /*
|
|
- * Most ThinkPads return 0x01.
|
|
- *
|
|
- * Others--namely the R50p, T41p, and T42p--return 0x03. These laptops
|
|
- * have "inverted" axises.
|
|
- *
|
|
- * The 0x02 value occurs when the chip has been previously initialized.
|
|
- */
|
|
- if (__check_latch(0x1611, 0x03) &&
|
|
- __check_latch(0x1611, 0x02) &&
|
|
- __check_latch(0x1611, 0x01))
|
|
- goto out;
|
|
+ printk(KERN_DEBUG "hdaps: initial mode latch is 0x%02x\n", mode);
|
|
+ if (mode==0x00)
|
|
+ { ABORT_INIT("accelerometer not available"); goto bad; }
|
|
|
|
- printk(KERN_DEBUG "hdaps: initial latch check good (0x%02x).\n",
|
|
- __get_latch(0x1611));
|
|
+ if (hdaps_check_ec())
|
|
+ { ABORT_INIT("hdaps_check_ec failed"); goto bad; }
|
|
|
|
- outb(0x17, 0x1610);
|
|
- outb(0x81, 0x1611);
|
|
- outb(0x01, 0x161f);
|
|
- if (__wait_latch(0x161f, 0x00))
|
|
- goto out;
|
|
- if (__wait_latch(0x1611, 0x00))
|
|
- goto out;
|
|
- if (__wait_latch(0x1612, 0x60))
|
|
- goto out;
|
|
- if (__wait_latch(0x1613, 0x00))
|
|
- goto out;
|
|
- outb(0x14, 0x1610);
|
|
- outb(0x01, 0x1611);
|
|
- outb(0x01, 0x161f);
|
|
- if (__wait_latch(0x161f, 0x00))
|
|
- goto out;
|
|
- outb(0x10, 0x1610);
|
|
- outb(0xc8, 0x1611);
|
|
- outb(0x00, 0x1612);
|
|
- outb(0x02, 0x1613);
|
|
- outb(0x01, 0x161f);
|
|
- if (__wait_latch(0x161f, 0x00))
|
|
- goto out;
|
|
- if (__device_refresh_sync())
|
|
- goto out;
|
|
- if (__wait_latch(0x1611, 0x00))
|
|
- goto out;
|
|
+ if (hdaps_set_power(1))
|
|
+ { ABORT_INIT("hdaps_set_power failed"); goto bad; }
|
|
|
|
- /* we have done our dance, now let's wait for the applause */
|
|
- for (total = INIT_TIMEOUT_MSECS; total > 0; total -= INIT_WAIT_MSECS) {
|
|
- int x, y;
|
|
-
|
|
- /* a read of the device helps push it into action */
|
|
- __hdaps_read_pair(HDAPS_PORT_XPOS, HDAPS_PORT_YPOS, &x, &y);
|
|
- if (!__wait_latch(0x1611, 0x02)) {
|
|
- ret = 0;
|
|
- break;
|
|
- }
|
|
+ if (hdaps_set_ec_config(sampling_rate*oversampling_ratio,
|
|
+ running_avg_filter_order))
|
|
+ { ABORT_INIT("hdaps_set_ec_config failed"); goto bad; }
|
|
|
|
- msleep(INIT_WAIT_MSECS);
|
|
- }
|
|
+ if (hdaps_set_fake_data_mode(fake_data_mode))
|
|
+ { ABORT_INIT("hdaps_set_fake_data_mode failed"); goto bad; }
|
|
|
|
-out:
|
|
- mutex_unlock(&hdaps_mtx);
|
|
+ thinkpad_ec_invalidate();
|
|
+ udelay(200);
|
|
+
|
|
+ /* Just prefetch instead of reading, to avoid ~1sec delay on load */
|
|
+ ret = thinkpad_ec_prefetch_row(&ec_accel_args);
|
|
+ if (ret)
|
|
+ { ABORT_INIT("initial prefetch failed"); goto bad; }
|
|
+ goto good;
|
|
+bad:
|
|
+ thinkpad_ec_invalidate();
|
|
+ ret = -ENXIO;
|
|
+good:
|
|
+ stale_readout = 1;
|
|
+ thinkpad_ec_unlock();
|
|
return ret;
|
|
}
|
|
|
|
+/**
|
|
+ * hdaps_device_shutdown - power off the accelerometer
|
|
+ * Returns nonzero on failure. Can sleep.
|
|
+ */
|
|
+static int hdaps_device_shutdown(void)
|
|
+{
|
|
+ int ret;
|
|
+ ret = hdaps_set_power(0);
|
|
+ if (ret) {
|
|
+ printk(KERN_WARNING "hdaps: cannot power off\n");
|
|
+ return ret;
|
|
+ }
|
|
+ ret = hdaps_set_ec_config(0, 1);
|
|
+ if (ret)
|
|
+ printk(KERN_WARNING "hdaps: cannot stop EC sampling\n");
|
|
+ return ret;
|
|
+}
|
|
|
|
/* Device model stuff */
|
|
|
|
@@ -301,13 +422,30 @@ static int hdaps_probe(struct platform_d
|
|
return 0;
|
|
}
|
|
|
|
+static int hdaps_suspend(struct platform_device *dev, pm_message_t state)
|
|
+{
|
|
+ /* Don't do hdaps polls until resume re-initializes the sensor. */
|
|
+ del_timer_sync(&hdaps_timer);
|
|
+ hdaps_device_shutdown(); /* ignore errors, effect is negligible */
|
|
+ return 0;
|
|
+}
|
|
+
|
|
static int hdaps_resume(struct platform_device *dev)
|
|
{
|
|
- return hdaps_device_init();
|
|
+ int ret = hdaps_device_init();
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ mutex_lock(&hdaps_users_mtx);
|
|
+ if (hdaps_users)
|
|
+ mod_timer(&hdaps_timer, jiffies + HZ/sampling_rate);
|
|
+ mutex_unlock(&hdaps_users_mtx);
|
|
+ return 0;
|
|
}
|
|
|
|
static struct platform_driver hdaps_driver = {
|
|
.probe = hdaps_probe,
|
|
+ .suspend = hdaps_suspend,
|
|
.resume = hdaps_resume,
|
|
.driver = {
|
|
.name = "hdaps",
|
|
@@ -315,35 +453,48 @@ static struct platform_driver hdaps_driv
|
|
},
|
|
};
|
|
|
|
-/*
|
|
- * hdaps_calibrate - Set our "resting" values. Callers must hold hdaps_mtx.
|
|
+/**
|
|
+ * hdaps_calibrate - set our "resting" values.
|
|
+ * Does its own locking.
|
|
*/
|
|
static void hdaps_calibrate(void)
|
|
{
|
|
- __hdaps_read_pair(HDAPS_PORT_XPOS, HDAPS_PORT_YPOS, &rest_x, &rest_y);
|
|
+ needs_calibration = 1;
|
|
+ hdaps_update();
|
|
+ /* If that fails, the mousedev poll will take care of things later. */
|
|
}
|
|
|
|
+/* Timer handler for updating the input device. Runs in softirq context,
|
|
+ * so avoid lenghty or blocking operations.
|
|
+ */
|
|
static void hdaps_mousedev_poll(unsigned long unused)
|
|
{
|
|
- int x, y;
|
|
+ int ret;
|
|
+
|
|
+ stale_readout = 1;
|
|
|
|
/* Cannot sleep. Try nonblockingly. If we fail, try again later. */
|
|
- if (mutex_trylock(&hdaps_mtx)) {
|
|
- mod_timer(&hdaps_timer,jiffies + HDAPS_POLL_PERIOD);
|
|
+ if (thinkpad_ec_try_lock())
|
|
+ goto keep_active;
|
|
+
|
|
+ ret = __hdaps_update(1); /* fast update, we're in softirq context */
|
|
+ thinkpad_ec_unlock();
|
|
+ /* Any of "successful", "not yet ready" and "not prefetched"? */
|
|
+ if (ret!=0 && ret!=-EBUSY && ret!=-ENODATA) {
|
|
+ printk(KERN_ERR
|
|
+ "hdaps: poll failed, disabling updates\n");
|
|
return;
|
|
}
|
|
|
|
- if (__hdaps_read_pair(HDAPS_PORT_XPOS, HDAPS_PORT_YPOS, &x, &y))
|
|
- goto out;
|
|
-
|
|
- input_report_abs(hdaps_idev, ABS_X, x - rest_x);
|
|
- input_report_abs(hdaps_idev, ABS_Y, y - rest_y);
|
|
+keep_active:
|
|
+ /* Even if we failed now, pos_x,y may have been updated earlier: */
|
|
+ input_report_abs(hdaps_idev, ABS_X, pos_x - rest_x);
|
|
+ input_report_abs(hdaps_idev, ABS_Y, pos_y - rest_y);
|
|
input_sync(hdaps_idev);
|
|
-
|
|
- mod_timer(&hdaps_timer, jiffies + HDAPS_POLL_PERIOD);
|
|
-
|
|
-out:
|
|
- mutex_unlock(&hdaps_mtx);
|
|
+ input_report_abs(hdaps_idev_raw, ABS_X, pos_x);
|
|
+ input_report_abs(hdaps_idev_raw, ABS_Y, pos_y);
|
|
+ input_sync(hdaps_idev_raw);
|
|
+ mod_timer(&hdaps_timer, jiffies + HZ/sampling_rate);
|
|
}
|
|
|
|
|
|
@@ -352,65 +503,41 @@ out:
|
|
static ssize_t hdaps_position_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
- int ret, x, y;
|
|
-
|
|
- ret = hdaps_read_pair(HDAPS_PORT_XPOS, HDAPS_PORT_YPOS, &x, &y);
|
|
+ int ret = hdaps_update();
|
|
if (ret)
|
|
return ret;
|
|
-
|
|
- return sprintf(buf, "(%d,%d)\n", x, y);
|
|
-}
|
|
-
|
|
-static ssize_t hdaps_variance_show(struct device *dev,
|
|
- struct device_attribute *attr, char *buf)
|
|
-{
|
|
- int ret, x, y;
|
|
-
|
|
- ret = hdaps_read_pair(HDAPS_PORT_XVAR, HDAPS_PORT_YVAR, &x, &y);
|
|
- if (ret)
|
|
- return ret;
|
|
-
|
|
- return sprintf(buf, "(%d,%d)\n", x, y);
|
|
+ return sprintf(buf, "(%d,%d)\n", pos_x, pos_y);
|
|
}
|
|
|
|
static ssize_t hdaps_temp1_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
- u8 temp;
|
|
- int ret;
|
|
-
|
|
- ret = hdaps_readb_one(HDAPS_PORT_TEMP1, &temp);
|
|
- if (ret < 0)
|
|
- return ret;
|
|
-
|
|
- return sprintf(buf, "%u\n", temp);
|
|
-}
|
|
-
|
|
-static ssize_t hdaps_temp2_show(struct device *dev,
|
|
- struct device_attribute *attr, char *buf)
|
|
-{
|
|
- u8 temp;
|
|
- int ret;
|
|
-
|
|
- ret = hdaps_readb_one(HDAPS_PORT_TEMP2, &temp);
|
|
- if (ret < 0)
|
|
+ int ret = hdaps_update();
|
|
+ if (ret)
|
|
return ret;
|
|
-
|
|
- return sprintf(buf, "%u\n", temp);
|
|
+ return sprintf(buf, "%d\n", temperature);
|
|
}
|
|
|
|
static ssize_t hdaps_keyboard_activity_show(struct device *dev,
|
|
struct device_attribute *attr,
|
|
char *buf)
|
|
{
|
|
- return sprintf(buf, "%u\n", KEYBD_ISSET(km_activity));
|
|
+ int ret = hdaps_update();
|
|
+ if (ret)
|
|
+ return ret;
|
|
+ return sprintf(buf, "%u\n",
|
|
+ get_jiffies_64() < last_keyboard_jiffies + KMACT_REMEMBER_PERIOD);
|
|
}
|
|
|
|
static ssize_t hdaps_mouse_activity_show(struct device *dev,
|
|
struct device_attribute *attr,
|
|
char *buf)
|
|
{
|
|
- return sprintf(buf, "%u\n", MOUSE_ISSET(km_activity));
|
|
+ int ret = hdaps_update();
|
|
+ if (ret)
|
|
+ return ret;
|
|
+ return sprintf(buf, "%u\n",
|
|
+ get_jiffies_64() < last_mouse_jiffies + KMACT_REMEMBER_PERIOD);
|
|
}
|
|
|
|
static ssize_t hdaps_calibrate_show(struct device *dev,
|
|
@@ -423,10 +550,7 @@ static ssize_t hdaps_calibrate_store(str
|
|
struct device_attribute *attr,
|
|
const char *buf, size_t count)
|
|
{
|
|
- mutex_lock(&hdaps_mtx);
|
|
hdaps_calibrate();
|
|
- mutex_unlock(&hdaps_mtx);
|
|
-
|
|
return count;
|
|
}
|
|
|
|
@@ -451,24 +575,150 @@ static ssize_t hdaps_invert_store(struct
|
|
return count;
|
|
}
|
|
|
|
+static ssize_t hdaps_sampling_rate_show(
|
|
+ struct device *dev, struct device_attribute *attr, char *buf)
|
|
+{
|
|
+ return sprintf(buf, "%d\n", sampling_rate);
|
|
+}
|
|
+
|
|
+static ssize_t hdaps_sampling_rate_store(
|
|
+ struct device *dev, struct device_attribute *attr,
|
|
+ const char *buf, size_t count)
|
|
+{
|
|
+ int rate, ret;
|
|
+ if (sscanf(buf, "%d", &rate) != 1 || rate>HZ || rate<=0) {
|
|
+ printk(KERN_WARNING
|
|
+ "must have 0<input_sampling_rate<=HZ=%d\n", HZ);
|
|
+ return -EINVAL;
|
|
+ }
|
|
+ ret = hdaps_set_ec_config(rate*oversampling_ratio,
|
|
+ running_avg_filter_order);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+ sampling_rate = rate;
|
|
+ return count;
|
|
+}
|
|
+
|
|
+static ssize_t hdaps_oversampling_ratio_show(
|
|
+ struct device *dev, struct device_attribute *attr, char *buf)
|
|
+{
|
|
+ int ec_rate, order;
|
|
+ int ret = hdaps_get_ec_config(&ec_rate, &order);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+ return sprintf(buf, "%u\n", ec_rate / sampling_rate);
|
|
+}
|
|
+
|
|
+static ssize_t hdaps_oversampling_ratio_store(
|
|
+ struct device *dev, struct device_attribute *attr,
|
|
+ const char *buf, size_t count)
|
|
+{
|
|
+ int ratio, ret;
|
|
+ if (sscanf(buf, "%d", &ratio) != 1 || ratio<1)
|
|
+ return -EINVAL;
|
|
+ ret = hdaps_set_ec_config(sampling_rate*ratio,
|
|
+ running_avg_filter_order);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+ oversampling_ratio = ratio;
|
|
+ return count;
|
|
+}
|
|
+
|
|
+static ssize_t hdaps_running_avg_filter_order_show(
|
|
+ struct device *dev, struct device_attribute *attr, char *buf)
|
|
+{
|
|
+ int rate, order;
|
|
+ int ret = hdaps_get_ec_config(&rate, &order);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+ return sprintf(buf, "%u\n", order);
|
|
+}
|
|
+
|
|
+static ssize_t hdaps_running_avg_filter_order_store(
|
|
+ struct device *dev, struct device_attribute *attr,
|
|
+ const char *buf, size_t count)
|
|
+{
|
|
+ int order, ret;
|
|
+ if (sscanf(buf, "%d", &order) != 1)
|
|
+ return -EINVAL;
|
|
+ ret = hdaps_set_ec_config(sampling_rate*oversampling_ratio, order);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+ running_avg_filter_order = order;
|
|
+ return count;
|
|
+}
|
|
+
|
|
+static ssize_t hdaps_fake_data_mode_store(struct device *dev,
|
|
+ struct device_attribute *attr,
|
|
+ const char *buf, size_t count)
|
|
+{
|
|
+ int on, ret;
|
|
+ if (sscanf(buf, "%d", &on) != 1 || on<0 || on>1)
|
|
+ return -EINVAL;
|
|
+ ret = hdaps_set_fake_data_mode(on);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+ fake_data_mode = on;
|
|
+ return count;
|
|
+}
|
|
+
|
|
+static ssize_t hdaps_fake_data_mode_show(
|
|
+ struct device *dev, struct device_attribute *attr, char *buf)
|
|
+{
|
|
+ return sprintf(buf, "%d\n", fake_data_mode);
|
|
+}
|
|
+
|
|
+static int hdaps_mousedev_open(struct input_dev *dev)
|
|
+{
|
|
+ if (!try_module_get(THIS_MODULE))
|
|
+ return -ENODEV;
|
|
+
|
|
+ mutex_lock(&hdaps_users_mtx);
|
|
+ if (hdaps_users++ == 0) /* first input user */
|
|
+ mod_timer(&hdaps_timer, jiffies + HZ/sampling_rate);
|
|
+ mutex_unlock(&hdaps_users_mtx);
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static void hdaps_mousedev_close(struct input_dev *dev)
|
|
+{
|
|
+ mutex_lock(&hdaps_users_mtx);
|
|
+ if (--hdaps_users == 0) /* no input users left */
|
|
+ del_timer_sync(&hdaps_timer);
|
|
+ mutex_unlock(&hdaps_users_mtx);
|
|
+
|
|
+ module_put(THIS_MODULE);
|
|
+}
|
|
+
|
|
static DEVICE_ATTR(position, 0444, hdaps_position_show, NULL);
|
|
-static DEVICE_ATTR(variance, 0444, hdaps_variance_show, NULL);
|
|
static DEVICE_ATTR(temp1, 0444, hdaps_temp1_show, NULL);
|
|
-static DEVICE_ATTR(temp2, 0444, hdaps_temp2_show, NULL);
|
|
+ /* "temp1" instead of "temperature" is hwmon convention */
|
|
static DEVICE_ATTR(keyboard_activity, 0444, hdaps_keyboard_activity_show, NULL);
|
|
static DEVICE_ATTR(mouse_activity, 0444, hdaps_mouse_activity_show, NULL);
|
|
static DEVICE_ATTR(calibrate, 0644, hdaps_calibrate_show,hdaps_calibrate_store);
|
|
static DEVICE_ATTR(invert, 0644, hdaps_invert_show, hdaps_invert_store);
|
|
+static DEVICE_ATTR(sampling_rate, 0644,
|
|
+ hdaps_sampling_rate_show, hdaps_sampling_rate_store);
|
|
+static DEVICE_ATTR(oversampling_ratio, 0644,
|
|
+ hdaps_oversampling_ratio_show,
|
|
+ hdaps_oversampling_ratio_store);
|
|
+static DEVICE_ATTR(running_avg_filter_order, 0644,
|
|
+ hdaps_running_avg_filter_order_show,
|
|
+ hdaps_running_avg_filter_order_store);
|
|
+static DEVICE_ATTR(fake_data_mode, 0644,
|
|
+ hdaps_fake_data_mode_show, hdaps_fake_data_mode_store);
|
|
|
|
static struct attribute *hdaps_attributes[] = {
|
|
&dev_attr_position.attr,
|
|
- &dev_attr_variance.attr,
|
|
&dev_attr_temp1.attr,
|
|
- &dev_attr_temp2.attr,
|
|
&dev_attr_keyboard_activity.attr,
|
|
&dev_attr_mouse_activity.attr,
|
|
&dev_attr_calibrate.attr,
|
|
&dev_attr_invert.attr,
|
|
+ &dev_attr_sampling_rate.attr,
|
|
+ &dev_attr_oversampling_ratio.attr,
|
|
+ &dev_attr_running_avg_filter_order.attr,
|
|
+ &dev_attr_fake_data_mode.attr,
|
|
NULL,
|
|
};
|
|
|
|
@@ -479,28 +729,13 @@ static struct attribute_group hdaps_attr
|
|
|
|
/* Module stuff */
|
|
|
|
-/* hdaps_dmi_match - found a match. return one, short-circuiting the hunt. */
|
|
-static int __init hdaps_dmi_match(struct dmi_system_id *id)
|
|
-{
|
|
- printk(KERN_INFO "hdaps: %s detected.\n", id->ident);
|
|
- return 1;
|
|
-}
|
|
-
|
|
/* hdaps_dmi_match_invert - found an inverted match. */
|
|
static int __init hdaps_dmi_match_invert(struct dmi_system_id *id)
|
|
{
|
|
hdaps_invert = 1;
|
|
- printk(KERN_INFO "hdaps: inverting axis readings.\n");
|
|
- return hdaps_dmi_match(id);
|
|
-}
|
|
-
|
|
-#define HDAPS_DMI_MATCH_NORMAL(vendor, model) { \
|
|
- .ident = vendor " " model, \
|
|
- .callback = hdaps_dmi_match, \
|
|
- .matches = { \
|
|
- DMI_MATCH(DMI_BOARD_VENDOR, vendor), \
|
|
- DMI_MATCH(DMI_PRODUCT_VERSION, model) \
|
|
- } \
|
|
+ printk(KERN_INFO "hdaps: %s detected, inverting axes\n",
|
|
+ id->ident);
|
|
+ return 1;
|
|
}
|
|
|
|
#define HDAPS_DMI_MATCH_INVERT(vendor, model) { \
|
|
@@ -512,46 +747,28 @@ static int __init hdaps_dmi_match_invert
|
|
} \
|
|
}
|
|
|
|
-/* Note that HDAPS_DMI_MATCH_NORMAL("ThinkPad T42") would match
|
|
- "ThinkPad T42p", so the order of the entries matters.
|
|
- If your ThinkPad is not recognized, please update to latest
|
|
- BIOS. This is especially the case for some R52 ThinkPads. */
|
|
-static struct dmi_system_id __initdata hdaps_whitelist[] = {
|
|
- HDAPS_DMI_MATCH_INVERT("IBM", "ThinkPad R50p"),
|
|
- HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad R50"),
|
|
- HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad R51"),
|
|
- HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad R52"),
|
|
- HDAPS_DMI_MATCH_INVERT("IBM", "ThinkPad T41p"),
|
|
- HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad T41"),
|
|
- HDAPS_DMI_MATCH_INVERT("IBM", "ThinkPad T42p"),
|
|
- HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad T42"),
|
|
- HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad T43"),
|
|
- HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad T60"),
|
|
- HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad X40"),
|
|
- HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad X41"),
|
|
- HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad X60"),
|
|
- HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad Z60m"),
|
|
- { .ident = NULL }
|
|
-};
|
|
-
|
|
static int __init hdaps_init(void)
|
|
{
|
|
int ret;
|
|
|
|
- if (!dmi_check_system(hdaps_whitelist)) {
|
|
- printk(KERN_WARNING "hdaps: supported laptop not found!\n");
|
|
- ret = -ENODEV;
|
|
- goto out;
|
|
- }
|
|
+ /* List of models with abnormal axis configuration. */
|
|
+ struct dmi_system_id hdaps_whitelist[] = {
|
|
+ HDAPS_DMI_MATCH_INVERT("IBM","ThinkPad R50p"),
|
|
+ HDAPS_DMI_MATCH_INVERT("IBM","ThinkPad T41p"),
|
|
+ HDAPS_DMI_MATCH_INVERT("IBM","ThinkPad T42p"),
|
|
+ HDAPS_DMI_MATCH_INVERT("LENOVO","ThinkPad T60"),
|
|
+ HDAPS_DMI_MATCH_INVERT("LENOVO","ThinkPad X60"),
|
|
+ { .ident = NULL }
|
|
+ };
|
|
|
|
- if (!request_region(HDAPS_LOW_PORT, HDAPS_NR_PORTS, "hdaps")) {
|
|
- ret = -ENXIO;
|
|
- goto out;
|
|
- }
|
|
+ dmi_check_system(hdaps_whitelist); /* default to normal axes */
|
|
|
|
+ /* Init timer before platform_driver_register, in case of suspend */
|
|
+ init_timer(&hdaps_timer);
|
|
+ hdaps_timer.function = hdaps_mousedev_poll;
|
|
ret = platform_driver_register(&hdaps_driver);
|
|
if (ret)
|
|
- goto out_region;
|
|
+ goto out;
|
|
|
|
pdev = platform_device_register_simple("hdaps", -1, NULL, 0);
|
|
if (IS_ERR(pdev)) {
|
|
@@ -569,13 +786,26 @@ static int __init hdaps_init(void)
|
|
goto out_group;
|
|
}
|
|
|
|
- /* initial calibrate for the input device */
|
|
- hdaps_calibrate();
|
|
+ hdaps_idev_raw = input_allocate_device();
|
|
+ if (!hdaps_idev_raw) {
|
|
+ ret = -ENOMEM;
|
|
+ goto out_idev_first;
|
|
+ }
|
|
|
|
- /* initialize the input class */
|
|
- hdaps_idev->name = "hdaps";
|
|
- hdaps_idev->dev.parent = &pdev->dev;
|
|
+ /* calibration for the input device (deferred to avoid delay) */
|
|
+ needs_calibration = 1;
|
|
+
|
|
+ /* initialize the joystick-like fuzzed input device */
|
|
+ hdaps_idev->name = "ThinkPad HDAPS joystick emulation";
|
|
+ hdaps_idev->phys = "hdaps/input0";
|
|
+ hdaps_idev->id.bustype = BUS_HOST;
|
|
+ hdaps_idev->id.vendor = HDAPS_INPUT_VENDOR;
|
|
+ hdaps_idev->id.product = HDAPS_INPUT_PRODUCT;
|
|
+ hdaps_idev->id.version = HDAPS_INPUT_JS_VERSION;
|
|
+ hdaps_idev->cdev.dev = &pdev->dev;
|
|
hdaps_idev->evbit[0] = BIT(EV_ABS);
|
|
+ hdaps_idev->open = hdaps_mousedev_open;
|
|
+ hdaps_idev->close = hdaps_mousedev_close;
|
|
input_set_abs_params(hdaps_idev, ABS_X,
|
|
-256, 256, HDAPS_INPUT_FUZZ, HDAPS_INPUT_FLAT);
|
|
input_set_abs_params(hdaps_idev, ABS_Y,
|
|
@@ -585,16 +815,32 @@ static int __init hdaps_init(void)
|
|
if (ret)
|
|
goto out_idev;
|
|
|
|
- /* start up our timer for the input device */
|
|
- init_timer(&hdaps_timer);
|
|
- hdaps_timer.function = hdaps_mousedev_poll;
|
|
- hdaps_timer.expires = jiffies + HDAPS_POLL_PERIOD;
|
|
- add_timer(&hdaps_timer);
|
|
+ /* initialize the raw data input device */
|
|
+ hdaps_idev_raw->name = "ThinkPad HDAPS accelerometer data";
|
|
+ hdaps_idev_raw->phys = "hdaps/input1";
|
|
+ hdaps_idev_raw->id.bustype = BUS_HOST;
|
|
+ hdaps_idev_raw->id.vendor = HDAPS_INPUT_VENDOR;
|
|
+ hdaps_idev_raw->id.product = HDAPS_INPUT_PRODUCT;
|
|
+ hdaps_idev_raw->id.version = HDAPS_INPUT_RAW_VERSION;
|
|
+ hdaps_idev_raw->cdev.dev = &pdev->dev;
|
|
+ hdaps_idev_raw->evbit[0] = BIT(EV_ABS);
|
|
+ hdaps_idev_raw->open = hdaps_mousedev_open;
|
|
+ hdaps_idev_raw->close = hdaps_mousedev_close;
|
|
+ input_set_abs_params(hdaps_idev_raw, ABS_X, -32768, 32767, 0, 0);
|
|
+ input_set_abs_params(hdaps_idev_raw, ABS_Y, -32768, 32767, 0, 0);
|
|
+
|
|
+ ret = input_register_device(hdaps_idev_raw);
|
|
+ if (ret)
|
|
+ goto out_idev_reg_first;
|
|
|
|
printk(KERN_INFO "hdaps: driver successfully loaded.\n");
|
|
return 0;
|
|
|
|
+out_idev_reg_first:
|
|
+ input_unregister_device(hdaps_idev);
|
|
out_idev:
|
|
+ input_free_device(hdaps_idev_raw);
|
|
+out_idev_first:
|
|
input_free_device(hdaps_idev);
|
|
out_group:
|
|
sysfs_remove_group(&pdev->dev.kobj, &hdaps_attribute_group);
|
|
@@ -602,8 +848,7 @@ out_device:
|
|
platform_device_unregister(pdev);
|
|
out_driver:
|
|
platform_driver_unregister(&hdaps_driver);
|
|
-out_region:
|
|
- release_region(HDAPS_LOW_PORT, HDAPS_NR_PORTS);
|
|
+ hdaps_device_shutdown();
|
|
out:
|
|
printk(KERN_WARNING "hdaps: driver init failed (ret=%d)!\n", ret);
|
|
return ret;
|
|
@@ -611,12 +856,12 @@ out:
|
|
|
|
static void __exit hdaps_exit(void)
|
|
{
|
|
- del_timer_sync(&hdaps_timer);
|
|
+ input_unregister_device(hdaps_idev_raw);
|
|
input_unregister_device(hdaps_idev);
|
|
+ hdaps_device_shutdown(); /* ignore errors, effect is negligible */
|
|
sysfs_remove_group(&pdev->dev.kobj, &hdaps_attribute_group);
|
|
platform_device_unregister(pdev);
|
|
platform_driver_unregister(&hdaps_driver);
|
|
- release_region(HDAPS_LOW_PORT, HDAPS_NR_PORTS);
|
|
|
|
printk(KERN_INFO "hdaps: driver unloaded.\n");
|
|
}
|
|
Index: linux-2.6.23-rc6/drivers/hwmon/Kconfig
|
|
===================================================================
|
|
--- linux-2.6.23-rc6.orig/drivers/hwmon/Kconfig
|
|
+++ linux-2.6.23-rc6/drivers/hwmon/Kconfig
|
|
@@ -644,6 +644,7 @@ config SENSORS_W83627EHF
|
|
config SENSORS_HDAPS
|
|
tristate "IBM Hard Drive Active Protection System (hdaps)"
|
|
depends on INPUT && X86
|
|
+ select THINKPAD_EC
|
|
default n
|
|
help
|
|
This driver provides support for the IBM Hard Drive Active Protection
|
|
Index: linux-2.6.23-rc6/drivers/misc/Kconfig
|
|
===================================================================
|
|
--- linux-2.6.23-rc6.orig/drivers/misc/Kconfig
|
|
+++ linux-2.6.23-rc6/drivers/misc/Kconfig
|
|
@@ -222,5 +222,25 @@ config THINKPAD_ACPI_INPUT_ENABLED
|
|
set up the thinkpad-acpi hot key handling using the sysfs interace
|
|
after loading the driver.
|
|
|
|
+config THINKPAD_EC
|
|
+ tristate
|
|
+ depends on X86
|
|
+ ---help---
|
|
+ This is a low-level driver for accessing the ThinkPad H8S embedded
|
|
+ controller over the LPC bus (not to be confused with the ACPI Embedded
|
|
+ Controller interface).
|
|
+
|
|
+config TP_SMAPI
|
|
+ tristate "ThinkPad SMAPI Support"
|
|
+ depends on X86
|
|
+ select THINKPAD_EC
|
|
+ default n
|
|
+ help
|
|
+ This adds SMAPI support on IBM ThinkPads, mostly used for battery
|
|
+ charge control. For more information about this driver see
|
|
+ <http://www.thinkwiki.org/wiki/tp_smapi>.
|
|
+
|
|
+ If you have an IBM ThinkPad laptop, say Y or M here.
|
|
+
|
|
|
|
endif # MISC_DEVICES
|
|
Index: linux-2.6.23-rc6/drivers/misc/Makefile
|
|
===================================================================
|
|
--- linux-2.6.23-rc6.orig/drivers/misc/Makefile
|
|
+++ linux-2.6.23-rc6/drivers/misc/Makefile
|
|
@@ -14,4 +14,6 @@ obj-$(CONFIG_PHANTOM) += phantom.o
|
|
obj-$(CONFIG_SGI_IOC4) += ioc4.o
|
|
obj-$(CONFIG_SONY_LAPTOP) += sony-laptop.o
|
|
obj-$(CONFIG_THINKPAD_ACPI) += thinkpad_acpi.o
|
|
+obj-$(CONFIG_THINKPAD_EC) += thinkpad_ec.o
|
|
+obj-$(CONFIG_TP_SMAPI) += tp_smapi.o
|
|
obj-$(CONFIG_EEPROM_93CX6) += eeprom_93cx6.o
|
|
Index: linux-2.6.23-rc6/drivers/misc/thinkpad_ec.c
|
|
===================================================================
|
|
--- /dev/null
|
|
+++ linux-2.6.23-rc6/drivers/misc/thinkpad_ec.c
|
|
@@ -0,0 +1,489 @@
|
|
+/*
|
|
+ * thinkpad_ec.c - coordinate access to ThinkPad-specific hardware resources
|
|
+ *
|
|
+ * The embedded controller on ThinkPad laptops has a non-standard interface
|
|
+ * at IO ports 0x1600-0x161F (mapped to LCP channel 3 of the H8S chip).
|
|
+ * The interface provides various system management services (currently
|
|
+ * known: battery information and accelerometer readouts). This driver
|
|
+ * provides access and mutual exclusion for the EC interface.
|
|
+*
|
|
+ * The LPC protocol and terminology is documented here:
|
|
+ * "H8S/2104B Group Hardware Manual",
|
|
+ * http://documentation.renesas.com/eng/products/mpumcu/rej09b0300_2140bhm.pdf
|
|
+ *
|
|
+ * Copyright (C) 2006-2007 Shem Multinymous <multinymous@gmail.com>
|
|
+ *
|
|
+ * This program is free software; you can redistribute it and/or modify
|
|
+ * it under the terms of the GNU General Public License as published by
|
|
+ * the Free Software Foundation; either version 2 of the License, or
|
|
+ * (at your option) any later version.
|
|
+ *
|
|
+ * This program is distributed in the hope that it will be useful,
|
|
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
+ * GNU General Public License for more details.
|
|
+ *
|
|
+ * You should have received a copy of the GNU General Public License
|
|
+ * along with this program; if not, write to the Free Software
|
|
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
+ */
|
|
+
|
|
+#include <linux/kernel.h>
|
|
+#include <linux/module.h>
|
|
+#include <linux/dmi.h>
|
|
+#include <linux/ioport.h>
|
|
+#include <linux/delay.h>
|
|
+#include <linux/thinkpad_ec.h>
|
|
+#include <linux/jiffies.h>
|
|
+#include <asm/semaphore.h>
|
|
+#include <asm/io.h>
|
|
+
|
|
+#define TP_VERSION "0.32"
|
|
+
|
|
+MODULE_AUTHOR("Shem Multinymous");
|
|
+MODULE_DESCRIPTION("ThinkPad embedded controller hardware access");
|
|
+MODULE_VERSION(TP_VERSION);
|
|
+MODULE_LICENSE("GPL");
|
|
+
|
|
+/* IO ports used by embedded controller LPC channel 3: */
|
|
+#define TPC_BASE_PORT 0x1600
|
|
+#define TPC_NUM_PORTS 0x20
|
|
+#define TPC_STR3_PORT 0x1604 /* Reads H8S EC register STR3 */
|
|
+#define TPC_TWR0_PORT 0x1610 /* Mapped to H8S EC register TWR0MW/SW */
|
|
+#define TPC_TWR15_PORT 0x161F /* Mapped to H8S EC register TWR15. */
|
|
+ /* (and port TPC_TWR0_PORT+i is mapped to H8S reg TWRi for 0<i<16) */
|
|
+
|
|
+/* H8S STR3 status flags (see "H8S/2104B Group Hardware Manual" p.549) */
|
|
+#define H8S_STR3_IBF3B 0x80 /* Bidi. Data Register Input Buffer Full */
|
|
+#define H8S_STR3_OBF3B 0x40 /* Bidi. Data Register Output Buffer Full */
|
|
+#define H8S_STR3_MWMF 0x20 /* Master Write Mode Flag */
|
|
+#define H8S_STR3_SWMF 0x10 /* Slave Write Mode Flag */
|
|
+#define H8S_STR3_MASK 0xF0 /* All bits we care about in STR3 */
|
|
+
|
|
+/* Timeouts and retries */
|
|
+#define TPC_READ_RETRIES 150
|
|
+#define TPC_READ_NDELAY 500
|
|
+#define TPC_REQUEST_RETRIES 1000
|
|
+#define TPC_REQUEST_NDELAY 10
|
|
+#define TPC_PREFETCH_TIMEOUT (HZ/10) /* invalidate prefetch after 0.1sec */
|
|
+
|
|
+/* A few macros for printk()ing: */
|
|
+#define MSG_FMT(fmt, args...) \
|
|
+ "thinkpad_ec: %s: " fmt "\n", __func__, ## args
|
|
+#define REQ_FMT(msg, code) \
|
|
+ MSG_FMT("%s: (0x%02x:0x%02x)->0x%02x", \
|
|
+ msg, args->val[0x0], args->val[0xF], code)
|
|
+
|
|
+/* State of request prefetching: */
|
|
+static u8 prefetch_arg0, prefetch_argF; /* Args of last prefetch */
|
|
+static u64 prefetch_jiffies; /* time of prefetch, or one of: */
|
|
+#define TPC_PREFETCH_NONE INITIAL_JIFFIES /* - No prefetch */
|
|
+#define TPC_PREFETCH_JUNK (INITIAL_JIFFIES+1) /* - Ignore prefetch */
|
|
+
|
|
+/* Locking: */
|
|
+
|
|
+static DECLARE_MUTEX(thinkpad_ec_mutex);
|
|
+
|
|
+/**
|
|
+ * thinkpad_ec_lock - get lock on the ThinkPad EC
|
|
+ *
|
|
+ * Get exclusive lock for accesing the ThinkPad embedded controller LPC3
|
|
+ * interface. Returns 0 iff lock acquired.
|
|
+ */
|
|
+int thinkpad_ec_lock(void)
|
|
+{
|
|
+ int ret;
|
|
+ ret = down_interruptible(&thinkpad_ec_mutex);
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+EXPORT_SYMBOL_GPL(thinkpad_ec_lock);
|
|
+
|
|
+/**
|
|
+ * thinkpad_ec_try_lock - try getting lock on the ThinkPad EC
|
|
+ *
|
|
+ * Try getting an exclusive lock for accesing the ThinkPad embedded
|
|
+ * controller LPC3. Returns immediately if lock is not available; neither
|
|
+ * blocks nor sleeps. Returns 0 iff lock acquired .
|
|
+ */
|
|
+int thinkpad_ec_try_lock(void)
|
|
+{
|
|
+ return down_trylock(&thinkpad_ec_mutex);
|
|
+}
|
|
+
|
|
+EXPORT_SYMBOL_GPL(thinkpad_ec_try_lock);
|
|
+
|
|
+/**
|
|
+ * thinkpad_ec_unlock - release lock on ThinkPad EC
|
|
+ *
|
|
+ * Release a previously acquired exclusive lock on the ThinkPad ebmedded
|
|
+ * controller LPC3 interface.
|
|
+ */
|
|
+void thinkpad_ec_unlock(void)
|
|
+{
|
|
+ up(&thinkpad_ec_mutex);
|
|
+}
|
|
+
|
|
+EXPORT_SYMBOL_GPL(thinkpad_ec_unlock);
|
|
+
|
|
+/**
|
|
+ * thinkpad_ec_request_row - tell embedded controller to prepare a row
|
|
+ * @args Input register arguments
|
|
+ *
|
|
+ * Requests a data row by writing to H8S LPC registers TRW0 through TWR15 (or
|
|
+ * a subset thereof) following the protocol prescribed by the "H8S/2104B Group
|
|
+* Hardware Manual". Does sanity checks via status register STR3.
|
|
+ */
|
|
+static int thinkpad_ec_request_row(const struct thinkpad_ec_row *args)
|
|
+{
|
|
+ u8 str3;
|
|
+ int i;
|
|
+
|
|
+ /* EC protocol requires write to TWR0 (function code): */
|
|
+ if (!(args->mask & 0x0001)) {
|
|
+ printk(KERN_ERR MSG_FMT("bad args->mask=0x%02x", args->mask));
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ /* Check initial STR3 status: */
|
|
+ str3 = inb(TPC_STR3_PORT) & H8S_STR3_MASK;
|
|
+ if (str3 & H8S_STR3_OBF3B) { /* data already pending */
|
|
+ inb(TPC_TWR15_PORT); /* marks end of previous transaction */
|
|
+ if (prefetch_jiffies == TPC_PREFETCH_NONE)
|
|
+ printk(KERN_WARNING
|
|
+ REQ_FMT("readout already pending", str3));
|
|
+ return -EBUSY; /* EC will be ready in a few usecs */
|
|
+ } else if (str3 == H8S_STR3_SWMF) { /* busy with previous request */
|
|
+ if (prefetch_jiffies == TPC_PREFETCH_NONE)
|
|
+ printk(KERN_WARNING
|
|
+ REQ_FMT("EC handles previous request", str3));
|
|
+ return -EBUSY; /* data will be pending in a few usecs */
|
|
+ } else if (str3 != 0x00) { /* unexpected status? */
|
|
+ printk(KERN_WARNING REQ_FMT("bad initial STR3", str3));
|
|
+ return -EIO;
|
|
+ }
|
|
+
|
|
+ /* Send TWR0MW: */
|
|
+ outb(args->val[0], TPC_TWR0_PORT);
|
|
+ str3 = inb(TPC_STR3_PORT) & H8S_STR3_MASK;
|
|
+ if (str3 != H8S_STR3_MWMF) { /* not accepted? */
|
|
+ printk(KERN_WARNING REQ_FMT("arg0 rejected", str3));
|
|
+ return -EIO;
|
|
+ }
|
|
+
|
|
+ /* Send TWR1 through TWR14: */
|
|
+ for (i=1; i<TP_CONTROLLER_ROW_LEN-1; i++)
|
|
+ if ((args->mask>>i)&1)
|
|
+ outb(args->val[i], TPC_TWR0_PORT+i);
|
|
+
|
|
+ /* Send TWR15 (default to 0x01). This marks end of command. */
|
|
+ outb((args->mask & 0x8000) ? args->val[0xF] : 0x01, TPC_TWR15_PORT);
|
|
+
|
|
+ /* Wait until EC starts writing its reply (~60ns on average).
|
|
+ * Releasing locks before this happens may cause an EC hang
|
|
+ * due to firmware bug!
|
|
+ */
|
|
+ for (i=0; i<TPC_REQUEST_RETRIES; i++) {
|
|
+ str3 = inb(TPC_STR3_PORT) & H8S_STR3_MASK;
|
|
+ if (str3 & H8S_STR3_SWMF) /* EC started replying */
|
|
+ return 0;
|
|
+ else if (!(str3 & ~(H8S_STR3_IBF3B|H8S_STR3_MWMF)))
|
|
+ /* Normal progress (the EC hasn't seen the request
|
|
+ * yet, or is processing it). Wait it out. */
|
|
+ ndelay(TPC_REQUEST_NDELAY);
|
|
+ else { /* weird EC status */
|
|
+ printk(KERN_WARNING
|
|
+ REQ_FMT("bad end STR3", str3));
|
|
+ return -EIO;
|
|
+ }
|
|
+ }
|
|
+ printk(KERN_WARNING REQ_FMT("EC is mysteriously silent", str3));
|
|
+ return -EIO;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * thinkpad_ec_read_data - read pre-requested row-data from EC
|
|
+ * @args Input register arguments of pre-requested rows
|
|
+ * @data Output register values
|
|
+ *
|
|
+ * Reads current row data from the controller, assuming it's already
|
|
+ * requested. Follows the H8S spec for register access and status checks.
|
|
+ */
|
|
+static int thinkpad_ec_read_data(const struct thinkpad_ec_row *args,
|
|
+ struct thinkpad_ec_row *data)
|
|
+{
|
|
+ int i;
|
|
+ u8 str3 = inb(TPC_STR3_PORT) & H8S_STR3_MASK;
|
|
+ /* Once we make a request, STR3 assumes the sequence of values listed
|
|
+ * in the following 'if' as it reads the request and writes its data.
|
|
+ * It takes about a few dozen nanosecs total, with very high variance.
|
|
+ */
|
|
+ if (str3==(H8S_STR3_IBF3B|H8S_STR3_MWMF) ||
|
|
+ str3==0x00 || /* the 0x00 is indistinguishable from idle EC! */
|
|
+ str3==H8S_STR3_SWMF )
|
|
+ return -EBUSY; /* not ready yet */
|
|
+ /* Finally, the EC signals output buffer full: */
|
|
+ if (str3 != (H8S_STR3_OBF3B|H8S_STR3_SWMF)) {
|
|
+ printk(KERN_WARNING
|
|
+ REQ_FMT("bad initial STR3", str3));
|
|
+ return -EIO;
|
|
+ }
|
|
+
|
|
+ /* Read first byte (signals start of read transactions): */
|
|
+ data->val[0] = inb(TPC_TWR0_PORT);
|
|
+ /* Optionally read 14 more bytes: */
|
|
+ for (i=1; i<TP_CONTROLLER_ROW_LEN-1; i++)
|
|
+ if ((data->mask >> i)&1)
|
|
+ data->val[i] = inb(TPC_TWR0_PORT+i);
|
|
+ /* Read last byte from 0x161F (signals end of read transaction): */
|
|
+ data->val[0xF] = inb(TPC_TWR15_PORT);
|
|
+
|
|
+ /* Readout still pending? */
|
|
+ str3 = inb(TPC_STR3_PORT) & H8S_STR3_MASK;
|
|
+ if (str3 & H8S_STR3_OBF3B)
|
|
+ printk(KERN_WARNING
|
|
+ REQ_FMT("OBF3B=1 after read", str3));
|
|
+ /* If port 0x161F returns 0x80 too often, the EC may lock up: */
|
|
+ if (data->val[0xF] == 0x80)
|
|
+ printk(KERN_WARNING
|
|
+ REQ_FMT("0x161F reports error", data->val[0xF]));
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+/* Is the given row currently prefetched?
|
|
+ * To keep things simple we compare only the first and last args;
|
|
+ * in practice this suffices .*/
|
|
+static int thinkpad_ec_is_row_fetched(const struct thinkpad_ec_row *args)
|
|
+{
|
|
+ return (prefetch_jiffies != TPC_PREFETCH_NONE) &&
|
|
+ (prefetch_jiffies != TPC_PREFETCH_JUNK) &&
|
|
+ (prefetch_arg0 == args->val[0]) &&
|
|
+ (prefetch_argF == args->val[0xF]) &&
|
|
+ (get_jiffies_64() < prefetch_jiffies + TPC_PREFETCH_TIMEOUT);
|
|
+}
|
|
+
|
|
+/**
|
|
+ * thinkpad_ec_read_row - request and read data from ThinkPad EC
|
|
+ * @args Input register arguments
|
|
+ * @data Output register values
|
|
+ *
|
|
+ * Read a data row from the ThinkPad embedded controller LPC3 interface.
|
|
+ * Does fetching and retrying if needed. The row args are specified by
|
|
+ * 16 byte arguments, some of which may be missing (but the first is
|
|
+ * mandatory). These are given in @args->val[], where @args->val[i] is
|
|
+ * used iff (@args->mask>>i)&1). The rows's data is stored in @data->val[],
|
|
+ * but is only guaranteed to be valid for indices corresponding to set
|
|
+ * bit in @data->mask. That is, if (@data->mask>>i)&1==0 then @data->val[i]
|
|
+ * may not be filled (to save time).
|
|
+ *
|
|
+ * Returns -EBUSY on transient error and -EIO on abnormal condition.
|
|
+ * Caller must hold controller lock.
|
|
+ */
|
|
+int thinkpad_ec_read_row(const struct thinkpad_ec_row *args,
|
|
+ struct thinkpad_ec_row *data)
|
|
+{
|
|
+ int retries, ret;
|
|
+
|
|
+ if (thinkpad_ec_is_row_fetched(args))
|
|
+ goto read_row; /* already requested */
|
|
+
|
|
+ /* Request the row */
|
|
+ for (retries=0; retries<TPC_READ_RETRIES; ++retries) {
|
|
+ ret = thinkpad_ec_request_row(args);
|
|
+ if (!ret)
|
|
+ goto read_row;
|
|
+ if (ret != -EBUSY)
|
|
+ break;
|
|
+ ndelay(TPC_READ_NDELAY);
|
|
+ }
|
|
+ printk(KERN_ERR REQ_FMT("failed requesting row", ret));
|
|
+ goto out;
|
|
+
|
|
+read_row:
|
|
+ /* Read the row's data */
|
|
+ for (retries=0; retries<TPC_READ_RETRIES; ++retries) {
|
|
+ ret = thinkpad_ec_read_data(args, data);
|
|
+ if (!ret)
|
|
+ goto out;
|
|
+ if (ret!=-EBUSY)
|
|
+ break;
|
|
+ ndelay(TPC_READ_NDELAY);
|
|
+ }
|
|
+
|
|
+ printk(KERN_ERR REQ_FMT("failed waiting for data", ret));
|
|
+
|
|
+out:
|
|
+ prefetch_jiffies = TPC_PREFETCH_JUNK;
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+EXPORT_SYMBOL_GPL(thinkpad_ec_read_row);
|
|
+
|
|
+/**
|
|
+ * thinkpad_ec_try_read_row - try reading prefetched data from ThinkPad EC
|
|
+ * @args Input register arguments
|
|
+ * @data Output register values
|
|
+ *
|
|
+ * Try reading a data row from the ThinkPad embedded controller LPC3
|
|
+ * interface, if this raw was recently prefetched using
|
|
+ * thinkpad_ec_prefetch_row(). Does not fetch, retry or block.
|
|
+ * The parameters have the same meaning as in thinkpad_ec_read_row().
|
|
+ *
|
|
+ * Returns -EBUSY is data not ready and -ENODATA if row not prefetched.
|
|
+ * Caller must hold controller lock.
|
|
+ */
|
|
+int thinkpad_ec_try_read_row(const struct thinkpad_ec_row *args,
|
|
+ struct thinkpad_ec_row *data)
|
|
+{
|
|
+ int ret;
|
|
+ if (!thinkpad_ec_is_row_fetched(args)) {
|
|
+ ret = -ENODATA;
|
|
+ } else {
|
|
+ ret = thinkpad_ec_read_data(args, data);
|
|
+ if (!ret)
|
|
+ prefetch_jiffies = TPC_PREFETCH_NONE; /* eaten up */
|
|
+ }
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+EXPORT_SYMBOL_GPL(thinkpad_ec_try_read_row);
|
|
+
|
|
+/**
|
|
+ * thinkpad_ec_prefetch_row - prefetch data from ThinkPad EC
|
|
+ * @args Input register arguments
|
|
+ *
|
|
+ * Prefetch a data row from the ThinkPad embedded controller LCP3
|
|
+ * interface. A subsequent call to thinkpad_ec_read_row() with the
|
|
+ * same arguments will be faster, and a subsequent call to
|
|
+ * thinkpad_ec_try_read_row() stands a good chance of succeeding if
|
|
+ * done neither too soon nor too late. See
|
|
+ * thinkpad_ec_read_row() for the meaning of @args.
|
|
+ *
|
|
+ * Returns -EBUSY on transient error and -EIO on abnormal condition.
|
|
+ * Caller must hold controller lock.
|
|
+ */
|
|
+int thinkpad_ec_prefetch_row(const struct thinkpad_ec_row *args)
|
|
+{
|
|
+ int ret;
|
|
+ ret = thinkpad_ec_request_row(args);
|
|
+ if (ret) {
|
|
+ prefetch_jiffies = TPC_PREFETCH_JUNK;
|
|
+ } else {
|
|
+ prefetch_jiffies = get_jiffies_64();
|
|
+ prefetch_arg0 = args->val[0x0];
|
|
+ prefetch_argF = args->val[0xF];
|
|
+ }
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+EXPORT_SYMBOL_GPL(thinkpad_ec_prefetch_row);
|
|
+
|
|
+/**
|
|
+ * thinkpad_ec_invalidate - invalidate prefetched ThinkPad EC data
|
|
+ *
|
|
+ * Invalidate the data prefetched via thinkpad_ec_prefetch_row() from the
|
|
+ * ThinkPad embedded controller LPC3 interface.
|
|
+ * Must be called before unlocking by any code that accesses the controller
|
|
+ * ports directly.
|
|
+ */
|
|
+void thinkpad_ec_invalidate(void)
|
|
+{
|
|
+ prefetch_jiffies = TPC_PREFETCH_JUNK;
|
|
+}
|
|
+
|
|
+EXPORT_SYMBOL_GPL(thinkpad_ec_invalidate);
|
|
+
|
|
+
|
|
+/*** Checking for EC hardware ***/
|
|
+
|
|
+/**
|
|
+ * thinkpad_ec_test - verify the EC is present and follows protocol
|
|
+ *
|
|
+ * Ensure the EC LPC3 channel really works on this machine by making
|
|
+ * an EC request and seeing if the EC follows the documented H8S protocol.
|
|
+ * The requested row just reads battery status, so it should be harmless to
|
|
+ * access it (on a correct EC).
|
|
+ * This test writes to IO ports, so execute only after checking DMI.
|
|
+ */
|
|
+static int __init thinkpad_ec_test(void)
|
|
+{
|
|
+ int ret;
|
|
+ const struct thinkpad_ec_row args = /* battery 0 basic status */
|
|
+ { .mask=0x8001, .val={0x01,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0x00} };
|
|
+ struct thinkpad_ec_row data = { .mask = 0x0000 };
|
|
+ ret = thinkpad_ec_lock();
|
|
+ if (ret)
|
|
+ return ret;
|
|
+ ret = thinkpad_ec_read_row(&args, &data);
|
|
+ thinkpad_ec_unlock();
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+/* Search all DMI device names of a given type for a substring */
|
|
+static int __init dmi_find_substring(int type, const char *substr)
|
|
+{
|
|
+ struct dmi_device *dev = NULL;
|
|
+ while ((dev = dmi_find_device(type, NULL, dev))) {
|
|
+ if (strstr(dev->name, substr))
|
|
+ return 1;
|
|
+ }
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+#define TP_DMI_MATCH(vendor,model) { \
|
|
+ .ident = vendor " " model, \
|
|
+ .matches = { \
|
|
+ DMI_MATCH(DMI_BOARD_VENDOR, vendor), \
|
|
+ DMI_MATCH(DMI_PRODUCT_VERSION, model) \
|
|
+ } \
|
|
+}
|
|
+
|
|
+/* Check DMI for existence of ThinkPad embedded controller */
|
|
+static int __init check_dmi_for_ec(void)
|
|
+{
|
|
+ /* A few old models that have a good EC but don't report it in DMI */
|
|
+ struct dmi_system_id tp_whitelist[] = {
|
|
+ TP_DMI_MATCH("IBM","ThinkPad A30"),
|
|
+ TP_DMI_MATCH("IBM","ThinkPad T23"),
|
|
+ TP_DMI_MATCH("IBM","ThinkPad X24"),
|
|
+ { .ident = NULL }
|
|
+ };
|
|
+ return dmi_find_substring(DMI_DEV_TYPE_OEM_STRING,
|
|
+ "IBM ThinkPad Embedded Controller") ||
|
|
+ dmi_check_system(tp_whitelist);
|
|
+}
|
|
+
|
|
+/*** Init and cleanup ***/
|
|
+
|
|
+static int __init thinkpad_ec_init(void)
|
|
+{
|
|
+ if (!check_dmi_for_ec()) {
|
|
+ printk(KERN_WARNING "thinkpad_ec: no ThinkPad embedded controller!\n");
|
|
+ return -ENODEV;
|
|
+ }
|
|
+
|
|
+ if (!request_region(TPC_BASE_PORT, TPC_NUM_PORTS,
|
|
+ "thinkpad_ec")) {
|
|
+ printk(KERN_ERR "thinkpad_ec: cannot claim io ports %#x-%#x\n",
|
|
+ TPC_BASE_PORT,
|
|
+ TPC_BASE_PORT + TPC_NUM_PORTS -1);
|
|
+ return -ENXIO;
|
|
+ }
|
|
+ prefetch_jiffies = TPC_PREFETCH_JUNK;
|
|
+ if (thinkpad_ec_test()) {
|
|
+ printk(KERN_ERR "thinkpad_ec: initial ec test failed\n");
|
|
+ release_region(TPC_BASE_PORT, TPC_NUM_PORTS);
|
|
+ return -ENXIO;
|
|
+ }
|
|
+ printk(KERN_INFO "thinkpad_ec: thinkpad_ec " TP_VERSION " loaded.\n");
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static void __exit thinkpad_ec_exit(void)
|
|
+{
|
|
+ release_region(TPC_BASE_PORT, TPC_NUM_PORTS);
|
|
+ printk(KERN_INFO "thinkpad_ec: unloaded.\n");
|
|
+}
|
|
+
|
|
+module_init(thinkpad_ec_init);
|
|
+module_exit(thinkpad_ec_exit);
|
|
Index: linux-2.6.23-rc6/drivers/misc/tp_smapi.c
|
|
===================================================================
|
|
--- /dev/null
|
|
+++ linux-2.6.23-rc6/drivers/misc/tp_smapi.c
|
|
@@ -0,0 +1,1385 @@
|
|
+/*
|
|
+ * tp_smapi.c - ThinkPad SMAPI support
|
|
+ *
|
|
+ * This driver exposes some features of the System Management Application
|
|
+ * Program Interface (SMAPI) BIOS found on ThinkPad laptops. It works on
|
|
+ * models in which the SMAPI BIOS runs in SMM and is invoked by writing
|
|
+ * to the APM control port 0xB2. Older models use a different interface;
|
|
+ * for those, try the out-of-tree "thinkpad" module from "tpctl".
|
|
+ * It also exposes battery status information, obtained from the ThinkPad
|
|
+ * embedded controller (via the thinkpad_ec module).
|
|
+ *
|
|
+ *
|
|
+ * Copyright (C) 2006 Shem Multinymous <multinymous@gmail.com>.
|
|
+ * SMAPI access code based on the mwave driver by Mike Sullivan.
|
|
+ *
|
|
+ * This program is free software; you can redistribute it and/or modify
|
|
+ * it under the terms of the GNU General Public License as published by
|
|
+ * the Free Software Foundation; either version 2 of the License, or
|
|
+ * (at your option) any later version.
|
|
+ *
|
|
+ * This program is distributed in the hope that it will be useful,
|
|
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
+ * GNU General Public License for more details.
|
|
+ *
|
|
+ * You should have received a copy of the GNU General Public License
|
|
+ * along with this program; if not, write to the Free Software
|
|
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
+ */
|
|
+
|
|
+#include <linux/kernel.h>
|
|
+#include <linux/module.h>
|
|
+#include <linux/init.h>
|
|
+#include <linux/types.h>
|
|
+#include <linux/proc_fs.h>
|
|
+#include <linux/mc146818rtc.h> /* CMOS defines */
|
|
+#include <linux/delay.h>
|
|
+#include <linux/version.h>
|
|
+#include <linux/thinkpad_ec.h>
|
|
+#include <linux/platform_device.h>
|
|
+#include <asm/uaccess.h>
|
|
+#include <asm/io.h>
|
|
+
|
|
+#define TP_VERSION "0.32"
|
|
+#define TP_DESC "ThinkPad SMAPI Support"
|
|
+#define TP_DIR "smapi"
|
|
+
|
|
+MODULE_AUTHOR("Shem Multinymous");
|
|
+MODULE_DESCRIPTION(TP_DESC);
|
|
+MODULE_VERSION(TP_VERSION);
|
|
+MODULE_LICENSE("GPL");
|
|
+
|
|
+static struct platform_device *pdev;
|
|
+
|
|
+static int tp_debug = 0;
|
|
+module_param_named(debug, tp_debug, int, 0600);
|
|
+MODULE_PARM_DESC(debug, "Debug level (0=off, 1=on)");
|
|
+
|
|
+/* A few macros for printk()ing: */
|
|
+#define TPRINTK(level, fmt, args...) \
|
|
+ dev_printk(level, &(pdev->dev), "%s: " fmt "\n", __func__, ## args)
|
|
+#define DPRINTK(fmt, args...) \
|
|
+ do { if (tp_debug) TPRINTK(KERN_DEBUG, fmt, ## args); } while(0)
|
|
+
|
|
+/*********************************************************************
|
|
+ * SMAPI interface
|
|
+ */
|
|
+
|
|
+/* SMAPI functions (register BX when making the SMM call). */
|
|
+#define SMAPI_GET_INHIBIT_CHARGE 0x2114
|
|
+#define SMAPI_SET_INHIBIT_CHARGE 0x2115
|
|
+#define SMAPI_GET_THRESH_START 0x2116
|
|
+#define SMAPI_SET_THRESH_START 0x2117
|
|
+#define SMAPI_GET_FORCE_DISCHARGE 0x2118
|
|
+#define SMAPI_SET_FORCE_DISCHARGE 0x2119
|
|
+#define SMAPI_GET_THRESH_STOP 0x211a
|
|
+#define SMAPI_SET_THRESH_STOP 0x211b
|
|
+
|
|
+/* SMAPI error codes (see ThinkPad 770 Technical Reference Manual p.83 at
|
|
+ http://www-307.ibm.com/pc/support/site.wss/document.do?lndocid=PFAN-3TUQQD */
|
|
+#define SMAPI_RETCODE_EOF 0xff
|
|
+static struct {u8 rc; char *msg; int ret;} smapi_retcode[]=
|
|
+{
|
|
+ {0x00,"OK",0},
|
|
+ {0x53,"SMAPI fuction is not available",-ENXIO},
|
|
+ {0x81,"Invalid parameter",-EINVAL},
|
|
+ {0x86,"Function is not supported by SMAPI BIOS",-ENOSYS},
|
|
+ {0x90,"System error",-EIO},
|
|
+ {0x91,"System is invalid",-EIO},
|
|
+ {0x92,"System is busy,-EBUSY"},
|
|
+ {0xa0,"Device error (disk read error)",-EIO},
|
|
+ {0xa1,"Device is busy",-EBUSY},
|
|
+ {0xa2,"Device is not attached",-ENXIO},
|
|
+ {0xa3,"Device is disbled",-EIO},
|
|
+ {0xa4,"Request parameter is out of range",-EINVAL},
|
|
+ {0xa5,"Request parameter is not accepted",-EINVAL},
|
|
+ {0xa6,"Transient error",-EBUSY}, /* ? */
|
|
+ {SMAPI_RETCODE_EOF,"Unknown error code",-EIO}
|
|
+};
|
|
+
|
|
+
|
|
+#define SMAPI_MAX_RETRIES 10
|
|
+#define SMAPI_PORT2 0x4F /* fixed port, meaning unclear */
|
|
+static unsigned short smapi_port = 0; /* APM control port, normally 0xB2 */
|
|
+
|
|
+static DECLARE_MUTEX(smapi_mutex);
|
|
+
|
|
+/**
|
|
+ * find_smapi_port - read SMAPI port from NVRAM
|
|
+ */
|
|
+static int __init find_smapi_port(void)
|
|
+{
|
|
+ u16 smapi_id = 0;
|
|
+ unsigned short port = 0;
|
|
+ unsigned long flags;
|
|
+
|
|
+ spin_lock_irqsave(&rtc_lock, flags);
|
|
+ smapi_id = CMOS_READ(0x7C);
|
|
+ smapi_id |= (CMOS_READ(0x7D) << 8);
|
|
+ spin_unlock_irqrestore(&rtc_lock, flags);
|
|
+
|
|
+ if (smapi_id != 0x5349) {
|
|
+ printk(KERN_ERR "SMAPI not supported (ID=0x%x)\n", smapi_id);
|
|
+ return -ENXIO;
|
|
+ }
|
|
+ spin_lock_irqsave(&rtc_lock, flags);
|
|
+ port = CMOS_READ(0x7E);
|
|
+ port |= (CMOS_READ(0x7F) << 8);
|
|
+ spin_unlock_irqrestore(&rtc_lock, flags);
|
|
+ if (port == 0) {
|
|
+ printk(KERN_ERR "unable to read SMAPI port number\n");
|
|
+ return -ENXIO;
|
|
+ }
|
|
+ return port;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * smapi_request - make a SMAPI call
|
|
+ * @inEBX, @inECX, @inEDI, @inESI: input registers
|
|
+ * @outEBX, @outECX, @outEDX, @outEDI, @outESI: outputs registers
|
|
+ * @msg: textual error message
|
|
+ * Invokes the SMAPI SMBIOS with the given input and outpu args.
|
|
+ * All outputs are optional (can be %NULL).
|
|
+ * Returns 0 when successful, and a negative errno constant
|
|
+ * (see smapi_retcode above) upon failure.
|
|
+ */
|
|
+static int smapi_request(u32 inEBX, u32 inECX,
|
|
+ u32 inEDI, u32 inESI,
|
|
+ u32 *outEBX, u32 *outECX, u32 *outEDX,
|
|
+ u32 *outEDI, u32 *outESI, const char** msg)
|
|
+{
|
|
+ int ret = 0;
|
|
+ int i;
|
|
+ int retries;
|
|
+ u8 rc;
|
|
+ /* Must use local vars for output regs, due to reg pressure. */
|
|
+ u32 tmpEAX, tmpEBX, tmpECX, tmpEDX, tmpEDI, tmpESI;
|
|
+
|
|
+ for (retries=0; retries<SMAPI_MAX_RETRIES; ++retries) {
|
|
+ DPRINTK("req_in: BX=%x CX=%x DI=%x SI=%x",
|
|
+ inEBX, inECX, inEDI, inESI);
|
|
+
|
|
+ /* SMAPI's SMBIOS call and thinkpad_ec end up using use
|
|
+ * different interfaces to the same chip, so play it safe. */
|
|
+ ret = thinkpad_ec_lock();
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ __asm__ __volatile__(
|
|
+ "movl $0x00005380,%%eax\n\t"
|
|
+ "movl %6,%%ebx\n\t"
|
|
+ "movl %7,%%ecx\n\t"
|
|
+ "movl %8,%%edi\n\t"
|
|
+ "movl %9,%%esi\n\t"
|
|
+ "xorl %%edx,%%edx\n\t"
|
|
+ "movw %10,%%dx\n\t"
|
|
+ "out %%al,%%dx\n\t" /* trigger SMI to SMBIOS */
|
|
+ "out %%al,$0x4F\n\t"
|
|
+ "movl %%eax,%0\n\t"
|
|
+ "movl %%ebx,%1\n\t"
|
|
+ "movl %%ecx,%2\n\t"
|
|
+ "movl %%edx,%3\n\t"
|
|
+ "movl %%edi,%4\n\t"
|
|
+ "movl %%esi,%5\n\t"
|
|
+ :"=m"(tmpEAX),
|
|
+ "=m"(tmpEBX),
|
|
+ "=m"(tmpECX),
|
|
+ "=m"(tmpEDX),
|
|
+ "=m"(tmpEDI),
|
|
+ "=m"(tmpESI)
|
|
+ :"m"(inEBX), "m"(inECX), "m"(inEDI), "m"(inESI),
|
|
+ "m"((u16)smapi_port)
|
|
+ :"%eax", "%ebx", "%ecx", "%edx", "%edi",
|
|
+ "%esi");
|
|
+
|
|
+ thinkpad_ec_invalidate();
|
|
+ thinkpad_ec_unlock();
|
|
+
|
|
+ /* Don't let the next SMAPI access happen too quickly,
|
|
+ * may case problems. (We're hold smapi_mutex). */
|
|
+ msleep(50);
|
|
+
|
|
+ if (outEBX) *outEBX = tmpEBX;
|
|
+ if (outECX) *outECX = tmpECX;
|
|
+ if (outEDX) *outEDX = tmpEDX;
|
|
+ if (outESI) *outESI = tmpESI;
|
|
+ if (outEDI) *outEDI = tmpEDI;
|
|
+
|
|
+ /* Look up error code */
|
|
+ rc = (tmpEAX>>8)&0xFF;
|
|
+ for (i=0; smapi_retcode[i].rc!=SMAPI_RETCODE_EOF &&
|
|
+ smapi_retcode[i].rc!=rc; ++i) {}
|
|
+ ret = smapi_retcode[i].ret;
|
|
+ if (msg)
|
|
+ *msg = smapi_retcode[i].msg;
|
|
+
|
|
+ DPRINTK("req_out: AX=%x BX=%x CX=%x DX=%x DI=%x SI=%x r=%d",
|
|
+ tmpEAX, tmpEBX, tmpECX, tmpEDX, tmpEDI, tmpESI, ret);
|
|
+ if (ret)
|
|
+ TPRINTK(KERN_NOTICE, "SMAPI error: %s (func=%x)",
|
|
+ smapi_retcode[i].msg, inEBX);
|
|
+
|
|
+ if (ret!=-EBUSY)
|
|
+ return ret;
|
|
+ }
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+/* Convenience wrapper: discard output arguments */
|
|
+static int smapi_write(u32 inEBX, u32 inECX,
|
|
+ u32 inEDI, u32 inESI, const char **msg)
|
|
+{
|
|
+ return smapi_request(inEBX, inECX, inEDI, inESI,
|
|
+ NULL, NULL, NULL, NULL, NULL, msg);
|
|
+}
|
|
+
|
|
+
|
|
+/*********************************************************************
|
|
+ * Specific SMAPI services
|
|
+ * All of these functions return 0 upon success, and a negative errno
|
|
+ * constant (see smapi_retcode) on failure.
|
|
+ */
|
|
+
|
|
+enum thresh_type {
|
|
+ THRESH_STOP = 0, /* the code assumes this is 0 for brevity */
|
|
+ THRESH_START
|
|
+};
|
|
+#define THRESH_NAME(which) ( (which==THRESH_START)?"start":"stop" )
|
|
+
|
|
+/**
|
|
+ * __get_real_thresh - read battery charge start/stop threshold from SMAPI
|
|
+ * @bat: battery number (0 or 1)
|
|
+ * @which: THRESH_START or THRESH_STOP
|
|
+ * @thresh: 1..99, 0=default 1..99, 0=default (pass this as-is to SMAPI)
|
|
+ * @outEDI: some additional state that needs to be preserved, meaning unknown
|
|
+ * @outESI: some additional state that needs to be preserved, meaning unknown
|
|
+ */
|
|
+static int __get_real_thresh(int bat, enum thresh_type which, int *thresh,
|
|
+ u32 *outEDI, u32 *outESI)
|
|
+{
|
|
+ u32 ebx = (which==THRESH_START) ? SMAPI_GET_THRESH_START
|
|
+ : SMAPI_GET_THRESH_STOP;
|
|
+ u32 ecx = (bat+1)<<8;
|
|
+ const char* msg;
|
|
+ int ret = smapi_request(ebx, ecx, 0, 0, NULL, &ecx, NULL, outEDI, outESI, &msg);
|
|
+ if (ret) {
|
|
+ TPRINTK(KERN_NOTICE, "cannot get %s_thresh of bat=%d: %s",
|
|
+ THRESH_NAME(which), bat, msg);
|
|
+ return ret;
|
|
+ }
|
|
+ if (!(ecx&0x00000100)) {
|
|
+ TPRINTK(KERN_NOTICE, "cannot get %s_thresh of bat=%d: ecx=0%x",
|
|
+ THRESH_NAME(which), bat, ecx);
|
|
+ return -EIO;
|
|
+ }
|
|
+ if (thresh)
|
|
+ *thresh = ecx&0xFF;
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * get_real_thresh - read battery charge start/stop threshold from SMAPI
|
|
+ * @bat: battery number (0 or 1)
|
|
+ * @which: THRESH_START or THRESH_STOP
|
|
+ * @thresh: 1..99, 0=default (passes as-is to SMAPI)
|
|
+ */
|
|
+static int get_real_thresh(int bat, enum thresh_type which, int *thresh)
|
|
+{
|
|
+ return __get_real_thresh(bat, which, thresh, NULL, NULL);
|
|
+}
|
|
+
|
|
+/**
|
|
+ * set_real_thresh - write battery start/top charge threshold to SMAPI
|
|
+ * @bat: battery number (0 or 1)
|
|
+ * @which: THRESH_START or THRESH_STOP
|
|
+ * @thresh: 1..99, 0=default (passes as-is to SMAPI)
|
|
+ */
|
|
+static int set_real_thresh(int bat, enum thresh_type which, int thresh)
|
|
+{
|
|
+ u32 ebx = (which==THRESH_START) ? SMAPI_SET_THRESH_START
|
|
+ : SMAPI_SET_THRESH_STOP;
|
|
+ u32 ecx = ((bat+1)<<8) + thresh;
|
|
+ u32 getDI, getSI;
|
|
+ const char* msg;
|
|
+ int ret;
|
|
+
|
|
+ /* verify read before writing */
|
|
+ ret = __get_real_thresh(bat, which, NULL, &getDI, &getSI);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ ret = smapi_write(ebx, ecx, getDI, getSI, &msg);
|
|
+ if (ret)
|
|
+ TPRINTK(KERN_NOTICE, "set %s to %d for bat=%d failed: %s",
|
|
+ THRESH_NAME(which), thresh, bat, msg);
|
|
+ else
|
|
+ TPRINTK(KERN_INFO, "set %s to %d for bat=%d",
|
|
+ THRESH_NAME(which), thresh, bat);
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * __get_inhibit_charge_minutes - get inhibit charge period from SMAPI
|
|
+ * @bat: battery number (0 or 1)
|
|
+ * @minutes: period in minutes (1..65535 minutes, 0=disabled)
|
|
+ * @outECX: some additional state that needs to be preserved, meaning unknown
|
|
+ * Note that @minutes is the originally set value, it does not count down.
|
|
+ */
|
|
+static int __get_inhibit_charge_minutes(int bat, int *minutes, u32 *outECX)
|
|
+{
|
|
+ u32 ecx = (bat+1)<<8;
|
|
+ u32 esi;
|
|
+ const char* msg;
|
|
+ int ret = smapi_request(SMAPI_GET_INHIBIT_CHARGE, ecx, 0, 0,
|
|
+ NULL, &ecx, NULL, NULL, &esi, &msg);
|
|
+ if (ret) {
|
|
+ TPRINTK(KERN_NOTICE, "failed for bat=%d: %s", bat, msg);
|
|
+ return ret;
|
|
+ }
|
|
+ if (!(ecx&0x0100)) {
|
|
+ TPRINTK(KERN_NOTICE, "bad ecx=0x%x for bat=%d", ecx, bat);
|
|
+ return -EIO;
|
|
+ }
|
|
+ if (minutes)
|
|
+ *minutes = (ecx&0x0001)?esi:0;
|
|
+ if (outECX)
|
|
+ *outECX = ecx;
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * get_inhibit_charge_minutes - get inhibit charge period from SMAPI
|
|
+ * @bat: battery number (0 or 1)
|
|
+ * @minutes: period in minutes (1..65535 minutes, 0=disabled)
|
|
+ * Note that @minutes is the originally set value, it does not count down.
|
|
+ */
|
|
+static int get_inhibit_charge_minutes(int bat, int *minutes)
|
|
+{
|
|
+ return __get_inhibit_charge_minutes(bat, minutes, NULL);
|
|
+}
|
|
+
|
|
+/**
|
|
+ * set_inhibit_charge_minutes - write inhibit charge period to SMAPI
|
|
+ * @bat: battery number (0 or 1)
|
|
+ * @minutes: period in minutes (1..65535 minutes, 0=disabled)
|
|
+ */
|
|
+static int set_inhibit_charge_minutes(int bat, int minutes)
|
|
+{
|
|
+ u32 ecx;
|
|
+ const char* msg;
|
|
+ int ret;
|
|
+
|
|
+ /* verify read before writing */
|
|
+ ret = __get_inhibit_charge_minutes(bat, NULL, &ecx);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ ecx = ((bat+1)<<8) | (ecx&0x00FE) | (minutes>0 ? 0x0001 : 0x0000);
|
|
+ if (minutes>0xFFFF)
|
|
+ minutes=0xFFFF;
|
|
+ ret = smapi_write(SMAPI_SET_INHIBIT_CHARGE, ecx, 0, minutes, &msg);
|
|
+ if (ret)
|
|
+ TPRINTK(KERN_NOTICE,
|
|
+ "set to %d failed for bat=%d: %s", minutes, bat, msg);
|
|
+ else
|
|
+ TPRINTK(KERN_INFO, "set to %d for bat=%d\n", minutes, bat);
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+
|
|
+/**
|
|
+ * get_force_discharge - get status of forced discharging from SMAPI
|
|
+ * @bat: battery number (0 or 1)
|
|
+ * @enabled: 1 if forced discharged is enabled, 0 if not
|
|
+ */
|
|
+static int get_force_discharge(int bat, int *enabled)
|
|
+{
|
|
+ u32 ecx = (bat+1)<<8;
|
|
+ const char* msg;
|
|
+ int ret = smapi_request(SMAPI_GET_FORCE_DISCHARGE, ecx, 0, 0,
|
|
+ NULL, &ecx, NULL, NULL, NULL, &msg);
|
|
+ if (ret) {
|
|
+ TPRINTK(KERN_NOTICE, "failed for bat=%d: %s", bat, msg);
|
|
+ return ret;
|
|
+ }
|
|
+ *enabled = (!(ecx&0x00000100) && (ecx&0x00000001))?1:0;
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * set_force_discharge - write status of forced discharging to SMAPI
|
|
+ * @bat: battery number (0 or 1)
|
|
+ * @enabled: 1 if forced discharged is enabled, 0 if not
|
|
+ */
|
|
+static int set_force_discharge(int bat, int enabled)
|
|
+{
|
|
+ u32 ecx = (bat+1)<<8;
|
|
+ const char* msg;
|
|
+ int ret = smapi_request(SMAPI_GET_FORCE_DISCHARGE, ecx, 0, 0,
|
|
+ NULL, &ecx, NULL, NULL, NULL, &msg);
|
|
+ if (ret) {
|
|
+ TPRINTK(KERN_NOTICE, "get failed for bat=%d: %s", bat, msg);
|
|
+ return ret;
|
|
+ }
|
|
+ if (ecx&0x00000100) {
|
|
+ TPRINTK(KERN_NOTICE, "cannot force discharge bat=%d", bat);
|
|
+ return -EIO;
|
|
+ }
|
|
+
|
|
+ ecx = ((bat+1)<<8) | (ecx&0x000000FA) | (enabled?0x00000001:0);
|
|
+ ret = smapi_write(SMAPI_SET_FORCE_DISCHARGE, ecx, 0, 0, &msg);
|
|
+ if (ret)
|
|
+ TPRINTK(KERN_NOTICE, "set to %d failed for bat=%d: %s",
|
|
+ enabled, bat, msg);
|
|
+ else
|
|
+ TPRINTK(KERN_INFO, "set to %d for bat=%d", enabled, bat);
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+
|
|
+/*********************************************************************
|
|
+ * Wrappers to threshold-related SMAPI functions, which handle default
|
|
+ * thresholds and related quirks.
|
|
+ */
|
|
+
|
|
+/* Minimum, default and minimum difference for battery charging thresholds: */
|
|
+#define MIN_THRESH_DELTA 4 /* Min delta between start and stop thresh */
|
|
+#define MIN_THRESH_START 2
|
|
+#define MAX_THRESH_START (100-MIN_THRESH_DELTA)
|
|
+#define MIN_THRESH_STOP (MIN_THRESH_START + MIN_THRESH_DELTA)
|
|
+#define MAX_THRESH_STOP 100
|
|
+#define DEFAULT_THRESH_START MAX_THRESH_START
|
|
+#define DEFAULT_THRESH_STOP MAX_THRESH_STOP
|
|
+
|
|
+/* The GUI of IBM's Battery Maximizer seems to show a start threshold that
|
|
+ * is 1 more than the value we set/get via SMAPI. Since the threshold is
|
|
+ * maintained across reboot, this can be confusing. So we kludge our
|
|
+ * interface for interoperability: */
|
|
+#define BATMAX_FIX 1
|
|
+
|
|
+/* Get charge start/stop threshold (1..100),
|
|
+ * substituting default values if needed and applying BATMAT_FIX. */
|
|
+static int get_thresh(int bat, enum thresh_type which, int *thresh) {
|
|
+ int ret = get_real_thresh(bat, which, thresh);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+ if (*thresh==0)
|
|
+ *thresh = (which==THRESH_START) ? DEFAULT_THRESH_START
|
|
+ : DEFAULT_THRESH_STOP;
|
|
+ else if (which==THRESH_START)
|
|
+ *thresh += BATMAX_FIX;
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+
|
|
+/* Set charge start/stop threshold (1..100),
|
|
+ * substituting default values if needed and applying BATMAT_FIX. */
|
|
+static int set_thresh(int bat, enum thresh_type which, int thresh)
|
|
+{
|
|
+ if (which==THRESH_STOP && thresh==DEFAULT_THRESH_STOP)
|
|
+ thresh = 0; /* 100 is out of range, but default means 100 */
|
|
+ if (which==THRESH_START)
|
|
+ thresh -= BATMAX_FIX;
|
|
+ return set_real_thresh(bat, which, thresh);
|
|
+}
|
|
+
|
|
+/*********************************************************************
|
|
+ * ThinkPad embedded controller readout and basic functions
|
|
+ */
|
|
+
|
|
+/**
|
|
+ * read_tp_ec_row - read data row from the ThinkPad embedded controller
|
|
+ * @arg0: EC command code
|
|
+ * @bat: battery number, 0 or 1
|
|
+ * @j: the byte value to be used for "junk" (unused) input/outputs
|
|
+ * @dataval: result vector
|
|
+ */
|
|
+static int read_tp_ec_row(u8 arg0, int bat, u8 j, u8* dataval) {
|
|
+ int ret;
|
|
+ const struct thinkpad_ec_row args = { .mask=0xFFFF,
|
|
+ .val={arg0, j,j,j,j,j,j,j,j,j,j,j,j,j,j, (u8)bat} };
|
|
+ struct thinkpad_ec_row data = { .mask = 0xFFFF };
|
|
+
|
|
+ ret = thinkpad_ec_lock();
|
|
+ if (ret)
|
|
+ return ret;
|
|
+ ret = thinkpad_ec_read_row(&args, &data);
|
|
+ thinkpad_ec_unlock();
|
|
+ memcpy(dataval, &data.val, TP_CONTROLLER_ROW_LEN);
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * power_device_present - check for presence of battery or AC power
|
|
+ * @bat: 0 for battery 0, 1 for battery 1, otherwise AC power
|
|
+ * Returns 1 if present, 0 if not present, negative if error.
|
|
+ */
|
|
+static int power_device_present(int bat)
|
|
+{
|
|
+ u8 row[TP_CONTROLLER_ROW_LEN];
|
|
+ u8 test;
|
|
+ int ret = read_tp_ec_row(1, bat, 0, row);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+ if (bat==0)
|
|
+ test=0x40;
|
|
+ else if (bat==1)
|
|
+ test=0x20;
|
|
+ else
|
|
+ test=0x80; /* AC power */
|
|
+ return (row[0] & test) ? 1 : 0;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * bat_has_status - check if battery can report detailed status
|
|
+ * @bat: 0 for battery 0, 1 for battery 1
|
|
+ * Returns 1 if yes, 0 if no, negative if error.
|
|
+ */
|
|
+static int bat_has_status(int bat)
|
|
+{
|
|
+ u8 row[TP_CONTROLLER_ROW_LEN];
|
|
+ int ret = read_tp_ec_row(1, bat, 0, row);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+ if ((row[0] & (bat?0x20:0x40)) == 0) /* no battery */
|
|
+ return 0;
|
|
+ if ((row[1] & (0x60)) == 0) /* no status */
|
|
+ return 0;
|
|
+ return 1;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * get_tp_ec_bat_16 - read a 16-bit value from EC battery status data
|
|
+ * @arg0: first argument to EC
|
|
+ * @off: offset in row returned from EC
|
|
+ * @bat: battery (0 or 1)
|
|
+ * @val: the 16-bit value obtained
|
|
+ * Returns nonzero on error.
|
|
+ */
|
|
+static int get_tp_ec_bat_16(u8 arg0, int offset, int bat, u16 *val)
|
|
+{
|
|
+ u8 row[TP_CONTROLLER_ROW_LEN];
|
|
+ int ret;
|
|
+ if (bat_has_status(bat)!=1)
|
|
+ return -ENXIO;
|
|
+ ret = read_tp_ec_row(arg0, bat, 0, row);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+ *val = *(u16*)(row+offset);
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+/*********************************************************************
|
|
+ * sysfs attributes for batteries -
|
|
+ * definitions and helper functions
|
|
+ */
|
|
+
|
|
+/* Define a custom device attribute struct which adds a battery number */
|
|
+struct bat_device_attribute {
|
|
+ struct device_attribute dev_attr;
|
|
+ int bat;
|
|
+};
|
|
+
|
|
+/* Get the battery to which the attribute belongs */
|
|
+static int attr_get_bat(struct device_attribute *attr) {
|
|
+ return container_of(attr, struct bat_device_attribute, dev_attr)->bat;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * show_tp_ec_bat_u16 - show an unsigned 16-bit battery attribute
|
|
+ * @arg0: specified 1st argument of EC raw to read
|
|
+ * @offset: byte offset in EC raw data
|
|
+ * @mul: correction factor to multiply by
|
|
+ * @na_msg: string to output is value not available (0xFFFFFFFF)
|
|
+ * @attr: battery attribute
|
|
+ * @buf: output buffer
|
|
+ * The 16-bit value is read from the EC, treated as unsigned,
|
|
+ * transformed as x->mul*x, and printed to the buffer.
|
|
+ * If the value is 0xFFFFFFFF and na_msg!=%NULL, na_msg is printed instead.
|
|
+ */
|
|
+static ssize_t show_tp_ec_bat_u16(u8 arg0, int offset, int mul,
|
|
+ const char* na_msg,
|
|
+ struct device_attribute *attr, char *buf)
|
|
+{
|
|
+ u16 val;
|
|
+ int ret = get_tp_ec_bat_16(arg0, offset, attr_get_bat(attr), &val);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+ if (na_msg && val == 0xFFFF)
|
|
+ return sprintf(buf, "%s\n", na_msg);
|
|
+ else
|
|
+ return sprintf(buf, "%u\n", mul*(unsigned int)val);
|
|
+}
|
|
+
|
|
+/**
|
|
+ * show_tp_ec_bat_s16 - show an signed 16-bit battery attribute
|
|
+ * @arg0: specified 1st argument of EC raw to read
|
|
+ * @offset: byte offset in EC raw data
|
|
+ * @mul: correction factor to multiply by
|
|
+ * @add: correction term to add after multiplication
|
|
+ * @attr: battery attribute
|
|
+ * @buf: output buffer
|
|
+ * The 16-bit value is read from the EC, treated as signed,
|
|
+ * transformed as x->mul*x+add, and printed to the buffer.
|
|
+ */
|
|
+static ssize_t show_tp_ec_bat_s16(u8 arg0, int offset, int mul, int add,
|
|
+ struct device_attribute *attr, char *buf)
|
|
+{
|
|
+ u16 val;
|
|
+ int ret = get_tp_ec_bat_16(arg0, offset, attr_get_bat(attr), &val);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+ return sprintf(buf, "%d\n", mul*(s16)val+add);
|
|
+}
|
|
+
|
|
+/**
|
|
+ * show_tp_ec_bat_str - show a string from EC battery status data
|
|
+ * @arg0: specified 1st argument of EC raw to read
|
|
+ * @offset: byte offset in EC raw data
|
|
+ * @maxlen: maximum string length
|
|
+ * @attr: battery attribute
|
|
+ * @buf: output buffer
|
|
+ */
|
|
+static ssize_t show_tp_ec_bat_str(u8 arg0, int offset, int maxlen,
|
|
+ struct device_attribute *attr, char *buf)
|
|
+{
|
|
+ int bat = attr_get_bat(attr);
|
|
+ u8 row[TP_CONTROLLER_ROW_LEN];
|
|
+ int ret;
|
|
+ if (bat_has_status(bat)!=1)
|
|
+ return -ENXIO;
|
|
+ ret = read_tp_ec_row(arg0, bat, 0, row);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+ strncpy(buf, (char*)row+offset, maxlen);
|
|
+ buf[maxlen] = 0;
|
|
+ strcat(buf, "\n");
|
|
+ return strlen(buf);
|
|
+}
|
|
+
|
|
+/**
|
|
+ * show_tp_ec_bat_power - show a power readout from EC battery status data
|
|
+ * @arg0: specified 1st argument of EC raw to read
|
|
+ * @offV: byte offset of voltage in EC raw data
|
|
+ * @offI: byte offset of current in EC raw data
|
|
+ * @attr: battery attribute
|
|
+ * @buf: output buffer
|
|
+ * Computes the power as current*voltage from the two given readout offsets.
|
|
+ */
|
|
+static ssize_t show_tp_ec_bat_power(u8 arg0, int offV, int offI,
|
|
+ struct device_attribute *attr, char *buf)
|
|
+{
|
|
+ u8 row[TP_CONTROLLER_ROW_LEN];
|
|
+ int milliamp, millivolt, ret;
|
|
+ int bat = attr_get_bat(attr);
|
|
+ if (bat_has_status(bat)!=1)
|
|
+ return -ENXIO;
|
|
+ ret = read_tp_ec_row(1, bat, 0, row);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+ millivolt = *(u16*)(row+offV);
|
|
+ milliamp = *(s16*)(row+offI);
|
|
+ return sprintf(buf, "%d\n", milliamp*millivolt/1000); /* type: mW */
|
|
+}
|
|
+
|
|
+/**
|
|
+ * show_tp_ec_bat_date - decode and show a date from EC battery status data
|
|
+ * @arg0: specified 1st argument of EC raw to read
|
|
+ * @offset: byte offset in EC raw data
|
|
+ * @attr: battery attribute
|
|
+ * @buf: output buffer
|
|
+ */
|
|
+static ssize_t show_tp_ec_bat_date(u8 arg0, int offset,
|
|
+ struct device_attribute *attr, char *buf)
|
|
+{
|
|
+ u8 row[TP_CONTROLLER_ROW_LEN];
|
|
+ u16 v;
|
|
+ int ret;
|
|
+ int day, month, year;
|
|
+ int bat = attr_get_bat(attr);
|
|
+ if (bat_has_status(bat)!=1)
|
|
+ return -ENXIO;
|
|
+ ret = read_tp_ec_row(arg0, bat, 0, row);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ /* Decode bit-packed: v = day | (month<<5) | ((year-1980)<<9) */
|
|
+ v = *(u16*)(row+offset);
|
|
+ day = v & 0x1F;
|
|
+ month = (v >> 5) & 0xF;
|
|
+ year = (v >> 9) + 1980;
|
|
+
|
|
+ return sprintf(buf, "%04d-%02d-%02d\n", year, month, day);
|
|
+}
|
|
+
|
|
+
|
|
+/*********************************************************************
|
|
+ * sysfs attribute I/O for batteries -
|
|
+ * the actual attribute show/store functions
|
|
+ */
|
|
+
|
|
+static ssize_t show_battery_start_charge_thresh(struct device *dev,
|
|
+ struct device_attribute *attr, char *buf)
|
|
+{
|
|
+ int thresh;
|
|
+ int bat = attr_get_bat(attr);
|
|
+ int ret = get_thresh(bat, THRESH_START, &thresh);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+ return sprintf(buf, "%d\n", thresh); /* type: percent */
|
|
+}
|
|
+
|
|
+static ssize_t show_battery_stop_charge_thresh(struct device *dev,
|
|
+ struct device_attribute *attr, char *buf)
|
|
+{
|
|
+ int thresh;
|
|
+ int bat = attr_get_bat(attr);
|
|
+ int ret = get_thresh(bat, THRESH_STOP, &thresh);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+ return sprintf(buf, "%d\n", thresh); /* type: percent */
|
|
+}
|
|
+
|
|
+/**
|
|
+ * store_battery_start_charge_thresh - store battery_start_charge_thresh attr
|
|
+ * Since this is a kernel<->user interface, we ensure a valid state for
|
|
+ * the hardware. We do this by clamping the requested threshold to the
|
|
+ * valid range and, if necessary, moving the other threshold so that
|
|
+ * it's MIN_THRESH_DELTA away from this one.
|
|
+ */
|
|
+static ssize_t store_battery_start_charge_thresh(struct device *dev,
|
|
+ struct device_attribute *attr, const char *buf, size_t count)
|
|
+{
|
|
+ int thresh, other_thresh, ret;
|
|
+ int bat = attr_get_bat(attr);
|
|
+
|
|
+ if (sscanf(buf, "%d", &thresh)!=1 || thresh<1 || thresh>100)
|
|
+ return -EINVAL;
|
|
+
|
|
+ if (thresh < MIN_THRESH_START) /* clamp up to MIN_THRESH_START */
|
|
+ thresh = MIN_THRESH_START;
|
|
+ if (thresh > MAX_THRESH_START) /* clamp down to MAX_THRESH_START */
|
|
+ thresh = MAX_THRESH_START;
|
|
+
|
|
+ down(&smapi_mutex);
|
|
+ ret = get_thresh(bat, THRESH_STOP, &other_thresh);
|
|
+ if (ret!=-ENOSYS) {
|
|
+ if (ret) /* other threshold is set? */
|
|
+ goto out;
|
|
+ ret = get_real_thresh(bat, THRESH_START, NULL);
|
|
+ if (ret) /* this threshold is set? */
|
|
+ goto out;
|
|
+ if (other_thresh < thresh+MIN_THRESH_DELTA) {
|
|
+ /* move other thresh to keep it above this one */
|
|
+ ret = set_thresh(bat, THRESH_STOP,
|
|
+ thresh+MIN_THRESH_DELTA);
|
|
+ if (ret)
|
|
+ goto out;
|
|
+ }
|
|
+ }
|
|
+ ret = set_thresh(bat, THRESH_START, thresh);
|
|
+out:
|
|
+ up(&smapi_mutex);
|
|
+ return count;
|
|
+
|
|
+}
|
|
+
|
|
+/**
|
|
+ * store_battery_stop_charge_thresh - store battery_stop_charge_thresh attr
|
|
+ * Since this is a kernel<->user interface, we ensure a valid state for
|
|
+ * the hardware. We do this by clamping the requested threshold to the
|
|
+ * valid range and, if necessary, moving the other threshold so that
|
|
+ * it's MIN_THRESH_DELTA away from this one.
|
|
+ */
|
|
+static ssize_t store_battery_stop_charge_thresh(struct device *dev,
|
|
+ struct device_attribute *attr, const char *buf, size_t count)
|
|
+{
|
|
+ int thresh, other_thresh, ret;
|
|
+ int bat = attr_get_bat(attr);
|
|
+
|
|
+ if (sscanf(buf, "%d", &thresh)!=1 || thresh<1 || thresh>100)
|
|
+ return -EINVAL;
|
|
+
|
|
+ if (thresh<MIN_THRESH_STOP) /* clamp up to MIN_THRESH_STOP */
|
|
+ thresh = MIN_THRESH_STOP;
|
|
+
|
|
+ down(&smapi_mutex);
|
|
+ ret = get_thresh(bat, THRESH_START, &other_thresh);
|
|
+ if (ret!=-ENOSYS) { /* other threshold exists? */
|
|
+ if (ret)
|
|
+ goto out;
|
|
+ /* this threshold exists? */
|
|
+ ret = get_real_thresh(bat, THRESH_STOP, NULL);
|
|
+ if (ret)
|
|
+ goto out;
|
|
+ if (other_thresh>=thresh-MIN_THRESH_DELTA) {
|
|
+ /* move other thresh to be below this one */
|
|
+ ret = set_thresh(bat, THRESH_START,
|
|
+ thresh-MIN_THRESH_DELTA);
|
|
+ if (ret)
|
|
+ goto out;
|
|
+ }
|
|
+ }
|
|
+ ret = set_thresh(bat, THRESH_STOP, thresh);
|
|
+out:
|
|
+ up(&smapi_mutex);
|
|
+ return count;
|
|
+}
|
|
+
|
|
+static ssize_t show_battery_inhibit_charge_minutes(struct device *dev,
|
|
+ struct device_attribute *attr, char *buf)
|
|
+{
|
|
+ int minutes;
|
|
+ int bat = attr_get_bat(attr);
|
|
+ int ret = get_inhibit_charge_minutes(bat, &minutes);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+ return sprintf(buf, "%d\n", minutes); /* type: minutes */
|
|
+}
|
|
+
|
|
+static ssize_t store_battery_inhibit_charge_minutes(struct device *dev,
|
|
+ struct device_attribute *attr,
|
|
+ const char *buf, size_t count)
|
|
+{
|
|
+ int ret;
|
|
+ int minutes;
|
|
+ int bat = attr_get_bat(attr);
|
|
+ if (sscanf(buf, "%d", &minutes)!=1 || minutes<0) {
|
|
+ TPRINTK(KERN_ERR, "inhibit_charge_minutes: "
|
|
+ "must be a non-negative integer");
|
|
+ return -EINVAL;
|
|
+ }
|
|
+ ret = set_inhibit_charge_minutes(bat, minutes);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+ return count;
|
|
+}
|
|
+
|
|
+static ssize_t show_battery_force_discharge(struct device *dev,
|
|
+ struct device_attribute *attr, char *buf)
|
|
+{
|
|
+ int enabled;
|
|
+ int bat = attr_get_bat(attr);
|
|
+ int ret = get_force_discharge(bat, &enabled);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+ return sprintf(buf, "%d\n", enabled); /* type: boolean */
|
|
+}
|
|
+
|
|
+static ssize_t store_battery_force_discharge(struct device *dev,
|
|
+ struct device_attribute *attr, const char *buf, size_t count)
|
|
+{
|
|
+ int ret;
|
|
+ int enabled;
|
|
+ int bat = attr_get_bat(attr);
|
|
+ if (sscanf(buf, "%d", &enabled)!=1 || enabled<0 || enabled>1)
|
|
+ return -EINVAL;
|
|
+ ret = set_force_discharge(bat, enabled);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+ return count;
|
|
+}
|
|
+
|
|
+static ssize_t show_battery_installed(
|
|
+ struct device *dev, struct device_attribute *attr, char *buf)
|
|
+{
|
|
+ int bat = attr_get_bat(attr);
|
|
+ int ret = power_device_present(bat);
|
|
+ if (ret<0)
|
|
+ return ret;
|
|
+ return sprintf(buf, "%d\n", ret); /* type: boolean */
|
|
+}
|
|
+
|
|
+static ssize_t show_battery_state(
|
|
+ struct device *dev, struct device_attribute *attr, char *buf)
|
|
+{
|
|
+ u8 row[TP_CONTROLLER_ROW_LEN];
|
|
+ const char* txt;
|
|
+ int ret;
|
|
+ int bat = attr_get_bat(attr);
|
|
+ if (bat_has_status(bat)!=1)
|
|
+ return sprintf(buf, "none\n");
|
|
+ ret = read_tp_ec_row(1, bat, 0, row);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+ switch (row[1] & 0xf0) {
|
|
+ case 0xc0: txt = "idle"; break;
|
|
+ case 0xd0: txt = "discharging"; break;
|
|
+ case 0xe0: txt = "charging"; break;
|
|
+ default: return sprintf(buf, "unknown (0x%x)\n", row[1]);
|
|
+ }
|
|
+ return sprintf(buf, "%s\n", txt); /* type: string from fixed set */
|
|
+}
|
|
+
|
|
+static ssize_t show_battery_manufacturer(
|
|
+ struct device *dev, struct device_attribute *attr, char *buf)
|
|
+{
|
|
+ return show_tp_ec_bat_str(4, 2, TP_CONTROLLER_ROW_LEN-2, attr, buf);
|
|
+}
|
|
+
|
|
+static ssize_t show_battery_model(
|
|
+ struct device *dev, struct device_attribute *attr, char *buf)
|
|
+{
|
|
+ return show_tp_ec_bat_str(5, 2, TP_CONTROLLER_ROW_LEN-2, attr, buf);
|
|
+}
|
|
+
|
|
+static ssize_t show_battery_barcoding(
|
|
+ struct device *dev, struct device_attribute *attr, char *buf)
|
|
+{
|
|
+ return show_tp_ec_bat_str(7, 2, TP_CONTROLLER_ROW_LEN-2, attr, buf);
|
|
+}
|
|
+
|
|
+static ssize_t show_battery_chemistry(
|
|
+ struct device *dev, struct device_attribute *attr, char *buf)
|
|
+{
|
|
+ return show_tp_ec_bat_str(6, 2, 5, attr, buf);
|
|
+}
|
|
+
|
|
+static ssize_t show_battery_voltage(
|
|
+ struct device *dev, struct device_attribute *attr, char *buf)
|
|
+{
|
|
+ return show_tp_ec_bat_u16(1, 6, 1, NULL, attr, buf); /* type: mV */
|
|
+}
|
|
+
|
|
+static ssize_t show_battery_design_voltage(
|
|
+ struct device *dev, struct device_attribute *attr, char *buf)
|
|
+{
|
|
+ return show_tp_ec_bat_u16(3, 4, 1, NULL, attr, buf); /* type: mV */
|
|
+}
|
|
+
|
|
+static ssize_t show_battery_current_now(
|
|
+ struct device *dev, struct device_attribute *attr, char *buf)
|
|
+{
|
|
+ return show_tp_ec_bat_s16(1, 8, 1, 0, attr, buf); /* type: mA */
|
|
+}
|
|
+
|
|
+static ssize_t show_battery_current_avg(
|
|
+ struct device *dev, struct device_attribute *attr, char *buf)
|
|
+{
|
|
+ return show_tp_ec_bat_s16(1, 10, 1, 0, attr, buf); /* type: mA */
|
|
+}
|
|
+
|
|
+static ssize_t show_battery_power_now(
|
|
+ struct device *dev, struct device_attribute *attr, char *buf)
|
|
+{
|
|
+ return show_tp_ec_bat_power(1, 6, 8, attr, buf); /* type: mW */
|
|
+}
|
|
+
|
|
+static ssize_t show_battery_power_avg(
|
|
+ struct device *dev, struct device_attribute *attr, char *buf)
|
|
+{
|
|
+ return show_tp_ec_bat_power(1, 6, 10, attr, buf); /* type: mW */
|
|
+}
|
|
+
|
|
+static ssize_t show_battery_remaining_percent(
|
|
+ struct device *dev, struct device_attribute *attr, char *buf)
|
|
+{
|
|
+ return show_tp_ec_bat_u16(1, 12, 1, NULL, attr, buf); /* type: % */
|
|
+}
|
|
+
|
|
+static ssize_t show_battery_remaining_charging_time(
|
|
+ struct device *dev, struct device_attribute *attr, char *buf)
|
|
+{
|
|
+ return show_tp_ec_bat_u16(2, 8, 1, "not_charging",
|
|
+ attr, buf); /* type: minutes */
|
|
+}
|
|
+
|
|
+static ssize_t show_battery_remaining_running_time(
|
|
+ struct device *dev, struct device_attribute *attr, char *buf)
|
|
+{
|
|
+ return show_tp_ec_bat_u16(2, 6, 1, "not_discharging",
|
|
+ attr, buf); /* type: minutes */
|
|
+}
|
|
+
|
|
+static ssize_t show_battery_remaining_capacity(
|
|
+ struct device *dev, struct device_attribute *attr, char *buf)
|
|
+{
|
|
+ return show_tp_ec_bat_u16(1, 14, 10, "", attr, buf); /* type: mWh */
|
|
+}
|
|
+
|
|
+static ssize_t show_battery_last_full_capacity(
|
|
+ struct device *dev, struct device_attribute *attr, char *buf)
|
|
+{
|
|
+ return show_tp_ec_bat_u16(2, 2, 10, "", attr, buf); /* type: mWh */
|
|
+}
|
|
+
|
|
+static ssize_t show_battery_design_capacity(
|
|
+ struct device *dev, struct device_attribute *attr, char *buf)
|
|
+{
|
|
+ return show_tp_ec_bat_u16(3, 2, 10, "", attr, buf); /* type: mWh */
|
|
+}
|
|
+
|
|
+static ssize_t show_battery_cycle_count(
|
|
+ struct device *dev, struct device_attribute *attr, char *buf)
|
|
+{
|
|
+ return show_tp_ec_bat_u16(2, 12, 1, "", attr, buf); /* type: ordinal */
|
|
+}
|
|
+
|
|
+static ssize_t show_battery_temperature(
|
|
+ struct device *dev, struct device_attribute *attr, char *buf)
|
|
+{
|
|
+ return show_tp_ec_bat_s16(1, 4, 100, -273100, attr, buf); /* type: millicelsius */
|
|
+}
|
|
+
|
|
+static ssize_t show_battery_serial(
|
|
+ struct device *dev, struct device_attribute *attr, char *buf)
|
|
+{
|
|
+ return show_tp_ec_bat_u16(3, 10, 1, "", attr, buf); /* type: int */
|
|
+}
|
|
+
|
|
+static ssize_t show_battery_manufacture_date(
|
|
+ struct device *dev, struct device_attribute *attr, char *buf)
|
|
+{
|
|
+ return show_tp_ec_bat_date(3, 8, attr, buf); /* type: YYYY-MM-DD */
|
|
+}
|
|
+
|
|
+static ssize_t show_battery_first_use_date(
|
|
+ struct device *dev, struct device_attribute *attr, char *buf)
|
|
+{
|
|
+ return show_tp_ec_bat_date(8, 2, attr, buf); /* type: YYYY-MM-DD */
|
|
+}
|
|
+
|
|
+/**
|
|
+ * show_battery_dump - show the battery_dump attribute
|
|
+ * The battery_dump attribute gives a hex dump of all EC readouts related to
|
|
+ * a battery. Some of the enumerated values don't exist (i.e., the EC function
|
|
+ * does not touch a register); we use a kludge to detect and denote these.
|
|
+ */
|
|
+#define MIN_DUMP_ARG0 0x00
|
|
+#define MAX_DUMP_ARG0 0x0a /* 0x0b is useful too but hangs old EC firmware */
|
|
+static ssize_t show_battery_dump(
|
|
+ struct device *dev, struct device_attribute *attr, char *buf)
|
|
+{
|
|
+ int i;
|
|
+ char* p = buf;
|
|
+ int bat = attr_get_bat(attr);
|
|
+ u8 arg0; /* first argument to EC */
|
|
+ u8 rowa[TP_CONTROLLER_ROW_LEN], rowb[TP_CONTROLLER_ROW_LEN];
|
|
+ const u8 junka=0xAA, junkb=0x55; /* junk values for testing changes */
|
|
+ int ret;
|
|
+
|
|
+ for (arg0=MIN_DUMP_ARG0; arg0<=MAX_DUMP_ARG0; ++arg0) {
|
|
+ if ( (p-buf) > PAGE_SIZE-TP_CONTROLLER_ROW_LEN*5 )
|
|
+ return -ENOMEM; /* don't overflow sysfs buf */
|
|
+ /* Read raw twice with different junk values,
|
|
+ * to detect unused output bytes which are left unchaged: */
|
|
+ ret = read_tp_ec_row(arg0, bat, junka, rowa);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+ ret = read_tp_ec_row(arg0, bat, junkb, rowb);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+ for (i=0; i<TP_CONTROLLER_ROW_LEN; i++) {
|
|
+ if (rowa[i]==junka && rowb[i]==junkb)
|
|
+ p += sprintf(p, "-- "); /* unused by EC */
|
|
+ else
|
|
+ p += sprintf(p, "%02x ", rowa[i]);
|
|
+ }
|
|
+ p += sprintf(p, "\n");
|
|
+ }
|
|
+ return p-buf;
|
|
+}
|
|
+
|
|
+
|
|
+/*********************************************************************
|
|
+ * sysfs attribute I/O, other than batteries
|
|
+ */
|
|
+
|
|
+static ssize_t show_ac_connected(
|
|
+ struct device *dev, struct device_attribute *attr, char *buf)
|
|
+{
|
|
+ int ret = power_device_present(0xFF);
|
|
+ if (ret<0)
|
|
+ return ret;
|
|
+ return sprintf(buf, "%d\n", ret); /* type: boolean */
|
|
+}
|
|
+
|
|
+/*********************************************************************
|
|
+ * The the "smapi_request" sysfs attribute executes a raw SMAPI call.
|
|
+ * You write to make a request and read to get the result. The state
|
|
+ * is saved globally rather than per fd (sysfs limitation), so
|
|
+ * simultaenous requests may get each other's results! So this is for
|
|
+ * development and debugging only.
|
|
+ */
|
|
+#define MAX_SMAPI_ATTR_ANSWER_LEN 128
|
|
+static char smapi_attr_answer[MAX_SMAPI_ATTR_ANSWER_LEN] = "";
|
|
+
|
|
+static ssize_t show_smapi_request(struct device *dev,
|
|
+ struct device_attribute *attr, char *buf)
|
|
+{
|
|
+ int ret = snprintf(buf, PAGE_SIZE, "%s", smapi_attr_answer);
|
|
+ smapi_attr_answer[0] = '\0';
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static ssize_t store_smapi_request(struct device *dev,
|
|
+ struct device_attribute *attr,
|
|
+ const char *buf, size_t count)
|
|
+{
|
|
+ unsigned int inEBX, inECX, inEDI, inESI;
|
|
+ u32 outEBX, outECX, outEDX, outEDI, outESI;
|
|
+ const char* msg;
|
|
+ int ret;
|
|
+ if (sscanf(buf, "%x %x %x %x", &inEBX, &inECX, &inEDI, &inESI) != 4) {
|
|
+ smapi_attr_answer[0] = '\0';
|
|
+ return -EINVAL;
|
|
+ }
|
|
+ ret = smapi_request(
|
|
+ inEBX, inECX, inEDI, inESI,
|
|
+ &outEBX, &outECX, &outEDX, &outEDI, &outESI, &msg);
|
|
+ snprintf(smapi_attr_answer, MAX_SMAPI_ATTR_ANSWER_LEN,
|
|
+ "%x %x %x %x %x %d '%s'\n",
|
|
+ (unsigned int)outEBX, (unsigned int)outECX, (unsigned int)outEDX,
|
|
+ (unsigned int)outEDI, (unsigned int)outESI, ret, msg);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+ else
|
|
+ return count;
|
|
+}
|
|
+
|
|
+/*********************************************************************
|
|
+ * Power management: the embedded controller forgets the battery
|
|
+ * thresholds when the system is suspended to disk and unplugged from
|
|
+ * AC and battery, so we restore it upon resume.
|
|
+ */
|
|
+
|
|
+static int saved_threshs[4] = {-1, -1, -1, -1}; /* -1 = don't know */
|
|
+
|
|
+static int tp_suspend(struct platform_device *dev, pm_message_t state)
|
|
+{
|
|
+ if (get_real_thresh(0, THRESH_STOP , &saved_threshs[0]))
|
|
+ saved_threshs[0]=-1;
|
|
+ if (get_real_thresh(0, THRESH_START, &saved_threshs[1]))
|
|
+ saved_threshs[1]=-1;
|
|
+ if (get_real_thresh(1, THRESH_STOP , &saved_threshs[2]))
|
|
+ saved_threshs[2]=-1;
|
|
+ if (get_real_thresh(1, THRESH_START, &saved_threshs[3]))
|
|
+ saved_threshs[3]=-1;
|
|
+ DPRINTK("suspend saved: %d %d %d %d", saved_threshs[0],
|
|
+ saved_threshs[1], saved_threshs[2], saved_threshs[3]);
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int tp_resume(struct platform_device *dev)
|
|
+{
|
|
+ DPRINTK("resume restoring: %d %d %d %d", saved_threshs[0],
|
|
+ saved_threshs[1], saved_threshs[2], saved_threshs[3]);
|
|
+ if (saved_threshs[0]>=0)
|
|
+ set_real_thresh(0, THRESH_STOP , saved_threshs[0]);
|
|
+ if (saved_threshs[1]>=0)
|
|
+ set_real_thresh(0, THRESH_START, saved_threshs[1]);
|
|
+ if (saved_threshs[2]>=0)
|
|
+ set_real_thresh(1, THRESH_STOP , saved_threshs[2]);
|
|
+ if (saved_threshs[3]>=0)
|
|
+ set_real_thresh(1, THRESH_START, saved_threshs[3]);
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+
|
|
+/*********************************************************************
|
|
+ * Driver model
|
|
+ */
|
|
+
|
|
+static struct platform_driver tp_driver = {
|
|
+ .suspend = tp_suspend,
|
|
+ .resume = tp_resume,
|
|
+ .driver = {
|
|
+ .name = "smapi",
|
|
+ .owner = THIS_MODULE
|
|
+ },
|
|
+};
|
|
+
|
|
+
|
|
+/*********************************************************************
|
|
+ * Sysfs device model
|
|
+ */
|
|
+
|
|
+/* Attributes in /sys/devices/platform/smapi/ */
|
|
+
|
|
+static DEVICE_ATTR(ac_connected, 0444, show_ac_connected, NULL);
|
|
+static DEVICE_ATTR(smapi_request, 0600, show_smapi_request,
|
|
+ store_smapi_request);
|
|
+
|
|
+static struct attribute *tp_root_attributes[] = {
|
|
+ &dev_attr_ac_connected.attr,
|
|
+ &dev_attr_smapi_request.attr,
|
|
+ NULL
|
|
+};
|
|
+static struct attribute_group tp_root_attribute_group = {
|
|
+ .attrs = tp_root_attributes
|
|
+};
|
|
+
|
|
+/* Attributes under /sys/devices/platform/smapi/BAT{0,1}/ :
|
|
+ * Every attribute needs to be defined (i.e., statically allocated) for
|
|
+ * each battery, and then referenced in the attribute list of each battery.
|
|
+ * We use preprocessor voodoo to avoid duplicating the list of attributes 4
|
|
+ * times. The preprocessor output is just normal sysfs attributes code.
|
|
+ */
|
|
+
|
|
+/**
|
|
+ * FOREACH_BAT_ATTR - invoke the given macros on all our battery attributes
|
|
+ * @_BAT: battery number (0 or 1)
|
|
+ * @_ATTR_RW: macro to invoke for each read/write attribute
|
|
+ * @_ATTR_R: macro to invoke for each read-only attribute
|
|
+ */
|
|
+#define FOREACH_BAT_ATTR(_BAT, _ATTR_RW, _ATTR_R) \
|
|
+ _ATTR_RW(_BAT, start_charge_thresh) \
|
|
+ _ATTR_RW(_BAT, stop_charge_thresh) \
|
|
+ _ATTR_RW(_BAT, inhibit_charge_minutes) \
|
|
+ _ATTR_RW(_BAT, force_discharge) \
|
|
+ _ATTR_R (_BAT, installed) \
|
|
+ _ATTR_R (_BAT, state) \
|
|
+ _ATTR_R (_BAT, manufacturer) \
|
|
+ _ATTR_R (_BAT, model) \
|
|
+ _ATTR_R (_BAT, barcoding) \
|
|
+ _ATTR_R (_BAT, chemistry) \
|
|
+ _ATTR_R (_BAT, voltage) \
|
|
+ _ATTR_R (_BAT, current_now) \
|
|
+ _ATTR_R (_BAT, current_avg) \
|
|
+ _ATTR_R (_BAT, power_now) \
|
|
+ _ATTR_R (_BAT, power_avg) \
|
|
+ _ATTR_R (_BAT, remaining_percent) \
|
|
+ _ATTR_R (_BAT, remaining_charging_time) \
|
|
+ _ATTR_R (_BAT, remaining_running_time) \
|
|
+ _ATTR_R (_BAT, remaining_capacity) \
|
|
+ _ATTR_R (_BAT, last_full_capacity) \
|
|
+ _ATTR_R (_BAT, design_voltage) \
|
|
+ _ATTR_R (_BAT, design_capacity) \
|
|
+ _ATTR_R (_BAT, cycle_count) \
|
|
+ _ATTR_R (_BAT, temperature) \
|
|
+ _ATTR_R (_BAT, serial) \
|
|
+ _ATTR_R (_BAT, manufacture_date) \
|
|
+ _ATTR_R (_BAT, first_use_date) \
|
|
+ _ATTR_R (_BAT, dump)
|
|
+
|
|
+/* Define several macros we will feed into FOREACH_BAT_ATTR: */
|
|
+
|
|
+#define DEFINE_BAT_ATTR_RW(_BAT,_NAME) \
|
|
+ static struct bat_device_attribute dev_attr_##_NAME##_##_BAT = { \
|
|
+ .dev_attr = __ATTR(_NAME, 0644, show_battery_##_NAME, \
|
|
+ store_battery_##_NAME), \
|
|
+ .bat = _BAT \
|
|
+ };
|
|
+
|
|
+#define DEFINE_BAT_ATTR_R(_BAT,_NAME) \
|
|
+ static struct bat_device_attribute dev_attr_##_NAME##_##_BAT = { \
|
|
+ .dev_attr = __ATTR(_NAME, 0644, show_battery_##_NAME, 0), \
|
|
+ .bat = _BAT \
|
|
+ };
|
|
+
|
|
+#define REF_BAT_ATTR(_BAT,_NAME) \
|
|
+ &dev_attr_##_NAME##_##_BAT.dev_attr.attr,
|
|
+
|
|
+/* This provide all attributes for one battery: */
|
|
+
|
|
+#define PROVIDE_BAT_ATTRS(_BAT) \
|
|
+ FOREACH_BAT_ATTR(_BAT, DEFINE_BAT_ATTR_RW, DEFINE_BAT_ATTR_R) \
|
|
+ static struct attribute *tp_bat##_BAT##_attributes[] = { \
|
|
+ FOREACH_BAT_ATTR(_BAT, REF_BAT_ATTR, REF_BAT_ATTR) \
|
|
+ NULL \
|
|
+ }; \
|
|
+ static struct attribute_group tp_bat##_BAT##_attribute_group = { \
|
|
+ .name = "BAT" #_BAT, \
|
|
+ .attrs = tp_bat##_BAT##_attributes \
|
|
+ };
|
|
+
|
|
+/* Finally genereate the attributes: */
|
|
+
|
|
+PROVIDE_BAT_ATTRS(0)
|
|
+PROVIDE_BAT_ATTRS(1)
|
|
+
|
|
+/* List of attribute groups */
|
|
+
|
|
+static struct attribute_group *attr_groups[] = {
|
|
+ &tp_root_attribute_group,
|
|
+ &tp_bat0_attribute_group,
|
|
+ &tp_bat1_attribute_group,
|
|
+ NULL
|
|
+};
|
|
+
|
|
+
|
|
+/*********************************************************************
|
|
+ * Init and cleanup
|
|
+ */
|
|
+
|
|
+static struct attribute_group **next_attr_group; /* next to register */
|
|
+
|
|
+static int __init tp_init(void)
|
|
+{
|
|
+ int ret;
|
|
+ printk(KERN_INFO "tp_smapi " TP_VERSION " loading...\n");
|
|
+
|
|
+ ret = find_smapi_port();
|
|
+ if (ret<0)
|
|
+ goto err;
|
|
+ else
|
|
+ smapi_port = ret;
|
|
+
|
|
+ if (!request_region(smapi_port, 1, "smapi")) {
|
|
+ printk(KERN_ERR "tp_smapi cannot claim port 0x%x\n", smapi_port);
|
|
+ ret = -ENXIO;
|
|
+ goto err;
|
|
+ }
|
|
+
|
|
+ if (!request_region(SMAPI_PORT2, 1, "smapi")) {
|
|
+ printk(KERN_ERR "tp_smapi cannot claim port 0x%x\n", SMAPI_PORT2);
|
|
+ ret = -ENXIO;
|
|
+ goto err_port1;
|
|
+ }
|
|
+
|
|
+ ret = platform_driver_register(&tp_driver);
|
|
+ if (ret)
|
|
+ goto err_port2;
|
|
+
|
|
+ pdev = platform_device_alloc("smapi", -1);
|
|
+ if (!pdev) {
|
|
+ ret = -ENOMEM;
|
|
+ goto err_driver;
|
|
+ }
|
|
+
|
|
+ ret = platform_device_add(pdev);
|
|
+ if (ret)
|
|
+ goto err_device_free;
|
|
+
|
|
+ for (next_attr_group = attr_groups; *next_attr_group; ++next_attr_group) {
|
|
+ ret = sysfs_create_group(&pdev->dev.kobj, *next_attr_group);
|
|
+ if (ret)
|
|
+ goto err_attr;
|
|
+ }
|
|
+
|
|
+ printk(KERN_INFO "tp_smapi successfully loaded (smapi_port=0x%x).\n", smapi_port);
|
|
+ return 0;
|
|
+
|
|
+err_attr:
|
|
+ while (--next_attr_group >= attr_groups)
|
|
+ sysfs_remove_group(&pdev->dev.kobj, *next_attr_group);
|
|
+ platform_device_unregister(pdev);
|
|
+err_device_free:
|
|
+ platform_device_put(pdev);
|
|
+err_driver:
|
|
+ platform_driver_unregister(&tp_driver);
|
|
+err_port2:
|
|
+ release_region(SMAPI_PORT2, 1);
|
|
+err_port1:
|
|
+ release_region(smapi_port, 1);
|
|
+err:
|
|
+ printk(KERN_ERR "tp_smapi init failed (ret=%d)!\n", ret);
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static void __exit tp_exit(void)
|
|
+{
|
|
+ while (next_attr_group && --next_attr_group >= attr_groups)
|
|
+ sysfs_remove_group(&pdev->dev.kobj, *next_attr_group);
|
|
+ platform_device_unregister(pdev);
|
|
+ platform_driver_unregister(&tp_driver);
|
|
+ release_region(SMAPI_PORT2, 1);
|
|
+ if (smapi_port)
|
|
+ release_region(smapi_port, 1);
|
|
+
|
|
+ printk(KERN_INFO "tp_smapi unloaded.\n");
|
|
+}
|
|
+
|
|
+module_init(tp_init);
|
|
+module_exit(tp_exit);
|
|
Index: linux-2.6.23-rc6/include/linux/thinkpad_ec.h
|
|
===================================================================
|
|
--- /dev/null
|
|
+++ linux-2.6.23-rc6/include/linux/thinkpad_ec.h
|
|
@@ -0,0 +1,47 @@
|
|
+/*
|
|
+ * thinkpad_ec.h - interface to ThinkPad embedded controller LPC3 functions
|
|
+ *
|
|
+ * Copyright (C) 2005 Shem Multinymous <multinymous@gmail.com>
|
|
+ *
|
|
+ * This program is free software; you can redistribute it and/or modify
|
|
+ * it under the terms of the GNU General Public License as published by
|
|
+ * the Free Software Foundation; either version 2 of the License, or
|
|
+ * (at your option) any later version.
|
|
+ *
|
|
+ * This program is distributed in the hope that it will be useful,
|
|
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
+ * GNU General Public License for more details.
|
|
+ *
|
|
+ * You should have received a copy of the GNU General Public License
|
|
+ * along with this program; if not, write to the Free Software
|
|
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
+ */
|
|
+
|
|
+#ifndef _THINKPAD_EC_H
|
|
+#define _THINKPAD_EC_H
|
|
+
|
|
+#ifdef __KERNEL__
|
|
+
|
|
+#define TP_CONTROLLER_ROW_LEN 16
|
|
+
|
|
+/* EC transactions input and output (possibly partial) vectors of 16 bytes. */
|
|
+struct thinkpad_ec_row {
|
|
+ u16 mask; /* bitmap of which entries of val[] are meaningful */
|
|
+ u8 val[TP_CONTROLLER_ROW_LEN];
|
|
+};
|
|
+
|
|
+extern int __must_check thinkpad_ec_lock(void);
|
|
+extern int __must_check thinkpad_ec_try_lock(void);
|
|
+extern void thinkpad_ec_unlock(void);
|
|
+
|
|
+extern int thinkpad_ec_read_row(const struct thinkpad_ec_row *args,
|
|
+ struct thinkpad_ec_row *data);
|
|
+extern int thinkpad_ec_try_read_row(const struct thinkpad_ec_row *args,
|
|
+ struct thinkpad_ec_row *mask);
|
|
+extern int thinkpad_ec_prefetch_row(const struct thinkpad_ec_row *args);
|
|
+extern void thinkpad_ec_invalidate(void);
|
|
+
|
|
+
|
|
+#endif /* __KERNEL */
|
|
+#endif /* _THINKPAD_EC_H */
|