From 57f0f512b273f60d52568b8c6b77e17f5636edc0 Mon Sep 17 00:00:00 2001 From: André Fabian Silva Delgado Date: Wed, 5 Aug 2015 17:04:01 -0300 Subject: Initial import --- drivers/staging/iio/adc/Kconfig | 118 +++ drivers/staging/iio/adc/Makefile | 17 + drivers/staging/iio/adc/ad7192.c | 720 ++++++++++++++ drivers/staging/iio/adc/ad7192.h | 47 + drivers/staging/iio/adc/ad7280a.c | 985 +++++++++++++++++++ drivers/staging/iio/adc/ad7280a.h | 38 + drivers/staging/iio/adc/ad7606.h | 104 ++ drivers/staging/iio/adc/ad7606_core.c | 603 ++++++++++++ drivers/staging/iio/adc/ad7606_par.c | 152 +++ drivers/staging/iio/adc/ad7606_ring.c | 101 ++ drivers/staging/iio/adc/ad7606_spi.c | 116 +++ drivers/staging/iio/adc/ad7780.c | 276 ++++++ drivers/staging/iio/adc/ad7780.h | 30 + drivers/staging/iio/adc/ad7816.c | 446 +++++++++ drivers/staging/iio/adc/lpc32xx_adc.c | 215 +++++ drivers/staging/iio/adc/mxs-lradc.c | 1712 +++++++++++++++++++++++++++++++++ drivers/staging/iio/adc/spear_adc.c | 400 ++++++++ 17 files changed, 6080 insertions(+) create mode 100644 drivers/staging/iio/adc/Kconfig create mode 100644 drivers/staging/iio/adc/Makefile create mode 100644 drivers/staging/iio/adc/ad7192.c create mode 100644 drivers/staging/iio/adc/ad7192.h create mode 100644 drivers/staging/iio/adc/ad7280a.c create mode 100644 drivers/staging/iio/adc/ad7280a.h create mode 100644 drivers/staging/iio/adc/ad7606.h create mode 100644 drivers/staging/iio/adc/ad7606_core.c create mode 100644 drivers/staging/iio/adc/ad7606_par.c create mode 100644 drivers/staging/iio/adc/ad7606_ring.c create mode 100644 drivers/staging/iio/adc/ad7606_spi.c create mode 100644 drivers/staging/iio/adc/ad7780.c create mode 100644 drivers/staging/iio/adc/ad7780.h create mode 100644 drivers/staging/iio/adc/ad7816.c create mode 100644 drivers/staging/iio/adc/lpc32xx_adc.c create mode 100644 drivers/staging/iio/adc/mxs-lradc.c create mode 100644 drivers/staging/iio/adc/spear_adc.c (limited to 'drivers/staging/iio/adc') diff --git a/drivers/staging/iio/adc/Kconfig b/drivers/staging/iio/adc/Kconfig new file mode 100644 index 000000000..d0016ce6e --- /dev/null +++ b/drivers/staging/iio/adc/Kconfig @@ -0,0 +1,118 @@ +# +# ADC drivers +# +menu "Analog to digital converters" + +config AD7606 + tristate "Analog Devices AD7606 ADC driver" + depends on GPIOLIB + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + Say yes here to build support for Analog Devices: + ad7606, ad7606-6, ad7606-4 analog to digital converters (ADC). + + To compile this driver as a module, choose M here: the + module will be called ad7606. + +config AD7606_IFACE_PARALLEL + tristate "parallel interface support" + depends on AD7606 + help + Say yes here to include parallel interface support on the AD7606 + ADC driver. + + To compile this driver as a module, choose M here: the + module will be called ad7606_iface_parallel. + +config AD7606_IFACE_SPI + tristate "spi interface support" + depends on AD7606 + depends on SPI + help + Say yes here to include parallel interface support on the AD7606 + ADC driver. + + To compile this driver as a module, choose M here: the + module will be called ad7606_iface_spi. + +config AD7780 + tristate "Analog Devices AD7780 and similar ADCs driver" + depends on SPI + depends on GPIOLIB + select AD_SIGMA_DELTA + help + Say yes here to build support for Analog Devices AD7170, AD7171, + AD7780 and AD7781 SPI analog to digital converters (ADC). + If unsure, say N (but it's safe to say "Y"). + + To compile this driver as a module, choose M here: the + module will be called ad7780. + +config AD7816 + tristate "Analog Devices AD7816/7/8 temperature sensor and ADC driver" + depends on SPI + depends on GPIOLIB + help + Say yes here to build support for Analog Devices AD7816/7/8 + temperature sensors and ADC. + +config AD7192 + tristate "Analog Devices AD7190 AD7192 AD7195 ADC driver" + depends on SPI + select AD_SIGMA_DELTA + help + Say yes here to build support for Analog Devices AD7190, + AD7192 or AD7195 SPI analog to digital converters (ADC). + If unsure, say N (but it's safe to say "Y"). + + To compile this driver as a module, choose M here: the + module will be called ad7192. + +config AD7280 + tristate "Analog Devices AD7280A Lithium Ion Battery Monitoring System" + depends on SPI + help + Say yes here to build support for Analog Devices AD7280A + Lithium Ion Battery Monitoring System. + + To compile this driver as a module, choose M here: the + module will be called ad7280a + +config LPC32XX_ADC + tristate "NXP LPC32XX ADC" + depends on ARCH_LPC32XX || COMPILE_TEST + depends on HAS_IOMEM + help + Say yes here to build support for the integrated ADC inside the + LPC32XX SoC. Note that this feature uses the same hardware as the + touchscreen driver, so you should either select only one of the two + drivers (lpc32xx_adc or lpc32xx_ts) or, in the OpenFirmware case, + activate only one via device tree selection. Provides direct access + via sysfs. + +config MXS_LRADC + tristate "Freescale i.MX23/i.MX28 LRADC" + depends on (ARCH_MXS || COMPILE_TEST) && HAS_IOMEM + depends on INPUT + select STMP_DEVICE + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + Say yes here to build support for i.MX23/i.MX28 LRADC convertor + built into these chips. + + To compile this driver as a module, choose M here: the + module will be called mxs-lradc. + +config SPEAR_ADC + tristate "ST SPEAr ADC" + depends on PLAT_SPEAR || COMPILE_TEST + depends on HAS_IOMEM + help + Say yes here to build support for the integrated ADC inside the + ST SPEAr SoC. Provides direct access via sysfs. + + To compile this driver as a module, choose M here: the + module will be called spear_adc. +endmenu diff --git a/drivers/staging/iio/adc/Makefile b/drivers/staging/iio/adc/Makefile new file mode 100644 index 000000000..1c4277dbd --- /dev/null +++ b/drivers/staging/iio/adc/Makefile @@ -0,0 +1,17 @@ +# +# Makefile for industrial I/O ADC drivers +# + +ad7606-y := ad7606_core.o +ad7606-$(CONFIG_IIO_BUFFER) += ad7606_ring.o +ad7606-$(CONFIG_AD7606_IFACE_PARALLEL) += ad7606_par.o +ad7606-$(CONFIG_AD7606_IFACE_SPI) += ad7606_spi.o +obj-$(CONFIG_AD7606) += ad7606.o + +obj-$(CONFIG_AD7780) += ad7780.o +obj-$(CONFIG_AD7816) += ad7816.o +obj-$(CONFIG_AD7192) += ad7192.o +obj-$(CONFIG_AD7280) += ad7280a.o +obj-$(CONFIG_LPC32XX_ADC) += lpc32xx_adc.o +obj-$(CONFIG_MXS_LRADC) += mxs-lradc.o +obj-$(CONFIG_SPEAR_ADC) += spear_adc.o diff --git a/drivers/staging/iio/adc/ad7192.c b/drivers/staging/iio/adc/ad7192.c new file mode 100644 index 000000000..fe56fb6c7 --- /dev/null +++ b/drivers/staging/iio/adc/ad7192.c @@ -0,0 +1,720 @@ +/* + * AD7190 AD7192 AD7195 SPI ADC driver + * + * Copyright 2011-2012 Analog Devices Inc. + * + * Licensed under the GPL-2. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "ad7192.h" + +/* Registers */ +#define AD7192_REG_COMM 0 /* Communications Register (WO, 8-bit) */ +#define AD7192_REG_STAT 0 /* Status Register (RO, 8-bit) */ +#define AD7192_REG_MODE 1 /* Mode Register (RW, 24-bit */ +#define AD7192_REG_CONF 2 /* Configuration Register (RW, 24-bit) */ +#define AD7192_REG_DATA 3 /* Data Register (RO, 24/32-bit) */ +#define AD7192_REG_ID 4 /* ID Register (RO, 8-bit) */ +#define AD7192_REG_GPOCON 5 /* GPOCON Register (RO, 8-bit) */ +#define AD7192_REG_OFFSET 6 /* Offset Register (RW, 16-bit + * (AD7792)/24-bit (AD7192)) */ +#define AD7192_REG_FULLSALE 7 /* Full-Scale Register + * (RW, 16-bit (AD7792)/24-bit (AD7192)) */ + +/* Communications Register Bit Designations (AD7192_REG_COMM) */ +#define AD7192_COMM_WEN BIT(7) /* Write Enable */ +#define AD7192_COMM_WRITE 0 /* Write Operation */ +#define AD7192_COMM_READ BIT(6) /* Read Operation */ +#define AD7192_COMM_ADDR(x) (((x) & 0x7) << 3) /* Register Address */ +#define AD7192_COMM_CREAD BIT(2) /* Continuous Read of Data Register */ + +/* Status Register Bit Designations (AD7192_REG_STAT) */ +#define AD7192_STAT_RDY BIT(7) /* Ready */ +#define AD7192_STAT_ERR BIT(6) /* Error (Overrange, Underrange) */ +#define AD7192_STAT_NOREF BIT(5) /* Error no external reference */ +#define AD7192_STAT_PARITY BIT(4) /* Parity */ +#define AD7192_STAT_CH3 BIT(2) /* Channel 3 */ +#define AD7192_STAT_CH2 BIT(1) /* Channel 2 */ +#define AD7192_STAT_CH1 BIT(0) /* Channel 1 */ + +/* Mode Register Bit Designations (AD7192_REG_MODE) */ +#define AD7192_MODE_SEL(x) (((x) & 0x7) << 21) /* Operation Mode Select */ +#define AD7192_MODE_SEL_MASK (0x7 << 21) /* Operation Mode Select Mask */ +#define AD7192_MODE_DAT_STA BIT(20) /* Status Register transmission */ +#define AD7192_MODE_CLKSRC(x) (((x) & 0x3) << 18) /* Clock Source Select */ +#define AD7192_MODE_SINC3 BIT(15) /* SINC3 Filter Select */ +#define AD7192_MODE_ACX BIT(14) /* AC excitation enable(AD7195 only)*/ +#define AD7192_MODE_ENPAR BIT(13) /* Parity Enable */ +#define AD7192_MODE_CLKDIV BIT(12) /* Clock divide by 2 (AD7190/2 only)*/ +#define AD7192_MODE_SCYCLE BIT(11) /* Single cycle conversion */ +#define AD7192_MODE_REJ60 BIT(10) /* 50/60Hz notch filter */ +#define AD7192_MODE_RATE(x) ((x) & 0x3FF) /* Filter Update Rate Select */ + +/* Mode Register: AD7192_MODE_SEL options */ +#define AD7192_MODE_CONT 0 /* Continuous Conversion Mode */ +#define AD7192_MODE_SINGLE 1 /* Single Conversion Mode */ +#define AD7192_MODE_IDLE 2 /* Idle Mode */ +#define AD7192_MODE_PWRDN 3 /* Power-Down Mode */ +#define AD7192_MODE_CAL_INT_ZERO 4 /* Internal Zero-Scale Calibration */ +#define AD7192_MODE_CAL_INT_FULL 5 /* Internal Full-Scale Calibration */ +#define AD7192_MODE_CAL_SYS_ZERO 6 /* System Zero-Scale Calibration */ +#define AD7192_MODE_CAL_SYS_FULL 7 /* System Full-Scale Calibration */ + +/* Mode Register: AD7192_MODE_CLKSRC options */ +#define AD7192_CLK_EXT_MCLK1_2 0 /* External 4.92 MHz Clock connected + * from MCLK1 to MCLK2 */ +#define AD7192_CLK_EXT_MCLK2 1 /* External Clock applied to MCLK2 */ +#define AD7192_CLK_INT 2 /* Internal 4.92 MHz Clock not + * available at the MCLK2 pin */ +#define AD7192_CLK_INT_CO 3 /* Internal 4.92 MHz Clock available + * at the MCLK2 pin */ + + +/* Configuration Register Bit Designations (AD7192_REG_CONF) */ + +#define AD7192_CONF_CHOP BIT(23) /* CHOP enable */ +#define AD7192_CONF_REFSEL BIT(20) /* REFIN1/REFIN2 Reference Select */ +#define AD7192_CONF_CHAN(x) (((1 << (x)) & 0xFF) << 8) /* Channel select */ +#define AD7192_CONF_CHAN_MASK (0xFF << 8) /* Channel select mask */ +#define AD7192_CONF_BURN BIT(7) /* Burnout current enable */ +#define AD7192_CONF_REFDET BIT(6) /* Reference detect enable */ +#define AD7192_CONF_BUF BIT(4) /* Buffered Mode Enable */ +#define AD7192_CONF_UNIPOLAR BIT(3) /* Unipolar/Bipolar Enable */ +#define AD7192_CONF_GAIN(x) ((x) & 0x7) /* Gain Select */ + +#define AD7192_CH_AIN1P_AIN2M 0 /* AIN1(+) - AIN2(-) */ +#define AD7192_CH_AIN3P_AIN4M 1 /* AIN3(+) - AIN4(-) */ +#define AD7192_CH_TEMP 2 /* Temp Sensor */ +#define AD7192_CH_AIN2P_AIN2M 3 /* AIN2(+) - AIN2(-) */ +#define AD7192_CH_AIN1 4 /* AIN1 - AINCOM */ +#define AD7192_CH_AIN2 5 /* AIN2 - AINCOM */ +#define AD7192_CH_AIN3 6 /* AIN3 - AINCOM */ +#define AD7192_CH_AIN4 7 /* AIN4 - AINCOM */ + +/* ID Register Bit Designations (AD7192_REG_ID) */ +#define ID_AD7190 0x4 +#define ID_AD7192 0x0 +#define ID_AD7195 0x6 +#define AD7192_ID_MASK 0x0F + +/* GPOCON Register Bit Designations (AD7192_REG_GPOCON) */ +#define AD7192_GPOCON_BPDSW BIT(6) /* Bridge power-down switch enable */ +#define AD7192_GPOCON_GP32EN BIT(5) /* Digital Output P3 and P2 enable */ +#define AD7192_GPOCON_GP10EN BIT(4) /* Digital Output P1 and P0 enable */ +#define AD7192_GPOCON_P3DAT BIT(3) /* P3 state */ +#define AD7192_GPOCON_P2DAT BIT(2) /* P2 state */ +#define AD7192_GPOCON_P1DAT BIT(1) /* P1 state */ +#define AD7192_GPOCON_P0DAT BIT(0) /* P0 state */ + +#define AD7192_INT_FREQ_MHz 4915200 + +/* NOTE: + * The AD7190/2/5 features a dual use data out ready DOUT/RDY output. + * In order to avoid contentions on the SPI bus, it's therefore necessary + * to use spi bus locking. + * + * The DOUT/RDY output must also be wired to an interrupt capable GPIO. + */ + +struct ad7192_state { + struct regulator *reg; + u16 int_vref_mv; + u32 mclk; + u32 f_order; + u32 mode; + u32 conf; + u32 scale_avail[8][2]; + u8 gpocon; + u8 devid; + + struct ad_sigma_delta sd; +}; + +static struct ad7192_state *ad_sigma_delta_to_ad7192(struct ad_sigma_delta *sd) +{ + return container_of(sd, struct ad7192_state, sd); +} + +static int ad7192_set_channel(struct ad_sigma_delta *sd, unsigned int channel) +{ + struct ad7192_state *st = ad_sigma_delta_to_ad7192(sd); + + st->conf &= ~AD7192_CONF_CHAN_MASK; + st->conf |= AD7192_CONF_CHAN(channel); + + return ad_sd_write_reg(&st->sd, AD7192_REG_CONF, 3, st->conf); +} + +static int ad7192_set_mode(struct ad_sigma_delta *sd, + enum ad_sigma_delta_mode mode) +{ + struct ad7192_state *st = ad_sigma_delta_to_ad7192(sd); + + st->mode &= ~AD7192_MODE_SEL_MASK; + st->mode |= AD7192_MODE_SEL(mode); + + return ad_sd_write_reg(&st->sd, AD7192_REG_MODE, 3, st->mode); +} + +static const struct ad_sigma_delta_info ad7192_sigma_delta_info = { + .set_channel = ad7192_set_channel, + .set_mode = ad7192_set_mode, + .has_registers = true, + .addr_shift = 3, + .read_mask = BIT(6), +}; + +static const struct ad_sd_calib_data ad7192_calib_arr[8] = { + {AD7192_MODE_CAL_INT_ZERO, AD7192_CH_AIN1}, + {AD7192_MODE_CAL_INT_FULL, AD7192_CH_AIN1}, + {AD7192_MODE_CAL_INT_ZERO, AD7192_CH_AIN2}, + {AD7192_MODE_CAL_INT_FULL, AD7192_CH_AIN2}, + {AD7192_MODE_CAL_INT_ZERO, AD7192_CH_AIN3}, + {AD7192_MODE_CAL_INT_FULL, AD7192_CH_AIN3}, + {AD7192_MODE_CAL_INT_ZERO, AD7192_CH_AIN4}, + {AD7192_MODE_CAL_INT_FULL, AD7192_CH_AIN4} +}; + +static int ad7192_calibrate_all(struct ad7192_state *st) +{ + return ad_sd_calibrate_all(&st->sd, ad7192_calib_arr, + ARRAY_SIZE(ad7192_calib_arr)); +} + +static int ad7192_setup(struct ad7192_state *st, + const struct ad7192_platform_data *pdata) +{ + struct iio_dev *indio_dev = spi_get_drvdata(st->sd.spi); + unsigned long long scale_uv; + int i, ret, id; + u8 ones[6]; + + /* reset the serial interface */ + memset(&ones, 0xFF, 6); + ret = spi_write(st->sd.spi, &ones, 6); + if (ret < 0) + goto out; + usleep_range(500, 1000); /* Wait for at least 500us */ + + /* write/read test for device presence */ + ret = ad_sd_read_reg(&st->sd, AD7192_REG_ID, 1, &id); + if (ret) + goto out; + + id &= AD7192_ID_MASK; + + if (id != st->devid) + dev_warn(&st->sd.spi->dev, "device ID query failed (0x%X)\n", + id); + + switch (pdata->clock_source_sel) { + case AD7192_CLK_EXT_MCLK1_2: + case AD7192_CLK_EXT_MCLK2: + st->mclk = AD7192_INT_FREQ_MHz; + break; + case AD7192_CLK_INT: + case AD7192_CLK_INT_CO: + if (pdata->ext_clk_Hz) + st->mclk = pdata->ext_clk_Hz; + else + st->mclk = AD7192_INT_FREQ_MHz; + break; + default: + ret = -EINVAL; + goto out; + } + + st->mode = AD7192_MODE_SEL(AD7192_MODE_IDLE) | + AD7192_MODE_CLKSRC(pdata->clock_source_sel) | + AD7192_MODE_RATE(480); + + st->conf = AD7192_CONF_GAIN(0); + + if (pdata->rej60_en) + st->mode |= AD7192_MODE_REJ60; + + if (pdata->sinc3_en) + st->mode |= AD7192_MODE_SINC3; + + if (pdata->refin2_en && (st->devid != ID_AD7195)) + st->conf |= AD7192_CONF_REFSEL; + + if (pdata->chop_en) { + st->conf |= AD7192_CONF_CHOP; + if (pdata->sinc3_en) + st->f_order = 3; /* SINC 3rd order */ + else + st->f_order = 4; /* SINC 4th order */ + } else { + st->f_order = 1; + } + + if (pdata->buf_en) + st->conf |= AD7192_CONF_BUF; + + if (pdata->unipolar_en) + st->conf |= AD7192_CONF_UNIPOLAR; + + if (pdata->burnout_curr_en) + st->conf |= AD7192_CONF_BURN; + + ret = ad_sd_write_reg(&st->sd, AD7192_REG_MODE, 3, st->mode); + if (ret) + goto out; + + ret = ad_sd_write_reg(&st->sd, AD7192_REG_CONF, 3, st->conf); + if (ret) + goto out; + + ret = ad7192_calibrate_all(st); + if (ret) + goto out; + + /* Populate available ADC input ranges */ + for (i = 0; i < ARRAY_SIZE(st->scale_avail); i++) { + scale_uv = ((u64)st->int_vref_mv * 100000000) + >> (indio_dev->channels[0].scan_type.realbits - + ((st->conf & AD7192_CONF_UNIPOLAR) ? 0 : 1)); + scale_uv >>= i; + + st->scale_avail[i][1] = do_div(scale_uv, 100000000) * 10; + st->scale_avail[i][0] = scale_uv; + } + + return 0; +out: + dev_err(&st->sd.spi->dev, "setup failed\n"); + return ret; +} + +static ssize_t ad7192_read_frequency(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct ad7192_state *st = iio_priv(indio_dev); + + return sprintf(buf, "%d\n", st->mclk / + (st->f_order * 1024 * AD7192_MODE_RATE(st->mode))); +} + +static ssize_t ad7192_write_frequency(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct ad7192_state *st = iio_priv(indio_dev); + unsigned long lval; + int div, ret; + + ret = kstrtoul(buf, 10, &lval); + if (ret) + return ret; + if (lval == 0) + return -EINVAL; + + mutex_lock(&indio_dev->mlock); + if (iio_buffer_enabled(indio_dev)) { + mutex_unlock(&indio_dev->mlock); + return -EBUSY; + } + + div = st->mclk / (lval * st->f_order * 1024); + if (div < 1 || div > 1023) { + ret = -EINVAL; + goto out; + } + + st->mode &= ~AD7192_MODE_RATE(-1); + st->mode |= AD7192_MODE_RATE(div); + ad_sd_write_reg(&st->sd, AD7192_REG_MODE, 3, st->mode); + +out: + mutex_unlock(&indio_dev->mlock); + + return ret ? ret : len; +} + +static IIO_DEV_ATTR_SAMP_FREQ(S_IWUSR | S_IRUGO, + ad7192_read_frequency, + ad7192_write_frequency); + +static ssize_t ad7192_show_scale_available(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct ad7192_state *st = iio_priv(indio_dev); + int i, len = 0; + + for (i = 0; i < ARRAY_SIZE(st->scale_avail); i++) + len += sprintf(buf + len, "%d.%09u ", st->scale_avail[i][0], + st->scale_avail[i][1]); + + len += sprintf(buf + len, "\n"); + + return len; +} + +static IIO_DEVICE_ATTR_NAMED(in_v_m_v_scale_available, + in_voltage-voltage_scale_available, + S_IRUGO, ad7192_show_scale_available, NULL, 0); + +static IIO_DEVICE_ATTR(in_voltage_scale_available, S_IRUGO, + ad7192_show_scale_available, NULL, 0); + +static ssize_t ad7192_show_ac_excitation(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct ad7192_state *st = iio_priv(indio_dev); + + return sprintf(buf, "%d\n", !!(st->mode & AD7192_MODE_ACX)); +} + +static ssize_t ad7192_show_bridge_switch(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct ad7192_state *st = iio_priv(indio_dev); + + return sprintf(buf, "%d\n", !!(st->gpocon & AD7192_GPOCON_BPDSW)); +} + +static ssize_t ad7192_set(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct ad7192_state *st = iio_priv(indio_dev); + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + int ret; + bool val; + + ret = strtobool(buf, &val); + if (ret < 0) + return ret; + + mutex_lock(&indio_dev->mlock); + if (iio_buffer_enabled(indio_dev)) { + mutex_unlock(&indio_dev->mlock); + return -EBUSY; + } + + switch ((u32) this_attr->address) { + case AD7192_REG_GPOCON: + if (val) + st->gpocon |= AD7192_GPOCON_BPDSW; + else + st->gpocon &= ~AD7192_GPOCON_BPDSW; + + ad_sd_write_reg(&st->sd, AD7192_REG_GPOCON, 1, st->gpocon); + break; + case AD7192_REG_MODE: + if (val) + st->mode |= AD7192_MODE_ACX; + else + st->mode &= ~AD7192_MODE_ACX; + + ad_sd_write_reg(&st->sd, AD7192_REG_MODE, 3, st->mode); + break; + default: + ret = -EINVAL; + } + + mutex_unlock(&indio_dev->mlock); + + return ret ? ret : len; +} + +static IIO_DEVICE_ATTR(bridge_switch_en, S_IRUGO | S_IWUSR, + ad7192_show_bridge_switch, ad7192_set, + AD7192_REG_GPOCON); + +static IIO_DEVICE_ATTR(ac_excitation_en, S_IRUGO | S_IWUSR, + ad7192_show_ac_excitation, ad7192_set, + AD7192_REG_MODE); + +static struct attribute *ad7192_attributes[] = { + &iio_dev_attr_sampling_frequency.dev_attr.attr, + &iio_dev_attr_in_v_m_v_scale_available.dev_attr.attr, + &iio_dev_attr_in_voltage_scale_available.dev_attr.attr, + &iio_dev_attr_bridge_switch_en.dev_attr.attr, + &iio_dev_attr_ac_excitation_en.dev_attr.attr, + NULL +}; + +static const struct attribute_group ad7192_attribute_group = { + .attrs = ad7192_attributes, +}; + +static struct attribute *ad7195_attributes[] = { + &iio_dev_attr_sampling_frequency.dev_attr.attr, + &iio_dev_attr_in_v_m_v_scale_available.dev_attr.attr, + &iio_dev_attr_in_voltage_scale_available.dev_attr.attr, + &iio_dev_attr_bridge_switch_en.dev_attr.attr, + NULL +}; + +static const struct attribute_group ad7195_attribute_group = { + .attrs = ad7195_attributes, +}; + +static unsigned int ad7192_get_temp_scale(bool unipolar) +{ + return unipolar ? 2815 * 2 : 2815; +} + +static int ad7192_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long m) +{ + struct ad7192_state *st = iio_priv(indio_dev); + bool unipolar = !!(st->conf & AD7192_CONF_UNIPOLAR); + + switch (m) { + case IIO_CHAN_INFO_RAW: + return ad_sigma_delta_single_conversion(indio_dev, chan, val); + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_VOLTAGE: + mutex_lock(&indio_dev->mlock); + *val = st->scale_avail[AD7192_CONF_GAIN(st->conf)][0]; + *val2 = st->scale_avail[AD7192_CONF_GAIN(st->conf)][1]; + mutex_unlock(&indio_dev->mlock); + return IIO_VAL_INT_PLUS_NANO; + case IIO_TEMP: + *val = 0; + *val2 = 1000000000 / ad7192_get_temp_scale(unipolar); + return IIO_VAL_INT_PLUS_NANO; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_OFFSET: + if (!unipolar) + *val = -(1 << (chan->scan_type.realbits - 1)); + else + *val = 0; + /* Kelvin to Celsius */ + if (chan->type == IIO_TEMP) + *val -= 273 * ad7192_get_temp_scale(unipolar); + return IIO_VAL_INT; + } + + return -EINVAL; +} + +static int ad7192_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, + int val2, + long mask) +{ + struct ad7192_state *st = iio_priv(indio_dev); + int ret, i; + unsigned int tmp; + + mutex_lock(&indio_dev->mlock); + if (iio_buffer_enabled(indio_dev)) { + mutex_unlock(&indio_dev->mlock); + return -EBUSY; + } + + switch (mask) { + case IIO_CHAN_INFO_SCALE: + ret = -EINVAL; + for (i = 0; i < ARRAY_SIZE(st->scale_avail); i++) + if (val2 == st->scale_avail[i][1]) { + ret = 0; + tmp = st->conf; + st->conf &= ~AD7192_CONF_GAIN(-1); + st->conf |= AD7192_CONF_GAIN(i); + if (tmp == st->conf) + break; + ad_sd_write_reg(&st->sd, AD7192_REG_CONF, + 3, st->conf); + ad7192_calibrate_all(st); + break; + } + break; + default: + ret = -EINVAL; + } + + mutex_unlock(&indio_dev->mlock); + + return ret; +} + +static int ad7192_write_raw_get_fmt(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + long mask) +{ + return IIO_VAL_INT_PLUS_NANO; +} + +static const struct iio_info ad7192_info = { + .read_raw = &ad7192_read_raw, + .write_raw = &ad7192_write_raw, + .write_raw_get_fmt = &ad7192_write_raw_get_fmt, + .attrs = &ad7192_attribute_group, + .validate_trigger = ad_sd_validate_trigger, + .driver_module = THIS_MODULE, +}; + +static const struct iio_info ad7195_info = { + .read_raw = &ad7192_read_raw, + .write_raw = &ad7192_write_raw, + .write_raw_get_fmt = &ad7192_write_raw_get_fmt, + .attrs = &ad7195_attribute_group, + .validate_trigger = ad_sd_validate_trigger, + .driver_module = THIS_MODULE, +}; + +static const struct iio_chan_spec ad7192_channels[] = { + AD_SD_DIFF_CHANNEL(0, 1, 2, AD7192_CH_AIN1P_AIN2M, 24, 32, 0), + AD_SD_DIFF_CHANNEL(1, 3, 4, AD7192_CH_AIN3P_AIN4M, 24, 32, 0), + AD_SD_TEMP_CHANNEL(2, AD7192_CH_TEMP, 24, 32, 0), + AD_SD_SHORTED_CHANNEL(3, 2, AD7192_CH_AIN2P_AIN2M, 24, 32, 0), + AD_SD_CHANNEL(4, 1, AD7192_CH_AIN1, 24, 32, 0), + AD_SD_CHANNEL(5, 2, AD7192_CH_AIN2, 24, 32, 0), + AD_SD_CHANNEL(6, 3, AD7192_CH_AIN3, 24, 32, 0), + AD_SD_CHANNEL(7, 4, AD7192_CH_AIN4, 24, 32, 0), + IIO_CHAN_SOFT_TIMESTAMP(8), +}; + +static int ad7192_probe(struct spi_device *spi) +{ + const struct ad7192_platform_data *pdata = spi->dev.platform_data; + struct ad7192_state *st; + struct iio_dev *indio_dev; + int ret, voltage_uv = 0; + + if (!pdata) { + dev_err(&spi->dev, "no platform data?\n"); + return -ENODEV; + } + + if (!spi->irq) { + dev_err(&spi->dev, "no IRQ?\n"); + return -ENODEV; + } + + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st)); + if (indio_dev == NULL) + return -ENOMEM; + + st = iio_priv(indio_dev); + + st->reg = devm_regulator_get(&spi->dev, "vcc"); + if (!IS_ERR(st->reg)) { + ret = regulator_enable(st->reg); + if (ret) + return ret; + + voltage_uv = regulator_get_voltage(st->reg); + } + + if (pdata && pdata->vref_mv) + st->int_vref_mv = pdata->vref_mv; + else if (voltage_uv) + st->int_vref_mv = voltage_uv / 1000; + else + dev_warn(&spi->dev, "reference voltage undefined\n"); + + spi_set_drvdata(spi, indio_dev); + st->devid = spi_get_device_id(spi)->driver_data; + indio_dev->dev.parent = &spi->dev; + indio_dev->name = spi_get_device_id(spi)->name; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = ad7192_channels; + indio_dev->num_channels = ARRAY_SIZE(ad7192_channels); + if (st->devid == ID_AD7195) + indio_dev->info = &ad7195_info; + else + indio_dev->info = &ad7192_info; + + ad_sd_init(&st->sd, indio_dev, spi, &ad7192_sigma_delta_info); + + ret = ad_sd_setup_buffer_and_trigger(indio_dev); + if (ret) + goto error_disable_reg; + + ret = ad7192_setup(st, pdata); + if (ret) + goto error_remove_trigger; + + ret = iio_device_register(indio_dev); + if (ret < 0) + goto error_remove_trigger; + return 0; + +error_remove_trigger: + ad_sd_cleanup_buffer_and_trigger(indio_dev); +error_disable_reg: + if (!IS_ERR(st->reg)) + regulator_disable(st->reg); + + return ret; +} + +static int ad7192_remove(struct spi_device *spi) +{ + struct iio_dev *indio_dev = spi_get_drvdata(spi); + struct ad7192_state *st = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + ad_sd_cleanup_buffer_and_trigger(indio_dev); + + if (!IS_ERR(st->reg)) + regulator_disable(st->reg); + + return 0; +} + +static const struct spi_device_id ad7192_id[] = { + {"ad7190", ID_AD7190}, + {"ad7192", ID_AD7192}, + {"ad7195", ID_AD7195}, + {} +}; +MODULE_DEVICE_TABLE(spi, ad7192_id); + +static struct spi_driver ad7192_driver = { + .driver = { + .name = "ad7192", + .owner = THIS_MODULE, + }, + .probe = ad7192_probe, + .remove = ad7192_remove, + .id_table = ad7192_id, +}; +module_spi_driver(ad7192_driver); + +MODULE_AUTHOR("Michael Hennerich "); +MODULE_DESCRIPTION("Analog Devices AD7190, AD7192, AD7195 ADC"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/staging/iio/adc/ad7192.h b/drivers/staging/iio/adc/ad7192.h new file mode 100644 index 000000000..a0a5b61a4 --- /dev/null +++ b/drivers/staging/iio/adc/ad7192.h @@ -0,0 +1,47 @@ +/* + * AD7190 AD7192 AD7195 SPI ADC driver + * + * Copyright 2011 Analog Devices Inc. + * + * Licensed under the GPL-2. + */ +#ifndef IIO_ADC_AD7192_H_ +#define IIO_ADC_AD7192_H_ + +/* + * TODO: struct ad7192_platform_data needs to go into include/linux/iio + */ + +/** + * struct ad7192_platform_data - platform/board specific information + * @vref_mv: the external reference voltage in millivolt + * @clock_source_sel: [0..3] + * 0 External 4.92 MHz clock connected from MCLK1 to MCLK2 + * 1 External Clock applied to MCLK2 + * 2 Internal 4.92 MHz Clock not available at the MCLK2 pin + * 3 Internal 4.92 MHz Clock available at the MCLK2 pin + * @ext_clk_Hz: the external clock frequency in Hz, if not set + * the driver uses the internal clock (16.776 MHz) + * @refin2_en: REFIN1/REFIN2 Reference Select (AD7190/2 only) + * @rej60_en: 50/60Hz notch filter enable + * @sinc3_en: SINC3 filter enable (default SINC4) + * @chop_en: CHOP mode enable + * @buf_en: buffered input mode enable + * @unipolar_en: unipolar mode enable + * @burnout_curr_en: constant current generators on AIN(+|-) enable + */ + +struct ad7192_platform_data { + u16 vref_mv; + u8 clock_source_sel; + u32 ext_clk_Hz; + bool refin2_en; + bool rej60_en; + bool sinc3_en; + bool chop_en; + bool buf_en; + bool unipolar_en; + bool burnout_curr_en; +}; + +#endif /* IIO_ADC_AD7192_H_ */ diff --git a/drivers/staging/iio/adc/ad7280a.c b/drivers/staging/iio/adc/ad7280a.c new file mode 100644 index 000000000..d98e229c4 --- /dev/null +++ b/drivers/staging/iio/adc/ad7280a.c @@ -0,0 +1,985 @@ +/* + * AD7280A Lithium Ion Battery Monitoring System + * + * Copyright 2011 Analog Devices Inc. + * + * Licensed under the GPL-2. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "ad7280a.h" + +/* Registers */ +#define AD7280A_CELL_VOLTAGE_1 0x0 /* D11 to D0, Read only */ +#define AD7280A_CELL_VOLTAGE_2 0x1 /* D11 to D0, Read only */ +#define AD7280A_CELL_VOLTAGE_3 0x2 /* D11 to D0, Read only */ +#define AD7280A_CELL_VOLTAGE_4 0x3 /* D11 to D0, Read only */ +#define AD7280A_CELL_VOLTAGE_5 0x4 /* D11 to D0, Read only */ +#define AD7280A_CELL_VOLTAGE_6 0x5 /* D11 to D0, Read only */ +#define AD7280A_AUX_ADC_1 0x6 /* D11 to D0, Read only */ +#define AD7280A_AUX_ADC_2 0x7 /* D11 to D0, Read only */ +#define AD7280A_AUX_ADC_3 0x8 /* D11 to D0, Read only */ +#define AD7280A_AUX_ADC_4 0x9 /* D11 to D0, Read only */ +#define AD7280A_AUX_ADC_5 0xA /* D11 to D0, Read only */ +#define AD7280A_AUX_ADC_6 0xB /* D11 to D0, Read only */ +#define AD7280A_SELF_TEST 0xC /* D11 to D0, Read only */ +#define AD7280A_CONTROL_HB 0xD /* D15 to D8, Read/write */ +#define AD7280A_CONTROL_LB 0xE /* D7 to D0, Read/write */ +#define AD7280A_CELL_OVERVOLTAGE 0xF /* D7 to D0, Read/write */ +#define AD7280A_CELL_UNDERVOLTAGE 0x10 /* D7 to D0, Read/write */ +#define AD7280A_AUX_ADC_OVERVOLTAGE 0x11 /* D7 to D0, Read/write */ +#define AD7280A_AUX_ADC_UNDERVOLTAGE 0x12 /* D7 to D0, Read/write */ +#define AD7280A_ALERT 0x13 /* D7 to D0, Read/write */ +#define AD7280A_CELL_BALANCE 0x14 /* D7 to D0, Read/write */ +#define AD7280A_CB1_TIMER 0x15 /* D7 to D0, Read/write */ +#define AD7280A_CB2_TIMER 0x16 /* D7 to D0, Read/write */ +#define AD7280A_CB3_TIMER 0x17 /* D7 to D0, Read/write */ +#define AD7280A_CB4_TIMER 0x18 /* D7 to D0, Read/write */ +#define AD7280A_CB5_TIMER 0x19 /* D7 to D0, Read/write */ +#define AD7280A_CB6_TIMER 0x1A /* D7 to D0, Read/write */ +#define AD7280A_PD_TIMER 0x1B /* D7 to D0, Read/write */ +#define AD7280A_READ 0x1C /* D7 to D0, Read/write */ +#define AD7280A_CNVST_CONTROL 0x1D /* D7 to D0, Read/write */ + +/* Bits and Masks */ +#define AD7280A_CTRL_HB_CONV_INPUT_ALL 0 +#define AD7280A_CTRL_HB_CONV_INPUT_6CELL_AUX1_3_4 BIT(6) +#define AD7280A_CTRL_HB_CONV_INPUT_6CELL BIT(7) +#define AD7280A_CTRL_HB_CONV_INPUT_SELF_TEST (BIT(7) | BIT(6)) +#define AD7280A_CTRL_HB_CONV_RES_READ_ALL 0 +#define AD7280A_CTRL_HB_CONV_RES_READ_6CELL_AUX1_3_4 BIT(4) +#define AD7280A_CTRL_HB_CONV_RES_READ_6CELL BIT(5) +#define AD7280A_CTRL_HB_CONV_RES_READ_NO (BIT(5) | BIT(4)) +#define AD7280A_CTRL_HB_CONV_START_CNVST 0 +#define AD7280A_CTRL_HB_CONV_START_CS BIT(3) +#define AD7280A_CTRL_HB_CONV_AVG_DIS 0 +#define AD7280A_CTRL_HB_CONV_AVG_2 BIT(1) +#define AD7280A_CTRL_HB_CONV_AVG_4 BIT(2) +#define AD7280A_CTRL_HB_CONV_AVG_8 (BIT(2) | BIT(1)) +#define AD7280A_CTRL_HB_CONV_AVG(x) ((x) << 1) +#define AD7280A_CTRL_HB_PWRDN_SW BIT(0) + +#define AD7280A_CTRL_LB_SWRST BIT(7) +#define AD7280A_CTRL_LB_ACQ_TIME_400ns 0 +#define AD7280A_CTRL_LB_ACQ_TIME_800ns BIT(5) +#define AD7280A_CTRL_LB_ACQ_TIME_1200ns BIT(6) +#define AD7280A_CTRL_LB_ACQ_TIME_1600ns (BIT(6) | BIT(5)) +#define AD7280A_CTRL_LB_ACQ_TIME(x) ((x) << 5) +#define AD7280A_CTRL_LB_MUST_SET BIT(4) +#define AD7280A_CTRL_LB_THERMISTOR_EN BIT(3) +#define AD7280A_CTRL_LB_LOCK_DEV_ADDR BIT(2) +#define AD7280A_CTRL_LB_INC_DEV_ADDR BIT(1) +#define AD7280A_CTRL_LB_DAISY_CHAIN_RB_EN BIT(0) + +#define AD7280A_ALERT_GEN_STATIC_HIGH BIT(6) +#define AD7280A_ALERT_RELAY_SIG_CHAIN_DOWN (BIT(7) | BIT(6)) + +#define AD7280A_ALL_CELLS (0xAD << 16) + +#define AD7280A_MAX_SPI_CLK_Hz 700000 /* < 1MHz */ +#define AD7280A_MAX_CHAIN 8 +#define AD7280A_CELLS_PER_DEV 6 +#define AD7280A_BITS 12 +#define AD7280A_NUM_CH (AD7280A_AUX_ADC_6 - \ + AD7280A_CELL_VOLTAGE_1 + 1) + +#define AD7280A_DEVADDR_MASTER 0 +#define AD7280A_DEVADDR_ALL 0x1F +/* 5-bit device address is sent LSB first */ +#define AD7280A_DEVADDR(addr) (((addr & 0x1) << 4) | ((addr & 0x2) << 3) | \ + (addr & 0x4) | ((addr & 0x8) >> 3) | \ + ((addr & 0x10) >> 4)) + +/* During a read a valid write is mandatory. + * So writing to the highest available address (Address 0x1F) + * and setting the address all parts bit to 0 is recommended + * So the TXVAL is AD7280A_DEVADDR_ALL + CRC + */ +#define AD7280A_READ_TXVAL 0xF800030A + +/* + * AD7280 CRC + * + * P(x) = x^8 + x^5 + x^3 + x^2 + x^1 + x^0 = 0b100101111 => 0x2F + */ +#define POLYNOM 0x2F +#define POLYNOM_ORDER 8 +#define HIGHBIT (1 << (POLYNOM_ORDER - 1)) + +struct ad7280_state { + struct spi_device *spi; + struct iio_chan_spec *channels; + struct iio_dev_attr *iio_attr; + int slave_num; + int scan_cnt; + int readback_delay_us; + unsigned char crc_tab[256]; + unsigned char ctrl_hb; + unsigned char ctrl_lb; + unsigned char cell_threshhigh; + unsigned char cell_threshlow; + unsigned char aux_threshhigh; + unsigned char aux_threshlow; + unsigned char cb_mask[AD7280A_MAX_CHAIN]; + + __be32 buf[2] ____cacheline_aligned; +}; + +static void ad7280_crc8_build_table(unsigned char *crc_tab) +{ + unsigned char bit, crc; + int cnt, i; + + for (cnt = 0; cnt < 256; cnt++) { + crc = cnt; + for (i = 0; i < 8; i++) { + bit = crc & HIGHBIT; + crc <<= 1; + if (bit) + crc ^= POLYNOM; + } + crc_tab[cnt] = crc; + } +} + +static unsigned char ad7280_calc_crc8(unsigned char *crc_tab, unsigned val) +{ + unsigned char crc; + + crc = crc_tab[val >> 16 & 0xFF]; + crc = crc_tab[crc ^ (val >> 8 & 0xFF)]; + + return crc ^ (val & 0xFF); +} + +static int ad7280_check_crc(struct ad7280_state *st, unsigned val) +{ + unsigned char crc = ad7280_calc_crc8(st->crc_tab, val >> 10); + + if (crc != ((val >> 2) & 0xFF)) + return -EIO; + + return 0; +} + +/* After initiating a conversion sequence we need to wait until the + * conversion is done. The delay is typically in the range of 15..30 us + * however depending an the number of devices in the daisy chain and the + * number of averages taken, conversion delays and acquisition time options + * it may take up to 250us, in this case we better sleep instead of busy + * wait. + */ + +static void ad7280_delay(struct ad7280_state *st) +{ + if (st->readback_delay_us < 50) + udelay(st->readback_delay_us); + else + usleep_range(250, 500); +} + +static int __ad7280_read32(struct ad7280_state *st, unsigned *val) +{ + int ret; + struct spi_transfer t = { + .tx_buf = &st->buf[0], + .rx_buf = &st->buf[1], + .len = 4, + }; + + st->buf[0] = cpu_to_be32(AD7280A_READ_TXVAL); + + ret = spi_sync_transfer(st->spi, &t, 1); + if (ret) + return ret; + + *val = be32_to_cpu(st->buf[1]); + + return 0; +} + +static int ad7280_write(struct ad7280_state *st, unsigned devaddr, + unsigned addr, bool all, unsigned val) +{ + unsigned reg = (devaddr << 27 | addr << 21 | + (val & 0xFF) << 13 | all << 12); + + reg |= ad7280_calc_crc8(st->crc_tab, reg >> 11) << 3 | 0x2; + st->buf[0] = cpu_to_be32(reg); + + return spi_write(st->spi, &st->buf[0], 4); +} + +static int ad7280_read(struct ad7280_state *st, unsigned devaddr, + unsigned addr) +{ + int ret; + unsigned tmp; + + /* turns off the read operation on all parts */ + ret = ad7280_write(st, AD7280A_DEVADDR_MASTER, AD7280A_CONTROL_HB, 1, + AD7280A_CTRL_HB_CONV_INPUT_ALL | + AD7280A_CTRL_HB_CONV_RES_READ_NO | + st->ctrl_hb); + if (ret) + return ret; + + /* turns on the read operation on the addressed part */ + ret = ad7280_write(st, devaddr, AD7280A_CONTROL_HB, 0, + AD7280A_CTRL_HB_CONV_INPUT_ALL | + AD7280A_CTRL_HB_CONV_RES_READ_ALL | + st->ctrl_hb); + if (ret) + return ret; + + /* Set register address on the part to be read from */ + ret = ad7280_write(st, devaddr, AD7280A_READ, 0, addr << 2); + if (ret) + return ret; + + __ad7280_read32(st, &tmp); + + if (ad7280_check_crc(st, tmp)) + return -EIO; + + if (((tmp >> 27) != devaddr) || (((tmp >> 21) & 0x3F) != addr)) + return -EFAULT; + + return (tmp >> 13) & 0xFF; +} + +static int ad7280_read_channel(struct ad7280_state *st, unsigned devaddr, + unsigned addr) +{ + int ret; + unsigned tmp; + + ret = ad7280_write(st, devaddr, AD7280A_READ, 0, addr << 2); + if (ret) + return ret; + + ret = ad7280_write(st, AD7280A_DEVADDR_MASTER, AD7280A_CONTROL_HB, 1, + AD7280A_CTRL_HB_CONV_INPUT_ALL | + AD7280A_CTRL_HB_CONV_RES_READ_NO | + st->ctrl_hb); + if (ret) + return ret; + + ret = ad7280_write(st, devaddr, AD7280A_CONTROL_HB, 0, + AD7280A_CTRL_HB_CONV_INPUT_ALL | + AD7280A_CTRL_HB_CONV_RES_READ_ALL | + AD7280A_CTRL_HB_CONV_START_CS | + st->ctrl_hb); + if (ret) + return ret; + + ad7280_delay(st); + + __ad7280_read32(st, &tmp); + + if (ad7280_check_crc(st, tmp)) + return -EIO; + + if (((tmp >> 27) != devaddr) || (((tmp >> 23) & 0xF) != addr)) + return -EFAULT; + + return (tmp >> 11) & 0xFFF; +} + +static int ad7280_read_all_channels(struct ad7280_state *st, unsigned cnt, + unsigned *array) +{ + int i, ret; + unsigned tmp, sum = 0; + + ret = ad7280_write(st, AD7280A_DEVADDR_MASTER, AD7280A_READ, 1, + AD7280A_CELL_VOLTAGE_1 << 2); + if (ret) + return ret; + + ret = ad7280_write(st, AD7280A_DEVADDR_MASTER, AD7280A_CONTROL_HB, 1, + AD7280A_CTRL_HB_CONV_INPUT_ALL | + AD7280A_CTRL_HB_CONV_RES_READ_ALL | + AD7280A_CTRL_HB_CONV_START_CS | + st->ctrl_hb); + if (ret) + return ret; + + ad7280_delay(st); + + for (i = 0; i < cnt; i++) { + __ad7280_read32(st, &tmp); + + if (ad7280_check_crc(st, tmp)) + return -EIO; + + if (array) + array[i] = tmp; + /* only sum cell voltages */ + if (((tmp >> 23) & 0xF) <= AD7280A_CELL_VOLTAGE_6) + sum += ((tmp >> 11) & 0xFFF); + } + + return sum; +} + +static int ad7280_chain_setup(struct ad7280_state *st) +{ + unsigned val, n; + int ret; + + ret = ad7280_write(st, AD7280A_DEVADDR_MASTER, AD7280A_CONTROL_LB, 1, + AD7280A_CTRL_LB_DAISY_CHAIN_RB_EN | + AD7280A_CTRL_LB_LOCK_DEV_ADDR | + AD7280A_CTRL_LB_MUST_SET | + AD7280A_CTRL_LB_SWRST | + st->ctrl_lb); + if (ret) + return ret; + + ret = ad7280_write(st, AD7280A_DEVADDR_MASTER, AD7280A_CONTROL_LB, 1, + AD7280A_CTRL_LB_DAISY_CHAIN_RB_EN | + AD7280A_CTRL_LB_LOCK_DEV_ADDR | + AD7280A_CTRL_LB_MUST_SET | + st->ctrl_lb); + if (ret) + return ret; + + ret = ad7280_write(st, AD7280A_DEVADDR_MASTER, AD7280A_READ, 1, + AD7280A_CONTROL_LB << 2); + if (ret) + return ret; + + for (n = 0; n <= AD7280A_MAX_CHAIN; n++) { + __ad7280_read32(st, &val); + if (val == 0) + return n - 1; + + if (ad7280_check_crc(st, val)) + return -EIO; + + if (n != AD7280A_DEVADDR(val >> 27)) + return -EIO; + } + + return -EFAULT; +} + +static ssize_t ad7280_show_balance_sw(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct ad7280_state *st = iio_priv(indio_dev); + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + + return sprintf(buf, "%d\n", + !!(st->cb_mask[this_attr->address >> 8] & + (1 << ((this_attr->address & 0xFF) + 2)))); +} + +static ssize_t ad7280_store_balance_sw(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct ad7280_state *st = iio_priv(indio_dev); + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + bool readin; + int ret; + unsigned devaddr, ch; + + ret = strtobool(buf, &readin); + if (ret) + return ret; + + devaddr = this_attr->address >> 8; + ch = this_attr->address & 0xFF; + + mutex_lock(&indio_dev->mlock); + if (readin) + st->cb_mask[devaddr] |= 1 << (ch + 2); + else + st->cb_mask[devaddr] &= ~(1 << (ch + 2)); + + ret = ad7280_write(st, devaddr, AD7280A_CELL_BALANCE, + 0, st->cb_mask[devaddr]); + mutex_unlock(&indio_dev->mlock); + + return ret ? ret : len; +} + +static ssize_t ad7280_show_balance_timer(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct ad7280_state *st = iio_priv(indio_dev); + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + int ret; + unsigned msecs; + + mutex_lock(&indio_dev->mlock); + ret = ad7280_read(st, this_attr->address >> 8, + this_attr->address & 0xFF); + mutex_unlock(&indio_dev->mlock); + + if (ret < 0) + return ret; + + msecs = (ret >> 3) * 71500; + + return sprintf(buf, "%u\n", msecs); +} + +static ssize_t ad7280_store_balance_timer(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct ad7280_state *st = iio_priv(indio_dev); + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + unsigned long val; + int ret; + + ret = kstrtoul(buf, 10, &val); + if (ret) + return ret; + + val /= 71500; + + if (val > 31) + return -EINVAL; + + mutex_lock(&indio_dev->mlock); + ret = ad7280_write(st, this_attr->address >> 8, + this_attr->address & 0xFF, + 0, (val & 0x1F) << 3); + mutex_unlock(&indio_dev->mlock); + + return ret ? ret : len; +} + +static struct attribute *ad7280_attributes[AD7280A_MAX_CHAIN * + AD7280A_CELLS_PER_DEV * 2 + 1]; + +static struct attribute_group ad7280_attrs_group = { + .attrs = ad7280_attributes, +}; + +static int ad7280_channel_init(struct ad7280_state *st) +{ + int dev, ch, cnt; + + st->channels = kcalloc((st->slave_num + 1) * 12 + 2, + sizeof(*st->channels), GFP_KERNEL); + if (st->channels == NULL) + return -ENOMEM; + + for (dev = 0, cnt = 0; dev <= st->slave_num; dev++) + for (ch = AD7280A_CELL_VOLTAGE_1; ch <= AD7280A_AUX_ADC_6; ch++, + cnt++) { + if (ch < AD7280A_AUX_ADC_1) { + st->channels[cnt].type = IIO_VOLTAGE; + st->channels[cnt].differential = 1; + st->channels[cnt].channel = (dev * 6) + ch; + st->channels[cnt].channel2 = + st->channels[cnt].channel + 1; + } else { + st->channels[cnt].type = IIO_TEMP; + st->channels[cnt].channel = (dev * 6) + ch - 6; + } + st->channels[cnt].indexed = 1; + st->channels[cnt].info_mask_separate = + BIT(IIO_CHAN_INFO_RAW); + st->channels[cnt].info_mask_shared_by_type = + BIT(IIO_CHAN_INFO_SCALE); + st->channels[cnt].address = + AD7280A_DEVADDR(dev) << 8 | ch; + st->channels[cnt].scan_index = cnt; + st->channels[cnt].scan_type.sign = 'u'; + st->channels[cnt].scan_type.realbits = 12; + st->channels[cnt].scan_type.storagebits = 32; + st->channels[cnt].scan_type.shift = 0; + } + + st->channels[cnt].type = IIO_VOLTAGE; + st->channels[cnt].differential = 1; + st->channels[cnt].channel = 0; + st->channels[cnt].channel2 = dev * 6; + st->channels[cnt].address = AD7280A_ALL_CELLS; + st->channels[cnt].indexed = 1; + st->channels[cnt].info_mask_separate = BIT(IIO_CHAN_INFO_RAW); + st->channels[cnt].info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE); + st->channels[cnt].scan_index = cnt; + st->channels[cnt].scan_type.sign = 'u'; + st->channels[cnt].scan_type.realbits = 32; + st->channels[cnt].scan_type.storagebits = 32; + st->channels[cnt].scan_type.shift = 0; + cnt++; + st->channels[cnt].type = IIO_TIMESTAMP; + st->channels[cnt].channel = -1; + st->channels[cnt].scan_index = cnt; + st->channels[cnt].scan_type.sign = 's'; + st->channels[cnt].scan_type.realbits = 64; + st->channels[cnt].scan_type.storagebits = 64; + st->channels[cnt].scan_type.shift = 0; + + return cnt + 1; +} + +static int ad7280_attr_init(struct ad7280_state *st) +{ + int dev, ch, cnt; + + st->iio_attr = kcalloc(2, sizeof(*st->iio_attr) * + (st->slave_num + 1) * AD7280A_CELLS_PER_DEV, + GFP_KERNEL); + if (st->iio_attr == NULL) + return -ENOMEM; + + for (dev = 0, cnt = 0; dev <= st->slave_num; dev++) + for (ch = AD7280A_CELL_VOLTAGE_1; ch <= AD7280A_CELL_VOLTAGE_6; + ch++, cnt++) { + st->iio_attr[cnt].address = + AD7280A_DEVADDR(dev) << 8 | ch; + st->iio_attr[cnt].dev_attr.attr.mode = + S_IWUSR | S_IRUGO; + st->iio_attr[cnt].dev_attr.show = + ad7280_show_balance_sw; + st->iio_attr[cnt].dev_attr.store = + ad7280_store_balance_sw; + st->iio_attr[cnt].dev_attr.attr.name = + kasprintf(GFP_KERNEL, + "in%d-in%d_balance_switch_en", + (dev * AD7280A_CELLS_PER_DEV) + ch, + (dev * AD7280A_CELLS_PER_DEV) + ch + 1); + ad7280_attributes[cnt] = + &st->iio_attr[cnt].dev_attr.attr; + cnt++; + st->iio_attr[cnt].address = + AD7280A_DEVADDR(dev) << 8 | + (AD7280A_CB1_TIMER + ch); + st->iio_attr[cnt].dev_attr.attr.mode = + S_IWUSR | S_IRUGO; + st->iio_attr[cnt].dev_attr.show = + ad7280_show_balance_timer; + st->iio_attr[cnt].dev_attr.store = + ad7280_store_balance_timer; + st->iio_attr[cnt].dev_attr.attr.name = + kasprintf(GFP_KERNEL, "in%d-in%d_balance_timer", + (dev * AD7280A_CELLS_PER_DEV) + ch, + (dev * AD7280A_CELLS_PER_DEV) + ch + 1); + ad7280_attributes[cnt] = + &st->iio_attr[cnt].dev_attr.attr; + } + + ad7280_attributes[cnt] = NULL; + + return 0; +} + +static ssize_t ad7280_read_channel_config(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct ad7280_state *st = iio_priv(indio_dev); + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + unsigned val; + + switch ((u32) this_attr->address) { + case AD7280A_CELL_OVERVOLTAGE: + val = 1000 + (st->cell_threshhigh * 1568) / 100; + break; + case AD7280A_CELL_UNDERVOLTAGE: + val = 1000 + (st->cell_threshlow * 1568) / 100; + break; + case AD7280A_AUX_ADC_OVERVOLTAGE: + val = (st->aux_threshhigh * 196) / 10; + break; + case AD7280A_AUX_ADC_UNDERVOLTAGE: + val = (st->aux_threshlow * 196) / 10; + break; + default: + return -EINVAL; + } + + return sprintf(buf, "%u\n", val); +} + +static ssize_t ad7280_write_channel_config(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct ad7280_state *st = iio_priv(indio_dev); + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + + long val; + int ret; + + ret = kstrtol(buf, 10, &val); + if (ret) + return ret; + + switch ((u32) this_attr->address) { + case AD7280A_CELL_OVERVOLTAGE: + case AD7280A_CELL_UNDERVOLTAGE: + val = ((val - 1000) * 100) / 1568; /* LSB 15.68mV */ + break; + case AD7280A_AUX_ADC_OVERVOLTAGE: + case AD7280A_AUX_ADC_UNDERVOLTAGE: + val = (val * 10) / 196; /* LSB 19.6mV */ + break; + default: + return -EFAULT; + } + + val = clamp(val, 0L, 0xFFL); + + mutex_lock(&indio_dev->mlock); + switch ((u32) this_attr->address) { + case AD7280A_CELL_OVERVOLTAGE: + st->cell_threshhigh = val; + break; + case AD7280A_CELL_UNDERVOLTAGE: + st->cell_threshlow = val; + break; + case AD7280A_AUX_ADC_OVERVOLTAGE: + st->aux_threshhigh = val; + break; + case AD7280A_AUX_ADC_UNDERVOLTAGE: + st->aux_threshlow = val; + break; + } + + ret = ad7280_write(st, AD7280A_DEVADDR_MASTER, + this_attr->address, 1, val); + + mutex_unlock(&indio_dev->mlock); + + return ret ? ret : len; +} + +static irqreturn_t ad7280_event_handler(int irq, void *private) +{ + struct iio_dev *indio_dev = private; + struct ad7280_state *st = iio_priv(indio_dev); + unsigned *channels; + int i, ret; + + channels = kcalloc(st->scan_cnt, sizeof(*channels), GFP_KERNEL); + if (channels == NULL) + return IRQ_HANDLED; + + ret = ad7280_read_all_channels(st, st->scan_cnt, channels); + if (ret < 0) + goto out; + + for (i = 0; i < st->scan_cnt; i++) { + if (((channels[i] >> 23) & 0xF) <= AD7280A_CELL_VOLTAGE_6) { + if (((channels[i] >> 11) & 0xFFF) >= + st->cell_threshhigh) + iio_push_event(indio_dev, + IIO_EVENT_CODE(IIO_VOLTAGE, + 1, + 0, + IIO_EV_DIR_RISING, + IIO_EV_TYPE_THRESH, + 0, 0, 0), + iio_get_time_ns()); + else if (((channels[i] >> 11) & 0xFFF) <= + st->cell_threshlow) + iio_push_event(indio_dev, + IIO_EVENT_CODE(IIO_VOLTAGE, + 1, + 0, + IIO_EV_DIR_FALLING, + IIO_EV_TYPE_THRESH, + 0, 0, 0), + iio_get_time_ns()); + } else { + if (((channels[i] >> 11) & 0xFFF) >= st->aux_threshhigh) + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_TEMP, + 0, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_RISING), + iio_get_time_ns()); + else if (((channels[i] >> 11) & 0xFFF) <= + st->aux_threshlow) + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_TEMP, + 0, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_FALLING), + iio_get_time_ns()); + } + } + +out: + kfree(channels); + + return IRQ_HANDLED; +} + +static IIO_DEVICE_ATTR_NAMED(in_thresh_low_value, + in_voltage-voltage_thresh_low_value, + S_IRUGO | S_IWUSR, + ad7280_read_channel_config, + ad7280_write_channel_config, + AD7280A_CELL_UNDERVOLTAGE); + +static IIO_DEVICE_ATTR_NAMED(in_thresh_high_value, + in_voltage-voltage_thresh_high_value, + S_IRUGO | S_IWUSR, + ad7280_read_channel_config, + ad7280_write_channel_config, + AD7280A_CELL_OVERVOLTAGE); + +static IIO_DEVICE_ATTR(in_temp_thresh_low_value, + S_IRUGO | S_IWUSR, + ad7280_read_channel_config, + ad7280_write_channel_config, + AD7280A_AUX_ADC_UNDERVOLTAGE); + +static IIO_DEVICE_ATTR(in_temp_thresh_high_value, + S_IRUGO | S_IWUSR, + ad7280_read_channel_config, + ad7280_write_channel_config, + AD7280A_AUX_ADC_OVERVOLTAGE); + + +static struct attribute *ad7280_event_attributes[] = { + &iio_dev_attr_in_thresh_low_value.dev_attr.attr, + &iio_dev_attr_in_thresh_high_value.dev_attr.attr, + &iio_dev_attr_in_temp_thresh_low_value.dev_attr.attr, + &iio_dev_attr_in_temp_thresh_high_value.dev_attr.attr, + NULL, +}; + +static struct attribute_group ad7280_event_attrs_group = { + .attrs = ad7280_event_attributes, +}; + +static int ad7280_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long m) +{ + struct ad7280_state *st = iio_priv(indio_dev); + int ret; + + switch (m) { + case IIO_CHAN_INFO_RAW: + mutex_lock(&indio_dev->mlock); + if (chan->address == AD7280A_ALL_CELLS) + ret = ad7280_read_all_channels(st, st->scan_cnt, NULL); + else + ret = ad7280_read_channel(st, chan->address >> 8, + chan->address & 0xFF); + mutex_unlock(&indio_dev->mlock); + + if (ret < 0) + return ret; + + *val = ret; + + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + if ((chan->address & 0xFF) <= AD7280A_CELL_VOLTAGE_6) + *val = 4000; + else + *val = 5000; + + *val2 = AD7280A_BITS; + return IIO_VAL_FRACTIONAL_LOG2; + } + return -EINVAL; +} + +static const struct iio_info ad7280_info = { + .read_raw = &ad7280_read_raw, + .event_attrs = &ad7280_event_attrs_group, + .attrs = &ad7280_attrs_group, + .driver_module = THIS_MODULE, +}; + +static const struct ad7280_platform_data ad7793_default_pdata = { + .acquisition_time = AD7280A_ACQ_TIME_400ns, + .conversion_averaging = AD7280A_CONV_AVG_DIS, + .thermistor_term_en = true, +}; + +static int ad7280_probe(struct spi_device *spi) +{ + const struct ad7280_platform_data *pdata = spi->dev.platform_data; + struct ad7280_state *st; + int ret; + const unsigned short tACQ_ns[4] = {465, 1010, 1460, 1890}; + const unsigned short nAVG[4] = {1, 2, 4, 8}; + struct iio_dev *indio_dev; + + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st)); + if (indio_dev == NULL) + return -ENOMEM; + + st = iio_priv(indio_dev); + spi_set_drvdata(spi, indio_dev); + st->spi = spi; + + if (!pdata) + pdata = &ad7793_default_pdata; + + ad7280_crc8_build_table(st->crc_tab); + + st->spi->max_speed_hz = AD7280A_MAX_SPI_CLK_Hz; + st->spi->mode = SPI_MODE_1; + spi_setup(st->spi); + + st->ctrl_lb = AD7280A_CTRL_LB_ACQ_TIME(pdata->acquisition_time & 0x3); + st->ctrl_hb = AD7280A_CTRL_HB_CONV_AVG(pdata->conversion_averaging + & 0x3) | (pdata->thermistor_term_en ? + AD7280A_CTRL_LB_THERMISTOR_EN : 0); + + ret = ad7280_chain_setup(st); + if (ret < 0) + return ret; + + st->slave_num = ret; + st->scan_cnt = (st->slave_num + 1) * AD7280A_NUM_CH; + st->cell_threshhigh = 0xFF; + st->aux_threshhigh = 0xFF; + + /* + * Total Conversion Time = ((tACQ + tCONV) * + * (Number of Conversions per Part)) − + * tACQ + ((N - 1) * tDELAY) + * + * Readback Delay = Total Conversion Time + tWAIT + */ + + st->readback_delay_us = + ((tACQ_ns[pdata->acquisition_time & 0x3] + 695) * + (AD7280A_NUM_CH * nAVG[pdata->conversion_averaging & 0x3])) + - tACQ_ns[pdata->acquisition_time & 0x3] + + st->slave_num * 250; + + /* Convert to usecs */ + st->readback_delay_us = DIV_ROUND_UP(st->readback_delay_us, 1000); + st->readback_delay_us += 5; /* Add tWAIT */ + + indio_dev->name = spi_get_device_id(spi)->name; + indio_dev->dev.parent = &spi->dev; + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = ad7280_channel_init(st); + if (ret < 0) + return ret; + + indio_dev->num_channels = ret; + indio_dev->channels = st->channels; + indio_dev->info = &ad7280_info; + + ret = ad7280_attr_init(st); + if (ret < 0) + goto error_free_channels; + + ret = iio_device_register(indio_dev); + if (ret) + goto error_free_attr; + + if (spi->irq > 0) { + ret = ad7280_write(st, AD7280A_DEVADDR_MASTER, + AD7280A_ALERT, 1, + AD7280A_ALERT_RELAY_SIG_CHAIN_DOWN); + if (ret) + goto error_unregister; + + ret = ad7280_write(st, AD7280A_DEVADDR(st->slave_num), + AD7280A_ALERT, 0, + AD7280A_ALERT_GEN_STATIC_HIGH | + (pdata->chain_last_alert_ignore & 0xF)); + if (ret) + goto error_unregister; + + ret = request_threaded_irq(spi->irq, + NULL, + ad7280_event_handler, + IRQF_TRIGGER_FALLING | + IRQF_ONESHOT, + indio_dev->name, + indio_dev); + if (ret) + goto error_unregister; + } + + return 0; +error_unregister: + iio_device_unregister(indio_dev); + +error_free_attr: + kfree(st->iio_attr); + +error_free_channels: + kfree(st->channels); + + return ret; +} + +static int ad7280_remove(struct spi_device *spi) +{ + struct iio_dev *indio_dev = spi_get_drvdata(spi); + struct ad7280_state *st = iio_priv(indio_dev); + + if (spi->irq > 0) + free_irq(spi->irq, indio_dev); + iio_device_unregister(indio_dev); + + ad7280_write(st, AD7280A_DEVADDR_MASTER, AD7280A_CONTROL_HB, 1, + AD7280A_CTRL_HB_PWRDN_SW | st->ctrl_hb); + + kfree(st->channels); + kfree(st->iio_attr); + + return 0; +} + +static const struct spi_device_id ad7280_id[] = { + {"ad7280a", 0}, + {} +}; +MODULE_DEVICE_TABLE(spi, ad7280_id); + +static struct spi_driver ad7280_driver = { + .driver = { + .name = "ad7280", + .owner = THIS_MODULE, + }, + .probe = ad7280_probe, + .remove = ad7280_remove, + .id_table = ad7280_id, +}; +module_spi_driver(ad7280_driver); + +MODULE_AUTHOR("Michael Hennerich "); +MODULE_DESCRIPTION("Analog Devices AD7280A"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/staging/iio/adc/ad7280a.h b/drivers/staging/iio/adc/ad7280a.h new file mode 100644 index 000000000..732347a9b --- /dev/null +++ b/drivers/staging/iio/adc/ad7280a.h @@ -0,0 +1,38 @@ +/* + * AD7280A Lithium Ion Battery Monitoring System + * + * Copyright 2011 Analog Devices Inc. + * + * Licensed under the GPL-2. + */ + +#ifndef IIO_ADC_AD7280_H_ +#define IIO_ADC_AD7280_H_ + +/* + * TODO: struct ad7280_platform_data needs to go into include/linux/iio + */ + +#define AD7280A_ACQ_TIME_400ns 0 +#define AD7280A_ACQ_TIME_800ns 1 +#define AD7280A_ACQ_TIME_1200ns 2 +#define AD7280A_ACQ_TIME_1600ns 3 + +#define AD7280A_CONV_AVG_DIS 0 +#define AD7280A_CONV_AVG_2 1 +#define AD7280A_CONV_AVG_4 2 +#define AD7280A_CONV_AVG_8 3 + +#define AD7280A_ALERT_REMOVE_VIN5 BIT(2) +#define AD7280A_ALERT_REMOVE_VIN4_VIN5 BIT(3) +#define AD7280A_ALERT_REMOVE_AUX5 BIT(0) +#define AD7280A_ALERT_REMOVE_AUX4_AUX5 BIT(1) + +struct ad7280_platform_data { + unsigned acquisition_time; + unsigned conversion_averaging; + unsigned chain_last_alert_ignore; + bool thermistor_term_en; +}; + +#endif /* IIO_ADC_AD7280_H_ */ diff --git a/drivers/staging/iio/adc/ad7606.h b/drivers/staging/iio/adc/ad7606.h new file mode 100644 index 000000000..ec89d055c --- /dev/null +++ b/drivers/staging/iio/adc/ad7606.h @@ -0,0 +1,104 @@ +/* + * AD7606 ADC driver + * + * Copyright 2011 Analog Devices Inc. + * + * Licensed under the GPL-2. + */ + +#ifndef IIO_ADC_AD7606_H_ +#define IIO_ADC_AD7606_H_ + +/* + * TODO: struct ad7606_platform_data needs to go into include/linux/iio + */ + +/** + * struct ad7606_platform_data - platform/board specific information + * @default_os: default oversampling value {0, 2, 4, 8, 16, 32, 64} + * @default_range: default range +/-{5000, 10000} mVolt + * @gpio_convst: number of gpio connected to the CONVST pin + * @gpio_reset: gpio connected to the RESET pin, if not used set to -1 + * @gpio_range: gpio connected to the RANGE pin, if not used set to -1 + * @gpio_os0: gpio connected to the OS0 pin, if not used set to -1 + * @gpio_os1: gpio connected to the OS1 pin, if not used set to -1 + * @gpio_os2: gpio connected to the OS2 pin, if not used set to -1 + * @gpio_frstdata: gpio connected to the FRSTDAT pin, if not used set to -1 + * @gpio_stby: gpio connected to the STBY pin, if not used set to -1 + */ + +struct ad7606_platform_data { + unsigned default_os; + unsigned default_range; + unsigned gpio_convst; + unsigned gpio_reset; + unsigned gpio_range; + unsigned gpio_os0; + unsigned gpio_os1; + unsigned gpio_os2; + unsigned gpio_frstdata; + unsigned gpio_stby; +}; + +/** + * struct ad7606_chip_info - chip specific information + * @name: identification string for chip + * @int_vref_mv: the internal reference voltage + * @channels: channel specification + * @num_channels: number of channels + */ + +struct ad7606_chip_info { + const char *name; + u16 int_vref_mv; + const struct iio_chan_spec *channels; + unsigned num_channels; +}; + +/** + * struct ad7606_state - driver instance specific data + */ + +struct ad7606_state { + struct device *dev; + const struct ad7606_chip_info *chip_info; + struct ad7606_platform_data *pdata; + struct regulator *reg; + struct work_struct poll_work; + wait_queue_head_t wq_data_avail; + const struct ad7606_bus_ops *bops; + unsigned range; + unsigned oversampling; + bool done; + void __iomem *base_address; + + /* + * DMA (thus cache coherency maintenance) requires the + * transfer buffers to live in their own cache lines. + */ + + unsigned short data[8] ____cacheline_aligned; +}; + +struct ad7606_bus_ops { + /* more methods added in future? */ + int (*read_block)(struct device *, int, void *); +}; + +void ad7606_suspend(struct iio_dev *indio_dev); +void ad7606_resume(struct iio_dev *indio_dev); +struct iio_dev *ad7606_probe(struct device *dev, int irq, + void __iomem *base_address, unsigned id, + const struct ad7606_bus_ops *bops); +int ad7606_remove(struct iio_dev *indio_dev, int irq); +int ad7606_reset(struct ad7606_state *st); + +enum ad7606_supported_device_ids { + ID_AD7606_8, + ID_AD7606_6, + ID_AD7606_4 +}; + +int ad7606_register_ring_funcs_and_init(struct iio_dev *indio_dev); +void ad7606_ring_cleanup(struct iio_dev *indio_dev); +#endif /* IIO_ADC_AD7606_H_ */ diff --git a/drivers/staging/iio/adc/ad7606_core.c b/drivers/staging/iio/adc/ad7606_core.c new file mode 100644 index 000000000..bf2c80131 --- /dev/null +++ b/drivers/staging/iio/adc/ad7606_core.c @@ -0,0 +1,603 @@ +/* + * AD7606 SPI ADC driver + * + * Copyright 2011 Analog Devices Inc. + * + * Licensed under the GPL-2. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "ad7606.h" + +int ad7606_reset(struct ad7606_state *st) +{ + if (gpio_is_valid(st->pdata->gpio_reset)) { + gpio_set_value(st->pdata->gpio_reset, 1); + ndelay(100); /* t_reset >= 100ns */ + gpio_set_value(st->pdata->gpio_reset, 0); + return 0; + } + + return -ENODEV; +} + +static int ad7606_scan_direct(struct iio_dev *indio_dev, unsigned ch) +{ + struct ad7606_state *st = iio_priv(indio_dev); + int ret; + + st->done = false; + gpio_set_value(st->pdata->gpio_convst, 1); + + ret = wait_event_interruptible(st->wq_data_avail, st->done); + if (ret) + goto error_ret; + + if (gpio_is_valid(st->pdata->gpio_frstdata)) { + ret = st->bops->read_block(st->dev, 1, st->data); + if (ret) + goto error_ret; + if (!gpio_get_value(st->pdata->gpio_frstdata)) { + /* This should never happen */ + ad7606_reset(st); + ret = -EIO; + goto error_ret; + } + ret = st->bops->read_block(st->dev, + st->chip_info->num_channels - 1, &st->data[1]); + if (ret) + goto error_ret; + } else { + ret = st->bops->read_block(st->dev, + st->chip_info->num_channels, st->data); + if (ret) + goto error_ret; + } + + ret = st->data[ch]; + +error_ret: + gpio_set_value(st->pdata->gpio_convst, 0); + + return ret; +} + +static int ad7606_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long m) +{ + int ret; + struct ad7606_state *st = iio_priv(indio_dev); + + switch (m) { + case IIO_CHAN_INFO_RAW: + mutex_lock(&indio_dev->mlock); + if (iio_buffer_enabled(indio_dev)) + ret = -EBUSY; + else + ret = ad7606_scan_direct(indio_dev, chan->address); + mutex_unlock(&indio_dev->mlock); + + if (ret < 0) + return ret; + *val = (short) ret; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + *val = st->range * 2; + *val2 = st->chip_info->channels[0].scan_type.realbits; + return IIO_VAL_FRACTIONAL_LOG2; + } + return -EINVAL; +} + +static ssize_t ad7606_show_range(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct ad7606_state *st = iio_priv(indio_dev); + + return sprintf(buf, "%u\n", st->range); +} + +static ssize_t ad7606_store_range(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct ad7606_state *st = iio_priv(indio_dev); + unsigned long lval; + int ret; + + ret = kstrtoul(buf, 10, &lval); + if (ret) + return ret; + + if (!(lval == 5000 || lval == 10000)) { + dev_err(dev, "range is not supported\n"); + return -EINVAL; + } + mutex_lock(&indio_dev->mlock); + gpio_set_value(st->pdata->gpio_range, lval == 10000); + st->range = lval; + mutex_unlock(&indio_dev->mlock); + + return count; +} + +static IIO_DEVICE_ATTR(in_voltage_range, S_IRUGO | S_IWUSR, + ad7606_show_range, ad7606_store_range, 0); +static IIO_CONST_ATTR(in_voltage_range_available, "5000 10000"); + +static ssize_t ad7606_show_oversampling_ratio(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct ad7606_state *st = iio_priv(indio_dev); + + return sprintf(buf, "%u\n", st->oversampling); +} + +static int ad7606_oversampling_get_index(unsigned val) +{ + unsigned char supported[] = {0, 2, 4, 8, 16, 32, 64}; + int i; + + for (i = 0; i < ARRAY_SIZE(supported); i++) + if (val == supported[i]) + return i; + + return -EINVAL; +} + +static ssize_t ad7606_store_oversampling_ratio(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct ad7606_state *st = iio_priv(indio_dev); + unsigned long lval; + int ret; + + ret = kstrtoul(buf, 10, &lval); + if (ret) + return ret; + + ret = ad7606_oversampling_get_index(lval); + if (ret < 0) { + dev_err(dev, "oversampling %lu is not supported\n", lval); + return ret; + } + + mutex_lock(&indio_dev->mlock); + gpio_set_value(st->pdata->gpio_os0, (ret >> 0) & 1); + gpio_set_value(st->pdata->gpio_os1, (ret >> 1) & 1); + gpio_set_value(st->pdata->gpio_os1, (ret >> 2) & 1); + st->oversampling = lval; + mutex_unlock(&indio_dev->mlock); + + return count; +} + +static IIO_DEVICE_ATTR(oversampling_ratio, S_IRUGO | S_IWUSR, + ad7606_show_oversampling_ratio, + ad7606_store_oversampling_ratio, 0); +static IIO_CONST_ATTR(oversampling_ratio_available, "0 2 4 8 16 32 64"); + +static struct attribute *ad7606_attributes_os_and_range[] = { + &iio_dev_attr_in_voltage_range.dev_attr.attr, + &iio_const_attr_in_voltage_range_available.dev_attr.attr, + &iio_dev_attr_oversampling_ratio.dev_attr.attr, + &iio_const_attr_oversampling_ratio_available.dev_attr.attr, + NULL, +}; + +static const struct attribute_group ad7606_attribute_group_os_and_range = { + .attrs = ad7606_attributes_os_and_range, +}; + +static struct attribute *ad7606_attributes_os[] = { + &iio_dev_attr_oversampling_ratio.dev_attr.attr, + &iio_const_attr_oversampling_ratio_available.dev_attr.attr, + NULL, +}; + +static const struct attribute_group ad7606_attribute_group_os = { + .attrs = ad7606_attributes_os, +}; + +static struct attribute *ad7606_attributes_range[] = { + &iio_dev_attr_in_voltage_range.dev_attr.attr, + &iio_const_attr_in_voltage_range_available.dev_attr.attr, + NULL, +}; + +static const struct attribute_group ad7606_attribute_group_range = { + .attrs = ad7606_attributes_range, +}; + +#define AD7606_CHANNEL(num) \ + { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .channel = num, \ + .address = num, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE),\ + .scan_index = num, \ + .scan_type = { \ + .sign = 's', \ + .realbits = 16, \ + .storagebits = 16, \ + .endianness = IIO_CPU, \ + }, \ + } + +static const struct iio_chan_spec ad7606_8_channels[] = { + AD7606_CHANNEL(0), + AD7606_CHANNEL(1), + AD7606_CHANNEL(2), + AD7606_CHANNEL(3), + AD7606_CHANNEL(4), + AD7606_CHANNEL(5), + AD7606_CHANNEL(6), + AD7606_CHANNEL(7), + IIO_CHAN_SOFT_TIMESTAMP(8), +}; + +static const struct iio_chan_spec ad7606_6_channels[] = { + AD7606_CHANNEL(0), + AD7606_CHANNEL(1), + AD7606_CHANNEL(2), + AD7606_CHANNEL(3), + AD7606_CHANNEL(4), + AD7606_CHANNEL(5), + IIO_CHAN_SOFT_TIMESTAMP(6), +}; + +static const struct iio_chan_spec ad7606_4_channels[] = { + AD7606_CHANNEL(0), + AD7606_CHANNEL(1), + AD7606_CHANNEL(2), + AD7606_CHANNEL(3), + IIO_CHAN_SOFT_TIMESTAMP(4), +}; + +static const struct ad7606_chip_info ad7606_chip_info_tbl[] = { + /* + * More devices added in future + */ + [ID_AD7606_8] = { + .name = "ad7606", + .int_vref_mv = 2500, + .channels = ad7606_8_channels, + .num_channels = 8, + }, + [ID_AD7606_6] = { + .name = "ad7606-6", + .int_vref_mv = 2500, + .channels = ad7606_6_channels, + .num_channels = 6, + }, + [ID_AD7606_4] = { + .name = "ad7606-4", + .int_vref_mv = 2500, + .channels = ad7606_4_channels, + .num_channels = 4, + }, +}; + +static int ad7606_request_gpios(struct ad7606_state *st) +{ + struct gpio gpio_array[3] = { + [0] = { + .gpio = st->pdata->gpio_os0, + .flags = GPIOF_DIR_OUT | ((st->oversampling & 1) ? + GPIOF_INIT_HIGH : GPIOF_INIT_LOW), + .label = "AD7606_OS0", + }, + [1] = { + .gpio = st->pdata->gpio_os1, + .flags = GPIOF_DIR_OUT | ((st->oversampling & 2) ? + GPIOF_INIT_HIGH : GPIOF_INIT_LOW), + .label = "AD7606_OS1", + }, + [2] = { + .gpio = st->pdata->gpio_os2, + .flags = GPIOF_DIR_OUT | ((st->oversampling & 4) ? + GPIOF_INIT_HIGH : GPIOF_INIT_LOW), + .label = "AD7606_OS2", + }, + }; + int ret; + + if (gpio_is_valid(st->pdata->gpio_convst)) { + ret = gpio_request_one(st->pdata->gpio_convst, + GPIOF_OUT_INIT_LOW, + "AD7606_CONVST"); + if (ret) { + dev_err(st->dev, "failed to request GPIO CONVST\n"); + goto error_ret; + } + } else { + ret = -EIO; + goto error_ret; + } + + if (gpio_is_valid(st->pdata->gpio_os0) && + gpio_is_valid(st->pdata->gpio_os1) && + gpio_is_valid(st->pdata->gpio_os2)) { + ret = gpio_request_array(gpio_array, ARRAY_SIZE(gpio_array)); + if (ret < 0) + goto error_free_convst; + } + + if (gpio_is_valid(st->pdata->gpio_reset)) { + ret = gpio_request_one(st->pdata->gpio_reset, + GPIOF_OUT_INIT_LOW, + "AD7606_RESET"); + if (ret < 0) + goto error_free_os; + } + + if (gpio_is_valid(st->pdata->gpio_range)) { + ret = gpio_request_one(st->pdata->gpio_range, GPIOF_DIR_OUT | + ((st->range == 10000) ? GPIOF_INIT_HIGH : + GPIOF_INIT_LOW), "AD7606_RANGE"); + if (ret < 0) + goto error_free_reset; + } + if (gpio_is_valid(st->pdata->gpio_stby)) { + ret = gpio_request_one(st->pdata->gpio_stby, + GPIOF_OUT_INIT_HIGH, + "AD7606_STBY"); + if (ret < 0) + goto error_free_range; + } + + if (gpio_is_valid(st->pdata->gpio_frstdata)) { + ret = gpio_request_one(st->pdata->gpio_frstdata, GPIOF_IN, + "AD7606_FRSTDATA"); + if (ret < 0) + goto error_free_stby; + } + + return 0; + +error_free_stby: + if (gpio_is_valid(st->pdata->gpio_stby)) + gpio_free(st->pdata->gpio_stby); +error_free_range: + if (gpio_is_valid(st->pdata->gpio_range)) + gpio_free(st->pdata->gpio_range); +error_free_reset: + if (gpio_is_valid(st->pdata->gpio_reset)) + gpio_free(st->pdata->gpio_reset); +error_free_os: + if (gpio_is_valid(st->pdata->gpio_os0) && + gpio_is_valid(st->pdata->gpio_os1) && + gpio_is_valid(st->pdata->gpio_os2)) + gpio_free_array(gpio_array, ARRAY_SIZE(gpio_array)); +error_free_convst: + gpio_free(st->pdata->gpio_convst); +error_ret: + return ret; +} + +static void ad7606_free_gpios(struct ad7606_state *st) +{ + if (gpio_is_valid(st->pdata->gpio_frstdata)) + gpio_free(st->pdata->gpio_frstdata); + if (gpio_is_valid(st->pdata->gpio_stby)) + gpio_free(st->pdata->gpio_stby); + if (gpio_is_valid(st->pdata->gpio_range)) + gpio_free(st->pdata->gpio_range); + if (gpio_is_valid(st->pdata->gpio_reset)) + gpio_free(st->pdata->gpio_reset); + if (gpio_is_valid(st->pdata->gpio_os0) && + gpio_is_valid(st->pdata->gpio_os1) && + gpio_is_valid(st->pdata->gpio_os2)) { + gpio_free(st->pdata->gpio_os2); + gpio_free(st->pdata->gpio_os1); + gpio_free(st->pdata->gpio_os0); + } + gpio_free(st->pdata->gpio_convst); +} + +/** + * Interrupt handler + */ +static irqreturn_t ad7606_interrupt(int irq, void *dev_id) +{ + struct iio_dev *indio_dev = dev_id; + struct ad7606_state *st = iio_priv(indio_dev); + + if (iio_buffer_enabled(indio_dev)) { + schedule_work(&st->poll_work); + } else { + st->done = true; + wake_up_interruptible(&st->wq_data_avail); + } + + return IRQ_HANDLED; +}; + +static const struct iio_info ad7606_info_no_os_or_range = { + .driver_module = THIS_MODULE, + .read_raw = &ad7606_read_raw, +}; + +static const struct iio_info ad7606_info_os_and_range = { + .driver_module = THIS_MODULE, + .read_raw = &ad7606_read_raw, + .attrs = &ad7606_attribute_group_os_and_range, +}; + +static const struct iio_info ad7606_info_os = { + .driver_module = THIS_MODULE, + .read_raw = &ad7606_read_raw, + .attrs = &ad7606_attribute_group_os, +}; + +static const struct iio_info ad7606_info_range = { + .driver_module = THIS_MODULE, + .read_raw = &ad7606_read_raw, + .attrs = &ad7606_attribute_group_range, +}; + +struct iio_dev *ad7606_probe(struct device *dev, int irq, + void __iomem *base_address, + unsigned id, + const struct ad7606_bus_ops *bops) +{ + struct ad7606_platform_data *pdata = dev->platform_data; + struct ad7606_state *st; + int ret; + struct iio_dev *indio_dev; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*st)); + if (!indio_dev) + return ERR_PTR(-ENOMEM); + + st = iio_priv(indio_dev); + + st->dev = dev; + st->bops = bops; + st->base_address = base_address; + st->range = pdata->default_range == 10000 ? 10000 : 5000; + + ret = ad7606_oversampling_get_index(pdata->default_os); + if (ret < 0) { + dev_warn(dev, "oversampling %d is not supported\n", + pdata->default_os); + st->oversampling = 0; + } else { + st->oversampling = pdata->default_os; + } + + st->reg = devm_regulator_get(dev, "vcc"); + if (!IS_ERR(st->reg)) { + ret = regulator_enable(st->reg); + if (ret) + return ERR_PTR(ret); + } + + st->pdata = pdata; + st->chip_info = &ad7606_chip_info_tbl[id]; + + indio_dev->dev.parent = dev; + if (gpio_is_valid(st->pdata->gpio_os0) && + gpio_is_valid(st->pdata->gpio_os1) && + gpio_is_valid(st->pdata->gpio_os2)) { + if (gpio_is_valid(st->pdata->gpio_range)) + indio_dev->info = &ad7606_info_os_and_range; + else + indio_dev->info = &ad7606_info_os; + } else { + if (gpio_is_valid(st->pdata->gpio_range)) + indio_dev->info = &ad7606_info_range; + else + indio_dev->info = &ad7606_info_no_os_or_range; + } + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->name = st->chip_info->name; + indio_dev->channels = st->chip_info->channels; + indio_dev->num_channels = st->chip_info->num_channels; + + init_waitqueue_head(&st->wq_data_avail); + + ret = ad7606_request_gpios(st); + if (ret) + goto error_disable_reg; + + ret = ad7606_reset(st); + if (ret) + dev_warn(st->dev, "failed to RESET: no RESET GPIO specified\n"); + + ret = request_irq(irq, ad7606_interrupt, + IRQF_TRIGGER_FALLING, st->chip_info->name, indio_dev); + if (ret) + goto error_free_gpios; + + ret = ad7606_register_ring_funcs_and_init(indio_dev); + if (ret) + goto error_free_irq; + + ret = iio_device_register(indio_dev); + if (ret) + goto error_unregister_ring; + + return indio_dev; +error_unregister_ring: + ad7606_ring_cleanup(indio_dev); + +error_free_irq: + free_irq(irq, indio_dev); + +error_free_gpios: + ad7606_free_gpios(st); + +error_disable_reg: + if (!IS_ERR(st->reg)) + regulator_disable(st->reg); + return ERR_PTR(ret); +} + +int ad7606_remove(struct iio_dev *indio_dev, int irq) +{ + struct ad7606_state *st = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + ad7606_ring_cleanup(indio_dev); + + free_irq(irq, indio_dev); + if (!IS_ERR(st->reg)) + regulator_disable(st->reg); + + ad7606_free_gpios(st); + + return 0; +} + +void ad7606_suspend(struct iio_dev *indio_dev) +{ + struct ad7606_state *st = iio_priv(indio_dev); + + if (gpio_is_valid(st->pdata->gpio_stby)) { + if (gpio_is_valid(st->pdata->gpio_range)) + gpio_set_value(st->pdata->gpio_range, 1); + gpio_set_value(st->pdata->gpio_stby, 0); + } +} + +void ad7606_resume(struct iio_dev *indio_dev) +{ + struct ad7606_state *st = iio_priv(indio_dev); + + if (gpio_is_valid(st->pdata->gpio_stby)) { + if (gpio_is_valid(st->pdata->gpio_range)) + gpio_set_value(st->pdata->gpio_range, + st->range == 10000); + + gpio_set_value(st->pdata->gpio_stby, 1); + ad7606_reset(st); + } +} + +MODULE_AUTHOR("Michael Hennerich "); +MODULE_DESCRIPTION("Analog Devices AD7606 ADC"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/staging/iio/adc/ad7606_par.c b/drivers/staging/iio/adc/ad7606_par.c new file mode 100644 index 000000000..9e24b4d44 --- /dev/null +++ b/drivers/staging/iio/adc/ad7606_par.c @@ -0,0 +1,152 @@ +/* + * AD7606 Parallel Interface ADC driver + * + * Copyright 2011 Analog Devices Inc. + * + * Licensed under the GPL-2. + */ + +#include +#include +#include +#include +#include + +#include +#include "ad7606.h" + +static int ad7606_par16_read_block(struct device *dev, + int count, void *buf) +{ + struct platform_device *pdev = to_platform_device(dev); + struct iio_dev *indio_dev = platform_get_drvdata(pdev); + struct ad7606_state *st = iio_priv(indio_dev); + + insw((unsigned long) st->base_address, buf, count); + + return 0; +} + +static const struct ad7606_bus_ops ad7606_par16_bops = { + .read_block = ad7606_par16_read_block, +}; + +static int ad7606_par8_read_block(struct device *dev, + int count, void *buf) +{ + struct platform_device *pdev = to_platform_device(dev); + struct iio_dev *indio_dev = platform_get_drvdata(pdev); + struct ad7606_state *st = iio_priv(indio_dev); + + insb((unsigned long) st->base_address, buf, count * 2); + + return 0; +} + +static const struct ad7606_bus_ops ad7606_par8_bops = { + .read_block = ad7606_par8_read_block, +}; + +static int ad7606_par_probe(struct platform_device *pdev) +{ + struct resource *res; + struct iio_dev *indio_dev; + void __iomem *addr; + resource_size_t remap_size; + int irq; + + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + dev_err(&pdev->dev, "no irq\n"); + return -ENODEV; + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + addr = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(addr)) + return PTR_ERR(addr); + + remap_size = resource_size(res); + + indio_dev = ad7606_probe(&pdev->dev, irq, addr, + platform_get_device_id(pdev)->driver_data, + remap_size > 1 ? &ad7606_par16_bops : + &ad7606_par8_bops); + + if (IS_ERR(indio_dev)) + return PTR_ERR(indio_dev); + + platform_set_drvdata(pdev, indio_dev); + + return 0; +} + +static int ad7606_par_remove(struct platform_device *pdev) +{ + struct iio_dev *indio_dev = platform_get_drvdata(pdev); + + ad7606_remove(indio_dev, platform_get_irq(pdev, 0)); + + return 0; +} + +#ifdef CONFIG_PM +static int ad7606_par_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + + ad7606_suspend(indio_dev); + + return 0; +} + +static int ad7606_par_resume(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + + ad7606_resume(indio_dev); + + return 0; +} + +static const struct dev_pm_ops ad7606_pm_ops = { + .suspend = ad7606_par_suspend, + .resume = ad7606_par_resume, +}; +#define AD7606_PAR_PM_OPS (&ad7606_pm_ops) + +#else +#define AD7606_PAR_PM_OPS NULL +#endif /* CONFIG_PM */ + +static struct platform_device_id ad7606_driver_ids[] = { + { + .name = "ad7606-8", + .driver_data = ID_AD7606_8, + }, { + .name = "ad7606-6", + .driver_data = ID_AD7606_6, + }, { + .name = "ad7606-4", + .driver_data = ID_AD7606_4, + }, + { } +}; + +MODULE_DEVICE_TABLE(platform, ad7606_driver_ids); + +static struct platform_driver ad7606_driver = { + .probe = ad7606_par_probe, + .remove = ad7606_par_remove, + .id_table = ad7606_driver_ids, + .driver = { + .name = "ad7606", + .pm = AD7606_PAR_PM_OPS, + }, +}; + +module_platform_driver(ad7606_driver); + +MODULE_AUTHOR("Michael Hennerich "); +MODULE_DESCRIPTION("Analog Devices AD7606 ADC"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/staging/iio/adc/ad7606_ring.c b/drivers/staging/iio/adc/ad7606_ring.c new file mode 100644 index 000000000..a6f8eb112 --- /dev/null +++ b/drivers/staging/iio/adc/ad7606_ring.c @@ -0,0 +1,101 @@ +/* + * Copyright 2011-2012 Analog Devices Inc. + * + * Licensed under the GPL-2. + * + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "ad7606.h" + +/** + * ad7606_trigger_handler_th() th/bh of trigger launched polling to ring buffer + * + **/ +static irqreturn_t ad7606_trigger_handler_th_bh(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct ad7606_state *st = iio_priv(pf->indio_dev); + + gpio_set_value(st->pdata->gpio_convst, 1); + + return IRQ_HANDLED; +} + +/** + * ad7606_poll_bh_to_ring() bh of trigger launched polling to ring buffer + * @work_s: the work struct through which this was scheduled + * + * Currently there is no option in this driver to disable the saving of + * timestamps within the ring. + * I think the one copy of this at a time was to avoid problems if the + * trigger was set far too high and the reads then locked up the computer. + **/ +static void ad7606_poll_bh_to_ring(struct work_struct *work_s) +{ + struct ad7606_state *st = container_of(work_s, struct ad7606_state, + poll_work); + struct iio_dev *indio_dev = iio_priv_to_dev(st); + __u8 *buf; + int ret; + + buf = kzalloc(indio_dev->scan_bytes, GFP_KERNEL); + if (!buf) + return; + + if (gpio_is_valid(st->pdata->gpio_frstdata)) { + ret = st->bops->read_block(st->dev, 1, buf); + if (ret) + goto done; + if (!gpio_get_value(st->pdata->gpio_frstdata)) { + /* This should never happen. However + * some signal glitch caused by bad PCB desgin or + * electrostatic discharge, could cause an extra read + * or clock. This allows recovery. + */ + ad7606_reset(st); + goto done; + } + ret = st->bops->read_block(st->dev, + st->chip_info->num_channels - 1, buf + 2); + if (ret) + goto done; + } else { + ret = st->bops->read_block(st->dev, + st->chip_info->num_channels, buf); + if (ret) + goto done; + } + + iio_push_to_buffers_with_timestamp(indio_dev, buf, iio_get_time_ns()); +done: + gpio_set_value(st->pdata->gpio_convst, 0); + iio_trigger_notify_done(indio_dev->trig); + kfree(buf); +} + +int ad7606_register_ring_funcs_and_init(struct iio_dev *indio_dev) +{ + struct ad7606_state *st = iio_priv(indio_dev); + + INIT_WORK(&st->poll_work, &ad7606_poll_bh_to_ring); + + return iio_triggered_buffer_setup(indio_dev, + &ad7606_trigger_handler_th_bh, &ad7606_trigger_handler_th_bh, + NULL); +} + +void ad7606_ring_cleanup(struct iio_dev *indio_dev) +{ + iio_triggered_buffer_cleanup(indio_dev); +} diff --git a/drivers/staging/iio/adc/ad7606_spi.c b/drivers/staging/iio/adc/ad7606_spi.c new file mode 100644 index 000000000..7303983e6 --- /dev/null +++ b/drivers/staging/iio/adc/ad7606_spi.c @@ -0,0 +1,116 @@ +/* + * AD7606 SPI ADC driver + * + * Copyright 2011 Analog Devices Inc. + * + * Licensed under the GPL-2. + */ + +#include +#include +#include +#include + +#include +#include "ad7606.h" + +#define MAX_SPI_FREQ_HZ 23500000 /* VDRIVE above 4.75 V */ + +static int ad7606_spi_read_block(struct device *dev, + int count, void *buf) +{ + struct spi_device *spi = to_spi_device(dev); + int i, ret; + unsigned short *data = buf; + + ret = spi_read(spi, buf, count * 2); + if (ret < 0) { + dev_err(&spi->dev, "SPI read error\n"); + return ret; + } + + for (i = 0; i < count; i++) + data[i] = be16_to_cpu(data[i]); + + return 0; +} + +static const struct ad7606_bus_ops ad7606_spi_bops = { + .read_block = ad7606_spi_read_block, +}; + +static int ad7606_spi_probe(struct spi_device *spi) +{ + struct iio_dev *indio_dev; + + indio_dev = ad7606_probe(&spi->dev, spi->irq, NULL, + spi_get_device_id(spi)->driver_data, + &ad7606_spi_bops); + + if (IS_ERR(indio_dev)) + return PTR_ERR(indio_dev); + + spi_set_drvdata(spi, indio_dev); + + return 0; +} + +static int ad7606_spi_remove(struct spi_device *spi) +{ + struct iio_dev *indio_dev = dev_get_drvdata(&spi->dev); + + return ad7606_remove(indio_dev, spi->irq); +} + +#ifdef CONFIG_PM +static int ad7606_spi_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + + ad7606_suspend(indio_dev); + + return 0; +} + +static int ad7606_spi_resume(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + + ad7606_resume(indio_dev); + + return 0; +} + +static const struct dev_pm_ops ad7606_pm_ops = { + .suspend = ad7606_spi_suspend, + .resume = ad7606_spi_resume, +}; +#define AD7606_SPI_PM_OPS (&ad7606_pm_ops) + +#else +#define AD7606_SPI_PM_OPS NULL +#endif + +static const struct spi_device_id ad7606_id[] = { + {"ad7606-8", ID_AD7606_8}, + {"ad7606-6", ID_AD7606_6}, + {"ad7606-4", ID_AD7606_4}, + {} +}; +MODULE_DEVICE_TABLE(spi, ad7606_id); + +static struct spi_driver ad7606_driver = { + .driver = { + .name = "ad7606", + .owner = THIS_MODULE, + .pm = AD7606_SPI_PM_OPS, + }, + .probe = ad7606_spi_probe, + .remove = ad7606_spi_remove, + .id_table = ad7606_id, +}; +module_spi_driver(ad7606_driver); + +MODULE_AUTHOR("Michael Hennerich "); +MODULE_DESCRIPTION("Analog Devices AD7606 ADC"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/staging/iio/adc/ad7780.c b/drivers/staging/iio/adc/ad7780.c new file mode 100644 index 000000000..9f03fe3ee --- /dev/null +++ b/drivers/staging/iio/adc/ad7780.c @@ -0,0 +1,276 @@ +/* + * AD7170/AD7171 and AD7780/AD7781 SPI ADC driver + * + * Copyright 2011 Analog Devices Inc. + * + * Licensed under the GPL-2. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "ad7780.h" + +#define AD7780_RDY BIT(7) +#define AD7780_FILTER BIT(6) +#define AD7780_ERR BIT(5) +#define AD7780_ID1 BIT(4) +#define AD7780_ID0 BIT(3) +#define AD7780_GAIN BIT(2) +#define AD7780_PAT1 BIT(1) +#define AD7780_PAT0 BIT(0) + +struct ad7780_chip_info { + struct iio_chan_spec channel; + unsigned int pattern_mask; + unsigned int pattern; +}; + +struct ad7780_state { + const struct ad7780_chip_info *chip_info; + struct regulator *reg; + int powerdown_gpio; + unsigned int gain; + u16 int_vref_mv; + + struct ad_sigma_delta sd; +}; + +enum ad7780_supported_device_ids { + ID_AD7170, + ID_AD7171, + ID_AD7780, + ID_AD7781, +}; + +static struct ad7780_state *ad_sigma_delta_to_ad7780(struct ad_sigma_delta *sd) +{ + return container_of(sd, struct ad7780_state, sd); +} + +static int ad7780_set_mode(struct ad_sigma_delta *sigma_delta, + enum ad_sigma_delta_mode mode) +{ + struct ad7780_state *st = ad_sigma_delta_to_ad7780(sigma_delta); + unsigned val; + + switch (mode) { + case AD_SD_MODE_SINGLE: + case AD_SD_MODE_CONTINUOUS: + val = 1; + break; + default: + val = 0; + break; + } + + if (gpio_is_valid(st->powerdown_gpio)) + gpio_set_value(st->powerdown_gpio, val); + + return 0; +} + +static int ad7780_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long m) +{ + struct ad7780_state *st = iio_priv(indio_dev); + + switch (m) { + case IIO_CHAN_INFO_RAW: + return ad_sigma_delta_single_conversion(indio_dev, chan, val); + case IIO_CHAN_INFO_SCALE: + *val = st->int_vref_mv * st->gain; + *val2 = chan->scan_type.realbits - 1; + return IIO_VAL_FRACTIONAL_LOG2; + case IIO_CHAN_INFO_OFFSET: + *val -= (1 << (chan->scan_type.realbits - 1)); + return IIO_VAL_INT; + } + + return -EINVAL; +} + +static int ad7780_postprocess_sample(struct ad_sigma_delta *sigma_delta, + unsigned int raw_sample) +{ + struct ad7780_state *st = ad_sigma_delta_to_ad7780(sigma_delta); + const struct ad7780_chip_info *chip_info = st->chip_info; + + if ((raw_sample & AD7780_ERR) || + ((raw_sample & chip_info->pattern_mask) != chip_info->pattern)) + return -EIO; + + if (raw_sample & AD7780_GAIN) + st->gain = 1; + else + st->gain = 128; + + return 0; +} + +static const struct ad_sigma_delta_info ad7780_sigma_delta_info = { + .set_mode = ad7780_set_mode, + .postprocess_sample = ad7780_postprocess_sample, + .has_registers = false, +}; + +#define AD7780_CHANNEL(bits, wordsize) \ + AD_SD_CHANNEL(1, 0, 0, bits, 32, wordsize - bits) + +static const struct ad7780_chip_info ad7780_chip_info_tbl[] = { + [ID_AD7170] = { + .channel = AD7780_CHANNEL(12, 24), + .pattern = 0x5, + .pattern_mask = 0x7, + }, + [ID_AD7171] = { + .channel = AD7780_CHANNEL(16, 24), + .pattern = 0x5, + .pattern_mask = 0x7, + }, + [ID_AD7780] = { + .channel = AD7780_CHANNEL(24, 32), + .pattern = 0x1, + .pattern_mask = 0x3, + }, + [ID_AD7781] = { + .channel = AD7780_CHANNEL(20, 32), + .pattern = 0x1, + .pattern_mask = 0x3, + }, +}; + +static const struct iio_info ad7780_info = { + .read_raw = &ad7780_read_raw, + .driver_module = THIS_MODULE, +}; + +static int ad7780_probe(struct spi_device *spi) +{ + struct ad7780_platform_data *pdata = spi->dev.platform_data; + struct ad7780_state *st; + struct iio_dev *indio_dev; + int ret, voltage_uv = 0; + + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + + st = iio_priv(indio_dev); + st->gain = 1; + + ad_sd_init(&st->sd, indio_dev, spi, &ad7780_sigma_delta_info); + + st->reg = devm_regulator_get(&spi->dev, "vcc"); + if (!IS_ERR(st->reg)) { + ret = regulator_enable(st->reg); + if (ret) + return ret; + + voltage_uv = regulator_get_voltage(st->reg); + } + + st->chip_info = + &ad7780_chip_info_tbl[spi_get_device_id(spi)->driver_data]; + + if (pdata && pdata->vref_mv) + st->int_vref_mv = pdata->vref_mv; + else if (voltage_uv) + st->int_vref_mv = voltage_uv / 1000; + else + dev_warn(&spi->dev, "reference voltage unspecified\n"); + + spi_set_drvdata(spi, indio_dev); + + indio_dev->dev.parent = &spi->dev; + indio_dev->name = spi_get_device_id(spi)->name; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = &st->chip_info->channel; + indio_dev->num_channels = 1; + indio_dev->info = &ad7780_info; + + if (pdata && gpio_is_valid(pdata->gpio_pdrst)) { + + ret = devm_gpio_request_one(&spi->dev, pdata->gpio_pdrst, + GPIOF_OUT_INIT_LOW, "AD7780 /PDRST"); + if (ret) { + dev_err(&spi->dev, "failed to request GPIO PDRST\n"); + goto error_disable_reg; + } + st->powerdown_gpio = pdata->gpio_pdrst; + } else { + st->powerdown_gpio = -1; + } + + ret = ad_sd_setup_buffer_and_trigger(indio_dev); + if (ret) + goto error_disable_reg; + + ret = iio_device_register(indio_dev); + if (ret) + goto error_cleanup_buffer_and_trigger; + + return 0; + +error_cleanup_buffer_and_trigger: + ad_sd_cleanup_buffer_and_trigger(indio_dev); +error_disable_reg: + if (!IS_ERR(st->reg)) + regulator_disable(st->reg); + + return ret; +} + +static int ad7780_remove(struct spi_device *spi) +{ + struct iio_dev *indio_dev = spi_get_drvdata(spi); + struct ad7780_state *st = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + ad_sd_cleanup_buffer_and_trigger(indio_dev); + + if (!IS_ERR(st->reg)) + regulator_disable(st->reg); + + return 0; +} + +static const struct spi_device_id ad7780_id[] = { + {"ad7170", ID_AD7170}, + {"ad7171", ID_AD7171}, + {"ad7780", ID_AD7780}, + {"ad7781", ID_AD7781}, + {} +}; +MODULE_DEVICE_TABLE(spi, ad7780_id); + +static struct spi_driver ad7780_driver = { + .driver = { + .name = "ad7780", + .owner = THIS_MODULE, + }, + .probe = ad7780_probe, + .remove = ad7780_remove, + .id_table = ad7780_id, +}; +module_spi_driver(ad7780_driver); + +MODULE_AUTHOR("Michael Hennerich "); +MODULE_DESCRIPTION("Analog Devices AD7780 and similar ADCs"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/staging/iio/adc/ad7780.h b/drivers/staging/iio/adc/ad7780.h new file mode 100644 index 000000000..67e511c3d --- /dev/null +++ b/drivers/staging/iio/adc/ad7780.h @@ -0,0 +1,30 @@ +/* + * AD7780/AD7781 SPI ADC driver + * + * Copyright 2011 Analog Devices Inc. + * + * Licensed under the GPL-2. + */ +#ifndef IIO_ADC_AD7780_H_ +#define IIO_ADC_AD7780_H_ + +/* + * TODO: struct ad7780_platform_data needs to go into include/linux/iio + */ + +/* NOTE: + * The AD7780 doesn't feature a dedicated SPI chip select, in addition it + * features a dual use data out ready DOUT/RDY output. + * In order to avoid contentions on the SPI bus, it's therefore necessary + * to use spi bus locking combined with a dedicated GPIO to control the + * power down reset signal of the AD7780. + * + * The DOUT/RDY output must also be wired to an interrupt capable GPIO. + */ + +struct ad7780_platform_data { + u16 vref_mv; + int gpio_pdrst; +}; + +#endif /* IIO_ADC_AD7780_H_ */ diff --git a/drivers/staging/iio/adc/ad7816.c b/drivers/staging/iio/adc/ad7816.c new file mode 100644 index 000000000..48b1c3740 --- /dev/null +++ b/drivers/staging/iio/adc/ad7816.c @@ -0,0 +1,446 @@ +/* + * AD7816 digital temperature sensor driver supporting AD7816/7/8 + * + * Copyright 2010 Analog Devices Inc. + * + * Licensed under the GPL-2 or later. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +/* + * AD7816 config masks + */ +#define AD7816_FULL 0x1 +#define AD7816_PD 0x2 +#define AD7816_CS_MASK 0x7 +#define AD7816_CS_MAX 0x4 + +/* + * AD7816 temperature masks + */ +#define AD7816_VALUE_OFFSET 6 +#define AD7816_BOUND_VALUE_BASE 0x8 +#define AD7816_BOUND_VALUE_MIN -95 +#define AD7816_BOUND_VALUE_MAX 152 +#define AD7816_TEMP_FLOAT_OFFSET 2 +#define AD7816_TEMP_FLOAT_MASK 0x3 + + +/* + * struct ad7816_chip_info - chip specific information + */ + +struct ad7816_chip_info { + struct spi_device *spi_dev; + u16 rdwr_pin; + u16 convert_pin; + u16 busy_pin; + u8 oti_data[AD7816_CS_MAX+1]; + u8 channel_id; /* 0 always be temperature */ + u8 mode; +}; + +/* + * ad7816 data access by SPI + */ +static int ad7816_spi_read(struct ad7816_chip_info *chip, u16 *data) +{ + struct spi_device *spi_dev = chip->spi_dev; + int ret = 0; + + gpio_set_value(chip->rdwr_pin, 1); + gpio_set_value(chip->rdwr_pin, 0); + ret = spi_write(spi_dev, &chip->channel_id, sizeof(chip->channel_id)); + if (ret < 0) { + dev_err(&spi_dev->dev, "SPI channel setting error\n"); + return ret; + } + gpio_set_value(chip->rdwr_pin, 1); + + + if (chip->mode == AD7816_PD) { /* operating mode 2 */ + gpio_set_value(chip->convert_pin, 1); + gpio_set_value(chip->convert_pin, 0); + } else { /* operating mode 1 */ + gpio_set_value(chip->convert_pin, 0); + gpio_set_value(chip->convert_pin, 1); + } + + while (gpio_get_value(chip->busy_pin)) + cpu_relax(); + + gpio_set_value(chip->rdwr_pin, 0); + gpio_set_value(chip->rdwr_pin, 1); + ret = spi_read(spi_dev, (u8 *)data, sizeof(*data)); + if (ret < 0) { + dev_err(&spi_dev->dev, "SPI data read error\n"); + return ret; + } + + *data = be16_to_cpu(*data); + + return ret; +} + +static int ad7816_spi_write(struct ad7816_chip_info *chip, u8 data) +{ + struct spi_device *spi_dev = chip->spi_dev; + int ret = 0; + + gpio_set_value(chip->rdwr_pin, 1); + gpio_set_value(chip->rdwr_pin, 0); + ret = spi_write(spi_dev, &data, sizeof(data)); + if (ret < 0) + dev_err(&spi_dev->dev, "SPI oti data write error\n"); + + return ret; +} + +static ssize_t ad7816_show_mode(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct ad7816_chip_info *chip = iio_priv(indio_dev); + + if (chip->mode) + return sprintf(buf, "power-save\n"); + return sprintf(buf, "full\n"); +} + +static ssize_t ad7816_store_mode(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct ad7816_chip_info *chip = iio_priv(indio_dev); + + if (strcmp(buf, "full")) { + gpio_set_value(chip->rdwr_pin, 1); + chip->mode = AD7816_FULL; + } else { + gpio_set_value(chip->rdwr_pin, 0); + chip->mode = AD7816_PD; + } + + return len; +} + +static IIO_DEVICE_ATTR(mode, S_IRUGO | S_IWUSR, + ad7816_show_mode, + ad7816_store_mode, + 0); + +static ssize_t ad7816_show_available_modes(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + return sprintf(buf, "full\npower-save\n"); +} + +static IIO_DEVICE_ATTR(available_modes, S_IRUGO, ad7816_show_available_modes, + NULL, 0); + +static ssize_t ad7816_show_channel(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct ad7816_chip_info *chip = iio_priv(indio_dev); + + return sprintf(buf, "%d\n", chip->channel_id); +} + +static ssize_t ad7816_store_channel(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct ad7816_chip_info *chip = iio_priv(indio_dev); + unsigned long data; + int ret; + + ret = kstrtoul(buf, 10, &data); + if (ret) + return ret; + + if (data > AD7816_CS_MAX && data != AD7816_CS_MASK) { + dev_err(&chip->spi_dev->dev, "Invalid channel id %lu for %s.\n", + data, indio_dev->name); + return -EINVAL; + } else if (strcmp(indio_dev->name, "ad7818") == 0 && data > 1) { + dev_err(&chip->spi_dev->dev, + "Invalid channel id %lu for ad7818.\n", data); + return -EINVAL; + } else if (strcmp(indio_dev->name, "ad7816") == 0 && data > 0) { + dev_err(&chip->spi_dev->dev, + "Invalid channel id %lu for ad7816.\n", data); + return -EINVAL; + } + + chip->channel_id = data; + + return len; +} + +static IIO_DEVICE_ATTR(channel, S_IRUGO | S_IWUSR, + ad7816_show_channel, + ad7816_store_channel, + 0); + + +static ssize_t ad7816_show_value(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct ad7816_chip_info *chip = iio_priv(indio_dev); + u16 data; + s8 value; + int ret; + + ret = ad7816_spi_read(chip, &data); + if (ret) + return -EIO; + + data >>= AD7816_VALUE_OFFSET; + + if (chip->channel_id == 0) { + value = (s8)((data >> AD7816_TEMP_FLOAT_OFFSET) - 103); + data &= AD7816_TEMP_FLOAT_MASK; + if (value < 0) + data = (1 << AD7816_TEMP_FLOAT_OFFSET) - data; + return sprintf(buf, "%d.%.2d\n", value, data * 25); + } + return sprintf(buf, "%u\n", data); +} + +static IIO_DEVICE_ATTR(value, S_IRUGO, ad7816_show_value, NULL, 0); + +static struct attribute *ad7816_attributes[] = { + &iio_dev_attr_available_modes.dev_attr.attr, + &iio_dev_attr_mode.dev_attr.attr, + &iio_dev_attr_channel.dev_attr.attr, + &iio_dev_attr_value.dev_attr.attr, + NULL, +}; + +static const struct attribute_group ad7816_attribute_group = { + .attrs = ad7816_attributes, +}; + +/* + * temperature bound events + */ + +#define IIO_EVENT_CODE_AD7816_OTI IIO_UNMOD_EVENT_CODE(IIO_TEMP, \ + 0, \ + IIO_EV_TYPE_THRESH, \ + IIO_EV_DIR_FALLING) + +static irqreturn_t ad7816_event_handler(int irq, void *private) +{ + iio_push_event(private, IIO_EVENT_CODE_AD7816_OTI, iio_get_time_ns()); + return IRQ_HANDLED; +} + +static ssize_t ad7816_show_oti(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct ad7816_chip_info *chip = iio_priv(indio_dev); + int value; + + if (chip->channel_id > AD7816_CS_MAX) { + dev_err(dev, "Invalid oti channel id %d.\n", chip->channel_id); + return -EINVAL; + } else if (chip->channel_id == 0) { + value = AD7816_BOUND_VALUE_MIN + + (chip->oti_data[chip->channel_id] - + AD7816_BOUND_VALUE_BASE); + return sprintf(buf, "%d\n", value); + } + return sprintf(buf, "%u\n", chip->oti_data[chip->channel_id]); +} + +static inline ssize_t ad7816_set_oti(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct ad7816_chip_info *chip = iio_priv(indio_dev); + long value; + u8 data; + int ret; + + ret = kstrtol(buf, 10, &value); + if (ret) + return ret; + + if (chip->channel_id > AD7816_CS_MAX) { + dev_err(dev, "Invalid oti channel id %d.\n", chip->channel_id); + return -EINVAL; + } else if (chip->channel_id == 0) { + if (ret || value < AD7816_BOUND_VALUE_MIN || + value > AD7816_BOUND_VALUE_MAX) + return -EINVAL; + + data = (u8)(value - AD7816_BOUND_VALUE_MIN + + AD7816_BOUND_VALUE_BASE); + } else { + if (ret || value < AD7816_BOUND_VALUE_BASE || value > 255) + return -EINVAL; + + data = (u8)value; + } + + ret = ad7816_spi_write(chip, data); + if (ret) + return -EIO; + + chip->oti_data[chip->channel_id] = data; + + return len; +} + +static IIO_DEVICE_ATTR(oti, S_IRUGO | S_IWUSR, + ad7816_show_oti, ad7816_set_oti, 0); + +static struct attribute *ad7816_event_attributes[] = { + &iio_dev_attr_oti.dev_attr.attr, + NULL, +}; + +static struct attribute_group ad7816_event_attribute_group = { + .attrs = ad7816_event_attributes, + .name = "events", +}; + +static const struct iio_info ad7816_info = { + .attrs = &ad7816_attribute_group, + .event_attrs = &ad7816_event_attribute_group, + .driver_module = THIS_MODULE, +}; + +/* + * device probe and remove + */ + +static int ad7816_probe(struct spi_device *spi_dev) +{ + struct ad7816_chip_info *chip; + struct iio_dev *indio_dev; + unsigned short *pins = spi_dev->dev.platform_data; + int ret = 0; + int i; + + if (!pins) { + dev_err(&spi_dev->dev, "No necessary GPIO platform data.\n"); + return -EINVAL; + } + + indio_dev = devm_iio_device_alloc(&spi_dev->dev, sizeof(*chip)); + if (!indio_dev) + return -ENOMEM; + chip = iio_priv(indio_dev); + /* this is only used for device removal purposes */ + dev_set_drvdata(&spi_dev->dev, indio_dev); + + chip->spi_dev = spi_dev; + for (i = 0; i <= AD7816_CS_MAX; i++) + chip->oti_data[i] = 203; + chip->rdwr_pin = pins[0]; + chip->convert_pin = pins[1]; + chip->busy_pin = pins[2]; + + ret = devm_gpio_request(&spi_dev->dev, chip->rdwr_pin, + spi_get_device_id(spi_dev)->name); + if (ret) { + dev_err(&spi_dev->dev, "Fail to request rdwr gpio PIN %d.\n", + chip->rdwr_pin); + return ret; + } + gpio_direction_input(chip->rdwr_pin); + ret = devm_gpio_request(&spi_dev->dev, chip->convert_pin, + spi_get_device_id(spi_dev)->name); + if (ret) { + dev_err(&spi_dev->dev, "Fail to request convert gpio PIN %d.\n", + chip->convert_pin); + return ret; + } + gpio_direction_input(chip->convert_pin); + ret = devm_gpio_request(&spi_dev->dev, chip->busy_pin, + spi_get_device_id(spi_dev)->name); + if (ret) { + dev_err(&spi_dev->dev, "Fail to request busy gpio PIN %d.\n", + chip->busy_pin); + return ret; + } + gpio_direction_input(chip->busy_pin); + + indio_dev->name = spi_get_device_id(spi_dev)->name; + indio_dev->dev.parent = &spi_dev->dev; + indio_dev->info = &ad7816_info; + indio_dev->modes = INDIO_DIRECT_MODE; + + if (spi_dev->irq) { + /* Only low trigger is supported in ad7816/7/8 */ + ret = devm_request_threaded_irq(&spi_dev->dev, spi_dev->irq, + NULL, + &ad7816_event_handler, + IRQF_TRIGGER_LOW | IRQF_ONESHOT, + indio_dev->name, + indio_dev); + if (ret) + return ret; + } + + ret = devm_iio_device_register(&spi_dev->dev, indio_dev); + if (ret) + return ret; + + dev_info(&spi_dev->dev, "%s temperature sensor and ADC registered.\n", + indio_dev->name); + + return 0; +} + +static const struct spi_device_id ad7816_id[] = { + { "ad7816", 0 }, + { "ad7817", 0 }, + { "ad7818", 0 }, + {} +}; + +MODULE_DEVICE_TABLE(spi, ad7816_id); + +static struct spi_driver ad7816_driver = { + .driver = { + .name = "ad7816", + .owner = THIS_MODULE, + }, + .probe = ad7816_probe, + .id_table = ad7816_id, +}; +module_spi_driver(ad7816_driver); + +MODULE_AUTHOR("Sonic Zhang "); +MODULE_DESCRIPTION("Analog Devices AD7816/7/8 digital temperature sensor driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/staging/iio/adc/lpc32xx_adc.c b/drivers/staging/iio/adc/lpc32xx_adc.c new file mode 100644 index 000000000..5331c442f --- /dev/null +++ b/drivers/staging/iio/adc/lpc32xx_adc.c @@ -0,0 +1,215 @@ +/* + * lpc32xx_adc.c - Support for ADC in LPC32XX + * + * 3-channel, 10-bit ADC + * + * Copyright (C) 2011, 2012 Roland Stigge + * + * 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; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +/* + * LPC32XX registers definitions + */ +#define LPC32XX_ADC_SELECT(x) ((x) + 0x04) +#define LPC32XX_ADC_CTRL(x) ((x) + 0x08) +#define LPC32XX_ADC_VALUE(x) ((x) + 0x48) + +/* Bit definitions for LPC32XX_ADC_SELECT: */ +#define AD_REFm 0x00000200 /* constant, always write this value! */ +#define AD_REFp 0x00000080 /* constant, always write this value! */ +#define AD_IN 0x00000010 /* multiple of this is the */ + /* channel number: 0, 1, 2 */ +#define AD_INTERNAL 0x00000004 /* constant, always write this value! */ + +/* Bit definitions for LPC32XX_ADC_CTRL: */ +#define AD_STROBE 0x00000002 +#define AD_PDN_CTRL 0x00000004 + +/* Bit definitions for LPC32XX_ADC_VALUE: */ +#define ADC_VALUE_MASK 0x000003FF + +#define MOD_NAME "lpc32xx-adc" + +struct lpc32xx_adc_info { + void __iomem *adc_base; + struct clk *clk; + struct completion completion; + + u32 value; +}; + +static int lpc32xx_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long mask) +{ + struct lpc32xx_adc_info *info = iio_priv(indio_dev); + + if (mask == IIO_CHAN_INFO_RAW) { + mutex_lock(&indio_dev->mlock); + clk_enable(info->clk); + /* Measurement setup */ + __raw_writel(AD_INTERNAL | (chan->address) | AD_REFp | AD_REFm, + LPC32XX_ADC_SELECT(info->adc_base)); + /* Trigger conversion */ + __raw_writel(AD_PDN_CTRL | AD_STROBE, + LPC32XX_ADC_CTRL(info->adc_base)); + wait_for_completion(&info->completion); /* set by ISR */ + clk_disable(info->clk); + *val = info->value; + mutex_unlock(&indio_dev->mlock); + + return IIO_VAL_INT; + } + + return -EINVAL; +} + +static const struct iio_info lpc32xx_adc_iio_info = { + .read_raw = &lpc32xx_read_raw, + .driver_module = THIS_MODULE, +}; + +#define LPC32XX_ADC_CHANNEL(_index) { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .channel = _index, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .address = AD_IN * _index, \ + .scan_index = _index, \ +} + +static const struct iio_chan_spec lpc32xx_adc_iio_channels[] = { + LPC32XX_ADC_CHANNEL(0), + LPC32XX_ADC_CHANNEL(1), + LPC32XX_ADC_CHANNEL(2), +}; + +static irqreturn_t lpc32xx_adc_isr(int irq, void *dev_id) +{ + struct lpc32xx_adc_info *info = dev_id; + + /* Read value and clear irq */ + info->value = __raw_readl(LPC32XX_ADC_VALUE(info->adc_base)) & + ADC_VALUE_MASK; + complete(&info->completion); + + return IRQ_HANDLED; +} + +static int lpc32xx_adc_probe(struct platform_device *pdev) +{ + struct lpc32xx_adc_info *info = NULL; + struct resource *res; + int retval = -ENODEV; + struct iio_dev *iodev = NULL; + int irq; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(&pdev->dev, "failed to get platform I/O memory\n"); + return -EBUSY; + } + + iodev = devm_iio_device_alloc(&pdev->dev, sizeof(*info)); + if (!iodev) + return -ENOMEM; + + info = iio_priv(iodev); + + info->adc_base = devm_ioremap(&pdev->dev, res->start, + resource_size(res)); + if (!info->adc_base) { + dev_err(&pdev->dev, "failed mapping memory\n"); + return -EBUSY; + } + + info->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(info->clk)) { + dev_err(&pdev->dev, "failed getting clock\n"); + return PTR_ERR(info->clk); + } + + irq = platform_get_irq(pdev, 0); + if (irq <= 0) { + dev_err(&pdev->dev, "failed getting interrupt resource\n"); + return -EINVAL; + } + + retval = devm_request_irq(&pdev->dev, irq, lpc32xx_adc_isr, 0, + MOD_NAME, info); + if (retval < 0) { + dev_err(&pdev->dev, "failed requesting interrupt\n"); + return retval; + } + + platform_set_drvdata(pdev, iodev); + + init_completion(&info->completion); + + iodev->name = MOD_NAME; + iodev->dev.parent = &pdev->dev; + iodev->info = &lpc32xx_adc_iio_info; + iodev->modes = INDIO_DIRECT_MODE; + iodev->channels = lpc32xx_adc_iio_channels; + iodev->num_channels = ARRAY_SIZE(lpc32xx_adc_iio_channels); + + retval = devm_iio_device_register(&pdev->dev, iodev); + if (retval) + return retval; + + dev_info(&pdev->dev, "LPC32XX ADC driver loaded, IRQ %d\n", irq); + + return 0; +} + +#ifdef CONFIG_OF +static const struct of_device_id lpc32xx_adc_match[] = { + { .compatible = "nxp,lpc3220-adc" }, + {}, +}; +MODULE_DEVICE_TABLE(of, lpc32xx_adc_match); +#endif + +static struct platform_driver lpc32xx_adc_driver = { + .probe = lpc32xx_adc_probe, + .driver = { + .name = MOD_NAME, + .of_match_table = of_match_ptr(lpc32xx_adc_match), + }, +}; + +module_platform_driver(lpc32xx_adc_driver); + +MODULE_AUTHOR("Roland Stigge "); +MODULE_DESCRIPTION("LPC32XX ADC driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/iio/adc/mxs-lradc.c b/drivers/staging/iio/adc/mxs-lradc.c new file mode 100644 index 000000000..d7c5223f1 --- /dev/null +++ b/drivers/staging/iio/adc/mxs-lradc.c @@ -0,0 +1,1712 @@ +/* + * Freescale i.MX28 LRADC driver + * + * Copyright (c) 2012 DENX Software Engineering, GmbH. + * Marek Vasut + * + * 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; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#define DRIVER_NAME "mxs-lradc" + +#define LRADC_MAX_DELAY_CHANS 4 +#define LRADC_MAX_MAPPED_CHANS 8 +#define LRADC_MAX_TOTAL_CHANS 16 + +#define LRADC_DELAY_TIMER_HZ 2000 + +/* + * Make this runtime configurable if necessary. Currently, if the buffered mode + * is enabled, the LRADC takes LRADC_DELAY_TIMER_LOOP samples of data before + * triggering IRQ. The sampling happens every (LRADC_DELAY_TIMER_PER / 2000) + * seconds. The result is that the samples arrive every 500mS. + */ +#define LRADC_DELAY_TIMER_PER 200 +#define LRADC_DELAY_TIMER_LOOP 5 + +/* + * Once the pen touches the touchscreen, the touchscreen switches from + * IRQ-driven mode to polling mode to prevent interrupt storm. The polling + * is realized by worker thread, which is called every 20 or so milliseconds. + * This gives the touchscreen enough fluence and does not strain the system + * too much. + */ +#define LRADC_TS_SAMPLE_DELAY_MS 5 + +/* + * The LRADC reads the following amount of samples from each touchscreen + * channel and the driver then computes avarage of these. + */ +#define LRADC_TS_SAMPLE_AMOUNT 4 + +enum mxs_lradc_id { + IMX23_LRADC, + IMX28_LRADC, +}; + +static const char * const mx23_lradc_irq_names[] = { + "mxs-lradc-touchscreen", + "mxs-lradc-channel0", + "mxs-lradc-channel1", + "mxs-lradc-channel2", + "mxs-lradc-channel3", + "mxs-lradc-channel4", + "mxs-lradc-channel5", + "mxs-lradc-channel6", + "mxs-lradc-channel7", +}; + +static const char * const mx28_lradc_irq_names[] = { + "mxs-lradc-touchscreen", + "mxs-lradc-thresh0", + "mxs-lradc-thresh1", + "mxs-lradc-channel0", + "mxs-lradc-channel1", + "mxs-lradc-channel2", + "mxs-lradc-channel3", + "mxs-lradc-channel4", + "mxs-lradc-channel5", + "mxs-lradc-channel6", + "mxs-lradc-channel7", + "mxs-lradc-button0", + "mxs-lradc-button1", +}; + +struct mxs_lradc_of_config { + const int irq_count; + const char * const *irq_name; + const uint32_t *vref_mv; +}; + +#define VREF_MV_BASE 1850 + +static const uint32_t mx23_vref_mv[LRADC_MAX_TOTAL_CHANS] = { + VREF_MV_BASE, /* CH0 */ + VREF_MV_BASE, /* CH1 */ + VREF_MV_BASE, /* CH2 */ + VREF_MV_BASE, /* CH3 */ + VREF_MV_BASE, /* CH4 */ + VREF_MV_BASE, /* CH5 */ + VREF_MV_BASE * 2, /* CH6 VDDIO */ + VREF_MV_BASE * 4, /* CH7 VBATT */ + VREF_MV_BASE, /* CH8 Temp sense 0 */ + VREF_MV_BASE, /* CH9 Temp sense 1 */ + VREF_MV_BASE, /* CH10 */ + VREF_MV_BASE, /* CH11 */ + VREF_MV_BASE, /* CH12 USB_DP */ + VREF_MV_BASE, /* CH13 USB_DN */ + VREF_MV_BASE, /* CH14 VBG */ + VREF_MV_BASE * 4, /* CH15 VDD5V */ +}; + +static const uint32_t mx28_vref_mv[LRADC_MAX_TOTAL_CHANS] = { + VREF_MV_BASE, /* CH0 */ + VREF_MV_BASE, /* CH1 */ + VREF_MV_BASE, /* CH2 */ + VREF_MV_BASE, /* CH3 */ + VREF_MV_BASE, /* CH4 */ + VREF_MV_BASE, /* CH5 */ + VREF_MV_BASE, /* CH6 */ + VREF_MV_BASE * 4, /* CH7 VBATT */ + VREF_MV_BASE, /* CH8 Temp sense 0 */ + VREF_MV_BASE, /* CH9 Temp sense 1 */ + VREF_MV_BASE * 2, /* CH10 VDDIO */ + VREF_MV_BASE, /* CH11 VTH */ + VREF_MV_BASE * 2, /* CH12 VDDA */ + VREF_MV_BASE, /* CH13 VDDD */ + VREF_MV_BASE, /* CH14 VBG */ + VREF_MV_BASE * 4, /* CH15 VDD5V */ +}; + +static const struct mxs_lradc_of_config mxs_lradc_of_config[] = { + [IMX23_LRADC] = { + .irq_count = ARRAY_SIZE(mx23_lradc_irq_names), + .irq_name = mx23_lradc_irq_names, + .vref_mv = mx23_vref_mv, + }, + [IMX28_LRADC] = { + .irq_count = ARRAY_SIZE(mx28_lradc_irq_names), + .irq_name = mx28_lradc_irq_names, + .vref_mv = mx28_vref_mv, + }, +}; + +enum mxs_lradc_ts { + MXS_LRADC_TOUCHSCREEN_NONE = 0, + MXS_LRADC_TOUCHSCREEN_4WIRE, + MXS_LRADC_TOUCHSCREEN_5WIRE, +}; + +/* + * Touchscreen handling + */ +enum lradc_ts_plate { + LRADC_TOUCH = 0, + LRADC_SAMPLE_X, + LRADC_SAMPLE_Y, + LRADC_SAMPLE_PRESSURE, + LRADC_SAMPLE_VALID, +}; + +enum mxs_lradc_divbytwo { + MXS_LRADC_DIV_DISABLED = 0, + MXS_LRADC_DIV_ENABLED, +}; + +struct mxs_lradc_scale { + unsigned int integer; + unsigned int nano; +}; + +struct mxs_lradc { + struct device *dev; + void __iomem *base; + int irq[13]; + + struct clk *clk; + + uint32_t *buffer; + struct iio_trigger *trig; + + struct mutex lock; + + struct completion completion; + + const uint32_t *vref_mv; + struct mxs_lradc_scale scale_avail[LRADC_MAX_TOTAL_CHANS][2]; + unsigned long is_divided; + + /* + * When the touchscreen is enabled, we give it two private virtual + * channels: #6 and #7. This means that only 6 virtual channels (instead + * of 8) will be available for buffered capture. + */ +#define TOUCHSCREEN_VCHANNEL1 7 +#define TOUCHSCREEN_VCHANNEL2 6 +#define BUFFER_VCHANS_LIMITED 0x3f +#define BUFFER_VCHANS_ALL 0xff + u8 buffer_vchans; + + /* + * Furthermore, certain LRADC channels are shared between touchscreen + * and/or touch-buttons and generic LRADC block. Therefore when using + * either of these, these channels are not available for the regular + * sampling. The shared channels are as follows: + * + * CH0 -- Touch button #0 + * CH1 -- Touch button #1 + * CH2 -- Touch screen XPUL + * CH3 -- Touch screen YPLL + * CH4 -- Touch screen XNUL + * CH5 -- Touch screen YNLR + * CH6 -- Touch screen WIPER (5-wire only) + * + * The bitfields below represents which parts of the LRADC block are + * switched into special mode of operation. These channels can not + * be sampled as regular LRADC channels. The driver will refuse any + * attempt to sample these channels. + */ +#define CHAN_MASK_TOUCHBUTTON (BIT(1) | BIT(0)) +#define CHAN_MASK_TOUCHSCREEN_4WIRE (0xf << 2) +#define CHAN_MASK_TOUCHSCREEN_5WIRE (0x1f << 2) + enum mxs_lradc_ts use_touchscreen; + bool use_touchbutton; + + struct input_dev *ts_input; + + enum mxs_lradc_id soc; + enum lradc_ts_plate cur_plate; /* statemachine */ + bool ts_valid; + unsigned ts_x_pos; + unsigned ts_y_pos; + unsigned ts_pressure; + + /* handle touchscreen's physical behaviour */ + /* samples per coordinate */ + unsigned over_sample_cnt; + /* time clocks between samples */ + unsigned over_sample_delay; + /* time in clocks to wait after the plates where switched */ + unsigned settling_delay; +}; + +#define LRADC_CTRL0 0x00 +# define LRADC_CTRL0_MX28_TOUCH_DETECT_ENABLE BIT(23) +# define LRADC_CTRL0_MX28_TOUCH_SCREEN_TYPE BIT(22) +# define LRADC_CTRL0_MX28_YNNSW /* YM */ BIT(21) +# define LRADC_CTRL0_MX28_YPNSW /* YP */ BIT(20) +# define LRADC_CTRL0_MX28_YPPSW /* YP */ BIT(19) +# define LRADC_CTRL0_MX28_XNNSW /* XM */ BIT(18) +# define LRADC_CTRL0_MX28_XNPSW /* XM */ BIT(17) +# define LRADC_CTRL0_MX28_XPPSW /* XP */ BIT(16) + +# define LRADC_CTRL0_MX23_TOUCH_DETECT_ENABLE BIT(20) +# define LRADC_CTRL0_MX23_YM BIT(19) +# define LRADC_CTRL0_MX23_XM BIT(18) +# define LRADC_CTRL0_MX23_YP BIT(17) +# define LRADC_CTRL0_MX23_XP BIT(16) + +# define LRADC_CTRL0_MX28_PLATE_MASK \ + (LRADC_CTRL0_MX28_TOUCH_DETECT_ENABLE | \ + LRADC_CTRL0_MX28_YNNSW | LRADC_CTRL0_MX28_YPNSW | \ + LRADC_CTRL0_MX28_YPPSW | LRADC_CTRL0_MX28_XNNSW | \ + LRADC_CTRL0_MX28_XNPSW | LRADC_CTRL0_MX28_XPPSW) + +# define LRADC_CTRL0_MX23_PLATE_MASK \ + (LRADC_CTRL0_MX23_TOUCH_DETECT_ENABLE | \ + LRADC_CTRL0_MX23_YM | LRADC_CTRL0_MX23_XM | \ + LRADC_CTRL0_MX23_YP | LRADC_CTRL0_MX23_XP) + +#define LRADC_CTRL1 0x10 +#define LRADC_CTRL1_TOUCH_DETECT_IRQ_EN BIT(24) +#define LRADC_CTRL1_LRADC_IRQ_EN(n) (1 << ((n) + 16)) +#define LRADC_CTRL1_MX28_LRADC_IRQ_EN_MASK (0x1fff << 16) +#define LRADC_CTRL1_MX23_LRADC_IRQ_EN_MASK (0x01ff << 16) +#define LRADC_CTRL1_LRADC_IRQ_EN_OFFSET 16 +#define LRADC_CTRL1_TOUCH_DETECT_IRQ BIT(8) +#define LRADC_CTRL1_LRADC_IRQ(n) (1 << (n)) +#define LRADC_CTRL1_MX28_LRADC_IRQ_MASK 0x1fff +#define LRADC_CTRL1_MX23_LRADC_IRQ_MASK 0x01ff +#define LRADC_CTRL1_LRADC_IRQ_OFFSET 0 + +#define LRADC_CTRL2 0x20 +#define LRADC_CTRL2_DIVIDE_BY_TWO_OFFSET 24 +#define LRADC_CTRL2_TEMPSENSE_PWD BIT(15) + +#define LRADC_STATUS 0x40 +#define LRADC_STATUS_TOUCH_DETECT_RAW BIT(0) + +#define LRADC_CH(n) (0x50 + (0x10 * (n))) +#define LRADC_CH_ACCUMULATE BIT(29) +#define LRADC_CH_NUM_SAMPLES_MASK (0x1f << 24) +#define LRADC_CH_NUM_SAMPLES_OFFSET 24 +#define LRADC_CH_NUM_SAMPLES(x) \ + ((x) << LRADC_CH_NUM_SAMPLES_OFFSET) +#define LRADC_CH_VALUE_MASK 0x3ffff +#define LRADC_CH_VALUE_OFFSET 0 + +#define LRADC_DELAY(n) (0xd0 + (0x10 * (n))) +#define LRADC_DELAY_TRIGGER_LRADCS_MASK (0xff << 24) +#define LRADC_DELAY_TRIGGER_LRADCS_OFFSET 24 +#define LRADC_DELAY_TRIGGER(x) \ + (((x) << LRADC_DELAY_TRIGGER_LRADCS_OFFSET) & \ + LRADC_DELAY_TRIGGER_LRADCS_MASK) +#define LRADC_DELAY_KICK (1 << 20) +#define LRADC_DELAY_TRIGGER_DELAYS_MASK (0xf << 16) +#define LRADC_DELAY_TRIGGER_DELAYS_OFFSET 16 +#define LRADC_DELAY_TRIGGER_DELAYS(x) \ + (((x) << LRADC_DELAY_TRIGGER_DELAYS_OFFSET) & \ + LRADC_DELAY_TRIGGER_DELAYS_MASK) +#define LRADC_DELAY_LOOP_COUNT_MASK (0x1f << 11) +#define LRADC_DELAY_LOOP_COUNT_OFFSET 11 +#define LRADC_DELAY_LOOP(x) \ + (((x) << LRADC_DELAY_LOOP_COUNT_OFFSET) & \ + LRADC_DELAY_LOOP_COUNT_MASK) +#define LRADC_DELAY_DELAY_MASK 0x7ff +#define LRADC_DELAY_DELAY_OFFSET 0 +#define LRADC_DELAY_DELAY(x) \ + (((x) << LRADC_DELAY_DELAY_OFFSET) & \ + LRADC_DELAY_DELAY_MASK) + +#define LRADC_CTRL4 0x140 +#define LRADC_CTRL4_LRADCSELECT_MASK(n) (0xf << ((n) * 4)) +#define LRADC_CTRL4_LRADCSELECT_OFFSET(n) ((n) * 4) +#define LRADC_CTRL4_LRADCSELECT(n, x) \ + (((x) << LRADC_CTRL4_LRADCSELECT_OFFSET(n)) & \ + LRADC_CTRL4_LRADCSELECT_MASK(n)) + +#define LRADC_RESOLUTION 12 +#define LRADC_SINGLE_SAMPLE_MASK ((1 << LRADC_RESOLUTION) - 1) + +static void mxs_lradc_reg_set(struct mxs_lradc *lradc, u32 val, u32 reg) +{ + writel(val, lradc->base + reg + STMP_OFFSET_REG_SET); +} + +static void mxs_lradc_reg_clear(struct mxs_lradc *lradc, u32 val, u32 reg) +{ + writel(val, lradc->base + reg + STMP_OFFSET_REG_CLR); +} + +static void mxs_lradc_reg_wrt(struct mxs_lradc *lradc, u32 val, u32 reg) +{ + writel(val, lradc->base + reg); +} + +static u32 mxs_lradc_plate_mask(struct mxs_lradc *lradc) +{ + if (lradc->soc == IMX23_LRADC) + return LRADC_CTRL0_MX23_PLATE_MASK; + return LRADC_CTRL0_MX28_PLATE_MASK; +} + +static u32 mxs_lradc_irq_en_mask(struct mxs_lradc *lradc) +{ + if (lradc->soc == IMX23_LRADC) + return LRADC_CTRL1_MX23_LRADC_IRQ_EN_MASK; + return LRADC_CTRL1_MX28_LRADC_IRQ_EN_MASK; +} + +static u32 mxs_lradc_irq_mask(struct mxs_lradc *lradc) +{ + if (lradc->soc == IMX23_LRADC) + return LRADC_CTRL1_MX23_LRADC_IRQ_MASK; + return LRADC_CTRL1_MX28_LRADC_IRQ_MASK; +} + +static u32 mxs_lradc_touch_detect_bit(struct mxs_lradc *lradc) +{ + if (lradc->soc == IMX23_LRADC) + return LRADC_CTRL0_MX23_TOUCH_DETECT_ENABLE; + return LRADC_CTRL0_MX28_TOUCH_DETECT_ENABLE; +} + +static u32 mxs_lradc_drive_x_plate(struct mxs_lradc *lradc) +{ + if (lradc->soc == IMX23_LRADC) + return LRADC_CTRL0_MX23_XP | LRADC_CTRL0_MX23_XM; + return LRADC_CTRL0_MX28_XPPSW | LRADC_CTRL0_MX28_XNNSW; +} + +static u32 mxs_lradc_drive_y_plate(struct mxs_lradc *lradc) +{ + if (lradc->soc == IMX23_LRADC) + return LRADC_CTRL0_MX23_YP | LRADC_CTRL0_MX23_YM; + return LRADC_CTRL0_MX28_YPPSW | LRADC_CTRL0_MX28_YNNSW; +} + +static u32 mxs_lradc_drive_pressure(struct mxs_lradc *lradc) +{ + if (lradc->soc == IMX23_LRADC) + return LRADC_CTRL0_MX23_YP | LRADC_CTRL0_MX23_XM; + return LRADC_CTRL0_MX28_YPPSW | LRADC_CTRL0_MX28_XNNSW; +} + +static bool mxs_lradc_check_touch_event(struct mxs_lradc *lradc) +{ + return !!(readl(lradc->base + LRADC_STATUS) & + LRADC_STATUS_TOUCH_DETECT_RAW); +} + +static void mxs_lradc_map_channel(struct mxs_lradc *lradc, unsigned vch, + unsigned ch) +{ + mxs_lradc_reg_clear(lradc, LRADC_CTRL4_LRADCSELECT_MASK(vch), + LRADC_CTRL4); + mxs_lradc_reg_set(lradc, LRADC_CTRL4_LRADCSELECT(vch, ch), LRADC_CTRL4); +} + +static void mxs_lradc_setup_ts_channel(struct mxs_lradc *lradc, unsigned ch) +{ + /* + * prepare for oversampling conversion + * + * from the datasheet: + * "The ACCUMULATE bit in the appropriate channel register + * HW_LRADC_CHn must be set to 1 if NUM_SAMPLES is greater then 0; + * otherwise, the IRQs will not fire." + */ + mxs_lradc_reg_wrt(lradc, LRADC_CH_ACCUMULATE | + LRADC_CH_NUM_SAMPLES(lradc->over_sample_cnt - 1), + LRADC_CH(ch)); + + /* from the datasheet: + * "Software must clear this register in preparation for a + * multi-cycle accumulation. + */ + mxs_lradc_reg_clear(lradc, LRADC_CH_VALUE_MASK, LRADC_CH(ch)); + + /* + * prepare the delay/loop unit according to the oversampling count + * + * from the datasheet: + * "The DELAY fields in HW_LRADC_DELAY0, HW_LRADC_DELAY1, + * HW_LRADC_DELAY2, and HW_LRADC_DELAY3 must be non-zero; otherwise, + * the LRADC will not trigger the delay group." + */ + mxs_lradc_reg_wrt(lradc, LRADC_DELAY_TRIGGER(1 << ch) | + LRADC_DELAY_TRIGGER_DELAYS(0) | + LRADC_DELAY_LOOP(lradc->over_sample_cnt - 1) | + LRADC_DELAY_DELAY(lradc->over_sample_delay - 1), + LRADC_DELAY(3)); + + mxs_lradc_reg_clear(lradc, LRADC_CTRL1_LRADC_IRQ(ch), LRADC_CTRL1); + + /* + * after changing the touchscreen plates setting + * the signals need some initial time to settle. Start the + * SoC's delay unit and start the conversion later + * and automatically. + */ + mxs_lradc_reg_wrt(lradc, + LRADC_DELAY_TRIGGER(0) | /* don't trigger ADC */ + LRADC_DELAY_TRIGGER_DELAYS(BIT(3)) | /* trigger DELAY unit#3 */ + LRADC_DELAY_KICK | + LRADC_DELAY_DELAY(lradc->settling_delay), + LRADC_DELAY(2)); +} + +/* + * Pressure detection is special: + * We want to do both required measurements for the pressure detection in + * one turn. Use the hardware features to chain both conversions and let the + * hardware report one interrupt if both conversions are done + */ +static void mxs_lradc_setup_ts_pressure(struct mxs_lradc *lradc, unsigned ch1, + unsigned ch2) +{ + u32 reg; + + /* + * prepare for oversampling conversion + * + * from the datasheet: + * "The ACCUMULATE bit in the appropriate channel register + * HW_LRADC_CHn must be set to 1 if NUM_SAMPLES is greater then 0; + * otherwise, the IRQs will not fire." + */ + reg = LRADC_CH_ACCUMULATE | + LRADC_CH_NUM_SAMPLES(lradc->over_sample_cnt - 1); + mxs_lradc_reg_wrt(lradc, reg, LRADC_CH(ch1)); + mxs_lradc_reg_wrt(lradc, reg, LRADC_CH(ch2)); + + /* from the datasheet: + * "Software must clear this register in preparation for a + * multi-cycle accumulation. + */ + mxs_lradc_reg_clear(lradc, LRADC_CH_VALUE_MASK, LRADC_CH(ch1)); + mxs_lradc_reg_clear(lradc, LRADC_CH_VALUE_MASK, LRADC_CH(ch2)); + + /* prepare the delay/loop unit according to the oversampling count */ + mxs_lradc_reg_wrt(lradc, LRADC_DELAY_TRIGGER(1 << ch1) | + LRADC_DELAY_TRIGGER(1 << ch2) | /* start both channels */ + LRADC_DELAY_TRIGGER_DELAYS(0) | + LRADC_DELAY_LOOP(lradc->over_sample_cnt - 1) | + LRADC_DELAY_DELAY(lradc->over_sample_delay - 1), + LRADC_DELAY(3)); + + mxs_lradc_reg_clear(lradc, LRADC_CTRL1_LRADC_IRQ(ch2), LRADC_CTRL1); + + /* + * after changing the touchscreen plates setting + * the signals need some initial time to settle. Start the + * SoC's delay unit and start the conversion later + * and automatically. + */ + mxs_lradc_reg_wrt(lradc, + LRADC_DELAY_TRIGGER(0) | /* don't trigger ADC */ + LRADC_DELAY_TRIGGER_DELAYS(BIT(3)) | /* trigger DELAY unit#3 */ + LRADC_DELAY_KICK | + LRADC_DELAY_DELAY(lradc->settling_delay), LRADC_DELAY(2)); +} + +static unsigned mxs_lradc_read_raw_channel(struct mxs_lradc *lradc, + unsigned channel) +{ + u32 reg; + unsigned num_samples, val; + + reg = readl(lradc->base + LRADC_CH(channel)); + if (reg & LRADC_CH_ACCUMULATE) + num_samples = lradc->over_sample_cnt; + else + num_samples = 1; + + val = (reg & LRADC_CH_VALUE_MASK) >> LRADC_CH_VALUE_OFFSET; + return val / num_samples; +} + +static unsigned mxs_lradc_read_ts_pressure(struct mxs_lradc *lradc, + unsigned ch1, unsigned ch2) +{ + u32 reg, mask; + unsigned pressure, m1, m2; + + mask = LRADC_CTRL1_LRADC_IRQ(ch1) | LRADC_CTRL1_LRADC_IRQ(ch2); + reg = readl(lradc->base + LRADC_CTRL1) & mask; + + while (reg != mask) { + reg = readl(lradc->base + LRADC_CTRL1) & mask; + dev_dbg(lradc->dev, "One channel is still busy: %X\n", reg); + } + + m1 = mxs_lradc_read_raw_channel(lradc, ch1); + m2 = mxs_lradc_read_raw_channel(lradc, ch2); + + if (m2 == 0) { + dev_warn(lradc->dev, "Cannot calculate pressure\n"); + return 1 << (LRADC_RESOLUTION - 1); + } + + /* simply scale the value from 0 ... max ADC resolution */ + pressure = m1; + pressure *= (1 << LRADC_RESOLUTION); + pressure /= m2; + + dev_dbg(lradc->dev, "Pressure = %u\n", pressure); + return pressure; +} + +#define TS_CH_XP 2 +#define TS_CH_YP 3 +#define TS_CH_XM 4 +#define TS_CH_YM 5 + +/* + * YP(open)--+-------------+ + * | |--+ + * | | | + * YM(-)--+-------------+ | + * +--------------+ + * | | + * XP(weak+) XM(open) + * + * "weak+" means 200k Ohm VDDIO + * (-) means GND + */ +static void mxs_lradc_setup_touch_detection(struct mxs_lradc *lradc) +{ + /* + * In order to detect a touch event the 'touch detect enable' bit + * enables: + * - a weak pullup to the X+ connector + * - a strong ground at the Y- connector + */ + mxs_lradc_reg_clear(lradc, mxs_lradc_plate_mask(lradc), LRADC_CTRL0); + mxs_lradc_reg_set(lradc, mxs_lradc_touch_detect_bit(lradc), + LRADC_CTRL0); +} + +/* + * YP(meas)--+-------------+ + * | |--+ + * | | | + * YM(open)--+-------------+ | + * +--------------+ + * | | + * XP(+) XM(-) + * + * (+) means here 1.85 V + * (-) means here GND + */ +static void mxs_lradc_prepare_x_pos(struct mxs_lradc *lradc) +{ + mxs_lradc_reg_clear(lradc, mxs_lradc_plate_mask(lradc), LRADC_CTRL0); + mxs_lradc_reg_set(lradc, mxs_lradc_drive_x_plate(lradc), LRADC_CTRL0); + + lradc->cur_plate = LRADC_SAMPLE_X; + mxs_lradc_map_channel(lradc, TOUCHSCREEN_VCHANNEL1, TS_CH_YP); + mxs_lradc_setup_ts_channel(lradc, TOUCHSCREEN_VCHANNEL1); +} + +/* + * YP(+)--+-------------+ + * | |--+ + * | | | + * YM(-)--+-------------+ | + * +--------------+ + * | | + * XP(open) XM(meas) + * + * (+) means here 1.85 V + * (-) means here GND + */ +static void mxs_lradc_prepare_y_pos(struct mxs_lradc *lradc) +{ + mxs_lradc_reg_clear(lradc, mxs_lradc_plate_mask(lradc), LRADC_CTRL0); + mxs_lradc_reg_set(lradc, mxs_lradc_drive_y_plate(lradc), LRADC_CTRL0); + + lradc->cur_plate = LRADC_SAMPLE_Y; + mxs_lradc_map_channel(lradc, TOUCHSCREEN_VCHANNEL1, TS_CH_XM); + mxs_lradc_setup_ts_channel(lradc, TOUCHSCREEN_VCHANNEL1); +} + +/* + * YP(+)--+-------------+ + * | |--+ + * | | | + * YM(meas)--+-------------+ | + * +--------------+ + * | | + * XP(meas) XM(-) + * + * (+) means here 1.85 V + * (-) means here GND + */ +static void mxs_lradc_prepare_pressure(struct mxs_lradc *lradc) +{ + mxs_lradc_reg_clear(lradc, mxs_lradc_plate_mask(lradc), LRADC_CTRL0); + mxs_lradc_reg_set(lradc, mxs_lradc_drive_pressure(lradc), LRADC_CTRL0); + + lradc->cur_plate = LRADC_SAMPLE_PRESSURE; + mxs_lradc_map_channel(lradc, TOUCHSCREEN_VCHANNEL1, TS_CH_YM); + mxs_lradc_map_channel(lradc, TOUCHSCREEN_VCHANNEL2, TS_CH_XP); + mxs_lradc_setup_ts_pressure(lradc, TOUCHSCREEN_VCHANNEL2, + TOUCHSCREEN_VCHANNEL1); +} + +static void mxs_lradc_enable_touch_detection(struct mxs_lradc *lradc) +{ + mxs_lradc_setup_touch_detection(lradc); + + lradc->cur_plate = LRADC_TOUCH; + mxs_lradc_reg_clear(lradc, LRADC_CTRL1_TOUCH_DETECT_IRQ | + LRADC_CTRL1_TOUCH_DETECT_IRQ_EN, LRADC_CTRL1); + mxs_lradc_reg_set(lradc, LRADC_CTRL1_TOUCH_DETECT_IRQ_EN, LRADC_CTRL1); +} + +static void mxs_lradc_start_touch_event(struct mxs_lradc *lradc) +{ + mxs_lradc_reg_clear(lradc, LRADC_CTRL1_TOUCH_DETECT_IRQ_EN, + LRADC_CTRL1); + mxs_lradc_reg_set(lradc, + LRADC_CTRL1_LRADC_IRQ_EN(TOUCHSCREEN_VCHANNEL1), LRADC_CTRL1); + /* + * start with the Y-pos, because it uses nearly the same plate + * settings like the touch detection + */ + mxs_lradc_prepare_y_pos(lradc); +} + +static void mxs_lradc_report_ts_event(struct mxs_lradc *lradc) +{ + input_report_abs(lradc->ts_input, ABS_X, lradc->ts_x_pos); + input_report_abs(lradc->ts_input, ABS_Y, lradc->ts_y_pos); + input_report_abs(lradc->ts_input, ABS_PRESSURE, lradc->ts_pressure); + input_report_key(lradc->ts_input, BTN_TOUCH, 1); + input_sync(lradc->ts_input); +} + +static void mxs_lradc_complete_touch_event(struct mxs_lradc *lradc) +{ + mxs_lradc_setup_touch_detection(lradc); + lradc->cur_plate = LRADC_SAMPLE_VALID; + /* + * start a dummy conversion to burn time to settle the signals + * note: we are not interested in the conversion's value + */ + mxs_lradc_reg_wrt(lradc, 0, LRADC_CH(TOUCHSCREEN_VCHANNEL1)); + mxs_lradc_reg_clear(lradc, + LRADC_CTRL1_LRADC_IRQ(TOUCHSCREEN_VCHANNEL1) | + LRADC_CTRL1_LRADC_IRQ(TOUCHSCREEN_VCHANNEL2), LRADC_CTRL1); + mxs_lradc_reg_wrt(lradc, + LRADC_DELAY_TRIGGER(1 << TOUCHSCREEN_VCHANNEL1) | + LRADC_DELAY_KICK | LRADC_DELAY_DELAY(10), /* waste 5 ms */ + LRADC_DELAY(2)); +} + +/* + * in order to avoid false measurements, report only samples where + * the surface is still touched after the position measurement + */ +static void mxs_lradc_finish_touch_event(struct mxs_lradc *lradc, bool valid) +{ + /* if it is still touched, report the sample */ + if (valid && mxs_lradc_check_touch_event(lradc)) { + lradc->ts_valid = true; + mxs_lradc_report_ts_event(lradc); + } + + /* if it is even still touched, continue with the next measurement */ + if (mxs_lradc_check_touch_event(lradc)) { + mxs_lradc_prepare_y_pos(lradc); + return; + } + + if (lradc->ts_valid) { + /* signal the release */ + lradc->ts_valid = false; + input_report_key(lradc->ts_input, BTN_TOUCH, 0); + input_sync(lradc->ts_input); + } + + /* if it is released, wait for the next touch via IRQ */ + lradc->cur_plate = LRADC_TOUCH; + mxs_lradc_reg_wrt(lradc, 0, LRADC_DELAY(2)); + mxs_lradc_reg_wrt(lradc, 0, LRADC_DELAY(3)); + mxs_lradc_reg_clear(lradc, LRADC_CTRL1_TOUCH_DETECT_IRQ | + LRADC_CTRL1_LRADC_IRQ_EN(TOUCHSCREEN_VCHANNEL1) | + LRADC_CTRL1_LRADC_IRQ(TOUCHSCREEN_VCHANNEL1), LRADC_CTRL1); + mxs_lradc_reg_set(lradc, LRADC_CTRL1_TOUCH_DETECT_IRQ_EN, LRADC_CTRL1); +} + +/* touchscreen's state machine */ +static void mxs_lradc_handle_touch(struct mxs_lradc *lradc) +{ + switch (lradc->cur_plate) { + case LRADC_TOUCH: + if (mxs_lradc_check_touch_event(lradc)) + mxs_lradc_start_touch_event(lradc); + mxs_lradc_reg_clear(lradc, LRADC_CTRL1_TOUCH_DETECT_IRQ, + LRADC_CTRL1); + return; + + case LRADC_SAMPLE_Y: + lradc->ts_y_pos = mxs_lradc_read_raw_channel(lradc, + TOUCHSCREEN_VCHANNEL1); + mxs_lradc_prepare_x_pos(lradc); + return; + + case LRADC_SAMPLE_X: + lradc->ts_x_pos = mxs_lradc_read_raw_channel(lradc, + TOUCHSCREEN_VCHANNEL1); + mxs_lradc_prepare_pressure(lradc); + return; + + case LRADC_SAMPLE_PRESSURE: + lradc->ts_pressure = mxs_lradc_read_ts_pressure(lradc, + TOUCHSCREEN_VCHANNEL2, + TOUCHSCREEN_VCHANNEL1); + mxs_lradc_complete_touch_event(lradc); + return; + + case LRADC_SAMPLE_VALID: + mxs_lradc_finish_touch_event(lradc, 1); + break; + } +} + +/* + * Raw I/O operations + */ +static int mxs_lradc_read_single(struct iio_dev *iio_dev, int chan, int *val) +{ + struct mxs_lradc *lradc = iio_priv(iio_dev); + int ret; + + /* + * See if there is no buffered operation in progess. If there is, simply + * bail out. This can be improved to support both buffered and raw IO at + * the same time, yet the code becomes horribly complicated. Therefore I + * applied KISS principle here. + */ + ret = mutex_trylock(&lradc->lock); + if (!ret) + return -EBUSY; + + reinit_completion(&lradc->completion); + + /* + * No buffered operation in progress, map the channel and trigger it. + * Virtual channel 0 is always used here as the others are always not + * used if doing raw sampling. + */ + if (lradc->soc == IMX28_LRADC) + mxs_lradc_reg_clear(lradc, LRADC_CTRL1_LRADC_IRQ_EN(0), + LRADC_CTRL1); + mxs_lradc_reg_clear(lradc, 0x1, LRADC_CTRL0); + + /* Enable / disable the divider per requirement */ + if (test_bit(chan, &lradc->is_divided)) + mxs_lradc_reg_set(lradc, 1 << LRADC_CTRL2_DIVIDE_BY_TWO_OFFSET, + LRADC_CTRL2); + else + mxs_lradc_reg_clear(lradc, + 1 << LRADC_CTRL2_DIVIDE_BY_TWO_OFFSET, LRADC_CTRL2); + + /* Clean the slot's previous content, then set new one. */ + mxs_lradc_reg_clear(lradc, LRADC_CTRL4_LRADCSELECT_MASK(0), + LRADC_CTRL4); + mxs_lradc_reg_set(lradc, chan, LRADC_CTRL4); + + mxs_lradc_reg_wrt(lradc, 0, LRADC_CH(0)); + + /* Enable the IRQ and start sampling the channel. */ + mxs_lradc_reg_set(lradc, LRADC_CTRL1_LRADC_IRQ_EN(0), LRADC_CTRL1); + mxs_lradc_reg_set(lradc, BIT(0), LRADC_CTRL0); + + /* Wait for completion on the channel, 1 second max. */ + ret = wait_for_completion_killable_timeout(&lradc->completion, HZ); + if (!ret) + ret = -ETIMEDOUT; + if (ret < 0) + goto err; + + /* Read the data. */ + *val = readl(lradc->base + LRADC_CH(0)) & LRADC_CH_VALUE_MASK; + ret = IIO_VAL_INT; + +err: + mxs_lradc_reg_clear(lradc, LRADC_CTRL1_LRADC_IRQ_EN(0), LRADC_CTRL1); + + mutex_unlock(&lradc->lock); + + return ret; +} + +static int mxs_lradc_read_temp(struct iio_dev *iio_dev, int *val) +{ + int ret, min, max; + + ret = mxs_lradc_read_single(iio_dev, 8, &min); + if (ret != IIO_VAL_INT) + return ret; + + ret = mxs_lradc_read_single(iio_dev, 9, &max); + if (ret != IIO_VAL_INT) + return ret; + + *val = max - min; + + return IIO_VAL_INT; +} + +static int mxs_lradc_read_raw(struct iio_dev *iio_dev, + const struct iio_chan_spec *chan, + int *val, int *val2, long m) +{ + struct mxs_lradc *lradc = iio_priv(iio_dev); + + switch (m) { + case IIO_CHAN_INFO_RAW: + if (chan->type == IIO_TEMP) + return mxs_lradc_read_temp(iio_dev, val); + + return mxs_lradc_read_single(iio_dev, chan->channel, val); + + case IIO_CHAN_INFO_SCALE: + if (chan->type == IIO_TEMP) { + /* From the datasheet, we have to multiply by 1.012 and + * divide by 4 + */ + *val = 0; + *val2 = 253000; + return IIO_VAL_INT_PLUS_MICRO; + } + + *val = lradc->vref_mv[chan->channel]; + *val2 = chan->scan_type.realbits - + test_bit(chan->channel, &lradc->is_divided); + return IIO_VAL_FRACTIONAL_LOG2; + + case IIO_CHAN_INFO_OFFSET: + if (chan->type == IIO_TEMP) { + /* The calculated value from the ADC is in Kelvin, we + * want Celsius for hwmon so the offset is + * -272.15 * scale + */ + *val = -1075; + *val2 = 691699; + + return IIO_VAL_INT_PLUS_MICRO; + } + + return -EINVAL; + + default: + break; + } + + return -EINVAL; +} + +static int mxs_lradc_write_raw(struct iio_dev *iio_dev, + const struct iio_chan_spec *chan, + int val, int val2, long m) +{ + struct mxs_lradc *lradc = iio_priv(iio_dev); + struct mxs_lradc_scale *scale_avail = + lradc->scale_avail[chan->channel]; + int ret; + + ret = mutex_trylock(&lradc->lock); + if (!ret) + return -EBUSY; + + switch (m) { + case IIO_CHAN_INFO_SCALE: + ret = -EINVAL; + if (val == scale_avail[MXS_LRADC_DIV_DISABLED].integer && + val2 == scale_avail[MXS_LRADC_DIV_DISABLED].nano) { + /* divider by two disabled */ + clear_bit(chan->channel, &lradc->is_divided); + ret = 0; + } else if (val == scale_avail[MXS_LRADC_DIV_ENABLED].integer && + val2 == scale_avail[MXS_LRADC_DIV_ENABLED].nano) { + /* divider by two enabled */ + set_bit(chan->channel, &lradc->is_divided); + ret = 0; + } + + break; + default: + ret = -EINVAL; + break; + } + + mutex_unlock(&lradc->lock); + + return ret; +} + +static int mxs_lradc_write_raw_get_fmt(struct iio_dev *iio_dev, + const struct iio_chan_spec *chan, + long m) +{ + return IIO_VAL_INT_PLUS_NANO; +} + +static ssize_t mxs_lradc_show_scale_available_ch(struct device *dev, + struct device_attribute *attr, + char *buf, + int ch) +{ + struct iio_dev *iio = dev_to_iio_dev(dev); + struct mxs_lradc *lradc = iio_priv(iio); + int i, len = 0; + + for (i = 0; i < ARRAY_SIZE(lradc->scale_avail[ch]); i++) + len += sprintf(buf + len, "%u.%09u ", + lradc->scale_avail[ch][i].integer, + lradc->scale_avail[ch][i].nano); + + len += sprintf(buf + len, "\n"); + + return len; +} + +static ssize_t mxs_lradc_show_scale_available(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev_attr *iio_attr = to_iio_dev_attr(attr); + + return mxs_lradc_show_scale_available_ch(dev, attr, buf, + iio_attr->address); +} + +#define SHOW_SCALE_AVAILABLE_ATTR(ch) \ +static IIO_DEVICE_ATTR(in_voltage##ch##_scale_available, S_IRUGO, \ + mxs_lradc_show_scale_available, NULL, ch) + +SHOW_SCALE_AVAILABLE_ATTR(0); +SHOW_SCALE_AVAILABLE_ATTR(1); +SHOW_SCALE_AVAILABLE_ATTR(2); +SHOW_SCALE_AVAILABLE_ATTR(3); +SHOW_SCALE_AVAILABLE_ATTR(4); +SHOW_SCALE_AVAILABLE_ATTR(5); +SHOW_SCALE_AVAILABLE_ATTR(6); +SHOW_SCALE_AVAILABLE_ATTR(7); +SHOW_SCALE_AVAILABLE_ATTR(10); +SHOW_SCALE_AVAILABLE_ATTR(11); +SHOW_SCALE_AVAILABLE_ATTR(12); +SHOW_SCALE_AVAILABLE_ATTR(13); +SHOW_SCALE_AVAILABLE_ATTR(14); +SHOW_SCALE_AVAILABLE_ATTR(15); + +static struct attribute *mxs_lradc_attributes[] = { + &iio_dev_attr_in_voltage0_scale_available.dev_attr.attr, + &iio_dev_attr_in_voltage1_scale_available.dev_attr.attr, + &iio_dev_attr_in_voltage2_scale_available.dev_attr.attr, + &iio_dev_attr_in_voltage3_scale_available.dev_attr.attr, + &iio_dev_attr_in_voltage4_scale_available.dev_attr.attr, + &iio_dev_attr_in_voltage5_scale_available.dev_attr.attr, + &iio_dev_attr_in_voltage6_scale_available.dev_attr.attr, + &iio_dev_attr_in_voltage7_scale_available.dev_attr.attr, + &iio_dev_attr_in_voltage10_scale_available.dev_attr.attr, + &iio_dev_attr_in_voltage11_scale_available.dev_attr.attr, + &iio_dev_attr_in_voltage12_scale_available.dev_attr.attr, + &iio_dev_attr_in_voltage13_scale_available.dev_attr.attr, + &iio_dev_attr_in_voltage14_scale_available.dev_attr.attr, + &iio_dev_attr_in_voltage15_scale_available.dev_attr.attr, + NULL +}; + +static const struct attribute_group mxs_lradc_attribute_group = { + .attrs = mxs_lradc_attributes, +}; + +static const struct iio_info mxs_lradc_iio_info = { + .driver_module = THIS_MODULE, + .read_raw = mxs_lradc_read_raw, + .write_raw = mxs_lradc_write_raw, + .write_raw_get_fmt = mxs_lradc_write_raw_get_fmt, + .attrs = &mxs_lradc_attribute_group, +}; + +static int mxs_lradc_ts_open(struct input_dev *dev) +{ + struct mxs_lradc *lradc = input_get_drvdata(dev); + + /* Enable the touch-detect circuitry. */ + mxs_lradc_enable_touch_detection(lradc); + + return 0; +} + +static void mxs_lradc_disable_ts(struct mxs_lradc *lradc) +{ + /* stop all interrupts from firing */ + mxs_lradc_reg_clear(lradc, LRADC_CTRL1_TOUCH_DETECT_IRQ_EN | + LRADC_CTRL1_LRADC_IRQ_EN(TOUCHSCREEN_VCHANNEL1) | + LRADC_CTRL1_LRADC_IRQ_EN(TOUCHSCREEN_VCHANNEL2), LRADC_CTRL1); + + /* Power-down touchscreen touch-detect circuitry. */ + mxs_lradc_reg_clear(lradc, mxs_lradc_plate_mask(lradc), LRADC_CTRL0); +} + +static void mxs_lradc_ts_close(struct input_dev *dev) +{ + struct mxs_lradc *lradc = input_get_drvdata(dev); + + mxs_lradc_disable_ts(lradc); +} + +static int mxs_lradc_ts_register(struct mxs_lradc *lradc) +{ + struct input_dev *input; + struct device *dev = lradc->dev; + int ret; + + if (!lradc->use_touchscreen) + return 0; + + input = input_allocate_device(); + if (!input) + return -ENOMEM; + + input->name = DRIVER_NAME; + input->id.bustype = BUS_HOST; + input->dev.parent = dev; + input->open = mxs_lradc_ts_open; + input->close = mxs_lradc_ts_close; + + __set_bit(EV_ABS, input->evbit); + __set_bit(EV_KEY, input->evbit); + __set_bit(BTN_TOUCH, input->keybit); + input_set_abs_params(input, ABS_X, 0, LRADC_SINGLE_SAMPLE_MASK, 0, 0); + input_set_abs_params(input, ABS_Y, 0, LRADC_SINGLE_SAMPLE_MASK, 0, 0); + input_set_abs_params(input, ABS_PRESSURE, 0, LRADC_SINGLE_SAMPLE_MASK, + 0, 0); + + lradc->ts_input = input; + input_set_drvdata(input, lradc); + ret = input_register_device(input); + if (ret) + input_free_device(lradc->ts_input); + + return ret; +} + +static void mxs_lradc_ts_unregister(struct mxs_lradc *lradc) +{ + if (!lradc->use_touchscreen) + return; + + mxs_lradc_disable_ts(lradc); + input_unregister_device(lradc->ts_input); +} + +/* + * IRQ Handling + */ +static irqreturn_t mxs_lradc_handle_irq(int irq, void *data) +{ + struct iio_dev *iio = data; + struct mxs_lradc *lradc = iio_priv(iio); + unsigned long reg = readl(lradc->base + LRADC_CTRL1); + uint32_t clr_irq = mxs_lradc_irq_mask(lradc); + const uint32_t ts_irq_mask = + LRADC_CTRL1_TOUCH_DETECT_IRQ | + LRADC_CTRL1_LRADC_IRQ(TOUCHSCREEN_VCHANNEL1) | + LRADC_CTRL1_LRADC_IRQ(TOUCHSCREEN_VCHANNEL2); + + if (!(reg & mxs_lradc_irq_mask(lradc))) + return IRQ_NONE; + + if (lradc->use_touchscreen && (reg & ts_irq_mask)) { + mxs_lradc_handle_touch(lradc); + + /* Make sure we don't clear the next conversion's interrupt. */ + clr_irq &= ~(LRADC_CTRL1_LRADC_IRQ(TOUCHSCREEN_VCHANNEL1) | + LRADC_CTRL1_LRADC_IRQ(TOUCHSCREEN_VCHANNEL2)); + } + + if (iio_buffer_enabled(iio)) { + if (reg & lradc->buffer_vchans) + iio_trigger_poll(iio->trig); + } else if (reg & LRADC_CTRL1_LRADC_IRQ(0)) { + complete(&lradc->completion); + } + + mxs_lradc_reg_clear(lradc, reg & clr_irq, LRADC_CTRL1); + + return IRQ_HANDLED; +} + +/* + * Trigger handling + */ +static irqreturn_t mxs_lradc_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *iio = pf->indio_dev; + struct mxs_lradc *lradc = iio_priv(iio); + const uint32_t chan_value = LRADC_CH_ACCUMULATE | + ((LRADC_DELAY_TIMER_LOOP - 1) << LRADC_CH_NUM_SAMPLES_OFFSET); + unsigned int i, j = 0; + + for_each_set_bit(i, iio->active_scan_mask, LRADC_MAX_TOTAL_CHANS) { + lradc->buffer[j] = readl(lradc->base + LRADC_CH(j)); + mxs_lradc_reg_wrt(lradc, chan_value, LRADC_CH(j)); + lradc->buffer[j] &= LRADC_CH_VALUE_MASK; + lradc->buffer[j] /= LRADC_DELAY_TIMER_LOOP; + j++; + } + + iio_push_to_buffers_with_timestamp(iio, lradc->buffer, pf->timestamp); + + iio_trigger_notify_done(iio->trig); + + return IRQ_HANDLED; +} + +static int mxs_lradc_configure_trigger(struct iio_trigger *trig, bool state) +{ + struct iio_dev *iio = iio_trigger_get_drvdata(trig); + struct mxs_lradc *lradc = iio_priv(iio); + const uint32_t st = state ? STMP_OFFSET_REG_SET : STMP_OFFSET_REG_CLR; + + mxs_lradc_reg_wrt(lradc, LRADC_DELAY_KICK, LRADC_DELAY(0) + st); + + return 0; +} + +static const struct iio_trigger_ops mxs_lradc_trigger_ops = { + .owner = THIS_MODULE, + .set_trigger_state = &mxs_lradc_configure_trigger, +}; + +static int mxs_lradc_trigger_init(struct iio_dev *iio) +{ + int ret; + struct iio_trigger *trig; + struct mxs_lradc *lradc = iio_priv(iio); + + trig = iio_trigger_alloc("%s-dev%i", iio->name, iio->id); + if (trig == NULL) + return -ENOMEM; + + trig->dev.parent = lradc->dev; + iio_trigger_set_drvdata(trig, iio); + trig->ops = &mxs_lradc_trigger_ops; + + ret = iio_trigger_register(trig); + if (ret) { + iio_trigger_free(trig); + return ret; + } + + lradc->trig = trig; + + return 0; +} + +static void mxs_lradc_trigger_remove(struct iio_dev *iio) +{ + struct mxs_lradc *lradc = iio_priv(iio); + + iio_trigger_unregister(lradc->trig); + iio_trigger_free(lradc->trig); +} + +static int mxs_lradc_buffer_preenable(struct iio_dev *iio) +{ + struct mxs_lradc *lradc = iio_priv(iio); + int ret = 0, chan, ofs = 0; + unsigned long enable = 0; + uint32_t ctrl4_set = 0; + uint32_t ctrl4_clr = 0; + uint32_t ctrl1_irq = 0; + const uint32_t chan_value = LRADC_CH_ACCUMULATE | + ((LRADC_DELAY_TIMER_LOOP - 1) << LRADC_CH_NUM_SAMPLES_OFFSET); + const int len = bitmap_weight(iio->active_scan_mask, + LRADC_MAX_TOTAL_CHANS); + + if (!len) + return -EINVAL; + + /* + * Lock the driver so raw access can not be done during buffered + * operation. This simplifies the code a lot. + */ + ret = mutex_trylock(&lradc->lock); + if (!ret) + return -EBUSY; + + lradc->buffer = kmalloc_array(len, sizeof(*lradc->buffer), GFP_KERNEL); + if (!lradc->buffer) { + ret = -ENOMEM; + goto err_mem; + } + + if (lradc->soc == IMX28_LRADC) + mxs_lradc_reg_clear(lradc, + lradc->buffer_vchans << LRADC_CTRL1_LRADC_IRQ_EN_OFFSET, + LRADC_CTRL1); + mxs_lradc_reg_clear(lradc, lradc->buffer_vchans, LRADC_CTRL0); + + for_each_set_bit(chan, iio->active_scan_mask, LRADC_MAX_TOTAL_CHANS) { + ctrl4_set |= chan << LRADC_CTRL4_LRADCSELECT_OFFSET(ofs); + ctrl4_clr |= LRADC_CTRL4_LRADCSELECT_MASK(ofs); + ctrl1_irq |= LRADC_CTRL1_LRADC_IRQ_EN(ofs); + mxs_lradc_reg_wrt(lradc, chan_value, LRADC_CH(ofs)); + bitmap_set(&enable, ofs, 1); + ofs++; + } + + mxs_lradc_reg_clear(lradc, LRADC_DELAY_TRIGGER_LRADCS_MASK | + LRADC_DELAY_KICK, LRADC_DELAY(0)); + mxs_lradc_reg_clear(lradc, ctrl4_clr, LRADC_CTRL4); + mxs_lradc_reg_set(lradc, ctrl4_set, LRADC_CTRL4); + mxs_lradc_reg_set(lradc, ctrl1_irq, LRADC_CTRL1); + mxs_lradc_reg_set(lradc, enable << LRADC_DELAY_TRIGGER_LRADCS_OFFSET, + LRADC_DELAY(0)); + + return 0; + +err_mem: + mutex_unlock(&lradc->lock); + return ret; +} + +static int mxs_lradc_buffer_postdisable(struct iio_dev *iio) +{ + struct mxs_lradc *lradc = iio_priv(iio); + + mxs_lradc_reg_clear(lradc, LRADC_DELAY_TRIGGER_LRADCS_MASK | + LRADC_DELAY_KICK, LRADC_DELAY(0)); + + mxs_lradc_reg_clear(lradc, lradc->buffer_vchans, LRADC_CTRL0); + if (lradc->soc == IMX28_LRADC) + mxs_lradc_reg_clear(lradc, + lradc->buffer_vchans << LRADC_CTRL1_LRADC_IRQ_EN_OFFSET, + LRADC_CTRL1); + + kfree(lradc->buffer); + mutex_unlock(&lradc->lock); + + return 0; +} + +static bool mxs_lradc_validate_scan_mask(struct iio_dev *iio, + const unsigned long *mask) +{ + struct mxs_lradc *lradc = iio_priv(iio); + const int map_chans = bitmap_weight(mask, LRADC_MAX_TOTAL_CHANS); + int rsvd_chans = 0; + unsigned long rsvd_mask = 0; + + if (lradc->use_touchbutton) + rsvd_mask |= CHAN_MASK_TOUCHBUTTON; + if (lradc->use_touchscreen == MXS_LRADC_TOUCHSCREEN_4WIRE) + rsvd_mask |= CHAN_MASK_TOUCHSCREEN_4WIRE; + if (lradc->use_touchscreen == MXS_LRADC_TOUCHSCREEN_5WIRE) + rsvd_mask |= CHAN_MASK_TOUCHSCREEN_5WIRE; + + if (lradc->use_touchbutton) + rsvd_chans++; + if (lradc->use_touchscreen) + rsvd_chans += 2; + + /* Test for attempts to map channels with special mode of operation. */ + if (bitmap_intersects(mask, &rsvd_mask, LRADC_MAX_TOTAL_CHANS)) + return false; + + /* Test for attempts to map more channels then available slots. */ + if (map_chans + rsvd_chans > LRADC_MAX_MAPPED_CHANS) + return false; + + return true; +} + +static const struct iio_buffer_setup_ops mxs_lradc_buffer_ops = { + .preenable = &mxs_lradc_buffer_preenable, + .postenable = &iio_triggered_buffer_postenable, + .predisable = &iio_triggered_buffer_predisable, + .postdisable = &mxs_lradc_buffer_postdisable, + .validate_scan_mask = &mxs_lradc_validate_scan_mask, +}; + +/* + * Driver initialization + */ + +#define MXS_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), \ + .address = (idx), \ + .scan_type = { \ + .sign = 'u', \ + .realbits = LRADC_RESOLUTION, \ + .storagebits = 32, \ + }, \ +} + +static const struct iio_chan_spec mxs_lradc_chan_spec[] = { + MXS_ADC_CHAN(0, IIO_VOLTAGE), + MXS_ADC_CHAN(1, IIO_VOLTAGE), + MXS_ADC_CHAN(2, IIO_VOLTAGE), + MXS_ADC_CHAN(3, IIO_VOLTAGE), + MXS_ADC_CHAN(4, IIO_VOLTAGE), + MXS_ADC_CHAN(5, IIO_VOLTAGE), + MXS_ADC_CHAN(6, IIO_VOLTAGE), + MXS_ADC_CHAN(7, IIO_VOLTAGE), /* VBATT */ + /* Combined Temperature sensors */ + { + .type = IIO_TEMP, + .indexed = 1, + .scan_index = 8, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_OFFSET) | + BIT(IIO_CHAN_INFO_SCALE), + .channel = 8, + .scan_type = {.sign = 'u', .realbits = 18, .storagebits = 32,}, + }, + /* Hidden channel to keep indexes */ + { + .type = IIO_TEMP, + .indexed = 1, + .scan_index = -1, + .channel = 9, + }, + MXS_ADC_CHAN(10, IIO_VOLTAGE), /* VDDIO */ + MXS_ADC_CHAN(11, IIO_VOLTAGE), /* VTH */ + MXS_ADC_CHAN(12, IIO_VOLTAGE), /* VDDA */ + MXS_ADC_CHAN(13, IIO_VOLTAGE), /* VDDD */ + MXS_ADC_CHAN(14, IIO_VOLTAGE), /* VBG */ + MXS_ADC_CHAN(15, IIO_VOLTAGE), /* VDD5V */ +}; + +static int mxs_lradc_hw_init(struct mxs_lradc *lradc) +{ + /* The ADC always uses DELAY CHANNEL 0. */ + const uint32_t adc_cfg = + (1 << (LRADC_DELAY_TRIGGER_DELAYS_OFFSET + 0)) | + (LRADC_DELAY_TIMER_PER << LRADC_DELAY_DELAY_OFFSET); + + int ret = stmp_reset_block(lradc->base); + + if (ret) + return ret; + + /* Configure DELAY CHANNEL 0 for generic ADC sampling. */ + mxs_lradc_reg_wrt(lradc, adc_cfg, LRADC_DELAY(0)); + + /* Disable remaining DELAY CHANNELs */ + mxs_lradc_reg_wrt(lradc, 0, LRADC_DELAY(1)); + mxs_lradc_reg_wrt(lradc, 0, LRADC_DELAY(2)); + mxs_lradc_reg_wrt(lradc, 0, LRADC_DELAY(3)); + + /* Configure the touchscreen type */ + if (lradc->soc == IMX28_LRADC) { + mxs_lradc_reg_clear(lradc, LRADC_CTRL0_MX28_TOUCH_SCREEN_TYPE, + LRADC_CTRL0); + + if (lradc->use_touchscreen == MXS_LRADC_TOUCHSCREEN_5WIRE) + mxs_lradc_reg_set(lradc, LRADC_CTRL0_MX28_TOUCH_SCREEN_TYPE, + LRADC_CTRL0); + } + + /* Start internal temperature sensing. */ + mxs_lradc_reg_wrt(lradc, 0, LRADC_CTRL2); + + return 0; +} + +static void mxs_lradc_hw_stop(struct mxs_lradc *lradc) +{ + int i; + + mxs_lradc_reg_clear(lradc, mxs_lradc_irq_en_mask(lradc), LRADC_CTRL1); + + for (i = 0; i < LRADC_MAX_DELAY_CHANS; i++) + mxs_lradc_reg_wrt(lradc, 0, LRADC_DELAY(i)); +} + +static const struct of_device_id mxs_lradc_dt_ids[] = { + { .compatible = "fsl,imx23-lradc", .data = (void *)IMX23_LRADC, }, + { .compatible = "fsl,imx28-lradc", .data = (void *)IMX28_LRADC, }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, mxs_lradc_dt_ids); + +static int mxs_lradc_probe_touchscreen(struct mxs_lradc *lradc, + struct device_node *lradc_node) +{ + int ret; + u32 ts_wires = 0, adapt; + + ret = of_property_read_u32(lradc_node, "fsl,lradc-touchscreen-wires", + &ts_wires); + if (ret) + return -ENODEV; /* touchscreen feature disabled */ + + switch (ts_wires) { + case 4: + lradc->use_touchscreen = MXS_LRADC_TOUCHSCREEN_4WIRE; + break; + case 5: + if (lradc->soc == IMX28_LRADC) { + lradc->use_touchscreen = MXS_LRADC_TOUCHSCREEN_5WIRE; + break; + } + /* fall through an error message for i.MX23 */ + default: + dev_err(lradc->dev, + "Unsupported number of touchscreen wires (%d)\n", + ts_wires); + return -EINVAL; + } + + if (of_property_read_u32(lradc_node, "fsl,ave-ctrl", &adapt)) { + lradc->over_sample_cnt = 4; + } else { + if (adapt < 1 || adapt > 32) { + dev_err(lradc->dev, "Invalid sample count (%u)\n", + adapt); + return -EINVAL; + } + lradc->over_sample_cnt = adapt; + } + + if (of_property_read_u32(lradc_node, "fsl,ave-delay", &adapt)) { + lradc->over_sample_delay = 2; + } else { + if (adapt < 2 || adapt > LRADC_DELAY_DELAY_MASK + 1) { + dev_err(lradc->dev, "Invalid sample delay (%u)\n", + adapt); + return -EINVAL; + } + lradc->over_sample_delay = adapt; + } + + if (of_property_read_u32(lradc_node, "fsl,settling", &adapt)) { + lradc->settling_delay = 10; + } else { + if (adapt < 1 || adapt > LRADC_DELAY_DELAY_MASK) { + dev_err(lradc->dev, "Invalid settling delay (%u)\n", + adapt); + return -EINVAL; + } + lradc->settling_delay = adapt; + } + + return 0; +} + +static int mxs_lradc_probe(struct platform_device *pdev) +{ + const struct of_device_id *of_id = + of_match_device(mxs_lradc_dt_ids, &pdev->dev); + const struct mxs_lradc_of_config *of_cfg = + &mxs_lradc_of_config[(enum mxs_lradc_id)of_id->data]; + struct device *dev = &pdev->dev; + struct device_node *node = dev->of_node; + struct mxs_lradc *lradc; + struct iio_dev *iio; + struct resource *iores; + int ret = 0, touch_ret; + int i, s; + uint64_t scale_uv; + + /* Allocate the IIO device. */ + iio = devm_iio_device_alloc(dev, sizeof(*lradc)); + if (!iio) { + dev_err(dev, "Failed to allocate IIO device\n"); + return -ENOMEM; + } + + lradc = iio_priv(iio); + lradc->soc = (enum mxs_lradc_id)of_id->data; + + /* Grab the memory area */ + iores = platform_get_resource(pdev, IORESOURCE_MEM, 0); + lradc->dev = &pdev->dev; + lradc->base = devm_ioremap_resource(dev, iores); + if (IS_ERR(lradc->base)) + return PTR_ERR(lradc->base); + + lradc->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(lradc->clk)) { + dev_err(dev, "Failed to get the delay unit clock\n"); + return PTR_ERR(lradc->clk); + } + ret = clk_prepare_enable(lradc->clk); + if (ret != 0) { + dev_err(dev, "Failed to enable the delay unit clock\n"); + return ret; + } + + touch_ret = mxs_lradc_probe_touchscreen(lradc, node); + + if (touch_ret == 0) + lradc->buffer_vchans = BUFFER_VCHANS_LIMITED; + else + lradc->buffer_vchans = BUFFER_VCHANS_ALL; + + /* Grab all IRQ sources */ + for (i = 0; i < of_cfg->irq_count; i++) { + lradc->irq[i] = platform_get_irq(pdev, i); + if (lradc->irq[i] < 0) { + ret = lradc->irq[i]; + goto err_clk; + } + + ret = devm_request_irq(dev, lradc->irq[i], + mxs_lradc_handle_irq, 0, + of_cfg->irq_name[i], iio); + if (ret) + goto err_clk; + } + + lradc->vref_mv = of_cfg->vref_mv; + + platform_set_drvdata(pdev, iio); + + init_completion(&lradc->completion); + mutex_init(&lradc->lock); + + iio->name = pdev->name; + iio->dev.parent = &pdev->dev; + iio->info = &mxs_lradc_iio_info; + iio->modes = INDIO_DIRECT_MODE; + iio->channels = mxs_lradc_chan_spec; + iio->num_channels = ARRAY_SIZE(mxs_lradc_chan_spec); + iio->masklength = LRADC_MAX_TOTAL_CHANS; + + ret = iio_triggered_buffer_setup(iio, &iio_pollfunc_store_time, + &mxs_lradc_trigger_handler, + &mxs_lradc_buffer_ops); + if (ret) + goto err_clk; + + ret = mxs_lradc_trigger_init(iio); + if (ret) + goto err_trig; + + /* Populate available ADC input ranges */ + for (i = 0; i < LRADC_MAX_TOTAL_CHANS; i++) { + for (s = 0; s < ARRAY_SIZE(lradc->scale_avail[i]); s++) { + /* + * [s=0] = optional divider by two disabled (default) + * [s=1] = optional divider by two enabled + * + * The scale is calculated by doing: + * Vref >> (realbits - s) + * which multiplies by two on the second component + * of the array. + */ + scale_uv = ((u64)lradc->vref_mv[i] * 100000000) >> + (LRADC_RESOLUTION - s); + lradc->scale_avail[i][s].nano = + do_div(scale_uv, 100000000) * 10; + lradc->scale_avail[i][s].integer = scale_uv; + } + } + + /* Configure the hardware. */ + ret = mxs_lradc_hw_init(lradc); + if (ret) + goto err_dev; + + /* Register the touchscreen input device. */ + if (touch_ret == 0) { + ret = mxs_lradc_ts_register(lradc); + if (ret) + goto err_ts_register; + } + + /* Register IIO device. */ + ret = iio_device_register(iio); + if (ret) { + dev_err(dev, "Failed to register IIO device\n"); + goto err_ts; + } + + return 0; + +err_ts: + mxs_lradc_ts_unregister(lradc); +err_ts_register: + mxs_lradc_hw_stop(lradc); +err_dev: + mxs_lradc_trigger_remove(iio); +err_trig: + iio_triggered_buffer_cleanup(iio); +err_clk: + clk_disable_unprepare(lradc->clk); + return ret; +} + +static int mxs_lradc_remove(struct platform_device *pdev) +{ + struct iio_dev *iio = platform_get_drvdata(pdev); + struct mxs_lradc *lradc = iio_priv(iio); + + iio_device_unregister(iio); + mxs_lradc_ts_unregister(lradc); + mxs_lradc_hw_stop(lradc); + mxs_lradc_trigger_remove(iio); + iio_triggered_buffer_cleanup(iio); + + clk_disable_unprepare(lradc->clk); + return 0; +} + +static struct platform_driver mxs_lradc_driver = { + .driver = { + .name = DRIVER_NAME, + .of_match_table = mxs_lradc_dt_ids, + }, + .probe = mxs_lradc_probe, + .remove = mxs_lradc_remove, +}; + +module_platform_driver(mxs_lradc_driver); + +MODULE_AUTHOR("Marek Vasut "); +MODULE_DESCRIPTION("Freescale i.MX28 LRADC driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:" DRIVER_NAME); diff --git a/drivers/staging/iio/adc/spear_adc.c b/drivers/staging/iio/adc/spear_adc.c new file mode 100644 index 000000000..c5382374d --- /dev/null +++ b/drivers/staging/iio/adc/spear_adc.c @@ -0,0 +1,400 @@ +/* + * ST SPEAr ADC driver + * + * Copyright 2012 Stefan Roese + * + * Licensed under the GPL-2. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +/* SPEAR registers definitions */ +#define SPEAR600_ADC_SCAN_RATE_LO(x) ((x) & 0xFFFF) +#define SPEAR600_ADC_SCAN_RATE_HI(x) (((x) >> 0x10) & 0xFFFF) +#define SPEAR_ADC_CLK_LOW(x) (((x) & 0xf) << 0) +#define SPEAR_ADC_CLK_HIGH(x) (((x) & 0xf) << 4) + +/* Bit definitions for SPEAR_ADC_STATUS */ +#define SPEAR_ADC_STATUS_START_CONVERSION BIT(0) +#define SPEAR_ADC_STATUS_CHANNEL_NUM(x) ((x) << 1) +#define SPEAR_ADC_STATUS_ADC_ENABLE BIT(4) +#define SPEAR_ADC_STATUS_AVG_SAMPLE(x) ((x) << 5) +#define SPEAR_ADC_STATUS_VREF_INTERNAL BIT(9) + +#define SPEAR_ADC_DATA_MASK 0x03ff +#define SPEAR_ADC_DATA_BITS 10 + +#define SPEAR_ADC_MOD_NAME "spear-adc" + +#define SPEAR_ADC_CHANNEL_NUM 8 + +#define SPEAR_ADC_CLK_MIN 2500000 +#define SPEAR_ADC_CLK_MAX 20000000 + +struct adc_regs_spear3xx { + u32 status; + u32 average; + u32 scan_rate; + u32 clk; /* Not avail for 1340 & 1310 */ + u32 ch_ctrl[SPEAR_ADC_CHANNEL_NUM]; + u32 ch_data[SPEAR_ADC_CHANNEL_NUM]; +}; + +struct chan_data { + u32 lsb; + u32 msb; +}; + +struct adc_regs_spear6xx { + u32 status; + u32 pad[2]; + u32 clk; + u32 ch_ctrl[SPEAR_ADC_CHANNEL_NUM]; + struct chan_data ch_data[SPEAR_ADC_CHANNEL_NUM]; + u32 scan_rate_lo; + u32 scan_rate_hi; + struct chan_data average; +}; + +struct spear_adc_state { + struct device_node *np; + struct adc_regs_spear3xx __iomem *adc_base_spear3xx; + struct adc_regs_spear6xx __iomem *adc_base_spear6xx; + struct clk *clk; + struct completion completion; + u32 current_clk; + u32 sampling_freq; + u32 avg_samples; + u32 vref_external; + u32 value; +}; + +/* + * Functions to access some SPEAr ADC register. Abstracted into + * static inline functions, because of different register offsets + * on different SoC variants (SPEAr300 vs SPEAr600 etc). + */ +static void spear_adc_set_status(struct spear_adc_state *st, u32 val) +{ + __raw_writel(val, &st->adc_base_spear6xx->status); +} + +static void spear_adc_set_clk(struct spear_adc_state *st, u32 val) +{ + u32 clk_high, clk_low, count; + u32 apb_clk = clk_get_rate(st->clk); + + count = DIV_ROUND_UP(apb_clk, val); + clk_low = count / 2; + clk_high = count - clk_low; + st->current_clk = apb_clk / count; + + __raw_writel(SPEAR_ADC_CLK_LOW(clk_low) | SPEAR_ADC_CLK_HIGH(clk_high), + &st->adc_base_spear6xx->clk); +} + +static void spear_adc_set_ctrl(struct spear_adc_state *st, int n, + u32 val) +{ + __raw_writel(val, &st->adc_base_spear6xx->ch_ctrl[n]); +} + +static u32 spear_adc_get_average(struct spear_adc_state *st) +{ + if (of_device_is_compatible(st->np, "st,spear600-adc")) { + return __raw_readl(&st->adc_base_spear6xx->average.msb) & + SPEAR_ADC_DATA_MASK; + } else { + return __raw_readl(&st->adc_base_spear3xx->average) & + SPEAR_ADC_DATA_MASK; + } +} + +static void spear_adc_set_scanrate(struct spear_adc_state *st, u32 rate) +{ + if (of_device_is_compatible(st->np, "st,spear600-adc")) { + __raw_writel(SPEAR600_ADC_SCAN_RATE_LO(rate), + &st->adc_base_spear6xx->scan_rate_lo); + __raw_writel(SPEAR600_ADC_SCAN_RATE_HI(rate), + &st->adc_base_spear6xx->scan_rate_hi); + } else { + __raw_writel(rate, &st->adc_base_spear3xx->scan_rate); + } +} + +static int spear_adc_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long mask) +{ + struct spear_adc_state *st = iio_priv(indio_dev); + u32 status; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + mutex_lock(&indio_dev->mlock); + + status = SPEAR_ADC_STATUS_CHANNEL_NUM(chan->channel) | + SPEAR_ADC_STATUS_AVG_SAMPLE(st->avg_samples) | + SPEAR_ADC_STATUS_START_CONVERSION | + SPEAR_ADC_STATUS_ADC_ENABLE; + if (st->vref_external == 0) + status |= SPEAR_ADC_STATUS_VREF_INTERNAL; + + spear_adc_set_status(st, status); + wait_for_completion(&st->completion); /* set by ISR */ + *val = st->value; + + mutex_unlock(&indio_dev->mlock); + + return IIO_VAL_INT; + + case IIO_CHAN_INFO_SCALE: + *val = st->vref_external; + *val2 = SPEAR_ADC_DATA_BITS; + return IIO_VAL_FRACTIONAL_LOG2; + case IIO_CHAN_INFO_SAMP_FREQ: + *val = st->current_clk; + return IIO_VAL_INT; + } + + return -EINVAL; +} + +static int spear_adc_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, + int val2, + long mask) +{ + struct spear_adc_state *st = iio_priv(indio_dev); + int ret = 0; + + if (mask != IIO_CHAN_INFO_SAMP_FREQ) + return -EINVAL; + + mutex_lock(&indio_dev->mlock); + + if ((val < SPEAR_ADC_CLK_MIN) || + (val > SPEAR_ADC_CLK_MAX) || + (val2 != 0)) { + ret = -EINVAL; + goto out; + } + + spear_adc_set_clk(st, val); + +out: + mutex_unlock(&indio_dev->mlock); + return ret; +} + +#define SPEAR_ADC_CHAN(idx) { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ),\ + .channel = idx, \ +} + +static const struct iio_chan_spec spear_adc_iio_channels[] = { + SPEAR_ADC_CHAN(0), + SPEAR_ADC_CHAN(1), + SPEAR_ADC_CHAN(2), + SPEAR_ADC_CHAN(3), + SPEAR_ADC_CHAN(4), + SPEAR_ADC_CHAN(5), + SPEAR_ADC_CHAN(6), + SPEAR_ADC_CHAN(7), +}; + +static irqreturn_t spear_adc_isr(int irq, void *dev_id) +{ + struct spear_adc_state *st = dev_id; + + /* Read value to clear IRQ */ + st->value = spear_adc_get_average(st); + complete(&st->completion); + + return IRQ_HANDLED; +} + +static int spear_adc_configure(struct spear_adc_state *st) +{ + int i; + + /* Reset ADC core */ + spear_adc_set_status(st, 0); + __raw_writel(0, &st->adc_base_spear6xx->clk); + for (i = 0; i < 8; i++) + spear_adc_set_ctrl(st, i, 0); + spear_adc_set_scanrate(st, 0); + + spear_adc_set_clk(st, st->sampling_freq); + + return 0; +} + +static const struct iio_info spear_adc_info = { + .read_raw = &spear_adc_read_raw, + .write_raw = &spear_adc_write_raw, + .driver_module = THIS_MODULE, +}; + +static int spear_adc_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct device *dev = &pdev->dev; + struct spear_adc_state *st; + struct iio_dev *indio_dev = NULL; + int ret = -ENODEV; + int irq; + + indio_dev = devm_iio_device_alloc(dev, sizeof(struct spear_adc_state)); + if (!indio_dev) { + dev_err(dev, "failed allocating iio device\n"); + return -ENOMEM; + } + + st = iio_priv(indio_dev); + st->np = np; + + /* + * SPEAr600 has a different register layout than other SPEAr SoC's + * (e.g. SPEAr3xx). Let's provide two register base addresses + * to support multi-arch kernels. + */ + st->adc_base_spear6xx = of_iomap(np, 0); + if (!st->adc_base_spear6xx) { + dev_err(dev, "failed mapping memory\n"); + return -ENOMEM; + } + st->adc_base_spear3xx = + (struct adc_regs_spear3xx __iomem *)st->adc_base_spear6xx; + + st->clk = clk_get(dev, NULL); + if (IS_ERR(st->clk)) { + dev_err(dev, "failed getting clock\n"); + goto errout1; + } + + ret = clk_prepare_enable(st->clk); + if (ret) { + dev_err(dev, "failed enabling clock\n"); + goto errout2; + } + + irq = platform_get_irq(pdev, 0); + if (irq <= 0) { + dev_err(dev, "failed getting interrupt resource\n"); + ret = -EINVAL; + goto errout3; + } + + ret = devm_request_irq(dev, irq, spear_adc_isr, 0, SPEAR_ADC_MOD_NAME, + st); + if (ret < 0) { + dev_err(dev, "failed requesting interrupt\n"); + goto errout3; + } + + if (of_property_read_u32(np, "sampling-frequency", + &st->sampling_freq)) { + dev_err(dev, "sampling-frequency missing in DT\n"); + ret = -EINVAL; + goto errout3; + } + + /* + * Optional avg_samples defaults to 0, resulting in single data + * conversion + */ + of_property_read_u32(np, "average-samples", &st->avg_samples); + + /* + * Optional vref_external defaults to 0, resulting in internal vref + * selection + */ + of_property_read_u32(np, "vref-external", &st->vref_external); + + spear_adc_configure(st); + + platform_set_drvdata(pdev, indio_dev); + + init_completion(&st->completion); + + indio_dev->name = SPEAR_ADC_MOD_NAME; + indio_dev->dev.parent = dev; + indio_dev->info = &spear_adc_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = spear_adc_iio_channels; + indio_dev->num_channels = ARRAY_SIZE(spear_adc_iio_channels); + + ret = iio_device_register(indio_dev); + if (ret) + goto errout3; + + dev_info(dev, "SPEAR ADC driver loaded, IRQ %d\n", irq); + + return 0; + +errout3: + clk_disable_unprepare(st->clk); +errout2: + clk_put(st->clk); +errout1: + iounmap(st->adc_base_spear6xx); + return ret; +} + +static int spear_adc_remove(struct platform_device *pdev) +{ + struct iio_dev *indio_dev = platform_get_drvdata(pdev); + struct spear_adc_state *st = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + clk_disable_unprepare(st->clk); + clk_put(st->clk); + iounmap(st->adc_base_spear6xx); + + return 0; +} + +#ifdef CONFIG_OF +static const struct of_device_id spear_adc_dt_ids[] = { + { .compatible = "st,spear600-adc", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, spear_adc_dt_ids); +#endif + +static struct platform_driver spear_adc_driver = { + .probe = spear_adc_probe, + .remove = spear_adc_remove, + .driver = { + .name = SPEAR_ADC_MOD_NAME, + .of_match_table = of_match_ptr(spear_adc_dt_ids), + }, +}; + +module_platform_driver(spear_adc_driver); + +MODULE_AUTHOR("Stefan Roese "); +MODULE_DESCRIPTION("SPEAr ADC driver"); +MODULE_LICENSE("GPL"); -- cgit v1.2.3-54-g00ecf