[PATCH v4 0/3] imx93: add ADC support

Add ADC support for NXP iMX93
Changes for v2: - add "static" to functions - enable ADC in iMX93 EVK
Changes for v3: - split in 3 commits - keep dts file in sync with Linux devicetree - add comments to commits
Changes for v4: - add imx93_adc_power_down() in imx93_adc_stop()
Luca Ellero (3): dm: adc: add iMX93 ADC support imx93_evk: add adc node to dts file imx93_evk: defconfig: add adc support
arch/arm/dts/imx93-11x11-evk.dts | 4 + configs/imx93_11x11_evk_defconfig | 1 + drivers/adc/Kconfig | 8 + drivers/adc/Makefile | 1 + drivers/adc/imx93-adc.c | 286 ++++++++++++++++++++++++++++++ 5 files changed, 300 insertions(+) create mode 100644 drivers/adc/imx93-adc.c

This commit adds driver for iMX93 ADC.
The driver is implemented using driver model and provides ADC uclass's methods for ADC single channel operations: - adc_start_channel() - adc_channel_data() - adc_stop()
ADC features: - channels: 4 - resolution: 12-bit
Signed-off-by: Luca Ellero l.ellero@asem.it --- drivers/adc/Kconfig | 8 ++ drivers/adc/Makefile | 1 + drivers/adc/imx93-adc.c | 286 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 295 insertions(+) create mode 100644 drivers/adc/imx93-adc.c
diff --git a/drivers/adc/Kconfig b/drivers/adc/Kconfig index e719c38bb3..4336732dee 100644 --- a/drivers/adc/Kconfig +++ b/drivers/adc/Kconfig @@ -63,3 +63,11 @@ config STM32_ADC - core driver to deal with common resources - child driver to deal with individual ADC resources (declare ADC device and associated channels, start/stop conversions) + +config ADC_IMX93 + bool "Enable NXP IMX93 ADC driver" + help + This enables basic driver for NXP IMX93 ADC. + It provides: + - 4 analog input channels + - 12-bit resolution diff --git a/drivers/adc/Makefile b/drivers/adc/Makefile index c1387f3a34..5336c82097 100644 --- a/drivers/adc/Makefile +++ b/drivers/adc/Makefile @@ -10,3 +10,4 @@ obj-$(CONFIG_ADC_SANDBOX) += sandbox.o obj-$(CONFIG_SARADC_ROCKCHIP) += rockchip-saradc.o obj-$(CONFIG_SARADC_MESON) += meson-saradc.o obj-$(CONFIG_STM32_ADC) += stm32-adc.o stm32-adc-core.o +obj-$(CONFIG_ADC_IMX93) += imx93-adc.o diff --git a/drivers/adc/imx93-adc.c b/drivers/adc/imx93-adc.c new file mode 100644 index 0000000000..b3bbea6c4e --- /dev/null +++ b/drivers/adc/imx93-adc.c @@ -0,0 +1,286 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2023 ASEM Srl + * Author: Luca Ellero l.ellero@asem.it + * + * Originally based on NXP linux-imx kernel v5.15 drivers/iio/adc/imx93_adc.c + */ + +#include <common.h> +#include <errno.h> +#include <dm.h> +#include <linux/bitfield.h> +#include <linux/iopoll.h> +#include <adc.h> + +#define IMX93_ADC_MCR 0x00 +#define IMX93_ADC_MSR 0x04 +#define IMX93_ADC_ISR 0x10 +#define IMX93_ADC_IMR 0x20 +#define IMX93_ADC_CIMR0 0x24 +#define IMX93_ADC_CTR0 0x94 +#define IMX93_ADC_NCMR0 0xA4 +#define IMX93_ADC_PCDR0 0x100 +#define IMX93_ADC_PCDR1 0x104 +#define IMX93_ADC_PCDR2 0x108 +#define IMX93_ADC_PCDR3 0x10c +#define IMX93_ADC_PCDR4 0x110 +#define IMX93_ADC_PCDR5 0x114 +#define IMX93_ADC_PCDR6 0x118 +#define IMX93_ADC_PCDR7 0x11c +#define IMX93_ADC_CALSTAT 0x39C + +#define IMX93_ADC_MCR_MODE_MASK BIT(29) +#define IMX93_ADC_MCR_NSTART_MASK BIT(24) +#define IMX93_ADC_MCR_CALSTART_MASK BIT(14) +#define IMX93_ADC_MCR_ADCLKSE_MASK BIT(8) +#define IMX93_ADC_MCR_PWDN_MASK BIT(0) + +#define IMX93_ADC_MSR_CALFAIL_MASK BIT(30) +#define IMX93_ADC_MSR_CALBUSY_MASK BIT(29) +#define IMX93_ADC_MSR_ADCSTATUS_MASK GENMASK(2, 0) + +#define IMX93_ADC_ISR_EOC_MASK BIT(1) + +#define IMX93_ADC_IMR_EOC_MASK BIT(1) +#define IMX93_ADC_IMR_ECH_MASK BIT(0) + +#define IMX93_ADC_PCDR_CDATA_MASK GENMASK(11, 0) + +#define IDLE 0 +#define POWER_DOWN 1 +#define WAIT_STATE 2 +#define BUSY_IN_CALIBRATION 3 +#define SAMPLE 4 +#define CONVERSION 6 + +#define IMX93_ADC_MAX_CHANNEL 3 +#define IMX93_ADC_DAT_MASK 0xfff +#define IMX93_ADC_TIMEOUT 100000 + +struct imx93_adc_priv { + int active_channel; + void __iomem *regs; +}; + +static int imx93_adc_power_down(struct imx93_adc_priv *adc) +{ + u32 mcr, msr; + int ret; + + mcr = readl(adc->regs + IMX93_ADC_MCR); + mcr |= FIELD_PREP(IMX93_ADC_MCR_PWDN_MASK, 1); + writel(mcr, adc->regs + IMX93_ADC_MCR); + + ret = readl_poll_timeout(adc->regs + IMX93_ADC_MSR, msr, + ((msr & IMX93_ADC_MSR_ADCSTATUS_MASK) == POWER_DOWN), 50); + if (ret == -ETIMEDOUT) + pr_warn("ADC not in power down mode, current MSR: %x\n", msr); + + return ret; +} + +static int imx93_adc_config_ad_clk(struct imx93_adc_priv *adc) +{ + u32 mcr; + int ret; + + /* put adc in power down mode */ + ret = imx93_adc_power_down(adc); + if (ret < 0) + return ret; + + /* config the AD_CLK equal to bus clock */ + mcr = readl(adc->regs + IMX93_ADC_MCR); + mcr |= FIELD_PREP(IMX93_ADC_MCR_ADCLKSE_MASK, 1); + writel(mcr, adc->regs + IMX93_ADC_MCR); + + /* bring ADC out of power down state, in idle state */ + mcr = readl(adc->regs + IMX93_ADC_MCR); + mcr &= ~FIELD_PREP(IMX93_ADC_MCR_PWDN_MASK, 1); + writel(mcr, adc->regs + IMX93_ADC_MCR); + + return ret; +} + +static int imx93_adc_calibration(struct imx93_adc_priv *adc) +{ + u32 mcr, msr, adc_status; + int ret; + + /* make sure ADC is in power down mode */ + msr = readl(adc->regs + IMX93_ADC_MSR); + + adc_status = FIELD_GET(IMX93_ADC_MSR_ADCSTATUS_MASK, msr); + if (adc_status != POWER_DOWN) { + ret = imx93_adc_power_down(adc); + if (ret < 0) + return ret; + } + + /* config SAR controller operating clock */ + mcr = readl(adc->regs + IMX93_ADC_MCR); + mcr &= ~FIELD_PREP(IMX93_ADC_MCR_ADCLKSE_MASK, 1); + writel(mcr, adc->regs + IMX93_ADC_MCR); + + /* bring ADC out of power down state */ + mcr = readl(adc->regs + IMX93_ADC_MCR); + mcr &= ~FIELD_PREP(IMX93_ADC_MCR_PWDN_MASK, 1); + writel(mcr, adc->regs + IMX93_ADC_MCR); + + /* + * we use the default TSAMP/NRSMPL/AVGEN in MCR, + * can add the setting of these bit if need + */ + + /* run calibration */ + mcr = readl(adc->regs + IMX93_ADC_MCR); + mcr |= FIELD_PREP(IMX93_ADC_MCR_CALSTART_MASK, 1); + writel(mcr, adc->regs + IMX93_ADC_MCR); + + /* wait calibration to be finished */ + ret = readl_poll_timeout(adc->regs + IMX93_ADC_MSR, msr, + !(msr & IMX93_ADC_MSR_CALBUSY_MASK), 2000000); + if (ret == -ETIMEDOUT) { + pr_warn("ADC calibration timeout\n"); + return ret; + } + + /* check whether calbration is successful or not */ + msr = readl(adc->regs + IMX93_ADC_MSR); + if (msr & IMX93_ADC_MSR_CALFAIL_MASK) { + pr_warn("ADC calibration failed!\n"); + return -EAGAIN; + } + + return 0; +} + +static int imx93_adc_channel_data(struct udevice *dev, int channel, + unsigned int *data) +{ + struct imx93_adc_priv *adc = dev_get_priv(dev); + u32 isr, pcda; + int ret; + + if (channel != adc->active_channel) { + pr_err("Requested channel is not active!\n"); + return -EINVAL; + } + + ret = readl_poll_timeout(adc->regs + IMX93_ADC_ISR, isr, + (isr & IMX93_ADC_ISR_EOC_MASK), IMX93_ADC_TIMEOUT); + + /* clear interrupts */ + writel(isr, adc->regs + IMX93_ADC_ISR); + + if (ret == -ETIMEDOUT) { + pr_warn("ADC conversion timeout!\n"); + return ret; + } + + pcda = readl(adc->regs + IMX93_ADC_PCDR0 + channel * 4); + + *data = FIELD_GET(IMX93_ADC_PCDR_CDATA_MASK, pcda); + + return 0; +} + +static int imx93_adc_start_channel(struct udevice *dev, int channel) +{ + struct imx93_adc_priv *adc = dev_get_priv(dev); + u32 imr, mcr; + + /* config channel mask register */ + writel(1 << channel, adc->regs + IMX93_ADC_NCMR0); + + /* config interrupt mask */ + imr = FIELD_PREP(IMX93_ADC_IMR_EOC_MASK, 1); + writel(imr, adc->regs + IMX93_ADC_IMR); + writel(1 << channel, adc->regs + IMX93_ADC_CIMR0); + + /* config one-shot mode */ + mcr = readl(adc->regs + IMX93_ADC_MCR); + mcr &= ~FIELD_PREP(IMX93_ADC_MCR_MODE_MASK, 1); + writel(mcr, adc->regs + IMX93_ADC_MCR); + + /* start normal conversion */ + mcr = readl(adc->regs + IMX93_ADC_MCR); + mcr |= FIELD_PREP(IMX93_ADC_MCR_NSTART_MASK, 1); + writel(mcr, adc->regs + IMX93_ADC_MCR); + + adc->active_channel = channel; + + return 0; +} + +static int imx93_adc_stop(struct udevice *dev) +{ + struct imx93_adc_priv *adc = dev_get_priv(dev); + + imx93_adc_power_down(adc); + + adc->active_channel = -1; + + return 0; +} + +static int imx93_adc_probe(struct udevice *dev) +{ + struct imx93_adc_priv *adc = dev_get_priv(dev); + unsigned int ret; + + ret = imx93_adc_calibration(adc); + if (ret < 0) + return ret; + + ret = imx93_adc_config_ad_clk(adc); + if (ret < 0) + return ret; + + adc->active_channel = -1; + + return 0; +} + +static int imx93_adc_of_to_plat(struct udevice *dev) +{ + struct adc_uclass_plat *uc_pdata = dev_get_uclass_plat(dev); + struct imx93_adc_priv *adc = dev_get_priv(dev); + + adc->regs = dev_read_addr_ptr(dev); + if (adc->regs == (struct imx93_adc *)FDT_ADDR_T_NONE) { + pr_err("Dev: %s - can't get address!", dev->name); + return -ENODATA; + } + + uc_pdata->data_mask = IMX93_ADC_DAT_MASK; + uc_pdata->data_format = ADC_DATA_FORMAT_BIN; + uc_pdata->data_timeout_us = IMX93_ADC_TIMEOUT; + + /* Mask available channel bits: [0:3] */ + uc_pdata->channel_mask = (2 << IMX93_ADC_MAX_CHANNEL) - 1; + + return 0; +} + +static const struct adc_ops imx93_adc_ops = { + .start_channel = imx93_adc_start_channel, + .channel_data = imx93_adc_channel_data, + .stop = imx93_adc_stop, +}; + +static const struct udevice_id imx93_adc_ids[] = { + { .compatible = "nxp,imx93-adc" }, + { } +}; + +U_BOOT_DRIVER(imx93_adc) = { + .name = "imx93-adc", + .id = UCLASS_ADC, + .of_match = imx93_adc_ids, + .ops = &imx93_adc_ops, + .probe = imx93_adc_probe, + .of_to_plat = imx93_adc_of_to_plat, + .priv_auto = sizeof(struct imx93_adc_priv), +};

