From: Guoying Zhang <Guoying.Zhang@xxxxxxx> SiRFSoC internal ADC provides touchscreen single or dual touch channels, and provides several auxiliary channels to measure temperature, battery and so on. Signed-off-by: Guoying Zhang <Guoying.Zhang@xxxxxxx> Signed-off-by: Barry Song <Baohua.Song@xxxxxxx> --- drivers/iio/adc/Kconfig | 9 + drivers/iio/adc/Makefile | 1 + drivers/iio/adc/sirfsoc_adc.c | 722 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 732 insertions(+) create mode 100644 drivers/iio/adc/sirfsoc_adc.c diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig index a80d236..8ea6e4b 100644 --- a/drivers/iio/adc/Kconfig +++ b/drivers/iio/adc/Kconfig @@ -187,6 +187,15 @@ config NAU7802 To compile this driver as a module, choose M here: the module will be called nau7802. +config SIRFSOC_ADC + tristate "SiRFSoC ADC" + depends on ARCH_SIRF + help + If you say yes here you get support for CSR SiRFSoC internal ADC. + + This driver can also be built as a module. If so, the module will be + called sirfsoc_adc. + config TI_ADC081C tristate "Texas Instruments ADC081C021/027" depends on I2C diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile index 9d60f2d..2912e58 100644 --- a/drivers/iio/adc/Makefile +++ b/drivers/iio/adc/Makefile @@ -20,6 +20,7 @@ obj-$(CONFIG_MCP320X) += mcp320x.o obj-$(CONFIG_MCP3422) += mcp3422.o obj-$(CONFIG_MEN_Z188_ADC) += men_z188_adc.o obj-$(CONFIG_NAU7802) += nau7802.o +obj-$(CONFIG_SIRFSOC_ADC) += sirfsoc_adc.o obj-$(CONFIG_TI_ADC081C) += ti-adc081c.o obj-$(CONFIG_TI_AM335X_ADC) += ti_am335x_adc.o obj-$(CONFIG_TWL4030_MADC) += twl4030-madc.o diff --git a/drivers/iio/adc/sirfsoc_adc.c b/drivers/iio/adc/sirfsoc_adc.c new file mode 100644 index 0000000..568ae1c --- /dev/null +++ b/drivers/iio/adc/sirfsoc_adc.c @@ -0,0 +1,722 @@ +/* +* ADC Driver for CSR SiRFprimaII/AtlasVI +* +* Copyright (c) 2014 Cambridge Silicon Radio Limited, a CSR plc group company. +* +* Licensed under GPLv2. +*/ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/clk.h> +#include <linux/io.h> +#include <linux/interrupt.h> +#include <linux/of.h> +#include <linux/pm.h> +#include <linux/platform_device.h> +#include <linux/of_platform.h> +#include <linux/reset.h> +#include <linux/rtc/sirfsoc_rtciobrg.h> + +#include <linux/iio/iio.h> +#include <linux/iio/machine.h> +#include <linux/iio/driver.h> + +#define DRIVER_NAME "sirfsoc_adc" + +#define PWR_WAKEEN_TSC_SHIFT 23 +#define PWR_WAKEEN_TS_SHIFT 5 +#define SIRFSOC_PWRC_TRIGGER_EN 0x8 +#define SIRFSOC_PWRC_BASE 0x3000 + +#define DATA_SHIFT_BITS 14 + +#define ADC_CONTROL1 0x00 +#define ADC_CONTROL2 0x04 +#define ADC_INTR 0x08 +#define ADC_COORD 0x0C +#define ADC_PRESSURE 0x10 +#define ADC_AUX1 0x14 +#define ADC_AUX2 0x18 +#define ADC_AUX3 0x1C +#define ADC_AUX4 0x20 +#define ADC_AUX5 0x24 +#define ADC_AUX6 0x28 +#define ADC_CB 0x2C /* Read Back calibration register */ +#define ADC_COORD2 0x30 +#define ADC_COORD3 0x34 +#define ADC_COORD4 0x38 +#define ADC_CONTROL3 0x3C + +/* CTRL1 defines */ +#define ADC_RESET_QUANT_EN BIT(24) +#define ADC_RST_B BIT(23) +#define ADC_RESOLUTION_12 BIT(22) +#define ADC_RBAT_DISABLE (0x0 << 21) +#define ADC_RBAT_ENABLE (0x1 << 21) +#define ADC_EXTCM_MASK (0x3 << 19) +#define ADC_EXTCM(x) (((x) & 0x3) << 19) +#define ADC_SGAIN_MASK (0x7 << 16) +#define ADC_SGAIN(x) (((x) & 0x7) << 16) +#define ADC_POLL BIT(15) +#define ADC_SEL_MASK (0xF << 11) +#define ADC_SEL(x) (((x) & 0xF) << 11) +#define ADC_FREQ_6K (0x0 << 8) +#define ADC_FREQ_13K (0x1 << 8) +#define ADC_DEL_SET_MASK (0xF << 4) +#define ADC_DEL_SET(x) (((x) & 0xF) << 4) +#define ADC_TP_TIME_MASK (0x7) +#define ADC_TP_TIME(x) (((x) & 0x7) << 0) + +/* CTRL2 defines */ +#define ADC_PRP_MASK (3 << 14) +/* Pen detector off, digitizer off */ +#define ADC_PRP_MODE0 (0 << 14) +/* Pen detector on, digitizer off, digitizer wakes up on pen detect */ +#define ADC_PRP_MODE1 BIT(14) +/* Pen detector on, digitizer off, no wake up on pen detect */ +#define ADC_PRP_MODE2 (2 << 14) +/* Pen detector on, digitizer on */ +#define ADC_PRP_MODE3 (3 << 14) +#define ADC_RTOUCH_MASK (0x3 << 12) +#define ADC_RTOUCH(x) (((x) & 0x3) << 12) +#define ADC_DEL_AUTO_MASK (0xF << 8) +#define ADC_DEL_AUTO(x) (((x) & 0xF) << 8) +#define ADC_DEL_PRE(x) (((x) & 0xF) << 4) +#define ADC_DEL_DIS(x) (((x) & 0xF) << 0) + +/* INTR register defines */ +#define PEN_INTR_EN BIT(5) +#define DATA_INTR_EN BIT(4) +#define PEN_INTR BIT(1) +#define DATA_INTR BIT(0) + +/* DATA register defines */ +#define PEN_DOWN BIT(31) +#define DATA_YVALID BIT(30) +#define DATA_XVALID BIT(29) +#define DATA_Z2VALID BIT(30) +#define DATA_Z1VALID BIT(29) +#define DATA_AUXVALID BIT(30) +#define DATA_CB_VALID BIT(30) +#define DATA_Y2VALID BIT(30) +#define DATA_X2VALID BIT(29) +#define DATA_6VALID BIT(30) +#define DATA_5VALID BIT(29) +#define DATA_8VALID BIT(30) +#define DATA_7VALID BIT(29) + +#define ADC_DATA_MASK(x) (0x3FFF << (x)) +#define DATA_XMASK ADC_DATA_MASK(0) +#define DATA_YMASK ADC_DATA_MASK(DATA_SHIFT_BITS) +#define DATA_Z1MASK ADC_DATA_MASK(0) +#define DATA_Z2MASK ADC_DATA_MASK(DATA_SHIFT_BITS) +#define DATA_AUXMASK ADC_DATA_MASK(0) +#define DATA_CBMASK ADC_DATA_MASK(0) +#define DATA_X2MASK ADC_DATA_MASK(0) +#define DATA_Y2MASK ADC_DATA_MASK(DATA_SHIFT_BITS) +#define DATA_5MASK ADC_DATA_MASK(0) +#define DATA_6MASK ADC_DATA_MASK(DATA_SHIFT_BITS) +#define DATA_7MASK ADC_DATA_MASK(0) +#define DATA_8MASK ADC_DATA_MASK(DATA_SHIFT_BITS) + +#define ADC_IDEAL_RELA_RESULT 11597 +#define ADC_IDEAL_ABSO_RESULT 9446 + +#define ADC_MORE_CTL1 (of_machine_is_compatible("sirf,atlas6") ?\ + (ADC_RESET_QUANT_EN | ADC_RST_B) : (0)) + +/* Select AD samples to read (SEL bits in ADC_CONTROL1 register) */ +#define SIRFSOC_ADC_AUX1_SEL 0x04 +#define SIRFSOC_ADC_AUX4_SEL 0x07 +#define SIRFSOC_ADC_AUX5_SEL 0x08 +#define SIRFSOC_ADC_AUX6_SEL 0x09 +#define SIRFSOC_ADC_TS_SEL 0x0A /* xy sample */ +#define SIRFSOC_ADC_TS_SEL_DUAL 0x0F /* samples for dual touch */ +#define SIRFSOC_ADC_TS_SAMPLE_SIZE 4 +#define SIRFSOC_ADC_CTL1(sel) (ADC_POLL | ADC_SEL(sel) | ADC_DEL_SET(6) \ + | ADC_FREQ_6K | ADC_TP_TIME(0) | ADC_SGAIN(0) \ + | ADC_EXTCM(0) | ADC_RBAT_DISABLE | ADC_MORE_CTL1) + +#define SIRFSOC_ADC_CHANNEL(_index) { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .channel = _index, \ + .address = _index, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ +} + +/* high priority queue with the bigger value */ +enum sirfsoc_adc_service_t { + SIRFSOC_ADC_SERVICE_AUX = 0, + SIRFSOC_ADC_SERVICE_TS_PARADOX, + SIRFSOC_ADC_SERVICE_MAX, +}; + +enum sirfsoc_adc_req_status_t { + SIRFSOC_ADC_REQ_NONE = 0, + SIRFSOC_ADC_REQ_ACTIVE, + SIRFSOC_ADC_REQ_BUSY, + SIRFSOC_ADC_REQ_MAX, +}; + +enum sirfsoc_adc_iio_channel { + CHANNEL_COORD, + CHANNEL_COORD_DUAL, + CHANNEL_AUX1, + CHANNEL_AUX4, + CHANNEL_AUX5, + CHANNEL_AUX6, +}; + +struct sirfsoc_adc_data { + u16 x; + u16 y; + u16 z1; + u16 z2; + u16 aux; + u8 datavalid; +}; + +struct sirfsoc_adc_request { + enum sirfsoc_adc_service_t type; + struct sirfsoc_adc_data adc_data; + u16 mode; + u16 aux; + u16 reference; + u8 delay_bits; + u32 s_gain_bits; + enum sirfsoc_adc_req_status_t req_status; +}; + +struct sirfsoc_adc { + struct clk *clk; + void __iomem *base; + struct sirfsoc_adc_request req; + struct completion done; + struct mutex adc_lock; +}; + +static int sirfsoc_adc_send_request(struct sirfsoc_adc_request *req) +{ + struct sirfsoc_adc *adc = container_of(req, struct sirfsoc_adc, req); + int control1, control2, intr; + int data, reg_offset; + int ret = 0; + + mutex_lock(&adc->adc_lock); + + intr = readl(adc->base + ADC_INTR); + control1 = readl(adc->base + ADC_CONTROL1); + control2 = readl(adc->base + ADC_CONTROL2); + writel(intr | DATA_INTR_EN | DATA_INTR, (adc->base + ADC_INTR)); + + writel(ADC_PRP_MODE3 | req->reference, + adc->base + ADC_CONTROL2); + + writel(ADC_POLL | ADC_MORE_CTL1 | req->mode | + req->aux | req->delay_bits | ADC_RESOLUTION_12, + adc->base + ADC_CONTROL1); + + if (!wait_for_completion_timeout(&adc->done, + msecs_to_jiffies(50))) { + ret = -EINVAL; + goto out; + } + + switch (req->mode) { + case ADC_SEL(3): + data = readl(adc->base + ADC_PRESSURE); + if ((data & DATA_Z1VALID) && (data & DATA_Z2VALID)) { + req->adc_data.z1 = data & DATA_Z1MASK; + req->adc_data.z2 = + (data & DATA_Z2MASK) >> DATA_SHIFT_BITS; + req->adc_data.datavalid = 1; + } + break; + case ADC_SEL(4): + case ADC_SEL(5): + case ADC_SEL(6): + case ADC_SEL(7): + case ADC_SEL(8): + case ADC_SEL(9): + reg_offset = 0x14 + ((req->mode >> 11) - 0x04) * 4; + data = readl(adc->base + reg_offset); + if ((data & DATA_AUXVALID)) { + req->adc_data.aux = data & DATA_AUXMASK; + req->adc_data.datavalid = 1; + } + break; + case ADC_SEL(11): + case ADC_SEL(12): + case ADC_SEL(13): + reg_offset = ADC_CB; + data = readl(adc->base + reg_offset); + if ((data & DATA_AUXVALID)) { + req->adc_data.aux = data & DATA_AUXMASK; + req->adc_data.datavalid = 1; + } + break; + default: + break; + } + +out: + writel(intr, adc->base + ADC_INTR); + writel(control1, adc->base + ADC_CONTROL1); + writel(control2, adc->base + ADC_CONTROL2); + mutex_unlock(&adc->adc_lock); + return ret; +} + +/*struct store params to calibrate*/ +struct sirfsoc_adc_cali_data { + u32 digital_offset; + u32 digital_again; + u32 digital_ideal; + bool is_calibration; +}; + +static u32 sirfsoc_adc_offset_cali(struct sirfsoc_adc_request *req) +{ + u32 i, digital_offset = 0, count = 0, sum = 0; + /* To set the reigsters in order to get the ADC offset */ + req->mode = ADC_SEL(11); + req->aux = ADC_CB; + req->s_gain_bits = ADC_SGAIN(7); + req->delay_bits = ADC_DEL_SET(4); + req->req_status = SIRFSOC_ADC_REQ_NONE; + + for (i = 0; i < 10; i++) { + if (unlikely(sirfsoc_adc_send_request(req))) + break; + digital_offset = req->adc_data.aux; + /* Maybe the value is wrong, so remove it use experience */ + if (digital_offset < 230 && digital_offset > 130) { + sum += digital_offset; + count++; + } + } + if (!sum || !count) + digital_offset = 170; + else + digital_offset = sum / count; + + return digital_offset; +} + + +/* Gain Calibration calibrates the ADC gain error */ +static u32 sirfsoc_adc_gain_cali(struct sirfsoc_adc_request *req) +{ + u32 i, digital_gain = 0, count = 0, sum = 0; + /* To set the reigsters in order to get the ADC gain */ + req->mode = ADC_SEL(12); + req->aux = ADC_CB; + req->s_gain_bits = ADC_SGAIN(0); + req->delay_bits = ADC_DEL_SET(4); + req->req_status = SIRFSOC_ADC_REQ_NONE; + + for (i = 0; i < 10; i++) { + if (unlikely(sirfsoc_adc_send_request(req))) + break; + digital_gain = req->adc_data.aux; + /* Maybe the value is wrong, so remove it use experience */ + if (digital_gain < 6500 && digital_gain > 5500) { + sum += digital_gain; + count++; + } + } + if (!sum || !count) + digital_gain = 5555; + else + digital_gain = sum / count; + + return digital_gain; +} + +/* absolute gain calibration */ +static int sirfsoc_adc_adc_cali(struct sirfsoc_adc_request *req, + struct sirfsoc_adc_cali_data *cali_data) +{ + cali_data->digital_offset = sirfsoc_adc_offset_cali(req); + if (!(cali_data->digital_offset)) + return -EINVAL; + cali_data->digital_again = sirfsoc_adc_gain_cali(req); + if (!(cali_data->digital_again)) + return -EINVAL; + /* + * see Equation 3.2 of SiRFprimaII™ Internal ADC and Touch + * User Guide + */ + cali_data->digital_ideal = (16384 * 1200) / (14 * 333); + return 0; +} + + +/* get voltage after ADC conversion */ +static u32 sirfsoc_adc_get_adc_volt(struct sirfsoc_adc *adc, + struct sirfsoc_adc_cali_data *cali_data) +{ + u32 digital_out, digital_convert, volt; + struct sirfsoc_adc_request *req = &adc->req; + if (!(cali_data->is_calibration)) { + if (sirfsoc_adc_adc_cali(req, cali_data)) + return 0; + cali_data->is_calibration = true; + } + req->s_gain_bits = ADC_SGAIN(0); + req->delay_bits = ADC_DEL_SET(4); + + sirfsoc_adc_send_request(req); + if (req->adc_data.aux) { + digital_out = req->adc_data.aux; + /* + * see Equation 3.3 of SiRFprimaII™ Internal ADC and Touch + */ + digital_convert = ((digital_out - 2 * cali_data->digital_offset + * 11645 / 140000) * cali_data->digital_ideal) + / (cali_data->digital_again - + 2 * cali_data->digital_offset + * 11645 / 140000); + volt = (1200 * digital_convert) / cali_data->digital_ideal; + volt = volt * 2; + + } else { + return 0; + } + + return volt; +} + +/* get touchscreen coordinates for single touch */ +static int sirfsoc_adc_single_ts_sample(struct sirfsoc_adc *adc, int *sample) +{ + int adc_intr; + + adc_intr = readl(adc->base + ADC_INTR); + if (adc_intr & PEN_INTR) + writel(PEN_INTR | PEN_INTR_EN | DATA_INTR_EN, + adc->base + ADC_INTR); + + /* check pen status */ + if (!(readl(adc->base + ADC_COORD) & PEN_DOWN)) + return -EINVAL; + + writel(SIRFSOC_ADC_CTL1(SIRFSOC_ADC_TS_SEL), + adc->base + ADC_CONTROL1); + + if (!wait_for_completion_timeout(&adc->done, + msecs_to_jiffies(50))) { + return -EBUSY; + } + + *sample = readl(adc->base + ADC_COORD); + return 0; +} + +static const u32 sirfsoc_adc_ts_reg[SIRFSOC_ADC_TS_SAMPLE_SIZE] = { + ADC_COORD, ADC_COORD2, ADC_COORD3, ADC_COORD4 +}; + +/* get touchscreen coordinates for dual touch */ +static int sirfsoc_adc_dual_ts_sample(struct sirfsoc_adc *adc, int *samples) +{ + int adc_intr; + int i; + + adc_intr = readl(adc->base + ADC_INTR); + if (adc_intr & PEN_INTR) + writel(PEN_INTR | PEN_INTR_EN | DATA_INTR_EN, + adc->base + ADC_INTR); + + /* check pen status */ + if (!(readl(adc->base + ADC_COORD) & PEN_DOWN)) + return -EINVAL; + + writel(SIRFSOC_ADC_CTL1(SIRFSOC_ADC_TS_SEL_DUAL), + adc->base + ADC_CONTROL1); + + if (!wait_for_completion_timeout(&adc->done, + msecs_to_jiffies(50))) { + return -EBUSY; + } + + for (i = 0; i < SIRFSOC_ADC_TS_SAMPLE_SIZE; i++) + samples[i] = readl(adc->base + sirfsoc_adc_ts_reg[i]); + + return 0; +} + +static int sirfsoc_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long mask) +{ + struct sirfsoc_adc *adc = iio_priv(indio_dev); + struct sirfsoc_adc_cali_data cali_data; + int ret = 0; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + switch (chan->channel) { + case CHANNEL_COORD: + ret = sirfsoc_adc_single_ts_sample(adc, val); + break; + case CHANNEL_COORD_DUAL: + ret = sirfsoc_adc_dual_ts_sample(adc, val); + break; + case CHANNEL_AUX1: + adc->req.mode = ADC_SEL(SIRFSOC_ADC_AUX1_SEL); + adc->req.aux = ADC_AUX1; + *val = sirfsoc_adc_get_adc_volt(adc, + &cali_data); + break; + case CHANNEL_AUX4: + adc->req.mode = ADC_SEL(SIRFSOC_ADC_AUX4_SEL); + adc->req.aux = ADC_AUX4; + *val = sirfsoc_adc_get_adc_volt(adc, + &cali_data); + break; + case CHANNEL_AUX5: + adc->req.mode = ADC_SEL(SIRFSOC_ADC_AUX5_SEL); + adc->req.aux = ADC_AUX5; + *val = sirfsoc_adc_get_adc_volt(adc, + &cali_data); + break; + case CHANNEL_AUX6: + adc->req.mode = ADC_SEL(SIRFSOC_ADC_AUX6_SEL); + adc->req.aux = ADC_AUX6; + *val = sirfsoc_adc_get_adc_volt(adc, + &cali_data); + break; + } + + if (ret) + return ret; + + return IIO_VAL_INT; + default: + return -EINVAL; + } + return 0; +} + +static irqreturn_t sirfsoc_adc_data_irq(int irq, void *handle) +{ + struct iio_dev *indio_dev = handle; + struct sirfsoc_adc *adc = iio_priv(indio_dev); + int val; + + val = readl(adc->base + ADC_INTR); + + if (val & DATA_INTR) { + writel(PEN_INTR_EN | DATA_INTR | DATA_INTR_EN, + adc->base + ADC_INTR); + complete(&adc->done); + } + + return IRQ_HANDLED; +} + +#ifdef CONFIG_PM_SLEEP +static int sirfsoc_adc_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct sirfsoc_adc *adc = iio_priv(indio_dev); + + sirfsoc_rtc_iobrg_writel(sirfsoc_rtc_iobrg_readl( + SIRFSOC_PWRC_BASE + SIRFSOC_PWRC_TRIGGER_EN) + & ~(1 << PWR_WAKEEN_TSC_SHIFT), + SIRFSOC_PWRC_BASE + SIRFSOC_PWRC_TRIGGER_EN); + + clk_disable_unprepare(adc->clk); + return 0; +} + +static int sirfsoc_adc_resume(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct sirfsoc_adc *adc = iio_priv(indio_dev); + int ret; + int val; + + clk_prepare_enable(adc->clk); + + sirfsoc_rtc_iobrg_writel(sirfsoc_rtc_iobrg_readl( + SIRFSOC_PWRC_BASE + SIRFSOC_PWRC_TRIGGER_EN) + | (1 << PWR_WAKEEN_TS_SHIFT), + SIRFSOC_PWRC_BASE + SIRFSOC_PWRC_TRIGGER_EN); + + ret = device_reset(dev); + if (ret) { + dev_err(dev, "Failed to reset\n"); + return ret; + } + + writel(ADC_PRP_MODE3 | ADC_RTOUCH(1) | ADC_DEL_PRE(2) | + ADC_DEL_DIS(5), adc->base + ADC_CONTROL2); + + val = readl(adc->base + ADC_INTR); + + /* Clear interrupts and enable PEN interrupt */ + writel(val | PEN_INTR | DATA_INTR | PEN_INTR_EN | + DATA_INTR_EN, adc->base + ADC_INTR); + + return 0; +} +#endif + +static const struct dev_pm_ops sirfsoc_adc_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(sirfsoc_adc_suspend, sirfsoc_adc_resume) +}; + +static const struct of_device_id sirfsoc_adc_of_match[] = { + { .compatible = "sirf,prima2-adc",}, + {} +}; + +static struct iio_chan_spec const sirfsoc_adc_iio_channels[] = { + SIRFSOC_ADC_CHANNEL(CHANNEL_COORD), + SIRFSOC_ADC_CHANNEL(CHANNEL_COORD_DUAL), + SIRFSOC_ADC_CHANNEL(CHANNEL_AUX1), + /* AtlasVI has no AUX2 and AUX3 */ + SIRFSOC_ADC_CHANNEL(CHANNEL_AUX4), + SIRFSOC_ADC_CHANNEL(CHANNEL_AUX5), + SIRFSOC_ADC_CHANNEL(CHANNEL_AUX6), +}; + +static const struct iio_info sirfsoc_adc_info = { + .read_raw = &sirfsoc_read_raw, + .driver_module = THIS_MODULE, +}; + +static int sirfsoc_adc_probe(struct platform_device *pdev) +{ + struct resource *mem_res; + struct sirfsoc_adc *adc; + struct iio_dev *indio_dev; + int irq; + int ret = 0; + + indio_dev = iio_device_alloc(sizeof(struct sirfsoc_adc)); + if (!indio_dev) + return -ENOMEM; + + indio_dev->info = &sirfsoc_adc_info; + indio_dev->channels = sirfsoc_adc_iio_channels; + indio_dev->num_channels = ARRAY_SIZE(sirfsoc_adc_iio_channels); + indio_dev->name = "sirfsoc adc"; + indio_dev->dev.parent = &pdev->dev; + indio_dev->modes = INDIO_DIRECT_MODE; + adc = iio_priv(indio_dev); + + platform_set_drvdata(pdev, indio_dev); + + adc->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(adc->clk)) { + dev_err(&pdev->dev, "get adc clk err\n"); + ret = -ENOMEM; + goto err; + } + clk_prepare_enable(adc->clk); + + mem_res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!mem_res) { + dev_err(&pdev->dev, "Unalbe to get io resource\n"); + ret = -ENODEV; + goto err; + } + + adc->base = devm_request_and_ioremap(&pdev->dev, mem_res); + if (adc->base == NULL) { + dev_err(&pdev->dev, "IO remap failed!\n"); + ret = -ENOMEM; + goto err; + } + + init_completion(&adc->done); + mutex_init(&adc->adc_lock); + + sirfsoc_rtc_iobrg_writel(sirfsoc_rtc_iobrg_readl(SIRFSOC_PWRC_BASE + + SIRFSOC_PWRC_TRIGGER_EN) | (1 << PWR_WAKEEN_TS_SHIFT), + SIRFSOC_PWRC_BASE + SIRFSOC_PWRC_TRIGGER_EN); + + ret = device_reset(&pdev->dev); + if (ret) { + dev_err(&pdev->dev, "Failed to reset\n"); + goto err; + } + + writel(ADC_PRP_MODE3 | ADC_RTOUCH(1) | ADC_DEL_PRE(2) | + ADC_DEL_DIS(5), adc->base + ADC_CONTROL2); + + /* Clear interrupts and enable PEN INTR */ + writel(readl(adc->base + ADC_INTR) | PEN_INTR | DATA_INTR | + PEN_INTR_EN | DATA_INTR_EN, adc->base + ADC_INTR); + + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + dev_err(&pdev->dev, "Failed to get IRQ!\n"); + ret = -ENOMEM; + goto err; + } + + ret = devm_request_irq(&pdev->dev, irq, sirfsoc_adc_data_irq, + 0, DRIVER_NAME, indio_dev); + + if (ret < 0) { + dev_err(&pdev->dev, "Failed to register irq handler\n"); + ret = -ENODEV; + goto err; + } + + ret = of_platform_populate(pdev->dev.of_node, sirfsoc_adc_of_match, + NULL, &pdev->dev); + if (ret < 0) { + dev_err(&pdev->dev, "Failed to add child nodes\n"); + goto err; + } + + ret = iio_device_register(indio_dev); + if (ret) { + dev_err(&pdev->dev, "Failed to register adc iio dev\n"); + goto err; + } + + return 0; + +err: + iio_device_free(indio_dev); + return ret; +} + +static int sirfsoc_adc_remove(struct platform_device *pdev) +{ + struct iio_dev *indio_dev = platform_get_drvdata(pdev); + struct sirfsoc_adc *adc = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + clk_disable_unprepare(adc->clk); + iio_device_free(indio_dev); + + return 0; +} + +static struct platform_driver sirfsoc_adc_driver = { + .driver = { + .name = DRIVER_NAME, + .of_match_table = sirfsoc_adc_of_match, + .pm = &sirfsoc_adc_pm_ops, + }, + .probe = sirfsoc_adc_probe, + .remove = sirfsoc_adc_remove, +}; + +module_platform_driver(sirfsoc_adc_driver); + +MODULE_AUTHOR("Guoying Zhang <Guoying.Zhang@xxxxxxx>"); +MODULE_DESCRIPTION("SiRF SoC On-chip ADC driver"); +MODULE_LICENSE("GPL v2"); -- 1.9.3 -- To unsubscribe from this list: send the line "unsubscribe linux-iio" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html