aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/input/misc/ak8975.c
diff options
context:
space:
mode:
authorcodeworkx <daniel.hillenbrand@codeworkx.de>2012-06-02 13:09:29 +0200
committercodeworkx <daniel.hillenbrand@codeworkx.de>2012-06-02 13:09:29 +0200
commitc6da2cfeb05178a11c6d062a06f8078150ee492f (patch)
treef3b4021d252c52d6463a9b3c1bb7245e399b009c /drivers/input/misc/ak8975.c
parentc6d7c4dbff353eac7919342ae6b3299a378160a6 (diff)
downloadkernel_samsung_smdk4412-c6da2cfeb05178a11c6d062a06f8078150ee492f.zip
kernel_samsung_smdk4412-c6da2cfeb05178a11c6d062a06f8078150ee492f.tar.gz
kernel_samsung_smdk4412-c6da2cfeb05178a11c6d062a06f8078150ee492f.tar.bz2
samsung update 1
Diffstat (limited to 'drivers/input/misc/ak8975.c')
-rwxr-xr-xdrivers/input/misc/ak8975.c863
1 files changed, 863 insertions, 0 deletions
diff --git a/drivers/input/misc/ak8975.c b/drivers/input/misc/ak8975.c
new file mode 100755
index 0000000..d95f229
--- /dev/null
+++ b/drivers/input/misc/ak8975.c
@@ -0,0 +1,863 @@
+/*
+ * Copyright (C) 2010, Samsung Electronics Co. Ltd. All Rights Reserved.
+ *
+ * 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; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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.
+ *
+*/
+
+#include <linux/i2c.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/slab.h>
+#include <linux/gpio.h>
+#include <linux/miscdevice.h>
+#include <linux/uaccess.h>
+#include <linux/delay.h>
+#include <linux/fs.h>
+#include <linux/i2c/ak8975.h>
+#include <linux/completion.h>
+#include "ak8975-reg.h"
+
+#define VENDOR_NAME "AKM"
+#define CHIP_NAME "AK8975C"
+
+
+#define AK8975_REG_CNTL 0x0A
+#define REG_CNTL_MODE_SHIFT 0
+#define REG_CNTL_MODE_MASK (0xF << REG_CNTL_MODE_SHIFT)
+#define REG_CNTL_MODE_POWER_DOWN 0
+#define REG_CNTL_MODE_ONCE 0x01
+#define REG_CNTL_MODE_SELF_TEST 0x08
+#define REG_CNTL_MODE_FUSE_ROM 0x0F
+
+#define AK8975_REG_RSVC 0x0B
+#define AK8975_REG_ASTC 0x0C
+#define AK8975_REG_TS1 0x0D
+#define AK8975_REG_TS2 0x0E
+#define AK8975_REG_I2CDIS 0x0F
+#define AK8975_REG_ASAX 0x10
+#define AK8975_REG_ASAY 0x11
+#define AK8975_REG_ASAZ 0x12
+
+#define USING_IRQ 0
+
+struct akm8975_data {
+ struct i2c_client *this_client;
+ struct akm8975_platform_data *pdata;
+ struct mutex lock;
+ struct miscdevice akmd_device;
+ struct completion data_ready;
+ struct device *dev;
+ wait_queue_head_t state_wq;
+ u8 asa[3];
+#if USING_IRQ
+ int irq;
+#endif
+};
+
+#ifdef FACTORY_TEST
+static bool ak8975_selftest_passed;
+static s16 sf_x, sf_y, sf_z;
+#endif
+
+extern int sensors_register(struct device *dev, void *drvdata,
+ struct device_attribute *attributes[], char *name);
+
+static s32 akm8975_ecs_set_mode_power_down(struct akm8975_data *akm)
+{
+ s32 ret;
+ ret = i2c_smbus_write_byte_data(akm->this_client,
+ AK8975_REG_CNTL, AK8975_MODE_POWER_DOWN);
+ return ret;
+}
+
+static int akm8975_ecs_set_mode(struct akm8975_data *akm, char mode)
+{
+ s32 ret;
+
+ switch (mode) {
+ case AK8975_MODE_SNG_MEASURE:
+ ret = i2c_smbus_write_byte_data(akm->this_client,
+ AK8975_REG_CNTL, AK8975_MODE_SNG_MEASURE);
+ break;
+ case AK8975_MODE_FUSE_ACCESS:
+ ret = i2c_smbus_write_byte_data(akm->this_client,
+ AK8975_REG_CNTL, AK8975_MODE_FUSE_ACCESS);
+ break;
+ case AK8975_MODE_POWER_DOWN:
+ ret = akm8975_ecs_set_mode_power_down(akm);
+ break;
+ case AK8975_MODE_SELF_TEST:
+ ret = i2c_smbus_write_byte_data(akm->this_client,
+ AK8975_REG_CNTL, AK8975_MODE_SELF_TEST);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ if (ret < 0)
+ return ret;
+
+ /* Wait at least 300us after changing mode. */
+ udelay(300);
+
+ return 0;
+}
+
+static int akmd_copy_in(unsigned int cmd, void __user *argp,
+ void *buf, size_t buf_size)
+{
+ if (!(cmd & IOC_IN))
+ return 0;
+ if (_IOC_SIZE(cmd) > buf_size)
+ return -EINVAL;
+ if (copy_from_user(buf, argp, _IOC_SIZE(cmd)))
+ return -EFAULT;
+ return 0;
+}
+
+static int akmd_copy_out(unsigned int cmd, void __user *argp,
+ void *buf, size_t buf_size)
+{
+ if (!(cmd & IOC_OUT))
+ return 0;
+ if (_IOC_SIZE(cmd) > buf_size)
+ return -EINVAL;
+ if (copy_to_user(argp, buf, _IOC_SIZE(cmd)))
+ return -EFAULT;
+ return 0;
+}
+
+#if USING_IRQ
+static void akm8975_disable_irq(struct akm8975_data *akm)
+{
+ disable_irq(akm->irq);
+ if (try_wait_for_completion(&akm->data_ready)) {
+ /* we actually got the interrupt before we could disable it
+ * so we need to enable again to undo our disable since the
+ * irq_handler already disabled it
+ */
+ enable_irq(akm->irq);
+ }
+}
+
+static irqreturn_t akm8975_irq_handler(int irq, void *data)
+{
+ struct akm8975_data *akm = data;
+ disable_irq_nosync(irq);
+ complete(&akm->data_ready);
+ return IRQ_HANDLED;
+}
+#endif
+
+static int akm8975_wait_for_data_ready(struct akm8975_data *akm)
+{
+#if USING_IRQ
+ int data_ready = gpio_get_value(akm->pdata->gpio_data_ready_int);
+ int err;
+
+ pr_info("1 akm8975_wait_for_data_ready: %d", data_ready);
+ if (data_ready)
+ return 0;
+
+ enable_irq(akm->irq);
+
+ err = wait_for_completion_timeout(&akm->data_ready, 5*HZ);
+ data_ready = gpio_get_value(akm->pdata->gpio_data_ready_int);
+ pr_info("2 akm8975_wait_for_data_ready: %d", data_ready);
+
+ if (err > 0)
+ return 0;
+
+ akm8975_disable_irq(akm);
+
+ if (err == 0) {
+ pr_err("akm: wait timed out");
+ return -ETIMEDOUT;
+ }
+
+ pr_err("akm: wait restart");
+ return err;
+#else
+ int err;
+ u8 buf;
+
+ int count = 10;
+
+ while (1) {
+ msleep(20);
+ err = i2c_smbus_read_i2c_block_data(akm->this_client,
+ AK8975_REG_ST1, sizeof(buf), &buf);
+
+ if (err != sizeof(buf)) {
+ pr_err("%s: read data over i2c failed", __func__);
+ return -1;
+ }
+
+ if (buf&0x1)
+ break;
+
+ count--;
+ if (!count)
+ break;
+ }
+
+ return 0;
+#endif
+
+}
+
+static ssize_t akmd_read(struct file *file, char __user *buf,
+ size_t count, loff_t *pos)
+{
+ struct akm8975_data *akm = container_of(file->private_data,
+ struct akm8975_data, akmd_device);
+ short x = 0, y = 0, z = 0;
+ int ret;
+ u8 data[8];
+
+ mutex_lock(&akm->lock);
+ ret = akm8975_ecs_set_mode(akm, AK8975_MODE_SNG_MEASURE);
+ if (ret) {
+ mutex_unlock(&akm->lock);
+ goto done;
+ }
+ ret = akm8975_wait_for_data_ready(akm);
+ if (ret) {
+ mutex_unlock(&akm->lock);
+ goto done;
+ }
+ ret = i2c_smbus_read_i2c_block_data(akm->this_client, AK8975_REG_ST1,
+ sizeof(data), data);
+ mutex_unlock(&akm->lock);
+
+ if (ret != sizeof(data)) {
+ pr_err("%s: failed to read %d bytes of mag data",
+ __func__, sizeof(data));
+ goto done;
+ }
+
+ if (data[0] & 0x01) {
+ x = (data[2] << 8) + data[1];
+ y = (data[4] << 8) + data[3];
+ z = (data[6] << 8) + data[5];
+ } else
+ pr_err("%s: invalid raw data(st1 = %d)",
+ __func__, data[0] & 0x01);
+
+done:
+ return sprintf(buf, "%d,%d,%d\n", x, y, z);
+}
+
+static long akmd_ioctl(struct file *file, unsigned int cmd,
+ unsigned long arg)
+{
+ void __user *argp = (void __user *)arg;
+ struct akm8975_data *akm = container_of(file->private_data,
+ struct akm8975_data, akmd_device);
+ int ret;
+ #ifdef MAGNETIC_LOGGING
+ short x, y, z;
+ #endif
+ union {
+ char raw[RWBUF_SIZE];
+ int status;
+ char mode;
+ u8 data[8];
+ } rwbuf;
+
+ ret = akmd_copy_in(cmd, argp, rwbuf.raw, sizeof(rwbuf));
+ if (ret)
+ return ret;
+
+ switch (cmd) {
+ case ECS_IOCTL_WRITE:
+ if ((rwbuf.raw[0] < 2) || (rwbuf.raw[0] > (RWBUF_SIZE - 1)))
+ return -EINVAL;
+ if (copy_from_user(&rwbuf.raw[2], argp+2, rwbuf.raw[0]-1))
+ return -EFAULT;
+
+ ret = i2c_smbus_write_i2c_block_data(akm->this_client,
+ rwbuf.raw[1],
+ rwbuf.raw[0] - 1,
+ &rwbuf.raw[2]);
+ break;
+ case ECS_IOCTL_READ:
+ if ((rwbuf.raw[0] < 1) || (rwbuf.raw[0] > (RWBUF_SIZE - 1)))
+ return -EINVAL;
+
+ ret = i2c_smbus_read_i2c_block_data(akm->this_client,
+ rwbuf.raw[1],
+ rwbuf.raw[0],
+ &rwbuf.raw[1]);
+ if (ret < 0)
+ return ret;
+ if (copy_to_user(argp+1, rwbuf.raw+1, rwbuf.raw[0]))
+ return -EFAULT;
+ return 0;
+ case ECS_IOCTL_SET_MODE:
+ mutex_lock(&akm->lock);
+ ret = akm8975_ecs_set_mode(akm, rwbuf.mode);
+ mutex_unlock(&akm->lock);
+ break;
+ case ECS_IOCTL_GETDATA:
+ mutex_lock(&akm->lock);
+ ret = akm8975_wait_for_data_ready(akm);
+ if (ret) {
+ mutex_unlock(&akm->lock);
+ return ret;
+ }
+
+ ret = i2c_smbus_read_i2c_block_data(akm->this_client,
+ AK8975_REG_ST1,
+ sizeof(rwbuf.data),
+ rwbuf.data);
+
+ #ifdef MAGNETIC_LOGGING
+ x = (rwbuf.data[2] << 8) + rwbuf.data[1];
+ y = (rwbuf.data[4] << 8) + rwbuf.data[3];
+ z = (rwbuf.data[6] << 8) + rwbuf.data[5];
+
+ pr_info("%s: raw x = %d, y = %d, z = %d",
+ __func__, x, y, z);
+ #endif
+
+ mutex_unlock(&akm->lock);
+ if (ret != sizeof(rwbuf.data)) {
+ pr_err("%s : failed to read %d bytes of mag data",
+ __func__, sizeof(rwbuf.data));
+ return -EIO;
+ }
+ break;
+ default:
+ return -ENOTTY;
+ }
+
+ if (ret < 0)
+ return ret;
+
+ return akmd_copy_out(cmd, argp, rwbuf.raw, sizeof(rwbuf));
+}
+
+static const struct file_operations akmd_fops = {
+ .owner = THIS_MODULE,
+ .open = nonseekable_open,
+ .read = akmd_read,
+ .unlocked_ioctl = akmd_ioctl,
+};
+
+#if USING_IRQ
+
+static int akm8975_setup_irq(struct akm8975_data *akm)
+{
+ int rc = -EIO;
+ struct akm8975_platform_data *pdata = akm->pdata;
+ int irq;
+
+ if (akm->this_client->irq)
+ irq = akm->this_client->irq;
+ else {
+ rc = gpio_request(pdata->gpio_data_ready_int, "gpio_akm_int");
+ if (rc < 0) {
+ pr_err("%s: gpio %d request failed (%d)",
+ __func__, pdata->gpio_data_ready_int, rc);
+ return rc;
+ }
+
+ rc = gpio_direction_input(pdata->gpio_data_ready_int);
+ if (rc < 0) {
+ pr_err("%s: failed to set gpio %d as input (%d)",
+ __func__, pdata->gpio_data_ready_int, rc);
+ goto err_request_irq;
+ }
+
+ irq = gpio_to_irq(pdata->gpio_data_ready_int);
+ }
+ /* trigger high so we don't miss initial interrupt if it
+ * is already pending
+ */
+ rc = request_irq(irq, akm8975_irq_handler, IRQF_TRIGGER_RISING,
+ "akm_int", akm);
+ if (rc < 0) {
+ pr_err("%s: request_irq(%d) failed for gpio %d (%d)",
+ __func__, irq,
+ pdata->gpio_data_ready_int, rc);
+ goto err_request_irq;
+ }
+
+ /* start with interrupt disabled until the driver is enabled */
+ akm->irq = irq;
+ akm8975_disable_irq(akm);
+
+ goto done;
+
+err_request_irq:
+ gpio_free(pdata->gpio_data_ready_int);
+done:
+ return rc;
+}
+
+#endif
+
+#if (defined DEBUG) || (defined FACTORY_TEST)
+#ifdef FACTORY_TEST
+static bool ak8975_selftest_passed;
+static s16 sf_x, sf_y, sf_z;
+#endif
+
+static void ak8975c_selftest(struct akm8975_data *ak_data)
+{
+ u8 buf[6];
+ s16 x, y, z;
+
+ /* read device info */
+ i2c_smbus_read_i2c_block_data(ak_data->this_client,
+ AK8975_REG_WIA, 2, buf);
+ pr_info("%s: device id = 0x%x, info = 0x%x",
+ __func__, buf[0], buf[1]);
+
+ /* set ATSC self test bit to 1 */
+ i2c_smbus_write_byte_data(ak_data->this_client,
+ AK8975_REG_ASTC, 0x40);
+
+ /* start self test */
+ i2c_smbus_write_byte_data(ak_data->this_client,
+ AK8975_REG_CNTL,
+ REG_CNTL_MODE_SELF_TEST);
+
+ /* wait for data ready */
+ while (1) {
+ msleep(20);
+ if (i2c_smbus_read_byte_data(ak_data->this_client,
+ AK8975_REG_ST1) == 1) {
+ break;
+ }
+ }
+
+ i2c_smbus_read_i2c_block_data(ak_data->this_client,
+ AK8975_REG_HXL, sizeof(buf), buf);
+
+ /* set ATSC self test bit to 0 */
+ i2c_smbus_write_byte_data(ak_data->this_client,
+ AK8975_REG_ASTC, 0x00);
+
+ x = buf[0] | (buf[1] << 8);
+ y = buf[2] | (buf[3] << 8);
+ z = buf[4] | (buf[5] << 8);
+
+ /* Hadj = (H*(Asa+128))/256 */
+ x = (x*(ak_data->asa[0] + 128)) >> 8;
+ y = (y*(ak_data->asa[1] + 128)) >> 8;
+ z = (z*(ak_data->asa[2] + 128)) >> 8;
+
+ pr_info("%s: self test x = %d, y = %d, z = %d",
+ __func__, x, y, z);
+ if ((x >= -100) && (x <= 100))
+ pr_info("%s: x passed self test, expect -100<=x<=100",
+ __func__);
+ else
+ pr_info("%s: x failed self test, expect -100<=x<=100",
+ __func__);
+ if ((y >= -100) && (y <= 100))
+ pr_info("%s: y passed self test, expect -100<=y<=100",
+ __func__);
+ else
+ pr_info("%s: y failed self test, expect -100<=y<=100",
+ __func__);
+ if ((z >= -1000) && (z <= -300))
+ pr_info("%s: z passed self test, expect -1000<=z<=-300",
+ __func__);
+ else
+ pr_info("%s: z failed self test, expect -1000<=z<=-300",
+ __func__);
+
+#ifdef FACTORY_TEST
+ if (((x >= -100) && (x <= 100)) && ((y >= -100) && (y <= 100)) &&
+ ((z >= -1000) && (z <= -300)))
+ ak8975_selftest_passed = 1;
+
+ sf_x = x;
+ sf_y = y;
+ sf_z = z;
+#endif
+}
+
+static ssize_t ak8975c_get_asa(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct akm8975_data *ak_data = dev_get_drvdata(dev);
+
+ return sprintf(buf, "%d, %d, %d\n", ak_data->asa[0],
+ ak_data->asa[1], ak_data->asa[2]);
+}
+
+static ssize_t ak8975c_get_selftest(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ ak8975c_selftest(dev_get_drvdata(dev));
+ return sprintf(buf, "%d, %d, %d, %d\n",
+ ak8975_selftest_passed, sf_x, sf_y, sf_z);
+}
+
+static ssize_t ak8975c_check_registers(struct device *dev,
+ struct device_attribute *attr, char *strbuf)
+{
+ struct akm8975_data *ak_data = dev_get_drvdata(dev);
+ u8 buf[13];
+
+ /* power down */
+ i2c_smbus_write_byte_data(ak_data->this_client,
+ AK8975_REG_CNTL, REG_CNTL_MODE_POWER_DOWN);
+
+ /* get the value */
+ i2c_smbus_read_i2c_block_data(ak_data->this_client,
+ AK8975_REG_WIA, 11, buf);
+
+ buf[11] = i2c_smbus_read_byte_data(ak_data->this_client,
+ AK8975_REG_ASTC);
+ buf[12] = i2c_smbus_read_byte_data(ak_data->this_client,
+ AK8975_REG_I2CDIS);
+
+ return sprintf(strbuf, "%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d\n",
+ buf[0], buf[1], buf[2], buf[3], buf[4], buf[5],
+ buf[6], buf[7], buf[8], buf[9], buf[10], buf[11],
+ buf[12]);
+}
+
+static ssize_t ak8975c_check_cntl(struct device *dev,
+ struct device_attribute *attr, char *strbuf)
+{
+ struct akm8975_data *ak_data = dev_get_drvdata(dev);
+ u8 buf;
+ int err;
+
+ /* power down */
+ err = i2c_smbus_write_byte_data(ak_data->this_client,
+ AK8975_REG_CNTL, REG_CNTL_MODE_POWER_DOWN);
+
+ buf = i2c_smbus_read_byte_data(ak_data->this_client,
+ AK8975_REG_CNTL);
+
+ return sprintf(strbuf, "%s\n", (!buf ? "OK" : "NG"));
+}
+
+static ssize_t ak8975c_get_status(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct akm8975_data *ak_data = dev_get_drvdata(dev);
+ int success;
+
+ if ((ak_data->asa[0] == 0) | (ak_data->asa[0] == 0xff)
+ | (ak_data->asa[1] == 0) | (ak_data->asa[1] == 0xff)
+ | (ak_data->asa[2] == 0) | (ak_data->asa[2] == 0xff))
+ success = 0;
+ else
+ success = 1;
+
+ return sprintf(buf, "%s\n", (success ? "OK" : "NG"));
+}
+
+static ssize_t ak8975_adc(struct device *dev,
+ struct device_attribute *attr, char *strbuf)
+{
+ struct akm8975_data *ak_data = dev_get_drvdata(dev);
+ u8 buf[8];
+ s16 x, y, z;
+ int err, success;
+
+ /* start ADC conversion */
+ err = i2c_smbus_write_byte_data(ak_data->this_client,
+ AK8975_REG_CNTL, REG_CNTL_MODE_ONCE);
+
+ /* wait for ADC conversion to complete */
+ err = akm8975_wait_for_data_ready(ak_data);
+ if (err) {
+ pr_err("%s: wait for data ready failed", __func__);
+ return err;
+ }
+ msleep(20);
+ /* get the value and report it */
+ err = i2c_smbus_read_i2c_block_data(ak_data->this_client,
+ AK8975_REG_ST1, sizeof(buf), buf);
+ if (err != sizeof(buf)) {
+ pr_err("%s: read data over i2c failed", __func__);
+ return err;
+ }
+
+ /* buf[0] is status1, buf[7] is status2 */
+ if ((buf[0] == 0) | (buf[7] == 1))
+ success = 0;
+ else
+ success = 1;
+
+ x = buf[1] | (buf[2] << 8);
+ y = buf[3] | (buf[4] << 8);
+ z = buf[5] | (buf[6] << 8);
+
+ pr_info("%s: raw x = %d, y = %d, z = %d", __func__, x, y, z);
+ return sprintf(strbuf, "%s, %d, %d, %d\n", (success ? "OK" : "NG"),
+ x, y, z);
+}
+#endif
+
+static ssize_t ak8975_show_raw_data(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct akm8975_data *akm = dev_get_drvdata(dev);
+ short x = 0, y = 0, z = 0;
+ int ret;
+ u8 data[8] = {0,};
+
+ mutex_lock(&akm->lock);
+ ret = akm8975_ecs_set_mode(akm, AK8975_MODE_SNG_MEASURE);
+ if (ret) {
+ mutex_unlock(&akm->lock);
+ goto done;
+ }
+ ret = akm8975_wait_for_data_ready(akm);
+ if (ret) {
+ mutex_unlock(&akm->lock);
+ goto done;
+ }
+ ret = i2c_smbus_read_i2c_block_data(akm->this_client, AK8975_REG_ST1,
+ sizeof(data), data);
+ mutex_unlock(&akm->lock);
+
+ if (ret != sizeof(data)) {
+ pr_err("%s: failed to read %d bytes of mag data\n",
+ __func__, sizeof(data));
+ goto done;
+ }
+
+ if (data[0] & 0x01) {
+ x = (data[2] << 8) + data[1];
+ y = (data[4] << 8) + data[3];
+ z = (data[6] << 8) + data[5];
+ } else
+ pr_err("%s: invalid raw data(st1 = %d)",
+ __func__, data[0] & 0x01);
+
+done:
+ return sprintf(buf, "%d,%d,%d\n", x, y, z);
+}
+static ssize_t get_vendor_name(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ return sprintf(buf, "%s\n", VENDOR_NAME);
+}
+
+static ssize_t get_chip_name(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ return sprintf(buf, "%s\n", CHIP_NAME);
+}
+static DEVICE_ATTR(vendor, S_IRUGO, get_vendor_name, NULL);
+static DEVICE_ATTR(name, S_IRUGO, get_chip_name, NULL);
+
+static DEVICE_ATTR(raw_data, 0664,
+ ak8975_show_raw_data, NULL);
+
+#ifdef FACTORY_TEST
+static DEVICE_ATTR(asa, 0664,
+ ak8975c_get_asa, NULL);
+static DEVICE_ATTR(selftest, 0664,
+ ak8975c_get_selftest, NULL);
+static DEVICE_ATTR(chk_registers, 0664,
+ ak8975c_check_registers, NULL);
+static DEVICE_ATTR(dac, 0664,
+ ak8975c_check_cntl, NULL);
+static DEVICE_ATTR(status, 0664,
+ ak8975c_get_status, NULL);
+static DEVICE_ATTR(adc, 0664,
+ ak8975_adc, NULL);
+
+static struct device_attribute *magnetic_sensor_attrs[] = {
+ &dev_attr_raw_data,
+ &dev_attr_asa,
+ &dev_attr_selftest,
+ &dev_attr_chk_registers,
+ &dev_attr_dac,
+ &dev_attr_status,
+ &dev_attr_adc,
+ &dev_attr_vendor,
+ &dev_attr_name,
+ NULL,
+};
+
+#else
+static struct device_attribute *magnetic_sensor_attrs[] = {
+ &dev_attr_raw_data,
+ &dev_attr_vendor,
+ &dev_attr_name,
+ NULL,
+};
+#endif
+
+int akm8975_probe(struct i2c_client *client,
+ const struct i2c_device_id *devid)
+{
+ struct akm8975_data *akm;
+ int err;
+
+ pr_info("%s is called.\n", __func__);
+ if (client->dev.platform_data == NULL && client->irq == 0) {
+ dev_err(&client->dev, "platform data & irq are NULL.");
+ err = -ENODEV;
+ goto exit_platform_data_null;
+ }
+
+ if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
+ dev_err(&client->dev, "I2C check failed, exiting.");
+ err = -ENODEV;
+ goto exit_check_functionality_failed;
+ }
+
+ akm = kzalloc(sizeof(struct akm8975_data), GFP_KERNEL);
+ if (!akm) {
+ dev_err(&client->dev,
+ "failed to allocate memory for module data");
+ err = -ENOMEM;
+ goto exit_alloc_data_failed;
+ }
+
+ akm->pdata = client->dev.platform_data;
+ mutex_init(&akm->lock);
+ init_completion(&akm->data_ready);
+
+ i2c_set_clientdata(client, akm);
+ akm->this_client = client;
+
+ err = akm8975_ecs_set_mode_power_down(akm);
+ if (err < 0)
+ goto exit_set_mode_power_down_failed;
+
+#if USING_IRQ
+ akm->irq = client->irq;
+ err = akm8975_setup_irq(akm);
+ if (err) {
+ pr_err("%s: could not setup irq", __func__);
+ goto exit_setup_irq;
+ }
+#endif
+
+ akm->akmd_device.minor = MISC_DYNAMIC_MINOR;
+ akm->akmd_device.name = "akm8975";
+ akm->akmd_device.fops = &akmd_fops;
+
+ err = misc_register(&akm->akmd_device);
+ if (err) {
+ pr_err("%s, misc_register failed.", __func__);
+ goto exit_akmd_device_register_failed;
+ }
+
+ init_waitqueue_head(&akm->state_wq);
+
+ /* put into fuse access mode to read asa data */
+ err = i2c_smbus_write_byte_data(client, AK8975_REG_CNTL,
+ REG_CNTL_MODE_FUSE_ROM);
+ if (err) {
+ pr_err("%s: unable to enter fuse rom mode", __func__);
+ goto exit_i2c_failed;
+ }
+
+ err = i2c_smbus_read_i2c_block_data(client, AK8975_REG_ASAX,
+ sizeof(akm->asa), akm->asa);
+ if (err != sizeof(akm->asa)) {
+ pr_err("%s: unable to load factory sensitivity adjust values",
+ __func__);
+ goto exit_i2c_failed;
+ } else
+ pr_info("%s: asa_x = %d, asa_y = %d, asa_z = %d", __func__,
+ akm->asa[0], akm->asa[1], akm->asa[2]);
+
+ err = i2c_smbus_write_byte_data(client, AK8975_REG_CNTL,
+ REG_CNTL_MODE_POWER_DOWN);
+ if (err) {
+ dev_err(&client->dev, "Error in setting power down mode");
+ goto exit_i2c_failed;
+ }
+
+#ifdef FACTORY_TEST
+ ak8975c_selftest(akm);
+#endif
+ err = sensors_register(akm->dev, akm, magnetic_sensor_attrs,
+ "magnetic_sensor");
+ if (err) {
+ pr_info("%s: cound not register"
+ "magnetic sensor device(%d).", __func__, err);
+ goto exit_class_create_failed;
+ }
+
+ /* dev_set_drvdata(akm->dev, akm); */
+
+pr_info("%s is successful.", __func__);
+return 0;
+
+exit_class_create_failed:
+exit_i2c_failed:
+exit_akmd_device_register_failed:
+#if USING_IRQ
+ free_irq(akm->irq, akm);
+ gpio_free(akm->pdata->gpio_data_ready_int);
+exit_setup_irq:
+#endif
+exit_set_mode_power_down_failed:
+ mutex_destroy(&akm->lock);
+ kfree(akm);
+exit_alloc_data_failed:
+exit_check_functionality_failed:
+exit_platform_data_null:
+ return err;
+}
+
+static int __devexit akm8975_remove(struct i2c_client *client)
+{
+ struct akm8975_data *akm = i2c_get_clientdata(client);
+ misc_deregister(&akm->akmd_device);
+#if USING_IRQ
+ free_irq(akm->irq, akm);
+ gpio_free(akm->pdata->gpio_data_ready_int);
+#endif
+ mutex_destroy(&akm->lock);
+ kfree(akm);
+ return 0;
+}
+
+static const struct i2c_device_id akm8975_id[] = {
+ {AKM8975_I2C_NAME, 0 },
+ { }
+};
+
+static struct i2c_driver akm8975_driver = {
+ .probe = akm8975_probe,
+ .remove = akm8975_remove,
+ .id_table = akm8975_id,
+ .driver = {
+ .name = AKM8975_I2C_NAME,
+ },
+};
+
+static int __init akm8975_init(void)
+{
+ return i2c_add_driver(&akm8975_driver);
+}
+
+static void __exit akm8975_exit(void)
+{
+ i2c_del_driver(&akm8975_driver);
+}
+
+module_init(akm8975_init);
+module_exit(akm8975_exit);
+
+MODULE_DESCRIPTION("AKM8975 compass driver");
+MODULE_AUTHOR("Samsung Electronics");
+MODULE_LICENSE("GPL");