[PATCH 3/3] hwmon: Add interrupt support to applesmc

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



The applesmc hardware supports providing interrupts for significant
accelerometer events. Add framework for this - we'll want to notify
userspace on these in the long run to handle head unloading, but we need
to figure out the best interface for that first. Code based on a patch by
Nicolas Boichat and the FreeBSD kernel code.

Signed-off-by: Matthew Garrett <mjg@xxxxxxxxxx>
Cc: Nicolas Boichat <nicolas@xxxxxxxxxx>
---
 drivers/hwmon/applesmc.c |  138 +++++++++++++++++++++++++++++++++++++++++-----
 1 files changed, 123 insertions(+), 15 deletions(-)

diff --git a/drivers/hwmon/applesmc.c b/drivers/hwmon/applesmc.c
index 4540bb1..ac2f9a2 100644
--- a/drivers/hwmon/applesmc.c
+++ b/drivers/hwmon/applesmc.c
@@ -45,11 +45,13 @@
 #include <linux/workqueue.h>
 #include <linux/slab.h>
 #include <linux/pnp.h>
+#include <linux/interrupt.h>
 
 /* data port used by Apple SMC */
 #define APPLESMC_DATA_PORT	0
 /* command/status port used by Apple SMC */
 #define APPLESMC_CMD_PORT	0x4
+#define APPLESMC_INT_PORT	0x1f
 
 #define APPLESMC_NR_PORTS	32
 
@@ -78,6 +80,14 @@
 #define MOTION_SENSOR_Z_KEY	"MO_Z" /* r-o sp78 (2 bytes) */
 #define MOTION_SENSOR_KEY	"MOCN" /* r/w ui16 */
 
+#define NOTIFY_KEY		"NTOK" /* r/w ui8 */
+
+#define NOTIFY_LOW_RATE		"MOLD" /* r/w ui8 */
+#define NOTIFY_HIGH_RATE	"MOHD" /* r/w ui8 */
+#define NOTIFY_LOW_THRESH	"MOLT" /* r/w ui16 */
+#define NOTIFY_HIGH_THRESH	"MOHT" /* r/w ui16 */
+#define NOTIFY_FLAG		"MSDW" /* r/w ui8 */
+
 #define FANS_COUNT		"FNum" /* r-o ui8 */
 #define FANS_MANUAL		"FS! " /* r-w ui16 */
 #define FAN_ID_FMT		"F%dID" /* r-o char[16] */
