11e23dcaaSCai Huoqing // SPDX-License-Identifier: GPL-2.0+
21e23dcaaSCai Huoqing /*
31e23dcaaSCai Huoqing * NXP i.MX8QXP ADC driver
41e23dcaaSCai Huoqing *
51e23dcaaSCai Huoqing * Based on the work of Haibo Chen <haibo.chen@nxp.com>
61e23dcaaSCai Huoqing * The initial developer of the original code is Haibo Chen.
71e23dcaaSCai Huoqing * Portions created by Haibo Chen are Copyright (C) 2018 NXP.
81e23dcaaSCai Huoqing * All Rights Reserved.
91e23dcaaSCai Huoqing *
101e23dcaaSCai Huoqing * Copyright (C) 2018 NXP
111e23dcaaSCai Huoqing * Copyright (C) 2021 Cai Huoqing
121e23dcaaSCai Huoqing */
131e23dcaaSCai Huoqing #include <linux/bitfield.h>
141e23dcaaSCai Huoqing #include <linux/bits.h>
151e23dcaaSCai Huoqing #include <linux/clk.h>
161e23dcaaSCai Huoqing #include <linux/completion.h>
171e23dcaaSCai Huoqing #include <linux/delay.h>
181e23dcaaSCai Huoqing #include <linux/err.h>
191e23dcaaSCai Huoqing #include <linux/interrupt.h>
201e23dcaaSCai Huoqing #include <linux/io.h>
211e23dcaaSCai Huoqing #include <linux/kernel.h>
22c2bb705fSNuno Sá #include <linux/mod_devicetable.h>
231e23dcaaSCai Huoqing #include <linux/module.h>
241e23dcaaSCai Huoqing #include <linux/platform_device.h>
251e23dcaaSCai Huoqing #include <linux/pm_runtime.h>
261e23dcaaSCai Huoqing #include <linux/regulator/consumer.h>
271e23dcaaSCai Huoqing
281e23dcaaSCai Huoqing #include <linux/iio/iio.h>
291e23dcaaSCai Huoqing
301e23dcaaSCai Huoqing #define ADC_DRIVER_NAME "imx8qxp-adc"
311e23dcaaSCai Huoqing
321e23dcaaSCai Huoqing /* Register map definition */
331e23dcaaSCai Huoqing #define IMX8QXP_ADR_ADC_CTRL 0x10
341e23dcaaSCai Huoqing #define IMX8QXP_ADR_ADC_STAT 0x14
351e23dcaaSCai Huoqing #define IMX8QXP_ADR_ADC_IE 0x18
361e23dcaaSCai Huoqing #define IMX8QXP_ADR_ADC_DE 0x1c
371e23dcaaSCai Huoqing #define IMX8QXP_ADR_ADC_CFG 0x20
381e23dcaaSCai Huoqing #define IMX8QXP_ADR_ADC_FCTRL 0x30
391e23dcaaSCai Huoqing #define IMX8QXP_ADR_ADC_SWTRIG 0x34
401e23dcaaSCai Huoqing #define IMX8QXP_ADR_ADC_TCTRL(tid) (0xc0 + (tid) * 4)
41*850101b3SPhilipp Rossak #define IMX8QXP_ADR_ADC_CMDL(cid) (0x100 + (cid) * 8)
42*850101b3SPhilipp Rossak #define IMX8QXP_ADR_ADC_CMDH(cid) (0x104 + (cid) * 8)
431e23dcaaSCai Huoqing #define IMX8QXP_ADR_ADC_RESFIFO 0x300
441e23dcaaSCai Huoqing #define IMX8QXP_ADR_ADC_TST 0xffc
451e23dcaaSCai Huoqing
461e23dcaaSCai Huoqing /* ADC bit shift */
471e23dcaaSCai Huoqing #define IMX8QXP_ADC_IE_FWMIE_MASK GENMASK(1, 0)
481e23dcaaSCai Huoqing #define IMX8QXP_ADC_CTRL_FIFO_RESET_MASK BIT(8)
491e23dcaaSCai Huoqing #define IMX8QXP_ADC_CTRL_SOFTWARE_RESET_MASK BIT(1)
501e23dcaaSCai Huoqing #define IMX8QXP_ADC_CTRL_ADC_EN_MASK BIT(0)
511e23dcaaSCai Huoqing #define IMX8QXP_ADC_TCTRL_TCMD_MASK GENMASK(31, 24)
521e23dcaaSCai Huoqing #define IMX8QXP_ADC_TCTRL_TDLY_MASK GENMASK(23, 16)
531e23dcaaSCai Huoqing #define IMX8QXP_ADC_TCTRL_TPRI_MASK GENMASK(15, 8)
541e23dcaaSCai Huoqing #define IMX8QXP_ADC_TCTRL_HTEN_MASK GENMASK(7, 0)
551e23dcaaSCai Huoqing #define IMX8QXP_ADC_CMDL_CSCALE_MASK GENMASK(13, 8)
561e23dcaaSCai Huoqing #define IMX8QXP_ADC_CMDL_MODE_MASK BIT(7)
571e23dcaaSCai Huoqing #define IMX8QXP_ADC_CMDL_DIFF_MASK BIT(6)
581e23dcaaSCai Huoqing #define IMX8QXP_ADC_CMDL_ABSEL_MASK BIT(5)
591e23dcaaSCai Huoqing #define IMX8QXP_ADC_CMDL_ADCH_MASK GENMASK(2, 0)
601e23dcaaSCai Huoqing #define IMX8QXP_ADC_CMDH_NEXT_MASK GENMASK(31, 24)
611e23dcaaSCai Huoqing #define IMX8QXP_ADC_CMDH_LOOP_MASK GENMASK(23, 16)
621e23dcaaSCai Huoqing #define IMX8QXP_ADC_CMDH_AVGS_MASK GENMASK(15, 12)
631e23dcaaSCai Huoqing #define IMX8QXP_ADC_CMDH_STS_MASK BIT(8)
641e23dcaaSCai Huoqing #define IMX8QXP_ADC_CMDH_LWI_MASK GENMASK(7, 7)
651e23dcaaSCai Huoqing #define IMX8QXP_ADC_CMDH_CMPEN_MASK GENMASK(0, 0)
661e23dcaaSCai Huoqing #define IMX8QXP_ADC_CFG_PWREN_MASK BIT(28)
671e23dcaaSCai Huoqing #define IMX8QXP_ADC_CFG_PUDLY_MASK GENMASK(23, 16)
681e23dcaaSCai Huoqing #define IMX8QXP_ADC_CFG_REFSEL_MASK GENMASK(7, 6)
691e23dcaaSCai Huoqing #define IMX8QXP_ADC_CFG_PWRSEL_MASK GENMASK(5, 4)
701e23dcaaSCai Huoqing #define IMX8QXP_ADC_CFG_TPRICTRL_MASK GENMASK(3, 0)
711e23dcaaSCai Huoqing #define IMX8QXP_ADC_FCTRL_FWMARK_MASK GENMASK(20, 16)
721e23dcaaSCai Huoqing #define IMX8QXP_ADC_FCTRL_FCOUNT_MASK GENMASK(4, 0)
731e23dcaaSCai Huoqing #define IMX8QXP_ADC_RESFIFO_VAL_MASK GENMASK(18, 3)
741e23dcaaSCai Huoqing
751e23dcaaSCai Huoqing /* ADC PARAMETER*/
761e23dcaaSCai Huoqing #define IMX8QXP_ADC_CMDL_CHANNEL_SCALE_FULL GENMASK(5, 0)
771e23dcaaSCai Huoqing #define IMX8QXP_ADC_CMDL_SEL_A_A_B_CHANNEL 0
781e23dcaaSCai Huoqing #define IMX8QXP_ADC_CMDL_STANDARD_RESOLUTION 0
791e23dcaaSCai Huoqing #define IMX8QXP_ADC_CMDL_MODE_SINGLE 0
801e23dcaaSCai Huoqing #define IMX8QXP_ADC_CMDH_LWI_INCREMENT_DIS 0
811e23dcaaSCai Huoqing #define IMX8QXP_ADC_CMDH_CMPEN_DIS 0
821e23dcaaSCai Huoqing #define IMX8QXP_ADC_PAUSE_EN BIT(31)
831e23dcaaSCai Huoqing #define IMX8QXP_ADC_TCTRL_TPRI_PRIORITY_HIGH 0
841e23dcaaSCai Huoqing
851e23dcaaSCai Huoqing #define IMX8QXP_ADC_TCTRL_HTEN_HW_TIRG_DIS 0
861e23dcaaSCai Huoqing
871e23dcaaSCai Huoqing #define IMX8QXP_ADC_TIMEOUT msecs_to_jiffies(100)
881e23dcaaSCai Huoqing
890fc3562aSFrank Li #define IMX8QXP_ADC_MAX_FIFO_SIZE 16
900fc3562aSFrank Li
911e23dcaaSCai Huoqing struct imx8qxp_adc {
921e23dcaaSCai Huoqing struct device *dev;
931e23dcaaSCai Huoqing void __iomem *regs;
941e23dcaaSCai Huoqing struct clk *clk;
951e23dcaaSCai Huoqing struct clk *ipg_clk;
961e23dcaaSCai Huoqing struct regulator *vref;
971e23dcaaSCai Huoqing /* Serialise ADC channel reads */
981e23dcaaSCai Huoqing struct mutex lock;
991e23dcaaSCai Huoqing struct completion completion;
1000fc3562aSFrank Li u32 fifo[IMX8QXP_ADC_MAX_FIFO_SIZE];
1011e23dcaaSCai Huoqing };
1021e23dcaaSCai Huoqing
1031e23dcaaSCai Huoqing #define IMX8QXP_ADC_CHAN(_idx) { \
1041e23dcaaSCai Huoqing .type = IIO_VOLTAGE, \
1051e23dcaaSCai Huoqing .indexed = 1, \
1061e23dcaaSCai Huoqing .channel = (_idx), \
1071e23dcaaSCai Huoqing .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
1081e23dcaaSCai Huoqing .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | \
1091e23dcaaSCai Huoqing BIT(IIO_CHAN_INFO_SAMP_FREQ), \
1101e23dcaaSCai Huoqing }
1111e23dcaaSCai Huoqing
1121e23dcaaSCai Huoqing static const struct iio_chan_spec imx8qxp_adc_iio_channels[] = {
1131e23dcaaSCai Huoqing IMX8QXP_ADC_CHAN(0),
1141e23dcaaSCai Huoqing IMX8QXP_ADC_CHAN(1),
1151e23dcaaSCai Huoqing IMX8QXP_ADC_CHAN(2),
1161e23dcaaSCai Huoqing IMX8QXP_ADC_CHAN(3),
1171e23dcaaSCai Huoqing IMX8QXP_ADC_CHAN(4),
1181e23dcaaSCai Huoqing IMX8QXP_ADC_CHAN(5),
1191e23dcaaSCai Huoqing IMX8QXP_ADC_CHAN(6),
1201e23dcaaSCai Huoqing IMX8QXP_ADC_CHAN(7),
1211e23dcaaSCai Huoqing };
1221e23dcaaSCai Huoqing
imx8qxp_adc_reset(struct imx8qxp_adc * adc)1231e23dcaaSCai Huoqing static void imx8qxp_adc_reset(struct imx8qxp_adc *adc)
1241e23dcaaSCai Huoqing {
1251e23dcaaSCai Huoqing u32 ctrl;
1261e23dcaaSCai Huoqing
1271e23dcaaSCai Huoqing /*software reset, need to clear the set bit*/
1281e23dcaaSCai Huoqing ctrl = readl(adc->regs + IMX8QXP_ADR_ADC_CTRL);
1291e23dcaaSCai Huoqing ctrl |= FIELD_PREP(IMX8QXP_ADC_CTRL_SOFTWARE_RESET_MASK, 1);
1301e23dcaaSCai Huoqing writel(ctrl, adc->regs + IMX8QXP_ADR_ADC_CTRL);
1311e23dcaaSCai Huoqing udelay(10);
1321e23dcaaSCai Huoqing ctrl &= ~FIELD_PREP(IMX8QXP_ADC_CTRL_SOFTWARE_RESET_MASK, 1);
1331e23dcaaSCai Huoqing writel(ctrl, adc->regs + IMX8QXP_ADR_ADC_CTRL);
1341e23dcaaSCai Huoqing
1351e23dcaaSCai Huoqing /* reset the fifo */
1361e23dcaaSCai Huoqing ctrl |= FIELD_PREP(IMX8QXP_ADC_CTRL_FIFO_RESET_MASK, 1);
1371e23dcaaSCai Huoqing writel(ctrl, adc->regs + IMX8QXP_ADR_ADC_CTRL);
1381e23dcaaSCai Huoqing }
1391e23dcaaSCai Huoqing
imx8qxp_adc_reg_config(struct imx8qxp_adc * adc,int channel)1401e23dcaaSCai Huoqing static void imx8qxp_adc_reg_config(struct imx8qxp_adc *adc, int channel)
1411e23dcaaSCai Huoqing {
1421e23dcaaSCai Huoqing u32 adc_cfg, adc_tctrl, adc_cmdl, adc_cmdh;
1431e23dcaaSCai Huoqing
1441e23dcaaSCai Huoqing /* ADC configuration */
1451e23dcaaSCai Huoqing adc_cfg = FIELD_PREP(IMX8QXP_ADC_CFG_PWREN_MASK, 1) |
1461e23dcaaSCai Huoqing FIELD_PREP(IMX8QXP_ADC_CFG_PUDLY_MASK, 0x80)|
1471e23dcaaSCai Huoqing FIELD_PREP(IMX8QXP_ADC_CFG_REFSEL_MASK, 0) |
1481e23dcaaSCai Huoqing FIELD_PREP(IMX8QXP_ADC_CFG_PWRSEL_MASK, 3) |
1491e23dcaaSCai Huoqing FIELD_PREP(IMX8QXP_ADC_CFG_TPRICTRL_MASK, 0);
1501e23dcaaSCai Huoqing writel(adc_cfg, adc->regs + IMX8QXP_ADR_ADC_CFG);
1511e23dcaaSCai Huoqing
1521e23dcaaSCai Huoqing /* config the trigger control */
1531e23dcaaSCai Huoqing adc_tctrl = FIELD_PREP(IMX8QXP_ADC_TCTRL_TCMD_MASK, 1) |
1541e23dcaaSCai Huoqing FIELD_PREP(IMX8QXP_ADC_TCTRL_TDLY_MASK, 0) |
1551e23dcaaSCai Huoqing FIELD_PREP(IMX8QXP_ADC_TCTRL_TPRI_MASK, IMX8QXP_ADC_TCTRL_TPRI_PRIORITY_HIGH) |
1561e23dcaaSCai Huoqing FIELD_PREP(IMX8QXP_ADC_TCTRL_HTEN_MASK, IMX8QXP_ADC_TCTRL_HTEN_HW_TIRG_DIS);
1571e23dcaaSCai Huoqing writel(adc_tctrl, adc->regs + IMX8QXP_ADR_ADC_TCTRL(0));
1581e23dcaaSCai Huoqing
1591e23dcaaSCai Huoqing /* config the cmd */
1601e23dcaaSCai Huoqing adc_cmdl = FIELD_PREP(IMX8QXP_ADC_CMDL_CSCALE_MASK, IMX8QXP_ADC_CMDL_CHANNEL_SCALE_FULL) |
1611e23dcaaSCai Huoqing FIELD_PREP(IMX8QXP_ADC_CMDL_MODE_MASK, IMX8QXP_ADC_CMDL_STANDARD_RESOLUTION) |
1621e23dcaaSCai Huoqing FIELD_PREP(IMX8QXP_ADC_CMDL_DIFF_MASK, IMX8QXP_ADC_CMDL_MODE_SINGLE) |
1631e23dcaaSCai Huoqing FIELD_PREP(IMX8QXP_ADC_CMDL_ABSEL_MASK, IMX8QXP_ADC_CMDL_SEL_A_A_B_CHANNEL) |
1641e23dcaaSCai Huoqing FIELD_PREP(IMX8QXP_ADC_CMDL_ADCH_MASK, channel);
1651e23dcaaSCai Huoqing writel(adc_cmdl, adc->regs + IMX8QXP_ADR_ADC_CMDL(0));
1661e23dcaaSCai Huoqing
1671e23dcaaSCai Huoqing adc_cmdh = FIELD_PREP(IMX8QXP_ADC_CMDH_NEXT_MASK, 0) |
1681e23dcaaSCai Huoqing FIELD_PREP(IMX8QXP_ADC_CMDH_LOOP_MASK, 0) |
1691e23dcaaSCai Huoqing FIELD_PREP(IMX8QXP_ADC_CMDH_AVGS_MASK, 7) |
1701e23dcaaSCai Huoqing FIELD_PREP(IMX8QXP_ADC_CMDH_STS_MASK, 0) |
1711e23dcaaSCai Huoqing FIELD_PREP(IMX8QXP_ADC_CMDH_LWI_MASK, IMX8QXP_ADC_CMDH_LWI_INCREMENT_DIS) |
1721e23dcaaSCai Huoqing FIELD_PREP(IMX8QXP_ADC_CMDH_CMPEN_MASK, IMX8QXP_ADC_CMDH_CMPEN_DIS);
1731e23dcaaSCai Huoqing writel(adc_cmdh, adc->regs + IMX8QXP_ADR_ADC_CMDH(0));
1741e23dcaaSCai Huoqing }
1751e23dcaaSCai Huoqing
imx8qxp_adc_fifo_config(struct imx8qxp_adc * adc)1761e23dcaaSCai Huoqing static void imx8qxp_adc_fifo_config(struct imx8qxp_adc *adc)
1771e23dcaaSCai Huoqing {
1781e23dcaaSCai Huoqing u32 fifo_ctrl, interrupt_en;
1791e23dcaaSCai Huoqing
1801e23dcaaSCai Huoqing fifo_ctrl = readl(adc->regs + IMX8QXP_ADR_ADC_FCTRL);
1811e23dcaaSCai Huoqing fifo_ctrl &= ~IMX8QXP_ADC_FCTRL_FWMARK_MASK;
1821e23dcaaSCai Huoqing /* set the watermark level to 1 */
1831e23dcaaSCai Huoqing fifo_ctrl |= FIELD_PREP(IMX8QXP_ADC_FCTRL_FWMARK_MASK, 0);
1841e23dcaaSCai Huoqing writel(fifo_ctrl, adc->regs + IMX8QXP_ADR_ADC_FCTRL);
1851e23dcaaSCai Huoqing
1861e23dcaaSCai Huoqing /* FIFO Watermark Interrupt Enable */
1871e23dcaaSCai Huoqing interrupt_en = readl(adc->regs + IMX8QXP_ADR_ADC_IE);
1881e23dcaaSCai Huoqing interrupt_en |= FIELD_PREP(IMX8QXP_ADC_IE_FWMIE_MASK, 1);
1891e23dcaaSCai Huoqing writel(interrupt_en, adc->regs + IMX8QXP_ADR_ADC_IE);
1901e23dcaaSCai Huoqing }
1911e23dcaaSCai Huoqing
imx8qxp_adc_disable(struct imx8qxp_adc * adc)1921e23dcaaSCai Huoqing static void imx8qxp_adc_disable(struct imx8qxp_adc *adc)
1931e23dcaaSCai Huoqing {
1941e23dcaaSCai Huoqing u32 ctrl;
1951e23dcaaSCai Huoqing
1961e23dcaaSCai Huoqing ctrl = readl(adc->regs + IMX8QXP_ADR_ADC_CTRL);
1971e23dcaaSCai Huoqing ctrl &= ~FIELD_PREP(IMX8QXP_ADC_CTRL_ADC_EN_MASK, 1);
1981e23dcaaSCai Huoqing writel(ctrl, adc->regs + IMX8QXP_ADR_ADC_CTRL);
1991e23dcaaSCai Huoqing }
2001e23dcaaSCai Huoqing
imx8qxp_adc_read_raw(struct iio_dev * indio_dev,struct iio_chan_spec const * chan,int * val,int * val2,long mask)2011e23dcaaSCai Huoqing static int imx8qxp_adc_read_raw(struct iio_dev *indio_dev,
2021e23dcaaSCai Huoqing struct iio_chan_spec const *chan,
2031e23dcaaSCai Huoqing int *val, int *val2, long mask)
2041e23dcaaSCai Huoqing {
2051e23dcaaSCai Huoqing struct imx8qxp_adc *adc = iio_priv(indio_dev);
2061e23dcaaSCai Huoqing struct device *dev = adc->dev;
2071e23dcaaSCai Huoqing
208bd1d558cSMartin Larsson u32 ctrl;
2091e23dcaaSCai Huoqing long ret;
2101e23dcaaSCai Huoqing
2111e23dcaaSCai Huoqing switch (mask) {
2121e23dcaaSCai Huoqing case IIO_CHAN_INFO_RAW:
2131e23dcaaSCai Huoqing pm_runtime_get_sync(dev);
2141e23dcaaSCai Huoqing
2151e23dcaaSCai Huoqing mutex_lock(&adc->lock);
2161e23dcaaSCai Huoqing reinit_completion(&adc->completion);
2171e23dcaaSCai Huoqing
2181e23dcaaSCai Huoqing imx8qxp_adc_reg_config(adc, chan->channel);
2191e23dcaaSCai Huoqing
2201e23dcaaSCai Huoqing imx8qxp_adc_fifo_config(adc);
2211e23dcaaSCai Huoqing
2221e23dcaaSCai Huoqing /* adc enable */
2231e23dcaaSCai Huoqing ctrl = readl(adc->regs + IMX8QXP_ADR_ADC_CTRL);
2241e23dcaaSCai Huoqing ctrl |= FIELD_PREP(IMX8QXP_ADC_CTRL_ADC_EN_MASK, 1);
2251e23dcaaSCai Huoqing writel(ctrl, adc->regs + IMX8QXP_ADR_ADC_CTRL);
2261e23dcaaSCai Huoqing /* adc start */
2271e23dcaaSCai Huoqing writel(1, adc->regs + IMX8QXP_ADR_ADC_SWTRIG);
2281e23dcaaSCai Huoqing
2291e23dcaaSCai Huoqing ret = wait_for_completion_interruptible_timeout(&adc->completion,
2301e23dcaaSCai Huoqing IMX8QXP_ADC_TIMEOUT);
2311e23dcaaSCai Huoqing
2321e23dcaaSCai Huoqing pm_runtime_mark_last_busy(dev);
2331e23dcaaSCai Huoqing pm_runtime_put_sync_autosuspend(dev);
2341e23dcaaSCai Huoqing
2351e23dcaaSCai Huoqing if (ret == 0) {
2361e23dcaaSCai Huoqing mutex_unlock(&adc->lock);
2371e23dcaaSCai Huoqing return -ETIMEDOUT;
2381e23dcaaSCai Huoqing }
2391e23dcaaSCai Huoqing if (ret < 0) {
2401e23dcaaSCai Huoqing mutex_unlock(&adc->lock);
2411e23dcaaSCai Huoqing return ret;
2421e23dcaaSCai Huoqing }
2431e23dcaaSCai Huoqing
2440fc3562aSFrank Li *val = adc->fifo[0];
2451e23dcaaSCai Huoqing
2461e23dcaaSCai Huoqing mutex_unlock(&adc->lock);
2471e23dcaaSCai Huoqing return IIO_VAL_INT;
2481e23dcaaSCai Huoqing
2491e23dcaaSCai Huoqing case IIO_CHAN_INFO_SCALE:
250bd1d558cSMartin Larsson ret = regulator_get_voltage(adc->vref);
251bd1d558cSMartin Larsson if (ret < 0)
252bd1d558cSMartin Larsson return ret;
253bd1d558cSMartin Larsson *val = ret / 1000;
2541e23dcaaSCai Huoqing *val2 = 12;
2551e23dcaaSCai Huoqing return IIO_VAL_FRACTIONAL_LOG2;
2561e23dcaaSCai Huoqing
2571e23dcaaSCai Huoqing case IIO_CHAN_INFO_SAMP_FREQ:
2581e23dcaaSCai Huoqing *val = clk_get_rate(adc->clk) / 3;
2591e23dcaaSCai Huoqing return IIO_VAL_INT;
2601e23dcaaSCai Huoqing
2611e23dcaaSCai Huoqing default:
2621e23dcaaSCai Huoqing return -EINVAL;
2631e23dcaaSCai Huoqing }
2641e23dcaaSCai Huoqing }
2651e23dcaaSCai Huoqing
imx8qxp_adc_isr(int irq,void * dev_id)2661e23dcaaSCai Huoqing static irqreturn_t imx8qxp_adc_isr(int irq, void *dev_id)
2671e23dcaaSCai Huoqing {
2681e23dcaaSCai Huoqing struct imx8qxp_adc *adc = dev_id;
2691e23dcaaSCai Huoqing u32 fifo_count;
2700fc3562aSFrank Li int i;
2711e23dcaaSCai Huoqing
2721e23dcaaSCai Huoqing fifo_count = FIELD_GET(IMX8QXP_ADC_FCTRL_FCOUNT_MASK,
2731e23dcaaSCai Huoqing readl(adc->regs + IMX8QXP_ADR_ADC_FCTRL));
2741e23dcaaSCai Huoqing
2750fc3562aSFrank Li for (i = 0; i < fifo_count; i++)
2760fc3562aSFrank Li adc->fifo[i] = FIELD_GET(IMX8QXP_ADC_RESFIFO_VAL_MASK,
2770fc3562aSFrank Li readl_relaxed(adc->regs + IMX8QXP_ADR_ADC_RESFIFO));
2780fc3562aSFrank Li
2791e23dcaaSCai Huoqing if (fifo_count)
2801e23dcaaSCai Huoqing complete(&adc->completion);
2811e23dcaaSCai Huoqing
2821e23dcaaSCai Huoqing return IRQ_HANDLED;
2831e23dcaaSCai Huoqing }
2841e23dcaaSCai Huoqing
imx8qxp_adc_reg_access(struct iio_dev * indio_dev,unsigned int reg,unsigned int writeval,unsigned int * readval)2851e23dcaaSCai Huoqing static int imx8qxp_adc_reg_access(struct iio_dev *indio_dev, unsigned int reg,
2861e23dcaaSCai Huoqing unsigned int writeval, unsigned int *readval)
2871e23dcaaSCai Huoqing {
2881e23dcaaSCai Huoqing struct imx8qxp_adc *adc = iio_priv(indio_dev);
2891e23dcaaSCai Huoqing struct device *dev = adc->dev;
2901e23dcaaSCai Huoqing
2911e23dcaaSCai Huoqing if (!readval || reg % 4 || reg > IMX8QXP_ADR_ADC_TST)
2921e23dcaaSCai Huoqing return -EINVAL;
2931e23dcaaSCai Huoqing
2941e23dcaaSCai Huoqing pm_runtime_get_sync(dev);
2951e23dcaaSCai Huoqing
2961e23dcaaSCai Huoqing *readval = readl(adc->regs + reg);
2971e23dcaaSCai Huoqing
2981e23dcaaSCai Huoqing pm_runtime_mark_last_busy(dev);
2991e23dcaaSCai Huoqing pm_runtime_put_sync_autosuspend(dev);
3001e23dcaaSCai Huoqing
3011e23dcaaSCai Huoqing return 0;
3021e23dcaaSCai Huoqing }
3031e23dcaaSCai Huoqing
3041e23dcaaSCai Huoqing static const struct iio_info imx8qxp_adc_iio_info = {
3051e23dcaaSCai Huoqing .read_raw = &imx8qxp_adc_read_raw,
3061e23dcaaSCai Huoqing .debugfs_reg_access = &imx8qxp_adc_reg_access,
3071e23dcaaSCai Huoqing };
3081e23dcaaSCai Huoqing
imx8qxp_adc_probe(struct platform_device * pdev)3091e23dcaaSCai Huoqing static int imx8qxp_adc_probe(struct platform_device *pdev)
3101e23dcaaSCai Huoqing {
3111e23dcaaSCai Huoqing struct imx8qxp_adc *adc;
3121e23dcaaSCai Huoqing struct iio_dev *indio_dev;
3131e23dcaaSCai Huoqing struct device *dev = &pdev->dev;
3141e23dcaaSCai Huoqing int irq;
3151e23dcaaSCai Huoqing int ret;
3161e23dcaaSCai Huoqing
3171e23dcaaSCai Huoqing indio_dev = devm_iio_device_alloc(dev, sizeof(*adc));
3181e23dcaaSCai Huoqing if (!indio_dev) {
3191e23dcaaSCai Huoqing dev_err(dev, "Failed allocating iio device\n");
3201e23dcaaSCai Huoqing return -ENOMEM;
3211e23dcaaSCai Huoqing }
3221e23dcaaSCai Huoqing
3231e23dcaaSCai Huoqing adc = iio_priv(indio_dev);
3241e23dcaaSCai Huoqing adc->dev = dev;
3251e23dcaaSCai Huoqing
3261e23dcaaSCai Huoqing mutex_init(&adc->lock);
3271e23dcaaSCai Huoqing adc->regs = devm_platform_ioremap_resource(pdev, 0);
3281e23dcaaSCai Huoqing if (IS_ERR(adc->regs))
3291e23dcaaSCai Huoqing return PTR_ERR(adc->regs);
3301e23dcaaSCai Huoqing
3311e23dcaaSCai Huoqing irq = platform_get_irq(pdev, 0);
3321e23dcaaSCai Huoqing if (irq < 0)
3331e23dcaaSCai Huoqing return irq;
3341e23dcaaSCai Huoqing
3351e23dcaaSCai Huoqing adc->clk = devm_clk_get(dev, "per");
3361e23dcaaSCai Huoqing if (IS_ERR(adc->clk))
3371e23dcaaSCai Huoqing return dev_err_probe(dev, PTR_ERR(adc->clk), "Failed getting clock\n");
3381e23dcaaSCai Huoqing
3391e23dcaaSCai Huoqing adc->ipg_clk = devm_clk_get(dev, "ipg");
3401e23dcaaSCai Huoqing if (IS_ERR(adc->ipg_clk))
3411e23dcaaSCai Huoqing return dev_err_probe(dev, PTR_ERR(adc->ipg_clk), "Failed getting clock\n");
3421e23dcaaSCai Huoqing
3431e23dcaaSCai Huoqing adc->vref = devm_regulator_get(dev, "vref");
3441e23dcaaSCai Huoqing if (IS_ERR(adc->vref))
3451e23dcaaSCai Huoqing return dev_err_probe(dev, PTR_ERR(adc->vref), "Failed getting reference voltage\n");
3461e23dcaaSCai Huoqing
3471e23dcaaSCai Huoqing ret = regulator_enable(adc->vref);
3481e23dcaaSCai Huoqing if (ret) {
3491e23dcaaSCai Huoqing dev_err(dev, "Can't enable adc reference top voltage\n");
3501e23dcaaSCai Huoqing return ret;
3511e23dcaaSCai Huoqing }
3521e23dcaaSCai Huoqing
3531e23dcaaSCai Huoqing platform_set_drvdata(pdev, indio_dev);
3541e23dcaaSCai Huoqing
3551e23dcaaSCai Huoqing init_completion(&adc->completion);
3561e23dcaaSCai Huoqing
3571e23dcaaSCai Huoqing indio_dev->name = ADC_DRIVER_NAME;
3581e23dcaaSCai Huoqing indio_dev->info = &imx8qxp_adc_iio_info;
3591e23dcaaSCai Huoqing indio_dev->modes = INDIO_DIRECT_MODE;
3601e23dcaaSCai Huoqing indio_dev->channels = imx8qxp_adc_iio_channels;
3611e23dcaaSCai Huoqing indio_dev->num_channels = ARRAY_SIZE(imx8qxp_adc_iio_channels);
3621e23dcaaSCai Huoqing
3631e23dcaaSCai Huoqing ret = clk_prepare_enable(adc->clk);
3641e23dcaaSCai Huoqing if (ret) {
3651e23dcaaSCai Huoqing dev_err(&pdev->dev, "Could not prepare or enable the clock.\n");
3661e23dcaaSCai Huoqing goto error_regulator_disable;
3671e23dcaaSCai Huoqing }
3681e23dcaaSCai Huoqing
3691e23dcaaSCai Huoqing ret = clk_prepare_enable(adc->ipg_clk);
3701e23dcaaSCai Huoqing if (ret) {
3711e23dcaaSCai Huoqing dev_err(&pdev->dev, "Could not prepare or enable the clock.\n");
3721e23dcaaSCai Huoqing goto error_adc_clk_disable;
3731e23dcaaSCai Huoqing }
3741e23dcaaSCai Huoqing
3751e23dcaaSCai Huoqing ret = devm_request_irq(dev, irq, imx8qxp_adc_isr, 0, ADC_DRIVER_NAME, adc);
3761e23dcaaSCai Huoqing if (ret < 0) {
3771e23dcaaSCai Huoqing dev_err(dev, "Failed requesting irq, irq = %d\n", irq);
3781e23dcaaSCai Huoqing goto error_ipg_clk_disable;
3791e23dcaaSCai Huoqing }
3801e23dcaaSCai Huoqing
3811e23dcaaSCai Huoqing imx8qxp_adc_reset(adc);
3821e23dcaaSCai Huoqing
3831e23dcaaSCai Huoqing ret = iio_device_register(indio_dev);
3841e23dcaaSCai Huoqing if (ret) {
3851e23dcaaSCai Huoqing imx8qxp_adc_disable(adc);
3861e23dcaaSCai Huoqing dev_err(dev, "Couldn't register the device.\n");
3871e23dcaaSCai Huoqing goto error_ipg_clk_disable;
3881e23dcaaSCai Huoqing }
3891e23dcaaSCai Huoqing
3901e23dcaaSCai Huoqing pm_runtime_set_active(dev);
3911e23dcaaSCai Huoqing pm_runtime_set_autosuspend_delay(dev, 50);
3921e23dcaaSCai Huoqing pm_runtime_use_autosuspend(dev);
3931e23dcaaSCai Huoqing pm_runtime_enable(dev);
3941e23dcaaSCai Huoqing
3951e23dcaaSCai Huoqing return 0;
3961e23dcaaSCai Huoqing
3971e23dcaaSCai Huoqing error_ipg_clk_disable:
3981e23dcaaSCai Huoqing clk_disable_unprepare(adc->ipg_clk);
3991e23dcaaSCai Huoqing error_adc_clk_disable:
4001e23dcaaSCai Huoqing clk_disable_unprepare(adc->clk);
4011e23dcaaSCai Huoqing error_regulator_disable:
4021e23dcaaSCai Huoqing regulator_disable(adc->vref);
4031e23dcaaSCai Huoqing
4041e23dcaaSCai Huoqing return ret;
4051e23dcaaSCai Huoqing }
4061e23dcaaSCai Huoqing
imx8qxp_adc_remove(struct platform_device * pdev)4071e23dcaaSCai Huoqing static int imx8qxp_adc_remove(struct platform_device *pdev)
4081e23dcaaSCai Huoqing {
4091e23dcaaSCai Huoqing struct iio_dev *indio_dev = platform_get_drvdata(pdev);
4101e23dcaaSCai Huoqing struct imx8qxp_adc *adc = iio_priv(indio_dev);
4111e23dcaaSCai Huoqing struct device *dev = adc->dev;
4121e23dcaaSCai Huoqing
4131e23dcaaSCai Huoqing pm_runtime_get_sync(dev);
4141e23dcaaSCai Huoqing
4151e23dcaaSCai Huoqing iio_device_unregister(indio_dev);
4161e23dcaaSCai Huoqing
4171e23dcaaSCai Huoqing imx8qxp_adc_disable(adc);
4181e23dcaaSCai Huoqing
4191e23dcaaSCai Huoqing clk_disable_unprepare(adc->clk);
4201e23dcaaSCai Huoqing clk_disable_unprepare(adc->ipg_clk);
4211e23dcaaSCai Huoqing regulator_disable(adc->vref);
4221e23dcaaSCai Huoqing
4231e23dcaaSCai Huoqing pm_runtime_disable(dev);
4241e23dcaaSCai Huoqing pm_runtime_put_noidle(dev);
4251e23dcaaSCai Huoqing
4261e23dcaaSCai Huoqing return 0;
4271e23dcaaSCai Huoqing }
4281e23dcaaSCai Huoqing
imx8qxp_adc_runtime_suspend(struct device * dev)4292583f5e8SJonathan Cameron static int imx8qxp_adc_runtime_suspend(struct device *dev)
4301e23dcaaSCai Huoqing {
4311e23dcaaSCai Huoqing struct iio_dev *indio_dev = dev_get_drvdata(dev);
4321e23dcaaSCai Huoqing struct imx8qxp_adc *adc = iio_priv(indio_dev);
4331e23dcaaSCai Huoqing
4341e23dcaaSCai Huoqing imx8qxp_adc_disable(adc);
4351e23dcaaSCai Huoqing
4361e23dcaaSCai Huoqing clk_disable_unprepare(adc->clk);
4371e23dcaaSCai Huoqing clk_disable_unprepare(adc->ipg_clk);
4381e23dcaaSCai Huoqing regulator_disable(adc->vref);
4391e23dcaaSCai Huoqing
4401e23dcaaSCai Huoqing return 0;
4411e23dcaaSCai Huoqing }
4421e23dcaaSCai Huoqing
imx8qxp_adc_runtime_resume(struct device * dev)4432583f5e8SJonathan Cameron static int imx8qxp_adc_runtime_resume(struct device *dev)
4441e23dcaaSCai Huoqing {
4451e23dcaaSCai Huoqing struct iio_dev *indio_dev = dev_get_drvdata(dev);
4461e23dcaaSCai Huoqing struct imx8qxp_adc *adc = iio_priv(indio_dev);
4471e23dcaaSCai Huoqing int ret;
4481e23dcaaSCai Huoqing
4491e23dcaaSCai Huoqing ret = regulator_enable(adc->vref);
4501e23dcaaSCai Huoqing if (ret) {
4511e23dcaaSCai Huoqing dev_err(dev, "Can't enable adc reference top voltage, err = %d\n", ret);
4521e23dcaaSCai Huoqing return ret;
4531e23dcaaSCai Huoqing }
4541e23dcaaSCai Huoqing
4551e23dcaaSCai Huoqing ret = clk_prepare_enable(adc->clk);
4561e23dcaaSCai Huoqing if (ret) {
4571e23dcaaSCai Huoqing dev_err(dev, "Could not prepare or enable clock.\n");
4581e23dcaaSCai Huoqing goto err_disable_reg;
4591e23dcaaSCai Huoqing }
4601e23dcaaSCai Huoqing
4611e23dcaaSCai Huoqing ret = clk_prepare_enable(adc->ipg_clk);
4621e23dcaaSCai Huoqing if (ret) {
4631e23dcaaSCai Huoqing dev_err(dev, "Could not prepare or enable clock.\n");
4641e23dcaaSCai Huoqing goto err_unprepare_clk;
4651e23dcaaSCai Huoqing }
4661e23dcaaSCai Huoqing
4671e23dcaaSCai Huoqing imx8qxp_adc_reset(adc);
4681e23dcaaSCai Huoqing
4691e23dcaaSCai Huoqing return 0;
4701e23dcaaSCai Huoqing
4711e23dcaaSCai Huoqing err_unprepare_clk:
4721e23dcaaSCai Huoqing clk_disable_unprepare(adc->clk);
4731e23dcaaSCai Huoqing
4741e23dcaaSCai Huoqing err_disable_reg:
4751e23dcaaSCai Huoqing regulator_disable(adc->vref);
4761e23dcaaSCai Huoqing
4771e23dcaaSCai Huoqing return ret;
4781e23dcaaSCai Huoqing }
4791e23dcaaSCai Huoqing
4802583f5e8SJonathan Cameron static DEFINE_RUNTIME_DEV_PM_OPS(imx8qxp_adc_pm_ops,
4812583f5e8SJonathan Cameron imx8qxp_adc_runtime_suspend,
4822583f5e8SJonathan Cameron imx8qxp_adc_runtime_resume, NULL);
4831e23dcaaSCai Huoqing
4841e23dcaaSCai Huoqing static const struct of_device_id imx8qxp_adc_match[] = {
4851e23dcaaSCai Huoqing { .compatible = "nxp,imx8qxp-adc", },
4861e23dcaaSCai Huoqing { /* sentinel */ }
4871e23dcaaSCai Huoqing };
4881e23dcaaSCai Huoqing MODULE_DEVICE_TABLE(of, imx8qxp_adc_match);
4891e23dcaaSCai Huoqing
4901e23dcaaSCai Huoqing static struct platform_driver imx8qxp_adc_driver = {
4911e23dcaaSCai Huoqing .probe = imx8qxp_adc_probe,
4921e23dcaaSCai Huoqing .remove = imx8qxp_adc_remove,
4931e23dcaaSCai Huoqing .driver = {
4941e23dcaaSCai Huoqing .name = ADC_DRIVER_NAME,
4951e23dcaaSCai Huoqing .of_match_table = imx8qxp_adc_match,
4962583f5e8SJonathan Cameron .pm = pm_ptr(&imx8qxp_adc_pm_ops),
4971e23dcaaSCai Huoqing },
4981e23dcaaSCai Huoqing };
4991e23dcaaSCai Huoqing
5001e23dcaaSCai Huoqing module_platform_driver(imx8qxp_adc_driver);
5011e23dcaaSCai Huoqing
5021e23dcaaSCai Huoqing MODULE_DESCRIPTION("i.MX8QuadXPlus ADC driver");
5031e23dcaaSCai Huoqing MODULE_LICENSE("GPL v2");
504