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

Reply via email to