diff options
author | Patil, Rachna <rachna@ti.com> | 2012-10-16 12:55:45 +0530 |
---|---|---|
committer | Samuel Ortiz <sameo@linux.intel.com> | 2012-11-05 23:50:27 +0100 |
commit | 5e53a69b44e893227b046a7bc74db3cb40d7f39b (patch) | |
tree | 4586fb856eb6709e40df4c38b0840177adbf4d7c /drivers | |
parent | 2b99bafab19145a72e2c557326fc4662a864a162 (diff) |
IIO : ADC: tiadc: Add support of TI's ADC driver
This patch adds support for TI's ADC driver.
This is a multifunctional device.
Analog input lines are provided on which
voltage measurements can be carried out.
You can have upto 8 input lines.
Signed-off-by: Patil, Rachna <rachna@ti.com>
Acked-by: Jonathan Cameron <jic23@kernel.org>
Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/iio/adc/Kconfig | 7 | ||||
-rw-r--r-- | drivers/iio/adc/Makefile | 1 | ||||
-rw-r--r-- | drivers/iio/adc/ti_am335x_adc.c | 260 | ||||
-rw-r--r-- | drivers/mfd/ti_am335x_tscadc.c | 18 |
4 files changed, 285 insertions, 1 deletions
diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig index 492758120338..1401ed1af39f 100644 --- a/drivers/iio/adc/Kconfig +++ b/drivers/iio/adc/Kconfig @@ -60,4 +60,11 @@ config LP8788_ADC help Say yes here to build support for TI LP8788 ADC. +config TI_AM335X_ADC + tristate "TI's ADC driver" + depends on MFD_TI_AM335X_TSCADC + help + Say yes here to build support for Texas Instruments ADC + driver which is also a MFD client. + endmenu diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile index 900995d5e179..4410a90fc84b 100644 --- a/drivers/iio/adc/Makefile +++ b/drivers/iio/adc/Makefile @@ -8,3 +8,4 @@ obj-$(CONFIG_AD7476) += ad7476.o obj-$(CONFIG_AD7791) += ad7791.o obj-$(CONFIG_AT91_ADC) += at91_adc.o obj-$(CONFIG_LP8788_ADC) += lp8788_adc.o +obj-$(CONFIG_TI_AM335X_ADC) += ti_am335x_adc.o diff --git a/drivers/iio/adc/ti_am335x_adc.c b/drivers/iio/adc/ti_am335x_adc.c new file mode 100644 index 000000000000..02a43c87a8a3 --- /dev/null +++ b/drivers/iio/adc/ti_am335x_adc.c @@ -0,0 +1,260 @@ +/* + * TI ADC MFD driver + * + * Copyright (C) 2012 Texas Instruments Incorporated - http://www.ti.com/ + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation version 2. + * + * This program is distributed "as is" WITHOUT ANY WARRANTY of any + * kind, whether express or implied; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/err.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <linux/io.h> +#include <linux/iio/iio.h> + +#include <linux/mfd/ti_am335x_tscadc.h> +#include <linux/platform_data/ti_am335x_adc.h> + +struct tiadc_device { + struct ti_tscadc_dev *mfd_tscadc; + int channels; +}; + +static unsigned int tiadc_readl(struct tiadc_device *adc, unsigned int reg) +{ + return readl(adc->mfd_tscadc->tscadc_base + reg); +} + +static void tiadc_writel(struct tiadc_device *adc, unsigned int reg, + unsigned int val) +{ + writel(val, adc->mfd_tscadc->tscadc_base + reg); +} + +static void tiadc_step_config(struct tiadc_device *adc_dev) +{ + unsigned int stepconfig; + int i, channels = 0, steps; + + /* + * There are 16 configurable steps and 8 analog input + * lines available which are shared between Touchscreen and ADC. + * + * Steps backwards i.e. from 16 towards 0 are used by ADC + * depending on number of input lines needed. + * Channel would represent which analog input + * needs to be given to ADC to digitalize data. + */ + + steps = TOTAL_STEPS - adc_dev->channels; + channels = TOTAL_CHANNELS - adc_dev->channels; + + stepconfig = STEPCONFIG_AVG_16 | STEPCONFIG_FIFO1; + + for (i = (steps + 1); i <= TOTAL_STEPS; i++) { + tiadc_writel(adc_dev, REG_STEPCONFIG(i), + stepconfig | STEPCONFIG_INP(channels)); + tiadc_writel(adc_dev, REG_STEPDELAY(i), + STEPCONFIG_OPENDLY); + channels++; + } + tiadc_writel(adc_dev, REG_SE, STPENB_STEPENB); +} + +static int tiadc_channel_init(struct iio_dev *indio_dev, int channels) +{ + struct iio_chan_spec *chan_array; + int i; + + indio_dev->num_channels = channels; + chan_array = kcalloc(indio_dev->num_channels, + sizeof(struct iio_chan_spec), GFP_KERNEL); + + if (chan_array == NULL) + return -ENOMEM; + + for (i = 0; i < (indio_dev->num_channels); i++) { + struct iio_chan_spec *chan = chan_array + i; + chan->type = IIO_VOLTAGE; + chan->indexed = 1; + chan->channel = i; + chan->info_mask = IIO_CHAN_INFO_RAW_SEPARATE_BIT; + } + + indio_dev->channels = chan_array; + + return indio_dev->num_channels; +} + +static void tiadc_channels_remove(struct iio_dev *indio_dev) +{ + kfree(indio_dev->channels); +} + +static int tiadc_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct tiadc_device *adc_dev = iio_priv(indio_dev); + int i; + unsigned int fifo1count, readx1; + + /* + * When the sub-system is first enabled, + * the sequencer will always start with the + * lowest step (1) and continue until step (16). + * For ex: If we have enabled 4 ADC channels and + * currently use only 1 out of them, the + * sequencer still configures all the 4 steps, + * leading to 3 unwanted data. + * Hence we need to flush out this data. + */ + + fifo1count = tiadc_readl(adc_dev, REG_FIFO1CNT); + for (i = 0; i < fifo1count; i++) { + readx1 = tiadc_readl(adc_dev, REG_FIFO1); + if (i == chan->channel) + *val = readx1 & 0xfff; + } + tiadc_writel(adc_dev, REG_SE, STPENB_STEPENB); + + return IIO_VAL_INT; +} + +static const struct iio_info tiadc_info = { + .read_raw = &tiadc_read_raw, +}; + +static int __devinit tiadc_probe(struct platform_device *pdev) +{ + struct iio_dev *indio_dev; + struct tiadc_device *adc_dev; + struct ti_tscadc_dev *tscadc_dev = pdev->dev.platform_data; + struct mfd_tscadc_board *pdata; + int err; + + pdata = tscadc_dev->dev->platform_data; + if (!pdata || !pdata->adc_init) { + dev_err(&pdev->dev, "Could not find platform data\n"); + return -EINVAL; + } + + indio_dev = iio_device_alloc(sizeof(struct tiadc_device)); + if (indio_dev == NULL) { + dev_err(&pdev->dev, "failed to allocate iio device\n"); + err = -ENOMEM; + goto err_ret; + } + adc_dev = iio_priv(indio_dev); + + adc_dev->mfd_tscadc = tscadc_dev; + adc_dev->channels = pdata->adc_init->adc_channels; + + indio_dev->dev.parent = &pdev->dev; + indio_dev->name = dev_name(&pdev->dev); + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->info = &tiadc_info; + + tiadc_step_config(adc_dev); + + err = tiadc_channel_init(indio_dev, adc_dev->channels); + if (err < 0) + goto err_free_device; + + err = iio_device_register(indio_dev); + if (err) + goto err_free_channels; + + platform_set_drvdata(pdev, indio_dev); + + return 0; + +err_free_channels: + tiadc_channels_remove(indio_dev); +err_free_device: + iio_device_free(indio_dev); +err_ret: + return err; +} + +static int __devexit tiadc_remove(struct platform_device *pdev) +{ + struct iio_dev *indio_dev = platform_get_drvdata(pdev); + + iio_device_unregister(indio_dev); + tiadc_channels_remove(indio_dev); + + iio_device_free(indio_dev); + + return 0; +} + +#ifdef CONFIG_PM +static int tiadc_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct tiadc_device *adc_dev = iio_priv(indio_dev); + struct ti_tscadc_dev *tscadc_dev = dev->platform_data; + unsigned int idle; + + if (!device_may_wakeup(tscadc_dev->dev)) { + idle = tiadc_readl(adc_dev, REG_CTRL); + idle &= ~(CNTRLREG_TSCSSENB); + tiadc_writel(adc_dev, REG_CTRL, (idle | + CNTRLREG_POWERDOWN)); + } + + return 0; +} + +static int tiadc_resume(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct tiadc_device *adc_dev = iio_priv(indio_dev); + unsigned int restore; + + /* Make sure ADC is powered up */ + restore = tiadc_readl(adc_dev, REG_CTRL); + restore &= ~(CNTRLREG_POWERDOWN); + tiadc_writel(adc_dev, REG_CTRL, restore); + + tiadc_step_config(adc_dev); + + return 0; +} + +static const struct dev_pm_ops tiadc_pm_ops = { + .suspend = tiadc_suspend, + .resume = tiadc_resume, +}; +#define TIADC_PM_OPS (&tiadc_pm_ops) +#else +#define TIADC_PM_OPS NULL +#endif + +static struct platform_driver tiadc_driver = { + .driver = { + .name = "tiadc", + .owner = THIS_MODULE, + .pm = TIADC_PM_OPS, + }, + .probe = tiadc_probe, + .remove = __devexit_p(tiadc_remove), +}; + +module_platform_driver(tiadc_driver); + +MODULE_DESCRIPTION("TI ADC controller driver"); +MODULE_AUTHOR("Rachna Patil <rachna@ti.com>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/mfd/ti_am335x_tscadc.c b/drivers/mfd/ti_am335x_tscadc.c index d812be4b61df..e947dd8bbcc3 100644 --- a/drivers/mfd/ti_am335x_tscadc.c +++ b/drivers/mfd/ti_am335x_tscadc.c @@ -25,6 +25,7 @@ #include <linux/mfd/ti_am335x_tscadc.h> #include <linux/input/ti_am335x_tsc.h> +#include <linux/platform_data/ti_am335x_adc.h> static unsigned int tscadc_readl(struct ti_tscadc_dev *tsadc, unsigned int reg) { @@ -67,14 +68,23 @@ static int __devinit ti_tscadc_probe(struct platform_device *pdev) int irq; int err, ctrl; int clk_value, clock_rate; - int tsc_wires; + int tsc_wires, adc_channels = 0, total_channels; if (!pdata) { dev_err(&pdev->dev, "Could not find platform data\n"); return -EINVAL; } + if (pdata->adc_init) + adc_channels = pdata->adc_init->adc_channels; + tsc_wires = pdata->tsc_init->wires; + total_channels = tsc_wires + adc_channels; + + if (total_channels > 8) { + dev_err(&pdev->dev, "Number of i/p channels more than 8\n"); + return -EINVAL; + } res = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (!res) { @@ -172,6 +182,12 @@ static int __devinit ti_tscadc_probe(struct platform_device *pdev) cell->platform_data = tscadc; cell->pdata_size = sizeof(*tscadc); + /* ADC Cell */ + cell = &tscadc->cells[ADC_CELL]; + cell->name = "tiadc"; + cell->platform_data = tscadc; + cell->pdata_size = sizeof(*tscadc); + err = mfd_add_devices(&pdev->dev, pdev->id, tscadc->cells, TSCADC_CELLS, NULL, 0, NULL); if (err < 0) |