> 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. comments below > 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; why add buffered, duplicate state and not query iio_buffer_enabled()? > }; > > -#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, \ shift not strictly needed > + .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; there would be iio_device_claim_direct_mode() > + > 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, > -- Peter Meerwald-Stadler +43-664-2444418 (mobile) -- 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