Add initial ak8975 eCompass driver.
We are aware of the conflicting upstream driver, and need a get well
plan to align/merge with it, but this version is currently required.
Patch from: <[email protected]>
Signed-off-by: Ken Lierman <[email protected]>
>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.
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