diff --git a/Documentation/devicetree/bindings/iio/light/amstaos,tcs3472.yaml b/Documentation/devicetree/bindings/iio/light/amstaos,tcs3472.yaml new file mode 100644 index 00000000000000..3bdc61ae1c07ae --- /dev/null +++ b/Documentation/devicetree/bindings/iio/light/amstaos,tcs3472.yaml @@ -0,0 +1,84 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/iio/light/amstaos,tcs3472.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: AMS/TAOS TCS3472/TMD3782 Color Light and Proximity Sensor + +maintainers: + - Peter Meerwald + +description: | + RGBC color light-to-digital converter with optional proximity detection. + TCS3472 provides 4-channel RGBC sensing. TMD3782 adds an integrated + proximity detector with IR LED driver. The TMD3782 family also includes + TMD37825/TMD37827 variants at I2C address 0x29 (same die, untested). + + TCS3472 datasheet: + https://ams.com/documents/20143/36005/TCS3472_DS000190_1-00.pdf + +properties: + compatible: + enum: + - amstaos,tcs3472 + - amstaos,tmd3782 + + reg: + maxItems: 1 + + interrupts: + maxItems: 1 + + vdd-supply: + description: Regulator that provides power to the sensor + + vddio-supply: + description: Regulator that provides power to the I2C bus + + vled-supply: + description: Regulator that provides power to the proximity IR LED + + led-max-microamp: + description: Maximum proximity LED drive current in microamps + enum: + - 12500 + - 25000 + - 50000 + - 100000 + + amstaos,proximity-pulse-count: + description: + Number of proximity IR LED pulses per measurement cycle. + Higher values increase signal strength at the cost of power. + $ref: /schemas/types.yaml#/definitions/uint32 + minimum: 1 + maximum: 255 + default: 8 + +required: + - compatible + - reg + +additionalProperties: false + +examples: + - | + #include + + i2c { + #address-cells = <1>; + #size-cells = <0>; + + light-sensor@39 { + compatible = "amstaos,tmd3782"; + reg = <0x39>; + interrupts = <113 IRQ_TYPE_LEVEL_LOW>; + vdd-supply = <®_prox_vdd>; + vddio-supply = <&pm8916_l5>; + vled-supply = <®_prox_led>; + led-max-microamp = <100000>; + amstaos,proximity-pulse-count = <6>; + }; + }; +... diff --git a/arch/arm64/boot/dts/qcom/msm8916-samsung-a5u-eur.dts b/arch/arm64/boot/dts/qcom/msm8916-samsung-a5u-eur.dts index 48298862a91bf9..45250abf4784f0 100644 --- a/arch/arm64/boot/dts/qcom/msm8916-samsung-a5u-eur.dts +++ b/arch/arm64/boot/dts/qcom/msm8916-samsung-a5u-eur.dts @@ -47,6 +47,16 @@ pinctrl-names = "default"; pinctrl-0 = <&tkey_en_default>; }; + + reg_prox_vled: regulator-prox-vled { + compatible = "regulator-fixed"; + regulator-name = "prox_vled"; + gpios = <&tlmm 8 GPIO_ACTIVE_HIGH>; + enable-active-high; + + pinctrl-0 = <&prox_led_default>; + pinctrl-names = "default"; + }; }; &accelerometer { @@ -61,6 +71,23 @@ constant-charge-voltage-max-microvolt = <4350000>; }; +&blsp_i2c2 { + light-sensor@39 { + compatible = "amstaos,tmd3782"; + reg = <0x39>; + interrupts-extended = <&tlmm 113 IRQ_TYPE_LEVEL_LOW>; + + vddio-supply = <&pm8916_l5>; + vled-supply = <®_prox_vled>; + + led-max-microamp = <100000>; + amstaos,proximity-pulse-count = <6>; + + pinctrl-0 = <&prox_int_default>; + pinctrl-names = "default"; + }; +}; + &blsp_i2c5 { status = "okay"; @@ -145,4 +172,18 @@ drive-strength = <2>; bias-disable; }; + + prox_int_default: prox-int-default-state { + pins = "gpio113"; + function = "gpio"; + drive-strength = <2>; + bias-pull-up; + }; + + prox_led_default: prox-led-default-state { + pins = "gpio8"; + function = "gpio"; + drive-strength = <2>; + bias-disable; + }; }; diff --git a/arch/arm64/boot/dts/qcom/msm8939-samsung-a7.dts b/arch/arm64/boot/dts/qcom/msm8939-samsung-a7.dts index 9b9ed21199f3a3..b0535b4c1a784f 100644 --- a/arch/arm64/boot/dts/qcom/msm8939-samsung-a7.dts +++ b/arch/arm64/boot/dts/qcom/msm8939-samsung-a7.dts @@ -166,6 +166,22 @@ vdd-supply = <&pm8916_l17>; vddio-supply = <&pm8916_l5>; }; + + light-sensor@39 { + compatible = "amstaos,tmd3782"; + reg = <0x39>; + interrupts-extended = <&tlmm 113 IRQ_TYPE_LEVEL_LOW>; + + vdd-supply = <&pm8916_l17>; + vddio-supply = <&pm8916_l5>; + vled-supply = <&pm8916_l17>; + + led-max-microamp = <100000>; + amstaos,proximity-pulse-count = <6>; + + pinctrl-0 = <&prox_int_default>; + pinctrl-names = "default"; + }; }; i2c-tkey { @@ -700,6 +716,13 @@ bias-disable; }; + prox_int_default: prox-int-default-state { + pins = "gpio113"; + function = "gpio"; + drive-strength = <2>; + bias-pull-up; + }; + reg_tsp_en_default: reg-tsp-en-default-state { pins = "gpio73"; function = "gpio"; diff --git a/drivers/iio/light/tcs3472.c b/drivers/iio/light/tcs3472.c index 04452b4664f306..fdab9608b3d10a 100644 --- a/drivers/iio/light/tcs3472.c +++ b/drivers/iio/light/tcs3472.c @@ -16,7 +16,9 @@ #include #include #include +#include #include +#include #include #include @@ -32,6 +34,7 @@ #define TCS3472_SPECIAL_FUNC (BIT(5) | BIT(6)) #define TCS3472_INTR_CLEAR (TCS3472_COMMAND | TCS3472_SPECIAL_FUNC | 0x06) +#define TCS3472_ALL_INTR_CLEAR (TCS3472_COMMAND | TCS3472_SPECIAL_FUNC | 0x07) #define TCS3472_ENABLE (TCS3472_COMMAND | 0x00) #define TCS3472_ATIME (TCS3472_COMMAND | 0x01) @@ -48,6 +51,30 @@ #define TCS3472_GDATA (TCS3472_COMMAND | TCS3472_AUTO_INCR | 0x18) #define TCS3472_BDATA (TCS3472_COMMAND | TCS3472_AUTO_INCR | 0x1a) +/* TMD3782 proximity registers */ +#define TCS3472_PILT (TCS3472_COMMAND | TCS3472_AUTO_INCR | 0x08) +#define TCS3472_PIHT (TCS3472_COMMAND | TCS3472_AUTO_INCR | 0x0a) +#define TCS3472_PPULSE (TCS3472_COMMAND | 0x0e) +#define TCS3472_PDATA (TCS3472_COMMAND | TCS3472_AUTO_INCR | 0x1c) +#define TCS3472_REVID (TCS3472_COMMAND | 0x11) + +/* ENABLE register: proximity bits */ +#define TCS3472_ENABLE_PIEN BIT(5) +#define TCS3472_ENABLE_WEN BIT(3) +#define TCS3472_ENABLE_PEN BIT(2) + +/* CONTROL register: proximity bits (TMD3782) */ +#define TCS3472_CONTROL_PDRIVE_MASK GENMASK(7, 6) +/* TMD3782 datasheet page 25, Figure 34: bit 5 must be written as 1 */ +#define TCS3472_CONTROL_RSVD5 BIT(5) + +/* STATUS register: proximity bits */ +#define TCS3472_STATUS_PINT BIT(5) +#define TCS3472_STATUS_PVALID BIT(1) + +/* Interrupt clear: proximity */ +#define TCS3472_PROX_INTR_CLEAR (TCS3472_COMMAND | TCS3472_SPECIAL_FUNC | 0x05) + #define TCS3472_STATUS_AINT BIT(4) #define TCS3472_STATUS_AVALID BIT(0) #define TCS3472_ENABLE_AIEN BIT(4) @@ -55,18 +82,44 @@ #define TCS3472_ENABLE_PON BIT(0) #define TCS3472_CONTROL_AGAIN_MASK (BIT(0) | BIT(1)) +enum { + TCS3472_CHIP_TCS3472, + TCS3472_CHIP_TMD3782, +}; + +struct tcs3472_chip_info { + const struct iio_chan_spec *channels; + int num_channels; + bool has_proximity; + const char *name; +}; + +static const char *const tcs3472_supply_names[] = { + "vdd", + "vddio", +}; + struct tcs3472_data { struct i2c_client *client; + const struct tcs3472_chip_info *chip_info; struct mutex lock; + bool prox_event_enabled; + bool prox_buf_enabled; u16 low_thresh; u16 high_thresh; + u16 prox_low_thresh; + u16 prox_high_thresh; u8 enable; + u8 enable_saved; u8 control; u8 atime; u8 apers; + u8 ppers; + u8 ppulse; /* Ensure timestamp is naturally aligned */ struct { - u16 chans[4]; + /* 5 channels: RGBC (4) + proximity (1, TMD3782 only) */ + u16 chans[5]; s64 timestamp __aligned(8); } scan; }; @@ -88,6 +141,27 @@ static const struct iio_event_spec tcs3472_events[] = { }, }; +/* + * Proximity events: threshold rising/falling + enable. + * No IIO_EV_INFO_PERIOD — PPERS is a linear 0-15 count, not the non-linear + * APERS mapping. Fixed at 3 in v1 (not configurable by userspace). + */ +static const struct iio_event_spec tmd3782_prox_events[] = { + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_RISING, + .mask_separate = BIT(IIO_EV_INFO_VALUE), + }, { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_FALLING, + .mask_separate = BIT(IIO_EV_INFO_VALUE), + }, { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_EITHER, + .mask_separate = BIT(IIO_EV_INFO_ENABLE), + }, +}; + #define TCS3472_CHANNEL(_color, _si, _addr) { \ .type = IIO_INTENSITY, \ .modified = 1, \ @@ -109,6 +183,14 @@ static const struct iio_event_spec tcs3472_events[] = { static const int tcs3472_agains[] = { 1, 4, 16, 60 }; +static const int tcs3472_led_currents[][2] = { + { 100000, 0x00 }, + { 50000, 0x01 }, + { 25000, 0x02 }, + { 12500, 0x03 }, + { 0, 0x00 }, /* sentinel, also default = 100mA */ +}; + static const struct iio_chan_spec tcs3472_channels[] = { TCS3472_CHANNEL(CLEAR, 0, TCS3472_CDATA), TCS3472_CHANNEL(RED, 1, TCS3472_RDATA), @@ -117,7 +199,44 @@ static const struct iio_chan_spec tcs3472_channels[] = { IIO_CHAN_SOFT_TIMESTAMP(4), }; -static int tcs3472_req_data(struct tcs3472_data *data) +static const struct iio_chan_spec tmd3782_channels[] = { + TCS3472_CHANNEL(CLEAR, 0, TCS3472_CDATA), + TCS3472_CHANNEL(RED, 1, TCS3472_RDATA), + TCS3472_CHANNEL(GREEN, 2, TCS3472_GDATA), + TCS3472_CHANNEL(BLUE, 3, TCS3472_BDATA), + { + .type = IIO_PROXIMITY, + .address = TCS3472_PDATA, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .scan_index = 4, + .scan_type = { + .sign = 'u', + .realbits = 16, + .storagebits = 16, + .endianness = IIO_CPU, + }, + .event_spec = tmd3782_prox_events, + .num_event_specs = ARRAY_SIZE(tmd3782_prox_events), + }, + IIO_CHAN_SOFT_TIMESTAMP(5), +}; + +static const struct tcs3472_chip_info tcs3472_chip_info_tbl[] = { + [TCS3472_CHIP_TCS3472] = { + .channels = tcs3472_channels, + .num_channels = ARRAY_SIZE(tcs3472_channels), + .has_proximity = false, + .name = "tcs3472", + }, + [TCS3472_CHIP_TMD3782] = { + .channels = tmd3782_channels, + .num_channels = ARRAY_SIZE(tmd3782_channels), + .has_proximity = true, + .name = "tmd3782", + }, +}; + +static int tcs3472_req_data(struct tcs3472_data *data, unsigned int status_mask) { int tries = 50; int ret; @@ -126,7 +245,7 @@ static int tcs3472_req_data(struct tcs3472_data *data) ret = i2c_smbus_read_byte_data(data->client, TCS3472_STATUS); if (ret < 0) return ret; - if (ret & TCS3472_STATUS_AVALID) + if ((ret & status_mask) == status_mask) break; msleep(20); } @@ -151,12 +270,52 @@ static int tcs3472_read_raw(struct iio_dev *indio_dev, ret = iio_device_claim_direct_mode(indio_dev); if (ret) return ret; - ret = tcs3472_req_data(data); - if (ret < 0) { - iio_device_release_direct_mode(indio_dev); - return ret; + + if (chan->type == IIO_PROXIMITY) { + bool cold; + + mutex_lock(&data->lock); + cold = !data->prox_event_enabled && !data->prox_buf_enabled; + if (cold) { + data->enable |= TCS3472_ENABLE_PEN | + TCS3472_ENABLE_WEN; + ret = i2c_smbus_write_byte_data(data->client, + TCS3472_ENABLE, + data->enable); + if (ret) { + data->enable &= ~(TCS3472_ENABLE_PEN | + TCS3472_ENABLE_WEN); + mutex_unlock(&data->lock); + iio_device_release_direct_mode(indio_dev); + return ret; + } + } + mutex_unlock(&data->lock); + + ret = tcs3472_req_data(data, TCS3472_STATUS_PVALID); + if (ret >= 0) + ret = i2c_smbus_read_word_data(data->client, + chan->address); + + if (cold) { + mutex_lock(&data->lock); + if (!data->prox_event_enabled && + !data->prox_buf_enabled) { + data->enable &= ~(TCS3472_ENABLE_PEN | + TCS3472_ENABLE_WEN); + i2c_smbus_write_byte_data(data->client, + TCS3472_ENABLE, + data->enable); + } + mutex_unlock(&data->lock); + } + } else { + ret = tcs3472_req_data(data, TCS3472_STATUS_AVALID); + if (ret >= 0) + ret = i2c_smbus_read_word_data(data->client, + chan->address); } - ret = i2c_smbus_read_word_data(data->client, chan->address); + iio_device_release_direct_mode(indio_dev); if (ret < 0) return ret; @@ -233,8 +392,12 @@ static int tcs3472_read_event(struct iio_dev *indio_dev, switch (info) { case IIO_EV_INFO_VALUE: - *val = (dir == IIO_EV_DIR_RISING) ? - data->high_thresh : data->low_thresh; + if (chan->type == IIO_PROXIMITY) + *val = (dir == IIO_EV_DIR_RISING) ? + data->prox_high_thresh : data->prox_low_thresh; + else + *val = (dir == IIO_EV_DIR_RISING) ? + data->high_thresh : data->low_thresh; ret = IIO_VAL_INT; break; case IIO_EV_INFO_PERIOD: @@ -268,34 +431,61 @@ static int tcs3472_write_event(struct iio_dev *indio_dev, mutex_lock(&data->lock); switch (info) { case IIO_EV_INFO_VALUE: - switch (dir) { - case IIO_EV_DIR_RISING: - command = TCS3472_AIHT; - break; - case IIO_EV_DIR_FALLING: - command = TCS3472_AILT; - break; - default: - ret = -EINVAL; - goto error; + if (chan->type == IIO_PROXIMITY) { + switch (dir) { + case IIO_EV_DIR_RISING: + command = TCS3472_PIHT; + break; + case IIO_EV_DIR_FALLING: + command = TCS3472_PILT; + break; + default: + ret = -EINVAL; + goto error; + } + } else { + switch (dir) { + case IIO_EV_DIR_RISING: + command = TCS3472_AIHT; + break; + case IIO_EV_DIR_FALLING: + command = TCS3472_AILT; + break; + default: + ret = -EINVAL; + goto error; + } } ret = i2c_smbus_write_word_data(data->client, command, val); if (ret) goto error; - if (dir == IIO_EV_DIR_RISING) - data->high_thresh = val; - else - data->low_thresh = val; + if (chan->type == IIO_PROXIMITY) { + if (dir == IIO_EV_DIR_RISING) + data->prox_high_thresh = val; + else + data->prox_low_thresh = val; + } else { + if (dir == IIO_EV_DIR_RISING) + data->high_thresh = val; + else + data->low_thresh = val; + } break; case IIO_EV_INFO_PERIOD: + /* Period only applies to ALS events (APERS non-linear mapping) */ + if (chan->type == IIO_PROXIMITY) { + ret = -EINVAL; + goto error; + } period = val * USEC_PER_SEC + val2; for (i = 1; i < ARRAY_SIZE(tcs3472_intr_pers) - 1; i++) { if (period <= (256 - data->atime) * 2400 * tcs3472_intr_pers[i]) break; } - ret = i2c_smbus_write_byte_data(data->client, TCS3472_PERS, i); + ret = i2c_smbus_write_byte_data(data->client, TCS3472_PERS, + (data->ppers << 4) | i); if (ret) goto error; @@ -319,7 +509,10 @@ static int tcs3472_read_event_config(struct iio_dev *indio_dev, int ret; mutex_lock(&data->lock); - ret = !!(data->enable & TCS3472_ENABLE_AIEN); + if (chan->type == IIO_PROXIMITY) + ret = data->prox_event_enabled; + else + ret = !!(data->enable & TCS3472_ENABLE_AIEN); mutex_unlock(&data->lock); return ret; @@ -333,20 +526,42 @@ static int tcs3472_write_event_config(struct iio_dev *indio_dev, int ret = 0; u8 enable_old; + /* No IRQ handler → enabling interrupts would leave INT stuck low */ + if (!data->client->irq) + return -EINVAL; + mutex_lock(&data->lock); enable_old = data->enable; - if (state) - data->enable |= TCS3472_ENABLE_AIEN; - else - data->enable &= ~TCS3472_ENABLE_AIEN; + if (chan->type == IIO_PROXIMITY) { + data->prox_event_enabled = !!state; + if (state) { + data->enable |= TCS3472_ENABLE_PIEN | + TCS3472_ENABLE_PEN | + TCS3472_ENABLE_WEN; + } else { + data->enable &= ~TCS3472_ENABLE_PIEN; + /* Only drop PEN+WEN if buffer isn't using proximity */ + if (!data->prox_buf_enabled) + data->enable &= ~(TCS3472_ENABLE_PEN | + TCS3472_ENABLE_WEN); + } + } else { + if (state) + data->enable |= TCS3472_ENABLE_AIEN; + else + data->enable &= ~TCS3472_ENABLE_AIEN; + } if (enable_old != data->enable) { ret = i2c_smbus_write_byte_data(data->client, TCS3472_ENABLE, data->enable); - if (ret) + if (ret) { data->enable = enable_old; + if (chan->type == IIO_PROXIMITY) + data->prox_event_enabled = !state; + } } mutex_unlock(&data->lock); @@ -360,14 +575,30 @@ static irqreturn_t tcs3472_event_handler(int irq, void *priv) int ret; ret = i2c_smbus_read_byte_data(data->client, TCS3472_STATUS); - if (ret >= 0 && (ret & TCS3472_STATUS_AINT)) { - iio_push_event(indio_dev, IIO_UNMOD_EVENT_CODE(IIO_INTENSITY, 0, - IIO_EV_TYPE_THRESH, - IIO_EV_DIR_EITHER), - iio_get_time_ns(indio_dev)); - + if (ret < 0) + return IRQ_HANDLED; + + if (ret & TCS3472_STATUS_AINT) + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_INTENSITY, 0, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_EITHER), + iio_get_time_ns(indio_dev)); + + if (ret & TCS3472_STATUS_PINT) + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_PROXIMITY, 0, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_EITHER), + iio_get_time_ns(indio_dev)); + + /* Clear only the interrupts we observed */ + if ((ret & TCS3472_STATUS_AINT) && (ret & TCS3472_STATUS_PINT)) + i2c_smbus_read_byte_data(data->client, TCS3472_ALL_INTR_CLEAR); + else if (ret & TCS3472_STATUS_AINT) i2c_smbus_read_byte_data(data->client, TCS3472_INTR_CLEAR); - } + else if (ret & TCS3472_STATUS_PINT) + i2c_smbus_read_byte_data(data->client, TCS3472_PROX_INTR_CLEAR); return IRQ_HANDLED; } @@ -377,15 +608,20 @@ static irqreturn_t tcs3472_trigger_handler(int irq, void *p) struct iio_poll_func *pf = p; struct iio_dev *indio_dev = pf->indio_dev; struct tcs3472_data *data = iio_priv(indio_dev); + unsigned int status_mask = TCS3472_STATUS_AVALID; int i, j = 0; + int ret; - int ret = tcs3472_req_data(data); + if (data->prox_event_enabled || data->prox_buf_enabled) + status_mask |= TCS3472_STATUS_PVALID; + + ret = tcs3472_req_data(data, status_mask); if (ret < 0) goto done; iio_for_each_active_channel(indio_dev, i) { ret = i2c_smbus_read_word_data(data->client, - TCS3472_CDATA + 2*i); + TCS3472_CDATA + 2 * i); if (ret < 0) goto done; @@ -401,6 +637,56 @@ static irqreturn_t tcs3472_trigger_handler(int irq, void *p) return IRQ_HANDLED; } +static int tcs3472_buffer_preenable(struct iio_dev *indio_dev) +{ + struct tcs3472_data *data = iio_priv(indio_dev); + int ret; + + if (!data->chip_info->has_proximity) + return 0; + + /* Check if proximity channel (scan_index 4) is in the scan mask */ + if (!test_bit(4, indio_dev->active_scan_mask)) + return 0; + + mutex_lock(&data->lock); + data->prox_buf_enabled = true; + data->enable |= TCS3472_ENABLE_PEN | TCS3472_ENABLE_WEN; + ret = i2c_smbus_write_byte_data(data->client, TCS3472_ENABLE, data->enable); + if (ret) { + data->prox_buf_enabled = false; + if (!data->prox_event_enabled) + data->enable &= ~(TCS3472_ENABLE_PEN | TCS3472_ENABLE_WEN); + } + mutex_unlock(&data->lock); + + return ret; +} + +static int tcs3472_buffer_postdisable(struct iio_dev *indio_dev) +{ + struct tcs3472_data *data = iio_priv(indio_dev); + + if (!data->prox_buf_enabled) + return 0; + + mutex_lock(&data->lock); + data->prox_buf_enabled = false; + if (!data->prox_event_enabled) { + data->enable &= ~(TCS3472_ENABLE_PEN | TCS3472_ENABLE_WEN); + i2c_smbus_write_byte_data(data->client, TCS3472_ENABLE, + data->enable); + } + mutex_unlock(&data->lock); + + return 0; +} + +static const struct iio_buffer_setup_ops tcs3472_buffer_setup_ops = { + .preenable = tcs3472_buffer_preenable, + .postdisable = tcs3472_buffer_postdisable, +}; + static ssize_t tcs3472_show_int_time_available(struct device *dev, struct device_attribute *attr, char *buf) @@ -445,6 +731,7 @@ static int tcs3472_probe(struct i2c_client *client) { struct tcs3472_data *data; struct iio_dev *indio_dev; + const struct tcs3472_chip_info *match_info; int ret; indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); @@ -455,29 +742,72 @@ static int tcs3472_probe(struct i2c_client *client) i2c_set_clientdata(client, indio_dev); data->client = client; mutex_init(&data->lock); + match_info = i2c_get_match_data(client); - indio_dev->info = &tcs3472_info; - indio_dev->name = TCS3472_DRV_NAME; - indio_dev->channels = tcs3472_channels; - indio_dev->num_channels = ARRAY_SIZE(tcs3472_channels); - indio_dev->modes = INDIO_DIRECT_MODE; + ret = devm_regulator_bulk_get_enable(&client->dev, + ARRAY_SIZE(tcs3472_supply_names), + tcs3472_supply_names); + if (ret) + return dev_err_probe(&client->dev, ret, + "failed to get regulators\n"); + + /* 2.4ms PON warm-up after regulator enable */ + usleep_range(2500, 3000); ret = i2c_smbus_read_byte_data(data->client, TCS3472_ID); if (ret < 0) return ret; - if (ret == 0x44) - dev_info(&client->dev, "TCS34721/34725 found\n"); - else if (ret == 0x4d) - dev_info(&client->dev, "TCS34723/34727 found\n"); - else + if (match_info) { + data->chip_info = match_info; + } else if (ret == 0x44 || ret == 0x4d) { + data->chip_info = &tcs3472_chip_info_tbl[TCS3472_CHIP_TCS3472]; + } else if (ret == 0x60 || ret == 0x69) { + data->chip_info = &tcs3472_chip_info_tbl[TCS3472_CHIP_TMD3782]; + } else { return -ENODEV; + } + + dev_info(&client->dev, "%s (id 0x%02x, rev 0x%02x) found\n", + data->chip_info->name, ret, + i2c_smbus_read_byte_data(client, TCS3472_REVID)); + + indio_dev->info = &tcs3472_info; + indio_dev->name = data->chip_info->name; + indio_dev->channels = data->chip_info->channels; + indio_dev->num_channels = data->chip_info->num_channels; + indio_dev->modes = INDIO_DIRECT_MODE; ret = i2c_smbus_read_byte_data(data->client, TCS3472_CONTROL); if (ret < 0) return ret; data->control = ret; + if (data->chip_info->has_proximity) { + u32 led_ua; + int i, pdrive = 0x00; /* default 100mA */ + + /* TMD3782 datasheet page 25, Figure 34: bit 5 must be 1 */ + data->control |= TCS3472_CONTROL_RSVD5; + + if (!device_property_read_u32(&client->dev, "led-max-microamp", + &led_ua)) { + for (i = 0; tcs3472_led_currents[i][0]; i++) { + if (led_ua == tcs3472_led_currents[i][0]) { + pdrive = tcs3472_led_currents[i][1]; + break; + } + } + } + data->control &= ~TCS3472_CONTROL_PDRIVE_MASK; + data->control |= (pdrive << 6); + + ret = i2c_smbus_write_byte_data(data->client, TCS3472_CONTROL, + data->control); + if (ret < 0) + return ret; + } + ret = i2c_smbus_read_byte_data(data->client, TCS3472_ATIME); if (ret < 0) return ret; @@ -493,9 +823,47 @@ static int tcs3472_probe(struct i2c_client *client) return ret; data->high_thresh = ret; + if (data->chip_info->has_proximity) { + u32 ppulse_val = 8; /* default: datasheet Figure 11 test conditions */ + + device_property_read_u32(&client->dev, + "amstaos,proximity-pulse-count", + &ppulse_val); + data->ppulse = clamp_val(ppulse_val, 1, 255); + ret = i2c_smbus_write_byte_data(data->client, TCS3472_PPULSE, + data->ppulse); + if (ret < 0) + return ret; + + /* + * PPERS=3 matches downstream (intr_filter=0x33): interrupt fires + * after 3 consecutive out-of-range readings, filtering transient + * reflections. Downstream uses APERS=3 too, but we keep APERS=1 + * (existing tcs3472 default) for backward compatibility. + */ + data->ppers = 3; + + /* Read proximity thresholds from hardware */ + ret = i2c_smbus_read_word_data(data->client, TCS3472_PILT); + if (ret < 0) + return ret; + data->prox_low_thresh = ret; + + ret = i2c_smbus_read_word_data(data->client, TCS3472_PIHT); + if (ret < 0) + return ret; + data->prox_high_thresh = ret; + + /* vled regulator for IR LED — optional */ + ret = devm_regulator_get_enable_optional(&client->dev, "vled"); + if (ret && ret != -ENODEV) + return dev_err_probe(&client->dev, ret, + "failed to get vled regulator\n"); + } + data->apers = 1; ret = i2c_smbus_write_byte_data(data->client, TCS3472_PERS, - data->apers); + (data->ppers << 4) | data->apers); if (ret < 0) return ret; @@ -511,16 +879,17 @@ static int tcs3472_probe(struct i2c_client *client) if (ret < 0) return ret; + data->enable_saved = data->enable; + ret = iio_triggered_buffer_setup(indio_dev, NULL, - tcs3472_trigger_handler, NULL); + tcs3472_trigger_handler, &tcs3472_buffer_setup_ops); if (ret < 0) return ret; if (client->irq) { ret = request_threaded_irq(client->irq, NULL, tcs3472_event_handler, - IRQF_TRIGGER_FALLING | IRQF_SHARED | - IRQF_ONESHOT, + IRQF_SHARED | IRQF_ONESHOT, client->name, indio_dev); if (ret) goto buffer_cleanup; @@ -543,14 +912,13 @@ static int tcs3472_probe(struct i2c_client *client) static int tcs3472_powerdown(struct tcs3472_data *data) { int ret; - u8 enable_mask = TCS3472_ENABLE_AEN | TCS3472_ENABLE_PON; mutex_lock(&data->lock); - ret = i2c_smbus_write_byte_data(data->client, TCS3472_ENABLE, - data->enable & ~enable_mask); + data->enable_saved = data->enable; + ret = i2c_smbus_write_byte_data(data->client, TCS3472_ENABLE, 0x00); if (!ret) - data->enable &= ~enable_mask; + data->enable = 0; mutex_unlock(&data->lock); @@ -580,15 +948,49 @@ static int tcs3472_resume(struct device *dev) struct tcs3472_data *data = iio_priv(i2c_get_clientdata( to_i2c_client(dev))); int ret; - u8 enable_mask = TCS3472_ENABLE_AEN | TCS3472_ENABLE_PON; mutex_lock(&data->lock); + /* Write PON first, then wait for oscillator warm-up */ + ret = i2c_smbus_write_byte_data(data->client, TCS3472_ENABLE, + TCS3472_ENABLE_PON); + if (ret) + goto unlock; + + usleep_range(2500, 3000); + + /* Restore all configuration registers */ + i2c_smbus_write_byte_data(data->client, TCS3472_ATIME, data->atime); + i2c_smbus_write_byte_data(data->client, TCS3472_WTIME, 0xff); + i2c_smbus_write_word_data(data->client, TCS3472_AILT, data->low_thresh); + i2c_smbus_write_word_data(data->client, TCS3472_AIHT, data->high_thresh); + if (data->chip_info->has_proximity) { + i2c_smbus_write_word_data(data->client, TCS3472_PILT, + data->prox_low_thresh); + i2c_smbus_write_word_data(data->client, TCS3472_PIHT, + data->prox_high_thresh); + i2c_smbus_write_byte_data(data->client, TCS3472_PPULSE, + data->ppulse); + } + i2c_smbus_write_byte_data(data->client, TCS3472_PERS, + (data->ppers << 4) | data->apers); + i2c_smbus_write_byte_data(data->client, TCS3472_CONFIG, 0x00); + i2c_smbus_write_byte_data(data->client, TCS3472_CONTROL, data->control); + + /* Clear stale interrupt flags before re-enabling interrupt sources. + * Use ALL_INTR_CLEAR (0xE7) rather than INTR_CLEAR (0xE6) so + * proximity interrupts are also cleared for TMD3782. Harmless + * for TCS3472 — PINT is never set on that chip. + */ + i2c_smbus_read_byte_data(data->client, TCS3472_ALL_INTR_CLEAR); + + /* Restore ENABLE last — re-activates AEN, interrupt enables, etc. */ ret = i2c_smbus_write_byte_data(data->client, TCS3472_ENABLE, - data->enable | enable_mask); + data->enable_saved); if (!ret) - data->enable |= enable_mask; + data->enable = data->enable_saved; +unlock: mutex_unlock(&data->lock); return ret; @@ -597,8 +999,18 @@ static int tcs3472_resume(struct device *dev) static DEFINE_SIMPLE_DEV_PM_OPS(tcs3472_pm_ops, tcs3472_suspend, tcs3472_resume); +static const struct of_device_id tcs3472_of_match[] = { + { .compatible = "amstaos,tcs3472", + .data = &tcs3472_chip_info_tbl[TCS3472_CHIP_TCS3472] }, + { .compatible = "amstaos,tmd3782", + .data = &tcs3472_chip_info_tbl[TCS3472_CHIP_TMD3782] }, + { } +}; +MODULE_DEVICE_TABLE(of, tcs3472_of_match); + static const struct i2c_device_id tcs3472_id[] = { { "tcs3472" }, + { "tmd3782" }, { } }; MODULE_DEVICE_TABLE(i2c, tcs3472_id); @@ -607,6 +1019,7 @@ static struct i2c_driver tcs3472_driver = { .driver = { .name = TCS3472_DRV_NAME, .pm = pm_sleep_ptr(&tcs3472_pm_ops), + .of_match_table = tcs3472_of_match, }, .probe = tcs3472_probe, .remove = tcs3472_remove, @@ -615,5 +1028,5 @@ static struct i2c_driver tcs3472_driver = { module_i2c_driver(tcs3472_driver); MODULE_AUTHOR("Peter Meerwald "); -MODULE_DESCRIPTION("TCS3472 color light sensors driver"); +MODULE_DESCRIPTION("TCS3472/TMD3782 color light and proximity sensors driver"); MODULE_LICENSE("GPL");