[PATCH 1/3] drivers: misc: ak8974 / ami305 magnetometer driver

Samu Onkalo
2010-08-27T11:55:52Z

Patch provides support for ak8974 / ami305 3-axis magnetometer chip.
Results are provided via misc character device.
Driver supports multiple instaces of the chip.
Driver has PM and regulator framework support.

Signed-off-by: Samu Onkalo <samu.p.onkalo@nokia.com>
---
drivers/misc/ak8974.c | 703 ++++++++++++++++++++++++++++++++++++++++++++
include/linux/i2c/ak8974.h | 50 ++++
2 files changed, 753 insertions(+), 0 deletions(-)
create mode 100644 drivers/misc/ak8974.c
create mode 100644 include/linux/i2c/ak8974.h

diff --git a/drivers/misc/ak8974.c b/drivers/misc/ak8974.c
new file mode 100644
index 0000000..e480314
--- /dev/null
+++ b/drivers/misc/ak8974.c
@@ -0,0 +1,703 @@
+/*
+ * Driver for ak8974 (Asahi Kasei EMD Corporation)
+ * and ami305 (Aichi Steel) magnetometer chip.
+ *
+ * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
+ *
+ * Contact: Samu Onkalo <samu.p.onkalo@nokia.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * 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., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/i2c.h>
+#include <linux/interrupt.h>
+#include <linux/mutex.h>
+#include <asm/atomic.h>
+#include <linux/delay.h>
+#include <linux/i2c/ak8974.h>
+#include <linux/regulator/consumer.h>
+#include <linux/poll.h>
+#include <linux/device.h>
+#include <linux/miscdevice.h>
+#include <linux/slab.h>
+
+/*
+ * 16-bit registers are little-endian. LSB is at the address defined below
+ * and MSB is at the next higher address.
+ */
+
+/* for AK8974 and AMI305 registers */
+#define AK8974_SELFTEST 0x0C
+#define AK8974_INFO 0x0D
+#define AK8974_WHOAMI 0x0F
+#define AK8974_DATA_X 0x10
+#define AK8974_DATA_Y 0x12
+#define AK8974_DATA_Z 0x14
+#define AK8974_INT_SRC 0x16
+#define AK8974_STATUS 0x18
+#define AK8974_INT_CLEAR 0x1A
+#define AK8974_CTRL1 0x1B
+#define AK8974_CTRL2 0x1C
+#define AK8974_CTRL3 0x1D
+#define AK8974_INT_CTRL 0x1E
+#define AK8974_OFFSET_X 0x20
+#define AK8974_OFFSET_Y 0x22
+#define AK8974_OFFSET_Z 0x24
+#define AK8974_INT_THRES 0x26 /* absolute any axis value threshold */
+#define AK8974_PRESET 0x30
+#define AK8974_TEMP 0x31
+
+#define AK8974_SELFTEST_IDLE 0x55
+#define AK8974_SELFTEST_OK 0xAA
+
+#define AK8974_WHOAMI_VALUE_AMI305 0x47
+#define AK8974_WHOAMI_VALUE_AK8974 0x48
+#define AK8974_INT_X_HIGH 0x80 /* Axis over +threshold */
+#define AK8974_INT_Y_HIGH 0x40
+#define AK8974_INT_Z_HIGH 0x20
+#define AK8974_INT_X_LOW 0x10 /* Axis below -threshold */
+#define AK8974_INT_Y_LOW 0x08
+#define AK8974_INT_Z_LOW 0x04
+#define AK8974_INT_RANGE 0x02 /* Range overflow (any axis) */
+
+#define AK8974_STATUS_DRDY 0x40 /* Data ready */
+#define AK8974_STATUS_OVERRUN 0x20 /* Data overrun */
+#define AK8974_STATUS_INT 0x10 /* Interrupt occurred */
+
+#define AK8974_CTRL1_POWER 0x80 /* 0 = standby; 1 = active */
+#define AK8974_CTRL1_RATE 0x10 /* 0 = 10 Hz; 1 = 20 Hz */
+#define AK8974_CTRL1_FORCE_EN 0x02 /* 0 = normal; 1 = force */
+#define AK8974_CTRL1_MODE2 0x01 /* 0 */
+
+#define AK8974_CTRL2_INT_EN 0x10 /* 1 = enable interrupts */
+#define AK8974_CTRL2_DRDY_EN 0x08 /* 1 = enable data ready signal */
+#define AK8974_CTRL2_DRDY_POL 0x04 /* 1 = data ready active high */
+#define AK8974_CTRL2_RESDEF (AK8974_CTRL2_DRDY_POL)
+
+#define AK8974_CTRL3_RESET 0x80 /* Software reset */
+#define AK8974_CTRL3_FORCE 0x40 /* Start forced measurement */
+#define AK8974_CTRL3_SELFTEST 0x10 /* Set selftest register */
+#define AK8974_CTRL3_RESDEF 0x00
+
+#define AK8974_INT_CTRL_XEN 0x80 /* Enable interrupt for this axis */
+#define AK8974_INT_CTRL_YEN 0x40
+#define AK8974_INT_CTRL_ZEN 0x20
+#define AK8974_INT_CTRL_XYZEN 0xE0
+#define AK8974_INT_CTRL_POL 0x08 /* 0 = active low; 1 = active high */
+#define AK8974_INT_CTRL_PULSE 0x02 /* 0 = latched; 1 = pulse (50 usec) */
+#define AK8974_INT_CTRL_RESDEF (AK8974_INT_CTRL_XYZEN | AK8974_INT_CTRL_POL)
+
+#define AK8974_INT_SCR_MROI 0x02
+
+#define AK8974_MAX_RANGE 2048
+
+#define AK8974_POWERON_DELAY 50
+#define AK8974_ACTIVATE_DELAY 1
+#define AK8974_SELFTEST_DELAY 1
+
+#define AK8974_MEASTIME 3
+
+#define AK8974_PWR_ON 1
+#define AK8974_PWR_OFF 0
+
+struct ak8974_chip {
+ struct miscdevice miscdev;
+ struct mutex lock; /* Serialize access to chip */
+ struct mutex users_lock;
+ struct i2c_client *client;
+ struct regulator_bulk_data regs[2];
+ struct work_struct work;
+ wait_queue_head_t misc_wait;
+ loff_t offset;
+
+ int max_range;
+ int users;
+
+ const char *id;
+ u8 info[2];
+
+ s16 x, y, z; /* Latest measurements */
+ s8 axis_x;
+ s8 axis_y;
+ s8 axis_z;
+ bool valid;
+
+ char name[20];
+};
+
+static const char reg_avdd[] = "AVdd";
+static const char reg_dvdd[] = "DVdd";
+static atomic_t device_number = ATOMIC_INIT(0);
+
+static inline int ak8974_write(struct ak8974_chip *chip, u8 reg, u8 data)
+{
+ return i2c_smbus_write_byte_data(chip->client, reg, data);
+}
+
+static inline int ak8974_read(struct ak8974_chip *chip, u8 reg)
+{
+ return i2c_smbus_read_byte_data(chip->client, reg);
+}
+
+static inline int ak8974_read_block(struct ak8974_chip *chip, u8 reg,
+ u8 *data, u8 length)
+{
+ return i2c_smbus_read_i2c_block_data(chip->client, reg, length, data);
+}
+
+static int ak8974_enable(struct ak8974_chip *chip, bool mode)
+{
+ int r, v;
+
+ v = mode ? AK8974_CTRL1_POWER : 0;
+ v |= AK8974_CTRL1_FORCE_EN;
+ r = ak8974_write(chip, AK8974_CTRL1, v);
+ if (r < 0)
+ return r;
+
+ if (mode)
+ msleep(AK8974_ACTIVATE_DELAY);
+
+ return 0;
+}
+
+static int ak8974_reset(struct ak8974_chip *chip)
+{
+ int r;
+
+ /* Power on to get register access. Sets CTRL1 reg to reset state */
+ r = ak8974_enable(chip, AK8974_PWR_ON);
+ r |= ak8974_write(chip, AK8974_CTRL2, AK8974_CTRL2_RESDEF);
+ r |= ak8974_write(chip, AK8974_CTRL3, AK8974_CTRL3_RESDEF);
+ r |= ak8974_write(chip, AK8974_INT_CTRL, AK8974_INT_CTRL_RESDEF);
+
+ /* After reset, power off is default state */
+ r |= ak8974_enable(chip, AK8974_PWR_OFF);
+
+ return r;
+}
+
+static int ak8974_configure(struct ak8974_chip *chip)
+{
+ int err;
+
+ err = ak8974_write(chip, AK8974_CTRL2, AK8974_CTRL2_DRDY_EN |
+ AK8974_CTRL2_INT_EN);
+ err |= ak8974_write(chip, AK8974_CTRL3, 0);
+ err |= ak8974_write(chip, AK8974_INT_CTRL, AK8974_INT_CTRL_POL);
+ err |= ak8974_write(chip, AK8974_PRESET, 0);
+
+ return err;
+}
+
+static int ak8974_poll_drdy(struct ak8974_chip *chip)
+{
+ int timeout = 2;
+ int ret;
+
+ do {
+ msleep(AK8974_MEASTIME);
+ ret = ak8974_read(chip, AK8974_STATUS);
+ if (ret < 0)
+ return ret;
+ if (ret & AK8974_STATUS_DRDY)
+ return 0;
+ } while (--timeout);
+ return ret;
+}
+
+static int ak8974_trigmeas(struct ak8974_chip *chip)
+{
+ int ctrl3;
+
+ /* Clear previous measurement overflow status */
+ ak8974_read(chip, AK8974_INT_CLEAR);
+
+ ctrl3 = ak8974_read(chip, AK8974_CTRL3);
+ if (ctrl3 < 0)
+ return ctrl3;
+
+ return ak8974_write(chip, AK8974_CTRL3, ctrl3 | AK8974_CTRL3_FORCE);
+}
+
+static int ak8974_getresult(struct ak8974_chip *chip, s16 *result)
+{
+ int ret;
+ int i;
+
+ ret = ak8974_poll_drdy(chip);
+ /* Measurement overflow ? */
+ ret |= ak8974_read(chip, AK8974_INT_SRC);
+ if (ret < 0)
+ return ret;
+ if (ret & AK8974_INT_SCR_MROI)
+ ret = -ERANGE;
+ else
+ ret = 0;
+
+ ak8974_read_block(chip, AK8974_DATA_X, (u8 *)result, 6);
+ for (i = 0; i < 3; i++)
+ result[i] = le16_to_cpu(result[i]);
+
+ return ret;
+}
+
+static int ak8974_selftest(struct ak8974_chip *chip)
+{
+ int r;
+ int ret = -EIO;
+
+ r = ak8974_read(chip, AK8974_SELFTEST);
+ if (r != AK8974_SELFTEST_IDLE)
+ goto out;
+
+ r = ak8974_read(chip, AK8974_CTRL3);
+ if (r < 0)
+ goto out;
+
+ r = ak8974_write(chip, AK8974_CTRL3, r | AK8974_CTRL3_SELFTEST);
+ if (r < 0)
+ goto out;
+
+ msleep(AK8974_SELFTEST_DELAY);
+
+ r = ak8974_read(chip, AK8974_SELFTEST);
+ if (r != AK8974_SELFTEST_OK)
+ goto out;
+
+ r = ak8974_read(chip, AK8974_SELFTEST);
+ if (r == AK8974_SELFTEST_IDLE)
+ ret = 0;
+
+out:
+ return ret;
+}
+
+static int ak8974_regulators_on(struct ak8974_chip *chip)
+{
+ int ret;
+ ret = regulator_bulk_enable(ARRAY_SIZE(chip->regs), chip->regs);
+ if (ret == 0)
+ msleep(AK8974_POWERON_DELAY);
+
+ return ret;
+}
+
+static inline void ak8974_regulators_off(struct ak8974_chip *chip)
+{
+ regulator_bulk_disable(ARRAY_SIZE(chip->regs), chip->regs);
+}
+
+static int ak8974_add_users(struct ak8974_chip *chip)
+{
+ int r = 0;
+ mutex_lock(&chip->users_lock);
+
+ if (chip->users == 0) {
+ r = ak8974_regulators_on(chip);
+ if (r < 0)
+ goto release_lock;
+ r = ak8974_enable(chip, AK8974_PWR_ON);
+ if (r < 0)
+ goto fail2;
+ r = ak8974_configure(chip);
+ if (r < 0)
+ goto fail3;
+ }
+ chip->users++;
+ goto release_lock;
+fail3:
+ ak8974_enable(chip, AK8974_PWR_OFF);
+fail2:
+ ak8974_regulators_off(chip);
+release_lock:
+ mutex_unlock(&chip->users_lock);
+ return r;
+}
+
+static void ak8974_remove_users(struct ak8974_chip *chip)
+{
+ mutex_lock(&chip->users_lock);
+
+ if (chip->users != 0)
+ chip->users--;
+
+ if (chip->users == 0) {
+ ak8974_enable(chip, AK8974_PWR_OFF);
+ ak8974_regulators_off(chip);
+ }
+ mutex_unlock(&chip->users_lock);
+}
+
+static int ak8974_get_axis(s8 axis, s16 hw_values[3])
+{
+ if (axis > 0)
+ return hw_values[axis - 1];
+ else
+ return -hw_values[-axis - 1];
+}
+
+static int ak8974_read_values(struct ak8974_chip *chip)
+{
+ s16 hw_values[3];
+ int ret;
+
+ mutex_lock(&chip->lock);
+ ak8974_trigmeas(chip);
+ ret = ak8974_getresult(chip, hw_values);
+ chip->x = ak8974_get_axis(chip->axis_x, hw_values);
+ chip->y = ak8974_get_axis(chip->axis_y, hw_values);
+ chip->z = ak8974_get_axis(chip->axis_z, hw_values);
+ chip->valid = (ret == 0) ? 1 : 0;
+ chip->offset += sizeof(struct ak8974_data);
+ mutex_unlock(&chip->lock);
+ wake_up_interruptible(&chip->misc_wait);
+ return 0;
+}
+
+static void ak8974_work(struct work_struct *work)
+{
+ struct ak8974_chip *chip = container_of(work,
+ struct ak8974_chip,
+ work);
+ ak8974_read_values(chip);
+}
+
+static int ak8974_misc_open(struct inode *inode, struct file *file)
+{
+ struct ak8974_chip *chip = container_of(file->private_data,
+ struct ak8974_chip,
+ miscdev);
+ file->f_pos = chip->offset;
+ return ak8974_add_users(chip);
+}
+
+static int ak8974_misc_close(struct inode *inode, struct file *file)
+{
+ struct ak8974_chip *chip = container_of(file->private_data,
+ struct ak8974_chip,
+ miscdev);
+ ak8974_remove_users(chip);
+ return 0;
+}
+
+static ssize_t ak8974_misc_read(struct file *file, char __user *buf,
+ size_t count, loff_t *offset)
+{
+ struct ak8974_chip *chip = container_of(file->private_data,
+ struct ak8974_chip,
+ miscdev);
+ struct ak8974_data data;
+
+ if (count < sizeof(data))
+ return -EINVAL;
+
+ if (*offset >= chip->offset) {
+ schedule_work(&chip->work);
+ if (file->f_flags & O_NONBLOCK)
+ return -EAGAIN;
+ if (wait_event_interruptible(chip->misc_wait,
+ (*offset < chip->offset)))
+ return -ERESTARTSYS;
+ }
+
+ mutex_lock(&chip->lock);
+ data.x = chip->x;
+ data.y = chip->y;
+ data.z = chip->z;
+ data.valid = chip->valid;
+ *offset = chip->offset;
+ mutex_unlock(&chip->lock);
+
+ return copy_to_user(buf, &data, sizeof(data)) ? -EFAULT : sizeof(data);
+}
+
+static ssize_t ak8974_show_selftest(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct ak8974_chip *chip = dev_get_drvdata(dev);
+ int ret;
+
+ ak8974_add_users(chip);
+ mutex_lock(&chip->lock);
+ ret = ak8974_selftest(chip);
+ mutex_unlock(&chip->lock);
+ ak8974_remove_users(chip);
+
+ return sprintf(buf, "%s\n", ret ? "FAIL" : "OK");
+}
+
+static DEVICE_ATTR(selftest, S_IRUGO, ak8974_show_selftest, NULL);
+
+static ssize_t ak8974_show_chip_id(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct ak8974_chip *chip = dev_get_drvdata(dev);
+ return sprintf(buf, "%s rev %d\n", chip->id,
+ chip->info[0] | (u16)chip->info[1] << 8);
+}
+
+static DEVICE_ATTR(chip_id, S_IRUGO, ak8974_show_chip_id, NULL);
+
+static ssize_t ak8974_show_range(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct ak8974_chip *chip = dev_get_drvdata(dev);
+ return sprintf(buf, "%d\n", chip->max_range);
+}
+
+static DEVICE_ATTR(range, S_IRUGO, ak8974_show_range, NULL);
+
+static struct attribute *sysfs_attrs[] = {
+ &dev_attr_selftest.attr,
+ &dev_attr_range.attr,
+ &dev_attr_chip_id.attr,
+ NULL
+};
+
+static struct attribute_group ak8974_attribute_group = {
+ .attrs = sysfs_attrs
+};
+
+static const struct file_operations ak8974_fops = {
+ .owner = THIS_MODULE,
+ .llseek = no_llseek,
+ .read = ak8974_misc_read,
+ .open = ak8974_misc_open,
+ .release = ak8974_misc_close,
+};
+
+static int ak8974_detect(struct ak8974_chip *chip, const char *name)
+{
+ int whoiam;
+ whoiam = ak8974_read(chip, AK8974_WHOAMI);
+
+ switch (whoiam) {
+ case AK8974_WHOAMI_VALUE_AMI305:
+ chip->id = chip->client->driver->id_table[0].name;
+ break;
+ case AK8974_WHOAMI_VALUE_AK8974:
+ chip->id = chip->client->driver->id_table[1].name;
+ break;
+ default:
+ dev_dbg(&chip->client->dev, "unsupported device\n");
+ return -ENODEV;
+ }
+
+ ak8974_read_block(chip, AK8974_INFO, chip->info, 2);
+
+ chip->max_range = AK8974_MAX_RANGE;
+
+ return 0;
+}
+
+static int __devinit ak8974_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct ak8974_chip *chip;
+ struct ak8974_platform_data *pdata;
+ int err;
+ int x, y, z;
+
+ chip = kzalloc(sizeof *chip, GFP_KERNEL);
+ if (!chip)
+ return -ENOMEM;
+
+ i2c_set_clientdata(client, chip);
+ chip->client = client;
+
+ pdata = client->dev.platform_data;
+
+ x = AK8974_DEV_X;
+ y = AK8974_DEV_Y;
+ z = AK8974_DEV_Z;
+
+ if (pdata) {
+ /* Remap axes */
+ x = pdata->axis_x ? pdata->axis_x : x;
+ y = pdata->axis_y ? pdata->axis_y : y;
+ z = pdata->axis_z ? pdata->axis_z : z;
+ }
+
+ if ((abs(x) > 3) || (abs(y) > 3) || (abs(z) > 3)) {
+ dev_err(&client->dev, "Incorrect platform data\n");
+ err = -EINVAL;
+ goto fail1;
+ }
+
+ chip->axis_x = x;
+ chip->axis_y = y;
+ chip->axis_z = z;
+
+ mutex_init(&chip->lock);
+ mutex_init(&chip->users_lock);
+ init_waitqueue_head(&chip->misc_wait);
+ INIT_WORK(&chip->work, ak8974_work);
+
+ chip->regs[0].supply = reg_avdd;
+ chip->regs[1].supply = reg_dvdd;
+
+ err = regulator_bulk_get(&client->dev,
+ ARRAY_SIZE(chip->regs), chip->regs);
+ if (err < 0) {
+ dev_err(&client->dev, "Cannot get regulators\n");
+ goto fail1;
+ }
+
+ err = ak8974_regulators_on(chip);
+ if (err < 0) {
+ dev_err(&client->dev, "Cannot enable regulators\n");
+ goto fail2;
+ }
+
+ err = ak8974_detect(chip, id->name);
+ if (err < 0) {
+ dev_err(&client->dev, "Neither AK8974 nor ami305 found\n");
+ goto fail3;
+ }
+
+ snprintf(chip->name, sizeof(chip->name), "ak8974%d",
+ atomic_add_return(1, &device_number) - 1);
+
+ err = ak8974_reset(chip);
+ if (err) {
+ dev_err(&client->dev, "Chip reset failed");
+ goto fail3;
+ }
+
+ ak8974_regulators_off(chip);
+
+ chip->miscdev.minor = MISC_DYNAMIC_MINOR;
+ chip->miscdev.name = chip->name;
+ chip->miscdev.fops = &ak8974_fops;
+ chip->miscdev.parent = &chip->client->dev;
+ err = misc_register(&chip->miscdev);
+ if (err < 0) {
+ dev_err(&chip->client->dev, "Device registration failed\n");
+ goto fail3;
+ }
+
+ err = sysfs_create_group(&chip->client->dev.kobj,
+ &ak8974_attribute_group);
+ if (err) {
+ dev_err(&client->dev, "Sysfs registration failed\n");
+ goto fail4;
+ }
+
+
+ return 0;
+fail4:
+ misc_deregister(&chip->miscdev);
+fail3:
+ ak8974_regulators_off(chip);
+fail2:
+ regulator_bulk_free(ARRAY_SIZE(chip->regs), chip->regs);
+fail1:
+ kfree(chip);
+ return err;
+}
+
+static int __devexit ak8974_remove(struct i2c_client *client)
+{
+ struct ak8974_chip *chip = i2c_get_clientdata(client);
+ misc_deregister(&chip->miscdev);
+ cancel_work_sync(&chip->work);
+ sysfs_remove_group(&chip->client->dev.kobj,
+ &ak8974_attribute_group);
+ /* No users after sysfs removal and unregisterin of input device */
+ regulator_bulk_free(ARRAY_SIZE(chip->regs), chip->regs);
+ kfree(chip);
+ return 0;
+}
+
+
+#ifdef CONFIG_PM
+static int ak8974_suspend(struct i2c_client *client, pm_message_t mesg)
+{
+ struct ak8974_chip *chip = i2c_get_clientdata(client);
+ mutex_lock(&chip->users_lock);
+ if (chip->users > 0)
+ ak8974_enable(chip, AK8974_PWR_OFF);
+ mutex_unlock(&chip->users_lock);
+ return 0;
+}
+
+static int ak8974_resume(struct i2c_client *client)
+{
+ struct ak8974_chip *chip = i2c_get_clientdata(client);
+ mutex_lock(&chip->users_lock);
+ if (chip->users > 0)
+ ak8974_enable(chip, AK8974_PWR_ON);
+ mutex_unlock(&chip->users_lock);
+ return 0;
+}
+
+static void ak8974_shutdown(struct i2c_client *client)
+{
+ struct ak8974_chip *chip = i2c_get_clientdata(client);
+
+ mutex_lock(&chip->users_lock);
+ if (chip->users > 0)
+ ak8974_enable(chip, AK8974_PWR_OFF);
+
+ mutex_unlock(&chip->users_lock);
+}
+
+#else
+#define ak8974_suspend NULL
+#define ak8974_shutdown NULL
+#define ak8974_resume NULL
+#endif
+
+static const struct i2c_device_id ak8974_id[] = {
+ {"ami305", 0 },
+ {"ak8974", 0 },
+ {}
+};
+
+MODULE_DEVICE_TABLE(i2c, ak8974_id);
+
+static struct i2c_driver ak8974_driver = {
+ .driver = {
+ .name = "ak8974",
+ .owner = THIS_MODULE,
+ },
+ .suspend = ak8974_suspend,
+ .shutdown = ak8974_shutdown,
+ .resume = ak8974_resume,
+ .probe = ak8974_probe,
+ .remove = __devexit_p(ak8974_remove),
+ .id_table = ak8974_id,
+};
+
+static int __init ak8974_init(void)
+{
+ return i2c_add_driver(&ak8974_driver);
+}
+
+static void __exit ak8974_exit(void)
+{
+ i2c_del_driver(&ak8974_driver);
+}
+
+
+MODULE_DESCRIPTION("AK8974/5 and AMI305 3-axis magnetometer driver");
+MODULE_AUTHOR("Samu Onkalo, Nokia Corporation");
+MODULE_LICENSE("GPL v2");
+
+module_init(ak8974_init);
+module_exit(ak8974_exit);
diff --git a/include/linux/i2c/ak8974.h b/include/linux/i2c/ak8974.h
new file mode 100644
index 0000000..b991404
--- /dev/null
+++ b/include/linux/i2c/ak8974.h
@@ -0,0 +1,50 @@
+/*
+ * Driver for ak8974 (Asahi Kasei EMD Corporation)
+ * and ami305 (Aichi Steel) magnetometer chip.
+ *
+ * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
+ *
+ * Contact: Samu Onkalo <samu.p.onkalo@nokia.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * 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., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+
+#ifndef __LINUX_I2C_AK8974_H
+#define __LINUX_I2C_AK8974_H
+
+#define AK8974_NO_MAP 0
+#define AK8974_DEV_X 1
+#define AK8974_DEV_Y 2
+#define AK8974_DEV_Z 3
+#define AK8974_INV_DEV_X -1
+#define AK8974_INV_DEV_Y -2
+#define AK8974_INV_DEV_Z -3
+
+struct ak8974_platform_data {
+ s8 axis_x;
+ s8 axis_y;
+ s8 axis_z;
+};
+
+/* Device name: /dev/ak8974n, where n is a running number */
+struct ak8974_data {
+ __s16 x;
+ __s16 y;
+ __s16 z;
+ __u16 valid;
+} __attribute__((packed));
+
+#endif
--
1.6.3.3