* [PATCH v2 1/2] dt-bindings: iio: adc: Add the Ingenic X1600 ADC
@ 2026-10-24 4:13 Olivier Louvignes
2026-10-24 4:13 ` [PATCH v2 2/2] iio: adc: Add a driver for " Olivier Louvignes
2026-10-24 6:38 ` [PATCH v2 1/2] dt-bindings: iio: adc: Add " Joshua Crofts
0 siblings, 2 replies; 7+ messages in thread
From: Olivier Louvignes @ 2026-10-24 4:13 UTC (permalink / raw)
To: Jonathan Cameron
Cc: David Lechner, Nuno Sá,
Andy Shevchenko, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Paul Cercueil, 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>
---
Changes in v2: none, resent with 2/2.
.../bindings/iio/adc/ingenic,x1600-adc.yaml | 60 +++++++++++++++++++
1 file changed, 60 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>;
+ };
base-commit: a3b3580713f3ac5a32dc2874ee546828977a1d68
--
2.39.5
^ permalink raw reply [flat|nested] 7+ messages in thread
* [PATCH v2 2/2] iio: adc: Add a driver for the Ingenic X1600 ADC
2026-10-24 4:13 [PATCH v2 1/2] dt-bindings: iio: adc: Add the Ingenic X1600 ADC Olivier Louvignes
@ 2026-10-24 4:13 ` Olivier Louvignes
2026-10-24 7:00 ` Joshua Crofts
` (2 more replies)
2026-10-24 6:38 ` [PATCH v2 1/2] dt-bindings: iio: adc: Add " Joshua Crofts
1 sibling, 3 replies; 7+ messages in thread
From: Olivier Louvignes @ 2026-10-24 4:13 UTC (permalink / raw)
To: Jonathan Cameron
Cc: David Lechner, Nuno Sá,
Andy Shevchenko, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Paul Cercueil, 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.
There is no datasheet here: the register layout and the clock dividers
come from the X1600 driver of the vendor's 4.4 kernel.
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>
---
Changes in v2, after Jonathan Cameron's review (thanks):
- Drop the comparison with the JZ47xx ADCs from the file header
- Drop the mod_devicetable.h include
- X1600_ADC_VREF_MV -> X1600_ADC_VREF_mV
- Split the status declaration from its readl() in the interrupt handler
- field_get() for the channel's result
- fsleep(2000) instead of usleep_range()
v1 was tested on a HiBy R1; v2 is build-tested with W=1 on iio togreg.
MAINTAINERS | 7 +
drivers/iio/adc/Kconfig | 10 ++
drivers/iio/adc/Makefile | 1 +
drivers/iio/adc/ingenic-x1600-adc.c | 226 ++++++++++++++++++++++++++++
4 files changed, 244 insertions(+)
create mode 100644 drivers/iio/adc/ingenic-x1600-adc.c
diff --git a/MAINTAINERS b/MAINTAINERS
index ecc22d1149d1..bbb63228e330 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -12925,6 +12925,13 @@ 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>
+L: linux-iio@vger.kernel.org
+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>
S: Maintained
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..60683f26c693
--- /dev/null
+++ b/drivers/iio/adc/ingenic-x1600-adc.c
@@ -0,0 +1,226 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * ADC driver for the Ingenic X1600 SoC
+ *
+ * The X1600's SAR ADC has four auxiliary channels, each with its own start
+ * bit, interrupt and 12-bit result. The register layout and the clock set-up
+ * were read from the vendor 4.4 kernel's driver.
+ */
+
+#include <linux/bitfield.h>
+#include <linux/clk.h>
+#include <linux/completion.h>
+#include <linux/delay.h>
+#include <linux/iio/iio.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/platform_device.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))
+#define X1600_ADC_CLKDIV_DIV0 GENMASK(7, 0)
+#define X1600_ADC_CLKDIV_DIV1 GENMASK(15, 8)
+#define X1600_ADC_CLKDIV_DIV2 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 vendor kernel brings its 140 MHz APB clock down to about this. */
+#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)
+{
+ u32 data;
+ int ret = 0;
+
+ 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);
+ return ret ? ret : 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 div0, 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);
+ div0 = clamp(DIV_ROUND_UP(rate, X1600_ADC_CLK_RATE), 1UL, 256UL) - 1;
+ /* DIV1 and DIV2 as the vendor kernel sets them. */
+ writel(FIELD_PREP(X1600_ADC_CLKDIV_DIV0, div0) |
+ FIELD_PREP(X1600_ADC_CLKDIV_DIV1, 1) |
+ FIELD_PREP(X1600_ADC_CLKDIV_DIV2, 99),
+ 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 dev_err_probe(dev, ret, "Failed to request irq\n");
+
+ /*
+ * Powered up for good: the keys poll it every few tens of milliseconds,
+ * and powering up takes 2 ms.
+ */
+ writel(0, adc->base + X1600_ADC_REG_ENABLE);
+ fsleep(2000);
+ 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_DESCRIPTION("ADC driver for the Ingenic X1600 SoC");
+MODULE_LICENSE("GPL");
--
2.39.5
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH v2 1/2] dt-bindings: iio: adc: Add the Ingenic X1600 ADC
2026-10-24 4:13 [PATCH v2 1/2] dt-bindings: iio: adc: Add the Ingenic X1600 ADC Olivier Louvignes
2026-10-24 4:13 ` [PATCH v2 2/2] iio: adc: Add a driver for " Olivier Louvignes
@ 2026-10-24 6:38 ` Joshua Crofts
1 sibling, 0 replies; 7+ messages in thread
From: Joshua Crofts @ 2026-10-24 6:38 UTC (permalink / raw)
To: Olivier Louvignes
Cc: Jonathan Cameron, David Lechner, Nuno Sá,
Andy Shevchenko, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Paul Cercueil, linux-iio, devicetree, linux-mips, linux-kernel
On Sat, 24 Oct 2026 06:13:56 +0200
Olivier Louvignes <olivier@mgcrea.io> wrote:
> 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>
> ---
Please use a cover letter when submitting a patch series.
--
Kind regards,
Joshua Crofts
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH v2 2/2] iio: adc: Add a driver for the Ingenic X1600 ADC
2026-10-24 4:13 ` [PATCH v2 2/2] iio: adc: Add a driver for " Olivier Louvignes
@ 2026-10-24 7:00 ` Joshua Crofts
2026-10-24 10:12 ` Andy Shevchenko
2026-10-24 8:22 ` H. Nikolaus Schaller
2026-10-24 17:57 ` H. Nikolaus Schaller
2 siblings, 1 reply; 7+ messages in thread
From: Joshua Crofts @ 2026-10-24 7:00 UTC (permalink / raw)
To: Olivier Louvignes
Cc: Jonathan Cameron, David Lechner, Nuno Sá,
Andy Shevchenko, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Paul Cercueil, linux-iio, devicetree, linux-mips, linux-kernel
On Sat, 24 Oct 2026 06:13:57 +0200
Olivier Louvignes <olivier@mgcrea.io> wrote:
> diff --git a/MAINTAINERS b/MAINTAINERS
> index ecc22d1149d1..bbb63228e330 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -12925,6 +12925,13 @@ 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>
> +L: linux-iio@vger.kernel.org
Drop the list, get_maintainer gets this via the directory.
> +S: Maintained
> +F: Documentation/devicetree/bindings/iio/adc/ingenic,x1600-adc.yaml
The changes above should go into patch 1 (checkpatch will
raise a warning if you're creating a new file and it's not
documented in MAINTAINERS).
> +F: drivers/iio/adc/ingenic-x1600-adc.c
And this addition goes into patch 2.
...
> diff --git a/drivers/iio/adc/ingenic-x1600-adc.c b/drivers/iio/adc/ingenic-x1600-adc.c
> new file mode 100644
> index 000000000000..60683f26c693
> --- /dev/null
> +++ b/drivers/iio/adc/ingenic-x1600-adc.c
> @@ -0,0 +1,226 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * ADC driver for the Ingenic X1600 SoC
> + *
+ Copyright (c) 2026 YOUR_NAME <YOUR_EMAIL>
> + * The X1600's SAR ADC has four auxiliary channels, each with its own start
> + * bit, interrupt and 12-bit result. The register layout and the clock set-up
> + * were read from the vendor 4.4 kernel's driver.
> + */
> +
> +#include <linux/bitfield.h>
> +#include <linux/clk.h>
> +#include <linux/completion.h>
> +#include <linux/delay.h>
> +#include <linux/iio/iio.h>
> +#include <linux/interrupt.h>
> +#include <linux/io.h>
> +#include <linux/module.h>
> +#include <linux/mutex.h>
> +#include <linux/platform_device.h>
+ bits.h, types.h, array_size.h, cleanup.h, jiffies.h, minmax.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))
> +#define X1600_ADC_CLKDIV_DIV0 GENMASK(7, 0)
> +#define X1600_ADC_CLKDIV_DIV1 GENMASK(15, 8)
> +#define X1600_ADC_CLKDIV_DIV2 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 vendor kernel brings its 140 MHz APB clock down to about this. */
> +#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)
> +{
> + u32 data;
> + int ret = 0;
Reverse xmas tree.
> +
> + 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);
> + return ret ? ret : IIO_VAL_INT;
You can use the Elvis operator here.
> + 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) { \
Small nit, but align the \ with the others.
> + .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), \
> +}
> +
x
...
> + /* 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);
> + div0 = clamp(DIV_ROUND_UP(rate, X1600_ADC_CLK_RATE), 1UL, 256UL) - 1;
> + /* DIV1 and DIV2 as the vendor kernel sets them. */
> + writel(FIELD_PREP(X1600_ADC_CLKDIV_DIV0, div0) |
> + FIELD_PREP(X1600_ADC_CLKDIV_DIV1, 1) |
> + FIELD_PREP(X1600_ADC_CLKDIV_DIV2, 99),
> + 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 dev_err_probe(dev, ret, "Failed to request irq\n");
devm_request_irq() already prints an error message on failure, just
"return ret;" is fine.
> +
> + /*
> + * Powered up for good: the keys poll it every few tens of milliseconds,
> + * and powering up takes 2 ms.
> + */
> + writel(0, adc->base + X1600_ADC_REG_ENABLE);
> + fsleep(2000);
> + writel(0, adc->base + X1600_ADC_REG_CTRL);
+ blank line.
> + 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_DESCRIPTION("ADC driver for the Ingenic X1600 SoC");
MODULE_AUTHOR()?
> +MODULE_LICENSE("GPL");
--
Kind regards,
Joshua Crofts
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH v2 2/2] iio: adc: Add a driver for the Ingenic X1600 ADC
2026-10-24 4:13 ` [PATCH v2 2/2] iio: adc: Add a driver for " Olivier Louvignes
2026-10-24 7:00 ` Joshua Crofts
@ 2026-10-24 8:22 ` H. Nikolaus Schaller
2026-10-24 17:57 ` H. Nikolaus Schaller
2 siblings, 0 replies; 7+ messages in thread
From: H. Nikolaus Schaller @ 2026-10-24 8:22 UTC (permalink / raw)
To: Olivier Louvignes
Cc: Jonathan Cameron, David Lechner, Nuno Sá,
Andy Shevchenko, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Paul Cercueil, linux-iio, devicetree, linux-mips, linux-kernel,
Discussions about the Letux Kernel, Paul Boddie, Andreas Kemnade
Hi,
> Am 24.10.2026 um 06:13 schrieb Olivier Louvignes <olivier@mgcrea.io>:
>
> There is no datasheet here: the register layout and the clock dividers
> come from the X1600 driver of the vendor's 4.4 kernel.
You can find the programming manual X1600_PM_20220117.pdf here:
https://github.com/Ingenic-community/datasheets/blob/main/X/
Chapter 19.4 describes the registers.
There is also an X1000 PM to compare.
BTW: we have tons of patches for the x1600/x2000/x2600 (and even jz4730) waiting for cleanup
and upstreaming so we should avoid double and incompatible work but join forces.
Please refer to https://github.com/goldelico/letux-kernel/tree/letux-current
BR,
Nikolaus
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH v2 2/2] iio: adc: Add a driver for the Ingenic X1600 ADC
2026-10-24 7:00 ` Joshua Crofts
@ 2026-10-24 10:12 ` Andy Shevchenko
0 siblings, 0 replies; 7+ messages in thread
From: Andy Shevchenko @ 2026-10-24 10:12 UTC (permalink / raw)
To: Joshua Crofts
Cc: Olivier Louvignes, Jonathan Cameron, David Lechner, Nuno Sá,
Andy Shevchenko, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Paul Cercueil, linux-iio, devicetree, linux-mips, linux-kernel
On Sat, Oct 24, 2026 at 09:00:06AM +0200, Joshua Crofts wrote:
> On Sat, 24 Oct 2026 06:13:57 +0200
> Olivier Louvignes <olivier@mgcrea.io> wrote:
Olivier, I also missed the cover letter, can you make sure it's there in v3,
please?
...
> > + case IIO_CHAN_INFO_RAW:
> > + ret = x1600_adc_read_raw_channel(adc, chan->channel, val);
> > + return ret ? ret : IIO_VAL_INT;
>
> You can use the Elvis operator here.
While true, Jonathan preference to open code this type of cases
if (ret)
return ret;
return IIO_VAL_INT;
...
> > + /*
> > + * Powered up for good: the keys poll it every few tens of milliseconds,
> > + * and powering up takes 2 ms.
> > + */
> > + writel(0, adc->base + X1600_ADC_REG_ENABLE);
> > + fsleep(2000);
fsleep(2 * USEC_PER_MSEC);
(will require time.h to be included).
> > + writel(0, adc->base + X1600_ADC_REG_CTRL);
--
With Best Regards,
Andy Shevchenko
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH v2 2/2] iio: adc: Add a driver for the Ingenic X1600 ADC
2026-10-24 4:13 ` [PATCH v2 2/2] iio: adc: Add a driver for " Olivier Louvignes
2026-10-24 7:00 ` Joshua Crofts
2026-10-24 8:22 ` H. Nikolaus Schaller
@ 2026-10-24 17:57 ` H. Nikolaus Schaller
2 siblings, 0 replies; 7+ messages in thread
From: H. Nikolaus Schaller @ 2026-10-24 17:57 UTC (permalink / raw)
To: Olivier Louvignes
Cc: Jonathan Cameron, David Lechner, Nuno Sá,
Andy Shevchenko, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Paul Cercueil, linux-iio, devicetree, linux-mips, linux-kernel,
Discussions about the Letux Kernel, Paul Boddie, Andreas Kemnade
Hi,
> Am 24.10.2026 um 06:13 schrieb Olivier Louvignes <olivier@mgcrea.io>:
>
> 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
I have checked a little and
jz4780 has its unique SADC register set
x1000 has no SADC (just a note for a clock reserved)
x1600 has this register set for 4 channels
x1830 shares the same register set
but has only 3 channels
x2000 has a register set similar to the x1600
but has 5 channels
x2600 has a completely different register model (and 16 channels)
So the x1600, x1830, x2000 only differ in number of channels and the bits
in ADENA, ADCTRL, ADSTATE
> driver that exposes the four channels as voltages against the 3.3 V
> reference.
>
> There is no datasheet here: the register layout and the clock dividers
> come from the X1600 driver of the vendor's 4.4 kernel.
Or x2000... Since the x2000 is older, this IMHO should be an x2000 ADC
driver where the x1600 is also compatible to.
But I am not sure what the policy is here. If the sequence of publication of
some chips and derivatives should be used for the driver name and internal
constants. Or if it can be based on which chip is lucky to find someone
who writes a driver for that chip.
I think the x1830 was published after the x2000 but this would have to
be researched.
>
> 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>
> ---
> Changes in v2, after Jonathan Cameron's review (thanks):
> - Drop the comparison with the JZ47xx ADCs from the file header
> - Drop the mod_devicetable.h include
> - X1600_ADC_VREF_MV -> X1600_ADC_VREF_mV
> - Split the status declaration from its readl() in the interrupt handler
> - field_get() for the channel's result
> - fsleep(2000) instead of usleep_range()
>
> v1 was tested on a HiBy R1; v2 is build-tested with W=1 on iio togreg.
>
> MAINTAINERS | 7 +
> drivers/iio/adc/Kconfig | 10 ++
> drivers/iio/adc/Makefile | 1 +
> drivers/iio/adc/ingenic-x1600-adc.c | 226 ++++++++++++++++++++++++++++
> 4 files changed, 244 insertions(+)
> create mode 100644 drivers/iio/adc/ingenic-x1600-adc.c
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index ecc22d1149d1..bbb63228e330 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -12925,6 +12925,13 @@ 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>
> +L: linux-iio@vger.kernel.org
> +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>
> S: Maintained
> 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.
X1830 and X2000 should be mentioned here.
> +
> + 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..60683f26c693
> --- /dev/null
> +++ b/drivers/iio/adc/ingenic-x1600-adc.c
> @@ -0,0 +1,226 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * ADC driver for the Ingenic X1600 SoC
> + *
> + * The X1600's SAR ADC has four auxiliary channels, each with its own start
> + * bit, interrupt and 12-bit result. The register layout and the clock set-up
> + * were read from the vendor 4.4 kernel's driver.
> + */
> +
> +#include <linux/bitfield.h>
> +#include <linux/clk.h>
> +#include <linux/completion.h>
> +#include <linux/delay.h>
> +#include <linux/iio/iio.h>
> +#include <linux/interrupt.h>
> +#include <linux/io.h>
> +#include <linux/module.h>
> +#include <linux/mutex.h>
> +#include <linux/platform_device.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))
> +#define X1600_ADC_CLKDIV_DIV0 GENMASK(7, 0)
> +#define X1600_ADC_CLKDIV_DIV1 GENMASK(15, 8)
> +#define X1600_ADC_CLKDIV_DIV2 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 vendor kernel brings its 140 MHz APB clock down to about this. */
> +#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)
> +{
> + u32 data;
> + int ret = 0;
> +
> + 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);
> + return ret ? ret : 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),
X21830 has no ADC_CHANNEL(3).
X2000 has an ADC_CHANNEL(4).
> +};
> +
> +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 div0, 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);
> + div0 = clamp(DIV_ROUND_UP(rate, X1600_ADC_CLK_RATE), 1UL, 256UL) - 1;
> + /* DIV1 and DIV2 as the vendor kernel sets them. */
> + writel(FIELD_PREP(X1600_ADC_CLKDIV_DIV0, div0) |
> + FIELD_PREP(X1600_ADC_CLKDIV_DIV1, 1) |
> + FIELD_PREP(X1600_ADC_CLKDIV_DIV2, 99),
> + 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 dev_err_probe(dev, ret, "Failed to request irq\n");
> +
> + /*
> + * Powered up for good: the keys poll it every few tens of milliseconds,
> + * and powering up takes 2 ms.
> + */
> + writel(0, adc->base + X1600_ADC_REG_ENABLE);
> + fsleep(2000);
> + 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" },
There should be some mechanism to make this driver compatible to x1830
and x2000 as well and base the number of channels on this.
> + { }
> +};
> +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_DESCRIPTION("ADC driver for the Ingenic X1600 SoC");
> +MODULE_LICENSE("GPL");
> --
> 2.39.5
>
>
BR, H. Nikolaus Schaller <hns@goldelico.com>
^ permalink raw reply [flat|nested] 7+ messages in thread
end of thread, other threads:[~2026-10-24 17:58 UTC | newest]
Thread overview: 7+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-10-24 4:13 [PATCH v2 1/2] dt-bindings: iio: adc: Add the Ingenic X1600 ADC Olivier Louvignes
2026-10-24 4:13 ` [PATCH v2 2/2] iio: adc: Add a driver for " Olivier Louvignes
2026-10-24 7:00 ` Joshua Crofts
2026-10-24 10:12 ` Andy Shevchenko
2026-10-24 8:22 ` H. Nikolaus Schaller
2026-10-24 17:57 ` H. Nikolaus Schaller
2026-10-24 6:38 ` [PATCH v2 1/2] dt-bindings: iio: adc: Add " Joshua Crofts
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®