-----Original Message----- From: Luca Ellero l.ellero@asem.it Sent: 2023年3月15日 21:36 To: u-boot@lists.denx.de; sbabic@denx.de; festevam@gmail.com; dl-uboot-imx uboot-imx@nxp.com; luca.ellero@brickedbrain.com; Ye Li ye.li@nxp.com; Peng Fan peng.fan@nxp.com; Bough Chen haibo.chen@nxp.com Cc: Luca Ellero l.ellero@asem.it Subject: [PATCH 1/3] dm: adc: add iMX93 ADC support
This commit adds driver for iMX93 ADC.
The driver is implemented using driver model and provides ADC uclass's methods for ADC single channel operations: - adc_start_channel() - adc_channel_data() - adc_stop()
ADC features: - channels: 4 - resolution: 12-bit
Signed-off-by: Luca Ellero l.ellero@asem.it
drivers/adc/Kconfig | 8 ++ drivers/adc/Makefile | 1 + drivers/adc/imx93-adc.c | 286 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 295 insertions(+) create mode 100644 drivers/adc/imx93-adc.c
diff --git a/drivers/adc/Kconfig b/drivers/adc/Kconfig index e719c38bb3..4336732dee 100644 --- a/drivers/adc/Kconfig +++ b/drivers/adc/Kconfig @@ -63,3 +63,11 @@ config STM32_ADC - core driver to deal with common resources - child driver to deal with individual ADC resources (declare ADC device and associated channels, start/stop conversions)
+config ADC_IMX93
- bool "Enable NXP IMX93 ADC driver"
- help
This enables basic driver for NXP IMX93 ADC.
It provides:
- 4 analog input channels
- 12-bit resolution
diff --git a/drivers/adc/Makefile b/drivers/adc/Makefile index c1387f3a34..5336c82097 100644 --- a/drivers/adc/Makefile +++ b/drivers/adc/Makefile @@ -10,3 +10,4 @@ obj-$(CONFIG_ADC_SANDBOX) += sandbox.o obj-$(CONFIG_SARADC_ROCKCHIP) += rockchip-saradc.o obj-$(CONFIG_SARADC_MESON) += meson-saradc.o obj-$(CONFIG_STM32_ADC) += stm32-adc.o stm32-adc-core.o +obj-$(CONFIG_ADC_IMX93) += imx93-adc.o diff --git a/drivers/adc/imx93-adc.c b/drivers/adc/imx93-adc.c new file mode 100644 index 0000000000..b3bbea6c4e --- /dev/null +++ b/drivers/adc/imx93-adc.c @@ -0,0 +1,286 @@ +// SPDX-License-Identifier: GPL-2.0+ +/*
- Copyright (C) 2023 ASEM Srl
- Author: Luca Ellero l.ellero@asem.it
- Originally based on NXP linux-imx kernel v5.15
+drivers/iio/adc/imx93_adc.c */
+#include <common.h> +#include <errno.h> +#include <dm.h> +#include <linux/bitfield.h> +#include <linux/iopoll.h> +#include <adc.h>
+#define IMX93_ADC_MCR 0x00 +#define IMX93_ADC_MSR 0x04 +#define IMX93_ADC_ISR 0x10 +#define IMX93_ADC_IMR 0x20 +#define IMX93_ADC_CIMR0 0x24 +#define IMX93_ADC_CTR0 0x94 +#define IMX93_ADC_NCMR0 0xA4 +#define IMX93_ADC_PCDR0 0x100 +#define IMX93_ADC_PCDR1 0x104 +#define IMX93_ADC_PCDR2 0x108 +#define IMX93_ADC_PCDR3 0x10c +#define IMX93_ADC_PCDR4 0x110 +#define IMX93_ADC_PCDR5 0x114 +#define IMX93_ADC_PCDR6 0x118 +#define IMX93_ADC_PCDR7 0x11c +#define IMX93_ADC_CALSTAT 0x39C
+#define IMX93_ADC_MCR_MODE_MASK BIT(29) +#define IMX93_ADC_MCR_NSTART_MASK BIT(24) +#define IMX93_ADC_MCR_CALSTART_MASK BIT(14) +#define IMX93_ADC_MCR_ADCLKSE_MASK BIT(8) +#define IMX93_ADC_MCR_PWDN_MASK BIT(0)
+#define IMX93_ADC_MSR_CALFAIL_MASK BIT(30) +#define IMX93_ADC_MSR_CALBUSY_MASK BIT(29) +#define IMX93_ADC_MSR_ADCSTATUS_MASK GENMASK(2, 0)
+#define IMX93_ADC_ISR_EOC_MASK BIT(1)
+#define IMX93_ADC_IMR_EOC_MASK BIT(1) +#define IMX93_ADC_IMR_ECH_MASK BIT(0)
+#define IMX93_ADC_PCDR_CDATA_MASK GENMASK(11, 0)
+#define IDLE 0 +#define POWER_DOWN 1 +#define WAIT_STATE 2 +#define BUSY_IN_CALIBRATION 3 +#define SAMPLE 4 +#define CONVERSION 6
+#define IMX93_ADC_MAX_CHANNEL 3 +#define IMX93_ADC_DAT_MASK 0xfff +#define IMX93_ADC_TIMEOUT 100000
+struct imx93_adc_priv {
- int active_channel;
- void __iomem *regs;
+};
+static int imx93_adc_power_down(struct imx93_adc_priv *adc) {
- u32 mcr, msr;
- int ret;
- mcr = readl(adc->regs + IMX93_ADC_MCR);
- mcr |= FIELD_PREP(IMX93_ADC_MCR_PWDN_MASK, 1);
- writel(mcr, adc->regs + IMX93_ADC_MCR);
- ret = readl_poll_timeout(adc->regs + IMX93_ADC_MSR, msr,
((msr & IMX93_ADC_MSR_ADCSTATUS_MASK) == POWER_DOWN),
50);
- if (ret == -ETIMEDOUT)
pr_warn("ADC not in power down mode, current MSR: %x\n", msr);
- return ret;
+}
+static int imx93_adc_config_ad_clk(struct imx93_adc_priv *adc) {
- u32 mcr;
- int ret;
- /* put adc in power down mode */
- ret = imx93_adc_power_down(adc);
- if (ret < 0)
return ret;
- /* config the AD_CLK equal to bus clock */
- mcr = readl(adc->regs + IMX93_ADC_MCR);
- mcr |= FIELD_PREP(IMX93_ADC_MCR_ADCLKSE_MASK, 1);
- writel(mcr, adc->regs + IMX93_ADC_MCR);
- /* bring ADC out of power down state, in idle state */
- mcr = readl(adc->regs + IMX93_ADC_MCR);
- mcr &= ~FIELD_PREP(IMX93_ADC_MCR_PWDN_MASK, 1);
- writel(mcr, adc->regs + IMX93_ADC_MCR);
Few same code in the following, better to use a function like imx93_adc_power_up() to do this.
You can refer to latest upstream linux imx93 ADC version, seems you just refer to the downstream driver. https://elixir.bootlin.com/linux/v6.3-rc2/source/drivers/iio/adc/imx93_adc.c
- return ret;
+}
+static int imx93_adc_calibration(struct imx93_adc_priv *adc) {
- u32 mcr, msr, adc_status;
- int ret;
- /* make sure ADC is in power down mode */
- msr = readl(adc->regs + IMX93_ADC_MSR);
- adc_status = FIELD_GET(IMX93_ADC_MSR_ADCSTATUS_MASK, msr);
- if (adc_status != POWER_DOWN) {
ret = imx93_adc_power_down(adc);
if (ret < 0)
return ret;
- }
Directly power down the adc to simplify the code logic.
- /* config SAR controller operating clock */
- mcr = readl(adc->regs + IMX93_ADC_MCR);
- mcr &= ~FIELD_PREP(IMX93_ADC_MCR_ADCLKSE_MASK, 1);
- writel(mcr, adc->regs + IMX93_ADC_MCR);
- /* bring ADC out of power down state */
- mcr = readl(adc->regs + IMX93_ADC_MCR);
- mcr &= ~FIELD_PREP(IMX93_ADC_MCR_PWDN_MASK, 1);
- writel(mcr, adc->regs + IMX93_ADC_MCR);
Use the function adc power up, to reduce the redundant code.
- /*
* we use the default TSAMP/NRSMPL/AVGEN in MCR,
* can add the setting of these bit if need
*/
- /* run calibration */
- mcr = readl(adc->regs + IMX93_ADC_MCR);
- mcr |= FIELD_PREP(IMX93_ADC_MCR_CALSTART_MASK, 1);
- writel(mcr, adc->regs + IMX93_ADC_MCR);
- /* wait calibration to be finished */
- ret = readl_poll_timeout(adc->regs + IMX93_ADC_MSR, msr,
!(msr & IMX93_ADC_MSR_CALBUSY_MASK), 2000000);
- if (ret == -ETIMEDOUT) {
pr_warn("ADC calibration timeout\n");
return ret;
- }
- /* check whether calbration is successful or not */
- msr = readl(adc->regs + IMX93_ADC_MSR);
- if (msr & IMX93_ADC_MSR_CALFAIL_MASK) {
pr_warn("ADC calibration failed!\n");
return -EAGAIN;
- }
- return 0;
+}
+static int imx93_adc_channel_data(struct udevice *dev, int channel,
unsigned int *data)
+{
- struct imx93_adc_priv *adc = dev_get_priv(dev);
- u32 isr, pcda;
- int ret;
- if (channel != adc->active_channel) {
pr_err("Requested channel is not active!\n");
return -EINVAL;
- }
- ret = readl_poll_timeout(adc->regs + IMX93_ADC_ISR, isr,
(isr & IMX93_ADC_ISR_EOC_MASK), IMX93_ADC_TIMEOUT);
- /* clear interrupts */
- writel(isr, adc->regs + IMX93_ADC_ISR);
- if (ret == -ETIMEDOUT) {
pr_warn("ADC conversion timeout!\n");
return ret;
- }
- pcda = readl(adc->regs + IMX93_ADC_PCDR0 + channel * 4);
- *data = FIELD_GET(IMX93_ADC_PCDR_CDATA_MASK, pcda);
- return 0;
+}
+static int imx93_adc_start_channel(struct udevice *dev, int channel) {
- struct imx93_adc_priv *adc = dev_get_priv(dev);
- u32 imr, mcr;
- /* config channel mask register */
- writel(1 << channel, adc->regs + IMX93_ADC_NCMR0);
- /* config interrupt mask */
- imr = FIELD_PREP(IMX93_ADC_IMR_EOC_MASK, 1);
- writel(imr, adc->regs + IMX93_ADC_IMR);
- writel(1 << channel, adc->regs + IMX93_ADC_CIMR0);
- /* config one-shot mode */
- mcr = readl(adc->regs + IMX93_ADC_MCR);
- mcr &= ~FIELD_PREP(IMX93_ADC_MCR_MODE_MASK, 1);
- writel(mcr, adc->regs + IMX93_ADC_MCR);
- /* start normal conversion */
- mcr = readl(adc->regs + IMX93_ADC_MCR);
- mcr |= FIELD_PREP(IMX93_ADC_MCR_NSTART_MASK, 1);
- writel(mcr, adc->regs + IMX93_ADC_MCR);
- adc->active_channel = channel;
- return 0;
+}
+static int imx93_adc_stop(struct udevice *dev) {
- struct imx93_adc_priv *adc = dev_get_priv(dev);
- imx93_adc_power_down(adc);
- adc->active_channel = -1;
- return 0;
+}
+static int imx93_adc_probe(struct udevice *dev) {
- struct imx93_adc_priv *adc = dev_get_priv(dev);
- unsigned int ret;
- ret = imx93_adc_calibration(adc);
- if (ret < 0)
return ret;
- ret = imx93_adc_config_ad_clk(adc);
- if (ret < 0)
return ret;
- adc->active_channel = -1;
- return 0;
+}
+static int imx93_adc_of_to_plat(struct udevice *dev) {
- struct adc_uclass_plat *uc_pdata = dev_get_uclass_plat(dev);
- struct imx93_adc_priv *adc = dev_get_priv(dev);
- adc->regs = dev_read_addr_ptr(dev);
- if (adc->regs == (struct imx93_adc *)FDT_ADDR_T_NONE) {
pr_err("Dev: %s - can't get address!", dev->name);
return -ENODATA;
- }
- uc_pdata->data_mask = IMX93_ADC_DAT_MASK;
- uc_pdata->data_format = ADC_DATA_FORMAT_BIN;
- uc_pdata->data_timeout_us = IMX93_ADC_TIMEOUT;
- /* Mask available channel bits: [0:3] */
- uc_pdata->channel_mask = (2 << IMX93_ADC_MAX_CHANNEL) - 1;
Seems you forget to handle the ADC ipg clock: IMX93_CLK_ADC1_GATE, please add it in the driver.
Best Regards Haibo Chen
- return 0;
+}
+static const struct adc_ops imx93_adc_ops = {
- .start_channel = imx93_adc_start_channel,
- .channel_data = imx93_adc_channel_data,
- .stop = imx93_adc_stop,
+};
+static const struct udevice_id imx93_adc_ids[] = {
- { .compatible = "nxp,imx93-adc" },
- { }
+};
+U_BOOT_DRIVER(imx93_adc) = {
- .name = "imx93-adc",
- .id = UCLASS_ADC,
- .of_match = imx93_adc_ids,
- .ops = &imx93_adc_ops,
- .probe = imx93_adc_probe,
- .of_to_plat = imx93_adc_of_to_plat,
- .priv_auto = sizeof(struct imx93_adc_priv),
+};
2.25.1

