* 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
* Re: [PATCH 1/5] input: Add new sun4i-ts driver for Allwinner sunxi SoC's rtp controller
From: Hans de Goede @ 2013-12-26 22:33 UTC (permalink / raw)
To: Dmitry Torokhov
Cc: linux-input-u79uwXL29TY76Z2rM5mHXA, LM Sensors, Maxime Ripard,
linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
linux-sunxi-/JYPxA39Uh5TLH3MbocFFw
In-Reply-To: <20131226221558.GA18562-WlK9ik9hQGAhIp7JRqBPierSzoNAToWh@public.gmane.org>
Hi Dimitri,
Thanks for the review. I've one or two small questions / remarks.
I'll do a v2 addressing your and others comments tomorrow.
On 12/26/2013 11:15 PM, Dmitry Torokhov wrote:
>> +static int sun4i_ts_register_input(struct sun4i_ts_data *ts,
>> + const char *name)
>> +{
>> + int ret;
>> + struct input_dev *input;
>> +
>> + input = devm_input_allocate_device(ts->dev);
>> + if (!input)
>> + return -ENOMEM;
>> +
>> + input->name = name;
>> + input->phys = "sun4i_ts/input0";
>> + input->id.bustype = BUS_HOST;
>> + input->id.vendor = 0x0001;
>> + input->id.product = 0x0001;
>> + input->id.version = 0x0100;
>> + input->dev.parent = ts->dev;
>> + input->evbit[0] = BIT(EV_SYN) | BIT(EV_KEY) | BIT(EV_ABS);
>> + set_bit(BTN_TOUCH, input->keybit);
>> + input_set_abs_params(input, ABS_X, 0, 4095, 0, 0);
>> + input_set_abs_params(input, ABS_Y, 0, 4095, 0, 0);
>> +
>> + ret = input_register_device(input);
>> + if (ret)
>> + return ret;
>> +
>> + ts->input = input;
>> + return 0;
>> +}
>> +
>> +static int sun4i_ts_remove(struct platform_device *pdev)
>> +{
>> + struct sun4i_ts_data *ts = platform_get_drvdata(pdev);
>> +
>> + /* Deactivate all IRQs */
>> + writel(0, ts->base + TP_INT_FIFOC);
>
> Should this be moved into close() method of input device?
Yes and no, given patch 2/5 we should leave the temp_data irq enabled in the
close method. But disabling the other irqs on close is a good idea.
>
>> + msleep(20);
>
> Why do you need msleep here? Are you trying to ensure that interrupts
> are not happening?
Yes.
> In that case synchronize_irq() is better option I think.
I did not know about synchronize_irq(), that definitely is a better
option.
>
>> +
>> + if (ts->input)
>> + input_unregister_device(ts->input);
>
> Since you are using managed input device you do not need to call
> input_unregister_device() explicitly.
Yes I was already wondering about that and wanted to check this for v2,
now I can skip the checking and simply remove this :)
>
>> +
>> + kfree(ts);
>> +
>> + return 0;
>> +}
>> +
>> +static int sun4i_ts_probe(struct platform_device *pdev)
>> +{
>> + struct sun4i_ts_data *ts;
>> + int ret = -ENOMEM;
>> +
>> + ts = kzalloc(sizeof(struct sun4i_ts_data), GFP_KERNEL);
>> + if (!ts)
>> + return -ENOMEM;
>
> I believe someone already mentioned devm_kzalloc()...
Yep, already fixed locally.
>
>> +
>> + platform_set_drvdata(pdev, ts);
>> +
>> + ts->dev = &pdev->dev;
>> + ts->base = devm_ioremap_resource(&pdev->dev,
>> + platform_get_resource(pdev, IORESOURCE_MEM, 0));
>> + if (IS_ERR(ts->base)) {
>> + ret = PTR_ERR(ts->base);
>> + goto error;
>> + }
>> +
>> + ret = devm_request_irq(&pdev->dev, platform_get_irq(pdev, 0),
>> + sun4i_ts_irq, 0, "sun4i-ts", ts);
>> + if (ret)
>> + goto error;
>> +
>
> 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?
>
>> + ret = sun4i_ts_register_input(ts, pdev->name);
>> + if (ret)
>> + goto error;
>> +
>> + /*
>> + * 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);
>> +
>> + /* Flush fifo, set trig level to 1, enable data and pen up irqs */
>> + writel(DATA_IRQ_EN(1) | FIFO_TRIG(1) | FIFO_FLUSH(1) | TP_UP_IRQ_EN(1),
>> + ts->base + TP_INT_FIFOC);
>> +
>> + /*
>> + * 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);
>
> Should all of some of this be moved into open() method for your input
> device?
The flushing of the fifo, and enabling of the input related irqs is probably
best done in the open method, the rest is also needed for the temp sensor
parts. I'll move the fifo-flush + irq enabling to the open method.
<snip>
Regards,
Hans
^ permalink raw reply
* Re: Re: [lm-sensors] [PATCH 2/5] input: sun4i-ts: Add support for temperature sensor
From: Dmitry Torokhov @ 2013-12-26 22:19 UTC (permalink / raw)
To: Hans de Goede
Cc: linux-sunxi-/JYPxA39Uh5TLH3MbocFFw, LM Sensors,
linux-input-u79uwXL29TY76Z2rM5mHXA, Maxime Ripard,
linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r
In-Reply-To: <52BAB963.30707-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
On Wed, Dec 25, 2013 at 11:54:27AM +0100, Hans de Goede wrote:
> On 12/25/2013 11:37 AM, Guenter Roeck wrote:
> >On Tue, Dec 24, 2013 at 11:24:04PM +0100, Hans de Goede wrote:
> >> struct sun4i_ts_data {
> >> struct device *dev;
> >> void __iomem *base;
> >> struct input_dev *input;
> >>+ struct device *hwmon;
> >>+ atomic_t temp_data;
> >
> >Unless I am missing something, this variable does not have to be an atomic.
> >Either it was written or it wasn't, but the order should not matter.
>
> That is true, but it does assume that 32 bit int accesses are atomic, in the
> sense that we can never have a case where one core has only written ie the
> lower 8 bits when the other reads the variable. I believe that is the
> case on ARM (the only architecture relevant here), but rather safe then
> sorry. So I plan to keep this as is until someone, who knows more about
> these things then me, tells me it is safe to turn it into a regular int.
I believe on all arches that we support writes to aligned double word
memory locations will not be split.
Thanks.
--
Dmitry
^ 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 22:15 UTC (permalink / raw)
To: Hans de Goede
Cc: linux-input, LM Sensors, Maxime Ripard, linux-arm-kernel,
linux-sunxi
In-Reply-To: <1387923847-1294-2-git-send-email-hdegoede@redhat.com>
Hi Hans,
On Tue, Dec 24, 2013 at 11:24:03PM +0100, Hans de Goede wrote:
> 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@redhat.com>
> ---
> drivers/input/touchscreen/Kconfig | 10 ++
> drivers/input/touchscreen/Makefile | 1 +
> drivers/input/touchscreen/sun4i-ts.c | 272 +++++++++++++++++++++++++++++++++++
> 3 files changed, 283 insertions(+)
> create mode 100644 drivers/input/touchscreen/sun4i-ts.c
>
> 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..b3f01c0
> --- /dev/null
> +++ b/drivers/input/touchscreen/sun4i-ts.c
> @@ -0,0 +1,272 @@
> +/*
> + * sun4i-ts.c Allwinner sunxi resistive touchscreen controller driver
No file names please.
> + *
> + * Copyright (C) 2013 - 2014 Hans de Goede <hdegoede@redhat.com>
> + *
> + * 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/delay.h>
> +#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 (1 << 18)
> +#define FIFO_OVERRUN_PENDING (1 << 17)
> +#define FIFO_DATA_PENDING (1 << 16)
> +#define TP_IDLE_FLG (1 << 2)
> +#define TP_UP_PENDING (1 << 1)
> +#define TP_DOWN_PENDING (1 << 0)
> +
> +struct sun4i_ts_data {
> + struct device *dev;
> + void __iomem *base;
> + struct input_dev *input;
bool?
> + int 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 = 0;
Please use braces either in none, or in both branches.
> + }
> +
> + if (reg_val & TP_UP_PENDING) {
> + ts->ignore_fifo_data = 1;
> + 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_register_input(struct sun4i_ts_data *ts,
> + const char *name)
> +{
> + int ret;
> + struct input_dev *input;
> +
> + input = devm_input_allocate_device(ts->dev);
> + if (!input)
> + return -ENOMEM;
> +
> + input->name = name;
> + input->phys = "sun4i_ts/input0";
> + input->id.bustype = BUS_HOST;
> + input->id.vendor = 0x0001;
> + input->id.product = 0x0001;
> + input->id.version = 0x0100;
> + input->dev.parent = ts->dev;
> + input->evbit[0] = BIT(EV_SYN) | BIT(EV_KEY) | BIT(EV_ABS);
> + set_bit(BTN_TOUCH, input->keybit);
> + input_set_abs_params(input, ABS_X, 0, 4095, 0, 0);
> + input_set_abs_params(input, ABS_Y, 0, 4095, 0, 0);
> +
> + ret = input_register_device(input);
> + if (ret)
> + return ret;
> +
> + ts->input = input;
> + return 0;
> +}
> +
> +static int sun4i_ts_remove(struct platform_device *pdev)
> +{
> + struct sun4i_ts_data *ts = platform_get_drvdata(pdev);
> +
> + /* Deactivate all IRQs */
> + writel(0, ts->base + TP_INT_FIFOC);
Should this be moved into close() method of input device?
> + msleep(20);
Why do you need msleep here? Are you trying to ensure that interrupts
are not happening? In that case synchronize_irq() is better option I
think.
> +
> + if (ts->input)
> + input_unregister_device(ts->input);
Since you are using managed input device you do not need to call
input_unregister_device() explicitly.
> +
> + kfree(ts);
> +
> + return 0;
> +}
> +
> +static int sun4i_ts_probe(struct platform_device *pdev)
> +{
> + struct sun4i_ts_data *ts;
> + int ret = -ENOMEM;
> +
> + ts = kzalloc(sizeof(struct sun4i_ts_data), GFP_KERNEL);
> + if (!ts)
> + return -ENOMEM;
I believe someone already mentioned devm_kzalloc()...
> +
> + platform_set_drvdata(pdev, ts);
> +
> + ts->dev = &pdev->dev;
> + ts->base = devm_ioremap_resource(&pdev->dev,
> + platform_get_resource(pdev, IORESOURCE_MEM, 0));
> + if (IS_ERR(ts->base)) {
> + ret = PTR_ERR(ts->base);
> + goto error;
> + }
> +
> + ret = devm_request_irq(&pdev->dev, platform_get_irq(pdev, 0),
> + sun4i_ts_irq, 0, "sun4i-ts", ts);
> + if (ret)
> + goto error;
> +
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.
> + ret = sun4i_ts_register_input(ts, pdev->name);
> + if (ret)
> + goto error;
> +
> + /*
> + * 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);
> +
> + /* Flush fifo, set trig level to 1, enable data and pen up irqs */
> + writel(DATA_IRQ_EN(1) | FIFO_TRIG(1) | FIFO_FLUSH(1) | TP_UP_IRQ_EN(1),
> + ts->base + TP_INT_FIFOC);
> +
> + /*
> + * 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);
Should all of some of this be moved into open() method for your input
device?
> +
> + return 0;
> +
> +error:
> + sun4i_ts_remove(pdev);
> + return ret;
> +}
> +
> +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,
> + .remove = sun4i_ts_remove,
> +};
> +
> +module_platform_driver(sun4i_ts_driver);
> +
> +MODULE_DESCRIPTION("Allwinner sun4i resistive touchscreen controller driver");
> +MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com>");
> +MODULE_LICENSE("GPL");
> --
> 1.8.4.2
>
Thanks.
--
Dmitry
^ permalink raw reply
* [PATCH v2] Add ff-memless-next module
From: Michal Malý @ 2013-12-26 18:18 UTC (permalink / raw)
To: linux-input; +Cc: linux-kernel, dmitry.torokhov, joe, elias.vds
This patch adds "ff-memless-next" module.
Changelog:
v2 - Remove unnecessary "else" branches
Tested-by: Elias Vanderstuyft <elias.vds@gmail.com>
Signed-off-by: Michal Malý <madcatxster@prifuk.cz>
---
From 351553a9951fe424b330a26256958b986c4de880 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Michal=20Mal=C3=BD?= <madcatxster@prifuk.cz>
Date: Thu, 26 Dec 2013 19:12:01 +0100
Subject: [PATCH] Add ff-memless-next module
---
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
* Re: [PATCH] Add ff-memless-next module
From: Joe Perches @ 2013-12-26 16:58 UTC (permalink / raw)
To: Michal Malý; +Cc: linux-input, linux-kernel, dmitry.torokhov, elias.vds
In-Reply-To: <4629786.9msWpURVJS@geidi-prime>
On Thu, 2013-12-26 at 17:33 +0100, Michal Malý wrote:
> This patch adds "ff-memless-next" module. This module is based on the current
> "ff-memless" module but it has considerably extended capabilities. These include:
[]
> Why is this written as a new module and not a patch for "ff-memless"? This
> module uses a different API for the HW-specific driver; this was a necessary
> step to make use of the module's additional features. All 25 drivers that
> currently use "ff-memless" would have to be updated. "ff-memless-next" currently
> does not support FF_RUMBLE which is needed to support various joypads.
Thanks for that explanation.
[]
> +static unsigned long mlnx_get_envelope_update_time(const struct mlnx_effect *mlnxeff,
> + const unsigned long update_rate_jiffies)
> +{
> + /* 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 */
> + else {
> + fade_next = mlnxeff->updated_at + update_rate_jiffies;
> +
There's no need for an else and extra indentation here
if (foo)
return;
else { /* unnecessary else */
bar...
}
please just use
if (foo)
return;
bar...
> + /* Schedule update when the effect stops */
> + if (time_after(fade_next, mlnxeff->stop_at))
> + return mlnxeff->stop_at;
> + /* Schedule update at the next checkpoint */
> + else
> + return fade_next;
> + }
> + }
^ permalink raw reply
* [PATCH] Add ff-memless-next module
From: Michal Malý @ 2013-12-26 16:33 UTC (permalink / raw)
To: linux-input; +Cc: linux-kernel, dmitry.torokhov, joe, elias.vds
This patch adds "ff-memless-next" module. This module is based on the current
"ff-memless" module but it has considerably extended capabilities. These include:
- Support for periodic and conditional effects
- Support for ramp effect
- Variable update time
Why is this written as a new module and not a patch for "ff-memless"? This
module uses a different API for the HW-specific driver; this was a necessary
step to make use of the module's additional features. All 25 drivers that
currently use "ff-memless" would have to be updated. "ff-memless-next" currently
does not support FF_RUMBLE which is needed to support various joypads. My
intention is to:
1) Port hid-lg4ff driver for Logitech gaming wheels to "ff-memless-next" API to
test the module with real hardware.
2) Add support for FF_RUMBLE and information reporting about effect's status
changes identical to that in iforce driver.
3) Gradually port other drivers to "ff-memless-next".
4) Fully replace "ff-memless" with "ff-memless-next".
Because of the scope of the changes I am concerned about breaking functionality
of devices that are currently working perfectly fine with "ff-memless" which is
why I'd like to make the move towards "ff-memless-next" a gradual process.
Tested-by: Elias Vanderstuyft <elias.vds@gmail.com>
Signed-off-by: Michal Malý <madcatxster@prifuk.cz>
---
From 491e246b634d6d8ffcaecdaebace5e38efc1cbd3 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Michal=20Mal=C3=BD?= <madcatxster@prifuk.cz>
Date: Thu, 26 Dec 2013 15:16:23 +0100
Subject: [PATCH] Add ff-memless-next
---
Documentation/input/ff-memless-next.txt | 149 ++++++
drivers/input/Kconfig | 12 +
drivers/input/Makefile | 2 +
drivers/input/ff-memless-next.c | 796 ++++++++++++++++++++++++++++++++
include/linux/input/ff-memless-next.h | 31 ++
5 files changed, 990 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..302f273
--- /dev/null
+++ b/drivers/input/ff-memless-next.c
@@ -0,0 +1,796 @@
+/*
+ * 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;
+ else
+ 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 */
+ else {
+ 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 */
+ else
+ 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;
+ else /* 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;
+ else
+ 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
^ permalink raw reply related
* Re: [PATCH 2/5] input: sun4i-ts: Add support for temperature sensor
From: Thomas Petazzoni @ 2013-12-26 8:39 UTC (permalink / raw)
To: Hans de Goede
Cc: Dmitry Torokhov, LM Sensors, linux-sunxi-/JYPxA39Uh5TLH3MbocFFw,
linux-input-u79uwXL29TY76Z2rM5mHXA, Maxime Ripard,
linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r
In-Reply-To: <1387923847-1294-3-git-send-email-hdegoede-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
Dear Hans de Goede,
On Tue, 24 Dec 2013 23:24:04 +0100, Hans de Goede wrote:
> + of_property_read_u32(np, "ts-attached", &ts_attached);
ts-attached should be a boolean property, and therefore you should use
of_property_read_bool() here.
And in your Device Tree, you should use:
ts-attached;
when a touchscreen is attached. And nothing when no touchscreen is
attached.
Note that this patch is also changing the DT binding, so it should
update the DT binding documentation created in the first patch.
Thanks!
Thomas
--
Thomas Petazzoni, CTO, Free Electrons
Embedded Linux, Kernel and Android engineering
http://free-electrons.com
^ permalink raw reply
* Re: [PATCH 1/5] input: Add new sun4i-ts driver for Allwinner sunxi SoC's rtp controller
From: Thomas Petazzoni @ 2013-12-26 8:37 UTC (permalink / raw)
To: Hans de Goede
Cc: Dmitry Torokhov, LM Sensors, linux-sunxi-/JYPxA39Uh5TLH3MbocFFw,
linux-input-u79uwXL29TY76Z2rM5mHXA, Maxime Ripard,
linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r
In-Reply-To: <1387923847-1294-2-git-send-email-hdegoede-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
Dear Hans de Goede,
On Tue, 24 Dec 2013 23:24:03 +0100, Hans de Goede wrote:
> +/* TP_INT_FIFOS irq and fifo status bits */
> +#define TEMP_DATA_PENDING (1 << 18)
> +#define FIFO_OVERRUN_PENDING (1 << 17)
> +#define FIFO_DATA_PENDING (1 << 16)
> +#define TP_IDLE_FLG (1 << 2)
> +#define TP_UP_PENDING (1 << 1)
> +#define TP_DOWN_PENDING (1 << 0)
You could use the BIT() macro for these definitions.
> + ret = sun4i_ts_register_input(ts, pdev->name);
> + if (ret)
> + goto error;
> +
> + /*
> + * 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);
> +
> + /* Flush fifo, set trig level to 1, enable data and pen up irqs */
> + writel(DATA_IRQ_EN(1) | FIFO_TRIG(1) | FIFO_FLUSH(1) | TP_UP_IRQ_EN(1),
> + ts->base + TP_INT_FIFOC);
> +
> + /*
> + * 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);
It seems weird to do all the hardware initialization *after* the input
device has been registered. We normally initialize all the hardware
*and* then register the device, so that we guarantee that userspace
cannot access the device before it is properly initialized.
> +static const struct of_device_id sun4i_ts_of_match[] = {
> + { .compatible = "allwinner,sun4i-ts", },
> + { /* sentinel */ }
> +};
> +MODULE_DEVICE_TABLE(of, sun4i_ts_of_match);
You're introducing a new DT binding, so the appropriate documentation
should be written for it.
Thanks!
Thomas
--
Thomas Petazzoni, CTO, Free Electrons
Embedded Linux, Kernel and Android engineering
http://free-electrons.com
^ permalink raw reply
* Re: [PATCH] Input: gamepad - use independent axes for analog D-Pad buttons
From: David Herrmann @ 2013-12-25 17:40 UTC (permalink / raw)
To: Antonio Ospite; +Cc: open list:HID CORE LAYER, Dmitry Torokhov, Jiri Kosina
In-Reply-To: <1387815463-29345-1-git-send-email-ospite@studenti.unina.it>
Hi
On Mon, Dec 23, 2013 at 5:17 PM, Antonio Ospite
<ospite@studenti.unina.it> wrote:
> Model this part of the API after the Sony PlayStation 3 Controller which
> exposes independent analog values for each one of the D-Pad buttons.
>
> The PS3 programming API psl1ght also maps the analog D-Pad buttons
> individually.
>
> Cc: David Herrmann <dh.herrmann@gmail.com>
> Signed-off-by: Antonio Ospite <ospite@studenti.unina.it>
> ---
>
> Hi,
>
> as David mentioned no gamepad defines analog D-Pad buttons as mentioned in the
> gamepad API, so the API change should be OK.
>
> And is it OK too to fill the holes in the ABS space or should I just use
> higher codes?
You might wanna try searching git-history, but apart from ABS_MISC to
ABS_MT_* I think all other spaces can be filled.
Patch is fine with me, but I'd also be ok with the ABS_DPAD_HORIZ/VERT pair.
Reviewed-by: David Herrmann <dh.herrmann@gmail.com>
Thanks
David
> Thanks,
> Antonio
>
> Documentation/input/gamepad.txt | 4 ++--
> drivers/staging/et131x/Module.symvers | 0
> include/uapi/linux/input.h | 5 +++++
> 3 files changed, 7 insertions(+), 2 deletions(-)
> delete mode 100644 drivers/staging/et131x/Module.symvers
>
> diff --git a/Documentation/input/gamepad.txt b/Documentation/input/gamepad.txt
> index ed13782..aab000d 100644
> --- a/Documentation/input/gamepad.txt
> +++ b/Documentation/input/gamepad.txt
> @@ -124,8 +124,8 @@ D-Pad:
> Digital buttons are reported as:
> BTN_DPAD_*
> Analog buttons are reported as:
> - ABS_HAT0X and ABS_HAT0Y
> - (for ABS values negative is left/up, positive is right/down)
> + ABS_DPAD_*
> + (ABS values start at 0, pressure is reported as positive values)
>
> Analog-Sticks:
> The left analog-stick is reported as ABS_X, ABS_Y. The right analog stick is
> diff --git a/drivers/staging/et131x/Module.symvers b/drivers/staging/et131x/Module.symvers
> deleted file mode 100644
> index e69de29..0000000
> diff --git a/include/uapi/linux/input.h b/include/uapi/linux/input.h
> index d3fcbff..dcc73c0 100644
> --- a/include/uapi/linux/input.h
> +++ b/include/uapi/linux/input.h
> @@ -842,6 +842,11 @@ struct input_keymap_entry {
>
> #define ABS_VOLUME 0x20
>
> +#define ABS_DPAD_UP 0x21 /* Analog D-Pad Up for gamepads */
> +#define ABS_DPAD_DOWN 0x22 /* Analog D-Pad Down for gamepads */
> +#define ABS_DPAD_LEFT 0x23 /* Analog D-Pad Left for gamepads */
> +#define ABS_DPAD_RIGHT 0x24 /* Analog D-Pad Right for gamepads */
> +
> #define ABS_MISC 0x28
>
> #define ABS_MT_SLOT 0x2f /* MT slot being modified */
> --
> 1.8.5.2
>
^ permalink raw reply
* Re: Re: [lm-sensors] [PATCH 2/5] input: sun4i-ts: Add support for temperature sensor
From: Hans de Goede @ 2013-12-25 10:54 UTC (permalink / raw)
To: linux-sunxi-/JYPxA39Uh5TLH3MbocFFw
Cc: Dmitry Torokhov, LM Sensors, linux-input-u79uwXL29TY76Z2rM5mHXA,
Maxime Ripard, linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r
In-Reply-To: <20131225103723.GA18256-0h96xk9xTtrk1uMJSBkQmQ@public.gmane.org>
On 12/25/2013 11:37 AM, Guenter Roeck wrote:
> On Tue, Dec 24, 2013 at 11:24:04PM +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 attribute,
>> 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,
Hi,
Merry Christmas and thanks for the review.
>
> can you use the new hwmon_device_register_with_groups API instead,
> or even devm_hwmon_device_register_with_groups ?
Its been a while since I last did a hwmon driver, so I did not know about
those, I'm a great fan of devm* so I'll use that one in my next revision.
>
> This way you don't have to create and remove the sysfs group, you don't have to
> create the name attribute, and if you use the devm_ API you don't have to remove
> anything. Also, the hwmon attributes will be attached to the hwmon device,
> meaning the platform device won't be cluttered with hwmon attributes and
> vice versa.
>
> Couple of related comments inline below.
>
> Thanks,
> Guenter
>
>> ---
>> drivers/input/touchscreen/sun4i-ts.c | 106 +++++++++++++++++++++++++++++++++--
>> 1 file changed, 102 insertions(+), 4 deletions(-)
>>
>> diff --git a/drivers/input/touchscreen/sun4i-ts.c b/drivers/input/touchscreen/sun4i-ts.c
>> index b3f01c0..15a486d 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
>> @@ -31,6 +34,7 @@
>>
>> #include <linux/delay.h>
>> #include <linux/err.h>
>> +#include <linux/hwmon.h>
>> #include <linux/init.h>
>> #include <linux/input.h>
>> #include <linux/interrupt.h>
>> @@ -97,10 +101,16 @@
>> #define TP_UP_PENDING (1 << 1)
>> #define TP_DOWN_PENDING (1 << 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;
>> void __iomem *base;
>> struct input_dev *input;
>> + struct device *hwmon;
>> + atomic_t temp_data;
>
> Unless I am missing something, this variable does not have to be an atomic.
> Either it was written or it wasn't, but the order should not matter.
That is true, but it does assume that 32 bit int accesses are atomic, in the
sense that we can never have a case where one core has only written ie the
lower 8 bits when the other reads the variable. I believe that is the
case on ARM (the only architecture relevant here), but rather safe then
sorry. So I plan to keep this as is until someone, who knows more about
these things then me, tells me it is safe to turn it into a regular int.
>
>> int ignore_fifo_data;
>> };
>>
>> @@ -111,6 +121,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)
>> + atomic_set(&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);
>> @@ -135,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;
>> @@ -170,6 +187,66 @@ static int sun4i_ts_register_input(struct sun4i_ts_data *ts,
>> return 0;
>> }
>>
>> +static ssize_t show_name(struct device *dev, struct device_attribute *devattr,
>> + char *buf)
>> +{
>> + return sprintf(buf, "sun4i_ts\n");
>> +}
>> +
>> +static ssize_t show_temp(struct device *dev, struct device_attribute *devattr,
>> + char *buf)
>> +{
>> + struct sun4i_ts_data *ts = dev_get_drvdata(dev);
>> + int temp_data;
>> +
>> + temp_data = atomic_read(&ts->temp_data);
>> + /* No temp_data until the first irq */
>> + if (temp_data == -1)
>> + return -EAGAIN;
>> +
>> + return sprintf(buf, "%d\n", (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(name, S_IRUGO, show_name, NULL);
>> +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_attributes[] = {
>> + &dev_attr_name.attr,
>> + &dev_attr_temp1_input.attr,
>> + &dev_attr_temp1_label.attr,
>> + NULL
>> +};
>> +
>> +static const struct attribute_group sun4i_ts_attr_group = {
>> + .attrs = sun4i_ts_attributes,
>> +};
>
> If you rename the attributes to sun4i_ts_attrs, you should be able to use
> ATTRIBUTE_GROUPS() here (this gives you a list of groups which you'll need
> if you use the new API).
Ok, sounds good.
>
>> +
>> +static int sun4i_ts_register_hwmon(struct sun4i_ts_data *ts)
>> +{
>> + int ret;
>> +
>> + atomic_set(&ts->temp_data, -1);
>> +
>> + ret = sysfs_create_group(&ts->dev->kobj, &sun4i_ts_attr_group);
>> + if (ret)
>> + return ret;
>> +
>> + ts->hwmon = hwmon_device_register(ts->dev);
>> + if (IS_ERR(ts->hwmon)) {
>> + ret = PTR_ERR(ts->hwmon);
>> + ts->hwmon = NULL;
>> + }
>> +
>> + return ret;
>
> With the new API, this would be something like
>
> hwmon_dev = devm_hwmon_device_register_with_groups(ts->dev, "sun4i_ts",
> ts, sun4i_ts_attrs);
> return PTR_ERR_OR_ZERO(hwmon_dev);
>
> With the devm_ API, you don't have to store the hwmon device as you don't need
> to access it anymore from within the driver. Note that you can only use it if
> you also use devm_kzalloc to allocate ts; otherwise you would have a race condition
> during cleanup.
Sounds good again, I'll do a v2 with these changes one of the coming days.
>> +}
>> +
>> static int sun4i_ts_remove(struct platform_device *pdev)
>> {
>> struct sun4i_ts_data *ts = platform_get_drvdata(pdev);
>> @@ -178,9 +255,13 @@ static int sun4i_ts_remove(struct platform_device *pdev)
>> writel(0, ts->base + TP_INT_FIFOC);
>> msleep(20);
>>
>> + if (ts->hwmon)
>> + hwmon_device_unregister(ts->hwmon);
>> +
>> if (ts->input)
>> input_unregister_device(ts->input);
>>
>> + sysfs_remove_group(&ts->dev->kobj, &sun4i_ts_attr_group);
>> kfree(ts);
>>
>> return 0;
>> @@ -189,7 +270,11 @@ static int sun4i_ts_remove(struct platform_device *pdev)
>> static int sun4i_ts_probe(struct platform_device *pdev)
>> {
>> struct sun4i_ts_data *ts;
>> + struct device_node *np = pdev->dev.of_node;
>> int ret = -ENOMEM;
>> + u32 ts_attached = 0;
>> +
>> + of_property_read_u32(np, "ts-attached", &ts_attached);
>>
>> ts = kzalloc(sizeof(struct sun4i_ts_data), GFP_KERNEL);
>
> Different patch, but why not just use devm_kzalloc() ? Since you are using devm_
> functions elsewhere, that would seem natural. I'll also comment on that in the
> other patch.
I did not use devm_kzalloc simply because I did not knew about it until now :)
Using it sounds like an excellent idea so I'll do that.
<snip>
Regards,
Hans
^ permalink raw reply
* Re: [lm-sensors] [PATCH 1/5] input: Add new sun4i-ts driver for Allwinner sunxi SoC's rtp controller
From: Guenter Roeck @ 2013-12-25 10:40 UTC (permalink / raw)
To: Hans de Goede
Cc: Dmitry Torokhov, LM Sensors, linux-sunxi-/JYPxA39Uh5TLH3MbocFFw,
linux-input-u79uwXL29TY76Z2rM5mHXA, Maxime Ripard,
linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r
In-Reply-To: <1387923847-1294-2-git-send-email-hdegoede-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
On Tue, Dec 24, 2013 at 11:24:03PM +0100, Hans de Goede wrote:
> 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>
> ---
> drivers/input/touchscreen/Kconfig | 10 ++
> drivers/input/touchscreen/Makefile | 1 +
> drivers/input/touchscreen/sun4i-ts.c | 272 +++++++++++++++++++++++++++++++++++
[ ... ]
> +
> +static int sun4i_ts_probe(struct platform_device *pdev)
> +{
> + struct sun4i_ts_data *ts;
> + int ret = -ENOMEM;
> +
> + ts = kzalloc(sizeof(struct sun4i_ts_data), GFP_KERNEL);
How about using devm_kzalloc() here ? That would simplify the cleanup
path significantly as you could just return from most error cases.
Guenter
^ permalink raw reply
* Re: [lm-sensors] [PATCH 2/5] input: sun4i-ts: Add support for temperature sensor
From: Guenter Roeck @ 2013-12-25 10:37 UTC (permalink / raw)
To: Hans de Goede
Cc: Dmitry Torokhov, LM Sensors, linux-sunxi-/JYPxA39Uh5TLH3MbocFFw,
linux-input-u79uwXL29TY76Z2rM5mHXA, Maxime Ripard,
linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r
In-Reply-To: <1387923847-1294-3-git-send-email-hdegoede-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
On Tue, Dec 24, 2013 at 11:24:04PM +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 attribute,
> 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,
can you use the new hwmon_device_register_with_groups API instead,
or even devm_hwmon_device_register_with_groups ?
This way you don't have to create and remove the sysfs group, you don't have to
create the name attribute, and if you use the devm_ API you don't have to remove
anything. Also, the hwmon attributes will be attached to the hwmon device,
meaning the platform device won't be cluttered with hwmon attributes and
vice versa.
Couple of related comments inline below.
Thanks,
Guenter
> ---
> drivers/input/touchscreen/sun4i-ts.c | 106 +++++++++++++++++++++++++++++++++--
> 1 file changed, 102 insertions(+), 4 deletions(-)
>
> diff --git a/drivers/input/touchscreen/sun4i-ts.c b/drivers/input/touchscreen/sun4i-ts.c
> index b3f01c0..15a486d 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
> @@ -31,6 +34,7 @@
>
> #include <linux/delay.h>
> #include <linux/err.h>
> +#include <linux/hwmon.h>
> #include <linux/init.h>
> #include <linux/input.h>
> #include <linux/interrupt.h>
> @@ -97,10 +101,16 @@
> #define TP_UP_PENDING (1 << 1)
> #define TP_DOWN_PENDING (1 << 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;
> void __iomem *base;
> struct input_dev *input;
> + struct device *hwmon;
> + atomic_t temp_data;
Unless I am missing something, this variable does not have to be an atomic.
Either it was written or it wasn't, but the order should not matter.
> int ignore_fifo_data;
> };
>
> @@ -111,6 +121,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)
> + atomic_set(&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);
> @@ -135,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;
> @@ -170,6 +187,66 @@ static int sun4i_ts_register_input(struct sun4i_ts_data *ts,
> return 0;
> }
>
> +static ssize_t show_name(struct device *dev, struct device_attribute *devattr,
> + char *buf)
> +{
> + return sprintf(buf, "sun4i_ts\n");
> +}
> +
> +static ssize_t show_temp(struct device *dev, struct device_attribute *devattr,
> + char *buf)
> +{
> + struct sun4i_ts_data *ts = dev_get_drvdata(dev);
> + int temp_data;
> +
> + temp_data = atomic_read(&ts->temp_data);
> + /* No temp_data until the first irq */
> + if (temp_data == -1)
> + return -EAGAIN;
> +
> + return sprintf(buf, "%d\n", (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(name, S_IRUGO, show_name, NULL);
> +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_attributes[] = {
> + &dev_attr_name.attr,
> + &dev_attr_temp1_input.attr,
> + &dev_attr_temp1_label.attr,
> + NULL
> +};
> +
> +static const struct attribute_group sun4i_ts_attr_group = {
> + .attrs = sun4i_ts_attributes,
> +};
If you rename the attributes to sun4i_ts_attrs, you should be able to use
ATTRIBUTE_GROUPS() here (this gives you a list of groups which you'll need
if you use the new API).
> +
> +static int sun4i_ts_register_hwmon(struct sun4i_ts_data *ts)
> +{
> + int ret;
> +
> + atomic_set(&ts->temp_data, -1);
> +
> + ret = sysfs_create_group(&ts->dev->kobj, &sun4i_ts_attr_group);
> + if (ret)
> + return ret;
> +
> + ts->hwmon = hwmon_device_register(ts->dev);
> + if (IS_ERR(ts->hwmon)) {
> + ret = PTR_ERR(ts->hwmon);
> + ts->hwmon = NULL;
> + }
> +
> + return ret;
With the new API, this would be something like
hwmon_dev = devm_hwmon_device_register_with_groups(ts->dev, "sun4i_ts",
ts, sun4i_ts_attrs);
return PTR_ERR_OR_ZERO(hwmon_dev);
With the devm_ API, you don't have to store the hwmon device as you don't need
to access it anymore from within the driver. Note that you can only use it if
you also use devm_kzalloc to allocate ts; otherwise you would have a race condition
during cleanup.
> +}
> +
> static int sun4i_ts_remove(struct platform_device *pdev)
> {
> struct sun4i_ts_data *ts = platform_get_drvdata(pdev);
> @@ -178,9 +255,13 @@ static int sun4i_ts_remove(struct platform_device *pdev)
> writel(0, ts->base + TP_INT_FIFOC);
> msleep(20);
>
> + if (ts->hwmon)
> + hwmon_device_unregister(ts->hwmon);
> +
> if (ts->input)
> input_unregister_device(ts->input);
>
> + sysfs_remove_group(&ts->dev->kobj, &sun4i_ts_attr_group);
> kfree(ts);
>
> return 0;
> @@ -189,7 +270,11 @@ static int sun4i_ts_remove(struct platform_device *pdev)
> static int sun4i_ts_probe(struct platform_device *pdev)
> {
> struct sun4i_ts_data *ts;
> + struct device_node *np = pdev->dev.of_node;
> int ret = -ENOMEM;
> + u32 ts_attached = 0;
> +
> + of_property_read_u32(np, "ts-attached", &ts_attached);
>
> ts = kzalloc(sizeof(struct sun4i_ts_data), GFP_KERNEL);
Different patch, but why not just use devm_kzalloc() ? Since you are using devm_
functions elsewhere, that would seem natural. I'll also comment on that in the
other patch.
> if (!ts)
> @@ -210,7 +295,13 @@ static int sun4i_ts_probe(struct platform_device *pdev)
> if (ret)
> goto error;
>
> - ret = sun4i_ts_register_input(ts, pdev->name);
> + if (ts_attached) {
> + ret = sun4i_ts_register_input(ts, pdev->name);
> + if (ret)
> + goto error;
> + }
> +
> + ret = sun4i_ts_register_hwmon(ts);
> if (ret)
> goto error;
>
> @@ -231,9 +322,16 @@ static int sun4i_ts_probe(struct platform_device *pdev)
> /* Enable median filter, type 1 : 5/3 */
> writel(FILTER_EN(1) | FILTER_TYPE(1), ts->base + TP_CTRL3);
>
> - /* Flush fifo, set trig level to 1, enable data and pen up irqs */
> - writel(DATA_IRQ_EN(1) | FIFO_TRIG(1) | FIFO_FLUSH(1) | TP_UP_IRQ_EN(1),
> - ts->base + TP_INT_FIFOC);
> + if (ts_attached) {
> + /* 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);
> + } else
> + writel(TEMP_IRQ_EN(1), ts->base + TP_INT_FIFOC);
> +
> + /* 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
> --
> 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 5/5] ARM: dts: sun7i: Add rtp controller node
From: Hans de Goede @ 2013-12-24 22:24 UTC (permalink / raw)
To: Dmitry Torokhov
Cc: linux-input-u79uwXL29TY76Z2rM5mHXA, LM Sensors, Maxime Ripard,
linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
linux-sunxi-/JYPxA39Uh5TLH3MbocFFw, Hans de Goede
In-Reply-To: <1387923847-1294-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 | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/arch/arm/boot/dts/sun7i-a20.dtsi b/arch/arm/boot/dts/sun7i-a20.dtsi
index 3231789..fdfe4b2 100644
--- a/arch/arm/boot/dts/sun7i-a20.dtsi
+++ b/arch/arm/boot/dts/sun7i-a20.dtsi
@@ -536,6 +536,13 @@
reg = <0x01c23800 0x200>;
};
+ rtp: rtp@01c25000 {
+ compatible = "allwinner,sun4i-ts";
+ reg = <0x01c25000 0x100>;
+ interrupts = <0 29 4>;
+ ts-attached = <0>;
+ };
+
uart0: serial@01c28000 {
compatible = "snps,dw-apb-uart";
reg = <0x01c28000 0x400>;
--
1.8.4.2
^ permalink raw reply related
* [PATCH 4/5] ARM: dts: sun5i: Add rtp controller node
From: Hans de Goede @ 2013-12-24 22:24 UTC (permalink / raw)
To: Dmitry Torokhov
Cc: linux-input-u79uwXL29TY76Z2rM5mHXA, LM Sensors, Maxime Ripard,
linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
linux-sunxi-/JYPxA39Uh5TLH3MbocFFw, Hans de Goede
In-Reply-To: <1387923847-1294-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 | 7 +++++++
arch/arm/boot/dts/sun5i-a13.dtsi | 7 +++++++
2 files changed, 14 insertions(+)
diff --git a/arch/arm/boot/dts/sun5i-a10s.dtsi b/arch/arm/boot/dts/sun5i-a10s.dtsi
index 948eb10..df9ba3b 100644
--- a/arch/arm/boot/dts/sun5i-a10s.dtsi
+++ b/arch/arm/boot/dts/sun5i-a10s.dtsi
@@ -409,6 +409,13 @@
reg = <0x01c23800 0x10>;
};
+ rtp: rtp@01c25000 {
+ compatible = "allwinner,sun4i-ts";
+ reg = <0x01c25000 0x100>;
+ interrupts = <29>;
+ ts-attached = <0>;
+ };
+
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..4de2adb 100644
--- a/arch/arm/boot/dts/sun5i-a13.dtsi
+++ b/arch/arm/boot/dts/sun5i-a13.dtsi
@@ -367,6 +367,13 @@
reg = <0x01c23800 0x10>;
};
+ rtp: rtp@01c25000 {
+ compatible = "allwinner,sun4i-ts";
+ reg = <0x01c25000 0x100>;
+ interrupts = <29>;
+ ts-attached = <0>;
+ };
+
uart1: serial@01c28400 {
compatible = "snps,dw-apb-uart";
reg = <0x01c28400 0x400>;
--
1.8.4.2
^ permalink raw reply related
* [PATCH 3/5] ARM: dts: sun4i: Add rtp controller node
From: Hans de Goede @ 2013-12-24 22:24 UTC (permalink / raw)
To: Dmitry Torokhov
Cc: linux-input-u79uwXL29TY76Z2rM5mHXA, LM Sensors, Maxime Ripard,
linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
linux-sunxi-/JYPxA39Uh5TLH3MbocFFw, Hans de Goede
In-Reply-To: <1387923847-1294-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 | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/arch/arm/boot/dts/sun4i-a10.dtsi b/arch/arm/boot/dts/sun4i-a10.dtsi
index de1dd73..67ccdec 100644
--- a/arch/arm/boot/dts/sun4i-a10.dtsi
+++ b/arch/arm/boot/dts/sun4i-a10.dtsi
@@ -452,6 +452,13 @@
reg = <0x01c23800 0x10>;
};
+ rtp: rtp@01c25000 {
+ compatible = "allwinner,sun4i-ts";
+ reg = <0x01c25000 0x100>;
+ interrupts = <29>;
+ ts-attached = <0>;
+ };
+
uart0: serial@01c28000 {
compatible = "snps,dw-apb-uart";
reg = <0x01c28000 0x400>;
--
1.8.4.2
^ permalink raw reply related
* [PATCH 2/5] input: sun4i-ts: Add support for temperature sensor
From: Hans de Goede @ 2013-12-24 22:24 UTC (permalink / raw)
To: Dmitry Torokhov
Cc: linux-input-u79uwXL29TY76Z2rM5mHXA, LM Sensors, Maxime Ripard,
linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
linux-sunxi-/JYPxA39Uh5TLH3MbocFFw, Hans de Goede
In-Reply-To: <1387923847-1294-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 attribute,
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>
---
drivers/input/touchscreen/sun4i-ts.c | 106 +++++++++++++++++++++++++++++++++--
1 file changed, 102 insertions(+), 4 deletions(-)
diff --git a/drivers/input/touchscreen/sun4i-ts.c b/drivers/input/touchscreen/sun4i-ts.c
index b3f01c0..15a486d 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
@@ -31,6 +34,7 @@
#include <linux/delay.h>
#include <linux/err.h>
+#include <linux/hwmon.h>
#include <linux/init.h>
#include <linux/input.h>
#include <linux/interrupt.h>
@@ -97,10 +101,16 @@
#define TP_UP_PENDING (1 << 1)
#define TP_DOWN_PENDING (1 << 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;
void __iomem *base;
struct input_dev *input;
+ struct device *hwmon;
+ atomic_t temp_data;
int ignore_fifo_data;
};
@@ -111,6 +121,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)
+ atomic_set(&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);
@@ -135,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;
@@ -170,6 +187,66 @@ static int sun4i_ts_register_input(struct sun4i_ts_data *ts,
return 0;
}
+static ssize_t show_name(struct device *dev, struct device_attribute *devattr,
+ char *buf)
+{
+ return sprintf(buf, "sun4i_ts\n");
+}
+
+static ssize_t show_temp(struct device *dev, struct device_attribute *devattr,
+ char *buf)
+{
+ struct sun4i_ts_data *ts = dev_get_drvdata(dev);
+ int temp_data;
+
+ temp_data = atomic_read(&ts->temp_data);
+ /* No temp_data until the first irq */
+ if (temp_data == -1)
+ return -EAGAIN;
+
+ return sprintf(buf, "%d\n", (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(name, S_IRUGO, show_name, NULL);
+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_attributes[] = {
+ &dev_attr_name.attr,
+ &dev_attr_temp1_input.attr,
+ &dev_attr_temp1_label.attr,
+ NULL
+};
+
+static const struct attribute_group sun4i_ts_attr_group = {
+ .attrs = sun4i_ts_attributes,
+};
+
+static int sun4i_ts_register_hwmon(struct sun4i_ts_data *ts)
+{
+ int ret;
+
+ atomic_set(&ts->temp_data, -1);
+
+ ret = sysfs_create_group(&ts->dev->kobj, &sun4i_ts_attr_group);
+ if (ret)
+ return ret;
+
+ ts->hwmon = hwmon_device_register(ts->dev);
+ if (IS_ERR(ts->hwmon)) {
+ ret = PTR_ERR(ts->hwmon);
+ ts->hwmon = NULL;
+ }
+
+ return ret;
+}
+
static int sun4i_ts_remove(struct platform_device *pdev)
{
struct sun4i_ts_data *ts = platform_get_drvdata(pdev);
@@ -178,9 +255,13 @@ static int sun4i_ts_remove(struct platform_device *pdev)
writel(0, ts->base + TP_INT_FIFOC);
msleep(20);
+ if (ts->hwmon)
+ hwmon_device_unregister(ts->hwmon);
+
if (ts->input)
input_unregister_device(ts->input);
+ sysfs_remove_group(&ts->dev->kobj, &sun4i_ts_attr_group);
kfree(ts);
return 0;
@@ -189,7 +270,11 @@ static int sun4i_ts_remove(struct platform_device *pdev)
static int sun4i_ts_probe(struct platform_device *pdev)
{
struct sun4i_ts_data *ts;
+ struct device_node *np = pdev->dev.of_node;
int ret = -ENOMEM;
+ u32 ts_attached = 0;
+
+ of_property_read_u32(np, "ts-attached", &ts_attached);
ts = kzalloc(sizeof(struct sun4i_ts_data), GFP_KERNEL);
if (!ts)
@@ -210,7 +295,13 @@ static int sun4i_ts_probe(struct platform_device *pdev)
if (ret)
goto error;
- ret = sun4i_ts_register_input(ts, pdev->name);
+ if (ts_attached) {
+ ret = sun4i_ts_register_input(ts, pdev->name);
+ if (ret)
+ goto error;
+ }
+
+ ret = sun4i_ts_register_hwmon(ts);
if (ret)
goto error;
@@ -231,9 +322,16 @@ static int sun4i_ts_probe(struct platform_device *pdev)
/* Enable median filter, type 1 : 5/3 */
writel(FILTER_EN(1) | FILTER_TYPE(1), ts->base + TP_CTRL3);
- /* Flush fifo, set trig level to 1, enable data and pen up irqs */
- writel(DATA_IRQ_EN(1) | FIFO_TRIG(1) | FIFO_FLUSH(1) | TP_UP_IRQ_EN(1),
- ts->base + TP_INT_FIFOC);
+ if (ts_attached) {
+ /* 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);
+ } else
+ writel(TEMP_IRQ_EN(1), ts->base + TP_INT_FIFOC);
+
+ /* 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
--
1.8.4.2
^ permalink raw reply related
* [PATCH 1/5] input: Add new sun4i-ts driver for Allwinner sunxi SoC's rtp controller
From: Hans de Goede @ 2013-12-24 22:24 UTC (permalink / raw)
To: Dmitry Torokhov
Cc: linux-input-u79uwXL29TY76Z2rM5mHXA, LM Sensors, Maxime Ripard,
linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
linux-sunxi-/JYPxA39Uh5TLH3MbocFFw, Hans de Goede
In-Reply-To: <1387923847-1294-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>
---
drivers/input/touchscreen/Kconfig | 10 ++
drivers/input/touchscreen/Makefile | 1 +
drivers/input/touchscreen/sun4i-ts.c | 272 +++++++++++++++++++++++++++++++++++
3 files changed, 283 insertions(+)
create mode 100644 drivers/input/touchscreen/sun4i-ts.c
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..b3f01c0
--- /dev/null
+++ b/drivers/input/touchscreen/sun4i-ts.c
@@ -0,0 +1,272 @@
+/*
+ * sun4i-ts.c 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/delay.h>
+#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 (1 << 18)
+#define FIFO_OVERRUN_PENDING (1 << 17)
+#define FIFO_DATA_PENDING (1 << 16)
+#define TP_IDLE_FLG (1 << 2)
+#define TP_UP_PENDING (1 << 1)
+#define TP_DOWN_PENDING (1 << 0)
+
+struct sun4i_ts_data {
+ struct device *dev;
+ void __iomem *base;
+ struct input_dev *input;
+ int 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 = 0;
+ }
+
+ if (reg_val & TP_UP_PENDING) {
+ ts->ignore_fifo_data = 1;
+ 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_register_input(struct sun4i_ts_data *ts,
+ const char *name)
+{
+ int ret;
+ struct input_dev *input;
+
+ input = devm_input_allocate_device(ts->dev);
+ if (!input)
+ return -ENOMEM;
+
+ input->name = name;
+ input->phys = "sun4i_ts/input0";
+ input->id.bustype = BUS_HOST;
+ input->id.vendor = 0x0001;
+ input->id.product = 0x0001;
+ input->id.version = 0x0100;
+ input->dev.parent = ts->dev;
+ input->evbit[0] = BIT(EV_SYN) | BIT(EV_KEY) | BIT(EV_ABS);
+ set_bit(BTN_TOUCH, input->keybit);
+ input_set_abs_params(input, ABS_X, 0, 4095, 0, 0);
+ input_set_abs_params(input, ABS_Y, 0, 4095, 0, 0);
+
+ ret = input_register_device(input);
+ if (ret)
+ return ret;
+
+ ts->input = input;
+ return 0;
+}
+
+static int sun4i_ts_remove(struct platform_device *pdev)
+{
+ struct sun4i_ts_data *ts = platform_get_drvdata(pdev);
+
+ /* Deactivate all IRQs */
+ writel(0, ts->base + TP_INT_FIFOC);
+ msleep(20);
+
+ if (ts->input)
+ input_unregister_device(ts->input);
+
+ kfree(ts);
+
+ return 0;
+}
+
+static int sun4i_ts_probe(struct platform_device *pdev)
+{
+ struct sun4i_ts_data *ts;
+ int ret = -ENOMEM;
+
+ ts = kzalloc(sizeof(struct sun4i_ts_data), GFP_KERNEL);
+ if (!ts)
+ return -ENOMEM;
+
+ platform_set_drvdata(pdev, ts);
+
+ ts->dev = &pdev->dev;
+ ts->base = devm_ioremap_resource(&pdev->dev,
+ platform_get_resource(pdev, IORESOURCE_MEM, 0));
+ if (IS_ERR(ts->base)) {
+ ret = PTR_ERR(ts->base);
+ goto error;
+ }
+
+ ret = devm_request_irq(&pdev->dev, platform_get_irq(pdev, 0),
+ sun4i_ts_irq, 0, "sun4i-ts", ts);
+ if (ret)
+ goto error;
+
+ ret = sun4i_ts_register_input(ts, pdev->name);
+ if (ret)
+ goto error;
+
+ /*
+ * 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);
+
+ /* Flush fifo, set trig level to 1, enable data and pen up irqs */
+ writel(DATA_IRQ_EN(1) | FIFO_TRIG(1) | FIFO_FLUSH(1) | TP_UP_IRQ_EN(1),
+ ts->base + TP_INT_FIFOC);
+
+ /*
+ * 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);
+
+ return 0;
+
+error:
+ sun4i_ts_remove(pdev);
+ return ret;
+}
+
+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,
+ .remove = sun4i_ts_remove,
+};
+
+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 0/5] input: Add new sun4i-ts driver for Allwinner sunxi SoC's
From: Hans de Goede @ 2013-12-24 22:24 UTC (permalink / raw)
To: Dmitry Torokhov
Cc: linux-input-u79uwXL29TY76Z2rM5mHXA, LM Sensors, Maxime Ripard,
linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
linux-sunxi-/JYPxA39Uh5TLH3MbocFFw
Hi Dimitri,
Here is a new driver for the resistive touchscreen controller found in
Allwinner sunxi SoC's. Please review, and when ready pull this driver into
your tree for Linus.
This series consist of 5 patches, the 1st patch is the actual new driver,
the 2nd patch extends it with support for the temperature sensor built into
the rtp controller. Since this depends on the 1st one I would like to ask
you to pull it into your tree too. I've written and maintain several hwmon
drivers, so I'm confident the hwmon bits are solid, I've also CC-ed the
sensors list so that they can review and if they've any comments I will of
course fix those and send a new version.
Patch 3-5 are dts patches adding the controller to the dts files describing
the sunxi SoC variants. These will be merged through Maxime Ripard's tree,
and are here so that the dts bits can be reviewed at the same time. I'll ask
Maxime to merge them once you're happy with Patch 1 + 2.
Thanks & Regards,
Hans
^ permalink raw reply
* Re: [RFC v3] Add ff-memless-next
From: Randy Dunlap @ 2013-12-24 19:38 UTC (permalink / raw)
To: Michal Malý, linux-input; +Cc: linux-kernel, elias.vds, joe
In-Reply-To: <1494187.y8mYFDm1lA@geidi-prime>
On 12/23/13 14:46, Michal Malý wrote:
> ---
> Documentation/input/ff-memless-next.txt | 145 ++++++
> drivers/input/Kconfig | 12 +
> drivers/input/Makefile | 2 +
> drivers/input/ff-memless-next.c | 786 ++++++++++++++++++++++++++++++++
> include/linux/input/ff-memless-next.h | 31 ++
> 5 files changed, 976 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..00f893d
> --- /dev/null
> +++ b/Documentation/input/ff-memless-next.txt
> @@ -0,0 +1,145 @@
> +"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"
driver
(or module)
> +and its purpose. This document also contains summary of "ff-memless-next" API
a summary of the
> +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.
> diff --git a/drivers/input/Kconfig b/drivers/input/Kconfig
> index a11ff74..893ab00 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)
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
--
~Randy
--
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: [Dell Inspiron 17R SE 7720] Synaptics touchpad recognized as PS/2 Generic Mouse
From: Robert van Geerenstein @ 2013-12-24 9:45 UTC (permalink / raw)
To: Tommy Will; +Cc: Kevin Cernekee, linux-input, Justin Clift
In-Reply-To: <CA+F6ckNU9FHSMQfF9Q640QDa1rmsiR5-ZCCM6m7iPMydjBiSwg@mail.gmail.com>
Hi Tommy,
Will do, thank you. Let me know when it's available so I can test it.
Is there a manual on how and what to test?
I'm pretty much new with all of this, so any info would be awesome.
Yesterday I did find this post:
http://linuxg.net/ubuntu-13-04-touchpad-option-does-not-exist-error-fix/
After doing that I was able to use multitouch scroll and double tap with
2 fingers, but it's not that smooth yet.
The touchpad is now added as AlpsPS/2 ALPS GlidePoint
On 24-12-2013 2:04, Tommy Will wrote:
> Hi Kevin,
>
> Thanks for you following this issue.
> About the trackstick issue, although I still have not got the
> feedback, I'd start preparing the new patch since it
> does not have problem as far as my self testing.
>
> Hi Robert,
>
> Please wait and try my new patch later. Hopes it could fix your problem.
>
> --
> Best Regards,
> Tommy
>
> 2013/12/24 Robert van Geerenstein <info@r15n.nl>:
>> On 23-12-2013 19:47, Kevin Cernekee wrote:
>>> On Mon, Dec 23, 2013 at 10:20 AM, Robert van Geerenstein <info@r15n.nl>
>>> wrote:
>>>> My touchpad is detected as a PS/2 Generic Mouse on my Dell Inspiron 7720
>>>> (17R SE).
>>>> It only has the functionality of a generic mouse (movement, left clik,
>>>> right
>>>> click) and no scrolling or multitouch.
>>> Hmm, doesn't this laptop have an ALPS Dolphin V2 touchpad (not Synaptics)?
>>>
>>> Tommy and Justin were working on this a while back. There are a few
>>> patches in the list archives that you could try out. I believe
>>> they're mostly ready for merging except for one remaining issue with
>>> the trackstick.
>>> --
>>> 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
>>
>> Hmm, looks like you're right. :/
>> Didn't even notice after all this time, guess back when I made the bugreport
>> on launchpad I was all over the place trying to get it to work.
>>
>> I'll go ahead and see if I can find anything in the archives that might get
>> it to work.
>>
>> Good to hear they're mostly ready for merging though.
> --
> 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
* [PATCH v5] Input: Improve the performance of alps v5-protocol's touchpad
From: Yunkang Tang @ 2013-12-24 9:18 UTC (permalink / raw)
To: dmitry.torokhov, cernekee, dturvene
Cc: linux-input, ndevos, jclift, yunkang.tang
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>
---
drivers/input/mouse/alps.c | 214 ++++++++++++++++++++++++++++++++++++---------
drivers/input/mouse/alps.h | 7 +-
2 files changed, 179 insertions(+), 42 deletions(-)
diff --git a/drivers/input/mouse/alps.c b/drivers/input/mouse/alps.c
index 5cf62e3..fb15c64 100644
--- a/drivers/input/mouse/alps.c
+++ b/drivers/input/mouse/alps.c
@@ -277,6 +277,57 @@ static void alps_process_packet_v1_v2(struct psmouse *psmouse)
}
/*
+ * Process bitmap data for V5 protocols. Return value is null.
+ *
+ * The bitmaps don't have enough data to track fingers, so this function
+ * only generates points representing a bounding box of at most two contacts.
+ * These two points are returned in x1, y1, x2, and y2.
+ */
+static void alps_process_bitmap_dolphin(struct alps_data *priv,
+ struct alps_fields *fields,
+ int *x1, int *y1, int *x2, int *y2)
+{
+ int box_middle_x, box_middle_y;
+ unsigned int x_map, y_map;
+ unsigned char start_bit, end_bit;
+ unsigned char x_msb, x_lsb, y_msb, y_lsb;
+
+ x_map = fields->x_map;
+ y_map = fields->y_map;
+
+ if (!x_map || !y_map)
+ return;
+
+ /* Get Most-significant and Least-significant bit */
+ x_msb = fls(x_map);
+ x_lsb = ffs(x_map);
+ y_msb = fls(y_map);
+ y_lsb = ffs(y_map);
+
+ /* Most-significant bit should never exceed max sensor line number */
+ if (x_msb > priv->x_bits || y_msb > priv->y_bits)
+ return;
+
+ *x1 = *y1 = *x2 = *y2 = 0;
+
+ if (fields->fingers > 1) {
+ start_bit = priv->x_bits - x_msb;
+ end_bit = priv->x_bits - x_lsb;
+ box_middle_x = (priv->x_max * (start_bit + end_bit)) /
+ (2 * (priv->x_bits - 1));
+
+ start_bit = y_lsb - 1;
+ end_bit = y_msb - 1;
+ box_middle_y = (priv->y_max * (start_bit + end_bit)) /
+ (2 * (priv->y_bits - 1));
+ *x1 = fields->x;
+ *y1 = fields->y;
+ *x2 = 2 * box_middle_x - *x1;
+ *y2 = 2 * box_middle_y - *y1;
+ }
+}
+
+/*
* Process bitmap data from v3 and v4 protocols. Returns the number of
* fingers detected. A return value of 0 means at least one of the
* bitmaps was empty.
@@ -481,7 +532,8 @@ static void alps_decode_buttons_v3(struct alps_fields *f, unsigned char *p)
f->ts_middle = !!(p[3] & 0x40);
}
-static void alps_decode_pinnacle(struct alps_fields *f, unsigned char *p)
+static void alps_decode_pinnacle(struct alps_fields *f, unsigned char *p,
+ struct psmouse *psmouse)
{
f->first_mp = !!(p[4] & 0x40);
f->is_mp = !!(p[0] & 0x40);
@@ -502,48 +554,61 @@ static void alps_decode_pinnacle(struct alps_fields *f, unsigned char *p)
alps_decode_buttons_v3(f, p);
}
-static void alps_decode_rushmore(struct alps_fields *f, unsigned char *p)
+static void alps_decode_rushmore(struct alps_fields *f, unsigned char *p,
+ struct psmouse *psmouse)
{
- alps_decode_pinnacle(f, p);
+ alps_decode_pinnacle(f, p, psmouse);
f->x_map |= (p[5] & 0x10) << 11;
f->y_map |= (p[5] & 0x20) << 6;
}
-static void alps_decode_dolphin(struct alps_fields *f, unsigned char *p)
+static void alps_decode_dolphin(struct alps_fields *f, unsigned char *p,
+ struct psmouse *psmouse)
{
+ u64 palm_data = 0;
+ struct alps_data *priv = psmouse->private;
+
f->first_mp = !!(p[0] & 0x02);
f->is_mp = !!(p[0] & 0x20);
- f->fingers = ((p[0] & 0x6) >> 1 |
+ if (!f->is_mp) {
+ f->x = ((p[1] & 0x7f) | ((p[4] & 0x0f) << 7));
+ f->y = ((p[2] & 0x7f) | ((p[4] & 0xf0) << 3));
+ f->z = (p[0] & 4) ? 0 : p[5] & 0x7f;
+ alps_decode_buttons_v3(f, p);
+ } else {
+ f->fingers = ((p[0] & 0x6) >> 1 |
(p[0] & 0x10) >> 2);
- f->x_map = ((p[2] & 0x60) >> 5) |
- ((p[4] & 0x7f) << 2) |
- ((p[5] & 0x7f) << 9) |
- ((p[3] & 0x07) << 16) |
- ((p[3] & 0x70) << 15) |
- ((p[0] & 0x01) << 22);
- f->y_map = (p[1] & 0x7f) |
- ((p[2] & 0x1f) << 7);
-
- f->x = ((p[1] & 0x7f) | ((p[4] & 0x0f) << 7));
- f->y = ((p[2] & 0x7f) | ((p[4] & 0xf0) << 3));
- f->z = (p[0] & 4) ? 0 : p[5] & 0x7f;
- alps_decode_buttons_v3(f, p);
+ palm_data = (p[1] & 0x7f) |
+ ((p[2] & 0x7f) << 7) |
+ ((p[4] & 0x7f) << 14) |
+ ((p[5] & 0x7f) << 21) |
+ ((p[3] & 0x07) << 28) |
+ (((u64)p[3] & 0x70) << 27) |
+ (((u64)p[0] & 0x01) << 34);
+
+ /* Y-profile is stored in P(0) to p(n-1), n = y_bits; */
+ f->y_map = palm_data & (BIT(priv->y_bits) - 1);
+
+ /* X-profile is stored in p(n) to p(n+m-1), m = x_bits; */
+ f->x_map = (palm_data >> priv->y_bits) &
+ (BIT(priv->x_bits) - 1);
+ }
}
-static void alps_process_touchpad_packet_v3(struct psmouse *psmouse)
+static void alps_process_touchpad_packet_v3_v5(struct psmouse *psmouse)
{
struct alps_data *priv = psmouse->private;
unsigned char *packet = psmouse->packet;
struct input_dev *dev = psmouse->dev;
struct input_dev *dev2 = priv->dev2;
int x1 = 0, y1 = 0, x2 = 0, y2 = 0;
- int fingers = 0, bmap_fingers;
- struct alps_fields f;
+ int fingers = 0, bmap_fn;
+ struct alps_fields f = {0};
- priv->decode_fields(&f, packet);
+ priv->decode_fields(&f, packet, psmouse);
/*
* There's no single feature of touchpad position and bitmap packets
@@ -560,19 +625,38 @@ static void alps_process_touchpad_packet_v3(struct psmouse *psmouse)
*/
if (f.is_mp) {
fingers = f.fingers;
- bmap_fingers = alps_process_bitmap(priv,
- f.x_map, f.y_map,
- &x1, &y1, &x2, &y2);
-
- /*
- * We shouldn't report more than one finger if
- * we don't have two coordinates.
- */
- if (fingers > 1 && bmap_fingers < 2)
- fingers = bmap_fingers;
-
- /* Now process position packet */
- priv->decode_fields(&f, priv->multi_data);
+ if (priv->proto_version == ALPS_PROTO_V3) {
+ bmap_fn = alps_process_bitmap(priv, f.x_map,
+ f.y_map, &x1, &y1,
+ &x2, &y2);
+
+ /*
+ * We shouldn't report more than one finger if
+ * we don't have two coordinates.
+ */
+ if (fingers > 1 && bmap_fn < 2)
+ fingers = bmap_fn;
+
+ /* Now process position packet */
+ priv->decode_fields(&f, priv->multi_data,
+ psmouse);
+ } else {
+ /*
+ * Because Dolphin uses position packet's
+ * coordinate data as Pt1 and uses it to
+ * calculate Pt2, so we need to do position
+ * packet decode first.
+ */
+ priv->decode_fields(&f, priv->multi_data,
+ psmouse);
+
+ /*
+ * Since Dolphin's finger number is reliable,
+ * there is no need to compare with bmap_fn.
+ */
+ alps_process_bitmap_dolphin(priv, &f, &x1, &y1,
+ &x2, &y2);
+ }
} else {
priv->multi_packet = 0;
}
@@ -662,7 +746,7 @@ static void alps_process_packet_v3(struct psmouse *psmouse)
return;
}
- alps_process_touchpad_packet_v3(psmouse);
+ alps_process_touchpad_packet_v3_v5(psmouse);
}
static void alps_process_packet_v6(struct psmouse *psmouse)
@@ -1709,6 +1793,52 @@ error:
return -1;
}
+static int alps_dolphin_get_device_area(struct psmouse *psmouse,
+ struct alps_data *priv)
+{
+ struct ps2dev *ps2dev = &psmouse->ps2dev;
+ unsigned char param[4] = {0};
+ int num_x_electrode, num_y_electrode;
+
+ if (alps_enter_command_mode(psmouse))
+ return -1;
+
+ param[0] = 0x0a;
+ if (ps2_command(ps2dev, NULL, PSMOUSE_CMD_RESET_WRAP) ||
+ ps2_command(ps2dev, NULL, PSMOUSE_CMD_SETPOLL) ||
+ ps2_command(ps2dev, NULL, PSMOUSE_CMD_SETPOLL) ||
+ ps2_command(ps2dev, ¶m[0], PSMOUSE_CMD_SETRATE) ||
+ ps2_command(ps2dev, ¶m[0], PSMOUSE_CMD_SETRATE))
+ return -1;
+
+ if (ps2_command(ps2dev, param, PSMOUSE_CMD_GETINFO))
+ return -1;
+
+ /*
+ * Dolphin's sensor line number is not fixed. It can be calculated
+ * by adding the device's register value with DOLPHIN_PROFILE_X/YOFFSET.
+ * Further more, we can get device's x_max and y_max by multiplying
+ * sensor line number with DOLPHIN_COUNT_PER_ELECTRODE.
+ *
+ * e.g. When we get register's sensor_x = 11 & sensor_y = 8,
+ * real sensor line number X = 11 + 8 = 19, and
+ * real sensor line number Y = 8 + 1 = 9.
+ * So, x_max = (19 - 1) * 64 = 1152, and
+ * y_max = (9 - 1) * 64 = 512.
+ */
+ num_x_electrode = DOLPHIN_PROFILE_XOFFSET + (param[2] & 0x0F);
+ num_y_electrode = DOLPHIN_PROFILE_YOFFSET + ((param[2] >> 4) & 0x0F);
+ priv->x_bits = num_x_electrode;
+ priv->y_bits = num_y_electrode;
+ priv->x_max = (num_x_electrode - 1) * DOLPHIN_COUNT_PER_ELECTRODE;
+ priv->y_max = (num_y_electrode - 1) * DOLPHIN_COUNT_PER_ELECTRODE;
+
+ if (alps_exit_command_mode(psmouse))
+ return -1;
+
+ return 0;
+}
+
static int alps_hw_init_dolphin_v1(struct psmouse *psmouse)
{
struct ps2dev *ps2dev = &psmouse->ps2dev;
@@ -1763,13 +1893,13 @@ static void alps_set_defaults(struct alps_data *priv)
break;
case ALPS_PROTO_V5:
priv->hw_init = alps_hw_init_dolphin_v1;
- priv->process_packet = alps_process_packet_v3;
+ priv->process_packet = alps_process_touchpad_packet_v3_v5;
priv->decode_fields = alps_decode_dolphin;
priv->set_abs_params = alps_set_abs_params_mt;
priv->nibble_commands = alps_v3_nibble_commands;
priv->addr_command = PSMOUSE_CMD_RESET_WRAP;
priv->byte0 = 0xc8;
- priv->mask0 = 0xc8;
+ priv->mask0 = 0xd8;
priv->flags = 0;
priv->x_max = 1360;
priv->y_max = 660;
@@ -1845,11 +1975,13 @@ static int alps_identify(struct psmouse *psmouse, struct alps_data *priv)
if (alps_match_table(psmouse, priv, e7, ec) == 0) {
return 0;
} else if (e7[0] == 0x73 && e7[1] == 0x03 && e7[2] == 0x50 &&
- ec[0] == 0x73 && ec[1] == 0x01) {
+ ec[0] == 0x73 && (ec[1] == 0x01 || ec[1] == 0x02)) {
priv->proto_version = ALPS_PROTO_V5;
alps_set_defaults(priv);
-
- return 0;
+ if (alps_dolphin_get_device_area(psmouse, priv))
+ return -EIO;
+ else
+ return 0;
} else if (ec[0] == 0x88 && ec[1] == 0x08) {
priv->proto_version = ALPS_PROTO_V3;
alps_set_defaults(priv);
diff --git a/drivers/input/mouse/alps.h b/drivers/input/mouse/alps.h
index 704f0f9..03f88b6 100644
--- a/drivers/input/mouse/alps.h
+++ b/drivers/input/mouse/alps.h
@@ -19,6 +19,10 @@
#define ALPS_PROTO_V5 5
#define ALPS_PROTO_V6 6
+#define DOLPHIN_COUNT_PER_ELECTRODE 64
+#define DOLPHIN_PROFILE_XOFFSET 8 /* x-electrode offset */
+#define DOLPHIN_PROFILE_YOFFSET 1 /* y-electrode offset */
+
/**
* struct alps_model_info - touchpad ID table
* @signature: E7 response string to match.
@@ -146,7 +150,8 @@ struct alps_data {
int (*hw_init)(struct psmouse *psmouse);
void (*process_packet)(struct psmouse *psmouse);
- void (*decode_fields)(struct alps_fields *f, unsigned char *p);
+ void (*decode_fields)(struct alps_fields *f, unsigned char *p,
+ struct psmouse *psmouse);
void (*set_abs_params)(struct alps_data *priv, struct input_dev *dev1);
int prev_fin;
--
1.8.1.2
^ permalink raw reply related
* [PATCH V2] input synaptics-rmi4: Bug fixes to ATTN GPIO handling.
From: Christopher Heiny @ 2013-12-24 2:44 UTC (permalink / raw)
To: Dmitry Torokhov
Cc: Linux Input, Christopher Heiny, Andrew Duggan, Vincent Huang,
Vivian Ly, Daniel Rosenberg, Jean Delvare, Joerie de Gram,
Linus Walleij, Benjamin Tissoires
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);
+
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) {
- 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;
^ permalink raw reply related
* RE: [PATCH] Input: gpio_keys: support wakeup system from freeze mode
From: Anson.Huang @ 2013-12-24 2:29 UTC (permalink / raw)
To: Dmitry Torokhov; +Cc: sachin.kamat@linaro.org, linux-input@vger.kernel.org
In-Reply-To: <20131224003036.GA16590@core.coreip.homeip.net>
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?
>
>>
>> Signed-off-by: Anson Huang <b20788@freescale.com>
>> ---
>> drivers/input/keyboard/gpio_keys.c | 2 ++
>> 1 file changed, 2 insertions(+)
>>
>> diff --git a/drivers/input/keyboard/gpio_keys.c
>> b/drivers/input/keyboard/gpio_keys.c
>> index 2db1324..aadb1db 100644
>> --- a/drivers/input/keyboard/gpio_keys.c
>> +++ b/drivers/input/keyboard/gpio_keys.c
>> @@ -473,6 +473,8 @@ static int gpio_keys_setup_key(struct
>> platform_device *pdev,
>>
>> isr = gpio_keys_gpio_isr;
>> irqflags = IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING;
>> + if (bdata->button->wakeup)
>> + irqflags |= IRQF_NO_SUSPEND;
>>
>> } else {
>> if (!button->irq) {
>> --
>> 1.7.9.5
>>
>>
>
>--
>Dmitry
>
^ permalink raw reply
* Re: [RFC v3] Add ff-memless-next
From: Michal Malý @ 2013-12-24 2:11 UTC (permalink / raw)
To: linux-input; +Cc: linux-kernel, elias.vds
In-Reply-To: <1387844508.22671.73.camel@joe-AO722>
On Monday 23 of December 2013 16:21:48 you wrote:
> On Mon, 2013-12-23 at 23:46 +0100, Michal Malý wrote:
> > One case where uncombinable effects were mishandled was corrected. Rest of
> > the changes are just coding style fixes.
>
> trivia:
> > diff --git a/drivers/input/ff-memless-next.c
> > b/drivers/input/ff-memless-next.c
> []
>
> > +static __always_inline s32 mlnx_calculate_x_force(const s32 level,
> > + const u16 direction)
>
> __always_inline is almost never warranted.
> gcc generally does the right thing.
I did some reading about __always_inline vs. plain inline and I ran into
some contradictory information about what is considered broken. Thanks
for letting me know...
Is there anything else that needs some reviewing?
--
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
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