1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | /* |
3 | * Support for ON Semiconductor NOA1305 ambient light sensor |
4 | * |
5 | * Copyright (C) 2016 Emcraft Systems |
6 | * Copyright (C) 2019 Collabora Ltd. |
7 | */ |
8 | |
9 | #include <linux/delay.h> |
10 | #include <linux/err.h> |
11 | #include <linux/i2c.h> |
12 | #include <linux/iio/iio.h> |
13 | #include <linux/iio/sysfs.h> |
14 | #include <linux/module.h> |
15 | #include <linux/regmap.h> |
16 | #include <linux/regulator/consumer.h> |
17 | |
18 | #define NOA1305_REG_POWER_CONTROL 0x0 |
19 | #define NOA1305_POWER_CONTROL_DOWN 0x00 |
20 | #define NOA1305_POWER_CONTROL_ON 0x08 |
21 | #define NOA1305_REG_RESET 0x1 |
22 | #define NOA1305_RESET_RESET 0x10 |
23 | #define NOA1305_REG_INTEGRATION_TIME 0x2 |
24 | #define NOA1305_INTEGR_TIME_800MS 0x00 |
25 | #define NOA1305_INTEGR_TIME_400MS 0x01 |
26 | #define NOA1305_INTEGR_TIME_200MS 0x02 |
27 | #define NOA1305_INTEGR_TIME_100MS 0x03 |
28 | #define NOA1305_INTEGR_TIME_50MS 0x04 |
29 | #define NOA1305_INTEGR_TIME_25MS 0x05 |
30 | #define NOA1305_INTEGR_TIME_12_5MS 0x06 |
31 | #define NOA1305_INTEGR_TIME_6_25MS 0x07 |
32 | #define NOA1305_REG_INT_SELECT 0x3 |
33 | #define NOA1305_INT_SEL_ACTIVE_HIGH 0x01 |
34 | #define NOA1305_INT_SEL_ACTIVE_LOW 0x02 |
35 | #define NOA1305_INT_SEL_INACTIVE 0x03 |
36 | #define NOA1305_REG_INT_THRESH_LSB 0x4 |
37 | #define NOA1305_REG_INT_THRESH_MSB 0x5 |
38 | #define NOA1305_REG_ALS_DATA_LSB 0x6 |
39 | #define NOA1305_REG_ALS_DATA_MSB 0x7 |
40 | #define NOA1305_REG_DEVICE_ID_LSB 0x8 |
41 | #define NOA1305_REG_DEVICE_ID_MSB 0x9 |
42 | |
43 | #define NOA1305_DEVICE_ID 0x0519 |
44 | #define NOA1305_DRIVER_NAME "noa1305" |
45 | |
46 | struct noa1305_priv { |
47 | struct i2c_client *client; |
48 | struct regmap *regmap; |
49 | }; |
50 | |
51 | static int noa1305_measure(struct noa1305_priv *priv) |
52 | { |
53 | __le16 data; |
54 | int ret; |
55 | |
56 | ret = regmap_bulk_read(map: priv->regmap, NOA1305_REG_ALS_DATA_LSB, val: &data, |
57 | val_count: 2); |
58 | if (ret < 0) |
59 | return ret; |
60 | |
61 | return le16_to_cpu(data); |
62 | } |
63 | |
64 | static int noa1305_scale(struct noa1305_priv *priv, int *val, int *val2) |
65 | { |
66 | int data; |
67 | int ret; |
68 | |
69 | ret = regmap_read(map: priv->regmap, NOA1305_REG_INTEGRATION_TIME, val: &data); |
70 | if (ret < 0) |
71 | return ret; |
72 | |
73 | /* |
74 | * Lux = count / (<Integration Constant> * <Integration Time>) |
75 | * |
76 | * Integration Constant = 7.7 |
77 | * Integration Time in Seconds |
78 | */ |
79 | switch (data) { |
80 | case NOA1305_INTEGR_TIME_800MS: |
81 | *val = 100; |
82 | *val2 = 77 * 8; |
83 | break; |
84 | case NOA1305_INTEGR_TIME_400MS: |
85 | *val = 100; |
86 | *val2 = 77 * 4; |
87 | break; |
88 | case NOA1305_INTEGR_TIME_200MS: |
89 | *val = 100; |
90 | *val2 = 77 * 2; |
91 | break; |
92 | case NOA1305_INTEGR_TIME_100MS: |
93 | *val = 100; |
94 | *val2 = 77; |
95 | break; |
96 | case NOA1305_INTEGR_TIME_50MS: |
97 | *val = 1000; |
98 | *val2 = 77 * 5; |
99 | break; |
100 | case NOA1305_INTEGR_TIME_25MS: |
101 | *val = 10000; |
102 | *val2 = 77 * 25; |
103 | break; |
104 | case NOA1305_INTEGR_TIME_12_5MS: |
105 | *val = 100000; |
106 | *val2 = 77 * 125; |
107 | break; |
108 | case NOA1305_INTEGR_TIME_6_25MS: |
109 | *val = 1000000; |
110 | *val2 = 77 * 625; |
111 | break; |
112 | default: |
113 | return -EINVAL; |
114 | } |
115 | |
116 | return IIO_VAL_FRACTIONAL; |
117 | } |
118 | |
119 | static const struct iio_chan_spec noa1305_channels[] = { |
120 | { |
121 | .type = IIO_LIGHT, |
122 | .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), |
123 | .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), |
124 | } |
125 | }; |
126 | |
127 | static int noa1305_read_raw(struct iio_dev *indio_dev, |
128 | struct iio_chan_spec const *chan, |
129 | int *val, int *val2, long mask) |
130 | { |
131 | int ret = -EINVAL; |
132 | struct noa1305_priv *priv = iio_priv(indio_dev); |
133 | |
134 | switch (mask) { |
135 | case IIO_CHAN_INFO_RAW: |
136 | switch (chan->type) { |
137 | case IIO_LIGHT: |
138 | ret = noa1305_measure(priv); |
139 | if (ret < 0) |
140 | return ret; |
141 | *val = ret; |
142 | return IIO_VAL_INT; |
143 | default: |
144 | break; |
145 | } |
146 | break; |
147 | case IIO_CHAN_INFO_SCALE: |
148 | switch (chan->type) { |
149 | case IIO_LIGHT: |
150 | return noa1305_scale(priv, val, val2); |
151 | default: |
152 | break; |
153 | } |
154 | break; |
155 | default: |
156 | break; |
157 | } |
158 | |
159 | return ret; |
160 | } |
161 | |
162 | static const struct iio_info noa1305_info = { |
163 | .read_raw = noa1305_read_raw, |
164 | }; |
165 | |
166 | static bool noa1305_writable_reg(struct device *dev, unsigned int reg) |
167 | { |
168 | switch (reg) { |
169 | case NOA1305_REG_POWER_CONTROL: |
170 | case NOA1305_REG_RESET: |
171 | case NOA1305_REG_INTEGRATION_TIME: |
172 | case NOA1305_REG_INT_SELECT: |
173 | case NOA1305_REG_INT_THRESH_LSB: |
174 | case NOA1305_REG_INT_THRESH_MSB: |
175 | return true; |
176 | default: |
177 | return false; |
178 | } |
179 | } |
180 | |
181 | static const struct regmap_config noa1305_regmap_config = { |
182 | .name = NOA1305_DRIVER_NAME, |
183 | .reg_bits = 8, |
184 | .val_bits = 8, |
185 | .max_register = NOA1305_REG_DEVICE_ID_MSB, |
186 | .writeable_reg = noa1305_writable_reg, |
187 | }; |
188 | |
189 | static int noa1305_probe(struct i2c_client *client) |
190 | { |
191 | struct noa1305_priv *priv; |
192 | struct iio_dev *indio_dev; |
193 | struct regmap *regmap; |
194 | __le16 data; |
195 | unsigned int dev_id; |
196 | int ret; |
197 | |
198 | indio_dev = devm_iio_device_alloc(parent: &client->dev, sizeof_priv: sizeof(*priv)); |
199 | if (!indio_dev) |
200 | return -ENOMEM; |
201 | |
202 | regmap = devm_regmap_init_i2c(client, &noa1305_regmap_config); |
203 | if (IS_ERR(ptr: regmap)) { |
204 | dev_err(&client->dev, "Regmap initialization failed.\n" ); |
205 | return PTR_ERR(ptr: regmap); |
206 | } |
207 | |
208 | priv = iio_priv(indio_dev); |
209 | |
210 | ret = devm_regulator_get_enable(dev: &client->dev, id: "vin" ); |
211 | if (ret) |
212 | return dev_err_probe(dev: &client->dev, err: ret, |
213 | fmt: "get regulator vin failed\n" ); |
214 | |
215 | i2c_set_clientdata(client, data: indio_dev); |
216 | priv->client = client; |
217 | priv->regmap = regmap; |
218 | |
219 | ret = regmap_bulk_read(map: regmap, NOA1305_REG_DEVICE_ID_LSB, val: &data, val_count: 2); |
220 | if (ret < 0) { |
221 | dev_err(&client->dev, "ID reading failed: %d\n" , ret); |
222 | return ret; |
223 | } |
224 | |
225 | dev_id = le16_to_cpu(data); |
226 | if (dev_id != NOA1305_DEVICE_ID) { |
227 | dev_err(&client->dev, "Unknown device ID: 0x%x\n" , dev_id); |
228 | return -ENODEV; |
229 | } |
230 | |
231 | ret = regmap_write(map: regmap, NOA1305_REG_POWER_CONTROL, |
232 | NOA1305_POWER_CONTROL_ON); |
233 | if (ret < 0) { |
234 | dev_err(&client->dev, "Enabling power control failed\n" ); |
235 | return ret; |
236 | } |
237 | |
238 | ret = regmap_write(map: regmap, NOA1305_REG_RESET, NOA1305_RESET_RESET); |
239 | if (ret < 0) { |
240 | dev_err(&client->dev, "Device reset failed\n" ); |
241 | return ret; |
242 | } |
243 | |
244 | ret = regmap_write(map: regmap, NOA1305_REG_INTEGRATION_TIME, |
245 | NOA1305_INTEGR_TIME_800MS); |
246 | if (ret < 0) { |
247 | dev_err(&client->dev, "Setting integration time failed\n" ); |
248 | return ret; |
249 | } |
250 | |
251 | indio_dev->info = &noa1305_info; |
252 | indio_dev->channels = noa1305_channels; |
253 | indio_dev->num_channels = ARRAY_SIZE(noa1305_channels); |
254 | indio_dev->name = NOA1305_DRIVER_NAME; |
255 | indio_dev->modes = INDIO_DIRECT_MODE; |
256 | |
257 | ret = devm_iio_device_register(&client->dev, indio_dev); |
258 | if (ret) |
259 | dev_err(&client->dev, "registering device failed\n" ); |
260 | |
261 | return ret; |
262 | } |
263 | |
264 | static const struct of_device_id noa1305_of_match[] = { |
265 | { .compatible = "onnn,noa1305" }, |
266 | { } |
267 | }; |
268 | MODULE_DEVICE_TABLE(of, noa1305_of_match); |
269 | |
270 | static const struct i2c_device_id noa1305_ids[] = { |
271 | { "noa1305" , 0 }, |
272 | { } |
273 | }; |
274 | MODULE_DEVICE_TABLE(i2c, noa1305_ids); |
275 | |
276 | static struct i2c_driver noa1305_driver = { |
277 | .driver = { |
278 | .name = NOA1305_DRIVER_NAME, |
279 | .of_match_table = noa1305_of_match, |
280 | }, |
281 | .probe = noa1305_probe, |
282 | .id_table = noa1305_ids, |
283 | }; |
284 | |
285 | module_i2c_driver(noa1305_driver); |
286 | |
287 | MODULE_AUTHOR("Sergei Miroshnichenko <sergeimir@emcraft.com>" ); |
288 | MODULE_AUTHOR("Martyn Welch <martyn.welch@collabora.com" ); |
289 | MODULE_DESCRIPTION("ON Semiconductor NOA1305 ambient light sensor" ); |
290 | MODULE_LICENSE("GPL" ); |
291 | |