From 89e49770632b6249279e8bbdfa81b84b94c02fca Mon Sep 17 00:00:00 2001 From: PlayDay <2457-playday3008@users.noreply.gitlab.postmarketos.org> Date: Thu, 23 Apr 2026 13:14:04 +0200 Subject: [PATCH 01/15] dt-bindings: iio: light: Add amstaos,tcs3472.yaml Add devicetree binding for the AMS/TAOS TCS3472 RGBC color light sensor and TMD3782 RGBC + proximity sensor. The TCS3472 has no existing binding despite having a mainline driver since 2013. The TMD3782 is a superset of TCS3472 adding an integrated proximity detector with IR LED. Additional properties for proximity configuration (pulse count, LED current, LED supply) are optional and only relevant for TMD3782. --- .../bindings/iio/light/amstaos,tcs3472.yaml | 84 +++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 Documentation/devicetree/bindings/iio/light/amstaos,tcs3472.yaml 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>; + }; + }; +... From 4c74878ba556dd985cb28ec1cfdb1c840b420189 Mon Sep 17 00:00:00 2001 From: PlayDay <2457-playday3008@users.noreply.gitlab.postmarketos.org> Date: Thu, 23 Apr 2026 14:05:10 +0200 Subject: [PATCH 02/15] iio: light: tcs3472: Add OF match table and regulator support Add of_device_id table for devicetree-based probing of TCS3472 and TMD3782 sensors. Add optional vdd/vddio regulator support with 2.4ms power-on settling delay. Update tcs3472_powerdown() to write 0x00 for full chip power-down instead of only clearing AEN+PON. This is a behavior change: the old code preserved AIEN, the new code clears everything. This is safe because resume now performs a full register restore. Update resume to rewrite all configuration registers (ATIME, WTIME, thresholds, PERS, CONFIG, CONTROL, ENABLE) and clear stale interrupt flags. This hardens against potential register state loss during PON=0. Remove hardcoded IRQF_TRIGGER_FALLING from request_threaded_irq to let the devicetree specify the interrupt trigger type. --- drivers/iio/light/tcs3472.c | 73 ++++++++++++++++++++++++++++++++----- 1 file changed, 63 insertions(+), 10 deletions(-) diff --git a/drivers/iio/light/tcs3472.c b/drivers/iio/light/tcs3472.c index 04452b4664f306..5c31545c3c1c0d 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) @@ -55,12 +58,18 @@ #define TCS3472_ENABLE_PON BIT(0) #define TCS3472_CONTROL_AGAIN_MASK (BIT(0) | BIT(1)) +static const char *const tcs3472_supply_names[] = { + "vdd", + "vddio", +}; + struct tcs3472_data { struct i2c_client *client; struct mutex lock; u16 low_thresh; u16 high_thresh; u8 enable; + u8 enable_saved; u8 control; u8 atime; u8 apers; @@ -462,6 +471,16 @@ static int tcs3472_probe(struct i2c_client *client) 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; @@ -511,6 +530,8 @@ 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); if (ret < 0) @@ -519,8 +540,7 @@ static int tcs3472_probe(struct i2c_client *client) 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 +563,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 +599,40 @@ 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); + i2c_smbus_write_byte_data(data->client, TCS3472_PERS, 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 +641,16 @@ 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" }, + { .compatible = "amstaos,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 +659,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 +668,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"); From 7807ea12c496eacd2887be51de769fbe7665f906 Mon Sep 17 00:00:00 2001 From: PlayDay <2457-playday3008@users.noreply.gitlab.postmarketos.org> Date: Thu, 23 Apr 2026 14:25:04 +0200 Subject: [PATCH 03/15] iio: light: tcs3472: Introduce chip_info for per-variant support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add tcs3472_chip_info struct to hold per-variant channel arrays, channel counts, and feature flags. This prepares for adding TMD3782 proximity support without scattering variant checks throughout the driver. No functional change — only TCS3472 is populated. The TMD3782 entry will be added when proximity support lands. --- drivers/iio/light/tcs3472.c | 50 +++++++++++++++++++++++++++++-------- 1 file changed, 39 insertions(+), 11 deletions(-) diff --git a/drivers/iio/light/tcs3472.c b/drivers/iio/light/tcs3472.c index 5c31545c3c1c0d..6c4df12a59a477 100644 --- a/drivers/iio/light/tcs3472.c +++ b/drivers/iio/light/tcs3472.c @@ -58,6 +58,18 @@ #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", @@ -65,6 +77,7 @@ static const char *const tcs3472_supply_names[] = { struct tcs3472_data { struct i2c_client *client; + const struct tcs3472_chip_info *chip_info; struct mutex lock; u16 low_thresh; u16 high_thresh; @@ -126,6 +139,15 @@ static const struct iio_chan_spec tcs3472_channels[] = { IIO_CHAN_SOFT_TIMESTAMP(4), }; +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", + }, +}; + static int tcs3472_req_data(struct tcs3472_data *data) { int tries = 50; @@ -454,6 +476,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)); @@ -464,12 +487,7 @@ static int tcs3472_probe(struct i2c_client *client) i2c_set_clientdata(client, indio_dev); data->client = client; mutex_init(&data->lock); - - 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; + match_info = i2c_get_match_data(client); ret = devm_regulator_bulk_get_enable(&client->dev, ARRAY_SIZE(tcs3472_supply_names), @@ -485,13 +503,22 @@ static int tcs3472_probe(struct i2c_client *client) 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"); + 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 return -ENODEV; + dev_info(&client->dev, "%s (id 0x%02x) found\n", + data->chip_info->name, ret); + + 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; @@ -642,7 +669,8 @@ 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" }, + { .compatible = "amstaos,tcs3472", + .data = &tcs3472_chip_info_tbl[TCS3472_CHIP_TCS3472] }, { .compatible = "amstaos,tmd3782" }, { } }; From 468d00821526d708c185a7e6ffca248e1a012b41 Mon Sep 17 00:00:00 2001 From: PlayDay <2457-playday3008@users.noreply.gitlab.postmarketos.org> Date: Thu, 23 Apr 2026 14:32:26 +0200 Subject: [PATCH 04/15] iio: light: tcs3472: Add TMD3782 register definitions and chip ID Add proximity register definitions for the TMD3782 (PILT, PIHT, PPULSE, PDATA, PIEN, PEN, WEN, PINT, PVALID) and interrupt clear commands. Detect TMD3782 chip IDs (0x60 for TMD37821, 0x69 for TMD37823) in probe. Initialize the CONTROL register with the mandatory bit 5 and LED drive current from the led-max-microamp DT property. Configure proximity pulse count from DT (default 8) and set PPERS to 3 to match downstream behavior. Fix the PERS register write to merge both APERS and PPERS nibbles, preventing the existing bug where writing APERS clobbers PPERS. Extend resume to restore proximity thresholds, pulse count, and clear stale interrupt flags from both ALS and proximity. --- drivers/iio/light/tcs3472.c | 137 +++++++++++++++++++++++++++++++++--- 1 file changed, 129 insertions(+), 8 deletions(-) diff --git a/drivers/iio/light/tcs3472.c b/drivers/iio/light/tcs3472.c index 6c4df12a59a477..34910ff44e4558 100644 --- a/drivers/iio/light/tcs3472.c +++ b/drivers/iio/light/tcs3472.c @@ -51,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) @@ -79,16 +103,23 @@ 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; }; @@ -131,6 +162,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), @@ -146,6 +185,12 @@ static const struct tcs3472_chip_info tcs3472_chip_info_tbl[] = { .has_proximity = false, .name = "tcs3472", }, + [TCS3472_CHIP_TMD3782] = { + .channels = tcs3472_channels, + .num_channels = ARRAY_SIZE(tcs3472_channels), + .has_proximity = true, + .name = "tmd3782", + }, }; static int tcs3472_req_data(struct tcs3472_data *data) @@ -503,15 +548,19 @@ static int tcs3472_probe(struct i2c_client *client) if (ret < 0) return ret; - if (match_info) + if (match_info) { data->chip_info = match_info; - else if (ret == 0x44 || ret == 0x4d) + } else if (ret == 0x44 || ret == 0x4d) { data->chip_info = &tcs3472_chip_info_tbl[TCS3472_CHIP_TCS3472]; - else + } 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) found\n", - data->chip_info->name, ret); + 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; @@ -524,6 +573,31 @@ static int tcs3472_probe(struct i2c_client *client) 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; @@ -539,9 +613,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; @@ -642,7 +754,16 @@ static int tcs3472_resume(struct device *dev) 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); - i2c_smbus_write_byte_data(data->client, TCS3472_PERS, data->apers); + 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); From 72c833d83b2484f8b947e6b5a1386f841f1ad5a2 Mon Sep 17 00:00:00 2001 From: PlayDay <2457-playday3008@users.noreply.gitlab.postmarketos.org> Date: Thu, 23 Apr 2026 14:45:10 +0200 Subject: [PATCH 05/15] iio: light: tcs3472: Add TMD3782 proximity channel and events MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add proximity channel (IIO_PROXIMITY, scan_index 4) to TMD3782 channel array. The proximity data register at 0x1C fits the existing trigger handler's CDATA+2*i arithmetic naturally: 0xB4+8 = 0xBC. Add proximity threshold events (rising/falling/enable) with a separate event spec — no IIO_EV_INFO_PERIOD since PPERS uses a linear count, not the non-linear APERS mapping. PPERS is fixed at 3 (not configurable by userspace in v1). Extend event value read/write for proximity thresholds (PILT/PIHT). Extend write_event_config to manage PEN+WEN+PIEN for proximity events, with proper coordination against buffer enable state. Guard against enabling interrupts when no IRQ is configured. Extend interrupt handler to check both AINT and PINT in STATUS and clear only the interrupts actually observed, preventing a race where a new interrupt between STATUS read and clear would be silently lost. --- drivers/iio/light/tcs3472.c | 182 +++++++++++++++++++++++++++++------- 1 file changed, 149 insertions(+), 33 deletions(-) diff --git a/drivers/iio/light/tcs3472.c b/drivers/iio/light/tcs3472.c index 34910ff44e4558..03866a01daa61b 100644 --- a/drivers/iio/light/tcs3472.c +++ b/drivers/iio/light/tcs3472.c @@ -141,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, \ @@ -178,6 +199,28 @@ static const struct iio_chan_spec tcs3472_channels[] = { IIO_CHAN_SOFT_TIMESTAMP(4), }; +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, @@ -186,8 +229,8 @@ static const struct tcs3472_chip_info tcs3472_chip_info_tbl[] = { .name = "tcs3472", }, [TCS3472_CHIP_TMD3782] = { - .channels = tcs3472_channels, - .num_channels = ARRAY_SIZE(tcs3472_channels), + .channels = tmd3782_channels, + .num_channels = ARRAY_SIZE(tmd3782_channels), .has_proximity = true, .name = "tmd3782", }, @@ -309,8 +352,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: @@ -344,34 +391,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; @@ -395,7 +469,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; @@ -409,20 +486,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); @@ -436,14 +535,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; } @@ -792,7 +907,8 @@ static DEFINE_SIMPLE_DEV_PM_OPS(tcs3472_pm_ops, tcs3472_suspend, static const struct of_device_id tcs3472_of_match[] = { { .compatible = "amstaos,tcs3472", .data = &tcs3472_chip_info_tbl[TCS3472_CHIP_TCS3472] }, - { .compatible = "amstaos,tmd3782" }, + { .compatible = "amstaos,tmd3782", + .data = &tcs3472_chip_info_tbl[TCS3472_CHIP_TMD3782] }, { } }; MODULE_DEVICE_TABLE(of, tcs3472_of_match); From 3933b710d51086922624c53435ba8f492c362cb1 Mon Sep 17 00:00:00 2001 From: PlayDay <2457-playday3008@users.noreply.gitlab.postmarketos.org> Date: Thu, 23 Apr 2026 14:48:46 +0200 Subject: [PATCH 06/15] iio: light: tcs3472: Add direct-mode and buffer proximity reads Extend tcs3472_req_data() to accept a status mask parameter for polling AVALID, PVALID, or both. For direct-mode proximity reads (in_proximity_raw), temporarily enable PEN+WEN when no events or buffer are active. Uses a re-check-under-lock pattern on teardown to avoid clobbering concurrent event enables. Add buffer preenable/postdisable callbacks that manage PEN+WEN when the proximity channel (scan_index 4) is in the active scan mask. This prevents the trigger handler from hanging on PVALID when PEN is off. Coordinates with event enable state so neither path clobbers the other. --- drivers/iio/light/tcs3472.c | 115 ++++++++++++++++++++++++++++++++---- 1 file changed, 105 insertions(+), 10 deletions(-) diff --git a/drivers/iio/light/tcs3472.c b/drivers/iio/light/tcs3472.c index 03866a01daa61b..fdab9608b3d10a 100644 --- a/drivers/iio/light/tcs3472.c +++ b/drivers/iio/light/tcs3472.c @@ -236,7 +236,7 @@ static const struct tcs3472_chip_info tcs3472_chip_info_tbl[] = { }, }; -static int tcs3472_req_data(struct tcs3472_data *data) +static int tcs3472_req_data(struct tcs3472_data *data, unsigned int status_mask) { int tries = 50; int ret; @@ -245,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); } @@ -270,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; @@ -568,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; @@ -592,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) @@ -787,7 +882,7 @@ static int tcs3472_probe(struct i2c_client *client) 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; From 236f6404c7a1d9e392a1d0b6f444ff681e6411dc Mon Sep 17 00:00:00 2001 From: PlayDay <2457-playday3008@users.noreply.gitlab.postmarketos.org> Date: Thu, 23 Apr 2026 15:19:15 +0200 Subject: [PATCH 07/15] arm64: dts: qcom: msm8916-samsung-a5u-eur: Add proximity/light sensor Add TMD3782 RGBC + proximity sensor on blsp_i2c2 at address 0x39. Uses GPIO113 as level-triggered interrupt input and GPIO8 as VDD enable for the sensor (modeled as a fixed regulator). The sensor shares the I2C bus with the existing BMC150 accelerometer and magnetometer. VDD (GPIO8) and VDDIO (pm8916_l5) need explicit control. The a5-zt variant inherits this node via its include of a5u-eur.dts. --- .../boot/dts/qcom/msm8916-samsung-a5u-eur.dts | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) 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; + }; }; From 879878807417b18f55a8d10a898e8cf6aff04d94 Mon Sep 17 00:00:00 2001 From: PlayDay <2457-playday3008@users.noreply.gitlab.postmarketos.org> Date: Fri, 24 Apr 2026 17:52:28 +0200 Subject: [PATCH 08/15] arm64: dts: qcom: msm8939-samsung-a7: Add proximity/light sensor Add TMD3782 RGBC + proximity sensor on the bit-banged i2c-sensor bus at address 0x39. Uses GPIO113 as level-triggered interrupt input. Unlike the A5 which uses a GPIO-controlled fixed regulator for sensor VDD, the A7 powers the sensor directly from pm8916_l17. --- .../boot/dts/qcom/msm8939-samsung-a7.dts | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) 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"; From 3a0037bf8756dd96c783ce69bb9ee7d150778bc4 Mon Sep 17 00:00:00 2001 From: PlayDay <2457-playday3008@users.noreply.gitlab.postmarketos.org> Date: Mon, 11 May 2026 16:26:55 +0200 Subject: [PATCH 09/15] iio: light: tcs3472: Rename PDRIVE mask with TMD3782 prefix The PDRIVE field exists only in TMD3782, not TCS3472. Rename TCS3472_CONTROL_PDRIVE_MASK to TMD3782_CONTROL_PDRIVE_MASK to make this explicit. --- drivers/iio/light/tcs3472.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/drivers/iio/light/tcs3472.c b/drivers/iio/light/tcs3472.c index fdab9608b3d10a..9b23b835d0fe91 100644 --- a/drivers/iio/light/tcs3472.c +++ b/drivers/iio/light/tcs3472.c @@ -63,8 +63,7 @@ #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) +#define TMD3782_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) @@ -799,7 +798,7 @@ static int tcs3472_probe(struct i2c_client *client) } } } - data->control &= ~TCS3472_CONTROL_PDRIVE_MASK; + data->control &= ~TMD3782_CONTROL_PDRIVE_MASK; data->control |= (pdrive << 6); ret = i2c_smbus_write_byte_data(data->client, TCS3472_CONTROL, From a2db347d1be3e562cb112f509cfc434e544e3d19 Mon Sep 17 00:00:00 2001 From: PlayDay <2457-playday3008@users.noreply.gitlab.postmarketos.org> Date: Mon, 11 May 2026 16:27:55 +0200 Subject: [PATCH 10/15] iio: light: tcs3472: Use standalone chip_info structs instead of enum table Replace the enum-indexed tcs3472_chip_info_tbl[] array with standalone tcs3472_chip_info and tmd3782_chip_info structs, following the pattern recommended for modern IIO drivers (cf. veml6030). Add driver_data to the i2c_device_id table so i2c_get_match_data() works for non-OF paths. --- drivers/iio/light/tcs3472.c | 50 ++++++++++++++++++------------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/drivers/iio/light/tcs3472.c b/drivers/iio/light/tcs3472.c index 9b23b835d0fe91..f26555437443fa 100644 --- a/drivers/iio/light/tcs3472.c +++ b/drivers/iio/light/tcs3472.c @@ -81,10 +81,11 @@ #define TCS3472_ENABLE_PON BIT(0) #define TCS3472_CONTROL_AGAIN_MASK (BIT(0) | BIT(1)) -enum { - TCS3472_CHIP_TCS3472, - TCS3472_CHIP_TMD3782, -}; +/* Chip ID register values */ +#define TCS34721_CHIP_ID 0x44 +#define TCS34723_CHIP_ID 0x4d +#define TMD37821_CHIP_ID 0x60 +#define TMD37823_CHIP_ID 0x69 struct tcs3472_chip_info { const struct iio_chan_spec *channels; @@ -220,19 +221,18 @@ static const struct iio_chan_spec tmd3782_channels[] = { 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 const struct tcs3472_chip_info tcs3472_chip_info = { + .channels = tcs3472_channels, + .num_channels = ARRAY_SIZE(tcs3472_channels), + .has_proximity = false, + .name = "tcs3472", +}; + +static const struct tcs3472_chip_info tmd3782_chip_info = { + .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) @@ -759,10 +759,10 @@ static int tcs3472_probe(struct i2c_client *client) 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 if (ret == TCS34721_CHIP_ID || ret == TCS34723_CHIP_ID) { + data->chip_info = &tcs3472_chip_info; + } else if (ret == TMD37821_CHIP_ID || ret == TMD37823_CHIP_ID) { + data->chip_info = &tmd3782_chip_info; } else { return -ENODEV; } @@ -1000,16 +1000,16 @@ static DEFINE_SIMPLE_DEV_PM_OPS(tcs3472_pm_ops, tcs3472_suspend, static const struct of_device_id tcs3472_of_match[] = { { .compatible = "amstaos,tcs3472", - .data = &tcs3472_chip_info_tbl[TCS3472_CHIP_TCS3472] }, + .data = &tcs3472_chip_info }, { .compatible = "amstaos,tmd3782", - .data = &tcs3472_chip_info_tbl[TCS3472_CHIP_TMD3782] }, + .data = &tmd3782_chip_info }, { } }; MODULE_DEVICE_TABLE(of, tcs3472_of_match); static const struct i2c_device_id tcs3472_id[] = { - { "tcs3472" }, - { "tmd3782" }, + { "tcs3472", (kernel_ulong_t)&tcs3472_chip_info }, + { "tmd3782", (kernel_ulong_t)&tmd3782_chip_info }, { } }; MODULE_DEVICE_TABLE(i2c, tcs3472_id); From 4847c298fcd262c12cb6ebcf31ed74f7c56a8747 Mon Sep 17 00:00:00 2001 From: PlayDay <2457-playday3008@users.noreply.gitlab.postmarketos.org> Date: Mon, 11 May 2026 16:28:35 +0200 Subject: [PATCH 11/15] iio: light: tcs3472: Clear ALS and proximity interrupts independently Check AINT and PINT status bits independently and clear each interrupt separately, rather than using ALL_INTR_CLEAR when both fire simultaneously. Both interrupt sources can be pending at the same time and should each be cleared after being handled. --- drivers/iio/light/tcs3472.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/drivers/iio/light/tcs3472.c b/drivers/iio/light/tcs3472.c index f26555437443fa..1d8b45a03e25ec 100644 --- a/drivers/iio/light/tcs3472.c +++ b/drivers/iio/light/tcs3472.c @@ -591,12 +591,9 @@ static irqreturn_t tcs3472_event_handler(int irq, void *priv) 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) + if (ret & TCS3472_STATUS_AINT) i2c_smbus_read_byte_data(data->client, TCS3472_INTR_CLEAR); - else if (ret & TCS3472_STATUS_PINT) + if (ret & TCS3472_STATUS_PINT) i2c_smbus_read_byte_data(data->client, TCS3472_PROX_INTR_CLEAR); return IRQ_HANDLED; From 64e92da8bd11785f2ea9c6b00aa86515e1bb35ff Mon Sep 17 00:00:00 2001 From: PlayDay <2457-playday3008@users.noreply.gitlab.postmarketos.org> Date: Mon, 11 May 2026 16:28:46 +0200 Subject: [PATCH 12/15] dt-bindings: iio: light: amstaos,tcs3472: Add TMD3782 datasheet link Add a reference to the TMD3782 v2 datasheet hosted on DigiKey. --- .../devicetree/bindings/iio/light/amstaos,tcs3472.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Documentation/devicetree/bindings/iio/light/amstaos,tcs3472.yaml b/Documentation/devicetree/bindings/iio/light/amstaos,tcs3472.yaml index 3bdc61ae1c07ae..45a5c0063c4f84 100644 --- a/Documentation/devicetree/bindings/iio/light/amstaos,tcs3472.yaml +++ b/Documentation/devicetree/bindings/iio/light/amstaos,tcs3472.yaml @@ -18,6 +18,9 @@ description: | TCS3472 datasheet: https://ams.com/documents/20143/36005/TCS3472_DS000190_1-00.pdf + TMD3782 datasheet: + https://media.digikey.com/pdf/Data%20Sheets/Austriamicrosystems%20PDFs/TMD3782_v2.pdf + properties: compatible: enum: From db0ca8151b0bc3fd36540bf02918a60eb5cca4cc Mon Sep 17 00:00:00 2001 From: PlayDay <2457-playday3008@users.noreply.gitlab.postmarketos.org> Date: Mon, 11 May 2026 16:32:16 +0200 Subject: [PATCH 13/15] iio: light: tcs3472: Convert to guard(mutex) for automatic lock management Replace all manual mutex_lock/mutex_unlock pairs with guard(mutex), which auto-releases on scope exit. This eliminates goto-based error cleanup in write_event and resume, and prevents potential missed unlocks on early-return paths. --- drivers/iio/light/tcs3472.c | 122 ++++++++++++++---------------------- 1 file changed, 47 insertions(+), 75 deletions(-) diff --git a/drivers/iio/light/tcs3472.c b/drivers/iio/light/tcs3472.c index 1d8b45a03e25ec..3cf6cee7e4f62c 100644 --- a/drivers/iio/light/tcs3472.c +++ b/drivers/iio/light/tcs3472.c @@ -15,6 +15,7 @@ #include #include +#include #include #include #include @@ -273,23 +274,26 @@ static int tcs3472_read_raw(struct iio_dev *indio_dev, 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 | + { + guard(mutex)(&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; + iio_device_release_direct_mode( + indio_dev); + return ret; + } } } - mutex_unlock(&data->lock); ret = tcs3472_req_data(data, TCS3472_STATUS_PVALID); if (ret >= 0) @@ -297,7 +301,7 @@ static int tcs3472_read_raw(struct iio_dev *indio_dev, chan->address); if (cold) { - mutex_lock(&data->lock); + guard(mutex)(&data->lock); if (!data->prox_event_enabled && !data->prox_buf_enabled) { data->enable &= ~(TCS3472_ENABLE_PEN | @@ -306,7 +310,6 @@ static int tcs3472_read_raw(struct iio_dev *indio_dev, TCS3472_ENABLE, data->enable); } - mutex_unlock(&data->lock); } } else { ret = tcs3472_req_data(data, TCS3472_STATUS_AVALID); @@ -345,6 +348,7 @@ static int tcs3472_write_raw(struct iio_dev *indio_dev, return -EINVAL; for (i = 0; i < ARRAY_SIZE(tcs3472_agains); i++) { if (val == tcs3472_agains[i]) { + guard(mutex)(&data->lock); data->control &= ~TCS3472_CONTROL_AGAIN_MASK; data->control |= i; return i2c_smbus_write_byte_data( @@ -384,10 +388,9 @@ static int tcs3472_read_event(struct iio_dev *indio_dev, int *val2) { struct tcs3472_data *data = iio_priv(indio_dev); - int ret; unsigned int period; - mutex_lock(&data->lock); + guard(mutex)(&data->lock); switch (info) { case IIO_EV_INFO_VALUE: @@ -397,23 +400,16 @@ static int tcs3472_read_event(struct iio_dev *indio_dev, else *val = (dir == IIO_EV_DIR_RISING) ? data->high_thresh : data->low_thresh; - ret = IIO_VAL_INT; - break; + return IIO_VAL_INT; case IIO_EV_INFO_PERIOD: period = (256 - data->atime) * 2400 * tcs3472_intr_pers[data->apers]; *val = period / USEC_PER_SEC; *val2 = period % USEC_PER_SEC; - ret = IIO_VAL_INT_PLUS_MICRO; - break; + return IIO_VAL_INT_PLUS_MICRO; default: - ret = -EINVAL; - break; + return -EINVAL; } - - mutex_unlock(&data->lock); - - return ret; } static int tcs3472_write_event(struct iio_dev *indio_dev, @@ -427,7 +423,8 @@ static int tcs3472_write_event(struct iio_dev *indio_dev, int period; int i; - mutex_lock(&data->lock); + guard(mutex)(&data->lock); + switch (info) { case IIO_EV_INFO_VALUE: if (chan->type == IIO_PROXIMITY) { @@ -439,8 +436,7 @@ static int tcs3472_write_event(struct iio_dev *indio_dev, command = TCS3472_PILT; break; default: - ret = -EINVAL; - goto error; + return -EINVAL; } } else { switch (dir) { @@ -451,13 +447,12 @@ static int tcs3472_write_event(struct iio_dev *indio_dev, command = TCS3472_AILT; break; default: - ret = -EINVAL; - goto error; + return -EINVAL; } } ret = i2c_smbus_write_word_data(data->client, command, val); if (ret) - goto error; + return ret; if (chan->type == IIO_PROXIMITY) { if (dir == IIO_EV_DIR_RISING) @@ -470,13 +465,11 @@ static int tcs3472_write_event(struct iio_dev *indio_dev, else data->low_thresh = val; } - break; + return 0; case IIO_EV_INFO_PERIOD: - /* Period only applies to ALS events (APERS non-linear mapping) */ - if (chan->type == IIO_PROXIMITY) { - ret = -EINVAL; - goto error; - } + if (chan->type == IIO_PROXIMITY) + return -EINVAL; + period = val * USEC_PER_SEC + val2; for (i = 1; i < ARRAY_SIZE(tcs3472_intr_pers) - 1; i++) { if (period <= (256 - data->atime) * 2400 * @@ -486,18 +479,13 @@ static int tcs3472_write_event(struct iio_dev *indio_dev, ret = i2c_smbus_write_byte_data(data->client, TCS3472_PERS, (data->ppers << 4) | i); if (ret) - goto error; + return ret; data->apers = i; - break; + return 0; default: - ret = -EINVAL; - break; + return -EINVAL; } -error: - mutex_unlock(&data->lock); - - return ret; } static int tcs3472_read_event_config(struct iio_dev *indio_dev, @@ -505,16 +493,13 @@ static int tcs3472_read_event_config(struct iio_dev *indio_dev, enum iio_event_direction dir) { struct tcs3472_data *data = iio_priv(indio_dev); - int ret; - mutex_lock(&data->lock); + guard(mutex)(&data->lock); + if (chan->type == IIO_PROXIMITY) - ret = data->prox_event_enabled; - else - ret = !!(data->enable & TCS3472_ENABLE_AIEN); - mutex_unlock(&data->lock); + return data->prox_event_enabled; - return ret; + return !!(data->enable & TCS3472_ENABLE_AIEN); } static int tcs3472_write_event_config(struct iio_dev *indio_dev, @@ -529,7 +514,7 @@ static int tcs3472_write_event_config(struct iio_dev *indio_dev, if (!data->client->irq) return -EINVAL; - mutex_lock(&data->lock); + guard(mutex)(&data->lock); enable_old = data->enable; @@ -541,7 +526,6 @@ static int tcs3472_write_event_config(struct iio_dev *indio_dev, 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); @@ -562,7 +546,6 @@ static int tcs3472_write_event_config(struct iio_dev *indio_dev, data->prox_event_enabled = !state; } } - mutex_unlock(&data->lock); return ret; } @@ -645,7 +628,7 @@ static int tcs3472_buffer_preenable(struct iio_dev *indio_dev) if (!test_bit(4, indio_dev->active_scan_mask)) return 0; - mutex_lock(&data->lock); + guard(mutex)(&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); @@ -654,7 +637,6 @@ static int tcs3472_buffer_preenable(struct iio_dev *indio_dev) if (!data->prox_event_enabled) data->enable &= ~(TCS3472_ENABLE_PEN | TCS3472_ENABLE_WEN); } - mutex_unlock(&data->lock); return ret; } @@ -666,14 +648,13 @@ static int tcs3472_buffer_postdisable(struct iio_dev *indio_dev) if (!data->prox_buf_enabled) return 0; - mutex_lock(&data->lock); + guard(mutex)(&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; } @@ -909,15 +890,13 @@ static int tcs3472_powerdown(struct tcs3472_data *data) { int ret; - mutex_lock(&data->lock); + guard(mutex)(&data->lock); data->enable_saved = data->enable; ret = i2c_smbus_write_byte_data(data->client, TCS3472_ENABLE, 0x00); if (!ret) data->enable = 0; - mutex_unlock(&data->lock); - return ret; } @@ -945,13 +924,13 @@ static int tcs3472_resume(struct device *dev) to_i2c_client(dev))); int ret; - mutex_lock(&data->lock); + guard(mutex)(&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; + return ret; usleep_range(2500, 3000); @@ -973,11 +952,7 @@ static int tcs3472_resume(struct device *dev) 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. - */ + /* Clear stale interrupts before re-enabling sources */ i2c_smbus_read_byte_data(data->client, TCS3472_ALL_INTR_CLEAR); /* Restore ENABLE last — re-activates AEN, interrupt enables, etc. */ @@ -986,9 +961,6 @@ static int tcs3472_resume(struct device *dev) if (!ret) data->enable = data->enable_saved; -unlock: - mutex_unlock(&data->lock); - return ret; } From 97ec5224076eae3491c1b5f24f4ad763657c5550 Mon Sep 17 00:00:00 2001 From: PlayDay <2457-playday3008@users.noreply.gitlab.postmarketos.org> Date: Mon, 11 May 2026 16:34:49 +0200 Subject: [PATCH 14/15] iio: light: tcs3472: Expose LED drive current via IIO CALIBBIAS attribute Expose the TMD3782 proximity LED drive current (PDRIVE) as an IIO_CHAN_INFO_CALIBBIAS attribute on the proximity channel, allowing runtime control from userspace. Remove the led-max-microamp DT property since the hardware default (100mA) applies and userspace can override via calibbias. Accepted values: 12500, 25000, 50000, 100000 (microamps). --- .../bindings/iio/light/amstaos,tcs3472.yaml | 9 --- .../boot/dts/qcom/msm8916-samsung-a5u-eur.dts | 1 - .../boot/dts/qcom/msm8939-samsung-a7.dts | 1 - drivers/iio/light/tcs3472.c | 67 ++++++++++++------- 4 files changed, 42 insertions(+), 36 deletions(-) diff --git a/Documentation/devicetree/bindings/iio/light/amstaos,tcs3472.yaml b/Documentation/devicetree/bindings/iio/light/amstaos,tcs3472.yaml index 45a5c0063c4f84..b8cf952a6ad58b 100644 --- a/Documentation/devicetree/bindings/iio/light/amstaos,tcs3472.yaml +++ b/Documentation/devicetree/bindings/iio/light/amstaos,tcs3472.yaml @@ -42,14 +42,6 @@ properties: 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. @@ -80,7 +72,6 @@ examples: 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 45250abf4784f0..34e216c1cf4a0e 100644 --- a/arch/arm64/boot/dts/qcom/msm8916-samsung-a5u-eur.dts +++ b/arch/arm64/boot/dts/qcom/msm8916-samsung-a5u-eur.dts @@ -80,7 +80,6 @@ vddio-supply = <&pm8916_l5>; vled-supply = <®_prox_vled>; - led-max-microamp = <100000>; amstaos,proximity-pulse-count = <6>; pinctrl-0 = <&prox_int_default>; diff --git a/arch/arm64/boot/dts/qcom/msm8939-samsung-a7.dts b/arch/arm64/boot/dts/qcom/msm8939-samsung-a7.dts index b0535b4c1a784f..650fe73c4ef017 100644 --- a/arch/arm64/boot/dts/qcom/msm8939-samsung-a7.dts +++ b/arch/arm64/boot/dts/qcom/msm8939-samsung-a7.dts @@ -176,7 +176,6 @@ vddio-supply = <&pm8916_l5>; vled-supply = <&pm8916_l17>; - led-max-microamp = <100000>; amstaos,proximity-pulse-count = <6>; pinctrl-0 = <&prox_int_default>; diff --git a/drivers/iio/light/tcs3472.c b/drivers/iio/light/tcs3472.c index 3cf6cee7e4f62c..f6da57f60bda4e 100644 --- a/drivers/iio/light/tcs3472.c +++ b/drivers/iio/light/tcs3472.c @@ -15,6 +15,7 @@ #include #include +#include #include #include #include @@ -184,13 +185,7 @@ static const struct iio_event_spec tmd3782_prox_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 int tmd3782_pdrive_uamp[] = { 100000, 50000, 25000, 12500 }; static const struct iio_chan_spec tcs3472_channels[] = { TCS3472_CHANNEL(CLEAR, 0, TCS3472_CDATA), @@ -208,7 +203,10 @@ static const struct iio_chan_spec tmd3782_channels[] = { { .type = IIO_PROXIMITY, .address = TCS3472_PDATA, - .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_CALIBBIAS), + .info_mask_separate_available = + BIT(IIO_CHAN_INFO_CALIBBIAS), .scan_index = 4, .scan_type = { .sign = 'u', @@ -331,6 +329,10 @@ static int tcs3472_read_raw(struct iio_dev *indio_dev, *val = 0; *val2 = (256 - data->atime) * 2400; return IIO_VAL_INT_PLUS_MICRO; + case IIO_CHAN_INFO_CALIBBIAS: + *val = tmd3782_pdrive_uamp[FIELD_GET(TMD3782_CONTROL_PDRIVE_MASK, + data->control)]; + return IIO_VAL_INT; } return -EINVAL; } @@ -367,7 +369,21 @@ static int tcs3472_write_raw(struct iio_dev *indio_dev, data->client, TCS3472_ATIME, data->atime); } - + } + return -EINVAL; + case IIO_CHAN_INFO_CALIBBIAS: + if (val2 != 0) + return -EINVAL; + for (i = 0; i < ARRAY_SIZE(tmd3782_pdrive_uamp); i++) { + if (val == tmd3782_pdrive_uamp[i]) { + guard(mutex)(&data->lock); + data->control &= ~TMD3782_CONTROL_PDRIVE_MASK; + data->control |= FIELD_PREP( + TMD3782_CONTROL_PDRIVE_MASK, i); + return i2c_smbus_write_byte_data( + data->client, TCS3472_CONTROL, + data->control); + } } return -EINVAL; } @@ -694,9 +710,26 @@ static const struct attribute_group tcs3472_attribute_group = { .attrs = tcs3472_attributes, }; +static int tcs3472_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, + int *length, long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_CALIBBIAS: + *vals = tmd3782_pdrive_uamp; + *type = IIO_VAL_INT; + *length = ARRAY_SIZE(tmd3782_pdrive_uamp); + return IIO_AVAIL_LIST; + default: + return -EINVAL; + } +} + static const struct iio_info tcs3472_info = { .read_raw = tcs3472_read_raw, .write_raw = tcs3472_write_raw, + .read_avail = tcs3472_read_avail, .read_event_value = tcs3472_read_event, .write_event_value = tcs3472_write_event, .read_event_config = tcs3472_read_event_config, @@ -761,24 +794,8 @@ static int tcs3472_probe(struct i2c_client *client) 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 &= ~TMD3782_CONTROL_PDRIVE_MASK; - data->control |= (pdrive << 6); - ret = i2c_smbus_write_byte_data(data->client, TCS3472_CONTROL, data->control); if (ret < 0) From 9a0b423e384b46c96f630ad0b58416e542044432 Mon Sep 17 00:00:00 2001 From: PlayDay <2457-playday3008@users.noreply.gitlab.postmarketos.org> Date: Mon, 11 May 2026 16:39:10 +0200 Subject: [PATCH 15/15] iio: light: tcs3472: Expose pulse count via IIO OVERSAMPLING_RATIO attribute Expose the TMD3782 proximity pulse count (PPULSE) as an IIO_CHAN_INFO_OVERSAMPLING_RATIO attribute on the proximity channel, allowing runtime tuning from userspace. Remove the amstaos,proximity-pulse-count DT property since the driver defaults to 6 pulses and userspace can override. Accepted values: 1-255. --- .../bindings/iio/light/amstaos,tcs3472.yaml | 10 ------- .../boot/dts/qcom/msm8916-samsung-a5u-eur.dts | 2 -- .../boot/dts/qcom/msm8939-samsung-a7.dts | 2 -- drivers/iio/light/tcs3472.c | 29 ++++++++++++++----- 4 files changed, 21 insertions(+), 22 deletions(-) diff --git a/Documentation/devicetree/bindings/iio/light/amstaos,tcs3472.yaml b/Documentation/devicetree/bindings/iio/light/amstaos,tcs3472.yaml index b8cf952a6ad58b..68606586a21df5 100644 --- a/Documentation/devicetree/bindings/iio/light/amstaos,tcs3472.yaml +++ b/Documentation/devicetree/bindings/iio/light/amstaos,tcs3472.yaml @@ -42,15 +42,6 @@ properties: vled-supply: description: Regulator that provides power to the proximity IR LED - 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 @@ -72,7 +63,6 @@ examples: vdd-supply = <®_prox_vdd>; vddio-supply = <&pm8916_l5>; vled-supply = <®_prox_led>; - 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 34e216c1cf4a0e..0160cd4d09d9bd 100644 --- a/arch/arm64/boot/dts/qcom/msm8916-samsung-a5u-eur.dts +++ b/arch/arm64/boot/dts/qcom/msm8916-samsung-a5u-eur.dts @@ -80,8 +80,6 @@ vddio-supply = <&pm8916_l5>; vled-supply = <®_prox_vled>; - amstaos,proximity-pulse-count = <6>; - pinctrl-0 = <&prox_int_default>; pinctrl-names = "default"; }; diff --git a/arch/arm64/boot/dts/qcom/msm8939-samsung-a7.dts b/arch/arm64/boot/dts/qcom/msm8939-samsung-a7.dts index 650fe73c4ef017..5717d344fd511d 100644 --- a/arch/arm64/boot/dts/qcom/msm8939-samsung-a7.dts +++ b/arch/arm64/boot/dts/qcom/msm8939-samsung-a7.dts @@ -176,8 +176,6 @@ vddio-supply = <&pm8916_l5>; vled-supply = <&pm8916_l17>; - amstaos,proximity-pulse-count = <6>; - pinctrl-0 = <&prox_int_default>; pinctrl-names = "default"; }; diff --git a/drivers/iio/light/tcs3472.c b/drivers/iio/light/tcs3472.c index f6da57f60bda4e..c785f46e43a0a1 100644 --- a/drivers/iio/light/tcs3472.c +++ b/drivers/iio/light/tcs3472.c @@ -186,6 +186,7 @@ static const struct iio_event_spec tmd3782_prox_events[] = { static const int tcs3472_agains[] = { 1, 4, 16, 60 }; static const int tmd3782_pdrive_uamp[] = { 100000, 50000, 25000, 12500 }; +static const int tmd3782_ppulse_range[] = { 1, 1, 255 }; static const struct iio_chan_spec tcs3472_channels[] = { TCS3472_CHANNEL(CLEAR, 0, TCS3472_CDATA), @@ -204,9 +205,11 @@ static const struct iio_chan_spec tmd3782_channels[] = { .type = IIO_PROXIMITY, .address = TCS3472_PDATA, .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | - BIT(IIO_CHAN_INFO_CALIBBIAS), + BIT(IIO_CHAN_INFO_CALIBBIAS) | + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), .info_mask_separate_available = - BIT(IIO_CHAN_INFO_CALIBBIAS), + BIT(IIO_CHAN_INFO_CALIBBIAS) | + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), .scan_index = 4, .scan_type = { .sign = 'u', @@ -333,6 +336,9 @@ static int tcs3472_read_raw(struct iio_dev *indio_dev, *val = tmd3782_pdrive_uamp[FIELD_GET(TMD3782_CONTROL_PDRIVE_MASK, data->control)]; return IIO_VAL_INT; + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + *val = data->ppulse; + return IIO_VAL_INT; } return -EINVAL; } @@ -386,6 +392,13 @@ static int tcs3472_write_raw(struct iio_dev *indio_dev, } } return -EINVAL; + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + if (val < 1 || val > 255 || val2 != 0) + return -EINVAL; + guard(mutex)(&data->lock); + data->ppulse = val; + return i2c_smbus_write_byte_data(data->client, + TCS3472_PPULSE, data->ppulse); } return -EINVAL; } @@ -721,6 +734,11 @@ static int tcs3472_read_avail(struct iio_dev *indio_dev, *type = IIO_VAL_INT; *length = ARRAY_SIZE(tmd3782_pdrive_uamp); return IIO_AVAIL_LIST; + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + *vals = tmd3782_ppulse_range; + *type = IIO_VAL_INT; + *length = ARRAY_SIZE(tmd3782_ppulse_range); + return IIO_AVAIL_RANGE; default: return -EINVAL; } @@ -818,12 +836,7 @@ static int tcs3472_probe(struct i2c_client *client) 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); + data->ppulse = 6; ret = i2c_smbus_write_byte_data(data->client, TCS3472_PPULSE, data->ppulse); if (ret < 0)