* Re: [PATCH V2] input synaptics-rmi4: Bug fixes to ATTN GPIO handling.
From: Christopher Heiny @ 2013-12-27 23:43 UTC (permalink / raw)
To: Dmitry Torokhov
Cc: Linux Input, Andrew Duggan, Vincent Huang, Vivian Ly,
Daniel Rosenberg, Jean Delvare, Joerie de Gram, Linus Walleij,
Benjamin Tissoires
In-Reply-To: <20131226223718.GC18562@core.coreip.homeip.net>
On 12/26/2013 02:37 PM, Dmitry Torokhov wrote:
> Hi Chris,
>
> On Mon, Dec 23, 2013 at 06:44:19PM -0800, Christopher Heiny wrote:
>> This patch fixes some bugs in handling of the RMI4 attention line GPIO.
>>
>> 1) in enable_sensor(), eliminate the complicated check on ATTN and just
>> call process_interrupt_requests(). This will have minimal overhead if ATTN
>> is not asserted, and clears the state of the RMI4 device in any case.
>>
>> 2) Correctly free the GPIO in rmi_driver_remove().
>>
>> 3) in rmi_driver_probe()
>> - declare the name of the attention gpio (GPIO_LABEL)
>> - use gpio_request_one() to get the gpio and export it.
>> - simplify conditional gpio acquisition logic and combine with interrupt
>> setup
>>
>> 4) use gpio_is_valid() instead of comparing to 0.
>>
>> Signed-off-by: Christopher Heiny <cheiny@synaptics.com>
>> Cc: Dmitry Torokhov <dmitry.torokhov@gmail.com>
>> Cc: Benjamin Tissoires <benjamin.tissoires@redhat.com>
>>
>> ---
>>
>> drivers/input/rmi4/rmi_driver.c | 43 ++++++++++++++++++++++-------------------
>> 1 file changed, 23 insertions(+), 20 deletions(-)
>>
>> diff --git a/drivers/input/rmi4/rmi_driver.c b/drivers/input/rmi4/rmi_driver.c
>> index a30c7d3..9b02358 100644
>> --- a/drivers/input/rmi4/rmi_driver.c
>> +++ b/drivers/input/rmi4/rmi_driver.c
>> @@ -140,7 +140,6 @@ static int enable_sensor(struct rmi_device *rmi_dev)
>> struct rmi_driver_data *data = dev_get_drvdata(&rmi_dev->dev);
>> struct rmi_phys_device *rmi_phys;
>> int retval = 0;
>> - struct rmi_device_platform_data *pdata = to_rmi_platform_data(rmi_dev);
>>
>> if (data->enabled)
>> return 0;
>> @@ -169,11 +168,7 @@ static int enable_sensor(struct rmi_device *rmi_dev)
>>
>> data->enabled = true;
>>
>> - if (!pdata->level_triggered &&
>> - gpio_get_value(pdata->attn_gpio) == pdata->attn_polarity)
>> - retval = process_interrupt_requests(rmi_dev);
>> -
>> - return retval;
>> + return process_interrupt_requests(rmi_dev);
>> }
>>
>> static void rmi_free_function_list(struct rmi_device *rmi_dev)
>> @@ -800,13 +795,20 @@ static SIMPLE_DEV_PM_OPS(rmi_driver_pm, rmi_driver_suspend, rmi_driver_resume);
>> static int rmi_driver_remove(struct device *dev)
>> {
>> struct rmi_device *rmi_dev = to_rmi_device(dev);
>> + const struct rmi_device_platform_data *pdata =
>> + to_rmi_platform_data(rmi_dev);
>>
>> disable_sensor(rmi_dev);
>> rmi_free_function_list(rmi_dev);
>>
>> + if (gpio_is_valid(pdata->attn_gpio))
>> + gpio_free(pdata->attn_gpio);
>
> It looks like you let driver registration to continue even if GPIO
> request fails. You probably need to introduce a flag indicating whether
> you successfully requested gpio or not. Or you can treat failure to
> acquire gpio as fatal.
In testing, the driver continues to work even if the GPIO is not
acquired, but some diagnostic features may not be available, so we treat
the failure to acquire as a warning. I'll add a flag to indicate
whether it was acquired or not.
>
>> +
>> return 0;
>> }
>>
>> +static const char GPIO_LABEL[] = "attn";
>> +
>> static int rmi_driver_probe(struct device *dev)
>> {
>> struct rmi_driver *rmi_driver;
>> @@ -937,7 +939,7 @@ static int rmi_driver_probe(struct device *dev)
>> mutex_init(&data->suspend_mutex);
>> }
>>
>> - if (pdata->attn_gpio) {
>> + if (gpio_is_valid(pdata->attn_gpio)) {
>> data->irq = gpio_to_irq(pdata->attn_gpio);
>> if (pdata->level_triggered) {
>> data->irq_flags = IRQF_ONESHOT |
>> @@ -948,24 +950,17 @@ static int rmi_driver_probe(struct device *dev)
>> (pdata->attn_polarity == RMI_ATTN_ACTIVE_HIGH)
>> ? IRQF_TRIGGER_RISING : IRQF_TRIGGER_FALLING;
>> }
>> - } else
>> - data->poll_interval = ktime_set(0,
>> - (pdata->poll_interval_ms ? pdata->poll_interval_ms :
>> - DEFAULT_POLL_INTERVAL_MS) * 1000 * 1000);
>> -
>> - if (data->f01_container->dev.driver) {
>> - /* Driver already bound, so enable ATTN now. */
>> - enable_sensor(rmi_dev);
>> - }
>>
>> - if (IS_ENABLED(CONFIG_RMI4_DEV) && pdata->attn_gpio) {
>
> Do you really need to export gpio if you do not have RMI4_DEV option?
Not really, but it doesn't hurt to do so. I was trying to simplify the
driver logic, but I guess that went too far. I'll re-add the check.
[snip]
^ permalink raw reply
* Re: [PATCH] ims-pcu: Add commands supported by the new version of the FW
From: Dmitry Torokhov @ 2013-12-27 18:27 UTC (permalink / raw)
To: Andrey Smirnov; +Cc: linux-input, linux-kernel
In-Reply-To: <CAHQ1cqGPORvQQLbA_maguYU=e7hjEJu3O-zKvd4Jt7j0O6E-Tg@mail.gmail.com>
On Fri, Dec 27, 2013 at 08:54:10AM -0800, Andrey Smirnov wrote:
> Sorry for the spam, sent the first version of the reply in non plain/text.
>
> >> +static ssize_t ims_pcu_ofn_reg_data_show(struct device *dev,
> >> + struct device_attribute *dattr,
> >> + char *buf)
> >> +{
> >> + struct usb_interface *intf = to_usb_interface(dev);
> >> + struct ims_pcu *pcu = usb_get_intfdata(intf);
> >> + int error;
> >> +
> >> + mutex_lock(&pcu->cmd_mutex);
> >> +
> >> + error = ims_pcu_execute_command(pcu, OFN_GET_CONFIG,
> >> + &pcu->ofn_reg_addr,
> >> + sizeof(pcu->ofn_reg_addr));
> >> + if (error >= 0) {
> >> + const s16 result = pcu->cmd_buf[OFN_REG_RESULT_MSB_OFFSET] << 8 |
> >> + pcu->cmd_buf[OFN_REG_RESULT_LSB_OFFSET];
> >
> > const here seems overkill.
>
> That variable has a lifetime limited by the scope of if statement and
> during that lifetime its value it constant, so I'm not sure I
> understand what you mean by "overkill"?
I usually only bother with const when dealing with pointers and rarely
if ever for non-pointer arguments and local variables. That style mostly
matches the rest of the kernel.
[...]
>
> >
> >> + error = result;
> >
> > Does firmware guarantee that it would return errors that make sense to
> > Linux?
>
> Firmware returns -ENOENT.
OK, still, I'd feel safer if we did something like
if (result != 0)
error = -EIO; /* or -ENOENT */
This way we can be sure that even if subsequent firmware version change
error codes for some reason we'd still be passing to the upper layer
Linux-specific ones.
[...]
> >> if (error)
> >> + goto err_stop_io;
> >> +
> >> + error = sysfs_create_group(&intf->dev.kobj, &ims_pcu_attr_group);
> >> + if (error)
> >> goto err_remove_sysfs;
> >
> > Why did you move sysfs group creation? Now one can not observe progress
> > of firmware update...
>
> I need the device ID to be known before the sysfs group is created in
> order to make the decision about OFN-related attributes visibility,
> and for that I need "ims_pcu_init_application_mode" to be called
> before "sysfs_create_group" call is made.
>
> I see two potential ways of solving this problem:
>
> 1. Split the calls to "ims_pcu_init_bootlader_mode" and
> "ims_pcu_init_application_mode" and make the former after the group is
> created and the latter before.
> 2. Remove the "update_firmware_status" attribute (Does the userspace
> in the system that uses this driver actually rely on this argument or
> is just added for convenience? I know that they have a userspace tool
> that they use to update the FW, so that's why I am wondering if they
> ever use it to monitor the progress)
The update_firmware_status attribute was a requirement from IMS. As far
as I know the flashing is done with standard udev facilities, so the
status is there to see what happened if something goes wrong. Still, I
believe we have to keep it.
I guess we can split the calls but even then ims_pcu_identify_type() may
fail and leave you with garbage device_id. Maybe we should alter
ims_pcu_is_attr_visible() to do:
if (pcu->bootloader_mode) {
...
} else {
if (pcu->setup_complete) {
check_if_ofn_attrs_should_be_visible;
}
}
and then do
ysfs_update_group(&pcu->dev->kobj, &ims_pcu_attr_group);
at the end of ims_pcu_init_application_mode().
Thanks.
--
Dmitry
^ permalink raw reply
* Re: [PATCH] ims-pcu: Add commands supported by the new version of the FW
From: Andrey Smirnov @ 2013-12-27 16:54 UTC (permalink / raw)
To: Dmitry Torokhov; +Cc: linux-input, linux-kernel
In-Reply-To: <20131226225317.GD18562@core.coreip.homeip.net>
Sorry for the spam, sent the first version of the reply in non plain/text.
>> +static ssize_t ims_pcu_ofn_reg_data_show(struct device *dev,
>> + struct device_attribute *dattr,
>> + char *buf)
>> +{
>> + struct usb_interface *intf = to_usb_interface(dev);
>> + struct ims_pcu *pcu = usb_get_intfdata(intf);
>> + int error;
>> +
>> + mutex_lock(&pcu->cmd_mutex);
>> +
>> + error = ims_pcu_execute_command(pcu, OFN_GET_CONFIG,
>> + &pcu->ofn_reg_addr,
>> + sizeof(pcu->ofn_reg_addr));
>> + if (error >= 0) {
>> + const s16 result = pcu->cmd_buf[OFN_REG_RESULT_MSB_OFFSET] << 8 |
>> + pcu->cmd_buf[OFN_REG_RESULT_LSB_OFFSET];
>
> const here seems overkill.
That variable has a lifetime limited by the scope of if statement and
during that lifetime its value it constant, so I'm not sure I
understand what you mean by "overkill"?
I usually try to declare all of the variables values of which I do not
intend to change as constant to allow the compiler to hopefully make
more informed decision about optimizing that piece of code and also to
warn me when I go against my intention and try to change that value.
Also, IMHO, this makes it easier to read the code since from it's
declaration I know that that value would not be changed and I don't
have to wonder if I missed a line of code that actually changes it.
>
>> + if (result < 0)
>> + error = result;
>> + else
>> + error = scnprintf(buf, PAGE_SIZE, "%x\n", (u8)result);
>
> Why cast to u8?
Will fix in the next version.
>
>> + }
>> +
>> + mutex_unlock(&pcu->cmd_mutex);
>> +
>> + return error;
>> +}
>> +
>> +static ssize_t ims_pcu_ofn_reg_data_store(struct device *dev,
>> + struct device_attribute *dattr,
>> + const char *buf, size_t count)
>> +{
>> + struct usb_interface *intf = to_usb_interface(dev);
>> + struct ims_pcu *pcu = usb_get_intfdata(intf);
>> + int error;
>> + int value;
>> + u8 buffer[2];
>> +
>> + error = kstrtoint(buf, 0, &value);
>> + if (error)
>> + return error;
>> +
>> + buffer[0] = pcu->ofn_reg_addr;
>> + buffer[1] = (u8) value;
>
> If you want u8 we have kstrtou8().
Ditto.
>
>> +
>> + mutex_lock(&pcu->cmd_mutex);
>> +
>> + error = ims_pcu_execute_command(pcu, OFN_SET_CONFIG,
>> + &buffer, sizeof(buffer));
>> +
>> + mutex_unlock(&pcu->cmd_mutex);
>> +
>> + if (!error) {
>> + const s16 result = pcu->cmd_buf[OFN_REG_RESULT_MSB_OFFSET] << 8 |
>> + pcu->cmd_buf[OFN_REG_RESULT_LSB_OFFSET];
>
> You should not be checking contents of pcu->cmd_buf after releasing
> mutex as someone else could be accessing the same sysfs attribute and
> stomping on your data.
Ditto.
>
>> + error = result;
>
> Does firmware guarantee that it would return errors that make sense to
> Linux?
Firmware returns -ENOENT.
>
>> + }
>> +
>> + return error ?: count;
>> +}
>> +
>> +static DEVICE_ATTR(ofn_reg_data, S_IRUGO | S_IWUSR,
>> + ims_pcu_ofn_reg_data_show, ims_pcu_ofn_reg_data_store);
>> +
>> +static ssize_t ims_pcu_ofn_reg_addr_show(struct device *dev,
>> + struct device_attribute *dattr,
>> + char *buf)
>> +{
>> + struct usb_interface *intf = to_usb_interface(dev);
>> + struct ims_pcu *pcu = usb_get_intfdata(intf);
>> + int error;
>> +
>> + mutex_lock(&pcu->cmd_mutex);
>> + error = scnprintf(buf, PAGE_SIZE, "%x\n", pcu->ofn_reg_addr);
>> + mutex_unlock(&pcu->cmd_mutex);
>> +
>> + return error;
>> +}
>> +
>> +static ssize_t ims_pcu_ofn_reg_addr_store(struct device *dev,
>> + struct device_attribute *dattr,
>> + const char *buf, size_t count)
>> +{
>> + struct usb_interface *intf = to_usb_interface(dev);
>> + struct ims_pcu *pcu = usb_get_intfdata(intf);
>> + int error;
>> + int value;
>> +
>> + error = kstrtoint(buf, 0, &value);
>> + if (error)
>> + return error;
>
> kstrtou8().
Will fix in the next version.
>
>> +
>> + mutex_lock(&pcu->cmd_mutex);
>> + pcu->ofn_reg_addr = (u8) value;
>> + mutex_unlock(&pcu->cmd_mutex);
>> +
>> + return error ?: count;
>> +}
>> +
>> +static DEVICE_ATTR(ofn_reg_addr, S_IRUGO | S_IWUSR,
>> + ims_pcu_ofn_reg_addr_show, ims_pcu_ofn_reg_addr_store);
>> +
>> +static ssize_t ims_pcu_ofn_bit_show(u8 addr, u8 nr,
>> + struct device *dev,
>> + struct device_attribute *dattr,
>> + char *buf)
>> +{
>> + struct usb_interface *intf = to_usb_interface(dev);
>> + struct ims_pcu *pcu = usb_get_intfdata(intf);
>> + int error;
>> +
>> + mutex_lock(&pcu->cmd_mutex);
>> +
>> + error = ims_pcu_execute_command(pcu, OFN_GET_CONFIG,
>> + &addr, sizeof(addr));
>> + if (error >= 0) {
>> + const s16 result = pcu->cmd_buf[OFN_REG_RESULT_MSB_OFFSET] << 8 |
>> + pcu->cmd_buf[OFN_REG_RESULT_LSB_OFFSET];
>> + if (result < 0)
>> + error = result;
>> + else
>> + error = scnprintf(buf, PAGE_SIZE, "%d\n",
>> + !!(result & (1 << nr)));
>> + }
>> +
>> + mutex_unlock(&pcu->cmd_mutex);
>> + return error;
>> +}
>> +
>> +static ssize_t ims_pcu_ofn_bit_store(u8 addr, u8 nr,
>> + struct device *dev,
>> + struct device_attribute *dattr,
>> + const char *buf, size_t count)
>> +{
>> + struct usb_interface *intf = to_usb_interface(dev);
>> + struct ims_pcu *pcu = usb_get_intfdata(intf);
>> + int error;
>> + int value;
>> + u8 contents;
>> +
>> +
>> + error = kstrtoint(buf, 0, &value);
>> + if (error)
>> + return error;
>> +
>> + if (value > 1)
>> + return -EINVAL;
>> +
>> + mutex_lock(&pcu->cmd_mutex);
>> +
>> + error = ims_pcu_execute_command(pcu, OFN_GET_CONFIG,
>> + &addr, sizeof(addr));
>> + if (error < 0)
>> + goto exit;
>> +
>> + {
>
> Generally dislike artificial code blocks. Please declare all needed
> variables upfront.
This is done because pre '99 C standard does not allow for variable
declaration in the middle of the function and I wanted to have the
result variable to be constant.
But, sure, since you are the author of the driver I don't really want
to spend any time arguing coding styles, I'll change that in the next
version.
>
>> + const s16 result = pcu->cmd_buf[OFN_REG_RESULT_MSB_OFFSET] << 8 |
>> + pcu->cmd_buf[OFN_REG_RESULT_LSB_OFFSET];
>> + if (result < 0) {
>> + error = result;
>> + goto exit;
>> + }
>> + contents = (u8) result;
>> + }
>> +
>> + if (value)
>> + contents |= 1 << nr;
>> @@ -1783,14 +2035,14 @@ static int ims_pcu_probe(struct usb_interface *intf,
>> if (error)
>> goto err_stop_io;
>>
>> - error = sysfs_create_group(&intf->dev.kobj, &ims_pcu_attr_group);
>> - if (error)
>> - goto err_stop_io;
>> -
>> error = pcu->bootloader_mode ?
>> ims_pcu_init_bootloader_mode(pcu) :
>> ims_pcu_init_application_mode(pcu);
>> if (error)
>> + goto err_stop_io;
>> +
>> + error = sysfs_create_group(&intf->dev.kobj, &ims_pcu_attr_group);
>> + if (error)
>> goto err_remove_sysfs;
>
> Why did you move sysfs group creation? Now one can not observe progress
> of firmware update...
I need the device ID to be known before the sysfs group is created in
order to make the decision about OFN-related attributes visibility,
and for that I need "ims_pcu_init_application_mode" to be called
before "sysfs_create_group" call is made.
I see two potential ways of solving this problem:
1. Split the calls to "ims_pcu_init_bootlader_mode" and
"ims_pcu_init_application_mode" and make the former after the group is
created and the latter before.
2. Remove the "update_firmware_status" attribute (Does the userspace
in the system that uses this driver actually rely on this argument or
is just added for convenience? I know that they have a userspace tool
that they use to update the FW, so that's why I am wondering if they
ever use it to monitor the progress)
>
> Thanks.
>
> --
> Dmitry
^ permalink raw reply
* Re: [lm-sensors] [PATCH v2 2/5] input: sun4i-ts: Add support for temperature sensor
From: Guenter Roeck @ 2013-12-27 16:34 UTC (permalink / raw)
To: Hans de Goede
Cc: Dmitry Torokhov, LM Sensors, linux-sunxi-/JYPxA39Uh5TLH3MbocFFw,
Corentin LABBE, linux-input-u79uwXL29TY76Z2rM5mHXA, Maxime Ripard,
linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r
In-Reply-To: <1388156399-29677-3-git-send-email-hdegoede-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
On Fri, Dec 27, 2013 at 03:59:56PM +0100, Hans de Goede wrote:
> The sun4i resisitive touchscreen controller also comes with a built-in
> temperature sensor. This commit adds support for it.
>
> This commit also introduces a new "ts-attached" device-tree property,
> when this is not set, the input part of the driver won't register. This way
> the internal temperature sensor can be used to measure the SoC temperature
> independent of there actually being a touchscreen attached to the controller.
>
> Signed-off-by: Hans de Goede <hdegoede-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
Hi Hans,
Almost good, except for a couple of nitpicks and one moe question.
Assuming you fix the nitpicks and the question is not a concern,
feel free to add
Reviewed-by: Guenter Roeck <linux-0h96xk9xTtrk1uMJSBkQmQ@public.gmane.org>
Guenter
> ---
> .../bindings/input/touchscreen/sun4i.txt | 6 +
> drivers/input/touchscreen/sun4i-ts.c | 129 ++++++++++++++++-----
> 2 files changed, 109 insertions(+), 26 deletions(-)
>
> diff --git a/Documentation/devicetree/bindings/input/touchscreen/sun4i.txt b/Documentation/devicetree/bindings/input/touchscreen/sun4i.txt
> index e45927e..1fffd11 100644
> --- a/Documentation/devicetree/bindings/input/touchscreen/sun4i.txt
> +++ b/Documentation/devicetree/bindings/input/touchscreen/sun4i.txt
> @@ -6,10 +6,16 @@ Required properties:
> - reg: mmio address range of the chip
> - interrupts: interrupt to which the chip is connected
>
> +Optional properties:
> + - ts-attached: boolean set this to tell the driver that an actual touchscreen
> + is attached and that it should register an input device,
> + without this it only registers the builtin temperate sensor
> +
> Example:
>
> rtp: rtp@01c25000 {
> compatible = "allwinner,sun4i-ts";
> reg = <0x01c25000 0x100>;
> interrupts = <29>;
> + ts-attached;
> };
> diff --git a/drivers/input/touchscreen/sun4i-ts.c b/drivers/input/touchscreen/sun4i-ts.c
> index 10839d2..2d437f6 100644
> --- a/drivers/input/touchscreen/sun4i-ts.c
> +++ b/drivers/input/touchscreen/sun4i-ts.c
> @@ -3,6 +3,9 @@
> *
> * Copyright (C) 2013 - 2014 Hans de Goede <hdegoede-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
> *
> + * The hwmon parts are based on work by Corentin LABBE which is:
> + * Copyright (C) 2013 Corentin LABBE <clabbe.montjoie-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
> + *
> * 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
> @@ -30,6 +33,7 @@
> */
>
> #include <linux/err.h>
> +#include <linux/hwmon.h>
> #include <linux/init.h>
> #include <linux/input.h>
> #include <linux/interrupt.h>
> @@ -106,6 +110,7 @@ struct sun4i_ts_data {
> void __iomem *base;
> unsigned int irq;
> bool ignore_fifo_data;
> + int temp_data;
> };
>
> static irqreturn_t sun4i_ts_irq(int irq, void *dev_id)
> @@ -115,6 +120,12 @@ static irqreturn_t sun4i_ts_irq(int irq, void *dev_id)
>
> reg_val = readl(ts->base + TP_INT_FIFOS);
>
> + if (reg_val & TEMP_DATA_PENDING)
> + ts->temp_data = readl(ts->base + TEMP_DATA);
Is this read guaranteed to return 0 in the upper bit, or in other words is it
guaranteed to never return 0xffffffff ? Otherwise there might be a problem with
the implicit conversion to a negative integer.
> +
> + if (!ts->input)
> + goto leave;
> +
> if (reg_val & FIFO_DATA_PENDING) {
> x = readl(ts->base + TP_DATA);
> y = readl(ts->base + TP_DATA);
> @@ -140,6 +151,7 @@ static irqreturn_t sun4i_ts_irq(int irq, void *dev_id)
> input_sync(ts->input);
> }
>
> +leave:
> writel(reg_val, ts->base + TP_INT_FIFOS);
>
> return IRQ_HANDLED;
> @@ -149,9 +161,9 @@ static int sun4i_ts_open(struct input_dev *dev)
> {
> struct sun4i_ts_data *ts = input_get_drvdata(dev);
>
> - /* Flush, set trig level to 1, enable data and up irqs */
> - writel(DATA_IRQ_EN(1) | FIFO_TRIG(1) |FIFO_FLUSH(1) | TP_UP_IRQ_EN(1),
> - ts->base + TP_INT_FIFOC);
> + /* Flush, set trig level to 1, enable temp, data and up irqs */
> + writel(TEMP_IRQ_EN(1) | DATA_IRQ_EN(1) | FIFO_TRIG(1) | FIFO_FLUSH(1) |
> + TP_UP_IRQ_EN(1), ts->base + TP_INT_FIFOC);
>
> return 0;
> }
> @@ -160,40 +172,76 @@ static void sun4i_ts_close(struct input_dev *dev)
> {
> struct sun4i_ts_data *ts = input_get_drvdata(dev);
>
> - /* Deactivate all IRQs */
> - writel(0, ts->base + TP_INT_FIFOC);
> + /* Deactivate all input IRQs */
> + writel(TEMP_IRQ_EN(1), ts->base + TP_INT_FIFOC);
> synchronize_irq(ts->irq);
> }
>
> +static ssize_t show_temp(struct device *dev, struct device_attribute *devattr,
> + char *buf)
> +{
> + struct sun4i_ts_data *ts = dev_get_drvdata(dev);
> +
> + /* No temp_data until the first irq */
> + if (ts->temp_data == -1)
> + return -EAGAIN;
> +
> + return sprintf(buf, "%d\n", (ts->temp_data - 1447) * 100);
> +}
> +
> +static ssize_t show_temp_label(struct device *dev,
> + struct device_attribute *devattr, char *buf)
> +{
> + return sprintf(buf, "SoC temperature\n");
> +}
> +
> +static DEVICE_ATTR(temp1_input, S_IRUGO, show_temp, NULL);
> +static DEVICE_ATTR(temp1_label, S_IRUGO, show_temp_label, NULL);
> +
> +static struct attribute *sun4i_ts_attrs[] = {
> + &dev_attr_temp1_input.attr,
> + &dev_attr_temp1_label.attr,
> + NULL
> +};
> +ATTRIBUTE_GROUPS(sun4i_ts);
> +
> static int sun4i_ts_probe(struct platform_device *pdev)
> {
> struct sun4i_ts_data *ts;
> struct device *dev = &pdev->dev;
> + struct device_node *np =dev->of_node;
Missing space after '=' (checkpatch error).
> + struct device *hwmon;
> int ret;
> + bool ts_attached;
> +
> + ts_attached = of_property_read_bool(np, "ts-attached");
>
> ts = devm_kzalloc(dev, sizeof(struct sun4i_ts_data), GFP_KERNEL);
> if (!ts)
> return -ENOMEM;
>
> ts->dev = dev;
> -
> - ts->input = devm_input_allocate_device(dev);
> - if (!ts->input)
> - return -ENOMEM;
> -
> - ts->input->name = pdev->name;
> - ts->input->phys = "sun4i_ts/input0";
> - ts->input->open = sun4i_ts_open;
> - ts->input->close = sun4i_ts_close;
> - ts->input->id.bustype = BUS_HOST;
> - ts->input->id.vendor = 0x0001;
> - ts->input->id.product = 0x0001;
> - ts->input->id.version = 0x0100;
> - ts->input->evbit[0] = BIT(EV_SYN) | BIT(EV_KEY) | BIT(EV_ABS);
> - set_bit(BTN_TOUCH, ts->input->keybit);
> - input_set_abs_params(ts->input, ABS_X, 0, 4095, 0, 0);
> - input_set_abs_params(ts->input, ABS_Y, 0, 4095, 0, 0);
> - input_set_drvdata(ts->input, ts);
> + ts->temp_data = -1;
> +
> + if (ts_attached) {
> + ts->input = devm_input_allocate_device(dev);
> + if (!ts->input)
> + return -ENOMEM;
> +
> + ts->input->name = pdev->name;
> + ts->input->phys = "sun4i_ts/input0";
> + ts->input->open = sun4i_ts_open;
> + ts->input->close = sun4i_ts_close;
> + ts->input->id.bustype = BUS_HOST;
> + ts->input->id.vendor = 0x0001;
> + ts->input->id.product = 0x0001;
> + ts->input->id.version = 0x0100;
> + ts->input->evbit[0] = BIT(EV_SYN) | BIT(EV_KEY) | BIT(EV_ABS);
> + set_bit(BTN_TOUCH, ts->input->keybit);
> + input_set_abs_params(ts->input, ABS_X, 0, 4095, 0, 0);
> + input_set_abs_params(ts->input, ABS_Y, 0, 4095, 0, 0);
> + input_set_drvdata(ts->input, ts);
> + }
>
> ts->base = devm_ioremap_resource(dev,
> platform_get_resource(pdev, IORESOURCE_MEM, 0));
> @@ -232,14 +280,42 @@ static int sun4i_ts_probe(struct platform_device *pdev)
> writel(STYLUS_UP_DEBOUN(5) | STYLUS_UP_DEBOUN_EN(1) | TP_MODE_EN(1),
> ts->base + TP_CTRL1);
>
> - ret = input_register_device(ts->input);
> - if (ret)
> - return ret;
> + hwmon = devm_hwmon_device_register_with_groups(ts->dev, "sun4i_ts",
> + ts, sun4i_ts_groups);
> + if (IS_ERR(hwmon)) {
> + return PTR_ERR(hwmon);
> + }
{ } are not needed here (checkpatch warning).
> +
> + writel(TEMP_IRQ_EN(1), ts->base + TP_INT_FIFOC);
> +
> + if (ts_attached) {
> + ret = input_register_device(ts->input);
> + if (ret) {
> + writel(0, ts->base + TP_INT_FIFOC);
> + synchronize_irq(ts->irq);
> + return ret;
> + }
> + }
>
> platform_set_drvdata(pdev, ts);
> return 0;
> }
>
> +static int sun4i_ts_remove(struct platform_device *pdev)
> +{
> + struct sun4i_ts_data *ts = platform_get_drvdata(pdev);
> +
> + /* Explicit unregister to avoid open/close changing the imask later */
> + if (ts->input)
> + input_unregister_device(ts->input);
> +
> + /* Deactivate all IRQs */
> + writel(0, ts->base + TP_INT_FIFOC);
> + synchronize_irq(ts->irq);
> +
> + return 0;
> +}
> +
> static const struct of_device_id sun4i_ts_of_match[] = {
> { .compatible = "allwinner,sun4i-ts", },
> { /* sentinel */ }
> @@ -253,6 +329,7 @@ static struct platform_driver sun4i_ts_driver = {
> .of_match_table = of_match_ptr(sun4i_ts_of_match),
> },
> .probe = sun4i_ts_probe,
> + .remove = sun4i_ts_remove,
> };
>
> module_platform_driver(sun4i_ts_driver);
> --
> 1.8.4.2
>
>
> _______________________________________________
> lm-sensors mailing list
> lm-sensors-GZX6beZjE8VD60Wz+7aTrA@public.gmane.org
> http://lists.lm-sensors.org/mailman/listinfo/lm-sensors
>
^ permalink raw reply
* [PATCH v3] Add ff-memless-next module
From: Michal Malý @ 2013-12-27 16:02 UTC (permalink / raw)
To: linux-input
Cc: linux-kernel, dmitry.torokhov, joe, elias.vds, Michal Malý
Introduce ff-memless-next module as a possible future replacement for
ff-memless.
Tested-by: Elias Vanderstuyft <elias.vds@gmail.com>
Signed-off-by: Michal Malý <madcatxster@prifuk.cz>
---
Documentation/input/ff-memless-next.txt | 149 ++++++
drivers/input/Kconfig | 12 +
drivers/input/Makefile | 2 +
drivers/input/ff-memless-next.c | 793 ++++++++++++++++++++++++++++++++
include/linux/input/ff-memless-next.h | 31 ++
5 files changed, 987 insertions(+)
create mode 100644 Documentation/input/ff-memless-next.txt
create mode 100644 drivers/input/ff-memless-next.c
create mode 100644 include/linux/input/ff-memless-next.h
diff --git a/Documentation/input/ff-memless-next.txt b/Documentation/input/ff-memless-next.txt
new file mode 100644
index 0000000..470a5bc
--- /dev/null
+++ b/Documentation/input/ff-memless-next.txt
@@ -0,0 +1,149 @@
+"ff-memless-next" force feedback module for memoryless devices.
+By Michal Malý <madcatxster@prifuk.cz> on 2013/12/21
+----------------------------------------------------------------------------
+
+1. Introduction
+~~~~~~~~~~~~~~~
+This document describes basic working principles of the "ff-memless-next"
+module and its purpose. This document also contains a summary
+of the "ff-memless-next" API and brief instructions on how to use it to write
+a hardware-specific backend with "ff-memless-next". Some specifics
+of ff-memless-next that might be of interest for userspace developers
+are also discussed.
+
+2. Basic principles of ff-memless-next
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+A lot of commonly used force feedback devices do not have a memory of their
+own. This means that it is not possible to upload a force feedback effect
+to such a device and let the device's CPU handle the playback. Instead,
+the device relies solely on its driver to tell it what force to generate.
+"ff-memless-next" was written to serve in this capacity. Its purpose is to
+calculate the overall force the device should apply and pass the result to
+a hardware-specific backend which then submits the appropriate command to
+the device.
+
+"ff-memless-next" differentiates between two types of force feedback effects,
+"combinable" and "uncombinable".
+"Combinable" effects are effects that generate a force of a given
+magnitude and direction. The magnitude can change in time according to the
+envelope of the effect and its waveform; the latter applies only to periodic
+effects. Force generated by "combinable" effect does not depend on the position
+of the device. Forces generated by each "combinable" effect that is active
+are periodically recalculated as needed and superposed into one overall force.
+"Combinable" effects are FF_CONSTANT, FF_PERIODIC and FF_RAMP.
+
+"Uncombinable" effects generate a force that depends on the position of
+the device. "ff-memless-next" assumes that the device takes care of processing
+such an effect. However, a device might have a limit on how many "uncombinable"
+effects can be active at once and this limit might be lower than the maximum
+effect count set in "ff-memless-next". "ff-memless-next" allows a
+hardware-specific driver to check whether the device is able to play
+an "uncombinable" effect. Error is reported back to userspace if the device
+cannot play the effect. The hardware-specific driver may choose not to
+perform this check in which case the userspace will have no way of knowing
+whether an "uncombinable" effect is really being played or not.
+"Uncombinable" effects are FF_DAMPER, FF_FRICTION, FF_INERTIA and FF_SPRING.
+
+FF_CUSTOM is currently not handled by "ff-memless-next".
+
+3. API provided by "ff-memless-next"
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+"ff-memless-next" provides an API for developers of hardware-specific
+drivers. In order to use the API, the hardware-specific driver should
+include <linux/input/ff-memless-next.h>
+Functions and structs defined by this API are:
+
+int input_ff_create_mlnx(struct input_dev *dev, void *data,
+ int(*control_effect)(struct input_dev *, void *, const struct mlnx_effect_command *),
+ const u16 update_rate)
+- Any hardware-specific driver that wants to use "ff-memless-next" must call
+this function. The function takes care of creating and registering a force
+feedback device within the kernel. It also initializes resources needed by
+"ff-memless-next" to handle a new device. No other initialization steps are necessary.
+ Parameters:
+ * dev - pointer to valid struct input_dev
+ * data - pointer to custom data the hardware-specific backend
+ might need to pass to the control_effect() callback function
+ (discussed later). * data will be freed automatically by
+ "ff-memless-next" upon device's destruction.
+ * control_effect - A callback function. "ff-memless-next" will call
+ this function when it is done processing effects.
+ Implementation of control_effect() in the
+ hardware-specific driver should create an appropriate
+ command and submit it to the device.
+ This function is called with dev->event_lock
+ spinlock held.
+ update_rate - Rate in milliseconds at which envelopes and periodic
+ effects are recalculated. Minimum value is limited by the
+ kernel timer resolution and changes with value of
+ CONFIG_HZ.
+
+struct mlnx_effect_command
+^^^^^^^^^^^^^^^^^^^^^^^^^^
+- This struct is passed to the hardware-specific backend in
+the control_effect() function. See "ff-memless-next.h" for details.
+
+enum mlnx_commands
+^^^^^^^^^^^^^^^^^^
+- Types of commands generated by ff-memless-next
+ MLNX_START_COMBINED - Start or update "combinable" effect
+ MLNX_STOP_COMBINED - Stop "combinable" effect. In most cases this means
+ to set the force to zero.
+ MLNX_UPLOAD_UNCOMB - Check if the device can accept and play
+ "uncombinable" effect.
+ Hardware-specific driver should return 0
+ on success.
+ MLNX_START_UNCOMB - Start playback of "uncombinable" effect.
+ MLNX_STOP_UNCOMB - Stop playback of "uncombinable" effect.
+
+struct mlnx_simple_force
+^^^^^^^^^^^^^^^^^^^^^^^^
+ x - overall force along X axis
+ y - overall force along Y axis
+
+struct mlnx_uncomb_effect
+^^^^^^^^^^^^^^^^^^^^^^^^^
+- Information about "uncombinable" effect.
+ id - internal ID of the effect
+ * effect - pointer to the internal copy of the effect kept by
+ "ff-memless-next". This pointer will remain valid until
+ the effect is removed.
+
+Sample implementation of a dummy driver that uses "ff-memless-next" is
+available at "git://prifuk.cz/ff-dummy-device". Link to the source will
+be kept up to date.
+
+Direction of the effect's force translates to Cartesian coordinates system
+as follows:
+ Direction = 0: Down
+ Direction (0; 16384): 3rd quadrant
+ Direction = 16384: Left
+ Direction (16385; 32768): 2nd quadrant
+ Direction = 32768: Up
+ Direction (32769; 49152): 1st quadrant
+ Direction = 49152: Right
+ Direction (49153; 65535) :4th quadrant
+ Direction = 65565: Down
+
+4. Specifics of "ff-memless-next" for userspace developers
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+None of the persons involved in development or testing of "ff-memless-next"
+is aware of any reference force feedback specifications. "ff-memless-next"
+tries to adhere to Microsoft's DirectInput specifications because we
+believe that is what most developers have experience with.
+
+- Waveforms of periodic effects do not start at the origin, but as follows:
+ SAW_UP: At minimum
+ SAW_DOWN: At maximum
+ SQUARE: At maximum
+ TRIANGLE: At maximum
+ SINE: At origin
+
+- Updating periodic effects:
+ - All periodic effects are restarted when their parameters are updated.
+
+- Updating effects of finite duration:
+ - Once an effect of finite length finishes playing, it is considered
+ stopped. Only effects that are playing can be updated on the fly.
+ Therefore effects of finite duration can by updated only until
+ they finish playing.
diff --git a/drivers/input/Kconfig b/drivers/input/Kconfig
index a11ff74..4371a12 100644
--- a/drivers/input/Kconfig
+++ b/drivers/input/Kconfig
@@ -77,6 +77,18 @@ config INPUT_MATRIXKMAP
To compile this driver as a module, choose M here: the
module will be called matrix-keymap.
+config INPUT_FF_MEMLESS_NEXT
+ tristate "New version of support for memoryless force feedback devices"
+ help
+ Say Y here if you want to enable support for various memoryless
+ force feedback devices (as of now there is no hardware-specific
+ driver that supports this).
+
+ If unsure, say N.
+
+ To compile this driver as a module, choose M here: the
+ module will be called ff-memless-next.
+
comment "Userland interfaces"
config INPUT_MOUSEDEV
diff --git a/drivers/input/Makefile b/drivers/input/Makefile
index 5ca3f63..269a22d 100644
--- a/drivers/input/Makefile
+++ b/drivers/input/Makefile
@@ -16,6 +16,8 @@ obj-$(CONFIG_INPUT_MOUSEDEV) += mousedev.o
obj-$(CONFIG_INPUT_JOYDEV) += joydev.o
obj-$(CONFIG_INPUT_EVDEV) += evdev.o
obj-$(CONFIG_INPUT_EVBUG) += evbug.o
+obj-$(CONFIG_INPUT_MATRIXKMAP) += matrix-keymap.o
+obj-$(CONFIG_INPUT_FF_MEMLESS_NEXT) += ff-memless-next.o
obj-$(CONFIG_INPUT_KEYBOARD) += keyboard/
obj-$(CONFIG_INPUT_MOUSE) += mouse/
diff --git a/drivers/input/ff-memless-next.c b/drivers/input/ff-memless-next.c
new file mode 100644
index 0000000..97de791
--- /dev/null
+++ b/drivers/input/ff-memless-next.c
@@ -0,0 +1,793 @@
+/*
+ * Force feedback support for memoryless devices
+ *
+ * This module is based on "ff-memless" orignally written by Anssi Hannula.
+ * It is extended to support all force feedback effects currently supported
+ * by the Linux input stack.
+ *
+ * Copyright(c) 2013 Michal Maly <madcatxster@prifuk.cz>
+ *
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/spinlock.h>
+#include <linux/jiffies.h>
+#include <linux/fixp-arith.h>
+#include <linux/input/ff-memless-next.h>
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Michal \"MadCatX\" Maly");
+MODULE_DESCRIPTION("Force feedback support for memoryless force feedback devices");
+
+#define FF_MAX_EFFECTS 16
+#define FF_MIN_EFFECT_LENGTH ((1000 / CONFIG_HZ) + 1)
+#define FF_EFFECT_STARTED 1
+#define FF_EFFECT_PLAYING 2
+
+
+struct mlnx_effect {
+ struct ff_effect effect;
+ unsigned long flags;
+ unsigned long begin_at;
+ unsigned long stop_at;
+ unsigned long updated_at;
+ unsigned long attack_stop;
+ unsigned long fade_begin;
+ int repeat;
+ u16 playback_time;
+};
+
+struct mlnx_device {
+ u8 combinable_playing;
+ unsigned long update_rate_jiffies;
+ void *private;
+ struct mlnx_effect effects[FF_MAX_EFFECTS];
+ int gain;
+ struct timer_list timer;
+ struct input_dev *dev;
+
+ int (*control_effect)(struct input_dev *, void *,
+ const struct mlnx_effect_command *);
+};
+
+static s32 mlnx_calculate_x_force(const s32 level,
+ const u16 direction)
+{
+ s32 new = (level * -fixp_sin(direction)) >> FRAC_N;
+ pr_debug("x force: %d\n", new);
+ return new;
+}
+
+static s32 mlnx_calculate_y_force(const s32 level,
+ const u16 direction)
+{
+ s32 new = (level * -fixp_cos(direction)) >> FRAC_N;
+ pr_debug("y force: %d\n", new);
+ return new;
+}
+
+static bool mlnx_is_combinable(const struct ff_effect *effect)
+{
+ switch (effect->type) {
+ case FF_CONSTANT:
+ case FF_PERIODIC:
+ case FF_RAMP:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static bool mlnx_is_conditional(const struct ff_effect *effect)
+{
+ switch (effect->type) {
+ case FF_DAMPER:
+ case FF_FRICTION:
+ case FF_INERTIA:
+ case FF_SPRING:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static bool mlnx_is_square(const struct ff_effect *effect)
+{
+ if (effect->type == FF_PERIODIC)
+ return effect->u.periodic.waveform == FF_SQUARE;
+ return false;
+}
+
+static void mlnx_clr_playing(struct mlnx_effect *mlnxeff)
+{
+ __clear_bit(FF_EFFECT_PLAYING, &mlnxeff->flags);
+}
+
+static void mlnx_clr_started(struct mlnx_effect *mlnxeff)
+{
+ __clear_bit(FF_EFFECT_STARTED, &mlnxeff->flags);
+}
+
+static bool mlnx_is_playing(const struct mlnx_effect *mlnxeff)
+{
+ return test_bit(FF_EFFECT_PLAYING, &mlnxeff->flags);
+}
+
+static bool mlnx_is_started(const struct mlnx_effect *mlnxeff)
+{
+ return test_bit(FF_EFFECT_STARTED, &mlnxeff->flags);
+}
+
+static bool mlnx_test_set_playing(struct mlnx_effect *mlnxeff)
+{
+ return test_and_set_bit(FF_EFFECT_PLAYING, &mlnxeff->flags);
+}
+
+static const struct ff_envelope *mlnx_get_envelope(const struct ff_effect *effect)
+{
+ static const struct ff_envelope empty;
+
+ switch (effect->type) {
+ case FF_CONSTANT:
+ return &effect->u.constant.envelope;
+ case FF_PERIODIC:
+ return &effect->u.periodic.envelope;
+ case FF_RAMP:
+ return &effect->u.ramp.envelope;
+ default:
+ return ∅
+ }
+}
+
+/* Some devices might have a limit on how many uncombinable effects
+ * can be played at once */
+static int mlnx_upload_conditional(struct mlnx_device *mlnxdev,
+ const struct ff_effect *effect)
+{
+ struct mlnx_effect_command ecmd = {
+ .cmd = MLNX_UPLOAD_UNCOMB,
+ .u.uncomb.id = effect->id,
+ .u.uncomb.effect = effect
+ };
+ return mlnxdev->control_effect(mlnxdev->dev, mlnxdev->private, &ecmd);
+}
+
+static void mlnx_set_envelope_times(struct mlnx_effect *mlnxeff)
+{
+ const struct ff_effect *effect = &mlnxeff->effect;
+ const struct ff_envelope *envelope = mlnx_get_envelope(effect);
+
+ if (envelope->attack_length) {
+ unsigned long j = msecs_to_jiffies(envelope->attack_length);
+ mlnxeff->attack_stop = mlnxeff->begin_at + j;
+ }
+ if (effect->replay.length && envelope->fade_length) {
+ unsigned long j = msecs_to_jiffies(envelope->fade_length);
+ mlnxeff->fade_begin = mlnxeff->stop_at - j;
+ }
+}
+
+static void mlnx_set_trip_times(struct mlnx_effect *mlnxeff,
+ const unsigned long now)
+{
+ const struct ff_effect *effect = &mlnxeff->effect;
+ const u16 replay_delay = effect->replay.delay;
+ const u16 replay_length = effect->replay.length;
+
+ mlnxeff->begin_at = now + msecs_to_jiffies(replay_delay);
+ mlnxeff->stop_at = mlnxeff->begin_at + msecs_to_jiffies(replay_length);
+ mlnxeff->updated_at = mlnxeff->begin_at;
+ if (effect->type == FF_PERIODIC)
+ mlnxeff->playback_time = effect->u.periodic.phase;
+}
+
+static void mlnx_start_effect(struct mlnx_effect *mlnxeff)
+{
+ const unsigned long now = jiffies;
+
+ mlnx_set_trip_times(mlnxeff, now);
+ mlnx_set_envelope_times(mlnxeff);
+ __set_bit(FF_EFFECT_STARTED, &mlnxeff->flags);
+}
+
+static void mlnx_stop_effect(struct mlnx_device *mlnxdev,
+ const struct mlnx_effect *mlnxeff)
+{
+ switch (mlnxeff->effect.type) {
+ case FF_CONSTANT:
+ case FF_PERIODIC:
+ case FF_RAMP:
+ if (--mlnxdev->combinable_playing == 0) {
+ const struct mlnx_effect_command c = {
+ .cmd = MLNX_STOP_COMBINED
+ };
+ mlnxdev->control_effect(mlnxdev->dev, mlnxdev->private,
+ &c);
+ }
+ return;
+ case FF_DAMPER:
+ case FF_FRICTION:
+ case FF_INERTIA:
+ case FF_SPRING:
+ {
+ const struct mlnx_effect_command c = {
+ .cmd = MLNX_STOP_UNCOMB,
+ .u.uncomb.id = mlnxeff->effect.id,
+ .u.uncomb.effect = &mlnxeff->effect
+ };
+ mlnxdev->control_effect(mlnxdev->dev, mlnxdev->private, &c);
+ return;
+ }
+ default:
+ return;
+ }
+}
+
+static int mlnx_restart_effect(struct mlnx_device *mlnxdev,
+ struct mlnx_effect *mlnxeff)
+{
+ const struct ff_effect *effect = &mlnxeff->effect;
+ const unsigned long now = jiffies;
+
+ if (mlnx_is_combinable(effect)) {
+ if (effect->replay.delay)
+ mlnx_stop_effect(mlnxdev, mlnxeff);
+ else
+ mlnxdev->combinable_playing--;
+ } else if (mlnx_is_conditional(effect)) {
+ int ret;
+ if (effect->replay.delay)
+ mlnx_stop_effect(mlnxdev, mlnxeff);
+
+ ret = mlnx_upload_conditional(mlnxdev, &mlnxeff->effect);
+ if (ret)
+ return ret;
+ }
+
+ mlnx_set_trip_times(mlnxeff, now);
+ mlnx_set_envelope_times(mlnxeff);
+
+ return 0;
+}
+
+static s32 mlnx_apply_envelope(const struct mlnx_effect *mlnxeff,
+ const s32 level)
+{
+ const struct ff_effect *effect = &mlnxeff->effect;
+ const struct ff_envelope *envelope = mlnx_get_envelope(effect);
+ const unsigned long now = jiffies;
+ const s32 alevel = abs(level);
+
+ /* Effect has an envelope with nonzero attack time */
+ if (envelope->attack_length && time_before(now, mlnxeff->attack_stop)) {
+ const s32 clength = jiffies_to_msecs(now - mlnxeff->begin_at);
+ const s32 alength = envelope->attack_length;
+ const s32 dlevel = (alevel - envelope->attack_level)
+ * clength / alength;
+ return level < 0 ? -(dlevel + envelope->attack_level) :
+ (dlevel + envelope->attack_level);
+ } else if (envelope->fade_length && time_before_eq(mlnxeff->fade_begin, now)) {
+ const s32 clength = jiffies_to_msecs(now - mlnxeff->fade_begin);
+ const s32 flength = envelope->fade_length;
+ const s32 dlevel = (envelope->fade_level - alevel)
+ * clength / flength;
+ return level < 0 ? -(dlevel + alevel) : (dlevel + alevel);
+ }
+
+ return level;
+}
+
+static s32 mlnx_calculate_periodic(struct mlnx_effect *mlnxeff, const s32 level)
+{
+ const struct ff_effect *effect = &mlnxeff->effect;
+ const unsigned long now = jiffies;
+ const u16 period = effect->u.periodic.period;
+ const unsigned long dt = jiffies_to_msecs(now - mlnxeff->updated_at);
+ const s16 offset = effect->u.periodic.offset;
+ s32 new = level;
+ u16 t;
+
+ mlnxeff->playback_time += dt;
+ t = mlnxeff->playback_time % period;
+
+ switch (effect->u.periodic.waveform) {
+ case FF_SINE:
+ {
+ u16 degrees = (360 * t) / period;
+ new = ((level * fixp_sin(degrees)) >> FRAC_N) + offset;
+ break;
+ }
+ case FF_SQUARE:
+ {
+ u16 degrees = (360 * t) / period;
+ new = level * (degrees < 180 ? 1 : -1) + offset;
+ break;
+ }
+ case FF_SAW_UP:
+ new = 2 * level * t / period - level + offset;
+ break;
+ case FF_SAW_DOWN:
+ new = level - 2 * level * t / period + offset;
+ break;
+ case FF_TRIANGLE:
+ {
+ new = (2 * abs(level - (2 * level * t) / period));
+ new = new - level + offset;
+ break;
+ }
+ case FF_CUSTOM:
+ pr_debug("Custom waveform is not handled by this driver\n");
+ return level;
+ default:
+ pr_err("Invalid waveform\n");
+ return level;
+ }
+
+ mlnxeff->playback_time = t;
+ /* Ensure that the offset did not make the value exceed s16 range */
+ new = clamp(new, -0x7fff, 0x7fff);
+ pr_debug("level: %d, playback: %u, t: %u, dt: %lu\n",
+ new, mlnxeff->playback_time, t, dt);
+ return new;
+}
+
+static s32 mlnx_calculate_ramp(const struct mlnx_effect *mlnxeff)
+{
+ const struct ff_effect *effect = &mlnxeff->effect;
+ const struct ff_envelope *envelope = mlnx_get_envelope(effect);
+ const unsigned long now = jiffies;
+ const u16 length = effect->replay.length;
+ const s16 mean = (effect->u.ramp.start_level + effect->u.ramp.end_level) / 2;
+ const u16 t = jiffies_to_msecs(now - mlnxeff->begin_at);
+ s32 start = effect->u.ramp.start_level;
+ s32 end = effect->u.ramp.end_level;
+ s32 new;
+
+ if (envelope->attack_length && time_before(now, mlnxeff->attack_stop)) {
+ const s32 clength = jiffies_to_msecs(now - mlnxeff->begin_at);
+ const s32 alength = envelope->attack_length;
+ s32 attack_level;
+ if (end > start)
+ attack_level = mean - envelope->attack_level;
+ else
+ attack_level = mean + envelope->attack_level;
+ start = (start - attack_level) * clength / alength
+ + attack_level;
+ } else if (envelope->fade_length && time_before_eq(mlnxeff->fade_begin, now)) {
+ const s32 clength = jiffies_to_msecs(now - mlnxeff->fade_begin);
+ const s32 flength = envelope->fade_length;
+ s32 fade_level;
+ if (end > start)
+ fade_level = mean + envelope->fade_level;
+ else
+ fade_level = mean - envelope->fade_level;
+ end = (fade_level - end) * clength / flength + end;
+ }
+
+ new = ((end - start) * t) / length + start;
+ new = clamp(new, -0x7fff, 0x7fff);
+ pr_debug("RAMP level: %d, t: %u\n", new, t);
+ return new;
+}
+
+static void mlnx_destroy(struct ff_device *dev)
+{
+ struct mlnx_device *mlnxdev = dev->private;
+ del_timer_sync(&mlnxdev->timer);
+
+ kfree(mlnxdev->private);
+}
+
+static unsigned long mlnx_get_envelope_update_time(const struct mlnx_effect *mlnxeff,
+ const unsigned long update_rate_jiffies)
+{
+ const struct ff_effect *effect = &mlnxeff->effect;
+ const struct ff_envelope *envelope = mlnx_get_envelope(effect);
+ const unsigned long now = jiffies;
+ unsigned long fade_next;
+
+ /* Effect has an envelope with nonzero attack time */
+ if (envelope->attack_length && time_before(now, mlnxeff->attack_stop)) {
+ if (time_before(mlnxeff->updated_at + update_rate_jiffies, mlnxeff->attack_stop))
+ return mlnxeff->updated_at + update_rate_jiffies;
+
+ return mlnxeff->attack_stop;
+ }
+
+ /* Effect has an envelope with nonzero fade time */
+ if (mlnxeff->effect.replay.length) {
+ if (!envelope->fade_length)
+ return mlnxeff->stop_at;
+
+ /* Schedule the next update when the fade begins */
+ if (time_before(mlnxeff->updated_at, mlnxeff->fade_begin))
+ return mlnxeff->fade_begin;
+
+ /* Already fading */
+ fade_next = mlnxeff->updated_at + update_rate_jiffies;
+
+ /* Schedule update when the effect stops */
+ if (time_after(fade_next, mlnxeff->stop_at))
+ return mlnxeff->stop_at;
+ /* Schedule update at the next checkpoint */
+ return fade_next;
+ }
+
+ /* There is no envelope */
+
+ /* Prevent the effect from being started twice */
+ if (mlnxeff->begin_at == now && mlnx_is_playing(mlnxeff))
+ return now - 1;
+
+ return mlnxeff->begin_at;
+}
+
+static unsigned long mlnx_get_update_time(const struct mlnx_effect *mlnxeff,
+ const unsigned long update_rate_jiffies)
+{
+ const unsigned long now = jiffies;
+ unsigned long time, update_periodic;
+
+ switch (mlnxeff->effect.type) {
+ /* Constant effect does not change with time, but it can have
+ * an envelope and a duration */
+ case FF_CONSTANT:
+ return mlnx_get_envelope_update_time(mlnxeff,
+ update_rate_jiffies);
+ /* Periodic and ramp effects have to be periodically updated */
+ case FF_PERIODIC:
+ case FF_RAMP:
+ time = mlnx_get_envelope_update_time(mlnxeff,
+ update_rate_jiffies);
+
+ if (mlnx_is_square(&mlnxeff->effect)) {
+ const u16 half_T = mlnxeff->effect.u.periodic.period/2;
+ update_periodic = msecs_to_jiffies(half_T)
+ + mlnxeff->updated_at;
+ } else
+ update_periodic = mlnxeff->updated_at
+ + update_rate_jiffies;
+
+ /* Periodic effect has to be updated earlier than envelope
+ * or envelope update time is in the past */
+ if (time_before(update_periodic, time) || time_before(time, now))
+ return update_periodic;
+ /* Envelope needs to be updated */
+ return time;
+ case FF_DAMPER:
+ case FF_FRICTION:
+ case FF_INERTIA:
+ case FF_SPRING:
+ default:
+ if (time_after_eq(mlnxeff->begin_at, now))
+ return mlnxeff->begin_at;
+
+ return mlnxeff->stop_at;
+ }
+}
+
+static void mlnx_schedule_playback(struct mlnx_device *mlnxdev)
+{
+ struct mlnx_effect *mlnxeff;
+ const unsigned long now = jiffies;
+ int events = 0;
+ int i;
+ unsigned long earliest = 0;
+ unsigned long time;
+
+ /* Iterate over all effects and determine the earliest
+ * time when we have to attend to any */
+ for (i = 0; i < FF_MAX_EFFECTS; i++) {
+ mlnxeff = &mlnxdev->effects[i];
+
+ if (!mlnx_is_started(mlnxeff))
+ continue; /* Effect is not started, skip it */
+
+ if (mlnx_is_playing(mlnxeff))
+ time = mlnx_get_update_time(mlnxeff,
+ mlnxdev->update_rate_jiffies);
+ else
+ time = mlnxeff->begin_at;
+
+ pr_debug("Update time for effect %d: %lu\n", i, time);
+
+ /* Scheduled time is in the future and is either
+ * before the current earliest time or it is
+ * the first valid time value in this pass */
+ if (time_before_eq(now, time) &&
+ (++events == 1 || time_before(time, earliest)))
+ earliest = time;
+ }
+
+ if (events) {
+ pr_debug("Events: %d, earliest: %lu\n", events, earliest);
+ mod_timer(&mlnxdev->timer, earliest);
+ }
+}
+
+static void mlnx_add_force(struct mlnx_effect *mlnxeff, s32 *cfx, s32 *cfy,
+ const u16 gain)
+{
+ const struct ff_effect *effect = &mlnxeff->effect;
+ u16 direction;
+ s32 level;
+
+ pr_debug("Processing effect type %d, ID %d\n",
+ mlnxeff->effect.type, mlnxeff->effect.id);
+
+ direction = mlnxeff->effect.direction * 360 / 0xffff;
+ pr_debug("Direction deg: %u\n", direction);
+
+ switch (mlnxeff->effect.type) {
+ case FF_CONSTANT:
+ level = mlnx_apply_envelope(mlnxeff, effect->u.constant.level);
+ break;
+ case FF_PERIODIC:
+ level = mlnx_apply_envelope(mlnxeff,
+ effect->u.periodic.magnitude);
+ level = mlnx_calculate_periodic(mlnxeff, level);
+ break;
+ case FF_RAMP:
+ level = mlnx_calculate_ramp(mlnxeff);
+ break;
+ default:
+ pr_err("Effect %d not handled by mlnx_add_force\n",
+ mlnxeff->effect.type);
+ return;
+ }
+
+ *cfx += mlnx_calculate_x_force(level, direction) * gain / 0xffff;
+ *cfy += mlnx_calculate_y_force(level, direction) * gain / 0xffff;
+}
+
+static void mlnx_play_effects(struct mlnx_device *mlnxdev)
+{
+ const u16 gain = mlnxdev->gain;
+ const unsigned long now = jiffies;
+ int i;
+ int cfx = 0;
+ int cfy = 0;
+
+ for (i = 0; i < FF_MAX_EFFECTS; i++) {
+ struct mlnx_effect *mlnxeff = &mlnxdev->effects[i];
+
+ if (!mlnx_is_started(mlnxeff)) {
+ pr_debug("Effect %hd/%d not started\n",
+ mlnxeff->effect.id, i);
+ continue;
+ }
+
+ if (time_before(now, mlnxeff->begin_at)) {
+ pr_debug("Effect %hd/%d begins at a later time\n",
+ mlnxeff->effect.id, i);
+ continue;
+ }
+
+ if (time_before_eq(mlnxeff->stop_at, now) && mlnxeff->effect.replay.length) {
+ pr_debug("Effect %hd/%d has to be stopped\n",
+ mlnxeff->effect.id, i);
+
+ mlnx_clr_playing(mlnxeff);
+ if (--mlnxeff->repeat > 0)
+ mlnx_restart_effect(mlnxdev, mlnxeff);
+ else {
+ mlnx_clr_started(mlnxeff);
+ mlnx_stop_effect(mlnxdev, mlnxeff);
+ }
+
+ continue;
+ }
+
+ switch (mlnxeff->effect.type) {
+ case FF_CONSTANT:
+ case FF_PERIODIC:
+ case FF_RAMP:
+ if (!mlnx_test_set_playing(mlnxeff)) {
+ mlnxdev->combinable_playing++;
+ pr_debug("Starting combinable effect, total %u\n",
+ mlnxdev->combinable_playing);
+ }
+ mlnx_add_force(mlnxeff, &cfx, &cfy, gain);
+ break;
+ case FF_DAMPER:
+ case FF_FRICTION:
+ case FF_INERTIA:
+ case FF_SPRING:
+ if (!mlnx_test_set_playing(mlnxeff)) {
+ const struct mlnx_effect_command ecmd = {
+ .cmd = MLNX_START_UNCOMB,
+ .u.uncomb.id = i,
+ .u.uncomb.effect = &mlnxeff->effect
+ };
+ mlnxdev->control_effect(mlnxdev->dev,
+ mlnxdev->private, &ecmd);
+ }
+ break;
+ default:
+ pr_debug("Unhandled type of effect\n");
+ }
+ mlnxeff->updated_at = now;
+ }
+
+ if (mlnxdev->combinable_playing) {
+ const struct mlnx_effect_command ecmd = {
+ .cmd = MLNX_START_COMBINED,
+ .u.simple_force = {
+ .x = clamp(cfx, -0x7fff, 0x7fff),
+ .y = clamp(cfy, -0x7fff, 0x7fff)
+ }
+ };
+ mlnxdev->control_effect(mlnxdev->dev, mlnxdev->private, &ecmd);
+ }
+
+ mlnx_schedule_playback(mlnxdev);
+}
+
+static void mlnx_set_gain(struct input_dev *dev, u16 gain)
+{
+ struct mlnx_device *mlnxdev = dev->ff->private;
+ int i;
+
+ mlnxdev->gain = gain;
+
+ for (i = 0; i < FF_MAX_EFFECTS; i++)
+ mlnx_clr_playing(&mlnxdev->effects[i]);
+
+ mlnx_play_effects(mlnxdev);
+}
+
+static int mlnx_startstop(struct input_dev *dev, int effect_id, int repeat)
+{
+ struct mlnx_device *mlnxdev = dev->ff->private;
+ struct mlnx_effect *mlnxeff = &mlnxdev->effects[effect_id];
+
+ if (repeat > 0) {
+ pr_debug("Starting effect ID %d\n", effect_id);
+ mlnxeff->repeat = repeat;
+ if (!mlnx_is_started(mlnxeff))
+ mlnx_start_effect(mlnxeff);
+ } else {
+ pr_debug("Stopping effect ID %d\n", effect_id);
+ if (mlnx_is_started(mlnxeff)) {
+ if (mlnx_is_playing(mlnxeff)) {
+ mlnx_clr_playing(mlnxeff);
+ mlnx_stop_effect(mlnxdev, mlnxeff);
+ }
+ mlnx_clr_started(mlnxeff);
+ } else {
+ pr_debug("Effect ID %d already stopped\n", effect_id);
+ return 0;
+ }
+ }
+ mlnx_play_effects(mlnxdev);
+
+ return 0;
+}
+
+static void mlnx_timer_fired(unsigned long data)
+{
+ struct input_dev *dev = (struct input_dev *)data;
+ unsigned long flags;
+
+ spin_lock_irqsave(&dev->event_lock, flags);
+ mlnx_play_effects(dev->ff->private);
+ spin_unlock_irqrestore(&dev->event_lock, flags);
+}
+
+static int mlnx_upload(struct input_dev *dev, struct ff_effect *effect,
+ struct ff_effect *old)
+{
+ struct mlnx_device *mlnxdev = dev->ff->private;
+ struct mlnx_effect *mlnxeff = &mlnxdev->effects[effect->id];
+ const u16 length = effect->replay.length;
+ const u16 delay = effect->replay.delay;
+ int ret, fade_from;
+
+ /* Effect's timing is below kernel timer resolution */
+ if (length && length < FF_MIN_EFFECT_LENGTH)
+ effect->replay.length = FF_MIN_EFFECT_LENGTH;
+ if (delay && delay < FF_MIN_EFFECT_LENGTH)
+ effect->replay.delay = FF_MIN_EFFECT_LENGTH;
+
+ /* Periodic effects must have a non-zero period */
+ if (effect->type == FF_PERIODIC) {
+ if (!effect->u.periodic.period)
+ return -EINVAL;
+ }
+ /* Ramp effects cannot be infinite */
+ if (effect->type == FF_RAMP && !length)
+ return -EINVAL;
+
+ if (mlnx_is_combinable(effect)) {
+ const struct ff_envelope *envelope = mlnx_get_envelope(effect);
+
+ /* Infinite effects cannot fade */
+ if (!length && envelope->fade_length > 0)
+ return -EINVAL;
+ /* Fade length cannot be greater than effect duration */
+ fade_from = (int)length - (int)envelope->fade_length;
+ if (fade_from < 0)
+ return -EINVAL;
+ /* Envelope cannot start fading before it finishes attacking */
+ if (fade_from < envelope->attack_length && fade_from > 0)
+ return -EINVAL;
+ }
+
+
+ spin_lock_irq(&dev->event_lock);
+ mlnxeff->effect = *effect; /* Keep internal copy of the effect */
+ /* Check if the effect being modified is playing */
+ if (mlnx_is_started(mlnxeff)) {
+ if (mlnx_is_playing(mlnxeff)) {
+ mlnx_clr_playing(mlnxeff);
+ ret = mlnx_restart_effect(mlnxdev, mlnxeff);
+
+ if (ret) {
+ /* Restore the original effect */
+ if (old)
+ mlnxeff->effect = *old;
+ spin_unlock_irq(&dev->event_lock);
+ return ret;
+ }
+ }
+
+ mlnx_schedule_playback(mlnxdev);
+ spin_unlock_irq(&dev->event_lock);
+ return 0;
+ }
+
+ if (mlnx_is_conditional(effect)) {
+ ret = mlnx_upload_conditional(mlnxdev, &mlnxeff->effect);
+ if (ret) {
+ /* Restore the original effect */
+ if (old)
+ mlnxeff->effect = *old;
+ spin_unlock_irq(&dev->event_lock);
+ return ret;
+ }
+ }
+
+ spin_unlock_irq(&dev->event_lock);
+
+ return 0;
+}
+
+int input_ff_create_mlnx(struct input_dev *dev, void *data,
+ int(*control_effect)(struct input_dev *, void *, const struct mlnx_effect_command *),
+ const u16 update_rate)
+{
+ struct mlnx_device *mlnxdev;
+ int ret;
+
+ mlnxdev = kzalloc(sizeof(*mlnxdev), GFP_KERNEL);
+ if (!mlnxdev)
+ return -ENOMEM;
+
+ mlnxdev->dev = dev;
+ mlnxdev->private = data;
+ mlnxdev->control_effect = control_effect;
+ mlnxdev->gain = 0xffff;
+ mlnxdev->update_rate_jiffies = update_rate < FF_MIN_EFFECT_LENGTH ?
+ FF_MIN_EFFECT_LENGTH : update_rate;
+ setup_timer(&mlnxdev->timer, mlnx_timer_fired, (unsigned long)dev);
+
+ ret = input_ff_create(dev, FF_MAX_EFFECTS);
+ if (ret) {
+ kfree(mlnxdev);
+ return ret;
+ }
+
+ dev->ff->private = mlnxdev;
+ dev->ff->upload = mlnx_upload;
+ dev->ff->set_gain = mlnx_set_gain;
+ dev->ff->destroy = mlnx_destroy;
+ dev->ff->playback = mlnx_startstop;
+
+ pr_debug("Device successfully registered.\n");
+ return 0;
+}
+EXPORT_SYMBOL_GPL(input_ff_create_mlnx);
diff --git a/include/linux/input/ff-memless-next.h b/include/linux/input/ff-memless-next.h
new file mode 100644
index 0000000..f2fb080
--- /dev/null
+++ b/include/linux/input/ff-memless-next.h
@@ -0,0 +1,31 @@
+#include <linux/input.h>
+
+enum mlnx_commands {
+ MLNX_START_COMBINED,
+ MLNX_STOP_COMBINED,
+ MLNX_START_UNCOMB,
+ MLNX_STOP_UNCOMB,
+ MLNX_UPLOAD_UNCOMB
+};
+
+struct mlnx_simple_force {
+ const s32 x;
+ const s32 y;
+};
+
+struct mlnx_uncomb_effect {
+ const int id;
+ const struct ff_effect *effect;
+};
+
+struct mlnx_effect_command {
+ const enum mlnx_commands cmd;
+ union {
+ const struct mlnx_simple_force simple_force;
+ const struct mlnx_uncomb_effect uncomb;
+ } u;
+};
+
+int input_ff_create_mlnx(struct input_dev *dev, void *data,
+ int(*control_effect)(struct input_dev *, void *, const struct mlnx_effect_command *),
+ const u16 update_rate);
--
1.8.5.2
--
To unsubscribe from this list: send the line "unsubscribe linux-input" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
^ permalink raw reply related
* [PATCH v2 5/5] ARM: dts: sun7i: Add rtp controller node
From: Hans de Goede @ 2013-12-27 14:59 UTC (permalink / raw)
To: Dmitry Torokhov
Cc: linux-input-u79uwXL29TY76Z2rM5mHXA, LM Sensors, Maxime Ripard,
Corentin LABBE, linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
linux-sunxi-/JYPxA39Uh5TLH3MbocFFw, Hans de Goede
In-Reply-To: <1388156399-29677-1-git-send-email-hdegoede-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
Signed-off-by: Hans de Goede <hdegoede-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
---
arch/arm/boot/dts/sun7i-a20.dtsi | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/arch/arm/boot/dts/sun7i-a20.dtsi b/arch/arm/boot/dts/sun7i-a20.dtsi
index 3231789..68e825a 100644
--- a/arch/arm/boot/dts/sun7i-a20.dtsi
+++ b/arch/arm/boot/dts/sun7i-a20.dtsi
@@ -536,6 +536,12 @@
reg = <0x01c23800 0x200>;
};
+ rtp: rtp@01c25000 {
+ compatible = "allwinner,sun4i-ts";
+ reg = <0x01c25000 0x100>;
+ interrupts = <0 29 4>;
+ };
+
uart0: serial@01c28000 {
compatible = "snps,dw-apb-uart";
reg = <0x01c28000 0x400>;
--
1.8.4.2
^ permalink raw reply related
* [PATCH v2 4/5] ARM: dts: sun5i: Add rtp controller node
From: Hans de Goede @ 2013-12-27 14:59 UTC (permalink / raw)
To: Dmitry Torokhov
Cc: linux-input-u79uwXL29TY76Z2rM5mHXA, LM Sensors, Maxime Ripard,
Corentin LABBE, linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
linux-sunxi-/JYPxA39Uh5TLH3MbocFFw, Hans de Goede
In-Reply-To: <1388156399-29677-1-git-send-email-hdegoede-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
Signed-off-by: Hans de Goede <hdegoede-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
---
arch/arm/boot/dts/sun5i-a10s.dtsi | 6 ++++++
arch/arm/boot/dts/sun5i-a13.dtsi | 6 ++++++
2 files changed, 12 insertions(+)
diff --git a/arch/arm/boot/dts/sun5i-a10s.dtsi b/arch/arm/boot/dts/sun5i-a10s.dtsi
index 948eb10..34dd303 100644
--- a/arch/arm/boot/dts/sun5i-a10s.dtsi
+++ b/arch/arm/boot/dts/sun5i-a10s.dtsi
@@ -409,6 +409,12 @@
reg = <0x01c23800 0x10>;
};
+ rtp: rtp@01c25000 {
+ compatible = "allwinner,sun4i-ts";
+ reg = <0x01c25000 0x100>;
+ interrupts = <29>;
+ };
+
uart0: serial@01c28000 {
compatible = "snps,dw-apb-uart";
reg = <0x01c28000 0x400>;
diff --git a/arch/arm/boot/dts/sun5i-a13.dtsi b/arch/arm/boot/dts/sun5i-a13.dtsi
index ca15672..264cfa4 100644
--- a/arch/arm/boot/dts/sun5i-a13.dtsi
+++ b/arch/arm/boot/dts/sun5i-a13.dtsi
@@ -367,6 +367,12 @@
reg = <0x01c23800 0x10>;
};
+ rtp: rtp@01c25000 {
+ compatible = "allwinner,sun4i-ts";
+ reg = <0x01c25000 0x100>;
+ interrupts = <29>;
+ };
+
uart1: serial@01c28400 {
compatible = "snps,dw-apb-uart";
reg = <0x01c28400 0x400>;
--
1.8.4.2
^ permalink raw reply related
* [PATCH v2 3/5] ARM: dts: sun4i: Add rtp controller node
From: Hans de Goede @ 2013-12-27 14:59 UTC (permalink / raw)
To: Dmitry Torokhov
Cc: linux-input-u79uwXL29TY76Z2rM5mHXA, LM Sensors, Maxime Ripard,
Corentin LABBE, linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
linux-sunxi-/JYPxA39Uh5TLH3MbocFFw, Hans de Goede
In-Reply-To: <1388156399-29677-1-git-send-email-hdegoede-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
Signed-off-by: Hans de Goede <hdegoede-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
---
arch/arm/boot/dts/sun4i-a10.dtsi | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/arch/arm/boot/dts/sun4i-a10.dtsi b/arch/arm/boot/dts/sun4i-a10.dtsi
index de1dd73..502f3e2 100644
--- a/arch/arm/boot/dts/sun4i-a10.dtsi
+++ b/arch/arm/boot/dts/sun4i-a10.dtsi
@@ -452,6 +452,12 @@
reg = <0x01c23800 0x10>;
};
+ rtp: rtp@01c25000 {
+ compatible = "allwinner,sun4i-ts";
+ reg = <0x01c25000 0x100>;
+ interrupts = <29>;
+ };
+
uart0: serial@01c28000 {
compatible = "snps,dw-apb-uart";
reg = <0x01c28000 0x400>;
--
1.8.4.2
^ permalink raw reply related
* [PATCH v2 2/5] input: sun4i-ts: Add support for temperature sensor
From: Hans de Goede @ 2013-12-27 14:59 UTC (permalink / raw)
To: Dmitry Torokhov
Cc: linux-input-u79uwXL29TY76Z2rM5mHXA, LM Sensors, Maxime Ripard,
Corentin LABBE, linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
linux-sunxi-/JYPxA39Uh5TLH3MbocFFw, Hans de Goede
In-Reply-To: <1388156399-29677-1-git-send-email-hdegoede-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
The sun4i resisitive touchscreen controller also comes with a built-in
temperature sensor. This commit adds support for it.
This commit also introduces a new "ts-attached" device-tree property,
when this is not set, the input part of the driver won't register. This way
the internal temperature sensor can be used to measure the SoC temperature
independent of there actually being a touchscreen attached to the controller.
Signed-off-by: Hans de Goede <hdegoede-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
---
.../bindings/input/touchscreen/sun4i.txt | 6 +
drivers/input/touchscreen/sun4i-ts.c | 129 ++++++++++++++++-----
2 files changed, 109 insertions(+), 26 deletions(-)
diff --git a/Documentation/devicetree/bindings/input/touchscreen/sun4i.txt b/Documentation/devicetree/bindings/input/touchscreen/sun4i.txt
index e45927e..1fffd11 100644
--- a/Documentation/devicetree/bindings/input/touchscreen/sun4i.txt
+++ b/Documentation/devicetree/bindings/input/touchscreen/sun4i.txt
@@ -6,10 +6,16 @@ Required properties:
- reg: mmio address range of the chip
- interrupts: interrupt to which the chip is connected
+Optional properties:
+ - ts-attached: boolean set this to tell the driver that an actual touchscreen
+ is attached and that it should register an input device,
+ without this it only registers the builtin temperate sensor
+
Example:
rtp: rtp@01c25000 {
compatible = "allwinner,sun4i-ts";
reg = <0x01c25000 0x100>;
interrupts = <29>;
+ ts-attached;
};
diff --git a/drivers/input/touchscreen/sun4i-ts.c b/drivers/input/touchscreen/sun4i-ts.c
index 10839d2..2d437f6 100644
--- a/drivers/input/touchscreen/sun4i-ts.c
+++ b/drivers/input/touchscreen/sun4i-ts.c
@@ -3,6 +3,9 @@
*
* Copyright (C) 2013 - 2014 Hans de Goede <hdegoede-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
*
+ * The hwmon parts are based on work by Corentin LABBE which is:
+ * Copyright (C) 2013 Corentin LABBE <clabbe.montjoie-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
+ *
* 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
@@ -30,6 +33,7 @@
*/
#include <linux/err.h>
+#include <linux/hwmon.h>
#include <linux/init.h>
#include <linux/input.h>
#include <linux/interrupt.h>
@@ -106,6 +110,7 @@ struct sun4i_ts_data {
void __iomem *base;
unsigned int irq;
bool ignore_fifo_data;
+ int temp_data;
};
static irqreturn_t sun4i_ts_irq(int irq, void *dev_id)
@@ -115,6 +120,12 @@ static irqreturn_t sun4i_ts_irq(int irq, void *dev_id)
reg_val = readl(ts->base + TP_INT_FIFOS);
+ if (reg_val & TEMP_DATA_PENDING)
+ ts->temp_data = readl(ts->base + TEMP_DATA);
+
+ if (!ts->input)
+ goto leave;
+
if (reg_val & FIFO_DATA_PENDING) {
x = readl(ts->base + TP_DATA);
y = readl(ts->base + TP_DATA);
@@ -140,6 +151,7 @@ static irqreturn_t sun4i_ts_irq(int irq, void *dev_id)
input_sync(ts->input);
}
+leave:
writel(reg_val, ts->base + TP_INT_FIFOS);
return IRQ_HANDLED;
@@ -149,9 +161,9 @@ static int sun4i_ts_open(struct input_dev *dev)
{
struct sun4i_ts_data *ts = input_get_drvdata(dev);
- /* Flush, set trig level to 1, enable data and up irqs */
- writel(DATA_IRQ_EN(1) | FIFO_TRIG(1) |FIFO_FLUSH(1) | TP_UP_IRQ_EN(1),
- ts->base + TP_INT_FIFOC);
+ /* Flush, set trig level to 1, enable temp, data and up irqs */
+ writel(TEMP_IRQ_EN(1) | DATA_IRQ_EN(1) | FIFO_TRIG(1) | FIFO_FLUSH(1) |
+ TP_UP_IRQ_EN(1), ts->base + TP_INT_FIFOC);
return 0;
}
@@ -160,40 +172,76 @@ static void sun4i_ts_close(struct input_dev *dev)
{
struct sun4i_ts_data *ts = input_get_drvdata(dev);
- /* Deactivate all IRQs */
- writel(0, ts->base + TP_INT_FIFOC);
+ /* Deactivate all input IRQs */
+ writel(TEMP_IRQ_EN(1), ts->base + TP_INT_FIFOC);
synchronize_irq(ts->irq);
}
+static ssize_t show_temp(struct device *dev, struct device_attribute *devattr,
+ char *buf)
+{
+ struct sun4i_ts_data *ts = dev_get_drvdata(dev);
+
+ /* No temp_data until the first irq */
+ if (ts->temp_data == -1)
+ return -EAGAIN;
+
+ return sprintf(buf, "%d\n", (ts->temp_data - 1447) * 100);
+}
+
+static ssize_t show_temp_label(struct device *dev,
+ struct device_attribute *devattr, char *buf)
+{
+ return sprintf(buf, "SoC temperature\n");
+}
+
+static DEVICE_ATTR(temp1_input, S_IRUGO, show_temp, NULL);
+static DEVICE_ATTR(temp1_label, S_IRUGO, show_temp_label, NULL);
+
+static struct attribute *sun4i_ts_attrs[] = {
+ &dev_attr_temp1_input.attr,
+ &dev_attr_temp1_label.attr,
+ NULL
+};
+ATTRIBUTE_GROUPS(sun4i_ts);
+
static int sun4i_ts_probe(struct platform_device *pdev)
{
struct sun4i_ts_data *ts;
struct device *dev = &pdev->dev;
+ struct device_node *np =dev->of_node;
+ struct device *hwmon;
int ret;
+ bool ts_attached;
+
+ ts_attached = of_property_read_bool(np, "ts-attached");
ts = devm_kzalloc(dev, sizeof(struct sun4i_ts_data), GFP_KERNEL);
if (!ts)
return -ENOMEM;
ts->dev = dev;
-
- ts->input = devm_input_allocate_device(dev);
- if (!ts->input)
- return -ENOMEM;
-
- ts->input->name = pdev->name;
- ts->input->phys = "sun4i_ts/input0";
- ts->input->open = sun4i_ts_open;
- ts->input->close = sun4i_ts_close;
- ts->input->id.bustype = BUS_HOST;
- ts->input->id.vendor = 0x0001;
- ts->input->id.product = 0x0001;
- ts->input->id.version = 0x0100;
- ts->input->evbit[0] = BIT(EV_SYN) | BIT(EV_KEY) | BIT(EV_ABS);
- set_bit(BTN_TOUCH, ts->input->keybit);
- input_set_abs_params(ts->input, ABS_X, 0, 4095, 0, 0);
- input_set_abs_params(ts->input, ABS_Y, 0, 4095, 0, 0);
- input_set_drvdata(ts->input, ts);
+ ts->temp_data = -1;
+
+ if (ts_attached) {
+ ts->input = devm_input_allocate_device(dev);
+ if (!ts->input)
+ return -ENOMEM;
+
+ ts->input->name = pdev->name;
+ ts->input->phys = "sun4i_ts/input0";
+ ts->input->open = sun4i_ts_open;
+ ts->input->close = sun4i_ts_close;
+ ts->input->id.bustype = BUS_HOST;
+ ts->input->id.vendor = 0x0001;
+ ts->input->id.product = 0x0001;
+ ts->input->id.version = 0x0100;
+ ts->input->evbit[0] = BIT(EV_SYN) | BIT(EV_KEY) | BIT(EV_ABS);
+ set_bit(BTN_TOUCH, ts->input->keybit);
+ input_set_abs_params(ts->input, ABS_X, 0, 4095, 0, 0);
+ input_set_abs_params(ts->input, ABS_Y, 0, 4095, 0, 0);
+ input_set_drvdata(ts->input, ts);
+ }
ts->base = devm_ioremap_resource(dev,
platform_get_resource(pdev, IORESOURCE_MEM, 0));
@@ -232,14 +280,42 @@ static int sun4i_ts_probe(struct platform_device *pdev)
writel(STYLUS_UP_DEBOUN(5) | STYLUS_UP_DEBOUN_EN(1) | TP_MODE_EN(1),
ts->base + TP_CTRL1);
- ret = input_register_device(ts->input);
- if (ret)
- return ret;
+ hwmon = devm_hwmon_device_register_with_groups(ts->dev, "sun4i_ts",
+ ts, sun4i_ts_groups);
+ if (IS_ERR(hwmon)) {
+ return PTR_ERR(hwmon);
+ }
+
+ writel(TEMP_IRQ_EN(1), ts->base + TP_INT_FIFOC);
+
+ if (ts_attached) {
+ ret = input_register_device(ts->input);
+ if (ret) {
+ writel(0, ts->base + TP_INT_FIFOC);
+ synchronize_irq(ts->irq);
+ return ret;
+ }
+ }
platform_set_drvdata(pdev, ts);
return 0;
}
+static int sun4i_ts_remove(struct platform_device *pdev)
+{
+ struct sun4i_ts_data *ts = platform_get_drvdata(pdev);
+
+ /* Explicit unregister to avoid open/close changing the imask later */
+ if (ts->input)
+ input_unregister_device(ts->input);
+
+ /* Deactivate all IRQs */
+ writel(0, ts->base + TP_INT_FIFOC);
+ synchronize_irq(ts->irq);
+
+ return 0;
+}
+
static const struct of_device_id sun4i_ts_of_match[] = {
{ .compatible = "allwinner,sun4i-ts", },
{ /* sentinel */ }
@@ -253,6 +329,7 @@ static struct platform_driver sun4i_ts_driver = {
.of_match_table = of_match_ptr(sun4i_ts_of_match),
},
.probe = sun4i_ts_probe,
+ .remove = sun4i_ts_remove,
};
module_platform_driver(sun4i_ts_driver);
--
1.8.4.2
^ permalink raw reply related
* [PATCH v2 1/5] input: Add new sun4i-ts driver for Allwinner sunxi SoC's rtp controller
From: Hans de Goede @ 2013-12-27 14:59 UTC (permalink / raw)
To: Dmitry Torokhov
Cc: linux-input-u79uwXL29TY76Z2rM5mHXA, LM Sensors, Maxime Ripard,
Corentin LABBE, linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
linux-sunxi-/JYPxA39Uh5TLH3MbocFFw, Hans de Goede
In-Reply-To: <1388156399-29677-1-git-send-email-hdegoede-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
Note the sun4i-ts controller is capable of detecting a second touch, but when
a second touch is present then the accuracy becomes so bad the reported touch
location is not useable.
The original android driver contains some complicated heuristics using the
aprox. distance between the 2 touches to see if the user is making a pinch
open / close movement, and then reports emulated multi-touch events around
the last touch coordinate (as the dual-touch coordinates are worthless).
These kinds of heuristics are just asking for trouble (and don't belong
in the kernel). So this driver offers straight forward, reliable single
touch functionality only.
Signed-off-by: Hans de Goede <hdegoede-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
---
.../bindings/input/touchscreen/sun4i.txt | 15 ++
drivers/input/touchscreen/Kconfig | 10 +
drivers/input/touchscreen/Makefile | 1 +
drivers/input/touchscreen/sun4i-ts.c | 262 +++++++++++++++++++++
4 files changed, 288 insertions(+)
create mode 100644 Documentation/devicetree/bindings/input/touchscreen/sun4i.txt
create mode 100644 drivers/input/touchscreen/sun4i-ts.c
diff --git a/Documentation/devicetree/bindings/input/touchscreen/sun4i.txt b/Documentation/devicetree/bindings/input/touchscreen/sun4i.txt
new file mode 100644
index 0000000..e45927e
--- /dev/null
+++ b/Documentation/devicetree/bindings/input/touchscreen/sun4i.txt
@@ -0,0 +1,15 @@
+sun4i resistive touchscreen controller
+--------------------------------------
+
+Required properties:
+ - compatible: "allwinner,sun4i-ts"
+ - reg: mmio address range of the chip
+ - interrupts: interrupt to which the chip is connected
+
+Example:
+
+ rtp: rtp@01c25000 {
+ compatible = "allwinner,sun4i-ts";
+ reg = <0x01c25000 0x100>;
+ interrupts = <29>;
+ };
diff --git a/drivers/input/touchscreen/Kconfig b/drivers/input/touchscreen/Kconfig
index 961d58d..95023de 100644
--- a/drivers/input/touchscreen/Kconfig
+++ b/drivers/input/touchscreen/Kconfig
@@ -906,6 +906,16 @@ config TOUCHSCREEN_STMPE
To compile this driver as a module, choose M here: the
module will be called stmpe-ts.
+config TOUCHSCREEN_SUN4I
+ tristate "Allwinner sun4i resistive touchscreen controller support"
+ depends on ARCH_SUNXI
+ help
+ This selects support for the resistive touchscreen controller
+ found on Allwinner sunxi SoCs.
+
+ To compile this driver as a module, choose M here: the
+ module will be called sun4i-ts.
+
config TOUCHSCREEN_SUR40
tristate "Samsung SUR40 (Surface 2.0/PixelSense) touchscreen"
depends on USB
diff --git a/drivers/input/touchscreen/Makefile b/drivers/input/touchscreen/Makefile
index 62801f2..c8f7375 100644
--- a/drivers/input/touchscreen/Makefile
+++ b/drivers/input/touchscreen/Makefile
@@ -54,6 +54,7 @@ obj-$(CONFIG_TOUCHSCREEN_PIXCIR) += pixcir_i2c_ts.o
obj-$(CONFIG_TOUCHSCREEN_S3C2410) += s3c2410_ts.o
obj-$(CONFIG_TOUCHSCREEN_ST1232) += st1232.o
obj-$(CONFIG_TOUCHSCREEN_STMPE) += stmpe-ts.o
+obj-$(CONFIG_TOUCHSCREEN_SUN4I) += sun4i-ts.o
obj-$(CONFIG_TOUCHSCREEN_SUR40) += sur40.o
obj-$(CONFIG_TOUCHSCREEN_TI_AM335X_TSC) += ti_am335x_tsc.o
obj-$(CONFIG_TOUCHSCREEN_TNETV107X) += tnetv107x-ts.o
diff --git a/drivers/input/touchscreen/sun4i-ts.c b/drivers/input/touchscreen/sun4i-ts.c
new file mode 100644
index 0000000..10839d2
--- /dev/null
+++ b/drivers/input/touchscreen/sun4i-ts.c
@@ -0,0 +1,262 @@
+/*
+ * Allwinner sunxi resistive touchscreen controller driver
+ *
+ * Copyright (C) 2013 - 2014 Hans de Goede <hdegoede-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
+ *
+ * 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.
+ */
+
+/*
+ * The sun4i-ts controller is capable of detecting a second touch, but when a
+ * second touch is present then the accuracy becomes so bad the reported touch
+ * location is not useable.
+ *
+ * The original android driver contains some complicated heuristics using the
+ * aprox. distance between the 2 touches to see if the user is making a pinch
+ * open / close movement, and then reports emulated multi-touch events around
+ * the last touch coordinate (as the dual-touch coordinates are worthless).
+ *
+ * These kinds of heuristics are just asking for trouble (and don't belong
+ * in the kernel). So this driver offers straight forward, reliable single
+ * touch functionality only.
+ */
+
+#include <linux/err.h>
+#include <linux/init.h>
+#include <linux/input.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/of_platform.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+
+#define TP_CTRL0 0x00
+#define TP_CTRL1 0x04
+#define TP_CTRL2 0x08
+#define TP_CTRL3 0x0c
+#define TP_INT_FIFOC 0x10
+#define TP_INT_FIFOS 0x14
+#define TP_TPR 0x18
+#define TP_CDAT 0x1c
+#define TEMP_DATA 0x20
+#define TP_DATA 0x24
+
+/* TP_CTRL0 bits */
+#define ADC_FIRST_DLY(x) ((x) << 24) /* 8 bits */
+#define ADC_FIRST_DLY_MODE(x) ((x) << 23)
+#define ADC_CLK_SEL(x) ((x) << 22)
+#define ADC_CLK_DIV(x) ((x) << 20) /* 3 bits */
+#define FS_DIV(x) ((x) << 16) /* 4 bits */
+#define T_ACQ(x) ((x) << 0) /* 16 bits */
+
+/* TP_CTRL1 bits */
+#define STYLUS_UP_DEBOUN(x) ((x) << 12) /* 8 bits */
+#define STYLUS_UP_DEBOUN_EN(x) ((x) << 9)
+#define TOUCH_PAN_CALI_EN(x) ((x) << 6)
+#define TP_DUAL_EN(x) ((x) << 5)
+#define TP_MODE_EN(x) ((x) << 4)
+#define TP_ADC_SELECT(x) ((x) << 3)
+#define ADC_CHAN_SELECT(x) ((x) << 0) /* 3 bits */
+
+/* TP_CTRL2 bits */
+#define TP_SENSITIVE_ADJUST(x) ((x) << 28) /* 4 bits */
+#define TP_MODE_SELECT(x) ((x) << 26) /* 2 bits */
+#define PRE_MEA_EN(x) ((x) << 24)
+#define PRE_MEA_THRE_CNT(x) ((x) << 0) /* 24 bits */
+
+/* TP_CTRL3 bits */
+#define FILTER_EN(x) ((x) << 2)
+#define FILTER_TYPE(x) ((x) << 0) /* 2 bits */
+
+/* TP_INT_FIFOC irq and fifo mask / control bits */
+#define TEMP_IRQ_EN(x) ((x) << 18)
+#define OVERRUN_IRQ_EN(x) ((x) << 17)
+#define DATA_IRQ_EN(x) ((x) << 16)
+#define TP_DATA_XY_CHANGE(x) ((x) << 13)
+#define FIFO_TRIG(x) ((x) << 8) /* 5 bits */
+#define DATA_DRQ_EN(x) ((x) << 7)
+#define FIFO_FLUSH(x) ((x) << 4)
+#define TP_UP_IRQ_EN(x) ((x) << 1)
+#define TP_DOWN_IRQ_EN(x) ((x) << 0)
+
+/* TP_INT_FIFOS irq and fifo status bits */
+#define TEMP_DATA_PENDING BIT(18)
+#define FIFO_OVERRUN_PENDING BIT(17)
+#define FIFO_DATA_PENDING BIT(16)
+#define TP_IDLE_FLG BIT(2)
+#define TP_UP_PENDING BIT(1)
+#define TP_DOWN_PENDING BIT(0)
+
+/* TP_TPR bits */
+#define TEMP_ENABLE(x) ((x) << 16)
+#define TEMP_PERIOD(x) ((x) << 0) /* t = x * 256 * 16 / clkin */
+
+struct sun4i_ts_data {
+ struct device *dev;
+ struct input_dev *input;
+ void __iomem *base;
+ unsigned int irq;
+ bool ignore_fifo_data;
+};
+
+static irqreturn_t sun4i_ts_irq(int irq, void *dev_id)
+{
+ struct sun4i_ts_data *ts = dev_id;
+ u32 reg_val, x, y;
+
+ reg_val = readl(ts->base + TP_INT_FIFOS);
+
+ if (reg_val & FIFO_DATA_PENDING) {
+ x = readl(ts->base + TP_DATA);
+ y = readl(ts->base + TP_DATA);
+ /* The 1st location reported after an up event is unreliable */
+ if (!ts->ignore_fifo_data) {
+ input_report_abs(ts->input, ABS_X, x);
+ input_report_abs(ts->input, ABS_Y, y);
+ /*
+ * The hardware has a separate down status bit, but
+ * that gets set before we get the first location,
+ * resulting in reporting a click on the old location.
+ */
+ input_report_key(ts->input, BTN_TOUCH, 1);
+ input_sync(ts->input);
+ } else {
+ ts->ignore_fifo_data = false;
+ }
+ }
+
+ if (reg_val & TP_UP_PENDING) {
+ ts->ignore_fifo_data = true;
+ input_report_key(ts->input, BTN_TOUCH, 0);
+ input_sync(ts->input);
+ }
+
+ writel(reg_val, ts->base + TP_INT_FIFOS);
+
+ return IRQ_HANDLED;
+}
+
+static int sun4i_ts_open(struct input_dev *dev)
+{
+ struct sun4i_ts_data *ts = input_get_drvdata(dev);
+
+ /* Flush, set trig level to 1, enable data and up irqs */
+ writel(DATA_IRQ_EN(1) | FIFO_TRIG(1) |FIFO_FLUSH(1) | TP_UP_IRQ_EN(1),
+ ts->base + TP_INT_FIFOC);
+
+ return 0;
+}
+
+static void sun4i_ts_close(struct input_dev *dev)
+{
+ struct sun4i_ts_data *ts = input_get_drvdata(dev);
+
+ /* Deactivate all IRQs */
+ writel(0, ts->base + TP_INT_FIFOC);
+ synchronize_irq(ts->irq);
+}
+
+static int sun4i_ts_probe(struct platform_device *pdev)
+{
+ struct sun4i_ts_data *ts;
+ struct device *dev = &pdev->dev;
+ int ret;
+
+ ts = devm_kzalloc(dev, sizeof(struct sun4i_ts_data), GFP_KERNEL);
+ if (!ts)
+ return -ENOMEM;
+
+ ts->dev = dev;
+
+ ts->input = devm_input_allocate_device(dev);
+ if (!ts->input)
+ return -ENOMEM;
+
+ ts->input->name = pdev->name;
+ ts->input->phys = "sun4i_ts/input0";
+ ts->input->open = sun4i_ts_open;
+ ts->input->close = sun4i_ts_close;
+ ts->input->id.bustype = BUS_HOST;
+ ts->input->id.vendor = 0x0001;
+ ts->input->id.product = 0x0001;
+ ts->input->id.version = 0x0100;
+ ts->input->evbit[0] = BIT(EV_SYN) | BIT(EV_KEY) | BIT(EV_ABS);
+ set_bit(BTN_TOUCH, ts->input->keybit);
+ input_set_abs_params(ts->input, ABS_X, 0, 4095, 0, 0);
+ input_set_abs_params(ts->input, ABS_Y, 0, 4095, 0, 0);
+ input_set_drvdata(ts->input, ts);
+
+ ts->base = devm_ioremap_resource(dev,
+ platform_get_resource(pdev, IORESOURCE_MEM, 0));
+ if (IS_ERR(ts->base))
+ return PTR_ERR(ts->base);
+
+ ts->irq = platform_get_irq(pdev, 0);
+ ret = devm_request_irq(dev, ts->irq, sun4i_ts_irq, 0, "sun4i-ts", ts);
+ if (ret)
+ return ret;
+
+ /*
+ * Select HOSC clk, clkin = clk / 6, adc samplefreq = clkin / 8192,
+ * t_acq = clkin / (16 * 64)
+ */
+ writel(ADC_CLK_SEL(0) | ADC_CLK_DIV(2) | FS_DIV(7) | T_ACQ(63),
+ ts->base + TP_CTRL0);
+
+ /*
+ * sensitive_adjust = 15 : max, which is not all that sensitive,
+ * tp_mode = 0 : only x and y coordinates, as we don't use dual touch
+ */
+ writel(TP_SENSITIVE_ADJUST(15) | TP_MODE_SELECT(0),
+ ts->base + TP_CTRL2);
+
+ /* Enable median filter, type 1 : 5/3 */
+ writel(FILTER_EN(1) | FILTER_TYPE(1), ts->base + TP_CTRL3);
+
+ /* Enable temperature measurement, period 1953 (2 seconds) */
+ writel(TEMP_ENABLE(1) | TEMP_PERIOD(1953), ts->base + TP_TPR);
+
+ /*
+ * Set stylus up debounce to aprox 10 ms, enable debounce, and
+ * finally enable tp mode.
+ */
+ writel(STYLUS_UP_DEBOUN(5) | STYLUS_UP_DEBOUN_EN(1) | TP_MODE_EN(1),
+ ts->base + TP_CTRL1);
+
+ ret = input_register_device(ts->input);
+ if (ret)
+ return ret;
+
+ platform_set_drvdata(pdev, ts);
+ return 0;
+}
+
+static const struct of_device_id sun4i_ts_of_match[] = {
+ { .compatible = "allwinner,sun4i-ts", },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, sun4i_ts_of_match);
+
+static struct platform_driver sun4i_ts_driver = {
+ .driver = {
+ .owner = THIS_MODULE,
+ .name = "sun4i-ts",
+ .of_match_table = of_match_ptr(sun4i_ts_of_match),
+ },
+ .probe = sun4i_ts_probe,
+};
+
+module_platform_driver(sun4i_ts_driver);
+
+MODULE_DESCRIPTION("Allwinner sun4i resistive touchscreen controller driver");
+MODULE_AUTHOR("Hans de Goede <hdegoede-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>");
+MODULE_LICENSE("GPL");
--
1.8.4.2
^ permalink raw reply related
* [PATCH v2 0/5] input: Add new sun4i-ts driver for Allwinner sunxi
From: Hans de Goede @ 2013-12-27 14:59 UTC (permalink / raw)
To: Dmitry Torokhov
Cc: linux-input-u79uwXL29TY76Z2rM5mHXA, LM Sensors, Maxime Ripard,
Corentin LABBE, linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
linux-sunxi-/JYPxA39Uh5TLH3MbocFFw
Hi Dimitri et al,
Here is v2 of my sun4i-ts patch-set.
Changes since v1:
-Use devm_kzalloc and devm_hwmon_device_register_with_groups as suggested
by Guenter Roeck
-Various small cleanups suggested by Thomas Petazzoni
-Add open/close methods and enable/disable the input related interrupts from
there as suggested by you (Dimitri)
Assuming there is no more feedback, can you please add the 1st 2 patches
to your tree for 3.14 ?
Maxime, can you please add the dts bits to your dts tree for 3.14 ?
Thanks & Regards,
Hans
^ permalink raw reply
* Re: [PATCH] Bluetooth: hidp: make sure input buffers are big enough
From: David Herrmann @ 2013-12-27 12:35 UTC (permalink / raw)
To: Marcel Holtmann
Cc: open list:HID CORE LAYER, Jiri Kosina,
linux-bluetooth@vger.kernel.org development, Gustavo F. Padovan
In-Reply-To: <3109C2D8-E01C-4728-A593-1978BB3D202D@holtmann.org>
Hi Marcel
On Fri, Dec 20, 2013 at 2:36 PM, Marcel Holtmann <marcel@holtmann.org> wrote:
> Hi David,
>
>> HID core expects the input buffers to be at least of size 4096
>> (HID_MAX_BUFFER_SIZE). Other sizes will result in buffer-overflows if an
>> input-report is smaller than advertised. We could, like i2c, compute the
>> biggest report-size instead of using HID_MAX_BUFFER_SIZE, but this will
>> blow up if report-descriptors are changed after ->start() has been called.
>> So lets be safe and just use the biggest buffer we have.
>>
>> Note that this adds an additional copy to the HIDP input path. If there is
>> a way to make sure the skb-buf is big enough, we should use that instead.
>>
>> The best way would be to make hid-core honor the @size argument, though,
>> that sounds easier than it is. So lets just fix the buffer-overflows for
>> now and afterwards look for a faster way for all transport drivers.
>>
>> Signed-off-by: David Herrmann <dh.herrmann@gmail.com>
>> ---
>> Hi
>>
>> Any ideas how to improve this patch? I'd like to avoid the extra copy but I have
>> no clue how the skb stuff works exactly.
>
> the buffers are coming straight from L2CAP. They might come in fragments. We have to reassemble them and while there are large packets for sure, we rather not want to allocate 4096 for every reassembled packet to make HID happy.
>
> I am not super familiar with the underlying memory management of SKBs, but I assume they use slices of some sort internally. So uses large SKBs for 20 byte HID report will cause an issue with all other protocols running on top of L2CAP
I was more thinking of an skb call to increase the underlying buffer
of a single package. We could call this together with skb_linearize()
in HIDP to guarantee the skb-buf is big enough. Technically, this will
probably result in the same behavior as my own patch so probably not
the way to go.
>> I also haven't figured out a nice way to make HID-core honor the "size"
>> parameter. hid-input depends on getting the whole input-report.
>
> I think this needs clearly fixing.
And we have a volunteer, yippie! ;)
I think hid_input_report() in hid-core.c is the only place that relies
on this. Maybe it really is easier to fix it. But I am not even
slightly familiar with that code. So if no-one of the HID core
developers steps up to fix it, we should take the transport-driver
fixes instead. As nearly all transport-drivers are affected by this,
maybe we should even move this buffer into hid_device and provide
hid_input_truncated_report() which does this.
Anyhow, waiting for Jiri's comments on this.
>> Comments welcome!
>> David
>>
>> net/bluetooth/hidp/core.c | 16 ++++++++++++++--
>> net/bluetooth/hidp/hidp.h | 4 ++++
>> 2 files changed, 18 insertions(+), 2 deletions(-)
>>
>> diff --git a/net/bluetooth/hidp/core.c b/net/bluetooth/hidp/core.c
>> index 292e619..d9fb934 100644
>> --- a/net/bluetooth/hidp/core.c
>> +++ b/net/bluetooth/hidp/core.c
>> @@ -430,6 +430,16 @@ static void hidp_del_timer(struct hidp_session *session)
>> del_timer(&session->timer);
>> }
>>
>> +static void hidp_process_report(struct hidp_session *session,
>> + int type, const u8 *data, int len, int intr)
>> +{
>> + if (len > HID_MAX_BUFFER_SIZE)
>> + len = HID_MAX_BUFFER_SIZE;
>> +
>> + memcpy(session->input_buf, data, len);
>> + hid_input_report(session->hid, type, session->input_buf, len, intr);
>> +}
>> +
>> static void hidp_process_handshake(struct hidp_session *session,
>> unsigned char param)
>> {
>> @@ -502,7 +512,8 @@ static int hidp_process_data(struct hidp_session *session, struct sk_buff *skb,
>> hidp_input_report(session, skb);
>>
>> if (session->hid)
>> - hid_input_report(session->hid, HID_INPUT_REPORT, skb->data, skb->len, 0);
>> + hidp_process_report(session, HID_INPUT_REPORT,
>> + skb->data, skb->len, 0);
>> break;
>>
>> case HIDP_DATA_RTYPE_OTHER:
>> @@ -584,7 +595,8 @@ static void hidp_recv_intr_frame(struct hidp_session *session,
>> hidp_input_report(session, skb);
>>
>> if (session->hid) {
>> - hid_input_report(session->hid, HID_INPUT_REPORT, skb->data, skb->len, 1);
>> + hidp_process_report(session, HID_INPUT_REPORT,
>> + skb->data, skb->len, 1);
>> BT_DBG("report len %d", skb->len);
>> }
>> } else {
>> diff --git a/net/bluetooth/hidp/hidp.h b/net/bluetooth/hidp/hidp.h
>> index ab52414..8798492 100644
>> --- a/net/bluetooth/hidp/hidp.h
>> +++ b/net/bluetooth/hidp/hidp.h
>> @@ -24,6 +24,7 @@
>> #define __HIDP_H
>>
>> #include <linux/types.h>
>> +#include <linux/hid.h>
>> #include <linux/kref.h>
>> #include <net/bluetooth/bluetooth.h>
>> #include <net/bluetooth/l2cap.h>
>> @@ -179,6 +180,9 @@ struct hidp_session {
>>
>> /* Used in hidp_output_raw_report() */
>> int output_report_success; /* boolean */
>> +
>> + /* temporary input buffer */
>> + u8 input_buf[HID_MAX_BUFFER_SIZE];
>> };
>
> The report does not need any specific alignment?
This is already aligned to native size, isn't it? Anyhow, HID core
does not have any alignment-restrictions I am aware of. Jiri? I
thought the extract()/implement() stuff was fixed recently.
Thanks
David
^ permalink raw reply
* [PATCH v1] Input: elants_i2c: Add Elan touchscreen support
From: scott @ 2013-12-27 12:15 UTC (permalink / raw)
To: Scott Liu, Dmitry Torokhov, linux-kernel, linux-input
Cc: Vincent Wang, Jeff Chuang, Agnes Cheng
From: Scott Liu <scott.liu@emc.com.tw>
This patch is for Elan eKTH Touchscreen product, I2C adpater module.
Signed-off-by: Scott Liu <scott.liu@emc.com.tw>
---
drivers/input/touchscreen/Kconfig | 12 +
drivers/input/touchscreen/Makefile | 1 +
drivers/input/touchscreen/elants_i2c.c | 1789 ++++++++++++++++++++++++++++++++
3 files changed, 1802 insertions(+)
create mode 100644 drivers/input/touchscreen/elants_i2c.c
diff --git a/drivers/input/touchscreen/Kconfig b/drivers/input/touchscreen/Kconfig
index 961d58d..b400faa 100644
--- a/drivers/input/touchscreen/Kconfig
+++ b/drivers/input/touchscreen/Kconfig
@@ -317,6 +317,18 @@ config TOUCHSCREEN_GUNZE
To compile this driver as a module, choose M here: the
module will be called gunze.
+config TOUCHSCREEN_ELAN
+ tristate "Elan touchscreens"
+ depends on I2C
+ help
+ Say Y here if you have an Elan touchscreen connected to
+ your system.
+
+ If unsure, say N.
+
+ To compile this driver as a module, choose M here: the
+ module will be called elan.
+
config TOUCHSCREEN_ELO
tristate "Elo serial touchscreens"
select SERIO
diff --git a/drivers/input/touchscreen/Makefile b/drivers/input/touchscreen/Makefile
index 62801f2..687d5a7 100644
--- a/drivers/input/touchscreen/Makefile
+++ b/drivers/input/touchscreen/Makefile
@@ -30,6 +30,7 @@ obj-$(CONFIG_TOUCHSCREEN_EDT_FT5X06) += edt-ft5x06.o
obj-$(CONFIG_TOUCHSCREEN_HAMPSHIRE) += hampshire.o
obj-$(CONFIG_TOUCHSCREEN_GUNZE) += gunze.o
obj-$(CONFIG_TOUCHSCREEN_EETI) += eeti_ts.o
+obj-$(CONFIG_TOUCHSCREEN_ELAN) += elants_i2c.o
obj-$(CONFIG_TOUCHSCREEN_ELO) += elo.o
obj-$(CONFIG_TOUCHSCREEN_EGALAX) += egalax_ts.o
obj-$(CONFIG_TOUCHSCREEN_FUJITSU) += fujitsu_ts.o
diff --git a/drivers/input/touchscreen/elants_i2c.c b/drivers/input/touchscreen/elants_i2c.c
new file mode 100644
index 0000000..e46675a
--- /dev/null
+++ b/drivers/input/touchscreen/elants_i2c.c
@@ -0,0 +1,1789 @@
+/*
+ * Elan Microelectronics touchpanels with I2C interface
+ *
+ * Copyright (C) 2012 Elan Microelectronics Corporation.
+ * Scott Liu <scott.liu@emc.com.tw>
+ *
+ * This code is partly based on hid-multitouch.c:
+ *
+ * Copyright (c) 2010-2012 Stephane Chatty <chatty@enac.fr>
+ * Copyright (c) 2010-2012 Benjamin Tissoires <benjamin.tissoires@gmail.com>
+ * Copyright (c) 2010-2012 Ecole Nationale de l'Aviation Civile, France
+ *
+ */
+
+/*
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/input.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/async.h>
+#include <linux/i2c.h>
+#include <linux/delay.h>
+#include <linux/uaccess.h>
+#include <linux/buffer_head.h>
+#include <linux/version.h>
+#include <linux/kfifo.h>
+#include <linux/slab.h>
+#include <linux/debugfs.h>
+#include <linux/firmware.h>
+#include <linux/version.h>
+#include <linux/input/mt.h>
+
+/* debug option */
+static bool debug = false;
+module_param(debug, bool, 0444);
+MODULE_PARM_DESC(debug, "print a lot of debug information");
+
+#define elan_dbg(client, fmt, arg...) \
+ do { \
+ if (debug) \
+ dev_printk(KERN_DEBUG, \
+ &client->dev, fmt, ##arg); \
+ } while (0)
+
+/*
+ * Device, Driver information
+ */
+#define DEVICE_NAME "elants_i2c"
+
+#define DRV_MA_VER 1
+#define DRV_MI_VER 0
+#define DRV_SUB_MI_VER 4
+
+#define _str(s) (#s)
+#define str(s) _str(s)
+#define DRIVER_VERSION str(DRV_MA_VER.DRV_MI_VER.DRV_SUB_MI_VER)
+
+/* For CMT (must match XRANGE/YRANGE as defined in board config */
+#define X_PIXELS_PER_MM 13
+#define Y_PIXELS_PER_MM 14
+
+/*
+ * Finger report description
+ */
+
+#define MAX_CONTACT_NUM 10
+
+/*
+ * kfifo buffer size, used for Read command handshake
+ */
+
+#define FIFO_SIZE (64)
+
+/*
+ *Convert from rows or columns into resolution
+ */
+
+#define ELAN_TS_RESOLUTION(n) ((n - 1) * 64)
+
+static const char hello_packet[4] = { 0x55, 0x55, 0x55, 0x55 };
+static const char iniap_packet[4] = { 0x55, 0xaa, 0x33, 0xcc };
+static const char recov_packet[4] = { 0x55, 0x55, 0x80, 0x80 };
+
+/*
+ * Elan I2C Address definition
+ */
+
+#define DEV_MASTER 0x10
+#define DEV_SLAVE1 0x20
+#define DEV_SLAVE2 0x21
+#define MAX_DEVICE 3
+
+/*
+ * Finger report header byte definition
+ */
+
+#define REPORT_HEADER_10_FINGER 0x62
+
+/*
+ * Buffer mode Queue Header information
+ */
+
+#define QUEUE_HEADER_SINGLE 0x62
+#define QUEUE_HEADER_NORMAL 0X63
+#define QUEUE_HEADER_WAIT 0x64
+#define QUEUE_HEADER_SIZE 4
+
+#define PACKET_SIZE 55
+#define MAX_PACKET_LEN 169
+
+/*
+ * Command header definition
+ */
+
+#define CMD_HEADER_WRITE 0x54
+#define CMD_HEADER_READ 0x53
+#define CMD_HEADER_6B_READ 0x5B
+#define CMD_HEADER_RESP 0x52
+#define CMD_HEADER_6B_RESP 0x9B
+#define CMD_HEADER_HELLO 0x55
+#define CMD_HEADER_REK 0x66
+#define CMD_RESP_LEN 4
+
+/*
+ * FW information position
+ */
+
+#define FW_POS_HEADER 0
+#define FW_POS_STATE 1
+#define FW_POS_TOTAL 2
+#define FW_POS_CHECKSUM 34
+#define FW_POS_WIDTH 35
+#define FW_POS_PRESSURE 45
+
+/*
+ * test_bit definition
+ */
+
+#define LOCK_FILE_OPERATE 0
+#define LOCK_CMD_HANDSHAKE 1
+#define LOCK_FINGER_REPORT 2
+#define LOCK_FW_UPDATE 3
+
+/*
+ * kfifo return value definition
+ */
+
+#define RET_OK 0
+#define RET_CMDRSP 1
+#define RET_FAIL -1
+
+/*
+ * define boot condition definition
+ */
+
+#define E_BOOT_NORM 0
+#define E_BOOT_IAP 1
+
+/* FW read command, 0x53 0x?? 0x0, 0x01 */
+#define E_ELAN_INFO_FW_VER 0x00
+#define E_ELAN_INFO_BC_VER 0x10
+#define E_ELAN_INFO_TEST_VER 0xe0
+#define E_ELAN_INFO_FW_ID 0xf0
+
+/**
+ * struct multi_queue_header - used by buffer queue header
+ *
+ * @packet_id: packet_id represented status of buffer.
+ * @report_count: number of finger report in buffer.
+ * @report_length: total length exclusive queue length.
+ */
+struct multi_queue_header {
+ u8 packet_id;
+ u8 report_count;
+ u8 report_length;
+ u8 reserved;
+};
+
+struct mt_slot {
+ __s32 x, y, p, w, h;
+ __s32 contactid; /* the device ContactID assigned to this slot */
+ bool touch_state; /* is the touch valid? */
+ bool seen_in_this_frame; /* has this slot been updated */
+};
+
+struct mt_device {
+ struct mt_slot curdata; /* placeholder of incoming data */
+ __u8 num_received; /* how many contacts we received */
+ __u8 num_expected; /* expected last contact index */
+ __u8 maxcontacts;
+ bool curvalid; /* is the current contact valid? */
+ unsigned mt_flags; /* flags to pass to input-mt */
+ struct mt_slot *slots;
+};
+
+/**
+ * struct elants_data - represents a global define of elants device
+ */
+struct elants_data {
+
+ bool irq_wake;
+
+ /* [0] : Solution version
+ [1] : minor version */
+ union {
+ u8 _fw_version[2];
+ u16 fw_version;
+ };
+
+ u8 bc_version;
+ u8 iap_version;
+
+ union {
+ u8 _test_version[2];
+ u16 test_version;
+ };
+
+ bool fw_enabled;
+ int rows;
+ int cols;
+ int x_max;
+ int y_max;
+#define IAP_MODE_ENABLE 1
+ unsigned int iap_mode;
+ unsigned int rx_size;
+
+ u8 packet_size;
+
+ struct multi_queue_header mq_header;
+
+ struct i2c_client *client;
+ struct input_dev *input;
+ struct task_struct *thread;
+
+ struct mutex mutex; /* Protects I2C accesses to device */
+ struct mutex tr_mutex; /* Protects I2C tx/rx */
+
+ unsigned long flags; /* device flags */
+
+ unsigned short i2caddr;
+
+ struct kfifo fifo;
+ struct mutex fifo_mutex;
+ wait_queue_head_t wait;
+ spinlock_t rx_kfifo_lock;
+
+ struct mt_device td; /* mt device */
+
+ /* fields required for debug fs */
+ struct mutex dbfs_mutex;
+ struct dentry *dbfs_root;
+
+ /* Add for TS driver debug */
+ long int irq_received;
+ long int packet_received;
+ long int touched_sync;
+ long int no_touched_sync;
+ long int checksum_correct;
+ long int wdt_reset;
+ u16 checksum_fail;
+ u16 header_fail;
+ u16 mq_header_fail;
+};
+
+/*
+ * Function implement
+ */
+static inline void elan_msleep(u32 t)
+{
+ /*
+ * If the sleeping time is 10us - 20ms, usleep_range() is recommended.
+ * Read Documentation/timers/timers-howto.txt
+ */
+ usleep_range(t * 1000, t * 1000 + 500);
+}
+
+static inline bool elan_chip_number(u8 addr)
+{
+ return addr == DEV_MASTER ? 1 : 0;
+}
+
+/**
+ * elan_set_data - set command to TP.
+ *
+ * @client: our i2c device
+ * @data: command or data which will send to TP.
+ * @len: command length usually.
+ *
+ * set command to our TP.
+ */
+static int elan_set_data(struct i2c_client *client, const u8 *data, size_t len)
+{
+ struct elants_data *ts = i2c_get_clientdata(client);
+ struct i2c_adapter *adap = client->adapter;
+ struct i2c_msg msg;
+ int rc = 0;
+
+ dev_dbg(&client->dev, "Enter: %s\n", __func__);
+ elan_dbg(client,
+ "%s cmd: %*phC, addr=%x\n",
+ __func__, (int)len, data, ts->i2caddr);
+
+ mutex_lock(&ts->tr_mutex);
+
+ msg.addr = ts->i2caddr;
+ msg.flags = ts->client->flags & I2C_M_TEN;
+ msg.len = len;
+ msg.buf = (char *)data;
+
+ rc = i2c_transfer(adap, &msg, 1);
+ if (rc != 1)
+ dev_err(&ts->client->dev,
+ "i2c_transfer write fail, rc=%d\n", rc);
+
+ mutex_unlock(&ts->tr_mutex);
+
+ /* If everything went ok (i.e. 1 msg transmitted), return #0
+ transmitted, else error code. */
+ return (rc < 0) ? -EIO : 0;
+
+}
+
+/**
+ * elan_get_data - get command to TP.
+ *
+ * @client: our i2c device
+ * @data: data to be received from TP.
+ * @len: command length usually.
+ *
+ * get data from our TP.
+ */
+static int elan_get_data(struct i2c_client *client, const u8 *buf, size_t len)
+{
+ struct elants_data *ts = i2c_get_clientdata(client);
+ struct i2c_adapter *adap = client->adapter;
+ struct i2c_msg msg;
+ int rc = 0;
+
+ dev_dbg(&client->dev, "Enter: %s id:0x%x\n", __func__, ts->i2caddr);
+
+ mutex_lock(&ts->tr_mutex);
+
+ msg.addr = ts->i2caddr;
+ msg.flags = client->flags & I2C_M_TEN;
+ msg.flags |= I2C_M_RD;
+ msg.len = len;
+ msg.buf = (char *)buf;
+
+ rc = i2c_transfer(adap, &msg, 1);
+ if (rc != 1)
+ dev_err(&client->dev, "i2c_transfer read fail, rc=%d\n", rc);
+
+ elan_dbg(client, "%s len=%zu data:%*phC\n",
+ __func__, len, (int)len, buf);
+
+ mutex_unlock(&ts->tr_mutex);
+
+ /* If everything went ok (i.e. 1 msg transmitted), return #0
+ transmitted, else error code. */
+ return rc != 1 ? -EIO : 0;
+}
+
+/**
+ * elan_i2c_read_block
+ *
+ * @client: our i2c device
+ * @cmd: command
+ * @val: data to be received from TP.
+ * @len: command length usually.
+ *
+ * get data from our TP by continueous read.
+ */
+static int elan_i2c_read_block(struct i2c_client *client,
+ u8 *cmd, u8 *val, u16 len)
+{
+ struct i2c_msg msgs[2];
+ int ret;
+
+ dev_dbg(&client->dev, "Enter: %s\n", __func__);
+
+ msgs[0].addr = client->addr;
+ msgs[0].flags = client->flags & I2C_M_TEN;
+ msgs[0].len = 4;
+ msgs[0].buf = cmd;
+
+ msgs[1].addr = client->addr;
+ msgs[1].flags = client->flags & I2C_M_TEN;
+ msgs[1].flags |= I2C_M_RD;
+ msgs[1].len = len;
+ msgs[1].buf = val;
+
+ ret = i2c_transfer(client->adapter, msgs, 2);
+ return (ret == 2) ? len : ret;
+}
+
+static int elan_dbfs_open(struct inode *inode, struct file *file)
+{
+ int retval = 0;
+ struct elants_data *ts = inode->i_private;
+
+ dev_dbg(&ts->client->dev, "Enter %s\n", __func__);
+
+ if (!ts)
+ return -ENODEV;
+
+ retval = mutex_lock_interruptible(&ts->dbfs_mutex);
+ if (retval)
+ return retval;
+
+ if (!kobject_get(&ts->client->dev.kobj)) {
+ retval = -ENODEV;
+ goto dbfs_out;
+ }
+
+ file->private_data = ts;
+dbfs_out:
+ mutex_unlock(&ts->dbfs_mutex);
+ return retval;
+}
+
+static ssize_t elan_dbfs_read(struct file *file,
+ char __user *buffer, size_t count, loff_t *ppos)
+{
+ u8 rxbuf[256];
+ int ret;
+ struct elants_data *ts = file->private_data;
+ struct i2c_adapter *adap = ts->client->adapter;
+ struct i2c_msg msg;
+
+ if (count > 256)
+ return -EMSGSIZE;
+
+ msg.addr = ts->i2caddr;
+ msg.flags = ts->client->flags & I2C_M_TEN;
+ msg.flags |= I2C_M_RD;
+ msg.len = count;
+ msg.buf = rxbuf;
+
+ ret = i2c_transfer(adap, &msg, 1);
+ if (ret == 1)
+ if (copy_to_user(buffer, rxbuf, count))
+ return -EFAULT;
+
+ /* If everything went ok (i.e. 1 msg transmitted), return #bytes
+ transmitted, else error code. */
+ return (ret == 1) ? count : ret;
+}
+
+static ssize_t elan_dbfs_write(struct file *file,
+ const char __user *buffer, size_t count,
+ loff_t *ppos)
+{
+ int ret;
+ u8 txbuf[256];
+ struct elants_data *ts = file->private_data;
+ struct i2c_adapter *adap = ts->client->adapter;
+ struct i2c_msg msg;
+
+ if (count > 256)
+ return -EMSGSIZE;
+
+ if (copy_from_user(txbuf, buffer, count))
+ return -EFAULT;
+
+ msg.addr = ts->i2caddr;
+ msg.flags = ts->client->flags & I2C_M_TEN;
+ msg.len = count;
+ msg.buf = (char *)txbuf;
+
+ ret = i2c_transfer(adap, &msg, 1);
+ if (ret != 1)
+ dev_err(&ts->client->dev,
+ "i2c_master_send fail, ret=%d\n", ret);
+
+ /* If everything went ok (i.e. 1 msg transmitted), return #bytes
+ transmitted, else error code. */
+ return (ret == 1) ? count : ret;
+}
+
+static int elan_dbfs_release(struct inode *inode, struct file *file)
+{
+ struct elants_data *ts = file->private_data;
+
+ if (!ts)
+ return -ENODEV;
+
+ if (ts->dbfs_root) {
+ debugfs_remove_recursive(ts->dbfs_root);
+ mutex_destroy(&ts->dbfs_mutex);
+ }
+
+ return 0;
+}
+
+static const struct file_operations elan_debug_fops = {
+ .owner = THIS_MODULE,
+ .open = elan_dbfs_open,
+ .release = elan_dbfs_release,
+ .read = elan_dbfs_read,
+ .write = elan_dbfs_write,
+};
+
+static int elan_dbfs_init(struct elants_data *ts)
+{
+ /* Create a global debugfs root for all elan ts devices */
+ ts->dbfs_root = debugfs_create_dir(DEVICE_NAME, NULL);
+ if (ts->dbfs_root == ERR_PTR(-ENODEV))
+ ts->dbfs_root = NULL;
+
+ mutex_init(&ts->dbfs_mutex);
+
+ debugfs_create_file("elan-iap", 0777,
+ ts->dbfs_root, ts, &elan_debug_fops);
+
+ return 0;
+}
+
+/**
+ * elan_sw_reset - software reset.
+ *
+ * @client: our i2c device
+ * retval >0 means reset success,
+ * otherwise is fail.
+ *
+ */
+static int elan_sw_reset(struct i2c_client *client)
+{
+ int ret;
+ const u8 srst[4] = { 0x77, 0x77, 0x77, 0x77 };
+
+ dev_dbg(&client->dev, "Enter: %s\n", __func__);
+
+ ret = i2c_master_send(client, srst, sizeof(srst));
+
+ if (ret != sizeof(srst)) {
+ dev_err(&client->dev, "%s: i2c_master_send failed\n", __func__);
+ return -ENODEV;
+ }
+
+ /* Wait to send fastboot command */
+ elan_msleep(10);
+
+ return ret;
+}
+
+static int elan_boot(struct i2c_client *client, u16 addr, u8 type)
+{
+ int rc;
+ struct elants_data *ts = i2c_get_clientdata(client);
+ uint8_t command[2][4] = {
+ {0x4D, 0x61, 0x69, 0x6E},
+ {0x45, 0x49, 0x41, 0x50},
+ };
+
+ ts->i2caddr = addr;
+ rc = elan_set_data(client, command[(int32_t) type], 4);
+ if (rc < 0) {
+ if (type == E_BOOT_IAP)
+ dev_err(&client->dev, "Boot IAP fail, error=%d\n", rc);
+ else
+ dev_dbg(&client->dev,
+ "Boot normal fail, error=%d\n", rc);
+ ts->i2caddr = DEV_MASTER;
+ return -EINVAL;
+ }
+ dev_info(&client->dev, "Boot success -- 0x%x\n", addr);
+ ts->i2caddr = DEV_MASTER;
+ return 0;
+}
+
+/**
+ * __hello_packet_handler - hadle hello packet.
+ *
+ * @client: our i2c device
+ * @return:
+ * >0 means success,
+ * otherwise fail.
+ *
+ * Normal hello packet is {0x55, 0x55, 0x55, 0x55}
+ * recovery mode hello packet is {0x55, 0x55, 0x80, 0x80}
+ */
+static int __hello_packet_handler(struct i2c_client *client)
+{
+ int rc = 0;
+ uint8_t buf_recv[4] = { 0 };
+ struct elants_data *ts = i2c_get_clientdata(client);
+
+ dev_dbg(&client->dev, "Enter: %s\n", __func__);
+
+ rc = elan_get_data(client, buf_recv, 4);
+
+ /* Print buf_recv anyway */
+ dev_info(&client->dev, "rc=%d HelloPacket:%*phC\n",
+ rc, 4, buf_recv);
+
+ if (rc < 0) {
+ dev_err(&client->dev,
+ "%s: Try recovery because of no hello\n", __func__);
+ }
+
+ if (memcmp(buf_recv, hello_packet, 4)) {
+ if (!memcmp(buf_recv, recov_packet, 4)) {
+ dev_info(&client->dev,
+ "got mainflow recovery message\n");
+
+ ts->iap_mode = IAP_MODE_ENABLE;
+ set_bit(LOCK_FW_UPDATE, &ts->flags);
+ }
+
+ return -ENODEV;
+ }
+
+ ts->i2caddr = DEV_MASTER;
+
+ return rc;
+}
+
+/**
+ * Firmware version is for something big movement.
+ * ex: CodeBase update.
+ */
+static int __fw_packet_handler(struct i2c_client *client)
+{
+ struct elants_data *ts = i2c_get_clientdata(client);
+ int rc, tries = 3;
+ const uint8_t cmd[] = { CMD_HEADER_READ,
+ E_ELAN_INFO_FW_VER, 0x00, 0x01
+ };
+ uint8_t buf_recv[4] = { 0x0 };
+
+ dev_dbg(&client->dev, "Enter: %s\n", __func__);
+
+ /* Command not support in IAP recovery mode */
+ if (test_bit(LOCK_FW_UPDATE, &ts->flags))
+ return 0;
+retry:
+ rc = elan_i2c_read_block(client, (u8 *) cmd, buf_recv, 4);
+ if (rc < 0)
+ elan_dbg(client,
+ "read fw version rc=%d, buf=%*phC\n", rc, 4, buf_recv);
+
+ if (buf_recv[0] == CMD_HEADER_RESP) {
+ ts->_fw_version[0] = ((buf_recv[1] & 0x0f) << 4) |
+ ((buf_recv[2] & 0xf0) >> 4);
+ ts->_fw_version[1] = ((buf_recv[2] & 0x0f) << 4) |
+ ((buf_recv[3] & 0xf0) >> 4);
+
+ if (ts->fw_version == 0x00 || ts->fw_version == 0xFF) {
+ dev_err(&client->dev,
+ "\n\nFW version is empty, "
+ "suggest IAP ELAN chip\n\n");
+ return -EINVAL;
+ }
+ } else {
+ elan_dbg(client, "read fw retry tries=%d\n", tries);
+ if (tries > 0) {
+ tries--;
+ goto retry;
+ }
+
+ ts->fw_version = 0xffff;
+ dev_err(&client->dev,
+ "\n\nFW version is empty, "
+ "suggest IAP ELAN chip\n\n");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+/**
+ * Test version is for something minor change.
+ * ex: parameters, coordination
+ */
+static int __test_packet_handler(struct i2c_client *client)
+{
+ struct elants_data *ts = i2c_get_clientdata(client);
+ int rc, tries = 3;
+ const uint8_t cmd[] = { CMD_HEADER_READ,
+ E_ELAN_INFO_TEST_VER, 0x00, 0x01
+ };
+ uint8_t buf_recv[4] = { 0x0 };
+
+ dev_dbg(&client->dev, "Enter: %s\n", __func__);
+
+ /* Command not support in IAP recovery mode */
+ if (test_bit(LOCK_FW_UPDATE, &ts->flags))
+ return 0;
+retry:
+ rc = elan_i2c_read_block(client, (u8 *) cmd, buf_recv, 4);
+ if (rc < 0) {
+ elan_dbg(client, "read test version error rc=%d, buf=%*phC\n",
+ rc, 4, buf_recv);
+ return rc;
+ }
+
+ if (buf_recv[0] == CMD_HEADER_RESP) {
+ ts->_test_version[0] = ((buf_recv[1] & 0x0f) << 4) |
+ ((buf_recv[2] & 0xf0) >> 4);
+ ts->_test_version[1] = ((buf_recv[2] & 0x0f) << 4) |
+ ((buf_recv[3] & 0xf0) >> 4);
+ } else {
+ elan_dbg(client, "read fw retry tries=%d\n", tries);
+ if (tries > 0) {
+ tries--;
+ goto retry;
+ }
+ ts->test_version = 0xffff;
+ dev_err(&client->dev, "Get test version fail\n");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int __tp_info_handler(struct i2c_client *client)
+{
+ struct i2c_msg msgs[2];
+ struct elants_data *ts = i2c_get_clientdata(client);
+ int rc;
+ uint8_t buf_recv[17] = { 0x0 };
+ const uint8_t get_resolution_cmd[] = {
+ CMD_HEADER_6B_READ, 0x00, 0x00, 0x00, 0x00, 0x00
+ };
+
+ dev_dbg(&client->dev, "Enter: %s\n", __func__);
+
+ /* Command not support in IAP recovery mode */
+ if (test_bit(LOCK_FW_UPDATE, &ts->flags))
+ return 0;
+
+ msgs[0].addr = client->addr;
+ msgs[0].flags = client->flags & I2C_M_TEN;
+ msgs[0].len = 6;
+ msgs[0].buf = (u8 *) get_resolution_cmd;
+
+ msgs[1].addr = client->addr;
+ msgs[1].flags = client->flags & I2C_M_TEN;
+ msgs[1].flags |= I2C_M_RD;
+ msgs[1].len = sizeof(buf_recv);
+ msgs[1].buf = buf_recv;
+
+ rc = i2c_transfer(client->adapter, msgs, 2);
+ if (rc < 0) {
+ elan_dbg(client, "tp_info error rc=%d\n", rc);
+ return rc;
+ }
+
+ if (buf_recv[0] != CMD_HEADER_6B_RESP)
+ return -EINVAL;
+
+ ts->rows = (buf_recv[2] + buf_recv[6] + buf_recv[10]);
+ ts->cols = (buf_recv[3] + buf_recv[7] + buf_recv[11]);
+
+ if (ts->rows < 2 || ts->cols < 2) {
+ dev_warn(&client->dev,
+ "Invalid resolution (%d, %d)\n", ts->rows, ts->cols);
+
+ ts->rows = -1;
+ ts->cols = -1;
+
+ return 0;
+ }
+
+ return 0;
+}
+
+/**
+* elan_touch_get_bc_ver - obtain bootcode data from device
+*
+* @client: the interface we are querying
+*
+* Send a bootcode version command and fill the results into the
+* elan device structures. Caller must hold the mutex
+*
+* Returns 0 or an error code
+*/
+static int __bc_packet_handler(struct i2c_client *client)
+{
+ struct elants_data *ts = i2c_get_clientdata(client);
+ const u8 get_bc_ver_cmd[] = { CMD_HEADER_READ,
+ E_ELAN_INFO_BC_VER, 0x00, 0x01
+ };
+ u8 buf_recv[4];
+ int rc;
+
+ dev_dbg(&client->dev, "Enter: %s\n", __func__);
+
+ /* Command not support in IAP recovery mode */
+ if (test_bit(LOCK_FW_UPDATE, &ts->flags))
+ return 0;
+
+ rc = elan_i2c_read_block(client, (u8 *) get_bc_ver_cmd,
+ buf_recv, sizeof(buf_recv));
+ if (rc < 0) {
+ dev_err(&client->dev,
+ "Read FW version error rc=%d, buf=%*phC\n", rc, 4,
+ buf_recv);
+ return rc;
+ }
+
+ ts->bc_version = (((buf_recv[1] & 0x0f) << 4) |
+ ((buf_recv[2] & 0xf0) >> 4));
+ ts->iap_version = (((buf_recv[2] & 0x0f) << 4) |
+ ((buf_recv[3] & 0xf0) >> 4));
+ return 0;
+}
+
+static int __elan_fastboot(struct i2c_client *client)
+{
+ int rc = 0;
+
+ dev_dbg(&client->dev, "Enter: %s\n", __func__);
+
+ rc = elan_boot(client, DEV_MASTER, E_BOOT_NORM);
+ if (rc < 0)
+ return -1;
+
+ /* Wait for Hello packets */
+ msleep(50);
+
+ return rc;
+}
+
+static int elan_open(struct input_dev *input)
+{
+ struct elants_data *ts = input_get_drvdata(input);
+
+ dev_dbg(&ts->client->dev, "Enter: %s\n", __func__);
+
+ return 0;
+}
+
+static void elan_close(struct input_dev *input)
+{
+ struct elants_data *ts = input_get_drvdata(input);
+
+ dev_dbg(&ts->client->dev, "Enter %s\n", __func__);
+
+ return;
+}
+
+/**
+* elan_touch_pull_frame - pull a frame from the fifo
+*
+* @ts: our elan touch device
+* @buf: data buffer
+*
+* Pulls a frame from the FIFO into the provided ehr and data buffer.
+* The data buffer must be at least CMD_RESP_LEN bytes long.
+*/
+static int elan_touch_pull_frame(struct elants_data *ts, u8 *buf)
+{
+ dev_dbg(&ts->client->dev, "Enter: %s\n", __func__);
+
+ WARN_ON(kfifo_out_locked(&ts->fifo, buf, CMD_RESP_LEN,
+ &ts->rx_kfifo_lock)
+ != CMD_RESP_LEN);
+ return CMD_RESP_LEN;
+}
+
+/**
+ * elan_touch_fifo_clean_old - Make room for new frames
+ *
+ * @ed: our elan touch device
+ * @room: space needed
+ *
+ * Empty old frames out of the FIFO until we can fit the new one into
+ * the other end.
+ */
+static void elan_touch_fifo_clean_old(struct elants_data *ts, int room)
+{
+ u8 buffer[CMD_RESP_LEN];
+
+ dev_dbg(&ts->client->dev, "Enter: %s\n", __func__);
+
+ while (kfifo_len(&ts->fifo) + room >= FIFO_SIZE)
+ elan_touch_pull_frame(ts, buffer);
+}
+
+/**
+ * elan_getrepoinfo - parse Multi-queue report header
+ *
+ * @client: our i2c device
+ * @buf: buffer data
+ *
+ * parsing report header and get data length.
+ *
+ */
+static int elan_getrepoinfo(struct i2c_client *client, uint8_t *buf)
+{
+ struct elants_data *ts = i2c_get_clientdata(client);
+ struct multi_queue_header *buff = (struct multi_queue_header *)buf;
+ struct multi_queue_header *mq = &ts->mq_header;
+ const u8 wait_packet[4] = { 0x64, 0x64, 0x64, 0x64 };
+ int times = 10, rc = 0;
+
+ dev_dbg(&client->dev, "Enter: %s\n", __func__);
+
+ switch (buf[FW_POS_HEADER]) {
+ case CMD_HEADER_HELLO:
+ if (!memcmp(buf, hello_packet, 4))
+ ts->wdt_reset++;
+ return RET_CMDRSP;
+ case CMD_HEADER_RESP:
+ case CMD_HEADER_REK:
+ /* Queue the data, using the fifo lock to serialize
+ * the multiple accesses to the FIFO
+ */
+ elan_dbg(client, "recv CMD_HEADER_RESP\n");
+
+ mutex_lock(&ts->fifo_mutex);
+ if (kfifo_len(&ts->fifo) + CMD_RESP_LEN >= FIFO_SIZE)
+ elan_touch_fifo_clean_old(ts, CMD_RESP_LEN);
+ /* Push the data */
+ kfifo_in_locked(&ts->fifo, buf,
+ CMD_RESP_LEN, &ts->rx_kfifo_lock);
+ mutex_unlock(&ts->fifo_mutex);
+
+ elan_dbg(client, "wake_up [%*phC]\n", 4, buf);
+ wake_up_interruptible(&ts->wait);
+ return RET_CMDRSP;
+ /* Buffer mode header */
+ case QUEUE_HEADER_NORMAL:
+ elan_dbg(client,
+ "report_count=%d report_len=%d\n",
+ buff->report_count, buff->report_length);
+
+ if (buff->report_count <= 3) {
+ mq->report_count = buff->report_count;
+ mq->report_length = buff->report_length;
+ ts->rx_size = mq->report_length;
+ ts->packet_size = mq->report_length / mq->report_count;
+ } else
+ return RET_FAIL;
+
+ break;
+ case QUEUE_HEADER_WAIT:
+ dev_info(&client->dev,
+ "QUEUE_HEADER_WAIT %x:%x:%x:%x\n",
+ buf[0], buf[1], buf[2], buf[3]);
+ /* BUGFIX: buff[0] might be wrong (0x63),
+ * check buff[1 ~ 3] is enough
+ */
+ if (!memcmp(buf + 1, &wait_packet[1], 3)) {
+ do {
+ udelay(30);
+ elan_get_data(client, (u8 *) buff, ts->rx_size);
+ } while ((buff->packet_id != QUEUE_HEADER_NORMAL) &&
+ (--times > 0));
+ if (times > 0)
+ rc = elan_getrepoinfo(client, (uint8_t *) buff);
+ else
+ return RET_FAIL;
+ elan_dbg(client,
+ "Detect Wait_Header:rx_size=%d, "
+ "report_count=%d report_len=%d\n",
+ ts->rx_size,
+ mq->report_count, mq->report_length);
+ } else
+ dev_err(&client->dev,
+ "ERROR!! wait header:%x:%x:%x:%x\n",
+ buf[0], buf[1], buf[2], buf[3]);
+ break;
+ /* Not buffer mode, it's single word mode */
+ case QUEUE_HEADER_SINGLE:
+ mq->report_count = 1;
+ mq->report_length = PACKET_SIZE;
+ ts->rx_size = mq->report_length;
+ return ts->rx_size;
+ default:
+ dev_err(&client->dev,
+ "unknown multi-queue command!! --%x %x:%x:%x--\n",
+ buf[0], buf[1], buf[2], buf[3]);
+ ts->mq_header_fail++;
+
+ /* If glitch causes frame error, drop all finger report */
+ ts->rx_size = MAX_PACKET_LEN;
+ elan_get_data(client, (u8 *) buff, ts->rx_size);
+ return RET_FAIL;
+ }
+
+ return ts->rx_size;
+}
+
+/**
+ * elan_touch_checksum - Add for checksum mechanism
+ *
+ * @client : our i2c device
+ * @buf : buffer data
+ *
+ * caculating checksum for make sure all data validity.
+ */
+static int elan_touch_checksum(struct i2c_client *client, u8 *buf)
+{
+ u8 i = 0, checksum = 0;
+ struct elants_data *ts = i2c_get_clientdata(client);
+
+ dev_dbg(&client->dev, "Enter: %s\n", __func__);
+
+ for (i = 0; i < FW_POS_CHECKSUM; i++)
+ checksum = checksum + buf[i];
+
+ ts->checksum_correct = checksum;
+
+ if (checksum != buf[FW_POS_CHECKSUM]) {
+ ts->checksum_fail++;
+ dev_err(&client->dev,
+ "elan touch checksum fail: %02x:%02x\n",
+ checksum, buf[FW_POS_CHECKSUM]);
+ return -EFAULT;
+ }
+
+ return 0;
+}
+
+/**
+ * elan_parse_fid - parse the 10 fid bits
+ *
+ * @data: the input bit stream
+ * @fid: an array of fid values
+ *
+ * Unpack the 10 bits into an array.
+ * FIXME: Review whether we can't just use << operators after making
+ * sure the bits are native endian ?
+ */
+static inline void elan_parse_fid(u8 *data, u8 *fid)
+{
+ fid[0] = (data[0] & 0x01);
+ fid[1] = (data[0] & 0x02);
+ fid[2] = (data[0] & 0x04);
+ fid[3] = (data[0] & 0x08);
+ fid[4] = (data[0] & 0x10);
+ fid[5] = (data[0] & 0x20);
+ fid[6] = (data[0] & 0x40);
+ fid[7] = (data[0] & 0x80);
+ fid[8] = (data[1] & 0x10);
+ fid[9] = (data[1] & 0x20);
+}
+
+/**
+ * elan_parse_wid - parse width data with length of 5-bit
+ *
+ * @data: the input bit stream
+ * @wid: an array of width level
+ *
+ * Unpack the 10 bits into an array.
+ */
+static inline void elan_parse_wid(u8 *data, u8 *wid)
+{
+ wid[0] = (data[0] & 0x1f);
+ wid[1] = (data[1] & 0x1f);
+ wid[2] = (data[2] & 0x1f);
+ wid[3] = (data[3] & 0x1f);
+ wid[4] = (data[4] & 0x1f);
+ wid[5] = (data[5] & 0x1f);
+ wid[6] = (data[6] & 0x1f);
+ wid[7] = (data[7] & 0x1f);
+ wid[8] = (data[8] & 0x1f);
+ wid[9] = (data[9] & 0x1f);
+
+ return;
+}
+
+/**
+ * elan_parse_pid - parse pressure data with length of 8-bit
+ *
+ * @data: the input bit stream
+ * @wid: an array of width level
+ *
+ * Unpack the 10 bits into an array.
+ */
+static inline void elan_parse_pid(u8 *data, u8 *pressure)
+{
+ pressure[0] = data[0];
+ pressure[1] = data[1];
+ pressure[2] = data[2];
+ pressure[3] = data[3];
+ pressure[4] = data[4];
+ pressure[5] = data[5];
+ pressure[6] = data[6];
+ pressure[7] = data[7];
+ pressure[8] = data[8];
+ pressure[9] = data[9];
+
+ return;
+}
+
+static inline int elan_parse_xy(uint8_t *data, uint16_t *x, uint16_t *y)
+{
+ *x = *y = 0;
+
+ *x = (data[0] & 0xf0);
+ *x <<= 4;
+ *x |= data[1];
+
+ *y = (data[0] & 0x0f);
+ *y <<= 8;
+ *y |= data[2];
+
+ return 0;
+}
+
+static inline int elan_lookup_wid(u8 data, u16 *w, u16 *h)
+{
+ static u16 pre_w = 0, pre_h = 0, cur_w, cur_h;
+ const u8 width_lookup_table[15][2] = {
+ {3, 2}, {3, 2}, {6, 3},
+ {6, 3}, {10, 3}, {15, 4},
+ {15, 5}, {15, 5}, {15, 5},
+ {15, 5}, {15, 5}, {15, 5},
+ {15, 5}, {15, 5}, {15, 5},
+ };
+
+ cur_w = width_lookup_table[data][0] * 17;
+ cur_h = width_lookup_table[data][1] * 17;
+
+ *w = (cur_w + pre_w) >> 1;
+ *h = (cur_h + pre_h) >> 1;
+
+ pre_w = cur_w;
+ pre_h = cur_h;
+
+ return 0;
+}
+
+static int elan_mt_compute_slot(struct mt_device *td)
+{
+ int i;
+ for (i = 0; i < td->maxcontacts; ++i) {
+ if (td->slots[i].contactid == td->curdata.contactid &&
+ td->slots[i].touch_state)
+ return i;
+ }
+ for (i = 0; i < td->maxcontacts; ++i) {
+ if (!td->slots[i].seen_in_this_frame &&
+ !td->slots[i].touch_state)
+ return i;
+ }
+ /* should not occurs. If this happens that means
+ * that the device sent more touches that it says
+ * in the report descriptor. It is ignored then. */
+ return -1;
+}
+
+/*
+* this function is called when a whole contact has been processed,
+* so that it can assign it to a slot and store the data there
+*/
+static void elan_mt_complete_slot(struct mt_device *td)
+{
+ td->curdata.seen_in_this_frame = true;
+ if (td->curvalid) {
+ int slotnum = elan_mt_compute_slot(td);
+
+ if (slotnum >= 0 && slotnum < td->maxcontacts)
+ td->slots[slotnum] = td->curdata;
+ }
+ td->num_received++;
+}
+
+/*
+* this function is called when a whole packet has been received and processed,
+* so that it can decide what to send to the input layer.
+*/
+static void elan_mt_emit_event(struct mt_device *td, struct input_dev *input)
+{
+ struct elants_data *ts = container_of(td, struct elants_data, td);
+ int i;
+
+ for (i = 0; i < td->maxcontacts; ++i) {
+ struct mt_slot *s = &(td->slots[i]);
+ if (!s->seen_in_this_frame)
+ s->touch_state = false;
+
+ input_mt_slot(input, i);
+ input_mt_report_slot_state(input, MT_TOOL_FINGER,
+ s->touch_state);
+ if (s->touch_state) {
+ /* this finger is on the screen */
+ int major = max(s->w, s->h), minor = min(s->w, s->h);
+
+ elan_dbg(ts->client, "i=%d x=%d y=%d p=%d w=%d h=%d.\n",
+ i, s->x, s->y, s->p, major, minor);
+
+ input_event(input, EV_ABS, ABS_MT_POSITION_X, s->x);
+ input_event(input, EV_ABS, ABS_MT_POSITION_Y, s->y);
+ input_event(input, EV_ABS, ABS_MT_PRESSURE, s->p);
+ input_event(input, EV_ABS, ABS_MT_TOUCH_MAJOR, major);
+ }
+ s->seen_in_this_frame = false;
+ }
+
+ input_mt_report_pointer_emulation(input, true);
+ input_sync(input);
+ td->num_received = 0;
+}
+
+/**
+ * elan_mt_event - process finger reports
+ * @ts: our touchscreen
+ * @finger_stat: number of fingers in packet
+ * @buf: received buffer
+ *
+ * Walk the received report and process the finger data, extracting
+ * and reporting co-ordinates. No locking is needed here as the workqueue
+ * does our threading for us.
+ */
+static int elan_mt_event(struct elants_data *ts, int finger_stat, u8 *buf)
+{
+ int i;
+ u8 fid[MAX_CONTACT_NUM] = { 0 };
+ u8 wid[MAX_CONTACT_NUM] = { 0 };
+ u8 pid[MAX_CONTACT_NUM] = { 0 };
+ u16 x = 0, y = 0, w = 0, h = 0;
+ struct i2c_client *client = ts->client;
+ struct mt_device *td = &ts->td;
+
+ dev_dbg(&client->dev, "Enter: %s\n", __func__);
+
+ /* Parsing Finger, Width, Pressure field */
+ elan_parse_fid(&buf[FW_POS_STATE], &fid[0]);
+ elan_parse_wid(&buf[FW_POS_WIDTH], &wid[0]);
+ elan_parse_pid(&buf[FW_POS_PRESSURE], &pid[0]);
+
+ /* number of fingers expects in this frame */
+ td->num_expected = finger_stat;
+ for (i = 0; i < td->maxcontacts; i++) {
+ if (finger_stat == 0)
+ break;
+
+ /* tracking id */
+ td->curdata.contactid = (fid[i] > 0) ? i + 1 : 0;
+
+ if (td->curdata.contactid == 0)
+ continue;
+
+ td->curdata.touch_state = true;
+
+ elan_parse_xy(&buf[3 + i * 3], &x, &y);
+ td->curdata.x = x;
+ td->curdata.y = y;
+ td->curdata.p = pid[i];
+
+ h = w = wid[i];
+ td->curdata.w = w;
+ td->curdata.h = h;
+
+ finger_stat--;
+
+ elan_mt_complete_slot(td);
+ }
+
+ if (td->num_received >= td->num_expected)
+ elan_mt_emit_event(td, ts->input);
+
+ return 1;
+}
+
+/**
+ * elan_report_data - report finger report to user space.
+ *
+ * @client : our i2c device
+ * @buf : raw data from TP device.
+ *
+ * - reporting finger data to user space.
+ *
+ */
+static void elan_report_data(struct i2c_client *client, uint8_t *buf)
+{
+ struct elants_data *ts = i2c_get_clientdata(client);
+
+ dev_dbg(&client->dev, "Enter: %s\n", __func__);
+
+ switch (buf[FW_POS_HEADER]) {
+ case REPORT_HEADER_10_FINGER:{
+ u8 finger_stat = buf[FW_POS_TOTAL] & 0x0f;
+ elan_dbg(client, "finger_stat == %d\n", finger_stat);
+ elan_dbg(client, "finger:%*phC\n", 10, buf);
+
+ /* Enter right process, reset int_status */
+ ts->packet_received++;
+
+ if (likely(finger_stat != 0)) {
+ ts->td.curvalid = true;
+ ts->touched_sync++;
+ } else {
+ ts->no_touched_sync++;
+ }
+ elan_mt_event(ts, finger_stat, buf);
+ }
+ break;
+ default:
+ ts->header_fail++;
+ dev_warn(&client->dev,
+ "%s: unknown packet type: %*phC\n", __func__, 10, buf);
+ break;
+ }
+
+ return;
+}
+
+static irqreturn_t elan_work_func(int irq, void *work)
+{
+ struct elants_data *ts = work;
+ struct i2c_client *client = ts->client;
+
+ uint8_t buf[MAX_PACKET_LEN] = { 0x0 };
+ u8 pos = 0, rc = 0;
+
+ dev_dbg(&client->dev, "Enter: %s\n", __func__);
+
+ ts->irq_received++;
+
+ if (test_bit(LOCK_FW_UPDATE, &ts->flags))
+ return IRQ_HANDLED;
+
+ mutex_lock(&ts->mutex);
+
+ set_bit(LOCK_FINGER_REPORT, &ts->flags);
+
+ /* - Read multi_queue header */
+ rc = elan_get_data(client, (u8 *) buf, ts->rx_size);
+ if (rc < 0)
+ goto fail;
+
+ /* - Get multi_queue header info */
+ rc = elan_getrepoinfo(ts->client, buf);
+ if (rc < 0 || rc == RET_CMDRSP)
+ goto fail;
+
+ /* - check if packet size is valid */
+ if ((ts->packet_size != PACKET_SIZE)) {
+ dev_err(&ts->client->dev, "%s: uncorrect packet size = %d\n",
+ __func__, ts->packet_size);
+ goto fail;
+ }
+
+ /* - Get finger report data */
+ rc = elan_get_data(client, (u8 *) buf, ts->rx_size);
+ if (rc < 0)
+ goto fail;
+
+ clear_bit(LOCK_FINGER_REPORT, &ts->flags);
+
+ mutex_unlock(&ts->mutex);
+
+ /* - parsing report and send out */
+ while (ts->mq_header.report_count--) {
+ if (elan_touch_checksum(ts->client, buf + pos) == 0)
+ elan_report_data(ts->client, buf + pos);
+ pos = pos + ts->packet_size;
+ udelay(10);
+ }
+
+ ts->rx_size = QUEUE_HEADER_SIZE;
+
+ return IRQ_HANDLED;
+
+fail:
+ clear_bit(LOCK_FINGER_REPORT, &ts->flags);
+ mutex_unlock(&ts->mutex);
+ ts->rx_size = QUEUE_HEADER_SIZE;
+
+ return IRQ_HANDLED;
+}
+
+static int remove_elants(struct i2c_client *client)
+{
+ int ret = 0;
+ struct elants_data *ts = i2c_get_clientdata(client);
+
+ if ((client->irq))
+ free_irq(client->irq, ts);
+
+ if (ts->input)
+ input_unregister_device(ts->input);
+
+ if (&ts->mutex)
+ mutex_destroy(&ts->mutex);
+ if (&ts->tr_mutex)
+ mutex_destroy(&ts->tr_mutex);
+ if (&ts->fifo_mutex)
+ mutex_destroy(&ts->fifo_mutex);
+
+ kfree(ts->td.slots);
+ kfifo_free(&ts->fifo);
+ kfree(ts);
+
+ return ret;
+}
+
+static int elan_input_dev_create(struct elants_data *ts)
+{
+ int err = 0;
+ struct i2c_client *client = ts->client;
+
+ if (ts->rows > 0 && ts->cols > 0) {
+ /* translate trace number to TSP resolution */
+ ts->x_max = ELAN_TS_RESOLUTION(ts->rows);
+ ts->y_max = ELAN_TS_RESOLUTION(ts->cols);
+ } else
+ dev_warn(&client->dev, "trace number error, %d,%d\n",
+ ts->rows, ts->cols);
+
+ /* Clear the existing one if it exists */
+ if (ts->input) {
+ input_unregister_device(ts->input);
+ ts->input = NULL;
+ }
+
+ ts->input = input_allocate_device();
+ if (ts->input == NULL) {
+ dev_err(&client->dev, "Failed to allocate input device\n");
+ return -ENOMEM;
+ }
+
+ ts->input->name = "Elan-Touchscreen";
+ ts->input->id.bustype = BUS_I2C;
+ ts->input->dev.parent = &ts->client->dev;
+ ts->input->open = elan_open;
+ ts->input->close = elan_close;
+
+ __set_bit(BTN_TOUCH, ts->input->keybit);
+ __set_bit(EV_ABS, ts->input->evbit);
+ __set_bit(EV_KEY, ts->input->evbit);
+
+ /*! - Single touch input params setup */
+ input_set_abs_params(ts->input, ABS_X, 0, ts->x_max, 0, 0);
+ input_set_abs_params(ts->input, ABS_Y, 0, ts->y_max, 0, 0);
+ input_set_abs_params(ts->input, ABS_PRESSURE, 0, 255, 0, 0);
+ input_abs_set_res(ts->input, ABS_X, X_PIXELS_PER_MM);
+ input_abs_set_res(ts->input, ABS_Y, Y_PIXELS_PER_MM);
+
+ ts->td.maxcontacts = MAX_CONTACT_NUM;
+ ts->td.mt_flags |= INPUT_MT_DIRECT;
+
+ /* Multitouch input params setup */
+ err =
+ input_mt_init_slots(ts->input, ts->td.maxcontacts, ts->td.mt_flags);
+ if (err) {
+ dev_err(&client->dev,
+ "allocate memory for MT slots failed, %d\n", err);
+ goto err_free_device;
+ }
+
+ input_set_abs_params(ts->input, ABS_MT_POSITION_X, 0, ts->x_max, 0, 0);
+ input_set_abs_params(ts->input, ABS_MT_POSITION_Y, 0, ts->y_max, 0, 0);
+ input_set_abs_params(ts->input, ABS_MT_TOUCH_MAJOR, 0, 255, 0, 0);
+ input_set_abs_params(ts->input, ABS_MT_PRESSURE, 0, 255, 0, 0);
+ input_abs_set_res(ts->input, ABS_MT_POSITION_X, X_PIXELS_PER_MM);
+ input_abs_set_res(ts->input, ABS_MT_POSITION_Y, Y_PIXELS_PER_MM);
+
+ input_set_drvdata(ts->input, ts);
+
+ ts->td.slots = kzalloc(MAX_CONTACT_NUM * sizeof(struct mt_slot),
+ GFP_KERNEL);
+ if (!ts->td.slots) {
+ dev_err(&client->dev, "cannot allocate multitouch slots\n");
+ goto err_free_device;
+ }
+
+ err = input_register_device(ts->input);
+ if (err) {
+ dev_err(&client->dev, "unable to register input device\n");
+ goto err_free_slot;
+ }
+
+ return 0;
+
+err_free_slot:
+ kfree(ts->td.slots);
+err_free_device:
+ input_free_device(ts->input);
+ ts->input = NULL;
+ return err;
+}
+
+/**
+ * elan_initialize - initialization process.
+ *
+ * @client: our i2c client
+ *
+ * set our TP up
+ * -# reset
+ * -# hello packet
+ * -# fw version
+ * -# test version
+ * -# TP info (resolution)
+ *
+ */
+static int elan_initialize(struct i2c_client *client)
+{
+ struct elants_data *ts = i2c_get_clientdata(client);
+ u8 buf[4] = { 0 };
+ int rc;
+
+ dev_dbg(&client->dev, "Enter: %s\n", __func__);
+
+ /* handle recovery packets */
+ rc = elan_get_data(client, buf, 3);
+ if (rc == 0) {
+ if (!memcmp(buf, recov_packet + 2, 2)) {
+ ts->iap_mode = IAP_MODE_ENABLE;
+ set_bit(LOCK_FW_UPDATE, &ts->flags);
+ dev_err(&client->dev, "Get recovery packet == %*phC\n",
+ 3, buf);
+
+ return -ENODEV;
+ }
+ }
+
+ rc = elan_sw_reset(client);
+ if (rc < 0) {
+ dev_err(&client->dev, "Software reset failed\n");
+ /* continue */
+ }
+
+ ts->rx_size = QUEUE_HEADER_SIZE;
+
+ rc = __elan_fastboot(client);
+ if (rc < 0)
+ dev_err(&client->dev, "fastboot failed, rc=%d\n", rc);
+
+ /*! - elan hello packet init */
+ rc = __hello_packet_handler(client);
+ if (rc < 0) {
+ dev_err(&client->dev, "hello packet error.\n");
+
+ return -ENODEV;
+ }
+
+ /* elan fw version */
+ rc = __fw_packet_handler(client);
+ if (rc < 0) {
+ dev_err(&client->dev, "firmware checking error rc=%d\n", rc);
+
+ if (rc == -EINVAL) {
+ set_bit(LOCK_FW_UPDATE, &ts->flags);
+ ts->iap_mode = IAP_MODE_ENABLE;
+ }
+ }
+
+ /* elan TP information */
+ rc = __tp_info_handler(client);
+ if (rc < 0) {
+ dev_err(&client->dev, "TP information checking error.\n");
+ /* Go through down */
+ }
+
+ /* elan test version */
+ rc = __test_packet_handler(client);
+ if (rc < 0) {
+ dev_err(&client->dev, "test version error\n");
+ /* Go through down */
+ }
+
+ /* Get TS BootCode version */
+ rc = __bc_packet_handler(client);
+ if (rc < 0) {
+ dev_err(&client->dev, "TP get BC version error.\n");
+ /* Go through down */
+ }
+
+ return 0;
+}
+
+/**
+ * elan_initialize_async - init touch device.
+ *
+ * @work: /INT work queue
+ *
+ * Perform real probe for our I2C device and if successful configure
+ * it up as an input device. If not then clean up and return an error
+ * code.
+ */
+
+static void elan_initialize_async(void *data, async_cookie_t cookie)
+{
+ struct elants_data *ts = data;
+ struct i2c_client *client = ts->client;
+ int err = 0;
+
+ mutex_lock(&ts->mutex);
+
+ err = elan_initialize(client);
+ if (err < 0)
+ dev_err(&client->dev, "probe failed! unbind device.\n");
+
+ err = elan_input_dev_create(ts);
+ if (err) {
+ dev_err(&client->dev, "%s crated failed, %d\n", __func__, err);
+ goto fail_un;
+ }
+
+ dev_info(&client->dev, "Elan Touchscreen Information:\n\r");
+ dev_info(&client->dev,
+ " Firmware Version: 0x%04x\n\r", ts->fw_version);
+ dev_info(&client->dev,
+ " Test Version: 0x%04x\n\r", ts->test_version);
+ dev_info(&client->dev, " BC Version: 0x%04x\n\r", ts->bc_version);
+ dev_info(&client->dev, " IAP Version: 0x%04x\n\r", ts->iap_version);
+ dev_info(&client->dev, " Trace Num: %d, %d\n", ts->rows, ts->cols);
+ dev_info(&client->dev, " Resolution X,Y: %d,%d\n",
+ ts->x_max, ts->y_max);
+
+ err = request_threaded_irq(client->irq, NULL,
+ elan_work_func,
+ IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
+ client->name, ts);
+ if (err) {
+ dev_err(&client->dev, "Failed to register interrupt\n");
+ goto fail_un;
+ }
+
+ mutex_unlock(&ts->mutex);
+
+ return;
+
+fail_un:
+ mutex_unlock(&ts->mutex);
+ remove_elants(client);
+ return;
+}
+
+/**
+ * elan_probe - probe for touchpad
+ *
+ * Perform setup and probe for our I2C device and if successful configure
+ * it up as an input device. If not then clean up and return an error
+ * code.
+ */
+static int elan_probe(struct i2c_client *client, const struct i2c_device_id *id)
+{
+ long err = -1;
+ struct elan_i2c_platform_data *pdata = NULL;
+ struct elants_data *ts =
+ kzalloc(sizeof(struct elants_data), GFP_KERNEL);
+
+ if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
+ dev_err(&client->dev,
+ "%s: i2c check functionality error\n", DEVICE_NAME);
+ return -ENODEV;
+ }
+
+ ts = kzalloc(sizeof(struct elants_data), GFP_KERNEL);
+ if (!ts)
+ return -ENOMEM;
+
+ mutex_init(&ts->mutex);
+ mutex_init(&ts->tr_mutex);
+ mutex_init(&ts->fifo_mutex);
+ init_waitqueue_head(&ts->wait);
+ spin_lock_init(&ts->rx_kfifo_lock);
+
+ err = elan_dbfs_init(ts);
+ if (err < 0) {
+ dev_err(&client->dev, "error create elan debugfs.\n");
+ goto fail_un;
+ } else
+ ts->fw_enabled = 1;
+
+ pdata = client->dev.platform_data;
+ if (!pdata) {
+ dev_err(&client->dev,
+ "%s No platform data provided\n", DEVICE_NAME);
+ }
+
+ /* set initial i2c address */
+ client->addr = DEV_MASTER;
+ ts->i2caddr = client->addr;
+
+ ts->client = client;
+ i2c_set_clientdata(client, ts);
+
+ /* initial kfifo */
+ err = kfifo_alloc(&ts->fifo, FIFO_SIZE, GFP_KERNEL);
+ if (!kfifo_initialized(&ts->fifo)) {
+ dev_err(&client->dev, "%s error kfifo_alloc\n", __func__);
+ goto fail_un;
+ }
+
+ /* Says HELLO to touch device */
+ async_schedule(elan_initialize_async, ts);
+
+ device_init_wakeup(&client->dev, true);
+
+ return 0;
+
+fail_un:
+ remove_elants(client);
+ return err;
+}
+
+static int elan_remove(struct i2c_client *client)
+{
+ return remove_elants(client);
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int elan_suspend(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct elants_data *ts = i2c_get_clientdata(client);
+ const u8 set_sleep_cmd[] = { 0x54, 0x50, 0x00, 0x01 };
+ int rc = 0;
+
+ dev_dbg(&client->dev, "Enter: %s\n", __func__);
+
+ /* Command not support in IAP recovery mode */
+ if (test_bit(LOCK_FW_UPDATE, &ts->flags))
+ return 0;
+
+ mutex_lock(&ts->mutex);
+ rc = elan_set_data(client, set_sleep_cmd, sizeof(set_sleep_cmd));
+ if (rc < 0)
+ dev_err(&client->dev, "suspend command failed!\n");
+
+ if (device_may_wakeup(dev))
+ ts->irq_wake = (enable_irq_wake(client->irq) == 0);
+
+ disable_irq(client->irq);
+
+ mutex_unlock(&ts->mutex);
+
+ return 0;
+}
+
+static int elan_resume(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct elants_data *ts = i2c_get_clientdata(client);
+ const u8 set_active_cmd[] = { 0x54, 0x58, 0x00, 0x01 };
+ int rc = 0;
+
+ dev_dbg(&client->dev, "Enter: %s\n", __func__);
+
+ /* Command not support in IAP recovery mode */
+ if (test_bit(LOCK_FW_UPDATE, &ts->flags))
+ return 0;
+
+ if (device_may_wakeup(dev) && ts->irq_wake)
+ disable_irq_wake(client->irq);
+
+ mutex_lock(&ts->mutex);
+
+ rc = elan_set_data(client, set_active_cmd, sizeof(set_active_cmd));
+ if (rc < 0)
+ dev_err(&client->dev, "resume command failed!\n");
+
+ enable_irq(client->irq);
+
+ mutex_unlock(&ts->mutex);
+
+ return 0;
+}
+#endif
+
+static SIMPLE_DEV_PM_OPS(elan_pm_ops, elan_suspend, elan_resume);
+
+static const struct i2c_device_id elan_ts_id[] = {
+ {DEVICE_NAME, 0},
+ {}
+};
+
+MODULE_DEVICE_TABLE(i2c, elan_ts_id);
+
+static struct i2c_driver elan_ts_driver = {
+ .probe = elan_probe,
+ .remove = elan_remove,
+ .id_table = elan_ts_id,
+ .driver = {
+ .name = DEVICE_NAME,
+ .owner = THIS_MODULE,
+ .pm = &elan_pm_ops,
+ },
+};
+
+module_i2c_driver(elan_ts_driver);
+
+MODULE_VERSION(DEVICE_NAME);
+MODULE_DESCRIPTION("Elan I2c Touchscreen driver");
+MODULE_LICENSE("GPL");
--
1.7.9.5
^ permalink raw reply related
* [PATCH 2/2] Input: keypad-omap - Cleanup header file
From: Sachin Kamat @ 2013-12-27 12:03 UTC (permalink / raw)
To: linux-input; +Cc: dmitry.torokhov, dtor, sachin.kamat, patches
In-Reply-To: <1388145788-6253-1-git-send-email-sachin.kamat@linaro.org>
Commit 2203747c9771 ("ARM: omap: move platform_data definitions")
moved the file to the current location but forgot to remove the pointer
to its previous location. Clean it up. While at it also change the header
file protection macros appropriately.
Signed-off-by: Sachin Kamat <sachin.kamat@linaro.org>
---
include/linux/platform_data/keypad-omap.h | 6 ++----
1 file changed, 2 insertions(+), 4 deletions(-)
diff --git a/include/linux/platform_data/keypad-omap.h b/include/linux/platform_data/keypad-omap.h
index a6b21eddb212..c3a3abae98f0 100644
--- a/include/linux/platform_data/keypad-omap.h
+++ b/include/linux/platform_data/keypad-omap.h
@@ -1,14 +1,12 @@
/*
- * arch/arm/plat-omap/include/mach/keypad.h
- *
* Copyright (C) 2006 Komal Shah <komal_shah802003@yahoo.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
-#ifndef ASMARM_ARCH_KEYPAD_H
-#define ASMARM_ARCH_KEYPAD_H
+#ifndef __KEYPAD_OMAP_H
+#define __KEYPAD_OMAP_H
#ifndef CONFIG_ARCH_OMAP1
#warning Please update the board to use matrix-keypad driver
--
1.7.9.5
^ permalink raw reply related
* [PATCH 1/2] Input: keypad-ep93xx - Cleanup header file
From: Sachin Kamat @ 2013-12-27 12:03 UTC (permalink / raw)
To: linux-input; +Cc: dmitry.torokhov, dtor, sachin.kamat, patches
Commit a3b2924547a7 ("ARM: ep93xx: move platform_data definitions")
moved the file to the current location but forgot to remove the pointer
to its previous location. Clean it up. While at it also change the header
file protection macros appropriately.
Signed-off-by: Sachin Kamat <sachin.kamat@linaro.org>
---
include/linux/platform_data/keypad-ep93xx.h | 10 +++-------
1 file changed, 3 insertions(+), 7 deletions(-)
diff --git a/include/linux/platform_data/keypad-ep93xx.h b/include/linux/platform_data/keypad-ep93xx.h
index 1e2f4e97f428..adccee25b162 100644
--- a/include/linux/platform_data/keypad-ep93xx.h
+++ b/include/linux/platform_data/keypad-ep93xx.h
@@ -1,9 +1,5 @@
-/*
- * arch/arm/mach-ep93xx/include/mach/ep93xx_keypad.h
- */
-
-#ifndef __ASM_ARCH_EP93XX_KEYPAD_H
-#define __ASM_ARCH_EP93XX_KEYPAD_H
+#ifndef __KEYPAD_EP93XX_H
+#define __KEYPAD_EP93XX_H
struct matrix_keymap_data;
@@ -32,4 +28,4 @@ struct ep93xx_keypad_platform_data {
#define EP93XX_MATRIX_ROWS (8)
#define EP93XX_MATRIX_COLS (8)
-#endif /* __ASM_ARCH_EP93XX_KEYPAD_H */
+#endif /* __KEYPAD_EP93XX_H */
--
1.7.9.5
^ permalink raw reply related
* Re: [v2] input: Add new driver for GPIO beeper
From: Alexander Shiyan @ 2013-12-27 3:46 UTC (permalink / raw)
To: Andrew Lunn; +Cc: linux-input, Dmitry Torokhov
In-Reply-To: <20131227033351.GA8515@lunn.ch>
Hello.
> On Mon, Nov 25, 2013 at 02:03:07PM +0400, Alexander Shiyan wrote:
> > This patch adds a new driver for the beeper controlled via GPIO pin.
> > The driver does not depend on the architecture and is positioned as
> > a replacement for the specific drivers that are used for this function.
> >
> > Signed-off-by: Alexander Shiyan <shc_work@mail.ru>
>
> Hi Alexander
>
> I just tried this driver out on a board port i will soon be
> contributing. Works for me. Please add a
>
> Tested-by: Andrew Lunn <andrew@lunn.ch>
>
> Has the Input maintainer picked it up for the next merge window?
AFAIK, this driver is already merged in the "-next" kernel tree.
---
^ permalink raw reply
* Re: [v2] input: Add new driver for GPIO beeper
From: Andrew Lunn @ 2013-12-27 3:33 UTC (permalink / raw)
To: Alexander Shiyan
Cc: linux-input, devicetree, Dmitry Torokhov, Rob Herring, Pawel Moll,
Mark Rutland, Stephen Warren, Ian Campbell, Grant Likely
In-Reply-To: <1385373787-15668-1-git-send-email-shc_work@mail.ru>
On Mon, Nov 25, 2013 at 02:03:07PM +0400, Alexander Shiyan wrote:
> This patch adds a new driver for the beeper controlled via GPIO pin.
> The driver does not depend on the architecture and is positioned as
> a replacement for the specific drivers that are used for this function.
>
> Signed-off-by: Alexander Shiyan <shc_work@mail.ru>
Hi Alexander
I just tried this driver out on a board port i will soon be
contributing. Works for me. Please add a
Tested-by: Andrew Lunn <andrew@lunn.ch>
Has the Input maintainer picked it up for the next merge window?
Thanks
Andrew
>
> ---
> .../devicetree/bindings/input/gpio-beeper.txt | 13 +++
> drivers/input/misc/Kconfig | 9 ++
> drivers/input/misc/Makefile | 1 +
> drivers/input/misc/gpio-beeper.c | 127 +++++++++++++++++++++
> 4 files changed, 150 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/input/gpio-beeper.txt
> create mode 100644 drivers/input/misc/gpio-beeper.c
>
> diff --git a/Documentation/devicetree/bindings/input/gpio-beeper.txt b/Documentation/devicetree/bindings/input/gpio-beeper.txt
> new file mode 100644
> index 0000000..a5086e3
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/input/gpio-beeper.txt
> @@ -0,0 +1,13 @@
> +* GPIO beeper device tree bindings
> +
> +Register a beeper connected to GPIO pin.
> +
> +Required properties:
> +- compatible: Should be "gpio-beeper".
> +- gpios: From common gpio binding; gpio connection to beeper enable pin.
> +
> +Example:
> + beeper: beeper {
> + compatible = "gpio-beeper";
> + gpios = <&gpio3 23 GPIO_ACTIVE_HIGH>;
> + };
> diff --git a/drivers/input/misc/Kconfig b/drivers/input/misc/Kconfig
> index 5f4967d..4ffc397 100644
> --- a/drivers/input/misc/Kconfig
> +++ b/drivers/input/misc/Kconfig
> @@ -222,6 +222,15 @@ config INPUT_GP2A
> To compile this driver as a module, choose M here: the
> module will be called gp2ap002a00f.
>
> +config INPUT_GPIO_BEEPER
> + tristate "Generic GPIO Beeper support"
> + depends on OF_GPIO
> + help
> + Say Y here if you have a beeper connected to a GPIO pin.
> +
> + To compile this driver as a module, choose M here: the
> + module will be called gpio-beeper.
> +
> config INPUT_GPIO_TILT_POLLED
> tristate "Polled GPIO tilt switch"
> depends on GPIOLIB
> diff --git a/drivers/input/misc/Makefile b/drivers/input/misc/Makefile
> index 0ebfb6d..cda71fc 100644
> --- a/drivers/input/misc/Makefile
> +++ b/drivers/input/misc/Makefile
> @@ -27,6 +27,7 @@ obj-$(CONFIG_INPUT_DA9052_ONKEY) += da9052_onkey.o
> obj-$(CONFIG_INPUT_DA9055_ONKEY) += da9055_onkey.o
> obj-$(CONFIG_INPUT_DM355EVM) += dm355evm_keys.o
> obj-$(CONFIG_INPUT_GP2A) += gp2ap002a00f.o
> +obj-$(CONFIG_INPUT_GPIO_BEEPER) += gpio-beeper.o
> obj-$(CONFIG_INPUT_GPIO_TILT_POLLED) += gpio_tilt_polled.o
> obj-$(CONFIG_HP_SDC_RTC) += hp_sdc_rtc.o
> obj-$(CONFIG_INPUT_IMS_PCU) += ims-pcu.o
> diff --git a/drivers/input/misc/gpio-beeper.c b/drivers/input/misc/gpio-beeper.c
> new file mode 100644
> index 0000000..cc81e6e
> --- /dev/null
> +++ b/drivers/input/misc/gpio-beeper.c
> @@ -0,0 +1,127 @@
> +/*
> + * Generic GPIO beeper driver
> + *
> + * Copyright (C) 2013 Alexander Shiyan <shc_work@mail.ru>
> + *
> + * 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.
> + */
> +
> +#include <linux/input.h>
> +#include <linux/module.h>
> +#include <linux/of_gpio.h>
> +#include <linux/workqueue.h>
> +#include <linux/platform_device.h>
> +
> +#define BEEPER_MODNAME "gpio-beeper"
> +
> +struct gpio_beeper {
> + struct work_struct work;
> + int gpio;
> + bool active_low;
> + bool beeping;
> +};
> +
> +static void gpio_beeper_toggle(struct gpio_beeper *beep, bool on)
> +{
> + gpio_set_value_cansleep(beep->gpio, on ^ beep->active_low);
> +}
> +
> +static void gpio_beeper_work(struct work_struct *work)
> +{
> + struct gpio_beeper *beep = container_of(work, struct gpio_beeper, work);
> +
> + gpio_beeper_toggle(beep, beep->beeping);
> +}
> +
> +static int gpio_beeper_event(struct input_dev *dev, unsigned int type,
> + unsigned int code, int value)
> +{
> + struct gpio_beeper *beep = input_get_drvdata(dev);
> +
> + if (type != EV_SND || code != SND_BELL)
> + return -ENOTSUPP;
> +
> + if (value < 0)
> + return -EINVAL;
> +
> + beep->beeping = value;
> + /* Schedule work to actually turn the beeper on or off */
> + schedule_work(&beep->work);
> +
> + return 0;
> +}
> +
> +static void gpio_beeper_close(struct input_dev *input)
> +{
> + struct gpio_beeper *beep = input_get_drvdata(input);
> +
> + cancel_work_sync(&beep->work);
> + gpio_beeper_toggle(beep, false);
> +}
> +
> +static int gpio_beeper_probe(struct platform_device *pdev)
> +{
> + struct gpio_beeper *beep;
> + enum of_gpio_flags flags;
> + struct input_dev *input;
> + unsigned long gflags;
> + int err;
> +
> + beep = devm_kzalloc(&pdev->dev, sizeof(*beep), GFP_KERNEL);
> + if (!beep)
> + return -ENOMEM;
> +
> + beep->gpio = of_get_gpio_flags(pdev->dev.of_node, 0, &flags);
> + if (!gpio_is_valid(beep->gpio))
> + return beep->gpio;
> +
> + input = devm_input_allocate_device(&pdev->dev);
> + if (!input)
> + return -ENOMEM;
> +
> + INIT_WORK(&beep->work, gpio_beeper_work);
> +
> + input->name = pdev->name;
> + input->id.bustype = BUS_HOST;
> + input->id.vendor = 0x0001;
> + input->id.product = 0x0001;
> + input->id.version = 0x0100;
> + input->close = gpio_beeper_close;
> + input->event = gpio_beeper_event;
> +
> + input_set_capability(input, EV_SND, SND_BELL);
> +
> + beep->active_low = flags & OF_GPIO_ACTIVE_LOW;
> + gflags = beep->active_low ? GPIOF_OUT_INIT_HIGH : GPIOF_OUT_INIT_LOW;
> +
> + err = devm_gpio_request_one(&pdev->dev, beep->gpio, gflags, pdev->name);
> + if (err)
> + return err;
> +
> + input_set_drvdata(input, beep);
> +
> + return input_register_device(input);
> +}
> +
> +static struct of_device_id gpio_beeper_of_match[] = {
> + { .compatible = BEEPER_MODNAME, },
> + { }
> +};
> +MODULE_DEVICE_TABLE(of, gpio_beeper_of_match);
> +
> +static struct platform_driver gpio_beeper_platform_driver = {
> + .driver = {
> + .name = BEEPER_MODNAME,
> + .owner = THIS_MODULE,
> + .of_match_table = gpio_beeper_of_match,
> + },
> + .probe = gpio_beeper_probe,
> +};
> +module_platform_driver(gpio_beeper_platform_driver);
> +
> +MODULE_LICENSE("GPL");
> +MODULE_AUTHOR("Alexander Shiyan <shc_work@mail.ru>");
> +MODULE_DESCRIPTION("Generic GPIO beeper driver");
^ permalink raw reply
* Re: kernel panic on gpio-keys
From: Dmitry Torokhov @ 2013-12-27 1:56 UTC (permalink / raw)
To: Paul Cercueil; +Cc: linux-input, Henrik Rydberg
In-Reply-To: <52B06B0E.20501@gmail.com>
On Tue, Dec 17, 2013 at 04:17:34PM +0100, Paul Cercueil wrote:
> On 14/12/2013 10:39, Dmitry Torokhov wrote:
> >On Wed, Dec 11, 2013 at 08:17:29PM +0100, Paul Cercueil wrote:
> >>Hi there,
> >>
> >>I am trying to use the gpio-keys driver to inject joystick events.
> >>There seems to be some basic support of it, looking at <linux/gpio_keys.h>.
> >>
> >>However, registering the following will trigger a kernel panic in
> >>the kernel:
> >>
> >>static struct gpio_keys_button my_buttons[] {
> >> {
> >> .gpio = GPIO_FOO,
> >> .type = EV_ABS,
> >> .code = ABS_HAT0X,
> >> .value = 1,
> >> },
> >>};
> >>
> >>(tested on kernel 3.12).
> >>
> >>I don't know well the input subsystem, so I have no idea of what is
> >>going wrong. Could anybody try to at least reproduce the issue?
> >
> >It woudl be helpful if you poster the stack trace from panic so we'd
> >have an idea where the fault happens.
> >
> >Thanks.
> >
>
> Here is the crash log I get: http://pastebin.com/FzTTGxsR
> (I did put it on pastebin because it's huge, 200+ lines).
>
> The first OOPS happen as soon as the GPIO button is pressed; the
> other ones seem to happen recursively. I included only a part of the
> log I get, as the OOPSes continue to flow until the watchdog kicks
> in.
Hmm, I have an idea: this driver is one of few that does not use
input_set_abs_info() and this does not allocate memory for absinfo data
that input core uses to handle absolute events.
Does the patch below work for you?
Thanks.
--
Dmitry
Input: allocate absinfo data when setting ABS capability
From: Dmitry Torokhov <dmitry.torokhov@gmail.com>
We need to make sure we allocate absinfo data when we are setting one of
EV_ABS/ABS_XXX capabilities, otherwise we may bomb when we try to emit this
event.
Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
---
drivers/input/input.c | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/drivers/input/input.c b/drivers/input/input.c
index 692435a..1c4c0db 100644
--- a/drivers/input/input.c
+++ b/drivers/input/input.c
@@ -1909,6 +1909,10 @@ void input_set_capability(struct input_dev *dev, unsigned int type, unsigned int
break;
case EV_ABS:
+ input_alloc_absinfo(dev);
+ if (!dev->absinfo)
+ return;
+
__set_bit(code, dev->absbit);
break;
^ permalink raw reply related
* Re: [PATCH v5] Input: Improve the performance of alps v5-protocol's touchpad
From: Dmitry Torokhov @ 2013-12-27 0:00 UTC (permalink / raw)
To: Yunkang Tang
Cc: cernekee, dturvene, linux-input, ndevos, jclift, yunkang.tang
In-Reply-To: <1387876717-3878-1-git-send-email-yunkang.tang@cn.alps.com>
On Tue, Dec 24, 2013 at 05:18:37PM +0800, Yunkang Tang wrote:
> Hi all,
>
> Here is the 5th patch of supporting ALPS v5 protocol's device.
>
> Change since v4:
> - Use temporary variables to store the result of fls() and ffs(). (Suggestion by Kevin)
> - Use PSMOUSE_* macro instead of raw hex directly. (Suggestion by Kevin)
> - Change V5 device's mask0 from 0xc8 to 0xd8 to protect the abnormal case. (Discuss with Dmitry)
>
> Self test with v5 device:
> - Checked on both 32-bit & 64-bit environment.
> - Cursor moves correctly.
> - 1Finger Tap/Drag works well.
> - 2Finger Tap works well.
> - 2Finger Scroll works well.
> - L/R Buttons do function
>
> Signed-off-by: Yunkang Tang <yunkang.tang@cn.alps.com>
Applied, thank you.
--
Dmitry
^ permalink raw reply
* Re: [PATCH] input synaptics-rmi4: Transport layer renaming.
From: Dmitry Torokhov @ 2013-12-26 23:43 UTC (permalink / raw)
To: Christopher Heiny
Cc: Linux Input, Andrew Duggan, Vincent Huang, Vivian Ly,
Daniel Rosenberg, Jean Delvare, Joerie de Gram, Linus Walleij,
Benjamin Tissoires
In-Reply-To: <1387315890-21681-1-git-send-email-cheiny@synaptics.com>
On Tue, Dec 17, 2013 at 01:31:30PM -0800, Christopher Heiny wrote:
> The current RMI4 driver uses the term "physical" for two different logical
> entities: the communications transport layer (I2C, SPI, and so on), and the
> actual RMI4 device that is being communicated with. Such usage makes the
> code harder to understand due to confusion as to just which elements are
> being referred to.
>
> This patch renames the transport layer elements in order to eliminate this
> confusion. Much of the renaming was accomplished by the following Bash script
>
> #!/bin/bash
> #
> # Update RMI4 driver transport layer naming.
> #
>
> files="rmi_bus.c rmi_bus.h rmi_driver.c rmi_driver.h rmi_f01.c rmi_i2c.c"
> cd drivers/input/rmi4
>
> for f in $files ; do
> echo $f
> sed -i.bak s/rmi_phys_device/rmi_transport_dev/g $f
> sed -i.bak s/rmi_phys_info/rmi_transport_info/g $f
> sed -i.bak "s/rmi_transport_dev \*phys/rmi_transport_dev \*xport/g" $f
> sed -i.bak "s/rmi_transport_dev \*rmi_phys/rmi_transport_dev \*xport/g" $f
> sed -i.bak "s/phys\([^i]\)/xport\1/g" $f
> sed -i.bak "s/->phys/->xport/g" $f
> sed -i.bak "s/register_physical_device/register_transport_device/g" $f
> sed -i.bak "s/physical_device_count/transport_device_count/g" $f
> done
>
> although some changes proved easier to simply do by hand, particularly in the
> comments. Changes are confined strictly to the renaming, to keep the patch
> relatively simple.
>
> Signed-off-by: Christopher Heiny <cheiny@synaptics.com>
> Cc: Dmitry Torokhov <dmitry.torokhov@gmail.com>
> Cc: Benjamin Tissoires <benjamin.tissoires@redhat.com>
Applied, thank you.
--
Dmitry
^ permalink raw reply
* Re: [PATCH] Input: gpio_keys: support wakeup system from freeze mode
From: Dmitry Torokhov @ 2013-12-26 23:35 UTC (permalink / raw)
To: Anson.Huang@freescale.com
Cc: sachin.kamat@linaro.org, linux-input@vger.kernel.org
In-Reply-To: <971c266b465e460bbea8b968d3e19cd4@BY2PR03MB315.namprd03.prod.outlook.com>
Hi Anson,
On Tue, Dec 24, 2013 at 02:29:54AM +0000, Anson.Huang@freescale.com
wrote:
> Hi, Dmitry
>
> See below:
>
> Best Regards. Anson huang 黄勇才 Freescale Semiconductor Shanghai
> 上海浦东新区亮景路192号A座2楼 201203 Tel:021-28937058
>
>
> >-----Original Message----- From: Dmitry Torokhov
> >[mailto:dmitry.torokhov@gmail.com] Sent: Tuesday, December 24, 2013
> >8:31 AM To: Huang Yongcai-B20788 Cc: sachin.kamat@linaro.org;
> >linux-input@vger.kernel.org Subject: Re: [PATCH] Input: gpio_keys:
> >support wakeup system from freeze mode
> >
> >Hi Anson,
> >
> >On Mon, Dec 23, 2013 at 01:21:20PM -0500, Anson Huang wrote:
> >> For "freeze" mode of suspend, cpu will go into idle and those
> >> wakeup sources' irq should NOT be disabled during devices suspend,
> >> so we need to add IRQF_NO_SUSPEND flag for those wakeup sources.
> >>
> >> Steps to test this patch:
> >>
> >> 1. echo freeze > /sys/power/state; 2. press gpio key which has
> >> wakeup function, then system will resume.
> >
> >I do no think this is correct approach, otheriwse every driver that
> >can be a wakeup source would have to use IRQF_NO_SUSPEND flag. The
> >driver does use enable_irq_wake() in its suspend path, and platform
> >code should perform all necessary work for this IRQ to be usable as a
> >wakeup source.
> >
> >Thanks.
>
>
> From the suspend flow, we can see that kernel will disable all
> devices' irq unless its IRQF_NO_SUSPEND flag is set. For freeze mode,
> as cpu will be in idle, SOC is not in low power mode, that means
> freeze mode equals cpu in idle and devices in suspend. The wakeup
> source must come from GIC. Yes, for normal standby/mem mode's suspend,
> the enable_irq_wake will make everything done for waking up a system,
> in gic_set_wake routine, it will call extern irq chip's wakeup
> function, but for freeze mode, the wakeup process is same as normal
> wakeup from cpu idle, so the wakeup source must be not masked in gic.
>
> Take our i.MX6 SOC for example, there is GPC module which is used to
> wake up SOC from STOP mode(low power mode of SOC), normal standby/mem
> mode suspend, the wakeup source's enable_irq_wake will set GPC to
> monitor these irq source and wakeup system from STOP mode when there
> is an wakeup event. But for freeze mode, kernel's suspend flow will
> not finish the SOC's low level power management, the secondary CPUs
> are even not disabled, kernel just suspend the devices and put CPU
> into idle and waiting for wakeup even, in GIC's gic_set_wake routine,
> it only calls our GPC's imx_gpc_irq_set_wake, here comes the problem,
> as our SOC is not entering STOP mode, GPC has no use to set this
> wakeup source enabled. So either we should enable GIC's wakeup in GIC
> driver, or just set this flag to make kernel do NOT disable this
> wakeup source's irq when executing a freeze mode suspend.
>
> Otherwise, we need to modify the GIC driver?
Yes, I believe it is the task of platform code to make sure that
enable_irq_wake() enables irq wakeup properly, be it suspend or
hibernate.
Thanks.
--
Dmitry
--
To unsubscribe from this list: send the line "unsubscribe linux-input" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
^ permalink raw reply
* Re: [PATCH 1/5] input: Add new sun4i-ts driver for Allwinner sunxi SoC's rtp controller
From: Dmitry Torokhov @ 2013-12-26 23:30 UTC (permalink / raw)
To: Hans de Goede
Cc: linux-input-u79uwXL29TY76Z2rM5mHXA, LM Sensors, Maxime Ripard,
linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
linux-sunxi-/JYPxA39Uh5TLH3MbocFFw
In-Reply-To: <52BCAEA2.6040607-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
On Thu, Dec 26, 2013 at 11:33:06PM +0100, Hans de Goede wrote:
> On 12/26/2013 11:15 PM, Dmitry Torokhov wrote:
> >
> >Are we 1000% sure that the device is quiesced here and we will not get a
> >stray interrupt? Even if we are sure I'd still rather allocate input
> >device earlier, together with ts structure.
>
> I will change things to allocate the input device earlier. What about
> registering it, when is the best time to do that?
The common pattern for the most input drivers is to register it last,
but that usually matters when you are not using managed input devices
since it helps making error unwinding path simpler. I'd still register
it last just so it looks the same as the rest of the drivers.
--
Dmitry
^ permalink raw reply
* Re: [PATCH] ims-pcu: Add commands supported by the new version of the FW
From: Dmitry Torokhov @ 2013-12-26 22:53 UTC (permalink / raw)
To: Andrey Smirnov; +Cc: linux-input, linux-kernel
In-Reply-To: <1387653408-10529-1-git-send-email-andrew.smirnov@gmail.com>
Hi Andrey,
On Sat, Dec 21, 2013 at 11:16:48AM -0800, Andrey Smirnov wrote:
> New version of the PCU firmware supports two new commands:
> - IMS_PCU_CMD_OFN_SET_CONFIG which allows to write data to the
> registers of one finger navigation(OFN) chip present on the device
> - IMS_PCU_CMD_OFN_GET_CONFIG which allows to read data form the
> registers of said chip.
>
> This commit adds two helper functions to use those commands and sysfs
> attributes to use them. It also exposes some OFN configuration
> parameters via sysfs.
>
> Signed-off-by: Andrey Smirnov <andrew.smirnov@gmail.com>
> ---
> drivers/input/misc/ims-pcu.c | 274 +++++++++++++++++++++++++++++++++++++++++--
> 1 file changed, 263 insertions(+), 11 deletions(-)
>
> diff --git a/drivers/input/misc/ims-pcu.c b/drivers/input/misc/ims-pcu.c
> index e204f26..050c960 100644
> --- a/drivers/input/misc/ims-pcu.c
> +++ b/drivers/input/misc/ims-pcu.c
> @@ -68,6 +68,9 @@ struct ims_pcu {
> char bl_version[IMS_PCU_BL_VERSION_LEN];
> char reset_reason[IMS_PCU_BL_RESET_REASON_LEN];
> int update_firmware_status;
> + u8 device_id;
> +
> + u8 ofn_reg_addr;
>
> struct usb_interface *ctrl_intf;
>
> @@ -371,6 +374,8 @@ static void ims_pcu_destroy_gamepad(struct ims_pcu *pcu)
> #define IMS_PCU_CMD_GET_DEVICE_ID 0xae
> #define IMS_PCU_CMD_SPECIAL_INFO 0xb0
> #define IMS_PCU_CMD_BOOTLOADER 0xb1 /* Pass data to bootloader */
> +#define IMS_PCU_CMD_OFN_SET_CONFIG 0xb3
> +#define IMS_PCU_CMD_OFN_GET_CONFIG 0xb4
>
> /* PCU responses */
> #define IMS_PCU_RSP_STATUS 0xc0
> @@ -389,6 +394,9 @@ static void ims_pcu_destroy_gamepad(struct ims_pcu *pcu)
> #define IMS_PCU_RSP_GET_DEVICE_ID 0xce
> #define IMS_PCU_RSP_SPECIAL_INFO 0xd0
> #define IMS_PCU_RSP_BOOTLOADER 0xd1 /* Bootloader response */
> +#define IMS_PCU_RSP_OFN_SET_CONFIG 0xd2
> +#define IMS_PCU_RSP_OFN_GET_CONFIG 0xd3
> +
>
> #define IMS_PCU_RSP_EVNT_BUTTONS 0xe0 /* Unsolicited, button state */
> #define IMS_PCU_GAMEPAD_MASK 0x0001ff80UL /* Bits 7 through 16 */
> @@ -1216,6 +1224,226 @@ ims_pcu_update_firmware_status_show(struct device *dev,
> static DEVICE_ATTR(update_firmware_status, S_IRUGO,
> ims_pcu_update_firmware_status_show, NULL);
>
> +enum pcu_ofn_offsets {
> + OFN_REG_RESULT_LSB_OFFSET = 2,
> + OFN_REG_RESULT_MSB_OFFSET = 3,
> +};
> +
> +static ssize_t ims_pcu_ofn_reg_data_show(struct device *dev,
> + struct device_attribute *dattr,
> + char *buf)
> +{
> + struct usb_interface *intf = to_usb_interface(dev);
> + struct ims_pcu *pcu = usb_get_intfdata(intf);
> + int error;
> +
> + mutex_lock(&pcu->cmd_mutex);
> +
> + error = ims_pcu_execute_command(pcu, OFN_GET_CONFIG,
> + &pcu->ofn_reg_addr,
> + sizeof(pcu->ofn_reg_addr));
> + if (error >= 0) {
> + const s16 result = pcu->cmd_buf[OFN_REG_RESULT_MSB_OFFSET] << 8 |
> + pcu->cmd_buf[OFN_REG_RESULT_LSB_OFFSET];
const here seems overkill.
> + if (result < 0)
> + error = result;
> + else
> + error = scnprintf(buf, PAGE_SIZE, "%x\n", (u8)result);
Why cast to u8?
> + }
> +
> + mutex_unlock(&pcu->cmd_mutex);
> +
> + return error;
> +}
> +
> +static ssize_t ims_pcu_ofn_reg_data_store(struct device *dev,
> + struct device_attribute *dattr,
> + const char *buf, size_t count)
> +{
> + struct usb_interface *intf = to_usb_interface(dev);
> + struct ims_pcu *pcu = usb_get_intfdata(intf);
> + int error;
> + int value;
> + u8 buffer[2];
> +
> + error = kstrtoint(buf, 0, &value);
> + if (error)
> + return error;
> +
> + buffer[0] = pcu->ofn_reg_addr;
> + buffer[1] = (u8) value;
If you want u8 we have kstrtou8().
> +
> + mutex_lock(&pcu->cmd_mutex);
> +
> + error = ims_pcu_execute_command(pcu, OFN_SET_CONFIG,
> + &buffer, sizeof(buffer));
> +
> + mutex_unlock(&pcu->cmd_mutex);
> +
> + if (!error) {
> + const s16 result = pcu->cmd_buf[OFN_REG_RESULT_MSB_OFFSET] << 8 |
> + pcu->cmd_buf[OFN_REG_RESULT_LSB_OFFSET];
You should not be checking contents of pcu->cmd_buf after releasing
mutex as someone else could be accessing the same sysfs attribute and
stomping on your data.
> + error = result;
Does firmware guarantee that it would return errors that make sense to
Linux?
> + }
> +
> + return error ?: count;
> +}
> +
> +static DEVICE_ATTR(ofn_reg_data, S_IRUGO | S_IWUSR,
> + ims_pcu_ofn_reg_data_show, ims_pcu_ofn_reg_data_store);
> +
> +static ssize_t ims_pcu_ofn_reg_addr_show(struct device *dev,
> + struct device_attribute *dattr,
> + char *buf)
> +{
> + struct usb_interface *intf = to_usb_interface(dev);
> + struct ims_pcu *pcu = usb_get_intfdata(intf);
> + int error;
> +
> + mutex_lock(&pcu->cmd_mutex);
> + error = scnprintf(buf, PAGE_SIZE, "%x\n", pcu->ofn_reg_addr);
> + mutex_unlock(&pcu->cmd_mutex);
> +
> + return error;
> +}
> +
> +static ssize_t ims_pcu_ofn_reg_addr_store(struct device *dev,
> + struct device_attribute *dattr,
> + const char *buf, size_t count)
> +{
> + struct usb_interface *intf = to_usb_interface(dev);
> + struct ims_pcu *pcu = usb_get_intfdata(intf);
> + int error;
> + int value;
> +
> + error = kstrtoint(buf, 0, &value);
> + if (error)
> + return error;
kstrtou8().
> +
> + mutex_lock(&pcu->cmd_mutex);
> + pcu->ofn_reg_addr = (u8) value;
> + mutex_unlock(&pcu->cmd_mutex);
> +
> + return error ?: count;
> +}
> +
> +static DEVICE_ATTR(ofn_reg_addr, S_IRUGO | S_IWUSR,
> + ims_pcu_ofn_reg_addr_show, ims_pcu_ofn_reg_addr_store);
> +
> +static ssize_t ims_pcu_ofn_bit_show(u8 addr, u8 nr,
> + struct device *dev,
> + struct device_attribute *dattr,
> + char *buf)
> +{
> + struct usb_interface *intf = to_usb_interface(dev);
> + struct ims_pcu *pcu = usb_get_intfdata(intf);
> + int error;
> +
> + mutex_lock(&pcu->cmd_mutex);
> +
> + error = ims_pcu_execute_command(pcu, OFN_GET_CONFIG,
> + &addr, sizeof(addr));
> + if (error >= 0) {
> + const s16 result = pcu->cmd_buf[OFN_REG_RESULT_MSB_OFFSET] << 8 |
> + pcu->cmd_buf[OFN_REG_RESULT_LSB_OFFSET];
> + if (result < 0)
> + error = result;
> + else
> + error = scnprintf(buf, PAGE_SIZE, "%d\n",
> + !!(result & (1 << nr)));
> + }
> +
> + mutex_unlock(&pcu->cmd_mutex);
> + return error;
> +}
> +
> +static ssize_t ims_pcu_ofn_bit_store(u8 addr, u8 nr,
> + struct device *dev,
> + struct device_attribute *dattr,
> + const char *buf, size_t count)
> +{
> + struct usb_interface *intf = to_usb_interface(dev);
> + struct ims_pcu *pcu = usb_get_intfdata(intf);
> + int error;
> + int value;
> + u8 contents;
> +
> +
> + error = kstrtoint(buf, 0, &value);
> + if (error)
> + return error;
> +
> + if (value > 1)
> + return -EINVAL;
> +
> + mutex_lock(&pcu->cmd_mutex);
> +
> + error = ims_pcu_execute_command(pcu, OFN_GET_CONFIG,
> + &addr, sizeof(addr));
> + if (error < 0)
> + goto exit;
> +
> + {
Generally dislike artificial code blocks. Please declare all needed
variables upfront.
> + const s16 result = pcu->cmd_buf[OFN_REG_RESULT_MSB_OFFSET] << 8 |
> + pcu->cmd_buf[OFN_REG_RESULT_LSB_OFFSET];
> + if (result < 0) {
> + error = result;
> + goto exit;
> + }
> + contents = (u8) result;
> + }
> +
> + if (value)
> + contents |= 1 << nr;
> + else
> + contents &= ~(1 << nr);
> +
> + {
> + const u8 buffer[] = { addr, contents };
> + error = ims_pcu_execute_command(pcu, OFN_SET_CONFIG,
> + &buffer, sizeof(buffer));
> + }
> +
> +exit:
> + mutex_unlock(&pcu->cmd_mutex);
> +
> + if (!error) {
> + const s16 result = pcu->cmd_buf[OFN_REG_RESULT_MSB_OFFSET] << 8 |
> + pcu->cmd_buf[OFN_REG_RESULT_LSB_OFFSET];
> + error = result;
> + }
> +
> + return error ?: count;
> +}
> +
> +
> +#define IMS_PCU_BIT_ATTR(name, addr, nr) \
> + static ssize_t ims_pcu_##name##_show(struct device *dev, \
> + struct device_attribute *dattr, \
> + char *buf) \
> + { \
> + return ims_pcu_ofn_bit_show(addr, nr, dev, dattr, buf); \
> + } \
> + \
> + static ssize_t ims_pcu_##name##_store(struct device *dev, \
> + struct device_attribute *dattr, \
> + const char *buf, size_t count) \
> + { \
> + return ims_pcu_ofn_bit_store(addr, nr, dev, dattr, buf, count); \
> + } \
> + \
> + static DEVICE_ATTR(name, S_IRUGO | S_IWUSR, \
> + ims_pcu_##name##_show, ims_pcu_##name##_store)
> +
> +IMS_PCU_BIT_ATTR(ofn_engine_enable, 0x60, 7);
> +IMS_PCU_BIT_ATTR(ofn_speed_enable, 0x60, 6);
> +IMS_PCU_BIT_ATTR(ofn_assert_enable, 0x60, 5);
> +IMS_PCU_BIT_ATTR(ofn_xyquant_enable, 0x60, 4);
> +IMS_PCU_BIT_ATTR(ofn_xyscale_enable, 0x60, 1);
> +
> +IMS_PCU_BIT_ATTR(ofn_scale_x2, 0x63, 6);
> +IMS_PCU_BIT_ATTR(ofn_scale_y2, 0x63, 7);
> +
> static struct attribute *ims_pcu_attrs[] = {
> &ims_pcu_attr_part_number.dattr.attr,
> &ims_pcu_attr_serial_number.dattr.attr,
> @@ -1226,6 +1454,18 @@ static struct attribute *ims_pcu_attrs[] = {
> &dev_attr_reset_device.attr,
> &dev_attr_update_firmware.attr,
> &dev_attr_update_firmware_status.attr,
> +
> +#define IMS_PCU_ATTRS_OFN_START_OFFSET (9)
> +
> + &dev_attr_ofn_reg_data.attr,
> + &dev_attr_ofn_reg_addr.attr,
> + &dev_attr_ofn_engine_enable.attr,
> + &dev_attr_ofn_speed_enable.attr,
> + &dev_attr_ofn_assert_enable.attr,
> + &dev_attr_ofn_xyquant_enable.attr,
> + &dev_attr_ofn_xyscale_enable.attr,
> + &dev_attr_ofn_scale_x2.attr,
> + &dev_attr_ofn_scale_y2.attr,
> NULL
> };
>
> @@ -1244,8 +1484,21 @@ static umode_t ims_pcu_is_attr_visible(struct kobject *kobj,
> mode = 0;
> }
> } else {
> - if (attr == &dev_attr_update_firmware_status.attr)
> + if (attr == &dev_attr_update_firmware_status.attr) {
> mode = 0;
> + } else if (pcu->device_id == 5) {
> + /*
> + PCU-B devices, both GEN_1 and GEN_2(device_id == 5),
> + have no OFN sensor so exposing those attributes
> + for them does not make any sense
> + */
> + int i;
> + for (i = IMS_PCU_ATTRS_OFN_START_OFFSET; ims_pcu_attrs[i]; i++)
> + if (attr == ims_pcu_attrs[i]) {
> + mode = 0;
> + break;
> + }
> + }
> }
>
> return mode;
> @@ -1624,7 +1877,6 @@ static int ims_pcu_init_application_mode(struct ims_pcu *pcu)
> static atomic_t device_no = ATOMIC_INIT(0);
>
> const struct ims_pcu_device_info *info;
> - u8 device_id;
> int error;
>
> error = ims_pcu_get_device_info(pcu);
> @@ -1633,7 +1885,7 @@ static int ims_pcu_init_application_mode(struct ims_pcu *pcu)
> return error;
> }
>
> - error = ims_pcu_identify_type(pcu, &device_id);
> + error = ims_pcu_identify_type(pcu, &pcu->device_id);
> if (error) {
> dev_err(pcu->dev,
> "Failed to identify device, error: %d\n", error);
> @@ -1645,9 +1897,9 @@ static int ims_pcu_init_application_mode(struct ims_pcu *pcu)
> return 0;
> }
>
> - if (device_id >= ARRAY_SIZE(ims_pcu_device_info) ||
> - !ims_pcu_device_info[device_id].keymap) {
> - dev_err(pcu->dev, "Device ID %d is not valid\n", device_id);
> + if (pcu->device_id >= ARRAY_SIZE(ims_pcu_device_info) ||
> + !ims_pcu_device_info[pcu->device_id].keymap) {
> + dev_err(pcu->dev, "Device ID %d is not valid\n", pcu->device_id);
> /* Same as above, punt to userspace */
> return 0;
> }
> @@ -1659,7 +1911,7 @@ static int ims_pcu_init_application_mode(struct ims_pcu *pcu)
> if (error)
> return error;
>
> - info = &ims_pcu_device_info[device_id];
> + info = &ims_pcu_device_info[pcu->device_id];
> error = ims_pcu_setup_buttons(pcu, info->keymap, info->keymap_len);
> if (error)
> goto err_destroy_backlight;
> @@ -1783,14 +2035,14 @@ static int ims_pcu_probe(struct usb_interface *intf,
> if (error)
> goto err_stop_io;
>
> - error = sysfs_create_group(&intf->dev.kobj, &ims_pcu_attr_group);
> - if (error)
> - goto err_stop_io;
> -
> error = pcu->bootloader_mode ?
> ims_pcu_init_bootloader_mode(pcu) :
> ims_pcu_init_application_mode(pcu);
> if (error)
> + goto err_stop_io;
> +
> + error = sysfs_create_group(&intf->dev.kobj, &ims_pcu_attr_group);
> + if (error)
> goto err_remove_sysfs;
Why did you move sysfs group creation? Now one can not observe progress
of firmware update...
Thanks.
--
Dmitry
^ permalink raw reply
* Re: [PATCH v2] Add ff-memless-next module
From: Antonio Ospite @ 2013-12-26 22:40 UTC (permalink / raw)
To: Michal Malý
Cc: linux-input, linux-kernel, dmitry.torokhov, joe, elias.vds
In-Reply-To: <5668718.jm7F3C2ifr@geidi-prime>
On Thu, 26 Dec 2013 19:18:31 +0100
Michal Malý <madcatxster@prifuk.cz> wrote:
> This patch adds "ff-memless-next" module.
>
> Changelog:
> v2 - Remove unnecessary "else" branches
>
Michal, these annotations should go after the three dash line (---), and
the commit message must go here, before the three dashes.
> Tested-by: Elias Vanderstuyft <elias.vds@gmail.com>
> Signed-off-by: Michal Malý <madcatxster@prifuk.cz>
> ---
^^^
So put from here on the stuff you want to tell humans but you don't want
to show up in the git history.
git-am will ignore the text between the "---" and the actual "diff"
blocks, so this is the best place to put annotations and patch
commentary.
See git-format-patch and git-am man pages for the details.
Ciao,
Antonio
--
Antonio Ospite
http://ao2.it
A: Because it messes up the order in which people normally read text.
See http://en.wikipedia.org/wiki/Posting_style
Q: Why is top-posting such a bad thing?
--
To unsubscribe from this list: send the line "unsubscribe linux-input" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
^ permalink raw reply
* Re: [PATCH V2] input synaptics-rmi4: Bug fixes to ATTN GPIO handling.
From: Dmitry Torokhov @ 2013-12-26 22:37 UTC (permalink / raw)
To: Christopher Heiny
Cc: Linux Input, Andrew Duggan, Vincent Huang, Vivian Ly,
Daniel Rosenberg, Jean Delvare, Joerie de Gram, Linus Walleij,
Benjamin Tissoires
In-Reply-To: <1387853059-19099-1-git-send-email-cheiny@synaptics.com>
Hi Chris,
On Mon, Dec 23, 2013 at 06:44:19PM -0800, Christopher Heiny wrote:
> This patch fixes some bugs in handling of the RMI4 attention line GPIO.
>
> 1) in enable_sensor(), eliminate the complicated check on ATTN and just
> call process_interrupt_requests(). This will have minimal overhead if ATTN
> is not asserted, and clears the state of the RMI4 device in any case.
>
> 2) Correctly free the GPIO in rmi_driver_remove().
>
> 3) in rmi_driver_probe()
> - declare the name of the attention gpio (GPIO_LABEL)
> - use gpio_request_one() to get the gpio and export it.
> - simplify conditional gpio acquisition logic and combine with interrupt
> setup
>
> 4) use gpio_is_valid() instead of comparing to 0.
>
> Signed-off-by: Christopher Heiny <cheiny@synaptics.com>
> Cc: Dmitry Torokhov <dmitry.torokhov@gmail.com>
> Cc: Benjamin Tissoires <benjamin.tissoires@redhat.com>
>
> ---
>
> drivers/input/rmi4/rmi_driver.c | 43 ++++++++++++++++++++++-------------------
> 1 file changed, 23 insertions(+), 20 deletions(-)
>
> diff --git a/drivers/input/rmi4/rmi_driver.c b/drivers/input/rmi4/rmi_driver.c
> index a30c7d3..9b02358 100644
> --- a/drivers/input/rmi4/rmi_driver.c
> +++ b/drivers/input/rmi4/rmi_driver.c
> @@ -140,7 +140,6 @@ static int enable_sensor(struct rmi_device *rmi_dev)
> struct rmi_driver_data *data = dev_get_drvdata(&rmi_dev->dev);
> struct rmi_phys_device *rmi_phys;
> int retval = 0;
> - struct rmi_device_platform_data *pdata = to_rmi_platform_data(rmi_dev);
>
> if (data->enabled)
> return 0;
> @@ -169,11 +168,7 @@ static int enable_sensor(struct rmi_device *rmi_dev)
>
> data->enabled = true;
>
> - if (!pdata->level_triggered &&
> - gpio_get_value(pdata->attn_gpio) == pdata->attn_polarity)
> - retval = process_interrupt_requests(rmi_dev);
> -
> - return retval;
> + return process_interrupt_requests(rmi_dev);
> }
>
> static void rmi_free_function_list(struct rmi_device *rmi_dev)
> @@ -800,13 +795,20 @@ static SIMPLE_DEV_PM_OPS(rmi_driver_pm, rmi_driver_suspend, rmi_driver_resume);
> static int rmi_driver_remove(struct device *dev)
> {
> struct rmi_device *rmi_dev = to_rmi_device(dev);
> + const struct rmi_device_platform_data *pdata =
> + to_rmi_platform_data(rmi_dev);
>
> disable_sensor(rmi_dev);
> rmi_free_function_list(rmi_dev);
>
> + if (gpio_is_valid(pdata->attn_gpio))
> + gpio_free(pdata->attn_gpio);
It looks like you let driver registration to continue even if GPIO
request fails. You probably need to introduce a flag indicating whether
you successfully requested gpio or not. Or you can treat failure to
acquire gpio as fatal.
> +
> return 0;
> }
>
> +static const char GPIO_LABEL[] = "attn";
> +
> static int rmi_driver_probe(struct device *dev)
> {
> struct rmi_driver *rmi_driver;
> @@ -937,7 +939,7 @@ static int rmi_driver_probe(struct device *dev)
> mutex_init(&data->suspend_mutex);
> }
>
> - if (pdata->attn_gpio) {
> + if (gpio_is_valid(pdata->attn_gpio)) {
> data->irq = gpio_to_irq(pdata->attn_gpio);
> if (pdata->level_triggered) {
> data->irq_flags = IRQF_ONESHOT |
> @@ -948,24 +950,17 @@ static int rmi_driver_probe(struct device *dev)
> (pdata->attn_polarity == RMI_ATTN_ACTIVE_HIGH)
> ? IRQF_TRIGGER_RISING : IRQF_TRIGGER_FALLING;
> }
> - } else
> - data->poll_interval = ktime_set(0,
> - (pdata->poll_interval_ms ? pdata->poll_interval_ms :
> - DEFAULT_POLL_INTERVAL_MS) * 1000 * 1000);
> -
> - if (data->f01_container->dev.driver) {
> - /* Driver already bound, so enable ATTN now. */
> - enable_sensor(rmi_dev);
> - }
>
> - if (IS_ENABLED(CONFIG_RMI4_DEV) && pdata->attn_gpio) {
Do you really need to export gpio if you do not have RMI4_DEV option?
> - retval = gpio_export(pdata->attn_gpio, false);
> + retval = gpio_request_one(pdata->attn_gpio,
> + GPIOF_EXPORT | GPIOF_DIR_IN,
> + GPIO_LABEL);
> if (retval) {
> - dev_warn(dev, "WARNING: Failed to export ATTN gpio!\n");
> + dev_warn(dev, "WARNING: Failed to request ATTN gpio %d, code=%d.\n",
> + pdata->attn_gpio, retval);
> retval = 0;
> } else {
> retval = gpio_export_link(dev,
> - "attn", pdata->attn_gpio);
> + GPIO_LABEL, pdata->attn_gpio);
> if (retval) {
> dev_warn(dev,
> "WARNING: Failed to symlink ATTN gpio!\n");
> @@ -975,6 +970,14 @@ static int rmi_driver_probe(struct device *dev)
> pdata->attn_gpio);
> }
> }
> + } else
> + data->poll_interval = ktime_set(0,
> + (pdata->poll_interval_ms ? pdata->poll_interval_ms :
> + DEFAULT_POLL_INTERVAL_MS) * 1000 * 1000);
> +
> + if (data->f01_container->dev.driver) {
> + /* Driver already bound, so enable ATTN now. */
> + enable_sensor(rmi_dev);
> }
>
> return 0;
Thanks.
--
Dmitry
^ permalink raw reply
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox