1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * isl29125.c - Support for Intersil ISL29125 RGB light sensor |
4 | * |
5 | * Copyright (c) 2014 Peter Meerwald <pmeerw@pmeerw.net> |
6 | * |
7 | * RGB light sensor with 16-bit channels for red, green, blue); |
8 | * 7-bit I2C slave address 0x44 |
9 | * |
10 | * TODO: interrupt support, IR compensation, thresholds, 12bit |
11 | */ |
12 | |
13 | #include <linux/module.h> |
14 | #include <linux/i2c.h> |
15 | #include <linux/delay.h> |
16 | #include <linux/pm.h> |
17 | |
18 | #include <linux/iio/iio.h> |
19 | #include <linux/iio/sysfs.h> |
20 | #include <linux/iio/trigger_consumer.h> |
21 | #include <linux/iio/buffer.h> |
22 | #include <linux/iio/triggered_buffer.h> |
23 | |
24 | #define ISL29125_DRV_NAME "isl29125" |
25 | |
26 | #define ISL29125_DEVICE_ID 0x00 |
27 | #define ISL29125_CONF1 0x01 |
28 | #define ISL29125_CONF2 0x02 |
29 | #define ISL29125_CONF3 0x03 |
30 | #define ISL29125_STATUS 0x08 |
31 | #define ISL29125_GREEN_DATA 0x09 |
32 | #define ISL29125_RED_DATA 0x0b |
33 | #define ISL29125_BLUE_DATA 0x0d |
34 | |
35 | #define ISL29125_ID 0x7d |
36 | |
37 | #define ISL29125_MODE_MASK GENMASK(2, 0) |
38 | #define ISL29125_MODE_PD 0x0 |
39 | #define ISL29125_MODE_G 0x1 |
40 | #define ISL29125_MODE_R 0x2 |
41 | #define ISL29125_MODE_B 0x3 |
42 | #define ISL29125_MODE_RGB 0x5 |
43 | |
44 | #define ISL29125_SENSING_RANGE_0 5722 /* 375 lux full range */ |
45 | #define ISL29125_SENSING_RANGE_1 152590 /* 10k lux full range */ |
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 | u8 conf1; |
54 | /* Ensure timestamp is naturally aligned */ |
55 | struct { |
56 | u16 chans[3]; |
57 | s64 timestamp __aligned(8); |
58 | } scan; |
59 | }; |
60 | |
61 | #define ISL29125_CHANNEL(_color, _si) { \ |
62 | .type = IIO_INTENSITY, \ |
63 | .modified = 1, \ |
64 | .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ |
65 | .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ |
66 | .channel2 = IIO_MOD_LIGHT_##_color, \ |
67 | .scan_index = _si, \ |
68 | .scan_type = { \ |
69 | .sign = 'u', \ |
70 | .realbits = 16, \ |
71 | .storagebits = 16, \ |
72 | .endianness = IIO_CPU, \ |
73 | }, \ |
74 | } |
75 | |
76 | static const struct iio_chan_spec isl29125_channels[] = { |
77 | ISL29125_CHANNEL(GREEN, 0), |
78 | ISL29125_CHANNEL(RED, 1), |
79 | ISL29125_CHANNEL(BLUE, 2), |
80 | IIO_CHAN_SOFT_TIMESTAMP(3), |
81 | }; |
82 | |
83 | static const struct { |
84 | u8 mode, data; |
85 | } isl29125_regs[] = { |
86 | {ISL29125_MODE_G, ISL29125_GREEN_DATA}, |
87 | {ISL29125_MODE_R, ISL29125_RED_DATA}, |
88 | {ISL29125_MODE_B, ISL29125_BLUE_DATA}, |
89 | }; |
90 | |
91 | static int isl29125_read_data(struct isl29125_data *data, int si) |
92 | { |
93 | int tries = 5; |
94 | int ret; |
95 | |
96 | ret = i2c_smbus_write_byte_data(client: data->client, ISL29125_CONF1, |
97 | value: data->conf1 | isl29125_regs[si].mode); |
98 | if (ret < 0) |
99 | return ret; |
100 | |
101 | msleep(msecs: 101); |
102 | |
103 | while (tries--) { |
104 | ret = i2c_smbus_read_byte_data(client: data->client, ISL29125_STATUS); |
105 | if (ret < 0) |
106 | goto fail; |
107 | if (ret & ISL29125_STATUS_CONV) |
108 | break; |
109 | msleep(msecs: 20); |
110 | } |
111 | |
112 | if (tries < 0) { |
113 | dev_err(&data->client->dev, "data not ready\n" ); |
114 | ret = -EIO; |
115 | goto fail; |
116 | } |
117 | |
118 | ret = i2c_smbus_read_word_data(client: data->client, command: isl29125_regs[si].data); |
119 | |
120 | fail: |
121 | i2c_smbus_write_byte_data(client: data->client, ISL29125_CONF1, value: data->conf1); |
122 | return ret; |
123 | } |
124 | |
125 | static int isl29125_read_raw(struct iio_dev *indio_dev, |
126 | struct iio_chan_spec const *chan, |
127 | int *val, int *val2, long mask) |
128 | { |
129 | struct isl29125_data *data = iio_priv(indio_dev); |
130 | int ret; |
131 | |
132 | switch (mask) { |
133 | case IIO_CHAN_INFO_RAW: |
134 | ret = iio_device_claim_direct_mode(indio_dev); |
135 | if (ret) |
136 | return ret; |
137 | ret = isl29125_read_data(data, si: chan->scan_index); |
138 | iio_device_release_direct_mode(indio_dev); |
139 | if (ret < 0) |
140 | return ret; |
141 | *val = ret; |
142 | return IIO_VAL_INT; |
143 | case IIO_CHAN_INFO_SCALE: |
144 | *val = 0; |
145 | if (data->conf1 & ISL29125_MODE_RANGE) |
146 | *val2 = ISL29125_SENSING_RANGE_1; /*10k lux full range*/ |
147 | else |
148 | *val2 = ISL29125_SENSING_RANGE_0; /*375 lux full range*/ |
149 | return IIO_VAL_INT_PLUS_MICRO; |
150 | } |
151 | return -EINVAL; |
152 | } |
153 | |
154 | static int isl29125_write_raw(struct iio_dev *indio_dev, |
155 | struct iio_chan_spec const *chan, |
156 | int val, int val2, long mask) |
157 | { |
158 | struct isl29125_data *data = iio_priv(indio_dev); |
159 | |
160 | switch (mask) { |
161 | case IIO_CHAN_INFO_SCALE: |
162 | if (val != 0) |
163 | return -EINVAL; |
164 | if (val2 == ISL29125_SENSING_RANGE_1) |
165 | data->conf1 |= ISL29125_MODE_RANGE; |
166 | else if (val2 == ISL29125_SENSING_RANGE_0) |
167 | data->conf1 &= ~ISL29125_MODE_RANGE; |
168 | else |
169 | return -EINVAL; |
170 | return i2c_smbus_write_byte_data(client: data->client, ISL29125_CONF1, |
171 | value: data->conf1); |
172 | default: |
173 | return -EINVAL; |
174 | } |
175 | } |
176 | |
177 | static irqreturn_t isl29125_trigger_handler(int irq, void *p) |
178 | { |
179 | struct iio_poll_func *pf = p; |
180 | struct iio_dev *indio_dev = pf->indio_dev; |
181 | struct isl29125_data *data = iio_priv(indio_dev); |
182 | int i, j = 0; |
183 | |
184 | for_each_set_bit(i, indio_dev->active_scan_mask, |
185 | indio_dev->masklength) { |
186 | int ret = i2c_smbus_read_word_data(client: data->client, |
187 | command: isl29125_regs[i].data); |
188 | if (ret < 0) |
189 | goto done; |
190 | |
191 | data->scan.chans[j++] = ret; |
192 | } |
193 | |
194 | iio_push_to_buffers_with_timestamp(indio_dev, data: &data->scan, |
195 | timestamp: iio_get_time_ns(indio_dev)); |
196 | |
197 | done: |
198 | iio_trigger_notify_done(trig: indio_dev->trig); |
199 | |
200 | return IRQ_HANDLED; |
201 | } |
202 | |
203 | static IIO_CONST_ATTR(scale_available, "0.005722 0.152590" ); |
204 | |
205 | static struct attribute *isl29125_attributes[] = { |
206 | &iio_const_attr_scale_available.dev_attr.attr, |
207 | NULL |
208 | }; |
209 | |
210 | static const struct attribute_group isl29125_attribute_group = { |
211 | .attrs = isl29125_attributes, |
212 | }; |
213 | |
214 | static const struct iio_info isl29125_info = { |
215 | .read_raw = isl29125_read_raw, |
216 | .write_raw = isl29125_write_raw, |
217 | .attrs = &isl29125_attribute_group, |
218 | }; |
219 | |
220 | static int isl29125_buffer_postenable(struct iio_dev *indio_dev) |
221 | { |
222 | struct isl29125_data *data = iio_priv(indio_dev); |
223 | |
224 | data->conf1 |= ISL29125_MODE_RGB; |
225 | return i2c_smbus_write_byte_data(client: data->client, ISL29125_CONF1, |
226 | value: data->conf1); |
227 | } |
228 | |
229 | static int isl29125_buffer_predisable(struct iio_dev *indio_dev) |
230 | { |
231 | struct isl29125_data *data = iio_priv(indio_dev); |
232 | |
233 | data->conf1 &= ~ISL29125_MODE_MASK; |
234 | data->conf1 |= ISL29125_MODE_PD; |
235 | return i2c_smbus_write_byte_data(client: data->client, ISL29125_CONF1, |
236 | value: data->conf1); |
237 | } |
238 | |
239 | static const struct iio_buffer_setup_ops isl29125_buffer_setup_ops = { |
240 | .postenable = isl29125_buffer_postenable, |
241 | .predisable = isl29125_buffer_predisable, |
242 | }; |
243 | |
244 | static int isl29125_probe(struct i2c_client *client) |
245 | { |
246 | struct isl29125_data *data; |
247 | struct iio_dev *indio_dev; |
248 | int ret; |
249 | |
250 | indio_dev = devm_iio_device_alloc(parent: &client->dev, sizeof_priv: sizeof(*data)); |
251 | if (indio_dev == NULL) |
252 | return -ENOMEM; |
253 | |
254 | data = iio_priv(indio_dev); |
255 | i2c_set_clientdata(client, data: indio_dev); |
256 | data->client = client; |
257 | |
258 | indio_dev->info = &isl29125_info; |
259 | indio_dev->name = ISL29125_DRV_NAME; |
260 | indio_dev->channels = isl29125_channels; |
261 | indio_dev->num_channels = ARRAY_SIZE(isl29125_channels); |
262 | indio_dev->modes = INDIO_DIRECT_MODE; |
263 | |
264 | ret = i2c_smbus_read_byte_data(client: data->client, ISL29125_DEVICE_ID); |
265 | if (ret < 0) |
266 | return ret; |
267 | if (ret != ISL29125_ID) |
268 | return -ENODEV; |
269 | |
270 | data->conf1 = ISL29125_MODE_PD | ISL29125_MODE_RANGE; |
271 | ret = i2c_smbus_write_byte_data(client: data->client, ISL29125_CONF1, |
272 | value: data->conf1); |
273 | if (ret < 0) |
274 | return ret; |
275 | |
276 | ret = i2c_smbus_write_byte_data(client: data->client, ISL29125_STATUS, value: 0); |
277 | if (ret < 0) |
278 | return ret; |
279 | |
280 | ret = iio_triggered_buffer_setup(indio_dev, NULL, |
281 | isl29125_trigger_handler, &isl29125_buffer_setup_ops); |
282 | if (ret < 0) |
283 | return ret; |
284 | |
285 | ret = iio_device_register(indio_dev); |
286 | if (ret < 0) |
287 | goto buffer_cleanup; |
288 | |
289 | return 0; |
290 | |
291 | buffer_cleanup: |
292 | iio_triggered_buffer_cleanup(indio_dev); |
293 | return ret; |
294 | } |
295 | |
296 | static int isl29125_powerdown(struct isl29125_data *data) |
297 | { |
298 | return i2c_smbus_write_byte_data(client: data->client, ISL29125_CONF1, |
299 | value: (data->conf1 & ~ISL29125_MODE_MASK) | ISL29125_MODE_PD); |
300 | } |
301 | |
302 | static void isl29125_remove(struct i2c_client *client) |
303 | { |
304 | struct iio_dev *indio_dev = i2c_get_clientdata(client); |
305 | |
306 | iio_device_unregister(indio_dev); |
307 | iio_triggered_buffer_cleanup(indio_dev); |
308 | isl29125_powerdown(data: iio_priv(indio_dev)); |
309 | } |
310 | |
311 | static int isl29125_suspend(struct device *dev) |
312 | { |
313 | struct isl29125_data *data = iio_priv(indio_dev: i2c_get_clientdata( |
314 | to_i2c_client(dev))); |
315 | return isl29125_powerdown(data); |
316 | } |
317 | |
318 | static int isl29125_resume(struct device *dev) |
319 | { |
320 | struct isl29125_data *data = iio_priv(indio_dev: i2c_get_clientdata( |
321 | to_i2c_client(dev))); |
322 | return i2c_smbus_write_byte_data(client: data->client, ISL29125_CONF1, |
323 | value: data->conf1); |
324 | } |
325 | |
326 | static DEFINE_SIMPLE_DEV_PM_OPS(isl29125_pm_ops, isl29125_suspend, |
327 | isl29125_resume); |
328 | |
329 | static const struct i2c_device_id isl29125_id[] = { |
330 | { "isl29125" , 0 }, |
331 | { } |
332 | }; |
333 | MODULE_DEVICE_TABLE(i2c, isl29125_id); |
334 | |
335 | static struct i2c_driver isl29125_driver = { |
336 | .driver = { |
337 | .name = ISL29125_DRV_NAME, |
338 | .pm = pm_sleep_ptr(&isl29125_pm_ops), |
339 | }, |
340 | .probe = isl29125_probe, |
341 | .remove = isl29125_remove, |
342 | .id_table = isl29125_id, |
343 | }; |
344 | module_i2c_driver(isl29125_driver); |
345 | |
346 | MODULE_AUTHOR("Peter Meerwald <pmeerw@pmeerw.net>" ); |
347 | MODULE_DESCRIPTION("ISL29125 RGB light sensor driver" ); |
348 | MODULE_LICENSE("GPL" ); |
349 | |