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