misc: Add ST Micro L3G4200D Gyroscope code
authorDan Murphy <wldm10@motorola.com>
Fri, 28 May 2010 13:53:37 +0000 (08:53 -0500)
committerColin Cross <ccross@android.com>
Wed, 6 Oct 2010 23:33:01 +0000 (16:33 -0700)
This is the initial submission of code for the gyroscope.

Change-Id: Ie56f79dad42b616058ea59cf05508337cc002f18
Signed-off-by: Dan Murphy <wldm10@motorola.com>
drivers/misc/Kconfig
drivers/misc/Makefile
drivers/misc/l3g4200d.c [new file with mode: 0644]
include/linux/l3g4200d.h [new file with mode: 0644]

index 0b5de4bb2fad87931c5ad8a3b5f5b3a4acee17d8..3b32cb25de934d8ffb29b10d6b953d639aa39aa5 100644 (file)
@@ -348,6 +348,14 @@ config SENSORS_KXTF9
         Say yes here if you wish to include the Kionix
         KXTF9 accelerometer driver.
 
+config SENSORS_L3G4200D
+       tristate "ST Micro Gyroscope"
+       default n
+       depends on I2C
+       help
+        Say yes here if you wish to include the ST Micro
+        L3G4200D gyroscope driver.
+
 config SENSORS_MAX9635
        tristate "Maxim Ambient Light Sensor"
        default n
index 9b68f3d5581131f9baf215b3ebb9ef25ff02ffc1..7a9d824005087d02a8d496da89d870c7509892a9 100644 (file)
@@ -43,3 +43,4 @@ obj-$(CONFIG_APANIC)          += apanic.o
 obj-$(CONFIG_SENSORS_AK8975)   += akm8975.o
 obj-$(CONFIG_SENSORS_KXTF9)    += kxtf9.o
 obj-$(CONFIG_SENSORS_MAX9635)  += max9635.o
