Commit | Line | Data |
---|---|---|
bbdb822c PM |
1 | /* |
2 | * adjd_s311.c - Support for ADJD-S311-CR999 digital color sensor | |
3 | * | |
4 | * Copyright (C) 2012 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 | * driver for ADJD-S311-CR999 digital color sensor (10-bit channels for | |
11 | * red, green, blue, clear); 7-bit I2C slave address 0x74 | |
12 | * | |
13 | * limitations: no calibration, no offset mode, no sleep mode | |
14 | */ | |
15 | ||
16 | #include <linux/module.h> | |
bbdb822c PM |
17 | #include <linux/interrupt.h> |
18 | #include <linux/i2c.h> | |
19 | #include <linux/slab.h> | |
20 | #include <linux/delay.h> | |
21 | #include <linux/bitmap.h> | |
22 | #include <linux/err.h> | |
23 | #include <linux/irq.h> | |
24 | ||
25 | #include <linux/iio/iio.h> | |
26 | #include <linux/iio/sysfs.h> | |
27 | #include <linux/iio/trigger_consumer.h> | |
28 | #include <linux/iio/buffer.h> | |
29 | #include <linux/iio/triggered_buffer.h> | |
30 | ||
31 | #define ADJD_S311_DRV_NAME "adjd_s311" | |
32 | ||
33 | #define ADJD_S311_CTRL 0x00 | |
34 | #define ADJD_S311_CONFIG 0x01 | |
35 | #define ADJD_S311_CAP_RED 0x06 | |
36 | #define ADJD_S311_CAP_GREEN 0x07 | |
37 | #define ADJD_S311_CAP_BLUE 0x08 | |
38 | #define ADJD_S311_CAP_CLEAR 0x09 | |
4c730292 PM |
39 | #define ADJD_S311_INT_RED 0x0a |
40 | #define ADJD_S311_INT_GREEN 0x0c | |
41 | #define ADJD_S311_INT_BLUE 0x0e | |
42 | #define ADJD_S311_INT_CLEAR 0x10 | |
43 | #define ADJD_S311_DATA_RED 0x40 | |
44 | #define ADJD_S311_DATA_GREEN 0x42 | |
45 | #define ADJD_S311_DATA_BLUE 0x44 | |
46 | #define ADJD_S311_DATA_CLEAR 0x46 | |
bbdb822c PM |
47 | #define ADJD_S311_OFFSET_RED 0x48 |
48 | #define ADJD_S311_OFFSET_GREEN 0x49 | |
49 | #define ADJD_S311_OFFSET_BLUE 0x4a | |
50 | #define ADJD_S311_OFFSET_CLEAR 0x4b | |
51 | ||
52 | #define ADJD_S311_CTRL_GOFS 0x02 | |
53 | #define ADJD_S311_CTRL_GSSR 0x01 | |
54 | #define ADJD_S311_CAP_MASK 0x0f | |
55 | #define ADJD_S311_INT_MASK 0x0fff | |
56 | #define ADJD_S311_DATA_MASK 0x03ff | |
57 | ||
58 | struct adjd_s311_data { | |
59 | struct i2c_client *client; | |
60 | u16 *buffer; | |
61 | }; | |
62 | ||
63 | enum adjd_s311_channel_idx { | |
64 | IDX_RED, IDX_GREEN, IDX_BLUE, IDX_CLEAR | |
65 | }; | |
66 | ||
4c730292 PM |
67 | #define ADJD_S311_DATA_REG(chan) (ADJD_S311_DATA_RED + (chan) * 2) |
68 | #define ADJD_S311_INT_REG(chan) (ADJD_S311_INT_RED + (chan) * 2) | |
bbdb822c PM |
69 | #define ADJD_S311_CAP_REG(chan) (ADJD_S311_CAP_RED + (chan)) |
70 | ||
71 | static int adjd_s311_req_data(struct iio_dev *indio_dev) | |
72 | { | |
73 | struct adjd_s311_data *data = iio_priv(indio_dev); | |
74 | int tries = 10; | |
75 | ||
76 | int ret = i2c_smbus_write_byte_data(data->client, ADJD_S311_CTRL, | |
77 | ADJD_S311_CTRL_GSSR); | |
78 | if (ret < 0) | |
79 | return ret; | |
80 | ||
81 | while (tries--) { | |
82 | ret = i2c_smbus_read_byte_data(data->client, ADJD_S311_CTRL); | |
83 | if (ret < 0) | |
84 | return ret; | |
85 | if (!(ret & ADJD_S311_CTRL_GSSR)) | |
86 | break; | |
87 | msleep(20); | |
88 | } | |
89 | ||
90 | if (tries < 0) { | |
91 | dev_err(&data->client->dev, | |
92 | "adjd_s311_req_data() failed, data not ready\n"); | |
93 | return -EIO; | |
94 | } | |
95 | ||
96 | return 0; | |
97 | } | |
98 | ||
99 | static int adjd_s311_read_data(struct iio_dev *indio_dev, u8 reg, int *val) | |
100 | { | |
101 | struct adjd_s311_data *data = iio_priv(indio_dev); | |
102 | ||
103 | int ret = adjd_s311_req_data(indio_dev); | |
104 | if (ret < 0) | |
105 | return ret; | |
106 | ||
107 | ret = i2c_smbus_read_word_data(data->client, reg); | |
108 | if (ret < 0) | |
109 | return ret; | |
110 | ||
111 | *val = ret & ADJD_S311_DATA_MASK; | |
112 | ||
113 | return 0; | |
114 | } | |
115 | ||
bbdb822c PM |
116 | static irqreturn_t adjd_s311_trigger_handler(int irq, void *p) |
117 | { | |
118 | struct iio_poll_func *pf = p; | |
119 | struct iio_dev *indio_dev = pf->indio_dev; | |
120 | struct adjd_s311_data *data = iio_priv(indio_dev); | |
bbdb822c | 121 | s64 time_ns = iio_get_time_ns(); |
bbdb822c PM |
122 | int i, j = 0; |
123 | ||
124 | int ret = adjd_s311_req_data(indio_dev); | |
125 | if (ret < 0) | |
126 | goto done; | |
127 | ||
128 | for_each_set_bit(i, indio_dev->active_scan_mask, | |
129 | indio_dev->masklength) { | |
130 | ret = i2c_smbus_read_word_data(data->client, | |
131 | ADJD_S311_DATA_REG(i)); | |
132 | if (ret < 0) | |
133 | goto done; | |
134 | ||
135 | data->buffer[j++] = ret & ADJD_S311_DATA_MASK; | |
bbdb822c PM |
136 | } |
137 | ||
a9f3acd0 | 138 | iio_push_to_buffers_with_timestamp(indio_dev, data->buffer, time_ns); |
bbdb822c PM |
139 | |
140 | done: | |
141 | iio_trigger_notify_done(indio_dev->trig); | |
142 | ||
143 | return IRQ_HANDLED; | |
144 | } | |
145 | ||
bbdb822c PM |
146 | #define ADJD_S311_CHANNEL(_color, _scan_idx) { \ |
147 | .type = IIO_INTENSITY, \ | |
148 | .modified = 1, \ | |
149 | .address = (IDX_##_color), \ | |
0112f521 | 150 | .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ |
caeac374 PM |
151 | BIT(IIO_CHAN_INFO_HARDWAREGAIN) | \ |
152 | BIT(IIO_CHAN_INFO_INT_TIME), \ | |
bbdb822c PM |
153 | .channel2 = (IIO_MOD_LIGHT_##_color), \ |
154 | .scan_index = (_scan_idx), \ | |
80ac4b8a JC |
155 | .scan_type = { \ |
156 | .sign = 'u', \ | |
157 | .realbits = 10, \ | |
158 | .storagebits = 16, \ | |
159 | .endianness = IIO_CPU, \ | |
160 | }, \ | |
bbdb822c PM |
161 | } |
162 | ||
163 | static const struct iio_chan_spec adjd_s311_channels[] = { | |
164 | ADJD_S311_CHANNEL(RED, 0), | |
165 | ADJD_S311_CHANNEL(GREEN, 1), | |
166 | ADJD_S311_CHANNEL(BLUE, 2), | |
167 | ADJD_S311_CHANNEL(CLEAR, 3), | |
168 | IIO_CHAN_SOFT_TIMESTAMP(4), | |
169 | }; | |
170 | ||
171 | static int adjd_s311_read_raw(struct iio_dev *indio_dev, | |
172 | struct iio_chan_spec const *chan, | |
173 | int *val, int *val2, long mask) | |
174 | { | |
175 | struct adjd_s311_data *data = iio_priv(indio_dev); | |
176 | int ret; | |
177 | ||
178 | switch (mask) { | |
179 | case IIO_CHAN_INFO_RAW: | |
71f42642 PM |
180 | ret = adjd_s311_read_data(indio_dev, |
181 | ADJD_S311_DATA_REG(chan->address), val); | |
bbdb822c PM |
182 | if (ret < 0) |
183 | return ret; | |
184 | return IIO_VAL_INT; | |
185 | case IIO_CHAN_INFO_HARDWAREGAIN: | |
186 | ret = i2c_smbus_read_byte_data(data->client, | |
187 | ADJD_S311_CAP_REG(chan->address)); | |
188 | if (ret < 0) | |
189 | return ret; | |
190 | *val = ret & ADJD_S311_CAP_MASK; | |
191 | return IIO_VAL_INT; | |
caeac374 PM |
192 | case IIO_CHAN_INFO_INT_TIME: |
193 | ret = i2c_smbus_read_word_data(data->client, | |
194 | ADJD_S311_INT_REG(chan->address)); | |
195 | if (ret < 0) | |
196 | return ret; | |
197 | *val = 0; | |
198 | /* | |
199 | * not documented, based on measurement: | |
200 | * 4095 LSBs correspond to roughly 4 ms | |
201 | */ | |
202 | *val2 = ret & ADJD_S311_INT_MASK; | |
203 | return IIO_VAL_INT_PLUS_MICRO; | |
bbdb822c PM |
204 | } |
205 | return -EINVAL; | |
206 | } | |
207 | ||
208 | static int adjd_s311_write_raw(struct iio_dev *indio_dev, | |
209 | struct iio_chan_spec const *chan, | |
210 | int val, int val2, long mask) | |
211 | { | |
212 | struct adjd_s311_data *data = iio_priv(indio_dev); | |
bbdb822c PM |
213 | |
214 | switch (mask) { | |
215 | case IIO_CHAN_INFO_HARDWAREGAIN: | |
216 | if (val < 0 || val > ADJD_S311_CAP_MASK) | |
217 | return -EINVAL; | |
218 | ||
caeac374 | 219 | return i2c_smbus_write_byte_data(data->client, |
bbdb822c | 220 | ADJD_S311_CAP_REG(chan->address), val); |
caeac374 PM |
221 | case IIO_CHAN_INFO_INT_TIME: |
222 | if (val != 0 || val2 < 0 || val2 > ADJD_S311_INT_MASK) | |
223 | return -EINVAL; | |
224 | ||
225 | return i2c_smbus_write_word_data(data->client, | |
226 | ADJD_S311_INT_REG(chan->address), val2); | |
bbdb822c PM |
227 | } |
228 | return -EINVAL; | |
229 | } | |
230 | ||
231 | static int adjd_s311_update_scan_mode(struct iio_dev *indio_dev, | |
232 | const unsigned long *scan_mask) | |
233 | { | |
234 | struct adjd_s311_data *data = iio_priv(indio_dev); | |
1c795ebd AK |
235 | |
236 | kfree(data->buffer); | |
237 | data->buffer = kmalloc(indio_dev->scan_bytes, GFP_KERNEL); | |
238 | if (data->buffer == NULL) | |
bbdb822c PM |
239 | return -ENOMEM; |
240 | ||
241 | return 0; | |
242 | } | |
243 | ||
244 | static const struct iio_info adjd_s311_info = { | |
245 | .read_raw = adjd_s311_read_raw, | |
246 | .write_raw = adjd_s311_write_raw, | |
247 | .update_scan_mode = adjd_s311_update_scan_mode, | |
248 | .driver_module = THIS_MODULE, | |
249 | }; | |
250 | ||
fc52692c GKH |
251 | static int adjd_s311_probe(struct i2c_client *client, |
252 | const struct i2c_device_id *id) | |
bbdb822c PM |
253 | { |
254 | struct adjd_s311_data *data; | |
255 | struct iio_dev *indio_dev; | |
256 | int err; | |
257 | ||
ccf12c33 PM |
258 | indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); |
259 | if (indio_dev == NULL) | |
260 | return -ENOMEM; | |
261 | ||
bbdb822c PM |
262 | data = iio_priv(indio_dev); |
263 | i2c_set_clientdata(client, indio_dev); | |
264 | data->client = client; | |
265 | ||
266 | indio_dev->dev.parent = &client->dev; | |
267 | indio_dev->info = &adjd_s311_info; | |
268 | indio_dev->name = ADJD_S311_DRV_NAME; | |
269 | indio_dev->channels = adjd_s311_channels; | |
270 | indio_dev->num_channels = ARRAY_SIZE(adjd_s311_channels); | |
271 | indio_dev->modes = INDIO_DIRECT_MODE; | |
272 | ||
273 | err = iio_triggered_buffer_setup(indio_dev, NULL, | |
274 | adjd_s311_trigger_handler, NULL); | |
275 | if (err < 0) | |
ccf12c33 | 276 | return err; |
bbdb822c PM |
277 | |
278 | err = iio_device_register(indio_dev); | |
279 | if (err) | |
280 | goto exit_unreg_buffer; | |
281 | ||
282 | dev_info(&client->dev, "ADJD-S311 color sensor registered\n"); | |
283 | ||
284 | return 0; | |
285 | ||
286 | exit_unreg_buffer: | |
287 | iio_triggered_buffer_cleanup(indio_dev); | |
bbdb822c PM |
288 | return err; |
289 | } | |
290 | ||
fc52692c | 291 | static int adjd_s311_remove(struct i2c_client *client) |
bbdb822c PM |
292 | { |
293 | struct iio_dev *indio_dev = i2c_get_clientdata(client); | |
294 | struct adjd_s311_data *data = iio_priv(indio_dev); | |
295 | ||
296 | iio_device_unregister(indio_dev); | |
297 | iio_triggered_buffer_cleanup(indio_dev); | |
298 | kfree(data->buffer); | |
bbdb822c PM |
299 | |
300 | return 0; | |
301 | } | |
302 | ||
303 | static const struct i2c_device_id adjd_s311_id[] = { | |
304 | { "adjd_s311", 0 }, | |
305 | { } | |
306 | }; | |
307 | MODULE_DEVICE_TABLE(i2c, adjd_s311_id); | |
308 | ||
309 | static struct i2c_driver adjd_s311_driver = { | |
310 | .driver = { | |
311 | .name = ADJD_S311_DRV_NAME, | |
312 | }, | |
313 | .probe = adjd_s311_probe, | |
fc52692c | 314 | .remove = adjd_s311_remove, |
bbdb822c PM |
315 | .id_table = adjd_s311_id, |
316 | }; | |
317 | module_i2c_driver(adjd_s311_driver); | |
318 | ||
319 | MODULE_AUTHOR("Peter Meerwald <pmeerw@pmeerw.net>"); | |
320 | MODULE_DESCRIPTION("ADJD-S311 color sensor"); | |
321 | MODULE_LICENSE("GPL"); |