aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--drivers/hwmon/lis3lv02d.c52
-rw-r--r--drivers/hwmon/lis3lv02d.h13
-rw-r--r--drivers/hwmon/lis3lv02d_i2c.c55
-rw-r--r--include/linux/lis3lv02d.h2
4 files changed, 121 insertions, 1 deletions
diff --git a/drivers/hwmon/lis3lv02d.c b/drivers/hwmon/lis3lv02d.c
index 383a84938a98..159e402ddec3 100644
--- a/drivers/hwmon/lis3lv02d.c
+++ b/drivers/hwmon/lis3lv02d.c
@@ -31,6 +31,7 @@
31#include <linux/delay.h> 31#include <linux/delay.h>
32#include <linux/wait.h> 32#include <linux/wait.h>
33#include <linux/poll.h> 33#include <linux/poll.h>
34#include <linux/slab.h>
34#include <linux/freezer.h> 35#include <linux/freezer.h>
35#include <linux/uaccess.h> 36#include <linux/uaccess.h>
36#include <linux/miscdevice.h> 37#include <linux/miscdevice.h>
@@ -257,10 +258,46 @@ fail:
257 return ret; 258 return ret;
258} 259}
259 260
261/*
262 * Order of registers in the list affects to order of the restore process.
263 * Perhaps it is a good idea to set interrupt enable register as a last one
264 * after all other configurations
265 */
266static u8 lis3_wai8_regs[] = { FF_WU_CFG_1, FF_WU_THS_1, FF_WU_DURATION_1,
267 FF_WU_CFG_2, FF_WU_THS_2, FF_WU_DURATION_2,
268 CLICK_CFG, CLICK_SRC, CLICK_THSY_X, CLICK_THSZ,
269 CLICK_TIMELIMIT, CLICK_LATENCY, CLICK_WINDOW,
270 CTRL_REG1, CTRL_REG2, CTRL_REG3};
271
272static u8 lis3_wai12_regs[] = {FF_WU_CFG, FF_WU_THS_L, FF_WU_THS_H,
273 FF_WU_DURATION, DD_CFG, DD_THSI_L, DD_THSI_H,
274 DD_THSE_L, DD_THSE_H,
275 CTRL_REG1, CTRL_REG3, CTRL_REG2};
276
277static inline void lis3_context_save(struct lis3lv02d *lis3)
278{
279 int i;
280 for (i = 0; i < lis3->regs_size; i++)
281 lis3->read(lis3, lis3->regs[i], &lis3->reg_cache[i]);
282 lis3->regs_stored = true;
283}
284
285static inline void lis3_context_restore(struct lis3lv02d *lis3)
286{
287 int i;
288 if (lis3->regs_stored)
289 for (i = 0; i < lis3->regs_size; i++)
290 lis3->write(lis3, lis3->regs[i], lis3->reg_cache[i]);
291}
292
260void lis3lv02d_poweroff(struct lis3lv02d *lis3) 293void lis3lv02d_poweroff(struct lis3lv02d *lis3)
261{ 294{
295 if (lis3->reg_ctrl)
296 lis3_context_save(lis3);
262 /* disable X,Y,Z axis and power down */ 297 /* disable X,Y,Z axis and power down */
263 lis3->write(lis3, CTRL_REG1, 0x00); 298 lis3->write(lis3, CTRL_REG1, 0x00);
299 if (lis3->reg_ctrl)
300 lis3->reg_ctrl(lis3, LIS3_REG_OFF);
264} 301}
265EXPORT_SYMBOL_GPL(lis3lv02d_poweroff); 302EXPORT_SYMBOL_GPL(lis3lv02d_poweroff);
266 303
@@ -283,6 +320,8 @@ void lis3lv02d_poweron(struct lis3lv02d *lis3)
283 reg |= CTRL2_BDU; 320 reg |= CTRL2_BDU;
284 lis3->write(lis3, CTRL_REG2, reg); 321 lis3->write(lis3, CTRL_REG2, reg);
285 } 322 }
323 if (lis3->reg_ctrl)
324 lis3_context_restore(lis3);
286} 325}
287EXPORT_SYMBOL_GPL(lis3lv02d_poweron); 326EXPORT_SYMBOL_GPL(lis3lv02d_poweron);
288 327
@@ -674,6 +713,7 @@ int lis3lv02d_remove_fs(struct lis3lv02d *lis3)
674 pm_runtime_disable(lis3->pm_dev); 713 pm_runtime_disable(lis3->pm_dev);
675 pm_runtime_set_suspended(lis3->pm_dev); 714 pm_runtime_set_suspended(lis3->pm_dev);
676 } 715 }
716 kfree(lis3->reg_cache);
677 return 0; 717 return 0;
678} 718}
679EXPORT_SYMBOL_GPL(lis3lv02d_remove_fs); 719EXPORT_SYMBOL_GPL(lis3lv02d_remove_fs);
@@ -753,6 +793,8 @@ int lis3lv02d_init_device(struct lis3lv02d *dev)
753 dev->odrs = lis3_12_rates; 793 dev->odrs = lis3_12_rates;
754 dev->odr_mask = CTRL1_DF0 | CTRL1_DF1; 794 dev->odr_mask = CTRL1_DF0 | CTRL1_DF1;
755 dev->scale = LIS3_SENSITIVITY_12B; 795 dev->scale = LIS3_SENSITIVITY_12B;
796 dev->regs = lis3_wai12_regs;
797 dev->regs_size = ARRAY_SIZE(lis3_wai12_regs);
756 break; 798 break;
757 case WAI_8B: 799 case WAI_8B:
758 printk(KERN_INFO DRIVER_NAME ": 8 bits sensor found\n"); 800 printk(KERN_INFO DRIVER_NAME ": 8 bits sensor found\n");
@@ -762,6 +804,8 @@ int lis3lv02d_init_device(struct lis3lv02d *dev)
762 dev->odrs = lis3_8_rates; 804 dev->odrs = lis3_8_rates;
763 dev->odr_mask = CTRL1_DR; 805 dev->odr_mask = CTRL1_DR;
764 dev->scale = LIS3_SENSITIVITY_8B; 806 dev->scale = LIS3_SENSITIVITY_8B;
807 dev->regs = lis3_wai8_regs;
808 dev->regs_size = ARRAY_SIZE(lis3_wai8_regs);
765 break; 809 break;
766 case WAI_3DC: 810 case WAI_3DC:
767 printk(KERN_INFO DRIVER_NAME ": 8 bits 3DC sensor found\n"); 811 printk(KERN_INFO DRIVER_NAME ": 8 bits 3DC sensor found\n");
@@ -778,6 +822,14 @@ int lis3lv02d_init_device(struct lis3lv02d *dev)
778 return -EINVAL; 822 return -EINVAL;
779 } 823 }
780 824
825 dev->reg_cache = kzalloc(max(sizeof(lis3_wai8_regs),
826 sizeof(lis3_wai12_regs)), GFP_KERNEL);
827
828 if (dev->reg_cache == NULL) {
829 printk(KERN_ERR DRIVER_NAME "out of memory\n");
830 return -ENOMEM;
831 }
832
781 mutex_init(&dev->mutex); 833 mutex_init(&dev->mutex);
782 834
783 lis3lv02d_add_fs(dev); 835 lis3lv02d_add_fs(dev);
diff --git a/drivers/hwmon/lis3lv02d.h b/drivers/hwmon/lis3lv02d.h
index 633cf1debc52..c5c063de2f48 100644
--- a/drivers/hwmon/lis3lv02d.h
+++ b/drivers/hwmon/lis3lv02d.h
@@ -20,6 +20,7 @@
20 */ 20 */
21#include <linux/platform_device.h> 21#include <linux/platform_device.h>
22#include <linux/input-polldev.h> 22#include <linux/input-polldev.h>
23#include <linux/regulator/consumer.h>
23 24
24/* 25/*
25 * This driver tries to support the "digital" accelerometer chips from 26 * This driver tries to support the "digital" accelerometer chips from
@@ -223,11 +224,17 @@ enum lis3lv02d_click_src_8b {
223 CLICK_IA = 0x40, 224 CLICK_IA = 0x40,
224}; 225};
225 226
227enum lis3lv02d_reg_state {
228 LIS3_REG_OFF = 0x00,
229 LIS3_REG_ON = 0x01,
230};
231
226union axis_conversion { 232union axis_conversion {
227 struct { 233 struct {
228 int x, y, z; 234 int x, y, z;
229 }; 235 };
230 int as_array[3]; 236 int as_array[3];
237
231}; 238};
232 239
233struct lis3lv02d { 240struct lis3lv02d {
@@ -236,8 +243,13 @@ struct lis3lv02d {
236 int (*init) (struct lis3lv02d *lis3); 243 int (*init) (struct lis3lv02d *lis3);
237 int (*write) (struct lis3lv02d *lis3, int reg, u8 val); 244 int (*write) (struct lis3lv02d *lis3, int reg, u8 val);
238 int (*read) (struct lis3lv02d *lis3, int reg, u8 *ret); 245 int (*read) (struct lis3lv02d *lis3, int reg, u8 *ret);
246 int (*reg_ctrl) (struct lis3lv02d *lis3, bool state);
239 247
240 int *odrs; /* Supported output data rates */ 248 int *odrs; /* Supported output data rates */
249 u8 *regs; /* Regs to store / restore */
250 int regs_size;
251 u8 *reg_cache;
252 bool regs_stored;
241 u8 odr_mask; /* ODR bit mask */ 253 u8 odr_mask; /* ODR bit mask */
242 u8 whoami; /* indicates measurement precision */ 254 u8 whoami; /* indicates measurement precision */
243 s16 (*read_data) (struct lis3lv02d *lis3, int reg); 255 s16 (*read_data) (struct lis3lv02d *lis3, int reg);
@@ -250,6 +262,7 @@ struct lis3lv02d {
250 262
251 struct input_polled_dev *idev; /* input device */ 263 struct input_polled_dev *idev; /* input device */
252 struct platform_device *pdev; /* platform device */ 264 struct platform_device *pdev; /* platform device */
265 struct regulator_bulk_data regulators[2];
253 atomic_t count; /* interrupt count after last read */ 266 atomic_t count; /* interrupt count after last read */
254 union axis_conversion ac; /* hw -> logical axis */ 267 union axis_conversion ac; /* hw -> logical axis */
255 int mapped_btns[3]; 268 int mapped_btns[3];
diff --git a/drivers/hwmon/lis3lv02d_i2c.c b/drivers/hwmon/lis3lv02d_i2c.c
index d52095603e03..ec35c87819c0 100644
--- a/drivers/hwmon/lis3lv02d_i2c.c
+++ b/drivers/hwmon/lis3lv02d_i2c.c
@@ -30,10 +30,29 @@
30#include <linux/err.h> 30#include <linux/err.h>
31#include <linux/i2c.h> 31#include <linux/i2c.h>
32#include <linux/pm_runtime.h> 32#include <linux/pm_runtime.h>
33#include <linux/delay.h>
33#include "lis3lv02d.h" 34#include "lis3lv02d.h"
34 35
35#define DRV_NAME "lis3lv02d_i2c" 36#define DRV_NAME "lis3lv02d_i2c"
36 37
38static const char reg_vdd[] = "Vdd";
39static const char reg_vdd_io[] = "Vdd_IO";
40
41static int lis3_reg_ctrl(struct lis3lv02d *lis3, bool state)
42{
43 int ret;
44 if (state == LIS3_REG_OFF) {
45 ret = regulator_bulk_disable(ARRAY_SIZE(lis3->regulators),
46 lis3->regulators);
47 } else {
48 ret = regulator_bulk_enable(ARRAY_SIZE(lis3->regulators),
49 lis3->regulators);
50 /* Chip needs time to wakeup. Not mentioned in datasheet */
51 usleep_range(10000, 20000);
52 }
53 return ret;
54}
55
37static inline s32 lis3_i2c_write(struct lis3lv02d *lis3, int reg, u8 value) 56static inline s32 lis3_i2c_write(struct lis3lv02d *lis3, int reg, u8 value)
38{ 57{
39 struct i2c_client *c = lis3->bus_priv; 58 struct i2c_client *c = lis3->bus_priv;
@@ -52,6 +71,13 @@ static int lis3_i2c_init(struct lis3lv02d *lis3)
52 u8 reg; 71 u8 reg;
53 int ret; 72 int ret;
54 73
74 if (lis3->reg_ctrl)
75 lis3_reg_ctrl(lis3, LIS3_REG_ON);
76
77 lis3->read(lis3, WHO_AM_I, &reg);
78 if (reg != lis3->whoami)
79 printk(KERN_ERR "lis3: power on failure\n");
80
55 /* power up the device */ 81 /* power up the device */
56 ret = lis3->read(lis3, CTRL_REG1, &reg); 82 ret = lis3->read(lis3, CTRL_REG1, &reg);
57 if (ret < 0) 83 if (ret < 0)
@@ -72,6 +98,10 @@ static int __devinit lis3lv02d_i2c_probe(struct i2c_client *client,
72 struct lis3lv02d_platform_data *pdata = client->dev.platform_data; 98 struct lis3lv02d_platform_data *pdata = client->dev.platform_data;
73 99
74 if (pdata) { 100 if (pdata) {
101 /* Regulator control is optional */
102 if (pdata->driver_features & LIS3_USE_REGULATOR_CTRL)
103 lis3_dev.reg_ctrl = lis3_reg_ctrl;
104
75 if (pdata->axis_x) 105 if (pdata->axis_x)
76 lis3lv02d_axis_map.x = pdata->axis_x; 106 lis3lv02d_axis_map.x = pdata->axis_x;
77 107
@@ -88,6 +118,16 @@ static int __devinit lis3lv02d_i2c_probe(struct i2c_client *client,
88 goto fail; 118 goto fail;
89 } 119 }
90 120
121 if (lis3_dev.reg_ctrl) {
122 lis3_dev.regulators[0].supply = reg_vdd;
123 lis3_dev.regulators[1].supply = reg_vdd_io;
124 ret = regulator_bulk_get(&client->dev,
125 ARRAY_SIZE(lis3_dev.regulators),
126 lis3_dev.regulators);
127 if (ret < 0)
128 goto fail;
129 }
130
91 lis3_dev.pdata = pdata; 131 lis3_dev.pdata = pdata;
92 lis3_dev.bus_priv = client; 132 lis3_dev.bus_priv = client;
93 lis3_dev.init = lis3_i2c_init; 133 lis3_dev.init = lis3_i2c_init;
@@ -98,21 +138,34 @@ static int __devinit lis3lv02d_i2c_probe(struct i2c_client *client,
98 lis3_dev.pm_dev = &client->dev; 138 lis3_dev.pm_dev = &client->dev;
99 139
100 i2c_set_clientdata(client, &lis3_dev); 140 i2c_set_clientdata(client, &lis3_dev);
141
142 /* Provide power over the init call */
143 if (lis3_dev.reg_ctrl)
144 lis3_reg_ctrl(&lis3_dev, LIS3_REG_ON);
145
101 ret = lis3lv02d_init_device(&lis3_dev); 146 ret = lis3lv02d_init_device(&lis3_dev);
147
148 if (lis3_dev.reg_ctrl)
149 lis3_reg_ctrl(&lis3_dev, LIS3_REG_OFF);
102fail: 150fail:
103 return ret; 151 return ret;
104} 152}
105 153
106static int __devexit lis3lv02d_i2c_remove(struct i2c_client *client) 154static int __devexit lis3lv02d_i2c_remove(struct i2c_client *client)
107{ 155{
156 struct lis3lv02d *lis3 = i2c_get_clientdata(client);
108 struct lis3lv02d_platform_data *pdata = client->dev.platform_data; 157 struct lis3lv02d_platform_data *pdata = client->dev.platform_data;
109 158
110 if (pdata && pdata->release_resources) 159 if (pdata && pdata->release_resources)
111 pdata->release_resources(); 160 pdata->release_resources();
112 161
113 lis3lv02d_joystick_disable(); 162 lis3lv02d_joystick_disable();
163 lis3lv02d_remove_fs(&lis3_dev);
114 164
115 return lis3lv02d_remove_fs(&lis3_dev); 165 if (lis3_dev.reg_ctrl)
166 regulator_bulk_free(ARRAY_SIZE(lis3->regulators),
167 lis3_dev.regulators);
168 return 0;
116} 169}
117 170
118#ifdef CONFIG_PM 171#ifdef CONFIG_PM
diff --git a/include/linux/lis3lv02d.h b/include/linux/lis3lv02d.h
index 0e8a346424bb..c4a4a52c1de7 100644
--- a/include/linux/lis3lv02d.h
+++ b/include/linux/lis3lv02d.h
@@ -64,6 +64,8 @@ struct lis3lv02d_platform_data {
64 s8 axis_x; 64 s8 axis_x;
65 s8 axis_y; 65 s8 axis_y;
66 s8 axis_z; 66 s8 axis_z;
67#define LIS3_USE_REGULATOR_CTRL 0x01
68 u16 driver_features;
67 int (*setup_resources)(void); 69 int (*setup_resources)(void);
68 int (*release_resources)(void); 70 int (*release_resources)(void);
69 /* Limits for selftest are specified in chip data sheet */ 71 /* Limits for selftest are specified in chip data sheet */