* [PATCH v3 0/2] iio: adc: Add the Ingenic X1600 ADC
@ 2026-10-26 8:36 Olivier Louvignes
2026-10-26 8:36 ` [PATCH v3 1/2] dt-bindings: " Olivier Louvignes
` (2 more replies)
0 siblings, 3 replies; 6+ messages in thread
From: Olivier Louvignes @ 2026-10-26 8:36 UTC (permalink / raw)
To: Jonathan Cameron
Cc: David Lechner, Nuno Sá,
Andy Shevchenko, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Paul Cercueil, H . Nikolaus Schaller, Joshua Crofts, linux-iio,
devicetree, linux-mips, linux-kernel, Olivier Louvignes
A driver for the SAR ADC of the Ingenic X1600: four auxiliary channels,
12 bits, one interrupt per channel. Tested on a HiBy R1, whose keys are on
channel 0 behind adc-keys.
By their programming manuals, the X1830 (3 channels, 10 bits) and the
X2000 (5 channels, 10 bits, one more data register) have the same
register layout, two results to a data register. As Jonathan said on v2,
the X1600 name stays, and supporting them now is welcome but not
required: I have neither to test on, so this version is X1600-only, and
a chip_info with the channel count and the resolution can add them
later. Nikolaus, if Letux has either at hand, adding them on top with a
test would be welcome.
Changes since v2:
- Cover letter (Andy, Joshua).
- MAINTAINERS: no list line; the binding's path in patch 1, the driver's
in patch 2 (Joshua).
- Includes for what the driver uses, copyright line, MODULE_AUTHOR(),
reverse xmas tree, open-coded return, no message after
devm_request_irq(), a blank line (Joshua; Andy on the return).
- fsleep(2 * USEC_PER_MSEC) (Andy).
- The ADC clock divider is now even, as the X1600 programming manual
has it; v2 wrote the divider minus one, as the vendor kernel does. The
divider fields are named as in the manual, and the two clocks only
repeated sampling uses stay at their reset values.
- The probe comment on staying powered up no longer names the keys
(Jonathan).
v2: https://lore.kernel.org/all/20261024041357.34346-1-olivier@mgcrea.io/
Olivier Louvignes (2):
dt-bindings: iio: adc: Add the Ingenic X1600 ADC
iio: adc: Add a driver for the Ingenic X1600 ADC
.../bindings/iio/adc/ingenic,x1600-adc.yaml | 60 +++++
MAINTAINERS | 6 +
drivers/iio/adc/Kconfig | 10 +
drivers/iio/adc/Makefile | 1 +
drivers/iio/adc/ingenic-x1600-adc.c | 242 ++++++++++++++++++
5 files changed, 319 insertions(+)
create mode 100644 Documentation/devicetree/bindings/iio/adc/ingenic,x1600-adc.yaml
create mode 100644 drivers/iio/adc/ingenic-x1600-adc.c
base-commit: a3b3580713f3ac5a32dc2874ee546828977a1d68
--
2.39.5
^ permalink raw reply [flat|nested] 6+ messages in thread
* [PATCH v3 1/2] dt-bindings: iio: adc: Add the Ingenic X1600 ADC
2026-10-26 8:36 [PATCH v3 0/2] iio: adc: Add the Ingenic X1600 ADC Olivier Louvignes
@ 2026-10-26 8:36 ` Olivier Louvignes
2026-10-26 8:36 ` [PATCH v3 2/2] iio: adc: Add a driver for " Olivier Louvignes
2026-10-27 1:15 ` [PATCH v3 0/2] iio: adc: Add " Jonathan Cameron
2 siblings, 0 replies; 6+ messages in thread
From: Olivier Louvignes @ 2026-10-26 8:36 UTC (permalink / raw)
To: Jonathan Cameron
Cc: David Lechner, Nuno Sá,
Andy Shevchenko, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Paul Cercueil, H . Nikolaus Schaller, Joshua Crofts, linux-iio,
devicetree, linux-mips, linux-kernel, Olivier Louvignes
The X1600's SAR ADC has four auxiliary channels and none of the
touchscreen or battery engines of the JZ47xx ADCs in ingenic,adc.yaml,
so it gets a binding of its own.
Signed-off-by: Olivier Louvignes <olivier@mgcrea.io>
---
.../bindings/iio/adc/ingenic,x1600-adc.yaml | 60 +++++++++++++++++++
MAINTAINERS | 5 ++
2 files changed, 65 insertions(+)
create mode 100644 Documentation/devicetree/bindings/iio/adc/ingenic,x1600-adc.yaml
diff --git a/Documentation/devicetree/bindings/iio/adc/ingenic,x1600-adc.yaml b/Documentation/devicetree/bindings/iio/adc/ingenic,x1600-adc.yaml
new file mode 100644
index 000000000000..7886836b10c6
--- /dev/null
+++ b/Documentation/devicetree/bindings/iio/adc/ingenic,x1600-adc.yaml
@@ -0,0 +1,60 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/iio/adc/ingenic,x1600-adc.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Ingenic X1600 ADC
+
+maintainers:
+ - Olivier Louvignes <olivier@mgcrea.io>
+
+description:
+ The SAR ADC of the Ingenic X1600 SoC, with four auxiliary input channels
+ measured against a 3.3 V reference. Its clients select a channel by index
+ through "io-channels".
+
+properties:
+ compatible:
+ const: ingenic,x1600-adc
+
+ reg:
+ maxItems: 1
+
+ clocks:
+ maxItems: 1
+
+ clock-names:
+ items:
+ - const: adc
+
+ interrupts:
+ maxItems: 1
+
+ '#io-channel-cells':
+ const: 1
+
+required:
+ - compatible
+ - reg
+ - clocks
+ - clock-names
+ - interrupts
+ - '#io-channel-cells'
+
+additionalProperties: false
+
+examples:
+ - |
+ adc@10070000 {
+ compatible = "ingenic,x1600-adc";
+ reg = <0x10070000 0x30>;
+
+ clocks = <&cgu 28>;
+ clock-names = "adc";
+
+ interrupt-parent = <&intc>;
+ interrupts = <11>;
+
+ #io-channel-cells = <1>;
+ };
diff --git a/MAINTAINERS b/MAINTAINERS
index ecc22d1149d1..fd4bb5c6bf16 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -12925,6 +12925,11 @@ F: include/linux/mfd/ingenic-tcu.h
F: sound/soc/codecs/jz47*
F: sound/soc/jz4740/
+INGENIC X1600 ADC DRIVER
+M: Olivier Louvignes <olivier@mgcrea.io>
+S: Maintained
+F: Documentation/devicetree/bindings/iio/adc/ingenic,x1600-adc.yaml
+
INJOINIC IP5xxx POWER BANK IC DRIVER
M: Samuel Holland <samuel@sholland.org>
S: Maintained
--
2.39.5
^ permalink raw reply [flat|nested] 6+ messages in thread
* [PATCH v3 2/2] iio: adc: Add a driver for the Ingenic X1600 ADC
2026-10-26 8:36 [PATCH v3 0/2] iio: adc: Add the Ingenic X1600 ADC Olivier Louvignes
2026-10-26 8:36 ` [PATCH v3 1/2] dt-bindings: " Olivier Louvignes
@ 2026-10-26 8:36 ` Olivier Louvignes
2026-10-26 10:00 ` Joshua Crofts
2026-10-26 10:31 ` Andy Shevchenko
2026-10-27 1:15 ` [PATCH v3 0/2] iio: adc: Add " Jonathan Cameron
2 siblings, 2 replies; 6+ messages in thread
From: Olivier Louvignes @ 2026-10-26 8:36 UTC (permalink / raw)
To: Jonathan Cameron
Cc: David Lechner, Nuno Sá,
Andy Shevchenko, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Paul Cercueil, H . Nikolaus Schaller, Joshua Crofts, linux-iio,
devicetree, linux-mips, linux-kernel, Olivier Louvignes
The X1600's SAR ADC has four auxiliary channels, each with its own start
bit, interrupt flag and 12-bit result, and none of the touchscreen or
battery engines of the JZ47xx ADCs that ingenic-adc drives. Add a small
driver that exposes the four channels as voltages against the 3.3 V
reference.
The registers are in chapter 19 of the X1600 programming manual. The
ADC clock divider is even, as the manual has it (an odd value divides
by one less); the vendor kernel writes the divider minus one, which
gives a slightly faster clock than it asks for.
Tested on a HiBy R1, whose play/pause and volume keys sit on channel 0
behind adc-keys: all three read and report correctly.
Signed-off-by: Olivier Louvignes <olivier@mgcrea.io>
---
MAINTAINERS | 1 +
drivers/iio/adc/Kconfig | 10 ++
drivers/iio/adc/Makefile | 1 +
drivers/iio/adc/ingenic-x1600-adc.c | 242 ++++++++++++++++++++++++++++
4 files changed, 254 insertions(+)
create mode 100644 drivers/iio/adc/ingenic-x1600-adc.c
diff --git a/MAINTAINERS b/MAINTAINERS
index fd4bb5c6bf16..d1472b569fc0 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -12929,6 +12929,7 @@ INGENIC X1600 ADC DRIVER
M: Olivier Louvignes <olivier@mgcrea.io>
S: Maintained
F: Documentation/devicetree/bindings/iio/adc/ingenic,x1600-adc.yaml
+F: drivers/iio/adc/ingenic-x1600-adc.c
INJOINIC IP5xxx POWER BANK IC DRIVER
M: Samuel Holland <samuel@sholland.org>
diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
index a802d8ee4dd9..7b5c3d3fcfdb 100644
--- a/drivers/iio/adc/Kconfig
+++ b/drivers/iio/adc/Kconfig
@@ -881,6 +881,16 @@ config INGENIC_ADC
This driver can also be built as a module. If so, the module will be
called ingenic_adc.
+config INGENIC_X1600_ADC
+ tristate "Ingenic X1600 ADC driver"
+ depends on MIPS || COMPILE_TEST
+ help
+ Say yes here to build support for the SAR ADC of the Ingenic X1600
+ SoC and its four auxiliary channels.
+
+ This driver can also be built as a module. If so, the module will be
+ called ingenic-x1600-adc.
+
config INTEL_DC_TI_ADC
tristate "Intel Bay Trail / Cherry Trail Dollar Cove TI ADC driver"
depends on INTEL_SOC_PMIC_CHTDC_TI
diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile
index 9e82c21dbd37..f76e62b0cd04 100644
--- a/drivers/iio/adc/Makefile
+++ b/drivers/iio/adc/Makefile
@@ -77,6 +77,7 @@ obj-$(CONFIG_IMX8QXP_ADC) += imx8qxp-adc.o
obj-$(CONFIG_IMX93_ADC) += imx93_adc.o
obj-$(CONFIG_INA2XX_ADC) += ina2xx-adc.o
obj-$(CONFIG_INGENIC_ADC) += ingenic-adc.o
+obj-$(CONFIG_INGENIC_X1600_ADC) += ingenic-x1600-adc.o
obj-$(CONFIG_INTEL_DC_TI_ADC) += intel_dc_ti_adc.o
obj-$(CONFIG_INTEL_MRFLD_ADC) += intel_mrfld_adc.o
obj-$(CONFIG_LP8788_ADC) += lp8788_adc.o
diff --git a/drivers/iio/adc/ingenic-x1600-adc.c b/drivers/iio/adc/ingenic-x1600-adc.c
new file mode 100644
index 000000000000..236161cfedde
--- /dev/null
+++ b/drivers/iio/adc/ingenic-x1600-adc.c
@@ -0,0 +1,242 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * ADC driver for the Ingenic X1600 SoC
+ *
+ * Copyright (c) 2026 Olivier Louvignes <olivier@mgcrea.io>
+ *
+ * The X1600's SAR ADC has four auxiliary channels, each with its own start
+ * bit, interrupt and 12-bit result (X1600 programming manual, chapter 19).
+ */
+
+#include <linux/array_size.h>
+#include <linux/bitfield.h>
+#include <linux/bits.h>
+#include <linux/cleanup.h>
+#include <linux/clk.h>
+#include <linux/completion.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/iio/iio.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/jiffies.h>
+#include <linux/math.h>
+#include <linux/minmax.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/platform_device.h>
+#include <linux/time.h>
+#include <linux/types.h>
+
+#define X1600_ADC_REG_ENABLE 0x00
+#define X1600_ADC_REG_CTRL 0x08
+#define X1600_ADC_REG_STATUS 0x0c
+#define X1600_ADC_REG_DATA(ch) (0x10 + ((ch) / 2) * 4)
+#define X1600_ADC_REG_CLKDIV 0x20
+#define X1600_ADC_REG_STABLE 0x24
+
+/* ENABLE: bits 3..0 start a channel's conversion; bit 15 powers down. */
+#define X1600_ADC_ENABLE_POWER_DOWN BIT(15)
+/* CTRL: bits 3..0 mask the channels' interrupts. STATUS: write 1 to clear. */
+#define X1600_ADC_CHANNELS_MASK GENMASK(3, 0)
+#define X1600_ADC_DATA_MASK(ch) (GENMASK(11, 0) << ((ch) % 2 * 16))
+/* CLKDIV: an even divider from the device clock; US and MS: for repeats. */
+#define X1600_ADC_CLKDIV_CLKDIV GENMASK(7, 0)
+#define X1600_ADC_CLKDIV_US GENMASK(15, 8)
+#define X1600_ADC_CLKDIV_MS GENMASK(31, 16)
+#define X1600_ADC_STABLE_TIME GENMASK(15, 0)
+
+#define X1600_ADC_NUM_CHANNELS 4
+#define X1600_ADC_VREF_mV 3300
+#define X1600_ADC_BITS 12
+/* The ADC clock the vendor kernel runs at. */
+#define X1600_ADC_CLK_RATE 1166667
+#define X1600_ADC_TIMEOUT_MS 20
+
+struct x1600_adc {
+ void __iomem *base;
+ struct clk *clk;
+ struct completion done;
+ /* Serialises conversions: there is one completion for all channels. */
+ struct mutex lock;
+};
+
+static irqreturn_t x1600_adc_irq(int irq, void *data)
+{
+ struct x1600_adc *adc = data;
+ u32 status;
+
+ status = readl(adc->base + X1600_ADC_REG_STATUS) & X1600_ADC_CHANNELS_MASK;
+ if (!status)
+ return IRQ_NONE;
+
+ writel(status, adc->base + X1600_ADC_REG_STATUS);
+ complete(&adc->done);
+ return IRQ_HANDLED;
+}
+
+static int x1600_adc_read_raw_channel(struct x1600_adc *adc, unsigned int ch, int *val)
+{
+ int ret = 0;
+ u32 data;
+
+ guard(mutex)(&adc->lock);
+
+ reinit_completion(&adc->done);
+ writel(BIT(ch), adc->base + X1600_ADC_REG_STATUS);
+ writel(readl(adc->base + X1600_ADC_REG_ENABLE) | BIT(ch),
+ adc->base + X1600_ADC_REG_ENABLE);
+
+ if (!wait_for_completion_timeout(&adc->done,
+ msecs_to_jiffies(X1600_ADC_TIMEOUT_MS)))
+ ret = -ETIMEDOUT;
+
+ writel(readl(adc->base + X1600_ADC_REG_ENABLE) & ~BIT(ch),
+ adc->base + X1600_ADC_REG_ENABLE);
+ if (ret)
+ return ret;
+
+ data = readl(adc->base + X1600_ADC_REG_DATA(ch));
+ *val = field_get(X1600_ADC_DATA_MASK(ch), data);
+ return 0;
+}
+
+static int x1600_adc_read_raw(struct iio_dev *iio_dev,
+ struct iio_chan_spec const *chan,
+ int *val, int *val2, long mask)
+{
+ struct x1600_adc *adc = iio_priv(iio_dev);
+ int ret;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ ret = x1600_adc_read_raw_channel(adc, chan->channel, val);
+ if (ret)
+ return ret;
+
+ return IIO_VAL_INT;
+ case IIO_CHAN_INFO_SCALE:
+ *val = X1600_ADC_VREF_mV;
+ *val2 = X1600_ADC_BITS;
+ return IIO_VAL_FRACTIONAL_LOG2;
+ default:
+ return -EINVAL;
+ }
+}
+
+static const struct iio_info x1600_adc_info = {
+ .read_raw = x1600_adc_read_raw,
+};
+
+#define X1600_ADC_CHANNEL(_ch) { \
+ .type = IIO_VOLTAGE, \
+ .indexed = 1, \
+ .channel = (_ch), \
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
+ .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \
+}
+
+static const struct iio_chan_spec x1600_adc_channels[] = {
+ X1600_ADC_CHANNEL(0),
+ X1600_ADC_CHANNEL(1),
+ X1600_ADC_CHANNEL(2),
+ X1600_ADC_CHANNEL(3),
+};
+
+static void x1600_adc_power_down(void *data)
+{
+ struct x1600_adc *adc = data;
+
+ writel(X1600_ADC_CHANNELS_MASK, adc->base + X1600_ADC_REG_CTRL);
+ writel(X1600_ADC_ENABLE_POWER_DOWN, adc->base + X1600_ADC_REG_ENABLE);
+}
+
+static int x1600_adc_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct iio_dev *iio_dev;
+ struct x1600_adc *adc;
+ unsigned long rate;
+ u32 div, reg;
+ int irq, ret;
+
+ iio_dev = devm_iio_device_alloc(dev, sizeof(*adc));
+ if (!iio_dev)
+ return -ENOMEM;
+
+ adc = iio_priv(iio_dev);
+ init_completion(&adc->done);
+ ret = devm_mutex_init(dev, &adc->lock);
+ if (ret)
+ return ret;
+
+ adc->base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(adc->base))
+ return PTR_ERR(adc->base);
+
+ adc->clk = devm_clk_get_enabled(dev, "adc");
+ if (IS_ERR(adc->clk))
+ return dev_err_probe(dev, PTR_ERR(adc->clk), "Unable to get clock\n");
+
+ /* Powered down, interrupts masked and cleared, as the vendor does. */
+ writel(X1600_ADC_ENABLE_POWER_DOWN, adc->base + X1600_ADC_REG_ENABLE);
+ writel(X1600_ADC_CHANNELS_MASK, adc->base + X1600_ADC_REG_CTRL);
+ writel(X1600_ADC_CHANNELS_MASK, adc->base + X1600_ADC_REG_STATUS);
+
+ rate = clk_get_rate(adc->clk);
+ div = clamp(round_up(DIV_ROUND_UP(rate, X1600_ADC_CLK_RATE), 2), 2UL, 254UL);
+ /* US and MS at their reset values: single conversions don't use them. */
+ writel(FIELD_PREP(X1600_ADC_CLKDIV_CLKDIV, div) |
+ FIELD_PREP(X1600_ADC_CLKDIV_US, 16) |
+ FIELD_PREP(X1600_ADC_CLKDIV_MS, 4),
+ adc->base + X1600_ADC_REG_CLKDIV);
+ reg = readl(adc->base + X1600_ADC_REG_STABLE) & ~X1600_ADC_STABLE_TIME;
+ writel(reg | FIELD_PREP(X1600_ADC_STABLE_TIME, 1),
+ adc->base + X1600_ADC_REG_STABLE);
+
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0)
+ return irq;
+
+ ret = devm_request_irq(dev, irq, x1600_adc_irq, 0, dev_name(dev), adc);
+ if (ret)
+ return ret;
+
+ /*
+ * Left powered up: a typical use, such as buttons read through adc-keys,
+ * polls every few tens of milliseconds, and powering up takes 2 ms.
+ */
+ writel(0, adc->base + X1600_ADC_REG_ENABLE);
+ fsleep(2 * USEC_PER_MSEC);
+ writel(0, adc->base + X1600_ADC_REG_CTRL);
+
+ ret = devm_add_action_or_reset(dev, x1600_adc_power_down, adc);
+ if (ret)
+ return ret;
+
+ iio_dev->name = "x1600-adc";
+ iio_dev->modes = INDIO_DIRECT_MODE;
+ iio_dev->channels = x1600_adc_channels;
+ iio_dev->num_channels = ARRAY_SIZE(x1600_adc_channels);
+ iio_dev->info = &x1600_adc_info;
+
+ return devm_iio_device_register(dev, iio_dev);
+}
+
+static const struct of_device_id x1600_adc_of_match[] = {
+ { .compatible = "ingenic,x1600-adc" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, x1600_adc_of_match);
+
+static struct platform_driver x1600_adc_driver = {
+ .driver = {
+ .name = "ingenic-x1600-adc",
+ .of_match_table = x1600_adc_of_match,
+ },
+ .probe = x1600_adc_probe,
+};
+module_platform_driver(x1600_adc_driver);
+MODULE_AUTHOR("Olivier Louvignes <olivier@mgcrea.io>");
+MODULE_DESCRIPTION("ADC driver for the Ingenic X1600 SoC");
+MODULE_LICENSE("GPL");
--
2.39.5
^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [PATCH v3 2/2] iio: adc: Add a driver for the Ingenic X1600 ADC
2026-10-26 8:36 ` [PATCH v3 2/2] iio: adc: Add a driver for " Olivier Louvignes
@ 2026-10-26 10:00 ` Joshua Crofts
2026-10-26 10:31 ` Andy Shevchenko
1 sibling, 0 replies; 6+ messages in thread
From: Joshua Crofts @ 2026-10-26 10:00 UTC (permalink / raw)
To: Olivier Louvignes
Cc: Jonathan Cameron, David Lechner, Nuno Sá,
Andy Shevchenko, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Paul Cercueil, H . Nikolaus Schaller, linux-iio, devicetree,
linux-mips, linux-kernel
On Mon, 26 Oct 2026 09:36:31 +0100
Olivier Louvignes <olivier@mgcrea.io> wrote:
> The X1600's SAR ADC has four auxiliary channels, each with its own start
> bit, interrupt flag and 12-bit result, and none of the touchscreen or
> battery engines of the JZ47xx ADCs that ingenic-adc drives. Add a small
> driver that exposes the four channels as voltages against the 3.3 V
> reference.
>
> The registers are in chapter 19 of the X1600 programming manual. The
> ADC clock divider is even, as the manual has it (an odd value divides
> by one less); the vendor kernel writes the divider minus one, which
> gives a slightly faster clock than it asks for.
>
> Tested on a HiBy R1, whose play/pause and volume keys sit on channel 0
> behind adc-keys: all three read and report correctly.
>
> Signed-off-by: Olivier Louvignes <olivier@mgcrea.io>
> ---
The driver looks good to me now!
Reviewed-by: Joshua Crofts <joshua.crofts1@gmail.com>
--
Kind regards,
Joshua Crofts
^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [PATCH v3 2/2] iio: adc: Add a driver for the Ingenic X1600 ADC
2026-10-26 8:36 ` [PATCH v3 2/2] iio: adc: Add a driver for " Olivier Louvignes
2026-10-26 10:00 ` Joshua Crofts
@ 2026-10-26 10:31 ` Andy Shevchenko
1 sibling, 0 replies; 6+ messages in thread
From: Andy Shevchenko @ 2026-10-26 10:31 UTC (permalink / raw)
To: Olivier Louvignes
Cc: Jonathan Cameron, David Lechner, Nuno Sá,
Andy Shevchenko, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Paul Cercueil, H . Nikolaus Schaller, Joshua Crofts, linux-iio,
devicetree, linux-mips, linux-kernel
On Mon, Oct 26, 2026 at 09:36:31AM +0100, Olivier Louvignes wrote:
> The X1600's SAR ADC has four auxiliary channels, each with its own start
> bit, interrupt flag and 12-bit result, and none of the touchscreen or
> battery engines of the JZ47xx ADCs that ingenic-adc drives. Add a small
> driver that exposes the four channels as voltages against the 3.3 V
> reference.
>
> The registers are in chapter 19 of the X1600 programming manual. The
> ADC clock divider is even, as the manual has it (an odd value divides
> by one less); the vendor kernel writes the divider minus one, which
> gives a slightly faster clock than it asks for.
>
> Tested on a HiBy R1, whose play/pause and volume keys sit on channel 0
> behind adc-keys: all three read and report correctly.
...
> +config INGENIC_X1600_ADC
> + tristate "Ingenic X1600 ADC driver"
Make this text...
> + depends on MIPS || COMPILE_TEST
> + help
> + Say yes here to build support for the SAR ADC of the Ingenic X1600
> + SoC and its four auxiliary channels.
...
> +/*
> + * ADC driver for the Ingenic X1600 SoC
...and this comment along with the MODULE_DESCRIPTION() be the same.
> + * Copyright (c) 2026 Olivier Louvignes <olivier@mgcrea.io>
> + *
> + * The X1600's SAR ADC has four auxiliary channels, each with its own start
> + * bit, interrupt and 12-bit result (X1600 programming manual, chapter 19).
> + */
...
> +#define X1600_ADC_REG_DATA(ch) (0x10 + ((ch) / 2) * 4)
> +#define X1600_ADC_DATA_MASK(ch) (GENMASK(11, 0) << ((ch) % 2 * 16))
Drop /2 and %2 here, see below why. This probably need to have ch be renamed to
something else as it's like bank and shift.
...
> +#define X1600_ADC_CLK_RATE 1166667
Is this in Hz?
...
> +static int x1600_adc_read_raw_channel(struct x1600_adc *adc, unsigned int ch, int *val)
> +{
> + int ret = 0;
> + u32 data;
> +
> + guard(mutex)(&adc->lock);
> +
> + reinit_completion(&adc->done);
> + writel(BIT(ch), adc->base + X1600_ADC_REG_STATUS);
> + writel(readl(adc->base + X1600_ADC_REG_ENABLE) | BIT(ch),
> + adc->base + X1600_ADC_REG_ENABLE);
> +
> + if (!wait_for_completion_timeout(&adc->done,
> + msecs_to_jiffies(X1600_ADC_TIMEOUT_MS)))
> + ret = -ETIMEDOUT;
> +
> + writel(readl(adc->base + X1600_ADC_REG_ENABLE) & ~BIT(ch),
> + adc->base + X1600_ADC_REG_ENABLE);
> + if (ret)
> + return ret;
> + data = readl(adc->base + X1600_ADC_REG_DATA(ch));
> + *val = field_get(X1600_ADC_DATA_MASK(ch), data);
This is the only case when these two macros are being used. While / 2 and % 2
are power-of-2 based calculations, it's still make sense to have temporary
variable for both of them (see the proposed naming above) and use in a form
bank = ch / 2;
shift = ch % 2;
this can be optimised on some architectures to a single assembly instruction
(yes, both calculations). The problem is that they should be tighten to each
other so compiler sees it. In the current code they are go via macros and
compiler most likely can't deduce that.
All the above is based on the real life example, see 9b3cd5c7099f ("regmap:
place foo / 8 and foo % 8 closer to each other") for that.
> + return 0;
> +}
...
> + writel(X1600_ADC_CHANNELS_MASK, adc->base + X1600_ADC_REG_CTRL);
It makes sense to have read and write wrappers that will add adc->base.
static inline u32 void x1600_read(adc, unsigned int offset)
{
return readl(adc->base + offset);
}
static inline void x1600_write(adc, unsigned int offset, u32 value)
{
writel(value, adc->base + offset);
}
Also you may consider using regmap MMIO.
...
> + rate = clk_get_rate(adc->clk);
> + div = clamp(round_up(DIV_ROUND_UP(rate, X1600_ADC_CLK_RATE), 2), 2UL, 254UL);
Wouldn't it be better to clamp the rate first and then divide with rounding up?
--
With Best Regards,
Andy Shevchenko
^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [PATCH v3 0/2] iio: adc: Add the Ingenic X1600 ADC
2026-10-26 8:36 [PATCH v3 0/2] iio: adc: Add the Ingenic X1600 ADC Olivier Louvignes
2026-10-26 8:36 ` [PATCH v3 1/2] dt-bindings: " Olivier Louvignes
2026-10-26 8:36 ` [PATCH v3 2/2] iio: adc: Add a driver for " Olivier Louvignes
@ 2026-10-27 1:15 ` Jonathan Cameron
2 siblings, 0 replies; 6+ messages in thread
From: Jonathan Cameron @ 2026-10-27 1:15 UTC (permalink / raw)
To: Olivier Louvignes
Cc: David Lechner, Nuno Sá,
Andy Shevchenko, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Paul Cercueil, H . Nikolaus Schaller, Joshua Crofts, linux-iio,
devicetree, linux-mips, linux-kernel
On Mon, 26 Oct 2026 09:36:29 +0100
Olivier Louvignes <olivier@mgcrea.io> wrote:
> A driver for the SAR ADC of the Ingenic X1600: four auxiliary channels,
> 12 bits, one interrupt per channel. Tested on a HiBy R1, whose keys are on
> channel 0 behind adc-keys.
>
> By their programming manuals, the X1830 (3 channels, 10 bits) and the
> X2000 (5 channels, 10 bits, one more data register) have the same
> register layout, two results to a data register. As Jonathan said on v2,
> the X1600 name stays, and supporting them now is welcome but not
> required: I have neither to test on, so this version is X1600-only, and
> a chip_info with the channel count and the resolution can add them
> later. Nikolaus, if Letux has either at hand, adding them on top with a
> test would be welcome.
Nothing to add from me for this version but perhaps wait a few days to
see if this suggestion gets more feedback.
Jonathan
^ permalink raw reply [flat|nested] 6+ messages in thread
end of thread, other threads:[~2026-10-27 1:15 UTC | newest]
Thread overview: 6+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-10-26 8:36 [PATCH v3 0/2] iio: adc: Add the Ingenic X1600 ADC Olivier Louvignes
2026-10-26 8:36 ` [PATCH v3 1/2] dt-bindings: " Olivier Louvignes
2026-10-26 8:36 ` [PATCH v3 2/2] iio: adc: Add a driver for " Olivier Louvignes
2026-10-26 10:00 ` Joshua Crofts
2026-10-26 10:31 ` Andy Shevchenko
2026-10-27 1:15 ` [PATCH v3 0/2] iio: adc: Add " Jonathan Cameron
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®