From: Jorijn van der Graaf <jorijnvdgraaf@catcrafts.net>
To: Jonathan Cameron <jic23@kernel.org>, linux-iio@vger.kernel.org
Cc: "David Lechner" <dlechner@baylibre.com>,
"Nuno Sá" <nuno.sa@analog.com>,
"Andy Shevchenko" <andy@kernel.org>,
"Rob Herring" <robh@kernel.org>,
"Krzysztof Kozlowski" <krzk+dt@kernel.org>,
"Conor Dooley" <conor+dt@kernel.org>,
devicetree@vger.kernel.org, "Kees Cook" <kees@kernel.org>,
"Gustavo A . R . Silva" <gustavoars@kernel.org>,
linux-hardening@vger.kernel.org, linux-kernel@vger.kernel.org,
"Luca Weiss" <luca.weiss@fairphone.com>,
"Jorijn van der Graaf" <jorijnvdgraaf@catcrafts.net>
Subject: [PATCH v2 5/5] iio: light: stk3310: support the Sensortek STK36C61
Date: Wed, 26 Aug 2026 19:54:09 +0200 [thread overview]
Message-ID: <20260826175409.326131-6-jorijnvdgraaf@catcrafts.net> (raw)
In-Reply-To: <20260826175409.326131-1-jorijnvdgraaf@catcrafts.net>
The Sensortek STK36C61 is a 3-in-1 ambient light / proximity / RGB
colour sensor (chip ID 0x95) found in the Fairphone 6. Its register
interface is compatible with the feature set this driver uses: the
STATE/FLAG bit layout, the data and threshold registers and the gain
and integration-time fields, verified on that device (the ALS and
proximity readings scale with their gain and integration-time fields,
thresholds written through the event interface read back from the
chip, and the FLAG near/far bit crosses with them). Add its chip ID to
the known-ID list and the device table entries.
Whenever the ALS engine runs, the chip also measures four colour
channels, laid out directly after the ALS data as 16-bit big-endian
values in R (0x15), G (0x17), B (0x19), C (0x1B) order; the R, G and B
assignments were each confirmed by the matching channel dominating
under red, green and blue illumination, and clear by its broadband
response. The ALS data register tracks the green channel exactly.
The colour controls, all verified on the device: R, G and B are gained
by the same ALSCTRL gain field the illuminance channel uses, clear by
a GAIN_F_C field in a GAINCTRL register at 0x4E (documented in the
datasheet of the STK37660, a sibling part with the same data register
layout; stepping it multiplies the clear count by ~4 per step while
the other channels hold still), and the whole colour block integrates
over the ALS integration time. Expose the channels with per-channel
scale and a shared integration time, as suggested by Jonathan Cameron.
The scale attributes reuse the driver's existing table rather than the
iio-gts helper. The green channel equals the ALS data, so its scale
must read identically to the pre-existing in_illuminance_scale, which
has always been the gain selector alone with the integration time
exposed separately; iio-gts would fold the integration time into the
scale, and its scale writes may retune the integration time behind the
illuminance channel's back. The pre-existing available-values files
stay constant attributes; converting them to read_avail is a separate
cleanup, as it changes the long-standing scale lists' text ("6.4"
becomes "6.400000").
Assisted-by: Claude:claude-fable-5
Signed-off-by: Jorijn van der Graaf <jorijnvdgraaf@catcrafts.net>
---
Documentation/ABI/testing/sysfs-bus-iio | 1 +
drivers/iio/light/stk3310.c | 101 ++++++++++++++++++++----
2 files changed, 88 insertions(+), 14 deletions(-)
diff --git a/Documentation/ABI/testing/sysfs-bus-iio b/Documentation/ABI/testing/sysfs-bus-iio
index a4f5595722ad..08a8de215814 100644
--- a/Documentation/ABI/testing/sysfs-bus-iio
+++ b/Documentation/ABI/testing/sysfs-bus-iio
@@ -507,6 +507,7 @@ What: /sys/bus/iio/devices/iio:deviceX/in_intensity_z_scale
What: /sys/bus/iio/devices/iio:deviceX/in_intensity_red_scale
What: /sys/bus/iio/devices/iio:deviceX/in_intensity_green_scale
What: /sys/bus/iio/devices/iio:deviceX/in_intensity_blue_scale
+What: /sys/bus/iio/devices/iio:deviceX/in_intensity_clear_scale
What: /sys/bus/iio/devices/iio:deviceX/in_concentration_co2_scale
What: /sys/bus/iio/devices/iio:deviceX/in_volumeflow_scale
What: /sys/bus/iio/devices/iio:deviceX/in_volumeflowY_scale
diff --git a/drivers/iio/light/stk3310.c b/drivers/iio/light/stk3310.c
index d632e6447f06..efdb521881e6 100644
--- a/drivers/iio/light/stk3310.c
+++ b/drivers/iio/light/stk3310.c
@@ -40,7 +40,13 @@
#define STK3310_REG_PS_DATA_LSB 0x12
#define STK3310_REG_ALS_DATA_MSB 0x13
#define STK3310_REG_ALS_DATA_LSB 0x14
+#define STK36C61_REG_RED_DATA_MSB 0x15
+#define STK36C61_REG_GREEN_DATA_MSB 0x17
+#define STK36C61_REG_BLUE_DATA_MSB 0x19
+#define STK36C61_REG_CLEAR_DATA_MSB 0x1B
+#define STK36C61_REG_CLEAR_DATA_LSB 0x1C
#define STK3310_REG_ID 0x3E
+#define STK36C61_REG_GAINCTRL 0x4E
#define STK3310_MAX_REG 0x80
#define STK3310_STATE_EN_PS BIT(0)
@@ -54,6 +60,7 @@
#define STK3311S34_CHIP_ID_VAL 0x1E
#define STK3311X_CHIP_ID_VAL 0x12
#define STK3335_CHIP_ID_VAL 0x51
+#define STK36C61_CHIP_ID_VAL 0x95
#define STK3310_PSINT_EN 0x01
#define STK3310_PS_MAX_VAL 0xFFFF
@@ -83,6 +90,8 @@ static const struct reg_field stk3310_reg_field_als_gain =
REG_FIELD(STK3310_REG_ALSCTRL, 4, 5);
static const struct reg_field stk3310_reg_field_ps_gain =
REG_FIELD(STK3310_REG_PSCTRL, 4, 5);
+static const struct reg_field stk3310_reg_field_clear_gain =
+ REG_FIELD(STK36C61_REG_GAINCTRL, 4, 5);
static const struct reg_field stk3310_reg_field_als_it =
REG_FIELD(STK3310_REG_ALSCTRL, 0, 3);
static const struct reg_field stk3310_reg_field_ps_it =
@@ -102,6 +111,7 @@ static const u8 stk3310_chip_ids[] = {
STK3311X_CHIP_ID_VAL,
STK3311_CHIP_ID_VAL,
STK3335_CHIP_ID_VAL,
+ STK36C61_CHIP_ID_VAL,
};
/* Estimate maximum proximity values with regard to measurement scale. */
@@ -132,6 +142,7 @@ struct stk3310_data {
struct regmap_field *reg_state;
struct regmap_field *reg_als_gain;
struct regmap_field *reg_ps_gain;
+ struct regmap_field *reg_clear_gain;
struct regmap_field *reg_als_it;
struct regmap_field *reg_ps_it;
struct regmap_field *reg_int_ps;
@@ -202,11 +213,35 @@ static const struct iio_chan_spec_ext_info stk3310_ext_info[] = {
.ext_info = stk3310_ext_info, \
}
+#define STK36C61_INTENSITY_CHANNEL(_mod, _reg) { \
+ .type = IIO_INTENSITY, \
+ .address = _reg, \
+ .modified = 1, \
+ .channel2 = IIO_MOD_LIGHT_##_mod, \
+ .info_mask_separate = \
+ BIT(IIO_CHAN_INFO_RAW) | \
+ BIT(IIO_CHAN_INFO_SCALE), \
+ .info_mask_shared_by_type = \
+ BIT(IIO_CHAN_INFO_INT_TIME), \
+ .info_mask_shared_by_type_available = \
+ BIT(IIO_CHAN_INFO_SCALE) | \
+ BIT(IIO_CHAN_INFO_INT_TIME), \
+}
+
static const struct iio_chan_spec stk3310_channels[] = {
STK3310_LIGHT_CHANNEL,
STK3310_PROXIMITY_CHANNEL,
};
+static const struct iio_chan_spec stk36c61_channels[] = {
+ STK3310_LIGHT_CHANNEL,
+ STK3310_PROXIMITY_CHANNEL,
+ STK36C61_INTENSITY_CHANNEL(RED, STK36C61_REG_RED_DATA_MSB),
+ STK36C61_INTENSITY_CHANNEL(GREEN, STK36C61_REG_GREEN_DATA_MSB),
+ STK36C61_INTENSITY_CHANNEL(BLUE, STK36C61_REG_BLUE_DATA_MSB),
+ STK36C61_INTENSITY_CHANNEL(CLEAR, STK36C61_REG_CLEAR_DATA_MSB),
+};
+
/**
* struct stk3310_chip_info - chip-specific data
* @name: device name reported to the IIO core
@@ -225,6 +260,12 @@ static const struct stk3310_chip_info stk3310_chip_info = {
.num_channels = ARRAY_SIZE(stk3310_channels),
};
+static const struct stk3310_chip_info stk36c61_chip_info = {
+ .name = "stk36c61",
+ .channels = stk36c61_channels,
+ .num_channels = ARRAY_SIZE(stk36c61_channels),
+};
+
static IIO_CONST_ATTR(in_illuminance_scale_available, STK3310_SCALE_AVAILABLE);
static IIO_CONST_ATTR(in_proximity_scale_available, STK3310_SCALE_AVAILABLE);
@@ -401,7 +442,8 @@ static int stk3310_read_raw(struct iio_dev *indio_dev,
struct i2c_client *client = data->client;
struct regmap *map = data->regmap;
- if (chan->type != IIO_LIGHT && chan->type != IIO_PROXIMITY)
+ if (chan->type != IIO_LIGHT && chan->type != IIO_PROXIMITY &&
+ chan->type != IIO_INTENSITY)
return -EINVAL;
switch (mask) {
@@ -417,10 +459,10 @@ static int stk3310_read_raw(struct iio_dev *indio_dev,
mutex_unlock(&data->lock);
return IIO_VAL_INT;
case IIO_CHAN_INFO_INT_TIME:
- if (chan->type == IIO_LIGHT)
- ret = regmap_field_read(data->reg_als_it, &index);
- else
+ if (chan->type == IIO_PROXIMITY)
ret = regmap_field_read(data->reg_ps_it, &index);
+ else
+ ret = regmap_field_read(data->reg_als_it, &index);
if (ret < 0)
return ret;
@@ -428,10 +470,12 @@ static int stk3310_read_raw(struct iio_dev *indio_dev,
*val2 = stk3310_it_table[index][1];
return IIO_VAL_INT_PLUS_MICRO;
case IIO_CHAN_INFO_SCALE:
- if (chan->type == IIO_LIGHT)
- ret = regmap_field_read(data->reg_als_gain, &index);
- else
+ if (chan->type == IIO_PROXIMITY)
ret = regmap_field_read(data->reg_ps_gain, &index);
+ else if (chan->channel2 == IIO_MOD_LIGHT_CLEAR)
+ ret = regmap_field_read(data->reg_clear_gain, &index);
+ else
+ ret = regmap_field_read(data->reg_als_gain, &index);
if (ret < 0)
return ret;
@@ -451,7 +495,8 @@ static int stk3310_write_raw(struct iio_dev *indio_dev,
int index;
struct stk3310_data *data = iio_priv(indio_dev);
- if (chan->type != IIO_LIGHT && chan->type != IIO_PROXIMITY)
+ if (chan->type != IIO_LIGHT && chan->type != IIO_PROXIMITY &&
+ chan->type != IIO_INTENSITY)
return -EINVAL;
switch (mask) {
@@ -462,10 +507,10 @@ static int stk3310_write_raw(struct iio_dev *indio_dev,
if (index < 0)
return -EINVAL;
mutex_lock(&data->lock);
- if (chan->type == IIO_LIGHT)
- ret = regmap_field_write(data->reg_als_it, index);
- else
+ if (chan->type == IIO_PROXIMITY)
ret = regmap_field_write(data->reg_ps_it, index);
+ else
+ ret = regmap_field_write(data->reg_als_it, index);
if (ret < 0)
dev_err(&data->client->dev,
"sensor configuration failed\n");
@@ -479,10 +524,12 @@ static int stk3310_write_raw(struct iio_dev *indio_dev,
if (index < 0)
return -EINVAL;
mutex_lock(&data->lock);
- if (chan->type == IIO_LIGHT)
- ret = regmap_field_write(data->reg_als_gain, index);
- else
+ if (chan->type == IIO_PROXIMITY)
ret = regmap_field_write(data->reg_ps_gain, index);
+ else if (chan->channel2 == IIO_MOD_LIGHT_CLEAR)
+ ret = regmap_field_write(data->reg_clear_gain, index);
+ else
+ ret = regmap_field_write(data->reg_als_gain, index);
if (ret < 0)
dev_err(&data->client->dev,
"sensor configuration failed\n");
@@ -493,9 +540,31 @@ static int stk3310_write_raw(struct iio_dev *indio_dev,
return -EINVAL;
}
+static int stk3310_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_SCALE:
+ *vals = (const int *)stk3310_scale_table;
+ *length = 2 * ARRAY_SIZE(stk3310_scale_table);
+ *type = IIO_VAL_INT_PLUS_MICRO;
+ return IIO_AVAIL_LIST;
+ case IIO_CHAN_INFO_INT_TIME:
+ *vals = (const int *)stk3310_it_table;
+ *length = 2 * ARRAY_SIZE(stk3310_it_table);
+ *type = IIO_VAL_INT_PLUS_MICRO;
+ return IIO_AVAIL_LIST;
+ default:
+ return -EINVAL;
+ }
+}
+
static const struct iio_info stk3310_info = {
.read_raw = stk3310_read_raw,
.write_raw = stk3310_write_raw,
+ .read_avail = stk3310_read_avail,
.attrs = &stk3310_attribute_group,
.read_event_value = stk3310_read_event,
.write_event_value = stk3310_write_event,
@@ -567,6 +636,7 @@ static bool stk3310_is_volatile_reg(struct device *dev, unsigned int reg)
switch (reg) {
case STK3310_REG_ALS_DATA_MSB:
case STK3310_REG_ALS_DATA_LSB:
+ case STK36C61_REG_RED_DATA_MSB ... STK36C61_REG_CLEAR_DATA_LSB:
case STK3310_REG_PS_DATA_LSB:
case STK3310_REG_PS_DATA_MSB:
case STK3310_REG_FLAG:
@@ -601,6 +671,7 @@ static int stk3310_regmap_init(struct stk3310_data *data)
STK3310_REGFIELD(state);
STK3310_REGFIELD(als_gain);
STK3310_REGFIELD(ps_gain);
+ STK3310_REGFIELD(clear_gain);
STK3310_REGFIELD(als_it);
STK3310_REGFIELD(ps_it);
STK3310_REGFIELD(int_ps);
@@ -795,6 +866,7 @@ static const struct i2c_device_id stk3310_i2c_id[] = {
{ .name = "stk3310", .driver_data = (kernel_ulong_t)&stk3310_chip_info },
{ .name = "stk3311", .driver_data = (kernel_ulong_t)&stk3310_chip_info },
{ .name = "stk3335", .driver_data = (kernel_ulong_t)&stk3310_chip_info },
+ { .name = "stk36c61", .driver_data = (kernel_ulong_t)&stk36c61_chip_info },
{ }
};
MODULE_DEVICE_TABLE(i2c, stk3310_i2c_id);
@@ -812,6 +884,7 @@ static const struct of_device_id stk3310_of_match[] = {
{ .compatible = "sensortek,stk3310", .data = &stk3310_chip_info },
{ .compatible = "sensortek,stk3311", .data = &stk3310_chip_info },
{ .compatible = "sensortek,stk3335", .data = &stk3310_chip_info },
+ { .compatible = "sensortek,stk36c61", .data = &stk36c61_chip_info },
{ }
};
MODULE_DEVICE_TABLE(of, stk3310_of_match);
--
2.55.0
next prev parent reply other threads:[~2026-08-26 17:55 UTC|newest]
Thread overview: 13+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-08-26 17:54 [PATCH v2 0/5] iio: light: stk3310: per-chip match data and STK36C61 support Jorijn van der Graaf
2026-08-26 17:54 ` [PATCH v2 1/5] iio: light: stk3310: lower-case the i2c device ID names Jorijn van der Graaf
2026-08-26 18:03 ` sashiko-bot
2026-08-27 6:45 ` Andy Shevchenko
2026-08-26 17:54 ` [PATCH v2 2/5] dt-bindings: iio: light: stk33xx: document the Sensortek STK36C61 Jorijn van der Graaf
2026-08-26 17:54 ` [PATCH v2 3/5] iio: light: stk3310: move the data registers into the channel address Jorijn van der Graaf
2026-08-26 18:07 ` sashiko-bot
2026-08-27 6:47 ` Andy Shevchenko
2026-08-26 17:54 ` [PATCH v2 4/5] iio: light: stk3310: add per-chip match data Jorijn van der Graaf
2026-08-26 18:03 ` sashiko-bot
2026-08-27 7:47 ` Andy Shevchenko
2026-08-26 17:54 ` Jorijn van der Graaf [this message]
2026-08-27 7:53 ` [PATCH v2 5/5] iio: light: stk3310: support the Sensortek STK36C61 Andy Shevchenko
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260826175409.326131-6-jorijnvdgraaf@catcrafts.net \
--to=jorijnvdgraaf@catcrafts.net \
--cc=andy@kernel.org \
--cc=conor+dt@kernel.org \
--cc=devicetree@vger.kernel.org \
--cc=dlechner@baylibre.com \
--cc=gustavoars@kernel.org \
--cc=jic23@kernel.org \
--cc=kees@kernel.org \
--cc=krzk+dt@kernel.org \
--cc=linux-hardening@vger.kernel.org \
--cc=linux-iio@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=luca.weiss@fairphone.com \
--cc=nuno.sa@analog.com \
--cc=robh@kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox