1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * adjd_s311.c - Support for ADJD-S311-CR999 digital color sensor |
4 | * |
5 | * Copyright (C) 2012 Peter Meerwald <pmeerw@pmeerw.net> |
6 | * |
7 | * driver for ADJD-S311-CR999 digital color sensor (10-bit channels for |
8 | * red, green, blue, clear); 7-bit I2C slave address 0x74 |
9 | * |
10 | * limitations: no calibration, no offset mode, no sleep mode |
11 | */ |
12 | |
13 | #include <linux/module.h> |
14 | #include <linux/interrupt.h> |
15 | #include <linux/i2c.h> |
16 | #include <linux/slab.h> |
17 | #include <linux/delay.h> |
18 | #include <linux/bitmap.h> |
19 | #include <linux/err.h> |
20 | #include <linux/irq.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 ADJD_S311_DRV_NAME "adjd_s311" |
29 | |
30 | #define ADJD_S311_CTRL 0x00 |
31 | #define ADJD_S311_CONFIG 0x01 |
32 | #define ADJD_S311_CAP_RED 0x06 |
33 | #define ADJD_S311_CAP_GREEN 0x07 |
34 | #define ADJD_S311_CAP_BLUE 0x08 |
35 | #define ADJD_S311_CAP_CLEAR 0x09 |
36 | #define ADJD_S311_INT_RED 0x0a |
37 | #define ADJD_S311_INT_GREEN 0x0c |
38 | #define ADJD_S311_INT_BLUE 0x0e |
39 | #define ADJD_S311_INT_CLEAR 0x10 |
40 | #define ADJD_S311_DATA_RED 0x40 |
41 | #define ADJD_S311_DATA_GREEN 0x42 |
42 | #define ADJD_S311_DATA_BLUE 0x44 |
43 | #define ADJD_S311_DATA_CLEAR 0x46 |
44 | #define ADJD_S311_OFFSET_RED 0x48 |
45 | #define ADJD_S311_OFFSET_GREEN 0x49 |
46 | #define ADJD_S311_OFFSET_BLUE 0x4a |
47 | #define ADJD_S311_OFFSET_CLEAR 0x4b |
48 | |
49 | #define ADJD_S311_CTRL_GOFS 0x02 |
50 | #define ADJD_S311_CTRL_GSSR 0x01 |
51 | #define ADJD_S311_CAP_MASK 0x0f |
52 | #define ADJD_S311_INT_MASK 0x0fff |
53 | #define ADJD_S311_DATA_MASK 0x03ff |
54 | |
55 | struct adjd_s311_data { |
56 | struct i2c_client *client; |
57 | struct { |
58 | s16 chans[4]; |
59 | s64 ts __aligned(8); |
60 | } scan; |
61 | }; |
62 | |
63 | enum adjd_s311_channel_idx { |
64 | IDX_RED, IDX_GREEN, IDX_BLUE, IDX_CLEAR |
65 | }; |
66 | |
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) |
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(client: 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(client: data->client, ADJD_S311_CTRL); |
83 | if (ret < 0) |
84 | return ret; |
85 | if (!(ret & ADJD_S311_CTRL_GSSR)) |
86 | break; |
87 | msleep(msecs: 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(client: data->client, command: reg); |
108 | if (ret < 0) |
109 | return ret; |
110 | |
111 | *val = ret & ADJD_S311_DATA_MASK; |
112 | |
113 | return 0; |
114 | } |
115 | |
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); |
121 | s64 time_ns = iio_get_time_ns(indio_dev); |
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(client: data->client, |
131 | ADJD_S311_DATA_REG(i)); |
132 | if (ret < 0) |
133 | goto done; |
134 | |
135 | data->scan.chans[j++] = ret & ADJD_S311_DATA_MASK; |
136 | } |
137 | |
138 | iio_push_to_buffers_with_timestamp(indio_dev, data: &data->scan, timestamp: time_ns); |
139 | |
140 | done: |
141 | iio_trigger_notify_done(trig: indio_dev->trig); |
142 | |
143 | return IRQ_HANDLED; |
144 | } |
145 | |
146 | #define ADJD_S311_CHANNEL(_color, _scan_idx) { \ |
147 | .type = IIO_INTENSITY, \ |
148 | .modified = 1, \ |
149 | .address = (IDX_##_color), \ |
150 | .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ |
151 | BIT(IIO_CHAN_INFO_HARDWAREGAIN) | \ |
152 | BIT(IIO_CHAN_INFO_INT_TIME), \ |
153 | .channel2 = (IIO_MOD_LIGHT_##_color), \ |
154 | .scan_index = (_scan_idx), \ |
155 | .scan_type = { \ |
156 | .sign = 'u', \ |
157 | .realbits = 10, \ |
158 | .storagebits = 16, \ |
159 | .endianness = IIO_CPU, \ |
160 | }, \ |
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: |
180 | ret = adjd_s311_read_data(indio_dev, |
181 | ADJD_S311_DATA_REG(chan->address), val); |
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(client: 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; |
192 | case IIO_CHAN_INFO_INT_TIME: |
193 | ret = i2c_smbus_read_word_data(client: 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; |
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); |
213 | |
214 | switch (mask) { |
215 | case IIO_CHAN_INFO_HARDWAREGAIN: |
216 | if (val < 0 || val > ADJD_S311_CAP_MASK) |
217 | return -EINVAL; |
218 | |
219 | return i2c_smbus_write_byte_data(client: data->client, |
220 | ADJD_S311_CAP_REG(chan->address), value: val); |
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(client: data->client, |
226 | ADJD_S311_INT_REG(chan->address), value: val2); |
227 | } |
228 | return -EINVAL; |
229 | } |
230 | |
231 | static const struct iio_info adjd_s311_info = { |
232 | .read_raw = adjd_s311_read_raw, |
233 | .write_raw = adjd_s311_write_raw, |
234 | }; |
235 | |
236 | static int adjd_s311_probe(struct i2c_client *client) |
237 | { |
238 | struct adjd_s311_data *data; |
239 | struct iio_dev *indio_dev; |
240 | int err; |
241 | |
242 | indio_dev = devm_iio_device_alloc(parent: &client->dev, sizeof_priv: sizeof(*data)); |
243 | if (indio_dev == NULL) |
244 | return -ENOMEM; |
245 | |
246 | data = iio_priv(indio_dev); |
247 | data->client = client; |
248 | |
249 | indio_dev->info = &adjd_s311_info; |
250 | indio_dev->name = ADJD_S311_DRV_NAME; |
251 | indio_dev->channels = adjd_s311_channels; |
252 | indio_dev->num_channels = ARRAY_SIZE(adjd_s311_channels); |
253 | indio_dev->modes = INDIO_DIRECT_MODE; |
254 | |
255 | err = devm_iio_triggered_buffer_setup(&client->dev, indio_dev, NULL, |
256 | adjd_s311_trigger_handler, NULL); |
257 | if (err < 0) |
258 | return err; |
259 | |
260 | return devm_iio_device_register(&client->dev, indio_dev); |
261 | } |
262 | |
263 | static const struct i2c_device_id adjd_s311_id[] = { |
264 | { "adjd_s311" , 0 }, |
265 | { } |
266 | }; |
267 | MODULE_DEVICE_TABLE(i2c, adjd_s311_id); |
268 | |
269 | static struct i2c_driver adjd_s311_driver = { |
270 | .driver = { |
271 | .name = ADJD_S311_DRV_NAME, |
272 | }, |
273 | .probe = adjd_s311_probe, |
274 | .id_table = adjd_s311_id, |
275 | }; |
276 | module_i2c_driver(adjd_s311_driver); |
277 | |
278 | MODULE_AUTHOR("Peter Meerwald <pmeerw@pmeerw.net>" ); |
279 | MODULE_DESCRIPTION("ADJD-S311 color sensor" ); |
280 | MODULE_LICENSE("GPL" ); |
281 | |