On 17/03/2023 03:54, Bough Chen wrote:
-----Original Message----- From: Luca Ellero l.ellero@asem.it Sent: 2023年3月15日 21:36 To: u-boot@lists.denx.de; sbabic@denx.de; festevam@gmail.com; dl-uboot-imx uboot-imx@nxp.com; luca.ellero@brickedbrain.com; Ye Li ye.li@nxp.com; Peng Fan peng.fan@nxp.com; Bough Chen haibo.chen@nxp.com Cc: Luca Ellero l.ellero@asem.it Subject: [PATCH 1/3] dm: adc: add iMX93 ADC support
This commit adds driver for iMX93 ADC.
The driver is implemented using driver model and provides ADC uclass's methods for ADC single channel operations: - adc_start_channel() - adc_channel_data() - adc_stop()
ADC features: - channels: 4 - resolution: 12-bit
Signed-off-by: Luca Ellero l.ellero@asem.it
drivers/adc/Kconfig | 8 ++ drivers/adc/Makefile | 1 + drivers/adc/imx93-adc.c | 286 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 295 insertions(+) create mode 100644 drivers/adc/imx93-adc.c
diff --git a/drivers/adc/Kconfig b/drivers/adc/Kconfig index e719c38bb3..4336732dee 100644 --- a/drivers/adc/Kconfig +++ b/drivers/adc/Kconfig @@ -63,3 +63,11 @@ config STM32_ADC - core driver to deal with common resources - child driver to deal with individual ADC resources (declare ADC device and associated channels, start/stop conversions)
+config ADC_IMX93
- bool "Enable NXP IMX93 ADC driver"
- help
This enables basic driver for NXP IMX93 ADC.
It provides:
- 4 analog input channels
- 12-bit resolution
diff --git a/drivers/adc/Makefile b/drivers/adc/Makefile index c1387f3a34..5336c82097 100644 --- a/drivers/adc/Makefile +++ b/drivers/adc/Makefile @@ -10,3 +10,4 @@ obj-$(CONFIG_ADC_SANDBOX) += sandbox.o obj-$(CONFIG_SARADC_ROCKCHIP) += rockchip-saradc.o obj-$(CONFIG_SARADC_MESON) += meson-saradc.o obj-$(CONFIG_STM32_ADC) += stm32-adc.o stm32-adc-core.o +obj-$(CONFIG_ADC_IMX93) += imx93-adc.o diff --git a/drivers/adc/imx93-adc.c b/drivers/adc/imx93-adc.c new file mode 100644 index 0000000000..b3bbea6c4e --- /dev/null +++ b/drivers/adc/imx93-adc.c @@ -0,0 +1,286 @@ +// SPDX-License-Identifier: GPL-2.0+ +/*
- Copyright (C) 2023 ASEM Srl
- Author: Luca Ellero l.ellero@asem.it
- Originally based on NXP linux-imx kernel v5.15
+drivers/iio/adc/imx93_adc.c */
+#include <common.h> +#include <errno.h> +#include <dm.h> +#include <linux/bitfield.h> +#include <linux/iopoll.h> +#include <adc.h>
+#define IMX93_ADC_MCR 0x00 +#define IMX93_ADC_MSR 0x04 +#define IMX93_ADC_ISR 0x10 +#define IMX93_ADC_IMR 0x20 +#define IMX93_ADC_CIMR0 0x24 +#define IMX93_ADC_CTR0 0x94 +#define IMX93_ADC_NCMR0 0xA4 +#define IMX93_ADC_PCDR0 0x100 +#define IMX93_ADC_PCDR1 0x104 +#define IMX93_ADC_PCDR2 0x108 +#define IMX93_ADC_PCDR3 0x10c +#define IMX93_ADC_PCDR4 0x110 +#define IMX93_ADC_PCDR5 0x114 +#define IMX93_ADC_PCDR6 0x118 +#define IMX93_ADC_PCDR7 0x11c +#define IMX93_ADC_CALSTAT 0x39C
+#define IMX93_ADC_MCR_MODE_MASK BIT(29) +#define IMX93_ADC_MCR_NSTART_MASK BIT(24) +#define IMX93_ADC_MCR_CALSTART_MASK BIT(14) +#define IMX93_ADC_MCR_ADCLKSE_MASK BIT(8) +#define IMX93_ADC_MCR_PWDN_MASK BIT(0)
+#define IMX93_ADC_MSR_CALFAIL_MASK BIT(30) +#define IMX93_ADC_MSR_CALBUSY_MASK BIT(29) +#define IMX93_ADC_MSR_ADCSTATUS_MASK GENMASK(2, 0)
+#define IMX93_ADC_ISR_EOC_MASK BIT(1)
+#define IMX93_ADC_IMR_EOC_MASK BIT(1) +#define IMX93_ADC_IMR_ECH_MASK BIT(0)
+#define IMX93_ADC_PCDR_CDATA_MASK GENMASK(11, 0)
+#define IDLE 0 +#define POWER_DOWN 1 +#define WAIT_STATE 2 +#define BUSY_IN_CALIBRATION 3 +#define SAMPLE 4 +#define CONVERSION 6
+#define IMX93_ADC_MAX_CHANNEL 3 +#define IMX93_ADC_DAT_MASK 0xfff +#define IMX93_ADC_TIMEOUT 100000
+struct imx93_adc_priv {
- int active_channel;
- void __iomem *regs;
+};
+static int imx93_adc_power_down(struct imx93_adc_priv *adc) {
- u32 mcr, msr;
- int ret;
- mcr = readl(adc->regs + IMX93_ADC_MCR);
- mcr |= FIELD_PREP(IMX93_ADC_MCR_PWDN_MASK, 1);
- writel(mcr, adc->regs + IMX93_ADC_MCR);
- ret = readl_poll_timeout(adc->regs + IMX93_ADC_MSR, msr,
((msr & IMX93_ADC_MSR_ADCSTATUS_MASK) == POWER_DOWN),
50);
- if (ret == -ETIMEDOUT)
pr_warn("ADC not in power down mode, current MSR: %x\n", msr);
- return ret;
+}
+static int imx93_adc_config_ad_clk(struct imx93_adc_priv *adc) {
- u32 mcr;
- int ret;
- /* put adc in power down mode */
- ret = imx93_adc_power_down(adc);
- if (ret < 0)
return ret;
- /* config the AD_CLK equal to bus clock */
- mcr = readl(adc->regs + IMX93_ADC_MCR);
- mcr |= FIELD_PREP(IMX93_ADC_MCR_ADCLKSE_MASK, 1);
- writel(mcr, adc->regs + IMX93_ADC_MCR);
- /* bring ADC out of power down state, in idle state */
- mcr = readl(adc->regs + IMX93_ADC_MCR);
- mcr &= ~FIELD_PREP(IMX93_ADC_MCR_PWDN_MASK, 1);
- writel(mcr, adc->regs + IMX93_ADC_MCR);
Few same code in the following, better to use a function like imx93_adc_power_up() to do this.
You can refer to latest upstream linux imx93 ADC version, seems you just refer to the downstream driver. https://elixir.bootlin.com/linux/v6.3-rc2/source/drivers/iio/adc/imx93_adc.c
- return ret;
+}
+static int imx93_adc_calibration(struct imx93_adc_priv *adc) {
- u32 mcr, msr, adc_status;
- int ret;
- /* make sure ADC is in power down mode */
- msr = readl(adc->regs + IMX93_ADC_MSR);
- adc_status = FIELD_GET(IMX93_ADC_MSR_ADCSTATUS_MASK, msr);
- if (adc_status != POWER_DOWN) {
ret = imx93_adc_power_down(adc);
if (ret < 0)
return ret;
- }
Directly power down the adc to simplify the code logic.
- /* config SAR controller operating clock */
- mcr = readl(adc->regs + IMX93_ADC_MCR);
- mcr &= ~FIELD_PREP(IMX93_ADC_MCR_ADCLKSE_MASK, 1);
- writel(mcr, adc->regs + IMX93_ADC_MCR);
- /* bring ADC out of power down state */
- mcr = readl(adc->regs + IMX93_ADC_MCR);
- mcr &= ~FIELD_PREP(IMX93_ADC_MCR_PWDN_MASK, 1);
- writel(mcr, adc->regs + IMX93_ADC_MCR);
Use the function adc power up, to reduce the redundant code.
- /*
* we use the default TSAMP/NRSMPL/AVGEN in MCR,
* can add the setting of these bit if need
*/
- /* run calibration */
- mcr = readl(adc->regs + IMX93_ADC_MCR);
- mcr |= FIELD_PREP(IMX93_ADC_MCR_CALSTART_MASK, 1);
- writel(mcr, adc->regs + IMX93_ADC_MCR);
- /* wait calibration to be finished */
- ret = readl_poll_timeout(adc->regs + IMX93_ADC_MSR, msr,
!(msr & IMX93_ADC_MSR_CALBUSY_MASK), 2000000);
- if (ret == -ETIMEDOUT) {
pr_warn("ADC calibration timeout\n");
return ret;
- }
- /* check whether calbration is successful or not */
- msr = readl(adc->regs + IMX93_ADC_MSR);
- if (msr & IMX93_ADC_MSR_CALFAIL_MASK) {
pr_warn("ADC calibration failed!\n");
return -EAGAIN;
- }
- return 0;
+}
+static int imx93_adc_channel_data(struct udevice *dev, int channel,
unsigned int *data)
+{
- struct imx93_adc_priv *adc = dev_get_priv(dev);
- u32 isr, pcda;
- int ret;
- if (channel != adc->active_channel) {
pr_err("Requested channel is not active!\n");
return -EINVAL;
- }
- ret = readl_poll_timeout(adc->regs + IMX93_ADC_ISR, isr,
(isr & IMX93_ADC_ISR_EOC_MASK), IMX93_ADC_TIMEOUT);
- /* clear interrupts */
- writel(isr, adc->regs + IMX93_ADC_ISR);
- if (ret == -ETIMEDOUT) {
pr_warn("ADC conversion timeout!\n");
return ret;
- }
- pcda = readl(adc->regs + IMX93_ADC_PCDR0 + channel * 4);
- *data = FIELD_GET(IMX93_ADC_PCDR_CDATA_MASK, pcda);
- return 0;
+}
+static int imx93_adc_start_channel(struct udevice *dev, int channel) {
- struct imx93_adc_priv *adc = dev_get_priv(dev);
- u32 imr, mcr;
- /* config channel mask register */
- writel(1 << channel, adc->regs + IMX93_ADC_NCMR0);
- /* config interrupt mask */
- imr = FIELD_PREP(IMX93_ADC_IMR_EOC_MASK, 1);
- writel(imr, adc->regs + IMX93_ADC_IMR);
- writel(1 << channel, adc->regs + IMX93_ADC_CIMR0);
- /* config one-shot mode */
- mcr = readl(adc->regs + IMX93_ADC_MCR);
- mcr &= ~FIELD_PREP(IMX93_ADC_MCR_MODE_MASK, 1);
- writel(mcr, adc->regs + IMX93_ADC_MCR);
- /* start normal conversion */
- mcr = readl(adc->regs + IMX93_ADC_MCR);
- mcr |= FIELD_PREP(IMX93_ADC_MCR_NSTART_MASK, 1);
- writel(mcr, adc->regs + IMX93_ADC_MCR);
- adc->active_channel = channel;
- return 0;
+}
+static int imx93_adc_stop(struct udevice *dev) {
- struct imx93_adc_priv *adc = dev_get_priv(dev);
- imx93_adc_power_down(adc);
- adc->active_channel = -1;
- return 0;
+}
+static int imx93_adc_probe(struct udevice *dev) {
- struct imx93_adc_priv *adc = dev_get_priv(dev);
- unsigned int ret;
- ret = imx93_adc_calibration(adc);
- if (ret < 0)
return ret;
- ret = imx93_adc_config_ad_clk(adc);
- if (ret < 0)
return ret;
- adc->active_channel = -1;
- return 0;
+}
+static int imx93_adc_of_to_plat(struct udevice *dev) {
- struct adc_uclass_plat *uc_pdata = dev_get_uclass_plat(dev);
- struct imx93_adc_priv *adc = dev_get_priv(dev);
- adc->regs = dev_read_addr_ptr(dev);
- if (adc->regs == (struct imx93_adc *)FDT_ADDR_T_NONE) {
pr_err("Dev: %s - can't get address!", dev->name);
return -ENODATA;
- }
- uc_pdata->data_mask = IMX93_ADC_DAT_MASK;
- uc_pdata->data_format = ADC_DATA_FORMAT_BIN;
- uc_pdata->data_timeout_us = IMX93_ADC_TIMEOUT;
- /* Mask available channel bits: [0:3] */
- uc_pdata->channel_mask = (2 << IMX93_ADC_MAX_CHANNEL) - 1;
Seems you forget to handle the ADC ipg clock: IMX93_CLK_ADC1_GATE, please add it in the driver.
Best Regards Haibo Chen
- return 0;
+}
+static const struct adc_ops imx93_adc_ops = {
- .start_channel = imx93_adc_start_channel,
- .channel_data = imx93_adc_channel_data,
- .stop = imx93_adc_stop,
+};
+static const struct udevice_id imx93_adc_ids[] = {
- { .compatible = "nxp,imx93-adc" },
- { }
+};
+U_BOOT_DRIVER(imx93_adc) = {
- .name = "imx93-adc",
- .id = UCLASS_ADC,
- .of_match = imx93_adc_ids,
- .ops = &imx93_adc_ops,
- .probe = imx93_adc_probe,
- .of_to_plat = imx93_adc_of_to_plat,
- .priv_auto = sizeof(struct imx93_adc_priv),
+};
2.25.1
Hi Haibo, thanks for your suggestions. I will fix and resend. Luca Ellero

