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:34:36 -0400 |
commit | a244e7b57f0fb778bd333b10fffbeb362b94ffc3 (patch) | |
tree | 65baf50996e00103b0ce0d87c3293939c46afc0b /drivers/iio/light | |
parent | 6c25539cbc460f7f594e30ac6db88d5e61e8baff (diff) |
iio: Add driver for AMS/TAOS tcs3414 digital color sensor
16-bit digital color sensor with red, green, blue and clear channel
datasheet: http://ams.com/eng/content/download/250258/975997/TCS3414_Datasheet_EN_v1.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/tcs3414.c | 405 |
3 files changed, 418 insertions, 0 deletions
diff --git a/drivers/iio/light/Kconfig b/drivers/iio/light/Kconfig index 7d83dca6d080..bf05ca5b0a57 100644 --- a/drivers/iio/light/Kconfig +++ b/drivers/iio/light/Kconfig | |||
@@ -128,6 +128,18 @@ config LTR501 | |||
128 | This driver can also be built as a module. If so, the module | 128 | This driver can also be built as a module. If so, the module |
129 | will be called ltr501. | 129 | will be called ltr501. |
130 | 130 | ||
131 | config TCS3414 | ||
132 | tristate "TAOS TCS3414 digital color sensor" | ||
133 | depends on I2C | ||
134 | select IIO_BUFFER | ||
135 | select IIO_TRIGGERED_BUFFER | ||
136 | help | ||
137 | If you say yes here you get support for the TAOS TCS3414 | ||
138 | family of digital color sensors. | ||
139 | |||
140 | This driver can also be built as a module. If so, the module | ||
141 | will be called tcs3414. | ||
142 | |||
131 | config TCS3472 | 143 | config TCS3472 |
132 | tristate "TAOS TCS3472 color light-to-digital converter" | 144 | tristate "TAOS TCS3472 color light-to-digital converter" |
133 | depends on I2C | 145 | depends on I2C |
diff --git a/drivers/iio/light/Makefile b/drivers/iio/light/Makefile index f3d1857b2083..8b8c09f9c1f8 100644 --- a/drivers/iio/light/Makefile +++ b/drivers/iio/light/Makefile | |||
@@ -14,6 +14,7 @@ obj-$(CONFIG_ISL29125) += isl29125.o | |||
14 | obj-$(CONFIG_SENSORS_LM3533) += lm3533-als.o | 14 | obj-$(CONFIG_SENSORS_LM3533) += lm3533-als.o |
15 | obj-$(CONFIG_LTR501) += ltr501.o | 15 | obj-$(CONFIG_LTR501) += ltr501.o |
16 | obj-$(CONFIG_SENSORS_TSL2563) += tsl2563.o | 16 | obj-$(CONFIG_SENSORS_TSL2563) += tsl2563.o |
17 | obj-$(CONFIG_TCS3414) += tcs3414.o | ||
17 | obj-$(CONFIG_TCS3472) += tcs3472.o | 18 | obj-$(CONFIG_TCS3472) += tcs3472.o |
18 | obj-$(CONFIG_TSL4531) += tsl4531.o | 19 | obj-$(CONFIG_TSL4531) += tsl4531.o |
19 | obj-$(CONFIG_VCNL4000) += vcnl4000.o | 20 | obj-$(CONFIG_VCNL4000) += vcnl4000.o |
diff --git a/drivers/iio/light/tcs3414.c b/drivers/iio/light/tcs3414.c new file mode 100644 index 000000000000..a9e449b0be0c --- /dev/null +++ b/drivers/iio/light/tcs3414.c | |||
@@ -0,0 +1,405 @@ | |||
1 | /* | ||
2 | * tcs3414.c - Support for TAOS TCS3414 digital color 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 | * Digital color sensor with 16-bit channels for red, green, blue, clear); | ||
11 | * 7-bit I2C slave address 0x39 (TCS3414) or 0x29, 0x49, 0x59 (TCS3413, | ||
12 | * TCS3415, TCS3416, resp.) | ||
13 | * | ||
14 | * TODO: sync, interrupt support, thresholds, prescaler | ||
15 | */ | ||
16 | |||
17 | #include <linux/module.h> | ||
18 | #include <linux/i2c.h> | ||
19 | #include <linux/delay.h> | ||
20 | #include <linux/pm.h> | ||
21 | |||
22 | #include <linux/iio/iio.h> | ||
23 | #include <linux/iio/sysfs.h> | ||
24 | #include <linux/iio/trigger_consumer.h> | ||
25 | #include <linux/iio/buffer.h> | ||
26 | #include <linux/iio/triggered_buffer.h> | ||
27 | |||
28 | #define TCS3414_DRV_NAME "tcs3414" | ||
29 | |||
30 | #define TCS3414_COMMAND BIT(7) | ||
31 | #define TCS3414_COMMAND_WORD (TCS3414_COMMAND | BIT(5)) | ||
32 | |||
33 | #define TCS3414_CONTROL (TCS3414_COMMAND | 0x00) | ||
34 | #define TCS3414_TIMING (TCS3414_COMMAND | 0x01) | ||
35 | #define TCS3414_ID (TCS3414_COMMAND | 0x04) | ||
36 | #define TCS3414_GAIN (TCS3414_COMMAND | 0x07) | ||
37 | #define TCS3414_DATA_GREEN (TCS3414_COMMAND_WORD | 0x10) | ||
38 | #define TCS3414_DATA_RED (TCS3414_COMMAND_WORD | 0x12) | ||
39 | #define TCS3414_DATA_BLUE (TCS3414_COMMAND_WORD | 0x14) | ||
40 | #define TCS3414_DATA_CLEAR (TCS3414_COMMAND_WORD | 0x16) | ||
41 | |||
42 | #define TCS3414_CONTROL_ADC_VALID BIT(4) | ||
43 | #define TCS3414_CONTROL_ADC_EN BIT(1) | ||
44 | #define TCS3414_CONTROL_POWER BIT(0) | ||
45 | |||
46 | #define TCS3414_INTEG_MASK GENMASK(1, 0) | ||
47 | #define TCS3414_INTEG_12MS 0x0 | ||
48 | #define TCS3414_INTEG_100MS 0x1 | ||
49 | #define TCS3414_INTEG_400MS 0x2 | ||
50 | |||
51 | #define TCS3414_GAIN_MASK GENMASK(5, 4) | ||
52 | #define TCS3414_GAIN_SHIFT 4 | ||
53 | |||
54 | struct tcs3414_data { | ||
55 | struct i2c_client *client; | ||
56 | struct mutex lock; | ||
57 | u8 control; | ||
58 | u8 gain; | ||
59 | u8 timing; | ||
60 | u16 buffer[8]; /* 4x 16-bit + 8 bytes timestamp */ | ||
61 | }; | ||
62 | |||
63 | #define TCS3414_CHANNEL(_color, _si, _addr) { \ | ||
64 | .type = IIO_INTENSITY, \ | ||
65 | .modified = 1, \ | ||
66 | .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ | ||
67 | .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | \ | ||
68 | BIT(IIO_CHAN_INFO_INT_TIME), \ | ||
69 | .channel2 = IIO_MOD_LIGHT_##_color, \ | ||
70 | .address = _addr, \ | ||
71 | .scan_index = _si, \ | ||
72 | .scan_type = { \ | ||
73 | .sign = 'u', \ | ||
74 | .realbits = 16, \ | ||
75 | .storagebits = 16, \ | ||
76 | .endianness = IIO_CPU, \ | ||
77 | }, \ | ||
78 | } | ||
79 | |||
80 | /* scale factors: 1/gain */ | ||
81 | static const int tcs3414_scales[][2] = { | ||
82 | {1, 0}, {0, 250000}, {0, 62500}, {0, 15625} | ||
83 | }; | ||
84 | |||
85 | /* integration time in ms */ | ||
86 | static const int tcs3414_times[] = { 12, 100, 400 }; | ||
87 | |||
88 | static const struct iio_chan_spec tcs3414_channels[] = { | ||
89 | TCS3414_CHANNEL(GREEN, 0, TCS3414_DATA_GREEN), | ||
90 | TCS3414_CHANNEL(RED, 1, TCS3414_DATA_RED), | ||
91 | TCS3414_CHANNEL(BLUE, 2, TCS3414_DATA_BLUE), | ||
92 | TCS3414_CHANNEL(CLEAR, 3, TCS3414_DATA_CLEAR), | ||
93 | IIO_CHAN_SOFT_TIMESTAMP(4), | ||
94 | }; | ||
95 | |||
96 | static int tcs3414_req_data(struct tcs3414_data *data) | ||
97 | { | ||
98 | int tries = 25; | ||
99 | int ret; | ||
100 | |||
101 | ret = i2c_smbus_write_byte_data(data->client, TCS3414_CONTROL, | ||
102 | data->control | TCS3414_CONTROL_ADC_EN); | ||
103 | if (ret < 0) | ||
104 | return ret; | ||
105 | |||
106 | while (tries--) { | ||
107 | ret = i2c_smbus_read_byte_data(data->client, TCS3414_CONTROL); | ||
108 | if (ret < 0) | ||
109 | return ret; | ||
110 | if (ret & TCS3414_CONTROL_ADC_VALID) | ||
111 | break; | ||
112 | msleep(20); | ||
113 | } | ||
114 | |||
115 | ret = i2c_smbus_write_byte_data(data->client, TCS3414_CONTROL, | ||
116 | data->control); | ||
117 | if (ret < 0) | ||
118 | return ret; | ||
119 | |||
120 | if (tries < 0) { | ||
121 | dev_err(&data->client->dev, "data not ready\n"); | ||
122 | return -EIO; | ||
123 | } | ||
124 | |||
125 | return 0; | ||
126 | } | ||
127 | |||
128 | static int tcs3414_read_raw(struct iio_dev *indio_dev, | ||
129 | struct iio_chan_spec const *chan, | ||
130 | int *val, int *val2, long mask) | ||
131 | { | ||
132 | struct tcs3414_data *data = iio_priv(indio_dev); | ||
133 | int i, ret; | ||
134 | |||
135 | switch (mask) { | ||
136 | case IIO_CHAN_INFO_RAW: | ||
137 | if (iio_buffer_enabled(indio_dev)) | ||
138 | return -EBUSY; | ||
139 | mutex_lock(&data->lock); | ||
140 | ret = tcs3414_req_data(data); | ||
141 | if (ret < 0) { | ||
142 | mutex_unlock(&data->lock); | ||
143 | return ret; | ||
144 | } | ||
145 | ret = i2c_smbus_read_word_data(data->client, chan->address); | ||
146 | mutex_unlock(&data->lock); | ||
147 | if (ret < 0) | ||
148 | return ret; | ||
149 | *val = ret; | ||
150 | return IIO_VAL_INT; | ||
151 | case IIO_CHAN_INFO_SCALE: | ||
152 | i = (data->gain & TCS3414_GAIN_MASK) >> TCS3414_GAIN_SHIFT; | ||
153 | *val = tcs3414_scales[i][0]; | ||
154 | *val2 = tcs3414_scales[i][1]; | ||
155 | return IIO_VAL_INT_PLUS_MICRO; | ||
156 | case IIO_CHAN_INFO_INT_TIME: | ||
157 | *val = 0; | ||
158 | *val2 = tcs3414_times[data->timing & TCS3414_INTEG_MASK] * 1000; | ||
159 | return IIO_VAL_INT_PLUS_MICRO; | ||
160 | } | ||
161 | return -EINVAL; | ||
162 | } | ||
163 | |||
164 | static int tcs3414_write_raw(struct iio_dev *indio_dev, | ||
165 | struct iio_chan_spec const *chan, | ||
166 | int val, int val2, long mask) | ||
167 | { | ||
168 | struct tcs3414_data *data = iio_priv(indio_dev); | ||
169 | int i; | ||
170 | |||
171 | switch (mask) { | ||
172 | case IIO_CHAN_INFO_SCALE: | ||
173 | for (i = 0; i < ARRAY_SIZE(tcs3414_scales); i++) { | ||
174 | if (val == tcs3414_scales[i][0] && | ||
175 | val2 == tcs3414_scales[i][1]) { | ||
176 | data->gain &= ~TCS3414_GAIN_MASK; | ||
177 | data->gain |= i << TCS3414_GAIN_SHIFT; | ||
178 | return i2c_smbus_write_byte_data( | ||
179 | data->client, TCS3414_GAIN, | ||
180 | data->gain); | ||
181 | } | ||
182 | } | ||
183 | return -EINVAL; | ||
184 | case IIO_CHAN_INFO_INT_TIME: | ||
185 | if (val != 0) | ||
186 | return -EINVAL; | ||
187 | for (i = 0; i < ARRAY_SIZE(tcs3414_times); i++) { | ||
188 | if (val == tcs3414_times[i] * 1000) { | ||
189 | data->timing &= ~TCS3414_INTEG_MASK; | ||
190 | data->timing |= i; | ||
191 | return i2c_smbus_write_byte_data( | ||
192 | data->client, TCS3414_TIMING, | ||
193 | data->timing); | ||
194 | } | ||
195 | } | ||
196 | return -EINVAL; | ||
197 | default: | ||
198 | return -EINVAL; | ||
199 | } | ||
200 | } | ||
201 | |||
202 | static irqreturn_t tcs3414_trigger_handler(int irq, void *p) | ||
203 | { | ||
204 | struct iio_poll_func *pf = p; | ||
205 | struct iio_dev *indio_dev = pf->indio_dev; | ||
206 | struct tcs3414_data *data = iio_priv(indio_dev); | ||
207 | int i, j = 0; | ||
208 | |||
209 | for_each_set_bit(i, indio_dev->active_scan_mask, | ||
210 | indio_dev->masklength) { | ||
211 | int ret = i2c_smbus_read_word_data(data->client, | ||
212 | TCS3414_DATA_GREEN + 2*i); | ||
213 | if (ret < 0) | ||
214 | goto done; | ||
215 | |||
216 | data->buffer[j++] = ret; | ||
217 | } | ||
218 | |||
219 | iio_push_to_buffers_with_timestamp(indio_dev, data->buffer, | ||
220 | iio_get_time_ns()); | ||
221 | |||
222 | done: | ||
223 | iio_trigger_notify_done(indio_dev->trig); | ||
224 | |||
225 | return IRQ_HANDLED; | ||
226 | } | ||
227 | |||
228 | static IIO_CONST_ATTR(scale_available, "1 0.25 0.0625 0.015625"); | ||
229 | static IIO_CONST_ATTR_INT_TIME_AVAIL("0.012 0.1 0.4"); | ||
230 | |||
231 | static struct attribute *tcs3414_attributes[] = { | ||
232 | &iio_const_attr_scale_available.dev_attr.attr, | ||
233 | &iio_const_attr_integration_time_available.dev_attr.attr, | ||
234 | NULL | ||
235 | }; | ||
236 | |||
237 | static const struct attribute_group tcs3414_attribute_group = { | ||
238 | .attrs = tcs3414_attributes, | ||
239 | }; | ||
240 | |||
241 | static const struct iio_info tcs3414_info = { | ||
242 | .read_raw = tcs3414_read_raw, | ||
243 | .write_raw = tcs3414_write_raw, | ||
244 | .attrs = &tcs3414_attribute_group, | ||
245 | .driver_module = THIS_MODULE, | ||
246 | }; | ||
247 | |||
248 | static int tcs3414_buffer_preenable(struct iio_dev *indio_dev) | ||
249 | { | ||
250 | struct tcs3414_data *data = iio_priv(indio_dev); | ||
251 | |||
252 | data->control |= TCS3414_CONTROL_ADC_EN; | ||
253 | return i2c_smbus_write_byte_data(data->client, TCS3414_CONTROL, | ||
254 | data->control); | ||
255 | } | ||
256 | |||
257 | static int tcs3414_buffer_predisable(struct iio_dev *indio_dev) | ||
258 | { | ||
259 | struct tcs3414_data *data = iio_priv(indio_dev); | ||
260 | int ret; | ||
261 | |||
262 | ret = iio_triggered_buffer_predisable(indio_dev); | ||
263 | if (ret < 0) | ||
264 | return ret; | ||
265 | |||
266 | data->control &= ~TCS3414_CONTROL_ADC_EN; | ||
267 | return i2c_smbus_write_byte_data(data->client, TCS3414_CONTROL, | ||
268 | data->control); | ||
269 | } | ||
270 | |||
271 | static const struct iio_buffer_setup_ops tcs3414_buffer_setup_ops = { | ||
272 | .preenable = tcs3414_buffer_preenable, | ||
273 | .postenable = &iio_triggered_buffer_postenable, | ||
274 | .predisable = tcs3414_buffer_predisable, | ||
275 | }; | ||
276 | |||
277 | static int tcs3414_probe(struct i2c_client *client, | ||
278 | const struct i2c_device_id *id) | ||
279 | { | ||
280 | struct tcs3414_data *data; | ||
281 | struct iio_dev *indio_dev; | ||
282 | int ret; | ||
283 | |||
284 | indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); | ||
285 | if (indio_dev == NULL) | ||
286 | return -ENOMEM; | ||
287 | |||
288 | data = iio_priv(indio_dev); | ||
289 | i2c_set_clientdata(client, indio_dev); | ||
290 | data->client = client; | ||
291 | mutex_init(&data->lock); | ||
292 | |||
293 | indio_dev->dev.parent = &client->dev; | ||
294 | indio_dev->info = &tcs3414_info; | ||
295 | indio_dev->name = TCS3414_DRV_NAME; | ||
296 | indio_dev->channels = tcs3414_channels; | ||
297 | indio_dev->num_channels = ARRAY_SIZE(tcs3414_channels); | ||
298 | indio_dev->modes = INDIO_DIRECT_MODE; | ||
299 | |||
300 | ret = i2c_smbus_read_byte_data(data->client, TCS3414_ID); | ||
301 | if (ret < 0) | ||
302 | return ret; | ||
303 | |||
304 | switch (ret & 0xf0) { | ||
305 | case 0x00: | ||
306 | dev_info(&client->dev, "TCS3404 found\n"); | ||
307 | break; | ||
308 | case 0x10: | ||
309 | dev_info(&client->dev, "TCS3413/14/15/16 found\n"); | ||
310 | break; | ||
311 | default: | ||
312 | return -ENODEV; | ||
313 | } | ||
314 | |||
315 | data->control = TCS3414_CONTROL_POWER; | ||
316 | ret = i2c_smbus_write_byte_data(data->client, TCS3414_CONTROL, | ||
317 | data->control); | ||
318 | if (ret < 0) | ||
319 | return ret; | ||
320 | |||
321 | data->timing = TCS3414_INTEG_12MS; /* free running */ | ||
322 | ret = i2c_smbus_write_byte_data(data->client, TCS3414_TIMING, | ||
323 | data->timing); | ||
324 | if (ret < 0) | ||
325 | return ret; | ||
326 | |||
327 | ret = i2c_smbus_read_byte_data(data->client, TCS3414_GAIN); | ||
328 | if (ret < 0) | ||
329 | return ret; | ||
330 | data->gain = ret; | ||
331 | |||
332 | ret = iio_triggered_buffer_setup(indio_dev, NULL, | ||
333 | tcs3414_trigger_handler, &tcs3414_buffer_setup_ops); | ||
334 | if (ret < 0) | ||
335 | return ret; | ||
336 | |||
337 | ret = iio_device_register(indio_dev); | ||
338 | if (ret < 0) | ||
339 | goto buffer_cleanup; | ||
340 | |||
341 | return 0; | ||
342 | |||
343 | buffer_cleanup: | ||
344 | iio_triggered_buffer_cleanup(indio_dev); | ||
345 | return ret; | ||
346 | } | ||
347 | |||
348 | static int tcs3414_powerdown(struct tcs3414_data *data) | ||
349 | { | ||
350 | return i2c_smbus_write_byte_data(data->client, TCS3414_CONTROL, | ||
351 | data->control & ~(TCS3414_CONTROL_POWER | | ||
352 | TCS3414_CONTROL_ADC_EN)); | ||
353 | } | ||
354 | |||
355 | static int tcs3414_remove(struct i2c_client *client) | ||
356 | { | ||
357 | struct iio_dev *indio_dev = i2c_get_clientdata(client); | ||
358 | |||
359 | iio_device_unregister(indio_dev); | ||
360 | iio_triggered_buffer_cleanup(indio_dev); | ||
361 | tcs3414_powerdown(iio_priv(indio_dev)); | ||
362 | |||
363 | return 0; | ||
364 | } | ||
365 | |||
366 | #ifdef CONFIG_PM_SLEEP | ||
367 | static int tcs3414_suspend(struct device *dev) | ||
368 | { | ||
369 | struct tcs3414_data *data = iio_priv(i2c_get_clientdata( | ||
370 | to_i2c_client(dev))); | ||
371 | return tcs3414_powerdown(data); | ||
372 | } | ||
373 | |||
374 | static int tcs3414_resume(struct device *dev) | ||
375 | { | ||
376 | struct tcs3414_data *data = iio_priv(i2c_get_clientdata( | ||
377 | to_i2c_client(dev))); | ||
378 | return i2c_smbus_write_byte_data(data->client, TCS3414_CONTROL, | ||
379 | data->control); | ||
380 | } | ||
381 | #endif | ||
382 | |||
383 | static SIMPLE_DEV_PM_OPS(tcs3414_pm_ops, tcs3414_suspend, tcs3414_resume); | ||
384 | |||
385 | static const struct i2c_device_id tcs3414_id[] = { | ||
386 | { "tcs3414", 0 }, | ||
387 | { } | ||
388 | }; | ||
389 | MODULE_DEVICE_TABLE(i2c, tcs3414_id); | ||
390 | |||
391 | static struct i2c_driver tcs3414_driver = { | ||
392 | .driver = { | ||
393 | .name = TCS3414_DRV_NAME, | ||
394 | .pm = &tcs3414_pm_ops, | ||
395 | .owner = THIS_MODULE, | ||
396 | }, | ||
397 | .probe = tcs3414_probe, | ||
398 | .remove = tcs3414_remove, | ||
399 | .id_table = tcs3414_id, | ||
400 | }; | ||
401 | module_i2c_driver(tcs3414_driver); | ||
402 | |||
403 | MODULE_AUTHOR("Peter Meerwald <pmeerw@pmeerw.net>"); | ||
404 | MODULE_DESCRIPTION("TCS3414 digital color sensors driver"); | ||
405 | MODULE_LICENSE("GPL"); | ||