From 20ea8cc19b0681505f5ab78f54b41697fc3f147b Mon Sep 17 00:00:00 2001 From: Dan Murphy Date: Fri, 28 May 2010 08:53:37 -0500 Subject: [PATCH] misc: Add ST Micro L3G4200D Gyroscope code This is the initial submission of code for the gyroscope. Change-Id: Ie56f79dad42b616058ea59cf05508337cc002f18 Signed-off-by: Dan Murphy --- drivers/misc/Kconfig | 8 + drivers/misc/Makefile | 1 + drivers/misc/l3g4200d.c | 802 +++++++++++++++++++++++++++++++++++++++ include/linux/l3g4200d.h | 74 ++++ 4 files changed, 885 insertions(+) create mode 100644 drivers/misc/l3g4200d.c create mode 100644 include/linux/l3g4200d.h diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig index 0b5de4bb2fad..3b32cb25de93 100644 --- a/drivers/misc/Kconfig +++ b/drivers/misc/Kconfig @@ -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 diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile index 9b68f3d55811..7a9d82400508 100644 --- a/drivers/misc/Makefile +++ b/drivers/misc/Makefile @@ -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 index 000000000000..429f9e79db95 --- /dev/null +++ b/drivers/misc/l3g4200d.c @@ -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 +#include +#include +#ifdef CONFIG_HAS_EARLYSUSPEND +#include +#endif +#include +#include +#include +#include +#include +#include +#include + +#include + +#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 index 000000000000..e6d5faa06d9c --- /dev/null +++ b/include/linux/l3g4200d.h @@ -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 /* 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__ */ + -- 2.34.1