xref: /openbmc/linux/drivers/iio/adc/imx8qxp-adc.c (revision 850101b3)
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