[PATCH 2/2] iio: exynos-adc: add experimental touchscreen support

Jonathan Cameron
2014-07-20T13:51:37Z

On 20/07/14 14:49, Jonathan Cameron wrote:
> On 18/07/14 20:29, Arnd Bergmann wrote:
>> This adds support for the touchscreen on Samsung s3c64xx.
>> The driver is completely untested but shows roughly how
>> it could be done, following the example of the at91 driver.
>>
> Hi Arnd,
Cc'd linux-input and Dmitry.
>
>> Open questions include:
>>
>> - compared to the old plat-samsung/adc driver, there is
>> no support for prioritizing ts over other clients, nor
>> for oversampling. From my reading of the code, the
>> priorities didn't actually have any effect at all, but
>> the oversampling might be needed. Maybe the original
>> authors have some insight.
>>
>> - I simply register the input device from the adc driver
>> itself, as the at91 code does. The driver also supports
>> sub-nodes, but I don't understand how they are meant
>> to be used, so using those might be better.
> So, the alternative you are (I think) referring to is to use
> the buffered in kernel client interface. That way a separate
> touch screen driver can use the output channels provided by IIO
> in much the same way you might use a regulator or similar.
> Note that whilst this is similar to the simple polled interface
> used for things like the iio_hwmon driver, the data flow is
> quite different (clearly the polled interfce would be
> inappropriate here).
>
> Whilst we've discussed it in the past for touch screen drivers
> like this, usually the hardware isn't generic enough to be
> of any real use if not being used as a touch screen. As such
> it's often simpler to just have the support directly in the
> driver (as you've observed the at91 driver does this).
>
> Whilst the interface has been there a while, it's not really had
> all that much use. The original target was the simpler case
> of 3D accelerometer where we have a generic iio to input
> bridge driver. Time constraints meant that I haven't yet actually
> formally submitted the input side of this. Whilst there are lots
> of other things that can use this interface, right now nothing
> actually does so.
>
>>
>> - The new exynos_read_s3c64xx_ts() function is intentionally
>> very similar to the existing exynos_read_raw() functions.
>> It should probably be changed, either by merging the two
>> into one, or by simplifying the exynos_read_s3c64xx_ts()
>> function. This depends a bit on the answers to the questions
>> above.
> I'd be tempted to not bother keeping them that similar. It's
> not a generic IIO channel so simplify it where possible.
>>
>> - We probably need to add support for platform_data as well,
>> I've skipped this so far.
>>
>> - Is anybody able to debug this driver on real hardware?
>> While it's possible that it actually works, it's more
>> likely that I made a few subtle mistakes.
>>
>> Signed-off-by: Arnd Bergmann <arnd@arndb.de>
> Looks pretty good to me. A few symantic bits and pieces and
> one bug spotted. Short and sweet.
>>
>> diff --git a/Documentation/devicetree/bindings/arm/samsung/exynos-adc.txt b/Documentation/devicetree/bindings/arm/samsung/exynos-adc.txt
>> index e1b74828f413..4329bf3c3326 100644
>> --- a/Documentation/devicetree/bindings/arm/samsung/exynos-adc.txt
>> +++ b/Documentation/devicetree/bindings/arm/samsung/exynos-adc.txt
>> @@ -41,6 +41,10 @@ Required properties:
>> and compatible ADC block)
>> - vdd-supply VDD input supply.
>>
>> +Optional properties:
>> +- has-touchscreen: If present, indicates that a touchscreen is
>> + connected an usable.
>> +
>> Note: child nodes can be added for auto probing from device tree.
>>
>> Example: adding device info in dtsi file
>> diff --git a/drivers/iio/adc/exynos_adc.c b/drivers/iio/adc/exynos_adc.c
>> index 5f95638513d2..cf1d9f3e2492 100644
>> --- a/drivers/iio/adc/exynos_adc.c
>> +++ b/drivers/iio/adc/exynos_adc.c
>> @@ -34,6 +34,7 @@
>> #include <linux/regulator/consumer.h>
>> #include <linux/of_platform.h>
>> #include <linux/err.h>
>> +#include <linux/input.h>
> Might want to make the input side optional at compile time...
> I supose the existing parts are unlikely to be used much in headless
> devices, but you never know. Maybe we just leave this until someone
> shouts they want to be able to avoid compiling it in.
>>
>> #include <linux/iio/iio.h>
>> #include <linux/iio/machine.h>
>> @@ -103,6 +104,7 @@
>>
>> /* Bit definitions common for ADC_V1 and ADC_V2 */
>> #define ADC_CON_EN_START (1u << 0)
>> +#define ADC_DATX_PRESSED (1u << 15)
>> #define ADC_DATX_MASK 0xFFF
>>
>> #define EXYNOS_ADC_TIMEOUT (msecs_to_jiffies(100))
>> @@ -110,16 +112,20 @@
>> struct exynos_adc {
>> struct exynos_adc_data *data;
>> struct device *dev;
>> + struct input_dev *input;
>> void __iomem *regs;
>> void __iomem *enable_reg;
>> struct clk *clk;
>> struct clk *sclk;
>> unsigned int irq;
>> + unsigned int tsirq;
>> struct regulator *vdd;
>>
>> struct completion completion;
>>
>> + bool read_ts;
>> u32 value;
>> + u32 value2;
> As I state further down, I'd rather keep a little
> clear of the naming used in IIO for bits that aren't
> going through IIO (less confusing!). Maybe just
> have
> u32 x, y;
>> unsigned int version;
>> };
>>
>> @@ -390,12 +396,61 @@ static int exynos_read_raw(struct iio_dev *indio_dev,
>> return ret;
>> }
>>
>> +static int exynos_read_s3c64xx_ts(struct iio_dev *indio_dev,
>> + struct iio_chan_spec const *chan,
>> + int *val,
>> + int *val2,
>> + long mask)
>> +{
>> + struct exynos_adc *info = iio_priv(indio_dev);
>> + unsigned long timeout;
>> + int ret;
>> +
>> + if (mask != IIO_CHAN_INFO_RAW)
>> + return -EINVAL;
>> +
>> + mutex_lock(&indio_dev->mlock);
>> + info->read_ts = 1;
>> +
>> + reinit_completion(&info->completion);
>> +
>> + writel(ADC_S3C2410_TSC_PULL_UP_DISABLE | ADC_TSC_AUTOPST,
>> + ADC_V1_TSC(info->regs));
>> +
>> + /* Select the ts channel to be used and Trigger conversion */
>> + info->data->start_conv(info, 0);
> 0 is a rather magic value. A define perhaps?
>> +
>> + timeout = wait_for_completion_timeout
>> + (&info->completion, EXYNOS_ADC_TIMEOUT);
>> + if (timeout == 0) {
>> + dev_warn(&indio_dev->dev, "Conversion timed out! Resetting\n");
>> + if (info->data->init_hw)
>> + info->data->init_hw(info);
>> + ret = -ETIMEDOUT;
>> + } else {
>> + *val = info->value;
>> + *val2 = info->value2;
> This is definitely abuse as those two values are not intended for
> different values. If you want to do this please use different naming
> and don't try to fiddle it into the IIO read raw framework.
> As you've suggested above, better to simplify this code and drop the
> bits cloned from the other handler.
>> + ret = IIO_VAL_INT;
>> + }
>> +
>> + info->read_ts = 0;
>> + mutex_unlock(&indio_dev->mlock);
>> +
>> + return ret;
>> +}
>> +
>> static irqreturn_t exynos_adc_isr(int irq, void *dev_id)
>> {
>> struct exynos_adc *info = (struct exynos_adc *)dev_id;
>>
>> /* Read value */
>> - info->value = readl(ADC_V1_DATX(info->regs)) & ADC_DATX_MASK;
>> + if (info->read_ts) {
>> + info->value = readl(ADC_V1_DATX(info->regs)) & ADC_DATX_MASK;
>> + info->value2 = readl(ADC_V1_DATY(info->regs)) & ADC_DATX_MASK;
> ADC_DATY_MASK would be more obviously correct.
>
>> + writel(ADC_TSC_WAIT4INT | ADC_S3C2443_TSC_UD_SEN, ADC_V1_TSC(info->regs));
> Perhaps the above is cryptic enough to warrant a comment?
>> + } else {
>> + info->value = readl(ADC_V1_DATX(info->regs)) & ADC_DATX_MASK;
>> + }
>>
>> /* clear irq */
>> if (info->data->clear_irq)
>> @@ -406,6 +461,46 @@ static irqreturn_t exynos_adc_isr(int irq, void *dev_id)
>> return IRQ_HANDLED;
>> }
>>
>> +/*
>> + * Here we (ab)use a threaded interrupt handler to stay running
>> + * for as long as the touchscreen remains pressed, we report
>> + * a new event with the latest data and then sleep until the
>> + * next timer tick. This mirrors the behavior of the old
>> + * driver, with much less code.
>> + */
>> +static irqreturn_t exynos_ts_isr(int irq, void *dev_id)
>> +{
>> + struct exynos_adc *info = dev_id;
>> + struct iio_dev *dev = dev_get_drvdata(info->dev);
>> + u32 x, y;
>> + bool pressed;
>> + int ret;
>> +
>> + do {
>> + ret =exynos_read_s3c64xx_ts(dev, NULL, &x, &y, IIO_CHAN_INFO_RAW);
> = exynos
>> + if (ret == -ETIMEDOUT)
>> + break;
>> +
>> + pressed = x & y & ADC_DATX_PRESSED;
>> + if (!pressed)
>> + break;
>> +
>> + input_report_abs(info->input, ABS_X, x & ADC_DATX_MASK);
>> + input_report_abs(info->input, ABS_Y, y & ADC_DATX_MASK);
>> + input_report_key(info->input, BTN_TOUCH, 1);
>> + input_sync(info->input);
>> +
>> + msleep(1);
>> + } while (1);
>> +
>> + input_report_key(info->input, BTN_TOUCH, 0);
>> + input_sync(info->input);
>> +
>> + writel(0, ADC_V1_CLRINTPNDNUP(info->regs));
>> +
>> + return IRQ_HANDLED;
>> +}
>> +
>> static int exynos_adc_reg_access(struct iio_dev *indio_dev,
>> unsigned reg, unsigned writeval,
>> unsigned *readval)
>> @@ -457,12 +552,57 @@ static int exynos_adc_remove_devices(struct device *dev, void *c)
>> return 0;
>> }
>>
>> +static int exynos_adc_ts_init(struct exynos_adc *info)
>> +{
>> + int ret;
>> +
>> + info->input = input_allocate_device();
>> + if (!info->input)
>> + return -ENOMEM;
>> +
>> + info->input->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS);
>> + info->input->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH);
>> +
>> + input_set_abs_params(info->input, ABS_X, 0, 0x3FF, 0, 0);
>> + input_set_abs_params(info->input, ABS_Y, 0, 0x3FF, 0, 0);
>> +
>> + /* data from s3c2410_ts driver */
>> + info->input->name = "S3C24xx TouchScreen";
>> + info->input->id.bustype = BUS_HOST;
>> + info->input->id.vendor = 0xDEAD;
>> + info->input->id.product = 0xBEEF;
>> + info->input->id.version = 0x0200;
>> +
>> + ret = input_register_device(info->input);
>> + if (ret) {
>> + input_free_device(info->input);
>> + goto err;
>> + }
>> +
>> + if (info->tsirq > 0)
>> + ret = request_threaded_irq(info->irq, NULL, exynos_ts_isr,
>> + 0, "touchscreen", info);
> info->tsirq
> (that had me really confused for a moment ;)
> Also, perhaps a more specific name. touchscreen_updown or similar as the
> main interrupt is also used during touchscreen operation.
>> + if (ret < 0) {
>> + dev_err(info->dev, "failed requesting touchsccreen irq, irq = %d\n",
>> + info->irq);
>> + goto err_input;
>> + }
>> +
>> + return 0;
>> +
>> +err_input:
>> + input_unregister_device(info->input);
>> +err:
>> + return ret;
>> +}
>> +
>> static int exynos_adc_probe(struct platform_device *pdev)
>> {
>> struct exynos_adc *info = NULL;
>> struct device_node *np = pdev->dev.of_node;
>> struct iio_dev *indio_dev = NULL;
>> struct resource *mem;
>> + bool has_ts;
>> int ret = -ENODEV;
>> int irq;
>>
>> @@ -498,8 +638,14 @@ static int exynos_adc_probe(struct platform_device *pdev)
>> dev_err(&pdev->dev, "no irq resource?\n");
>> return irq;
>> }
>> -
>> info->irq = irq;
>> +
>> + irq = platform_get_irq(pdev, 1);
>> + if (irq == -EPROBE_DEFER)
>> + return irq;
>> +
>> + info->tsirq = irq;
>> +
>> info->dev = &pdev->dev;
>>
>> init_completion(&info->completion);
>> @@ -565,6 +711,12 @@ static int exynos_adc_probe(struct platform_device *pdev)
>> if (info->data->init_hw)
>> info->data->init_hw(info);
>>
>> + has_ts = of_property_read_bool(pdev->dev.of_node, "has-touchscreen");
>> + if (has_ts)
>> + ret = exynos_adc_ts_init(info);
>> + if (ret)
>> + goto err_iio;
>> +
>> ret = of_platform_populate(np, exynos_adc_match, NULL, &indio_dev->dev);
>> if (ret < 0) {
>> dev_err(&pdev->dev, "failed adding child nodes\n");
>> @@ -576,6 +728,11 @@ static int exynos_adc_probe(struct platform_device *pdev)
>> err_of_populate:
>> device_for_each_child(&indio_dev->dev, NULL,
>> exynos_adc_remove_devices);
>> + if (has_ts) {
>> + input_unregister_device(info->input);
>> + free_irq(info->tsirq, info);
>> + }
>> +err_iio:
>> iio_device_unregister(indio_dev);
>> err_irq:
>> free_irq(info->irq, info);
>> @@ -595,9 +752,12 @@ static int exynos_adc_remove(struct platform_device *pdev)
>> struct iio_dev *indio_dev = platform_get_drvdata(pdev);
>> struct exynos_adc *info = iio_priv(indio_dev);
>>
>> + input_free_device(info->input);
>> device_for_each_child(&indio_dev->dev, NULL,
>> exynos_adc_remove_devices);
>> iio_device_unregister(indio_dev);
>> + if (info->tsirq > 0)
>> + free_irq(info->tsirq, info);
>> free_irq(info->irq, info);
>> if (info->data->exit_hw)
>> info->data->exit_hw(info);
>>
>
> --
> To unsubscribe from this list: send the line "unsubscribe linux-iio" in
> the body of a message to majordomo at vger.kernel.org
> More majordomo info at http://vger.kernel.org/majordomo-info.html