@@ -147,6 +157,7 @@ struct applesmc_registers {
 struct applesmc_dev {
 	int iobase;
 	int iolen;
+	int irq;
 	struct work_struct backlight_work;
 	struct applesmc_registers smcreg;
 	/*
@@ -447,6 +458,37 @@ static int applesmc_write_key(struct applesmc_dev *pdev, const char *key,
 	return applesmc_write_entry(pdev, entry, buffer, len);
 }
 
+static int applesmc_write_key_check(struct applesmc_dev *pdev, const char *key,
+				    u8 *buffer, u8 len)
+{
+	int total, ret, i, compare;
+
+	u8 rdbuffer[APPLESMC_MAX_DATA_LENGTH];
+
+	for (total = INIT_TIMEOUT_MSECS; total > 0; total -= INIT_WAIT_MSECS) {
+		ret = applesmc_read_key(pdev, key, rdbuffer, len);
+		if (!ret) {
+			compare = 1;
+			for (i = 0; i < len; i++) {
+				if (rdbuffer[i] != buffer[i]) {
+					compare = 0;
+					break;
+				}
+			}
+
+			if (compare)
+				return 0;
+		}
+		ret = applesmc_write_key(pdev, key, buffer, len);
+		msleep(INIT_WAIT_MSECS);
+	}
+
+	if (ret)
+		return ret;
+	else
+		return -EIO;
+}
+
 static int applesmc_has_key(struct applesmc_dev *pdev, const char *key,
 			    bool *value)
 {
@@ -489,27 +531,49 @@ static int applesmc_read_motion_sensor(struct applesmc_dev *pdev, int index,
 }
 
 /*
- * applesmc_device_init - initialize the accelerometer.  Can sleep.
+ * applesmc_accel_init - initialize the accelerometer.  Can sleep.
  */
-static void applesmc_device_init(struct applesmc_dev *pdev)
+static void applesmc_accel_init(struct applesmc_dev *pdev)
 {
-	int total;
+	int ret;
 	u8 buffer[2];
 
 	if (!pdev->smcreg.has_accelerometer)
 		return;
 
-	for (total = INIT_TIMEOUT_MSECS; total > 0; total -= INIT_WAIT_MSECS) {
-		if (!applesmc_read_key(pdev, MOTION_SENSOR_KEY, buffer, 2) &&
-				(buffer[0] != 0x00 || buffer[1] != 0x00))
-			return;
-		buffer[0] = 0xe0;
-		buffer[1] = 0x00;
-		applesmc_write_key(pdev, MOTION_SENSOR_KEY, buffer, 2);
-		msleep(INIT_WAIT_MSECS);
-	}
+	buffer[0] = 20;
+	ret = applesmc_write_key_check(pdev, NOTIFY_LOW_RATE, buffer, 1);
+	if (ret)
+		goto out;
+
+	ret = applesmc_write_key_check(pdev, NOTIFY_HIGH_RATE, buffer, 1);
+	if (ret)
+		goto out;
 
-	pr_warn("failed to init the device\n");
+	buffer[0] = 0;
+	buffer[1] = 0x60;
+	ret = applesmc_write_key_check(pdev, NOTIFY_LOW_THRESH, buffer, 2);
+	if (ret)
+		goto out;
+
+	buffer[0] = 1;
+	buffer[1] = 0xc0;
+	ret = applesmc_write_key_check(pdev, NOTIFY_HIGH_THRESH, buffer, 2);
+	if (ret)
+		goto out;
+
+	buffer[0] = 0xe0;
+	buffer[1] = 0xf8;
+	ret = applesmc_write_key_check(pdev, MOTION_SENSOR_KEY, buffer, 2);
+
+	buffer[0] = 1;
+	ret = applesmc_write_key(pdev, NOTIFY_FLAG, buffer, 1);
+	if (ret)
+		goto out;
+
+out:
+	if (ret)
+		pr_warn("failed to init the accelerometer\n");
 }
 
 /*
@@ -629,7 +693,7 @@ static int applesmc_pm_restore(struct device *dev)
 	struct pnp_dev *pnpdev = to_pnp_dev(dev);
 	struct applesmc_dev *pdev = pnp_get_drvdata(pnpdev);
 
-	applesmc_device_init(pdev);
+	applesmc_accel_init(pdev);
 	return applesmc_pm_resume(dev);
 }
 
@@ -1265,10 +1329,36 @@ static void applesmc_release_key_backlight(struct applesmc_dev *pdev)
 	destroy_workqueue(pdev->applesmc_led_wq);
 }
 
+static irqreturn_t applesmc_interrupt(int irq, void *d)
+{
+	struct applesmc_dev *pdev = d;
+	u8 intr = applesmc_read_reg(pdev, APPLESMC_INT_PORT);
+
+	switch (intr) {
+	case 0x60:
+		printk(KERN_INFO "applesmc: Free fall interrupt\n");
+		break;
+	case 0x6f:
+		printk(KERN_INFO "applesmc: High acceleration interrupt\n");
+		break;
+	case 0x80:
+		printk(KERN_INFO "applesmc: Shock interrupt\n");
+		break;
+	default:
+		if (intr)
+			printk(KERN_INFO "applesmc: Unknown interrupt %x\n",
+			       intr);
+		break;
+	}
+
+	return IRQ_HANDLED;
+}
+
 static int __devinit applesmc_pnp_probe(struct pnp_dev *dev,
 					const struct pnp_device_id *dev_id)
 {
 	int ret;
+	u8 buffer = 1;
 	struct resource *res;
 	struct applesmc_dev *pdev = kzalloc(sizeof(struct applesmc_dev),
 					    GFP_KERNEL);
@@ -1295,6 +1385,15 @@ static int __devinit applesmc_pnp_probe(struct pnp_dev *dev,
 		goto out;
 	}
 
+	res = pnp_get_resource(dev, IORESOURCE_IRQ, 0);
+
+	if (res) {
+		ret = request_irq(res->start, applesmc_interrupt,
+				  IRQF_DISABLED, "applesmc", pdev);
+		if (!ret)
+			pdev->irq = res->start;
+	}
+
 	/* create register cache */
 
 	ret = applesmc_init_smcreg(pdev);
@@ -1333,7 +1432,9 @@ static int __devinit applesmc_pnp_probe(struct pnp_dev *dev,
 
 	INIT_WORK(&pdev->backlight_work, &applesmc_backlight_set);
 
-	applesmc_device_init(pdev);
+	applesmc_accel_init(pdev);
+
+	applesmc_write_key(pdev, NOTIFY_KEY, &buffer, 1);
 
 	return 0;
 
@@ -1352,6 +1453,8 @@ out_info:
 out_smcreg:
 	applesmc_destroy_smcreg(pdev);
 out_region:
+	if (pdev->irq)
+		free_irq(pdev->irq, pdev);
 	release_region(pdev->iobase, pdev->iolen);
 out:
 	kfree(pdev);
@@ -1362,7 +1465,12 @@ out:
 static void __devexit applesmc_pnp_remove(struct pnp_dev *dev)
 {
 	struct applesmc_dev *pdev = pnp_get_drvdata(dev);
+	u8 buffer = 0;
 
+	if (pdev->irq) {
+		applesmc_write_key(pdev, NOTIFY_KEY, &buffer, 1);
+		free_irq(pdev->irq, pdev);
+	}
 	hwmon_device_unregister(pdev->hwmon_dev);
 	applesmc_release_key_backlight(pdev);
 	applesmc_release_light_sensor(pdev);
-- 
1.7.6


_______________________________________________
lm-sensors mailing list
lm-sensors@xxxxxxxxxxxxxx
http://lists.lm-sensors.org/mailman/listinfo/lm-sensors


[Index of Archives]     [Linux Kernel]     [Linux Hardware Monitoring]     [Linux USB Devel]     [Linux Audio Users]     [Linux Kernel]     [Linux SCSI]     [Yosemite Backpacking]

  Powered by Linux