This enables the use of buffers on ADC channels of sunxi-gpadc-iio driver. It also prepares the code which will be used by the touchscreen driver named sunxi-gpadc-ts. The GPADC on Allwinner SoCs (A10, A13 and A31) has a 12 bits register for conversion's data. The GPADC uses the same ADC channels for the ADC and the touchscreen therefore exposes these channels to the sunxi-gpadc-ts iio consumer which will be in charge of reading data from these channels for the input framework. The temperature can only be read when in touchscreen mode. This means if the buffers are being used for the ADC, the temperature sensor cannot be read. When a FIFO_DATA_PENDING irq occurs, its handler will read the entire FIFO and fill a buffer before sending it to the consumers which registered in IIO for the ADC channels. When a consumer starts buffering ADC channels, sunxi_gpadc_buffer_postenable is called and will enable FIFO_DATA_PENDING irq and select the mode in which the GPADC should run (ADC or touchscreen) depending on a property of the DT ("allwinner,ts-attached"). When the consumer stops buffering, it disables the same irq. Signed-off-by: Quentin Schulz <quentin.schulz@xxxxxxxxxxxxxxxxxx> --- drivers/iio/adc/Kconfig | 1 + drivers/iio/adc/sunxi-gpadc-iio.c | 153 ++++++++++++++++++++++++++++++++++---- 2 files changed, 138 insertions(+), 16 deletions(-) diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig index 184856f..15e3b08 100644 --- a/drivers/iio/adc/Kconfig +++ b/drivers/iio/adc/Kconfig @@ -342,6 +342,7 @@ config SUNXI_ADC tristate "ADC driver for sunxi platforms" depends on IIO depends on MFD_SUNXI_ADC + depends on IIO_BUFFER_CB help Say yes here to build support for Allwinner (A10, A13 and A31) SoCs ADC. This ADC provides 4 channels which can be used as an ADC or as a diff --git a/drivers/iio/adc/sunxi-gpadc-iio.c b/drivers/iio/adc/sunxi-gpadc-iio.c index 87cc913..2e44ca7 100644 --- a/drivers/iio/adc/sunxi-gpadc-iio.c +++ b/drivers/iio/adc/sunxi-gpadc-iio.c @@ -16,8 +16,9 @@ #include <linux/platform_device.h> #include <linux/regmap.h> -#include <linux/iio/iio.h> +#include <linux/iio/buffer.h> #include <linux/iio/driver.h> +#include <linux/iio/iio.h> #include <linux/iio/machine.h> #include <linux/mfd/sunxi-gpadc-mfd.h> @@ -71,6 +72,7 @@ #define SUNXI_GPADC_TP_DATA_XY_CHANGE BIT(13) #define SUNXI_GPADC_TP_FIFO_TRIG_LEVEL(x) ((x) << 8) /* 5 bits */ #define SUNXI_GPADC_TP_DATA_DRQ_EN BIT(7) +/* Be careful, flushing FIFO spawns SUNXI_GPADC_FIFO_DATA_PENDING interrupts */ #define SUNXI_GPADC_TP_FIFO_FLUSH BIT(4) #define SUNXI_GPADC_TP_UP_IRQ_EN BIT(1) #define SUNXI_GPADC_TP_DOWN_IRQ_EN BIT(0) @@ -79,6 +81,7 @@ #define SUNXI_GPADC_TEMP_DATA_PENDING BIT(18) #define SUNXI_GPADC_FIFO_OVERRUN_PENDING BIT(17) #define SUNXI_GPADC_FIFO_DATA_PENDING BIT(16) +#define SUNXI_GPADC_RXA_CNT GENMASK(12, 8) #define SUNXI_GPADC_TP_IDLE_FLG BIT(2) #define SUNXI_GPADC_TP_UP_PENDING BIT(1) #define SUNXI_GPADC_TP_DOWN_PENDING BIT(0) @@ -101,19 +104,43 @@ struct sunxi_gpadc_dev { unsigned int fifo_data_irq; unsigned int temp_data_irq; unsigned int flags; + struct iio_dev *indio_dev; + struct sunxi_gpadc_buffer buffer; + bool ts_attached; + bool buffered; }; -#define SUNXI_GPADC_ADC_CHANNEL(_channel, _name) { \ +#define SUNXI_GPADC_ADC_CHANNEL(_channel, _name, _index) { \ .type = IIO_VOLTAGE, \ .indexed = 1, \ .channel = _channel, \ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ .datasheet_name = _name, \ + .scan_index = _index, \ + .scan_type = { \ + .sign = 'u', \ + .realbits = 12, \ + .storagebits = 16, \ + .shift = 0, \ + .endianness = IIO_LE, \ + }, \ } static struct iio_map sunxi_gpadc_hwmon_maps[] = { { + .adc_channel_label = "adc_chan0", + .consumer_dev_name = "sunxi-gpadc-ts.0", + }, { + .adc_channel_label = "adc_chan1", + .consumer_dev_name = "sunxi-gpadc-ts.0", + }, { + .adc_channel_label = "adc_chan2", + .consumer_dev_name = "sunxi-gpadc-ts.0", + }, { + .adc_channel_label = "adc_chan3", + .consumer_dev_name = "sunxi-gpadc-ts.0", + }, { .adc_channel_label = "temp_adc", .consumer_dev_name = "iio_hwmon.0", }, @@ -121,28 +148,33 @@ static struct iio_map sunxi_gpadc_hwmon_maps[] = { }; static const struct iio_chan_spec sunxi_gpadc_channels[] = { - SUNXI_GPADC_ADC_CHANNEL(0, "adc_chan0"), - SUNXI_GPADC_ADC_CHANNEL(1, "adc_chan1"), - SUNXI_GPADC_ADC_CHANNEL(2, "adc_chan2"), - SUNXI_GPADC_ADC_CHANNEL(3, "adc_chan3"), + SUNXI_GPADC_ADC_CHANNEL(0, "adc_chan0", 1), + SUNXI_GPADC_ADC_CHANNEL(1, "adc_chan1", 2), + SUNXI_GPADC_ADC_CHANNEL(2, "adc_chan2", 3), + SUNXI_GPADC_ADC_CHANNEL(3, "adc_chan3", 4), { .type = IIO_TEMP, .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), .datasheet_name = "temp_adc", .extend_name = "SoC temperature", }, - { /* sentinel */ }, }; static int sunxi_gpadc_adc_read(struct iio_dev *indio_dev, int channel, int *val) { struct sunxi_gpadc_dev *info = iio_priv(indio_dev); + bool buffered = info->buffered; int ret = 0; + unsigned int reg; mutex_lock(&indio_dev->mlock); reinit_completion(&info->completion); + + reg = SUNXI_GPADC_TP_FIFO_TRIG_LEVEL(1) | SUNXI_GPADC_TP_FIFO_FLUSH; + regmap_update_bits(info->regmap, SUNXI_GPADC_TP_INT_FIFOC, reg, reg); + if (info->flags & SUNXI_GPADC_ARCH_SUN6I) regmap_write(info->regmap, SUNXI_GPADC_TP_CTRL1, SUNXI_GPADC_SUN6I_TP_MODE_EN | @@ -153,9 +185,9 @@ static int sunxi_gpadc_adc_read(struct iio_dev *indio_dev, int channel, SUNXI_GPADC_TP_MODE_EN | SUNXI_GPADC_TP_ADC_SELECT | SUNXI_GPADC_ADC_CHAN_SELECT(channel)); - regmap_write(info->regmap, SUNXI_GPADC_TP_INT_FIFOC, - SUNXI_GPADC_TP_FIFO_TRIG_LEVEL(1) | - SUNXI_GPADC_TP_FIFO_FLUSH); + + info->buffered = false; + enable_irq(info->fifo_data_irq); if (!wait_for_completion_timeout(&info->completion, @@ -169,6 +201,7 @@ static int sunxi_gpadc_adc_read(struct iio_dev *indio_dev, int channel, out: disable_irq(info->fifo_data_irq); mutex_unlock(&indio_dev->mlock); + info->buffered = buffered; return ret; } @@ -177,20 +210,22 @@ static int sunxi_gpadc_temp_read(struct iio_dev *indio_dev, int *val) { struct sunxi_gpadc_dev *info = iio_priv(indio_dev); int ret = 0; + unsigned int reg; mutex_lock(&indio_dev->mlock); reinit_completion(&info->completion); - regmap_write(info->regmap, SUNXI_GPADC_TP_INT_FIFOC, - SUNXI_GPADC_TP_FIFO_TRIG_LEVEL(1) | - SUNXI_GPADC_TP_FIFO_FLUSH); + reg = SUNXI_GPADC_TP_FIFO_TRIG_LEVEL(1) | SUNXI_GPADC_TP_FIFO_FLUSH; + regmap_update_bits(info->regmap, SUNXI_GPADC_TP_INT_FIFOC, reg, reg); + if (info->flags & SUNXI_GPADC_ARCH_SUN6I) regmap_write(info->regmap, SUNXI_GPADC_TP_CTRL1, SUNXI_GPADC_SUN6I_TP_MODE_EN); else regmap_write(info->regmap, SUNXI_GPADC_TP_CTRL1, SUNXI_GPADC_TP_MODE_EN); + enable_irq(info->temp_data_irq); if (!wait_for_completion_timeout(&info->completion, @@ -211,7 +246,6 @@ out: mutex_unlock(&indio_dev->mlock); return ret; - } static int sunxi_gpadc_read_raw(struct iio_dev *indio_dev, @@ -219,15 +253,22 @@ static int sunxi_gpadc_read_raw(struct iio_dev *indio_dev, int *val, int *val2, long mask) { int ret; + struct sunxi_gpadc_dev *info = iio_priv(indio_dev); switch (mask) { case IIO_CHAN_INFO_PROCESSED: + if (info->buffered && !info->ts_attached) + return -EBUSY; + ret = sunxi_gpadc_temp_read(indio_dev, val); if (ret) return ret; return IIO_VAL_INT; case IIO_CHAN_INFO_RAW: + if (info->buffered) + return -EBUSY; + ret = sunxi_gpadc_adc_read(indio_dev, chan->channel, val); if (ret) return ret; @@ -261,7 +302,29 @@ static irqreturn_t sunxi_gpadc_temp_data_irq_handler(int irq, void *dev_id) static irqreturn_t sunxi_gpadc_fifo_data_irq_handler(int irq, void *dev_id) { struct sunxi_gpadc_dev *info = dev_id; - int ret; + int ret, reg, i, fifo_count; + + if (info->buffered) { + if (regmap_read(info->regmap, SUNXI_GPADC_TP_INT_FIFOS, ®)) + return IRQ_HANDLED; + + fifo_count = (reg & SUNXI_GPADC_RXA_CNT) >> 8; + /* Sometimes, the interrupt occurs when the FIFO is empty. */ + if (!fifo_count) + return IRQ_HANDLED; + + for (i = 0; i < fifo_count; i++) { + if (regmap_read(info->regmap, SUNXI_GPADC_TP_DATA, + &info->buffer.buffer[i])) + return IRQ_HANDLED; + } + + info->buffer.buff_size = i; + + iio_push_to_buffers(info->indio_dev, &info->buffer); + + return IRQ_HANDLED; + } ret = regmap_read(info->regmap, SUNXI_GPADC_TP_DATA, &info->adc_data); if (ret == 0) @@ -270,6 +333,58 @@ static irqreturn_t sunxi_gpadc_fifo_data_irq_handler(int irq, void *dev_id) return IRQ_HANDLED; } +static int sunxi_gpadc_buffer_postenable(struct iio_dev *indio_dev) +{ + struct sunxi_gpadc_dev *info = iio_priv(indio_dev); + unsigned int reg; + int ret; + + reg = SUNXI_GPADC_TP_FIFO_TRIG_LEVEL(1) | SUNXI_GPADC_TP_FIFO_FLUSH; + regmap_update_bits(info->regmap, SUNXI_GPADC_TP_INT_FIFOC, reg, reg); + + if (info->ts_attached) { + reg = SUNXI_GPADC_STYLUS_UP_DEBOUNCE(5) | + SUNXI_GPADC_STYLUS_UP_DEBOUNCE_EN; + + if (info->flags & SUNXI_GPADC_ARCH_SUN6I) + reg |= SUNXI_GPADC_SUN6I_TP_MODE_EN; + else + reg |= SUNXI_GPADC_TP_MODE_EN; + } else { + if (info->flags & SUNXI_GPADC_ARCH_SUN6I) + reg = SUNXI_GPADC_SUN6I_TP_MODE_EN | + SUNXI_GPADC_SUN6I_TP_ADC_SELECT; + else + reg = SUNXI_GPADC_TP_MODE_EN | + SUNXI_GPADC_TP_ADC_SELECT; + } + + if (regmap_write(info->regmap, SUNXI_GPADC_TP_CTRL1, reg)) + return ret; + + info->buffered = true; + + enable_irq(info->fifo_data_irq); + + return 0; +} + +static int sunxi_gpadc_buffer_predisable(struct iio_dev *indio_dev) +{ + struct sunxi_gpadc_dev *info = iio_priv(indio_dev); + + disable_irq(info->fifo_data_irq); + + info->buffered = false; + + return 0; +} + +static const struct iio_buffer_setup_ops sunxi_gpadc_buffer_setup_ops = { + .postenable = sunxi_gpadc_buffer_postenable, + .predisable = sunxi_gpadc_buffer_predisable, +}; + static int sunxi_gpadc_probe(struct platform_device *pdev) { struct sunxi_gpadc_dev *info; @@ -286,16 +401,20 @@ static int sunxi_gpadc_probe(struct platform_device *pdev) info = iio_priv(indio_dev); info->regmap = sunxi_gpadc_mfd_dev->regmap; + info->indio_dev = indio_dev; init_completion(&info->completion); indio_dev->name = dev_name(&pdev->dev); indio_dev->dev.parent = &pdev->dev; indio_dev->dev.of_node = pdev->dev.of_node; indio_dev->info = &sunxi_gpadc_iio_info; - indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->modes = INDIO_DIRECT_MODE | INDIO_BUFFER_SOFTWARE; indio_dev->num_channels = ARRAY_SIZE(sunxi_gpadc_channels); indio_dev->channels = sunxi_gpadc_channels; + indio_dev->setup_ops = &sunxi_gpadc_buffer_setup_ops; info->flags = platform_get_device_id(pdev)->driver_data; + info->ts_attached = of_property_read_bool(pdev->dev.parent->of_node, + "allwinner,ts-attached"); regmap_write(info->regmap, SUNXI_GPADC_TP_CTRL0, SUNXI_GPADC_FS_DIV(7) | SUNXI_GPADC_ADC_CLK_DIVIDER(2) | SUNXI_GPADC_T_ACQ(63)); @@ -305,6 +424,8 @@ static int sunxi_gpadc_probe(struct platform_device *pdev) else regmap_write(info->regmap, SUNXI_GPADC_TP_CTRL1, SUNXI_GPADC_TP_MODE_EN); + regmap_write(info->regmap, SUNXI_GPADC_TP_CTRL2, + SUNXI_GPADC_TP_SENSITIVE_ADJUST(15)); regmap_write(info->regmap, SUNXI_GPADC_TP_CTRL3, SUNXI_GPADC_FILTER_EN | SUNXI_GPADC_FILTER_TYPE(1)); regmap_write(info->regmap, SUNXI_GPADC_TP_TPR, -- 2.5.0 -- To unsubscribe from this list: send the line "unsubscribe linux-input" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html