diff options
author | Peter Meerwald <pmeerw@pmeerw.net> | 2014-05-07 08:38:00 -0400 |
---|---|---|
committer | Jonathan Cameron <jic23@kernel.org> | 2014-07-07 04:06:50 -0400 |
commit | 6c25539cbc460f7f594e30ac6db88d5e61e8baff (patch) | |
tree | 484010d1be4151afd2faad183e8afa36c5f73a6e /drivers/iio/light | |
parent | a034234277d2f51104deadd559dd9584ba00a8cc (diff) |
iio: Add Intersil isl29125 digital color light sensor driver
datasheet: http://www.intersil.com/content/dam/Intersil/documents/isl2/isl29125.pdf
Signed-off-by: Peter Meerwald <pmeerw@pmeerw.net>
Signed-off-by: Jonathan Cameron <jic23@kernel.org>
Diffstat (limited to 'drivers/iio/light')
-rw-r--r-- | drivers/iio/light/Kconfig | 12 | ||||
-rw-r--r-- | drivers/iio/light/Makefile | 1 | ||||
-rw-r--r-- | drivers/iio/light/isl29125.c | 347 |
3 files changed, 360 insertions, 0 deletions
diff --git a/drivers/iio/light/Kconfig b/drivers/iio/light/Kconfig index c89740d4748f..7d83dca6d080 100644 --- a/drivers/iio/light/Kconfig +++ b/drivers/iio/light/Kconfig | |||
@@ -62,6 +62,18 @@ config GP2AP020A00F | |||
62 | To compile this driver as a module, choose M here: the | 62 | To compile this driver as a module, choose M here: the |
63 | module will be called gp2ap020a00f. | 63 | module will be called gp2ap020a00f. |
64 | 64 | ||
65 | config ISL29125 | ||
66 | tristate "Intersil ISL29125 digital color light sensor" | ||
67 | depends on I2C | ||
68 | select IIO_BUFFER | ||
69 | select IIO_TRIGGERED_BUFFER | ||
70 | help | ||
71 | Say Y here if you want to build a driver for the Intersil ISL29125 | ||
72 | RGB light sensor for I2C. | ||
73 | |||
74 | To compile this driver as a module, choose M here: the module will be | ||
75 | called isl29125. | ||
76 | |||
65 | config HID_SENSOR_ALS | 77 | config HID_SENSOR_ALS |
66 | depends on HID_SENSOR_HUB | 78 | depends on HID_SENSOR_HUB |
67 | select IIO_BUFFER | 79 | select IIO_BUFFER |
diff --git a/drivers/iio/light/Makefile b/drivers/iio/light/Makefile index 3eb36e5151fa..f3d1857b2083 100644 --- a/drivers/iio/light/Makefile +++ b/drivers/iio/light/Makefile | |||
@@ -10,6 +10,7 @@ obj-$(CONFIG_CM36651) += cm36651.o | |||
10 | obj-$(CONFIG_GP2AP020A00F) += gp2ap020a00f.o | 10 | obj-$(CONFIG_GP2AP020A00F) += gp2ap020a00f.o |
11 | obj-$(CONFIG_HID_SENSOR_ALS) += hid-sensor-als.o | 11 | obj-$(CONFIG_HID_SENSOR_ALS) += hid-sensor-als.o |
12 | obj-$(CONFIG_HID_SENSOR_PROX) += hid-sensor-prox.o | 12 | obj-$(CONFIG_HID_SENSOR_PROX) += hid-sensor-prox.o |
13 | obj-$(CONFIG_ISL29125) += isl29125.o | ||
13 | obj-$(CONFIG_SENSORS_LM3533) += lm3533-als.o | 14 | obj-$(CONFIG_SENSORS_LM3533) += lm3533-als.o |
14 | obj-$(CONFIG_LTR501) += ltr501.o | 15 | obj-$(CONFIG_LTR501) += ltr501.o |
15 | obj-$(CONFIG_SENSORS_TSL2563) += tsl2563.o | 16 | obj-$(CONFIG_SENSORS_TSL2563) += tsl2563.o |
diff --git a/drivers/iio/light/isl29125.c b/drivers/iio/light/isl29125.c new file mode 100644 index 000000000000..c82f4a6f8464 --- /dev/null +++ b/drivers/iio/light/isl29125.c | |||
@@ -0,0 +1,347 @@ | |||
1 | /* | ||
2 | * isl29125.c - Support for Intersil ISL29125 RGB light sensor | ||
3 | * | ||
4 | * Copyright (c) 2014 Peter Meerwald <pmeerw@pmeerw.net> | ||
5 | * | ||
6 | * This file is subject to the terms and conditions of version 2 of | ||
7 | * the GNU General Public License. See the file COPYING in the main | ||
8 | * directory of this archive for more details. | ||
9 | * | ||
10 | * RGB light sensor with 16-bit channels for red, green, blue); | ||
11 | * 7-bit I2C slave address 0x44 | ||
12 | * | ||
13 | * TODO: interrupt support, IR compensation, thresholds, 12bit | ||
14 | */ | ||
15 | |||
16 | #include <linux/module.h> | ||
17 | #include <linux/i2c.h> | ||
18 | #include <linux/delay.h> | ||
19 | #include <linux/pm.h> | ||
20 | |||
21 | #include <linux/iio/iio.h> | ||
22 | #include <linux/iio/sysfs.h> | ||
23 | #include <linux/iio/trigger_consumer.h> | ||
24 | #include <linux/iio/buffer.h> | ||
25 | #include <linux/iio/triggered_buffer.h> | ||
26 | |||
27 | #define ISL29125_DRV_NAME "isl29125" | ||
28 | |||
29 | #define ISL29125_DEVICE_ID 0x00 | ||
30 | #define ISL29125_CONF1 0x01 | ||
31 | #define ISL29125_CONF2 0x02 | ||
32 | #define ISL29125_CONF3 0x03 | ||
33 | #define ISL29125_STATUS 0x08 | ||
34 | #define ISL29125_GREEN_DATA 0x09 | ||
35 | #define ISL29125_RED_DATA 0x0b | ||
36 | #define ISL29125_BLUE_DATA 0x0d | ||
37 | |||
38 | #define ISL29125_ID 0x7d | ||
39 | |||
40 | #define ISL29125_MODE_MASK GENMASK(2, 0) | ||
41 | #define ISL29125_MODE_PD 0x0 | ||
42 | #define ISL29125_MODE_G 0x1 | ||
43 | #define ISL29125_MODE_R 0x2 | ||
44 | #define ISL29125_MODE_B 0x3 | ||
45 | #define ISL29125_MODE_RGB 0x5 | ||
46 | |||
47 | #define ISL29125_MODE_RANGE BIT(3) | ||
48 | |||
49 | #define ISL29125_STATUS_CONV BIT(1) | ||
50 | |||
51 | struct isl29125_data { | ||
52 | struct i2c_client *client; | ||
53 | struct mutex lock; | ||
54 | u8 conf1; | ||
55 | u16 buffer[8]; /* 3x 16-bit, padding, 8 bytes timestamp */ | ||
56 | }; | ||
57 | |||
58 | #define ISL29125_CHANNEL(_color, _si) { \ | ||
59 | .type = IIO_INTENSITY, \ | ||
60 | .modified = 1, \ | ||
61 | .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ | ||
62 | .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ | ||
63 | .channel2 = IIO_MOD_LIGHT_##_color, \ | ||
64 | .scan_index = _si, \ | ||
65 | .scan_type = { \ | ||
66 | .sign = 'u', \ | ||
67 | .realbits = 16, \ | ||
68 | .storagebits = 16, \ | ||
69 | .endianness = IIO_CPU, \ | ||
70 | }, \ | ||
71 | } | ||
72 | |||
73 | static const struct iio_chan_spec isl29125_channels[] = { | ||
74 | ISL29125_CHANNEL(GREEN, 0), | ||
75 | ISL29125_CHANNEL(RED, 1), | ||
76 | ISL29125_CHANNEL(BLUE, 2), | ||
77 | IIO_CHAN_SOFT_TIMESTAMP(3), | ||
78 | }; | ||
79 | |||
80 | static const struct { | ||
81 | u8 mode, data; | ||
82 | } isl29125_regs[] = { | ||
83 | {ISL29125_MODE_G, ISL29125_GREEN_DATA}, | ||
84 | {ISL29125_MODE_R, ISL29125_RED_DATA}, | ||
85 | {ISL29125_MODE_B, ISL29125_BLUE_DATA}, | ||
86 | }; | ||
87 | |||
88 | static int isl29125_read_data(struct isl29125_data *data, int si) | ||
89 | { | ||
90 | int tries = 5; | ||
91 | int ret; | ||
92 | |||
93 | ret = i2c_smbus_write_byte_data(data->client, ISL29125_CONF1, | ||
94 | data->conf1 | isl29125_regs[si].mode); | ||
95 | if (ret < 0) | ||
96 | return ret; | ||
97 | |||
98 | msleep(101); | ||
99 | |||
100 | while (tries--) { | ||
101 | ret = i2c_smbus_read_byte_data(data->client, ISL29125_STATUS); | ||
102 | if (ret < 0) | ||
103 | goto fail; | ||
104 | if (ret & ISL29125_STATUS_CONV) | ||
105 | break; | ||
106 | msleep(20); | ||
107 | } | ||
108 | |||
109 | if (tries < 0) { | ||
110 | dev_err(&data->client->dev, "data not ready\n"); | ||
111 | ret = -EIO; | ||
112 | goto fail; | ||
113 | } | ||
114 | |||
115 | ret = i2c_smbus_read_word_data(data->client, isl29125_regs[si].data); | ||
116 | |||
117 | fail: | ||
118 | i2c_smbus_write_byte_data(data->client, ISL29125_CONF1, data->conf1); | ||
119 | return ret; | ||
120 | } | ||
121 | |||
122 | static int isl29125_read_raw(struct iio_dev *indio_dev, | ||
123 | struct iio_chan_spec const *chan, | ||
124 | int *val, int *val2, long mask) | ||
125 | { | ||
126 | struct isl29125_data *data = iio_priv(indio_dev); | ||
127 | int ret; | ||
128 | |||
129 | switch (mask) { | ||
130 | case IIO_CHAN_INFO_RAW: | ||
131 | if (iio_buffer_enabled(indio_dev)) | ||
132 | return -EBUSY; | ||
133 | mutex_lock(&data->lock); | ||
134 | ret = isl29125_read_data(data, chan->scan_index); | ||
135 | mutex_unlock(&data->lock); | ||
136 | if (ret < 0) | ||
137 | return ret; | ||
138 | *val = ret; | ||
139 | return IIO_VAL_INT; | ||
140 | case IIO_CHAN_INFO_SCALE: | ||
141 | *val = 0; | ||
142 | if (data->conf1 & ISL29125_MODE_RANGE) | ||
143 | *val2 = 152590; /* 10k lux full range */ | ||
144 | else | ||
145 | *val2 = 5722; /* 375 lux full range */ | ||
146 | return IIO_VAL_INT_PLUS_MICRO; | ||
147 | } | ||
148 | return -EINVAL; | ||
149 | } | ||
150 | |||
151 | static int isl29125_write_raw(struct iio_dev *indio_dev, | ||
152 | struct iio_chan_spec const *chan, | ||
153 | int val, int val2, long mask) | ||
154 | { | ||
155 | struct isl29125_data *data = iio_priv(indio_dev); | ||
156 | |||
157 | switch (mask) { | ||
158 | case IIO_CHAN_INFO_SCALE: | ||
159 | if (val != 0) | ||
160 | return -EINVAL; | ||
161 | if (val2 == 152590) | ||
162 | data->conf1 |= ISL29125_MODE_RANGE; | ||
163 | else if (val2 == 5722) | ||
164 | data->conf1 &= ~ISL29125_MODE_RANGE; | ||
165 | else | ||
166 | return -EINVAL; | ||
167 | return i2c_smbus_write_byte_data(data->client, ISL29125_CONF1, | ||
168 | data->conf1); | ||
169 | default: | ||
170 | return -EINVAL; | ||
171 | } | ||
172 | } | ||
173 | |||
174 | static irqreturn_t isl29125_trigger_handler(int irq, void *p) | ||
175 | { | ||
176 | struct iio_poll_func *pf = p; | ||
177 | struct iio_dev *indio_dev = pf->indio_dev; | ||
178 | struct isl29125_data *data = iio_priv(indio_dev); | ||
179 | int i, j = 0; | ||
180 | |||
181 | for_each_set_bit(i, indio_dev->active_scan_mask, | ||
182 | indio_dev->masklength) { | ||
183 | int ret = i2c_smbus_read_word_data(data->client, | ||
184 | isl29125_regs[i].data); | ||
185 | if (ret < 0) | ||
186 | goto done; | ||
187 | |||
188 | data->buffer[j++] = ret; | ||
189 | } | ||
190 | |||
191 | iio_push_to_buffers_with_timestamp(indio_dev, data->buffer, | ||
192 | iio_get_time_ns()); | ||
193 | |||
194 | done: | ||
195 | iio_trigger_notify_done(indio_dev->trig); | ||
196 | |||
197 | return IRQ_HANDLED; | ||
198 | } | ||
199 | |||
200 | static const struct iio_info isl29125_info = { | ||
201 | .read_raw = isl29125_read_raw, | ||
202 | .write_raw = isl29125_write_raw, | ||
203 | .driver_module = THIS_MODULE, | ||
204 | }; | ||
205 | |||
206 | static int isl29125_buffer_preenable(struct iio_dev *indio_dev) | ||
207 | { | ||
208 | struct isl29125_data *data = iio_priv(indio_dev); | ||
209 | |||
210 | data->conf1 |= ISL29125_MODE_RGB; | ||
211 | return i2c_smbus_write_byte_data(data->client, ISL29125_CONF1, | ||
212 | data->conf1); | ||
213 | } | ||
214 | |||
215 | static int isl29125_buffer_predisable(struct iio_dev *indio_dev) | ||
216 | { | ||
217 | struct isl29125_data *data = iio_priv(indio_dev); | ||
218 | int ret; | ||
219 | |||
220 | ret = iio_triggered_buffer_predisable(indio_dev); | ||
221 | if (ret < 0) | ||
222 | return ret; | ||
223 | |||
224 | data->conf1 &= ~ISL29125_MODE_MASK; | ||
225 | data->conf1 |= ISL29125_MODE_PD; | ||
226 | return i2c_smbus_write_byte_data(data->client, ISL29125_CONF1, | ||
227 | data->conf1); | ||
228 | } | ||
229 | |||
230 | static const struct iio_buffer_setup_ops isl29125_buffer_setup_ops = { | ||
231 | .preenable = isl29125_buffer_preenable, | ||
232 | .postenable = &iio_triggered_buffer_postenable, | ||
233 | .predisable = isl29125_buffer_predisable, | ||
234 | }; | ||
235 | |||
236 | static int isl29125_probe(struct i2c_client *client, | ||
237 | const struct i2c_device_id *id) | ||
238 | { | ||
239 | struct isl29125_data *data; | ||
240 | struct iio_dev *indio_dev; | ||
241 | int ret; | ||
242 | |||
243 | indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); | ||
244 | if (indio_dev == NULL) | ||
245 | return -ENOMEM; | ||
246 | |||
247 | data = iio_priv(indio_dev); | ||
248 | i2c_set_clientdata(client, indio_dev); | ||
249 | data->client = client; | ||
250 | mutex_init(&data->lock); | ||
251 | |||
252 | indio_dev->dev.parent = &client->dev; | ||
253 | indio_dev->info = &isl29125_info; | ||
254 | indio_dev->name = ISL29125_DRV_NAME; | ||
255 | indio_dev->channels = isl29125_channels; | ||
256 | indio_dev->num_channels = ARRAY_SIZE(isl29125_channels); | ||
257 | indio_dev->modes = INDIO_DIRECT_MODE; | ||
258 | |||
259 | ret = i2c_smbus_read_byte_data(data->client, ISL29125_DEVICE_ID); | ||
260 | if (ret < 0) | ||
261 | return ret; | ||
262 | if (ret != ISL29125_ID) | ||
263 | return -ENODEV; | ||
264 | |||
265 | data->conf1 = ISL29125_MODE_PD | ISL29125_MODE_RANGE; | ||
266 | ret = i2c_smbus_write_byte_data(data->client, ISL29125_CONF1, | ||
267 | data->conf1); | ||
268 | if (ret < 0) | ||
269 | return ret; | ||
270 | |||
271 | ret = i2c_smbus_write_byte_data(data->client, ISL29125_STATUS, 0); | ||
272 | if (ret < 0) | ||
273 | return ret; | ||
274 | |||
275 | ret = iio_triggered_buffer_setup(indio_dev, NULL, | ||
276 | isl29125_trigger_handler, &isl29125_buffer_setup_ops); | ||
277 | if (ret < 0) | ||
278 | return ret; | ||
279 | |||
280 | ret = iio_device_register(indio_dev); | ||
281 | if (ret < 0) | ||
282 | goto buffer_cleanup; | ||
283 | |||
284 | return 0; | ||
285 | |||
286 | buffer_cleanup: | ||
287 | iio_triggered_buffer_cleanup(indio_dev); | ||
288 | return ret; | ||
289 | } | ||
290 | |||
291 | static int isl29125_powerdown(struct isl29125_data *data) | ||
292 | { | ||
293 | return i2c_smbus_write_byte_data(data->client, ISL29125_CONF1, | ||
294 | (data->conf1 & ~ISL29125_MODE_MASK) | ISL29125_MODE_PD); | ||
295 | } | ||
296 | |||
297 | static int isl29125_remove(struct i2c_client *client) | ||
298 | { | ||
299 | struct iio_dev *indio_dev = i2c_get_clientdata(client); | ||
300 | |||
301 | iio_device_unregister(indio_dev); | ||
302 | iio_triggered_buffer_cleanup(indio_dev); | ||
303 | isl29125_powerdown(iio_priv(indio_dev)); | ||
304 | |||
305 | return 0; | ||
306 | } | ||
307 | |||
308 | #ifdef CONFIG_PM_SLEEP | ||
309 | static int isl29125_suspend(struct device *dev) | ||
310 | { | ||
311 | struct isl29125_data *data = iio_priv(i2c_get_clientdata( | ||
312 | to_i2c_client(dev))); | ||
313 | return isl29125_powerdown(data); | ||
314 | } | ||
315 | |||
316 | static int isl29125_resume(struct device *dev) | ||
317 | { | ||
318 | struct isl29125_data *data = iio_priv(i2c_get_clientdata( | ||
319 | to_i2c_client(dev))); | ||
320 | return i2c_smbus_write_byte_data(data->client, ISL29125_CONF1, | ||
321 | data->conf1); | ||
322 | } | ||
323 | #endif | ||
324 | |||
325 | static SIMPLE_DEV_PM_OPS(isl29125_pm_ops, isl29125_suspend, isl29125_resume); | ||
326 | |||
327 | static const struct i2c_device_id isl29125_id[] = { | ||
328 | { "isl29125", 0 }, | ||
329 | { } | ||
330 | }; | ||
331 | MODULE_DEVICE_TABLE(i2c, isl29125_id); | ||
332 | |||
333 | static struct i2c_driver isl29125_driver = { | ||
334 | .driver = { | ||
335 | .name = ISL29125_DRV_NAME, | ||
336 | .pm = &isl29125_pm_ops, | ||
337 | .owner = THIS_MODULE, | ||
338 | }, | ||
339 | .probe = isl29125_probe, | ||
340 | .remove = isl29125_remove, | ||
341 | .id_table = isl29125_id, | ||
342 | }; | ||
343 | module_i2c_driver(isl29125_driver); | ||
344 | |||
345 | MODULE_AUTHOR("Peter Meerwald <pmeerw@pmeerw.net>"); | ||
346 | MODULE_DESCRIPTION("ISL29125 RGB light sensor driver"); | ||
347 | MODULE_LICENSE("GPL"); | ||