This node uses imx93_adc driver under drivers/adc.
The node is added keeping dts file in sync with iMX9 evk Linux devicetree.
Signed-off-by: Luca Ellero l.ellero@asem.it --- arch/arm/dts/imx93-11x11-evk.dts | 4 ++++ 1 file changed, 4 insertions(+)
diff --git a/arch/arm/dts/imx93-11x11-evk.dts b/arch/arm/dts/imx93-11x11-evk.dts index b3a5a3d71e..91f99cebae 100644 --- a/arch/arm/dts/imx93-11x11-evk.dts +++ b/arch/arm/dts/imx93-11x11-evk.dts @@ -58,6 +58,10 @@
};
+&adc1 { + status = "okay"; +}; + &lpi2c1 { #address-cells = <1>; #size-cells = <0>;

iMX93 ADC features: - 4 channels - 12 bit resolution
Signed-off-by: Luca Ellero l.ellero@asem.it --- configs/imx93_11x11_evk_defconfig | 1 + 1 file changed, 1 insertion(+)
diff --git a/configs/imx93_11x11_evk_defconfig b/configs/imx93_11x11_evk_defconfig index 477fb25807..fa929dd4ec 100644 --- a/configs/imx93_11x11_evk_defconfig +++ b/configs/imx93_11x11_evk_defconfig @@ -82,6 +82,7 @@ CONFIG_NET_RANDOM_ETHADDR=y CONFIG_SPL_DM=y CONFIG_REGMAP=y CONFIG_SYSCON=y +CONFIG_ADC_IMX93=y CONFIG_IMX_RGPIO2P=y CONFIG_DM_PCA953X=y CONFIG_DM_I2C=y
participants (3)
-
Bough Chen
-
Luca Ellero
-
Luca Ellero