File Index
Files and directories
Sign in
FILE

adjd_s311.c

File preview with syntax highlighting.

linux-6.19.14/drivers/iio/light/adjd_s311.c C
  1// SPDX-License-Identifier: GPL-2.0-only
  2/*
  3 * adjd_s311.c - Support for ADJD-S311-CR999 digital color sensor
  4 *
  5 * Copyright (C) 2012 Peter Meerwald <pmeerw@pmeerw.net>
  6 *
  7 * driver for ADJD-S311-CR999 digital color sensor (10-bit channels for
  8 * red, green, blue, clear); 7-bit I2C slave address 0x74
  9 *
 10 * limitations: no calibration, no offset mode, no sleep mode
 11 */
 12
 13#include <linux/module.h>
 14#include <linux/interrupt.h>
 15#include <linux/i2c.h>
 16#include <linux/slab.h>
 17#include <linux/delay.h>
 18#include <linux/bitmap.h>
 19#include <linux/err.h>
 20#include <linux/irq.h>
 21
 22#include <linux/iio/iio.h>
 23#include <linux/iio/sysfs.h>
 24#include <linux/iio/trigger_consumer.h>
 25#include <linux/iio/buffer.h>
 26#include <linux/iio/triggered_buffer.h>
 27
 28#define ADJD_S311_DRV_NAME "adjd_s311"
 29
 30#define ADJD_S311_CTRL		0x00
 31#define ADJD_S311_CONFIG	0x01
 32#define ADJD_S311_CAP_RED	0x06
 33#define ADJD_S311_CAP_GREEN	0x07
 34#define ADJD_S311_CAP_BLUE	0x08
 35#define ADJD_S311_CAP_CLEAR	0x09
 36#define ADJD_S311_INT_RED	0x0a
 37#define ADJD_S311_INT_GREEN	0x0c
 38#define ADJD_S311_INT_BLUE	0x0e
 39#define ADJD_S311_INT_CLEAR	0x10
 40#define ADJD_S311_DATA_RED	0x40
 41#define ADJD_S311_DATA_GREEN	0x42
 42#define ADJD_S311_DATA_BLUE	0x44
 43#define ADJD_S311_DATA_CLEAR	0x46
 44#define ADJD_S311_OFFSET_RED	0x48
 45#define ADJD_S311_OFFSET_GREEN	0x49
 46#define ADJD_S311_OFFSET_BLUE	0x4a
 47#define ADJD_S311_OFFSET_CLEAR	0x4b
 48
 49#define ADJD_S311_CTRL_GOFS	0x02
 50#define ADJD_S311_CTRL_GSSR	0x01
 51#define ADJD_S311_CAP_MASK	0x0f
 52#define ADJD_S311_INT_MASK	0x0fff
 53#define ADJD_S311_DATA_MASK	0x03ff
 54
 55struct adjd_s311_data {
 56	struct i2c_client *client;
 57};
 58
 59enum adjd_s311_channel_idx {
 60	IDX_RED, IDX_GREEN, IDX_BLUE, IDX_CLEAR
 61};
 62
 63#define ADJD_S311_DATA_REG(chan) (ADJD_S311_DATA_RED + (chan) * 2)
 64#define ADJD_S311_INT_REG(chan) (ADJD_S311_INT_RED + (chan) * 2)
 65#define ADJD_S311_CAP_REG(chan) (ADJD_S311_CAP_RED + (chan))
 66
 67static int adjd_s311_req_data(struct iio_dev *indio_dev)
 68{
 69	struct adjd_s311_data *data = iio_priv(indio_dev);
 70	int tries = 10;
 71
 72	int ret = i2c_smbus_write_byte_data(data->client, ADJD_S311_CTRL,
 73		ADJD_S311_CTRL_GSSR);
 74	if (ret < 0)
 75		return ret;
 76
 77	while (tries--) {
 78		ret = i2c_smbus_read_byte_data(data->client, ADJD_S311_CTRL);
 79		if (ret < 0)
 80			return ret;
 81		if (!(ret & ADJD_S311_CTRL_GSSR))
 82			break;
 83		msleep(20);
 84	}
 85
 86	if (tries < 0) {
 87		dev_err(&data->client->dev,
 88			"adjd_s311_req_data() failed, data not ready\n");
 89		return -EIO;
 90	}
 91
 92	return 0;
 93}
 94
 95static int adjd_s311_read_data(struct iio_dev *indio_dev, u8 reg, int *val)
 96{
 97	struct adjd_s311_data *data = iio_priv(indio_dev);
 98
 99	int ret = adjd_s311_req_data(indio_dev);
100	if (ret < 0)
101		return ret;
102
103	ret = i2c_smbus_read_word_data(data->client, reg);
104	if (ret < 0)
105		return ret;
106
107	*val = ret & ADJD_S311_DATA_MASK;
108
109	return 0;
110}
111
112static irqreturn_t adjd_s311_trigger_handler(int irq, void *p)
113{
114	struct iio_poll_func *pf = p;
115	struct iio_dev *indio_dev = pf->indio_dev;
116	struct adjd_s311_data *data = iio_priv(indio_dev);
117	s64 time_ns = iio_get_time_ns(indio_dev);
118	int i, j = 0;
119	struct {
120		s16 chans[4];
121		aligned_s64 ts;
122	} scan = { };
123
124	int ret = adjd_s311_req_data(indio_dev);
125	if (ret < 0)
126		goto done;
127
128	iio_for_each_active_channel(indio_dev, i) {
129		ret = i2c_smbus_read_word_data(data->client,
130			ADJD_S311_DATA_REG(i));
131		if (ret < 0)
132			goto done;
133
134		scan.chans[j++] = ret & ADJD_S311_DATA_MASK;
135	}
136
137	iio_push_to_buffers_with_ts(indio_dev, &scan, sizeof(scan), time_ns);
138
139done:
140	iio_trigger_notify_done(indio_dev->trig);
141
142	return IRQ_HANDLED;
143}
144
145#define ADJD_S311_CHANNEL(_color, _scan_idx) { \
146	.type = IIO_INTENSITY, \
147	.modified = 1, \
148	.address = (IDX_##_color), \
149	.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \
150		BIT(IIO_CHAN_INFO_HARDWAREGAIN) | \
151		BIT(IIO_CHAN_INFO_INT_TIME), \
152	.channel2 = (IIO_MOD_LIGHT_##_color), \
153	.scan_index = (_scan_idx), \
154	.scan_type = { \
155		.sign = 'u', \
156		.realbits = 10, \
157		.storagebits = 16, \
158		.endianness = IIO_CPU, \
159	}, \
160}
161
162static const struct iio_chan_spec adjd_s311_channels[] = {
163	ADJD_S311_CHANNEL(RED, 0),
164	ADJD_S311_CHANNEL(GREEN, 1),
165	ADJD_S311_CHANNEL(BLUE, 2),
166	ADJD_S311_CHANNEL(CLEAR, 3),
167	IIO_CHAN_SOFT_TIMESTAMP(4),
168};
169
170static int adjd_s311_read_raw(struct iio_dev *indio_dev,
171			   struct iio_chan_spec const *chan,
172			   int *val, int *val2, long mask)
173{
174	struct adjd_s311_data *data = iio_priv(indio_dev);
175	int ret;
176
177	switch (mask) {
178	case IIO_CHAN_INFO_RAW:
179		ret = adjd_s311_read_data(indio_dev,
180			ADJD_S311_DATA_REG(chan->address), val);
181		if (ret < 0)
182			return ret;
183		return IIO_VAL_INT;
184	case IIO_CHAN_INFO_HARDWAREGAIN:
185		ret = i2c_smbus_read_byte_data(data->client,
186			ADJD_S311_CAP_REG(chan->address));
187		if (ret < 0)
188			return ret;
189		*val = ret & ADJD_S311_CAP_MASK;
190		return IIO_VAL_INT;
191	case IIO_CHAN_INFO_INT_TIME:
192		ret = i2c_smbus_read_word_data(data->client,
193			ADJD_S311_INT_REG(chan->address));
194		if (ret < 0)
195			return ret;
196		*val = 0;
197		/*
198		 * not documented, based on measurement:
199		 * 4095 LSBs correspond to roughly 4 ms
200		 */
201		*val2 = ret & ADJD_S311_INT_MASK;
202		return IIO_VAL_INT_PLUS_MICRO;
203	}
204	return -EINVAL;
205}
206
207static int adjd_s311_write_raw(struct iio_dev *indio_dev,
208			       struct iio_chan_spec const *chan,
209			       int val, int val2, long mask)
210{
211	struct adjd_s311_data *data = iio_priv(indio_dev);
212
213	switch (mask) {
214	case IIO_CHAN_INFO_HARDWAREGAIN:
215		if (val < 0 || val > ADJD_S311_CAP_MASK)
216			return -EINVAL;
217
218		return i2c_smbus_write_byte_data(data->client,
219			ADJD_S311_CAP_REG(chan->address), val);
220	case IIO_CHAN_INFO_INT_TIME:
221		if (val != 0 || val2 < 0 || val2 > ADJD_S311_INT_MASK)
222			return -EINVAL;
223
224		return i2c_smbus_write_word_data(data->client,
225			ADJD_S311_INT_REG(chan->address), val2);
226	}
227	return -EINVAL;
228}
229
230static const struct iio_info adjd_s311_info = {
231	.read_raw = adjd_s311_read_raw,
232	.write_raw = adjd_s311_write_raw,
233};
234
235static int adjd_s311_probe(struct i2c_client *client)
236{
237	struct adjd_s311_data *data;
238	struct iio_dev *indio_dev;
239	int err;
240
241	indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data));
242	if (indio_dev == NULL)
243		return -ENOMEM;
244
245	data = iio_priv(indio_dev);
246	data->client = client;
247
248	indio_dev->info = &adjd_s311_info;
249	indio_dev->name = ADJD_S311_DRV_NAME;
250	indio_dev->channels = adjd_s311_channels;
251	indio_dev->num_channels = ARRAY_SIZE(adjd_s311_channels);
252	indio_dev->modes = INDIO_DIRECT_MODE;
253
254	err = devm_iio_triggered_buffer_setup(&client->dev, indio_dev, NULL,
255					      adjd_s311_trigger_handler, NULL);
256	if (err < 0)
257		return err;
258
259	return devm_iio_device_register(&client->dev, indio_dev);
260}
261
262static const struct i2c_device_id adjd_s311_id[] = {
263	{ "adjd_s311" },
264	{ }
265};
266MODULE_DEVICE_TABLE(i2c, adjd_s311_id);
267
268static struct i2c_driver adjd_s311_driver = {
269	.driver = {
270		.name	= ADJD_S311_DRV_NAME,
271	},
272	.probe		= adjd_s311_probe,
273	.id_table	= adjd_s311_id,
274};
275module_i2c_driver(adjd_s311_driver);
276
277MODULE_AUTHOR("Peter Meerwald <pmeerw@pmeerw.net>");
278MODULE_DESCRIPTION("ADJD-S311 color sensor");
279MODULE_LICENSE("GPL");
~/ Read-only file preview.