diff --git a/drivers/input/misc/Kconfig b/drivers/input/misc/Kconfig index d279a638ed0..cc5cf9521ee 100644 --- a/drivers/input/misc/Kconfig +++ b/drivers/input/misc/Kconfig @@ -546,4 +546,14 @@ config BOSCH_BMA150 If you say yes here you get support for Bosch Sensortec's acceleration sensors SMB380/BMA150. +config INPUT_LSM303DLH + tristate "ST LSM303DLH 3-axis accelerometer and 3-axis magnetometer" + depends on I2C + depends on INPUT_POLLDEV + default n + help + This driver provides support for the LSM303DLH chip which includes a + 3-axis accelerometer and a 3-axis magnetometer. + endif + diff --git a/drivers/input/misc/Makefile b/drivers/input/misc/Makefile index c5c4639dfd5..fdd43a60f9a 100644 --- a/drivers/input/misc/Makefile +++ b/drivers/input/misc/Makefile @@ -51,4 +51,4 @@ obj-$(CONFIG_INPUT_YEALINK) += yealink.o obj-$(CONFIG_PMIC8058_OTHC) += pmic8058-othc.o obj-$(CONFIG_INPUT_PMIC8058_VIBRA_MEMLESS) += pmic8058-vib-memless.o obj-$(CONFIG_BOSCH_BMA150) += bma150.o - +obj-$(CONFIG_INPUT_LSM303DLH) += lsm303dlh_mag_sysfs.o lsm303dlh_acc_sysfs.o diff --git a/drivers/input/misc/lsm303dlh_acc_sysfs.c b/drivers/input/misc/lsm303dlh_acc_sysfs.c new file mode 100644 index 00000000000..80e4af61e61 --- /dev/null +++ b/drivers/input/misc/lsm303dlh_acc_sysfs.c @@ -0,0 +1,1452 @@ +/******************** (C) COPYRIGHT 2010 STMicroelectronics ******************** +* +* File Name : lsm303dlh_acc.c +* Authors : MSH - Motion Mems BU - Application Team +* : Carmine Iascone (carmine.iascone@st.com) +* : Matteo Dameno (matteo.dameno@st.com) +* : Both authors are willing to be considered the contact +* : and update points for the driver. +* Version : V 1.7.0 +* Date : 2011/03/02 +* Description : LSM303DLH 6D module sensor API +* +* +* 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. +* +* THE PRESENT SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES +* OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, FOR THE SOLE +* PURPOSE TO SUPPORT YOUR APPLICATION DEVELOPMENT. +* AS A RESULT, STMICROELECTRONICS SHALL NOT BE HELD LIABLE FOR ANY DIRECT, +* INDIRECT OR CONSEQUENTIAL DAMAGES WITH RESPECT TO ANY CLAIMS ARISING FROM THE +* CONTENT OF SUCH SOFTWARE AND/OR THE USE MADE BY CUSTOMERS OF THE CODING +* INFORMATION CONTAINED HEREIN IN CONNECTION WITH THEIR PRODUCTS. +* +****************************************************************************** + + Revision 1.5.0 2010/09/05: + lsm303dlh_acc_device_power_off now calling CTRL_REG1 to set power off + manages 2 interrupts; + correction to update_g_range; + modified_get_acceleration_data function + modified update_odr function and lsm303dlh_acc_odr_table; + don't support ioclt; + supports sysfs; + Revision 1.6.0 2011/02/28 + checks for availability of interrupts pins + Revision 1.7.0 2011/03/02 + adds self test enable/disable +******************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define DEBUG 1 + +#define G_MAX 8000 + +#define SENSITIVITY_2G 1 /** mg/LSB */ +#define SENSITIVITY_4G 2 /** mg/LSB */ +#define SENSITIVITY_8G 4 /** mg/LSB */ + +#define AXISDATA_REG 0x28 +#define WHOAMI_LSM303DLH_ACC 0x32 /* Expctd content for WAI */ + +/* CONTROL REGISTERS */ +#define WHO_AM_I 0x0F /* WhoAmI register */ +#define CTRL_REG1 0x20 /* */ +#define CTRL_REG2 0x21 /* */ +#define CTRL_REG3 0x22 /* */ +#define CTRL_REG4 0x23 /* */ +#define CTRL_REG5 0x24 /* */ + +#define INT_CFG1 0x30 /* interrupt 1 config */ +#define INT_SRC1 0x31 /* interrupt 1 source */ +#define INT_THS1 0x32 /* interrupt 1 threshold */ +#define INT_DUR1 0x33 /* interrupt 1 duration */ + +#define INT_CFG2 0x34 /* interrupt 2 config */ +#define INT_SRC2 0x35 /* interrupt 2 source */ +#define INT_THS2 0x36 /* interrupt 2 threshold */ +#define INT_DUR2 0x37 /* interrupt 2 duration */ +/* end CONTROL REGISTRES */ + +#define LSM303DLH_ACC_ENABLE_ALL_AXES 0x07 +#define LSM303DLH_SELFTEST_EN 0x02 +#define LSM303DLH_SELFTEST_DIS 0x00 +#define LSM303DLH_SELFTEST_POS 0x00 +#define LSM303DLH_SELFTEST_NEG 0x08 + +/* Accelerometer output data rate */ +#define LSM303DLH_ACC_ODRHALF 0x40 /* 0.5Hz output data rate */ +#define LSM303DLH_ACC_ODR1 0x60 /* 1Hz output data rate */ +#define LSM303DLH_ACC_ODR2 0x80 /* 2Hz output data rate */ +#define LSM303DLH_ACC_ODR5 0xA0 /* 5Hz output data rate */ +#define LSM303DLH_ACC_ODR10 0xC0 /* 10Hz output data rate */ +#define LSM303DLH_ACC_ODR50 0x00 /* 50Hz output data rate */ +#define LSM303DLH_ACC_ODR100 0x08 /* 100Hz output data rate */ +#define LSM303DLH_ACC_ODR400 0x10 /* 400Hz output data rate */ +#define LSM303DLH_ACC_ODR1000 0x18 /* 1000Hz output data rate */ + +#define FUZZ 0 +#define FLAT 0 +#define I2C_RETRY_DELAY 5 +#define I2C_RETRIES 5 +#define I2C_AUTO_INCREMENT 0x80 + +/* RESUME STATE INDICES */ +#define RES_CTRL_REG1 0 +#define RES_CTRL_REG2 1 +#define RES_CTRL_REG3 2 +#define RES_CTRL_REG4 3 +#define RES_CTRL_REG5 4 +#define RES_REFERENCE 5 + +#define RES_INT_CFG1 6 +#define RES_INT_THS1 7 +#define RES_INT_DUR1 8 +#define RES_INT_CFG2 9 +#define RES_INT_THS2 10 +#define RES_INT_DUR2 11 + +#define RESUME_ENTRIES 12 +/* end RESUME STATE INDICES */ + + +static struct { + unsigned int cutoff_ms; + unsigned int mask; +} lsm303dlh_acc_odr_table[] = { + {1, LSM303DLH_ACC_PM_NORMAL | LSM303DLH_ACC_ODR1000}, + {3, LSM303DLH_ACC_PM_NORMAL | LSM303DLH_ACC_ODR400}, + {10, LSM303DLH_ACC_PM_NORMAL | LSM303DLH_ACC_ODR100}, + {20, LSM303DLH_ACC_PM_NORMAL | LSM303DLH_ACC_ODR50}, + /* low power settings, max low pass filter cut-off freq */ + {100, LSM303DLH_ACC_ODR10 | LSM303DLH_ACC_ODR1000}, + {200, LSM303DLH_ACC_ODR5 | LSM303DLH_ACC_ODR1000}, + {5000, LSM303DLH_ACC_ODR2 | LSM303DLH_ACC_ODR1000 }, + {1000, LSM303DLH_ACC_ODR1 | LSM303DLH_ACC_ODR1000 }, + {2000, LSM303DLH_ACC_ODRHALF | LSM303DLH_ACC_ODR1000 }, +}; + +struct lsm303dlh_acc_data { + struct i2c_client *client; + struct lsm303dlh_acc_platform_data *pdata; + + struct mutex lock; + struct delayed_work input_work; + + struct input_dev *input_dev; + + int hw_initialized; + /* hw_working=-1 means not tested yet */ + int hw_working; + int selftest_enabled; + + atomic_t enabled; + int on_before_suspend; + + u8 sensitivity; + + u8 resume_state[RESUME_ENTRIES]; + + int irq1; + struct work_struct irq1_work; + struct workqueue_struct *irq1_work_queue; + int irq2; + struct work_struct irq2_work; + struct workqueue_struct *irq2_work_queue; + +#ifdef DEBUG + u8 reg_addr; +#endif +}; + + +static int lsm303dlh_acc_i2c_read(struct lsm303dlh_acc_data *acc, + u8 *buf, int len) +{ + int err; + int tries = 0; + + struct i2c_msg msgs[] = { + { + .addr = acc->client->addr, + .flags = acc->client->flags & I2C_M_TEN, + .len = 1, + .buf = buf, + }, + { + .addr = acc->client->addr, + .flags = (acc->client->flags & I2C_M_TEN) | I2C_M_RD, + .len = len, + .buf = buf, + }, + }; + + do { + err = i2c_transfer(acc->client->adapter, msgs, 2); + if (err != 2) + msleep_interruptible(I2C_RETRY_DELAY); + } while ((err != 2) && (++tries < I2C_RETRIES)); + + if (err != 2) { + dev_err(&acc->client->dev, "read transfer error\n"); + return -EIO; + } + return 0; +} + +static int lsm303dlh_acc_i2c_write(struct lsm303dlh_acc_data *acc, + u8 *buf, int len) +{ + int err; + int tries = 0; + struct i2c_msg msgs[] = { + { + .addr = acc->client->addr, + .flags = acc->client->flags & I2C_M_TEN, + .len = len + 1, + .buf = buf, + }, + }; + + do { + err = i2c_transfer(acc->client->adapter, msgs, 1); + if (err != 1) + msleep_interruptible(I2C_RETRY_DELAY); + } while ((err != 1) && (++tries < I2C_RETRIES)); + + if (err != 1) { + dev_err(&acc->client->dev, "write transfer error\n"); + return -EIO; + } + return err; +} + +static int lsm303dlh_acc_hw_init(struct lsm303dlh_acc_data *acc) +{ + int err = -1; + u8 buf[6]; + + printk(KERN_INFO "%s: hw init start\n", LSM303DLH_ACC_DEV_NAME); + + buf[0] = WHO_AM_I; + err = lsm303dlh_acc_i2c_read(acc, buf, 1); + if (err < 0){ + dev_warn(&acc->client->dev, "Error reading WHO_AM_I: is device " + "available/working?\n"); + goto err_firstread; + } else + acc->hw_working = 1; + if (buf[0] != WHOAMI_LSM303DLH_ACC) { + dev_err(&acc->client->dev, + "device unknown. Expected: 0x%x," + " Replies: 0x%x\n", WHOAMI_LSM303DLH_ACC, buf[0]); + err = -1; /* choose the right coded error */ + goto err_unknown_device; + } + + buf[0] = CTRL_REG1; + buf[1] = acc->resume_state[RES_CTRL_REG1]; + err = lsm303dlh_acc_i2c_write(acc, buf, 1); + if (err < 0) + goto err_resume_state; + + buf[0] = (I2C_AUTO_INCREMENT | INT_THS1); + buf[1] = acc->resume_state[RES_INT_THS1]; + buf[2] = acc->resume_state[RES_INT_DUR1]; + err = lsm303dlh_acc_i2c_write(acc, buf, 2); + if (err < 0) + goto err_resume_state; + buf[0] = INT_CFG1; + buf[1] = acc->resume_state[RES_INT_CFG1]; + err = lsm303dlh_acc_i2c_write(acc, buf, 1); + if (err < 0) + goto err_resume_state; + + buf[0] = (I2C_AUTO_INCREMENT | INT_THS2); + buf[1] = acc->resume_state[RES_INT_THS2]; + buf[2] = acc->resume_state[RES_INT_DUR2]; + err = lsm303dlh_acc_i2c_write(acc, buf, 2); + if (err < 0) + goto err_resume_state; + buf[0] = INT_CFG2; + buf[1] = acc->resume_state[RES_INT_CFG2]; + err = lsm303dlh_acc_i2c_write(acc, buf, 1); + if (err < 0) + goto err_resume_state; + + buf[0] = (I2C_AUTO_INCREMENT | CTRL_REG2); + buf[1] = acc->resume_state[RES_CTRL_REG2]; + buf[2] = acc->resume_state[RES_CTRL_REG3]; + buf[3] = acc->resume_state[RES_CTRL_REG4]; + buf[4] = acc->resume_state[RES_CTRL_REG5]; + err = lsm303dlh_acc_i2c_write(acc, buf, 4); + if (err < 0) + goto err_resume_state; + + acc->hw_initialized = 1; + printk(KERN_INFO "%s: hw init done\n", LSM303DLH_ACC_DEV_NAME); + return 0; + +err_firstread: + acc->hw_working = 0; +err_unknown_device: +err_resume_state: + acc->hw_initialized = 0; + dev_err(&acc->client->dev, "hw init error 0x%x,0x%x: %d\n", buf[0], + buf[1], err); + return err; +} + +static void lsm303dlh_acc_device_power_off(struct lsm303dlh_acc_data *acc) +{ + int err; + u8 buf[2] = { CTRL_REG1, LSM303DLH_ACC_PM_OFF }; + + err = lsm303dlh_acc_i2c_write(acc, buf, 1); + if (err < 0) + dev_err(&acc->client->dev, "soft power off failed: %d\n", err); + + if (acc->pdata->power_off) { + if(acc->pdata->gpio_int1 >= 0) + disable_irq_nosync(acc->irq1); + if(acc->pdata->gpio_int2 >= 0) + disable_irq_nosync(acc->irq2); + acc->pdata->power_off(); + acc->hw_initialized = 0; + } + if (acc->hw_initialized) { + if(acc->pdata->gpio_int1 >= 0) + disable_irq_nosync(acc->irq1); + if(acc->pdata->gpio_int2 >= 0) + disable_irq_nosync(acc->irq2); + acc->hw_initialized = 0; + } + +} + +static int lsm303dlh_acc_device_power_on(struct lsm303dlh_acc_data *acc) +{ + int err = -1; + + if (acc->pdata->power_on) { + err = acc->pdata->power_on(); + if (err < 0) { + dev_err(&acc->client->dev, + "power_on failed: %d\n", err); + return err; + } + if(acc->pdata->gpio_int1 >= 0) + enable_irq(acc->irq1); + if(acc->pdata->gpio_int2 >= 0) + enable_irq(acc->irq2); + } + + if (!acc->hw_initialized) { + err = lsm303dlh_acc_hw_init(acc); + if (acc->hw_working == 1 && err < 0) { + lsm303dlh_acc_device_power_off(acc); + return err; + } + } + + if (acc->hw_initialized) { + if(acc->pdata->gpio_int1 >= 0) + enable_irq(acc->irq1); + if(acc->pdata->gpio_int2 >= 0) + enable_irq(acc->irq2); + } + return 0; +} + +static irqreturn_t lsm303dlh_acc_isr1(int irq, void *dev) +{ + struct lsm303dlh_acc_data *acc = dev; + + disable_irq_nosync(irq); + queue_work(acc->irq1_work_queue, &acc->irq1_work); + printk(KERN_INFO "%s: isr1 queued\n", LSM303DLH_ACC_DEV_NAME); + + return IRQ_HANDLED; +} + +static irqreturn_t lsm303dlh_acc_isr2(int irq, void *dev) +{ + struct lsm303dlh_acc_data *acc = dev; + + disable_irq_nosync(irq); + queue_work(acc->irq2_work_queue, &acc->irq2_work); + printk(KERN_INFO "%s: isr2 queued\n", LSM303DLH_ACC_DEV_NAME); + + return IRQ_HANDLED; +} + +static void lsm303dlh_acc_irq1_work_func(struct work_struct *work) +{ + + struct lsm303dlh_acc_data *acc = + container_of(work, struct lsm303dlh_acc_data, irq1_work); + /* TODO add interrupt service procedure. + ie:lsm303dlh_acc_get_int1_source(acc); */ + ; + /* */ + printk(KERN_INFO "%s: IRQ1 triggered\n", LSM303DLH_ACC_DEV_NAME); +exit: + enable_irq(acc->irq1); +} + +static void lsm303dlh_acc_irq2_work_func(struct work_struct *work) +{ + + struct lsm303dlh_acc_data *acc = + container_of(work, struct lsm303dlh_acc_data, irq2_work); + /* TODO add interrupt service procedure. + ie:lsm303dlh_acc_get_tap_source(acc); */ + ; + /* */ + + printk(KERN_INFO "%s: IRQ2 triggered\n", LSM303DLH_ACC_DEV_NAME); +exit: + enable_irq(acc->irq2); +} + +int lsm303dlh_acc_update_g_range(struct lsm303dlh_acc_data *acc, u8 new_g_range) +{ + int err = -1; + + u8 sensitivity; + u8 buf[2]; + u8 updated_val; + u8 init_val; + u8 new_val; + u8 mask = LSM303DLH_ACC_FS_MASK; + + switch (new_g_range) { + case LSM303DLH_ACC_G_2G: + sensitivity = SENSITIVITY_2G; + break; + case LSM303DLH_ACC_G_4G: + sensitivity = SENSITIVITY_4G; + break; + case LSM303DLH_ACC_G_8G: + sensitivity = SENSITIVITY_8G; + break; + default: + dev_err(&acc->client->dev, "invalid g range requested: %u\n", + new_g_range); + return -EINVAL; + } + + if (atomic_read(&acc->enabled)) { + /* Set configuration register 4, which contains g range setting + * NOTE: this is a straight overwrite because this driver does + * not use any of the other configuration bits in this + * register. Should this become untrue, we will have to read + * out the value and only change the relevant bits --XX---- + * (marked by X) */ + buf[0] = CTRL_REG4; + err = lsm303dlh_acc_i2c_read(acc, buf, 1); + if (err < 0) + goto error; + init_val = buf[0]; + acc->resume_state[RES_CTRL_REG4] = init_val; + new_val = new_g_range; + updated_val = ((mask & new_val) | ((~mask) & init_val)); + buf[1] = updated_val; + buf[0] = CTRL_REG4; + err = lsm303dlh_acc_i2c_write(acc, buf, 1); + if (err < 0) + goto error; + acc->resume_state[RES_CTRL_REG4] = updated_val; + acc->sensitivity = sensitivity; + } + + + return err; +error: + dev_err(&acc->client->dev, "update g range failed 0x%x,0x%x: %d\n", + buf[0], buf[1], err); + + return err; +} + +int lsm303dlh_acc_update_odr(struct lsm303dlh_acc_data *acc, + int poll_interval_ms) +{ + int err = -1; + int i; + u8 config[2]; + + /* Following, looks for the longest possible odr interval scrolling the + * odr_table vector from the end (shortest interval) backward (longest + * interval), to support the poll_interval requested by the system. + * It must be the longest interval lower then the poll interval.*/ + for (i = ARRAY_SIZE(lsm303dlh_acc_odr_table) - 1; i >= 0; i--) { + if (lsm303dlh_acc_odr_table[i].cutoff_ms <= poll_interval_ms) + break; + } + config[1] = lsm303dlh_acc_odr_table[i].mask; + + config[1] |= LSM303DLH_ACC_ENABLE_ALL_AXES; + + /* If device is currently enabled, we need to write new + * configuration out to it */ + if (atomic_read(&acc->enabled)) { + config[0] = CTRL_REG1; + err = lsm303dlh_acc_i2c_write(acc, config, 1); + if (err < 0) + goto error; + acc->resume_state[RES_CTRL_REG1] = config[1]; + } + + return err; + +error: + dev_err(&acc->client->dev, "update odr failed 0x%x,0x%x: %d\n", + config[0], config[1], err); + + return err; +} + + + +static int lsm303dlh_acc_register_write(struct lsm303dlh_acc_data *acc, u8 *buf, + u8 reg_address, u8 new_value) +{ + int err = -1; + + /* Sets configuration register at reg_address + * NOTE: this is a straight overwrite */ + buf[0] = reg_address; + buf[1] = new_value; + err = lsm303dlh_acc_i2c_write(acc, buf, 1); + if (err < 0) + return err; + + return err; +} + +static int lsm303dlh_acc_register_read(struct lsm303dlh_acc_data *acc, u8 *buf, + u8 reg_address) +{ + + int err = -1; + buf[0] = (reg_address); + err = lsm303dlh_acc_i2c_read(acc, buf, 1); + return err; +} + +static int lsm303dlh_acc_register_update(struct lsm303dlh_acc_data *acc, + u8 *buf, u8 reg_address, u8 mask, u8 new_bit_values) +{ + int err = -1; + u8 init_val; + u8 updated_val; + err = lsm303dlh_acc_register_read(acc, buf, reg_address); + if (!(err < 0)) { + init_val = buf[1]; + updated_val = ((mask & new_bit_values) | ((~mask) & init_val)); + err = lsm303dlh_acc_register_write(acc, buf, reg_address, + updated_val); + } + return err; +} + + + +static int lsm303dlh_acc_selftest(struct lsm303dlh_acc_data *acc, u8 enable) +{ + int err = -1; + u8 buf[2]={0x00,0x00}; + char reg_address, mask, bit_values; + + reg_address = CTRL_REG4; + mask = 0x0A; + if (enable > 0) + bit_values = LSM303DLH_SELFTEST_EN | + LSM303DLH_SELFTEST_POS; + else + bit_values = LSM303DLH_SELFTEST_DIS | + LSM303DLH_SELFTEST_POS; + if (atomic_read(&acc->enabled)) { + mutex_lock(&acc->lock); + err = lsm303dlh_acc_register_update(acc, buf, reg_address, + mask, bit_values); + acc->selftest_enabled = enable; + mutex_unlock(&acc->lock); + if (err < 0) + return err; + acc->resume_state[RES_CTRL_REG4] = ((mask & bit_values) | + ( ~mask & acc->resume_state[RES_CTRL_REG4])); + } + return err; +} + +static int lsm303dlh_acc_get_acceleration_data(struct lsm303dlh_acc_data *acc, + int *xyz) +{ + int err = -1; + /* Data bytes from hardware xL, xH, yL, yH, zL, zH */ + u8 acc_data[6]; + /* x,y,z hardware data */ + s16 hw_d[3] = { 0 }; + + acc_data[0] = (I2C_AUTO_INCREMENT | AXISDATA_REG); + err = lsm303dlh_acc_i2c_read(acc, acc_data, 6); + if (err < 0) + return err; + + hw_d[0] = (((s16) ((acc_data[1] << 8) | acc_data[0])) >> 4); + hw_d[1] = (((s16) ((acc_data[3] << 8) | acc_data[2])) >> 4); + hw_d[2] = (((s16) ((acc_data[5] << 8) | acc_data[4])) >> 4); + + hw_d[0] = hw_d[0] * acc->sensitivity; + hw_d[1] = hw_d[1] * acc->sensitivity; + hw_d[2] = hw_d[2] * acc->sensitivity; + + + xyz[0] = ((acc->pdata->negate_x) ? (-hw_d[acc->pdata->axis_map_x]) + : (hw_d[acc->pdata->axis_map_x])); + xyz[1] = ((acc->pdata->negate_y) ? (-hw_d[acc->pdata->axis_map_y]) + : (hw_d[acc->pdata->axis_map_y])); + xyz[2] = ((acc->pdata->negate_z) ? (-hw_d[acc->pdata->axis_map_z]) + : (hw_d[acc->pdata->axis_map_z])); + + #ifdef DEBUG + /* + printk(KERN_INFO "%s read x=%d, y=%d, z=%d\n", + LSM303DLH_ACC_DEV_NAME, xyz[0], xyz[1], xyz[2]); + */ + #endif + return err; +} + +static void lsm303dlh_acc_report_values(struct lsm303dlh_acc_data *acc, + int *xyz) +{ + input_report_abs(acc->input_dev, ABS_X, xyz[0]); + input_report_abs(acc->input_dev, ABS_Y, xyz[1]); + input_report_abs(acc->input_dev, ABS_Z, xyz[2]); + input_sync(acc->input_dev); +} + +static int lsm303dlh_acc_enable(struct lsm303dlh_acc_data *acc) +{ + int err; + + if (!atomic_cmpxchg(&acc->enabled, 0, 1)) { + err = lsm303dlh_acc_device_power_on(acc); + if (err < 0) { + atomic_set(&acc->enabled, 0); + return err; + } + schedule_delayed_work(&acc->input_work, + msecs_to_jiffies(acc->pdata->poll_interval)); + } + + return 0; +} + +static int lsm303dlh_acc_disable(struct lsm303dlh_acc_data *acc) +{ + if (atomic_cmpxchg(&acc->enabled, 1, 0)) { + cancel_delayed_work_sync(&acc->input_work); + lsm303dlh_acc_device_power_off(acc); + } + + return 0; +} + + +static ssize_t read_single_reg(struct device *dev, char *buf, u8 reg) +{ + ssize_t ret; + struct lsm303dlh_acc_data *acc = dev_get_drvdata(dev); + int rc = 0; + + u8 data = reg; + rc = lsm303dlh_acc_i2c_read(acc, &data, 1); + /*TODO: error need to be managed */ + ret = sprintf(buf, "0x%02x\n", data); + return ret; + +} + +static int write_reg(struct device *dev, const char *buf, u8 reg) +{ + int rc = 0; + struct lsm303dlh_acc_data *acc = dev_get_drvdata(dev); + u8 x[2]; + unsigned long val; + + if (strict_strtoul(buf, 16, &val)) + return -EINVAL; + + x[0] = reg; + x[1] = val; + rc = lsm303dlh_acc_i2c_write(acc, x, 1); + /*TODO: error need to be managed */ + return rc; +} + +static ssize_t attr_get_polling_rate(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int val; + struct lsm303dlh_acc_data *acc = dev_get_drvdata(dev); + mutex_lock(&acc->lock); + val = acc->pdata->poll_interval; + mutex_unlock(&acc->lock); + return sprintf(buf, "%d\n", val); +} + +static ssize_t attr_set_polling_rate(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + struct lsm303dlh_acc_data *acc = dev_get_drvdata(dev); + unsigned long interval_ms; + + if (strict_strtoul(buf, 10, &interval_ms)) + return -EINVAL; + if (!interval_ms) + return -EINVAL; + mutex_lock(&acc->lock); + acc->pdata->poll_interval = interval_ms; + lsm303dlh_acc_update_odr(acc, interval_ms); + mutex_unlock(&acc->lock); + return size; +} + +static ssize_t attr_get_range(struct device *dev, + struct device_attribute *attr, char *buf) +{ + char val; + struct lsm303dlh_acc_data *acc = dev_get_drvdata(dev); + char range = 2; + mutex_lock(&acc->lock); + val = acc->pdata->g_range ; + switch (val) { + case LSM303DLH_ACC_G_2G: + range = 2; + break; + case LSM303DLH_ACC_G_4G: + range = 4; + break; + case LSM303DLH_ACC_G_8G: + range = 8; + break; + } + mutex_unlock(&acc->lock); + return sprintf(buf, "%d\n", range); +} + +static ssize_t attr_set_range(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + struct lsm303dlh_acc_data *acc = dev_get_drvdata(dev); + unsigned long val; + if (strict_strtoul(buf, 10, &val)) + return -EINVAL; + mutex_lock(&acc->lock); + acc->pdata->g_range = val; + lsm303dlh_acc_update_g_range(acc, val); + mutex_unlock(&acc->lock); + return size; +} + +static ssize_t attr_get_enable(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct lsm303dlh_acc_data *acc = dev_get_drvdata(dev); + int val = atomic_read(&acc->enabled); + return sprintf(buf, "%d\n", val); +} + +static ssize_t attr_set_enable(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + struct lsm303dlh_acc_data *acc = dev_get_drvdata(dev); + unsigned long val; + + if (strict_strtoul(buf, 10, &val)) + return -EINVAL; + + if (val) + lsm303dlh_acc_enable(acc); + else + lsm303dlh_acc_disable(acc); + + return size; +} + +static ssize_t attr_get_selftest(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int val; + struct lsm303dlh_acc_data *acc = dev_get_drvdata(dev); + mutex_lock(&acc->lock); + val = acc->selftest_enabled; + mutex_unlock(&acc->lock); + return sprintf(buf, "%d\n", val); +} + +static ssize_t attr_set_selftest(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + struct lsm303dlh_acc_data *acc = dev_get_drvdata(dev); + unsigned long val; + + if (strict_strtoul(buf, 10, &val)) + return -EINVAL; + + lsm303dlh_acc_selftest(acc, val); + + return size; +} + +static ssize_t attr_set_intconfig1(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + return write_reg(dev, buf, INT_CFG1); +} + +static ssize_t attr_get_intconfig1(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return read_single_reg(dev, buf, INT_CFG1); +} + +static ssize_t attr_set_duration1(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + return write_reg(dev, buf, INT_DUR1); +} + +static ssize_t attr_get_duration1(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return read_single_reg(dev, buf, INT_DUR1); +} + +static ssize_t attr_set_thresh1(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + return write_reg(dev, buf, INT_THS1); +} + +static ssize_t attr_get_thresh1(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return read_single_reg(dev, buf, INT_THS1); +} + +static ssize_t attr_get_source1(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return read_single_reg(dev, buf, INT_SRC1); +} + +static ssize_t attr_set_intconfig2(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + return write_reg(dev, buf, INT_CFG2); +} + +static ssize_t attr_get_intconfig2(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return read_single_reg(dev, buf, INT_CFG2); +} + +static ssize_t attr_set_duration2(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + return write_reg(dev, buf, INT_DUR2); +} + +static ssize_t attr_get_duration2(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return read_single_reg(dev, buf, INT_DUR2); +} + +static ssize_t attr_set_thresh2(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + return write_reg(dev, buf, INT_THS2); +} + +static ssize_t attr_get_thresh2(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return read_single_reg(dev, buf, INT_THS2); +} +static ssize_t attr_get_source2(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return read_single_reg(dev, buf, INT_SRC2); +} + + + + +#ifdef DEBUG +/* PAY ATTENTION: These DEBUG funtions don't manage resume_state */ +static ssize_t attr_reg_set(struct device *dev, struct device_attribute *attr, + const char *buf, size_t size) +{ + int rc; + struct lsm303dlh_acc_data *acc = dev_get_drvdata(dev); + u8 x[2]; + unsigned long val; + + if (strict_strtoul(buf, 16, &val)) + return -EINVAL; + mutex_lock(&acc->lock); + x[0] = acc->reg_addr; + mutex_unlock(&acc->lock); + x[1] = val; + rc = lsm303dlh_acc_i2c_write(acc, x, 1); + /*TODO: error need to be managed */ + return size; +} + +static ssize_t attr_reg_get(struct device *dev, struct device_attribute *attr, + char *buf) +{ + ssize_t ret; + struct lsm303dlh_acc_data *acc = dev_get_drvdata(dev); + int rc; + u8 data; + + mutex_lock(&acc->lock); + data = acc->reg_addr; + mutex_unlock(&acc->lock); + rc = lsm303dlh_acc_i2c_read(acc, &data, 1); + /*TODO: error need to be managed */ + ret = sprintf(buf, "0x%02x\n", data); + return ret; +} + +static ssize_t attr_addr_set(struct device *dev, struct device_attribute *attr, + const char *buf, size_t size) +{ + struct lsm303dlh_acc_data *acc = dev_get_drvdata(dev); + unsigned long val; + if (strict_strtoul(buf, 16, &val)) + return -EINVAL; + mutex_lock(&acc->lock); + acc->reg_addr = val; + mutex_unlock(&acc->lock); + return size; +} +#endif + +static struct device_attribute attributes[] = { + + __ATTR(pollrate_ms, 0666, attr_get_polling_rate, attr_set_polling_rate), + __ATTR(range, 0666, attr_get_range, attr_set_range), + __ATTR(enable_device, 0666, attr_get_enable, attr_set_enable), + __ATTR(enable_selftest, 0666, attr_get_selftest, attr_set_selftest), + __ATTR(int1_config, 0666, attr_get_intconfig1, attr_set_intconfig1), + __ATTR(int1_duration, 0666, attr_get_duration1, attr_set_duration1), + __ATTR(int1_threshold, 0666, attr_get_thresh1, attr_set_thresh1), + __ATTR(int1_source, 0444, attr_get_source1, NULL), + __ATTR(int2_config, 0666, attr_get_intconfig2, attr_set_intconfig2), + __ATTR(int2_duration, 0666, attr_get_duration2, attr_set_duration2), + __ATTR(int2_threshold, 0666, attr_get_thresh2, attr_set_thresh2), + __ATTR(int2_source, 0444, attr_get_source2, NULL), +#ifdef DEBUG + __ATTR(reg_value, 0600, attr_reg_get, attr_reg_set), + __ATTR(reg_addr, 0200, NULL, attr_addr_set), +#endif +}; + +static int create_sysfs_interfaces(struct device *dev) +{ + int i; + for (i = 0; i < ARRAY_SIZE(attributes); i++) + if (device_create_file(dev, attributes + i)) + goto error; + return 0; + +error: + for ( ; i >= 0; i--) + device_remove_file(dev, attributes + i); + dev_err(dev, "%s:Unable to create interface\n", __func__); + return -1; +} + +static int remove_sysfs_interfaces(struct device *dev) +{ + int i; + for (i = 0; i < ARRAY_SIZE(attributes); i++) + device_remove_file(dev, attributes + i); + return 0; +} + +static void lsm303dlh_acc_input_work_func(struct work_struct *work) +{ + struct lsm303dlh_acc_data *acc; + + int xyz[3] = { 0 }; + int err; + + acc = container_of((struct delayed_work *)work, + struct lsm303dlh_acc_data, input_work); + + mutex_lock(&acc->lock); + err = lsm303dlh_acc_get_acceleration_data(acc, xyz); + if (err < 0) + dev_err(&acc->client->dev, "get_acceleration_data failed\n"); + else + lsm303dlh_acc_report_values(acc, xyz); + + schedule_delayed_work(&acc->input_work, + msecs_to_jiffies(acc->pdata->poll_interval)); + mutex_unlock(&acc->lock); +} + +int lsm303dlh_acc_input_open(struct input_dev *input) +{ + struct lsm303dlh_acc_data *acc = input_get_drvdata(input); + + return lsm303dlh_acc_enable(acc); +} + +void lsm303dlh_acc_input_close(struct input_dev *dev) +{ + struct lsm303dlh_acc_data *acc = input_get_drvdata(dev); + + lsm303dlh_acc_disable(acc); +} + +static int lsm303dlh_acc_validate_pdata(struct lsm303dlh_acc_data *acc) +{ + acc->pdata->poll_interval = max(acc->pdata->poll_interval, + acc->pdata->min_interval); + + if (acc->pdata->axis_map_x > 2 || + acc->pdata->axis_map_y > 2 || acc->pdata->axis_map_z > 2) { + dev_err(&acc->client->dev, + "invalid axis_map value x:%u y:%u z%u\n", + acc->pdata->axis_map_x, acc->pdata->axis_map_y, + acc->pdata->axis_map_z); + return -EINVAL; + } + + /* Only allow 0 and 1 for negation boolean flag */ + if (acc->pdata->negate_x > 1 || acc->pdata->negate_y > 1 || + acc->pdata->negate_z > 1) { + dev_err(&acc->client->dev, + "invalid negate value x:%u y:%u z:%u\n", + acc->pdata->negate_x, acc->pdata->negate_y, + acc->pdata->negate_z); + return -EINVAL; + } + + /* Enforce minimum polling interval */ + if (acc->pdata->poll_interval < acc->pdata->min_interval) { + dev_err(&acc->client->dev, "minimum poll interval violated\n"); + return -EINVAL; + } + + return 0; +} + +static int lsm303dlh_acc_input_init(struct lsm303dlh_acc_data *acc) +{ + int err; + + INIT_DELAYED_WORK(&acc->input_work, lsm303dlh_acc_input_work_func); + acc->input_dev = input_allocate_device(); + if (!acc->input_dev) { + err = -ENOMEM; + dev_err(&acc->client->dev, "input device allocation failed\n"); + goto err0; + } + + acc->input_dev->open = lsm303dlh_acc_input_open; + acc->input_dev->close = lsm303dlh_acc_input_close; + acc->input_dev->name = LSM303DLH_ACC_DEV_NAME; + acc->input_dev->id.bustype = BUS_I2C; + acc->input_dev->dev.parent = &acc->client->dev; + + input_set_drvdata(acc->input_dev, acc); + + set_bit(EV_ABS, acc->input_dev->evbit); + /* next is used for interruptA sources data if the case */ + set_bit(ABS_MISC, acc->input_dev->absbit); + /* next is used for interruptB sources data if the case */ + set_bit(ABS_WHEEL, acc->input_dev->absbit); + + input_set_abs_params(acc->input_dev, ABS_X, -G_MAX, G_MAX, FUZZ, FLAT); + input_set_abs_params(acc->input_dev, ABS_Y, -G_MAX, G_MAX, FUZZ, FLAT); + input_set_abs_params(acc->input_dev, ABS_Z, -G_MAX, G_MAX, FUZZ, FLAT); + /* next is used for interruptA sources data if the case */ + input_set_abs_params(acc->input_dev, ABS_MISC, INT_MIN, INT_MAX, 0, 0); + /* next is used for interruptB sources data if the case */ + input_set_abs_params(acc->input_dev, ABS_WHEEL, INT_MIN, INT_MAX, 0, 0); + + + err = input_register_device(acc->input_dev); + if (err) { + dev_err(&acc->client->dev, + "unable to register input polled device %s\n", + acc->input_dev->name); + goto err1; + } + + return 0; + +err1: + input_free_device(acc->input_dev); +err0: + return err; +} + +static void lsm303dlh_acc_input_cleanup(struct lsm303dlh_acc_data *acc) +{ + input_unregister_device(acc->input_dev); + input_free_device(acc->input_dev); +} + +static int lsm303dlh_acc_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct lsm303dlh_acc_data *acc; + int err = -1; + //int tempvalue; + + pr_info("%s: probe start.\n", LSM303DLH_ACC_DEV_NAME); + + if (client->dev.platform_data == NULL) { + dev_err(&client->dev, "platform data is NULL. exiting.\n"); + err = -ENODEV; + goto exit_check_functionality_failed; + } + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + dev_err(&client->dev, "client not i2c capable\n"); + err = -ENODEV; + goto exit_check_functionality_failed; + } + + /* + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_BYTE | + I2C_FUNC_SMBUS_BYTE_DATA | + I2C_FUNC_SMBUS_WORD_DATA)) { + dev_err(&client->dev, "client not smb-i2c capable:2\n"); + err = -EIO; + goto exit_check_functionality_failed; + } + + + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_I2C_BLOCK)){ + dev_err(&client->dev, "client not smb-i2c capable:3\n"); + err = -EIO; + goto exit_check_functionality_failed; + } + */ + + acc = kzalloc(sizeof(struct lsm303dlh_acc_data), GFP_KERNEL); + if (acc == NULL) { + err = -ENOMEM; + dev_err(&client->dev, + "failed to allocate memory for module data: " + "%d\n", err); + goto exit_check_functionality_failed; + } + + + mutex_init(&acc->lock); + mutex_lock(&acc->lock); + + acc->client = client; + i2c_set_clientdata(client, acc); + + acc->pdata = kmalloc(sizeof(*acc->pdata), GFP_KERNEL); + if (acc->pdata == NULL) { + err = -ENOMEM; + dev_err(&client->dev, + "failed to allocate memory for pdata: %d\n", + err); + goto err_mutexunlock; + } + + memcpy(acc->pdata, client->dev.platform_data, sizeof(*acc->pdata)); + + err = lsm303dlh_acc_validate_pdata(acc); + if (err < 0) { + dev_err(&client->dev, "failed to validate platform data\n"); + goto exit_kfree_pdata; + } + + + if (acc->pdata->init) { + err = acc->pdata->init(); + if (err < 0) { + dev_err(&client->dev, "init failed: %d\n", err); + goto err_pdata_init; + } + } + + if(acc->pdata->gpio_int1 >= 0){ + acc->irq1 = gpio_to_irq(acc->pdata->gpio_int1); + printk(KERN_INFO "%s: %s has set irq1 to irq: %d " + "mapped on gpio:%d\n", + LSM303DLH_ACC_DEV_NAME, __func__, acc->irq1, + acc->pdata->gpio_int1); + } + + if(acc->pdata->gpio_int2 >= 0){ + acc->irq2 = gpio_to_irq(acc->pdata->gpio_int2); + printk(KERN_INFO "%s: %s has set irq2 to irq: %d " + "mapped on gpio:%d\n", + LSM303DLH_ACC_DEV_NAME, __func__, acc->irq2, + acc->pdata->gpio_int2); + } + + memset(acc->resume_state, 0, ARRAY_SIZE(acc->resume_state)); + + acc->resume_state[RES_CTRL_REG1] = LSM303DLH_ACC_ENABLE_ALL_AXES; + acc->resume_state[RES_CTRL_REG2] = 0x00; + acc->resume_state[RES_CTRL_REG3] = 0x00; + acc->resume_state[RES_CTRL_REG4] = 0x00; + acc->resume_state[RES_CTRL_REG5] = 0x00; + acc->resume_state[RES_REFERENCE] = 0x00; + + acc->resume_state[RES_INT_CFG1] = 0x00; + acc->resume_state[RES_INT_THS1] = 0x00; + acc->resume_state[RES_INT_DUR1] = 0x00; + acc->resume_state[RES_INT_CFG2] = 0x00; + acc->resume_state[RES_INT_THS2] = 0x00; + acc->resume_state[RES_INT_DUR2] = 0x00; + + err = lsm303dlh_acc_device_power_on(acc); + if (err < 0) { + dev_err(&client->dev, "power on failed: %d\n", err); + goto err_pdata_init; + } + + atomic_set(&acc->enabled, 1); + + err = lsm303dlh_acc_update_g_range(acc, acc->pdata->g_range); + if (err < 0) { + dev_err(&client->dev, "update_g_range failed\n"); + goto err_power_off; + } + + err = lsm303dlh_acc_update_odr(acc, acc->pdata->poll_interval); + if (err < 0) { + dev_err(&client->dev, "update_odr failed\n"); + goto err_power_off; + } + + err = lsm303dlh_acc_input_init(acc); + if (err < 0) { + dev_err(&client->dev, "input init failed\n"); + goto err_power_off; + } + + + err = create_sysfs_interfaces(&client->dev); + if (err < 0) { + dev_err(&client->dev, + "device LSM303DLH_ACC_DEV_NAME " + "sysfs register failed\n"); + goto err_input_cleanup; + } + + + lsm303dlh_acc_device_power_off(acc); + + /* As default, do not report information */ + atomic_set(&acc->enabled, 0); + + if(acc->pdata->gpio_int1 >= 0){ + INIT_WORK(&acc->irq1_work, lsm303dlh_acc_irq1_work_func); + acc->irq1_work_queue = + create_singlethread_workqueue("lsm303dlh_acc_wq1"); + if (!acc->irq1_work_queue) { + err = -ENOMEM; + dev_err(&client->dev, + "cannot create work queue1: %d\n", err); + goto err_remove_sysfs_int; + } + err = request_irq(acc->irq1, lsm303dlh_acc_isr1, + IRQF_TRIGGER_RISING, "lsm303dlh_acc_irq1", acc); + if (err < 0) { + dev_err(&client->dev, "request irq1 failed: %d\n", err); + goto err_destoyworkqueue1; + } + disable_irq_nosync(acc->irq1); + } + + if(acc->pdata->gpio_int2 >= 0){ + INIT_WORK(&acc->irq2_work, lsm303dlh_acc_irq2_work_func); + acc->irq2_work_queue = + create_singlethread_workqueue("lsm303dlh_acc_wq2"); + if (!acc->irq2_work_queue) { + err = -ENOMEM; + dev_err(&client->dev, + "cannot create work queue2: %d\n", err); + goto err_free_irq1; + } + err = request_irq(acc->irq2, lsm303dlh_acc_isr2, + IRQF_TRIGGER_RISING, "lsm303dlh_acc_irq2", acc); + if (err < 0) { + dev_err(&client->dev, "request irq2 failed: %d\n", err); + goto err_destoyworkqueue2; + } + disable_irq_nosync(acc->irq2); + } + + + + mutex_unlock(&acc->lock); + + dev_info(&client->dev, "%s: probed\n", LSM303DLH_ACC_DEV_NAME); + + return 0; + +err_destoyworkqueue2: + if(acc->pdata->gpio_int2 >= 0) + destroy_workqueue(acc->irq2_work_queue); +err_free_irq1: + free_irq(acc->irq1, acc); +err_destoyworkqueue1: + if(acc->pdata->gpio_int1 >= 0) + destroy_workqueue(acc->irq1_work_queue); +err_remove_sysfs_int: + remove_sysfs_interfaces(&client->dev); +err_input_cleanup: + lsm303dlh_acc_input_cleanup(acc); +err_power_off: + lsm303dlh_acc_device_power_off(acc); +err_pdata_init: + if (acc->pdata->exit) + acc->pdata->exit(); +exit_kfree_pdata: + kfree(acc->pdata); +err_mutexunlock: + mutex_unlock(&acc->lock); +//err_freedata: + kfree(acc); +exit_check_functionality_failed: + printk(KERN_ERR "%s: Driver Init failed\n", LSM303DLH_ACC_DEV_NAME); + return err; +} + +static int __devexit lsm303dlh_acc_remove(struct i2c_client *client) +{ + struct lsm303dlh_acc_data *acc = i2c_get_clientdata(client); + + if(acc->pdata->gpio_int1 >= 0){ + free_irq(acc->irq1, acc); + gpio_free(acc->pdata->gpio_int1); + destroy_workqueue(acc->irq1_work_queue); + } + + if(acc->pdata->gpio_int2 >= 0){ + free_irq(acc->irq2, acc); + gpio_free(acc->pdata->gpio_int2); + destroy_workqueue(acc->irq2_work_queue); + } + + lsm303dlh_acc_input_cleanup(acc); + lsm303dlh_acc_device_power_off(acc); + remove_sysfs_interfaces(&client->dev); + + if (acc->pdata->exit) + acc->pdata->exit(); + kfree(acc->pdata); + kfree(acc); + + return 0; +} + +#ifdef CONFIG_PM +static int lsm303dlh_acc_resume(struct i2c_client *client) +{ + struct lsm303dlh_acc_data *acc = i2c_get_clientdata(client); + + if (acc->on_before_suspend) + return lsm303dlh_acc_enable(acc); + return 0; +} + +static int lsm303dlh_acc_suspend(struct i2c_client *client, pm_message_t mesg) +{ + struct lsm303dlh_acc_data *acc = i2c_get_clientdata(client); + + acc->on_before_suspend = atomic_read(&acc->enabled); + return lsm303dlh_acc_disable(acc); +} +#else +#define lis3dh_acc_suspend NULL +#define lis3dh_acc_resume NULL +#endif + +static const struct i2c_device_id lsm303dlh_acc_id[] + = { { LSM303DLH_ACC_DEV_NAME, 0}, { },}; + +MODULE_DEVICE_TABLE(i2c, lsm303dlh_acc_id); + +static struct i2c_driver lsm303dlh_acc_driver = { + .driver = { + .owner = THIS_MODULE, + .name = LSM303DLH_ACC_DEV_NAME, + }, + .probe = lsm303dlh_acc_probe, + .remove = __devexit_p(lsm303dlh_acc_remove), + .resume = lsm303dlh_acc_resume, + .suspend = lsm303dlh_acc_suspend, + .id_table = lsm303dlh_acc_id, +}; + +static int __init lsm303dlh_acc_init(void) +{ + printk(KERN_DEBUG "%s accelerometer driver: init\n", + LSM303DLH_ACC_DEV_NAME); + return i2c_add_driver(&lsm303dlh_acc_driver); +} + +static void __exit lsm303dlh_acc_exit(void) +{ + #if DEBUG + printk(KERN_DEBUG "%s accelerometer driver exit\n", + LSM303DLH_ACC_DEV_NAME); + #endif + i2c_del_driver(&lsm303dlh_acc_driver); + return; +} + +module_init(lsm303dlh_acc_init); +module_exit(lsm303dlh_acc_exit); + +MODULE_DESCRIPTION("lsm303dlh accelerometer sysfs driver"); +MODULE_AUTHOR("Matteo Dameno, Carmine Iascone, STMicroelectronics"); +MODULE_LICENSE("GPL"); diff --git a/drivers/input/misc/lsm303dlh_mag_sysfs.c b/drivers/input/misc/lsm303dlh_mag_sysfs.c new file mode 100644 index 00000000000..740f37a4ed5 --- /dev/null +++ b/drivers/input/misc/lsm303dlh_mag_sysfs.c @@ -0,0 +1,1024 @@ +/******************** (C) COPYRIGHT 2010 STMicroelectronics ******************** +* +* File Name : lsm303dlh_mag_sys.c +* Authors : MSH - Motion Mems BU - Application Team +* : Carmine Iascone (carmine.iascone@st.com) +* : Matteo Dameno (matteo.dameno@st.com) +* : Both authors are willing to be considered the contact +* : and update points for the driver.* +* Version : V 1.0.7 +* Date : 16/02/2011 +* Description : LSM303DLH 6D module sensor device driver +* +******************************************************************************** +* +* 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. +* +* THE PRESENT SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES +* OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, FOR THE SOLE +* PURPOSE TO SUPPORT YOUR APPLICATION DEVELOPMENT. +* AS A RESULT, STMICROELECTRONICS SHALL NOT BE HELD LIABLE FOR ANY DIRECT, +* INDIRECT OR CONSEQUENTIAL DAMAGES WITH RESPECT TO ANY CLAIMS ARISING FROM THE +* CONTENT OF SUCH SOFTWARE AND/OR THE USE MADE BY CUSTOMERS OF THE CODING +* INFORMATION CONTAINED HEREIN IN CONNECTION WITH THEIR PRODUCTS. +* +******************************************************************************** + Revision 1.0.7: 22/11/2010 + corrects bug in enable/disable of polling polled device; +*******************************************************************************/ + +#include +#include +#include + +#include +#include + +#include + + +/** Maximum polled-device-reported g value */ +#define H_MAX 8100 + +/* Magnetometer registers */ +#define CRA_REG_M 0x00 /* Configuration register A */ +#define CRB_REG_M 0x01 /* Configuration register B */ +#define MR_REG_M 0x02 /* Mode register */ + +/* resume state index */ +#define RES_CRA_REG_M 0 /* Configuration register A */ +#define RES_CRB_REG_M 1 /* Configuration register B */ +#define RES_MR_REG_M 2 /* Mode register */ + +/* Output register start address*/ +#define OUT_X_M 0x03 + +/* Magnetic Sensor Operation Mode */ +#define NORMAL_MODE 0x00 +#define POS_BIAS 0x01 +#define NEG_BIAS 0x02 +#define CC_MODE 0x00 +#define SC_MODE 0x01 +#define SLEEP_MODE 0x03 + +/* Magnetometer X-Y sensitivity */ +#define XY_SENSITIVITY_1_3 1055 /* XY sensitivity at 1.3G */ +#define XY_SENSITIVITY_1_9 795 /* XY sensitivity at 1.9G */ +#define XY_SENSITIVITY_2_5 635 /* XY sensitivity at 2.5G */ +#define XY_SENSITIVITY_4_0 430 /* XY sensitivity at 4.0G */ +#define XY_SENSITIVITY_4_7 375 /* XY sensitivity at 4.7G */ +#define XY_SENSITIVITY_5_6 320 /* XY sensitivity at 5.6G */ +#define XY_SENSITIVITY_8_1 230 /* XY sensitivity at 8.1G */ + +/* Magnetometer Z sensitivity */ +#define Z_SENSITIVITY_1_3 950 /* Z sensitivity at 1.3G */ +#define Z_SENSITIVITY_1_9 710 /* Z sensitivity at 1.9G */ +#define Z_SENSITIVITY_2_5 570 /* Z sensitivity at 2.5G */ +#define Z_SENSITIVITY_4_0 385 /* Z sensitivity at 4.0G */ +#define Z_SENSITIVITY_4_7 335 /* Z sensitivity at 4.7G */ +#define Z_SENSITIVITY_5_6 285 /* Z sensitivity at 5.6G */ +#define Z_SENSITIVITY_8_1 205 /* Z sensitivity at 8.1G */ + +/* Magnetometer output data rate */ +#define LSM303DLH_MAG_ODR_75 0x00 /* 0.75Hz output data rate */ +#define LSM303DLH_MAG_ODR1_5 0x04 /* 1.5Hz output data rate */ +#define LSM303DLH_MAG_ODR3_0 0x08 /* 3Hz output data rate */ +#define LSM303DLH_MAG_ODR7_5 0x0C /* 7.5Hz output data rate */ +#define LSM303DLH_MAG_ODR15 0x10 /* 15Hz output data rate */ +#define LSM303DLH_MAG_ODR30 0x14 /* 30Hz output data rate */ +#define LSM303DLH_MAG_ODR75 0x18 /* 75Hz output data rate */ + +#define FUZZ 0 +#define FLAT 0 + +#define SELFTEST_CICLES 6 + +struct output_rate { + int poll_rate_ms; + u8 mask; +}; + +static const struct output_rate odr_table[] = { + + { 13, LSM303DLH_MAG_ODR75}, + { 34, LSM303DLH_MAG_ODR30}, + { 67, LSM303DLH_MAG_ODR15}, + { 134, LSM303DLH_MAG_ODR7_5}, + { 334, LSM303DLH_MAG_ODR3_0}, + { 667, LSM303DLH_MAG_ODR1_5}, + { 1334, LSM303DLH_MAG_ODR_75}, +}; + +struct lsm303dlh_mag_data { + struct i2c_client *client; + struct lsm303dlh_mag_platform_data *pdata; + + struct mutex lock; + + struct input_polled_dev *input_poll_dev; + + int hw_initialized; + atomic_t enabled; + atomic_t self_test_enabled; + + u16 xy_sensitivity; + u16 z_sensitivity; + + u8 resume_state[3]; + + u8 reg_addr; +}; + +static int lsm303dlh_mag_i2c_read(struct lsm303dlh_mag_data *mag, + u8 *buf, int len) +{ + int err; + + struct i2c_msg msgs[] = { + { + .addr = mag->client->addr, + .flags = mag->client->flags & I2C_M_TEN, + .len = 1, + .buf = buf, + }, + { + .addr = mag->client->addr, + .flags = (mag->client->flags & I2C_M_TEN) | I2C_M_RD, + .len = len, + .buf = buf, + }, + }; + + err = i2c_transfer(mag->client->adapter, msgs, 2); + + if (err != 2) { + dev_err(&mag->client->dev, "read transfer error\n"); + err = -EIO; + } else { + err = 0; + } + + return err; +} + +static int lsm303dlh_mag_i2c_write(struct lsm303dlh_mag_data *mag, + u8 *buf, int len) +{ + int err; + + struct i2c_msg msgs[] = { + { + .addr = mag->client->addr, + .flags = mag->client->flags & I2C_M_TEN, + .len = len + 1, + .buf = buf, + }, + }; + + err = i2c_transfer(mag->client->adapter, msgs, 1); + + if (err != 1) { + dev_err(&mag->client->dev, "write transfer error\n"); + err = -EIO; + } else { + err = 0; + } + + return err; + +} + +static int lsm303dlh_mag_i2c_update(struct lsm303dlh_mag_data *mag, + u8 reg_address, u8 mask, u8 new_bit_values) +{ + int err = -1; + u8 rdbuf[1] = { reg_address }; + u8 wrbuf[2] = { reg_address , 0x00 }; + + u8 init_val; + u8 updated_val; + err = lsm303dlh_mag_i2c_read(mag, rdbuf, 1); + if (!(err < 0)) { + init_val = rdbuf[0]; + updated_val = ((mask & new_bit_values) | ((~mask) & init_val)); + wrbuf[1] = updated_val; + err = lsm303dlh_mag_i2c_write(mag, wrbuf, 1); + } + return err; +} + +int lsm303dlh_mag_update_h_range(struct lsm303dlh_mag_data *mag, + u8 new_h_range) +{ + int err = -1; + u8 buf[2]; + + switch (new_h_range) { + case LSM303DLH_MAG_H_1_3G: + mag->xy_sensitivity = XY_SENSITIVITY_1_3; + mag->z_sensitivity = Z_SENSITIVITY_1_3; + break; + case LSM303DLH_MAG_H_1_9G: + mag->xy_sensitivity = XY_SENSITIVITY_1_9; + mag->z_sensitivity = Z_SENSITIVITY_1_9; + break; + case LSM303DLH_MAG_H_2_5G: + mag->xy_sensitivity = XY_SENSITIVITY_2_5; + mag->z_sensitivity = Z_SENSITIVITY_2_5; + break; + case LSM303DLH_MAG_H_4_0G: + mag->xy_sensitivity = XY_SENSITIVITY_4_0; + mag->z_sensitivity = Z_SENSITIVITY_4_0; + break; + case LSM303DLH_MAG_H_4_7G: + mag->xy_sensitivity = XY_SENSITIVITY_4_7; + mag->z_sensitivity = Z_SENSITIVITY_4_7; + break; + case LSM303DLH_MAG_H_5_6G: + mag->xy_sensitivity = XY_SENSITIVITY_5_6; + mag->z_sensitivity = Z_SENSITIVITY_5_6; + break; + case LSM303DLH_MAG_H_8_1G: + mag->xy_sensitivity = XY_SENSITIVITY_8_1; + mag->z_sensitivity = Z_SENSITIVITY_8_1; + break; + default: + return -EINVAL; + } + + if (atomic_read(&mag->enabled)) { + + buf[0] = CRB_REG_M; + buf[1] = new_h_range; + err = lsm303dlh_mag_i2c_write(mag, buf, 1); + if (err < 0) + return err; + mag->resume_state[RES_CRB_REG_M] = new_h_range; + } + + return 0; +} + +int lsm303dlh_mag_update_odr(struct lsm303dlh_mag_data *mag, + int poll_interval) +{ + int err = -1; + int i; + u8 config[2]; + + for (i = ARRAY_SIZE(odr_table)-1; i >= 0; i--) { + if (odr_table[i].poll_rate_ms <= poll_interval) + break; + } + + config[1] = odr_table[i].mask; + config[1] |= NORMAL_MODE; + + if (atomic_read(&mag->enabled)) { + config[0] = CRA_REG_M; + err = lsm303dlh_mag_i2c_write(mag, config, 1); + if (err < 0) + return err; + mag->resume_state[RES_CRA_REG_M] = config[1]; + } + + + + return 0; +} + +static int lsm303dlh_mag_selftest(struct lsm303dlh_mag_data *mag, u8 enable) +{ + int err = -1; + char reg_address1, mask1, bit_values1; + char reg_address2, mask2, bit_values2; + static int count = 0; + + if ((enable > 0) && (count < SELFTEST_CICLES) ){ + + reg_address1 = CRA_REG_M; + mask1 = 0x03; + bit_values1 = POS_BIAS ; + err = lsm303dlh_mag_i2c_update(mag, reg_address1, mask1, + bit_values1); + if (err < 0) + return err; + reg_address2 = MR_REG_M; + mask2 = 0x03; + bit_values2 = SC_MODE ; + err = lsm303dlh_mag_i2c_update(mag, reg_address2, mask2, + bit_values2); + if (err < 0) + { + lsm303dlh_mag_i2c_update(mag, reg_address1, mask1, + mag->resume_state[RES_CRA_REG_M]); + return err; + } + + mag->resume_state[RES_CRA_REG_M] = ((mask1 & bit_values1) | + ( ~mask1 & mag->resume_state[RES_CRA_REG_M])); + mag->resume_state[RES_MR_REG_M] = ((mask2 & bit_values2) | + ( ~mask2 & mag->resume_state[RES_MR_REG_M])); + count ++; + atomic_set(&mag->self_test_enabled, 1); + + } + else + { + + reg_address1 = MR_REG_M; + mask1 = 0x03; + bit_values1 = CC_MODE ; + err = lsm303dlh_mag_i2c_update(mag, reg_address1, mask1, + bit_values1); + if (err < 0) + return err; + reg_address2 = CRA_REG_M; + mask2 = 0x03; + bit_values2 = NORMAL_MODE ; + err = lsm303dlh_mag_i2c_update(mag, reg_address2, mask2, + bit_values2); + if (err < 0) + { + lsm303dlh_mag_i2c_update(mag, reg_address1, mask1, + mag->resume_state[RES_MR_REG_M]); + return err; + } + mag->resume_state[RES_CRA_REG_M] = ((mask1 & bit_values1) | + ( ~mask1 & mag->resume_state[RES_CRA_REG_M])); + mag->resume_state[RES_MR_REG_M] = ((mask2 & bit_values2) | + ( ~mask2 & mag->resume_state[RES_MR_REG_M])); + atomic_set(&mag->self_test_enabled, 0); + count = 0; + + } + return err; +} + +static int lsm303dlh_mag_get_data(struct lsm303dlh_mag_data *mag, + int *xyz) +{ + int err = -1; + /* Data bytes from hardware HxL, HxH, HyL, HyH, HzL, HzH */ + u8 mag_data[6]; + /* x,y,z hardware data */ + int hw_d[3] = { 0 }; + + mag_data[0] = OUT_X_M; + err = lsm303dlh_mag_i2c_read(mag, mag_data, 6); + if (err < 0) + return err; + + hw_d[0] = (int) (((mag_data[0]) << 8) | mag_data[1]); + hw_d[1] = (int) (((mag_data[2]) << 8) | mag_data[3]); + hw_d[2] = (int) (((mag_data[4]) << 8) | mag_data[5]); + + 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]); + + if (hw_d[0] != 0XF000) + hw_d[0] = hw_d[0] * 1000 / mag->xy_sensitivity; + else + hw_d[0] = 0X8000; + if (hw_d[1] != 0XF000) + hw_d[1] = hw_d[1] * 1000 / mag->xy_sensitivity; + else + hw_d[1] = 0x8000; + if (hw_d[2] != 0XF000) + hw_d[2] = hw_d[2] * 1000 / mag->z_sensitivity; + else + hw_d[2] = 0x8000; + + if ((hw_d[mag->pdata->axis_map_x] != 0x8000) && (mag->pdata->negate_x)) + xyz[0] = -hw_d[mag->pdata->axis_map_x]; + else + xyz[0] = hw_d[mag->pdata->axis_map_x]; + + if ((hw_d[mag->pdata->axis_map_y] != 0x8000) && (mag->pdata->negate_y)) + xyz[1] = -hw_d[mag->pdata->axis_map_y]; + else + xyz[1] = hw_d[mag->pdata->axis_map_y]; + + if ((hw_d[mag->pdata->axis_map_z] != 0x8000) && (mag->pdata->negate_z)) + xyz[2] = -hw_d[mag->pdata->axis_map_z]; + else + xyz[2] = hw_d[mag->pdata->axis_map_z]; + + return err; +} + +static void lsm303dlh_mag_report_values(struct lsm303dlh_mag_data *mag, + int *xyz) +{ + struct input_dev *input = mag->input_poll_dev->input; + input_report_abs(input, ABS_X, xyz[0]); + input_report_abs(input, ABS_Y, xyz[1]); + input_report_abs(input, ABS_Z, xyz[2]); + input_sync(input); +} + +static int lsm303dlh_mag_hw_init(struct lsm303dlh_mag_data *mag) +{ + int err = -1; + u8 buf[4]; + + buf[0] = CRA_REG_M; + buf[1] = mag->resume_state[RES_CRA_REG_M]; + buf[2] = mag->resume_state[RES_CRB_REG_M]; + buf[3] = mag->resume_state[RES_MR_REG_M]; + err = lsm303dlh_mag_i2c_write(mag, buf, 3); + + if (err < 0) + return err; + + mag->hw_initialized = 1; + + return 0; +} + +static void lsm303dlh_mag_device_power_off(struct lsm303dlh_mag_data *mag) +{ + int err; + u8 buf[2] = { MR_REG_M, SLEEP_MODE }; + + err = lsm303dlh_mag_i2c_write(mag, buf, 1); + if (err < 0) + dev_err(&mag->client->dev, "soft power off failed\n"); + + if (mag->pdata->power_off) { + mag->pdata->power_off(); + mag->hw_initialized = 0; + } +} + +static int lsm303dlh_mag_device_power_on(struct lsm303dlh_mag_data *mag) +{ + int err; + u8 buf[2] = { MR_REG_M, NORMAL_MODE }; + + if (mag->pdata->power_on) { + err = mag->pdata->power_on(); + if (err < 0) + return err; + } + + if (!mag->hw_initialized) { + err = lsm303dlh_mag_hw_init(mag); + if (err < 0) { + lsm303dlh_mag_device_power_off(mag); + return err; + } + } else { + err = lsm303dlh_mag_i2c_write(mag, buf, 1); + } + + return 0; +} + +static int lsm303dlh_mag_enable(struct lsm303dlh_mag_data *mag) +{ + int err; + + if (!atomic_cmpxchg(&mag->enabled, 0, 1)) { + + err = lsm303dlh_mag_device_power_on(mag); + if (err < 0) { + atomic_set(&mag->enabled, 0); + return err; + } + schedule_delayed_work(&mag->input_poll_dev->work, + msecs_to_jiffies(mag-> + pdata->poll_interval)); + } + + return 0; +} + +static int lsm303dlh_mag_disable(struct lsm303dlh_mag_data *mag) +{ + if (atomic_cmpxchg(&mag->enabled, 1, 0)) { + cancel_delayed_work_sync(&mag->input_poll_dev->work); + lsm303dlh_mag_device_power_off(mag); + } + + return 0; +} + +static ssize_t attr_get_polling_rate(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int val; + struct lsm303dlh_mag_data *mag = dev_get_drvdata(dev); + mutex_lock(&mag->lock); + val = mag->pdata->poll_interval; + mutex_unlock(&mag->lock); + return sprintf(buf, "%d\n", val); +} + +static ssize_t attr_set_polling_rate(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + struct lsm303dlh_mag_data *mag = dev_get_drvdata(dev); + unsigned long interval_ms; + + if (strict_strtoul(buf, 10, &interval_ms)) + return -EINVAL; + if (!interval_ms) + return -EINVAL; + mutex_lock(&mag->lock); + mag->pdata->poll_interval = interval_ms; + lsm303dlh_mag_update_odr(mag, interval_ms); + mutex_unlock(&mag->lock); + return size; +} + +static ssize_t attr_get_range(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct lsm303dlh_mag_data *mag = dev_get_drvdata(dev); + int range = 0; + char val; + mutex_lock(&mag->lock); + val = mag->pdata->h_range; + printk(KERN_INFO "%s, h_range = %d", __func__, val); + switch (val) { + case LSM303DLH_MAG_H_1_3G: + range = 1300; + break; + case LSM303DLH_MAG_H_1_9G: + range = 1900; + break; + case LSM303DLH_MAG_H_2_5G: + range = 2500; + break; + case LSM303DLH_MAG_H_4_0G: + range = 4000; + break; + case LSM303DLH_MAG_H_4_7G: + range = 4700; + break; + case LSM303DLH_MAG_H_5_6G: + range = 5600; + break; + case LSM303DLH_MAG_H_8_1G: + range = 8100; + break; + } + mutex_unlock(&mag->lock); + return sprintf(buf, "%d mGauss\n", range); +} + +static ssize_t attr_set_range(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + struct lsm303dlh_mag_data *mag = dev_get_drvdata(dev); + unsigned long val; + if (strict_strtoul(buf, 10, &val)) + return -EINVAL; + mutex_lock(&mag->lock); + mag->pdata->h_range = val; + lsm303dlh_mag_update_h_range(mag, val); + mutex_unlock(&mag->lock); + return size; +} + +static ssize_t attr_get_enable(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct lsm303dlh_mag_data *mag = dev_get_drvdata(dev); + int val = atomic_read(&mag->enabled); + return sprintf(buf, "%d\n", val); +} + +static ssize_t attr_set_enable(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + struct lsm303dlh_mag_data *mag = dev_get_drvdata(dev); + unsigned long val; + + if (strict_strtoul(buf, 10, &val)) + return -EINVAL; + + if (val) + lsm303dlh_mag_enable(mag); + else + lsm303dlh_mag_disable(mag); + + return size; +} + +static ssize_t attr_reg_set(struct device *dev, struct device_attribute *attr, + const char *buf, size_t size) +{ + int rc; + struct lsm303dlh_mag_data *mag = dev_get_drvdata(dev); + u8 x[2]; + unsigned long val; + + if (strict_strtoul(buf, 16, &val)) + return -EINVAL; + mutex_lock(&mag->lock); + x[0] = mag->reg_addr; + mutex_unlock(&mag->lock); + x[1] = val; + rc = lsm303dlh_mag_i2c_write(mag, x, 1); + return size; +} + +static ssize_t attr_reg_get(struct device *dev, struct device_attribute *attr, + char *buf) +{ + ssize_t ret; + struct lsm303dlh_mag_data *mag = dev_get_drvdata(dev); + int rc; + u8 data; + + mutex_lock(&mag->lock); + data = mag->reg_addr; + mutex_unlock(&mag->lock); + rc = lsm303dlh_mag_i2c_read(mag, &data, 1); + ret = sprintf(buf, "0x%02x\n", data); + return ret; +} + +static ssize_t attr_addr_set(struct device *dev, struct device_attribute *attr, + const char *buf, size_t size) +{ + struct lsm303dlh_mag_data *gyro = dev_get_drvdata(dev); + unsigned long val; + + if (strict_strtoul(buf, 16, &val)) + return -EINVAL; + + mutex_lock(&gyro->lock); + + gyro->reg_addr = val; + + mutex_unlock(&gyro->lock); + + return size; +} + +static struct device_attribute attributes[] = { + __ATTR(pollrate_ms, 0666, attr_get_polling_rate, attr_set_polling_rate), + __ATTR(range, 0666, attr_get_range, attr_set_range), + __ATTR(enable, 0666, attr_get_enable, attr_set_enable), + __ATTR(reg_value, 0600, attr_reg_get, attr_reg_set), + __ATTR(reg_addr, 0200, NULL, attr_addr_set), +}; + +static int create_sysfs_interfaces(struct device *dev) +{ + int i; + for (i = 0; i < ARRAY_SIZE(attributes); i++) + if (device_create_file(dev, attributes + i)) + goto error; + return 0; + +error: + for ( ; i >= 0; i--) + device_remove_file(dev, attributes + i); + dev_err(dev, "%s:Unable to create interface\n", __func__); + return -1; +} + +static int remove_sysfs_interfaces(struct device *dev) +{ + int i; + for (i = 0; i < ARRAY_SIZE(attributes); i++) + device_remove_file(dev, attributes + i); + return 0; +} + +static void lsm303dlh_mag_input_poll_func(struct input_polled_dev *dev) +{ + struct lsm303dlh_mag_data *mag = dev->private; + + int xyz[3] = { 0 }; + + int err; + + if (atomic_read(&mag->self_test_enabled)) + lsm303dlh_mag_selftest(mag, 0x01); + mutex_lock(&mag->lock); + err = lsm303dlh_mag_get_data(mag, xyz); + if (err < 0) + dev_err(&mag->client->dev, "get_magnetometer_data failed\n"); + else + lsm303dlh_mag_report_values(mag, xyz); + + mutex_unlock(&mag->lock); +} + +int lsm303dlh_mag_input_open(struct input_dev *input) +{ + struct lsm303dlh_mag_data *mag = input_get_drvdata(input); + + return lsm303dlh_mag_enable(mag); +} + +void lsm303dlh_mag_input_close(struct input_dev *dev) +{ + struct lsm303dlh_mag_data *mag = input_get_drvdata(dev); + + lsm303dlh_mag_disable(mag); +} + +static int lsm303dlh_mag_validate_pdata(struct lsm303dlh_mag_data *mag) +{ + mag->pdata->poll_interval = max(mag->pdata->poll_interval, + mag->pdata->min_interval); + + if (mag->pdata->axis_map_x > 2 || + mag->pdata->axis_map_y > 2 || mag->pdata->axis_map_z > 2) { + dev_err(&mag->client->dev, + "invalid axis_map value x:%u y:%u z%u\n", + mag->pdata->axis_map_x, mag->pdata->axis_map_y, + mag->pdata->axis_map_z); + return -EINVAL; + } + + /* Only allow 0 and 1 for negation boolean flag */ + if (mag->pdata->negate_x > 1 || mag->pdata->negate_y > 1 || + mag->pdata->negate_z > 1) { + dev_err(&mag->client->dev, + "invalid negate value x:%u y:%u z:%u\n", + mag->pdata->negate_x, mag->pdata->negate_y, + mag->pdata->negate_z); + return -EINVAL; + } + + /* Enforce minimum polling interval */ + if (mag->pdata->poll_interval < mag->pdata->min_interval) { + dev_err(&mag->client->dev, "minimum poll interval violated\n"); + return -EINVAL; + } + + return 0; +} + +static int lsm303dlh_mag_input_init(struct lsm303dlh_mag_data *mag) +{ + int err = -1; + struct input_dev *input; + + + mag->input_poll_dev = input_allocate_polled_device(); + if (!mag->input_poll_dev) { + err = -ENOMEM; + dev_err(&mag->client->dev, "input device allocate failed\n"); + goto err0; + } + + mag->input_poll_dev->private = mag; + mag->input_poll_dev->poll = lsm303dlh_mag_input_poll_func; + mag->input_poll_dev->poll_interval = mag->pdata->poll_interval; + + input = mag->input_poll_dev->input; + + input->open = lsm303dlh_mag_input_open; + input->close = lsm303dlh_mag_input_close; + + input->id.bustype = BUS_I2C; + input->dev.parent = &mag->client->dev; + + input_set_drvdata(mag->input_poll_dev->input, mag); + + set_bit(EV_ABS, input->evbit); + + input_set_abs_params(input, ABS_X, -H_MAX, H_MAX, FUZZ, FLAT); + input_set_abs_params(input, ABS_Y, -H_MAX, H_MAX, FUZZ, FLAT); + input_set_abs_params(input, ABS_Z, -H_MAX, H_MAX, FUZZ, FLAT); + + input->name = LSM303DLH_MAG_DEV_NAME; + + err = input_register_polled_device(mag->input_poll_dev); + if (err) { + dev_err(&mag->client->dev, + "unable to register input polled device %s\n", + mag->input_poll_dev->input->name); + goto err1; + } + + return 0; + +err1: + input_free_polled_device(mag->input_poll_dev); +err0: + return err; +} + +static void lsm303dlh_mag_input_cleanup(struct lsm303dlh_mag_data *mag) +{ + input_unregister_polled_device(mag->input_poll_dev); + input_free_polled_device(mag->input_poll_dev); +} + +static int lsm303dlh_mag_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct lsm303dlh_mag_data *mag; + + int err = -1; + + pr_err("%s: probe start\n", LSM303DLH_MAG_DEV_NAME); + + 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; + } + + mag = kzalloc(sizeof(*mag), GFP_KERNEL); + if (mag == NULL) { + dev_err(&client->dev, + "failed to allocate memory for module data\n"); + err = -ENOMEM; + goto err0; + } + + mutex_init(&mag->lock); + mutex_lock(&mag->lock); + mag->client = client; + + mag->pdata = kmalloc(sizeof(*mag->pdata), GFP_KERNEL); + if (mag->pdata == NULL) + goto err1; + + memcpy(mag->pdata, client->dev.platform_data, sizeof(*mag->pdata)); + + err = lsm303dlh_mag_validate_pdata(mag); + if (err < 0) { + dev_err(&client->dev, "failed to validate platform data\n"); + goto err1_1; + } + + i2c_set_clientdata(client, mag); + + if (mag->pdata->init) { + err = mag->pdata->init(); + if (err < 0) { + dev_err(&client->dev, "init failed: %d\n", err); + goto err1_1; + } + } + + memset(mag->resume_state, 0, ARRAY_SIZE(mag->resume_state)); + + mag->resume_state[RES_CRA_REG_M] = 0x90; + mag->resume_state[RES_CRB_REG_M] = 0x20; + mag->resume_state[RES_MR_REG_M] = 0x00; + + err = lsm303dlh_mag_device_power_on(mag); + if (err < 0) { + dev_err(&client->dev, "power on failed: %d\n", err); + goto err2; + } + + atomic_set(&mag->enabled, 1); + + err = lsm303dlh_mag_update_h_range(mag, mag->pdata->h_range); + if (err < 0) { + dev_err(&client->dev, "update_h_range failed\n"); + goto err2; + } + + err = lsm303dlh_mag_update_odr(mag, mag->pdata->poll_interval); + if (err < 0) { + dev_err(&client->dev, "update_odr failed\n"); + goto err2; + } + + err = lsm303dlh_mag_input_init(mag); + if (err < 0) + goto err3; + + err = create_sysfs_interfaces(&client->dev); + if (err < 0) { + dev_err(&client->dev, "%s register failed\n", + LSM303DLH_MAG_DEV_NAME); + goto err4; + } + + lsm303dlh_mag_device_power_off(mag); + + atomic_set(&mag->enabled, 0); + + mutex_unlock(&mag->lock); + + dev_info(&client->dev, "lsm303dlh_mag probed\n"); + + return 0; + +err4: + lsm303dlh_mag_input_cleanup(mag); +err3: + lsm303dlh_mag_device_power_off(mag); +err2: + if (mag->pdata->exit) + mag->pdata->exit(); +err1_1: + mutex_unlock(&mag->lock); + kfree(mag->pdata); +err1: + kfree(mag); +err0: + pr_err("%s: Driver Initialization failed\n", LSM303DLH_MAG_DEV_NAME); + return err; +} + +static int lsm303dlh_mag_remove(struct i2c_client *client) +{ + struct lsm303dlh_mag_data *mag = i2c_get_clientdata(client); + #ifdef DEBUG + pr_info(KERN_INFO "LSM303DLH driver removing\n"); + #endif + lsm303dlh_mag_input_cleanup(mag); + lsm303dlh_mag_device_power_off(mag); + remove_sysfs_interfaces(&client->dev); + + kfree(mag->pdata); + kfree(mag); + return 0; +} + +static int lsm303dlh_mag_suspend(struct device *dev) +{ + #ifdef CONFIG_SUSPEND + struct i2c_client *client = to_i2c_client(dev); + struct lsm303dlh_data *gyro = i2c_get_clientdata(client); + #if DEBUG + pr_info(KERN_INFO "lsm303dlh_suspend\n"); + #endif + /* TO DO */ + #endif + return 0; +} + +static int lsm303dlh_mag_resume(struct device *dev) +{ + #ifdef CONFIG_SUSPEND + struct i2c_client *client = to_i2c_client(dev); + struct lsm303dlh_data *mag = i2c_get_clientdata(client); + #if DEBUG + pr_info(KERN_INFO "lsm303dlh_resume\n"); + #endif + /* TO DO */ + #endif + return 0; +} + +static const struct i2c_device_id lsm303dlh_mag_id[] = { + {LSM303DLH_MAG_DEV_NAME, 0}, + {}, +}; + +MODULE_DEVICE_TABLE(i2c, lsm303dlh_mag_id); + +static struct dev_pm_ops lsm303dlh_pm = { + .suspend = lsm303dlh_mag_suspend, + .resume = lsm303dlh_mag_resume, +}; + +static struct i2c_driver lsm303dlh_mag_driver = { + .driver = { + .owner = THIS_MODULE, + .name = LSM303DLH_MAG_DEV_NAME, + .pm = &lsm303dlh_pm, + }, + .probe = lsm303dlh_mag_probe, + .remove = __devexit_p(lsm303dlh_mag_remove), + .id_table = lsm303dlh_mag_id, +}; + +static int __init lsm303dlh_mag_init(void) +{ + pr_info(KERN_INFO "lsm303dlh magnetometer driver\n"); + return i2c_add_driver(&lsm303dlh_mag_driver); +} + +static void __exit lsm303dlh_mag_exit(void) +{ + i2c_del_driver(&lsm303dlh_mag_driver); + return; +} + +module_init(lsm303dlh_mag_init); +module_exit(lsm303dlh_mag_exit); + +MODULE_DESCRIPTION("lsm303dlh driver for the magnetometer section"); +MODULE_AUTHOR("Matteo Dameno, Carmine Iascone, STMicroelectronics"); +MODULE_LICENSE("GPL"); diff --git a/include/linux/i2c/lsm303dlh.h b/include/linux/i2c/lsm303dlh.h new file mode 100644 index 00000000000..bb07cc630d0 --- /dev/null +++ b/include/linux/i2c/lsm303dlh.h @@ -0,0 +1,135 @@ +/******************** (C) COPYRIGHT 2010 STMicroelectronics ******************** +* +* File Name : lsm303dlh.h +* Authors : MSH - Motion Mems BU - Application Team +* : Carmine Iascone (carmine.iascone@st.com) +* : Matteo Dameno (matteo.dameno@st.com) +* Version : V 1.6.0 +* Date : 2011/02/28 +* Description : LSM303DLH 6D module sensor API +* +******************************************************************************** +* +* 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. +* +* THE PRESENT SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES +* OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, FOR THE SOLE +* PURPOSE TO SUPPORT YOUR APPLICATION DEVELOPMENT. +* AS A RESULT, STMICROELECTRONICS SHALL NOT BE HELD LIABLE FOR ANY DIRECT, +* INDIRECT OR CONSEQUENTIAL DAMAGES WITH RESPECT TO ANY CLAIMS ARISING FROM THE +* CONTENT OF SUCH SOFTWARE AND/OR THE USE MADE BY CUSTOMERS OF THE CODING +* INFORMATION CONTAINED HEREIN IN CONNECTION WITH THEIR PRODUCTS. +* +*******************************************************************************/ + +#ifndef __LSM303DLH_H__ +#define __LSM303DLH_H__ + +#define SAD0L 0x00 +#define SAD0H 0x01 +#define LSM303DLH_ACC_I2C_SADROOT 0x0C +#define LSM303DLH_ACC_I2C_SAD_L ((LSM303DLH_ACC_I2C_SADROOT<<1)|SAD0L) +#define LSM303DLH_ACC_I2C_SAD_H ((LSM303DLH_ACC_I2C_SADROOT<<1)|SAD0H) +#define LSM303DLH_ACC_DEV_NAME "lsm303dlh_acc_sysfs" + + +#define LSM303DLH_MAG_I2C_SAD 0x1E +#define LSM303DLH_MAG_DEV_NAME "lsm303dlh_mag_sysfs" + + + +/************************************************/ +/* Accelerometer section defines */ +/************************************************/ + +/* Accelerometer Sensor Full Scale */ +#define LSM303DLH_ACC_FS_MASK 0x30 +#define LSM303DLH_ACC_G_2G 0x00 +#define LSM303DLH_ACC_G_4G 0x10 +#define LSM303DLH_ACC_G_8G 0x30 + +/* Accelerometer Sensor Operating Mode */ +#define LSM303DLH_ACC_ENABLE 0x01 +#define LSM303DLH_ACC_DISABLE 0x00 +#define LSM303DLH_ACC_PM_NORMAL 0x20 +#define LSM303DLH_ACC_PM_OFF LSM303DLH_ACC_DISABLE + + + + +/************************************************/ +/* Magnetometer section defines */ +/************************************************/ + +/* Magnetometer Sensor Full Scale */ +#define LSM303DLH_MAG_H_1_3G 0x20 +#define LSM303DLH_MAG_H_1_9G 0x40 +#define LSM303DLH_MAG_H_2_5G 0x60 +#define LSM303DLH_MAG_H_4_0G 0x80 +#define LSM303DLH_MAG_H_4_7G 0xA0 +#define LSM303DLH_MAG_H_5_6G 0xC0 +#define LSM303DLH_MAG_H_8_1G 0xE0 + +/* Magnetic Sensor Operating Mode */ +#define LSM303DLH_MAG_NORMAL_MODE 0x00 +#define LSM303DLH_MAG_POS_BIAS 0x01 +#define LSM303DLH_MAG_NEG_BIAS 0x02 +#define LSM303DLH_MAG_CC_MODE 0x00 +#define LSM303DLH_MAG_SC_MODE 0x01 +#define LSM303DLH_MAG_SLEEP_MODE 0x03 + + +#ifdef __KERNEL__ +struct lsm303dlh_acc_platform_data { + + int poll_interval; + int min_interval; + + u8 g_range; + + u8 axis_map_x; + u8 axis_map_y; + u8 axis_map_z; + + u8 negate_x; + u8 negate_y; + u8 negate_z; + + int (*init)(void); + void (*exit)(void); + int (*power_on)(void); + int (*power_off)(void); + + /* set gpio_int[1,2] either to the choosen gpio pin number or to -EINVAL + * if leaved unconnected + */ + int gpio_int1; + int gpio_int2; +}; + +struct lsm303dlh_mag_platform_data { + + int poll_interval; + int min_interval; + + u8 h_range; + + u8 axis_map_x; + u8 axis_map_y; + u8 axis_map_z; + + u8 negate_x; + u8 negate_y; + u8 negate_z; + + int (*init)(void); + void (*exit)(void); + int (*power_on)(void); + int (*power_off)(void); + +}; +#endif /* __KERNEL__ */ + +#endif /* __LSM303DLH_H__ */