The AMS TCS3430 is a XYZ tristimulus color sensor, with an additional infrared channel. This driver implements support for all channels, with configurable integration time and gain. Both direct reading and triggered-buffer modes are supported. The device's interrupt can be used to sample all channels at the end of conversion and is optional. A thereshold mechanism is available in hardware but only applies to the Z channel. As a result it is of very low interest and was not implemented. Signed-off-by: Paul Kocialkowski <paulk@xxxxxxxxxxx> --- drivers/iio/light/Kconfig | 10 + drivers/iio/light/Makefile | 1 + drivers/iio/light/tcs3430.c | 997 ++++++++++++++++++++++++++++++++++++ 3 files changed, 1008 insertions(+) create mode 100644 drivers/iio/light/tcs3430.c diff --git a/drivers/iio/light/Kconfig b/drivers/iio/light/Kconfig index 6d0cc1eecae0..4aa66dee5582 100644 --- a/drivers/iio/light/Kconfig +++ b/drivers/iio/light/Kconfig @@ -585,6 +585,16 @@ config TCS3414 This driver can also be built as a module. If so, the module will be called tcs3414. +config TCS3430 + tristate "AMS TCS3430 XYZ tristimulus color sensor driver" + depends on I2C && PM + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + Support for the AMS TCS3430 XYZ tristimulus color sensor. + + This driver can be built-in or built as a module, called tcs3430. + config TCS3472 tristate "TAOS TCS3472 color light-to-digital converter" depends on I2C diff --git a/drivers/iio/light/Makefile b/drivers/iio/light/Makefile index f2031e6236f9..1a2175753dab 100644 --- a/drivers/iio/light/Makefile +++ b/drivers/iio/light/Makefile @@ -54,6 +54,7 @@ obj-$(CONFIG_ST_UVIS25) += st_uvis25_core.o obj-$(CONFIG_ST_UVIS25_I2C) += st_uvis25_i2c.o obj-$(CONFIG_ST_UVIS25_SPI) += st_uvis25_spi.o obj-$(CONFIG_TCS3414) += tcs3414.o +obj-$(CONFIG_TCS3430) += tcs3430.o obj-$(CONFIG_TCS3472) += tcs3472.o obj-$(CONFIG_SENSORS_TSL2563) += tsl2563.o obj-$(CONFIG_TSL2583) += tsl2583.o diff --git a/drivers/iio/light/tcs3430.c b/drivers/iio/light/tcs3430.c new file mode 100644 index 000000000000..553b088e5769 --- /dev/null +++ b/drivers/iio/light/tcs3430.c @@ -0,0 +1,997 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright 2024 Paul Kocialkowski <paulk@xxxxxxxxxxx> + */ + +#include <linux/delay.h> +#include <linux/i2c.h> +#include <linux/iio/iio.h> +#include <linux/iio/trigger_consumer.h> +#include <linux/iio/triggered_buffer.h> +#include <linux/module.h> +#include <linux/pm_runtime.h> +#include <linux/regulator/consumer.h> + +#define TCS3430_ENABLE 0x80 +#define TCS3430_ENABLE_PON BIT(0) +#define TCS3430_ENABLE_AEN BIT(1) +#define TCS3430_ENABLE_WEN BIT(3) + +#define TCS3430_ATIME 0x81 +#define TCS3430_ATIME_CYCLES(v) (((v) - 1) & GENMASK(7, 0)) + +#define TCS3430_WTIME 0x83 +#define TCS3430_WTIME_CYCLES(v) (((v) - 1) & GENMASK(7, 0)) + +#define TCS3430_AILTL 0x84 +#define TCS3430_AILTH 0x85 +#define TCS3430_AIHTL 0x86 +#define TCS3430_AIHTH 0x87 + +#define TCS3430_PERS 0x8c +#define TCS3430_PERS_EVERY_CYCLE 0 +#define TCS3430_PERS_OUTSIDE_THRESHOLD 1 + +#define TCS3430_CFG0 0x8d +#define TCS3430_CFG0_WLONG BIT(2) +#define TCS3430_CFG0_RESERVED (0x10 << 3) + +#define TCS3430_CFG1 0x90 +#define TCS3430_CFG1_AGAIN_1X 0 +#define TCS3430_CFG1_AGAIN_4X 1 +#define TCS3430_CFG1_AGAIN_16X 2 +#define TCS3430_CFG1_AGAIN_64X 3 +#define TCS3430_CFG1_AMUX_X 0 +#define TCS3430_CFG1_AMUX_IR2 BIT(3) + +#define TCS3430_REVID 0x91 + +#define TCS3430_ID 0x92 +#define TCS3430_ID_VALUE(v) (((v) & GENMASK(7, 2)) >> 2) +#define TCS3430_ID_TCS3430 0x37 + +#define TCS3430_STATUS 0x93 +#define TCS3430_STATUS_AINT BIT(4) +#define TCS3430_STATUS_ASAT BIT(7) + +#define TCS3430_CH0DATAL 0x94 +#define TCS3430_CH0DATAH 0x95 +#define TCS3430_CH1DATAL 0x96 +#define TCS3430_CH1DATAH 0x97 +#define TCS3430_CH2DATAL 0x98 +#define TCS3430_CH2DATAH 0x99 +#define TCS3430_CH3DATAL 0x9a +#define TCS3430_CH3DATAH 0x9b + +#define TCS3430_CFG2 0x9f +#define TCS3430_CFG2_RESERVED 0x4 +#define TCS3430_CFG2_HGAIN_128X BIT(4) + +#define TCS3430_CFG3 0xab +#define TCS3430_CFG3_RESERVED 0xc +#define TCS3430_CFG3_SAI BIT(4) +#define TCS3430_CFG3_INT_READ_CLEAR BIT(7) + +#define TCS3430_AZ_CONFIG 0xd6 +#define TCS3430_AZ_CONFIG_NTH(v) ((v) & GENMASK(6, 0)) +#define TCS3430_AZ_CONFIG_NTH_FIRST 0x7f +#define TCS3430_AZ_CONFIG_START_ZERO 0 +#define TCS3430_AZ_CONFIG_START_OFFSET BIT(7) + +#define TCS3430_INTENAB 0xdd +#define TCS3430_INTENAB_AIEN BIT(4) +#define TCS3430_INTENAB_ASIEN BIT(7) + +#define TCS3430_ATIME_CYCLE_TIME_US 2780 +#define TCS3430_ATIME_CYCLES_MAX 256 + +struct tcs3430_sensor_state { + bool active; + /* integration time */ + u16 atime_cycles; + /* wait time */ + u16 wtime_cycles; + bool wtime_long; + bool wtime_enabled; + /* analog gain */ + u8 again; +}; + +struct tcs3430_sensor_scan { + u16 channels[4]; + s64 timestamp __aligned(8); +}; + +struct tcs3430_sensor { + struct device *dev; + struct i2c_client *i2c_client; + struct iio_dev *iio_dev; + + struct regulator *vdd_supply; + + struct tcs3430_sensor_state state; + struct tcs3430_sensor_scan scan; + int atime_available[2 * TCS3430_ATIME_CYCLES_MAX]; + bool scan_sync; + struct mutex lock; +}; + +static int tcs3430_state_configure_power(struct tcs3430_sensor *sensor); + +static const int tcs3430_again_values[] = { + 1, 4, 16, 64, 128 +}; + +static void tcs3430_atime_available_setup(struct tcs3430_sensor *sensor) +{ + unsigned int atime_us; + unsigned int index; + unsigned int i; + + for (i = 0; i < TCS3430_ATIME_CYCLES_MAX; i++) { + atime_us = (i + 1) * TCS3430_ATIME_CYCLE_TIME_US; + + sensor->atime_available[index] = atime_us / USEC_PER_SEC; + index++; + sensor->atime_available[index] = atime_us % USEC_PER_SEC; + index++; + } +} + +static int tcs3430_data_scan(struct tcs3430_sensor *sensor, + struct tcs3430_sensor_scan *scan) +{ + struct i2c_client *i2c_client = sensor->i2c_client; + int channels_map[] = { 2, 1, 3, 0 }; + u8 values[8] = { 0 }; + unsigned int index = 0; + unsigned int i; + int ret; + + /* Read all values in one transaction to ensure coherency. */ + ret = i2c_smbus_read_i2c_block_data(i2c_client, TCS3430_CH0DATAL, 8, + values); + if (ret < 0) + return ret; + + for (i = 0; i < 4; i++) { + int scan_index; + u16 value; + + value = values[index + 1] << 8 | values[index]; + index += 2; + + scan_index = channels_map[i]; + if (scan_index < 0) + continue; + + scan->channels[scan_index] = value; + } + + return 0; +} + +static int tcs3430_identify(struct tcs3430_sensor *sensor) +{ + struct device *dev = sensor->dev; + int ret; + u8 id; + + ret = pm_runtime_resume_and_get(dev); + if (ret < 0) + return ret; + + ret = i2c_smbus_read_byte_data(sensor->i2c_client, TCS3430_ID); + if (ret < 0) + goto complete; + + id = (u8)TCS3430_ID_VALUE(ret); + + switch (id) { + case TCS3430_ID_TCS3430: + dev_info(dev, "identified TCS3430 sensor\n"); + ret = 0; + break; + default: + dev_err(dev, "unknown sensor with id: %#x\n", id); + ret = -ENODEV; + break; + } + +complete: + pm_runtime_put_sync(dev); + + return ret; +} + +static int tcs3430_power(struct tcs3430_sensor *sensor, bool on) +{ + struct tcs3430_sensor_state *state = &sensor->state; + int ret; + + mutex_lock(&sensor->lock); + + state->active = on; + ret = tcs3430_state_configure_power(sensor); + + mutex_unlock(&sensor->lock); + + return ret; +} + +/* State */ + +static int tcs3430_state_configure_power(struct tcs3430_sensor *sensor) +{ + struct tcs3430_sensor_state *state = &sensor->state; + struct i2c_client *i2c_client = sensor->i2c_client; + u8 value; + int ret; + + ret = i2c_smbus_read_byte_data(i2c_client, TCS3430_ENABLE); + if (ret < 0) + return ret; + + value = (u8)ret; + + if (state->active) + value |= TCS3430_ENABLE_PON | TCS3430_ENABLE_AEN; + else + value &= ~(TCS3430_ENABLE_PON | TCS3430_ENABLE_AEN); + + ret = i2c_smbus_write_byte_data(i2c_client, TCS3430_ENABLE, value); + if (ret < 0) + return ret; + + return 0; +} + +static int tcs3430_state_configure_atime(struct tcs3430_sensor *sensor) +{ + struct tcs3430_sensor_state *state = &sensor->state; + struct i2c_client *i2c_client = sensor->i2c_client; + u8 value; + int ret; + + value = TCS3430_ATIME_CYCLES(state->atime_cycles); + + ret = i2c_smbus_write_byte_data(i2c_client, TCS3430_ATIME, value); + if (ret < 0) + return ret; + + return 0; +} + +static int tcs3430_state_configure_wtime(struct tcs3430_sensor *sensor) +{ + struct tcs3430_sensor_state *state = &sensor->state; + struct i2c_client *i2c_client = sensor->i2c_client; + u8 value; + int ret; + + if (state->wtime_long) { + value = TCS3430_CFG0_WLONG | TCS3430_CFG0_RESERVED; + + ret = i2c_smbus_write_byte_data(i2c_client, TCS3430_CFG0, + value); + if (ret < 0) + return ret; + } + + value = TCS3430_WTIME_CYCLES(state->wtime_cycles); + + ret = i2c_smbus_write_byte_data(i2c_client, TCS3430_WTIME, value); + if (ret < 0) + return ret; + + ret = i2c_smbus_read_byte_data(sensor->i2c_client, TCS3430_ENABLE); + if (ret < 0) + return ret; + + value = (u8)ret; + + if (state->wtime_enabled) + value |= TCS3430_ENABLE_WEN; + else + value &= ~TCS3430_ENABLE_WEN; + + ret = i2c_smbus_write_byte_data(sensor->i2c_client, TCS3430_ENABLE, + value); + if (ret < 0) + return ret; + + return 0; +} + +static int tcs3430_state_configure_again(struct tcs3430_sensor *sensor) +{ + struct tcs3430_sensor_state *state = &sensor->state; + struct i2c_client *i2c_client = sensor->i2c_client; + u8 value; + int ret; + + value = TCS3430_CFG2_RESERVED; + + if (state->again == 128) + value |= TCS3430_CFG2_HGAIN_128X; + + ret = i2c_smbus_write_byte_data(i2c_client, TCS3430_CFG2, value); + if (ret < 0) + return ret; + + /* Alwawys mux CH3 to X channel. */ + value = TCS3430_CFG1_AMUX_X; + + switch (state->again) { + case 1: + value |= TCS3430_CFG1_AGAIN_1X; + break; + case 4: + value |= TCS3430_CFG1_AGAIN_4X; + break; + case 16: + value |= TCS3430_CFG1_AGAIN_16X; + break; + case 64: + case 128: + value |= TCS3430_CFG1_AGAIN_64X; + break; + default: + return -EINVAL; + } + + ret = i2c_smbus_write_byte_data(i2c_client, TCS3430_CFG1, value); + if (ret < 0) + return ret; + + return 0; +} + +static int tcs3430_state_configure_az(struct tcs3430_sensor *sensor) +{ + struct i2c_client *i2c_client = sensor->i2c_client; + u8 value; + int ret; + + /* Only run auto-zero at first cycle, start search at zero. */ + value = TCS3430_AZ_CONFIG_NTH_FIRST | TCS3430_AZ_CONFIG_START_ZERO; + + ret = i2c_smbus_write_byte_data(i2c_client, TCS3430_AZ_CONFIG, value); + if (ret < 0) + return ret; + + return 0; +} + +static int tcs3430_state_configure_interrupt(struct tcs3430_sensor *sensor) +{ + struct i2c_client *i2c_client = sensor->i2c_client; + u8 value; + int ret; + + if (i2c_client->irq) + value = TCS3430_INTENAB_AIEN; + else + value = 0; + + ret = i2c_smbus_write_byte_data(i2c_client, TCS3430_INTENAB, value); + if (ret < 0) + return ret; + + /* Clear flags with status read, don't sleep after interrupt. */ + value = TCS3430_CFG3_RESERVED | TCS3430_CFG3_INT_READ_CLEAR; + + ret = i2c_smbus_write_byte_data(i2c_client, TCS3430_CFG3, value); + if (ret < 0) + return ret; + + /* Enable interrupt for every ALS cycle. */ + value = TCS3430_PERS_EVERY_CYCLE; + ret = i2c_smbus_write_byte_data(i2c_client, TCS3430_PERS, value); + if (ret < 0) + return ret; + + return 0; +} + +static int tcs3430_state_configure(struct tcs3430_sensor *sensor) +{ + int ret; + + mutex_lock(&sensor->lock); + + /* Configure integration time. */ + ret = tcs3430_state_configure_atime(sensor); + if (ret) + goto complete; + + /* Configure wait time. */ + ret = tcs3430_state_configure_wtime(sensor); + if (ret) + goto complete; + + /* Configure analog gain. */ + ret = tcs3430_state_configure_again(sensor); + if (ret) + goto complete; + + /* Configure auto-zero. */ + ret = tcs3430_state_configure_az(sensor); + if (ret) + goto complete; + + /* Configure interrupt. */ + ret = tcs3430_state_configure_interrupt(sensor); + if (ret) + goto complete; + + /* Configure power. */ + ret = tcs3430_state_configure_power(sensor); + if (ret) + goto complete; + +complete: + mutex_unlock(&sensor->lock); + + return ret; +} + +static void tcs3430_state_reset(struct tcs3430_sensor *sensor) +{ + struct tcs3430_sensor_state *state = &sensor->state; + + /* At least 64 cycles are required to reach full scale. */ + state->atime_cycles = 64; + + /* No wait time between samples outside of trigger mode. */ + state->wtime_cycles = 0; + state->wtime_long = false; + state->wtime_enabled = false; + + /* Start with a low gain. */ + state->again = 4; +} + +/* IIO */ + +static const struct iio_chan_spec tcs3430_iio_channels[] = { + { + .type = IIO_INTENSITY, + .channel2 = IIO_MOD_X, + .address = TCS3430_CH3DATAL, + .modified = 1, + + .scan_index = 0, + .scan_type = { + .sign = 'u', + .realbits = 16, + .storagebits = 16, + .endianness = IIO_LE, + }, + + .info_mask_separate = + BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_type = + BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_HARDWAREGAIN), + .info_mask_shared_by_type_available = + BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_HARDWAREGAIN), + }, + { + .type = IIO_INTENSITY, + .channel2 = IIO_MOD_Y, + .address = TCS3430_CH1DATAL, + .modified = 1, + + .scan_index = 1, + .scan_type = { + .sign = 'u', + .realbits = 16, + .storagebits = 16, + .endianness = IIO_LE, + }, + + .info_mask_separate = + BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_type = + BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_HARDWAREGAIN), + .info_mask_shared_by_type_available = + BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_HARDWAREGAIN), + }, + { + .type = IIO_INTENSITY, + .channel2 = IIO_MOD_Z, + .address = TCS3430_CH0DATAL, + .modified = 1, + + .scan_index = 2, + .scan_type = { + .sign = 'u', + .realbits = 16, + .storagebits = 16, + .endianness = IIO_LE, + }, + + .info_mask_separate = + BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_type = + BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_HARDWAREGAIN), + .info_mask_shared_by_type_available = + BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_HARDWAREGAIN), + }, + { + .type = IIO_INTENSITY, + .channel2 = IIO_MOD_LIGHT_IR, + .address = TCS3430_CH2DATAL, + .modified = 1, + + .scan_index = 3, + .scan_type = { + .sign = 'u', + .realbits = 16, + .storagebits = 16, + .endianness = IIO_LE, + }, + + .info_mask_separate = + BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_type = + BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_HARDWAREGAIN), + .info_mask_shared_by_type_available = + BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_HARDWAREGAIN), + }, + IIO_CHAN_SOFT_TIMESTAMP(4), +}; + +static int tcs3430_iio_read_raw(struct iio_dev *iio_dev, + struct iio_chan_spec const *channel, + int *value_first, int *value_second, long mask) +{ + struct tcs3430_sensor *sensor = iio_priv(iio_dev); + struct tcs3430_sensor_state *state = &sensor->state; + struct i2c_client *i2c_client = sensor->i2c_client; + struct device *dev = sensor->dev; + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + unsigned int scan_index; + + ret = iio_device_claim_direct_mode(iio_dev); + if (ret) + return ret; + + ret = pm_runtime_resume_and_get(dev); + if (ret < 0) + goto release_direct_mode; + + if (sensor->scan_sync) { + scan_index = channel->scan_index; + + mutex_lock(&sensor->lock); + ret = (int)sensor->scan.channels[scan_index]; + mutex_unlock(&sensor->lock); + } else { + ret = i2c_smbus_read_word_data(i2c_client, + channel->address); + } + + pm_runtime_mark_last_busy(dev); + pm_runtime_put_autosuspend(dev); + +release_direct_mode: + iio_device_release_direct_mode(iio_dev); + + if (ret < 0) + return ret; + + *value_first = ret; + + return IIO_VAL_INT; + + case IIO_CHAN_INFO_INT_TIME: + unsigned int atime_us = state->atime_cycles * + TCS3430_ATIME_CYCLE_TIME_US; + + *value_first = atime_us / USEC_PER_SEC; + *value_second = atime_us % USEC_PER_SEC; + + return IIO_VAL_INT_PLUS_MICRO; + + case IIO_CHAN_INFO_HARDWAREGAIN: + *value_first = state->again; + + return IIO_VAL_INT; + + default: + return -EINVAL; + } +} + +static int tcs3430_iio_read_avail(struct iio_dev *iio_dev, + struct iio_chan_spec const *channel, + const int **values, int *type, int *length, + long mask) +{ + struct tcs3430_sensor *sensor = iio_priv(iio_dev); + + switch (mask) { + case IIO_CHAN_INFO_HARDWAREGAIN: + *length = ARRAY_SIZE(tcs3430_again_values); + *values = tcs3430_again_values; + *type = IIO_VAL_INT; + return IIO_AVAIL_LIST; + + case IIO_CHAN_INFO_INT_TIME: + *length = ARRAY_SIZE(sensor->atime_available); + *values = sensor->atime_available; + *type = IIO_VAL_INT_PLUS_MICRO; + return IIO_AVAIL_LIST; + + default: + return -EINVAL; + } +} + +static int tcs3430_iio_write_raw(struct iio_dev *iio_dev, + struct iio_chan_spec const *channel, + int value_first, int value_second, long mask) +{ + struct tcs3430_sensor *sensor = iio_priv(iio_dev); + struct tcs3430_sensor_state *state = &sensor->state; + struct device *dev = sensor->dev; + int ret; + + switch (mask) { + case IIO_CHAN_INFO_INT_TIME: + unsigned int atime_us = value_first * USEC_PER_SEC + + value_second; + unsigned int cycles = atime_us / TCS3430_ATIME_CYCLE_TIME_US; + unsigned int rest = atime_us % TCS3430_ATIME_CYCLE_TIME_US; + + if (rest || !cycles || cycles > TCS3430_ATIME_CYCLES_MAX) + return -EINVAL; + + state->atime_cycles = (u16)cycles; + + if (pm_runtime_suspended(dev)) + return 0; + + ret = tcs3430_state_configure_atime(sensor); + if (ret) + return ret; + + return 0; + + case IIO_CHAN_INFO_HARDWAREGAIN: + unsigned int i; + bool valid = false; + + for (i = 0; i < ARRAY_SIZE(tcs3430_again_values); i++) { + if (tcs3430_again_values[i] == value_first) { + valid = true; + break; + } + } + + if (!valid) + return -EINVAL; + + state->again = (u16)value_first; + + if (pm_runtime_suspended(dev)) + return 0; + + ret = tcs3430_state_configure_again(sensor); + if (ret) + return ret; + + return 0; + + default: + return -EINVAL; + } +} + +static const struct iio_info tcs3430_iio_info = { + .read_raw = tcs3430_iio_read_raw, + .read_avail = tcs3430_iio_read_avail, + .write_raw = tcs3430_iio_write_raw, +}; + +static irqreturn_t tcs3430_iio_buffer_trigger(int irq, void *data) +{ + struct iio_poll_func *poll_func = data; + struct iio_dev *iio_dev = poll_func->indio_dev; + struct tcs3430_sensor *sensor = iio_priv(iio_dev); + struct tcs3430_sensor_scan scan = { 0 }; + s64 timestamp; + unsigned int index = 0; + unsigned int i; + int ret; + + /* Capture timestamp just before reading values. */ + timestamp = iio_get_time_ns(iio_dev); + + mutex_lock(&sensor->lock); + + if (!sensor->scan_sync) { + ret = tcs3430_data_scan(sensor, &sensor->scan); + if (ret) + goto complete; + } + + for_each_set_bit(i, iio_dev->active_scan_mask, iio_dev->masklength) { + /* Assume scan index matches array index. */ + const struct iio_chan_spec *channel = &tcs3430_iio_channels[i]; + unsigned int scan_index = channel->scan_index; + + /* Only active channels are reported, in order. */ + scan.channels[index] = sensor->scan.channels[scan_index]; + index++; + } + + iio_push_to_buffers_with_timestamp(iio_dev, &scan, timestamp); + +complete: + mutex_unlock(&sensor->lock); + + iio_trigger_notify_done(iio_dev->trig); + + return IRQ_HANDLED; +} + +static int tcs3430_iio_buffer_preenable(struct iio_dev *iio_dev) +{ + struct tcs3430_sensor *sensor = iio_priv(iio_dev); + struct device *dev = sensor->dev; + + return pm_runtime_resume_and_get(dev); +} + +static int tcs3430_iio_buffer_postdisable(struct iio_dev *iio_dev) +{ + struct tcs3430_sensor *sensor = iio_priv(iio_dev); + struct device *dev = sensor->dev; + + pm_runtime_mark_last_busy(dev); + pm_runtime_put_autosuspend(dev); + + return 0; +} + +static const struct iio_buffer_setup_ops tcs3430_iio_buffer_setup_ops = { + .preenable = tcs3430_iio_buffer_preenable, + .postdisable = tcs3430_iio_buffer_postdisable, +}; + +static int tcs3430_iio_setup(struct tcs3430_sensor *sensor) +{ + struct iio_dev *iio_dev = sensor->iio_dev; + struct device *dev = sensor->dev; + int ret; + + iio_dev->info = &tcs3430_iio_info; + iio_dev->name = "tcs3430"; + iio_dev->channels = tcs3430_iio_channels; + iio_dev->num_channels = ARRAY_SIZE(tcs3430_iio_channels); + iio_dev->modes = INDIO_DIRECT_MODE; + + ret = devm_iio_triggered_buffer_setup(dev, iio_dev, NULL, + tcs3430_iio_buffer_trigger, + &tcs3430_iio_buffer_setup_ops); + if (ret) { + dev_err(dev, "failed to setup iio triggered buffer\n"); + return ret; + } + + ret = iio_device_register(iio_dev); + if (ret) { + dev_err(dev, "failed to register iio device\n"); + return ret; + } + + return 0; +} + +static void tcs3430_iio_cleanup(struct tcs3430_sensor *sensor) +{ + iio_device_unregister(sensor->iio_dev); +} + +/* IRQ */ + +static irqreturn_t tcs3430_irq(int irq, void *data) +{ + struct tcs3430_sensor *sensor = data; + struct tcs3430_sensor_state *state = &sensor->state; + u8 status; + int ret; + + /* Status register is reset by reading. */ + ret = i2c_smbus_read_byte_data(sensor->i2c_client, TCS3430_STATUS); + if (ret < 0) + return IRQ_HANDLED; + + status = (u8)ret; + + mutex_lock(&sensor->lock); + + if (!state->active) + goto complete; + + if (status & TCS3430_STATUS_AINT) + tcs3430_data_scan(sensor, &sensor->scan); + +complete: + mutex_unlock(&sensor->lock); + + return IRQ_HANDLED; +} + +/* PM */ + +static int tcs3430_suspend(struct device *dev) +{ + struct tcs3430_sensor *sensor = dev_get_drvdata(dev); + int ret; + + ret = tcs3430_power(sensor, 0); + if (ret) + goto error; + + ret = regulator_disable(sensor->vdd_supply); + if (ret) + goto error; + + return 0; + +error: + return -EAGAIN; +} + +static int tcs3430_resume(struct device *dev) +{ + struct tcs3430_sensor *sensor = dev_get_drvdata(dev); + int ret; + + ret = regulator_enable(sensor->vdd_supply); + if (ret) + goto error; + + /* Wait for the regulator to settle and the chip to power-on. */ + udelay(30); + + ret = tcs3430_state_configure(sensor); + if (ret) + goto error_regulator; + + ret = tcs3430_power(sensor, 1); + if (ret) + goto error_regulator; + + /* + * Wait a bit longer than integration time (cycles of 2.78 ms) for the + * first samples to be available. + */ + msleep(sensor->state.atime_cycles * 3); + + return 0; + +error_regulator: + regulator_disable(sensor->vdd_supply); + +error: + return -EAGAIN; +} + +static const struct dev_pm_ops tcs3430_pm_ops = { + .runtime_suspend = tcs3430_suspend, + .runtime_resume = tcs3430_resume, +}; + +/* I2C */ + +static int tcs3430_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct tcs3430_sensor *sensor; + struct iio_dev *iio_dev; + int irq = client->irq; + int ret; + + iio_dev = devm_iio_device_alloc(dev, sizeof(*sensor)); + if (!iio_dev) + return -ENOMEM; + + sensor = iio_priv(iio_dev); + + sensor->dev = dev; + sensor->i2c_client = client; + sensor->iio_dev = iio_dev; + + i2c_set_clientdata(client, sensor); + + mutex_init(&sensor->lock); + + sensor->vdd_supply = devm_regulator_get(dev, "vdd"); + if (IS_ERR(sensor->vdd_supply)) { + dev_err(dev, "failed to get VDD regulator\n"); + return PTR_ERR(sensor->vdd_supply); + } + + tcs3430_atime_available_setup(sensor); + tcs3430_state_reset(sensor); + + devm_pm_runtime_enable(dev); + + ret = tcs3430_identify(sensor); + if (ret) + return ret; + + pm_runtime_set_autosuspend_delay(&client->dev, 5000); + pm_runtime_use_autosuspend(&client->dev); + + if (irq > 0) { + ret = devm_request_threaded_irq(dev, irq, NULL, tcs3430_irq, + IRQF_TRIGGER_LOW | IRQF_ONESHOT, + "tcs3430", sensor); + if (ret) { + dev_err(dev, "failed to request irq\n"); + return ret; + } + + sensor->scan_sync = true; + } + + ret = tcs3430_iio_setup(sensor); + if (ret) + return ret; + + return 0; +} + +static void tcs3430_remove(struct i2c_client *client) +{ + struct tcs3430_sensor *sensor = i2c_get_clientdata(client); + + tcs3430_iio_cleanup(sensor); +} + +static const struct i2c_device_id tcs3430_id[] = { + { "tcs3430" }, + { } +}; + +MODULE_DEVICE_TABLE(i2c, tcs3430_id); + +static const struct of_device_id tcs3430_of_match[] = { + { .compatible = "ams,tcs3430" }, + { } +}; + +MODULE_DEVICE_TABLE(of, tcs3430_of_match); + +static struct i2c_driver tcs3430_driver = { + .driver = { + .name = "tcs3430", + .of_match_table = tcs3430_of_match, + .pm = &tcs3430_pm_ops, + }, + .id_table = tcs3430_id, + .probe = tcs3430_probe, + .remove = tcs3430_remove, +}; + +module_i2c_driver(tcs3430_driver); + +MODULE_AUTHOR("Paul Kocialkowski <paulk@xxxxxxxxxxx>"); +MODULE_DESCRIPTION("AMS TCS3430 XYZ tristimulus color sensor driver"); +MODULE_LICENSE("GPL"); -- 2.47.0