1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * AL3010 - Dyna Image Ambient Light Sensor |
4 | * |
5 | * Copyright (c) 2014, Intel Corporation. |
6 | * Copyright (c) 2016, Dyna-Image Corp. |
7 | * Copyright (c) 2020, David Heidelberg, Michał Mirosław, Dmitry Osipenko |
8 | * |
9 | * IIO driver for AL3010 (7-bit I2C slave address 0x1C). |
10 | * |
11 | * TODO: interrupt support, thresholds |
12 | * When the driver will get support for interrupt handling, then interrupt |
13 | * will need to be disabled before turning sensor OFF in order to avoid |
14 | * potential races with the interrupt handling. |
15 | */ |
16 | |
17 | #include <linux/bitfield.h> |
18 | #include <linux/i2c.h> |
19 | #include <linux/module.h> |
20 | #include <linux/mod_devicetable.h> |
21 | |
22 | #include <linux/iio/iio.h> |
23 | #include <linux/iio/sysfs.h> |
24 | |
25 | #define AL3010_DRV_NAME "al3010" |
26 | |
27 | #define AL3010_REG_SYSTEM 0x00 |
28 | #define AL3010_REG_DATA_LOW 0x0c |
29 | #define AL3010_REG_CONFIG 0x10 |
30 | |
31 | #define AL3010_CONFIG_DISABLE 0x00 |
32 | #define AL3010_CONFIG_ENABLE 0x01 |
33 | |
34 | #define AL3010_GAIN_MASK GENMASK(6,4) |
35 | |
36 | #define AL3010_SCALE_AVAILABLE "1.1872 0.2968 0.0742 0.018" |
37 | |
38 | enum al3xxxx_range { |
39 | AL3XXX_RANGE_1, /* 77806 lx */ |
40 | AL3XXX_RANGE_2, /* 19542 lx */ |
41 | AL3XXX_RANGE_3, /* 4863 lx */ |
42 | AL3XXX_RANGE_4 /* 1216 lx */ |
43 | }; |
44 | |
45 | static const int al3010_scales[][2] = { |
46 | {0, 1187200}, {0, 296800}, {0, 74200}, {0, 18600} |
47 | }; |
48 | |
49 | struct al3010_data { |
50 | struct i2c_client *client; |
51 | }; |
52 | |
53 | static const struct iio_chan_spec al3010_channels[] = { |
54 | { |
55 | .type = IIO_LIGHT, |
56 | .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | |
57 | BIT(IIO_CHAN_INFO_SCALE), |
58 | } |
59 | }; |
60 | |
61 | static IIO_CONST_ATTR(in_illuminance_scale_available, AL3010_SCALE_AVAILABLE); |
62 | |
63 | static struct attribute *al3010_attributes[] = { |
64 | &iio_const_attr_in_illuminance_scale_available.dev_attr.attr, |
65 | NULL, |
66 | }; |
67 | |
68 | static const struct attribute_group al3010_attribute_group = { |
69 | .attrs = al3010_attributes, |
70 | }; |
71 | |
72 | static int al3010_set_pwr(struct i2c_client *client, bool pwr) |
73 | { |
74 | u8 val = pwr ? AL3010_CONFIG_ENABLE : AL3010_CONFIG_DISABLE; |
75 | return i2c_smbus_write_byte_data(client, AL3010_REG_SYSTEM, value: val); |
76 | } |
77 | |
78 | static void al3010_set_pwr_off(void *_data) |
79 | { |
80 | struct al3010_data *data = _data; |
81 | |
82 | al3010_set_pwr(client: data->client, pwr: false); |
83 | } |
84 | |
85 | static int al3010_init(struct al3010_data *data) |
86 | { |
87 | int ret; |
88 | |
89 | ret = al3010_set_pwr(client: data->client, pwr: true); |
90 | |
91 | if (ret < 0) |
92 | return ret; |
93 | |
94 | ret = i2c_smbus_write_byte_data(client: data->client, AL3010_REG_CONFIG, |
95 | FIELD_PREP(AL3010_GAIN_MASK, |
96 | AL3XXX_RANGE_3)); |
97 | if (ret < 0) |
98 | return ret; |
99 | |
100 | return 0; |
101 | } |
102 | |
103 | static int al3010_read_raw(struct iio_dev *indio_dev, |
104 | struct iio_chan_spec const *chan, int *val, |
105 | int *val2, long mask) |
106 | { |
107 | struct al3010_data *data = iio_priv(indio_dev); |
108 | int ret; |
109 | |
110 | switch (mask) { |
111 | case IIO_CHAN_INFO_RAW: |
112 | /* |
113 | * ALS ADC value is stored in two adjacent registers: |
114 | * - low byte of output is stored at AL3010_REG_DATA_LOW |
115 | * - high byte of output is stored at AL3010_REG_DATA_LOW + 1 |
116 | */ |
117 | ret = i2c_smbus_read_word_data(client: data->client, |
118 | AL3010_REG_DATA_LOW); |
119 | if (ret < 0) |
120 | return ret; |
121 | *val = ret; |
122 | return IIO_VAL_INT; |
123 | case IIO_CHAN_INFO_SCALE: |
124 | ret = i2c_smbus_read_byte_data(client: data->client, |
125 | AL3010_REG_CONFIG); |
126 | if (ret < 0) |
127 | return ret; |
128 | |
129 | ret = FIELD_GET(AL3010_GAIN_MASK, ret); |
130 | *val = al3010_scales[ret][0]; |
131 | *val2 = al3010_scales[ret][1]; |
132 | |
133 | return IIO_VAL_INT_PLUS_MICRO; |
134 | } |
135 | return -EINVAL; |
136 | } |
137 | |
138 | static int al3010_write_raw(struct iio_dev *indio_dev, |
139 | struct iio_chan_spec const *chan, int val, |
140 | int val2, long mask) |
141 | { |
142 | struct al3010_data *data = iio_priv(indio_dev); |
143 | int i; |
144 | |
145 | switch (mask) { |
146 | case IIO_CHAN_INFO_SCALE: |
147 | for (i = 0; i < ARRAY_SIZE(al3010_scales); i++) { |
148 | if (val != al3010_scales[i][0] || |
149 | val2 != al3010_scales[i][1]) |
150 | continue; |
151 | |
152 | return i2c_smbus_write_byte_data(client: data->client, |
153 | AL3010_REG_CONFIG, |
154 | FIELD_PREP(AL3010_GAIN_MASK, i)); |
155 | } |
156 | break; |
157 | } |
158 | return -EINVAL; |
159 | } |
160 | |
161 | static const struct iio_info al3010_info = { |
162 | .read_raw = al3010_read_raw, |
163 | .write_raw = al3010_write_raw, |
164 | .attrs = &al3010_attribute_group, |
165 | }; |
166 | |
167 | static int al3010_probe(struct i2c_client *client) |
168 | { |
169 | struct al3010_data *data; |
170 | struct iio_dev *indio_dev; |
171 | int ret; |
172 | |
173 | indio_dev = devm_iio_device_alloc(parent: &client->dev, sizeof_priv: sizeof(*data)); |
174 | if (!indio_dev) |
175 | return -ENOMEM; |
176 | |
177 | data = iio_priv(indio_dev); |
178 | i2c_set_clientdata(client, data: indio_dev); |
179 | data->client = client; |
180 | |
181 | indio_dev->info = &al3010_info; |
182 | indio_dev->name = AL3010_DRV_NAME; |
183 | indio_dev->channels = al3010_channels; |
184 | indio_dev->num_channels = ARRAY_SIZE(al3010_channels); |
185 | indio_dev->modes = INDIO_DIRECT_MODE; |
186 | |
187 | ret = al3010_init(data); |
188 | if (ret < 0) { |
189 | dev_err(&client->dev, "al3010 chip init failed\n" ); |
190 | return ret; |
191 | } |
192 | |
193 | ret = devm_add_action_or_reset(&client->dev, |
194 | al3010_set_pwr_off, |
195 | data); |
196 | if (ret < 0) |
197 | return ret; |
198 | |
199 | return devm_iio_device_register(&client->dev, indio_dev); |
200 | } |
201 | |
202 | static int al3010_suspend(struct device *dev) |
203 | { |
204 | return al3010_set_pwr(to_i2c_client(dev), pwr: false); |
205 | } |
206 | |
207 | static int al3010_resume(struct device *dev) |
208 | { |
209 | return al3010_set_pwr(to_i2c_client(dev), pwr: true); |
210 | } |
211 | |
212 | static DEFINE_SIMPLE_DEV_PM_OPS(al3010_pm_ops, al3010_suspend, al3010_resume); |
213 | |
214 | static const struct i2c_device_id al3010_id[] = { |
215 | {"al3010" , }, |
216 | {} |
217 | }; |
218 | MODULE_DEVICE_TABLE(i2c, al3010_id); |
219 | |
220 | static const struct of_device_id al3010_of_match[] = { |
221 | { .compatible = "dynaimage,al3010" , }, |
222 | {}, |
223 | }; |
224 | MODULE_DEVICE_TABLE(of, al3010_of_match); |
225 | |
226 | static struct i2c_driver al3010_driver = { |
227 | .driver = { |
228 | .name = AL3010_DRV_NAME, |
229 | .of_match_table = al3010_of_match, |
230 | .pm = pm_sleep_ptr(&al3010_pm_ops), |
231 | }, |
232 | .probe = al3010_probe, |
233 | .id_table = al3010_id, |
234 | }; |
235 | module_i2c_driver(al3010_driver); |
236 | |
237 | MODULE_AUTHOR("Daniel Baluta <daniel.baluta@nxp.com>" ); |
238 | MODULE_AUTHOR("David Heidelberg <david@ixit.cz>" ); |
239 | MODULE_DESCRIPTION("AL3010 Ambient Light Sensor driver" ); |
240 | MODULE_LICENSE("GPL v2" ); |
241 | |