1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * CM3232 Ambient Light Sensor |
4 | * |
5 | * Copyright (C) 2014-2015 Capella Microsystems Inc. |
6 | * Author: Kevin Tsai <ktsai@capellamicro.com> |
7 | * |
8 | * IIO driver for CM3232 (7-bit I2C slave address 0x10). |
9 | */ |
10 | |
11 | #include <linux/i2c.h> |
12 | #include <linux/module.h> |
13 | #include <linux/mod_devicetable.h> |
14 | #include <linux/iio/iio.h> |
15 | #include <linux/iio/sysfs.h> |
16 | #include <linux/init.h> |
17 | |
18 | /* Registers Address */ |
19 | #define CM3232_REG_ADDR_CMD 0x00 |
20 | #define CM3232_REG_ADDR_ALS 0x50 |
21 | #define CM3232_REG_ADDR_ID 0x53 |
22 | |
23 | #define CM3232_CMD_ALS_DISABLE BIT(0) |
24 | |
25 | #define CM3232_CMD_ALS_IT_SHIFT 2 |
26 | #define CM3232_CMD_ALS_IT_MASK (BIT(2) | BIT(3) | BIT(4)) |
27 | #define CM3232_CMD_ALS_IT_DEFAULT (0x01 << CM3232_CMD_ALS_IT_SHIFT) |
28 | |
29 | #define CM3232_CMD_ALS_RESET BIT(6) |
30 | |
31 | #define CM3232_CMD_DEFAULT CM3232_CMD_ALS_IT_DEFAULT |
32 | |
33 | #define CM3232_HW_ID 0x32 |
34 | #define CM3232_CALIBSCALE_DEFAULT 100000 |
35 | #define CM3232_CALIBSCALE_RESOLUTION 100000 |
36 | #define CM3232_MLUX_PER_LUX 1000 |
37 | |
38 | #define CM3232_MLUX_PER_BIT_DEFAULT 64 |
39 | #define CM3232_MLUX_PER_BIT_BASE_IT 100000 |
40 | |
41 | static const struct { |
42 | int val; |
43 | int val2; |
44 | u8 it; |
45 | } cm3232_als_it_scales[] = { |
46 | {0, 100000, 0}, /* 0.100000 */ |
47 | {0, 200000, 1}, /* 0.200000 */ |
48 | {0, 400000, 2}, /* 0.400000 */ |
49 | {0, 800000, 3}, /* 0.800000 */ |
50 | {1, 600000, 4}, /* 1.600000 */ |
51 | {3, 200000, 5}, /* 3.200000 */ |
52 | }; |
53 | |
54 | struct cm3232_als_info { |
55 | u8 regs_cmd_default; |
56 | u8 hw_id; |
57 | int calibscale; |
58 | int mlux_per_bit; |
59 | int mlux_per_bit_base_it; |
60 | }; |
61 | |
62 | static struct cm3232_als_info cm3232_als_info_default = { |
63 | .regs_cmd_default = CM3232_CMD_DEFAULT, |
64 | .hw_id = CM3232_HW_ID, |
65 | .calibscale = CM3232_CALIBSCALE_DEFAULT, |
66 | .mlux_per_bit = CM3232_MLUX_PER_BIT_DEFAULT, |
67 | .mlux_per_bit_base_it = CM3232_MLUX_PER_BIT_BASE_IT, |
68 | }; |
69 | |
70 | struct cm3232_chip { |
71 | struct i2c_client *client; |
72 | struct cm3232_als_info *als_info; |
73 | u8 regs_cmd; |
74 | u16 regs_als; |
75 | }; |
76 | |
77 | /** |
78 | * cm3232_reg_init() - Initialize CM3232 |
79 | * @chip: pointer of struct cm3232_chip. |
80 | * |
81 | * Check and initialize CM3232 ambient light sensor. |
82 | * |
83 | * Return: 0 for success; otherwise for error code. |
84 | */ |
85 | static int cm3232_reg_init(struct cm3232_chip *chip) |
86 | { |
87 | struct i2c_client *client = chip->client; |
88 | s32 ret; |
89 | |
90 | chip->als_info = &cm3232_als_info_default; |
91 | |
92 | /* Identify device */ |
93 | ret = i2c_smbus_read_word_data(client, CM3232_REG_ADDR_ID); |
94 | if (ret < 0) { |
95 | dev_err(&chip->client->dev, "Error reading addr_id\n" ); |
96 | return ret; |
97 | } |
98 | |
99 | if ((ret & 0xFF) != chip->als_info->hw_id) |
100 | return -ENODEV; |
101 | |
102 | /* Disable and reset device */ |
103 | chip->regs_cmd = CM3232_CMD_ALS_DISABLE | CM3232_CMD_ALS_RESET; |
104 | ret = i2c_smbus_write_byte_data(client, CM3232_REG_ADDR_CMD, |
105 | value: chip->regs_cmd); |
106 | if (ret < 0) { |
107 | dev_err(&chip->client->dev, "Error writing reg_cmd\n" ); |
108 | return ret; |
109 | } |
110 | |
111 | /* Register default value */ |
112 | chip->regs_cmd = chip->als_info->regs_cmd_default; |
113 | |
114 | /* Configure register */ |
115 | ret = i2c_smbus_write_byte_data(client, CM3232_REG_ADDR_CMD, |
116 | value: chip->regs_cmd); |
117 | if (ret < 0) |
118 | dev_err(&chip->client->dev, "Error writing reg_cmd\n" ); |
119 | |
120 | return ret; |
121 | } |
122 | |
123 | /** |
124 | * cm3232_read_als_it() - Get sensor integration time |
125 | * @chip: pointer of struct cm3232_chip |
126 | * @val: pointer of int to load the integration (sec). |
127 | * @val2: pointer of int to load the integration time (microsecond). |
128 | * |
129 | * Report the current integration time. |
130 | * |
131 | * Return: IIO_VAL_INT_PLUS_MICRO for success, otherwise -EINVAL. |
132 | */ |
133 | static int cm3232_read_als_it(struct cm3232_chip *chip, int *val, int *val2) |
134 | { |
135 | u16 als_it; |
136 | int i; |
137 | |
138 | als_it = chip->regs_cmd; |
139 | als_it &= CM3232_CMD_ALS_IT_MASK; |
140 | als_it >>= CM3232_CMD_ALS_IT_SHIFT; |
141 | for (i = 0; i < ARRAY_SIZE(cm3232_als_it_scales); i++) { |
142 | if (als_it == cm3232_als_it_scales[i].it) { |
143 | *val = cm3232_als_it_scales[i].val; |
144 | *val2 = cm3232_als_it_scales[i].val2; |
145 | return IIO_VAL_INT_PLUS_MICRO; |
146 | } |
147 | } |
148 | |
149 | return -EINVAL; |
150 | } |
151 | |
152 | /** |
153 | * cm3232_write_als_it() - Write sensor integration time |
154 | * @chip: pointer of struct cm3232_chip. |
155 | * @val: integration time in second. |
156 | * @val2: integration time in microsecond. |
157 | * |
158 | * Convert integration time to sensor value. |
159 | * |
160 | * Return: i2c_smbus_write_byte_data command return value. |
161 | */ |
162 | static int cm3232_write_als_it(struct cm3232_chip *chip, int val, int val2) |
163 | { |
164 | struct i2c_client *client = chip->client; |
165 | u16 als_it, cmd; |
166 | int i; |
167 | s32 ret; |
168 | |
169 | for (i = 0; i < ARRAY_SIZE(cm3232_als_it_scales); i++) { |
170 | if (val == cm3232_als_it_scales[i].val && |
171 | val2 == cm3232_als_it_scales[i].val2) { |
172 | |
173 | als_it = cm3232_als_it_scales[i].it; |
174 | als_it <<= CM3232_CMD_ALS_IT_SHIFT; |
175 | |
176 | cmd = chip->regs_cmd & ~CM3232_CMD_ALS_IT_MASK; |
177 | cmd |= als_it; |
178 | ret = i2c_smbus_write_byte_data(client, |
179 | CM3232_REG_ADDR_CMD, |
180 | value: cmd); |
181 | if (ret < 0) |
182 | return ret; |
183 | chip->regs_cmd = cmd; |
184 | return 0; |
185 | } |
186 | } |
187 | return -EINVAL; |
188 | } |
189 | |
190 | /** |
191 | * cm3232_get_lux() - report current lux value |
192 | * @chip: pointer of struct cm3232_chip. |
193 | * |
194 | * Convert sensor data to lux. It depends on integration |
195 | * time and calibscale variable. |
196 | * |
197 | * Return: Zero or positive value is lux, otherwise error code. |
198 | */ |
199 | static int cm3232_get_lux(struct cm3232_chip *chip) |
200 | { |
201 | struct i2c_client *client = chip->client; |
202 | struct cm3232_als_info *als_info = chip->als_info; |
203 | int ret; |
204 | int val, val2; |
205 | int als_it; |
206 | u64 lux; |
207 | |
208 | /* Calculate mlux per bit based on als_it */ |
209 | ret = cm3232_read_als_it(chip, val: &val, val2: &val2); |
210 | if (ret < 0) |
211 | return -EINVAL; |
212 | als_it = val * 1000000 + val2; |
213 | lux = (__force u64)als_info->mlux_per_bit; |
214 | lux *= als_info->mlux_per_bit_base_it; |
215 | lux = div_u64(dividend: lux, divisor: als_it); |
216 | |
217 | ret = i2c_smbus_read_word_data(client, CM3232_REG_ADDR_ALS); |
218 | if (ret < 0) { |
219 | dev_err(&client->dev, "Error reading reg_addr_als\n" ); |
220 | return ret; |
221 | } |
222 | |
223 | chip->regs_als = (u16)ret; |
224 | lux *= chip->regs_als; |
225 | lux *= als_info->calibscale; |
226 | lux = div_u64(dividend: lux, CM3232_CALIBSCALE_RESOLUTION); |
227 | lux = div_u64(dividend: lux, CM3232_MLUX_PER_LUX); |
228 | |
229 | if (lux > 0xFFFF) |
230 | lux = 0xFFFF; |
231 | |
232 | return (int)lux; |
233 | } |
234 | |
235 | static int cm3232_read_raw(struct iio_dev *indio_dev, |
236 | struct iio_chan_spec const *chan, |
237 | int *val, int *val2, long mask) |
238 | { |
239 | struct cm3232_chip *chip = iio_priv(indio_dev); |
240 | struct cm3232_als_info *als_info = chip->als_info; |
241 | int ret; |
242 | |
243 | switch (mask) { |
244 | case IIO_CHAN_INFO_PROCESSED: |
245 | ret = cm3232_get_lux(chip); |
246 | if (ret < 0) |
247 | return ret; |
248 | *val = ret; |
249 | return IIO_VAL_INT; |
250 | case IIO_CHAN_INFO_CALIBSCALE: |
251 | *val = als_info->calibscale; |
252 | return IIO_VAL_INT; |
253 | case IIO_CHAN_INFO_INT_TIME: |
254 | return cm3232_read_als_it(chip, val, val2); |
255 | } |
256 | |
257 | return -EINVAL; |
258 | } |
259 | |
260 | static int cm3232_write_raw(struct iio_dev *indio_dev, |
261 | struct iio_chan_spec const *chan, |
262 | int val, int val2, long mask) |
263 | { |
264 | struct cm3232_chip *chip = iio_priv(indio_dev); |
265 | struct cm3232_als_info *als_info = chip->als_info; |
266 | |
267 | switch (mask) { |
268 | case IIO_CHAN_INFO_CALIBSCALE: |
269 | als_info->calibscale = val; |
270 | return 0; |
271 | case IIO_CHAN_INFO_INT_TIME: |
272 | return cm3232_write_als_it(chip, val, val2); |
273 | } |
274 | |
275 | return -EINVAL; |
276 | } |
277 | |
278 | /** |
279 | * cm3232_get_it_available() - Get available ALS IT value |
280 | * @dev: pointer of struct device. |
281 | * @attr: pointer of struct device_attribute. |
282 | * @buf: pointer of return string buffer. |
283 | * |
284 | * Display the available integration time in second. |
285 | * |
286 | * Return: string length. |
287 | */ |
288 | static ssize_t cm3232_get_it_available(struct device *dev, |
289 | struct device_attribute *attr, char *buf) |
290 | { |
291 | int i, len; |
292 | |
293 | for (i = 0, len = 0; i < ARRAY_SIZE(cm3232_als_it_scales); i++) |
294 | len += scnprintf(buf: buf + len, PAGE_SIZE - len, fmt: "%u.%06u " , |
295 | cm3232_als_it_scales[i].val, |
296 | cm3232_als_it_scales[i].val2); |
297 | return len + scnprintf(buf: buf + len, PAGE_SIZE - len, fmt: "\n" ); |
298 | } |
299 | |
300 | static const struct iio_chan_spec cm3232_channels[] = { |
301 | { |
302 | .type = IIO_LIGHT, |
303 | .info_mask_separate = |
304 | BIT(IIO_CHAN_INFO_PROCESSED) | |
305 | BIT(IIO_CHAN_INFO_CALIBSCALE) | |
306 | BIT(IIO_CHAN_INFO_INT_TIME), |
307 | } |
308 | }; |
309 | |
310 | static IIO_DEVICE_ATTR(in_illuminance_integration_time_available, |
311 | S_IRUGO, cm3232_get_it_available, NULL, 0); |
312 | |
313 | static struct attribute *cm3232_attributes[] = { |
314 | &iio_dev_attr_in_illuminance_integration_time_available.dev_attr.attr, |
315 | NULL, |
316 | }; |
317 | |
318 | static const struct attribute_group cm3232_attribute_group = { |
319 | .attrs = cm3232_attributes |
320 | }; |
321 | |
322 | static const struct iio_info cm3232_info = { |
323 | .read_raw = &cm3232_read_raw, |
324 | .write_raw = &cm3232_write_raw, |
325 | .attrs = &cm3232_attribute_group, |
326 | }; |
327 | |
328 | static int cm3232_probe(struct i2c_client *client) |
329 | { |
330 | const struct i2c_device_id *id = i2c_client_get_device_id(client); |
331 | struct cm3232_chip *chip; |
332 | struct iio_dev *indio_dev; |
333 | int ret; |
334 | |
335 | indio_dev = devm_iio_device_alloc(parent: &client->dev, sizeof_priv: sizeof(*chip)); |
336 | if (!indio_dev) |
337 | return -ENOMEM; |
338 | |
339 | chip = iio_priv(indio_dev); |
340 | i2c_set_clientdata(client, data: indio_dev); |
341 | chip->client = client; |
342 | |
343 | indio_dev->channels = cm3232_channels; |
344 | indio_dev->num_channels = ARRAY_SIZE(cm3232_channels); |
345 | indio_dev->info = &cm3232_info; |
346 | indio_dev->name = id->name; |
347 | indio_dev->modes = INDIO_DIRECT_MODE; |
348 | |
349 | ret = cm3232_reg_init(chip); |
350 | if (ret) { |
351 | dev_err(&client->dev, |
352 | "%s: register init failed\n" , |
353 | __func__); |
354 | return ret; |
355 | } |
356 | |
357 | return iio_device_register(indio_dev); |
358 | } |
359 | |
360 | static void cm3232_remove(struct i2c_client *client) |
361 | { |
362 | struct iio_dev *indio_dev = i2c_get_clientdata(client); |
363 | |
364 | i2c_smbus_write_byte_data(client, CM3232_REG_ADDR_CMD, |
365 | CM3232_CMD_ALS_DISABLE); |
366 | |
367 | iio_device_unregister(indio_dev); |
368 | } |
369 | |
370 | static const struct i2c_device_id cm3232_id[] = { |
371 | {"cm3232" , 0}, |
372 | {} |
373 | }; |
374 | |
375 | static int cm3232_suspend(struct device *dev) |
376 | { |
377 | struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); |
378 | struct cm3232_chip *chip = iio_priv(indio_dev); |
379 | struct i2c_client *client = chip->client; |
380 | int ret; |
381 | |
382 | chip->regs_cmd |= CM3232_CMD_ALS_DISABLE; |
383 | ret = i2c_smbus_write_byte_data(client, CM3232_REG_ADDR_CMD, |
384 | value: chip->regs_cmd); |
385 | |
386 | return ret; |
387 | } |
388 | |
389 | static int cm3232_resume(struct device *dev) |
390 | { |
391 | struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); |
392 | struct cm3232_chip *chip = iio_priv(indio_dev); |
393 | struct i2c_client *client = chip->client; |
394 | int ret; |
395 | |
396 | chip->regs_cmd &= ~CM3232_CMD_ALS_DISABLE; |
397 | ret = i2c_smbus_write_byte_data(client, CM3232_REG_ADDR_CMD, |
398 | value: chip->regs_cmd | CM3232_CMD_ALS_RESET); |
399 | |
400 | return ret; |
401 | } |
402 | |
403 | static DEFINE_SIMPLE_DEV_PM_OPS(cm3232_pm_ops, cm3232_suspend, cm3232_resume); |
404 | |
405 | MODULE_DEVICE_TABLE(i2c, cm3232_id); |
406 | |
407 | static const struct of_device_id cm3232_of_match[] = { |
408 | {.compatible = "capella,cm3232" }, |
409 | {} |
410 | }; |
411 | MODULE_DEVICE_TABLE(of, cm3232_of_match); |
412 | |
413 | static struct i2c_driver cm3232_driver = { |
414 | .driver = { |
415 | .name = "cm3232" , |
416 | .of_match_table = cm3232_of_match, |
417 | .pm = pm_sleep_ptr(&cm3232_pm_ops), |
418 | }, |
419 | .id_table = cm3232_id, |
420 | .probe = cm3232_probe, |
421 | .remove = cm3232_remove, |
422 | }; |
423 | |
424 | module_i2c_driver(cm3232_driver); |
425 | |
426 | MODULE_AUTHOR("Kevin Tsai <ktsai@capellamicro.com>" ); |
427 | MODULE_DESCRIPTION("CM3232 ambient light sensor driver" ); |
428 | MODULE_LICENSE("GPL" ); |
429 | |