From: Erikas Bitovtas <xerikasxx@gmail.com>
To: "Jonathan Cameron" <jic23@kernel.org>,
"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>,
"Kevin Tsai" <ktsai@capellamicro.com>
Cc: linux-iio@vger.kernel.org, devicetree@vger.kernel.org,
linux-kernel@vger.kernel.org,
~postmarketos/upstreaming@lists.sr.ht,
phone-devel@vger.kernel.org,
Erikas Bitovtas <xerikasxx@gmail.com>
Subject: [PATCH v2 2/2] iio: light: Add support for Capella cm36686 and cm36672p sensors
Date: Mon, 09 Feb 2026 16:23:42 +0200 [thread overview]
Message-ID: <20260209-cm36686-v2-2-a48126d2b124@gmail.com> (raw)
In-Reply-To: <20260209-cm36686-v2-0-a48126d2b124@gmail.com>
This driver adds the initial support for the Capella cm36686
ambient light and proximity sensor and cm36672p proximity sensor.
Signed-off-by: Erikas Bitovtas <xerikasxx@gmail.com>
---
drivers/iio/light/Kconfig | 11 +
drivers/iio/light/Makefile | 1 +
drivers/iio/light/cm36686.c | 940 ++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 952 insertions(+)
diff --git a/drivers/iio/light/Kconfig b/drivers/iio/light/Kconfig
index ac1408d374c9..b1d1943dec33 100644
--- a/drivers/iio/light/Kconfig
+++ b/drivers/iio/light/Kconfig
@@ -220,6 +220,17 @@ config CM36651
To compile this driver as a module, choose M here:
the module will be called cm36651.
+config CM36686
+ depends on I2C
+ tristate "CM36686 driver"
+ help
+ Say Y here if you use cm36686.
+ This option enables ambient light & proximity sensor using
+ Capella cm36686 device driver.
+
+ To compile this driver as a module, choose M here:
+ the module will be called cm36686.
+
config IIO_CROS_EC_LIGHT_PROX
tristate "ChromeOS EC Light and Proximity Sensors"
depends on IIO_CROS_EC_SENSORS_CORE
diff --git a/drivers/iio/light/Makefile b/drivers/iio/light/Makefile
index c0048e0d5ca8..806df80f6454 100644
--- a/drivers/iio/light/Makefile
+++ b/drivers/iio/light/Makefile
@@ -23,6 +23,7 @@ obj-$(CONFIG_CM3232) += cm3232.o
obj-$(CONFIG_CM3323) += cm3323.o
obj-$(CONFIG_CM3605) += cm3605.o
obj-$(CONFIG_CM36651) += cm36651.o
+obj-$(CONFIG_CM36686) += cm36686.o
obj-$(CONFIG_IIO_CROS_EC_LIGHT_PROX) += cros_ec_light_prox.o
obj-$(CONFIG_GP2AP002) += gp2ap002.o
obj-$(CONFIG_GP2AP020A00F) += gp2ap020a00f.o
diff --git a/drivers/iio/light/cm36686.c b/drivers/iio/light/cm36686.c
new file mode 100644
index 000000000000..aa1958cb0d7d
--- /dev/null
+++ b/drivers/iio/light/cm36686.c
@@ -0,0 +1,940 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/**
+ * Copyright (C) 2026 Erikas Bitovtas <xerikasxx@gmail.com>
+ *
+ * Based on downstream driver created by Capella Microsystems and modified
+ * by ASUS in drivers/misc/input/cm36283.c found in android_kernel_asus_msm8916
+ * Based on downstream IIO driver created by Qian Wenfa of Xiaomi
+ * in android_kernel_xiaomi_msm8992 in drivers/iio/light/cm36686.c
+ * Based on previous mailing list submission for cm36672p by Kevin Tsai:
+ * https://lore.kernel.org/linux-iio/1465462845-1571-1-git-send-email-capellamicro@gmail.com/
+ */
+
+#include <linux/array_size.h>
+#include <linux/bitfield.h>
+#include <linux/cleanup.h>
+#include <linux/dev_printk.h>
+#include <linux/device/devres.h>
+#include <linux/err.h>
+#include <linux/i2c.h>
+#include <linux/interrupt.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/property.h>
+#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
+#include <linux/sysfs.h>
+
+#include <linux/iio/events.h>
+#include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
+#include <linux/iio/types.h>
+
+/* Device registers */
+#define CM36686_REG_ALS_CONF 0x00
+#define CM36686_REG_PS_CONF1 0x03
+#define CM36686_REG_PS_CONF3 0x04
+#define CM36686_REG_PS_THDL 0x06
+#define CM36686_REG_PS_THDH 0x07
+#define CM36686_REG_PS_DATA 0x08
+#define CM36686_REG_ALS_DATA 0x09
+#define CM36686_REG_INT_FLAG 0x0B
+#define CM36686_REG_ID_FLAG 0x0C
+#define CM36686_MAX_REG 0x0D
+
+/* ALS_CONF */
+#define CM36686_ALS_IT GENMASK(7, 6)
+#define CM36686_ALS_GAIN GENMASK(3, 2)
+#define CM36686_ALS_INT_EN BIT(1)
+#define CM36686_ALS_SD BIT(0)
+
+/* PS_CONF1 */
+#define CM36686_PS_DR GENMASK(7, 6)
+#define CM36686_PS_PERS GENMASK(5, 4)
+#define CM36686_PS_IT GENMASK(3, 1)
+#define CM36686_PS_SD BIT(0)
+
+#define CM36686_PS_INT_OUT BIT(9)
+#define CM36686_PS_INT_IN BIT(8)
+
+/* PS_CONF3 */
+#define CM36686_PS_SMART_PERS_ENABLE BIT(4)
+
+#define CM36686_LED_I GENMASK(10, 8)
+
+/* INT_FLAG */
+#define CM36686_INT_FLAG_PS_IF_MASK GENMASK(9, 8)
+
+/* Default values */
+#define CM36686_ALS_ENABLE 0x00
+#define CM36686_PS_PERS_2 1
+#define CM36686_LED_I_50 0
+
+/* Max proximity thresholds */
+#define CM36686_MAX_PS_VALUE (BIT(12) - 1)
+
+#define CM36686_DEVICE_ID 0x86
+
+static const struct reg_field reg_field_als_it =
+ REG_FIELD(CM36686_REG_ALS_CONF, 6, 7);
+static const struct reg_field reg_field_ps_it =
+ REG_FIELD(CM36686_REG_PS_CONF1, 1, 3);
+static const struct reg_field reg_field_ps_int_in =
+ REG_FIELD(CM36686_REG_PS_CONF1, 8, 8);
+static const struct reg_field reg_field_ps_int_out =
+ REG_FIELD(CM36686_REG_PS_CONF1, 9, 9);
+
+enum cm36686_distance {
+ CM36686_AWAY = 1,
+ CM36686_CLOSE,
+ CM36686_BOTH,
+};
+
+static const int cm36686_als_it_times[][2] = {
+ { 0, 80000 },
+ { 0, 160000 },
+ { 0, 320000 },
+ { 0, 640000 },
+};
+
+static const int cm36686_ps_it_times[][2] = {
+ { 0, 80 },
+ { 0, 120 },
+ { 0, 160 },
+ { 0, 200 },
+ { 0, 240 },
+ { 0, 280 },
+ { 0, 320 },
+ { 0, 640 },
+};
+
+static const int cm36686_ps_led_current_uA[] = {
+ 50000,
+ 75000,
+ 100000,
+ 120000,
+ 140000,
+ 160000,
+ 180000,
+ 200000
+};
+
+struct cm36686_data {
+ /* Mutex lock to prevent simultaneous reads/writes into registers */
+ struct mutex lock;
+ struct i2c_client *client;
+ struct regmap *regmap;
+ struct regmap_field *reg_als_it;
+ struct regmap_field *reg_ps_it;
+ struct regmap_field *reg_ps_int_in;
+ struct regmap_field *reg_ps_int_out;
+ int glass_factor;
+};
+
+struct cm366xx_chip_info {
+ const char *name;
+ const struct iio_info *indio_info;
+ const struct iio_info *indio_info_no_irq;
+ const struct iio_chan_spec *channels;
+ const int num_channels;
+ const struct iio_chan_spec *channels_no_events;
+ const int num_channels_no_events;
+};
+
+static int cm36686_current_to_index(int led_current)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(cm36686_ps_led_current_uA); i++)
+ if (led_current <= cm36686_ps_led_current_uA[i])
+ return i;
+
+ return -EINVAL;
+}
+
+static ssize_t cm36686_read_near_level(struct iio_dev *indio_dev,
+ uintptr_t priv,
+ const struct iio_chan_spec *chan,
+ char *buf)
+{
+ struct cm36686_data *chip = iio_priv(indio_dev);
+ struct regmap *regmap = chip->regmap;
+ struct device *dev = &chip->client->dev;
+
+ int ps_close;
+ int ret = regmap_read(regmap, CM36686_REG_PS_THDH, &ps_close);
+
+ if (ret < 0) {
+ dev_err(dev, "Failed to read proximity near level");
+ return ret;
+ }
+
+ return sysfs_emit(buf, "%u\n", ps_close);
+}
+
+static const struct iio_chan_spec_ext_info cm36686_ext_info[] = {
+ {
+ .name = "nearlevel",
+ .shared = IIO_SEPARATE,
+ .read = cm36686_read_near_level,
+ },
+ { }
+};
+
+static const struct iio_event_spec cm36686_proximity_event_spec[] = {
+ {
+ .type = IIO_EV_TYPE_THRESH,
+ .dir = IIO_EV_DIR_FALLING,
+ .mask_separate = BIT(IIO_EV_INFO_VALUE) |
+ BIT(IIO_EV_INFO_ENABLE),
+ },
+ {
+ .type = IIO_EV_TYPE_THRESH,
+ .dir = IIO_EV_DIR_RISING,
+ .mask_separate = BIT(IIO_EV_INFO_VALUE) |
+ BIT(IIO_EV_INFO_ENABLE),
+ },
+};
+
+static const struct iio_chan_spec cm36686_channels[] = {
+ {
+ .type = IIO_LIGHT,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+ BIT(IIO_CHAN_INFO_INT_TIME) |
+ BIT(IIO_CHAN_INFO_SCALE),
+ .info_mask_separate_available = BIT(IIO_CHAN_INFO_INT_TIME),
+ .address = CM36686_REG_ALS_DATA,
+ },
+ {
+ .type = IIO_PROXIMITY,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+ BIT(IIO_CHAN_INFO_INT_TIME),
+ .info_mask_separate_available = BIT(IIO_CHAN_INFO_INT_TIME),
+ .address = CM36686_REG_PS_DATA,
+ .event_spec = cm36686_proximity_event_spec,
+ .num_event_specs = ARRAY_SIZE(cm36686_proximity_event_spec),
+ .ext_info = cm36686_ext_info,
+ },
+};
+
+static const struct iio_chan_spec cm36686_channels_no_events[] = {
+ {
+ .type = IIO_LIGHT,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+ BIT(IIO_CHAN_INFO_INT_TIME) |
+ BIT(IIO_CHAN_INFO_SCALE),
+ .info_mask_separate_available = BIT(IIO_CHAN_INFO_INT_TIME),
+ .address = CM36686_REG_ALS_DATA,
+ },
+ {
+ .type = IIO_PROXIMITY,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+ BIT(IIO_CHAN_INFO_INT_TIME),
+ .info_mask_separate_available = BIT(IIO_CHAN_INFO_INT_TIME),
+ .address = CM36686_REG_PS_DATA,
+ .ext_info = cm36686_ext_info,
+ },
+};
+
+static const struct iio_chan_spec cm36672p_channels[] = {
+ {
+ .type = IIO_PROXIMITY,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+ BIT(IIO_CHAN_INFO_INT_TIME),
+ .info_mask_separate_available = BIT(IIO_CHAN_INFO_INT_TIME),
+ .address = CM36686_REG_PS_DATA,
+ .event_spec = cm36686_proximity_event_spec,
+ .num_event_specs = ARRAY_SIZE(cm36686_proximity_event_spec),
+ .ext_info = cm36686_ext_info,
+ },
+};
+
+static const struct iio_chan_spec cm36672p_channels_no_events[] = {
+ {
+ .type = IIO_PROXIMITY,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+ BIT(IIO_CHAN_INFO_INT_TIME),
+ .info_mask_separate_available = BIT(IIO_CHAN_INFO_INT_TIME),
+ .address = CM36686_REG_PS_DATA,
+ .ext_info = cm36686_ext_info,
+ },
+};
+
+static int cm36686_read_avail(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ const int **vals, int *type, int *length,
+ long mask)
+{
+ if (mask != IIO_CHAN_INFO_INT_TIME)
+ return -EINVAL;
+
+ switch (chan->type) {
+ case IIO_LIGHT:
+ *vals = (int *)(cm36686_als_it_times);
+ *length = 2 * ARRAY_SIZE(cm36686_als_it_times);
+ *type = IIO_VAL_INT_PLUS_MICRO;
+ return IIO_AVAIL_LIST;
+ case IIO_PROXIMITY:
+ *vals = (int *)(cm36686_ps_it_times);
+ *length = 2 * ARRAY_SIZE(cm36686_ps_it_times);
+ *type = IIO_VAL_INT_PLUS_MICRO;
+ return IIO_AVAIL_LIST;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int cm36686_read_channel(struct cm36686_data *chip,
+ struct iio_chan_spec const *chan, int *val)
+{
+ struct regmap *regmap = chip->regmap;
+ int ret = regmap_read(regmap, chan->address, val);
+
+ if (ret < 0)
+ return -EIO;
+
+ return IIO_VAL_INT;
+}
+
+/**
+ * This is taken from Xiaomi's driver for cm36686. The device tree for cm36686
+ * in android_kernel_xiaomi_msm8992 includes a property called
+ * "als_trans_ratio". This property seems to be a number by which the actual
+ * device lux scale is multiplied. What we know for sure is that it is
+ * set to 16, and integration time in downstream is set to 160ms. Using this
+ * "als_trans_ratio" property, Xiaomi calculates scale like this:
+ * scale = als_trans_ratio * 40,000
+ * val = scale / 1,000,000
+ * val2 = scale % 1,000,000
+ * In our driver, however, integration time can be adjusted, so if it changes
+ * during runtime, the scale will be incorrect and lux value will be reported
+ * double or half what it actually is, depending on whether we increase or
+ * decrease integration time. In order to preserve the proportion by which the
+ * scale is calculated, we multiply "als_trans_ratio" (16) by 160ms and then
+ * divide it by our current integration time. This gives us new
+ * "als_trans_ratio" by which the scale will be calculated.
+ * Unfortunately, since the datasheet for this sensor is unavailable, this
+ * guess is the best we have at the moment.
+ */
+static int cm36686_read_scale(struct cm36686_data *chip,
+ struct iio_chan_spec const *chan, int *val,
+ int *val2)
+{
+ int als_index;
+ int ret;
+
+ if (chan->type != IIO_LIGHT)
+ return -EINVAL;
+
+ ret = regmap_field_read(chip->reg_als_it, &als_index);
+ if (ret < 0)
+ return ret;
+
+ int als_it = cm36686_als_it_times[als_index][1];
+ int scale = (chip->glass_factor * 160000 / als_it) * 40000;
+
+ *val = scale / 1000000;
+ *val2 = scale % 1000000;
+
+ return IIO_VAL_INT_PLUS_MICRO;
+}
+
+static int cm36686_read_int_time(struct cm36686_data *chip,
+ struct iio_chan_spec const *chan, int *val,
+ int *val2)
+{
+ int als_it_index, ps_it_index;
+ int ret;
+
+ switch (chan->type) {
+ case IIO_LIGHT:
+ ret = regmap_field_read(chip->reg_als_it, &als_it_index);
+ if (ret < 0)
+ return ret;
+ *val = cm36686_als_it_times[als_it_index][0];
+ *val2 = cm36686_als_it_times[als_it_index][1];
+ return IIO_VAL_INT_PLUS_MICRO;
+ case IIO_PROXIMITY:
+ ret = regmap_field_read(chip->reg_ps_it, &ps_it_index);
+ if (ret < 0)
+ return ret;
+ *val = cm36686_ps_it_times[ps_it_index][0];
+ *val2 = cm36686_ps_it_times[ps_it_index][1];
+ return IIO_VAL_INT_PLUS_MICRO;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int cm36686_write_light_int_time(struct cm36686_data *chip, int val2)
+{
+ int index = -1, ret;
+
+ for (int i = 0; i < ARRAY_SIZE(cm36686_als_it_times); i++) {
+ if (cm36686_als_it_times[i][1] == val2) {
+ index = i;
+ break;
+ }
+ }
+
+ if (index == -1)
+ return -EINVAL;
+
+ ret = regmap_field_write(chip->reg_als_it, index);
+ return ret;
+}
+
+static int cm36686_write_prox_int_time(struct cm36686_data *chip, int val2)
+{
+ int index = -1, ret;
+
+ for (int i = 0; i < ARRAY_SIZE(cm36686_ps_it_times); i++) {
+ if (cm36686_ps_it_times[i][1] == val2) {
+ index = i;
+ break;
+ }
+ }
+
+ if (index == -1)
+ return -EINVAL;
+
+ ret = regmap_field_write(chip->reg_ps_it, index);
+ return ret;
+}
+
+static int cm36686_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan, int *val,
+ int *val2, long mask)
+{
+ struct cm36686_data *chip = iio_priv(indio_dev);
+
+ guard(mutex)(&chip->lock);
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ return cm36686_read_channel(chip, chan, val);
+ case IIO_CHAN_INFO_SCALE:
+ return cm36686_read_scale(chip, chan, val, val2);
+ case IIO_CHAN_INFO_INT_TIME:
+ return cm36686_read_int_time(chip, chan, val, val2);
+ default:
+ return -EINVAL;
+ }
+}
+
+static int cm36686_write_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan, int val,
+ int val2, long mask)
+{
+ struct cm36686_data *chip = iio_priv(indio_dev);
+
+ if (val) /* Integration time more than 1s is not supported */
+ return -EINVAL;
+
+ if (mask != IIO_CHAN_INFO_INT_TIME)
+ return -EINVAL;
+
+ guard(mutex)(&chip->lock);
+ switch (chan->type) {
+ case IIO_LIGHT:
+ return cm36686_write_light_int_time(chip, val2);
+ case IIO_PROXIMITY:
+ return cm36686_write_prox_int_time(chip, val2);
+ default:
+ return -EINVAL;
+ }
+}
+
+static int cm36686_read_prox_thresh(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan,
+ enum iio_event_type type,
+ enum iio_event_direction dir,
+ enum iio_event_info info, int *val,
+ int *val2)
+{
+ struct cm36686_data *chip = iio_priv(indio_dev);
+ struct regmap *regmap = chip->regmap;
+ int ret;
+
+ if (chan->type != IIO_PROXIMITY)
+ return -EINVAL;
+
+ guard(mutex)(&chip->lock);
+ switch (dir) {
+ case IIO_EV_DIR_RISING:
+ ret = regmap_read(regmap, CM36686_REG_PS_THDH, val);
+ break;
+ case IIO_EV_DIR_FALLING:
+ ret = regmap_read(regmap, CM36686_REG_PS_THDL, val);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ if (ret < 0)
+ return ret;
+
+ return IIO_VAL_INT;
+}
+
+static int cm36686_write_prox_thresh(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan,
+ enum iio_event_type type,
+ enum iio_event_direction dir,
+ enum iio_event_info info, int val,
+ int val2)
+{
+ struct cm36686_data *chip = iio_priv(indio_dev);
+ struct regmap *regmap = chip->regmap;
+ int ps_close, ps_away, address;
+ int ret;
+
+ if (chan->type != IIO_PROXIMITY)
+ return -EINVAL;
+
+ ret = regmap_read(regmap, CM36686_REG_PS_THDH, &ps_close);
+ if (ret < 0)
+ return ret;
+
+ ret = regmap_read(regmap, CM36686_REG_PS_THDL, &ps_away);
+ if (ret < 0)
+ return ret;
+
+ switch (dir) {
+ case IIO_EV_DIR_FALLING:
+ if (val > ps_close || val < 0)
+ return -EINVAL;
+
+ address = CM36686_REG_PS_THDL;
+ break;
+ case IIO_EV_DIR_RISING:
+ if (val < ps_away || val > CM36686_MAX_PS_VALUE)
+ return -EINVAL;
+
+ address = CM36686_REG_PS_THDH;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ guard(mutex)(&chip->lock);
+ ret = regmap_write(regmap, address, val);
+
+ return ret;
+}
+
+static int cm36686_read_prox_event_config(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan,
+ enum iio_event_type type,
+ enum iio_event_direction dir)
+{
+ struct cm36686_data *chip = iio_priv(indio_dev);
+ struct regmap_field *reg_field;
+ int state;
+ int ret;
+
+ if (chan->type != IIO_PROXIMITY)
+ return -EINVAL;
+
+ switch (dir) {
+ case IIO_EV_DIR_FALLING:
+ reg_field = chip->reg_ps_int_out;
+ break;
+ case IIO_EV_DIR_RISING:
+ reg_field = chip->reg_ps_int_in;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ guard(mutex)(&chip->lock);
+ ret = regmap_field_read(reg_field, &state);
+ if (ret < 0)
+ return ret;
+
+ return state;
+}
+
+static int cm36686_write_prox_event_config(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan,
+ enum iio_event_type type,
+ enum iio_event_direction dir,
+ bool state)
+{
+ struct cm36686_data *chip = iio_priv(indio_dev);
+ struct regmap_field *reg_field;
+ int ret;
+
+ if (chan->type != IIO_PROXIMITY)
+ return -EINVAL;
+
+ switch (dir) {
+ case IIO_EV_DIR_FALLING:
+ reg_field = chip->reg_ps_int_out;
+ break;
+ case IIO_EV_DIR_RISING:
+ reg_field = chip->reg_ps_int_in;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ guard(mutex)(&chip->lock);
+ ret = regmap_field_write(reg_field, state);
+
+ return ret;
+}
+
+static int cm36686_fallback_read_ps(struct iio_dev *indio_dev)
+{
+ struct cm36686_data *chip = iio_priv(indio_dev);
+ struct regmap *regmap = chip->regmap;
+ int ps_close, ps_away, data;
+ int ret;
+
+ ret = regmap_read(regmap, CM36686_REG_PS_THDH, &ps_close);
+ if (ret < 0)
+ return ret;
+
+ ret = regmap_read(regmap, CM36686_REG_PS_THDL, &ps_away);
+ if (ret < 0)
+ return ret;
+
+ ret = regmap_read(regmap, CM36686_REG_PS_DATA, &data);
+ if (ret < 0)
+ return ret;
+
+ if (data < ps_away)
+ return IIO_EV_DIR_FALLING;
+ else if (data > ps_close)
+ return IIO_EV_DIR_RISING;
+ return IIO_EV_DIR_EITHER;
+}
+
+static irqreturn_t cm36686_irq_handler(int irq, void *data)
+{
+ struct iio_dev *indio_dev = data;
+ struct cm36686_data *chip = iio_priv(indio_dev);
+ struct i2c_client *client = chip->client;
+ struct regmap *regmap = chip->regmap;
+ int ev_dir, ret, status;
+ u64 ev_code;
+
+ /* Reading the interrupt flag acknowledges the interrupt */
+ ret = regmap_read(regmap, CM36686_REG_INT_FLAG, &status);
+ if (ret < 0) {
+ dev_err_ratelimited(&client->dev,
+ "Interrupt flag register read failed: %pe",
+ ERR_PTR(ret));
+ return IRQ_HANDLED;
+ }
+
+ status = FIELD_GET(CM36686_INT_FLAG_PS_IF_MASK, status);
+ switch (status) {
+ case CM36686_CLOSE:
+ ev_dir = IIO_EV_DIR_RISING;
+ break;
+ case CM36686_AWAY:
+ ev_dir = IIO_EV_DIR_FALLING;
+ break;
+ case CM36686_BOTH:
+ ev_dir = cm36686_fallback_read_ps(indio_dev);
+ if (ev_dir < 0) {
+ dev_err_ratelimited(&client->dev,
+ "Failed to settle interrupt state: %pe",
+ ERR_PTR(ret));
+ return IRQ_HANDLED;
+ }
+ break;
+ default:
+ dev_err_ratelimited(&client->dev,
+ "Unknown interrupt state: %x", status);
+ return IRQ_HANDLED;
+ }
+ ev_code = IIO_UNMOD_EVENT_CODE(IIO_PROXIMITY, IIO_EV_INFO_VALUE,
+ IIO_EV_TYPE_THRESH, ev_dir);
+
+ iio_push_event(indio_dev, ev_code, iio_get_time_ns(indio_dev));
+ return IRQ_HANDLED;
+}
+
+static const struct iio_info cm36686_info = {
+ .read_avail = cm36686_read_avail,
+ .read_raw = cm36686_read_raw,
+ .write_raw = cm36686_write_raw,
+ .read_event_value = cm36686_read_prox_thresh,
+ .write_event_value = cm36686_write_prox_thresh,
+ .read_event_config = cm36686_read_prox_event_config,
+ .write_event_config = cm36686_write_prox_event_config,
+};
+
+static const struct iio_info cm36686_info_no_irq = {
+ .read_avail = cm36686_read_avail,
+ .read_raw = cm36686_read_raw,
+ .write_raw = cm36686_write_raw,
+};
+
+static const struct cm366xx_chip_info cm36686_chip_info = {
+ .name = "cm36686",
+ .indio_info = &cm36686_info,
+ .indio_info_no_irq = &cm36686_info_no_irq,
+ .channels = cm36686_channels,
+ .num_channels = ARRAY_SIZE(cm36686_channels),
+ .channels_no_events = cm36686_channels_no_events,
+ .num_channels_no_events = ARRAY_SIZE(cm36686_channels_no_events),
+};
+
+static const struct cm366xx_chip_info cm36672p_chip_info = {
+ .name = "cm36672p",
+ .indio_info = &cm36686_info,
+ .indio_info_no_irq = &cm36686_info_no_irq,
+ .channels = cm36672p_channels,
+ .num_channels = ARRAY_SIZE(cm36672p_channels),
+ .channels_no_events = cm36672p_channels_no_events,
+ .num_channels_no_events = ARRAY_SIZE(cm36672p_channels_no_events),
+};
+
+static bool cm36686_is_volatile_reg(struct device *dev, unsigned int reg)
+{
+ if (reg >= CM36686_REG_PS_DATA)
+ return true;
+ return false;
+}
+
+static bool cm36686_is_precious_reg(struct device *dev, unsigned int reg)
+{
+ if (reg == CM36686_REG_INT_FLAG)
+ return true;
+ return false;
+}
+
+static const struct regmap_config cm36686_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 16,
+ .cache_type = REGCACHE_MAPLE,
+ .max_register = CM36686_MAX_REG,
+ .volatile_reg = cm36686_is_volatile_reg,
+ .precious_reg = cm36686_is_precious_reg,
+ .val_format_endian = REGMAP_ENDIAN_LITTLE,
+};
+
+static int cm36686_setup(struct cm36686_data *chip, struct iio_dev *indio_dev)
+{
+ struct i2c_client *client = chip->client;
+ struct regmap *regmap = chip->regmap;
+ struct device *dev = &client->dev;
+ const struct cm366xx_chip_info *info = i2c_get_match_data(client);
+ int led_current, led_index;
+ int als_conf;
+ int ps_conf1, ps_conf3;
+ int ps_close, ps_away;
+ int ret;
+
+ als_conf = CM36686_ALS_ENABLE;
+
+ ret = regmap_write(regmap, CM36686_REG_ALS_CONF, als_conf);
+ if (ret < 0)
+ return dev_err_probe(dev, ret,
+ "Failed to enable ambient light sensor");
+
+ /* Set proximity sensor persistence to two to prevent
+ * false triggering of the interrupt when probing the
+ * driver.
+ */
+ ps_conf1 = FIELD_PREP(CM36686_PS_PERS, CM36686_PS_PERS_2);
+ if (client->irq)
+ ps_conf1 |= CM36686_PS_INT_IN | CM36686_PS_INT_OUT;
+
+ ret = regmap_write(regmap, CM36686_REG_PS_CONF1, ps_conf1);
+ if (ret < 0)
+ return dev_err_probe(dev, ret,
+ "Failed to enable proximity sensor");
+
+ ps_conf3 = CM36686_PS_SMART_PERS_ENABLE;
+
+ led_index = CM36686_LED_I_50;
+ ret = device_property_read_u32(dev,
+ "capella,proximity-led-current-milliamp", &led_current);
+ if (!ret) {
+ led_index = cm36686_current_to_index(led_current);
+ if (led_index < 0)
+ return dev_err_probe(dev, led_index,
+ "Failed to find appropriate IR LED current.");
+ } else if (ret != -EINVAL) {
+ return dev_err_probe(dev, ret,
+ "Failed to read IR LED current.");
+ }
+
+ FIELD_MODIFY(CM36686_LED_I, &ps_conf3, led_index);
+
+ ret = regmap_write(regmap, CM36686_REG_PS_CONF3, ps_conf3);
+ if (ret < 0)
+ return dev_err_probe(dev, ret,
+ "Failed to enable proximity sensor");
+
+ if (device_property_read_u32(dev, "capella,glass-factor",
+ &chip->glass_factor))
+ chip->glass_factor = 1;
+
+ if (device_property_read_u32(dev, "proximity-near-level", &ps_close))
+ ps_close = 0;
+ ps_away = ps_close;
+
+ ret = regmap_write(regmap, CM36686_REG_PS_THDH, ps_close);
+ if (ret < 0)
+ return dev_err_probe(dev, ret,
+ "Failed to set close proximity threshold");
+
+ ret = regmap_write(regmap, CM36686_REG_PS_THDL, ps_away);
+ if (ret < 0)
+ return dev_err_probe(dev, ret,
+ "Failed to set away proximity threshold");
+
+ indio_dev->name = info->name;
+ indio_dev->modes = INDIO_DIRECT_MODE;
+ if (client->irq) {
+ indio_dev->info = info->indio_info;
+ indio_dev->channels = info->channels;
+ indio_dev->num_channels = info->num_channels;
+
+ ret = devm_request_threaded_irq(dev, client->irq,
+ NULL, cm36686_irq_handler,
+ IRQF_TRIGGER_FALLING |
+ IRQF_ONESHOT,
+ indio_dev->name, indio_dev);
+ if (ret < 0)
+ return dev_err_probe(dev, ret,
+ "Failed to request irq");
+ } else {
+ indio_dev->info = info->indio_info_no_irq;
+ indio_dev->channels = info->channels_no_events;
+ indio_dev->num_channels = info->num_channels_no_events;
+ }
+
+ return 0;
+}
+
+static void cm36686_shutdown(void *data)
+{
+ struct cm36686_data *chip = data;
+ struct i2c_client *client = chip->client;
+ int ret;
+
+ ret = regmap_set_bits(chip->regmap, CM36686_REG_ALS_CONF,
+ CM36686_ALS_SD);
+ if (ret < 0)
+ dev_err(&client->dev, "Failed to shutdown ALS");
+
+ ret = regmap_set_bits(chip->regmap, CM36686_REG_PS_CONF1,
+ CM36686_PS_SD);
+ if (ret < 0)
+ dev_err(&client->dev, "Failed to shutdown PS");
+}
+
+static int cm36686_probe(struct i2c_client *client)
+{
+ struct iio_dev *indio_dev;
+ struct cm36686_data *chip;
+ struct device *dev = &client->dev;
+ struct regmap *regmap;
+ int ret, partid;
+ static const char * const regulator_names[] = { "vdd", "vddio",
+ "vled" };
+
+ indio_dev = devm_iio_device_alloc(dev, sizeof(*chip));
+ if (!indio_dev)
+ return -ENOMEM;
+
+ chip = iio_priv(indio_dev);
+
+ regmap = devm_regmap_init_i2c(client, &cm36686_regmap_config);
+ if (IS_ERR(regmap))
+ return dev_err_probe(dev, PTR_ERR(regmap),
+ "Failed to initialize regmap");
+
+ chip->client = client;
+ chip->regmap = regmap;
+
+ ret = devm_mutex_init(dev, &chip->lock);
+ if (ret < 0)
+ return ret;
+
+ ret = devm_regulator_bulk_get_enable(dev,
+ ARRAY_SIZE(regulator_names),
+ regulator_names);
+ if (ret < 0)
+ return dev_err_probe(dev, ret, "Failed to enable regulators");
+
+ chip->reg_als_it = devm_regmap_field_alloc(dev, regmap,
+ reg_field_als_it);
+ if (IS_ERR(chip->reg_als_it))
+ return dev_err_probe(dev, PTR_ERR(chip->reg_als_it),
+ "Failed to initialize ALS it reg field");
+
+ chip->reg_ps_it = devm_regmap_field_alloc(dev, regmap,
+ reg_field_ps_it);
+ if (IS_ERR(chip->reg_ps_it))
+ return dev_err_probe(dev, PTR_ERR(chip->reg_ps_it),
+ "Failed to initialize PS it reg field");
+
+ chip->reg_ps_int_in = devm_regmap_field_alloc(dev, regmap,
+ reg_field_ps_int_in);
+ if (IS_ERR(chip->reg_ps_int_in))
+ return dev_err_probe(dev, PTR_ERR(chip->reg_ps_int_in),
+ "Failed to initialize PS int in field");
+
+ chip->reg_ps_int_out = devm_regmap_field_alloc(dev, regmap,
+ reg_field_ps_int_out);
+ if (IS_ERR(chip->reg_ps_int_out))
+ return dev_err_probe(dev, PTR_ERR(chip->reg_ps_int_out),
+ "Failed to initialize PS int out field");
+
+ ret = regmap_read(chip->regmap, CM36686_REG_ID_FLAG, &partid);
+ if (ret < 0)
+ return dev_err_probe(dev, ret, "Failed to read device ID");
+
+ partid &= 0xff;
+ if (partid != CM36686_DEVICE_ID)
+ dev_warn(dev, "Device ID: %02x, expected: %02x", partid,
+ CM36686_DEVICE_ID);
+
+
+ ret = cm36686_setup(chip, indio_dev);
+ if (ret < 0)
+ return ret;
+
+ ret = devm_add_action_or_reset(dev, cm36686_shutdown, chip);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "Failed to set shutdown action");
+
+ return devm_iio_device_register(dev, indio_dev);
+}
+
+static const struct i2c_device_id cm36686_id[] = {
+ { "cm36686", (kernel_ulong_t)&cm36686_chip_info },
+ { "cm36672p", (kernel_ulong_t)&cm36672p_chip_info },
+ { }
+};
+
+MODULE_DEVICE_TABLE(i2c, cm36686_id);
+
+static const struct of_device_id cm36686_of_match[] = {
+ { .compatible = "capella,cm36686", .data = &cm36686_chip_info, },
+ { .compatible = "capella,cm36672p", .data = &cm36672p_chip_info, },
+ { }
+};
+MODULE_DEVICE_TABLE(of, cm36686_of_match);
+
+static struct i2c_driver cm36686_driver = {
+ .driver = {
+ .name = "cm36686",
+ .of_match_table = cm36686_of_match,
+ },
+ .probe = cm36686_probe,
+ .id_table = cm36686_id,
+};
+module_i2c_driver(cm36686_driver);
+
+MODULE_AUTHOR("Erikas Bitovtas <xerikasxx@gmail.com>");
+MODULE_DESCRIPTION("CM36686 ambient light and proximity sensor driver");
+MODULE_LICENSE("GPL");
--
2.53.0
next prev parent reply other threads:[~2026-02-09 14:24 UTC|newest]
Thread overview: 11+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-02-09 14:23 [PATCH v2 0/2] iio: light: Add support for Capella cm36686 and cm36672p sensors Erikas Bitovtas
2026-02-09 14:23 ` [PATCH v2 1/2] dt-bindings: Add binding document for cm36686 Erikas Bitovtas
2026-02-09 14:45 ` Krzysztof Kozlowski
2026-02-09 15:02 ` Erikas Bitovtas
2026-02-09 16:12 ` Krzysztof Kozlowski
2026-02-09 14:23 ` Erikas Bitovtas [this message]
2026-02-10 3:27 ` [PATCH v2 2/2] iio: light: Add support for Capella cm36686 and cm36672p sensors kernel test robot
2026-02-09 15:48 ` [PATCH v2 0/2] " Andy Shevchenko
2026-02-09 15:49 ` Andy Shevchenko
2026-02-09 18:20 ` Erikas Bitovtas
2026-02-09 18:24 ` Jonathan Cameron
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=20260209-cm36686-v2-2-a48126d2b124@gmail.com \
--to=xerikasxx@gmail.com \
--cc=andy@kernel.org \
--cc=conor+dt@kernel.org \
--cc=devicetree@vger.kernel.org \
--cc=dlechner@baylibre.com \
--cc=jic23@kernel.org \
--cc=krzk+dt@kernel.org \
--cc=ktsai@capellamicro.com \
--cc=linux-iio@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=nuno.sa@analog.com \
--cc=phone-devel@vger.kernel.org \
--cc=robh@kernel.org \
--cc=~postmarketos/upstreaming@lists.sr.ht \
/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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.