Sure... sorry, here you go. I'll work with our team to straighten out the issues ASAP.

From 4b10b5aa9d9352c8a8d73d016435c8e58880250c Mon Sep 17 00:00:00 2001
From: Mark Dunatov <[email protected]>
Date: Tue, 7 Sep 2010 17:29:23 -0700
Subject: [PATCH] Add driver for AK8975 eCompass

includes self test support (via self_test sysfs entry), and sens_adjust
sysfs entry for access to Sensitivity Adjustment values.

Signed-off-by: Ken Lierman <[email protected]>
Change-Id: I46cc020a5f77275f0c4b5f0574a777bc9ed9c907
---
 drivers/misc/Kconfig  |    6 +
 drivers/misc/Makefile |    1 +
drivers/misc/ak8975.c | 421 +++++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 428 insertions(+), 0 deletions(-)
 create mode 100644 drivers/misc/ak8975.c

diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
index beb1168..677560d 100644
--- a/drivers/misc/Kconfig
+++ b/drivers/misc/Kconfig
@@ -334,6 +334,12 @@ config SENSORS_TSL2550
       This driver can also be built as a module.  If so, the module
       will be called tsl2550.

+config SENSORS_AK8975COMPASS
+    tristate "AKEMD 3 axis Compass"
+    depends on I2C
+    help
+      To get Compass Sensor output from AK8975 sensor.
+
 config HMC6352
     tristate "Honeywell HMC6352 compass"
     depends on I2C
diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
index 0d45a47..a40069b 100644
--- a/drivers/misc/Makefile
+++ b/drivers/misc/Makefile
@@ -29,6 +29,7 @@ obj-$(CONFIG_ISL29003)        += isl29003.o
 obj-$(CONFIG_ISL29020)        += isl29020.o
 obj-$(CONFIG_ISL29015)        += isl29015.o
 obj-$(CONFIG_SENSORS_TSL2550)    += tsl2550.o
+obj-$(CONFIG_SENSORS_AK8975COMPASS)   += ak8975.o
 obj-$(CONFIG_EP93XX_PWM)    += ep93xx_pwm.o
 obj-$(CONFIG_DS1682)        += ds1682.o
 obj-$(CONFIG_TI_DAC7512)    += ti_dac7512.o
diff --git a/drivers/misc/ak8975.c b/drivers/misc/ak8975.c
new file mode 100644
index 0000000..e677fe6
--- /dev/null
+++ b/drivers/misc/ak8975.c
@@ -0,0 +1,421 @@
+/*
+ * ak8975.c - AKM 8975 Compass Driver
+ *
+ * Copyright (C) 2010 Intel Corp
+ * Contains changes by Wind River Systems, 2010
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * 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; version 2 of the License.
+ *
+ * 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/module.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/i2c.h>
+#include <linux/hwmon.h>
+#include <linux/err.h>
+#include <linux/mutex.h>
+#include <linux/sysfs.h>
+
+MODULE_AUTHOR("KalhanTrisal,Anantha Narayanan<[email protected]>");
+MODULE_DESCRIPTION("ak8975 Compass Driver");
+MODULE_LICENSE("GPL v2");
+
+#define AK8975_SELF_TEST 1    /* Set to zero to disable sensor self test */
+
+#define RETRY_COUNT    5    /* Number of attempts for reading sensor */
+
+/* Register Addresses */
+#define REG_WIA        0x00    /* Device ID : RO */
+#define REG_INFO    0x01    /* AKM info : RO */
+#define REG_ST1        0x02    /* Status 1 : RO */
+#define REG_HXL        0x03    /* X data low : RO */
+#define REG_HXH        0x04    /* X data high : RO */
+#define REG_HYL        0x05    /* Y data low : RO */
+#define REG_HYH        0x06    /* Y data high : RO */
+#define REG_HZL        0x07    /* Z data low : RO */
+#define REG_HZH        0x08    /* Z data high : RO */
+#define REG_ST2        0x09    /* Status 2 : RO */
+#define REG_CNTL    0x0A    /* Control : RW */
+#define REG_RSV        0x0B    /* Reserved : NA */
+#define REG_ASTC    0x0C    /* Self Test Control : RW */
+#define REG_TS1        0x0D    /* Shipment Test 1 : NA */
+#define REG_TS2        0x0E    /* Shipment Test 2 : NA */
+#define REG_I2CDIS    0x0F    /* I2C Disable : RW */
+#define REG_ASAX    0x10    /* X Sensitivity Adj : RO */
+#define REG_ASAY    0x11    /* Y Sensitivity Adj : RO */
+#define REG_ASAZ    0x12    /* Z Sensitivity Adj : RO */
+
+/* REG_WIA definitions */
+#define DEV_ID        0x48    /* Fixed value for AK8975 */
+
+/* REG_ST1 definitions */
+#define DRDY        0x01    /* Data Ready bit */
+
+/* REG_ST2 definitions */
+#define DERR        0x04    /* Data Error bit */
+#define HOFL        0x08    /* Magnetic Sensor Overflow bit */
+
+/* REG_CNTL definitions */
+#define POWER_DOWN    0x00    /* Power Down mode */
+#define SINGLE_MEAS    0x01    /* Single Measurement mode */
+#define SELF_TEST    0x08    /* Self Test mode */
+#define FUSE_ROM    0x0F    /* Fuse ROM mode */
+
+/* REG_ASTC definitions */
+#define SELF        0x40    /* Generate Magnetic Field bit */
+
+/* REG_I2CDIS definitions */
+#define I2CDIS        0x01    /* I2C Disable bit */
+
+
+/* Unique Sensitivity Adjustment values */
+static unsigned char asax, asay, asaz = 128;
+
+/* For tracking power state as set by User Space code */
+static unsigned char power_state = false;
+
+struct compass_data {
+    struct device *hwmon_dev;
+    bool needresume;
+};
+
+
+static bool set_mode(struct i2c_client *client, unsigned char new_mode)
+{
+    unsigned char curr_mode;
+
+    /* Read the current mode and return if it's already in the new mode */
+    curr_mode = i2c_smbus_read_byte_data(client, REG_CNTL);
+    if (curr_mode == new_mode)
+        return true;
+
+    /* Changing operating modes requires entry to Power Down mode first */
+    i2c_smbus_write_byte_data(client, REG_CNTL, POWER_DOWN);
+
+    /* Delay a bit, then ensure the device is in Power Down mode */
+    msleep(0);
+    curr_mode = i2c_smbus_read_byte_data(client, REG_CNTL);
+    if (curr_mode != POWER_DOWN)
+        return false; /* Failure ! */
+
+    /* We're done if Power Down mode was requested */
+    if (new_mode == POWER_DOWN)
+        return true;
+
+    /* Set the new mode */
+    i2c_smbus_write_byte_data(client, REG_CNTL, new_mode);
+    return true;
+}
+
+static bool wait_for_data_ready(struct i2c_client *client)
+{
+    unsigned char data_ready = 0;
+    int wait_count = RETRY_COUNT;
+
+    /* Wait for Data Ready bit in ST1 to become 1 */
+    while (--wait_count) {
+        data_ready = i2c_smbus_read_byte_data(client, REG_ST1);
+        if (data_ready)
+            return true; /* Success */
+        msleep(0);
+    }
+    return false; /* Failure */
+}
+
+static bool get_current_field(struct i2c_client *client, unsigned char mode,
+                unsigned short *xc, unsigned short *yc, unsigned short *zc)
+{
+    int status, retries = 0;
+    unsigned short x, y, z = 0;
+    unsigned char temp = 0;
+
+    /* Work function for retrieving magnetic field values from sensor */
+    while (retries++ < RETRY_COUNT) {
+        /* Set Single Measurement mode */
+        if (set_mode(client, mode)) {
+            /* Wait for Data Ready */
+            if (wait_for_data_ready(client)) {
+                /* Read the current field values */
+                x = i2c_smbus_read_byte_data(client, REG_HXH);
+                temp = i2c_smbus_read_byte_data(client, REG_HXL);
+                x = x << 8 | temp;
+                y = i2c_smbus_read_byte_data(client, REG_HYH);
+                temp = i2c_smbus_read_byte_data(client, REG_HYL);
+                y = y << 8 | temp;
+                z = i2c_smbus_read_byte_data(client, REG_HZH);
+                temp = i2c_smbus_read_byte_data(client, REG_HZL);
+                z = z << 8 | temp;
+
+                /* Check for an error (status clears when read) */
+                status = i2c_smbus_read_byte_data(client, REG_ST2);
+                if (status & DERR)
+                    printk(KERN_WARNING "ak8975: Data Read Error !");
+                else if (status & HOFL)
+                    printk(KERN_WARNING "ak8975: Sensor Field Overflow !");
+                else {
+                    /* set the current field values and we're done */
+                    *xc = x;
+                    *yc = y;
+                    *zc = z;
+                    return true;
+                }
+            } else {
+                printk(KERN_WARNING "ak8975: sensor data NOT ready !");
+            }
+        } else {
+ printk(KERN_WARNING "ak8975: sensor mode change unsuccessful !");
+        }
+    }
+ printk(KERN_WARNING "ak8975: max retries exceeded while reading field values !");
+    return false;
+}
+
+static void get_sensitivity_data(struct i2c_client *client)
+{
+ /* Enter Fuse ROM Access Mode to retrieve Sensitivity Adjustment data */
+    if (set_mode(client, FUSE_ROM)) {
+        /* Fetch the factory-stored data and save in static vars */
+        asax = i2c_smbus_read_byte_data(client, REG_ASAX);
+        asay = i2c_smbus_read_byte_data(client, REG_ASAY);
+        asaz = i2c_smbus_read_byte_data(client, REG_ASAZ);
+
+        /* Exit Fuse ROM mode and return success */
+        (void)set_mode(client, POWER_DOWN);
+        if (!set_mode(client, POWER_DOWN))
+ printk(KERN_WARNING "ak8975: Problem exiting Fuse ROM mode !\n");
+        else
+ printk(KERN_WARNING "ak8975: Successfully retrieved Sensitivity data\n");
+        return;
+    }
+ printk(KERN_WARNING "ak8975: Fuse ROM mode entry for Sensitivity data failed !\n");
+}
+
+static bool sensor_self_test(struct i2c_client *client)
+{
+    signed short x, y, z = 0;
+    bool ret = false;
+
+ /* Enter internal Power Down mode prior to enabling internal field generator */
+    printk(KERN_WARNING "ak8975: Starting Sensor Self Test...\n");
+    if (!set_mode(client, POWER_DOWN)) {
+ printk(KERN_WARNING "ak8975: Sensor failed initial Power Down !\n");
+        return false;
+    }
+    /* Enable the Self Test's internal Field Generator */
+    i2c_smbus_write_byte_data(client, REG_ASTC, SELF);
+
+ /* Enter Self Test Mode and retrieve the measured/adjusted field values */
+    if (get_current_field(client, SELF_TEST, &x, &y, &z)) {
+ printk(KERN_WARNING "ak8975: criteria: -100<=x<=100,-100<=y<=100,-1000<=z<=-300\n");
+        printk(KERN_WARNING "ak8975: x, y, z = %d, %d, %d\n", x, y, z);
+        if ((x >=  -100 && x <=  100)
+ &&  (y >=  -100 && y <=  100)
+ &&  (z >= -1000 && z <= -300)) {
+ printk(KERN_WARNING "ak8975: Successfully passed sensor self test\n");
+            ret = true;
+        } else {
+ printk(KERN_WARNING "ak8975: Sensor failed Self Test criteria !\n");
+        }
+    } else {
+ printk(KERN_WARNING "ak8975: Unable to retrieve Sensor Self Test data !\n");
+    }
+ /* Disable internal field generation (sensor powers-down automatically) */
+    i2c_smbus_write_byte_data(client, REG_ASTC, 0);
+    return ret;
+}
+
+/*
+ * Unlike the AK8974, the AK8975 automatically transitions to Power Off
+ * mode after each operation, to minimize power consumption.  The operating
+ * mode is set by the driver each time a measurement is made.
+ *
+ * The interfaces here are maintained for User Space code that attempts to
+ * perform manual control through the expected "power_state" sysfs entry.
+ * (Tracking this should eliminate some thrash from reporting the actual
+ * power state -- which will consistently be "off".)
+ */
+
+static ssize_t power_mode_show(struct device *dev,
+            struct device_attribute *attr, char *buf)
+{
+    return sprintf(buf, "%d\n", power_state);
+}
+
+static ssize_t power_mode_store(struct device *dev,
+        struct device_attribute *attr, const  char *buf, size_t count)
+{
+    unsigned long set_val;
+
+    if (strict_strtoul(buf, 10, &set_val))
+        return -EINVAL;
+
+    if (set_val == 1) {
+        power_state = true;
+    } else if (set_val == 0) {
+        power_state = false;
+    } else
+        return -EINVAL;
+
+    return count;
+}
+
+static ssize_t curr_xyz_show(struct device *dev,
+                struct device_attribute *attr, char *buf)
+{
+    signed short x, y, z;
+    struct i2c_client *client = to_i2c_client(dev);
+    client->addr = 0x0F;/* Remove HC address after FW Fix*/
+
+    /* Retrieve the current/measured/adjusted field values */
+    if (get_current_field(client, SINGLE_MEAS, &x, &y, &z))
+        return sprintf(buf, "%d:%d:%d\n", x, y, z);
+
+    /* Unable to obtain measurement - return null field values */
+    return sprintf(buf, "%d:%d:%d", 0, 0, 0);
+}
+
+static ssize_t xyz_sensitivity_show(struct device *dev,
+                    struct device_attribute *attr, char *buf)
+{
+    /* Return the x/y/z sensitivity adjustment values read from Fuse ROM */
+    return sprintf(buf, "%d:%d:%d", asax, asay, asaz);
+}
+
+static ssize_t self_test_show(struct device *dev,
+                  struct device_attribute *attr, char *buf)
+{
+    struct i2c_client *client = to_i2c_client(dev);
+
+    /* Execute the self test and return the appropriate result */
+    return sprintf(buf, "%s", sensor_self_test(client) ? "pass" : "fail");
+}
+
+static DEVICE_ATTR(power_state, S_IRUGO | S_IWUSR, power_mode_show, power_mode_store);
+static DEVICE_ATTR(curr_pos, S_IRUGO, curr_xyz_show, NULL);
+static DEVICE_ATTR(sens_adjust, S_IRUGO, xyz_sensitivity_show, NULL);
+static DEVICE_ATTR(self_test, S_IRUGO, self_test_show, NULL);
+
+static struct attribute *mid_att_compass[] = {
+ &dev_attr_power_state.attr,
+ &dev_attr_curr_pos.attr,
+ &dev_attr_sens_adjust.attr,
+ &dev_attr_self_test.attr,
+    NULL
+};
+
+static struct attribute_group m_compass_gr = {
+    .name = "ak8975",
+    .attrs = mid_att_compass
+};
+
+static int ak8975_probe(struct i2c_client *client, const struct i2c_device_id *id)
+{
+    int res;
+    unsigned char device_id;
+    struct compass_data *data;
+    client->addr = 0x0F;/* Remove HC address after FW Fix*/
+
+    data = kzalloc(sizeof(struct compass_data), GFP_KERNEL);
+    if (data == NULL) {
+        printk(KERN_WARNING "ak8975: Memory initialization failed");
+        return -ENOMEM;
+    }
+    i2c_set_clientdata(client, data);
+
+    res = sysfs_create_group(&client->dev.kobj, &m_compass_gr);
+    if (res) {
+        printk(KERN_WARNING "ak8975: device_create_file failed!!\n");
+        goto compass_error1;
+    }
+    data->hwmon_dev = hwmon_device_register(&client->dev);
+    if (IS_ERR(data->hwmon_dev)) {
+        res = PTR_ERR(data->hwmon_dev);
+        data->hwmon_dev = NULL;
+        printk(KERN_WARNING "ak8975: fail to register hwmon device\n");
+        sysfs_remove_group(&client->dev.kobj, &m_compass_gr);
+        goto compass_error1;
+    }
+ /* Verify connectivity/presence of the AK8975 by reading the Device ID */
+    device_id = i2c_smbus_read_byte_data(client, REG_WIA);
+    if (device_id == DEV_ID) {
+        dev_info(&client->dev, "%s compass chip found\n", client->name);
+    } else {
+ dev_info(&client->dev, "%s compass chip NOT found !\n", client->name);
+        goto compass_error1;
+    }
+    /* Obtain the Sensitivity Adjustment values from Fuse ROM */
+    get_sensitivity_data(client);
+
+#if AK8975_SELF_TEST
+    /* Sensor self test verifes device operation */
+    (void)sensor_self_test(client);
+#endif
+    data->needresume = true;
+    return 0;
+
+compass_error1:
+    i2c_set_clientdata(client, NULL);
+    kfree(data);
+    return res;
+}
+
+static int ak8975_remove(struct i2c_client *client)
+{
+    struct compass_data *data = i2c_get_clientdata(client);
+
+    hwmon_device_unregister(data->hwmon_dev);
+    sysfs_remove_group(&client->dev.kobj, &m_compass_gr);
+    kfree(data);
+    return 0;
+}
+
+static int ak8975_suspend(struct i2c_client *client, pm_message_t mesg)
+{
+    return 0;
+}
+
+static int ak8975_resume(struct i2c_client *client)
+{
+    return 0;
+}
+static struct i2c_device_id ak8975_id[] = {
+    { "ak8975", 0 },
+    { }
+};
+static struct i2c_driver ak8975_driver = {
+    .driver = {
+    .name = "ak8975",
+    },
+    .probe = ak8975_probe,
+    .remove = ak8975_remove,
+    .suspend = ak8975_suspend,
+    .resume = ak8975_resume,
+    .id_table = ak8975_id,
+};
+
+static int __init sensor_ak8975_init(void)
+{
+    return i2c_add_driver(&ak8975_driver);
+}
+
+static void  __exit sensor_ak8975_exit(void)
+{
+    i2c_del_driver(&ak8975_driver);
+}
+module_init(sensor_ak8975_init);
+module_exit(sensor_ak8975_exit);
--
1.7.1


_______________________________________________
Meego-kernel mailing list
[email protected]
http://lists.meego.com/listinfo/meego-kernel

Reply via email to