+obj-$(CONFIG_SENSORS_L3G4200D) += l3g4200d.o
diff --git a/drivers/misc/l3g4200d.c b/drivers/misc/l3g4200d.c
new file mode 100644 (file)
index 0000000..429f9e7
--- /dev/null
@@ -0,0 +1,802 @@
+/*
+ * Copyright (C) 2010 Motorola, Inc.
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA
+ * 02111-1307, USA
+ */
+
+#include <linux/err.h>
+#include <linux/errno.h>
+#include <linux/delay.h>
+#ifdef CONFIG_HAS_EARLYSUSPEND
+#include <linux/earlysuspend.h>
+#endif
+#include <linux/fs.h>
+#include <linux/i2c.h>
+#include <linux/input.h>
+#include <linux/input-polldev.h>
+#include <linux/miscdevice.h>
+#include <linux/slab.h>
+#include <linux/uaccess.h>
+
+#include <linux/l3g4200d.h>
+
+#define DEBUG 1
+
+#define L3G4200D_G_2G                  0x00
+#define L3G4200D_G_4G                  0x10
+#define L3G4200D_G_8G                  0x30
+
+/** Register map */
+#define L3G4200D_WHO_AM_I              0x0f
+#define L3G4200D_CTRL_REG1             0x20
+#define L3G4200D_CTRL_REG2             0x21
+#define L3G4200D_CTRL_REG3             0x22
+#define L3G4200D_CTRL_REG4             0x23
+#define L3G4200D_CTRL_REG5             0x24
+
+#define L3G4200D_REF_DATA_CAP          0x25
+#define L3G4200D_STATUS_REG            0x27
+
+#define L3G4200D_OUT_X_L               0x28
+#define L3G4200D_OUT_X_H               0x29
+#define L3G4200D_OUT_Y_L               0x2a
+#define L3G4200D_OUT_Y_H               0x2b
+#define L3G4200D_OUT_Z_L               0x2c
+#define L3G4200D_OUT_Z_H               0x2d
+
+#define L3G4200D_INTERRUPT_CFG         0x30
+#define L3G4200D_INTERRUPT_SRC         0x31
+#define L3G4200D_INTERRUPT_THRESH_X_H  0x32
+#define L3G4200D_INTERRUPT_THRESH_X_L  0x33
+#define L3G4200D_INTERRUPT_THRESH_Y_H  0x34
+#define L3G4200D_INTERRUPT_THRESH_Y_L  0x35
+#define L3G4200D_INTERRUPT_THRESH_Z_H  0x36
+#define L3G4200D_INTERRUPT_THRESH_Z_L  0x37
+#define L3G4200D_INTERRUPT_DURATION    0x38
+
+/** Maximum polled-device-reported g value */
+#define G_MAX                          8000
+
+#define SHIFT_ADJ_2G                   4
+#define SHIFT_ADJ_4G                   3
+#define SHIFT_ADJ_8G                   2
+
+#define PM_OFF                         0x00
+#define PM_NORMAL                      0x20
+#define ENABLE_ALL_AXES                        0x07
+
+#define FUZZ                           32
+#define FLAT                           32
+#define I2C_RETRY_DELAY                        5
+#define I2C_RETRIES                    5
+#define AUTO_INCREMENT                 0x80
+
+#define ODRHALF                                0x40    /* 0.5Hz output data rate */
+#define ODR1                           0x60    /* 1Hz output data rate */
+#define ODR2                           0x80    /* 2Hz output data rate */
+#define ODR5                           0xA0    /* 5Hz output data rate */
+#define ODR10                          0xC0    /* 10Hz output data rate */
+#define ODR50                          0x00    /* 50Hz output data rate */
+#define ODR100                         0x08    /* 100Hz output data rate */
+#define ODR400                         0x10    /* 400Hz output data rate */
+#define ODR1000                                0x18    /* 1000Hz output data rate */
+
+struct l3g4200d_data {
+       struct i2c_client *client;
+       struct l3g4200d_platform_data *pdata;
+
+       struct delayed_work input_work;
+       struct input_dev *input_dev;
+
+       int hw_initialized;
+       atomic_t enabled;
+       int on_before_suspend;
+
+#ifdef CONFIG_HAS_EARLYSUSPEND
+       struct early_suspend early_suspend;
+#endif
+
+       u8 shift_adj;
+       u8 resume_state[5];
+};
+#ifdef DEBUG
+struct l3g4200d_reg {
+       const char *name;
+       uint8_t reg;
+} l3g4200d_regs[] = {
+       { "WHO_AM_I",           L3G4200D_WHO_AM_I },
+       { "CNTRL_1",            L3G4200D_CTRL_REG1 },
+       { "CNTRL_2",            L3G4200D_CTRL_REG2 },
+       { "CNTRL_3",            L3G4200D_CTRL_REG3 },
+       { "CNTRL_4",            L3G4200D_CTRL_REG4 },
+       { "CNTRL_5",            L3G4200D_CTRL_REG5 },
+       { "REF_DATA_CAP",       L3G4200D_REF_DATA_CAP },
+       { "STATUS_REG",         L3G4200D_STATUS_REG },
+       { "INT_CFG",            L3G4200D_INTERRUPT_CFG },
+       { "INT_SRC",            L3G4200D_INTERRUPT_SRC },
+       { "INT_TH_X_H",         L3G4200D_INTERRUPT_THRESH_X_H },
+       { "INT_TH_X_L",         L3G4200D_INTERRUPT_THRESH_X_L },
+       { "INT_TH_Y_H",         L3G4200D_INTERRUPT_THRESH_Y_H },
+       { "INT_TH_Y_L",         L3G4200D_INTERRUPT_THRESH_Y_L },
+       { "INT_TH_Z_H",         L3G4200D_INTERRUPT_THRESH_Z_H },
+       { "INT_TH_Z_L",         L3G4200D_INTERRUPT_THRESH_Z_L },
+       { "INT_DUR",            L3G4200D_INTERRUPT_DURATION },
+       { "OUT_X_H",            L3G4200D_OUT_X_H },
+       { "OUT_X_L",            L3G4200D_OUT_X_L },
+       { "OUT_Y_H",            L3G4200D_OUT_Y_H },
+       { "OUT_Y_L",            L3G4200D_OUT_Y_L },
+       { "OUT_Z_H",            L3G4200D_OUT_Z_H },
+       { "OUT_Z_L",            L3G4200D_OUT_Z_L },
+};
+#endif
+static uint32_t l3g4200d_debug = 0xff;
+module_param_named(gyro_debug, l3g4200d_debug, uint, 0664);
+
+#ifdef CONFIG_HAS_EARLYSUSPEND
+static void l3g4200d_early_suspend(struct early_suspend *handler);
+static void l3g4200d_late_resume(struct early_suspend *handler);
+#endif
+
+/*
+ * Because misc devices can not carry a pointer from driver register to
+ * open, we keep this global.  This limits the driver to a single instance.
+ */
+struct l3g4200d_data *l3g4200d_misc_data;
+
+static int l3g4200d_i2c_read(struct l3g4200d_data *gyro, u8 * buf, int len)
+{
+       int err;
+       int tries = 0;
+       struct i2c_msg msgs[] = {
+               {
+                       .addr = gyro->client->addr,
+                       .flags = gyro->client->flags & I2C_M_TEN,
+                       .len = 1,
+                       .buf = buf,
+               },
+               {
+                       .addr = gyro->client->addr,
+                       .flags = (gyro->client->flags & I2C_M_TEN) | I2C_M_RD,
+                       .len = len,
+                       .buf = buf,
+               },
+       };
+
+       do {
+               err = i2c_transfer(gyro->client->adapter, msgs, 2);
+               if (err != 2)
+                       msleep_interruptible(I2C_RETRY_DELAY);
+       } while ((err != 2) && (++tries < I2C_RETRIES));
+
+       if (err != 2) {
+               dev_err(&gyro->client->dev, "read transfer error\n");
+               err = -EIO;
+       } else {
+               err = 0;
+       }
+
+       return err;
+}
+
+static int l3g4200d_i2c_write(struct l3g4200d_data *gyro, u8 * buf, int len)
+{
+       int err;
+       int tries = 0;
+       struct i2c_msg msgs[] = {
+               {
+                       .addr = gyro->client->addr,
+                       .flags = gyro->client->flags & I2C_M_TEN,
+                       .len = len + 1,
+                       .buf = buf,
+               },
+       };
+
+       do {
+               err = i2c_transfer(gyro->client->adapter, msgs, 1);
+               if (err != 1)
+                       msleep_interruptible(I2C_RETRY_DELAY);
+       } while ((err != 1) && (++tries < I2C_RETRIES));
+
+       if (err != 1) {
+               dev_err(&gyro->client->dev, "write transfer error\n");
+               err = -EIO;
+       } else {
+               err = 0;
+       }
+
+       return err;
+}
+static int l3g4200d_hw_init(struct l3g4200d_data *gyro)
+{
+       int err = -1;
+       u8 buf[6];
+
+       buf[0] = (AUTO_INCREMENT | L3G4200D_CTRL_REG1);
+       buf[1] = gyro->resume_state[0];
+       buf[2] = gyro->resume_state[1];
+       buf[3] = gyro->resume_state[2];
+       buf[4] = gyro->resume_state[3];
+       buf[5] = gyro->resume_state[4];
+       err = l3g4200d_i2c_write(gyro, buf, 5);
+       if (err < 0)
+               return err;
+
+       gyro->hw_initialized = 1;
+
+       return 0;
+}
+
+static void l3g4200d_device_power_off(struct l3g4200d_data *gyro)
+{
+       int err;
+       u8 buf[2] = {L3G4200D_CTRL_REG1, PM_OFF};
+
+       err = l3g4200d_i2c_write(gyro, buf, 1);
+       if (err < 0)
+               dev_err(&gyro->client->dev, "soft power off failed\n");
+
+       if (gyro->pdata->power_off) {
+               gyro->pdata->power_off();
+               gyro->hw_initialized = 0;
+       }
+}
+
+static int l3g4200d_device_power_on(struct l3g4200d_data *gyro)
+{
+       int err;
+
+       if (gyro->pdata->power_on) {
+               err = gyro->pdata->power_on();
+               if (err < 0)
+                       return err;
+       }
+
+       if (!gyro->hw_initialized) {
+               err = l3g4200d_hw_init(gyro);
+               if (err < 0) {
+                       l3g4200d_device_power_off(gyro);
+                       return err;
+               }
+       }
+
+       return 0;
+}
+
+static int l3g4200d_get_gyro_data(struct l3g4200d_data *gyro, int *xyz)
+{
+       int err = -1;
+       /* Data bytes from hardware xL, xH, yL, yH, zL, zH */
+       u8 gyro_data[6];
+       /* x,y,z hardware data */
+       int hw_d[3] = { 0 };
+
+       gyro_data[0] = (AUTO_INCREMENT | L3G4200D_OUT_X_L);
+       err = l3g4200d_i2c_read(gyro, gyro_data, 6);
+       if (err < 0)
+               return err;
+
+       hw_d[0] = (int) (((gyro_data[1]) << 8) | gyro_data[0]);
+       hw_d[1] = (int) (((gyro_data[3]) << 8) | gyro_data[2]);
+       hw_d[2] = (int) (((gyro_data[5]) << 8) | gyro_data[4]);
+
+       hw_d[0] = (hw_d[0] & 0x8000) ? (hw_d[0] | 0xFFFF0000) : (hw_d[0]);
+       hw_d[1] = (hw_d[1] & 0x8000) ? (hw_d[1] | 0xFFFF0000) : (hw_d[1]);
+       hw_d[2] = (hw_d[2] & 0x8000) ? (hw_d[2] | 0xFFFF0000) : (hw_d[2]);
+
+       hw_d[0] >>= gyro->shift_adj;
+       hw_d[1] >>= gyro->shift_adj;
+       hw_d[2] >>= gyro->shift_adj;
+
+       xyz[0] = ((gyro->pdata->negate_x) ? (-hw_d[gyro->pdata->axis_map_x])
+                 : (hw_d[gyro->pdata->axis_map_x]));
+       xyz[1] = ((gyro->pdata->negate_y) ? (-hw_d[gyro->pdata->axis_map_y])
+                 : (hw_d[gyro->pdata->axis_map_y]));
+       xyz[2] = ((gyro->pdata->negate_z) ? (-hw_d[gyro->pdata->axis_map_z])
+                 : (hw_d[gyro->pdata->axis_map_z]));
+
+       return err;
+}
+
+static void l3g4200d_report_values(struct l3g4200d_data *gyro, int *xyz)
+{
+       input_report_abs(gyro->input_dev, ABS_X, xyz[0]);
+       input_report_abs(gyro->input_dev, ABS_Y, xyz[1]);
+       input_report_abs(gyro->input_dev, ABS_Z, xyz[2]);
+       if (l3g4200d_debug)
+               pr_info("%s: Reporting x: %d, y: %d, z: %d\n",
+               __func__, xyz[0], xyz[1], xyz[2]);
+       input_sync(gyro->input_dev);
+}
+
+static int l3g4200d_enable(struct l3g4200d_data *gyro)
+{
+       int err;
+
+       if (!atomic_cmpxchg(&gyro->enabled, 0, 1)) {
+
+               err = l3g4200d_device_power_on(gyro);
+               if (err < 0) {
+                       atomic_set(&gyro->enabled, 0);
+                       return err;
+               }
+               schedule_delayed_work(&gyro->input_work,
+                       msecs_to_jiffies(gyro->pdata->poll_interval));
+       }
+
+       return 0;
+}
+
+static int l3g4200d_disable(struct l3g4200d_data *gyro)
+{
+       if (atomic_cmpxchg(&gyro->enabled, 1, 0)) {
+               cancel_delayed_work_sync(&gyro->input_work);
+               l3g4200d_device_power_off(gyro);
+       }
+
+       return 0;
+}
+
+static int l3g4200d_misc_open(struct inode *inode, struct file *file)
+{
+       int err;
+       err = nonseekable_open(inode, file);
+       if (err < 0)
+               return err;
+
+       file->private_data = l3g4200d_misc_data;
+
+       return 0;
+}
+
+static int l3g4200d_misc_ioctl(struct inode *inode, struct file *file,
+                               unsigned int cmd, unsigned long arg)
+{
+       void __user *argp = (void __user *)arg;
+       int err = 0;
+       int interval;
+       struct l3g4200d_data *gyro = file->private_data;
+
+       switch (cmd) {
+       case L3G4200D_IOCTL_GET_DELAY:
+               interval = gyro->pdata->poll_interval;
+               if (copy_to_user(argp, &interval, sizeof(interval)))
+                       return -EFAULT;
+               break;
+
+       case L3G4200D_IOCTL_SET_DELAY:
+               if (copy_from_user(&interval, argp, sizeof(interval)))
+                       return -EFAULT;
+               if (interval < 0 || interval > 200)
+                       return -EINVAL;
+
+               gyro->pdata->poll_interval =
+                   max(interval, gyro->pdata->min_interval);
+               /* TODO: if update fails poll is still set */
+               if (err < 0)
+                       return err;
+
+               break;
+
+       case L3G4200D_IOCTL_SET_ENABLE:
+               if (copy_from_user(&interval, argp, sizeof(interval)))
+                       return -EFAULT;
+               if (interval > 1)
+                       return -EINVAL;
+
+               if (interval)
+                       l3g4200d_enable(gyro);
+               else
+                       l3g4200d_disable(gyro);
+
+               break;
+
+       case L3G4200D_IOCTL_GET_ENABLE:
+               interval = atomic_read(&gyro->enabled);
+               if (copy_to_user(argp, &interval, sizeof(interval)))
+                       return -EINVAL;
+
+               break;
+
+       default:
+               return -EINVAL;
+       }
+
+       return 0;
+}
+
+static const struct file_operations l3g4200d_misc_fops = {
+       .owner = THIS_MODULE,
+       .open = l3g4200d_misc_open,
+       .ioctl = l3g4200d_misc_ioctl,
+};
+
+static struct miscdevice l3g4200d_misc_device = {
+       .minor = MISC_DYNAMIC_MINOR,
+       .name = L3G4200D_NAME,
+       .fops = &l3g4200d_misc_fops,
+};
+
+static void l3g4200d_input_work_func(struct work_struct *work)
+{
+       struct l3g4200d_data *gyro = container_of((struct delayed_work *)work,
+                                                 struct l3g4200d_data,
+                                                 input_work);
+       int xyz[3] = { 0 };
+       int err;
+
+       err = l3g4200d_get_gyro_data(gyro, xyz);
+       if (err < 0)
+               dev_err(&gyro->client->dev, "get_acceleration_data failed\n");
+       else
+               l3g4200d_report_values(gyro, xyz);
+
+       schedule_delayed_work(&gyro->input_work,
+                             msecs_to_jiffies(gyro->pdata->poll_interval));
+}
+#ifdef DEBUG
+static ssize_t l3g4200d_registers_show(struct device *dev,
+                                    struct device_attribute *attr, char *buf)
+{
+       struct i2c_client *client = container_of(dev, struct i2c_client,
+                                                dev);
+       struct l3g4200d_data *gyro = i2c_get_clientdata(client);
+       u8 l3g4200d_buf[2];
+       unsigned i, n, reg_count;
+
+       reg_count = sizeof(l3g4200d_regs) / sizeof(l3g4200d_regs[0]);
+       for (i = 0, n = 0; i < reg_count; i++) {
+               l3g4200d_buf[0] = (AUTO_INCREMENT | l3g4200d_regs[i].reg);
+               l3g4200d_i2c_read(gyro, l3g4200d_buf, 1);
+               n += scnprintf(buf + n, PAGE_SIZE - n,
+                              "%-20s = 0x%02X\n",
+                              l3g4200d_regs[i].name, l3g4200d_buf[0]);
+       }
+       return n;
+}
+
+static ssize_t l3g4200d_registers_store(struct device *dev,
+                                     struct device_attribute *attr,
+                                     const char *buf, size_t count)
+{
+       struct i2c_client *client = container_of(dev, struct i2c_client,
+                                                dev);
+       struct l3g4200d_data *gyro = i2c_get_clientdata(client);
+       unsigned i, reg_count, value;
+       int error;
+       u8 l3g4200d_buf[2];
+       char name[30];
+
+       if (count >= 30) {
+               pr_err("%s:input too long\n", __func__);
+               return -1;
+       }
+
+       if (sscanf(buf, "%s %x", name, &value) != 2) {
+               pr_err("%s:unable to parse input\n", __func__);
+               return -1;
+       }
+
+       reg_count = sizeof(l3g4200d_regs) / sizeof(l3g4200d_regs[0]);
+       for (i = 0; i < reg_count; i++) {
+               if (!strcmp(name, l3g4200d_regs[i].name)) {
+                       l3g4200d_buf[0] = (AUTO_INCREMENT | l3g4200d_regs[i].reg);
+                       l3g4200d_buf[1] = value;
+                       error = l3g4200d_i2c_write(gyro, l3g4200d_buf, 2);
+                       if (error) {
+                               pr_err("%s:Failed to write register %s\n",
+                                      __func__, name);
+                               return -1;
+                       }
+                       return count;
+               }
+       }
+       if (!strcmp("Go", name)) {
+               l3g4200d_enable(gyro);
+               return 0;
+       }
+       if (!strcmp("Stop", name)) {
+               l3g4200d_disable(gyro);
+               return 0;
+       }
+       pr_err("%s:no such register %s\n", __func__, name);
+       return -1;
+}
+static DEVICE_ATTR(registers, 0644, l3g4200d_registers_show,
+                  l3g4200d_registers_store);
+#endif
+#ifdef L3G4200D_OPEN_ENABLE
+int l3g4200d_input_open(struct input_dev *input)
+{
+       struct l3g4200d_data *gyro = input_get_drvdata(input);
+
+       return l3g4200d_enable(gyro);
+}
+
+void l3g4200d_input_close(struct input_dev *dev)
+{
+       struct l3g4200d_data *gyro = input_get_drvdata(dev);
+
+       l3g4200d_disable(gyro);
+}
+#endif
+
+static int l3g4200d_validate_pdata(struct l3g4200d_data *gyro)
+{
+       gyro->pdata->poll_interval = max(gyro->pdata->poll_interval,
+                                       gyro->pdata->min_interval);
+
+       if (gyro->pdata->axis_map_x > 2 ||
+           gyro->pdata->axis_map_y > 2 || gyro->pdata->axis_map_z > 2) {
+               dev_err(&gyro->client->dev,
+                       "invalid axis_map value x:%u y:%u z%u\n",
+                       gyro->pdata->axis_map_x, gyro->pdata->axis_map_y,
+                       gyro->pdata->axis_map_z);
+               return -EINVAL;
+       }
+
+       /* Only allow 0 and 1 for negation boolean flag */
+       if (gyro->pdata->negate_x > 1 || gyro->pdata->negate_y > 1 ||
+           gyro->pdata->negate_z > 1) {
+               dev_err(&gyro->client->dev,
+                       "invalid negate value x:%u y:%u z:%u\n",
+                       gyro->pdata->negate_x, gyro->pdata->negate_y,
+                       gyro->pdata->negate_z);
+               return -EINVAL;
+       }
+
+       /* Enforce minimum polling interval */
+       if (gyro->pdata->poll_interval < gyro->pdata->min_interval) {
+               dev_err(&gyro->client->dev, "minimum poll interval violated\n");
+               return -EINVAL;
+       }
+
+       return 0;
+}
+
+static int l3g4200d_input_init(struct l3g4200d_data *gyro)
+{
+       int err;
+
+       INIT_DELAYED_WORK(&gyro->input_work, l3g4200d_input_work_func);
+
+       gyro->input_dev = input_allocate_device();
+       if (!gyro->input_dev) {
+               err = -ENOMEM;
+               dev_err(&gyro->client->dev, "input device allocate failed\n");
+               goto err0;
+       }
+
+#ifdef L3G4200D_OPEN_ENABLE
+       gyro->input_dev->open = l3g4200d_input_open;
+       gyro->input_dev->close = l3g4200d_input_close;
+#endif
+
+       input_set_drvdata(gyro->input_dev, gyro);
+
+       set_bit(EV_ABS, gyro->input_dev->evbit);
+
+       input_set_abs_params(gyro->input_dev, ABS_X, -G_MAX, G_MAX, FUZZ, FLAT);
+       input_set_abs_params(gyro->input_dev, ABS_Y, -G_MAX, G_MAX, FUZZ, FLAT);
+       input_set_abs_params(gyro->input_dev, ABS_Z, -G_MAX, G_MAX, FUZZ, FLAT);
+
+       gyro->input_dev->name = "gyroscope";
+
+       err = input_register_device(gyro->input_dev);
+       if (err) {
+               dev_err(&gyro->client->dev,
+                       "unable to register input polled device %s\n",
+                       gyro->input_dev->name);
+               goto err1;
+       }
+
+       return 0;
+
+err1:
+       input_free_device(gyro->input_dev);
+err0:
+       return err;
+}
+
+static void l3g4200d_input_cleanup(struct l3g4200d_data *gyro)
+{
+       input_unregister_device(gyro->input_dev);
+       input_free_device(gyro->input_dev);
+}
+
+static int l3g4200d_probe(struct i2c_client *client,
+                          const struct i2c_device_id *id)
+{
+       struct l3g4200d_data *gyro;
+       int err = -1;
+
+       pr_err("%s:Enter\n", __func__);
+       if (client->dev.platform_data == NULL) {
+               dev_err(&client->dev, "platform data is NULL. exiting.\n");
+               err = -ENODEV;
+               goto err0;
+       }
+
+       if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
+               dev_err(&client->dev, "client not i2c capable\n");
+               err = -ENODEV;
+               goto err0;
+       }
+
+       gyro = kzalloc(sizeof(*gyro), GFP_KERNEL);
+       if (gyro == NULL) {
+               dev_err(&client->dev,
+                       "failed to allocate memory for module data\n");
+               err = -ENOMEM;
+               goto err0;
+       }
+
+       gyro->client = client;
+
+       gyro->pdata = kzalloc(sizeof(*gyro->pdata), GFP_KERNEL);
+       if (gyro->pdata == NULL)
+               goto err1;
+
+       memcpy(gyro->pdata, client->dev.platform_data, sizeof(*gyro->pdata));
+
+       err = l3g4200d_validate_pdata(gyro);
+       if (err < 0) {
+               dev_err(&client->dev, "failed to validate platform data\n");
+               goto err2;
+       }
+
+       i2c_set_clientdata(client, gyro);
+
+       memset(gyro->resume_state, 0, ARRAY_SIZE(gyro->resume_state));
+
+       gyro->resume_state[0] = gyro->pdata->ctrl_reg_1;
+       gyro->resume_state[1] = gyro->pdata->ctrl_reg_2;
+       gyro->resume_state[2] = gyro->pdata->ctrl_reg_3;
+       gyro->resume_state[3] = gyro->pdata->ctrl_reg_4;
+       gyro->resume_state[4] = gyro->pdata->ctrl_reg_5;
+
+       /* As default, do not report information */
+       atomic_set(&gyro->enabled, 0);
+
+#ifdef CONFIG_HAS_EARLYSUSPEND
+       gyro->early_suspend.level = EARLY_SUSPEND_LEVEL_BLANK_SCREEN + 1;
+       gyro->early_suspend.suspend = l3g4200d_early_suspend;
+       gyro->early_suspend.resume = l3g4200d_late_resume;
+       register_early_suspend(&gyro->early_suspend);
+#endif
+
+       err = l3g4200d_input_init(gyro);
+       if (err < 0)
+               goto err3;
+
+       l3g4200d_misc_data = gyro;
+
+       err = misc_register(&l3g4200d_misc_device);
+       if (err < 0) {
+               dev_err(&client->dev, "l3g4200d_device register failed\n");
+               goto err4;
+       }
+#ifdef DEBUG
+       err = device_create_file(&client->dev, &dev_attr_registers);
+       if (err < 0)
+               pr_err("%s:File device creation failed: %d\n", __func__, err);
+#endif
+
+       pr_err("%s:Gyro probed\n", __func__);
+       return 0;
+
+err4:
+       l3g4200d_input_cleanup(gyro);
+err3:
+       if (gyro->pdata->exit)
+               gyro->pdata->exit();
+err2:
+       kfree(gyro->pdata);
+err1:
+       kfree(gyro);
+err0:
+       return err;
+}
+
+static int __devexit l3g4200d_remove(struct i2c_client *client)
+{
+       struct l3g4200d_data *gyro = i2c_get_clientdata(client);
+
+#ifdef DEBUG
+       device_remove_file(&client->dev, &dev_attr_registers);
+#endif
+       misc_deregister(&l3g4200d_misc_device);
+       l3g4200d_input_cleanup(gyro);
+       l3g4200d_device_power_off(gyro);
+       if (gyro->pdata->exit)
+               gyro->pdata->exit();
+       kfree(gyro->pdata);
+       kfree(gyro);
+
+       return 0;
+}
+
+static int l3g4200d_resume(struct i2c_client *client)
+{
+       struct l3g4200d_data *gyro = i2c_get_clientdata(client);
+
+       if (gyro->on_before_suspend)
+               return l3g4200d_enable(gyro);
+       return 0;
+}
+
+static int l3g4200d_suspend(struct i2c_client *client, pm_message_t mesg)
+{
+       struct l3g4200d_data *gyro = i2c_get_clientdata(client);
+
+       gyro->on_before_suspend = atomic_read(&gyro->enabled);
+       return l3g4200d_disable(gyro);
+}
+
+#ifdef CONFIG_HAS_EARLYSUSPEND
+static void l3g4200d_early_suspend(struct early_suspend *handler)
+{
+       struct l3g4200d_data *gyro;
+
+       gyro = container_of(handler, struct l3g4200d_data, early_suspend);
+       l3g4200d_suspend(gyro->client, PMSG_SUSPEND);
+}
+
+static void l3g4200d_late_resume(struct early_suspend *handler)
+{
+       struct l3g4200d_data *gyro;
+
+       gyro = container_of(handler, struct l3g4200d_data, early_suspend);
+       l3g4200d_resume(gyro->client);
+}
+#endif
+
+static const struct i2c_device_id l3g4200d_id[] = {
+       {L3G4200D_NAME, 0},
+       {},
+};
+
+MODULE_DEVICE_TABLE(i2c, l3g4200d_id);
+
+static struct i2c_driver l3g4200d_driver = {
+       .driver = {
+                  .name = L3G4200D_NAME,
+                  },
+       .probe = l3g4200d_probe,
+       .remove = __devexit_p(l3g4200d_remove),
+#ifndef CONFIG_HAS_EARLYSUSPEND
+       .resume = l3g4200d_resume,
+       .suspend = l3g4200d_suspend,
+#endif
+       .id_table = l3g4200d_id,
+};
+
+static int __init l3g4200d_init(void)
+{
+       pr_info("L3G4200D gyroscope driver\n");
+       return i2c_add_driver(&l3g4200d_driver);
+}
+
+static void __exit l3g4200d_exit(void)
+{
+       i2c_del_driver(&l3g4200d_driver);
+       return;
+}
+
+module_init(l3g4200d_init);
+module_exit(l3g4200d_exit);
+
+MODULE_DESCRIPTION("l3g4200d gyroscope driver");
+MODULE_AUTHOR("Dan Murphy D.Murphy@Motorola.com");
+MODULE_LICENSE("GPL");
diff --git a/include/linux/l3g4200d.h b/include/linux/l3g4200d.h
new file mode 100644 (file)
index 0000000..e6d5faa
--- /dev/null
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2010 Motorola, Inc.
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA
+ * 02111-1307, USA
+ */
+
+#ifndef __L3G4200D_H__
+#define __L3G4200D_H__
+
+#include <linux/ioctl.h>  /* For IOCTL macros */
+
+#define L3G4200D_NAME  "l3g4200d"
+
+#define L3G4200D_IOCTL_BASE 77
+/** The following define the IOCTL command values via the ioctl macros */
+#define L3G4200D_IOCTL_SET_DELAY       _IOW(L3G4200D_IOCTL_BASE, 0, int)
+#define L3G4200D_IOCTL_GET_DELAY       _IOR(L3G4200D_IOCTL_BASE, 1, int)
+#define L3G4200D_IOCTL_SET_ENABLE      _IOW(L3G4200D_IOCTL_BASE, 2, int)
+#define L3G4200D_IOCTL_GET_ENABLE      _IOR(L3G4200D_IOCTL_BASE, 3, int)
+
+#ifdef __KERNEL__
+
+struct l3g4200d_platform_data {
+       int poll_interval;
+       int min_interval;
+
+       u8 ctrl_reg_1;
+       u8 ctrl_reg_2;
+       u8 ctrl_reg_3;
+       u8 ctrl_reg_4;
+       u8 ctrl_reg_5;
+
+       u8 int_config;
+       u8 int_source;
+
+       u8 int_th_x_h;
+       u8 int_th_x_l;
+       u8 int_th_y_h;
+       u8 int_th_y_l;
+       u8 int_th_z_h;
+       u8 int_th_z_l;
+       u8 int_duration;
+
+       u8 g_range;
+
+       u8 axis_map_x;
+       u8 axis_map_y;
+       u8 axis_map_z;
+
+       u8 negate_x;
+       u8 negate_y;
+       u8 negate_z;
+
+       void (*exit)(void);
+       int (*power_on)(void);
+       int (*power_off)(void);
+
+};
+#endif /* __KERNEL__ */
+
+#endif  /* __L3G4200D_H__ */
+