rk3188 ds1006h: add akm8963
authorcdy <cdy@rock-chips.com>
Tue, 29 Jan 2013 07:26:26 +0000 (15:26 +0800)
committerwuhao <wuhao@wuhao@rock-chips.com>
Tue, 29 Jan 2013 07:26:26 +0000 (15:26 +0800)
arch/arm/mach-rk3188/board-rk3188-ds1006h.c
arch/arm/plat-rk/include/plat/board.h
drivers/misc/Kconfig
drivers/misc/Makefile
drivers/misc/akm8963.c [new file with mode: 0644]
include/linux/akm8963.h [new file with mode: 0644]

index 049c32112a5a946d7f41aef5870e4cf52557863b..d8fe65543d239a045c9865f4fe767265acb16f55 100755 (executable)
@@ -253,6 +253,17 @@ static struct sensor_platform_data lis3dh_info = {
        .orientation = {-1, 0, 0, 0, 0, -1, 0, 1, 0},
 };
 #endif
+
+#if (defined(CONFIG_SENSORS_AK8963) || defined(CONFIG_SENSORS_AK8963_MODULE)) 
+static struct akm8963_platform_data akm_platform_data_8963 = { 
+                 .gpio_DRDY      = RK30_PIN3_PD7, 
+                 .gpio_RST        = 0, 
+                 .layout              = 3, 
+                 .outbit           = 1, 
+}; 
+#endif
+
+
 #if defined (CONFIG_COMPASS_AK8975)
 static struct sensor_platform_data akm8975_info =
 {
@@ -1151,6 +1162,17 @@ static struct i2c_board_info __initdata i2c0_info[] = {
                .platform_data = &lis3dh_info,
        },
 #endif
+#if defined (CONFIG_SENSORS_AK8963)
+        {
+                .type             = "akm8963",
+                .addr           = 0x0d,
+                .flags           = I2C_CLIENT_WAKE,
+               .irq             = RK30_PIN3_PD7,
+              .platform_data = &akm_platform_data_8963,
+        },
+#endif
+
+
 #if defined (CONFIG_COMPASS_AK8975)
        {
                .type          = "ak8975",
index a18bbea9af5409b73c13e85f5b4f662ec9f49b82..31abdb4a76793fb511001a2f1d5057352dd45fe9 100755 (executable)
@@ -18,6 +18,14 @@ enum {
         I2C_SCL_LOW,
         BOTH_LOW,
 };
+
+struct akm8963_platform_data { 
+                 int gpio_DRDY;
+                 int gpio_RST;
+                 char layout;
+                 char outbit;         
+}; 
+
 struct rk30_i2c_platform_data {
        char *name;
        int bus_num;
index ca88ad64187c05fb9449d7c0ea2f721462d7afaa..bdbb18493632921ac659c29ae25768acb4bc6709 100755 (executable)
@@ -399,6 +399,15 @@ config SENSORS_AK8975
          If you say yes here you get support for Asahi Kasei's
          orientation sensor AK8975.
 
+config SENSORS_AK8963
+       tristate "AK8963 compass support"
+       default n
+       depends on I2C
+       help
+         If you say yes here you get support for Asahi Kasei's
+         orientation sensor AK8963.
+
+
 config EP93XX_PWM
        tristate "EP93xx PWM support"
        depends on ARCH_EP93XX
index a6cee2fc84ec60f5425dc16354027ea4803ae6c7..e70fbdcd55af0c9e91ac005456f7b3407d31e68d 100755 (executable)
@@ -50,6 +50,7 @@ obj-y                         += carma/
 obj-$(CONFIG_WL127X_RFKILL)    += wl127x-rfkill.o
 obj-$(CONFIG_APANIC)           += apanic.o
 obj-$(CONFIG_SENSORS_AK8975)   += akm8975.o
+obj-$(CONFIG_SENSORS_AK8963)   += akm8963.o
 obj-$(CONFIG_MTK23D)           += mtk23d.o
 obj-$(CONFIG_FM580X)           += fm580x.o
 obj-$(CONFIG_RK29_SUPPORT_MODEM)       += rk29_modem/
diff --git a/drivers/misc/akm8963.c b/drivers/misc/akm8963.c
new file mode 100644 (file)
index 0000000..3085e43
--- /dev/null
@@ -0,0 +1,1466 @@
+/* drivers/misc/akm8963.c - akm8963 compass driver
+ *
+ * Copyright (C) 2007-2008 HTC Corporation.
+ * Author: Hou-Kun Chen <houkun.chen@gmail.com>
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * 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.
+ *
+ */
+
+/*#define DEBUG*/
+/*#define VERBOSE_DEBUG*/
+
+#include <linux/device.h>
+#include <linux/interrupt.h>
+#include <linux/i2c.h>
+#include <linux/slab.h>
+#include <linux/irq.h>
+#include <linux/miscdevice.h>
+#include <linux/gpio.h>
+#include <linux/uaccess.h>
+#include <linux/delay.h>
+#include <linux/input.h>
+#include <linux/workqueue.h>
+#include <linux/freezer.h>
+#include <linux/akm8963.h>
+
+#define AKM8963_DEBUG_IF       0
+#define AKM8963_DEBUG_DATA     0
+
+#define AKM_ACCEL_ITEMS 3
+/* Wait timeout in millisecond */
+#define AKM8963_DRDY_TIMEOUT   100
+
+struct akm8963_data {
+       struct i2c_client       *i2c;
+       struct input_dev        *input;
+       struct device           *class_dev;
+       struct class            *compass;
+       struct delayed_work     work;
+
+       wait_queue_head_t       drdy_wq;
+       wait_queue_head_t       open_wq;
+
+       struct mutex sensor_mutex;
+       int8_t  sense_data[SENSOR_DATA_SIZE];
+       struct mutex accel_mutex;
+       int16_t accel_data[AKM_ACCEL_ITEMS];
+
+       struct mutex    val_mutex;
+       uint32_t        enable_flag;
+       int64_t         delay[AKM_NUM_SENSORS];
+
+       atomic_t        active;
+       atomic_t        is_busy;
+       atomic_t        drdy;
+       atomic_t        suspend;
+
+       char layout;
+       char outbit;
+       int     irq;
+       int     rstn;
+};
+
+static struct akm8963_data *s_akm;
+
+
+
+/***** I2C I/O function ***********************************************/
+static int akm8963_i2c_rxdata(
+       struct i2c_client *i2c,
+       unsigned char *rxData,
+       int length)
+{
+       struct i2c_msg msgs[] = {
+       {
+               .addr = i2c->addr,
+               .flags = 0,
+               .len = 1,
+               .buf = rxData,
+               .scl_rate = 200*1000,
+       },
+       {
+               .addr = i2c->addr,
+               .flags = I2C_M_RD,
+               .len = length,
+               .buf = rxData,
+               .scl_rate = 200*1000,
+       }, };
+       unsigned char addr = rxData[0];
+
+       if (i2c_transfer(i2c->adapter, msgs, 2) < 0) {
+               dev_err(&i2c->dev, "%s: transfer failed.", __func__);
+               return -EIO;
+       }
+
+       dev_vdbg(&i2c->dev, "RxData: len=%02x, addr=%02x, data=%02x",
+               length, addr, rxData[0]);
+       return 0;
+}
+
+static int akm8963_i2c_txdata(
+       struct i2c_client *i2c,
+       unsigned char *txData,
+       int length)
+{
+       struct i2c_msg msg[] = {
+       {
+               .addr = i2c->addr,
+               .flags = 0,
+               .len = length,
+               .buf = txData,
+               .scl_rate = 200*1000,
+       }, };
+
+       if (i2c_transfer(i2c->adapter, msg, 1) < 0) {
+               dev_err(&i2c->dev, "%s: transfer failed.", __func__);
+               return -EIO;
+       }
+
+       dev_vdbg(&i2c->dev, "TxData: len=%02x, addr=%02x data=%02x",
+               length, txData[0], txData[1]);
+       return 0;
+}
+
+static int akm8963_i2c_check_device(
+       struct i2c_client *client)
+{
+       unsigned char buffer[2];
+       int err;
+
+       /* Set measure mode */
+       buffer[0] = AK8963_REG_WIA;
+       err = akm8963_i2c_rxdata(client, buffer, 1);
+       if (err < 0) {
+               dev_err(&client->dev,
+                       "%s: Can not read WIA.", __func__);
+               return err;
+       }
+       /* Check read data */
+       if (buffer[0] != 0x48) {
+               dev_err(&client->dev,
+                       "%s: The device is not AK8963.", __func__);
+               return -ENXIO;
+       }
+
+       //printk("========[%x] ,[%x]" , buffer[0],buffer[1]);
+       return err;
+}
+
+/***** akm miscdevice functions *************************************/
+static int AKECS_Open(struct inode *inode, struct file *file);
+static int AKECS_Release(struct inode *inode, struct file *file);
+static long AKECS_ioctl(struct file *file,
+               unsigned int cmd, unsigned long arg);
+
+static struct file_operations AKECS_fops = {
+       .owner = THIS_MODULE,
+       .open = AKECS_Open,
+       .release = AKECS_Release,
+       .unlocked_ioctl = AKECS_ioctl,
+};
+
+static struct miscdevice akm8963_dev = {
+       .minor = MISC_DYNAMIC_MINOR,
+       .name = "akm8963_dev",
+       .fops = &AKECS_fops,
+};
+
+static int AKECS_Set_CNTL1(
+       struct akm8963_data *akm,
+       unsigned char mode)
+{
+       unsigned char buffer[2];
+       int err;
+
+       /* Busy check */
+       if (atomic_cmpxchg(&akm->is_busy, 0, 1) != 0) {
+               dev_err(&akm->i2c->dev, "%s: device is busy.", __func__);
+               return -EBUSY;
+       }
+
+       /* Set flag */
+       atomic_set(&akm->drdy, 0);
+
+       /* Set measure mode */
+       buffer[0] = AK8963_REG_CNTL1;
+       buffer[1] = mode;
+       err = akm8963_i2c_txdata(akm->i2c, buffer, 2);
+       if (err < 0) {
+               dev_err(&akm->i2c->dev, "%s: Can not set CNTL.", __func__);
+               atomic_set(&akm->is_busy, 0);
+       } else {
+               dev_dbg(&akm->i2c->dev, "Mode is set to (%d).", mode);
+       }
+
+       return err;
+}
+
+static int AKECS_Set_PowerDown(
+       struct akm8963_data *akm)
+{
+       unsigned char buffer[2];
+       int err;
+
+       /* Set measure mode */
+       buffer[0] = AK8963_REG_CNTL1;
+       buffer[1] = AK8963_MODE_POWERDOWN;
+       err = akm8963_i2c_txdata(akm->i2c, buffer, 2);
+       if (err < 0) {
+               dev_err(&akm->i2c->dev,
+                       "%s: Can not set to measurement mode.", __func__);
+               atomic_set(&akm->is_busy, 0);
+       } else {
+               dev_dbg(&akm->i2c->dev, "Powerdown mode is set.");
+       }
+
+       /* Set to initial status. */
+       atomic_set(&akm->is_busy, 0);
+       atomic_set(&akm->drdy, 0);
+
+       return err;
+}
+
+static int AKECS_Reset(
+       struct akm8963_data *akm,
+       int hard)
+{
+       unsigned char buffer[2];
+       int err = 0;
+
+       if (hard != 0) {
+               gpio_set_value(akm->rstn, 0);
+               udelay(5);
+               gpio_set_value(akm->rstn, 1);
+       } else {
+               /* Set measure mode */
+               buffer[0] = AK8963_REG_CNTL2;
+               buffer[1] = 0x01;
+               err = akm8963_i2c_txdata(akm->i2c, buffer, 2);
+               if (err < 0) {
+                       dev_err(&akm->i2c->dev,
+                               "%s: Can not set SRST bit.", __func__);
+               } else {
+                       dev_dbg(&akm->i2c->dev, "Soft reset is done.");
+               }
+       }
+
+       /* Device will be accessible 100 us after */
+       udelay(100);
+
+       return err;
+}
+
+static int AKECS_SetMode(
+       struct akm8963_data *akm,
+       unsigned char mode)
+{
+       int err;
+
+       switch (mode & 0x0F) {
+       case AK8963_MODE_SNG_MEASURE:
+       case AK8963_MODE_SELF_TEST:
+       case AK8963_MODE_FUSE_ACCESS:
+               err = AKECS_Set_CNTL1(akm, mode);
+               if ((err >= 0) && (akm->irq == 0)) {
+                       schedule_delayed_work(
+                               &akm->work,
+                               usecs_to_jiffies(AK8963_MEASUREMENT_TIME_US));
+               }
+               break;
+       case AK8963_MODE_POWERDOWN:
+               err = AKECS_Set_PowerDown(akm);
+               break;
+       default:
+               dev_err(&akm->i2c->dev,
+                       "%s: Unknown mode(%d).", __func__, mode);
+               return -EINVAL;
+       }
+
+       /* wait at least 100us after changing mode */
+       udelay(100);
+
+       return err;
+}
+
+/* This function will block a process until the latest measurement
+ * data is available.
+ */
+static int AKECS_GetData(
+       struct akm8963_data *akm,
+       char *rbuf,
+       int size)
+{
+       int err;
+       err = wait_event_interruptible_timeout(
+                       akm->drdy_wq,
+                       atomic_read(&akm->drdy),
+                       AKM8963_DRDY_TIMEOUT);
+
+       if (err < 0) {
+               dev_err(&akm->i2c->dev,
+                       "%s: wait_event failed (%d).", __func__, err);
+               return -1;
+       }
+       if (!atomic_read(&akm->drdy)) {
+               dev_err(&akm->i2c->dev,
+                       "%s: DRDY is not set.", __func__);
+               return -1;
+       }
+
+       mutex_lock(&akm->sensor_mutex);
+       memcpy(rbuf, akm->sense_data, size);
+       atomic_set(&akm->drdy, 0);
+       mutex_unlock(&akm->sensor_mutex);
+
+       return 0;
+}
+
+static void AKECS_SetYPR(
+       struct akm8963_data *akm,
+       int *rbuf)
+{
+       uint32_t ready;
+       dev_vdbg(&akm->i2c->dev, "AKM8963 %s: flag =0x%X", __func__,
+               rbuf[0]);
+       dev_vdbg(&akm->input->dev, "  Acceleration[LSB]: %6d,%6d,%6d stat=%d",
+               rbuf[1], rbuf[2], rbuf[3], rbuf[4]);
+       dev_vdbg(&akm->input->dev, "  Geomagnetism[LSB]: %6d,%6d,%6d stat=%d",
+               rbuf[5], rbuf[6], rbuf[7], rbuf[8]);
+       dev_vdbg(&akm->input->dev, "  Orientation[YPR] : %6d,%6d,%6d",
+               rbuf[9], rbuf[10], rbuf[11]);
+
+       /* No events are reported */
+       if (!rbuf[0]) {
+               dev_dbg(&akm->i2c->dev, "Don't waste a time.");
+               return;
+       }
+
+       mutex_lock(&akm->val_mutex);
+       ready = (akm->enable_flag & (uint32_t)rbuf[0]);
+       mutex_unlock(&akm->val_mutex);
+
+       /* Report acceleration sensor information */
+       if (ready & ACC_DATA_READY) {
+               input_report_abs(akm->input, ABS_X, rbuf[1]);
+               input_report_abs(akm->input, ABS_Y, rbuf[2]);
+               input_report_abs(akm->input, ABS_Z, rbuf[3]);
+               input_report_abs(akm->input, ABS_THROTTLE, rbuf[4]);
+       }
+       /* Report magnetic vector information */
+       if (ready & MAG_DATA_READY) {
+               input_report_abs(akm->input, ABS_RX, rbuf[5]);
+               input_report_abs(akm->input, ABS_RY, rbuf[6]);
+               input_report_abs(akm->input, ABS_RZ, rbuf[7]);
+               input_report_abs(akm->input, ABS_RUDDER, rbuf[8]);
+       }
+       /* Report orientation sensor information */
+       if (ready & ORI_DATA_READY) {
+               input_report_abs(akm->input, ABS_HAT0X, rbuf[9]);
+               input_report_abs(akm->input, ABS_HAT0Y, rbuf[10]);
+               input_report_abs(akm->input, ABS_HAT1X, rbuf[11]);
+               input_report_abs(akm->input, ABS_HAT1Y, rbuf[4]);
+       }
+
+       input_sync(akm->input);
+}
+
+static int AKECS_GetOpenStatus(
+       struct akm8963_data *akm)
+{
+       return wait_event_interruptible(
+                       akm->open_wq, (atomic_read(&akm->active) != 0));
+}
+
+static int AKECS_GetCloseStatus(
+       struct akm8963_data *akm)
+{
+       return wait_event_interruptible(
+                       akm->open_wq, (atomic_read(&akm->active) <= 0));
+}
+
+static int AKECS_Open(struct inode *inode, struct file *file)
+{
+       file->private_data = s_akm;
+       return nonseekable_open(inode, file);
+}
+
+static int AKECS_Release(struct inode *inode, struct file *file)
+{
+       return 0;
+}
+
+static long
+AKECS_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+{
+       void __user *argp = (void __user *)arg;
+       struct akm8963_data *akm = file->private_data;
+
+       /* NOTE: In this function the size of "char" should be 1-byte. */
+       char i2c_buf[RWBUF_SIZE];               /* for READ/WRITE */
+       int8_t sensor_buf[SENSOR_DATA_SIZE];/* for GETDATA */
+       int32_t ypr_buf[YPR_DATA_SIZE]; /* for SET_YPR */
+       int16_t acc_buf[3];                             /* for GET_ACCEL */
+       int64_t delay[AKM_NUM_SENSORS]; /* for GET_DELAY */
+       char mode;                      /* for SET_MODE*/
+       char layout;            /* for GET_LAYOUT */
+       char outbit;            /* for GET_OUTBIT */
+       int status;                     /* for OPEN/CLOSE_STATUS */
+       int ret = -1;           /* Return value. */
+
+       switch (cmd) {
+       case ECS_IOCTL_READ:
+       case ECS_IOCTL_WRITE:
+               if (argp == NULL) {
+                       dev_err(&akm->i2c->dev, "invalid argument.");
+                       return -EINVAL;
+               }
+               if (copy_from_user(&i2c_buf, argp, sizeof(i2c_buf))) {
+                       dev_err(&akm->i2c->dev, "copy_from_user failed.");
+                       return -EFAULT;
+               }
+               break;
+       case ECS_IOCTL_SET_MODE:
+               if (argp == NULL) {
+                       dev_err(&akm->i2c->dev, "invalid argument.");
+                       return -EINVAL;
+               }
+               if (copy_from_user(&mode, argp, sizeof(mode))) {
+                       dev_err(&akm->i2c->dev, "copy_from_user failed.");
+                       return -EFAULT;
+               }
+               break;
+       case ECS_IOCTL_SET_YPR:
+               if (argp == NULL) {
+                       dev_err(&akm->i2c->dev, "invalid argument.");
+                       return -EINVAL;
+               }
+               if (copy_from_user(&ypr_buf, argp, sizeof(ypr_buf))) {
+                       dev_err(&akm->i2c->dev, "copy_from_user failed.");
+                       return -EFAULT;
+               }
+       case ECS_IOCTL_GETDATA:
+       case ECS_IOCTL_GET_OPEN_STATUS:
+       case ECS_IOCTL_GET_CLOSE_STATUS:
+       case ECS_IOCTL_GET_DELAY:
+       case ECS_IOCTL_GET_LAYOUT:
+       case ECS_IOCTL_GET_OUTBIT:
+       case ECS_IOCTL_GET_ACCEL:
+               /* Just check buffer pointer */
+               if (argp == NULL) {
+                       dev_err(&akm->i2c->dev, "invalid argument.");
+                       return -EINVAL;
+               }
+               break;
+               break;
+       default:
+               break;
+       }
+
+       switch (cmd) {
+       case ECS_IOCTL_READ:
+               dev_vdbg(&akm->i2c->dev, "IOCTL_READ called.");
+               if ((i2c_buf[0] < 1) || (i2c_buf[0] > (RWBUF_SIZE-1))) {
+                       dev_err(&akm->i2c->dev, "invalid argument.");
+                       return -EINVAL;
+               }
+               ret = akm8963_i2c_rxdata(akm->i2c, &i2c_buf[1], i2c_buf[0]);
+               if (ret < 0)
+                       return ret;
+               break;
+       case ECS_IOCTL_WRITE:
+               dev_vdbg(&akm->i2c->dev, "IOCTL_WRITE called.");
+               if ((i2c_buf[0] < 2) || (i2c_buf[0] > (RWBUF_SIZE-1))) {
+                       dev_err(&akm->i2c->dev, "invalid argument.");
+                       return -EINVAL;
+               }
+               ret = akm8963_i2c_txdata(akm->i2c, &i2c_buf[1], i2c_buf[0]);
+               if (ret < 0)
+                       return ret;
+               break;
+       case ECS_IOCTL_SET_MODE:
+               dev_vdbg(&akm->i2c->dev, "IOCTL_SET_MODE called.");
+               ret = AKECS_SetMode(akm, mode);
+               if (ret < 0)
+                       return ret;
+               break;
+       case ECS_IOCTL_GETDATA:
+               dev_vdbg(&akm->i2c->dev, "IOCTL_GETDATA called.");
+               ret = AKECS_GetData(akm, sensor_buf, SENSOR_DATA_SIZE);
+               if (ret < 0)
+                       return ret;
+               break;
+       case ECS_IOCTL_SET_YPR:
+               dev_vdbg(&akm->i2c->dev, "IOCTL_SET_YPR called.");
+               AKECS_SetYPR(akm, ypr_buf);
+               break;
+       case ECS_IOCTL_GET_OPEN_STATUS:
+               dev_vdbg(&akm->i2c->dev, "IOCTL_GET_OPEN_STATUS called.");
+               ret = AKECS_GetOpenStatus(akm);
+               if (ret < 0) {
+                       dev_err(&akm->i2c->dev,
+                               "Get Open returns error (%d).", ret);
+               }
+               break;
+       case ECS_IOCTL_GET_CLOSE_STATUS:
+               dev_vdbg(&akm->i2c->dev, "IOCTL_GET_CLOSE_STATUS called.");
+               ret = AKECS_GetCloseStatus(akm);
+               if (ret < 0) {
+                       dev_err(&akm->i2c->dev,
+                               "Get Close returns error (%d).", ret);
+               }
+               break;
+       case ECS_IOCTL_GET_DELAY:
+               dev_vdbg(&akm->i2c->dev, "IOCTL_GET_DELAY called.");
+               mutex_lock(&akm->val_mutex);
+               delay[0] = akm->delay[0];
+               delay[1] = akm->delay[1];
+               delay[2] = akm->delay[2];
+               mutex_unlock(&akm->val_mutex);
+               break;
+       case ECS_IOCTL_GET_LAYOUT:
+               dev_vdbg(&akm->i2c->dev, "IOCTL_GET_LAYOUT called.");
+               layout = akm->layout;
+               break;
+       case ECS_IOCTL_GET_OUTBIT:
+               dev_vdbg(&akm->i2c->dev, "IOCTL_GET_OUTBIT called.");
+               outbit = akm->outbit;
+               break;
+       case ECS_IOCTL_RESET:
+               ret = AKECS_Reset(akm, akm->rstn);
+               if (ret < 0)
+                       return ret;
+               break;
+       case ECS_IOCTL_GET_ACCEL:
+               dev_vdbg(&akm->i2c->dev, "IOCTL_GET_ACCEL called.");
+               mutex_lock(&akm->accel_mutex);
+               acc_buf[0] = akm->accel_data[0];
+               acc_buf[1] = akm->accel_data[1];
+               acc_buf[2] = akm->accel_data[2];
+               mutex_unlock(&akm->accel_mutex);
+               break;
+       default:
+               return -ENOTTY;
+       }
+
+       switch (cmd) {
+       case ECS_IOCTL_READ:
+               if (copy_to_user(argp, &i2c_buf, i2c_buf[0]+1)) {
+                       dev_err(&akm->i2c->dev, "copy_to_user failed.");
+                       return -EFAULT;
+               }
+               break;
+       case ECS_IOCTL_GETDATA:
+               if (copy_to_user(argp, &sensor_buf, sizeof(sensor_buf))) {
+                       dev_err(&akm->i2c->dev, "copy_to_user failed.");
+                       return -EFAULT;
+               }
+               break;
+       case ECS_IOCTL_GET_OPEN_STATUS:
+       case ECS_IOCTL_GET_CLOSE_STATUS:
+               status = atomic_read(&akm->active);
+               if (copy_to_user(argp, &status, sizeof(status))) {
+                       dev_err(&akm->i2c->dev, "copy_to_user failed.");
+                       return -EFAULT;
+               }
+               break;
+       case ECS_IOCTL_GET_DELAY:
+               if (copy_to_user(argp, &delay, sizeof(delay))) {
+                       dev_err(&akm->i2c->dev, "copy_to_user failed.");
+                       return -EFAULT;
+               }
+               break;
+       case ECS_IOCTL_GET_LAYOUT:
+               if (copy_to_user(argp, &layout, sizeof(layout))) {
+                       dev_err(&akm->i2c->dev, "copy_to_user failed.");
+                       return -EFAULT;
+               }
+               break;
+       case ECS_IOCTL_GET_OUTBIT:
+               if (copy_to_user(argp, &outbit, sizeof(outbit))) {
+                       dev_err(&akm->i2c->dev, "copy_to_user failed.");
+                       return -EFAULT;
+               }
+               break;
+       case ECS_IOCTL_GET_ACCEL:
+               if (copy_to_user(argp, &acc_buf, sizeof(acc_buf))) {
+                       dev_err(&akm->i2c->dev, "copy_to_user failed.");
+                       return -EFAULT;
+               }
+               break;
+       default:
+               break;
+       }
+
+       return 0;
+}
+
+/***** akm sysfs functions ******************************************/
+static int create_device_attributes(
+       struct device *dev,
+       struct device_attribute *attrs)
+{
+       int i;
+       int err = 0;
+
+       for (i = 0 ; NULL != attrs[i].attr.name ; ++i) {
+               err = device_create_file(dev, &attrs[i]);
+               if (0 != err)
+                       break;
+       }
+
+       if (0 != err) {
+               for (; i >= 0 ; --i)
+                       device_remove_file(dev, &attrs[i]);
+       }
+
+       return err;
+}
+
+static void remove_device_attributes(
+       struct device *dev,
+       struct device_attribute *attrs)
+{
+       int i;
+
+       for (i = 0 ; NULL != attrs[i].attr.name ; ++i)
+               device_remove_file(dev, &attrs[i]);
+}
+
+static int create_device_binary_attributes(
+       struct kobject *kobj,
+       struct bin_attribute *attrs)
+{
+       int i;
+       int err = 0;
+
+       err = 0;
+
+       for (i = 0 ; NULL != attrs[i].attr.name ; ++i) {
+               err = sysfs_create_bin_file(kobj, &attrs[i]);
+               if (0 != err)
+                       break;
+       }
+
+       if (0 != err) {
+               for (; i >= 0 ; --i)
+                       sysfs_remove_bin_file(kobj, &attrs[i]);
+       }
+
+       return err;
+}
+
+static void remove_device_binary_attributes(
+       struct kobject *kobj,
+       struct bin_attribute *attrs)
+{
+       int i;
+
+       for (i = 0 ; NULL != attrs[i].attr.name ; ++i)
+               sysfs_remove_bin_file(kobj, &attrs[i]);
+}
+
+static bool get_value_as_int(char const *buf, size_t size, int *value)
+{
+       long tmp;
+
+       if (size == 0)
+               return false;
+
+       /* maybe text format value */
+       if ((buf[0] == '0') && (size > 1)) {
+               if ((buf[1] == 'x') || (buf[1] == 'X')) {
+                       /* hexadecimal format */
+                       if (0 != strict_strtol(buf, 16, &tmp))
+                               return false;
+               } else {
+                       /* octal format */
+                       if (0 != strict_strtol(buf, 8, &tmp))
+                               return false;
+               }
+       } else {
+               /* decimal format */
+               if (0 != strict_strtol(buf, 10, &tmp))
+                       return false;
+       }
+
+       if (tmp > INT_MAX)
+               return false;
+
+       *value = tmp;
+
+       return true;
+}
+
+static bool get_value_as_int64(char const *buf, size_t size, long long *value)
+{
+       long long tmp;
+
+       if (size == 0)
+               return false;
+
+       /* maybe text format value */
+       if ((buf[0] == '0') && (size > 1)) {
+               if ((buf[1] == 'x') || (buf[1] == 'X')) {
+                       /* hexadecimal format */
+                       if (0 != strict_strtoll(buf, 16, &tmp))
+                               return false;
+               } else {
+                       /* octal format */
+                       if (0 != strict_strtoll(buf, 8, &tmp))
+                               return false;
+               }
+       } else {
+               /* decimal format */
+               if (0 != strict_strtoll(buf, 10, &tmp))
+                       return false;
+       }
+
+       if (tmp > LLONG_MAX)
+               return false;
+
+       *value = tmp;
+
+       return true;
+}
+
+/*********************************************************************
+ *
+ * SysFS attribute functions
+ *
+ * directory : /sys/class/compass/akm8963/
+ * files :
+ *  - enable_acc [rw] [t] : enable flag for accelerometer
+ *  - enable_mag [rw] [t] : enable flag for magnetometer
+ *  - enable_ori [rw] [t] : enable flag for orientation
+ *  - delay_acc  [rw] [t] : delay in nanosecond for accelerometer
+ *  - delay_mag  [rw] [t] : delay in nanosecond for magnetometer
+ *  - delay_ori  [rw] [t] : delay in nanosecond for orientation
+ *  - accel         [w]  [b] : accelerometer data
+ *
+ * debug :
+ *  - mode       [w]  [t] : AK8963's mode
+ *  - bdata      [r]  [t] : raw data
+ *  - asa        [r]  [t] : FUSEROM data
+ *
+ * [b] = binary format
+ * [t] = text format
+ */
+
+/***** sysfs enable *************************************************/
+static void akm8963_sysfs_update_active_status(
+       struct akm8963_data *akm)
+{
+       uint32_t en;
+       mutex_lock(&akm->val_mutex);
+       en = akm->enable_flag;
+       mutex_unlock(&akm->val_mutex);
+
+       if (en == 0) {
+               if (atomic_cmpxchg(&akm->active, 1, 0) == 1) {
+                       wake_up(&akm->open_wq);
+                       dev_dbg(akm->class_dev, "Deactivated");
+               }
+       } else {
+               if (atomic_cmpxchg(&akm->active, 0, 1) == 0) {
+                       wake_up(&akm->open_wq);
+                       dev_dbg(akm->class_dev, "Activated");
+               }
+       }
+       dev_dbg(&akm->i2c->dev,
+               "Status updated: enable=0x%X, active=%d",
+               en, atomic_read(&akm->active));
+}
+
+static ssize_t akm8963_sysfs_enable_show(
+       struct akm8963_data *akm, char *buf, int pos)
+{
+       int flag;
+
+       mutex_lock(&akm->val_mutex);
+       flag = ((akm->enable_flag >> pos) & 1);
+       mutex_unlock(&akm->val_mutex);
+
+       return sprintf(buf, "%d\n", flag);
+}
+
+static ssize_t akm8963_sysfs_enable_store(
+       struct akm8963_data *akm, char const *buf, size_t count, int pos)
+{
+       int en = 0;
+
+       if (NULL == buf)
+               return -EINVAL;
+
+       if (0 == count)
+               return 0;
+
+       if (false == get_value_as_int(buf, count, &en))
+               return -EINVAL;
+
+       en = en ? 1 : 0;
+
+       mutex_lock(&akm->val_mutex);
+       akm->enable_flag &= ~(1<<pos);
+       akm->enable_flag |= ((uint32_t)(en))<<pos;
+       mutex_unlock(&akm->val_mutex);
+
+       akm8963_sysfs_update_active_status(akm);
+
+       return count;
+}
+
+/***** Acceleration ***/
+static ssize_t akm8963_enable_acc_show(
+       struct device *dev, struct device_attribute *attr, char *buf)
+{
+       return akm8963_sysfs_enable_show(
+               dev_get_drvdata(dev), buf, ACC_DATA_FLAG);
+}
+static ssize_t akm8963_enable_acc_store(
+       struct device *dev, struct device_attribute *attr,
+       char const *buf, size_t count)
+{
+       return akm8963_sysfs_enable_store(
+               dev_get_drvdata(dev), buf, count, ACC_DATA_FLAG);
+}
+
+/***** Magnetic field ***/
+static ssize_t akm8963_enable_mag_show(
+       struct device *dev, struct device_attribute *attr, char *buf)
+{
+       return akm8963_sysfs_enable_show(
+               dev_get_drvdata(dev), buf, MAG_DATA_FLAG);
+}
+static ssize_t akm8963_enable_mag_store(
+       struct device *dev, struct device_attribute *attr,
+       char const *buf, size_t count)
+{
+       return akm8963_sysfs_enable_store(
+               dev_get_drvdata(dev), buf, count, MAG_DATA_FLAG);
+}
+
+/***** Orientation ***/
+static ssize_t akm8963_enable_ori_show(
+       struct device *dev, struct device_attribute *attr, char *buf)
+{
+       return akm8963_sysfs_enable_show(
+               dev_get_drvdata(dev), buf, ORI_DATA_FLAG);
+}
+static ssize_t akm8963_enable_ori_store(
+       struct device *dev, struct device_attribute *attr,
+       char const *buf, size_t count)
+{
+       return akm8963_sysfs_enable_store(
+               dev_get_drvdata(dev), buf, count, ORI_DATA_FLAG);
+}
+
+/***** sysfs delay **************************************************/
+static ssize_t akm8963_sysfs_delay_show(
+       struct akm8963_data *akm, char *buf, int pos)
+{
+       int64_t val;
+
+       mutex_lock(&akm->val_mutex);
+       val = akm->delay[pos];
+       mutex_unlock(&akm->val_mutex);
+
+       return sprintf(buf, "%lld\n", val);
+}
+
+static ssize_t akm8963_sysfs_delay_store(
+       struct akm8963_data *akm, char const *buf, size_t count, int pos)
+{
+       long long val = 0;
+
+       if (NULL == buf)
+               return -EINVAL;
+
+       if (0 == count)
+               return 0;
+
+       if (false == get_value_as_int64(buf, count, &val))
+               return -EINVAL;
+
+       mutex_lock(&akm->val_mutex);
+       akm->delay[pos] = val;
+       mutex_unlock(&akm->val_mutex);
+
+       return count;
+}
+
+/***** Accelerometer ***/
+static ssize_t akm8963_delay_acc_show(
+       struct device *dev, struct device_attribute *attr, char *buf)
+{
+       return akm8963_sysfs_delay_show(
+               dev_get_drvdata(dev), buf, ACC_DATA_FLAG);
+}
+static ssize_t akm8963_delay_acc_store(
+       struct device *dev, struct device_attribute *attr,
+       char const *buf, size_t count)
+{
+       return akm8963_sysfs_delay_store(
+               dev_get_drvdata(dev), buf, count, ACC_DATA_FLAG);
+}
+
+/***** Magnetic field ***/
+static ssize_t akm8963_delay_mag_show(
+       struct device *dev, struct device_attribute *attr, char *buf)
+{
+       return akm8963_sysfs_delay_show(
+               dev_get_drvdata(dev), buf, MAG_DATA_FLAG);
+}
+static ssize_t akm8963_delay_mag_store(
+       struct device *dev, struct device_attribute *attr,
+       char const *buf, size_t count)
+{
+       return akm8963_sysfs_delay_store(
+               dev_get_drvdata(dev), buf, count, MAG_DATA_FLAG);
+}
+
+/***** Orientation ***/
+static ssize_t akm8963_delay_ori_show(
+       struct device *dev, struct device_attribute *attr, char *buf)
+{
+       return akm8963_sysfs_delay_show(
+               dev_get_drvdata(dev), buf, ORI_DATA_FLAG);
+}
+static ssize_t akm8963_delay_ori_store(
+       struct device *dev, struct device_attribute *attr,
+       char const *buf, size_t count)
+{
+       return akm8963_sysfs_delay_store(
+               dev_get_drvdata(dev), buf, count, ORI_DATA_FLAG);
+}
+
+
+/***** accel (binary) ***/
+static ssize_t akm8963_bin_accel_write(
+       struct file *file,
+       struct kobject *kobj,
+       struct bin_attribute *attr,
+                       char *buf,
+                       loff_t pos,
+                       size_t size)
+{
+       struct device *dev = container_of(kobj, struct device, kobj);
+       struct akm8963_data *akm = dev_get_drvdata(dev);
+       int16_t *accel_data;
+
+       if (size == 0)
+               return 0;
+
+       accel_data = (int16_t*)buf;
+
+       mutex_lock(&akm->accel_mutex);
+       akm->accel_data[0] = accel_data[0];
+       akm->accel_data[1] = accel_data[1];
+       akm->accel_data[2] = accel_data[2];
+       mutex_unlock(&akm->accel_mutex);
+
+       dev_vdbg(&akm->i2c->dev, "accel:%d,%d,%d\n",
+               accel_data[0], accel_data[1], accel_data[2]);
+
+       return size;
+}
+
+
+#ifdef AKM8963_DEBUG_IF
+static ssize_t akm8963_mode_store(
+       struct device *dev, struct device_attribute *attr,
+       char const *buf, size_t count)
+{
+       struct akm8963_data *akm = dev_get_drvdata(dev);
+       int mode = 0;
+
+       if (NULL == buf)
+               return -EINVAL;
+
+       if (0 == count)
+               return 0;
+
+       if (false == get_value_as_int(buf, count, &mode))
+               return -EINVAL;
+
+       if (AKECS_SetMode(akm, mode) < 0)
+               return -EINVAL;
+
+       return 1;
+}
+
+static ssize_t akm8963_bdata_show(
+       struct device *dev, struct device_attribute *attr, char *buf)
+{
+       struct akm8963_data *akm = dev_get_drvdata(dev);
+       char rbuf[SENSOR_DATA_SIZE];
+
+       mutex_lock(&akm->sensor_mutex);
+       memcpy(&rbuf, akm->sense_data, sizeof(rbuf));
+       mutex_unlock(&akm->sensor_mutex);
+
+       return sprintf(buf,
+               "0x%02X,0x%02X,0x%02X,0x%02X,"
+               "0x%02X,0x%02X,0x%02X,0x%02X\n",
+               rbuf[0],rbuf[1],rbuf[2],rbuf[3],
+               rbuf[4],rbuf[5],rbuf[6],rbuf[7]);
+}
+
+static ssize_t akm8963_asa_show(
+       struct device *dev, struct device_attribute *attr, char *buf)
+{
+       struct akm8963_data *akm = dev_get_drvdata(dev);
+       int err;
+       unsigned char asa[3];
+
+       err = AKECS_SetMode(akm, AK8963_MODE_FUSE_ACCESS);
+       if (err < 0)
+               return err;
+
+       asa[0] = AK8963_FUSE_ASAX;
+       err = akm8963_i2c_rxdata(akm->i2c, asa, 3);
+       if (err < 0)
+               return err;
+
+       err = AKECS_SetMode(akm, AK8963_MODE_POWERDOWN);
+       if (err < 0)
+               return err;
+
+       return sprintf(buf, "0x%02X,0x%02X,0x%02X\n",
+               asa[0], asa[1], asa[2]);
+}
+#endif
+
+static struct device_attribute akm8963_attributes[] = {
+       __ATTR(enable_acc, 0660, akm8963_enable_acc_show, akm8963_enable_acc_store),
+       __ATTR(enable_mag, 0660, akm8963_enable_mag_show, akm8963_enable_mag_store),
+       __ATTR(enable_ori, 0660, akm8963_enable_ori_show, akm8963_enable_ori_store),
+       __ATTR(delay_acc,  0660, akm8963_delay_acc_show,  akm8963_delay_acc_store),
+       __ATTR(delay_mag,  0660, akm8963_delay_mag_show,  akm8963_delay_mag_store),
+       __ATTR(delay_ori,  0660, akm8963_delay_ori_show,  akm8963_delay_ori_store),
+#ifdef AKM8963_DEBUG_IF
+       __ATTR(mode,  0220, NULL, akm8963_mode_store),
+       __ATTR(bdata, 0440, akm8963_bdata_show, NULL),
+       __ATTR(asa,   0440, akm8963_asa_show, NULL),
+#endif
+       __ATTR_NULL,
+};
+
+#define __BIN_ATTR(name_, mode_, size_, private_, read_, write_) \
+       { \
+               .attr    = { .name = __stringify(name_), .mode = mode_ }, \
+               .size    = size_, \
+               .private = private_, \
+               .read    = read_, \
+               .write   = write_, \
+       }
+
+#define __BIN_ATTR_NULL \
+       { \
+               .attr   = { .name = NULL }, \
+       }
+
+static struct bin_attribute akm8963_bin_attributes[] = {
+       __BIN_ATTR(accel, 0220, 6, NULL,
+                               NULL, akm8963_bin_accel_write),
+       __BIN_ATTR_NULL
+};
+
+static char const *const compass_class_name = "compass";
+static char const *const akm8963_device_name = "akm8963";
+static char const *const device_link_name = "i2c";
+static dev_t const akm8963_device_dev_t = MKDEV(MISC_MAJOR, 240);
+
+static int create_sysfs_interfaces(struct akm8963_data *akm)
+{
+       int err;
+
+       if (NULL == akm)
+               return -EINVAL;
+
+       err = 0;
+
+       akm->compass = class_create(THIS_MODULE, compass_class_name);
+       if (IS_ERR(akm->compass)) {
+               err = PTR_ERR(akm->compass);
+               goto exit_class_create_failed;
+       }
+
+       akm->class_dev = device_create(
+                                               akm->compass,
+                                               NULL,
+                                               akm8963_device_dev_t,
+                                               akm,
+                                               akm8963_device_name);
+       if (IS_ERR(akm->class_dev)) {
+               err = PTR_ERR(akm->class_dev);
+               goto exit_class_device_create_failed;
+       }
+
+       err = sysfs_create_link(
+                       &akm->class_dev->kobj,
+                       &akm->i2c->dev.kobj,
+                       device_link_name);
+       if (0 > err)
+               goto exit_sysfs_create_link_failed;
+
+       err = create_device_attributes(
+                       akm->class_dev,
+                       akm8963_attributes);
+       if (0 > err)
+               goto exit_device_attributes_create_failed;
+
+       err = create_device_binary_attributes(
+                       &akm->class_dev->kobj,
+                       akm8963_bin_attributes);
+       if (0 > err)
+               goto exit_device_binary_attributes_create_failed;
+
+       return err;
+
+exit_device_binary_attributes_create_failed:
+       remove_device_attributes(akm->class_dev, akm8963_attributes);
+exit_device_attributes_create_failed:
+       sysfs_remove_link(&akm->class_dev->kobj, device_link_name);
+exit_sysfs_create_link_failed:
+       device_destroy(akm->compass, akm8963_device_dev_t);
+exit_class_device_create_failed:
+       akm->class_dev = NULL;
+       class_destroy(akm->compass);
+exit_class_create_failed:
+       akm->compass = NULL;
+       return err;
+}
+
+static void remove_sysfs_interfaces(struct akm8963_data *akm)
+{
+       if (NULL == akm)
+               return;
+
+       if (NULL != akm->class_dev) {
+               remove_device_binary_attributes(
+                       &akm->class_dev->kobj,
+                       akm8963_bin_attributes);
+               remove_device_attributes(
+                       akm->class_dev,
+                       akm8963_attributes);
+               sysfs_remove_link(
+                       &akm->class_dev->kobj,
+                       device_link_name);
+               akm->class_dev = NULL;
+       }
+       if (NULL != akm->compass) {
+               device_destroy(
+                       akm->compass,
+                       akm8963_device_dev_t);
+               class_destroy(akm->compass);
+               akm->compass = NULL;
+       }
+}
+
+
+/***** akm input device functions ***********************************/
+static int akm8963_input_init(
+       struct input_dev **input)
+{
+       int err = 0;
+
+       /* Declare input device */
+       *input = input_allocate_device();
+       if (!*input)
+               return -ENOMEM;
+
+       /* Setup input device */
+       set_bit(EV_ABS, (*input)->evbit);
+       /* Accelerometer (720 x 16G)*/
+       input_set_abs_params(*input, ABS_X,
+                       -11520, 11520, 0, 0);
+       input_set_abs_params(*input, ABS_Y,
+                       -11520, 11520, 0, 0);
+       input_set_abs_params(*input, ABS_Z,
+                       -11520, 11520, 0, 0);
+       input_set_abs_params(*input, ABS_THROTTLE,
+                       0, 3, 0, 0);
+       /* Magnetic field (-81900, 81900) -> limited to 16bit*/
+       input_set_abs_params(*input, ABS_RX,
+                       -32768, 32767, 0, 0);
+       input_set_abs_params(*input, ABS_RY,
+                       -32768, 32767, 0, 0);
+       input_set_abs_params(*input, ABS_RZ,
+                       -32768, 32767, 0, 0);
+       input_set_abs_params(*input, ABS_RUDDER,
+                       0, 3, 0, 0);
+       /* Orientation (yaw:0,360 pitch:-180,180 roll:-90,90) */
+       input_set_abs_params(*input, ABS_HAT0X,
+                       0, 23040, 0, 0);
+       input_set_abs_params(*input, ABS_HAT0Y,
+                       -11520, 11520, 0, 0);
+       input_set_abs_params(*input, ABS_HAT1X,
+                       -5760, 5760, 0, 0);
+       input_set_abs_params(*input, ABS_HAT1Y,
+                       0, 3, 0, 0);
+
+       /* Set name */
+       (*input)->name = "compass";
+
+       /* Register */
+       err = input_register_device(*input);
+       if (err) {
+               input_free_device(*input);
+               return err;
+       }
+
+       return err;
+}
+
+/***** akm functions ************************************************/
+
+static irqreturn_t akm8963_irq(int irq, void *handle)
+{
+       struct akm8963_data *akm = handle;
+       char buffer[SENSOR_DATA_SIZE];
+       int err;
+       
+       memset(buffer, 0, sizeof(buffer));
+       buffer[0] = AK8963_REG_ST1;
+       err = akm8963_i2c_rxdata(akm->i2c, buffer, SENSOR_DATA_SIZE);
+       if (err < 0) {
+               dev_err(&akm->i2c->dev, "%s failed.", __func__);
+               goto work_func_end;
+       }
+       /* Check ST bit */
+       if ((buffer[0] & 0x01) != 0x01) {
+               dev_err(&akm->i2c->dev, "%s ST is not set.", __func__);
+               goto work_func_end;
+       }
+
+       mutex_lock(&akm->sensor_mutex);
+       memcpy(akm->sense_data, buffer, SENSOR_DATA_SIZE);
+       mutex_unlock(&akm->sensor_mutex);
+
+       atomic_set(&akm->drdy, 1);
+       atomic_set(&akm->is_busy, 0);
+       wake_up(&akm->drdy_wq);
+
+work_func_end:
+       return IRQ_HANDLED;
+}
+
+static void akm8963_delayed_work(struct work_struct *work)
+{
+       struct akm8963_data *akm = container_of(
+               work, struct akm8963_data, work.work);
+
+       akm8963_irq(akm->irq, akm);
+}
+
+static int akm8963_suspend(struct device *dev)
+{
+       struct akm8963_data *akm = dev_get_drvdata(dev);
+       dev_dbg(&akm->i2c->dev, "suspended\n");
+
+       return 0;
+}
+
+static int akm8963_resume(struct device *dev)
+{
+       struct akm8963_data *akm = dev_get_drvdata(dev);
+       dev_dbg(&akm->i2c->dev, "resumed\n");
+
+       return 0;
+}
+
+int akm8963_probe(struct i2c_client *client, const struct i2c_device_id *id)
+{
+       struct akm8963_platform_data *pdata;
+       int err = 0;
+       int i;
+
+       dev_dbg(&client->dev, "start probing.");
+
+       if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
+               dev_err(&client->dev, "%s: check_functionality failed.", __func__);
+               err = -ENODEV;
+               goto exit0;
+       }
+
+       /* Allocate memory for driver data */
+       s_akm = kzalloc(sizeof(struct akm8963_data), GFP_KERNEL);
+       if (!s_akm) {
+               dev_err(&client->dev, "%s: memory allocation failed.", __func__);
+               err = -ENOMEM;
+               goto exit1;
+       }
+
+       /***** Set layout information *****/
+       pdata = client->dev.platform_data;
+       if (pdata) {
+               /* Platform data is available. copy its value to local. */
+               s_akm->layout = pdata->layout;
+               s_akm->outbit = pdata->outbit;
+               s_akm->rstn = pdata->gpio_RST;
+       } else {
+               /* Platform data is not available.
+                  Layout and Outbit information should be set by each application. */
+               dev_dbg(&client->dev, "%s: No platform data.", __func__);
+               s_akm->layout = 0;
+               s_akm->outbit = 0;
+               s_akm->rstn = 0;
+       }
+
+       /***** I2C initialization *****/
+       s_akm->i2c = client;
+       /* check connection */
+       err = akm8963_i2c_check_device(client);
+       if (err < 0)
+               goto exit2;
+       /* set client data */
+       i2c_set_clientdata(client, s_akm);
+
+
+       /***** input *****/
+       err = akm8963_input_init(&s_akm->input);
+       if (err) {
+               dev_err(&client->dev,
+                       "%s: input_dev register failed", __func__);
+               goto exit3;
+       }
+       input_set_drvdata(s_akm->input, s_akm);
+
+
+       /**** initialize variables in akm8963_data *****/
+       init_waitqueue_head(&s_akm->drdy_wq);
+       init_waitqueue_head(&s_akm->open_wq);
+
+       mutex_init(&s_akm->sensor_mutex);
+       mutex_init(&s_akm->accel_mutex);
+       mutex_init(&s_akm->val_mutex);
+
+       atomic_set(&s_akm->active, 0);
+       atomic_set(&s_akm->is_busy, 0);
+       atomic_set(&s_akm->drdy, 0);
+       atomic_set(&s_akm->suspend, 0);
+
+       s_akm->enable_flag = 0;
+       for (i=0; i<AKM_NUM_SENSORS; i++)
+               s_akm->delay[i] = -1;
+
+
+       /***** IRQ setup *****/
+       s_akm->irq = client->irq;
+
+
+       if (s_akm->irq == 0) {
+               dev_dbg(&client->dev, "%s: IRQ is not set.", __func__);
+               /* Use timer to notify measurement end */
+               INIT_DELAYED_WORK(&s_akm->work, akm8963_delayed_work);
+       } else {
+               err = request_threaded_irq(
+                               s_akm->irq,
+                               NULL,
+                               akm8963_irq,
+                               IRQF_TRIGGER_HIGH|IRQF_ONESHOT,
+                               dev_name(&client->dev),
+                               s_akm);
+               if (err < 0) {
+                       dev_err(&client->dev,
+                               "%s: request irq failed.", __func__);
+                       goto exit4;
+               }
+       }
+
+       /***** misc *****/
+       err = misc_register(&akm8963_dev);
+       if (err) {
+               dev_err(&client->dev,
+                       "%s: akm8963_dev register failed", __func__);
+               goto exit5;
+       }
+
+       /***** sysfs *****/
+       err = create_sysfs_interfaces(s_akm);
+       if (0 > err) {
+               dev_err(&client->dev,
+                       "%s: create sysfs failed.", __func__);
+               goto exit6;
+       }
+
+       dev_info(&client->dev, "successfully probed.");
+       return 0;
+
+exit6:
+       misc_deregister(&akm8963_dev);
+exit5:
+       if (s_akm->irq)
+               free_irq(s_akm->irq, s_akm);
+exit4:
+       input_unregister_device(s_akm->input);
+exit3:
+exit2:
+       kfree(s_akm);
+exit1:
+exit0:
+       return err;
+}
+
+static int akm8963_remove(struct i2c_client *client)
+{
+       struct akm8963_data *akm = i2c_get_clientdata(client);
+
+       remove_sysfs_interfaces(akm);
+       if (misc_deregister(&akm8963_dev) < 0)
+               dev_err(&client->dev, "misc deregister failed.");
+       if (akm->irq)
+               free_irq(akm->irq, akm);
+       input_unregister_device(akm->input);
+       kfree(akm);
+       dev_info(&client->dev, "successfully removed.");
+       return 0;
+}
+
+static const struct i2c_device_id akm8963_id[] = {
+       {AKM8963_I2C_NAME, 0 },
+       { }
+};
+
+static const struct dev_pm_ops akm8963_pm_ops = {
+       .suspend    = akm8963_suspend,
+       .resume     = akm8963_resume,
+};
+
+static struct i2c_driver akm8963_driver = {
+       .probe          = akm8963_probe,
+       .remove         = akm8963_remove,
+       .id_table       = akm8963_id,
+       .driver = {
+               .name   = AKM8963_I2C_NAME,
+               .pm             = &akm8963_pm_ops,
+       },
+};
+
+static int __init akm8963_init(void)
+{
+       printk(KERN_INFO "AKM8963 compass driver: initialize.");
+       return i2c_add_driver(&akm8963_driver);
+}
+
+static void __exit akm8963_exit(void)
+{
+       printk(KERN_INFO "AKM8963 compass driver: release.");
+       i2c_del_driver(&akm8963_driver);
+}
+
+module_init(akm8963_init);
+module_exit(akm8963_exit);
+
+MODULE_AUTHOR("viral wang <viral_wang@htc.com>");
+MODULE_DESCRIPTION("AKM8963 compass driver");
+MODULE_LICENSE("GPL");
+
diff --git a/include/linux/akm8963.h b/include/linux/akm8963.h
new file mode 100644 (file)
index 0000000..431dfdf
--- /dev/null
@@ -0,0 +1,95 @@
+/*
+ * Definitions for akm8963 compass chip.
+ */
+#ifndef AKM8963_H
+#define AKM8963_H
+
+#include <linux/ioctl.h>
+
+#define AKM8963_I2C_NAME "akm8963"
+
+#define SENSOR_DATA_SIZE       8
+#define YPR_DATA_SIZE          12
+#define RWBUF_SIZE             16
+
+#define ACC_DATA_FLAG          0
+#define MAG_DATA_FLAG          1
+#define ORI_DATA_FLAG          2
+#define AKM_NUM_SENSORS                3
+
+#define ACC_DATA_READY         (1<<(ACC_DATA_FLAG))
+#define MAG_DATA_READY         (1<<(MAG_DATA_FLAG))
+#define ORI_DATA_READY         (1<<(ORI_DATA_FLAG))
+
+/*! \name AK8963 constant definition
+ \anchor AK8963_Def
+ Constant definitions of the AK8963.*/
+#define AK8963_MEASUREMENT_TIME_US     10000
+
+/*! \name AK8963 operation mode
+ \anchor AK8963_Mode
+ Defines an operation mode of the AK8963.*/
+/*! @{*/
+#define AK8963_MODE_SNG_MEASURE        0x01
+#define        AK8963_MODE_SELF_TEST   0x08
+#define        AK8963_MODE_FUSE_ACCESS 0x0F
+#define        AK8963_MODE_POWERDOWN   0x00
+
+/*! @}*/
+
+/*! \name AK8963 register address
+\anchor AK8963_REG
+Defines a register address of the AK8963.*/
+/*! @{*/
+#define AK8963_REG_WIA         0x00
+#define AK8963_REG_INFO                0x01
+#define AK8963_REG_ST1         0x02
+#define AK8963_REG_HXL         0x03
+#define AK8963_REG_HXH         0x04
+#define AK8963_REG_HYL         0x05
+#define AK8963_REG_HYH         0x06
+#define AK8963_REG_HZL         0x07
+#define AK8963_REG_HZH         0x08
+#define AK8963_REG_ST2         0x09
+#define AK8963_REG_CNTL1       0x0A
+#define AK8963_REG_CNTL2       0x0B
+#define AK8963_REG_ASTC                0x0C
+#define AK8963_REG_TS1         0x0D
+#define AK8963_REG_TS2         0x0E
+#define AK8963_REG_I2CDIS      0x0F
+/*! @}*/
+
+/*! \name AK8963 fuse-rom address
+\anchor AK8963_FUSE
+Defines a read-only address of the fuse ROM of the AK8963.*/
+/*! @{*/
+#define AK8963_FUSE_ASAX       0x10
+#define AK8963_FUSE_ASAY       0x11
+#define AK8963_FUSE_ASAZ       0x12
+/*! @}*/
+
+#define AKMIO                   0xA1
+
+/* IOCTLs for AKM library */
+#define ECS_IOCTL_READ              _IOWR(AKMIO, 0x01, char*)
+#define ECS_IOCTL_WRITE             _IOW(AKMIO, 0x02, char*)
+#define ECS_IOCTL_SET_MODE          _IOW(AKMIO, 0x03, short)
+#define ECS_IOCTL_GETDATA           _IOR(AKMIO, 0x04, char[SENSOR_DATA_SIZE])
+#define ECS_IOCTL_SET_YPR           _IOW(AKMIO, 0x05, int[YPR_DATA_SIZE])
+#define ECS_IOCTL_GET_OPEN_STATUS   _IOR(AKMIO, 0x06, int)
+#define ECS_IOCTL_GET_CLOSE_STATUS  _IOR(AKMIO, 0x07, int)
+#define ECS_IOCTL_GET_DELAY         _IOR(AKMIO, 0x08, long long int[AKM_NUM_SENSORS])
+#define ECS_IOCTL_GET_LAYOUT        _IOR(AKMIO, 0x09, char)
+#define ECS_IOCTL_GET_OUTBIT        _IOR(AKMIO, 0x0B, char)
+#define ECS_IOCTL_RESET             _IO(AKMIO, 0x0C)
+#define ECS_IOCTL_GET_ACCEL         _IOR(AKMIO, 0x30, short[3])
+
+struct akm8963_platform_data {
+       char layout;
+       char outbit;
+       int gpio_DRDY;
+       int gpio_RST;
+};
+
+#endif
+