brintos

brintos / linux-shallow public Read only

0
0
Text · 9.7 KiB · 4f6975e Raw
396 lines · c
1// SPDX-License-Identifier: GPL-2.0-or-later2/*3 * IIO driver for Lite-On LTR390 ALS and UV sensor4 * (7-bit I2C slave address 0x53)5 *6 * Based on the work of:7 *   Shreeya Patel and Shi Zhigang (LTRF216 Driver)8 *9 * Copyright (C) 2023 Anshul Dalal <anshulusr@gmail.com>10 *11 * Datasheet:12 *   https://optoelectronics.liteon.com/upload/download/DS86-2015-0004/LTR-390UV_Final_%20DS_V1%201.pdf13 *14 * TODO:15 *   - Support for configurable gain and resolution16 *   - Sensor suspend/resume support17 *   - Add support for reading the ALS18 *   - Interrupt support19 */20 21#include <linux/i2c.h>22#include <linux/math.h>23#include <linux/module.h>24#include <linux/mutex.h>25#include <linux/regmap.h>26#include <linux/bitfield.h>27 28#include <linux/iio/iio.h>29 30#include <linux/unaligned.h>31 32#define LTR390_MAIN_CTRL		0x0033#define LTR390_ALS_UVS_MEAS_RATE	0x0434#define LTR390_ALS_UVS_GAIN		0x0535#define LTR390_PART_ID			0x0636#define LTR390_ALS_DATA			0x0D37#define LTR390_UVS_DATA			0x1038#define LTR390_INT_CFG			0x1939 40#define LTR390_PART_NUMBER_ID		0xb41#define LTR390_ALS_UVS_GAIN_MASK	0x0742#define LTR390_ALS_UVS_INT_TIME_MASK	0x7043#define LTR390_ALS_UVS_INT_TIME(x)	FIELD_PREP(LTR390_ALS_UVS_INT_TIME_MASK, (x))44 45#define LTR390_SW_RESET	      BIT(4)46#define LTR390_UVS_MODE	      BIT(3)47#define LTR390_SENSOR_ENABLE  BIT(1)48 49#define LTR390_FRACTIONAL_PRECISION 10050 51/*52 * At 20-bit resolution (integration time: 400ms) and 18x gain, 2300 counts of53 * the sensor are equal to 1 UV Index [Datasheet Page#8].54 *55 * For the default resolution of 18-bit (integration time: 100ms) and default56 * gain of 3x, the counts/uvi are calculated as follows:57 * 2300 / ((3/18) * (100/400)) = 95.8358 */59#define LTR390_COUNTS_PER_UVI 9660 61/*62 * Window Factor is needed when the device is under Window glass with coated63 * tinted ink. This is to compensate for the light loss due to the lower64 * transmission rate of the window glass and helps * in calculating lux.65 */66#define LTR390_WINDOW_FACTOR 167 68enum ltr390_mode {69	LTR390_SET_ALS_MODE,70	LTR390_SET_UVS_MODE,71};72 73struct ltr390_data {74	struct regmap *regmap;75	struct i2c_client *client;76	/* Protects device from simulataneous reads */77	struct mutex lock;78	enum ltr390_mode mode;79	int gain;80	int int_time_us;81};82 83static const struct regmap_config ltr390_regmap_config = {84	.name = "ltr390",85	.reg_bits = 8,86	.reg_stride = 1,87	.val_bits = 8,88};89 90static int ltr390_register_read(struct ltr390_data *data, u8 register_address)91{92	struct device *dev = &data->client->dev;93	int ret;94	u8 recieve_buffer[3];95 96	ret = regmap_bulk_read(data->regmap, register_address, recieve_buffer,97			       sizeof(recieve_buffer));98	if (ret) {99		dev_err(dev, "failed to read measurement data");100		return ret;101	}102 103	return get_unaligned_le24(recieve_buffer);104}105 106static int ltr390_set_mode(struct ltr390_data *data, enum ltr390_mode mode)107{108	int ret;109 110	if (data->mode == mode)111		return 0;112 113	switch (mode) {114	case LTR390_SET_ALS_MODE:115		ret = regmap_clear_bits(data->regmap, LTR390_MAIN_CTRL, LTR390_UVS_MODE);116		break;117 118	case LTR390_SET_UVS_MODE:119		ret = regmap_set_bits(data->regmap, LTR390_MAIN_CTRL, LTR390_UVS_MODE);120		break;121	}122 123	if (ret)124		return ret;125 126	data->mode = mode;127	return 0;128}129 130static int ltr390_counts_per_uvi(struct ltr390_data *data)131{132	const int orig_gain = 18;133	const int orig_int_time = 400;134 135	return DIV_ROUND_CLOSEST(23 * data->gain * data->int_time_us, 10 * orig_gain * orig_int_time);136}137 138static int ltr390_read_raw(struct iio_dev *iio_device,139			   struct iio_chan_spec const *chan, int *val,140			   int *val2, long mask)141{142	int ret;143	struct ltr390_data *data = iio_priv(iio_device);144 145	guard(mutex)(&data->lock);146	switch (mask) {147	case IIO_CHAN_INFO_RAW:148		switch (chan->type) {149		case IIO_UVINDEX:150			ret = ltr390_set_mode(data, LTR390_SET_UVS_MODE);151			if (ret < 0)152				return ret;153 154			ret = ltr390_register_read(data, LTR390_UVS_DATA);155			if (ret < 0)156				return ret;157			break;158 159		case IIO_LIGHT:160			ret = ltr390_set_mode(data, LTR390_SET_ALS_MODE);161			if (ret < 0)162				return ret;163 164			ret = ltr390_register_read(data, LTR390_ALS_DATA);165			if (ret < 0)166				return ret;167			break;168 169		default:170			return -EINVAL;171		}172		*val = ret;173		return IIO_VAL_INT;174	case IIO_CHAN_INFO_SCALE:175		switch (chan->type) {176		case IIO_UVINDEX:177			*val = LTR390_WINDOW_FACTOR * LTR390_FRACTIONAL_PRECISION;178			*val2 = ltr390_counts_per_uvi(data);179			return IIO_VAL_FRACTIONAL;180 181		case IIO_LIGHT:182			*val = LTR390_WINDOW_FACTOR * 6 * 100;183			*val2 = data->gain * data->int_time_us;184			return IIO_VAL_FRACTIONAL;185 186		default:187			return -EINVAL;188		}189 190	case IIO_CHAN_INFO_INT_TIME:191		*val = data->int_time_us;192		return IIO_VAL_INT;193 194	default:195		return -EINVAL;196	}197}198 199/* integration time in us */200static const int ltr390_int_time_map_us[] = { 400000, 200000, 100000, 50000, 25000, 12500 };201static const int ltr390_gain_map[] = { 1, 3, 6, 9, 18 };202 203static const struct iio_chan_spec ltr390_channels[] = {204	/* UV sensor */205	{206		.type = IIO_UVINDEX,207		.scan_index = 0,208		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE),209		.info_mask_shared_by_all = BIT(IIO_CHAN_INFO_INT_TIME),210		.info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_INT_TIME) | BIT(IIO_CHAN_INFO_SCALE)211	},212	/* ALS sensor */213	{214		.type = IIO_LIGHT,215		.scan_index = 1,216		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE),217		.info_mask_shared_by_all = BIT(IIO_CHAN_INFO_INT_TIME),218		.info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_INT_TIME) | BIT(IIO_CHAN_INFO_SCALE)219	},220};221 222static int ltr390_set_gain(struct ltr390_data *data, int val)223{224	int ret, idx;225 226	for (idx = 0; idx < ARRAY_SIZE(ltr390_gain_map); idx++) {227		if (ltr390_gain_map[idx] != val)228			continue;229 230		guard(mutex)(&data->lock);231		ret = regmap_update_bits(data->regmap,232					LTR390_ALS_UVS_GAIN,233					LTR390_ALS_UVS_GAIN_MASK, idx);234		if (ret)235			return ret;236 237		data->gain = ltr390_gain_map[idx];238		return 0;239	}240 241	return -EINVAL;242}243 244static int ltr390_set_int_time(struct ltr390_data *data, int val)245{246	int ret, idx;247 248	for (idx = 0; idx < ARRAY_SIZE(ltr390_int_time_map_us); idx++) {249		if (ltr390_int_time_map_us[idx] != val)250			continue;251 252		guard(mutex)(&data->lock);253		ret = regmap_update_bits(data->regmap,254					LTR390_ALS_UVS_MEAS_RATE,255					LTR390_ALS_UVS_INT_TIME_MASK,256					LTR390_ALS_UVS_INT_TIME(idx));257		if (ret)258			return ret;259 260		data->int_time_us = ltr390_int_time_map_us[idx];261		return 0;262	}263 264	return -EINVAL;265}266 267static int ltr390_read_avail(struct iio_dev *indio_dev, struct iio_chan_spec const *chan,268				const int **vals, int *type, int *length, long mask)269{270	switch (mask) {271	case IIO_CHAN_INFO_SCALE:272		*length = ARRAY_SIZE(ltr390_gain_map);273		*type = IIO_VAL_INT;274		*vals = ltr390_gain_map;275		return IIO_AVAIL_LIST;276	case IIO_CHAN_INFO_INT_TIME:277		*length = ARRAY_SIZE(ltr390_int_time_map_us);278		*type = IIO_VAL_INT;279		*vals = ltr390_int_time_map_us;280		return IIO_AVAIL_LIST;281	default:282		return -EINVAL;283	}284}285 286static int ltr390_write_raw(struct iio_dev *indio_dev, struct iio_chan_spec const *chan,287				int val, int val2, long mask)288{289	struct ltr390_data *data = iio_priv(indio_dev);290 291	switch (mask) {292	case IIO_CHAN_INFO_SCALE:293		if (val2 != 0)294			return -EINVAL;295 296		return ltr390_set_gain(data, val);297 298	case IIO_CHAN_INFO_INT_TIME:299		if (val2 != 0)300			return -EINVAL;301 302		return ltr390_set_int_time(data, val);303 304	default:305		return -EINVAL;306	}307}308 309static const struct iio_info ltr390_info = {310	.read_raw = ltr390_read_raw,311	.write_raw = ltr390_write_raw,312	.read_avail = ltr390_read_avail,313};314 315static int ltr390_probe(struct i2c_client *client)316{317	struct ltr390_data *data;318	struct iio_dev *indio_dev;319	struct device *dev;320	int ret, part_number;321 322	dev = &client->dev;323	indio_dev = devm_iio_device_alloc(dev, sizeof(*data));324	if (!indio_dev)325		return -ENOMEM;326 327	data = iio_priv(indio_dev);328 329	data->regmap = devm_regmap_init_i2c(client, &ltr390_regmap_config);330	if (IS_ERR(data->regmap))331		return dev_err_probe(dev, PTR_ERR(data->regmap),332				     "regmap initialization failed\n");333 334	data->client = client;335	/* default value of integration time from pg: 15 of the datasheet */336	data->int_time_us = 100000;337	/* default value of gain from pg: 16 of the datasheet */338	data->gain = 3;339	/* default mode for ltr390 is ALS mode */340	data->mode = LTR390_SET_ALS_MODE;341 342	mutex_init(&data->lock);343 344	indio_dev->info = &ltr390_info;345	indio_dev->channels = ltr390_channels;346	indio_dev->num_channels = ARRAY_SIZE(ltr390_channels);347	indio_dev->name = "ltr390";348 349	ret = regmap_read(data->regmap, LTR390_PART_ID, &part_number);350	if (ret)351		return dev_err_probe(dev, ret,352				     "failed to get sensor's part id\n");353	/* Lower 4 bits of `part_number` change with hardware revisions */354	if (part_number >> 4 != LTR390_PART_NUMBER_ID)355		dev_info(dev, "received invalid product id: 0x%x", part_number);356	dev_dbg(dev, "LTR390, product id: 0x%x\n", part_number);357 358	/* reset sensor, chip fails to respond to this, so ignore any errors */359	regmap_set_bits(data->regmap, LTR390_MAIN_CTRL, LTR390_SW_RESET);360 361	/* Wait for the registers to reset before proceeding */362	usleep_range(1000, 2000);363 364	ret = regmap_set_bits(data->regmap, LTR390_MAIN_CTRL, LTR390_SENSOR_ENABLE);365	if (ret)366		return dev_err_probe(dev, ret, "failed to enable the sensor\n");367 368	return devm_iio_device_register(dev, indio_dev);369}370 371static const struct i2c_device_id ltr390_id[] = {372	{ "ltr390" },373	{ /* Sentinel */ }374};375MODULE_DEVICE_TABLE(i2c, ltr390_id);376 377static const struct of_device_id ltr390_of_table[] = {378	{ .compatible = "liteon,ltr390" },379	{ /* Sentinel */ }380};381MODULE_DEVICE_TABLE(of, ltr390_of_table);382 383static struct i2c_driver ltr390_driver = {384	.driver = {385		.name = "ltr390",386		.of_match_table = ltr390_of_table,387	},388	.probe = ltr390_probe,389	.id_table = ltr390_id,390};391module_i2c_driver(ltr390_driver);392 393MODULE_AUTHOR("Anshul Dalal <anshulusr@gmail.com>");394MODULE_DESCRIPTION("Lite-On LTR390 ALS and UV sensor Driver");395MODULE_LICENSE("GPL");396