From 5311de1d8c06920903395d0f9b84be8acfa61435 Mon Sep 17 00:00:00 2001 From: Tony MÃ¥nsson Date: Tue, 22 Mar 2011 14:23:03 +0100 Subject: Adding l3g4200d Gyroscope --- drivers/hwmon/Kconfig | 11 + drivers/hwmon/Makefile | 1 + drivers/hwmon/l3g4200d.c | 644 +++++++++++++++++++++++++++++++++++++++++++++++ include/linux/l3g4200d.h | 27 ++ 4 files changed, 683 insertions(+) create mode 100644 drivers/hwmon/l3g4200d.c create mode 100644 include/linux/l3g4200d.h diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 91be41f6080..788a7733f18 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -684,6 +684,17 @@ config SENSORS_LTC4151 This driver can also be built as a module. If so, the module will be called ltc4151. +config SENSORS_L3G4200D + tristate "ST L3G4200D 3-axis gyroscope" + depends on I2C + default n + help + If you say yes here you get support for 3-axis gyroscope device + L3g4200D. + + This driver can also be built as a module. If so, the module + will be called l3g4200d. + config SENSORS_LTC4215 tristate "Linear Technology LTC4215" depends on I2C && EXPERIMENTAL diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index 8251ce8cd03..fa78dba3cac 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -84,6 +84,7 @@ obj-$(CONFIG_SENSORS_LM93) += lm93.o obj-$(CONFIG_SENSORS_LM95241) += lm95241.o obj-$(CONFIG_SENSORS_LM95245) += lm95245.o obj-$(CONFIG_SENSORS_LTC4151) += ltc4151.o +obj-$(CONFIG_SENSORS_L3G4200D) += l3g4200d.o obj-$(CONFIG_SENSORS_LTC4215) += ltc4215.o obj-$(CONFIG_SENSORS_LTC4245) += ltc4245.o obj-$(CONFIG_SENSORS_LTC4261) += ltc4261.o diff --git a/drivers/hwmon/l3g4200d.c b/drivers/hwmon/l3g4200d.c new file mode 100644 index 00000000000..718abbb748b --- /dev/null +++ b/drivers/hwmon/l3g4200d.c @@ -0,0 +1,644 @@ +/* + * ST L3G4200D 3-Axis Gyroscope Driver + * + * Copyright (C) ST-Ericsson SA 2011 + * Author: Chethan Krishna N for ST-Ericsson + * Licence terms: GNU General Public Licence (GPL) version 2 + */ + +#include +#include +#include +#include +#include +#include + +#include +#include + +/* l3g4200d gyroscope registers */ + +#define WHO_AM_I 0x0F + +#define CTRL_REG1 0x20 /* CTRL REG1 */ +#define CTRL_REG2 0x21 /* CTRL REG2 */ +#define CTRL_REG3 0x22 /* CTRL_REG3 */ +#define CTRL_REG4 0x23 /* CTRL_REG4 */ +#define CTRL_REG5 0x24 /* CTRL_REG5 */ + +#define AXISDATA_REG 0x28 + +/** Registers Contents */ + +#define WHOAMI_L3G4200D 0x00D3 /* Expected content for WAI register*/ + +/* CTRL_REG1 */ +#define PM_OFF 0x00 +#define PM_ON 0x01 +#define ENABLE_ALL_AXES 0x07 +#define BW00 0x00 +#define BW01 0x10 +#define BW10 0x20 +#define BW11 0x30 +#define ODR00 0x00 /* ODR = 100Hz */ +#define ODR01 0x40 /* ODR = 200Hz */ +#define ODR10 0x80 /* ODR = 400Hz */ +#define ODR11 0xC0 /* ODR = 800Hz */ +#define L3G4200D_PM_BIT 3 +#define L3G4200D_PM_MASK (0x01 << L3G4200D_PM_BIT) +#define L3G4200D_ODR_BIT 4 +#define L3G4200D_ODR_MASK (0x0F << L3G4200D_ODR_BIT) +#define L3G4200D_ODR_MIN_VAL 0x00 +#define L3G4200D_ODR_MAX_VAL 0x0F + +/* CTRL_REG4 */ +#define FS250 0x00 +#define FS500 0x01 +#define FS2000 0x03 +#define BDU_ENABLE 0x80 +#define L3G4200D_FS_BIT 6 +#define L3G4200D_FS_MASK (0x3 << L3G4200D_FS_BIT) + +/* multiple byte transfer enable */ +#define MULTIPLE_I2C_TR 0x80 + +/* device status defines */ +#define DEVICE_OFF 0 +#define DEVICE_ON 1 +#define DEVICE_SUSPENDED 2 + +/* + * L3G4200D gyroscope data + * brief structure containing gyroscope values for yaw, pitch and roll in + * signed short + */ + +struct l3g4200d_gyro_values { + short x; /* x-axis angular rate data. */ + short y; /* y-axis angluar rate data. */ + short z; /* z-axis angular rate data. */ +}; + +struct l3g4200d_data { + struct i2c_client *client; + struct mutex lock; + struct l3g4200d_gyro_values data; + struct l3g4200d_gyr_platform_data pdata; + struct regulator *regulator; + unsigned char powermode; + unsigned char odr; + unsigned char range; + int device_status; +}; + +static int l3g4200d_write(struct l3g4200d_data *ddata, u8 reg, + u8 val, char *msg) +{ + int ret = i2c_smbus_write_byte_data(ddata->client, reg, val); + if (ret < 0) + dev_err(&ddata->client->dev, + "i2c_smbus_write_byte_data failed error %d\ + Register (%s)\n", ret, msg); + return ret; +} + +static int l3g4200d_read(struct l3g4200d_data *ddata, u8 reg, char *msg) +{ + int ret = i2c_smbus_read_byte_data(ddata->client, reg); + if (ret < 0) + dev_err(&ddata->client->dev, + "i2c_smbus_read_byte_data failed error %d\ + Register (%s)\n", ret, msg); + return ret; +} + +static int l3g4200d_readdata(struct l3g4200d_data *ddata) +{ + unsigned char gyro_data[6]; + short data[3]; + int ret; + + ret = i2c_smbus_read_i2c_block_data(ddata->client, + AXISDATA_REG | MULTIPLE_I2C_TR, 6, gyro_data); + if (ret < 0) { + dev_err(&ddata->client->dev, + "i2c_smbus_read_byte_data failed error %d\ + Register AXISDATA_REG\n", ret); + return ret; + } + + data[0] = (short) (((gyro_data[1]) << 8) | gyro_data[0]); + data[1] = (short) (((gyro_data[3]) << 8) | gyro_data[2]); + data[2] = (short) (((gyro_data[5]) << 8) | gyro_data[4]); + + data[ddata->pdata.axis_map_x] = ddata->pdata.negative_x ? + -data[ddata->pdata.axis_map_x] : data[ddata->pdata.axis_map_x]; + data[ddata->pdata.axis_map_y] = ddata->pdata.negative_y ? + -data[ddata->pdata.axis_map_y] : data[ddata->pdata.axis_map_y]; + data[ddata->pdata.axis_map_z] = ddata->pdata.negative_z ? + -data[ddata->pdata.axis_map_z] : data[ddata->pdata.axis_map_z]; + + ddata->data.x = data[ddata->pdata.axis_map_x]; + ddata->data.y = data[ddata->pdata.axis_map_y]; + ddata->data.z = data[ddata->pdata.axis_map_z]; + + return ret; +} + +static ssize_t l3g4200d_show_gyrodata(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct platform_device *pdev = to_platform_device(dev); + struct l3g4200d_data *ddata = platform_get_drvdata(pdev); + int ret = 0; + + mutex_lock(&ddata->lock); + + if (ddata->powermode == PM_OFF || + ddata->device_status == DEVICE_SUSPENDED) { + mutex_unlock(&ddata->lock); + return ret; + } + + ret = l3g4200d_readdata(ddata); + + if (ret < 0) { + mutex_unlock(&ddata->lock); + return ret; + } + + mutex_unlock(&ddata->lock); + + return sprintf(buf, "%8x:%8x:%8x\n", ddata->data.x, ddata->data.y, + ddata->data.z); +} + +static ssize_t l3g4200d_show_range(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct platform_device *pdev = to_platform_device(dev); + struct l3g4200d_data *ddata = platform_get_drvdata(pdev); + + return sprintf(buf, "%d\n", ddata->range); +} + +static ssize_t l3g4200d_store_range(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct platform_device *pdev = to_platform_device(dev); + struct l3g4200d_data *ddata = platform_get_drvdata(pdev); + long received_value; + unsigned char value; + int error; + + error = strict_strtol(buf, 0, &received_value); + if (error) + return error; + + /* check if the received range is in valid range */ + if (received_value < FS250 || received_value > FS2000) + return -EINVAL; + + mutex_lock(&ddata->lock); + + if (ddata->powermode == PM_OFF) { + dev_info(&ddata->client->dev, + "The device is switched off, turn it ON using powermode\n"); + mutex_unlock(&ddata->lock); + return count; + } + + /* enable the BDU bit */ + value = BDU_ENABLE; + value |= ((received_value << L3G4200D_FS_BIT) & L3G4200D_FS_MASK); + + ddata->range = received_value; + + error = l3g4200d_write(ddata, CTRL_REG4, value, "CTRL_REG4"); + if (error < 0) { + mutex_unlock(&ddata->lock); + return error; + } + + mutex_unlock(&ddata->lock); + return count; +} + +static ssize_t l3g4200d_show_datarate(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct platform_device *pdev = to_platform_device(dev); + struct l3g4200d_data *ddata = platform_get_drvdata(pdev); + + return sprintf(buf, "%d\n", ddata->odr >> L3G4200D_ODR_BIT); +} + +static ssize_t l3g4200d_store_datarate(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct platform_device *pdev = to_platform_device(dev); + struct l3g4200d_data *ddata = platform_get_drvdata(pdev); + long received_value; + unsigned char value; + int error; + + error = strict_strtol(buf, 0, &received_value); + if (error) + return error; + + /* check if the received output datarate value is in valid range */ + if (received_value < L3G4200D_ODR_MIN_VAL || + received_value > L3G4200D_ODR_MAX_VAL) + return -EINVAL; + + mutex_lock(&ddata->lock); + + if (ddata->powermode == PM_OFF) { + dev_info(&ddata->client->dev, + "The device is switched off, turn it ON using powermode\n"); + mutex_unlock(&ddata->lock); + return count; + } + + /* + * read the current contents of CTRL_REG1 + * retain any bits set other than the odr bits + */ + error = l3g4200d_read(ddata, CTRL_REG1, "CTRL_REG1"); + + if (error < 0) { + mutex_unlock(&ddata->lock); + return error; + } else + value = error; + + value &= ~L3G4200D_ODR_MASK; + value |= ((received_value << L3G4200D_ODR_BIT) & L3G4200D_ODR_MASK); + + ddata->odr = received_value << L3G4200D_ODR_BIT; + + error = l3g4200d_write(ddata, CTRL_REG1, value, "CTRL_REG1"); + if (error < 0) { + mutex_unlock(&ddata->lock); + return error; + } + + mutex_unlock(&ddata->lock); + return count; +} + +static ssize_t l3g4200d_show_powermode(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct platform_device *pdev = to_platform_device(dev); + struct l3g4200d_data *ddata = platform_get_drvdata(pdev); + + return sprintf(buf, "%d\n", ddata->powermode); +} + +static ssize_t l3g4200d_store_powermode(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct platform_device *pdev = to_platform_device(dev); + struct l3g4200d_data *ddata = platform_get_drvdata(pdev); + long received_value; + unsigned char value; + int error; + + error = strict_strtol(buf, 0, &received_value); + if (error) + return error; + + /* check if the received power mode is either 0 or 1 */ + if (received_value < PM_OFF || received_value > PM_ON) + return -EINVAL; + + mutex_lock(&ddata->lock); + + if (ddata->device_status == DEVICE_SUSPENDED && + received_value == PM_OFF) { + ddata->powermode = received_value; + mutex_unlock(&ddata->lock); + return 0; + } + + /* if sent value is same as current value do nothing */ + if (ddata->powermode == received_value) { + mutex_unlock(&ddata->lock); + return 0; + } + + /* turn on the power suppliy if it was turned off previously */ + if (ddata->regulator && ddata->powermode == PM_OFF + && (ddata->device_status == DEVICE_OFF + || ddata->device_status == DEVICE_SUSPENDED)) { + regulator_enable(ddata->regulator); + ddata->device_status = DEVICE_ON; + } + + /* + * read the current contents of CTRL_REG1 + * retain any bits set other than the power bit + */ + error = l3g4200d_read(ddata, CTRL_REG1, "CTRL_REG1"); + + if (error < 0) { + if (ddata->regulator && ddata->device_status == DEVICE_ON) { + regulator_disable(ddata->regulator); + ddata->device_status = DEVICE_OFF; + } + mutex_unlock(&ddata->lock); + return error; + } else + value = error; + + value &= ~L3G4200D_PM_MASK; + value |= ((received_value << L3G4200D_PM_BIT) & L3G4200D_PM_MASK); + + ddata->powermode = received_value; + + error = l3g4200d_write(ddata, CTRL_REG1, value, "CTRL_REG1"); + if (error < 0) { + if (ddata->regulator && ddata->device_status == DEVICE_ON) { + regulator_disable(ddata->regulator); + ddata->device_status = DEVICE_OFF; + } + mutex_unlock(&ddata->lock); + return error; + } + + if (received_value == PM_OFF) { + /* set the other configuration values to defaults */ + ddata->odr = ODR00 | BW00; + ddata->range = FS250; + + /* turn off the power supply */ + if (ddata->regulator && ddata->device_status == DEVICE_ON) { + regulator_disable(ddata->regulator); + ddata->device_status = DEVICE_OFF; + } + } + mutex_unlock(&ddata->lock); + return count; +} + +static DEVICE_ATTR(gyrodata, S_IRUGO, l3g4200d_show_gyrodata, NULL); + +static DEVICE_ATTR(range, S_IRUGO | S_IWUGO, + l3g4200d_show_range, l3g4200d_store_range); + +static DEVICE_ATTR(datarate, S_IRUGO | S_IWUGO, + l3g4200d_show_datarate, l3g4200d_store_datarate); + +static DEVICE_ATTR(powermode, S_IRUGO | S_IWUGO, + l3g4200d_show_powermode, l3g4200d_store_powermode); + +static struct attribute *l3g4200d_attributes[] = { + &dev_attr_gyrodata.attr, + &dev_attr_range.attr, + &dev_attr_datarate.attr, + &dev_attr_powermode.attr, + NULL +}; + +static const struct attribute_group l3g4200d_attr_group = { + .attrs = l3g4200d_attributes, +}; + +static int __devinit l3g4200d_probe(struct i2c_client *client, + const struct i2c_device_id *devid) +{ + int ret = -1; + struct l3g4200d_data *ddata = NULL; + + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_READ_I2C_BLOCK)) + goto exit; + + ddata = kzalloc(sizeof(struct l3g4200d_data), GFP_KERNEL); + if (ddata == NULL) { + ret = -ENOMEM; + goto error_op_failed; + } + + ddata->client = client; + i2c_set_clientdata(client, ddata); + + memcpy(&ddata->pdata, client->dev.platform_data, sizeof(ddata->pdata)); + /* store default values in the data structure */ + ddata->odr = ODR00 | BW00; + ddata->range = FS250; + ddata->powermode = PM_OFF; + ddata->device_status = DEVICE_OFF; + + dev_set_name(&client->dev, ddata->pdata.name_gyr); + + ddata->regulator = regulator_get(&client->dev, "v-gyro"); + if (IS_ERR(ddata->regulator)) { + dev_err(&client->dev, "failed to get regulator\n"); + ret = PTR_ERR(ddata->regulator); + ddata->regulator = NULL; + } + + if (ddata->regulator) { + regulator_enable(ddata->regulator); + ddata->device_status = DEVICE_ON; + } + + ret = l3g4200d_read(ddata, WHO_AM_I, "WHO_AM_I"); + if (ret < 0) + goto exit_free_regulator; + + if (ret == WHOAMI_L3G4200D) + dev_info(&client->dev, "3-Axis Gyroscope device identification: %d\n", ret); + else + dev_info(&client->dev, "Gyroscope identification did not match\n"); + + mutex_init(&ddata->lock); + + ret = sysfs_create_group(&client->dev.kobj, &l3g4200d_attr_group); + if (ret) + goto exit_free_regulator; + + /* + * turn off the supplies until somebody turns on the device + * using l3g4200d_store_powermode + */ + if (ddata->device_status == DEVICE_ON && ddata->regulator) { + regulator_disable(ddata->regulator); + ddata->device_status = DEVICE_OFF; + } + + return ret; + +exit_free_regulator: + if (ddata->device_status == DEVICE_ON && ddata->regulator) { + regulator_disable(ddata->regulator); + regulator_put(ddata->regulator); + ddata->device_status = DEVICE_OFF; + } +error_op_failed: + kfree(ddata); +exit: + dev_err(&client->dev, "probe function failed %x\n", ret); + return ret; +} + +static int __devexit l3g4200d_remove(struct i2c_client *client) +{ + struct l3g4200d_data *ddata; + ddata = i2c_get_clientdata(client); + sysfs_remove_group(&client->dev.kobj, &l3g4200d_attr_group); + + /* safer to turn off the device */ + if (ddata->powermode != PM_OFF) { + l3g4200d_write(ddata, CTRL_REG1, PM_OFF, "CONTROL"); + if (ddata->regulator && ddata->device_status == DEVICE_ON) { + regulator_disable(ddata->regulator); + regulator_put(ddata->regulator); + ddata->device_status = DEVICE_OFF; + } + } + + i2c_set_clientdata(client, NULL); + kfree(ddata); + + return 0; +} + +static int l3g4200d_do_suspend(struct l3g4200d_data *ddata) +{ + int ret; + + mutex_lock(&ddata->lock); + + if (ddata->powermode == PM_OFF) { + mutex_unlock(&ddata->lock); + return 0; + } + + ret = l3g4200d_write(ddata, CTRL_REG1, PM_OFF, "CONTROL"); + + /* turn off the power when suspending the device */ + if (ddata->regulator) + regulator_disable(ddata->regulator); + + ddata->device_status = DEVICE_SUSPENDED; + + mutex_unlock(&ddata->lock); + return ret; +} + +static int l3g4200d_do_resume(struct l3g4200d_data *ddata) +{ + unsigned char range_value; + unsigned char shifted_powermode = (ddata->powermode << L3G4200D_PM_BIT); + unsigned char shifted_odr = (ddata->odr << L3G4200D_ODR_BIT); + unsigned context = ((shifted_powermode | shifted_odr) | ENABLE_ALL_AXES); + int ret = 0; + + mutex_lock(&ddata->lock); + + if (ddata->device_status == DEVICE_ON) + goto fail; + + /* in correct mode, no need to change it */ + if (ddata->powermode == PM_OFF) { + ddata->device_status = DEVICE_OFF; + goto fail; + } else { + ddata->device_status = DEVICE_ON; + } + + /* turn on the power when resuming the device */ + if (ddata->regulator) + regulator_enable(ddata->regulator); + + ret = l3g4200d_write(ddata, CTRL_REG1, context, "CONTROL"); + if (ret < 0) + goto fail; + + range_value = ddata->range; + range_value <<= L3G4200D_FS_BIT; + range_value |= BDU_ENABLE; + + ret = l3g4200d_write(ddata, CTRL_REG4, range_value, "RANGE"); + +fail: + mutex_unlock(&ddata->lock); + return ret; +} + +#ifdef CONFIG_PM +static int l3g4200d_suspend(struct device *dev) +{ + struct l3g4200d_data *ddata; + int ret; + + ddata = dev_get_drvdata(dev); + + ret = l3g4200d_do_suspend(ddata); + + return ret; +} + +static int l3g4200d_resume(struct device *dev) +{ + struct l3g4200d_data *ddata; + int ret; + + ddata = dev_get_drvdata(dev); + + ret = l3g4200d_do_resume(ddata); + if (ret < 0) + dev_err(&ddata->client->dev, + "Error while resuming the device\n"); + + return ret; +} + +static const struct dev_pm_ops l3g4200d_dev_pm_ops = { + .suspend = l3g4200d_suspend, + .resume = l3g4200d_resume, +}; +#endif + +static const struct i2c_device_id l3g4200d_id[] = { + {"l3g4200d", 0 }, + { }, +}; + +static struct i2c_driver l3g4200d_driver = { + .driver = { + .name = "l3g4200d", +#ifdef CONFIG_PM + .pm = &l3g4200d_dev_pm_ops, +#endif + }, + .probe = l3g4200d_probe, + .remove = l3g4200d_remove, + .id_table = l3g4200d_id, +}; + +static int __init l3g4200d_init(void) +{ + return i2c_add_driver(&l3g4200d_driver); +} + +static void __exit l3g4200d_exit(void) +{ + i2c_del_driver(&l3g4200d_driver); +} + +module_init(l3g4200d_init); +module_exit(l3g4200d_exit); + +MODULE_DESCRIPTION("l3g4200d digital gyroscope driver"); +MODULE_AUTHOR("Chethan Krishna N"); +MODULE_LICENSE("GPL"); diff --git a/include/linux/l3g4200d.h b/include/linux/l3g4200d.h new file mode 100644 index 00000000000..28459601e4f --- /dev/null +++ b/include/linux/l3g4200d.h @@ -0,0 +1,27 @@ +/* + * ST L3G4200D 3-Axis Gyroscope header file + * + * Copyright (C) ST-Ericsson SA 2011 + * Author: Chethan Krishna N for ST-Ericsson + * Licence terms: GNU General Public Licence (GPL) version 2 + */ + +#ifndef __L3G4200D_H__ +#define __L3G4200D_H__ + +#ifdef __KERNEL__ +struct l3g4200d_gyr_platform_data { + const char *name_gyr; + + u8 axis_map_x; + u8 axis_map_y; + u8 axis_map_z; + + u8 negative_x; + u8 negative_y; + u8 negative_z; +}; + +#endif /* __KERNEL__ */ + +#endif /* __L3G4200D_H__ */ -- cgit v1.2.3 From fe775776cfdc755ff0454ea4516e3d89c28815a5 Mon Sep 17 00:00:00 2001 From: Philippe Langlais Date: Thu, 28 Apr 2011 11:41:24 +0200 Subject: hwmon: change L3G4200D regulators name for 2.6.38 Signed-off-by: Philippe Langlais --- drivers/hwmon/l3g4200d.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hwmon/l3g4200d.c b/drivers/hwmon/l3g4200d.c index 718abbb748b..bd78d4b003b 100644 --- a/drivers/hwmon/l3g4200d.c +++ b/drivers/hwmon/l3g4200d.c @@ -439,7 +439,7 @@ static int __devinit l3g4200d_probe(struct i2c_client *client, dev_set_name(&client->dev, ddata->pdata.name_gyr); - ddata->regulator = regulator_get(&client->dev, "v-gyro"); + ddata->regulator = regulator_get(&client->dev, "vdd"); if (IS_ERR(ddata->regulator)) { dev_err(&client->dev, "failed to get regulator\n"); ret = PTR_ERR(ddata->regulator); -- cgit v1.2.3 From a4318df66e5a785e29579f4dc2a7f262f9df8630 Mon Sep 17 00:00:00 2001 From: Philippe Langlais Date: Tue, 18 Oct 2011 11:16:07 +0200 Subject: hwmon: add st lsm303dlh driver Signed-off-by: Mian Yousaf Kaukab Signed-off-by: Philippe Langlais --- drivers/hwmon/Kconfig | 23 + drivers/hwmon/Makefile | 1 + drivers/hwmon/lsm303dlh_a.c | 1270 +++++++++++++++++++++++++++++++++++++++++++ drivers/hwmon/lsm303dlh_m.c | 876 +++++++++++++++++++++++++++++ include/linux/lsm303dlh.h | 56 ++ 5 files changed, 2226 insertions(+) create mode 100644 drivers/hwmon/lsm303dlh_a.c create mode 100644 drivers/hwmon/lsm303dlh_m.c create mode 100644 include/linux/lsm303dlh.h diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 788a7733f18..df91f1aca52 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -684,6 +684,29 @@ config SENSORS_LTC4151 This driver can also be built as a module. If so, the module will be called ltc4151. +config SENSORS_LSM303DLH + tristate "ST LSM303DLH 3-axis accelerometer and 3-axis magnetometer" + depends on I2C + default n + help + This driver provides support for the LSM303DLH chip which includes a + 3-axis accelerometer and a 3-axis magnetometer. + + This driver can also be built as modules. If so, the module for + accelerometer will be called lsm303dlh_a and for magnetometer it will + be called lsm303dlh_m. + + Say Y here if you have a device containing lsm303dlh chip. + +config SENSORS_LSM303DLH_INPUT_DEVICE + bool "ST LSM303DLH INPUT DEVICE" + depends on SENSORS_LSM303DLH + default n + help + This driver allows device to be used as an input device with + interrupts, need to be enabled only when input device support + is required. + config SENSORS_L3G4200D tristate "ST L3G4200D 3-axis gyroscope" depends on I2C diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index fa78dba3cac..9728a44b4a6 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -84,6 +84,7 @@ obj-$(CONFIG_SENSORS_LM93) += lm93.o obj-$(CONFIG_SENSORS_LM95241) += lm95241.o obj-$(CONFIG_SENSORS_LM95245) += lm95245.o obj-$(CONFIG_SENSORS_LTC4151) += ltc4151.o +obj-$(CONFIG_SENSORS_LSM303DLH) += lsm303dlh_a.o lsm303dlh_m.o obj-$(CONFIG_SENSORS_L3G4200D) += l3g4200d.o obj-$(CONFIG_SENSORS_LTC4215) += ltc4215.o obj-$(CONFIG_SENSORS_LTC4245) += ltc4245.o diff --git a/drivers/hwmon/lsm303dlh_a.c b/drivers/hwmon/lsm303dlh_a.c new file mode 100644 index 00000000000..bbcf1663061 --- /dev/null +++ b/drivers/hwmon/lsm303dlh_a.c @@ -0,0 +1,1270 @@ +/* + * lsm303dlh_a.c + * ST 3-Axis Accelerometer Driver + * + * Copyright (C) 2010 STMicroelectronics + * Author: Carmine Iascone (carmine.iascone@st.com) + * Author: Matteo Dameno (matteo.dameno@st.com) + * + * Copyright (C) 2010 STEricsson + * Author: Mian Yousaf Kaukab + * Updated:Preetham Rao Kaskurthi + * + * 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, see . + */ + +#include +#include +#include +#include +#include +#include + +#ifdef CONFIG_SENSORS_LSM303DLH_INPUT_DEVICE +#include +#include +#include +#endif + +#include +#include + + /* lsm303dlh accelerometer registers */ + #define WHO_AM_I 0x0F + + /* ctrl 1: pm2 pm1 pm0 dr1 dr0 zenable yenable zenable */ + #define CTRL_REG1 0x20 /* power control reg */ + #define CTRL_REG2 0x21 /* power control reg */ + #define CTRL_REG3 0x22 /* power control reg */ + #define CTRL_REG4 0x23 /* interrupt control reg */ + #define CTRL_REG5 0x24 /* interrupt control reg */ + + #define STATUS_REG 0x27 /* status register */ + + #define AXISDATA_REG 0x28 /* axis data */ + + #define INT1_CFG 0x30 /* interrupt 1 configuration */ + #define INT1_SRC 0x31 /* interrupt 1 source reg */ + #define INT1_THS 0x32 /* interrupt 1 threshold */ + #define INT1_DURATION 0x33 /* interrupt 1 threshold */ + + #define INT2_CFG 0x34 /* interrupt 2 configuration */ + #define INT2_SRC 0x35 /* interrupt 2 source reg */ + #define INT2_THS 0x36 /* interrupt 2 threshold */ + #define INT2_DURATION 0x37 /* interrupt 2 threshold */ + + /* Sensitivity adjustment */ + #define SHIFT_ADJ_2G 4 /* 1/16*/ + #define SHIFT_ADJ_4G 3 /* 2/16*/ + #define SHIFT_ADJ_8G 2 /* ~3.9/16*/ + + /* Control register 1 */ + #define LSM303DLH_A_CR1_PM_BIT 5 + #define LSM303DLH_A_CR1_PM_MASK (0x7 << LSM303DLH_A_CR1_PM_BIT) + #define LSM303DLH_A_CR1_DR_BIT 3 + #define LSM303DLH_A_CR1_DR_MASK (0x3 << LSM303DLH_A_CR1_DR_BIT) + #define LSM303DLH_A_CR1_EN_BIT 0 + #define LSM303DLH_A_CR1_EN_MASK (0x7 << LSM303DLH_A_CR1_EN_BIT) + #define LSM303DLH_A_CR1_AXIS_ENABLE 7 + + /* Control register 2 */ + #define LSM303DLH_A_CR4_ST_BIT 1 + #define LSM303DLH_A_CR4_ST_MASK (0x1 << LSM303DLH_A_CR4_ST_BIT) + #define LSM303DLH_A_CR4_STS_BIT 3 + #define LSM303DLH_A_CR4_STS_MASK (0x1 << LSM303DLH_A_CR4_STS_BIT) + #define LSM303DLH_A_CR4_FS_BIT 4 + #define LSM303DLH_A_CR4_FS_MASK (0x3 << LSM303DLH_A_CR4_FS_BIT) + #define LSM303DLH_A_CR4_BLE_BIT 6 + #define LSM303DLH_A_CR4_BLE_MASK (0x3 << LSM303DLH_A_CR4_BLE_BIT) + #define LSM303DLH_A_CR4_BDU_BIT 7 + #define LSM303DLH_A_CR4_BDU_MASK (0x1 << LSM303DLH_A_CR4_BDU_BIT) + + /* Control register 3 */ + #define LSM303DLH_A_CR3_I1_BIT 0 + #define LSM303DLH_A_CR3_I1_MASK (0x3 << LSM303DLH_A_CR3_I1_BIT) + #define LSM303DLH_A_CR3_LIR1_BIT 2 + #define LSM303DLH_A_CR3_LIR1_MASK (0x1 << LSM303DLH_A_CR3_LIR1_BIT) + #define LSM303DLH_A_CR3_I2_BIT 3 + #define LSM303DLH_A_CR3_I2_MASK (0x3 << LSM303DLH_A_CR3_I2_BIT) + #define LSM303DLH_A_CR3_LIR2_BIT 5 + #define LSM303DLH_A_CR3_LIR2_MASK (0x1 << LSM303DLH_A_CR3_LIR2_BIT) + #define LSM303DLH_A_CR3_PPOD_BIT 6 + #define LSM303DLH_A_CR3_PPOD_MASK (0x1 << LSM303DLH_A_CR3_PPOD_BIT) + #define LSM303DLH_A_CR3_IHL_BIT 7 + #define LSM303DLH_A_CR3_IHL_MASK (0x1 << LSM303DLH_A_CR3_IHL_BIT) + + #define LSM303DLH_A_CR3_I_SELF 0x0 + #define LSM303DLH_A_CR3_I_OR 0x1 + #define LSM303DLH_A_CR3_I_DATA 0x2 + #define LSM303DLH_A_CR3_I_BOOT 0x3 + + #define LSM303DLH_A_CR3_LIR_LATCH 0x1 + + /* Range */ + #define LSM303DLH_A_RANGE_2G 0x00 + #define LSM303DLH_A_RANGE_4G 0x01 + #define LSM303DLH_A_RANGE_8G 0x03 + + /* Mode */ + #define LSM303DLH_A_MODE_OFF 0x00 + #define LSM303DLH_A_MODE_NORMAL 0x01 + #define LSM303DLH_A_MODE_LP_HALF 0x02 + #define LSM303DLH_A_MODE_LP_1 0x03 + #define LSM303DLH_A_MODE_LP_2 0x02 + #define LSM303DLH_A_MODE_LP_5 0x05 + #define LSM303DLH_A_MODE_LP_10 0x06 + + /* Rate */ + #define LSM303DLH_A_RATE_50 0x00 + #define LSM303DLH_A_RATE_100 0x01 + #define LSM303DLH_A_RATE_400 0x02 + #define LSM303DLH_A_RATE_1000 0x03 + + /* Sleep & Wake */ + #define LSM303DLH_A_SLEEPWAKE_DISABLE 0x00 + #define LSM303DLH_A_SLEEPWAKE_ENABLE 0x3 + +/* Multiple byte transfer enable */ +#define MULTIPLE_I2C_TR 0x80 + +/* device status defines */ +#define DEVICE_OFF 0 +#define DEVICE_ON 1 +#define DEVICE_SUSPENDED 2 + +/* Range -2048 to 2047 */ +struct lsm303dlh_a_t { + short x; + short y; + short z; +}; + +/* + * accelerometer local data + */ +struct lsm303dlh_a_data { + struct i2c_client *client; + /* lock for sysfs operations */ + struct mutex lock; + struct lsm303dlh_a_t data; + +#ifdef CONFIG_SENSORS_LSM303DLH_INPUT_DEVICE + struct input_dev *input_dev; + struct input_dev *input_dev2; +#endif + + struct lsm303dlh_platform_data pdata; + struct regulator *regulator; + + unsigned char range; + unsigned char mode; + unsigned char rate; + unsigned char sleep_wake; + int shift_adjust; + + unsigned char interrupt_control; + unsigned int interrupt_channel; + + unsigned char interrupt_configure[2]; + unsigned char interrupt_duration[2]; + unsigned char interrupt_threshold[2]; + int device_status; +}; + +#ifdef CONFIG_HAS_EARLYSUSPEND +static void lsm303dlh_a_early_suspend(struct early_suspend *data); +static void lsm303dlh_a_late_resume(struct early_suspend *data); +#endif + +static int lsm303dlh_a_write(struct lsm303dlh_a_data *ddata, u8 reg, + u8 val, char *msg) +{ + int ret = i2c_smbus_write_byte_data(ddata->client, reg, val); + if (ret < 0) + dev_err(&ddata->client->dev, + "i2c_smbus_write_byte_data failed error %d\ + Register (%s)\n", ret, msg); + return ret; +} + +static int lsm303dlh_a_read(struct lsm303dlh_a_data *ddata, u8 reg, char *msg) +{ + int ret = i2c_smbus_read_byte_data(ddata->client, reg); + if (ret < 0) + dev_err(&ddata->client->dev, + "i2c_smbus_read_byte_data failed error %d\ + Register (%s)\n", ret, msg); + return ret; +} + +static int lsm303dlh_a_do_suspend(struct lsm303dlh_a_data *ddata) +{ + int ret; + + mutex_lock(&ddata->lock); + + if (ddata->mode == LSM303DLH_A_MODE_OFF) { + mutex_unlock(&ddata->lock); + return 0; + } + +#ifdef CONFIG_SENSORS_LSM303DLH_INPUT_DEVICE + disable_irq(gpio_to_irq(ddata->pdata.irq_a1)); + disable_irq(gpio_to_irq(ddata->pdata.irq_a2)); +#endif + + ret = lsm303dlh_a_write(ddata, CTRL_REG1, + LSM303DLH_A_MODE_OFF, "CONTROL"); + + if (ddata->regulator) + regulator_disable(ddata->regulator); + + ddata->device_status = DEVICE_SUSPENDED; + + mutex_unlock(&ddata->lock); + + return ret; +} + +static int lsm303dlh_a_restore(struct lsm303dlh_a_data *ddata) +{ + unsigned char reg; + unsigned char shifted_mode = (ddata->mode << LSM303DLH_A_CR1_PM_BIT); + unsigned char shifted_rate = (ddata->rate << LSM303DLH_A_CR1_DR_BIT); + unsigned char context = (shifted_mode | shifted_rate); + int ret = 0; + + mutex_lock(&ddata->lock); + + if (ddata->device_status == DEVICE_ON) { + mutex_unlock(&ddata->lock); + return 0; + } + + /* in correct mode, no need to change it */ + if (ddata->mode == LSM303DLH_A_MODE_OFF) { + ddata->device_status = DEVICE_OFF; + mutex_unlock(&ddata->lock); + return 0; + } else + ddata->device_status = DEVICE_ON; + +#ifdef CONFIG_SENSORS_LSM303DLH_INPUT_DEVICE + enable_irq(gpio_to_irq(ddata->pdata.irq_a1)); + enable_irq(gpio_to_irq(ddata->pdata.irq_a2)); +#endif + + if (ddata->regulator) + regulator_enable(ddata->regulator); + /* BDU should be enabled by default/recommened */ + reg = ddata->range; + reg |= LSM303DLH_A_CR4_BDU_MASK; + context |= LSM303DLH_A_CR1_AXIS_ENABLE; + + ret = lsm303dlh_a_write(ddata, CTRL_REG1, context, + "CTRL_REG1"); + if (ret < 0) + goto fail; + + ret = lsm303dlh_a_write(ddata, CTRL_REG4, reg, "CTRL_REG4"); + + if (ret < 0) + goto fail; + + ret = lsm303dlh_a_write(ddata, CTRL_REG3, ddata->interrupt_control, + "CTRL_REG3"); + + if (ret < 0) + goto fail; + + ret = lsm303dlh_a_write(ddata, INT1_CFG, ddata->interrupt_configure[0], + "INT1_CFG"); + + if (ret < 0) + goto fail; + + ret = lsm303dlh_a_write(ddata, INT2_CFG, ddata->interrupt_configure[1], + "INT2_CFG"); + + if (ret < 0) + goto fail; + + ret = lsm303dlh_a_write(ddata, INT1_THS, ddata->interrupt_threshold[0], + "INT1_THS"); + + if (ret < 0) + goto fail; + + ret = lsm303dlh_a_write(ddata, INT2_THS, ddata->interrupt_threshold[1], + "INT2_THS"); + + if (ret < 0) + goto fail; + + ret = lsm303dlh_a_write(ddata, INT1_DURATION, + ddata->interrupt_duration[0], "INT1_DURATION"); + + if (ret < 0) + goto fail; + + ret = lsm303dlh_a_write(ddata, INT1_DURATION, + ddata->interrupt_duration[1], "INT1_DURATION"); + + if (ret < 0) + goto fail; + +fail: + mutex_unlock(&ddata->lock); + return ret; +} + + +static int lsm303dlh_a_readdata(struct lsm303dlh_a_data *ddata) +{ + unsigned char acc_data[6]; + short data[3]; + + int ret = i2c_smbus_read_i2c_block_data(ddata->client, + AXISDATA_REG | MULTIPLE_I2C_TR, 6, acc_data); + if (ret < 0) { + dev_err(&ddata->client->dev, + "i2c_smbus_read_byte_data failed error %d\ + Register AXISDATA_REG \n", ret); + return ret; + } + + data[0] = (short) (((acc_data[1]) << 8) | acc_data[0]); + data[1] = (short) (((acc_data[3]) << 8) | acc_data[2]); + data[2] = (short) (((acc_data[5]) << 8) | acc_data[4]); + + data[0] >>= ddata->shift_adjust; + data[1] >>= ddata->shift_adjust; + data[2] >>= ddata->shift_adjust; + + /* taking position and orientation of x,y,z axis into account*/ + + data[ddata->pdata.axis_map_x] = ddata->pdata.negative_x ? + -data[ddata->pdata.axis_map_x] : data[ddata->pdata.axis_map_x]; + data[ddata->pdata.axis_map_y] = ddata->pdata.negative_y ? + -data[ddata->pdata.axis_map_y] : data[ddata->pdata.axis_map_y]; + data[ddata->pdata.axis_map_z] = ddata->pdata.negative_z ? + -data[ddata->pdata.axis_map_z] : data[ddata->pdata.axis_map_z]; + + ddata->data.x = data[ddata->pdata.axis_map_x]; + ddata->data.y = data[ddata->pdata.axis_map_y]; + ddata->data.z = data[ddata->pdata.axis_map_z]; + + return ret; +} + +static ssize_t lsm303dlh_a_show_data(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct platform_device *pdev = to_platform_device(dev); + struct lsm303dlh_a_data *ddata = platform_get_drvdata(pdev); + int ret = 0; + + mutex_lock(&ddata->lock); + + if (ddata->mode == LSM303DLH_A_MODE_OFF || + ddata->device_status == DEVICE_SUSPENDED) { + mutex_unlock(&ddata->lock); + return ret; + } + + ret = lsm303dlh_a_readdata(ddata); + + if (ret < 0) { + mutex_unlock(&ddata->lock); + return ret; + } + + mutex_unlock(&ddata->lock); + + return sprintf(buf, "%8x:%8x:%8x\n", ddata->data.x, ddata->data.y, + ddata->data.z); +} + +#ifdef CONFIG_SENSORS_LSM303DLH_INPUT_DEVICE +static irqreturn_t lsm303dlh_a_gpio_irq(int irq, void *device_data) +{ + + struct lsm303dlh_a_data *ddata = device_data; + int ret; + unsigned char reg; + struct input_dev *input; + + /* know your interrupt source */ + if (irq == gpio_to_irq(ddata->pdata.irq_a1)) { + reg = INT1_SRC; + input = ddata->input_dev; + } else if (irq == gpio_to_irq(ddata->pdata.irq_a2)) { + reg = INT2_SRC; + input = ddata->input_dev2; + } else { + dev_err(&ddata->client->dev, "spurious interrupt"); + return IRQ_HANDLED; + } + + /* read the axis */ + ret = lsm303dlh_a_readdata(ddata); + if (ret < 0) + dev_err(&ddata->client->dev, + "reading data of xyz failed error %d\n", ret); + + input_report_abs(input, ABS_X, ddata->data.x); + input_report_abs(input, ABS_Y, ddata->data.y); + input_report_abs(input, ABS_Z, ddata->data.z); + input_sync(input); + + /* clear the value by reading it */ + ret = lsm303dlh_a_read(ddata, reg, "INTTERUPT SOURCE"); + if (ret < 0) + dev_err(&ddata->client->dev, + "clearing interrupt source failed error %d\n", ret); + + return IRQ_HANDLED; + +} +#endif + +static ssize_t lsm303dlh_a_show_interrupt_control(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct platform_device *pdev = to_platform_device(dev); + struct lsm303dlh_a_data *ddata = platform_get_drvdata(pdev); + + return sprintf(buf, "%d\n", ddata->interrupt_control); +} + +static ssize_t lsm303dlh_a_store_interrupt_control(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct platform_device *pdev = to_platform_device(dev); + struct lsm303dlh_a_data *ddata = platform_get_drvdata(pdev); + unsigned long val; + int error; + + error = strict_strtoul(buf, 0, &val); + if (error) + return error; + + mutex_lock(&ddata->lock); + + if (ddata->mode == LSM303DLH_A_MODE_OFF) { + dev_info(&ddata->client->dev, + "device is switched off,make it ON using MODE"); + mutex_unlock(&ddata->lock); + return count; + } + + ddata->interrupt_control = val; + + error = lsm303dlh_a_write(ddata, CTRL_REG3, val, "CTRL_REG3"); + if (error < 0) { + mutex_unlock(&ddata->lock); + return error; + } + + mutex_unlock(&ddata->lock); + + return count; +} + +static ssize_t lsm303dlh_a_show_interrupt_channel(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct platform_device *pdev = to_platform_device(dev); + struct lsm303dlh_a_data *ddata = platform_get_drvdata(pdev); + + return sprintf(buf, "%d\n", ddata->interrupt_channel); +} + +static ssize_t lsm303dlh_a_store_interrupt_channel(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct platform_device *pdev = to_platform_device(dev); + struct lsm303dlh_a_data *ddata = platform_get_drvdata(pdev); + unsigned long val; + int error; + + error = strict_strtoul(buf, 0, &val); + if (error) + return error; + + ddata->interrupt_channel = val; + + return count; +} + +static ssize_t lsm303dlh_a_show_interrupt_configure(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct platform_device *pdev = to_platform_device(dev); + struct lsm303dlh_a_data *ddata = platform_get_drvdata(pdev); + + return sprintf(buf, "%d\n", + ddata->interrupt_configure[ddata->interrupt_channel]); +} + +static ssize_t lsm303dlh_a_store_interrupt_configure(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct platform_device *pdev = to_platform_device(dev); + struct lsm303dlh_a_data *ddata = platform_get_drvdata(pdev); + unsigned long val; + int error; + + error = strict_strtoul(buf, 0, &val); + if (error) + return error; + + mutex_lock(&ddata->lock); + + if (ddata->mode == LSM303DLH_A_MODE_OFF) { + dev_info(&ddata->client->dev, + "device is switched off,make it ON using MODE"); + mutex_unlock(&ddata->lock); + return count; + } + + ddata->interrupt_configure[ddata->interrupt_channel] = val; + + if (ddata->interrupt_channel == 0x0) + error = lsm303dlh_a_write(ddata, INT1_CFG, val, "INT1_CFG"); + else + error = lsm303dlh_a_write(ddata, INT2_CFG, val, "INT2_CFG"); + + if (error < 0) { + mutex_unlock(&ddata->lock); + return error; + } + + mutex_unlock(&ddata->lock); + + return count; +} + +static ssize_t lsm303dlh_a_show_interrupt_duration(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct platform_device *pdev = to_platform_device(dev); + struct lsm303dlh_a_data *ddata = platform_get_drvdata(pdev); + + return sprintf(buf, "%d\n", + ddata->interrupt_duration[ddata->interrupt_channel]); +} + +static ssize_t lsm303dlh_a_store_interrupt_duration(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct platform_device *pdev = to_platform_device(dev); + struct lsm303dlh_a_data *ddata = platform_get_drvdata(pdev); + unsigned long val; + int error; + + error = strict_strtoul(buf, 0, &val); + if (error) + return error; + + mutex_lock(&ddata->lock); + + + if (ddata->mode == LSM303DLH_A_MODE_OFF) { + dev_info(&ddata->client->dev, + "device is switched off,make it ON using MODE"); + mutex_unlock(&ddata->lock); + return count; + } + + ddata->interrupt_duration[ddata->interrupt_channel] = val; + + if (ddata->interrupt_channel == 0x0) + error = lsm303dlh_a_write(ddata, INT1_DURATION, val, + "INT1_DURATION"); + else + error = lsm303dlh_a_write(ddata, INT2_DURATION, val, + "INT2_DURATION"); + + if (error < 0) { + mutex_unlock(&ddata->lock); + return error; + } + + mutex_unlock(&ddata->lock); + + return count; +} + +static ssize_t lsm303dlh_a_show_interrupt_threshold(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct platform_device *pdev = to_platform_device(dev); + struct lsm303dlh_a_data *ddata = platform_get_drvdata(pdev); + + return sprintf(buf, "%d\n", + ddata->interrupt_threshold[ddata->interrupt_channel]); +} + +static ssize_t lsm303dlh_a_store_interrupt_threshold(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct platform_device *pdev = to_platform_device(dev); + struct lsm303dlh_a_data *ddata = platform_get_drvdata(pdev); + unsigned long val; + int error; + + error = strict_strtoul(buf, 0, &val); + if (error) + return error; + + mutex_lock(&ddata->lock); + + if (ddata->mode == LSM303DLH_A_MODE_OFF) { + dev_info(&ddata->client->dev, + "device is switched off,make it ON using MODE"); + mutex_unlock(&ddata->lock); + return count; + } + + ddata->interrupt_threshold[ddata->interrupt_channel] = val; + + if (ddata->interrupt_channel == 0x0) + error = lsm303dlh_a_write(ddata, INT1_THS, val, "INT1_THS"); + else + error = lsm303dlh_a_write(ddata, INT2_THS, val, "INT2_THS"); + + if (error < 0) { + mutex_unlock(&ddata->lock); + return error; + } + + mutex_unlock(&ddata->lock); + + return count; +} + +static ssize_t lsm303dlh_a_show_range(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct platform_device *pdev = to_platform_device(dev); + struct lsm303dlh_a_data *ddata = platform_get_drvdata(pdev); + + return sprintf(buf, "%d\n", ddata->range >> LSM303DLH_A_CR4_FS_BIT); +} + +static ssize_t lsm303dlh_a_store_range(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct platform_device *pdev = to_platform_device(dev); + struct lsm303dlh_a_data *ddata = platform_get_drvdata(pdev); + long val; + unsigned long bdu_enabled_val; + int error; + + + error = strict_strtol(buf, 0, &val); + if (error) + return error; + + if (val < LSM303DLH_A_RANGE_2G || val > LSM303DLH_A_RANGE_8G) + return -EINVAL; + + mutex_lock(&ddata->lock); + + if (ddata->mode == LSM303DLH_A_MODE_OFF) { + dev_info(&ddata->client->dev, + "device is switched off,make it ON using MODE"); + mutex_unlock(&ddata->lock); + return count; + } + + ddata->range = val; + ddata->range <<= LSM303DLH_A_CR4_FS_BIT; + + /* + * Block mode update is recommended for not + * ending up reading different values + */ + bdu_enabled_val = ddata->range; + bdu_enabled_val |= LSM303DLH_A_CR4_BDU_MASK; + + error = lsm303dlh_a_write(ddata, CTRL_REG4, bdu_enabled_val, + "CTRL_REG4"); + if (error < 0) { + mutex_unlock(&ddata->lock); + return error; + } + + switch (val) { + case LSM303DLH_A_RANGE_2G: + ddata->shift_adjust = SHIFT_ADJ_2G; + break; + case LSM303DLH_A_RANGE_4G: + ddata->shift_adjust = SHIFT_ADJ_4G; + break; + case LSM303DLH_A_RANGE_8G: + ddata->shift_adjust = SHIFT_ADJ_8G; + break; + default: + return -EINVAL; + } + + mutex_unlock(&ddata->lock); + + return count; +} + +static ssize_t lsm303dlh_a_show_mode(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct platform_device *pdev = to_platform_device(dev); + struct lsm303dlh_a_data *ddata = platform_get_drvdata(pdev); + + return sprintf(buf, "%d\n", ddata->mode); +} + +static ssize_t lsm303dlh_a_store_mode(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct platform_device *pdev = to_platform_device(dev); + struct lsm303dlh_a_data *ddata = platform_get_drvdata(pdev); + long val; + unsigned char data; + int error; + + error = strict_strtol(buf, 0, &val); + if (error) + return error; + + mutex_lock(&ddata->lock); + + /* not in correct range */ + + if (val < LSM303DLH_A_MODE_OFF || val > LSM303DLH_A_MODE_LP_10) { + mutex_unlock(&ddata->lock); + return -EINVAL; + } + + if (ddata->device_status == DEVICE_SUSPENDED && + val == LSM303DLH_A_MODE_OFF) { + ddata->mode = val; + mutex_unlock(&ddata->lock); + return 0; + } + + /* if same mode as existing, return */ + if (ddata->mode == val) { + mutex_unlock(&ddata->lock); + return 0; + } + + /* turn on the supplies if already off */ + if (ddata->regulator && ddata->mode == LSM303DLH_A_MODE_OFF + && (ddata->device_status == DEVICE_OFF + || ddata->device_status == DEVICE_SUSPENDED)) { + regulator_enable(ddata->regulator); + ddata->device_status = DEVICE_ON; +#ifdef CONFIG_SENSORS_LSM303DLH_INPUT_DEVICE + enable_irq(gpio_to_irq(ddata->pdata.irq_a1)); + enable_irq(gpio_to_irq(ddata->pdata.irq_a2)); +#endif + } + + data = lsm303dlh_a_read(ddata, CTRL_REG1, "CTRL_REG1"); + + data &= ~LSM303DLH_A_CR1_PM_MASK; + + ddata->mode = val; + + data |= ((val << LSM303DLH_A_CR1_PM_BIT) & LSM303DLH_A_CR1_PM_MASK); + + error = lsm303dlh_a_write(ddata, CTRL_REG1, data, "CTRL_REG1"); + if (error < 0) { + if (ddata->regulator && ddata->device_status == DEVICE_ON) { + regulator_disable(ddata->regulator); + ddata->device_status = DEVICE_OFF; + } + mutex_unlock(&ddata->lock); + return error; + } + + if (val == LSM303DLH_A_MODE_OFF) { +#ifdef CONFIG_SENSORS_LSM303DLH_INPUT_DEVICE + disable_irq(gpio_to_irq(ddata->pdata.irq_a1)); + disable_irq(gpio_to_irq(ddata->pdata.irq_a2)); +#endif + /* + * No need to store context here + * it is not like suspend/resume + * but fall back to default values + */ + ddata->rate = LSM303DLH_A_RATE_50; + ddata->range = LSM303DLH_A_RANGE_2G; + ddata->shift_adjust = SHIFT_ADJ_2G; + + if (ddata->regulator && ddata->device_status == DEVICE_ON) { + regulator_disable(ddata->regulator); + ddata->device_status = DEVICE_OFF; + } + } + mutex_unlock(&ddata->lock); + + return count; +} + +static ssize_t lsm303dlh_a_show_rate(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct platform_device *pdev = to_platform_device(dev); + struct lsm303dlh_a_data *ddata = platform_get_drvdata(pdev); + + return sprintf(buf, "%d\n", ddata->rate); +} + +static ssize_t lsm303dlh_a_store_rate(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct platform_device *pdev = to_platform_device(dev); + struct lsm303dlh_a_data *ddata = platform_get_drvdata(pdev); + long val; + unsigned char data; + int error; + + error = strict_strtol(buf, 0, &val); + if (error) + return error; + + if (val < LSM303DLH_A_RATE_50 || val > LSM303DLH_A_RATE_1000) + return -EINVAL; + + mutex_lock(&ddata->lock); + + if (ddata->mode == LSM303DLH_A_MODE_OFF) { + dev_info(&ddata->client->dev, + "device is switched off,make it ON using MODE"); + mutex_unlock(&ddata->lock); + return count; + } + + data = lsm303dlh_a_read(ddata, CTRL_REG1, "CTRL_REG1"); + + data &= ~LSM303DLH_A_CR1_DR_MASK; + + ddata->rate = val; + + data |= ((val << LSM303DLH_A_CR1_DR_BIT) & LSM303DLH_A_CR1_DR_MASK); + + error = lsm303dlh_a_write(ddata, CTRL_REG1, data, "CTRL_REG1"); + if (error < 0) { + mutex_unlock(&ddata->lock); + return error; + } + + mutex_unlock(&ddata->lock); + + return count; +} + +static ssize_t lsm303dlh_a_show_sleepwake(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct platform_device *pdev = to_platform_device(dev); + struct lsm303dlh_a_data *ddata = platform_get_drvdata(pdev); + + return sprintf(buf, "%d\n", ddata->sleep_wake); +} + +static ssize_t lsm303dlh_a_store_sleepwake(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct platform_device *pdev = to_platform_device(dev); + struct lsm303dlh_a_data *ddata = platform_get_drvdata(pdev); + long val; + int error; + + if (ddata->mode == LSM303DLH_A_MODE_OFF) { + dev_info(&ddata->client->dev, + "device is switched off,make it ON using MODE"); + return count; + } + + error = strict_strtoul(buf, 0, &val); + if (error) + return error; + + mutex_lock(&ddata->lock); + + ddata->sleep_wake = val; + + error = lsm303dlh_a_write(ddata, CTRL_REG5, ddata->sleep_wake, + "CTRL_REG5"); + if (error < 0) { + mutex_unlock(&ddata->lock); + return error; + } + + mutex_unlock(&ddata->lock); + + return count; +} + +static DEVICE_ATTR(data, S_IRUGO, lsm303dlh_a_show_data, NULL); + +static DEVICE_ATTR(range, S_IWUGO | S_IRUGO, + lsm303dlh_a_show_range, lsm303dlh_a_store_range); + +static DEVICE_ATTR(mode, S_IWUGO | S_IRUGO, + lsm303dlh_a_show_mode, lsm303dlh_a_store_mode); + +static DEVICE_ATTR(rate, S_IWUGO | S_IRUGO, + lsm303dlh_a_show_rate, lsm303dlh_a_store_rate); + +static DEVICE_ATTR(sleep_wake, S_IWUGO | S_IRUGO, + lsm303dlh_a_show_sleepwake, lsm303dlh_a_store_sleepwake); + +static DEVICE_ATTR(interrupt_control, S_IWUGO | S_IRUGO, + lsm303dlh_a_show_interrupt_control, + lsm303dlh_a_store_interrupt_control); + +static DEVICE_ATTR(interrupt_channel, S_IWUGO | S_IRUGO, + lsm303dlh_a_show_interrupt_channel, + lsm303dlh_a_store_interrupt_channel); + +static DEVICE_ATTR(interrupt_configure, S_IWUGO | S_IRUGO, + lsm303dlh_a_show_interrupt_configure, + lsm303dlh_a_store_interrupt_configure); + +static DEVICE_ATTR(interrupt_duration, S_IWUGO | S_IRUGO, + lsm303dlh_a_show_interrupt_duration, + lsm303dlh_a_store_interrupt_duration); + +static DEVICE_ATTR(interrupt_threshold, S_IWUGO | S_IRUGO, + lsm303dlh_a_show_interrupt_threshold, + lsm303dlh_a_store_interrupt_threshold); + +static struct attribute *lsm303dlh_a_attributes[] = { + &dev_attr_data.attr, + &dev_attr_range.attr, + &dev_attr_mode.attr, + &dev_attr_rate.attr, + &dev_attr_sleep_wake.attr, + &dev_attr_interrupt_control.attr, + &dev_attr_interrupt_channel.attr, + &dev_attr_interrupt_configure.attr, + &dev_attr_interrupt_duration.attr, + &dev_attr_interrupt_threshold.attr, + NULL +}; + +static const struct attribute_group lsm303dlh_a_attr_group = { + .attrs = lsm303dlh_a_attributes, +}; + +static int __devinit lsm303dlh_a_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int ret; + struct lsm303dlh_a_data *ddata = NULL; + + ddata = kzalloc(sizeof(struct lsm303dlh_a_data), GFP_KERNEL); + if (ddata == NULL) { + ret = -ENOMEM; + goto err_op_failed; + } + + ddata->client = client; + i2c_set_clientdata(client, ddata); + + /* copy platform specific data */ + memcpy(&ddata->pdata, client->dev.platform_data, sizeof(ddata->pdata)); + ddata->mode = LSM303DLH_A_MODE_OFF; + ddata->rate = LSM303DLH_A_RATE_50; + ddata->range = LSM303DLH_A_RANGE_2G; + ddata->sleep_wake = LSM303DLH_A_SLEEPWAKE_DISABLE; + ddata->shift_adjust = SHIFT_ADJ_2G; + ddata->device_status = DEVICE_OFF; + dev_set_name(&client->dev, ddata->pdata.name_a); + + ddata->regulator = regulator_get(&client->dev, "vdd"); + if (IS_ERR(ddata->regulator)) { + dev_err(&client->dev, "failed to get regulator\n"); + ret = PTR_ERR(ddata->regulator); + ddata->regulator = NULL; + } + + if (ddata->regulator) { + regulator_enable(ddata->regulator); + ddata->device_status = DEVICE_ON; + } + + ret = lsm303dlh_a_read(ddata, WHO_AM_I, "WHO_AM_I"); + if (ret < 0) + goto exit_free_regulator; + + dev_info(&client->dev, "3-Axis Accelerometer, ID : %d\n", + ret); + + mutex_init(&ddata->lock); + + ret = sysfs_create_group(&client->dev.kobj, &lsm303dlh_a_attr_group); + if (ret) + goto exit_free_regulator; + +#ifdef CONFIG_SENSORS_LSM303DLH_INPUT_DEVICE + + /* accelerometer has two interrupts channels + (thresholds,durations and sources) + and can support two input devices */ + + ddata->input_dev = input_allocate_device(); + if (!ddata->input_dev) { + ret = -ENOMEM; + dev_err(&client->dev, "Failed to allocate input device\n"); + goto exit_free_regulator; + } + + ddata->input_dev2 = input_allocate_device(); + if (!ddata->input_dev2) { + ret = -ENOMEM; + dev_err(&client->dev, "Failed to allocate input device\n"); + goto err_input_alloc_failed; + } + + set_bit(EV_ABS, ddata->input_dev->evbit); + set_bit(EV_ABS, ddata->input_dev2->evbit); + + /* x-axis acceleration */ + input_set_abs_params(ddata->input_dev, ABS_X, -32768, 32767, 0, 0); + input_set_abs_params(ddata->input_dev2, ABS_X, -32768, 32767, 0, 0); + /* y-axis acceleration */ + input_set_abs_params(ddata->input_dev, ABS_Y, -32768, 32767, 0, 0); + input_set_abs_params(ddata->input_dev2, ABS_Y, -32768, 32767, 0, 0); + /* z-axis acceleration */ + input_set_abs_params(ddata->input_dev, ABS_Z, -32768, 32767, 0, 0); + input_set_abs_params(ddata->input_dev2, ABS_Z, -32768, 32767, 0, 0); + + ddata->input_dev->name = "accelerometer"; + ddata->input_dev2->name = "motion"; + + ret = input_register_device(ddata->input_dev); + if (ret) { + dev_err(&client->dev, "Unable to register input device: %s\n", + ddata->input_dev->name); + goto err_input_register_failed; + } + + ret = input_register_device(ddata->input_dev2); + if (ret) { + dev_err(&client->dev, "Unable to register input device: %s\n", + ddata->input_dev->name); + goto err_input_register_failed2; + } + + /* Register interrupt */ + ret = request_threaded_irq(gpio_to_irq(ddata->pdata.irq_a1), NULL, + lsm303dlh_a_gpio_irq, + IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, + "lsm303dlh_a", ddata); + if (ret) { + dev_err(&client->dev, "request irq1 failed\n"); + goto err_input_failed; + } + + ret = request_threaded_irq(gpio_to_irq(ddata->pdata.irq_a2), NULL, + lsm303dlh_a_gpio_irq, + IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, + "lsm303dlh_a", ddata); + if (ret) { + dev_err(&client->dev, "request irq2 failed\n"); + goto err_input_failed; + } + + /* only mode can enable it */ + disable_irq(gpio_to_irq(ddata->pdata.irq_a1)); + disable_irq(gpio_to_irq(ddata->pdata.irq_a2)); + +#endif + +#ifdef CONFIG_HAS_EARLYSUSPEND + ddata->early_suspend.level = + EARLY_SUSPEND_LEVEL_BLANK_SCREEN + 1; + ddata->early_suspend.suspend = lsm303dlh_a_early_suspend; + ddata->early_suspend.resume = lsm303dlh_a_late_resume; + register_early_suspend(&ddata->early_suspend); +#endif + + if (ddata->device_status == DEVICE_ON && ddata->regulator) { + regulator_disable(ddata->regulator); + ddata->device_status = DEVICE_OFF; + } + return ret; + +#ifdef CONFIG_SENSORS_LSM303DLH_INPUT_DEVICE +err_input_failed: + input_unregister_device(ddata->input_dev2); +err_input_register_failed2: + input_unregister_device(ddata->input_dev); +err_input_register_failed: + input_free_device(ddata->input_dev2); +err_input_alloc_failed: + input_free_device(ddata->input_dev); +#endif +exit_free_regulator: + if (ddata->device_status == DEVICE_ON && ddata->regulator) { + regulator_disable(ddata->regulator); + regulator_put(ddata->regulator); + ddata->device_status = DEVICE_OFF; + } +err_op_failed: + kfree(ddata); + dev_err(&client->dev, "probe function fails %x", ret); + return ret; +} + +static int __devexit lsm303dlh_a_remove(struct i2c_client *client) +{ + struct lsm303dlh_a_data *ddata; + + ddata = i2c_get_clientdata(client); +#ifdef CONFIG_SENSORS_LSM303DLH_INPUT_DEVICE + input_unregister_device(ddata->input_dev); + input_unregister_device(ddata->input_dev2); + input_free_device(ddata->input_dev); + input_free_device(ddata->input_dev2); +#endif + sysfs_remove_group(&client->dev.kobj, &lsm303dlh_a_attr_group); + + /* safer to make device off */ + if (ddata->mode != LSM303DLH_A_MODE_OFF) { + lsm303dlh_a_write(ddata, CTRL_REG1, 0, "CONTROL"); + if (ddata->regulator && ddata->device_status == DEVICE_ON) { + regulator_disable(ddata->regulator); + regulator_put(ddata->regulator); + ddata->device_status = DEVICE_OFF; + } + } + + i2c_set_clientdata(client, NULL); + kfree(ddata); + + return 0; +} + +#if (!defined(CONFIG_HAS_EARLYSUSPEND) && defined(CONFIG_PM)) +static int lsm303dlh_a_suspend(struct device *dev) +{ + struct lsm303dlh_a_data *ddata; + int ret; + + ddata = dev_get_drvdata(dev); + + ret = lsm303dlh_a_do_suspend(ddata); + + return ret; +} + +static int lsm303dlh_a_resume(struct device *dev) +{ + struct lsm303dlh_a_data *ddata; + int ret; + + ddata = dev_get_drvdata(dev); + + ret = lsm303dlh_a_restore(ddata); + + if (ret < 0) + dev_err(&ddata->client->dev, + "Error while resuming the device"); + + return ret; +} +static const struct dev_pm_ops lsm303dlh_a_dev_pm_ops = { + .suspend = lsm303dlh_a_suspend, + .resume = lsm303dlh_a_resume, +}; +#else +static void lsm303dlh_a_early_suspend(struct early_suspend *data) +{ + struct lsm303dlh_a_data *ddata = + container_of(data, struct lsm303dlh_a_data, early_suspend); + int ret; + + ret = lsm303dlh_a_do_suspend(ddata); +} + +static void lsm303dlh_a_late_resume(struct early_suspend *data) +{ + struct lsm303dlh_a_data *ddata = + container_of(data, struct lsm303dlh_a_data, early_suspend); + int ret; + + ret = lsm303dlh_a_restore(ddata); + if (ret < 0) + dev_err(&ddata->client->dev, + "lsm303dlh_a late resume failed\n"); +} +#endif /* CONFIG_PM */ + +static const struct i2c_device_id lsm303dlh_a_id[] = { + { "lsm303dlh_a", 0 }, + { }, +}; + +static struct i2c_driver lsm303dlh_a_driver = { + .probe = lsm303dlh_a_probe, + .remove = lsm303dlh_a_remove, + .id_table = lsm303dlh_a_id, + .driver = { + .name = "lsm303dlh_a", + #if (!defined(CONFIG_HAS_EARLYSUSPEND) && defined(CONFIG_PM)) + .pm = &lsm303dlh_a_dev_pm_ops, + #endif + }, +}; + +static int __init lsm303dlh_a_init(void) +{ + return i2c_add_driver(&lsm303dlh_a_driver); +} + +static void __exit lsm303dlh_a_exit(void) +{ + i2c_del_driver(&lsm303dlh_a_driver); +} + +module_init(lsm303dlh_a_init) +module_exit(lsm303dlh_a_exit) + +MODULE_DESCRIPTION("lSM303DLH 3-Axis Accelerometer Driver"); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("STMicroelectronics"); diff --git a/drivers/hwmon/lsm303dlh_m.c b/drivers/hwmon/lsm303dlh_m.c new file mode 100644 index 00000000000..6f40c4b55eb --- /dev/null +++ b/drivers/hwmon/lsm303dlh_m.c @@ -0,0 +1,876 @@ +/* + * lsm303dlh_m.c + * ST 3-Axis Magnetometer Driver + * + * Copyright (C) 2010 STMicroelectronics + * Author: Carmine Iascone (carmine.iascone@st.com) + * Author: Matteo Dameno (matteo.dameno@st.com) + * + * Copyright (C) 2010 STEricsson + * Author: Mian Yousaf Kaukab + * Updated:Preetham Rao Kaskurthi + * + * 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, see . + */ + +#include +#include +#include +#include +#include +#include + +#ifdef CONFIG_SENSORS_LSM303DLH_INPUT_DEVICE +#include +#include +#include +#endif + +#include +#include +#include + +/* lsm303dlh magnetometer registers */ +#define IRA_REG_M 0x0A + +/* 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 */ +#define SR_REG_M 0x09 /* Status register */ + +/* Output register start address*/ +#define OUT_X_M 0x03 +#define OUT_Y_M 0x05 +#define OUT_Z_M 0x07 + +/* Magnetometer X-Y gain */ +#define XY_GAIN_1_3 1055 /* XY gain at 1.3G */ +#define XY_GAIN_1_9 795 /* XY gain at 1.9G */ +#define XY_GAIN_2_5 635 /* XY gain at 2.5G */ +#define XY_GAIN_4_0 430 /* XY gain at 4.0G */ +#define XY_GAIN_4_7 375 /* XY gain at 4.7G */ +#define XY_GAIN_5_6 320 /* XY gain at 5.6G */ +#define XY_GAIN_8_1 230 /* XY gain at 8.1G */ + +/* Magnetometer Z gain */ +#define Z_GAIN_1_3 950 /* Z gain at 1.3G */ +#define Z_GAIN_1_9 710 /* Z gain at 1.9G */ +#define Z_GAIN_2_5 570 /* Z gain at 2.5G */ +#define Z_GAIN_4_0 385 /* Z gain at 4.0G */ +#define Z_GAIN_4_7 335 /* Z gain at 4.7G */ +#define Z_GAIN_5_6 285 /* Z gain at 5.6G */ +#define Z_GAIN_8_1 205 /* Z gain at 8.1G */ + +/* Control A regsiter. */ +#define LSM303DLH_M_CRA_DO_BIT 2 +#define LSM303DLH_M_CRA_DO_MASK (0x7 << LSM303DLH_M_CRA_DO_BIT) +#define LSM303DLH_M_CRA_MS_BIT 0 +#define LSM303DLH_M_CRA_MS_MASK (0x3 << LSM303DLH_M_CRA_MS_BIT) + +/* Control B regsiter. */ +#define LSM303DLH_M_CRB_GN_BIT 5 +#define LSM303DLH_M_CRB_GN_MASK (0x7 << LSM303DLH_M_CRB_GN_BIT) + +/* Control Mode regsiter. */ +#define LSM303DLH_M_MR_MD_BIT 0 +#define LSM303DLH_M_MR_MD_MASK (0x3 << LSM303DLH_M_MR_MD_BIT) + +/* Control Status regsiter. */ +#define LSM303DLH_M_SR_RDY_BIT 0 +#define LSM303DLH_M_SR_RDY_MASK (0x1 << LSM303DLH_M_SR_RDY_BIT) +#define LSM303DLH_M_SR_LOC_BIT 1 +#define LSM303DLH_M_SR_LCO_MASK (0x1 << LSM303DLH_M_SR_LOC_BIT) +#define LSM303DLH_M_SR_REN_BIT 2 +#define LSM303DLH_M_SR_REN_MASK (0x1 << LSM303DLH_M_SR_REN_BIT) + +/* Magnetometer gain setting */ +#define LSM303DLH_M_RANGE_1_3G 0x01 +#define LSM303DLH_M_RANGE_1_9G 0x02 +#define LSM303DLH_M_RANGE_2_5G 0x03 +#define LSM303DLH_M_RANGE_4_0G 0x04 +#define LSM303DLH_M_RANGE_4_7G 0x05 +#define LSM303DLH_M_RANGE_5_6G 0x06 +#define LSM303DLH_M_RANGE_8_1G 0x07 + +/* Magnetometer capturing mode */ +#define LSM303DLH_M_MODE_CONTINUOUS 0 +#define LSM303DLH_M_MODE_SINGLE 1 +#define LSM303DLH_M_MODE_SLEEP 3 + +/* Magnetometer output data rate */ +#define LSM303DLH_M_RATE_00_75 0x00 +#define LSM303DLH_M_RATE_01_50 0x01 +#define LSM303DLH_M_RATE_03_00 0x02 +#define LSM303DLH_M_RATE_07_50 0x03 +#define LSM303DLH_M_RATE_15_00 0x04 +#define LSM303DLH_M_RATE_30_00 0x05 +#define LSM303DLH_M_RATE_75_00 0x06 + +/* Multiple byte transfer enable */ +#define MULTIPLE_I2C_TR 0x80 + +/* device status defines */ +#define DEVICE_OFF 0 +#define DEVICE_ON 1 +#define DEVICE_SUSPENDED 2 + +/* + * magnetometer local data + */ +struct lsm303dlh_m_data { + struct i2c_client *client; + /* lock for sysfs operations */ + struct mutex lock; + +#ifdef CONFIG_SENSORS_LSM303DLH_INPUT_DEVICE + struct input_dev *input_dev; +#endif + struct regulator *regulator; + struct lsm303dlh_platform_data pdata; + + short gain[3]; + short data[3]; + unsigned char mode; + unsigned char rate; + unsigned char range; + int device_status; +}; + +#ifdef CONFIG_HAS_EARLYSUSPEND +static void lsm303dlh_m_early_suspend(struct early_suspend *data); +static void lsm303dlh_m_late_resume(struct early_suspend *data); +#endif + +static int lsm303dlh_m_set_mode(struct lsm303dlh_m_data *ddata, + unsigned char mode); +static int lsm303dlh_m_write(struct lsm303dlh_m_data *ddata, + u8 reg, u8 val, char *msg) +{ + int ret = i2c_smbus_write_byte_data(ddata->client, reg, val); + if (ret < 0) + dev_err(&ddata->client->dev, + "i2c_smbus_write_byte_data failed error %d\ + Register (%s)\n", ret, msg); + return ret; +} + +static int lsm303dlh_m_do_suspend(struct lsm303dlh_m_data *ddata) +{ + int ret; + + mutex_lock(&ddata->lock); + + if (ddata->mode == LSM303DLH_M_MODE_SLEEP) { + mutex_unlock(&ddata->lock); + return 0; + } + +#ifdef CONFIG_SENSORS_LSM303DLH_INPUT_DEVICE + disable_irq(gpio_to_irq(ddata->pdata.irq_m)); +#endif + + ret = lsm303dlh_m_set_mode(ddata, LSM303DLH_M_MODE_SLEEP); + + if (ddata->regulator) + regulator_disable(ddata->regulator); + + ddata->device_status = DEVICE_SUSPENDED; + + mutex_unlock(&ddata->lock); + + return ret; +} + +static int lsm303dlh_m_restore(struct lsm303dlh_m_data *ddata) +{ + int ret = 0; + + mutex_lock(&ddata->lock); + + if (ddata->device_status == DEVICE_ON) { + mutex_unlock(&ddata->lock); + return 0; + } + + /* in correct mode, no need to change it */ + if (ddata->mode == LSM303DLH_M_MODE_SLEEP) { + ddata->device_status = DEVICE_OFF; + mutex_unlock(&ddata->lock); + return 0; + } else + ddata->device_status = DEVICE_ON; + +#ifdef CONFIG_SENSORS_LSM303DLH_INPUT_DEVICE + enable_irq(gpio_to_irq(ddata->pdata.irq_m)); +#endif + + if (ddata->regulator) + regulator_enable(ddata->regulator); + + ret = lsm303dlh_m_write(ddata, CRB_REG_M, ddata->range, "SET RANGE"); + + if (ret < 0) + goto fail; + + ret = lsm303dlh_m_write(ddata, CRA_REG_M, ddata->rate, "SET RATE"); + + if (ret < 0) + goto fail; + + ret = lsm303dlh_m_set_mode(ddata, ddata->mode); + + if (ret < 0) + goto fail; + +fail: + mutex_unlock(&ddata->lock); + return ret; +} + +static int lsm303dlh_m_read_multi(struct lsm303dlh_m_data *ddata, u8 reg, + u8 count, u8 *val, char *msg) +{ + int ret = i2c_smbus_read_i2c_block_data(ddata->client, + reg | MULTIPLE_I2C_TR, count, val); + if (ret < 0) + dev_err(&ddata->client->dev, + "i2c_smbus_read_i2c_block_data failed error %d\ + Register (%s)\n", ret, msg); + return ret; +} + +static ssize_t lsm303dlh_m_show_rate(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct platform_device *pdev = to_platform_device(dev); + struct lsm303dlh_m_data *ddata = platform_get_drvdata(pdev); + + return sprintf(buf, "%d\n", ddata->rate >> LSM303DLH_M_CRA_DO_BIT); +} + +/* set lsm303dlh magnetometer bandwidth */ +static ssize_t lsm303dlh_m_store_rate(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct platform_device *pdev = to_platform_device(dev); + struct lsm303dlh_m_data *ddata = platform_get_drvdata(pdev); + unsigned long val; + unsigned char data; + int error; + + error = strict_strtoul(buf, 0, &val); + if (error) + return error; + + mutex_lock(&ddata->lock); + if (ddata->mode == LSM303DLH_M_MODE_SLEEP) { + dev_info(&ddata->client->dev, + "device is switched off,make it ON using MODE"); + mutex_unlock(&ddata->lock); + return count; + } + + data = ((val << LSM303DLH_M_CRA_DO_BIT) & LSM303DLH_M_CRA_DO_MASK); + ddata->rate = data; + + error = lsm303dlh_m_write(ddata, CRA_REG_M, data, "SET RATE"); + + if (error < 0) { + mutex_unlock(&ddata->lock); + return error; + } + + mutex_unlock(&ddata->lock); + + return count; +} + +static int lsm303dlh_m_xyz_read(struct lsm303dlh_m_data *ddata) +{ + unsigned char xyz_data[6]; + int ret = lsm303dlh_m_read_multi(ddata, OUT_X_M, + 6, xyz_data, "OUT_X_M"); + if (ret < 0) + return -EINVAL; + + /* MSB is at lower address */ + ddata->data[0] = (short) + (((xyz_data[0]) << 8) | xyz_data[1]); + ddata->data[1] = (short) + (((xyz_data[2]) << 8) | xyz_data[3]); + ddata->data[2] = (short) + (((xyz_data[4]) << 8) | xyz_data[5]); + + /* taking orientation of x,y,z axis into account*/ + + ddata->data[ddata->pdata.axis_map_x] = ddata->pdata.negative_x ? + -ddata->data[ddata->pdata.axis_map_x] : + ddata->data[ddata->pdata.axis_map_x]; + ddata->data[ddata->pdata.axis_map_y] = ddata->pdata.negative_y ? + -ddata->data[ddata->pdata.axis_map_y] : + ddata->data[ddata->pdata.axis_map_y]; + ddata->data[ddata->pdata.axis_map_z] = ddata->pdata.negative_z ? + -ddata->data[ddata->pdata.axis_map_z] : + ddata->data[ddata->pdata.axis_map_z]; + + return ret; +} + +static ssize_t lsm303dlh_m_gain(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct platform_device *pdev = to_platform_device(dev); + struct lsm303dlh_m_data *ddata = platform_get_drvdata(pdev); + + return sprintf(buf, "%8x:%8x:%8x\n", + ddata->gain[ddata->pdata.axis_map_x], + ddata->gain[ddata->pdata.axis_map_y], + ddata->gain[ddata->pdata.axis_map_z]); +} + +static ssize_t lsm303dlh_m_values(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct platform_device *pdev = to_platform_device(dev); + struct lsm303dlh_m_data *ddata = platform_get_drvdata(pdev); + int ret = 0; + + mutex_lock(&ddata->lock); + + if (ddata->mode == LSM303DLH_M_MODE_SLEEP || + ddata->device_status == DEVICE_SUSPENDED) { + mutex_unlock(&ddata->lock); + return ret; + } + + ret = lsm303dlh_m_xyz_read(ddata); + + if (ret < 0) { + mutex_unlock(&ddata->lock); + return -EINVAL; + } + + mutex_unlock(&ddata->lock); + + /* taking orientation of x,y,z axis into account*/ + + return sprintf(buf, "%8x:%8x:%8x\n", + ddata->data[ddata->pdata.axis_map_x], + ddata->data[ddata->pdata.axis_map_y], + ddata->data[ddata->pdata.axis_map_z]); +} + +static int lsm303dlh_m_set_mode(struct lsm303dlh_m_data *ddata, + unsigned char mode) +{ + int ret; + + mode = (mode << LSM303DLH_M_MR_MD_BIT); + + ret = i2c_smbus_write_byte_data(ddata->client, MR_REG_M, mode); + + if (ret < 0) + dev_err(&ddata->client->dev, + "i2c_smbus_write_byte_data failed error %d\ + Register (%s)\n", ret, "MODE CONTROL"); + + return ret; +} + +#ifdef CONFIG_SENSORS_LSM303DLH_INPUT_DEVICE + +static irqreturn_t lsm303dlh_m_gpio_irq(int irq, void *device_data) +{ + struct lsm303dlh_m_data *ddata = device_data; + int ret; + + ret = lsm303dlh_m_xyz_read(ddata); + + if (ret < 0) { + dev_err(&ddata->client->dev, + "reading data of xyz failed error %d\n", ret); + return IRQ_NONE; + } + + /* taking orientation of x,y,z axis into account*/ + + input_report_abs(ddata->input_dev, ABS_X, + ddata->data[ddata->pdata.axis_map_x]); + input_report_abs(ddata->input_dev, ABS_Y, + ddata->data[ddata->pdata.axis_map_y]); + input_report_abs(ddata->input_dev, ABS_Z, + ddata->data[ddata->pdata.axis_map_z]); + input_sync(ddata->input_dev); + + return IRQ_HANDLED; + +} +#endif + +static ssize_t lsm303dlh_m_show_range(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct platform_device *pdev = to_platform_device(dev); + struct lsm303dlh_m_data *ddata = platform_get_drvdata(pdev); + + return sprintf(buf, "%d\n", ddata->range >> LSM303DLH_M_CRB_GN_BIT); +} + +static ssize_t lsm303dlh_m_store_range(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct platform_device *pdev = to_platform_device(dev); + struct lsm303dlh_m_data *ddata = platform_get_drvdata(pdev); + short xy_gain; + short z_gain; + unsigned long range; + int error; + + error = strict_strtoul(buf, 0, &range); + + if (error) + return error; + + mutex_lock(&ddata->lock); + + if (ddata->mode == LSM303DLH_M_MODE_SLEEP) { + dev_info(&ddata->client->dev, + "device is switched off,make it ON using MODE"); + mutex_unlock(&ddata->lock); + return count; + } + + switch (range) { + case LSM303DLH_M_RANGE_1_3G: + xy_gain = XY_GAIN_1_3; + z_gain = Z_GAIN_1_3; + break; + case LSM303DLH_M_RANGE_1_9G: + xy_gain = XY_GAIN_1_9; + z_gain = Z_GAIN_1_9; + break; + case LSM303DLH_M_RANGE_2_5G: + xy_gain = XY_GAIN_2_5; + z_gain = Z_GAIN_2_5; + break; + case LSM303DLH_M_RANGE_4_0G: + xy_gain = XY_GAIN_4_0; + z_gain = Z_GAIN_4_0; + break; + case LSM303DLH_M_RANGE_4_7G: + xy_gain = XY_GAIN_4_7; + z_gain = Z_GAIN_4_7; + break; + case LSM303DLH_M_RANGE_5_6G: + xy_gain = XY_GAIN_5_6; + z_gain = Z_GAIN_5_6; + break; + case LSM303DLH_M_RANGE_8_1G: + xy_gain = XY_GAIN_8_1; + z_gain = Z_GAIN_8_1; + break; + default: + return -EINVAL; + } + + ddata->gain[ddata->pdata.axis_map_x] = xy_gain; + ddata->gain[ddata->pdata.axis_map_y] = xy_gain; + ddata->gain[ddata->pdata.axis_map_z] = z_gain; + + range <<= LSM303DLH_M_CRB_GN_BIT; + range &= LSM303DLH_M_CRB_GN_MASK; + + ddata->range = range; + + error = lsm303dlh_m_write(ddata, CRB_REG_M, range, "SET RANGE"); + mutex_unlock(&ddata->lock); + + if (error < 0) + return error; + + return count; +} + +static ssize_t lsm303dlh_m_show_mode(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct platform_device *pdev = to_platform_device(dev); + struct lsm303dlh_m_data *ddata = platform_get_drvdata(pdev); + + return sprintf(buf, "%d\n", ddata->mode); +} + +static ssize_t lsm303dlh_m_store_mode(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct platform_device *pdev = to_platform_device(dev); + struct lsm303dlh_m_data *ddata = platform_get_drvdata(pdev); + unsigned long mode; + int error; + + error = strict_strtoul(buf, 0, &mode); + if (error) + return error; + + mutex_lock(&ddata->lock); + + if (ddata->device_status == DEVICE_SUSPENDED && + mode == LSM303DLH_M_MODE_SLEEP) { + ddata->mode = (mode >> LSM303DLH_M_MR_MD_BIT); + mutex_unlock(&ddata->lock); + return 0; + } + + /* if same mode as existing, return */ + if (ddata->mode == mode) { + mutex_unlock(&ddata->lock); + return 0; + } + + /* turn on the supplies if already off */ + if (ddata->mode == LSM303DLH_M_MODE_SLEEP && ddata->regulator + && (ddata->device_status == DEVICE_OFF + || ddata->device_status == DEVICE_SUSPENDED)) { + regulator_enable(ddata->regulator); + ddata->device_status = DEVICE_ON; + +#ifdef CONFIG_SENSORS_LSM303DLH_INPUT_DEVICE + enable_irq(gpio_to_irq(ddata->pdata.irq_m)); +#endif + } + + error = lsm303dlh_m_set_mode(ddata, mode); + + ddata->mode = (mode >> LSM303DLH_M_MR_MD_BIT); + if (error < 0) { + if (ddata->regulator && ddata->device_status == DEVICE_ON) { + regulator_disable(ddata->regulator); + ddata->device_status = DEVICE_OFF; + } + mutex_unlock(&ddata->lock); + return error; + } + + if (mode == LSM303DLH_M_MODE_SLEEP) { + +#ifdef CONFIG_SENSORS_LSM303DLH_INPUT_DEVICE + disable_irq(gpio_to_irq(ddata->pdata.irq_m)); +#endif + + /* + * No need to store context here, it is not like + * suspend/resume but fall back to default values + */ + ddata->rate = LSM303DLH_M_RATE_00_75; + ddata->range = LSM303DLH_M_RANGE_1_3G; + ddata->range <<= LSM303DLH_M_CRB_GN_BIT; + ddata->range &= LSM303DLH_M_CRB_GN_MASK; + ddata->gain[ddata->pdata.axis_map_x] = XY_GAIN_1_3; + ddata->gain[ddata->pdata.axis_map_y] = XY_GAIN_1_3; + ddata->gain[ddata->pdata.axis_map_z] = Z_GAIN_1_3; + + if (ddata->regulator && ddata->device_status == DEVICE_ON) { + regulator_disable(ddata->regulator); + ddata->device_status = DEVICE_OFF; + } + } + mutex_unlock(&ddata->lock); + + return count; +} + +static DEVICE_ATTR(gain, S_IRUGO, lsm303dlh_m_gain, NULL); + +static DEVICE_ATTR(data, S_IRUGO, lsm303dlh_m_values, NULL); + +static DEVICE_ATTR(mode, S_IWUGO | S_IRUGO, + lsm303dlh_m_show_mode, lsm303dlh_m_store_mode); + +static DEVICE_ATTR(range, S_IWUGO | S_IRUGO, + lsm303dlh_m_show_range, lsm303dlh_m_store_range); + +static DEVICE_ATTR(rate, S_IWUGO | S_IRUGO, + lsm303dlh_m_show_rate, lsm303dlh_m_store_rate); + +static struct attribute *lsm303dlh_m_attributes[] = { + &dev_attr_gain.attr, + &dev_attr_data.attr, + &dev_attr_mode.attr, + &dev_attr_range.attr, + &dev_attr_rate.attr, + NULL +}; + +static const struct attribute_group lsm303dlh_m_attr_group = { + .attrs = lsm303dlh_m_attributes, +}; + +static int __devinit lsm303dlh_m_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int ret; + struct lsm303dlh_m_data *ddata = NULL; + unsigned char version[3]; + + ddata = kzalloc(sizeof(struct lsm303dlh_m_data), GFP_KERNEL); + if (ddata == NULL) { + ret = -ENOMEM; + goto err_op_failed; + } + + ddata->client = client; + i2c_set_clientdata(client, ddata); + + /* copy platform specific data */ + memcpy(&ddata->pdata, client->dev.platform_data, sizeof(ddata->pdata)); + + ddata->mode = LSM303DLH_M_MODE_SLEEP; + ddata->rate = LSM303DLH_M_RATE_00_75; + ddata->range = LSM303DLH_M_RANGE_1_3G; + ddata->range <<= LSM303DLH_M_CRB_GN_BIT; + ddata->range &= LSM303DLH_M_CRB_GN_MASK; + ddata->gain[ddata->pdata.axis_map_x] = XY_GAIN_1_3; + ddata->gain[ddata->pdata.axis_map_y] = XY_GAIN_1_3; + ddata->gain[ddata->pdata.axis_map_z] = Z_GAIN_1_3; + ddata->device_status = DEVICE_OFF; + dev_set_name(&client->dev, ddata->pdata.name_m); + ddata->regulator = regulator_get(&client->dev, "vdd"); + + if (IS_ERR(ddata->regulator)) { + dev_err(&client->dev, "failed to get regulator\n"); + ret = PTR_ERR(ddata->regulator); + ddata->regulator = NULL; + } + + if (ddata->regulator) { + regulator_enable(ddata->regulator); + ddata->device_status = DEVICE_ON; + } + + ret = lsm303dlh_m_read_multi(ddata, IRA_REG_M, 3, version, "IRA_REG_M"); + if (ret < 0) + goto exit_free_regulator; + + dev_info(&client->dev, "Magnetometer, ID : %x:%x:%x", + version[0], version[1], version[2]); + + mutex_init(&ddata->lock); + + ret = sysfs_create_group(&client->dev.kobj, &lsm303dlh_m_attr_group); + if (ret) + goto exit_free_regulator; + +#ifdef CONFIG_SENSORS_LSM303DLH_INPUT_DEVICE + + ddata->input_dev = input_allocate_device(); + if (!ddata->input_dev) { + ret = -ENOMEM; + dev_err(&client->dev, "Failed to allocate input device\n"); + goto exit_free_regulator; + } + + set_bit(EV_ABS, ddata->input_dev->evbit); + + /* x-axis acceleration */ + input_set_abs_params(ddata->input_dev, ABS_X, -32768, 32767, 0, 0); + /* y-axis acceleration */ + input_set_abs_params(ddata->input_dev, ABS_Y, -32768, 32767, 0, 0); + /* z-axis acceleration */ + input_set_abs_params(ddata->input_dev, ABS_Z, -32768, 32767, 0, 0); + + ddata->input_dev->name = "magnetometer"; + + ret = input_register_device(ddata->input_dev); + if (ret) { + dev_err(&client->dev, "Unable to register input device: %s\n", + ddata->input_dev->name); + goto err_input_register_failed; + } + + /* register interrupt */ + ret = request_threaded_irq(gpio_to_irq(ddata->pdata.irq_m), NULL, + lsm303dlh_m_gpio_irq, + IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, "lsm303dlh_m", + ddata); + if (ret) { + dev_err(&client->dev, "request irq EGPIO_PIN_1 failed\n"); + goto err_input_failed; + } + + disable_irq(gpio_to_irq(ddata->pdata.irq_m)); +#endif + +#ifdef CONFIG_HAS_EARLYSUSPEND + ddata->early_suspend.level = + EARLY_SUSPEND_LEVEL_BLANK_SCREEN + 1; + ddata->early_suspend.suspend = lsm303dlh_m_early_suspend; + ddata->early_suspend.resume = lsm303dlh_m_late_resume; + register_early_suspend(&ddata->early_suspend); +#endif + + if (ddata->device_status == DEVICE_ON && ddata->regulator) { + regulator_disable(ddata->regulator); + ddata->device_status = DEVICE_OFF; + } + + return ret; + +#ifdef CONFIG_SENSORS_LSM303DLH_INPUT_DEVICE +err_input_failed: + input_unregister_device(ddata->input_dev); +err_input_register_failed: + input_free_device(ddata->input_dev); +#endif +exit_free_regulator: + if (ddata->device_status == DEVICE_ON && ddata->regulator) { + regulator_disable(ddata->regulator); + regulator_put(ddata->regulator); + ddata->device_status = DEVICE_OFF; + } +err_op_failed: + dev_err(&client->dev, "lsm303dlh_m_probe failed %x", ret); + kfree(ddata); + return ret; +} + +static int __devexit lsm303dlh_m_remove(struct i2c_client *client) +{ + struct lsm303dlh_m_data *ddata; + + ddata = i2c_get_clientdata(client); + +#ifdef CONFIG_SENSORS_LSM303DLH_INPUT_DEVICE + input_unregister_device(ddata->input_dev); + input_free_device(ddata->input_dev); +#endif + + sysfs_remove_group(&client->dev.kobj, &lsm303dlh_m_attr_group); + + /* safer to make device off */ + if (ddata->mode != LSM303DLH_M_MODE_SLEEP) { + lsm303dlh_m_set_mode(ddata, LSM303DLH_M_MODE_SLEEP); + if (ddata->regulator && ddata->device_status == DEVICE_ON) { + regulator_disable(ddata->regulator); + regulator_put(ddata->regulator); + ddata->device_status = DEVICE_OFF; + } + } + + i2c_set_clientdata(client, NULL); + kfree(ddata); + + return 0; +} + +#if (!defined(CONFIG_HAS_EARLYSUSPEND) && defined(CONFIG_PM)) +static int lsm303dlh_m_suspend(struct device *dev) +{ + struct lsm303dlh_m_data *ddata; + int ret; + + ddata = dev_get_drvdata(dev); + + ret = lsm303dlh_m_do_suspend(ddata); + if (ret < 0) + dev_err(&ddata->client->dev, + "Error while suspending the device"); + + return ret; +} + +static int lsm303dlh_m_resume(struct device *dev) +{ + struct lsm303dlh_m_data *ddata; + int ret; + + ddata = dev_get_drvdata(dev); + + ret = lsm303dlh_m_restore(ddata); + + if (ret < 0) + dev_err(&ddata->client->dev, + "Error while resuming the device"); + + return ret; +} +static const struct dev_pm_ops lsm303dlh_m_dev_pm_ops = { + .suspend = lsm303dlh_m_suspend, + .resume = lsm303dlh_m_resume, +}; +#else +static void lsm303dlh_m_early_suspend(struct early_suspend *data) +{ + struct lsm303dlh_m_data *ddata = + container_of(data, struct lsm303dlh_m_data, early_suspend); + int ret; + + ret = lsm303dlh_m_do_suspend(ddata); + if (ret < 0) + dev_err(&ddata->client->dev, + "Error while suspending the device"); +} + +static void lsm303dlh_m_late_resume(struct early_suspend *data) +{ + struct lsm303dlh_m_data *ddata = + container_of(data, struct lsm303dlh_m_data, early_suspend); + int ret; + + ret = lsm303dlh_m_restore(ddata); + + if (ret < 0) + dev_err(&ddata->client->dev, + "lsm303dlh_m late resume failed\n"); +} +#endif /* CONFIG_PM */ + +static const struct i2c_device_id lsm303dlh_m_id[] = { + { "lsm303dlh_m", 0 }, + { }, +}; + +static struct i2c_driver lsm303dlh_m_driver = { + .probe = lsm303dlh_m_probe, + .remove = lsm303dlh_m_remove, + .id_table = lsm303dlh_m_id, + .driver = { + .name = "lsm303dlh_m", + #if (!defined(CONFIG_HAS_EARLYSUSPEND) && defined(CONFIG_PM)) + .pm = &lsm303dlh_m_dev_pm_ops, + #endif + }, +}; + +static int __init lsm303dlh_m_init(void) +{ + return i2c_add_driver(&lsm303dlh_m_driver); +} + +static void __exit lsm303dlh_m_exit(void) +{ + i2c_del_driver(&lsm303dlh_m_driver); +} + +module_init(lsm303dlh_m_init); +module_exit(lsm303dlh_m_exit); + +MODULE_DESCRIPTION("lSM303DLH 3-Axis Magnetometer Driver"); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("STMicroelectronics"); diff --git a/include/linux/lsm303dlh.h b/include/linux/lsm303dlh.h new file mode 100644 index 00000000000..d9fd6148198 --- /dev/null +++ b/include/linux/lsm303dlh.h @@ -0,0 +1,56 @@ +/* + * lsm303dlh.h + * ST 3-Axis Accelerometer/Magnetometer header file + * + * Copyright (C) 2010 STMicroelectronics + * Author: Carmine Iascone (carmine.iascone@st.com) + * Author: Matteo Dameno (matteo.dameno@st.com) + * + * Copyright (C) 2010 STEricsson + * Author: Mian Yousaf Kaukab + * Updated:Preetham Rao Kaskurthi + * + * 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, see . + */ + +#ifndef __LSM303DLH_H__ +#define __LSM303DLH_H__ + +#include + +#ifdef __KERNEL__ +struct lsm303dlh_platform_data { + + /* name of device for regulator */ + + const char *name_a; /* acelerometer name */ + const char *name_m; /* magnetometer name */ + + /* interrupt data */ + u32 irq_a1; /* interrupt line 1 of accelrometer*/ + u32 irq_a2; /* interrupt line 2 of accelrometer*/ + u32 irq_m; /* interrupt line of magnetometer*/ + + /* position of x,y and z axis */ + u8 axis_map_x; /* [0-2] */ + u8 axis_map_y; /* [0-2] */ + u8 axis_map_z; /* [0-2] */ + + /* orientation of x,y and z axis */ + u8 negative_x; /* [0-1] */ + u8 negative_y; /* [0-1] */ + u8 negative_z; /* [0-1] */ +}; +#endif /* __KERNEL__ */ + +#endif /* __LSM303DLH_H__ */ -- cgit v1.2.3 From 1f0b8406e6f78e1a42a50289f0f3c816936a025a Mon Sep 17 00:00:00 2001 From: Chethan Krishna N Date: Tue, 8 Mar 2011 15:18:34 +0530 Subject: lsm303dlh: Reboot memory content after suspend/resume It is observed that device screen orientation doesn't behave as expected sometimes after suspend/resume. Rebooting the device's internal registers to ensure good device behavior. ST Ericsson ID : ER327596 Signed-off-by: Chethan Krishna N Change-Id: I6bdecf0959a3497b00d57d907d5a7a7d67b12c32 Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/17814 Reviewed-by: Srinidhi KASAGAR --- drivers/hwmon/lsm303dlh_a.c | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/drivers/hwmon/lsm303dlh_a.c b/drivers/hwmon/lsm303dlh_a.c index bbcf1663061..307a07cb031 100644 --- a/drivers/hwmon/lsm303dlh_a.c +++ b/drivers/hwmon/lsm303dlh_a.c @@ -324,6 +324,8 @@ static int lsm303dlh_a_restore(struct lsm303dlh_a_data *ddata) goto fail; fail: + /* write to the boot bit to reboot memory content */ + lsm303dlh_a_write(ddata, CTRL_REG2, 0x80, "CTRL_REG2"); mutex_unlock(&ddata->lock); return ret; } @@ -757,6 +759,7 @@ static ssize_t lsm303dlh_a_store_mode(struct device *dev, long val; unsigned char data; int error; + bool set_boot_bit = false; error = strict_strtol(buf, 0, &val); if (error) @@ -771,11 +774,15 @@ static ssize_t lsm303dlh_a_store_mode(struct device *dev, return -EINVAL; } - if (ddata->device_status == DEVICE_SUSPENDED && - val == LSM303DLH_A_MODE_OFF) { - ddata->mode = val; - mutex_unlock(&ddata->lock); - return 0; + if (ddata->device_status == DEVICE_SUSPENDED) { + if (val == LSM303DLH_A_MODE_OFF) { + ddata->mode = val; + mutex_unlock(&ddata->lock); + return 0; + } else { + /* device is turning on after suspend, reset memory */ + set_boot_bit = true; + } } /* if same mode as existing, return */ @@ -814,6 +821,14 @@ static ssize_t lsm303dlh_a_store_mode(struct device *dev, return error; } + /* + * Power on request when device is in suspended state + * write to the boot bit in CTRL_REG2 to reboot memory content + * and ensure correct device behavior after it resumes + */ + if (set_boot_bit) + lsm303dlh_a_write(ddata, CTRL_REG2, 0x80, "CTRL_REG2"); + if (val == LSM303DLH_A_MODE_OFF) { #ifdef CONFIG_SENSORS_LSM303DLH_INPUT_DEVICE disable_irq(gpio_to_irq(ddata->pdata.irq_a1)); -- cgit v1.2.3 From 58a50b100916cd2571e437796ee7c737c5c98171 Mon Sep 17 00:00:00 2001 From: Chethan Krishna N Date: Tue, 15 Mar 2011 11:15:06 +0530 Subject: lsm303dlh: Check written values for lsm303dlh_a_write calls Check written values for all calls of lsm303dlh_a_write function calls. ST Ericsson ID: ER328752 Change-Id: I88d1bc5d92cebb7c3483b3ba3073d155336c4909 Signed-off-by: Chethan Krishna N Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/18317 Reviewed-by: Srinidhi KASAGAR --- drivers/hwmon/lsm303dlh_a.c | 32 +++++++++++++++++++++++++++----- 1 file changed, 27 insertions(+), 5 deletions(-) diff --git a/drivers/hwmon/lsm303dlh_a.c b/drivers/hwmon/lsm303dlh_a.c index 307a07cb031..6d413cb837f 100644 --- a/drivers/hwmon/lsm303dlh_a.c +++ b/drivers/hwmon/lsm303dlh_a.c @@ -278,6 +278,12 @@ static int lsm303dlh_a_restore(struct lsm303dlh_a_data *ddata) ret = lsm303dlh_a_write(ddata, CTRL_REG4, reg, "CTRL_REG4"); + if (ret < 0) + goto fail; + + /* write to the boot bit to reboot memory content */ + ret = lsm303dlh_a_write(ddata, CTRL_REG2, 0x80, "CTRL_REG2"); + if (ret < 0) goto fail; @@ -324,8 +330,8 @@ static int lsm303dlh_a_restore(struct lsm303dlh_a_data *ddata) goto fail; fail: - /* write to the boot bit to reboot memory content */ - lsm303dlh_a_write(ddata, CTRL_REG2, 0x80, "CTRL_REG2"); + if (ret < 0) + dev_err(&ddata->client->dev, "could not restore the device %d\n", ret); mutex_unlock(&ddata->lock); return ret; } @@ -826,8 +832,17 @@ static ssize_t lsm303dlh_a_store_mode(struct device *dev, * write to the boot bit in CTRL_REG2 to reboot memory content * and ensure correct device behavior after it resumes */ - if (set_boot_bit) - lsm303dlh_a_write(ddata, CTRL_REG2, 0x80, "CTRL_REG2"); + if (set_boot_bit) { + error = lsm303dlh_a_write(ddata, CTRL_REG2, 0x80, "CTRL_REG2"); + if (error < 0) { + if (ddata->regulator && ddata->device_status == DEVICE_ON) { + regulator_disable(ddata->regulator); + ddata->device_status = DEVICE_OFF; + } + mutex_unlock(&ddata->lock); + return error; + } + } if (val == LSM303DLH_A_MODE_OFF) { #ifdef CONFIG_SENSORS_LSM303DLH_INPUT_DEVICE @@ -1168,6 +1183,7 @@ err_op_failed: static int __devexit lsm303dlh_a_remove(struct i2c_client *client) { + int ret; struct lsm303dlh_a_data *ddata; ddata = i2c_get_clientdata(client); @@ -1181,7 +1197,13 @@ static int __devexit lsm303dlh_a_remove(struct i2c_client *client) /* safer to make device off */ if (ddata->mode != LSM303DLH_A_MODE_OFF) { - lsm303dlh_a_write(ddata, CTRL_REG1, 0, "CONTROL"); + ret = lsm303dlh_a_write(ddata, CTRL_REG1, 0, "CONTROL"); + + if (ret < 0) { + dev_err(&client->dev, "could not turn off the device %d", ret); + return ret; + } + if (ddata->regulator && ddata->device_status == DEVICE_ON) { regulator_disable(ddata->regulator); regulator_put(ddata->regulator); -- cgit v1.2.3 From 3ac8324b3a4a8f6f46efab99000dfd89e7f3bbf8 Mon Sep 17 00:00:00 2001 From: Chethan Krishna N Date: Tue, 12 Apr 2011 16:43:53 +0530 Subject: lsm303dlh/l3g4200d: return count after storing mode corrects behavior while returning from store mode function calls. ST Ericsson ID : AP335036 Signed-off-by: Chethan Krishna N Change-Id: Ifa9a99eb713a51e1fc5b53eaacefacbf1db26d90 Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/20697 Reviewed-by: Chethan Krishna N Tested-by: Chethan Krishna N Reviewed-by: Srinidhi KASAGAR --- drivers/hwmon/l3g4200d.c | 4 ++-- drivers/hwmon/lsm303dlh_a.c | 4 ++-- drivers/hwmon/lsm303dlh_m.c | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/hwmon/l3g4200d.c b/drivers/hwmon/l3g4200d.c index bd78d4b003b..cb362ab7254 100644 --- a/drivers/hwmon/l3g4200d.c +++ b/drivers/hwmon/l3g4200d.c @@ -325,13 +325,13 @@ static ssize_t l3g4200d_store_powermode(struct device *dev, received_value == PM_OFF) { ddata->powermode = received_value; mutex_unlock(&ddata->lock); - return 0; + return count; } /* if sent value is same as current value do nothing */ if (ddata->powermode == received_value) { mutex_unlock(&ddata->lock); - return 0; + return count; } /* turn on the power suppliy if it was turned off previously */ diff --git a/drivers/hwmon/lsm303dlh_a.c b/drivers/hwmon/lsm303dlh_a.c index 6d413cb837f..7721310be06 100644 --- a/drivers/hwmon/lsm303dlh_a.c +++ b/drivers/hwmon/lsm303dlh_a.c @@ -784,7 +784,7 @@ static ssize_t lsm303dlh_a_store_mode(struct device *dev, if (val == LSM303DLH_A_MODE_OFF) { ddata->mode = val; mutex_unlock(&ddata->lock); - return 0; + return count; } else { /* device is turning on after suspend, reset memory */ set_boot_bit = true; @@ -794,7 +794,7 @@ static ssize_t lsm303dlh_a_store_mode(struct device *dev, /* if same mode as existing, return */ if (ddata->mode == val) { mutex_unlock(&ddata->lock); - return 0; + return count; } /* turn on the supplies if already off */ diff --git a/drivers/hwmon/lsm303dlh_m.c b/drivers/hwmon/lsm303dlh_m.c index 6f40c4b55eb..8352b3d0281 100644 --- a/drivers/hwmon/lsm303dlh_m.c +++ b/drivers/hwmon/lsm303dlh_m.c @@ -535,13 +535,13 @@ static ssize_t lsm303dlh_m_store_mode(struct device *dev, mode == LSM303DLH_M_MODE_SLEEP) { ddata->mode = (mode >> LSM303DLH_M_MR_MD_BIT); mutex_unlock(&ddata->lock); - return 0; + return count; } /* if same mode as existing, return */ if (ddata->mode == mode) { mutex_unlock(&ddata->lock); - return 0; + return count; } /* turn on the supplies if already off */ -- cgit v1.2.3 From cc2845825a8b539b47f960ef3a750bf2e6034c08 Mon Sep 17 00:00:00 2001 From: Philippe Langlais Date: Thu, 20 Oct 2011 10:31:40 +0200 Subject: lsm303dlh: add DocBook documentation Adding DocBook documentation for lsm303dlh accelerometer and magnetometer drivers. ST-Ericsson ID: 277198 Change-Id: Idfca43aa6ffaa39b7f73d0135dc6d2b01a0a44c6 Signed-off-by: Chethan Krishna N Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/23784 Reviewed-by: Srinidhi KASAGAR Conflicts: Documentation/DocBook/Makefile --- Documentation/DocBook/lsm303dlh.tmpl | 90 ++++++++++++++++++++++++++++++++++++ drivers/hwmon/lsm303dlh_a.c | 23 ++++++++- drivers/hwmon/lsm303dlh_m.c | 16 ++++++- include/linux/lsm303dlh.h | 45 ++++++++++-------- 4 files changed, 150 insertions(+), 24 deletions(-) create mode 100644 Documentation/DocBook/lsm303dlh.tmpl diff --git a/Documentation/DocBook/lsm303dlh.tmpl b/Documentation/DocBook/lsm303dlh.tmpl new file mode 100644 index 00000000000..3b1c6afa65f --- /dev/null +++ b/Documentation/DocBook/lsm303dlh.tmpl @@ -0,0 +1,90 @@ + + + + + + LSM303DLH Accelerometer and Magnetometer + + + + Chethan Krishna + N + +
+ chethan.krishna@stericsson.com +
+
+
+
+ + + 2010 + ST-Ericsson + + + + + Linux standard functions + + + + + + License terms: GNU General Public License (GPL) version 2. + + + +
+ + + + + Introduction + + This documentation describes the accelerometer and magnetometer drivers for lsm303dlh sensor chip. + + + + + Known Bugs And Assumptions + + + + None + + + None. + + + + + + + + + Public Functions Provided + + This accelerometer/magnetometer drivers don't export any functions. + + + + + Structures + + This chapter contains the autogenerated documentation of the structures which are + used in the accelerometer/magnetometer drivers. + +!Iinclude/linux/lsm303dlh.h + + + + Internal Functions Provided + + This chapter contains the autogenerated documentation of the internal functions. + +!Idrivers/hwmon/lsm303dlh_a.c +!Idrivers/hwmon/lsm303dlh_m.c + + +
diff --git a/drivers/hwmon/lsm303dlh_a.c b/drivers/hwmon/lsm303dlh_a.c index 7721310be06..1436e77a0d6 100644 --- a/drivers/hwmon/lsm303dlh_a.c +++ b/drivers/hwmon/lsm303dlh_a.c @@ -149,8 +149,27 @@ struct lsm303dlh_a_t { short z; }; -/* - * accelerometer local data +/** + * struct lsm303dlh_a_data - data structure used by lsm303dlh_a driver + * @client: i2c client + * @lock: mutex lock for sysfs operations + * @data: lsm303dlh_a_t struct containing x, y and z values + * @input_dev: input device + * @input_dev2: input device + * @pdata: lsm303dlh platform data + * @regulator: regulator + * @range: current range value of accelerometer + * @mode: current mode of operation + * @rate: current sampling rate + * @sleep_wake: sleep wake setting + * @shift_adjust: current shift adjust value set according to range + * @interrupt_control: interrupt control settings + * @interrupt_channel: interrupt channel 0 or 1 + * @interrupt_configure: interrupt configurations for two channels + * @interrupt_duration: interrupt duration for two channels + * @interrupt_threshold: interrupt threshold for two channels + * @early_suspend: early suspend structure + * @device_status: device is ON, OFF or SUSPENDED */ struct lsm303dlh_a_data { struct i2c_client *client; diff --git a/drivers/hwmon/lsm303dlh_m.c b/drivers/hwmon/lsm303dlh_m.c index 8352b3d0281..42ef63157ae 100644 --- a/drivers/hwmon/lsm303dlh_m.c +++ b/drivers/hwmon/lsm303dlh_m.c @@ -125,8 +125,20 @@ #define DEVICE_ON 1 #define DEVICE_SUSPENDED 2 -/* - * magnetometer local data +/** + * struct lsm303dlh_m_data - data structure used by lsm303dlh_m driver + * @client: i2c client + * @lock: mutex lock for sysfs operations + * @input_dev: input device + * @regulator: regulator + * @pdata: lsm303dlh platform data + * @gain: x, y and z axes gain + * @data: Magnetic field values of x, y and z axes + * @mode: current mode of operation + * @rate: current sampling rate + * @range: current range value of magnetometer + * @early_suspend: early suspend structure + * @device_status: device is ON, OFF or SUSPENDED */ struct lsm303dlh_m_data { struct i2c_client *client; diff --git a/include/linux/lsm303dlh.h b/include/linux/lsm303dlh.h index d9fd6148198..ad369b1fbd5 100644 --- a/include/linux/lsm303dlh.h +++ b/include/linux/lsm303dlh.h @@ -29,27 +29,32 @@ #include #ifdef __KERNEL__ +/** + * struct lsm303dlh_platform_data - platform datastructure for lsm303dlh + * @name_a: accelerometer name + * @name_m: magnetometer name + * @irq_a1: interrupt line 1 of accelerometer + * @irq_a2: interrupt line 2 of accelerometer + * @irq_m: interrupt line of magnetometer + * @axis_map_x: x axis position on the hardware, 0 1 or 2 + * @axis_map_y: y axis position on the hardware, 0 1 or 2 + * @axis_map_z: z axis position on the hardware, 0 1 or 2 + * @negative_x: x axis is orientation, 0 or 1 + * @negative_y: y axis is orientation, 0 or 1 + * @negative_z: z axis is orientation, 0 or 1 + */ struct lsm303dlh_platform_data { - - /* name of device for regulator */ - - const char *name_a; /* acelerometer name */ - const char *name_m; /* magnetometer name */ - - /* interrupt data */ - u32 irq_a1; /* interrupt line 1 of accelrometer*/ - u32 irq_a2; /* interrupt line 2 of accelrometer*/ - u32 irq_m; /* interrupt line of magnetometer*/ - - /* position of x,y and z axis */ - u8 axis_map_x; /* [0-2] */ - u8 axis_map_y; /* [0-2] */ - u8 axis_map_z; /* [0-2] */ - - /* orientation of x,y and z axis */ - u8 negative_x; /* [0-1] */ - u8 negative_y; /* [0-1] */ - u8 negative_z; /* [0-1] */ + const char *name_a; + const char *name_m; + u32 irq_a1; + u32 irq_a2; + u32 irq_m; + u8 axis_map_x; + u8 axis_map_y; + u8 axis_map_z; + u8 negative_x; + u8 negative_y; + u8 negative_z; }; #endif /* __KERNEL__ */ -- cgit v1.2.3 From 0fdc348af7879d9f27651674af9e0fa5077a872e Mon Sep 17 00:00:00 2001 From: Jonas Aaberg Date: Tue, 24 May 2011 11:45:00 +0200 Subject: hwmon: lsm303dlh_m: Fix ifdefs ST-Ericsson Linux next: Not tested, ask SSM for ER ST-Ericsson ID: - ST-Ericsson FOSS-OUT ID: Trivial Change-Id: I085ff9f7e54491b6dde56c956dada8ebd6c7d8a1 Signed-off-by: Jonas Aaberg Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/23698 --- drivers/hwmon/lsm303dlh_m.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/drivers/hwmon/lsm303dlh_m.c b/drivers/hwmon/lsm303dlh_m.c index 42ef63157ae..5d35dc2208d 100644 --- a/drivers/hwmon/lsm303dlh_m.c +++ b/drivers/hwmon/lsm303dlh_m.c @@ -177,6 +177,7 @@ static int lsm303dlh_m_write(struct lsm303dlh_m_data *ddata, return ret; } +#if defined(CONFIG_HAS_EARLYSUSPEND) || defined(CONFIG_PM) static int lsm303dlh_m_do_suspend(struct lsm303dlh_m_data *ddata) { int ret; @@ -249,6 +250,7 @@ fail: mutex_unlock(&ddata->lock); return ret; } +#endif static int lsm303dlh_m_read_multi(struct lsm303dlh_m_data *ddata, u8 reg, u8 count, u8 *val, char *msg) @@ -791,7 +793,8 @@ static int __devexit lsm303dlh_m_remove(struct i2c_client *client) return 0; } -#if (!defined(CONFIG_HAS_EARLYSUSPEND) && defined(CONFIG_PM)) +#ifndef CONFIG_HAS_EARLYSUSPEND +#ifdef CONFIG_PM static int lsm303dlh_m_suspend(struct device *dev) { struct lsm303dlh_m_data *ddata; @@ -826,6 +829,7 @@ static const struct dev_pm_ops lsm303dlh_m_dev_pm_ops = { .suspend = lsm303dlh_m_suspend, .resume = lsm303dlh_m_resume, }; +#endif #else static void lsm303dlh_m_early_suspend(struct early_suspend *data) { -- cgit v1.2.3 From ea73c24e55780c000558d75040984814fdf2b4f8 Mon Sep 17 00:00:00 2001 From: Jonas Aaberg Date: Tue, 24 May 2011 11:44:33 +0200 Subject: hwmon: lsm303dlh_a: Fix ifdefs ST-Ericsson Linux next: Not tested, ask SSM for ER ST-Ericsson ID: - ST-Ericsson FOSS-OUT ID: Trivial Change-Id: Ia2cdb2cde8f0baffe5b8f5847754780068153d9e Signed-off-by: Jonas Aaberg Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/23697 --- drivers/hwmon/lsm303dlh_a.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/drivers/hwmon/lsm303dlh_a.c b/drivers/hwmon/lsm303dlh_a.c index 1436e77a0d6..678f35a7730 100644 --- a/drivers/hwmon/lsm303dlh_a.c +++ b/drivers/hwmon/lsm303dlh_a.c @@ -226,6 +226,7 @@ static int lsm303dlh_a_read(struct lsm303dlh_a_data *ddata, u8 reg, char *msg) return ret; } +#if defined(CONFIG_HAS_EARLYSUSPEND) || defined(CONFIG_PM) static int lsm303dlh_a_do_suspend(struct lsm303dlh_a_data *ddata) { int ret; @@ -354,7 +355,7 @@ fail: mutex_unlock(&ddata->lock); return ret; } - +#endif static int lsm303dlh_a_readdata(struct lsm303dlh_a_data *ddata) { @@ -1236,7 +1237,8 @@ static int __devexit lsm303dlh_a_remove(struct i2c_client *client) return 0; } -#if (!defined(CONFIG_HAS_EARLYSUSPEND) && defined(CONFIG_PM)) +#ifndef CONFIG_HAS_EARLYSUSPEND +#ifdef CONFIG_PM static int lsm303dlh_a_suspend(struct device *dev) { struct lsm303dlh_a_data *ddata; @@ -1268,6 +1270,7 @@ static const struct dev_pm_ops lsm303dlh_a_dev_pm_ops = { .suspend = lsm303dlh_a_suspend, .resume = lsm303dlh_a_resume, }; +#endif #else static void lsm303dlh_a_early_suspend(struct early_suspend *data) { -- cgit v1.2.3 From 438996068ae78449e73baa0c569d6834dcea0880 Mon Sep 17 00:00:00 2001 From: Jonas Aaberg Date: Tue, 24 May 2011 11:43:09 +0200 Subject: hwmon: l3g4200d: Fix ifdefs ST-Ericsson Linux next: Not tested, ask SSM for ER ST-Ericsson ID: - ST-Ericsson FOSS-OUT ID: Trivial Change-Id: Ie228d5ac6533ff74d103a79ef379137428e92652 Signed-off-by: Jonas Aaberg Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/23696 --- drivers/hwmon/l3g4200d.c | 52 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 51 insertions(+), 1 deletion(-) diff --git a/drivers/hwmon/l3g4200d.c b/drivers/hwmon/l3g4200d.c index cb362ab7254..39a4e32ee2b 100644 --- a/drivers/hwmon/l3g4200d.c +++ b/drivers/hwmon/l3g4200d.c @@ -16,6 +16,10 @@ #include #include +#ifdef CONFIG_HAS_EARLYSUSPEND +#include +#endif + /* l3g4200d gyroscope registers */ #define WHO_AM_I 0x0F @@ -85,12 +89,20 @@ struct l3g4200d_data { struct l3g4200d_gyro_values data; struct l3g4200d_gyr_platform_data pdata; struct regulator *regulator; +#ifdef CONFIG_HAS_EARLYSUSPEND + struct early_suspend early_suspend; +#endif unsigned char powermode; unsigned char odr; unsigned char range; int device_status; }; +#ifdef CONFIG_HAS_EARLYSUSPEND +static void l3g4200d_early_suspend(struct early_suspend *ddata); +static void l3g4200d_late_resume(struct early_suspend *ddata); +#endif + static int l3g4200d_write(struct l3g4200d_data *ddata, u8 reg, u8 val, char *msg) { @@ -465,6 +477,13 @@ static int __devinit l3g4200d_probe(struct i2c_client *client, ret = sysfs_create_group(&client->dev.kobj, &l3g4200d_attr_group); if (ret) goto exit_free_regulator; +#ifdef CONFIG_HAS_EARLYSUSPEND + ddata->early_suspend.level = + EARLY_SUSPEND_LEVEL_BLANK_SCREEN + 1; + ddata->early_suspend.suspend = l3g4200d_early_suspend; + ddata->early_suspend.resume = l3g4200d_late_resume; + register_early_suspend(&ddata->early_suspend); +#endif /* * turn off the supplies until somebody turns on the device @@ -511,6 +530,7 @@ static int __devexit l3g4200d_remove(struct i2c_client *client) return 0; } +#if defined(CONFIG_HAS_EARLYSUSPEND) || defined(CONFIG_PM) static int l3g4200d_do_suspend(struct l3g4200d_data *ddata) { @@ -574,7 +594,9 @@ fail: mutex_unlock(&ddata->lock); return ret; } +#endif +#ifndef CONFIG_HAS_EARLYSUSPEND #ifdef CONFIG_PM static int l3g4200d_suspend(struct device *dev) { @@ -584,6 +606,9 @@ static int l3g4200d_suspend(struct device *dev) ddata = dev_get_drvdata(dev); ret = l3g4200d_do_suspend(ddata); + if (ret < 0) + dev_err(&ddata->client->dev, + "Error while suspending the device\n"); return ret; } @@ -608,6 +633,31 @@ static const struct dev_pm_ops l3g4200d_dev_pm_ops = { .resume = l3g4200d_resume, }; #endif +#else +static void l3g4200d_early_suspend(struct early_suspend *data) +{ + struct l3g4200d_data *ddata = + container_of(data, struct l3g4200d_data, early_suspend); + int ret; + + ret = l3g4200d_do_suspend(ddata); + if (ret < 0) + dev_err(&ddata->client->dev, + "Error while suspending the device\n"); +} + +static void l3g4200d_late_resume(struct early_suspend *data) +{ + struct l3g4200d_data *ddata = + container_of(data, struct l3g4200d_data, early_suspend); + int ret; + + ret = l3g4200d_do_resume(ddata); + if (ret < 0) + dev_err(&ddata->client->dev, + "Error while resuming the device\n"); +} +#endif static const struct i2c_device_id l3g4200d_id[] = { {"l3g4200d", 0 }, @@ -617,7 +667,7 @@ static const struct i2c_device_id l3g4200d_id[] = { static struct i2c_driver l3g4200d_driver = { .driver = { .name = "l3g4200d", -#ifdef CONFIG_PM +#if (!defined(CONFIG_HAS_EARLYSUSPEND) && defined(CONFIG_PM)) .pm = &l3g4200d_dev_pm_ops, #endif }, -- cgit v1.2.3 From f67c070ecef3315b49abe16bab7e832477546a63 Mon Sep 17 00:00:00 2001 From: Chethan Krishna N Date: Wed, 25 May 2011 19:36:32 +0530 Subject: lsm303dlh: adding functions under CONFIG_SENSORS_INPUT_DEVICE Functions that are called only when CONFIG_SENSORS_INPUT_DEVICE is enabled are put under the same switch Change-Id: If652e8ab2ca0d3482d24145bb5d908f5dbacf965 Signed-off-by: Chethan Krishna N Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/23882 Reviewed-by: Srinidhi KASAGAR --- drivers/hwmon/lsm303dlh_a.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/drivers/hwmon/lsm303dlh_a.c b/drivers/hwmon/lsm303dlh_a.c index 678f35a7730..af0ceee2e45 100644 --- a/drivers/hwmon/lsm303dlh_a.c +++ b/drivers/hwmon/lsm303dlh_a.c @@ -307,6 +307,7 @@ static int lsm303dlh_a_restore(struct lsm303dlh_a_data *ddata) if (ret < 0) goto fail; +#ifdef CONFIG_SENSORS_LSM303DLH_INPUT_DEVICE ret = lsm303dlh_a_write(ddata, CTRL_REG3, ddata->interrupt_control, "CTRL_REG3"); @@ -348,6 +349,7 @@ static int lsm303dlh_a_restore(struct lsm303dlh_a_data *ddata) if (ret < 0) goto fail; +#endif fail: if (ret < 0) @@ -465,7 +467,6 @@ static irqreturn_t lsm303dlh_a_gpio_irq(int irq, void *device_data) return IRQ_HANDLED; } -#endif static ssize_t lsm303dlh_a_show_interrupt_control(struct device *dev, struct device_attribute *attr, @@ -692,6 +693,7 @@ static ssize_t lsm303dlh_a_store_interrupt_threshold(struct device *dev, return count; } +#endif static ssize_t lsm303dlh_a_show_range(struct device *dev, struct device_attribute *attr, @@ -1002,6 +1004,7 @@ static DEVICE_ATTR(rate, S_IWUGO | S_IRUGO, static DEVICE_ATTR(sleep_wake, S_IWUGO | S_IRUGO, lsm303dlh_a_show_sleepwake, lsm303dlh_a_store_sleepwake); +#ifdef CONFIG_SENSORS_LSM303DLH_INPUT_DEVICE static DEVICE_ATTR(interrupt_control, S_IWUGO | S_IRUGO, lsm303dlh_a_show_interrupt_control, lsm303dlh_a_store_interrupt_control); @@ -1021,6 +1024,7 @@ static DEVICE_ATTR(interrupt_duration, S_IWUGO | S_IRUGO, static DEVICE_ATTR(interrupt_threshold, S_IWUGO | S_IRUGO, lsm303dlh_a_show_interrupt_threshold, lsm303dlh_a_store_interrupt_threshold); +#endif static struct attribute *lsm303dlh_a_attributes[] = { &dev_attr_data.attr, @@ -1028,11 +1032,13 @@ static struct attribute *lsm303dlh_a_attributes[] = { &dev_attr_mode.attr, &dev_attr_rate.attr, &dev_attr_sleep_wake.attr, +#ifdef CONFIG_SENSORS_LSM303DLH_INPUT_DEVICE &dev_attr_interrupt_control.attr, &dev_attr_interrupt_channel.attr, &dev_attr_interrupt_configure.attr, &dev_attr_interrupt_duration.attr, &dev_attr_interrupt_threshold.attr, +#endif NULL }; -- cgit v1.2.3 From d4bdb166c330ba195616a246558771b1aecaaa67 Mon Sep 17 00:00:00 2001 From: Chethan Krishna N Date: Thu, 25 Aug 2011 12:15:25 +0530 Subject: drivers:hwmon: add support for lsm303dlhc sensor Adding support for lsm303dlhc acclerometer and magnetometer sensor chip. ST-Ericsson ID: 317051 ST-Ericsson FOSS-OUT ID: NA ST-Ericsson Linux next: NA Change-Id: If55ac20c2665b44d3af45d7af0f2e3e16570d8a7 Signed-off-by: Chethan Krishna N Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/26993 Reviewed-by: QATEST Reviewed-by: Srinidhi KASAGAR --- drivers/hwmon/Kconfig | 14 + drivers/hwmon/Makefile | 1 + drivers/hwmon/lsm303dlh_a.c | 15 + drivers/hwmon/lsm303dlh_m.c | 4 + drivers/hwmon/lsm303dlhc_a.c | 646 +++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 680 insertions(+) create mode 100644 drivers/hwmon/lsm303dlhc_a.c diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index df91f1aca52..a07f1f18207 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -707,6 +707,20 @@ config SENSORS_LSM303DLH_INPUT_DEVICE interrupts, need to be enabled only when input device support is required. +config SENSORS_LSM303DLHC + tristate "ST LSM303DLHC 3-axis accelerometer and 3-axis magnetometer" + depends on I2C + default n + help + This driver provides support for the LSM303DLHC chip which includes a + 3-axis accelerometer and a 3-axis magnetometer. + + This driver can also be built as modules. If so, the module for + accelerometer will be called lsm303dlhc_a and for magnetometer it will + be called lsm303dlh_m. + + Say Y here if you have a device containing lsm303dlhc chip. + config SENSORS_L3G4200D tristate "ST L3G4200D 3-axis gyroscope" depends on I2C diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index 9728a44b4a6..477d395dbd4 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -85,6 +85,7 @@ obj-$(CONFIG_SENSORS_LM95241) += lm95241.o obj-$(CONFIG_SENSORS_LM95245) += lm95245.o obj-$(CONFIG_SENSORS_LTC4151) += ltc4151.o obj-$(CONFIG_SENSORS_LSM303DLH) += lsm303dlh_a.o lsm303dlh_m.o +obj-$(CONFIG_SENSORS_LSM303DLHC) += lsm303dlhc_a.o lsm303dlh_m.o obj-$(CONFIG_SENSORS_L3G4200D) += l3g4200d.o obj-$(CONFIG_SENSORS_LTC4215) += ltc4215.o obj-$(CONFIG_SENSORS_LTC4245) += ltc4245.o diff --git a/drivers/hwmon/lsm303dlh_a.c b/drivers/hwmon/lsm303dlh_a.c index af0ceee2e45..d3c30bc49e0 100644 --- a/drivers/hwmon/lsm303dlh_a.c +++ b/drivers/hwmon/lsm303dlh_a.c @@ -198,6 +198,7 @@ struct lsm303dlh_a_data { unsigned char interrupt_duration[2]; unsigned char interrupt_threshold[2]; int device_status; + int id; }; #ifdef CONFIG_HAS_EARLYSUSPEND @@ -990,6 +991,18 @@ static ssize_t lsm303dlh_a_store_sleepwake(struct device *dev, return count; } +static ssize_t lsm303dlh_a_show_id(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct platform_device *pdev = to_platform_device(dev); + struct lsm303dlh_a_data *ddata = platform_get_drvdata(pdev); + + return sprintf(buf, "%d\n", ddata->id); +} + +static DEVICE_ATTR(id, S_IRUGO, lsm303dlh_a_show_id, NULL); + static DEVICE_ATTR(data, S_IRUGO, lsm303dlh_a_show_data, NULL); static DEVICE_ATTR(range, S_IWUGO | S_IRUGO, @@ -1027,6 +1040,7 @@ static DEVICE_ATTR(interrupt_threshold, S_IWUGO | S_IRUGO, #endif static struct attribute *lsm303dlh_a_attributes[] = { + &dev_attr_id.attr, &dev_attr_data.attr, &dev_attr_range.attr, &dev_attr_mode.attr, @@ -1089,6 +1103,7 @@ static int __devinit lsm303dlh_a_probe(struct i2c_client *client, dev_info(&client->dev, "3-Axis Accelerometer, ID : %d\n", ret); + ddata->id = ret; mutex_init(&ddata->lock); diff --git a/drivers/hwmon/lsm303dlh_m.c b/drivers/hwmon/lsm303dlh_m.c index 5d35dc2208d..254f44d40c6 100644 --- a/drivers/hwmon/lsm303dlh_m.c +++ b/drivers/hwmon/lsm303dlh_m.c @@ -117,6 +117,10 @@ #define LSM303DLH_M_RATE_30_00 0x05 #define LSM303DLH_M_RATE_75_00 0x06 +#ifdef CONFIG_SENSORS_LSM303DLHC +#define LSM303DLH_M_RATE_220_00 0x07 +#endif + /* Multiple byte transfer enable */ #define MULTIPLE_I2C_TR 0x80 diff --git a/drivers/hwmon/lsm303dlhc_a.c b/drivers/hwmon/lsm303dlhc_a.c new file mode 100644 index 00000000000..2da79affdd1 --- /dev/null +++ b/drivers/hwmon/lsm303dlhc_a.c @@ -0,0 +1,646 @@ +/* + * ST LSM303DLHC 3-Axis Accelerometer Driver + * + * Copyright (C) ST-Ericsson SA 2011 + * Author: Chethan Krishna N for ST-Ericsson + * Licence terms: GNU General Public Licence (GPL) version 2 + */ + +#include +#include +#include +#include +#include +#include + +#include +#include + +#define WHO_AM_I 0x0F + +/* lsm303dlhc accelerometer registers */ +#define CTRL_REG1 0x20 +#define CTRL_REG2 0x21 +#define CTRL_REG3 0x22 +#define CTRL_REG4 0x23 +#define CTRL_REG5 0x24 +#define CTRL_REG6 0x25 + +/* lsm303dlhc accelerometer defines */ +#define LSM303DLHC_A_MODE_OFF 0x00 +#define LSM303DLHC_A_MODE_ON 0x04 +#define LSM303DLHC_A_MODE_MAX 0x09 +#define LSM303DLHC_A_CR1_MODE_BIT 4 +#define LSM303DLHC_A_CR1_MODE_MASK (0xF << LSM303DLHC_A_CR1_MODE_BIT) + #define LSM303DLHC_A_CR1_AXIS_ENABLE 7 + +/* Range */ +#define LSM303DLHC_A_RANGE_2G 0x00 +#define LSM303DLHC_A_RANGE_4G 0x01 +#define LSM303DLHC_A_RANGE_8G 0x02 +#define LSM303DLHC_A_RANGE_16G 0x03 +#define LSM303DLHC_A_CR4_FS_BIT 4 + +/* Sensitivity adjustment */ +#define SHIFT_ADJ_2G 4 /* 1/16*/ +#define SHIFT_ADJ_4G 3 /* 2/16*/ +#define SHIFT_ADJ_8G 2 /* ~3.9/16*/ +#define SHIFT_ADJ_16G 1 /* ~3.9/16*/ + +#define AXISDATA_REG 0x29 /* axis data */ + +/* lsm303dlh magnetometer registers */ +#define IRA_REG_M 0x0A + +/* multiple byte transfer enable */ +#define MULTIPLE_I2C_TR 0x80 + +/* device status defines */ +#define DEVICE_OFF 0 +#define DEVICE_ON 1 +#define DEVICE_SUSPENDED 2 + +struct lsm303dlhc_a_t { + short x; + short y; + short z; +}; + +/** + * struct lsm303dlhc_a_data - data structure used by lsm303dlhc_a driver + * @client: i2c client + * @lock: mutex lock for sysfs operations + * @data: lsm303dlhc_a_t struct containing x, y and z values + * @pdata: lsm303dlh platform data + * @regulator: regulator + * @range: current range value of accelerometer + * @mode: current mode of operation + * @rate: current sampling rate + * @shift_adjust: current shift adjust value set according to range + * @device_status: device is ON, OFF or SUSPENDED + * @id: accelerometer device id + */ +struct lsm303dlhc_a_data { + struct i2c_client *client; + /* lock for sysfs operations */ + struct mutex lock; + struct lsm303dlhc_a_t data; + struct lsm303dlh_platform_data pdata; + struct regulator *regulator; + unsigned char range; + unsigned char mode; + unsigned char rate; + int shift_adjust; + int device_status; + int id; +}; + +static int lsm303dlhc_a_write(struct lsm303dlhc_a_data *ddata, u8 reg, + u8 val, char *msg) +{ + int ret = i2c_smbus_write_byte_data(ddata->client, reg, val); + if (ret < 0) + dev_err(&ddata->client->dev, + "i2c_smbus_write_byte_data failed error %d\ + Register (%s)\n", ret, msg); + return ret; +} + +static int lsm303dlhc_a_read(struct lsm303dlhc_a_data *ddata, u8 reg, char *msg) +{ + int ret = i2c_smbus_read_byte_data(ddata->client, reg); + if (ret < 0) + dev_err(&ddata->client->dev, + "i2c_smbus_read_byte_data failed error %d\ + Register (%s)\n", ret, msg); + return ret; +} + +#ifdef (CONFIG_PM) +static int lsm303dlhc_a_do_suspend(struct lsm303dlhc_a_data *ddata) +{ + int ret; + + mutex_lock(&ddata->lock); + + if (ddata->mode == LSM303DLHC_A_MODE_OFF) { + ret = 0; + goto exit; + } + + ret = lsm303dlhc_a_write(ddata, CTRL_REG1, + LSM303DLHC_A_MODE_OFF, "CONTROL"); + + if (ddata->regulator) + regulator_disable(ddata->regulator); + + ddata->device_status = DEVICE_SUSPENDED; + +exit: + mutex_unlock(&ddata->lock); + + return ret; +} + +static int lsm303dlhc_a_restore(struct lsm303dlhc_a_data *ddata) +{ + unsigned char reg; + unsigned char shifted_mode = (ddata->mode << LSM303DLHC_A_CR1_MODE_BIT); + int ret = 0; + + mutex_lock(&ddata->lock); + + if (ddata->device_status == DEVICE_ON) { + mutex_unlock(&ddata->lock); + return 0; + } + + /* in correct mode, no need to change it */ + if (ddata->mode == LSM303DLHC_A_MODE_OFF) { + ddata->device_status = DEVICE_OFF; + goto fail; + } else + ddata->device_status = DEVICE_ON; + + if (ddata->regulator) + regulator_enable(ddata->regulator); + + /* BDU should be enabled by default/recommened */ + reg = ddata->range; + shifted_mode |= LSM303DLHC_A_CR1_AXIS_ENABLE; + + ret = lsm303dlhc_a_write(ddata, CTRL_REG1, shifted_mode, + "CTRL_REG1"); + if (ret < 0) + goto fail; + + ret = lsm303dlhc_a_write(ddata, CTRL_REG4, reg, "CTRL_REG4"); + + if (ret < 0) + goto fail; + + /* write to the boot bit to reboot memory content */ + ret = lsm303dlhc_a_write(ddata, CTRL_REG5, 0x80, "CTRL_REG5"); + + if (ret < 0) + goto fail; + +fail: + if (ret < 0) + dev_err(&ddata->client->dev, + "could not restore the device %d\n", ret); + mutex_unlock(&ddata->lock); + return ret; +} +#endif + +static int lsm303dlhc_a_readdata(struct lsm303dlhc_a_data *ddata) +{ + unsigned char acc_data[6]; + short data[3]; + + int ret = i2c_smbus_read_i2c_block_data(ddata->client, + AXISDATA_REG | MULTIPLE_I2C_TR, 6, acc_data); + if (ret < 0) { + dev_err(&ddata->client->dev, + "i2c_smbus_read_byte_data failed error %d\ + Register AXISDATA_REG \n", ret); + return ret; + } + + data[0] = (short) (((acc_data[1]) << 8) | acc_data[0]); + data[1] = (short) (((acc_data[3]) << 8) | acc_data[2]); + data[2] = (short) (((acc_data[5]) << 8) | acc_data[4]); + + data[0] >>= ddata->shift_adjust; + data[1] >>= ddata->shift_adjust; + data[2] >>= ddata->shift_adjust; + + /* taking position and orientation of x,y,z axis into account*/ + + data[ddata->pdata.axis_map_x] = ddata->pdata.negative_x ? + -data[ddata->pdata.axis_map_x] : data[ddata->pdata.axis_map_x]; + data[ddata->pdata.axis_map_y] = ddata->pdata.negative_y ? + -data[ddata->pdata.axis_map_y] : data[ddata->pdata.axis_map_y]; + data[ddata->pdata.axis_map_z] = ddata->pdata.negative_z ? + -data[ddata->pdata.axis_map_z] : data[ddata->pdata.axis_map_z]; + + ddata->data.x = data[ddata->pdata.axis_map_x]; + ddata->data.y = data[ddata->pdata.axis_map_y]; + ddata->data.z = data[ddata->pdata.axis_map_z]; + + return ret; +} + +static ssize_t lsm303dlhc_a_show_data(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct platform_device *pdev = to_platform_device(dev); + struct lsm303dlhc_a_data *ddata = platform_get_drvdata(pdev); + int ret = 0; + + mutex_lock(&ddata->lock); + + if (ddata->mode == LSM303DLHC_A_MODE_OFF || + ddata->device_status == DEVICE_SUSPENDED) { + mutex_unlock(&ddata->lock); + return ret; + } + + ret = lsm303dlhc_a_readdata(ddata); + + if (ret < 0) { + mutex_unlock(&ddata->lock); + return ret; + } + + mutex_unlock(&ddata->lock); + + return sprintf(buf, "%8x:%8x:%8x\n", ddata->data.x, ddata->data.y, + ddata->data.z); +} + +static ssize_t lsm303dlhc_a_show_range(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct platform_device *pdev = to_platform_device(dev); + struct lsm303dlhc_a_data *ddata = platform_get_drvdata(pdev); + + return sprintf(buf, "%d\n", ddata->range >> LSM303DLHC_A_CR4_FS_BIT); +} + +static ssize_t lsm303dlhc_a_store_range(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct platform_device *pdev = to_platform_device(dev); + struct lsm303dlhc_a_data *ddata = platform_get_drvdata(pdev); + long val; + int error; + + error = strict_strtol(buf, 0, &val); + if (error) + return error; + + if (val < LSM303DLHC_A_RANGE_2G || val > LSM303DLHC_A_RANGE_16G) + return -EINVAL; + + mutex_lock(&ddata->lock); + + if (ddata->mode == LSM303DLHC_A_MODE_OFF) { + dev_info(&ddata->client->dev, + "device is switched off,make it ON using MODE"); + mutex_unlock(&ddata->lock); + return count; + } + + ddata->range = val; + ddata->range <<= LSM303DLHC_A_CR4_FS_BIT; + + error = lsm303dlhc_a_write(ddata, CTRL_REG4, ddata->range, + "CTRL_REG4"); + if (error < 0) + return error; + + switch (val) { + case LSM303DLHC_A_RANGE_2G: + ddata->shift_adjust = SHIFT_ADJ_2G; + break; + case LSM303DLHC_A_RANGE_4G: + ddata->shift_adjust = SHIFT_ADJ_4G; + break; + case LSM303DLHC_A_RANGE_8G: + ddata->shift_adjust = SHIFT_ADJ_8G; + break; + case LSM303DLHC_A_RANGE_16G: + ddata->shift_adjust = SHIFT_ADJ_16G; + break; + default: + return -EINVAL; + } + + mutex_unlock(&ddata->lock); + + return count; +} + +static ssize_t lsm303dlhc_a_show_mode(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct platform_device *pdev = to_platform_device(dev); + struct lsm303dlhc_a_data *ddata = platform_get_drvdata(pdev); + + return sprintf(buf, "%d\n", ddata->mode); +} + +static ssize_t lsm303dlhc_a_store_mode(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct platform_device *pdev = to_platform_device(dev); + struct lsm303dlhc_a_data *ddata = platform_get_drvdata(pdev); + long val; + unsigned char data; + int error; + bool set_boot_bit = false; + + error = strict_strtol(buf, 0, &val); + if (error) + return error; + + mutex_lock(&ddata->lock); + + /* not in correct range */ + + if (val < LSM303DLHC_A_MODE_OFF || val > LSM303DLHC_A_MODE_MAX) { + mutex_unlock(&ddata->lock); + return -EINVAL; + } + + if (ddata->device_status == DEVICE_SUSPENDED) { + if (val == LSM303DLHC_A_MODE_OFF) { + ddata->mode = val; + mutex_unlock(&ddata->lock); + return count; + } else { + /* device is turning on after suspend, reset memory */ + set_boot_bit = true; + } + } + + /* if same mode as existing, return */ + if (ddata->mode == val) { + mutex_unlock(&ddata->lock); + return count; + } + + /* turn on the supplies if already off */ + if (ddata->regulator && ddata->mode == LSM303DLHC_A_MODE_OFF + && (ddata->device_status == DEVICE_OFF + || ddata->device_status == DEVICE_SUSPENDED)) { + regulator_enable(ddata->regulator); + ddata->device_status = DEVICE_ON; + } + + data = lsm303dlhc_a_read(ddata, CTRL_REG1, "CTRL_REG1"); + + data &= ~LSM303DLHC_A_CR1_MODE_MASK; + + ddata->mode = val; + + data |= ((val << LSM303DLHC_A_CR1_MODE_BIT) + & LSM303DLHC_A_CR1_MODE_MASK); + + error = lsm303dlhc_a_write(ddata, CTRL_REG1, data, "CTRL_REG1"); + if (error < 0) { + if (ddata->regulator && ddata->device_status == DEVICE_ON) { + regulator_disable(ddata->regulator); + ddata->device_status = DEVICE_OFF; + } + mutex_unlock(&ddata->lock); + return error; + } + + /* + * Power on request when device is in suspended state + * write to the boot bit in CTRL_REG2 to reboot memory content + * and ensure correct device behavior after it resumes + */ + if (set_boot_bit) { + error = lsm303dlhc_a_write(ddata, CTRL_REG5, 0x80, "CTRL_REG5"); + if (error < 0) { + if (ddata->regulator && + ddata->device_status == DEVICE_ON) { + regulator_disable(ddata->regulator); + ddata->device_status = DEVICE_OFF; + } + mutex_unlock(&ddata->lock); + return error; + } + } + + if (val == LSM303DLHC_A_MODE_OFF) { + + /* + * No need to store context here + * it is not like suspend/resume + * but fall back to default values + */ + ddata->range = LSM303DLHC_A_RANGE_2G; + ddata->shift_adjust = SHIFT_ADJ_2G; + + if (ddata->regulator && ddata->device_status == DEVICE_ON) { + regulator_disable(ddata->regulator); + ddata->device_status = DEVICE_OFF; + } + } + mutex_unlock(&ddata->lock); + + return count; +} + +static ssize_t lsm303dlhc_a_show_id(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct platform_device *pdev = to_platform_device(dev); + struct lsm303dlhc_a_data *ddata = platform_get_drvdata(pdev); + + return sprintf(buf, "%d\n", ddata->id); +} + +static DEVICE_ATTR(id, S_IRUGO, lsm303dlhc_a_show_id, NULL); + +static DEVICE_ATTR(data, S_IRUGO, lsm303dlhc_a_show_data, NULL); + +static DEVICE_ATTR(range, S_IWUGO | S_IRUGO, + lsm303dlhc_a_show_range, lsm303dlhc_a_store_range); + +static DEVICE_ATTR(mode, S_IWUGO | S_IRUGO, + lsm303dlhc_a_show_mode, lsm303dlhc_a_store_mode); + +static struct attribute *lsm303dlhc_a_attributes[] = { + &dev_attr_data.attr, + &dev_attr_range.attr, + &dev_attr_mode.attr, + &dev_attr_id.attr, + NULL +}; + +static const struct attribute_group lsm303dlhc_a_attr_group = { + .attrs = lsm303dlhc_a_attributes, +}; + +static int __devinit lsm303dlhc_a_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int ret; + struct lsm303dlhc_a_data *adata = NULL; + + adata = kzalloc(sizeof(struct lsm303dlhc_a_data), GFP_KERNEL); + if (adata == NULL) { + ret = -ENOMEM; + goto err_op_failed; + } + + adata->client = client; + i2c_set_clientdata(client, adata); + + /* copy platform specific data */ + memcpy(&adata->pdata, client->dev.platform_data, sizeof(adata->pdata)); + adata->mode = LSM303DLHC_A_MODE_OFF; + adata->range = LSM303DLHC_A_RANGE_2G; + adata->shift_adjust = SHIFT_ADJ_2G; + adata->device_status = DEVICE_OFF; + dev_set_name(&client->dev, adata->pdata.name_a); + + adata->regulator = regulator_get(&client->dev, "v-accel"); + if (IS_ERR(adata->regulator)) { + dev_err(&client->dev, "failed to get regulator\n"); + ret = PTR_ERR(adata->regulator); + adata->regulator = NULL; + } + + if (adata->regulator) { + regulator_enable(adata->regulator); + adata->device_status = DEVICE_ON; + } + + ret = lsm303dlhc_a_read(adata, WHO_AM_I, "WHO_AM_I"); + if (ret < 0) + goto exit_free_regulator; + + dev_info(&client->dev, "3-Axis Accelerometer, ID : %d\n", + ret); + adata->id = ret; + + mutex_init(&adata->lock); + + ret = sysfs_create_group(&client->dev.kobj, &lsm303dlhc_a_attr_group); + if (ret) + goto exit_free_regulator; + + if (adata->device_status == DEVICE_ON && adata->regulator) { + regulator_disable(adata->regulator); + adata->device_status = DEVICE_OFF; + } + + return ret; + +exit_free_regulator: + if (adata->device_status == DEVICE_ON && adata->regulator) { + regulator_disable(adata->regulator); + regulator_put(adata->regulator); + adata->device_status = DEVICE_OFF; + } +err_op_failed: + kfree(adata); + dev_err(&client->dev, "probe function fails %x", ret); + return ret; +} + +static int __devexit lsm303dlhc_a_remove(struct i2c_client *client) +{ + int ret; + struct lsm303dlhc_a_data *adata; + + adata = i2c_get_clientdata(client); + sysfs_remove_group(&client->dev.kobj, &lsm303dlhc_a_attr_group); + + /* safer to make device off */ + if (adata->mode != LSM303DLHC_A_MODE_OFF) { + ret = lsm303dlhc_a_write(adata, CTRL_REG1, 0, "CONTROL"); + + if (ret < 0) { + dev_err(&client->dev, + "could not turn off the device %d", + ret); + return ret; + } + + if (adata->regulator && adata->device_status == DEVICE_ON) { + regulator_disable(adata->regulator); + regulator_put(adata->regulator); + adata->device_status = DEVICE_OFF; + } + } + + i2c_set_clientdata(client, NULL); + kfree(adata); + + return 0; +} + +#ifdef (CONFIG_PM) +static int lsm303dlhc_a_suspend(struct device *dev) +{ + struct lsm303dlhc_a_data *ddata; + int ret; + + ddata = dev_get_drvdata(dev); + + ret = lsm303dlhc_a_do_suspend(ddata); + if (ret < 0) + dev_err(&ddata->client->dev, + "Error while suspending the device"); + + return ret; +} + +static int lsm303dlhc_a_resume(struct device *dev) +{ + struct lsm303dlhc_a_data *ddata; + int ret; + + ddata = dev_get_drvdata(dev); + + ret = lsm303dlhc_a_restore(ddata); + + if (ret < 0) + dev_err(&ddata->client->dev, + "Error while resuming the device"); + + return ret; +} +static const struct dev_pm_ops lsm303dlhc_a_dev_pm_ops = { + .suspend = lsm303dlhc_a_suspend, + .resume = lsm303dlhc_a_resume, +}; +#endif /* CONFIG_PM */ + +static const struct i2c_device_id lsm303dlhc_a_id[] = { + { "lsm303dlhc_a", 0 }, + { }, +}; + +static struct i2c_driver lsm303dlhc_a_driver = { + .probe = lsm303dlhc_a_probe, + .remove = lsm303dlhc_a_remove, + .id_table = lsm303dlhc_a_id, + .driver = { + .name = "lsm303dlhc_a", + #ifdef (CONFIG_PM) + .pm = &lsm303dlhc_a_dev_pm_ops, + #endif + }, +}; + +static int __init lsm303dlhc_a_init(void) +{ + return i2c_add_driver(&lsm303dlhc_a_driver); +} + +static void __exit lsm303dlhc_a_exit(void) +{ + i2c_del_driver(&lsm303dlhc_a_driver); +} + +module_init(lsm303dlhc_a_init) +module_exit(lsm303dlhc_a_exit) + +MODULE_DESCRIPTION("lSM303DLH 3-Axis Accelerometer Driver"); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("STMicroelectronics"); -- cgit v1.2.3 From 76e56cfd417a381ca097c40187f33b5d09b366be Mon Sep 17 00:00:00 2001 From: Chethan Krishna N Date: Wed, 13 Jul 2011 16:49:07 +0530 Subject: lsm303dlhc: add early suspend support Adding early suspend and late resume support for lsm303dlhc sensor devices. ST-Ericsson ID: 317051 ST-Ericsson FOSS-OUT ID: NA ST-Ericsson Linux next: NA Signed-off-by: Chethan Krishna N Change-Id: I839bb5bdbd0cb8d5e73a9c8ed3af3df722ac4c99 Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/27117 Reviewed-by: QATOOLS Reviewed-by: QATEST Reviewed-by: Srinidhi KASAGAR --- drivers/hwmon/lsm303dlhc_a.c | 46 +++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 43 insertions(+), 3 deletions(-) diff --git a/drivers/hwmon/lsm303dlhc_a.c b/drivers/hwmon/lsm303dlhc_a.c index 2da79affdd1..f855d57eb9f 100644 --- a/drivers/hwmon/lsm303dlhc_a.c +++ b/drivers/hwmon/lsm303dlhc_a.c @@ -16,6 +16,10 @@ #include #include +#ifdef CONFIG_HAS_EARLYSUSPEND +#include +#endif + #define WHO_AM_I 0x0F /* lsm303dlhc accelerometer registers */ @@ -77,6 +81,7 @@ struct lsm303dlhc_a_t { * @mode: current mode of operation * @rate: current sampling rate * @shift_adjust: current shift adjust value set according to range + * @early_suspend: early suspend structure * @device_status: device is ON, OFF or SUSPENDED * @id: accelerometer device id */ @@ -87,6 +92,7 @@ struct lsm303dlhc_a_data { struct lsm303dlhc_a_t data; struct lsm303dlh_platform_data pdata; struct regulator *regulator; + struct early_suspend early_suspend; unsigned char range; unsigned char mode; unsigned char rate; @@ -95,6 +101,11 @@ struct lsm303dlhc_a_data { int id; }; +#ifdef CONFIG_HAS_EARLYSUSPEND +static void lsm303dlhc_a_early_suspend(struct early_suspend *data); +static void lsm303dlhc_a_late_resume(struct early_suspend *data); +#endif + static int lsm303dlhc_a_write(struct lsm303dlhc_a_data *ddata, u8 reg, u8 val, char *msg) { @@ -116,7 +127,7 @@ static int lsm303dlhc_a_read(struct lsm303dlhc_a_data *ddata, u8 reg, char *msg) return ret; } -#ifdef (CONFIG_PM) +#if defined(CONFIG_HAS_EARLYSUSPEND) || defined(CONFIG_PM) static int lsm303dlhc_a_do_suspend(struct lsm303dlhc_a_data *ddata) { int ret; @@ -523,6 +534,14 @@ static int __devinit lsm303dlhc_a_probe(struct i2c_client *client, if (ret) goto exit_free_regulator; +#ifdef CONFIG_HAS_EARLYSUSPEND + adata->early_suspend.level = + EARLY_SUSPEND_LEVEL_BLANK_SCREEN + 1; + adata->early_suspend.suspend = lsm303dlhc_a_early_suspend; + adata->early_suspend.resume = lsm303dlhc_a_late_resume; + register_early_suspend(&adata->early_suspend); +#endif + if (adata->device_status == DEVICE_ON && adata->regulator) { regulator_disable(adata->regulator); adata->device_status = DEVICE_OFF; @@ -574,7 +593,7 @@ static int __devexit lsm303dlhc_a_remove(struct i2c_client *client) return 0; } -#ifdef (CONFIG_PM) +#if (!defined(CONFIG_HAS_EARLYSUSPEND) && defined(CONFIG_PM)) static int lsm303dlhc_a_suspend(struct device *dev) { struct lsm303dlhc_a_data *ddata; @@ -609,6 +628,27 @@ static const struct dev_pm_ops lsm303dlhc_a_dev_pm_ops = { .suspend = lsm303dlhc_a_suspend, .resume = lsm303dlhc_a_resume, }; +#else +static void lsm303dlhc_a_early_suspend(struct early_suspend *data) +{ + struct lsm303dlhc_a_data *ddata = + container_of(data, struct lsm303dlhc_a_data, early_suspend); + int ret; + + ret = lsm303dlhc_a_do_suspend(ddata); +} + +static void lsm303dlhc_a_late_resume(struct early_suspend *data) +{ + struct lsm303dlhc_a_data *ddata = + container_of(data, struct lsm303dlhc_a_data, early_suspend); + int ret; + + ret = lsm303dlhc_a_restore(ddata); + if (ret < 0) + dev_err(&ddata->client->dev, + "lsm303dlhc_a late resume failed\n"); +} #endif /* CONFIG_PM */ static const struct i2c_device_id lsm303dlhc_a_id[] = { @@ -622,7 +662,7 @@ static struct i2c_driver lsm303dlhc_a_driver = { .id_table = lsm303dlhc_a_id, .driver = { .name = "lsm303dlhc_a", - #ifdef (CONFIG_PM) + #if (!defined(CONFIG_HAS_EARLYSUSPEND) && defined(CONFIG_PM)) .pm = &lsm303dlhc_a_dev_pm_ops, #endif }, -- cgit v1.2.3 From 2b32844c728c9d7d771e044879b665e9e05315a4 Mon Sep 17 00:00:00 2001 From: Chethan Krishna N Date: Thu, 25 Aug 2011 13:04:38 +0530 Subject: lsm303dlh: Detect chip version at run time Enable both drivers and detect which probe has to be called based on hardware detected at run time. Change-Id: I7020b63c4345a23404cf06418aebaa876c504c1e Signed-off-by: Chethan Krishna N Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/27883 Reviewed-by: QATEST Reviewed-by: Srinidhi KASAGAR --- drivers/hwmon/Kconfig | 14 -------------- drivers/hwmon/Makefile | 3 +-- 2 files changed, 1 insertion(+), 16 deletions(-) diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index a07f1f18207..df91f1aca52 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -707,20 +707,6 @@ config SENSORS_LSM303DLH_INPUT_DEVICE interrupts, need to be enabled only when input device support is required. -config SENSORS_LSM303DLHC - tristate "ST LSM303DLHC 3-axis accelerometer and 3-axis magnetometer" - depends on I2C - default n - help - This driver provides support for the LSM303DLHC chip which includes a - 3-axis accelerometer and a 3-axis magnetometer. - - This driver can also be built as modules. If so, the module for - accelerometer will be called lsm303dlhc_a and for magnetometer it will - be called lsm303dlh_m. - - Say Y here if you have a device containing lsm303dlhc chip. - config SENSORS_L3G4200D tristate "ST L3G4200D 3-axis gyroscope" depends on I2C diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index 477d395dbd4..9dccff69241 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -84,8 +84,7 @@ obj-$(CONFIG_SENSORS_LM93) += lm93.o obj-$(CONFIG_SENSORS_LM95241) += lm95241.o obj-$(CONFIG_SENSORS_LM95245) += lm95245.o obj-$(CONFIG_SENSORS_LTC4151) += ltc4151.o -obj-$(CONFIG_SENSORS_LSM303DLH) += lsm303dlh_a.o lsm303dlh_m.o -obj-$(CONFIG_SENSORS_LSM303DLHC) += lsm303dlhc_a.o lsm303dlh_m.o +obj-$(CONFIG_SENSORS_LSM303DLH) += lsm303dlh_a.o lsm303dlh_m.o lsm303dlhc_a.o obj-$(CONFIG_SENSORS_L3G4200D) += l3g4200d.o obj-$(CONFIG_SENSORS_LTC4215) += ltc4215.o obj-$(CONFIG_SENSORS_LTC4245) += ltc4245.o -- cgit v1.2.3 From b49d1478d595c4879609884373de1e927054985d Mon Sep 17 00:00:00 2001 From: Rabin Vincent Date: Mon, 8 Aug 2011 11:51:05 +0530 Subject: lsm303dlhc: fix !CONFIG_HAS_EARLYSUSPEND build ST-Ericsson ID: 317051 ST-Ericsson FOSS-OUT ID: Trivial ST-Ericsson Linux next: NA Change-Id: I605e284d9702c48b0bcca2d0781c1ff0ce0d3c55 Signed-off-by: Rabin Vincent Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/28418 Reviewed-by: QATEST Reviewed-by: Srinidhi KASAGAR --- drivers/hwmon/lsm303dlhc_a.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/hwmon/lsm303dlhc_a.c b/drivers/hwmon/lsm303dlhc_a.c index f855d57eb9f..512d511ece4 100644 --- a/drivers/hwmon/lsm303dlhc_a.c +++ b/drivers/hwmon/lsm303dlhc_a.c @@ -92,7 +92,9 @@ struct lsm303dlhc_a_data { struct lsm303dlhc_a_t data; struct lsm303dlh_platform_data pdata; struct regulator *regulator; +#ifdef CONFIG_HAS_EARLYSUSPEND struct early_suspend early_suspend; +#endif unsigned char range; unsigned char mode; unsigned char rate; -- cgit v1.2.3 From 2cbb41764c5121af9bcf64aa8344ed7b23a44ca0 Mon Sep 17 00:00:00 2001 From: Chethan Krishna N Date: Thu, 25 Aug 2011 13:44:02 +0530 Subject: lsm303dlhc: Correct out register reading Out register reading method for lsm303dlhc sensor chip is corrected ST-Ericsson ID: 356390 ST-Ericsson FOSS-OUT ID: NA ST-Ericsson Linux next: NA Change-Id: Ia1805ad094886a8d23a29148a9d0e3ba460a06f7 Signed-off-by: Chethan Krishna N Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/28688 Reviewed-by: QATEST Reviewed-by: Philippe LANGLAIS --- drivers/hwmon/Kconfig | 14 ++++++++++++++ drivers/hwmon/Makefile | 3 ++- drivers/hwmon/lsm303dlh_m.c | 11 +++++++++++ drivers/hwmon/lsm303dlhc_a.c | 2 +- 4 files changed, 28 insertions(+), 2 deletions(-) diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index df91f1aca52..a07f1f18207 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -707,6 +707,20 @@ config SENSORS_LSM303DLH_INPUT_DEVICE interrupts, need to be enabled only when input device support is required. +config SENSORS_LSM303DLHC + tristate "ST LSM303DLHC 3-axis accelerometer and 3-axis magnetometer" + depends on I2C + default n + help + This driver provides support for the LSM303DLHC chip which includes a + 3-axis accelerometer and a 3-axis magnetometer. + + This driver can also be built as modules. If so, the module for + accelerometer will be called lsm303dlhc_a and for magnetometer it will + be called lsm303dlh_m. + + Say Y here if you have a device containing lsm303dlhc chip. + config SENSORS_L3G4200D tristate "ST L3G4200D 3-axis gyroscope" depends on I2C diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index 9dccff69241..8d461a2bfe5 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -84,7 +84,8 @@ obj-$(CONFIG_SENSORS_LM93) += lm93.o obj-$(CONFIG_SENSORS_LM95241) += lm95241.o obj-$(CONFIG_SENSORS_LM95245) += lm95245.o obj-$(CONFIG_SENSORS_LTC4151) += ltc4151.o -obj-$(CONFIG_SENSORS_LSM303DLH) += lsm303dlh_a.o lsm303dlh_m.o lsm303dlhc_a.o +obj-$(CONFIG_SENSORS_LSM303DLH) += lsm303dlh_a.o lsm303dlh_m.o +obj-$(CONFIG_SENSORS_LSM303DLHC)+= lsm303dlhc_a.o obj-$(CONFIG_SENSORS_L3G4200D) += l3g4200d.o obj-$(CONFIG_SENSORS_LTC4215) += ltc4215.o obj-$(CONFIG_SENSORS_LTC4245) += ltc4245.o diff --git a/drivers/hwmon/lsm303dlh_m.c b/drivers/hwmon/lsm303dlh_m.c index 254f44d40c6..aa43d055e67 100644 --- a/drivers/hwmon/lsm303dlh_m.c +++ b/drivers/hwmon/lsm303dlh_m.c @@ -319,6 +319,7 @@ static ssize_t lsm303dlh_m_store_rate(struct device *dev, static int lsm303dlh_m_xyz_read(struct lsm303dlh_m_data *ddata) { unsigned char xyz_data[6]; + short temp; int ret = lsm303dlh_m_read_multi(ddata, OUT_X_M, 6, xyz_data, "OUT_X_M"); if (ret < 0) @@ -332,6 +333,16 @@ static int lsm303dlh_m_xyz_read(struct lsm303dlh_m_data *ddata) ddata->data[2] = (short) (((xyz_data[4]) << 8) | xyz_data[5]); +#ifdef SENSORS_LSM303DLHC + /* + * the out registers are in x, z and y order + * so swap y and z values + */ + temp = ddata->data[1]; + ddata->data[1] = ddata->data[2]; + ddata->data[2] = temp; +#endif + /* taking orientation of x,y,z axis into account*/ ddata->data[ddata->pdata.axis_map_x] = ddata->pdata.negative_x ? diff --git a/drivers/hwmon/lsm303dlhc_a.c b/drivers/hwmon/lsm303dlhc_a.c index 512d511ece4..175a111cc0a 100644 --- a/drivers/hwmon/lsm303dlhc_a.c +++ b/drivers/hwmon/lsm303dlhc_a.c @@ -51,7 +51,7 @@ #define SHIFT_ADJ_8G 2 /* ~3.9/16*/ #define SHIFT_ADJ_16G 1 /* ~3.9/16*/ -#define AXISDATA_REG 0x29 /* axis data */ +#define AXISDATA_REG 0x28 /* axis data */ /* lsm303dlh magnetometer registers */ #define IRA_REG_M 0x0A -- cgit v1.2.3 From bfa1080195e64a07c991d3a66deccfa35e5dd028 Mon Sep 17 00:00:00 2001 From: Naga Radhesh Date: Tue, 30 Aug 2011 16:09:45 +0530 Subject: hwmon: change regulator for accelerometer change regulator supply from v-accel to vdd ST-Ericsson ID: - ST-Ericsson FOSS-OUT ID: Trivial ST-Ericsson Linux next: Not tested Signed-off-by: Naga Radhesh Change-Id: I16c1f6b3225939cb8c43513487c4a69cb879511d Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/29730 --- drivers/hwmon/lsm303dlhc_a.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hwmon/lsm303dlhc_a.c b/drivers/hwmon/lsm303dlhc_a.c index 175a111cc0a..17c74595ff2 100644 --- a/drivers/hwmon/lsm303dlhc_a.c +++ b/drivers/hwmon/lsm303dlhc_a.c @@ -510,7 +510,7 @@ static int __devinit lsm303dlhc_a_probe(struct i2c_client *client, adata->device_status = DEVICE_OFF; dev_set_name(&client->dev, adata->pdata.name_a); - adata->regulator = regulator_get(&client->dev, "v-accel"); + adata->regulator = regulator_get(&client->dev, "vdd"); if (IS_ERR(adata->regulator)) { dev_err(&client->dev, "failed to get regulator\n"); ret = PTR_ERR(adata->regulator); -- cgit v1.2.3 From 797e90e7d0156787f14ec6a46ab7c9e417d7c649 Mon Sep 17 00:00:00 2001 From: Robert Marklund Date: Wed, 26 Oct 2011 15:17:44 +0200 Subject: ux500: Separate sensors and stuib Separate sensors and stuib so we can start one without the other. Snowball want sensors but dont have any uib. Change-Id: I96fd1051c0a85c5e8eccd83b08076b7348d4fc17 Signed-off-by: Robert Marklund Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/35357 Reviewed-by: Philippe LANGLAIS --- arch/arm/mach-ux500/board-mop500-sensors.c | 116 +++++++++++++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 arch/arm/mach-ux500/board-mop500-sensors.c diff --git a/arch/arm/mach-ux500/board-mop500-sensors.c b/arch/arm/mach-ux500/board-mop500-sensors.c new file mode 100644 index 00000000000..51cdb5e50e8 --- /dev/null +++ b/arch/arm/mach-ux500/board-mop500-sensors.c @@ -0,0 +1,116 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * + * License terms: GNU General Public License (GPL), version 2 + */ + +#include +#include +#include +#include +#include +#include + +#include "board-mop500.h" + +/* + * LSM303DLH accelerometer + magnetometer sensors + */ +static struct lsm303dlh_platform_data __initdata lsm303dlh_pdata = { + .name_a = "lsm303dlh.0", + .name_m = "lsm303dlh.1", + .axis_map_x = 0, + .axis_map_y = 1, + .axis_map_z = 2, + .negative_x = 1, + .negative_y = 1, + .negative_z = 0, +}; + +static struct l3g4200d_gyr_platform_data __initdata l3g4200d_pdata_u8500 = { + .name_gyr = "l3g4200d", + .axis_map_x = 1, + .axis_map_y = 0, + .axis_map_z = 2, + .negative_x = 0, + .negative_y = 0, + .negative_z = 1, +}; + +static struct lps001wp_prs_platform_data __initdata lps001wp_pdata = { + .poll_interval = 500, + .min_interval = 10, +}; + +static struct i2c_board_info __initdata mop500_i2c2_devices[] = { + { + /* LSM303DLH Accelerometer */ + I2C_BOARD_INFO("lsm303dlh_a", 0x18), + .platform_data = &lsm303dlh_pdata, + }, + { + /* LSM303DLH Magnetometer */ + I2C_BOARD_INFO("lsm303dlh_m", 0x1E), + .platform_data = &lsm303dlh_pdata, + }, + { + /* L3G4200D Gyroscope */ + I2C_BOARD_INFO("l3g4200d", 0x68), + .platform_data = &l3g4200d_pdata_u8500, + }, + { + /* LSP001WM Barometer */ + I2C_BOARD_INFO("lps001wp_prs_sysfs", 0x5C), + .platform_data = &lps001wp_pdata, + }, +}; + +/* + * Register/Add i2c sensors + */ +void mop500_sensors_i2c_add(int busnum, struct i2c_board_info const *info, + unsigned n) +{ + struct i2c_adapter *adap; + struct i2c_client *client; + int i; + + adap = i2c_get_adapter(busnum); + if (!adap) { + /* We have no i2c adapter yet lets create it. */ + pr_info(__FILE__ ": Creating i2c adapter %d\n", busnum); + i2c_register_board_info(busnum, info, n); + return; + } + + for (i = 0; i < n; i++) { + client = i2c_new_device(adap, &info[i]); + if (!client) + pr_err(__FILE__ ": failed to register %s to i2c%d\n", + info[i].type, + busnum); + } + + i2c_put_adapter(adap); +} + + +void __init mop500_sensors_init(void) +{ + if (machine_is_hrefv60()) { + lsm303dlh_pdata.irq_a1 = HREFV60_ACCEL_INT1_GPIO; + lsm303dlh_pdata.irq_a2 = HREFV60_ACCEL_INT2_GPIO; + lsm303dlh_pdata.irq_m = HREFV60_MAGNET_DRDY_GPIO; + } else if (machine_is_snowball()) { + lsm303dlh_pdata.irq_a1 = SNOWBALL_ACCEL_INT1_GPIO; + lsm303dlh_pdata.irq_a2 = SNOWBALL_ACCEL_INT2_GPIO; + lsm303dlh_pdata.irq_m = SNOWBALL_MAGNET_DRDY_GPIO; + } else { + lsm303dlh_pdata.irq_a1 = GPIO_ACCEL_INT1; + lsm303dlh_pdata.irq_a2 = GPIO_ACCEL_INT2; + lsm303dlh_pdata.irq_m = GPIO_MAGNET_DRDY; + } + + mop500_sensors_i2c_add(2, mop500_i2c2_devices, + ARRAY_SIZE(mop500_i2c2_devices)); +} -- cgit v1.2.3 From 52fb14e08f9747b774e4eb9b769825a2a1ba23da Mon Sep 17 00:00:00 2001 From: Alessandro Rubini Date: Thu, 26 May 2011 09:20:58 +0200 Subject: input/misc: added lps001wp as internally delivered by ST This is the driver that I got from Matteo Dameno and Carmine Iascone, it doesn't currently compile for some missing symbols but I'd better first commit what I got and then make my own changes over it. Signed-off-by: Alessandro Rubini Change-Id: I1dada398da9fe30d5e1f29c9785707c8a84c7a6c Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/24784 Tested-by: Robert MARKLUND Reviewed-by: Philippe LANGLAIS --- drivers/input/misc/lps001wp_prs.c | 1280 +++++++++++++++++++++++++++++++++++++ include/linux/input/lps001wp.h | 80 +++ 2 files changed, 1360 insertions(+) create mode 100644 drivers/input/misc/lps001wp_prs.c create mode 100644 include/linux/input/lps001wp.h diff --git a/drivers/input/misc/lps001wp_prs.c b/drivers/input/misc/lps001wp_prs.c new file mode 100644 index 00000000000..6157c474bb4 --- /dev/null +++ b/drivers/input/misc/lps001wp_prs.c @@ -0,0 +1,1280 @@ + +/******************** (C) COPYRIGHT 2010 STMicroelectronics ******************** +* +* File Name : lps001wp_prs.c +* Authors : MSH - Motion Mems BU - Application Team +* : Matteo Dameno (matteo.dameno@st.com) +* : Carmine Iascone (carmine.iascone@st.com) +* : Both authors are willing to be considered the contact +* : and update points for the driver. +* Version : V 1.1.1 +* Date : 2010/11/22 +* Description : LPS001WP pressure temperature sensor 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 0.9.0 01/10/2010: + first beta release + Revision 1.1.0 05/11/2010: + add sysfs management + Revision 1.1.1 22/11/2010: + moved to input/misc +******************************************************************************/ + +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include + +#include + + + +#define DEBUG 1 + + +#define PR_ABS_MAX USHORT_MAX + +#define PR_ABS_MIN (u16)(0U) +#define PR_DLT_MAX SHORT_MAX +#define PR_DLT_MIN SHORT_MIN +#define TEMP_MAX SHORT_MAX +#define TEMP_MIN SHORT_MIN + + +#define SENSITIVITY_T_SHIFT 6 /** = 64 LSB/degrC */ +#define SENSITIVITY_P_SHIFT 4 /** = 16 LSB/mbar */ + + +#define OUTDATA_REG 0x28 +#define INDATA_REG 0X30 + +#define WHOAMI_LPS001WP_PRS 0xBA /* Expctd content for WAI */ + +/* CONTROL REGISTERS */ +#define WHO_AM_I 0x0F /* WhoAmI register */ +#define CTRL_REG1 0x20 /* power / ODR control reg */ +#define CTRL_REG2 0x21 /* boot reg */ +#define CTRL_REG3 0x22 /* interrupt control reg */ + +#define STATUS_REG 0X27 /* status reg */ + +#define PRESS_OUT_L OUTDATA_REG + + +#define REF_P_L INDATA_REG /* pressure reference */ +#define REF_P_H 0x31 /* pressure reference */ +#define THS_P_L 0x32 /* pressure threshold */ +#define THS_P_H 0x33 /* pressure threshold */ + +#define INT_CFG 0x34 /* interrupt config */ +#define INT_SRC 0x35 /* interrupt source */ +#define INT_ACK 0x36 /* interrupt acknoledge */ +/* end CONTROL REGISTRES */ + + +/* Barometer and Termometer output data rate ODR */ +#define LPS001WP_PRS_ODR_MASK 0x30 /* Mask to access odr bits only */ +#define LPS001WP_PRS_ODR_7_1 0x00 /* 7Hz baro and 1Hz term ODR */ +#define LPS001WP_PRS_ODR_7_7 0x01 /* 7Hz baro and 7Hz term ODR */ +#define LPS001WP_PRS_ODR_12_12 0x11 /* 12.5Hz baro and 12.5Hz term ODR */ + + +#define LPS001WP_PRS_ENABLE_MASK 0x40 /* */ +#define LPS001WP_PRS_DIFF_MASK 0x08 +#define LPS001WP_PRS_LPOW_MASK 0x80 + +#define LPS001WP_PRS_DIFF_ON 0x08 +#define LPS001WP_PRS_DIFF_OFF 0x00 + +#define LPS001WP_PRS_LPOW_ON 0x80 +#define LPS001WP_PRS_LPOW_OFF 0x00 + +#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_REF_P_L 3 +#define RES_REF_P_H 4 +#define RES_THS_P_L 5 +#define RES_THS_P_H 6 +#define RES_INT_CFG 7 + +#define RESUME_ENTRIES 8 +/* end RESUME STATE INDICES */ + +/* Pressure Sensor Operating Mode */ +#define LPS001WP_PRS_DIFF_ENABLE 1 +#define LPS001WP_PRS_DIFF_DISABLE 0 +#define LPS001WP_PRS_LPOWER_EN 1 +#define LPS001WP_PRS_LPOWER_DIS 0 + +static const struct { + unsigned int cutoff_ms; + unsigned int mask; +} lps001wp_prs_odr_table[] = { + {80, LPS001WP_PRS_ODR_12_12 }, + {143, LPS001WP_PRS_ODR_7_7 }, + {1000, LPS001WP_PRS_ODR_7_1 }, +}; + +struct lps001wp_prs_data { + struct i2c_client *client; + struct lps001wp_prs_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; + u8 diff_enabled; + u8 lpowmode_enabled ; + + atomic_t enabled; + int on_before_suspend; + + u8 resume_state[RESUME_ENTRIES]; + +#ifdef DEBUG + u8 reg_addr; +#endif +}; + +struct outputdata { + u16 abspress; + s16 temperature; + s16 deltapress; +}; + + +static int lps001wp_prs_i2c_read(struct lps001wp_prs_data *prs, + u8 *buf, int len) +{ + int err; + int tries = 0; + + struct i2c_msg msgs[] = { + { + .addr = prs->client->addr, + .flags = prs->client->flags & I2C_M_TEN, + .len = 1, + .buf = buf, + }, + { + .addr = prs->client->addr, + .flags = (prs->client->flags & I2C_M_TEN) | I2C_M_RD, + .len = len, + .buf = buf, + }, + }; + + do { + err = i2c_transfer(prs->client->adapter, msgs, 2); + if (err != 2) + msleep_interruptible(I2C_RETRY_DELAY); + } while ((err != 2) && (++tries < I2C_RETRIES)); + + if (err != 2) { + dev_err(&prs->client->dev, "read transfer error\n"); + err = -EIO; + } else { + err = 0; + } + + return err; +} + +static int lps001wp_prs_i2c_write(struct lps001wp_prs_data *prs, + u8 *buf, int len) +{ + int err; + int tries = 0; + struct i2c_msg msgs[] = { + { + .addr = prs->client->addr, + .flags = prs->client->flags & I2C_M_TEN, + .len = len + 1, + .buf = buf, + }, + }; + + do { + err = i2c_transfer(prs->client->adapter, msgs, 1); + if (err != 1) + msleep_interruptible(I2C_RETRY_DELAY); + } while ((err != 1) && (++tries < I2C_RETRIES)); + + if (err != 1) { + dev_err(&prs->client->dev, "write transfer error\n"); + err = -EIO; + } else { + err = 0; + } + + return err; +} + +static int lps001wp_prs_i2c_update(struct lps001wp_prs_data *prs, + 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 = lps001wp_prs_i2c_read(prs, rdbuf, 1); + if (!(err < 0)) { + init_val = rdbuf[0]; + updated_val = ((mask & new_bit_values) | ((~mask) & init_val)); + wrbuf[1] = updated_val; + err = lps001wp_prs_i2c_write(prs, wrbuf, 2); + } + return err; +} +/* */ + +static int lps001wp_prs_register_write(struct lps001wp_prs_data *prs, 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 = lps001wp_prs_i2c_write(prs, buf, 1); + if (err < 0) + return err; + return err; +} + +static int lps001wp_prs_register_read(struct lps001wp_prs_data *prs, u8 *buf, + u8 reg_address) +{ + + int err = -1; + buf[0] = (reg_address); + err = lps001wp_prs_i2c_read(prs, buf, 1); + + return err; +} + +static int lps001wp_prs_register_update(struct lps001wp_prs_data *prs, u8 *buf, + u8 reg_address, u8 mask, u8 new_bit_values) +{ + int err = -1; + u8 init_val; + u8 updated_val; + err = lps001wp_prs_register_read(prs, buf, reg_address); + if (!(err < 0)) { + init_val = buf[0]; + updated_val = ((mask & new_bit_values) | ((~mask) & init_val)); + err = lps001wp_prs_register_write(prs, buf, reg_address, + updated_val); + } + return err; +} + +/* */ + + +static int lps001wp_prs_hw_init(struct lps001wp_prs_data *prs) +{ + int err = -1; + u8 buf[6]; + + printk(KERN_DEBUG "%s: hw init start\n", LPS001WP_PRS_DEV_NAME); + + buf[0] = WHO_AM_I; + err = lps001wp_prs_i2c_read(prs, buf, 1); + if (err < 0) + goto error_firstread; + else + prs->hw_working = 1; + if (buf[0] != WHOAMI_LPS001WP_PRS) { + err = -1; /* TODO:choose the right coded error */ + goto error_unknown_device; + } + + + buf[0] = (I2C_AUTO_INCREMENT | INDATA_REG); + buf[1] = prs->resume_state[RES_REF_P_L]; + buf[2] = prs->resume_state[RES_REF_P_H]; + buf[3] = prs->resume_state[RES_THS_P_L]; + buf[4] = prs->resume_state[RES_THS_P_H]; + err = lps001wp_prs_i2c_write(prs, buf, 4); + if (err < 0) + goto error1; + + buf[0] = (I2C_AUTO_INCREMENT | CTRL_REG1); + buf[1] = prs->resume_state[RES_CTRL_REG1]; + buf[2] = prs->resume_state[RES_CTRL_REG2]; + buf[3] = prs->resume_state[RES_CTRL_REG3]; + err = lps001wp_prs_i2c_write(prs, buf, 3); + if (err < 0) + goto error1; + + buf[0] = INT_CFG; + buf[1] = prs->resume_state[RES_INT_CFG]; + err = lps001wp_prs_i2c_write(prs, buf, 1); + if (err < 0) + goto error1; + + + prs->hw_initialized = 1; + printk(KERN_DEBUG "%s: hw init done\n", LPS001WP_PRS_DEV_NAME); + return 0; + +error_firstread: + prs->hw_working = 0; + dev_warn(&prs->client->dev, "Error reading WHO_AM_I: is device " + "available/working?\n"); + goto error1; +error_unknown_device: + dev_err(&prs->client->dev, + "device unknown. Expected: 0x%x," + " Replies: 0x%x\n", WHOAMI_LPS001WP_PRS, buf[0]); +error1: + prs->hw_initialized = 0; + dev_err(&prs->client->dev, "hw init error 0x%x,0x%x: %d\n", buf[0], + buf[1], err); + return err; +} + +static void lps001wp_prs_device_power_off(struct lps001wp_prs_data *prs) +{ + int err; + u8 buf[2] = { CTRL_REG1, LPS001WP_PRS_PM_OFF }; + + err = lps001wp_prs_i2c_write(prs, buf, 1); + if (err < 0) + dev_err(&prs->client->dev, "soft power off failed: %d\n", err); + + if (prs->pdata->power_off) { + prs->pdata->power_off(); + prs->hw_initialized = 0; + } + if (prs->hw_initialized) { + prs->hw_initialized = 0; + } + +} + +static int lps001wp_prs_device_power_on(struct lps001wp_prs_data *prs) +{ + int err = -1; + + if (prs->pdata->power_on) { + err = prs->pdata->power_on(); + if (err < 0) { + dev_err(&prs->client->dev, + "power_on failed: %d\n", err); + return err; + } + } + + if (!prs->hw_initialized) { + err = lps001wp_prs_hw_init(prs); + if (prs->hw_working == 1 && err < 0) { + lps001wp_prs_device_power_off(prs); + return err; + } + } + + return 0; +} + + + +int lps001wp_prs_update_odr(struct lps001wp_prs_data *prs, int poll_interval_ms) +{ + int err = -1; + int i; + + u8 buf[2]; + u8 updated_val; + u8 init_val; + u8 new_val; + u8 mask = LPS001WP_PRS_ODR_MASK; + + /* 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(lps001wp_prs_odr_table) - 1; i >= 0; i--) { + if (lps001wp_prs_odr_table[i].cutoff_ms <= poll_interval_ms) + break; + } + + new_val = lps001wp_prs_odr_table[i].mask; + + /* If device is currently enabled, we need to write new + * configuration out to it */ + if (atomic_read(&prs->enabled)) { + buf[0] = CTRL_REG1; + err = lps001wp_prs_i2c_read(prs, buf, 1); + if (err < 0) + goto error; + init_val = buf[0]; + prs->resume_state[RES_CTRL_REG1] = init_val; + + buf[0] = CTRL_REG1; + updated_val = ((mask & new_val) | ((~mask) & init_val)); + buf[1] = updated_val; + buf[0] = CTRL_REG1; + err = lps001wp_prs_i2c_write(prs, buf, 1); + if (err < 0) + goto error; + prs->resume_state[RES_CTRL_REG1] = updated_val; + } + return err; + +error: + dev_err(&prs->client->dev, "update odr failed 0x%x,0x%x: %d\n", + buf[0], buf[1], err); + + return err; +} + +static int lps001wp_prs_set_press_reference(struct lps001wp_prs_data *prs, + u16 new_reference) +{ + int err = -1; + u8 const reg_addressL = REF_P_L; + u8 const reg_addressH = REF_P_H; + u8 bit_valuesL, bit_valuesH; + u8 buf[2]; + + bit_valuesL = (u8) (new_reference & 0x00FF); + bit_valuesH = (u8)((new_reference & 0xFF00) >> 8); + + err = lps001wp_prs_register_write(prs, buf, reg_addressL, + bit_valuesL); + if (err < 0) + return err; + err = lps001wp_prs_register_write(prs, buf, reg_addressH, + bit_valuesH); + if (err < 0) { + lps001wp_prs_register_write(prs, buf, reg_addressL, + prs->resume_state[RES_REF_P_L]); + return err; + } + prs->resume_state[RES_REF_P_L] = bit_valuesL; + prs->resume_state[RES_REF_P_H] = bit_valuesH; + return err; +} + +static int lps001wp_prs_get_press_reference(struct lps001wp_prs_data *prs, + u16 *buf16) +{ + int err = -1; + + u8 bit_valuesL, bit_valuesH; + u8 buf[2] = {0}; + u16 temp = 0; + + err = lps001wp_prs_register_read(prs, buf, REF_P_L); + if (err < 0) + return err; + bit_valuesL = buf[0]; + err = lps001wp_prs_register_read(prs, buf, REF_P_H); + if (err < 0) + return err; + bit_valuesH = buf[0]; + + temp = (((u16) bit_valuesH) << 8); + *buf16 = (temp | ((u16) bit_valuesL)); + + return err; +} + +static int lps001wp_prs_lpow_manage(struct lps001wp_prs_data *prs, u8 control) +{ + int err = -1; + u8 buf[2] = {0x00, 0x00}; + u8 const mask = LPS001WP_PRS_LPOW_MASK; + u8 bit_values = LPS001WP_PRS_LPOW_OFF; + + if (control >= LPS001WP_PRS_LPOWER_EN) { + bit_values = LPS001WP_PRS_LPOW_ON; + } + + err = lps001wp_prs_register_update(prs, buf, CTRL_REG1, + mask, bit_values); + + if (err < 0) + return err; + prs->resume_state[RES_CTRL_REG1] = ((mask & bit_values) | + (~mask & prs->resume_state[RES_CTRL_REG1])); + if (bit_values == LPS001WP_PRS_LPOW_ON) + prs->lpowmode_enabled = 1; + else + prs->lpowmode_enabled = 0; + return err; +} + +static int lps001wp_prs_diffen_manage(struct lps001wp_prs_data *prs, u8 control) +{ + int err = -1; + u8 buf[2] = {0x00, 0x00}; + u8 const mask = LPS001WP_PRS_DIFF_MASK; + u8 bit_values = LPS001WP_PRS_DIFF_OFF; + + if (control >= LPS001WP_PRS_DIFF_ENABLE) { + bit_values = LPS001WP_PRS_DIFF_ON; + } + + err = lps001wp_prs_register_update(prs, buf, CTRL_REG1, + mask, bit_values); + + if (err < 0) + return err; + prs->resume_state[RES_CTRL_REG1] = ((mask & bit_values) | + (~mask & prs->resume_state[RES_CTRL_REG1])); + if (bit_values == LPS001WP_PRS_DIFF_ON) + prs->diff_enabled = 1; + else + prs->diff_enabled = 0; + return err; +} + + +static int lps001wp_prs_get_presstemp_data(struct lps001wp_prs_data *prs, + struct outputdata *out) +{ + int err = -1; + /* Data bytes from hardware PRESS_OUT_L, PRESS_OUT_H, + * TEMP_OUT_L, TEMP_OUT_H, + * DELTA_L, DELTA_H */ + u8 prs_data[6]; + + u16 abspr; + s16 temperature, deltapr; + int regToRead = 4; + prs_data[4] = 0; + prs_data[5] = 0; + + if (prs->diff_enabled) + regToRead = 6; + + prs_data[0] = (I2C_AUTO_INCREMENT | OUTDATA_REG); + err = lps001wp_prs_i2c_read(prs, prs_data, regToRead); + if (err < 0) + return err; + + abspr = ((((u16) prs_data[1] << 8) | ((u16) prs_data[0]))); + temperature = ((s16) (((u16) prs_data[3] << 8) | ((u16)prs_data[2]))); + + out->abspress = abspr; + out->temperature = temperature; + + deltapr = ((s16) (((u16) prs_data[5] << 8) | ((u16)prs_data[4]))); + out->deltapress = deltapr; + + return err; +} + +static void lps001wp_prs_report_values(struct lps001wp_prs_data *prs, + struct outputdata *out) +{ + input_report_abs(prs->input_dev, ABS_PR, out->abspress); + input_report_abs(prs->input_dev, ABS_TEMP, out->temperature); + input_report_abs(prs->input_dev, ABS_DLTPR, out->deltapress); + input_sync(prs->input_dev); +} + +static int lps001wp_prs_enable(struct lps001wp_prs_data *prs) +{ + int err; + + if (!atomic_cmpxchg(&prs->enabled, 0, 1)) { + err = lps001wp_prs_device_power_on(prs); + if (err < 0) { + atomic_set(&prs->enabled, 0); + return err; + } + schedule_delayed_work(&prs->input_work, + msecs_to_jiffies(prs->pdata->poll_interval)); + } + + return 0; +} + +static int lps001wp_prs_disable(struct lps001wp_prs_data *prs) +{ + if (atomic_cmpxchg(&prs->enabled, 1, 0)) { + cancel_delayed_work_sync(&prs->input_work); + lps001wp_prs_device_power_off(prs); + } + + return 0; +} + +static ssize_t read_single_reg(struct device *dev, char *buf, u8 reg) +{ + ssize_t ret; + struct lps001wp_prs_data *prs = dev_get_drvdata(dev); + int rc = 0; + + u8 data = reg; + rc = lps001wp_prs_i2c_read(prs, &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 lps001wp_prs_data *prs = 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 = lps001wp_prs_i2c_write(prs, 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 lps001wp_prs_data *prs = dev_get_drvdata(dev); + mutex_lock(&prs->lock); + val = prs->pdata->poll_interval; + mutex_unlock(&prs->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 lps001wp_prs_data *prs = dev_get_drvdata(dev); + unsigned long interval_ms; + + if (strict_strtoul(buf, 10, &interval_ms)) + return -EINVAL; + if (!interval_ms) + return -EINVAL; + mutex_lock(&prs->lock); + prs->pdata->poll_interval = interval_ms; + lps001wp_prs_update_odr(prs, interval_ms); + mutex_unlock(&prs->lock); + return size; +} + +static ssize_t attr_get_diff_enable(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + u8 val; + struct lps001wp_prs_data *prs = dev_get_drvdata(dev); + mutex_lock(&prs->lock); + val = prs->diff_enabled; + mutex_unlock(&prs->lock); + return sprintf(buf, "%d\n", val); +} + +static ssize_t attr_set_diff_enable(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + struct lps001wp_prs_data *prs = dev_get_drvdata(dev); + unsigned long val; + + if (strict_strtoul(buf, 10, &val)) + return -EINVAL; + + mutex_lock(&prs->lock); + lps001wp_prs_diffen_manage(prs, (u8) val); + mutex_unlock(&prs->lock); + return size; +} + +static ssize_t attr_get_enable(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct lps001wp_prs_data *prs = dev_get_drvdata(dev); + int val = atomic_read(&prs->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 lps001wp_prs_data *prs = dev_get_drvdata(dev); + unsigned long val; + + if (strict_strtoul(buf, 10, &val)) + return -EINVAL; + + if (val) + lps001wp_prs_enable(prs); + else + lps001wp_prs_disable(prs); + + return size; +} + +static ssize_t attr_get_press_ref(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int err = -1; + struct lps001wp_prs_data *prs = dev_get_drvdata(dev); + u16 val = 0; + + mutex_lock(&prs->lock); + err = lps001wp_prs_get_press_reference(prs, &val); + mutex_unlock(&prs->lock); + if (err < 0) + return err; + + return sprintf(buf, "%d\n", val); +} + +static ssize_t attr_set_press_ref(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + int err = -1; + struct lps001wp_prs_data *prs = dev_get_drvdata(dev); + unsigned long val = 0; + + if (strict_strtoul(buf, 10, &val)) + return -EINVAL; + + if (val < PR_ABS_MIN || val > PR_ABS_MAX) + return -EINVAL; + + mutex_lock(&prs->lock); + err = lps001wp_prs_set_press_reference(prs, val); + mutex_unlock(&prs->lock); + if (err < 0) + return err; + return size; +} + + +static ssize_t attr_get_lowpowmode(struct device *dev, + struct device_attribute *attr, char *buf) +{ + u8 val; + struct lps001wp_prs_data *prs = dev_get_drvdata(dev); + mutex_lock(&prs->lock); + val = prs->lpowmode_enabled; + mutex_unlock(&prs->lock); + return sprintf(buf, "%d\n", val); +} + +static ssize_t attr_set_lowpowmode(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + int err = -1; + struct lps001wp_prs_data *prs = dev_get_drvdata(dev); + unsigned long val; + + if (strict_strtoul(buf, 10, &val)) + return -EINVAL; + + mutex_lock(&prs->lock); + err = lps001wp_prs_lpow_manage(prs, (u8) val); + mutex_unlock(&prs->lock); + if (err < 0) + return err; + return size; +} + + +#ifdef DEBUG +static ssize_t attr_reg_set(struct device *dev, struct device_attribute *attr, + const char *buf, size_t size) +{ + int rc; + struct lps001wp_prs_data *prs = dev_get_drvdata(dev); + u8 x[2]; + unsigned long val; + + if (strict_strtoul(buf, 16, &val)) + return -EINVAL; + mutex_lock(&prs->lock); + x[0] = prs->reg_addr; + mutex_unlock(&prs->lock); + x[1] = val; + rc = lps001wp_prs_i2c_write(prs, 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 lps001wp_prs_data *prs = dev_get_drvdata(dev); + int rc; + u8 data; + + mutex_lock(&prs->lock); + data = prs->reg_addr; + mutex_unlock(&prs->lock); + rc = lps001wp_prs_i2c_read(prs, &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 lps001wp_prs_data *prs = dev_get_drvdata(dev); + unsigned long val; + if (strict_strtoul(buf, 16, &val)) + return -EINVAL; + mutex_lock(&prs->lock); + prs->reg_addr = val; + mutex_unlock(&prs->lock); + return size; +} +#endif + + + +static struct device_attribute attributes[] = { + __ATTR(pollrate_ms, 0664, attr_get_polling_rate, attr_set_polling_rate), + __ATTR(enable, 0664, attr_get_enable, attr_set_enable), + __ATTR(diff_enable, 0664, attr_get_diff_enable, attr_set_diff_enable), + __ATTR(press_reference, 0664, attr_get_press_ref, attr_set_press_ref), + __ATTR(lowpow_enable, 0664, attr_get_lowpowmode, attr_set_lowpowmode), +#ifdef DEBUG + __ATTR(reg_value, 0664, attr_reg_get, attr_reg_set), + __ATTR(reg_addr, 0220, 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 lps001wp_prs_input_work_func(struct work_struct *work) +{ + struct lps001wp_prs_data *prs; + + struct outputdata output; + struct outputdata *out = &output; + int err; + + prs = container_of((struct delayed_work *)work, + struct lps001wp_prs_data, input_work); + + mutex_lock(&prs->lock); + err = lps001wp_prs_get_presstemp_data(prs, out); + if (err < 0) + dev_err(&prs->client->dev, "get_pressure_data failed\n"); + else + lps001wp_prs_report_values(prs, out); + + schedule_delayed_work(&prs->input_work, + msecs_to_jiffies(prs->pdata->poll_interval)); + mutex_unlock(&prs->lock); +} + +int lps001wp_prs_input_open(struct input_dev *input) +{ + struct lps001wp_prs_data *prs = input_get_drvdata(input); + + return lps001wp_prs_enable(prs); +} + +void lps001wp_prs_input_close(struct input_dev *dev) +{ + struct lps001wp_prs_data *prs = input_get_drvdata(dev); + + lps001wp_prs_disable(prs); +} + + +static int lps001wp_prs_validate_pdata(struct lps001wp_prs_data *prs) +{ + prs->pdata->poll_interval = max(prs->pdata->poll_interval, + prs->pdata->min_interval); + + /* Enforce minimum polling interval */ + if (prs->pdata->poll_interval < prs->pdata->min_interval) { + dev_err(&prs->client->dev, "minimum poll interval violated\n"); + return -EINVAL; + } + + return 0; +} + +static int lps001wp_prs_input_init(struct lps001wp_prs_data *prs) +{ + int err; + + INIT_DELAYED_WORK(&prs->input_work, lps001wp_prs_input_work_func); + prs->input_dev = input_allocate_device(); + if (!prs->input_dev) { + err = -ENOMEM; + dev_err(&prs->client->dev, "input device allocate failed\n"); + goto err0; + } + + prs->input_dev->open = lps001wp_prs_input_open; + prs->input_dev->close = lps001wp_prs_input_close; + prs->input_dev->name = LPS001WP_PRS_DEV_NAME; + prs->input_dev->id.bustype = BUS_I2C; + prs->input_dev->dev.parent = &prs->client->dev; + + input_set_drvdata(prs->input_dev, prs); + + set_bit(EV_ABS, prs->input_dev->evbit); + + input_set_abs_params(prs->input_dev, ABS_PR, + PR_ABS_MIN, PR_ABS_MAX, FUZZ, FLAT); + input_set_abs_params(prs->input_dev, ABS_TEMP, + PR_DLT_MIN, PR_DLT_MAX, FUZZ, FLAT); + input_set_abs_params(prs->input_dev, ABS_DLTPR, + TEMP_MIN, TEMP_MAX, FUZZ, FLAT); + + + prs->input_dev->name = "LPS001WP barometer"; + + err = input_register_device(prs->input_dev); + if (err) { + dev_err(&prs->client->dev, + "unable to register input polled device %s\n", + prs->input_dev->name); + goto err1; + } + + return 0; + +err1: + input_free_device(prs->input_dev); +err0: + return err; +} + +static void lps001wp_prs_input_cleanup(struct lps001wp_prs_data *prs) +{ + input_unregister_device(prs->input_dev); + input_free_device(prs->input_dev); +} + +static int lps001wp_prs_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct lps001wp_prs_data *prs; + int err = -1; + int tempvalue; + + pr_info("%s: probe start.\n", LPS001WP_PRS_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; + } + + + prs = kzalloc(sizeof(struct lps001wp_prs_data), GFP_KERNEL); + if (prs == NULL) { + err = -ENOMEM; + dev_err(&client->dev, + "failed to allocate memory for module data: " + "%d\n", err); + goto exit_alloc_data_failed; + } + + mutex_init(&prs->lock); + mutex_lock(&prs->lock); + + prs->client = client; + i2c_set_clientdata(client, prs); + + + if (i2c_smbus_read_byte(client) < 0) { + printk(KERN_ERR "i2c_smbus_read_byte error!!\n"); + goto err_mutexunlockfreedata; + } else { + printk(KERN_DEBUG "%s Device detected!\n", + LPS001WP_PRS_DEV_NAME); + } + + /* read chip id */ + tempvalue = i2c_smbus_read_word_data(client, WHO_AM_I); + if ((tempvalue & 0x00FF) == WHOAMI_LPS001WP_PRS) { + printk(KERN_DEBUG "%s I2C driver registered!\n", + LPS001WP_PRS_DEV_NAME); + } else { + prs->client = NULL; + printk(KERN_DEBUG "I2C driver not registered!" + " Device unknown\n"); + goto err_mutexunlockfreedata; + } + + prs->pdata = kmalloc(sizeof(*prs->pdata), GFP_KERNEL); + if (prs->pdata == NULL) { + err = -ENOMEM; + dev_err(&client->dev, + "failed to allocate memory for pdata: %d\n", + err); + goto err_mutexunlockfreedata; + } + + memcpy(prs->pdata, client->dev.platform_data, sizeof(*prs->pdata)); + + err = lps001wp_prs_validate_pdata(prs); + if (err < 0) { + dev_err(&client->dev, "failed to validate platform data\n"); + goto exit_kfree_pdata; + } + + i2c_set_clientdata(client, prs); + + + if (prs->pdata->init) { + err = prs->pdata->init(); + if (err < 0) { + dev_err(&client->dev, "init failed: %d\n", err); + goto err2; + } + } + + memset(prs->resume_state, 0, ARRAY_SIZE(prs->resume_state)); + + prs->resume_state[RES_CTRL_REG1] = LPS001WP_PRS_PM_NORMAL; + prs->resume_state[RES_CTRL_REG2] = 0x00; + prs->resume_state[RES_CTRL_REG3] = 0x00; + prs->resume_state[RES_REF_P_L] = 0x00; + prs->resume_state[RES_REF_P_H] = 0x00; + prs->resume_state[RES_THS_P_L] = 0x00; + prs->resume_state[RES_THS_P_H] = 0x00; + prs->resume_state[RES_INT_CFG] = 0x00; + + err = lps001wp_prs_device_power_on(prs); + if (err < 0) { + dev_err(&client->dev, "power on failed: %d\n", err); + goto err2; + } + + prs->diff_enabled = 0; + prs->lpowmode_enabled = 0; + atomic_set(&prs->enabled, 1); + + err = lps001wp_prs_update_odr(prs, prs->pdata->poll_interval); + if (err < 0) { + dev_err(&client->dev, "update_odr failed\n"); + goto err_power_off; + } + + err = lps001wp_prs_input_init(prs); + 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 LPS001WP_PRS_DEV_NAME sysfs register failed\n"); + goto err_input_cleanup; + } + + + lps001wp_prs_device_power_off(prs); + + /* As default, do not report information */ + atomic_set(&prs->enabled, 0); + + + mutex_unlock(&prs->lock); + + dev_info(&client->dev, "%s: probed\n", LPS001WP_PRS_DEV_NAME); + + return 0; + +/* +remove_sysfs_int: + remove_sysfs_interfaces(&client->dev); +*/ +err_input_cleanup: + lps001wp_prs_input_cleanup(prs); +err_power_off: + lps001wp_prs_device_power_off(prs); +err2: + if (prs->pdata->exit) + prs->pdata->exit(); +exit_kfree_pdata: + kfree(prs->pdata); + +err_mutexunlockfreedata: + mutex_unlock(&prs->lock); + kfree(prs); +exit_alloc_data_failed: +exit_check_functionality_failed: + printk(KERN_ERR "%s: Driver Init failed\n", LPS001WP_PRS_DEV_NAME); + return err; +} + +static int __devexit lps001wp_prs_remove(struct i2c_client *client) +{ + struct lps001wp_prs_data *prs = i2c_get_clientdata(client); + + lps001wp_prs_input_cleanup(prs); + lps001wp_prs_device_power_off(prs); + remove_sysfs_interfaces(&client->dev); + + if (prs->pdata->exit) + prs->pdata->exit(); + kfree(prs->pdata); + kfree(prs); + + return 0; +} + +static int lps001wp_prs_resume(struct i2c_client *client) +{ + struct lps001wp_prs_data *prs = i2c_get_clientdata(client); + + if (prs->on_before_suspend) + return lps001wp_prs_enable(prs); + return 0; +} + +static int lps001wp_prs_suspend(struct i2c_client *client, pm_message_t mesg) +{ + struct lps001wp_prs_data *prs = i2c_get_clientdata(client); + + prs->on_before_suspend = atomic_read(&prs->enabled); + return lps001wp_prs_disable(prs); +} + +static const struct i2c_device_id lps001wp_prs_id[] + = { { LPS001WP_PRS_DEV_NAME, 0}, { },}; + +MODULE_DEVICE_TABLE(i2c, lps001wp_prs_id); + +static struct i2c_driver lps001wp_prs_driver = { + .driver = { + .name = LPS001WP_PRS_DEV_NAME, + .owner = THIS_MODULE, + }, + .probe = lps001wp_prs_probe, + .remove = __devexit_p(lps001wp_prs_remove), + .id_table = lps001wp_prs_id, + .resume = lps001wp_prs_resume, + .suspend = lps001wp_prs_suspend, +}; + +static int __init lps001wp_prs_init(void) +{ + printk(KERN_DEBUG "%s barometer driver: init\n", + LPS001WP_PRS_DEV_NAME); + return i2c_add_driver(&lps001wp_prs_driver); +} + +static void __exit lps001wp_prs_exit(void) +{ + #if DEBUG + printk(KERN_DEBUG "%s barometer driver exit\n", + LPS001WP_PRS_DEV_NAME); + #endif + i2c_del_driver(&lps001wp_prs_driver); + return; +} + +module_init(lps001wp_prs_init); +module_exit(lps001wp_prs_exit); + +MODULE_DESCRIPTION("STMicrolelectronics lps001wp pressure sensor sysfs driver"); +MODULE_AUTHOR("Matteo Dameno, Carmine Iascone, STMicroelectronics"); +MODULE_LICENSE("GPL"); + diff --git a/include/linux/input/lps001wp.h b/include/linux/input/lps001wp.h new file mode 100644 index 00000000000..aa5eac9af8f --- /dev/null +++ b/include/linux/input/lps001wp.h @@ -0,0 +1,80 @@ +/******************** (C) COPYRIGHT 2010 STMicroelectronics ******************** +* +* File Name : lps001wp.h +* Authors : MSH - Motion Mems BU - Application Team +* : Matteo Dameno (matteo.dameno@st.com)* +* : Carmine Iascone (carmine.iascone@st.com) +* Version : V 1.1.1 +* Date : 05/11/2010 +* Description : LPS001WP pressure temperature sensor 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. +* +*******************************************************************************/ + +#ifndef __LPS001WP_H__ +#define __LPS001WP_H__ + + +#include + +#define SAD0L 0x00 +#define SAD0H 0x01 +#define LPS001WP_PRS_I2C_SADROOT 0x2E +#define LPS001WP_PRS_I2C_SAD_L ((LPS001WP_PRS_I2C_SADROOT<<1)|SAD0L) +#define LPS001WP_PRS_I2C_SAD_H ((LPS001WP_PRS_I2C_SADROOT<<1)|SAD0H) +#define LPS001WP_PRS_DEV_NAME "lps001wp_prs_sysfs" + +/* input define mappings */ +#define ABS_PR ABS_PRESSURE +#define ABS_TEMP ABS_GAS +#define ABS_DLTPR ABS_MISC + + + +/************************************************/ +/* Pressure section defines */ +/************************************************/ + +/* Pressure Sensor Operating Mode */ +#define LPS001WP_PRS_ENABLE 0x01 +#define LPS001WP_PRS_DISABLE 0x00 + + + + +#define LPS001WP_PRS_PM_NORMAL 0x40 +#define LPS001WP_PRS_PM_OFF LPS001WP_PRS_DISABLE + +#define SENSITIVITY_T 64 /** = 64 LSB/degrC */ +#define SENSITIVITY_P 16 /** = 16 LSB/mbar */ + + +#ifdef __KERNEL__ +struct lps001wp_prs_platform_data { + + int poll_interval; + int min_interval; + + int (*init)(void); + void (*exit)(void); + int (*power_on)(void); + int (*power_off)(void); + +}; + +#endif /* __KERNEL__ */ + +#endif /* __LPS001WP_H__ */ -- cgit v1.2.3 From 219273d7594bec84084c828ee4de05b500217ea4 Mon Sep 17 00:00:00 2001 From: Alessandro Rubini Date: Thu, 26 May 2011 12:44:52 +0200 Subject: input/misc: fix lps001wp_prs.c so it will compile (next commit) This fixes some undefined SHORT_MAX and so on. I use explicit hex because it is not short, it's exactly 16 bits. Moreover, it adds some needed headers and removes some unused ones. Signed-off-by: Alessandro Rubini Change-Id: I1029884822ba869fc14c34793a50f61e5f4889b2 Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/24796 Tested-by: Robert MARKLUND Reviewed-by: Philippe LANGLAIS --- drivers/input/misc/lps001wp_prs.c | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/drivers/input/misc/lps001wp_prs.c b/drivers/input/misc/lps001wp_prs.c index 6157c474bb4..9ec96ba3863 100644 --- a/drivers/input/misc/lps001wp_prs.c +++ b/drivers/input/misc/lps001wp_prs.c @@ -35,19 +35,16 @@ moved to input/misc ******************************************************************************/ +#include +#include +#include +#include #include #include #include -#include #include - #include -#include - #include -#include -#include -#include #include #include @@ -57,13 +54,12 @@ #define DEBUG 1 -#define PR_ABS_MAX USHORT_MAX - -#define PR_ABS_MIN (u16)(0U) -#define PR_DLT_MAX SHORT_MAX -#define PR_DLT_MIN SHORT_MIN -#define TEMP_MAX SHORT_MAX -#define TEMP_MIN SHORT_MIN +#define PR_ABS_MAX 0xffff +#define PR_ABS_MIN 0x0000 +#define PR_DLT_MAX 0x7ffff +#define PR_DLT_MIN -0x80000 /* 16-bit signed value */ +#define TEMP_MAX 0x7fff +#define TEMP_MIN -0x80000 /* 16-bit signed value */ #define SENSITIVITY_T_SHIFT 6 /** = 64 LSB/degrC */ -- cgit v1.2.3 From 2a433e73b9dddeaba79708373774fa7c51714515 Mon Sep 17 00:00:00 2001 From: Alessandro Rubini Date: Thu, 26 May 2011 12:46:17 +0200 Subject: drivers/misc: compile lps001wp using a Kconfig entry The value is "default y" for the MACH_U8500 family. Note that this is not ready for upstream because I have no dependencies in place for the entry. "make randomconfig" will find it sooner or later. Signed-off-by: Alessandro Rubini Change-Id: If490d78630a3388e0c7ffa97f2c8e5faf92e373f Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/24797 Reviewed-by: Philippe LANGLAIS Tested-by: Robert MARKLUND --- drivers/input/misc/Kconfig | 7 +++++++ drivers/input/misc/Makefile | 1 + 2 files changed, 8 insertions(+) diff --git a/drivers/input/misc/Kconfig b/drivers/input/misc/Kconfig index 22d875fde53..1766006bf16 100644 --- a/drivers/input/misc/Kconfig +++ b/drivers/input/misc/Kconfig @@ -275,6 +275,13 @@ config INPUT_KXTJ9_POLLED_MODE help Say Y here if you need accelerometer to work in polling mode. +config INPUT_LPS001WP + tristate "LPS0001WP pressure sensor from ST Micro" + default y if MACH_U8500 + help + This is a pressure sensor connected to I2C, mounted on the + snowball and other ST-E boards + config INPUT_POWERMATE tristate "Griffin PowerMate and Contour Jog support" depends on USB_ARCH_HAS_HCD diff --git a/drivers/input/misc/Makefile b/drivers/input/misc/Makefile index a244fc6a781..43442d18169 100644 --- a/drivers/input/misc/Makefile +++ b/drivers/input/misc/Makefile @@ -26,6 +26,7 @@ obj-$(CONFIG_HP_SDC_RTC) += hp_sdc_rtc.o obj-$(CONFIG_INPUT_IXP4XX_BEEPER) += ixp4xx-beeper.o obj-$(CONFIG_INPUT_KEYSPAN_REMOTE) += keyspan_remote.o obj-$(CONFIG_INPUT_KXTJ9) += kxtj9.o +obj-$(CONFIG_INPUT_LPS001WP) += lps001wp_prs.o obj-$(CONFIG_INPUT_M68K_BEEP) += m68kspkr.o obj-$(CONFIG_INPUT_MAX8925_ONKEY) += max8925_onkey.o obj-$(CONFIG_INPUT_MC13783_PWRBUTTON) += mc13783-pwrbutton.o -- cgit v1.2.3 From f645dc6eb6f6ab3608fe9ea9f95d067cd83def73 Mon Sep 17 00:00:00 2001 From: Robert Marklund Date: Fri, 28 Oct 2011 17:54:26 +0200 Subject: input/misc: Add regulator support to lps001wp Change-Id: I39f57da20017ff7e963d0001a99dd426e12c77d0 Signed-off-by: Robert Marklund Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/35833 Reviewed-by: Philippe LANGLAIS --- drivers/input/misc/lps001wp_prs.c | 36 +++++++++++++++++++++++++++--------- include/linux/input/lps001wp.h | 2 -- 2 files changed, 27 insertions(+), 11 deletions(-) diff --git a/drivers/input/misc/lps001wp_prs.c b/drivers/input/misc/lps001wp_prs.c index 9ec96ba3863..cb60762ac61 100644 --- a/drivers/input/misc/lps001wp_prs.c +++ b/drivers/input/misc/lps001wp_prs.c @@ -46,6 +46,7 @@ #include #include #include +#include #include @@ -164,6 +165,8 @@ struct lps001wp_prs_data { u8 resume_state[RESUME_ENTRIES]; + struct regulator *regulator; + #ifdef DEBUG u8 reg_addr; #endif @@ -380,10 +383,13 @@ static void lps001wp_prs_device_power_off(struct lps001wp_prs_data *prs) if (err < 0) dev_err(&prs->client->dev, "soft power off failed: %d\n", err); - if (prs->pdata->power_off) { - prs->pdata->power_off(); - prs->hw_initialized = 0; + /* disable regulator */ + if (prs->regulator) { + err = regulator_disable(prs->regulator); + if (err < 0) + dev_err(&prs->client->dev, "failed to disable regulator\n"); } + if (prs->hw_initialized) { prs->hw_initialized = 0; } @@ -394,15 +400,23 @@ static int lps001wp_prs_device_power_on(struct lps001wp_prs_data *prs) { int err = -1; - if (prs->pdata->power_on) { - err = prs->pdata->power_on(); - if (err < 0) { - dev_err(&prs->client->dev, - "power_on failed: %d\n", err); - return err; + /* get the regulator the first time */ + if (!prs->regulator) { + prs->regulator = regulator_get(&prs->client->dev, "vdd"); + if (IS_ERR(prs->regulator)) { + dev_err(&prs->client->dev, "failed to get regulator\n"); + prs->regulator = NULL; + return PTR_ERR(prs->regulator); } } + /* enable it also */ + err = regulator_enable(prs->regulator); + if (err < 0) { + dev_err(&prs->client->dev, "failed to enable regulator\n"); + return err; + } + if (!prs->hw_initialized) { err = lps001wp_prs_hw_init(prs); if (prs->hw_working == 1 && err < 0) { @@ -1210,6 +1224,10 @@ static int __devexit lps001wp_prs_remove(struct i2c_client *client) if (prs->pdata->exit) prs->pdata->exit(); + + if (prs->regulator) + regulator_put(prs->regulator); + kfree(prs->pdata); kfree(prs); diff --git a/include/linux/input/lps001wp.h b/include/linux/input/lps001wp.h index aa5eac9af8f..779a415ea68 100644 --- a/include/linux/input/lps001wp.h +++ b/include/linux/input/lps001wp.h @@ -70,8 +70,6 @@ struct lps001wp_prs_platform_data { int (*init)(void); void (*exit)(void); - int (*power_on)(void); - int (*power_off)(void); }; -- cgit v1.2.3 From 5cdeb7c61ce83ed0022a22182d64a19e268ca920 Mon Sep 17 00:00:00 2001 From: Robert Marklund Date: Mon, 21 Nov 2011 15:13:19 +0100 Subject: ux500: Detect accelerometer i2c address on snowball in board sensors file Snowball V7 and above the accelerometer changed i2c address and there is now way to detect that in runtime. So between V7 and V10 we need to probe for the right address. Signed-off-by: Robert Marklund --- arch/arm/mach-ux500/board-mop500-sensors.c | 82 +++++++++++++++++++++++++++--- 1 file changed, 76 insertions(+), 6 deletions(-) diff --git a/arch/arm/mach-ux500/board-mop500-sensors.c b/arch/arm/mach-ux500/board-mop500-sensors.c index 51cdb5e50e8..d546eb1be93 100644 --- a/arch/arm/mach-ux500/board-mop500-sensors.c +++ b/arch/arm/mach-ux500/board-mop500-sensors.c @@ -10,6 +10,7 @@ #include #include #include +#include #include "board-mop500.h" @@ -43,11 +44,6 @@ static struct lps001wp_prs_platform_data __initdata lps001wp_pdata = { }; static struct i2c_board_info __initdata mop500_i2c2_devices[] = { - { - /* LSM303DLH Accelerometer */ - I2C_BOARD_INFO("lsm303dlh_a", 0x18), - .platform_data = &lsm303dlh_pdata, - }, { /* LSM303DLH Magnetometer */ I2C_BOARD_INFO("lsm303dlh_m", 0x1E), @@ -65,6 +61,31 @@ static struct i2c_board_info __initdata mop500_i2c2_devices[] = { }, }; +/* + * Break this out due to the fact that this have changed address on snowball + */ +static struct i2c_board_info __initdata mop500_2_i2c2_devices[] = { + { + /* LSM303DLH Accelerometer */ + I2C_BOARD_INFO("lsm303dlh_a", 0x18), + .platform_data = &lsm303dlh_pdata, + }, +}; + +/* + * This is needed due to the fact that the i2c address changed in V7 =< + * and there is no way of knowing if the HW is V7 or higher so we just + * have to try and fail. + */ +static struct i2c_board_info __initdata snowball_i2c2_devices[] = { + { + /* LSM303DLH Accelerometer */ + I2C_BOARD_INFO("lsm303dlh_a", 0x19), + .platform_data = &lsm303dlh_pdata, + }, +}; + + /* * Register/Add i2c sensors */ @@ -94,9 +115,43 @@ void mop500_sensors_i2c_add(int busnum, struct i2c_board_info const *info, i2c_put_adapter(adap); } +/* + * Register/Add i2c sensors + */ +void mop500_sensors_probe_add_lsm303dlh_a(void) +{ + static const int busnum = 2; + struct i2c_adapter *adap; + struct i2c_client *client; + static const unsigned short i2c_addr_list[] = { + 0x18, 0x19, I2C_CLIENT_END }; + struct i2c_board_info i2c_info = { + /* LSM303DLH Accelerometer */ + I2C_BOARD_INFO("lsm303dlh_a", 0), + .platform_data = &lsm303dlh_pdata, + }; + + adap = i2c_get_adapter(busnum); + if (!adap) { + /* We have no i2c adapter yet lets create it. */ + pr_err(__FILE__ ": Could not get adapter %d\n", busnum); + return; + } + client = i2c_new_probed_device(adap, &i2c_info, + i2c_addr_list, NULL); + if (!client) + pr_err(__FILE__ ": failed to register %s to i2c%d\n", + i2c_info.type, + busnum); + i2c_put_adapter(adap); +} -void __init mop500_sensors_init(void) +static int mop500_sensors_init(void) { + + if (!machine_is_snowball() && !uib_is_stuib()) + return 0; + if (machine_is_hrefv60()) { lsm303dlh_pdata.irq_a1 = HREFV60_ACCEL_INT1_GPIO; lsm303dlh_pdata.irq_a2 = HREFV60_ACCEL_INT2_GPIO; @@ -113,4 +168,19 @@ void __init mop500_sensors_init(void) mop500_sensors_i2c_add(2, mop500_i2c2_devices, ARRAY_SIZE(mop500_i2c2_devices)); + + if (machine_is_snowball()) { + if (cpu_is_u8500v21()) + /* This is ugly but we cant know what address + * to use */ + mop500_sensors_probe_add_lsm303dlh_a(); + else /* Add the accelerometer with new addr */ + mop500_sensors_i2c_add(2, snowball_i2c2_devices, + ARRAY_SIZE(snowball_i2c2_devices)); + } else /* none snowball have the old addr */ + mop500_sensors_i2c_add(2, mop500_2_i2c2_devices, + ARRAY_SIZE(mop500_2_i2c2_devices)); + return 0; } + +module_init(mop500_sensors_init); -- cgit v1.2.3 From 1597048014f69ff39448d80fe13cb98fa9d02f29 Mon Sep 17 00:00:00 2001 From: Philippe Langlais Date: Fri, 2 Dec 2011 14:29:15 +0100 Subject: sensors: Include module.h after 3.2 update Signed-off-by: Philippe Langlais --- drivers/hwmon/l3g4200d.c | 1 + drivers/hwmon/lsm303dlh_a.c | 1 + drivers/hwmon/lsm303dlh_m.c | 1 + 3 files changed, 3 insertions(+) diff --git a/drivers/hwmon/l3g4200d.c b/drivers/hwmon/l3g4200d.c index 39a4e32ee2b..fcac3afc044 100644 --- a/drivers/hwmon/l3g4200d.c +++ b/drivers/hwmon/l3g4200d.c @@ -6,6 +6,7 @@ * Licence terms: GNU General Public Licence (GPL) version 2 */ +#include #include #include #include diff --git a/drivers/hwmon/lsm303dlh_a.c b/drivers/hwmon/lsm303dlh_a.c index d3c30bc49e0..65e92dfb0ff 100644 --- a/drivers/hwmon/lsm303dlh_a.c +++ b/drivers/hwmon/lsm303dlh_a.c @@ -23,6 +23,7 @@ * this program. If not, see . */ +#include #include #include #include diff --git a/drivers/hwmon/lsm303dlh_m.c b/drivers/hwmon/lsm303dlh_m.c index aa43d055e67..98704dc5a0b 100644 --- a/drivers/hwmon/lsm303dlh_m.c +++ b/drivers/hwmon/lsm303dlh_m.c @@ -23,6 +23,7 @@ * this program. If not, see . */ +#include #include #include #include -- cgit v1.2.3 From 45674bd48a0114105c33bdfa6662ddc5d2689d06 Mon Sep 17 00:00:00 2001 From: Philippe Langlais Date: Mon, 5 Dec 2011 11:28:28 +0100 Subject: mach-ux500: sensors: Add __init directive for mop500_sensors_init() Signed-off-by: Philippe Langlais --- arch/arm/mach-ux500/board-mop500-sensors.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/arm/mach-ux500/board-mop500-sensors.c b/arch/arm/mach-ux500/board-mop500-sensors.c index d546eb1be93..bc08e332699 100644 --- a/arch/arm/mach-ux500/board-mop500-sensors.c +++ b/arch/arm/mach-ux500/board-mop500-sensors.c @@ -146,7 +146,7 @@ void mop500_sensors_probe_add_lsm303dlh_a(void) i2c_put_adapter(adap); } -static int mop500_sensors_init(void) +static int __init mop500_sensors_init(void) { if (!machine_is_snowball() && !uib_is_stuib()) -- cgit v1.2.3