From: Denis Darwish <darwish.d.d@gmail.com>
To: linux-iio@vger.kernel.org
Subject: [PATCH v2] iio: basic ADC support for Freescale i.MX25
Date: Thu, 18 Apr 2013 14:45:16 +0400 [thread overview]
Message-ID: <516FCEBC.7080000@gmail.com> (raw)
From: Denis Darwish <darwish.d.d@gmail.com>
This patch adds basic support for Freescale i.MX25 ADC in the IIO Subsystem.
Signed-off-by: Denis Darwish <darwish.d.d@gmail.com>
---
I want to thank Lars-Peter Clausen for a detailed review.
Functionality tested with linux 3.8.0 and linux-next.
IIO_BUFFER and IIO_TRIGGER unsupported
Changes since v1:
+ end of conversion IRQ support
* cleanup debug info messages, vars etc.
* all definitions put into c file
* iio_chan_spec statically initialized
* get resources in modern way (clk, iomem etc)
arch/arm/mach-imx/clk-imx25.c | 2 +-
arch/arm/mach-imx/devices/Kconfig | 4 +
arch/arm/mach-imx/devices/Makefile | 1 +
arch/arm/mach-imx/devices/devices-common.h | 2 +
arch/arm/mach-imx/devices/platform-imx25-adc.c | 27 +
arch/arm/mach-imx/mx25.h | 2 +
drivers/iio/adc/Kconfig | 7 +
drivers/iio/adc/Makefile | 1 +
drivers/iio/adc/imx25_adc.c | 639 ++++++++++++++++++++++++
9 files changed, 684 insertions(+), 1 deletions(-)
create mode 100644 arch/arm/mach-imx/devices/platform-imx25-adc.c
create mode 100644 drivers/iio/adc/imx25_adc.c
diff --git a/arch/arm/mach-imx/clk-imx25.c b/arch/arm/mach-imx/clk-imx25.c
index 69858c7..febc054 100644
--- a/arch/arm/mach-imx/clk-imx25.c
+++ b/arch/arm/mach-imx/clk-imx25.c
@@ -274,7 +274,7 @@ int __init mx25_clocks_init(void)
clk_register_clkdev(clk[pwm1_ipg], "ipg", "mxc_pwm.3");
clk_register_clkdev(clk[per10], "per", "mxc_pwm.3");
clk_register_clkdev(clk[kpp_ipg], NULL, "imx-keypad");
- clk_register_clkdev(clk[tsc_ipg], NULL, "mx25-adc");
+ clk_register_clkdev(clk[tsc_ipg], NULL, "imx25-adc");
clk_register_clkdev(clk[i2c_ipg_per], NULL, "imx21-i2c.0");
clk_register_clkdev(clk[i2c_ipg_per], NULL, "imx21-i2c.1");
clk_register_clkdev(clk[i2c_ipg_per], NULL, "imx21-i2c.2");
diff --git a/arch/arm/mach-imx/devices/Kconfig b/arch/arm/mach-imx/devices/Kconfig
index 3dd2b1b..5096d56 100644
--- a/arch/arm/mach-imx/devices/Kconfig
+++ b/arch/arm/mach-imx/devices/Kconfig
@@ -16,6 +16,10 @@ config IMX_HAVE_PLATFORM_GPIO_KEYS
config IMX_HAVE_PLATFORM_IMX21_HCD
bool
+config IMX_HAVE_PLATFORM_IMX25_ADC
+ bool
+ default y if SOC_IMX25
+
config IMX_HAVE_PLATFORM_IMX27_CODA
bool
default y if SOC_IMX27
diff --git a/arch/arm/mach-imx/devices/Makefile b/arch/arm/mach-imx/devices/Makefile
index 67416fb..4450d19 100644
--- a/arch/arm/mach-imx/devices/Makefile
+++ b/arch/arm/mach-imx/devices/Makefile
@@ -6,6 +6,7 @@ obj-$(CONFIG_IMX_HAVE_PLATFORM_FSL_USB2_UDC) += platform-fsl-usb2-udc.o
obj-$(CONFIG_IMX_HAVE_PLATFORM_GPIO_KEYS) += platform-gpio_keys.o
obj-y += platform-gpio-mxc.o
obj-$(CONFIG_IMX_HAVE_PLATFORM_IMX21_HCD) += platform-imx21-hcd.o
+obj-$(CONFIG_IMX_HAVE_PLATFORM_IMX25_ADC) += platform-imx25-adc.o
obj-$(CONFIG_IMX_HAVE_PLATFORM_IMX27_CODA) += platform-imx27-coda.o
obj-$(CONFIG_IMX_HAVE_PLATFORM_IMX2_WDT) += platform-imx2-wdt.o
obj-$(CONFIG_IMX_HAVE_PLATFORM_IMXDI_RTC) += platform-imxdi_rtc.o
diff --git a/arch/arm/mach-imx/devices/devices-common.h b/arch/arm/mach-imx/devices/devices-common.h
index 453e20b..0781dcf 100644
--- a/arch/arm/mach-imx/devices/devices-common.h
+++ b/arch/arm/mach-imx/devices/devices-common.h
@@ -344,3 +344,5 @@ struct platform_device *imx_add_imx_dma(char *name, resource_size_t iobase,
int irq, int irq_err);
struct platform_device *imx_add_imx_sdma(char *name,
resource_size_t iobase, int irq, struct sdma_platform_data *pdata);
+
+struct platform_device *__init imx25_add_adc(void);
diff --git a/arch/arm/mach-imx/devices/platform-imx25-adc.c b/arch/arm/mach-imx/devices/platform-imx25-adc.c
new file mode 100644
index 0000000..628c890
--- /dev/null
+++ b/arch/arm/mach-imx/devices/platform-imx25-adc.c
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2013 Denis Darwish <darwish.d.d@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License version 2 as published by the
+ * Free Software Foundation.
+ */
+#include "../hardware.h"
+#include "devices-common.h"
+
+struct platform_device *__init imx25_add_adc(void)
+{
+ struct resource res[] = {
+ [0] = {
+ .start = MX25_TSC_BASE_ADDR,
+ .end = MX25_TSC_BASE_ADDR + SZ_16K - 1,
+ .flags = IORESOURCE_MEM,
+ },
+ [1] = {
+ .start = MX25_INT_TSC,
+ .end = MX25_INT_TSC,
+ .flags = IORESOURCE_IRQ,
+ },
+ };
+ return imx_add_platform_device("imx25-adc", -1,
+ res, ARRAY_SIZE(res), NULL, 0);
+}
diff --git a/arch/arm/mach-imx/mx25.h b/arch/arm/mach-imx/mx25.h
index ec46640..e3a3f33 100644
--- a/arch/arm/mach-imx/mx25.h
+++ b/arch/arm/mach-imx/mx25.h
@@ -37,6 +37,7 @@
#define MX25_CSPI3_BASE_ADDR 0x50004000
#define MX25_CSPI2_BASE_ADDR 0x50010000
+#define MX25_TSC_BASE_ADDR 0x50030000
#define MX25_FEC_BASE_ADDR 0x50038000
#define MX25_SSI2_BASE_ADDR 0x50014000
#define MX25_SSI1_BASE_ADDR 0x50034000
@@ -96,6 +97,7 @@
#define MX25_INT_CAN1 (NR_IRQS_LEGACY + 43)
#define MX25_INT_CAN2 (NR_IRQS_LEGACY + 44)
#define MX25_INT_UART1 (NR_IRQS_LEGACY + 45)
+#define MX25_INT_TSC (NR_IRQS_LEGACY + 46)
#define MX25_INT_GPIO2 (NR_IRQS_LEGACY + 51)
#define MX25_INT_GPIO1 (NR_IRQS_LEGACY + 52)
#define MX25_INT_GPT1 (NR_IRQS_LEGACY + 54)
diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
index ab0767e6..d56c7e6 100644
--- a/drivers/iio/adc/Kconfig
+++ b/drivers/iio/adc/Kconfig
@@ -150,6 +150,13 @@ config TI_AM335X_ADC
Say yes here to build support for Texas Instruments ADC
driver which is also a MFD client.
+config IMX25_ADC
+ tristate "Freescale IMX25 ADC"
+ depends on ARCH_MXC
+ select SYSFS
+ help
+ Say yes here to build support for Freescale i.MX25 ADC built into these chips.
+
config VIPERBOARD_ADC
tristate "Viperboard ADC support"
depends on MFD_VIPERBOARD && USB
diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile
index 0a825be..b2b9d63 100644
--- a/drivers/iio/adc/Makefile
+++ b/drivers/iio/adc/Makefile
@@ -17,3 +17,4 @@ obj-$(CONFIG_MAX1363) += max1363.o
obj-$(CONFIG_TI_ADC081C) += ti-adc081c.o
obj-$(CONFIG_TI_AM335X_ADC) += ti_am335x_adc.o
obj-$(CONFIG_VIPERBOARD_ADC) += viperboard_adc.o
+obj-$(CONFIG_IMX25_ADC) += imx25_adc.o
diff --git a/drivers/iio/adc/imx25_adc.c b/drivers/iio/adc/imx25_adc.c
new file mode 100644
index 0000000..a0e3570
--- /dev/null
+++ b/drivers/iio/adc/imx25_adc.c
@@ -0,0 +1,639 @@
+/**
+ * Copyright (c) 2013 Denis Darwish
+ * Copyright 2009-2011 Freescale Semiconductor, Inc.
+ */
+
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/io.h>
+#include <linux/clk.h>
+#include <linux/err.h>
+#include <linux/interrupt.h>
+#include <linux/completion.h>
+#include <linux/regulator/consumer.h>
+
+#include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
+#include <linux/iio/events.h>
+#include <linux/iio/buffer.h>
+
+#define IMX_ADC_DATA_BITS (12)
+
+ /* TSC General Config Register */
+#define IMX_ADC_TGCR 0x000
+#define IMX_ADC_TGCR_IPG_CLK_EN (1 << 0)
+#define IMX_ADC_TGCR_TSC_RST (1 << 1)
+#define IMX_ADC_TGCR_FUNC_RST (1 << 2)
+#define IMX_ADC_TGCR_SLPC (1 << 4)
+#define IMX_ADC_TGCR_STLC (1 << 5)
+#define IMX_ADC_TGCR_HSYNC_EN (1 << 6)
+#define IMX_ADC_TGCR_HSYNC_POL (1 << 7)
+#define IMX_ADC_TGCR_POWERMODE_SHIFT 8
+#define IMX_ADC_TGCR_POWER_OFF (0x0 << IMX_ADC_TGCR_POWERMODE_SHIFT)
+#define IMX_ADC_TGCR_POWER_SAVE (0x1 << IMX_ADC_TGCR_POWERMODE_SHIFT)
+#define IMX_ADC_TGCR_POWER_ON (0x3 << IMX_ADC_TGCR_POWERMODE_SHIFT)
+#define IMX_ADC_TGCR_POWER_MASK (0x3 << IMX_ADC_TGCR_POWERMODE_SHIFT)
+#define IMX_ADC_TGCR_INTREFEN (1 << 10)
+#define IMX_ADC_TGCR_ADCCLKCFG_SHIFT 16
+#define IMX_ADC_TGCR_ADCCLKCFG_MASK (0x1F << IMX_ADC_TGCR_ADCCLKCFG_SHIFT)
+#define IMX_ADC_TGCR_PD_EN (1 << 23)
+#define IMX_ADC_TGCR_PDB_EN (1 << 24)
+#define IMX_ADC_TGCR_PDBTIME_SHIFT 25
+#define IMX_ADC_TGCR_PDBTIME128 (0x3f << IMX_ADC_TGCR_PDBTIME_SHIFT)
+#define IMX_ADC_TGCR_PDBTIME_MASK (0x7f << IMX_ADC_TGCR_PDBTIME_SHIFT)
+
+/* TSC General Status Register */
+#define IMX_ADC_TGSR 0x004
+#define IMX_ADC_TCQ_INT (1 << 0)
+#define IMX_ADC_GCQ_INT (1 << 1)
+#define IMX_ADC_SLP_INT (1 << 2)
+#define IMX_ADC_TCQ_DMA (1 << 16)
+#define IMX_ADC_GCQ_DMA (1 << 17)
+
+/* TSC IDLE Config Register */
+#define IMX_ADC_TICR 0x008
+
+/* TouchScreen Convert Queue FIFO Register */
+/* #define TCQFIFO 0x400 */
+/* TouchScreen Convert Queue Control Register */
+#define IMX_ADC_TCQCR 0x404
+#define IMX_ADC_CQCR_QSM_SHIFT 0
+#define IMX_ADC_CQCR_QSM_STOP (0x0 << IMX_ADC_CQCR_QSM_SHIFT)
+#define IMX_ADC_CQCR_QSM_PEN (0x1 << IMX_ADC_CQCR_QSM_SHIFT)
+#define IMX_ADC_CQCR_QSM_FQS (0x2 << IMX_ADC_CQCR_QSM_SHIFT)
+#define IMX_ADC_CQCR_QSM_FQS_PEN (0x3 << IMX_ADC_CQCR_QSM_SHIFT)
+#define IMX_ADC_CQCR_QSM_MASK (0x3 << IMX_ADC_CQCR_QSM_SHIFT)
+#define IMX_ADC_CQCR_FQS (1 << 2)
+#define IMX_ADC_CQCR_RPT (1 << 3)
+#define IMX_ADC_CQCR_LAST_ITEM_ID_SHIFT 4
+#define IMX_ADC_CQCR_LAST_ITEM_ID_MASK \
+ (0xf << IMX_ADC_CQCR_LAST_ITEM_ID_SHIFT)
+#define IMX_ADC_CQCR_FIFOWATERMARK_SHIFT 8
+#define IMX_ADC_CQCR_FIFOWATERMARK_MASK \
+ (0xf << IMX_ADC_CQCR_FIFOWATERMARK_SHIFT)
+#define IMX_ADC_CQCR_REPEATWAIT_SHIFT 12
+#define IMX_ADC_CQCR_REPEATWAIT_MASK \
+ (0xf << IMX_ADC_CQCR_REPEATWAIT_SHIFT)
+#define IMX_ADC_CQCR_QRST (1 << 16)
+#define IMX_ADC_CQCR_FRST (1 << 17)
+#define IMX_ADC_CQCR_PD_MSK (1 << 18)
+#define IMX_ADC_CQCR_PD_CFG (1 << 19)
+
+/* TouchScreen Convert Queue Status Register */
+#define IMX_ADC_TCQSR 0x408
+#define IMX_ADC_CQSR_PD (1 << 0)
+#define IMX_ADC_CQSR_EOQ (1 << 1)
+#define IMX_ADC_CQSR_FOR (1 << 4)
+#define IMX_ADC_CQSR_FUR (1 << 5)
+#define IMX_ADC_CQSR_FER (1 << 6)
+#define IMX_ADC_CQSR_EMPT (1 << 13)
+#define IMX_ADC_CQSR_FULL (1 << 14)
+#define IMX_ADC_CQSR_FDRY (1 << 15)
+
+/* TouchScreen Convert Config 0-7 */
+#define IMX_ADC_TCC0 0x440
+#define IMX_ADC_TCC1 0x444
+#define IMX_ADC_TCC2 0x448
+#define IMX_ADC_TCC3 0x44c
+#define IMX_ADC_TCC4 0x450
+#define IMX_ADC_TCC5 0x454
+#define IMX_ADC_TCC6 0x458
+#define IMX_ADC_TCC7 0x45c
+#define IMX_ADC_CC_PEN_IACK (1 << 1)
+#define IMX_ADC_CC_SEL_REFN_SHIFT 2
+#define IMX_ADC_CC_SEL_REFN_YNLR (0x1 << IMX_ADC_CC_SEL_REFN_SHIFT)
+#define IMX_ADC_CC_SEL_REFN_AGND (0x2 << IMX_ADC_CC_SEL_REFN_SHIFT)
+#define IMX_ADC_CC_SEL_REFN_MASK (0x3 << IMX_ADC_CC_SEL_REFN_SHIFT)
+#define IMX_ADC_CC_SELIN_SHIFT 4
+#define IMX_ADC_CC_SELIN_XPUL (0x0 << IMX_ADC_CC_SELIN_SHIFT)
+#define IMX_ADC_CC_SELIN_YPLL (0x1 << IMX_ADC_CC_SELIN_SHIFT)
+#define IMX_ADC_CC_SELIN_XNUR (0x2 << IMX_ADC_CC_SELIN_SHIFT)
+#define IMX_ADC_CC_SELIN_YNLR (0x3 << IMX_ADC_CC_SELIN_SHIFT)
+#define IMX_ADC_CC_SELIN_WIPER (0x4 << IMX_ADC_CC_SELIN_SHIFT)
+#define IMX_ADC_CC_SELIN_INAUX0 (0x5 << IMX_ADC_CC_SELIN_SHIFT)
+#define IMX_ADC_CC_SELIN_INAUX1 (0x6 << IMX_ADC_CC_SELIN_SHIFT)
+#define IMX_ADC_CC_SELIN_INAUX2 (0x7 << IMX_ADC_CC_SELIN_SHIFT)
+#define IMX_ADC_CC_SELIN_MASK (0x7 << IMX_ADC_CC_SELIN_SHIFT)
+#define IMX_ADC_CC_SELREFP_SHIFT 7
+#define IMX_ADC_CC_SELREFP_YPLL (0x0 << IMX_ADC_CC_SELREFP_SHIFT)
+#define IMX_ADC_CC_SELREFP_XPUL (0x1 << IMX_ADC_CC_SELREFP_SHIFT)
+#define IMX_ADC_CC_SELREFP_EXT (0x2 << IMX_ADC_CC_SELREFP_SHIFT)
+#define IMX_ADC_CC_SELREFP_INT (0x3 << IMX_ADC_CC_SELREFP_SHIFT)
+#define IMX_ADC_CC_SELREFP_MASK (0x3 << IMX_ADC_CC_SELREFP_SHIFT)
+#define IMX_ADC_CC_XPULSW (1 << 9)
+#define IMX_ADC_CC_XNURSW_SHIFT 10
+#define IMX_ADC_CC_XNURSW_HIGH (0x0 << IMX_ADC_CC_XNURSW_SHIFT)
+#define IMX_ADC_CC_XNURSW_OFF (0x1 << IMX_ADC_CC_XNURSW_SHIFT)
+#define IMX_ADC_CC_XNURSW_LOW (0x3 << IMX_ADC_CC_XNURSW_SHIFT)
+#define IMX_ADC_CC_XNURSW_MASK (0x3 << IMX_ADC_CC_XNURSW_SHIFT)
+#define IMX_ADC_CC_YPLLSW_SHIFT 12
+#define IMX_ADC_CC_YPLLSW_MASK (0x3 << IMX_ADC_CC_YPLLSW_SHIFT)
+#define IMX_ADC_CC_YNLRSW (1 << 14)
+#define IMX_ADC_CC_WIPERSW (1 << 15)
+#define IMX_ADC_CC_NOS_SHIFT 16
+#define IMX_ADC_CC_YPLLSW_HIGH (0x0 << IMX_ADC_CC_NOS_SHIFT)
+#define IMX_ADC_CC_YPLLSW_OFF (0x1 << IMX_ADC_CC_NOS_SHIFT)
+#define IMX_ADC_CC_YPLLSW_LOW (0x3 << IMX_ADC_CC_NOS_SHIFT)
+#define IMX_ADC_CC_NOS_MASK (0xf << IMX_ADC_CC_NOS_SHIFT)
+#define IMX_ADC_CC_IGS (1 << 20)
+#define IMX_ADC_CC_SETTLING_TIME_SHIFT 24
+#define IMX_ADC_CC_SETTLING_TIME_MASK (0xff << IMX_ADC_CC_SETTLING_TIME_SHIFT)
+
+#define IMX_ADC_TSC_GENERAL_ADC_GCC0 0x17dc
+#define IMX_ADC_TSC_GENERAL_ADC_GCC1 0x17ec
+#define IMX_ADC_TSC_GENERAL_ADC_GCC2 0x17fc
+
+/* GeneralADC Convert Queue FIFO Register */
+#define IMX_ADC_GCQFIFO 0x800
+#define IMX_ADC_GCQFIFO_ADCOUT_SHIFT 4
+#define IMX_ADC_GCQFIFO_ADCOUT_MASK (0xfff << IMX_ADC_GCQFIFO_ADCOUT_SHIFT)
+/* GeneralADC Convert Queue Control Register */
+#define IMX_ADC_GCQCR 0x804
+/* GeneralADC Convert Queue Status Register */
+#define IMX_ADC_GCQSR 0x808
+/* GeneralADC Convert Queue Mask Register */
+#define IMX_ADC_GCQMR 0x80c
+#define IMX_ADC_GCQMR_PD_IRQ_MSK (1 << 0)
+#define IMX_ADC_GCQMR_EOQ_IRQ_MSK (1 << 1)
+#define IMX_ADC_GCQMR_FOR_IRQ_MSK (1 << 4)
+#define IMX_ADC_GCQMR_FUR_IRQ_MSK (1 << 5)
+#define IMX_ADC_GCQMR_FER_IRQ_MSK (1 << 6)
+#define IMX_ADC_GCQMR_PD_DMA_MSK (1 << 16)
+#define IMX_ADC_GCQMR_EOQ_DMA_MSK (1 << 17)
+#define IMX_ADC_GCQMR_FOR_DMA_MSK (1 << 20)
+#define IMX_ADC_GCQMR_FUR_DMA_MSK (1 << 21)
+#define IMX_ADC_GCQMR_FER_DMA_MSK (1 << 22)
+#define IMX_ADC_GCQMR_FDRY_DMA_MSK (1 << 31)
+
+/* GeneralADC Convert Queue ITEM 7~0 */
+#define IMX_ADC_GCQ_ITEM_7_0 0x820
+/* GeneralADC Convert Queue ITEM 15~8 */
+#define IMX_ADC_GCQ_ITEM_15_8 0x824
+
+#define IMX_ADC_GCQ_ITEM7_SHIFT 28
+#define IMX_ADC_GCQ_ITEM6_SHIFT 24
+#define IMX_ADC_GCQ_ITEM5_SHIFT 20
+#define IMX_ADC_GCQ_ITEM4_SHIFT 16
+#define IMX_ADC_GCQ_ITEM3_SHIFT 12
+#define IMX_ADC_GCQ_ITEM2_SHIFT 8
+#define IMX_ADC_GCQ_ITEM1_SHIFT 4
+#define IMX_ADC_GCQ_ITEM0_SHIFT 0
+
+#define IMX_ADC_GCQ_ITEM_GCC0 0x0
+#define IMX_ADC_GCQ_ITEM_GCC1 0x1
+#define IMX_ADC_GCQ_ITEM_GCC2 0x2
+#define IMX_ADC_GCQ_ITEM_GCC3 0x3
+
+/* GeneralADC Convert Config 0-7 */
+#define IMX_ADC_GCC0 0x840
+#define IMX_ADC_GCC1 0x844
+#define IMX_ADC_GCC2 0x848
+#define IMX_ADC_GCC3 0x84c
+#define IMX_ADC_GCC4 0x850
+#define IMX_ADC_GCC5 0x854
+#define IMX_ADC_GCC6 0x858
+#define IMX_ADC_GCC7 0x85c
+
+struct imx_adc_state {
+ struct regulator *reg;
+ struct clk *adc_clk;
+ u16 value;
+ struct mutex lock;
+ struct completion completion;
+ int irq;
+ void __iomem *reg_base;
+ u32 vref_mv;
+};
+
+static int adc_clk_enable(struct imx_adc_state *st)
+{
+ unsigned long reg;
+ int ret;
+
+ ret = clk_prepare_enable(st->adc_clk);
+ reg = readl_relaxed(st->reg_base + IMX_ADC_TGCR);
+ reg |= IMX_ADC_TGCR_IPG_CLK_EN;
+ writel_relaxed(reg, st->reg_base + IMX_ADC_TGCR);
+ return ret;
+}
+
+void adc_clk_disable(struct imx_adc_state *st)
+{
+ unsigned long reg;
+
+ reg = readl_relaxed(st->reg_base + IMX_ADC_TGCR);
+ reg &= ~IMX_ADC_TGCR_IPG_CLK_EN;
+ writel_relaxed(reg, st->reg_base + IMX_ADC_TGCR);
+ clk_disable_unprepare(st->adc_clk);
+}
+
+void tsc_self_reset(struct imx_adc_state *st)
+{
+ unsigned long reg;
+
+ reg = readl_relaxed(st->reg_base + IMX_ADC_TGCR);
+ reg |= IMX_ADC_TGCR_TSC_RST;
+ writel_relaxed(reg, st->reg_base + IMX_ADC_TGCR);
+
+ while (readl_relaxed(st->reg_base + IMX_ADC_TGCR) &
+ IMX_ADC_TGCR_TSC_RST)
+ continue;
+}
+
+/* Internal reference */
+void tsc_intref_enable(struct imx_adc_state *st)
+{
+ unsigned long reg;
+
+ reg = readl_relaxed(st->reg_base + IMX_ADC_TGCR);
+ reg |= IMX_ADC_TGCR_INTREFEN;
+ writel_relaxed(reg, st->reg_base + IMX_ADC_TGCR);
+}
+
+/* Set power mode */
+void adc_set_power_mode(struct imx_adc_state *st)
+{
+ unsigned long reg;
+
+ reg = readl_relaxed(st->reg_base + IMX_ADC_TGCR) &
+ ~IMX_ADC_TGCR_POWER_MASK;
+ reg |= IMX_ADC_TGCR_POWER_ON;
+ writel_relaxed(reg, st->reg_base + IMX_ADC_TGCR);
+}
+
+/* Set ADC clock configuration */
+void adc_set_clk(struct imx_adc_state *st)
+{
+ unsigned long reg;
+
+ reg = readl_relaxed(st->reg_base + IMX_ADC_TGCR) &
+ ~IMX_ADC_TGCR_ADCCLKCFG_MASK;
+ /* ADC clock = ipg_clk / (2*div+2) */
+ reg |= 31 << IMX_ADC_TGCR_ADCCLKCFG_SHIFT;
+ writel_relaxed(reg, st->reg_base + IMX_ADC_TGCR);
+}
+
+void adc_set_queue(struct imx_adc_state *st)
+{
+ unsigned long reg;
+
+ reg = (IMX_ADC_GCQ_ITEM_GCC0 << IMX_ADC_GCQ_ITEM0_SHIFT)
+ | (IMX_ADC_GCQ_ITEM_GCC1 << IMX_ADC_GCQ_ITEM1_SHIFT)
+ | (IMX_ADC_GCQ_ITEM_GCC2 << IMX_ADC_GCQ_ITEM2_SHIFT);
+ writel_relaxed(reg, st->reg_base + IMX_ADC_GCQ_ITEM_7_0);
+
+ reg = IMX_ADC_TSC_GENERAL_ADC_GCC0;
+ reg |= (0 << IMX_ADC_CC_NOS_SHIFT) |
+ (16 << IMX_ADC_CC_SETTLING_TIME_SHIFT);
+ writel_relaxed(reg, st->reg_base + IMX_ADC_GCC0);
+ reg = IMX_ADC_TSC_GENERAL_ADC_GCC1;
+ reg |= (0 << IMX_ADC_CC_NOS_SHIFT) |
+ (16 << IMX_ADC_CC_SETTLING_TIME_SHIFT);
+ writel_relaxed(reg, st->reg_base + IMX_ADC_GCC1);
+ reg = IMX_ADC_TSC_GENERAL_ADC_GCC2;
+ reg |= (0 << IMX_ADC_CC_NOS_SHIFT) |
+ (16 << IMX_ADC_CC_SETTLING_TIME_SHIFT);
+ writel_relaxed(reg, st->reg_base + IMX_ADC_GCC2);
+
+ reg = readl_relaxed(st->reg_base + IMX_ADC_GCQCR) &
+ ~IMX_ADC_CQCR_LAST_ITEM_ID_MASK;
+ reg |= 0 << IMX_ADC_CQCR_LAST_ITEM_ID_SHIFT;
+ writel_relaxed(reg, st->reg_base + IMX_ADC_GCQCR);
+
+ reg = readl_relaxed(st->reg_base + IMX_ADC_GCQCR) &
+ ~IMX_ADC_CQCR_QSM_MASK;
+ reg |= IMX_ADC_CQCR_QSM_FQS;
+ writel_relaxed(reg, st->reg_base + IMX_ADC_GCQCR);
+
+ reg = readl_relaxed(st->reg_base + IMX_ADC_GCQCR) &
+ ~IMX_ADC_CQCR_FIFOWATERMARK_MASK;
+ reg |= (0x0 << IMX_ADC_CQCR_FIFOWATERMARK_SHIFT);
+ writel_relaxed(reg, st->reg_base + IMX_ADC_GCQCR);
+}
+
+void imx_adc_set_chanel(struct imx_adc_state *st, u8 channel)
+{
+ unsigned long reg;
+
+ reg = readl_relaxed(st->reg_base + IMX_ADC_GCQ_ITEM_7_0);
+ switch (channel) {
+ case 0:
+ reg = IMX_ADC_GCQ_ITEM_GCC0 <<
+ IMX_ADC_GCQ_ITEM0_SHIFT;
+ break;
+ case 1:
+ reg = IMX_ADC_GCQ_ITEM_GCC1 <<
+ IMX_ADC_GCQ_ITEM0_SHIFT;
+ break;
+ case 2:
+ reg = IMX_ADC_GCQ_ITEM_GCC2 <<
+ IMX_ADC_GCQ_ITEM0_SHIFT;
+ break;
+ default:
+ break;
+ }
+ writel_relaxed(reg, st->reg_base + IMX_ADC_GCQ_ITEM_7_0);
+
+}
+
+void imx_adc_read_general(unsigned short *result, struct imx_adc_state *st)
+{
+ unsigned long reg;
+
+ reg = readl_relaxed(st->reg_base + IMX_ADC_GCQCR);
+ reg |= IMX_ADC_CQCR_FQS;
+ writel_relaxed(reg, st->reg_base + IMX_ADC_GCQCR);
+
+ while (!(readl_relaxed(st->reg_base + IMX_ADC_GCQSR) &
+ IMX_ADC_CQSR_EOQ))
+ continue;
+ reg = readl_relaxed(st->reg_base + IMX_ADC_GCQCR);
+ reg &= ~IMX_ADC_CQCR_FQS;
+ writel_relaxed(reg, st->reg_base + IMX_ADC_GCQCR);
+ reg = readl_relaxed(st->reg_base + IMX_ADC_GCQSR);
+ reg |= IMX_ADC_CQSR_EOQ;
+ writel_relaxed(reg, st->reg_base + IMX_ADC_GCQSR);
+
+ while (!(readl_relaxed(st->reg_base + IMX_ADC_GCQSR) &
+ IMX_ADC_CQSR_EMPT)) {
+ *result = readl_relaxed(st->reg_base + IMX_ADC_GCQFIFO) >>
+ IMX_ADC_GCQFIFO_ADCOUT_SHIFT;
+ }
+
+}
+
+static irqreturn_t imx_adc_handle_irq(int irq, void *private)
+{
+ struct iio_dev *iodev = private;
+ struct imx_adc_state *st = iio_priv(iodev);
+ unsigned long reg;
+
+ reg = readl_relaxed(st->reg_base + IMX_ADC_TGSR);
+ if (!(reg & IMX_ADC_GCQ_INT))
+ return IRQ_HANDLED;
+
+ /* GCQ interrupt negated */
+ reg |= IMX_ADC_GCQ_INT;
+ writel_relaxed(reg, st->reg_base + IMX_ADC_TGSR);
+
+ /* mask end-of-queue IRQ */
+ reg = readl_relaxed(st->reg_base + IMX_ADC_GCQMR);
+ reg |= IMX_ADC_GCQMR_EOQ_IRQ_MSK;
+ writel_relaxed(reg, st->reg_base + IMX_ADC_GCQMR);
+
+ /* FQS does not start the conversion queue */
+ reg = readl_relaxed(st->reg_base + IMX_ADC_GCQCR);
+ reg &= ~IMX_ADC_CQCR_FQS;
+ writel_relaxed(reg, st->reg_base + IMX_ADC_GCQCR);
+
+ /* Convert queue is completed */
+ reg = readl_relaxed(st->reg_base + IMX_ADC_GCQSR);
+ reg |= IMX_ADC_CQSR_EOQ;
+ writel_relaxed(reg, st->reg_base + IMX_ADC_GCQSR);
+
+ complete(&st->completion);
+
+ return IRQ_HANDLED;
+}
+
+#define IMX_ADC_CHAN(idx, chan_type) { \
+ .type = (chan_type), \
+ .indexed = 1, \
+ .scan_index = (idx), \
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \
+ BIT(IIO_CHAN_INFO_SCALE), \
+ .channel = (idx), \
+ .scan_type = { \
+ .sign = 'u', \
+ .realbits = 12, \
+ .storagebits = 16, \
+ }, \
+}
+
+static const struct iio_chan_spec imx_adc_iio_channels[] = {
+ IMX_ADC_CHAN(0, IIO_VOLTAGE),
+ IMX_ADC_CHAN(1, IIO_VOLTAGE),
+ IMX_ADC_CHAN(2, IIO_VOLTAGE),
+};
+
+static int imx_adc_read_raw(struct iio_dev *idev,
+ struct iio_chan_spec const *chan,
+ int *val, int *val2, long mask)
+{
+ struct imx_adc_state *st = iio_priv(idev);
+ int ret;
+ unsigned long reg;
+
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ ret = mutex_trylock(&st->lock);
+ if (!ret)
+ return -EBUSY;
+
+ INIT_COMPLETION(st->completion);
+
+ imx_adc_set_chanel(st, chan->channel);
+
+ /* Enable the IRQ, unmask end-of-queue IRQ */
+ reg = readl_relaxed(st->reg_base + IMX_ADC_GCQMR);
+ reg &= ~IMX_ADC_GCQMR_EOQ_IRQ_MSK;
+ writel_relaxed(reg, st->reg_base + IMX_ADC_GCQMR);
+
+ /* Start sampling the channel. */
+ reg = readl_relaxed(st->reg_base + IMX_ADC_GCQCR);
+ reg |= IMX_ADC_CQCR_FQS;
+ writel_relaxed(reg, st->reg_base + IMX_ADC_GCQCR);
+
+ ret = wait_for_completion_killable_timeout(&st->completion, HZ);
+ if (!ret)
+ ret = -ETIMEDOUT;
+ if (ret > 0) {
+ while (!(readl_relaxed(st->reg_base + IMX_ADC_GCQSR) &
+ IMX_ADC_CQSR_EMPT)) {
+ st->value = readl_relaxed(st->reg_base +
+ IMX_ADC_GCQFIFO) >>
+ IMX_ADC_GCQFIFO_ADCOUT_SHIFT;
+ }
+ *val = st->value;
+ ret = IIO_VAL_INT;
+ }
+ /* GCQ interrupt negated */
+ reg |= IMX_ADC_GCQ_INT;
+ writel_relaxed(reg, st->reg_base + IMX_ADC_TGSR);
+
+ /* mask end-of-queue IRQ */
+ reg = readl_relaxed(st->reg_base + IMX_ADC_GCQMR);
+ reg |= IMX_ADC_GCQMR_EOQ_IRQ_MSK;
+ writel_relaxed(reg, st->reg_base + IMX_ADC_GCQMR);
+
+ /* FQS does not start the conversion queue */
+ reg = readl_relaxed(st->reg_base + IMX_ADC_GCQCR);
+ reg &= ~IMX_ADC_CQCR_FQS;
+ writel_relaxed(reg, st->reg_base + IMX_ADC_GCQCR);
+
+ /* Convert queue is completed */
+ reg = readl_relaxed(st->reg_base + IMX_ADC_GCQSR);
+ reg |= IMX_ADC_CQSR_EOQ;
+ writel_relaxed(reg, st->reg_base + IMX_ADC_GCQSR);
+
+ mutex_unlock(&st->lock);
+ return IIO_VAL_INT;
+
+ case IIO_CHAN_INFO_SCALE:
+ *val = st->vref_mv;
+ *val2 = IMX_ADC_DATA_BITS;
+ return IIO_VAL_FRACTIONAL_LOG2;
+ default:
+ break;
+ }
+ return -EINVAL;
+}
+
+static const struct iio_info imx_adc_info = {
+ .driver_module = THIS_MODULE,
+ .read_raw = &imx_adc_read_raw,
+};
+
+static int imx_adc_probe(struct platform_device *pdev)
+{
+ struct imx_adc_state *st;
+ struct iio_dev *iodev;
+ int ret = -ENODEV;
+ struct resource *res;
+
+ iodev = iio_device_alloc(sizeof(struct imx_adc_state));
+ if (iodev == NULL) {
+ dev_err(&pdev->dev, "failed allocating iio device\n");
+ ret = -ENOMEM;
+ goto error_ret;
+ }
+
+ st = iio_priv(iodev);
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ st->reg_base = devm_request_and_ioremap(&pdev->dev, res);
+ if (IS_ERR(st->reg_base)) {
+ ret = PTR_ERR(st->reg_base);
+ goto error_free_device;
+ }
+
+ /* Reset */
+ tsc_self_reset(st);
+
+ st->reg = regulator_get(&pdev->dev, "ext-vref");
+ if (!IS_ERR_OR_NULL(st->reg)) {
+ ret = regulator_enable(st->reg);
+ if (ret)
+ goto error_put_reg;
+ st->vref_mv = regulator_get_voltage(st->reg);
+ } else {
+ /* Use internal reference */
+ st->vref_mv = 2500; /* internal reference = 2.5V */
+ tsc_intref_enable(st);
+ }
+
+ st->irq = platform_get_irq(pdev, 0);
+ if (st->irq < 0) {
+ dev_err(&pdev->dev, "No IRQ ID is designated\n");
+ ret = -EINVAL;
+ goto error_disable_reg;
+ }
+
+ ret = devm_request_irq(&pdev->dev, st->irq,
+ imx_adc_handle_irq, 0,
+ pdev->dev.driver->name, iodev);
+ if (ret)
+ goto error_disable_reg;
+
+ st->adc_clk = devm_clk_get(&pdev->dev, NULL);
+ if (IS_ERR(st->adc_clk)) {
+ dev_err(&pdev->dev, "Failed to get the clock.\n");
+ ret = PTR_ERR(st->adc_clk);
+ goto error_free_clk;
+ }
+
+ ret = adc_clk_enable(st);
+ if (ret) {
+ dev_err(&pdev->dev,
+ "Could not prepare or enable the clock.\n");
+ goto error_free_clk;
+ }
+
+ adc_set_clk(st);
+
+ /* Set power mode */
+ adc_set_power_mode(st);
+
+ /* Set queue */
+ adc_set_queue(st);
+
+ platform_set_drvdata(pdev, iodev);
+
+ iodev->name = dev_name(&pdev->dev);
+ iodev->dev.parent = &pdev->dev;
+ iodev->info = &imx_adc_info;
+ iodev->modes = INDIO_DIRECT_MODE;
+
+
+ iodev->channels = imx_adc_iio_channels;
+ iodev->num_channels = ARRAY_SIZE(imx_adc_iio_channels);
+
+ init_completion(&st->completion);
+ mutex_init(&st->lock);
+
+ ret = iio_device_register(iodev);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "Couldn't register the device.\n");
+ goto error_dev;
+ }
+
+ return 0;
+
+error_dev:
+
+error_free_clk:
+ adc_clk_disable(st);
+error_disable_reg:
+ if (!IS_ERR_OR_NULL(st->reg))
+ regulator_disable(st->reg);
+error_put_reg:
+ if (!IS_ERR_OR_NULL(st->reg))
+ regulator_put(st->reg);
+error_free_device:
+ iio_device_free(iodev);
+error_ret:
+ return ret;
+}
+
+static int imx_adc_remove(struct platform_device *pdev)
+{
+ struct iio_dev *iodev = platform_get_drvdata(pdev);
+ struct imx_adc_state *st = iio_priv(iodev);
+
+ iio_device_unregister(iodev);
+
+ adc_clk_disable(st);
+ clk_put(st->adc_clk);
+
+ iio_device_free(iodev);
+
+ return 0;
+}
+
+
+static struct platform_driver imx_adc_driver = {
+ .probe = imx_adc_probe,
+ .remove = imx_adc_remove,
+ .driver = {
+ .name = "imx25-adc",
+ },
+};
+
+module_platform_driver(imx_adc_driver);
+
+MODULE_AUTHOR("Denis Darwish <darwish.d.d@gmail.com>");
+MODULE_DESCRIPTION("Freescale i.MX25 ADC Driver");
+MODULE_LICENSE("GPL");
-- 1.7.3.4
next reply other threads:[~2013-04-18 10:48 UTC|newest]
Thread overview: 5+ messages / expand[flat|nested] mbox.gz Atom feed top
2013-04-18 10:45 Denis Darwish [this message]
2013-04-20 17:14 ` [PATCH v2] iio: basic ADC support for Freescale i.MX25 Lars-Peter Clausen
2013-04-21 6:37 ` Sascha Hauer
2013-04-21 7:18 ` Shawn Guo
2013-04-21 7:04 ` Sascha Hauer
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=516FCEBC.7080000@gmail.com \
--to=darwish.d.d@gmail.com \
--cc=linux-iio@vger.kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox