1 // SPDX-License-Identifier: GPL-2.0+
3 * Support for ON Semiconductor NOA1305 ambient light sensor
5 * Copyright (C) 2016 Emcraft Systems
6 * Copyright (C) 2019 Collabora Ltd.
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>
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_INTEGR_TIME_MASK 0x07
33 #define NOA1305_REG_INT_SELECT 0x3
34 #define NOA1305_INT_SEL_ACTIVE_HIGH 0x01
35 #define NOA1305_INT_SEL_ACTIVE_LOW 0x02
36 #define NOA1305_INT_SEL_INACTIVE 0x03
37 #define NOA1305_REG_INT_THRESH_LSB 0x4
38 #define NOA1305_REG_INT_THRESH_MSB 0x5
39 #define NOA1305_REG_ALS_DATA_LSB 0x6
40 #define NOA1305_REG_ALS_DATA_MSB 0x7
41 #define NOA1305_REG_DEVICE_ID_LSB 0x8
42 #define NOA1305_REG_DEVICE_ID_MSB 0x9
44 #define NOA1305_DEVICE_ID 0x0519
45 #define NOA1305_DRIVER_NAME "noa1305"
47 static int noa1305_scale_available
[] = {
48 100, 8 * 77, /* 800 ms */
49 100, 4 * 77, /* 400 ms */
50 100, 2 * 77, /* 200 ms */
51 100, 1 * 77, /* 100 ms */
52 1000, 5 * 77, /* 50 ms */
53 10000, 25 * 77, /* 25 ms */
54 100000, 125 * 77, /* 12.5 ms */
55 1000000, 625 * 77, /* 6.25 ms */
58 static int noa1305_int_time_available
[] = {
59 0, 800000, /* 800 ms */
60 0, 400000, /* 400 ms */
61 0, 200000, /* 200 ms */
62 0, 100000, /* 100 ms */
65 0, 12500, /* 12.5 ms */
66 0, 6250, /* 6.25 ms */
70 struct i2c_client
*client
;
71 struct regmap
*regmap
;
74 static int noa1305_measure(struct noa1305_priv
*priv
, int *val
)
79 ret
= regmap_bulk_read(priv
->regmap
, NOA1305_REG_ALS_DATA_LSB
, &data
,
84 *val
= le16_to_cpu(data
);
89 static int noa1305_scale(struct noa1305_priv
*priv
, int *val
, int *val2
)
94 ret
= regmap_read(priv
->regmap
, NOA1305_REG_INTEGRATION_TIME
, &data
);
99 * Lux = count / (<Integration Constant> * <Integration Time>)
101 * Integration Constant = 7.7
102 * Integration Time in Seconds
104 data
&= NOA1305_INTEGR_TIME_MASK
;
105 *val
= noa1305_scale_available
[2 * data
+ 0];
106 *val2
= noa1305_scale_available
[2 * data
+ 1];
108 return IIO_VAL_FRACTIONAL
;
111 static int noa1305_int_time(struct noa1305_priv
*priv
, int *val
, int *val2
)
116 ret
= regmap_read(priv
->regmap
, NOA1305_REG_INTEGRATION_TIME
, &data
);
120 data
&= NOA1305_INTEGR_TIME_MASK
;
121 *val
= noa1305_int_time_available
[2 * data
+ 0];
122 *val2
= noa1305_int_time_available
[2 * data
+ 1];
124 return IIO_VAL_INT_PLUS_MICRO
;
127 static const struct iio_chan_spec noa1305_channels
[] = {
130 .info_mask_separate
= BIT(IIO_CHAN_INFO_RAW
),
131 .info_mask_shared_by_type
= BIT(IIO_CHAN_INFO_SCALE
),
132 .info_mask_shared_by_type_available
= BIT(IIO_CHAN_INFO_SCALE
),
133 .info_mask_shared_by_all
= BIT(IIO_CHAN_INFO_INT_TIME
),
134 .info_mask_shared_by_all_available
= BIT(IIO_CHAN_INFO_INT_TIME
),
138 static int noa1305_read_avail(struct iio_dev
*indio_dev
,
139 struct iio_chan_spec
const *chan
,
140 const int **vals
, int *type
,
141 int *length
, long mask
)
143 if (chan
->type
!= IIO_LIGHT
)
147 case IIO_CHAN_INFO_SCALE
:
148 *vals
= noa1305_scale_available
;
149 *length
= ARRAY_SIZE(noa1305_scale_available
);
150 *type
= IIO_VAL_FRACTIONAL
;
151 return IIO_AVAIL_LIST
;
152 case IIO_CHAN_INFO_INT_TIME
:
153 *vals
= noa1305_int_time_available
;
154 *length
= ARRAY_SIZE(noa1305_int_time_available
);
155 *type
= IIO_VAL_INT_PLUS_MICRO
;
156 return IIO_AVAIL_LIST
;
162 static int noa1305_read_raw(struct iio_dev
*indio_dev
,
163 struct iio_chan_spec
const *chan
,
164 int *val
, int *val2
, long mask
)
166 struct noa1305_priv
*priv
= iio_priv(indio_dev
);
168 if (chan
->type
!= IIO_LIGHT
)
172 case IIO_CHAN_INFO_RAW
:
173 return noa1305_measure(priv
, val
);
174 case IIO_CHAN_INFO_SCALE
:
175 return noa1305_scale(priv
, val
, val2
);
176 case IIO_CHAN_INFO_INT_TIME
:
177 return noa1305_int_time(priv
, val
, val2
);
183 static int noa1305_write_raw(struct iio_dev
*indio_dev
,
184 struct iio_chan_spec
const *chan
,
185 int val
, int val2
, long mask
)
187 struct noa1305_priv
*priv
= iio_priv(indio_dev
);
190 if (chan
->type
!= IIO_LIGHT
)
193 if (mask
!= IIO_CHAN_INFO_INT_TIME
)
196 if (val
) /* >= 1s integration time not supported */
199 /* Look up integration time register settings and write it if found. */
200 for (i
= 0; i
< ARRAY_SIZE(noa1305_int_time_available
) / 2; i
++)
201 if (noa1305_int_time_available
[2 * i
+ 1] == val2
)
202 return regmap_write(priv
->regmap
, NOA1305_REG_INTEGRATION_TIME
, i
);
207 static const struct iio_info noa1305_info
= {
208 .read_avail
= noa1305_read_avail
,
209 .read_raw
= noa1305_read_raw
,
210 .write_raw
= noa1305_write_raw
,
213 static bool noa1305_writable_reg(struct device
*dev
, unsigned int reg
)
216 case NOA1305_REG_POWER_CONTROL
:
217 case NOA1305_REG_RESET
:
218 case NOA1305_REG_INTEGRATION_TIME
:
219 case NOA1305_REG_INT_SELECT
:
220 case NOA1305_REG_INT_THRESH_LSB
:
221 case NOA1305_REG_INT_THRESH_MSB
:
228 static const struct regmap_config noa1305_regmap_config
= {
229 .name
= NOA1305_DRIVER_NAME
,
232 .max_register
= NOA1305_REG_DEVICE_ID_MSB
,
233 .writeable_reg
= noa1305_writable_reg
,
236 static int noa1305_probe(struct i2c_client
*client
)
238 struct noa1305_priv
*priv
;
239 struct iio_dev
*indio_dev
;
240 struct regmap
*regmap
;
245 indio_dev
= devm_iio_device_alloc(&client
->dev
, sizeof(*priv
));
249 regmap
= devm_regmap_init_i2c(client
, &noa1305_regmap_config
);
250 if (IS_ERR(regmap
)) {
251 dev_err(&client
->dev
, "Regmap initialization failed.\n");
252 return PTR_ERR(regmap
);
255 priv
= iio_priv(indio_dev
);
257 ret
= devm_regulator_get_enable(&client
->dev
, "vin");
259 return dev_err_probe(&client
->dev
, ret
,
260 "get regulator vin failed\n");
262 i2c_set_clientdata(client
, indio_dev
);
263 priv
->client
= client
;
264 priv
->regmap
= regmap
;
266 ret
= regmap_bulk_read(regmap
, NOA1305_REG_DEVICE_ID_LSB
, &data
, 2);
268 dev_err(&client
->dev
, "ID reading failed: %d\n", ret
);
272 dev_id
= le16_to_cpu(data
);
273 if (dev_id
!= NOA1305_DEVICE_ID
) {
274 dev_err(&client
->dev
, "Unknown device ID: 0x%x\n", dev_id
);
278 ret
= regmap_write(regmap
, NOA1305_REG_POWER_CONTROL
,
279 NOA1305_POWER_CONTROL_ON
);
281 dev_err(&client
->dev
, "Enabling power control failed\n");
285 ret
= regmap_write(regmap
, NOA1305_REG_RESET
, NOA1305_RESET_RESET
);
287 dev_err(&client
->dev
, "Device reset failed\n");
291 ret
= regmap_write(regmap
, NOA1305_REG_INTEGRATION_TIME
,
292 NOA1305_INTEGR_TIME_800MS
);
294 dev_err(&client
->dev
, "Setting integration time failed\n");
298 indio_dev
->info
= &noa1305_info
;
299 indio_dev
->channels
= noa1305_channels
;
300 indio_dev
->num_channels
= ARRAY_SIZE(noa1305_channels
);
301 indio_dev
->name
= NOA1305_DRIVER_NAME
;
302 indio_dev
->modes
= INDIO_DIRECT_MODE
;
304 ret
= devm_iio_device_register(&client
->dev
, indio_dev
);
306 dev_err(&client
->dev
, "registering device failed\n");
311 static const struct of_device_id noa1305_of_match
[] = {
312 { .compatible
= "onnn,noa1305" },
315 MODULE_DEVICE_TABLE(of
, noa1305_of_match
);
317 static const struct i2c_device_id noa1305_ids
[] = {
321 MODULE_DEVICE_TABLE(i2c
, noa1305_ids
);
323 static struct i2c_driver noa1305_driver
= {
325 .name
= NOA1305_DRIVER_NAME
,
326 .of_match_table
= noa1305_of_match
,
328 .probe
= noa1305_probe
,
329 .id_table
= noa1305_ids
,
332 module_i2c_driver(noa1305_driver
);
334 MODULE_AUTHOR("Sergei Miroshnichenko <sergeimir@emcraft.com>");
335 MODULE_AUTHOR("Martyn Welch <martyn.welch@collabora.com");
336 MODULE_DESCRIPTION("ON Semiconductor NOA1305 ambient light sensor");
337 MODULE_LICENSE("GPL");