162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * zopt2201.c - Support for IDT ZOPT2201 ambient light and UV B sensor
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Copyright 2017 Peter Meerwald-Stadler <pmeerw@pmeerw.net>
662306a36Sopenharmony_ci *
762306a36Sopenharmony_ci * Datasheet: https://www.idt.com/document/dst/zopt2201-datasheet
862306a36Sopenharmony_ci * 7-bit I2C slave addresses 0x53 (default) or 0x52 (programmed)
962306a36Sopenharmony_ci *
1062306a36Sopenharmony_ci * TODO: interrupt support, ALS/UVB raw mode
1162306a36Sopenharmony_ci */
1262306a36Sopenharmony_ci
1362306a36Sopenharmony_ci#include <linux/module.h>
1462306a36Sopenharmony_ci#include <linux/i2c.h>
1562306a36Sopenharmony_ci#include <linux/mutex.h>
1662306a36Sopenharmony_ci#include <linux/err.h>
1762306a36Sopenharmony_ci#include <linux/delay.h>
1862306a36Sopenharmony_ci
1962306a36Sopenharmony_ci#include <linux/iio/iio.h>
2062306a36Sopenharmony_ci#include <linux/iio/sysfs.h>
2162306a36Sopenharmony_ci
2262306a36Sopenharmony_ci#include <asm/unaligned.h>
2362306a36Sopenharmony_ci
2462306a36Sopenharmony_ci#define ZOPT2201_DRV_NAME "zopt2201"
2562306a36Sopenharmony_ci
2662306a36Sopenharmony_ci/* Registers */
2762306a36Sopenharmony_ci#define ZOPT2201_MAIN_CTRL		0x00
2862306a36Sopenharmony_ci#define ZOPT2201_LS_MEAS_RATE		0x04
2962306a36Sopenharmony_ci#define ZOPT2201_LS_GAIN		0x05
3062306a36Sopenharmony_ci#define ZOPT2201_PART_ID		0x06
3162306a36Sopenharmony_ci#define ZOPT2201_MAIN_STATUS		0x07
3262306a36Sopenharmony_ci#define ZOPT2201_ALS_DATA		0x0d /* LSB first, 13 to 20 bits */
3362306a36Sopenharmony_ci#define ZOPT2201_UVB_DATA		0x10 /* LSB first, 13 to 20 bits */
3462306a36Sopenharmony_ci#define ZOPT2201_UV_COMP_DATA		0x13 /* LSB first, 13 to 20 bits */
3562306a36Sopenharmony_ci#define ZOPT2201_COMP_DATA		0x16 /* LSB first, 13 to 20 bits */
3662306a36Sopenharmony_ci#define ZOPT2201_INT_CFG		0x19
3762306a36Sopenharmony_ci#define ZOPT2201_INT_PST		0x1a
3862306a36Sopenharmony_ci
3962306a36Sopenharmony_ci#define ZOPT2201_MAIN_CTRL_LS_MODE	BIT(3) /* 0 .. ALS, 1 .. UV B */
4062306a36Sopenharmony_ci#define ZOPT2201_MAIN_CTRL_LS_EN	BIT(1)
4162306a36Sopenharmony_ci
4262306a36Sopenharmony_ci/* Values for ZOPT2201_LS_MEAS_RATE resolution / bit width */
4362306a36Sopenharmony_ci#define ZOPT2201_MEAS_RES_20BIT		0 /* takes 400 ms */
4462306a36Sopenharmony_ci#define ZOPT2201_MEAS_RES_19BIT		1 /* takes 200 ms */
4562306a36Sopenharmony_ci#define ZOPT2201_MEAS_RES_18BIT		2 /* takes 100 ms, default */
4662306a36Sopenharmony_ci#define ZOPT2201_MEAS_RES_17BIT		3 /* takes 50 ms */
4762306a36Sopenharmony_ci#define ZOPT2201_MEAS_RES_16BIT		4 /* takes 25 ms */
4862306a36Sopenharmony_ci#define ZOPT2201_MEAS_RES_13BIT		5 /* takes 3.125 ms */
4962306a36Sopenharmony_ci#define ZOPT2201_MEAS_RES_SHIFT		4
5062306a36Sopenharmony_ci
5162306a36Sopenharmony_ci/* Values for ZOPT2201_LS_MEAS_RATE measurement rate */
5262306a36Sopenharmony_ci#define ZOPT2201_MEAS_FREQ_25MS		0
5362306a36Sopenharmony_ci#define ZOPT2201_MEAS_FREQ_50MS		1
5462306a36Sopenharmony_ci#define ZOPT2201_MEAS_FREQ_100MS	2 /* default */
5562306a36Sopenharmony_ci#define ZOPT2201_MEAS_FREQ_200MS	3
5662306a36Sopenharmony_ci#define ZOPT2201_MEAS_FREQ_500MS	4
5762306a36Sopenharmony_ci#define ZOPT2201_MEAS_FREQ_1000MS	5
5862306a36Sopenharmony_ci#define ZOPT2201_MEAS_FREQ_2000MS	6
5962306a36Sopenharmony_ci
6062306a36Sopenharmony_ci/* Values for ZOPT2201_LS_GAIN */
6162306a36Sopenharmony_ci#define ZOPT2201_LS_GAIN_1		0
6262306a36Sopenharmony_ci#define ZOPT2201_LS_GAIN_3		1
6362306a36Sopenharmony_ci#define ZOPT2201_LS_GAIN_6		2
6462306a36Sopenharmony_ci#define ZOPT2201_LS_GAIN_9		3
6562306a36Sopenharmony_ci#define ZOPT2201_LS_GAIN_18		4
6662306a36Sopenharmony_ci
6762306a36Sopenharmony_ci/* Values for ZOPT2201_MAIN_STATUS */
6862306a36Sopenharmony_ci#define ZOPT2201_MAIN_STATUS_POWERON	BIT(5)
6962306a36Sopenharmony_ci#define ZOPT2201_MAIN_STATUS_INT	BIT(4)
7062306a36Sopenharmony_ci#define ZOPT2201_MAIN_STATUS_DRDY	BIT(3)
7162306a36Sopenharmony_ci
7262306a36Sopenharmony_ci#define ZOPT2201_PART_NUMBER		0xb2
7362306a36Sopenharmony_ci
7462306a36Sopenharmony_cistruct zopt2201_data {
7562306a36Sopenharmony_ci	struct i2c_client *client;
7662306a36Sopenharmony_ci	struct mutex lock;
7762306a36Sopenharmony_ci	u8 gain;
7862306a36Sopenharmony_ci	u8 res;
7962306a36Sopenharmony_ci	u8 rate;
8062306a36Sopenharmony_ci};
8162306a36Sopenharmony_ci
8262306a36Sopenharmony_cistatic const struct {
8362306a36Sopenharmony_ci	unsigned int gain; /* gain factor */
8462306a36Sopenharmony_ci	unsigned int scale; /* micro lux per count */
8562306a36Sopenharmony_ci} zopt2201_gain_als[] = {
8662306a36Sopenharmony_ci	{  1, 19200000 },
8762306a36Sopenharmony_ci	{  3,  6400000 },
8862306a36Sopenharmony_ci	{  6,  3200000 },
8962306a36Sopenharmony_ci	{  9,  2133333 },
9062306a36Sopenharmony_ci	{ 18,  1066666 },
9162306a36Sopenharmony_ci};
9262306a36Sopenharmony_ci
9362306a36Sopenharmony_cistatic const struct {
9462306a36Sopenharmony_ci	unsigned int gain; /* gain factor */
9562306a36Sopenharmony_ci	unsigned int scale; /* micro W/m2 per count */
9662306a36Sopenharmony_ci} zopt2201_gain_uvb[] = {
9762306a36Sopenharmony_ci	{  1, 460800 },
9862306a36Sopenharmony_ci	{  3, 153600 },
9962306a36Sopenharmony_ci	{  6,  76800 },
10062306a36Sopenharmony_ci	{  9,  51200 },
10162306a36Sopenharmony_ci	{ 18,  25600 },
10262306a36Sopenharmony_ci};
10362306a36Sopenharmony_ci
10462306a36Sopenharmony_cistatic const struct {
10562306a36Sopenharmony_ci	unsigned int bits; /* sensor resolution in bits */
10662306a36Sopenharmony_ci	unsigned long us; /* measurement time in micro seconds */
10762306a36Sopenharmony_ci} zopt2201_resolution[] = {
10862306a36Sopenharmony_ci	{ 20, 400000 },
10962306a36Sopenharmony_ci	{ 19, 200000 },
11062306a36Sopenharmony_ci	{ 18, 100000 },
11162306a36Sopenharmony_ci	{ 17,  50000 },
11262306a36Sopenharmony_ci	{ 16,  25000 },
11362306a36Sopenharmony_ci	{ 13,   3125 },
11462306a36Sopenharmony_ci};
11562306a36Sopenharmony_ci
11662306a36Sopenharmony_cistatic const struct {
11762306a36Sopenharmony_ci	unsigned int scale, uscale; /* scale factor as integer + micro */
11862306a36Sopenharmony_ci	u8 gain; /* gain register value */
11962306a36Sopenharmony_ci	u8 res; /* resolution register value */
12062306a36Sopenharmony_ci} zopt2201_scale_als[] = {
12162306a36Sopenharmony_ci	{ 19, 200000, 0, 5 },
12262306a36Sopenharmony_ci	{  6, 400000, 1, 5 },
12362306a36Sopenharmony_ci	{  3, 200000, 2, 5 },
12462306a36Sopenharmony_ci	{  2, 400000, 0, 4 },
12562306a36Sopenharmony_ci	{  2, 133333, 3, 5 },
12662306a36Sopenharmony_ci	{  1, 200000, 0, 3 },
12762306a36Sopenharmony_ci	{  1,  66666, 4, 5 },
12862306a36Sopenharmony_ci	{  0, 800000, 1, 4 },
12962306a36Sopenharmony_ci	{  0, 600000, 0, 2 },
13062306a36Sopenharmony_ci	{  0, 400000, 2, 4 },
13162306a36Sopenharmony_ci	{  0, 300000, 0, 1 },
13262306a36Sopenharmony_ci	{  0, 266666, 3, 4 },
13362306a36Sopenharmony_ci	{  0, 200000, 2, 3 },
13462306a36Sopenharmony_ci	{  0, 150000, 0, 0 },
13562306a36Sopenharmony_ci	{  0, 133333, 4, 4 },
13662306a36Sopenharmony_ci	{  0, 100000, 2, 2 },
13762306a36Sopenharmony_ci	{  0,  66666, 4, 3 },
13862306a36Sopenharmony_ci	{  0,  50000, 2, 1 },
13962306a36Sopenharmony_ci	{  0,  33333, 4, 2 },
14062306a36Sopenharmony_ci	{  0,  25000, 2, 0 },
14162306a36Sopenharmony_ci	{  0,  16666, 4, 1 },
14262306a36Sopenharmony_ci	{  0,   8333, 4, 0 },
14362306a36Sopenharmony_ci};
14462306a36Sopenharmony_ci
14562306a36Sopenharmony_cistatic const struct {
14662306a36Sopenharmony_ci	unsigned int scale, uscale; /* scale factor as integer + micro */
14762306a36Sopenharmony_ci	u8 gain; /* gain register value */
14862306a36Sopenharmony_ci	u8 res; /* resolution register value */
14962306a36Sopenharmony_ci} zopt2201_scale_uvb[] = {
15062306a36Sopenharmony_ci	{ 0, 460800, 0, 5 },
15162306a36Sopenharmony_ci	{ 0, 153600, 1, 5 },
15262306a36Sopenharmony_ci	{ 0,  76800, 2, 5 },
15362306a36Sopenharmony_ci	{ 0,  57600, 0, 4 },
15462306a36Sopenharmony_ci	{ 0,  51200, 3, 5 },
15562306a36Sopenharmony_ci	{ 0,  28800, 0, 3 },
15662306a36Sopenharmony_ci	{ 0,  25600, 4, 5 },
15762306a36Sopenharmony_ci	{ 0,  19200, 1, 4 },
15862306a36Sopenharmony_ci	{ 0,  14400, 0, 2 },
15962306a36Sopenharmony_ci	{ 0,   9600, 2, 4 },
16062306a36Sopenharmony_ci	{ 0,   7200, 0, 1 },
16162306a36Sopenharmony_ci	{ 0,   6400, 3, 4 },
16262306a36Sopenharmony_ci	{ 0,   4800, 2, 3 },
16362306a36Sopenharmony_ci	{ 0,   3600, 0, 0 },
16462306a36Sopenharmony_ci	{ 0,   3200, 4, 4 },
16562306a36Sopenharmony_ci	{ 0,   2400, 2, 2 },
16662306a36Sopenharmony_ci	{ 0,   1600, 4, 3 },
16762306a36Sopenharmony_ci	{ 0,   1200, 2, 1 },
16862306a36Sopenharmony_ci	{ 0,    800, 4, 2 },
16962306a36Sopenharmony_ci	{ 0,    600, 2, 0 },
17062306a36Sopenharmony_ci	{ 0,    400, 4, 1 },
17162306a36Sopenharmony_ci	{ 0,    200, 4, 0 },
17262306a36Sopenharmony_ci};
17362306a36Sopenharmony_ci
17462306a36Sopenharmony_cistatic int zopt2201_enable_mode(struct zopt2201_data *data, bool uvb_mode)
17562306a36Sopenharmony_ci{
17662306a36Sopenharmony_ci	u8 out = ZOPT2201_MAIN_CTRL_LS_EN;
17762306a36Sopenharmony_ci
17862306a36Sopenharmony_ci	if (uvb_mode)
17962306a36Sopenharmony_ci		out |= ZOPT2201_MAIN_CTRL_LS_MODE;
18062306a36Sopenharmony_ci
18162306a36Sopenharmony_ci	return i2c_smbus_write_byte_data(data->client, ZOPT2201_MAIN_CTRL, out);
18262306a36Sopenharmony_ci}
18362306a36Sopenharmony_ci
18462306a36Sopenharmony_cistatic int zopt2201_read(struct zopt2201_data *data, u8 reg)
18562306a36Sopenharmony_ci{
18662306a36Sopenharmony_ci	struct i2c_client *client = data->client;
18762306a36Sopenharmony_ci	int tries = 10;
18862306a36Sopenharmony_ci	u8 buf[3];
18962306a36Sopenharmony_ci	int ret;
19062306a36Sopenharmony_ci
19162306a36Sopenharmony_ci	mutex_lock(&data->lock);
19262306a36Sopenharmony_ci	ret = zopt2201_enable_mode(data, reg == ZOPT2201_UVB_DATA);
19362306a36Sopenharmony_ci	if (ret < 0)
19462306a36Sopenharmony_ci		goto fail;
19562306a36Sopenharmony_ci
19662306a36Sopenharmony_ci	while (tries--) {
19762306a36Sopenharmony_ci		unsigned long t = zopt2201_resolution[data->res].us;
19862306a36Sopenharmony_ci
19962306a36Sopenharmony_ci		if (t <= 20000)
20062306a36Sopenharmony_ci			usleep_range(t, t + 1000);
20162306a36Sopenharmony_ci		else
20262306a36Sopenharmony_ci			msleep(t / 1000);
20362306a36Sopenharmony_ci		ret = i2c_smbus_read_byte_data(client, ZOPT2201_MAIN_STATUS);
20462306a36Sopenharmony_ci		if (ret < 0)
20562306a36Sopenharmony_ci			goto fail;
20662306a36Sopenharmony_ci		if (ret & ZOPT2201_MAIN_STATUS_DRDY)
20762306a36Sopenharmony_ci			break;
20862306a36Sopenharmony_ci	}
20962306a36Sopenharmony_ci
21062306a36Sopenharmony_ci	if (tries < 0) {
21162306a36Sopenharmony_ci		ret = -ETIMEDOUT;
21262306a36Sopenharmony_ci		goto fail;
21362306a36Sopenharmony_ci	}
21462306a36Sopenharmony_ci
21562306a36Sopenharmony_ci	ret = i2c_smbus_read_i2c_block_data(client, reg, sizeof(buf), buf);
21662306a36Sopenharmony_ci	if (ret < 0)
21762306a36Sopenharmony_ci		goto fail;
21862306a36Sopenharmony_ci
21962306a36Sopenharmony_ci	ret = i2c_smbus_write_byte_data(client, ZOPT2201_MAIN_CTRL, 0x00);
22062306a36Sopenharmony_ci	if (ret < 0)
22162306a36Sopenharmony_ci		goto fail;
22262306a36Sopenharmony_ci	mutex_unlock(&data->lock);
22362306a36Sopenharmony_ci
22462306a36Sopenharmony_ci	return get_unaligned_le24(&buf[0]);
22562306a36Sopenharmony_ci
22662306a36Sopenharmony_cifail:
22762306a36Sopenharmony_ci	mutex_unlock(&data->lock);
22862306a36Sopenharmony_ci	return ret;
22962306a36Sopenharmony_ci}
23062306a36Sopenharmony_ci
23162306a36Sopenharmony_cistatic const struct iio_chan_spec zopt2201_channels[] = {
23262306a36Sopenharmony_ci	{
23362306a36Sopenharmony_ci		.type = IIO_LIGHT,
23462306a36Sopenharmony_ci		.address = ZOPT2201_ALS_DATA,
23562306a36Sopenharmony_ci		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
23662306a36Sopenharmony_ci				      BIT(IIO_CHAN_INFO_SCALE),
23762306a36Sopenharmony_ci		.info_mask_shared_by_all = BIT(IIO_CHAN_INFO_INT_TIME),
23862306a36Sopenharmony_ci	},
23962306a36Sopenharmony_ci	{
24062306a36Sopenharmony_ci		.type = IIO_INTENSITY,
24162306a36Sopenharmony_ci		.modified = 1,
24262306a36Sopenharmony_ci		.channel2 = IIO_MOD_LIGHT_UV,
24362306a36Sopenharmony_ci		.address = ZOPT2201_UVB_DATA,
24462306a36Sopenharmony_ci		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
24562306a36Sopenharmony_ci				      BIT(IIO_CHAN_INFO_SCALE),
24662306a36Sopenharmony_ci		.info_mask_shared_by_all = BIT(IIO_CHAN_INFO_INT_TIME),
24762306a36Sopenharmony_ci	},
24862306a36Sopenharmony_ci	{
24962306a36Sopenharmony_ci		.type = IIO_UVINDEX,
25062306a36Sopenharmony_ci		.info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED),
25162306a36Sopenharmony_ci	},
25262306a36Sopenharmony_ci};
25362306a36Sopenharmony_ci
25462306a36Sopenharmony_cistatic int zopt2201_read_raw(struct iio_dev *indio_dev,
25562306a36Sopenharmony_ci				struct iio_chan_spec const *chan,
25662306a36Sopenharmony_ci				int *val, int *val2, long mask)
25762306a36Sopenharmony_ci{
25862306a36Sopenharmony_ci	struct zopt2201_data *data = iio_priv(indio_dev);
25962306a36Sopenharmony_ci	u64 tmp;
26062306a36Sopenharmony_ci	int ret;
26162306a36Sopenharmony_ci
26262306a36Sopenharmony_ci	switch (mask) {
26362306a36Sopenharmony_ci	case IIO_CHAN_INFO_RAW:
26462306a36Sopenharmony_ci		ret = zopt2201_read(data, chan->address);
26562306a36Sopenharmony_ci		if (ret < 0)
26662306a36Sopenharmony_ci			return ret;
26762306a36Sopenharmony_ci		*val = ret;
26862306a36Sopenharmony_ci		return IIO_VAL_INT;
26962306a36Sopenharmony_ci	case IIO_CHAN_INFO_PROCESSED:
27062306a36Sopenharmony_ci		ret = zopt2201_read(data, ZOPT2201_UVB_DATA);
27162306a36Sopenharmony_ci		if (ret < 0)
27262306a36Sopenharmony_ci			return ret;
27362306a36Sopenharmony_ci		*val = ret * 18 *
27462306a36Sopenharmony_ci			(1 << (20 - zopt2201_resolution[data->res].bits)) /
27562306a36Sopenharmony_ci			zopt2201_gain_uvb[data->gain].gain;
27662306a36Sopenharmony_ci		return IIO_VAL_INT;
27762306a36Sopenharmony_ci	case IIO_CHAN_INFO_SCALE:
27862306a36Sopenharmony_ci		switch (chan->address) {
27962306a36Sopenharmony_ci		case ZOPT2201_ALS_DATA:
28062306a36Sopenharmony_ci			*val = zopt2201_gain_als[data->gain].scale;
28162306a36Sopenharmony_ci			break;
28262306a36Sopenharmony_ci		case ZOPT2201_UVB_DATA:
28362306a36Sopenharmony_ci			*val = zopt2201_gain_uvb[data->gain].scale;
28462306a36Sopenharmony_ci			break;
28562306a36Sopenharmony_ci		default:
28662306a36Sopenharmony_ci			return -EINVAL;
28762306a36Sopenharmony_ci		}
28862306a36Sopenharmony_ci
28962306a36Sopenharmony_ci		*val2 = 1000000;
29062306a36Sopenharmony_ci		*val2 *= (1 << (zopt2201_resolution[data->res].bits - 13));
29162306a36Sopenharmony_ci		tmp = div_s64(*val * 1000000ULL, *val2);
29262306a36Sopenharmony_ci		*val = div_s64_rem(tmp, 1000000, val2);
29362306a36Sopenharmony_ci
29462306a36Sopenharmony_ci		return IIO_VAL_INT_PLUS_MICRO;
29562306a36Sopenharmony_ci	case IIO_CHAN_INFO_INT_TIME:
29662306a36Sopenharmony_ci		*val = 0;
29762306a36Sopenharmony_ci		*val2 = zopt2201_resolution[data->res].us;
29862306a36Sopenharmony_ci		return IIO_VAL_INT_PLUS_MICRO;
29962306a36Sopenharmony_ci	default:
30062306a36Sopenharmony_ci		return -EINVAL;
30162306a36Sopenharmony_ci	}
30262306a36Sopenharmony_ci}
30362306a36Sopenharmony_ci
30462306a36Sopenharmony_cistatic int zopt2201_set_resolution(struct zopt2201_data *data, u8 res)
30562306a36Sopenharmony_ci{
30662306a36Sopenharmony_ci	int ret;
30762306a36Sopenharmony_ci
30862306a36Sopenharmony_ci	ret = i2c_smbus_write_byte_data(data->client, ZOPT2201_LS_MEAS_RATE,
30962306a36Sopenharmony_ci					(res << ZOPT2201_MEAS_RES_SHIFT) |
31062306a36Sopenharmony_ci					data->rate);
31162306a36Sopenharmony_ci	if (ret < 0)
31262306a36Sopenharmony_ci		return ret;
31362306a36Sopenharmony_ci
31462306a36Sopenharmony_ci	data->res = res;
31562306a36Sopenharmony_ci
31662306a36Sopenharmony_ci	return 0;
31762306a36Sopenharmony_ci}
31862306a36Sopenharmony_ci
31962306a36Sopenharmony_cistatic int zopt2201_write_resolution(struct zopt2201_data *data,
32062306a36Sopenharmony_ci				     int val, int val2)
32162306a36Sopenharmony_ci{
32262306a36Sopenharmony_ci	int i, ret;
32362306a36Sopenharmony_ci
32462306a36Sopenharmony_ci	if (val != 0)
32562306a36Sopenharmony_ci		return -EINVAL;
32662306a36Sopenharmony_ci
32762306a36Sopenharmony_ci	for (i = 0; i < ARRAY_SIZE(zopt2201_resolution); i++)
32862306a36Sopenharmony_ci		if (val2 == zopt2201_resolution[i].us) {
32962306a36Sopenharmony_ci			mutex_lock(&data->lock);
33062306a36Sopenharmony_ci			ret = zopt2201_set_resolution(data, i);
33162306a36Sopenharmony_ci			mutex_unlock(&data->lock);
33262306a36Sopenharmony_ci			return ret;
33362306a36Sopenharmony_ci		}
33462306a36Sopenharmony_ci
33562306a36Sopenharmony_ci	return -EINVAL;
33662306a36Sopenharmony_ci}
33762306a36Sopenharmony_ci
33862306a36Sopenharmony_cistatic int zopt2201_set_gain(struct zopt2201_data *data, u8 gain)
33962306a36Sopenharmony_ci{
34062306a36Sopenharmony_ci	int ret;
34162306a36Sopenharmony_ci
34262306a36Sopenharmony_ci	ret = i2c_smbus_write_byte_data(data->client, ZOPT2201_LS_GAIN, gain);
34362306a36Sopenharmony_ci	if (ret < 0)
34462306a36Sopenharmony_ci		return ret;
34562306a36Sopenharmony_ci
34662306a36Sopenharmony_ci	data->gain = gain;
34762306a36Sopenharmony_ci
34862306a36Sopenharmony_ci	return 0;
34962306a36Sopenharmony_ci}
35062306a36Sopenharmony_ci
35162306a36Sopenharmony_cistatic int zopt2201_write_scale_als_by_idx(struct zopt2201_data *data, int idx)
35262306a36Sopenharmony_ci{
35362306a36Sopenharmony_ci	int ret;
35462306a36Sopenharmony_ci
35562306a36Sopenharmony_ci	mutex_lock(&data->lock);
35662306a36Sopenharmony_ci	ret = zopt2201_set_resolution(data, zopt2201_scale_als[idx].res);
35762306a36Sopenharmony_ci	if (ret < 0)
35862306a36Sopenharmony_ci		goto unlock;
35962306a36Sopenharmony_ci
36062306a36Sopenharmony_ci	ret = zopt2201_set_gain(data, zopt2201_scale_als[idx].gain);
36162306a36Sopenharmony_ci
36262306a36Sopenharmony_ciunlock:
36362306a36Sopenharmony_ci	mutex_unlock(&data->lock);
36462306a36Sopenharmony_ci	return ret;
36562306a36Sopenharmony_ci}
36662306a36Sopenharmony_ci
36762306a36Sopenharmony_cistatic int zopt2201_write_scale_als(struct zopt2201_data *data,
36862306a36Sopenharmony_ci				     int val, int val2)
36962306a36Sopenharmony_ci{
37062306a36Sopenharmony_ci	int i;
37162306a36Sopenharmony_ci
37262306a36Sopenharmony_ci	for (i = 0; i < ARRAY_SIZE(zopt2201_scale_als); i++)
37362306a36Sopenharmony_ci		if (val == zopt2201_scale_als[i].scale &&
37462306a36Sopenharmony_ci		    val2 == zopt2201_scale_als[i].uscale) {
37562306a36Sopenharmony_ci			return zopt2201_write_scale_als_by_idx(data, i);
37662306a36Sopenharmony_ci		}
37762306a36Sopenharmony_ci
37862306a36Sopenharmony_ci	return -EINVAL;
37962306a36Sopenharmony_ci}
38062306a36Sopenharmony_ci
38162306a36Sopenharmony_cistatic int zopt2201_write_scale_uvb_by_idx(struct zopt2201_data *data, int idx)
38262306a36Sopenharmony_ci{
38362306a36Sopenharmony_ci	int ret;
38462306a36Sopenharmony_ci
38562306a36Sopenharmony_ci	mutex_lock(&data->lock);
38662306a36Sopenharmony_ci	ret = zopt2201_set_resolution(data, zopt2201_scale_als[idx].res);
38762306a36Sopenharmony_ci	if (ret < 0)
38862306a36Sopenharmony_ci		goto unlock;
38962306a36Sopenharmony_ci
39062306a36Sopenharmony_ci	ret = zopt2201_set_gain(data, zopt2201_scale_als[idx].gain);
39162306a36Sopenharmony_ci
39262306a36Sopenharmony_ciunlock:
39362306a36Sopenharmony_ci	mutex_unlock(&data->lock);
39462306a36Sopenharmony_ci	return ret;
39562306a36Sopenharmony_ci}
39662306a36Sopenharmony_ci
39762306a36Sopenharmony_cistatic int zopt2201_write_scale_uvb(struct zopt2201_data *data,
39862306a36Sopenharmony_ci				     int val, int val2)
39962306a36Sopenharmony_ci{
40062306a36Sopenharmony_ci	int i;
40162306a36Sopenharmony_ci
40262306a36Sopenharmony_ci	for (i = 0; i < ARRAY_SIZE(zopt2201_scale_uvb); i++)
40362306a36Sopenharmony_ci		if (val == zopt2201_scale_uvb[i].scale &&
40462306a36Sopenharmony_ci		    val2 == zopt2201_scale_uvb[i].uscale)
40562306a36Sopenharmony_ci			return zopt2201_write_scale_uvb_by_idx(data, i);
40662306a36Sopenharmony_ci
40762306a36Sopenharmony_ci	return -EINVAL;
40862306a36Sopenharmony_ci}
40962306a36Sopenharmony_ci
41062306a36Sopenharmony_cistatic int zopt2201_write_raw(struct iio_dev *indio_dev,
41162306a36Sopenharmony_ci			      struct iio_chan_spec const *chan,
41262306a36Sopenharmony_ci			      int val, int val2, long mask)
41362306a36Sopenharmony_ci{
41462306a36Sopenharmony_ci	struct zopt2201_data *data = iio_priv(indio_dev);
41562306a36Sopenharmony_ci
41662306a36Sopenharmony_ci	switch (mask) {
41762306a36Sopenharmony_ci	case IIO_CHAN_INFO_INT_TIME:
41862306a36Sopenharmony_ci		return zopt2201_write_resolution(data, val, val2);
41962306a36Sopenharmony_ci	case IIO_CHAN_INFO_SCALE:
42062306a36Sopenharmony_ci		switch (chan->address) {
42162306a36Sopenharmony_ci		case ZOPT2201_ALS_DATA:
42262306a36Sopenharmony_ci			return zopt2201_write_scale_als(data, val, val2);
42362306a36Sopenharmony_ci		case ZOPT2201_UVB_DATA:
42462306a36Sopenharmony_ci			return zopt2201_write_scale_uvb(data, val, val2);
42562306a36Sopenharmony_ci		default:
42662306a36Sopenharmony_ci			return -EINVAL;
42762306a36Sopenharmony_ci		}
42862306a36Sopenharmony_ci	}
42962306a36Sopenharmony_ci
43062306a36Sopenharmony_ci	return -EINVAL;
43162306a36Sopenharmony_ci}
43262306a36Sopenharmony_ci
43362306a36Sopenharmony_cistatic ssize_t zopt2201_show_int_time_available(struct device *dev,
43462306a36Sopenharmony_ci						struct device_attribute *attr,
43562306a36Sopenharmony_ci						char *buf)
43662306a36Sopenharmony_ci{
43762306a36Sopenharmony_ci	size_t len = 0;
43862306a36Sopenharmony_ci	int i;
43962306a36Sopenharmony_ci
44062306a36Sopenharmony_ci	for (i = 0; i < ARRAY_SIZE(zopt2201_resolution); i++)
44162306a36Sopenharmony_ci		len += scnprintf(buf + len, PAGE_SIZE - len, "0.%06lu ",
44262306a36Sopenharmony_ci				 zopt2201_resolution[i].us);
44362306a36Sopenharmony_ci	buf[len - 1] = '\n';
44462306a36Sopenharmony_ci
44562306a36Sopenharmony_ci	return len;
44662306a36Sopenharmony_ci}
44762306a36Sopenharmony_ci
44862306a36Sopenharmony_cistatic IIO_DEV_ATTR_INT_TIME_AVAIL(zopt2201_show_int_time_available);
44962306a36Sopenharmony_ci
45062306a36Sopenharmony_cistatic ssize_t zopt2201_show_als_scale_avail(struct device *dev,
45162306a36Sopenharmony_ci					     struct device_attribute *attr,
45262306a36Sopenharmony_ci					     char *buf)
45362306a36Sopenharmony_ci{
45462306a36Sopenharmony_ci	ssize_t len = 0;
45562306a36Sopenharmony_ci	int i;
45662306a36Sopenharmony_ci
45762306a36Sopenharmony_ci	for (i = 0; i < ARRAY_SIZE(zopt2201_scale_als); i++)
45862306a36Sopenharmony_ci		len += scnprintf(buf + len, PAGE_SIZE - len, "%d.%06u ",
45962306a36Sopenharmony_ci				 zopt2201_scale_als[i].scale,
46062306a36Sopenharmony_ci				 zopt2201_scale_als[i].uscale);
46162306a36Sopenharmony_ci	buf[len - 1] = '\n';
46262306a36Sopenharmony_ci
46362306a36Sopenharmony_ci	return len;
46462306a36Sopenharmony_ci}
46562306a36Sopenharmony_ci
46662306a36Sopenharmony_cistatic ssize_t zopt2201_show_uvb_scale_avail(struct device *dev,
46762306a36Sopenharmony_ci					     struct device_attribute *attr,
46862306a36Sopenharmony_ci					     char *buf)
46962306a36Sopenharmony_ci{
47062306a36Sopenharmony_ci	ssize_t len = 0;
47162306a36Sopenharmony_ci	int i;
47262306a36Sopenharmony_ci
47362306a36Sopenharmony_ci	for (i = 0; i < ARRAY_SIZE(zopt2201_scale_uvb); i++)
47462306a36Sopenharmony_ci		len += scnprintf(buf + len, PAGE_SIZE - len, "%d.%06u ",
47562306a36Sopenharmony_ci				 zopt2201_scale_uvb[i].scale,
47662306a36Sopenharmony_ci				 zopt2201_scale_uvb[i].uscale);
47762306a36Sopenharmony_ci	buf[len - 1] = '\n';
47862306a36Sopenharmony_ci
47962306a36Sopenharmony_ci	return len;
48062306a36Sopenharmony_ci}
48162306a36Sopenharmony_ci
48262306a36Sopenharmony_cistatic IIO_DEVICE_ATTR(in_illuminance_scale_available, 0444,
48362306a36Sopenharmony_ci		       zopt2201_show_als_scale_avail, NULL, 0);
48462306a36Sopenharmony_cistatic IIO_DEVICE_ATTR(in_intensity_uv_scale_available, 0444,
48562306a36Sopenharmony_ci		       zopt2201_show_uvb_scale_avail, NULL, 0);
48662306a36Sopenharmony_ci
48762306a36Sopenharmony_cistatic struct attribute *zopt2201_attributes[] = {
48862306a36Sopenharmony_ci	&iio_dev_attr_integration_time_available.dev_attr.attr,
48962306a36Sopenharmony_ci	&iio_dev_attr_in_illuminance_scale_available.dev_attr.attr,
49062306a36Sopenharmony_ci	&iio_dev_attr_in_intensity_uv_scale_available.dev_attr.attr,
49162306a36Sopenharmony_ci	NULL
49262306a36Sopenharmony_ci};
49362306a36Sopenharmony_ci
49462306a36Sopenharmony_cistatic const struct attribute_group zopt2201_attribute_group = {
49562306a36Sopenharmony_ci	.attrs = zopt2201_attributes,
49662306a36Sopenharmony_ci};
49762306a36Sopenharmony_ci
49862306a36Sopenharmony_cistatic const struct iio_info zopt2201_info = {
49962306a36Sopenharmony_ci	.read_raw = zopt2201_read_raw,
50062306a36Sopenharmony_ci	.write_raw = zopt2201_write_raw,
50162306a36Sopenharmony_ci	.attrs = &zopt2201_attribute_group,
50262306a36Sopenharmony_ci};
50362306a36Sopenharmony_ci
50462306a36Sopenharmony_cistatic int zopt2201_probe(struct i2c_client *client)
50562306a36Sopenharmony_ci{
50662306a36Sopenharmony_ci	struct zopt2201_data *data;
50762306a36Sopenharmony_ci	struct iio_dev *indio_dev;
50862306a36Sopenharmony_ci	int ret;
50962306a36Sopenharmony_ci
51062306a36Sopenharmony_ci	if (!i2c_check_functionality(client->adapter,
51162306a36Sopenharmony_ci				     I2C_FUNC_SMBUS_READ_I2C_BLOCK))
51262306a36Sopenharmony_ci		return -EOPNOTSUPP;
51362306a36Sopenharmony_ci
51462306a36Sopenharmony_ci	ret = i2c_smbus_read_byte_data(client, ZOPT2201_PART_ID);
51562306a36Sopenharmony_ci	if (ret < 0)
51662306a36Sopenharmony_ci		return ret;
51762306a36Sopenharmony_ci	if (ret != ZOPT2201_PART_NUMBER)
51862306a36Sopenharmony_ci		return -ENODEV;
51962306a36Sopenharmony_ci
52062306a36Sopenharmony_ci	indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data));
52162306a36Sopenharmony_ci	if (!indio_dev)
52262306a36Sopenharmony_ci		return -ENOMEM;
52362306a36Sopenharmony_ci
52462306a36Sopenharmony_ci	data = iio_priv(indio_dev);
52562306a36Sopenharmony_ci	i2c_set_clientdata(client, indio_dev);
52662306a36Sopenharmony_ci	data->client = client;
52762306a36Sopenharmony_ci	mutex_init(&data->lock);
52862306a36Sopenharmony_ci
52962306a36Sopenharmony_ci	indio_dev->info = &zopt2201_info;
53062306a36Sopenharmony_ci	indio_dev->channels = zopt2201_channels;
53162306a36Sopenharmony_ci	indio_dev->num_channels = ARRAY_SIZE(zopt2201_channels);
53262306a36Sopenharmony_ci	indio_dev->name = ZOPT2201_DRV_NAME;
53362306a36Sopenharmony_ci	indio_dev->modes = INDIO_DIRECT_MODE;
53462306a36Sopenharmony_ci
53562306a36Sopenharmony_ci	data->rate = ZOPT2201_MEAS_FREQ_100MS;
53662306a36Sopenharmony_ci	ret = zopt2201_set_resolution(data, ZOPT2201_MEAS_RES_18BIT);
53762306a36Sopenharmony_ci	if (ret < 0)
53862306a36Sopenharmony_ci		return ret;
53962306a36Sopenharmony_ci
54062306a36Sopenharmony_ci	ret = zopt2201_set_gain(data, ZOPT2201_LS_GAIN_3);
54162306a36Sopenharmony_ci	if (ret < 0)
54262306a36Sopenharmony_ci		return ret;
54362306a36Sopenharmony_ci
54462306a36Sopenharmony_ci	return devm_iio_device_register(&client->dev, indio_dev);
54562306a36Sopenharmony_ci}
54662306a36Sopenharmony_ci
54762306a36Sopenharmony_cistatic const struct i2c_device_id zopt2201_id[] = {
54862306a36Sopenharmony_ci	{ "zopt2201", 0 },
54962306a36Sopenharmony_ci	{ }
55062306a36Sopenharmony_ci};
55162306a36Sopenharmony_ciMODULE_DEVICE_TABLE(i2c, zopt2201_id);
55262306a36Sopenharmony_ci
55362306a36Sopenharmony_cistatic struct i2c_driver zopt2201_driver = {
55462306a36Sopenharmony_ci	.driver = {
55562306a36Sopenharmony_ci		.name   = ZOPT2201_DRV_NAME,
55662306a36Sopenharmony_ci	},
55762306a36Sopenharmony_ci	.probe = zopt2201_probe,
55862306a36Sopenharmony_ci	.id_table = zopt2201_id,
55962306a36Sopenharmony_ci};
56062306a36Sopenharmony_ci
56162306a36Sopenharmony_cimodule_i2c_driver(zopt2201_driver);
56262306a36Sopenharmony_ci
56362306a36Sopenharmony_ciMODULE_AUTHOR("Peter Meerwald-Stadler <pmeerw@pmeerw.net>");
56462306a36Sopenharmony_ciMODULE_DESCRIPTION("IDT ZOPT2201 ambient light and UV B sensor driver");
56562306a36Sopenharmony_ciMODULE_LICENSE("GPL");
566