* Re: [PATCH] Input: elantech - fix packet check for v3 and v4 hardware
From: Dmitry Torokhov @ 2013-08-15 7:12 UTC (permalink / raw)
To: Matteo Delfino; +Cc: linux-input
In-Reply-To: <1376473037-11211-1-git-send-email-kendatsuba@gmail.com>
Hi Matteo,
On Wed, Aug 14, 2013 at 11:37:17AM +0200, Matteo Delfino wrote:
> The signatures of v3 and v4 packets change depending on the value of a
> hardware flag called 'crc_enabled'. The packet type detection must change
> accordingly.
>
> This patch also restores a consistency check for v4 packets inadvertently
> removed by commit:
>
> 9eebed7de660c0b5ab129a9de4f89d20b60de68c
> Input: elantech - fix for newer hardware versions (v7)
>
> A note about the naming convention: v3 hardware is associated with IC body
> v5 while v4 hardware is associated with IC body v6 and v7. The above commit
> refers to IC body v7, not to v7 hardware.
>
> Tested on Samsung NP730U3E (fw = 0x675f05, ICv7, crc_enabled = 1)
>
> Tested-by: Giovanni Frigione <gio.frigione@gmail.com>
> Signed-off-by: Matteo Delfino <kendatsuba@gmail.com>
> ---
>
> Hello Dmitry,
> this patch is based on some information provided by elantech. It has
> been successfully tested on the above v4 hardware so the information
> seems to be reliable. More testing is needed on v3 hardware with
> crc_enabled=1 (if there's such hardware out there..). The behaviour
> should be unchanged for v3 and v4 hardware with crc_enabled=0 (the
> vast majority).
>
> I was asked to CC stable kernels too since this patch restores a
> consistency check on v4 packets removed by a previous commit already
> queued in linux-3.5.y-queue but, considering the above, I think it's
> best to leave this decision to you.
Once again, I think it should bake a bit in the trees before heading for
next, so I won't mark it for stable. Feel free to forward it later, once
it survives a few -rcs.
Thanks!
--
Dmitry
^ permalink raw reply
* Re: [PATCH v5 1/1] input: ideapad_slidebar: new input driver
From: Dmitry Torokhov @ 2013-08-15 7:27 UTC (permalink / raw)
To: Andrey Moiseev
Cc: linux-input, arnoques, russianneuromancer, ike.pan, linux-kernel
In-Reply-To: <5208B79B.3010104@gmail.com>
Hi Andrey,
On Mon, Aug 12, 2013 at 02:23:23PM +0400, Andrey Moiseev wrote:
> v5: __initconst goes after var name
>
> ideapad_slidebar is a new driver which enables slidebars on some
> Lenovo IdeaPad laptops (the slidebars work with SlideNav/Desktop
> Navigator under Windows)
>
> Fixes this: https://bugzilla.kernel.org/show_bug.cgi?id=16004
>
> Registers 'IdeaPad Slidebar' input device and
> /sys/devices/platform/ideapad_slidebar/slidebar_mode
> for switching slidebar's modes.
>
> Now works on:
> IdeaPad Y550, Y550P.
>
> May work on (testing and adding new models is needed):
> Ideapad Y560, Y460, Y450, Y650,
> and, probably, some others.
>
> Driver source: https://github.com/o2genum/ideapad-slidebar.git
>
> Patch is generated against current mainline kernel.
>
> Signed-off-by: Andrey Moiseev <o2g.org.ru@gmail.com>
> ---
> MAINTAINERS | 7 +
> drivers/input/misc/Kconfig | 9 +
> drivers/input/misc/Makefile | 1 +
> drivers/input/misc/ideapad_slidebar.c | 380 ++++++++++++++++++++++++++++++++++
> 4 files changed, 397 insertions(+)
> create mode 100644 drivers/input/misc/ideapad_slidebar.c
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index defc053..2ff3dd8 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -4096,6 +4096,13 @@ W: http://launchpad.net/ideapad-laptop
> S: Maintained
> F: drivers/platform/x86/ideapad-laptop.c
>
> +IDEAPAD LAPTOP SLIDEBAR DRIVER
> +M: Andrey Moiseev <o2g.org.ru@gmail.com>
> +L: linux-input@vger.kernel.org
> +W: https://github.com/o2genum/ideapad-slidebar
> +S: Maintained
> +F: drivers/input/misc/ideapad_slidebar.c
> +
> IDE/ATAPI DRIVERS
> M: Borislav Petkov <bp@alien8.de>
> L: linux-ide@vger.kernel.org
> diff --git a/drivers/input/misc/Kconfig b/drivers/input/misc/Kconfig
> index 0b541cd..45729a9 100644
> --- a/drivers/input/misc/Kconfig
> +++ b/drivers/input/misc/Kconfig
> @@ -647,4 +647,13 @@ config INPUT_SIRFSOC_ONKEY
>
> If unsure, say N.
>
> +config INPUT_IDEAPAD_SLIDEBAR
> + tristate "IdeaPad Laptop Slidebar"
> + depends on INPUT
> + help
> + Input driver for slidebars on some Lenovo IdeaPad laptops.
> +
> + If you have an IdeaPad laptop with a slidebar, say Y or M here.
> + Module name is ideapad_slidebar.
> +
> endif
> diff --git a/drivers/input/misc/Makefile b/drivers/input/misc/Makefile
> index 829de43..0ebfb6d 100644
> --- a/drivers/input/misc/Makefile
> +++ b/drivers/input/misc/Makefile
> @@ -61,3 +61,4 @@ obj-$(CONFIG_INPUT_WISTRON_BTNS) += wistron_btns.o
> obj-$(CONFIG_INPUT_WM831X_ON) += wm831x-on.o
> obj-$(CONFIG_INPUT_XEN_KBDDEV_FRONTEND) += xen-kbdfront.o
> obj-$(CONFIG_INPUT_YEALINK) += yealink.o
> +obj-$(CONFIG_INPUT_IDEAPAD_SLIDEBAR) += ideapad_slidebar.o
> diff --git a/drivers/input/misc/ideapad_slidebar.c b/drivers/input/misc/ideapad_slidebar.c
> new file mode 100644
> index 0000000..df437a4
> --- /dev/null
> +++ b/drivers/input/misc/ideapad_slidebar.c
> @@ -0,0 +1,380 @@
> +/*
> + * Input driver for slidebars on some Lenovo IdeaPad laptops
> + *
> + * Copyright (C) 2013 Andrey Moiseev <o2g.org.ru@gmail.com>
> + *
> + * Reverse-engineered from Lenovo SlideNav software (SBarHook.dll).
> + *
> + * 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.
> + *
> + * Trademarks are the property of their respective owners.
> + */
> +
> +/*
> + * Currently tested and works on:
> + * Lenovo IdeaPad Y550
> + * Lenovo IdeaPad Y550P
> + *
> + * Other models can be added easily. To test,
> + * load with 'force' parameter set 'true'.
> + *
> + * LEDs blinking and input mode are managed via sysfs,
> + * (hex, unsigned byte value):
> + * /sys/devices/platform/ideapad_slidebar/slidebar_mode
> + *
> + * The value is in byte range, however, I only figured out
> + * how bits 0b10011001 work. Some other bits, probably,
> + * are meaningfull too.
> + *
> + * Possible states:
> + *
> + * STD_INT, ONMOV_INT, OFF_INT, LAST_POLL, OFF_POLL
> + *
> + * Meaning:
> + * released touched
> + * STD 'heartbeat' lights follow the finger
> + * ONMOV no lights lights follow the finger
> + * LAST at last pos lights follow the finger
> + * OFF no lights no lights
> + *
> + * INT all input events are generated, interrupts are used
> + * POLL no input events by default, to get them,
> + * send 0b10000000 (read below)
> + *
> + * Commands: write
> + *
> + * All | 0b01001 -> STD_INT
> + * possible | 0b10001 -> ONMOV_INT
> + * states | 0b01000 -> OFF_INT
> + *
> + * | 0b0 -> LAST_POLL
> + * STD_INT or ONMOV_INT |
> + * | 0b1 -> STD_INT
> + *
> + * | 0b0 -> OFF_POLL
> + * OFF_INT or OFF_POLL |
> + * | 0b1 -> OFF_INT
> + *
> + * Any state | 0b10000000 -> if the slidebar has updated data,
> + * produce one input event (last position),
> + * switch to respective POLL mode
> + * (like 0x0), if not in POLL mode yet.
> + *
> + * Get current state: read
> + *
> + * masked by 0x11 read value means:
> + *
> + * 0x00 LAST
> + * 0x01 STD
> + * 0x10 OFF
> + * 0x11 ONMOV
> + */
> +
> +#include <linux/module.h>
> +#include <linux/kernel.h>
> +#include <linux/dmi.h>
> +#include <linux/spinlock.h>
> +#include <linux/platform_device.h>
> +#include <linux/input.h>
> +#include <linux/io.h>
> +#include <linux/i8042.h>
> +
> +static bool force;
> +module_param(force, bool, 0);
> +MODULE_PARM_DESC(force, "Force driver load, ignore DMI data");
> +
> +static spinlock_t sio_lock = __SPIN_LOCK_UNLOCKED(sio_lock);
> +
> +static struct input_dev *slidebar_input_dev;
> +static struct platform_device *slidebar_platform_dev;
> +
> +/* Hardware interacting */
> +static int slidebar_pos_get(void)
> +{
> + int res;
> + unsigned long flags;
A blank line between variables and code is appreciated.
> + spin_lock_irqsave(&sio_lock, flags);
> + outb(0xf4, 0xff29);
> + outb(0xbf, 0xff2a);
> + res = inb(0xff2b);
> + spin_unlock_irqrestore(&sio_lock, flags);
> + return res;
> +}
> +
> +static unsigned char slidebar_mode_get(void)
> +{
> + int res;
> + unsigned long flags;
> + spin_lock_irqsave(&sio_lock, flags);
> + outb(0xf7, 0xff29);
> + outb(0x8b, 0xff2a);
> + res = inb(0xff2b);
> + spin_unlock_irqrestore(&sio_lock, flags);
> + return res;
> +}
> +
> +static void slidebar_mode_set(unsigned char mode)
> +{
> + unsigned long flags;
> + spin_lock_irqsave(&sio_lock, flags);
> + outb(0xf7, 0xff29);
> + outb(0x8b, 0xff2a);
> + outb(mode, 0xff2b);
> + spin_unlock_irqrestore(&sio_lock, flags);
> +}
> +
> +/* Listening the keyboard (i8042 filter) */
> +static bool slidebar_i8042_filter(unsigned char data, unsigned char str,
> + struct serio *port)
> +{
> + static bool extended = false, touched = false;
> +
> + /* Scancodes: e03b on move, e0bb on release */
> + if (unlikely(data == 0xe0)) {
> + extended = true;
> + return false;
> + } else if (unlikely(extended && (data == 0x3b))) {
> + extended = false;
> + if (!touched)
> + input_report_key(slidebar_input_dev, BTN_TOUCH, 1);
> + touched = true;
> + input_report_abs(slidebar_input_dev, ABS_X, slidebar_pos_get());
> + input_sync(slidebar_input_dev);
> + return false;
> + } else if (unlikely(extended && (data == 0xbb))) {
> + touched = false;
> + input_report_key(slidebar_input_dev, BTN_TOUCH, 0);
> + input_sync(slidebar_input_dev);
> + }
> +
> + return false;
> +}
> +
> +/* Input device */
> +static int setup_input_dev(void)
> +{
> + int err;
> +
> + slidebar_input_dev = input_allocate_device();
> + if (!slidebar_input_dev) {
> + pr_err("ideapad_slidebar: Not enough memory\n");
> + return -ENOMEM;
> + }
> +
> + slidebar_input_dev->name = "IdeaPad Slidebar";
> + slidebar_input_dev->id.bustype = BUS_HOST;
> + slidebar_input_dev->dev.parent = &slidebar_platform_dev->dev;
> + input_set_capability(slidebar_input_dev, EV_KEY, BTN_TOUCH);
> + input_set_capability(slidebar_input_dev, EV_ABS, ABS_X);
> + input_alloc_absinfo(slidebar_input_dev);
This call is not needed, it will be done for you.
> + input_set_abs_params(slidebar_input_dev, ABS_X, 0, 0xff, 0, 0);
> +
> + err = input_register_device(slidebar_input_dev);
> + if (err) {
> + pr_err("ideapad_slidebar: Failed to register device\n");
> + goto err_free_dev;
> + }
> +
> + err = i8042_install_filter(slidebar_i8042_filter);
> + if (err) {
> + pr_err("ideapad_slidebar: Can't install i8042 filter \n");
> + goto err_unregister_dev;
> + }
> + return 0;
> +
> +err_unregister_dev:
> + input_unregister_device(slidebar_input_dev);
> +err_free_dev:
> + input_free_device(slidebar_input_dev);
No free after unregister for input devices. Consider swapping
registering device and installing the filter (allocated but not
registered input device can take events emitted by the filter just
fine).
> + return err;
> +}
> +
> +static void remove_input_dev(void)
> +{
> + i8042_remove_filter(slidebar_i8042_filter);
> + input_unregister_device(slidebar_input_dev);
> + input_free_device(slidebar_input_dev);
No free after unregister for input devices.
> +}
> +
> +/* Sysfs slidebar_mode interface */
> +static ssize_t show_slidebar_mode(struct device *dev,
> + struct device_attribute *attr,
> + char *buf)
> +{
> + return sprintf(buf, "%x\n", slidebar_mode_get());
> +}
> +
> +static ssize_t store_slidebar_mode(struct device *dev,
> + struct device_attribute *attr,
> + const char *buf, size_t count)
> +{
> + int mode;
> + if (!count)
> + return 0;
> +
> + if (sscanf(buf, "%x", &mode) != 1)
> + return -EINVAL;
> +
> + slidebar_mode_set(mode);
> + return count;
> +}
> +
> +static DEVICE_ATTR(slidebar_mode, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH,
> + show_slidebar_mode, store_slidebar_mode);
> +
> +static struct attribute *ideapad_attributes[] = {
> + &dev_attr_slidebar_mode.attr,
> + NULL
> +};
> +
> +static struct attribute_group ideapad_attribute_group = {
> + .attrs = ideapad_attributes
> +};
> +
> +static int setup_slidebar_mode_dev(void)
> +{
> + return sysfs_create_group(&slidebar_platform_dev->dev.kobj,
> + &ideapad_attribute_group);
To avoid races between creating device and attributes you want to simply
assign your group to the devices group pointer and then it will be
created for you by device core.
> +}
> +
> +static void remove_slidebar_mode_dev(void)
> +{
> + return sysfs_remove_group(&slidebar_platform_dev->dev.kobj,
> + &ideapad_attribute_group);
> +}
> +
> +/* Platform device */
> +static int setup_platform_dev(void)
> +{
> + int err;
> + slidebar_platform_dev = platform_device_alloc("ideapad_slidebar", -1);
> + if (!slidebar_platform_dev) {
> + pr_err("ideapad_slidebar: Not enough memory\n");
> + return -ENOMEM;
> + }
> +
> + err = platform_device_add(slidebar_platform_dev);
> + if (err) {
> + pr_err("ideapad_slidebar: Failed to register plarform device\n");
> + goto err_free_platform_device;
> + }
> + return 0;
> +
> +err_free_platform_device:
> + platform_device_put(slidebar_platform_dev);
> + return err;
> +}
> +
> +static void remove_platform_dev(void)
> +{
> + platform_device_del(slidebar_platform_dev);
> + platform_device_put(slidebar_platform_dev);
I do nto understand why you need all these wrappers when you can simply
call platform_device_unregister().
> +}
> +
> +/* Platform driver */
> +static struct platform_driver slidebar_drv = {
> + .driver = {
> + .name = "ideapad_slidebar",
> + .owner = THIS_MODULE,
> + },
> +};
> +
> +static int register_platform_drv(void)
> +{
> + int err;
> + err = platform_driver_register(&slidebar_drv);
> + if (err)
> + pr_err("ideapad_slidebar: Failed to register platform driver\n");
> +
> + return err;
> +}
> +
> +static void unregister_platform_drv(void)
> +{
> + platform_driver_unregister(&slidebar_drv);
> +}
> +
> +/* DMI */
> +static int ideapad_dmi_check(const struct dmi_system_id *id)
> +{
> + pr_info("ideapad_slidebar: Laptop model '%s'\n", id->ident);
> + return 1;
> +}
> +
> +static struct dmi_system_id ideapad_dmi_table[] __initconst = {
> + {
> + .ident = "Lenovo IdeaPad Y550",
> + .matches = {
> + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
> + DMI_MATCH(DMI_PRODUCT_NAME, "20017"),
> + DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo IdeaPad Y550")
> + },
> + .callback = ideapad_dmi_check
> + },
> + {
> + .ident = "Lenovo IdeaPad Y550P",
> + .matches = {
> + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
> + DMI_MATCH(DMI_PRODUCT_NAME, "20035"),
> + DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo IdeaPad Y550P")
> + },
> + .callback = ideapad_dmi_check
> + },
> + { }
> +};
> +
> +/* Init and cleanup */
> +static int __init slidebar_init(void)
> +{
> + int err;
> +
> + if (!force && !dmi_check_system(ideapad_dmi_table))
> + return -ENODEV;
> +
> + err = setup_platform_dev();
> + if (err)
> + return err;
> +
> + err = register_platform_drv();
> + if (err)
> + goto err_remove_platform_dev;
> +
> + err = setup_input_dev();
> + if (err)
> + goto err_unregister_platform_drv;
> +
> + err = setup_slidebar_mode_dev();
> + if (err)
> + goto err_remove_input_dev;
> + return 0;
> +
> +err_remove_input_dev:
> + remove_input_dev();
> +err_unregister_platform_drv:
> + unregister_platform_drv();
> +err_remove_platform_dev:
> + remove_platform_dev();
> + return err;
> +}
> +
> +static void __exit slidebar_exit(void)
> +{
> + remove_slidebar_mode_dev();
> + remove_input_dev();
> + remove_platform_dev();
> + unregister_platform_drv();
> +}
> +
> +module_init(slidebar_init);
> +module_exit(slidebar_exit);
> +
> +MODULE_AUTHOR("Andrey Moiseev <o2g.org.ru@gmail.com>");
> +MODULE_DESCRIPTION("Slidebar input support for some Lenovo IdeaPad laptops");
> +MODULE_LICENSE("GPL");
> +MODULE_VERSION("0.1");
> +
> +MODULE_ALIAS("dmi:*:svnLENOVO:pn20017:pvrLenovoIdeaPadY550:*");
> +MODULE_ALIAS("dmi:*:svnLENOVO:pn20035:pvrLenovoIdeaPadY550P:*");
Why not create MODULE_DEVICE_TABLE from dmi table instead of doing it by
hand?
Thanks.
--
Dmitry
^ permalink raw reply
* Re: [PATCH 4/4] iio: ti_am335x_adc: Add continuous sampling and trigger support
From: Jonathan Cameron @ 2013-08-15 11:43 UTC (permalink / raw)
To: Zubair Lutfullah
Cc: jic23-KWPb1pKIrIJaa/9Udqfwiw,
dmitry.torokhov-Re5JQEeQqe8AvxtiuMwx3w,
linux-iio-u79uwXL29TY76Z2rM5mHXA,
linux-input-u79uwXL29TY76Z2rM5mHXA,
linux-kernel-u79uwXL29TY76Z2rM5mHXA,
bigeasy-hfZtesqFncYOwBW4kG4KsQ,
gregkh-hQyY1W1yCW8ekmWlsbkhG0B+6BGkLq7r, Russ.Dill-l0cyMroinI0
In-Reply-To: <1376424303-22740-5-git-send-email-zubair.lutfullah-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
On 08/13/13 21:05, Zubair Lutfullah wrote:
> Previously the driver had only one-shot reading functionality.
> This patch adds triggered buffer support to the driver.
> A buffer of samples can now be read via /dev/iio.
> Any IIO trigger can be used to start acquisition.
>
> Patil Rachna (TI) laid the ground work for ADC HW register access.
> Russ Dill (TI) fixed bugs in the driver relevant to FIFOs and IRQs.
>
> I fixed channel scanning so multiple ADC channels can be read
> simultaneously and pushed to userspace. Restructured the driver
> to fit IIO ABI. And added trigger support.
>
> Signed-off-by: Zubair Lutfullah <zubair.lutfullah-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
> Acked-by: Greg Kroah-Hartman <gregkh-hQyY1W1yCW8ekmWlsbkhG0B+6BGkLq7r@public.gmane.org>
> Signed-off-by: Russ Dill <Russ.Dill-l0cyMroinI0@public.gmane.org>
Hi,
Whilst the below review is based on what we have here, I tried to apply it
to the latest iio.git togreg branch (which for this driver should be
much the same as staging-next and hence linux-next). It's not even
close and I can't immediately spot where some of the changes came
from. Have I missed a precursor patch or is this based on an
older version of this driver?
Note I'd also like a much more detailed description in the patch header.
This is an unusual driver in that it is pushing an entire fifo on a single
trigger into the iio buffers. Normally it is one trigger / one scan. I have
no particular problem with hybrid software / hardware buffer approach but
it should certainly be clearly documented!
I would also expect an option for the trigger to be supplied from the
chip itself based on fifo watershead interrupts. Thus the adc could be
operated at full rate without losing data. As you described in a previous
email this is much more similar to a one shot osciloscope trigger where it
grabs the next set of readings. Now this is an interesting option, but
it isn't the standard one for IIO. I'd be interested to see a proposal
for adding this functionality to the core in a more general fashion.
(I actually like this idea a lot!)
A quick thought on interface would be to have
iio:device0/buffer/oneshot_length
(need not actually be the same as the buffer_length - longer and it would
require userspace to have grabbed the data out in the meantime, shorter and
we could grab a series before userspace read any of them).
iio:device0/buffer/buffer_enable_oneshot
(capture would then occur until oneshot_length had been acquired - then stop
- if the trigger that is driving capture wants to be gated by another signal
that would not be that hard to do either).
Could be neffarouious and abuse one of the poll variants to indicate when
our oneshot sample is done (this was discussed before as we had a watershed
interrupt on a ring buffer at one point).
For now I'd be much happier if this driver conformed to more or less the
standard form with the one exception being that the 'trigger' is based on
the fifo threshold and so it might appear to grab multiple scans of the
enabled channels for each trigger (which is fine). Even if we provide the
functionality sketched out above, it would still be as core functionality, not
in the driver as you have done here.
Sorry I can't really take the patch as is because we would end up with one
driver with a substantially different ABI to all others and that would make
thinks very fiddly for userspace.
Jonathan
.
> ---
> drivers/iio/adc/ti_am335x_adc.c | 353 ++++++++++++++++++++++++++++------
> include/linux/mfd/ti_am335x_tscadc.h | 12 ++
> 2 files changed, 303 insertions(+), 62 deletions(-)
>
> diff --git a/drivers/iio/adc/ti_am335x_adc.c b/drivers/iio/adc/ti_am335x_adc.c
> index 3ceac3e..0d7e313 100644
> --- a/drivers/iio/adc/ti_am335x_adc.c
> +++ b/drivers/iio/adc/ti_am335x_adc.c
> @@ -24,16 +24,28 @@
> #include <linux/iio/iio.h>
> #include <linux/of.h>
> #include <linux/of_device.h>
> -#include <linux/iio/machine.h>
> #include <linux/iio/driver.h>
> +#include <linux/wait.h>
> +#include <linux/sched.h>
>
> #include <linux/mfd/ti_am335x_tscadc.h>
> +#include <linux/iio/buffer.h>
> +#include <linux/iio/trigger.h>
> +#include <linux/iio/trigger_consumer.h>
> +#include <linux/iio/triggered_buffer.h>
>
> struct tiadc_device {
> struct ti_tscadc_dev *mfd_tscadc;
> int channels;
> u8 channel_line[8];
> u8 channel_step[8];
> + struct work_struct poll_work;
> + wait_queue_head_t wq_data_avail;
> + bool data_avail;
> + u32 *inputbuffer;
> + int sample_count;
> + int irq;
> + int buffer_en_ch_steps;
> };
>
> static unsigned int tiadc_readl(struct tiadc_device *adc, unsigned int reg)
> @@ -56,27 +68,28 @@ static u32 get_adc_step_mask(struct tiadc_device *adc_dev)
> return step_en;
> }
>
> -static void tiadc_step_config(struct tiadc_device *adc_dev)
> +static void tiadc_step_config(struct iio_dev *indio_dev)
> {
> + struct tiadc_device *adc_dev = iio_priv(indio_dev);
> unsigned int stepconfig;
> - int i, steps;
> + int i, steps, chan;
>
> /*
> * There are 16 configurable steps and 8 analog input
> * lines available which are shared between Touchscreen and ADC.
> - *
> * Steps backwards i.e. from 16 towards 0 are used by ADC
> * depending on number of input lines needed.
> * Channel would represent which analog input
> * needs to be given to ADC to digitalize data.
> */
> -
> steps = TOTAL_STEPS - adc_dev->channels;
> - stepconfig = STEPCONFIG_AVG_16 | STEPCONFIG_FIFO1;
> + if (iio_buffer_enabled(indio_dev))
> + stepconfig = STEPCONFIG_AVG_16 | STEPCONFIG_FIFO1
> + | STEPCONFIG_MODE_SWCNT;
> + else
> + stepconfig = STEPCONFIG_AVG_16 | STEPCONFIG_FIFO1;
>
> for (i = 0; i < adc_dev->channels; i++) {
> - int chan;
> -
> chan = adc_dev->channel_line[i];
> tiadc_writel(adc_dev, REG_STEPCONFIG(steps),
> stepconfig | STEPCONFIG_INP(chan));
> @@ -85,7 +98,203 @@ static void tiadc_step_config(struct tiadc_device *adc_dev)
> adc_dev->channel_step[i] = steps;
> steps++;
> }
> +}
> +
> +static irqreturn_t tiadc_irq(int irq, void *private)
> +{
> + struct iio_dev *idev = private;
> + struct tiadc_device *adc_dev = iio_priv(idev);
> + unsigned int status, config;
> + status = tiadc_readl(adc_dev, REG_IRQSTATUS);
> +
> + /* FIFO Overrun. Clear flag. Disable/Enable ADC to recover */
> + if (status & IRQENB_FIFO1OVRRUN) {
> + config = tiadc_readl(adc_dev, REG_CTRL);
> + config &= ~(CNTRLREG_TSCSSENB);
> + tiadc_writel(adc_dev, REG_CTRL, config);
> + tiadc_writel(adc_dev, REG_IRQSTATUS, IRQENB_FIFO1OVRRUN |
> + IRQENB_FIFO1UNDRFLW | IRQENB_FIFO1THRES);
> + tiadc_writel(adc_dev, REG_CTRL, (config | CNTRLREG_TSCSSENB));
> + } else if (status & IRQENB_FIFO1THRES) {
I'd normally expect this interrupt to drive a trigger that in turn results in
the data being dumped into the software buffers.
> + /* Wake adc_work that pushes FIFO data to iio buffer */
> + tiadc_writel(adc_dev, REG_IRQCLR, IRQENB_FIFO1THRES);
> + adc_dev->data_avail = 1;
> + wake_up_interruptible(&adc_dev->wq_data_avail);
> + } else
> + return IRQ_NONE;
> +
> + status = tiadc_readl(adc_dev, REG_IRQSTATUS);
> + if (status == false)
> + return IRQ_HANDLED;
> + else
> + return IRQ_NONE;
> +}
> +
> +static irqreturn_t tiadc_trigger_h(int irq, void *p)
> +{
> + struct iio_poll_func *pf = p;
> + struct iio_dev *indio_dev = pf->indio_dev;
> + struct tiadc_device *adc_dev = iio_priv(indio_dev);
> + unsigned int config;
> +
> + schedule_work(&adc_dev->poll_work);
> + am335x_tsc_se_set(adc_dev->mfd_tscadc, adc_dev->buffer_en_ch_steps);
> + if (adc_dev->mfd_tscadc->tsc_cell == -1) {
> + config = tiadc_readl(adc_dev, REG_CTRL);
> + tiadc_writel(adc_dev, REG_CTRL, config & ~CNTRLREG_TSCSSENB);
> + tiadc_writel(adc_dev, REG_CTRL, config | CNTRLREG_TSCSSENB);
> + }
> +
> + tiadc_writel(adc_dev, REG_IRQSTATUS, IRQENB_FIFO1THRES |
> + IRQENB_FIFO1OVRRUN | IRQENB_FIFO1UNDRFLW);
> + tiadc_writel(adc_dev, REG_IRQENABLE, IRQENB_FIFO1THRES
> + | IRQENB_FIFO1OVRRUN);
> +
> + iio_trigger_notify_done(indio_dev->trig);
Are you actually done? What happens if another trigger turns up in the
meantime? I'd expect this to occur only after the results of the trigger
have been handled.
> + return IRQ_HANDLED;
> +}
> +
> +static int tiadc_buffer_preenable(struct iio_dev *indio_dev)
> +{
Don't have pointless wrappers like this.
> + return iio_sw_buffer_preenable(indio_dev);
> +}
> +
> +static int tiadc_buffer_postenable(struct iio_dev *indio_dev)
> +{
> + struct tiadc_device *adc_dev = iio_priv(indio_dev);
> + struct iio_buffer *buffer = indio_dev->buffer;
> + unsigned int enb = 0, stepnum;
> + u8 bit;
> +
> + tiadc_step_config(indio_dev);
> + for_each_set_bit(bit, buffer->scan_mask,
> + adc_dev->channels) {
> + struct iio_chan_spec const *chan = indio_dev->channels + bit;
> + /*
> + * There are a total of 16 steps available
> + * that are shared between ADC and touchscreen.
> + * We start configuring from step 16 to 0 incase of
> + * ADC. Hence the relation between input channel
> + * and step for ADC would be as below.
> + */
> + stepnum = chan->channel + 9;
> + enb |= (1 << stepnum);
> + }
> + adc_dev->buffer_en_ch_steps = enb;
> +
> + return iio_triggered_buffer_postenable(indio_dev);
> +}
> +
> +static int tiadc_buffer_predisable(struct iio_dev *indio_dev)
> +{
> + struct tiadc_device *adc_dev = iio_priv(indio_dev);
> + int fifo1count, i, read, config;
> +
> + if (adc_dev->mfd_tscadc->tsc_cell == -1) {
> + config = tiadc_readl(adc_dev, REG_CTRL);
> + config &= ~(CNTRLREG_TSCSSENB);
> + tiadc_writel(adc_dev, REG_CTRL, config);
> + } else
> + tiadc_writel(adc_dev, REG_SE, STPENB_STEPENB_TC);
> +
> + tiadc_writel(adc_dev, REG_IRQCLR, (IRQENB_FIFO1THRES |
> + IRQENB_FIFO1OVRRUN | IRQENB_FIFO1UNDRFLW));
> +
> + /* Flush FIFO of any leftover data */
> + fifo1count = tiadc_readl(adc_dev, REG_FIFO1CNT);
> + for (i = 0; i < fifo1count; i++)
> + read = tiadc_readl(adc_dev, REG_FIFO1);
> +
> + return iio_triggered_buffer_predisable(indio_dev);
> +}
> +
> +static int tiadc_buffer_postdisable(struct iio_dev *indio_dev)
> +{
> + struct tiadc_device *adc_dev = iio_priv(indio_dev);
> + int config;
> +
> + tiadc_step_config(indio_dev);
> + if (adc_dev->mfd_tscadc->tsc_cell == -1) {
> + config = tiadc_readl(adc_dev, REG_CTRL);
> + tiadc_writel(adc_dev, REG_CTRL, (config | CNTRLREG_TSCSSENB));
> + }
> +
> + return 0;
> +}
> +
> +static const struct iio_buffer_setup_ops tiadc_buffer_setup_ops = {
> + .preenable = &tiadc_buffer_preenable,
> + .postenable = &tiadc_buffer_postenable,
> + .predisable = &tiadc_buffer_predisable,
> + .postdisable = &tiadc_buffer_postdisable,
> +};
> +
> +static void tiadc_adc_work(struct work_struct *work_s)
> +{
> + struct tiadc_device *adc_dev =
> + container_of(work_s, struct tiadc_device, poll_work);
> + struct iio_dev *indio_dev = iio_priv_to_dev(adc_dev);
> + struct iio_buffer *buffer = indio_dev->buffer;
> + int i, j, k, fifo1count, read;
> + unsigned int config;
So normally we'd just fill with one sample hence for this case
I'd expect to push out whatever samples are in the fifo right now
then return. The next trigger would grab the next lot etc.
> + int size_to_acquire = buffer->access->get_length(buffer);
> + int sample_count = 0;
> + u32 *data;
> +
> + adc_dev->data_avail = 0;
> + data = kmalloc(indio_dev->scan_bytes, GFP_KERNEL);
> + if (data == NULL)
> + goto out;
> +
> + while (sample_count < size_to_acquire) {
> + tiadc_writel(adc_dev, REG_IRQSTATUS, IRQENB_FIFO1THRES);
> + tiadc_writel(adc_dev, REG_IRQENABLE, IRQENB_FIFO1THRES);
> +
> + wait_event_interruptible(adc_dev->wq_data_avail,
> + (adc_dev->data_avail == 1));
> + adc_dev->data_avail = 0;
> +
> + fifo1count = tiadc_readl(adc_dev, REG_FIFO1CNT);
> + if (fifo1count * sizeof(u32) <
> + buffer->access->get_bytes_per_datum(buffer))
> + continue;
> +
> + sample_count = sample_count + fifo1count;
> + for (k = 0; k < fifo1count; k = k + i) {
> + for (i = 0, j = 0; i < (indio_dev->scan_bytes)/4; i++) {
> + read = tiadc_readl(adc_dev, REG_FIFO1);
> + data[i] = read & FIFOREAD_DATA_MASK;
> + }
> + iio_push_to_buffers(indio_dev, (u8 *) data);
> + }
> + }
> +out:
> + tiadc_writel(adc_dev, REG_IRQCLR, (IRQENB_FIFO1THRES |
> + IRQENB_FIFO1OVRRUN | IRQENB_FIFO1UNDRFLW));
> + am335x_tsc_se_clr(adc_dev->mfd_tscadc, adc_dev->buffer_en_ch_steps);
> + if (adc_dev->mfd_tscadc->tsc_cell == -1) {
> + config = tiadc_readl(adc_dev, REG_CTRL);
> + tiadc_writel(adc_dev, REG_CTRL, config & ~CNTRLREG_TSCSSENB);
> + }
> +}
> +
> +irqreturn_t tiadc_iio_pollfunc(int irq, void *p)
> +{
> + struct iio_poll_func *pf = p;
> + struct iio_dev *indio_dev = pf->indio_dev;
> + struct tiadc_device *adc_dev = iio_priv(indio_dev);
> + int i, fifo1count, read;
> +
> + tiadc_writel(adc_dev, REG_IRQCLR, (IRQENB_FIFO1THRES |
> + IRQENB_FIFO1OVRRUN |
> + IRQENB_FIFO1UNDRFLW));
> +
> + /* Flush FIFO before trigger */
> + fifo1count = tiadc_readl(adc_dev, REG_FIFO1CNT);
> + for (i = 0; i < fifo1count; i++)
> + read = tiadc_readl(adc_dev, REG_FIFO1);
>
> + return IRQ_WAKE_THREAD;
> }
>
> static const char * const chan_name_ain[] = {
> @@ -120,13 +329,13 @@ static int tiadc_channel_init(struct iio_dev *indio_dev, int channels)
> chan->channel = adc_dev->channel_line[i];
> chan->info_mask_separate = BIT(IIO_CHAN_INFO_RAW);
> chan->datasheet_name = chan_name_ain[chan->channel];
> + chan->scan_index = i;
> chan->scan_type.sign = 'u';
> chan->scan_type.realbits = 12;
> chan->scan_type.storagebits = 32;
> }
>
> indio_dev->channels = chan_array;
> -
> return 0;
> }
>
> @@ -141,58 +350,51 @@ static int tiadc_read_raw(struct iio_dev *indio_dev,
> {
> struct tiadc_device *adc_dev = iio_priv(indio_dev);
> int i, map_val;
> - unsigned int fifo1count, read, stepid;
> - u32 step = UINT_MAX;
> - bool found = false;
> - u32 step_en;
> - unsigned long timeout = jiffies + usecs_to_jiffies
> - (IDLE_TIMEOUT * adc_dev->channels);
> - step_en = get_adc_step_mask(adc_dev);
> - am335x_tsc_se_set(adc_dev->mfd_tscadc, step_en);
> -
> - /* Wait for ADC sequencer to complete sampling */
> - while (tiadc_readl(adc_dev, REG_ADCFSM) & SEQ_STATUS) {
> - if (time_after(jiffies, timeout))
> - return -EAGAIN;
> - }
> - map_val = chan->channel + TOTAL_CHANNELS;
> -
> - /*
> - * When the sub-system is first enabled,
> - * the sequencer will always start with the
> - * lowest step (1) and continue until step (16).
> - * For ex: If we have enabled 4 ADC channels and
> - * currently use only 1 out of them, the
> - * sequencer still configures all the 4 steps,
> - * leading to 3 unwanted data.
> - * Hence we need to flush out this data.
> - */
> -
> - for (i = 0; i < ARRAY_SIZE(adc_dev->channel_step); i++) {
> - if (chan->channel == adc_dev->channel_line[i]) {
> - step = adc_dev->channel_step[i];
> - break;
> - }
> - }
> - if (WARN_ON_ONCE(step == UINT_MAX))
> - return -EINVAL;
> -
> - fifo1count = tiadc_readl(adc_dev, REG_FIFO1CNT);
> - for (i = 0; i < fifo1count; i++) {
> - read = tiadc_readl(adc_dev, REG_FIFO1);
> - stepid = read & FIFOREAD_CHNLID_MASK;
> - stepid = stepid >> 0x10;
> + unsigned int fifo1count, read, stepid, step_en;
>
> - if (stepid == map_val) {
> - read = read & FIFOREAD_DATA_MASK;
> - found = true;
> - *val = read;
> + if (iio_buffer_enabled(indio_dev))
> + return -EBUSY;
> + else {
> + unsigned long timeout = jiffies + usecs_to_jiffies
> + (IDLE_TIMEOUT * adc_dev->channels);
> + step_en = get_adc_step_mask(adc_dev);
> + am335x_tsc_se_set(adc_dev->mfd_tscadc, step_en);
> +
> + /* Wait for ADC sequencer to complete sampling */
> + while (tiadc_readl(adc_dev, REG_ADCFSM) & SEQ_STATUS) {
> + if (time_after(jiffies, timeout))
> + return -EAGAIN;
> + }
> + map_val = chan->channel + TOTAL_CHANNELS;
> +
> + /*
> + * When the sub-system is first enabled,
> + * the sequencer will always start with the
> + * lowest step (1) and continue until step (16).
> + * For ex: If we have enabled 4 ADC channels and
> + * currently use only 1 out of them, the
> + * sequencer still configures all the 4 steps,
> + * leading to 3 unwanted data.
> + * Hence we need to flush out this data.
> + */
> +
> + *val = -1;
> + fifo1count = tiadc_readl(adc_dev, REG_FIFO1CNT);
> + for (i = 0; i < fifo1count; i++) {
> + read = tiadc_readl(adc_dev, REG_FIFO1);
> + stepid = read & FIFOREAD_CHNLID_MASK;
> + stepid = stepid >> 0x10;
> +
> + if (stepid == map_val) {
> + read = read & FIFOREAD_DATA_MASK;
> + *val = read;
> + }
> }
> + if (*val != -1)
> + return IIO_VAL_INT;
> + else
> + return -EAGAIN;
> }
> -
> - if (found == false)
> - return -EBUSY;
> - return IIO_VAL_INT;
> }
>
> static const struct iio_info tiadc_info = {
> @@ -231,26 +433,45 @@ static int tiadc_probe(struct platform_device *pdev)
> channels++;
> }
> adc_dev->channels = channels;
> + adc_dev->irq = adc_dev->mfd_tscadc->irq;
>
> indio_dev->dev.parent = &pdev->dev;
> indio_dev->name = dev_name(&pdev->dev);
> indio_dev->modes = INDIO_DIRECT_MODE;
> indio_dev->info = &tiadc_info;
>
> - tiadc_step_config(adc_dev);
> + tiadc_step_config(indio_dev);
> + tiadc_writel(adc_dev, REG_FIFO1THR, FIFO1_THRESHOLD);
>
> err = tiadc_channel_init(indio_dev, adc_dev->channels);
> if (err < 0)
> goto err_free_device;
>
> - err = iio_device_register(indio_dev);
> + INIT_WORK(&adc_dev->poll_work, &tiadc_adc_work);
> + init_waitqueue_head(&adc_dev->wq_data_avail);
> +
> + err = request_irq(adc_dev->irq, tiadc_irq, IRQF_SHARED,
> + indio_dev->name, indio_dev);
> if (err)
> goto err_free_channels;
>
> + err = iio_triggered_buffer_setup(indio_dev, &tiadc_iio_pollfunc,
> + &tiadc_trigger_h, &tiadc_buffer_setup_ops);
> + if (err)
> + goto err_free_irq;
> +
> + err = iio_device_register(indio_dev);
> + if (err)
> + goto err_buffer_unregister;
> +
> platform_set_drvdata(pdev, indio_dev);
>
> return 0;
>
> +err_buffer_unregister:
> + iio_buffer_unregister(indio_dev);
> +err_free_irq:
> + free_irq(adc_dev->irq, indio_dev);
> err_free_channels:
> tiadc_channels_remove(indio_dev);
> err_free_device:
> @@ -265,7 +486,9 @@ static int tiadc_remove(struct platform_device *pdev)
> struct tiadc_device *adc_dev = iio_priv(indio_dev);
> u32 step_en;
>
> + free_irq(adc_dev->irq, indio_dev);
> iio_device_unregister(indio_dev);
> + iio_buffer_unregister(indio_dev);
> tiadc_channels_remove(indio_dev);
>
> step_en = get_adc_step_mask(adc_dev);
> @@ -303,10 +526,16 @@ static int tiadc_resume(struct device *dev)
>
> /* Make sure ADC is powered up */
> restore = tiadc_readl(adc_dev, REG_CTRL);
> - restore &= ~(CNTRLREG_POWERDOWN);
> + restore &= ~(CNTRLREG_TSCSSENB);
> tiadc_writel(adc_dev, REG_CTRL, restore);
>
> - tiadc_step_config(adc_dev);
> + tiadc_writel(adc_dev, REG_FIFO1THR, FIFO1_THRESHOLD);
> + tiadc_step_config(indio_dev);
> +
> + /* Make sure ADC is powered up */
> + restore &= ~(CNTRLREG_POWERDOWN);
> + restore |= CNTRLREG_TSCSSENB;
> + tiadc_writel(adc_dev, REG_CTRL, restore);
>
> return 0;
> }
> diff --git a/include/linux/mfd/ti_am335x_tscadc.h b/include/linux/mfd/ti_am335x_tscadc.h
> index e2db978..14f75a4 100644
> --- a/include/linux/mfd/ti_am335x_tscadc.h
> +++ b/include/linux/mfd/ti_am335x_tscadc.h
> @@ -46,6 +46,9 @@
> /* Step Enable */
> #define STEPENB_MASK (0x1FFFF << 0)
> #define STEPENB(val) ((val) << 0)
> +#define ENB(val) (1 << (val))
> +#define STPENB_STEPENB STEPENB(0x1FFFF)
> +#define STPENB_STEPENB_TC STEPENB(0x1FFF)
>
> /* IRQ enable */
> #define IRQENB_HW_PEN BIT(0)
> @@ -53,12 +56,15 @@
> #define IRQENB_FIFO0OVRRUN BIT(3)
> #define IRQENB_FIFO0UNDRFLW BIT(4)
> #define IRQENB_FIFO1THRES BIT(5)
> +#define IRQENB_FIFO1OVRRUN BIT(6)
> +#define IRQENB_FIFO1UNDRFLW BIT(7)
> #define IRQENB_PENUP BIT(9)
>
> /* Step Configuration */
> #define STEPCONFIG_MODE_MASK (3 << 0)
> #define STEPCONFIG_MODE(val) ((val) << 0)
> #define STEPCONFIG_MODE_HWSYNC STEPCONFIG_MODE(2)
> +#define STEPCONFIG_MODE_SWCNT STEPCONFIG_MODE(1)
> #define STEPCONFIG_AVG_MASK (7 << 2)
> #define STEPCONFIG_AVG(val) ((val) << 2)
> #define STEPCONFIG_AVG_16 STEPCONFIG_AVG(4)
> @@ -126,6 +132,8 @@
> #define MAX_CLK_DIV 7
> #define TOTAL_STEPS 16
> #define TOTAL_CHANNELS 8
> +#define FIFO1_THRESHOLD 19
> +#define FIFO_SIZE 64
>
> /*
> * ADC runs at 3MHz, and it takes
> @@ -155,6 +163,10 @@ struct ti_tscadc_dev {
>
> /* adc device */
> struct adc_device *adc;
> +
> + /* Context save */
> + unsigned int irqstat;
> + unsigned int ctrl;
> };
>
> static inline struct ti_tscadc_dev *ti_tscadc_dev_get(struct platform_device *p)
>
^ permalink raw reply
* Re: [PATCH 1/1] HID: hidraw: Add spinlock in struct hidraw to protect list
From: Yonghua Zheng @ 2013-08-15 14:57 UTC (permalink / raw)
To: Jiri Kosina; +Cc: linux-input
In-Reply-To: <CAMyfujdB0JrnNeHk3b=AqR+1nz_W=bzDJpk1=iYZBnytDk-ztw@mail.gmail.com>
Retitle
Hi Jiri,
As hidraw_report_event can be called from interrupt context, it is a mistake
to use mutex_lock for protecting the list member in my previous patch, so
update the patch which adds a spinlock in struct hidraw to protect the list
member from concurrent access:
>From 21e3dfdf124065dac8bb56f4a7ed48d20870323b Mon Sep 17 00:00:00 2001
From: Yonghua Zheng <younghua.zheng@gmail.com>
Date: Wed, 14 Aug 2013 17:43:36 +0800
Subject: [PATCH 1/1] HID: hidraw: Add spinlock in struct hidraw to protect
list
It is unsafe to call list_for_each_entry in hidraw_report_event to
traverse each hidraw_list node without a lock protection, the list
could be modified if someone calls hidraw_release and list_del to
remove itself from the list, this can cause hidraw_report_event
to touch a deleted list struct and panic.
To prevent this, introduce a spinlock in struct hidraw to protect
list from concurrent access.
Signed-off-by: Yonghua Zheng <younghua.zheng@gmail.com>
diff --git a/drivers/hid/hidraw.c b/drivers/hid/hidraw.c
index 6f1feb2..a5372b8 100644
--- a/drivers/hid/hidraw.c
+++ b/drivers/hid/hidraw.c
@@ -253,6 +253,7 @@ static int hidraw_open(struct inode *inode, struct file *file)
unsigned int minor = iminor(inode);
struct hidraw *dev;
struct hidraw_list *list;
+ unsigned long flags;
int err = 0;
if (!(list = kzalloc(sizeof(struct hidraw_list), GFP_KERNEL))) {
@@ -266,11 +267,6 @@ static int hidraw_open(struct inode *inode, struct file *file)
goto out_unlock;
}
- list->hidraw = hidraw_table[minor];
- mutex_init(&list->read_mutex);
- list_add_tail(&list->node, &hidraw_table[minor]->list);
- file->private_data = list;
-
dev = hidraw_table[minor];
if (!dev->open++) {
err = hid_hw_power(dev->hid, PM_HINT_FULLON);
@@ -283,9 +279,16 @@ static int hidraw_open(struct inode *inode, struct file *file)
if (err < 0) {
hid_hw_power(dev->hid, PM_HINT_NORMAL);
dev->open--;
+ goto out_unlock;
}
}
+ list->hidraw = hidraw_table[minor];
+ mutex_init(&list->read_mutex);
+ spin_lock_irqsave(&hidraw_table[minor]->list_lock, flags);
+ list_add_tail(&list->node, &hidraw_table[minor]->list);
+ spin_unlock_irqrestore(&hidraw_table[minor]->list_lock, flags);
+ file->private_data = list;
out_unlock:
mutex_unlock(&minors_lock);
out:
@@ -309,6 +312,7 @@ static int hidraw_release(struct inode * inode, struct file * file)
struct hidraw_list *list = file->private_data;
int ret;
int i;
+ unsigned long flags;
mutex_lock(&minors_lock);
if (!hidraw_table[minor]) {
@@ -316,8 +320,10 @@ static int hidraw_release(struct inode * inode, struct file * file)
goto unlock;
}
- list_del(&list->node);
dev = hidraw_table[minor];
+ spin_lock_irqsave(&dev->list_lock, flags);
+ list_del(&list->node);
+ spin_unlock_irqrestore(&dev->list_lock, flags);
if (!--dev->open) {
if (list->hidraw->exist) {
hid_hw_power(dev->hid, PM_HINT_NORMAL);
@@ -457,7 +463,9 @@ int hidraw_report_event(struct hid_device *hid, u8 *data, int len)
struct hidraw *dev = hid->hidraw;
struct hidraw_list *list;
int ret = 0;
+ unsigned long flags;
+ spin_lock_irqsave(&dev->list_lock, flags);
list_for_each_entry(list, &dev->list, node) {
int new_head = (list->head + 1) & (HIDRAW_BUFFER_SIZE - 1);
@@ -468,10 +476,12 @@ int hidraw_report_event(struct hid_device *hid, u8 *data, int len)
ret = -ENOMEM;
break;
}
+
list->buffer[list->head].len = len;
list->head = new_head;
kill_fasync(&list->fasync, SIGIO, POLL_IN);
}
+ spin_unlock_irqrestore(&dev->list_lock, flags);
wake_up_interruptible(&dev->wait);
return ret;
@@ -519,6 +529,7 @@ int hidraw_connect(struct hid_device *hid)
}
init_waitqueue_head(&dev->wait);
+ spin_lock_init(&dev->list_lock);
INIT_LIST_HEAD(&dev->list);
dev->hid = hid;
diff --git a/include/linux/hidraw.h b/include/linux/hidraw.h
index 2451662..ddf5261 100644
--- a/include/linux/hidraw.h
+++ b/include/linux/hidraw.h
@@ -23,6 +23,7 @@ struct hidraw {
wait_queue_head_t wait;
struct hid_device *hid;
struct device *dev;
+ spinlock_t list_lock;
struct list_head list;
};
--
1.7.9.5
Best regards
Yonghua
On Fri, Aug 09, 2013 at 04:45:19PM +0800, yonghua zheng wrote:
> Hi Jiri,
>
> We met another kernel panic issue in hidraw_report_event while using
> BT remote on our Android system, the panic log below shows a unhandle
> kernel paging request at virtual address 001000f0, this can happen if
> hidraw_report_event access a hidraw_list node which is already removed
> by list_del() in hidraw_release.
>
> One last thing list_del() does is set entry->next = LIST_POISON1;
> where LIST_POISON1 is 00100100, and this should be able to explain why
> panic address is 001000f0, hidraw_report_event try to access somewhere
> in struct hidraw_list:
>
> [ 1337.530941] Unable to handle kernel paging request at virtual
> address 001000f0
> [ 1337.538679] pgd = a0570000
> [ 1337.541877] [001000f0] *pgd=00000000
> [ 1337.545884] Internal error: Oops: 5 [#1] PREEMPT SMP ARM
> [ 1337.551373] Modules linked in: mbt8787 fusion(O) gal3d amp_core(O)
> [ 1337.557834] CPU: 0 Tainted: G O (3.4.50+ #1)
> [ 1337.563512] PC is at hidraw_report_event+0x38/0x98
> [ 1337.568462] LR is at hidraw_report_event+0x70/0x98
> [ 1337.573412] pc : [<8035286c>] lr : [<803528a4>] psr: a0000013
> [ 1337.573416] sp : a0eede30 ip : a0eede30 fp : a0eede54
> [ 1337.575086] [amp_driver] [vpp isr] MsgQ full
> [ 1337.589671] r10: a0b7e4c0 r9 : a035b800 r8 : a0bb002c
> [ 1337.595068] r7 : a0b7e4e4 r6 : 00000001 r5 : 0000001f r4 : 000ffef0
> [ 1337.601809] r3 : a36db808 r2 : 00000020 r1 : 0000001f r0 : a0bb002c
> [ 1337.608553] Flags: NzCv IRQs on FIQs on Mode SVC_32 ISA ARM Segment user
> [ 1337.615923] Control: 10c5387d Table: 2157004a DAC: 00000015
>
> So the fix is to grab the minors_lock to avoid list_del in the middle
> of hidraw_report_event()
>
> From 725ed6c402f36e34479739a8d6c68f78f2f96d31 Mon Sep 17 00:00:00 2001
> From: Yonghua Zheng <younghua.zheng@gmail.com>
> Date: Fri, 9 Aug 2013 15:54:59 +0800
> Subject: [PATCH 1/1] HID: hidraw: need to grab minors_lock in
> hidraw_report_event
>
> It is unsafe to call list_for_each_entry to go through each hidraw_list
> node without grabing the minors_lock, because the dev->list can be
> modified if other process calls hidraw_release to list_del itself from
> the same &dev->list, if this happens, it can result in kernel panic
> due to unable handle kernel paging request at invalid virtual address
>
> Signed-off-by: Yonghua Zheng <younghua.zheng@gmail.com>
> ---
> drivers/hid/hidraw.c | 3 +++
> 1 file changed, 3 insertions(+)
>
> diff --git a/drivers/hid/hidraw.c b/drivers/hid/hidraw.c
> index 6f1feb2..8b408c4 100644
> --- a/drivers/hid/hidraw.c
> +++ b/drivers/hid/hidraw.c
> @@ -458,6 +458,7 @@ int hidraw_report_event(struct hid_device *hid, u8
> *data, int len)
> struct hidraw_list *list;
> int ret = 0;
>
> + mutex_lock(&minors_lock);
> list_for_each_entry(list, &dev->list, node) {
> int new_head = (list->head + 1) & (HIDRAW_BUFFER_SIZE - 1);
>
> @@ -465,6 +466,7 @@ int hidraw_report_event(struct hid_device *hid, u8
> *data, int len)
> continue;
>
> if (!(list->buffer[list->head].value = kmemdup(data, len,
> GFP_ATOMIC))) {
> + mutex_unlock(&minors_lock);
> ret = -ENOMEM;
> break;
> }
> @@ -472,6 +474,7 @@ int hidraw_report_event(struct hid_device *hid, u8
> *data, int len)
> list->head = new_head;
> kill_fasync(&list->fasync, SIGIO, POLL_IN);
> }
> + mutex_unlock(&minors_lock);
>
> wake_up_interruptible(&dev->wait);
> return ret;
> --
> 1.7.9.5
>
> Best regards
> Yonghua
^ permalink raw reply related
* [PATCH v6 1/1] input: ideapad_slidebar: new input driver
From: Andrey Moiseev @ 2013-08-15 15:06 UTC (permalink / raw)
To: linux-input
Cc: dmitry.torokhov, arnoques, russianneuromancer, ike.pan,
linux-kernel
v6: fixed things addressed by Dmitry Torokhov to v5 (quoted message goes below the patch)
ideapad_slidebar is a new driver which enables slidebars on some
Lenovo IdeaPad laptops (the slidebars work with SlideNav/Desktop
Navigator under Windows)
Fixes this: https://bugzilla.kernel.org/show_bug.cgi?id=16004
Registers 'IdeaPad Slidebar' input device and
/sys/devices/platform/ideapad_slidebar/input/inputX/slidebar_mode
for switching slidebar's modes.
Now works on:
IdeaPad Y550, Y550P.
May work on (testing and adding new models is needed):
Ideapad Y560, Y460, Y450, Y650,
and, probably, some others.
Driver source: https://github.com/o2genum/ideapad-slidebar.git
Patch is generated against current mainline kernel.
Signed-off-by: Andrey Moiseev <o2g.org.ru@gmail.com>
---
MAINTAINERS | 7 +
drivers/input/misc/Kconfig | 9 +
drivers/input/misc/Makefile | 1 +
drivers/input/misc/ideapad_slidebar.c | 348 ++++++++++++++++++++++++++++++++++
4 files changed, 365 insertions(+)
create mode 100644 drivers/input/misc/ideapad_slidebar.c
diff --git a/MAINTAINERS b/MAINTAINERS
index defc053..2ff3dd8 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -4096,6 +4096,13 @@ W: http://launchpad.net/ideapad-laptop
S: Maintained
F: drivers/platform/x86/ideapad-laptop.c
+IDEAPAD LAPTOP SLIDEBAR DRIVER
+M: Andrey Moiseev <o2g.org.ru@gmail.com>
+L: linux-input@vger.kernel.org
+W: https://github.com/o2genum/ideapad-slidebar
+S: Maintained
+F: drivers/input/misc/ideapad_slidebar.c
+
IDE/ATAPI DRIVERS
M: Borislav Petkov <bp@alien8.de>
L: linux-ide@vger.kernel.org
diff --git a/drivers/input/misc/Kconfig b/drivers/input/misc/Kconfig
index 0b541cd..45729a9 100644
--- a/drivers/input/misc/Kconfig
+++ b/drivers/input/misc/Kconfig
@@ -647,4 +647,13 @@ config INPUT_SIRFSOC_ONKEY
If unsure, say N.
+config INPUT_IDEAPAD_SLIDEBAR
+ tristate "IdeaPad Laptop Slidebar"
+ depends on INPUT
+ help
+ Input driver for slidebars on some Lenovo IdeaPad laptops.
+
+ If you have an IdeaPad laptop with a slidebar, say Y or M here.
+ Module name is ideapad_slidebar.
+
endif
diff --git a/drivers/input/misc/Makefile b/drivers/input/misc/Makefile
index 829de43..0ebfb6d 100644
--- a/drivers/input/misc/Makefile
+++ b/drivers/input/misc/Makefile
@@ -61,3 +61,4 @@ obj-$(CONFIG_INPUT_WISTRON_BTNS) += wistron_btns.o
obj-$(CONFIG_INPUT_WM831X_ON) += wm831x-on.o
obj-$(CONFIG_INPUT_XEN_KBDDEV_FRONTEND) += xen-kbdfront.o
obj-$(CONFIG_INPUT_YEALINK) += yealink.o
+obj-$(CONFIG_INPUT_IDEAPAD_SLIDEBAR) += ideapad_slidebar.o
diff --git a/drivers/input/misc/ideapad_slidebar.c b/drivers/input/misc/ideapad_slidebar.c
new file mode 100644
index 0000000..5725d88
--- /dev/null
+++ b/drivers/input/misc/ideapad_slidebar.c
@@ -0,0 +1,348 @@
+/*
+ * Input driver for slidebars on some Lenovo IdeaPad laptops
+ *
+ * Copyright (C) 2013 Andrey Moiseev <o2g.org.ru@gmail.com>
+ *
+ * Reverse-engineered from Lenovo SlideNav software (SBarHook.dll).
+ *
+ * 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.
+ *
+ * Trademarks are the property of their respective owners.
+ */
+
+/*
+ * Currently tested and works on:
+ * Lenovo IdeaPad Y550
+ * Lenovo IdeaPad Y550P
+ *
+ * Other models can be added easily. To test,
+ * load with 'force' parameter set 'true'.
+ *
+ * LEDs blinking and input mode are managed via sysfs,
+ * (hex, unsigned byte value):
+ * /sys/devices/platform/ideapad_slidebar/input/inputX/slidebar_mode
+ *
+ * The value is in byte range, however, I only figured out
+ * how bits 0b10011001 work. Some other bits, probably,
+ * are meaningfull too.
+ *
+ * Possible states:
+ *
+ * STD_INT, ONMOV_INT, OFF_INT, LAST_POLL, OFF_POLL
+ *
+ * Meaning:
+ * released touched
+ * STD 'heartbeat' lights follow the finger
+ * ONMOV no lights lights follow the finger
+ * LAST at last pos lights follow the finger
+ * OFF no lights no lights
+ *
+ * INT all input events are generated, interrupts are used
+ * POLL no input events by default, to get them,
+ * send 0b10000000 (read below)
+ *
+ * Commands: write
+ *
+ * All | 0b01001 -> STD_INT
+ * possible | 0b10001 -> ONMOV_INT
+ * states | 0b01000 -> OFF_INT
+ *
+ * | 0b0 -> LAST_POLL
+ * STD_INT or ONMOV_INT |
+ * | 0b1 -> STD_INT
+ *
+ * | 0b0 -> OFF_POLL
+ * OFF_INT or OFF_POLL |
+ * | 0b1 -> OFF_INT
+ *
+ * Any state | 0b10000000 -> if the slidebar has updated data,
+ * produce one input event (last position),
+ * switch to respective POLL mode
+ * (like 0x0), if not in POLL mode yet.
+ *
+ * Get current state: read
+ *
+ * masked by 0x11 read value means:
+ *
+ * 0x00 LAST
+ * 0x01 STD
+ * 0x10 OFF
+ * 0x11 ONMOV
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/dmi.h>
+#include <linux/spinlock.h>
+#include <linux/platform_device.h>
+#include <linux/input.h>
+#include <linux/io.h>
+#include <linux/i8042.h>
+
+static bool force;
+module_param(force, bool, 0);
+MODULE_PARM_DESC(force, "Force driver load, ignore DMI data");
+
+static spinlock_t sio_lock = __SPIN_LOCK_UNLOCKED(sio_lock);
+
+static struct input_dev *slidebar_input_dev;
+static struct platform_device *slidebar_platform_dev;
+
+/* Hardware interacting */
+static unsigned char slidebar_pos_get(void)
+{
+ int res;
+ unsigned long flags;
+
+ spin_lock_irqsave(&sio_lock, flags);
+ outb(0xf4, 0xff29);
+ outb(0xbf, 0xff2a);
+ res = inb(0xff2b);
+ spin_unlock_irqrestore(&sio_lock, flags);
+ return res;
+}
+
+static unsigned char slidebar_mode_get(void)
+{
+ int res;
+ unsigned long flags;
+
+ spin_lock_irqsave(&sio_lock, flags);
+ outb(0xf7, 0xff29);
+ outb(0x8b, 0xff2a);
+ res = inb(0xff2b);
+ spin_unlock_irqrestore(&sio_lock, flags);
+ return res;
+}
+
+static void slidebar_mode_set(unsigned char mode)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&sio_lock, flags);
+ outb(0xf7, 0xff29);
+ outb(0x8b, 0xff2a);
+ outb(mode, 0xff2b);
+ spin_unlock_irqrestore(&sio_lock, flags);
+}
+
+/* Listening the keyboard (i8042 filter) */
+static bool slidebar_i8042_filter(unsigned char data, unsigned char str,
+ struct serio *port)
+{
+ static bool extended = false, touched = false;
+
+ /* Scancodes: e03b on move, e0bb on release */
+ if (unlikely(data == 0xe0)) {
+ extended = true;
+ return false;
+ } else if (unlikely(extended && (data == 0x3b))) {
+ extended = false;
+ if (!touched)
+ input_report_key(slidebar_input_dev, BTN_TOUCH, 1);
+ touched = true;
+ input_report_abs(slidebar_input_dev, ABS_X, slidebar_pos_get());
+ input_sync(slidebar_input_dev);
+ return false;
+ } else if (unlikely(extended && (data == 0xbb))) {
+ touched = false;
+ input_report_key(slidebar_input_dev, BTN_TOUCH, 0);
+ input_sync(slidebar_input_dev);
+ }
+ return false;
+}
+
+/* Sysfs slidebar_mode interface */
+static ssize_t show_slidebar_mode(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ return sprintf(buf, "%x\n", slidebar_mode_get());
+}
+
+static ssize_t store_slidebar_mode(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ int mode;
+
+ if (!count)
+ return 0;
+ if (sscanf(buf, "%x", &mode) != 1)
+ return -EINVAL;
+ slidebar_mode_set(mode);
+ return count;
+}
+
+static DEVICE_ATTR(slidebar_mode, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH,
+ show_slidebar_mode, store_slidebar_mode);
+
+static struct attribute *ideapad_attrs[] = {
+ &dev_attr_slidebar_mode.attr,
+ NULL
+};
+
+static struct attribute_group ideapad_attr_group = {
+ .attrs = ideapad_attrs
+};
+
+static const struct attribute_group *ideapad_attr_groups[] = {
+ &ideapad_attr_group,
+ NULL
+};
+
+/* Input device */
+static int __init setup_input_dev(void)
+{
+ int err;
+
+ slidebar_input_dev = input_allocate_device();
+ if (!slidebar_input_dev) {
+ pr_err("ideapad_slidebar: Not enough memory\n");
+ return -ENOMEM;
+ }
+
+ slidebar_input_dev->name = "IdeaPad Slidebar";
+ slidebar_input_dev->id.bustype = BUS_HOST;
+ slidebar_input_dev->dev.parent = &slidebar_platform_dev->dev;
+ slidebar_input_dev->dev.groups = ideapad_attr_groups;
+ input_set_capability(slidebar_input_dev, EV_KEY, BTN_TOUCH);
+ input_set_capability(slidebar_input_dev, EV_ABS, ABS_X);
+ input_set_abs_params(slidebar_input_dev, ABS_X, 0, 0xff, 0, 0);
+
+ err = i8042_install_filter(slidebar_i8042_filter);
+ if (err) {
+ pr_err("ideapad_slidebar: Can't install i8042 filter \n");
+ goto err_free_dev;
+ }
+ err = input_register_device(slidebar_input_dev);
+ if (err) {
+ pr_err("ideapad_slidebar: Failed to register device\n");
+ goto err_remove_filter;
+ }
+ return 0;
+
+err_remove_filter:
+ i8042_remove_filter(slidebar_i8042_filter);
+err_free_dev:
+ input_free_device(slidebar_input_dev);
+ return err;
+}
+
+static void remove_input_dev(void)
+{
+ i8042_remove_filter(slidebar_i8042_filter);
+ input_unregister_device(slidebar_input_dev);
+}
+
+/* Platform device */
+static int __init setup_platform_dev(void)
+{
+ int err;
+ slidebar_platform_dev = platform_device_alloc("ideapad_slidebar", -1);
+ if (!slidebar_platform_dev) {
+ pr_err("ideapad_slidebar: Not enough memory\n");
+ return -ENOMEM;
+ }
+ err = platform_device_add(slidebar_platform_dev);
+ if (err) {
+ pr_err("ideapad_slidebar: Failed to register plarform device\n");
+ goto err_free_platform_device;
+ }
+ return 0;
+
+err_free_platform_device:
+ platform_device_put(slidebar_platform_dev);
+ return err;
+}
+
+/* Platform driver */
+static struct platform_driver slidebar_drv = {
+ .driver = {
+ .name = "ideapad_slidebar",
+ .owner = THIS_MODULE,
+ },
+};
+
+static int __init register_platform_drv(void)
+{
+ int err;
+
+ err = platform_driver_register(&slidebar_drv);
+ if (err)
+ pr_err("ideapad_slidebar: Failed to register platform driver\n");
+
+ return err;
+}
+
+/* DMI */
+static int __init ideapad_dmi_check(const struct dmi_system_id *id)
+{
+ pr_info("ideapad_slidebar: Laptop model '%s'\n", id->ident);
+ return 1;
+}
+
+static const struct dmi_system_id ideapad_dmi[] __initconst = {
+ {
+ .ident = "Lenovo IdeaPad Y550",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "20017"),
+ DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo IdeaPad Y550")
+ },
+ .callback = ideapad_dmi_check
+ },
+ {
+ .ident = "Lenovo IdeaPad Y550P",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "20035"),
+ DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo IdeaPad Y550P")
+ },
+ .callback = ideapad_dmi_check
+ },
+ { NULL, }
+};
+MODULE_DEVICE_TABLE(dmi, ideapad_dmi);
+
+/* Init and cleanup */
+static int __init slidebar_init(void)
+{
+ int err;
+
+ if (!force && !dmi_check_system(ideapad_dmi))
+ return -ENODEV;
+ err = setup_platform_dev();
+ if (err)
+ return err;
+ err = register_platform_drv();
+ if (err)
+ goto err_unregister_platform_dev;
+ err = setup_input_dev();
+ if (err)
+ goto err_unregister_platform_drv;
+ return 0;
+
+err_unregister_platform_drv:
+ platform_driver_unregister(&slidebar_drv);
+err_unregister_platform_dev:
+ platform_device_unregister(slidebar_platform_dev);
+ return err;
+}
+
+static void __exit slidebar_exit(void)
+{
+ remove_input_dev();
+ platform_device_unregister(slidebar_platform_dev);
+ platform_driver_unregister(&slidebar_drv);
+}
+
+module_init(slidebar_init);
+module_exit(slidebar_exit);
+
+MODULE_AUTHOR("Andrey Moiseev <o2g.org.ru@gmail.com>");
+MODULE_DESCRIPTION("Slidebar input support for some Lenovo IdeaPad laptops");
+MODULE_LICENSE("GPL");
--
1.8.3.4
---
Quoted Re: to v5:
> Hi Andrey,
>
> On Mon, Aug 12, 2013 at 02:23:23PM +0400, Andrey Moiseev wrote:
>> v5: __initconst goes after var name
>>
>> ideapad_slidebar is a new driver which enables slidebars on some
>> Lenovo IdeaPad laptops (the slidebars work with SlideNav/Desktop
>> Navigator under Windows)
>>
>> Fixes this: https://bugzilla.kernel.org/show_bug.cgi?id=16004
>>
>> Registers 'IdeaPad Slidebar' input device and
>> /sys/devices/platform/ideapad_slidebar/slidebar_mode
>> for switching slidebar's modes.
>>
>> Now works on:
>> IdeaPad Y550, Y550P.
>>
>> May work on (testing and adding new models is needed):
>> Ideapad Y560, Y460, Y450, Y650,
>> and, probably, some others.
>>
>> Driver source: https://github.com/o2genum/ideapad-slidebar.git
>>
>> Patch is generated against current mainline kernel.
>>
>> Signed-off-by: Andrey Moiseev <o2g.org.ru@gmail.com>
>> ---
>> MAINTAINERS | 7 +
>> drivers/input/misc/Kconfig | 9 +
>> drivers/input/misc/Makefile | 1 +
>> drivers/input/misc/ideapad_slidebar.c | 380 ++++++++++++++++++++++++++++++++++
>> 4 files changed, 397 insertions(+)
>> create mode 100644 drivers/input/misc/ideapad_slidebar.c
>>
>> diff --git a/MAINTAINERS b/MAINTAINERS
>> index defc053..2ff3dd8 100644
>> --- a/MAINTAINERS
>> +++ b/MAINTAINERS
>> @@ -4096,6 +4096,13 @@ W: http://launchpad.net/ideapad-laptop
>> S: Maintained
>> F: drivers/platform/x86/ideapad-laptop.c
>>
>> +IDEAPAD LAPTOP SLIDEBAR DRIVER
>> +M: Andrey Moiseev <o2g.org.ru@gmail.com>
>> +L: linux-input@vger.kernel.org
>> +W: https://github.com/o2genum/ideapad-slidebar
>> +S: Maintained
>> +F: drivers/input/misc/ideapad_slidebar.c
>> +
>> IDE/ATAPI DRIVERS
>> M: Borislav Petkov <bp@alien8.de>
>> L: linux-ide@vger.kernel.org
>> diff --git a/drivers/input/misc/Kconfig b/drivers/input/misc/Kconfig
>> index 0b541cd..45729a9 100644
>> --- a/drivers/input/misc/Kconfig
>> +++ b/drivers/input/misc/Kconfig
>> @@ -647,4 +647,13 @@ config INPUT_SIRFSOC_ONKEY
>>
>> If unsure, say N.
>>
>> +config INPUT_IDEAPAD_SLIDEBAR
>> + tristate "IdeaPad Laptop Slidebar"
>> + depends on INPUT
>> + help
>> + Input driver for slidebars on some Lenovo IdeaPad laptops.
>> +
>> + If you have an IdeaPad laptop with a slidebar, say Y or M here.
>> + Module name is ideapad_slidebar.
>> +
>> endif
>> diff --git a/drivers/input/misc/Makefile b/drivers/input/misc/Makefile
>> index 829de43..0ebfb6d 100644
>> --- a/drivers/input/misc/Makefile
>> +++ b/drivers/input/misc/Makefile
>> @@ -61,3 +61,4 @@ obj-$(CONFIG_INPUT_WISTRON_BTNS) += wistron_btns.o
>> obj-$(CONFIG_INPUT_WM831X_ON) += wm831x-on.o
>> obj-$(CONFIG_INPUT_XEN_KBDDEV_FRONTEND) += xen-kbdfront.o
>> obj-$(CONFIG_INPUT_YEALINK) += yealink.o
>> +obj-$(CONFIG_INPUT_IDEAPAD_SLIDEBAR) += ideapad_slidebar.o
>> diff --git a/drivers/input/misc/ideapad_slidebar.c b/drivers/input/misc/ideapad_slidebar.c
>> new file mode 100644
>> index 0000000..df437a4
>> --- /dev/null
>> +++ b/drivers/input/misc/ideapad_slidebar.c
>> @@ -0,0 +1,380 @@
>> +/*
>> + * Input driver for slidebars on some Lenovo IdeaPad laptops
>> + *
>> + * Copyright (C) 2013 Andrey Moiseev <o2g.org.ru@gmail.com>
>> + *
>> + * Reverse-engineered from Lenovo SlideNav software (SBarHook.dll).
>> + *
>> + * 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.
>> + *
>> + * Trademarks are the property of their respective owners.
>> + */
>> +
>> +/*
>> + * Currently tested and works on:
>> + * Lenovo IdeaPad Y550
>> + * Lenovo IdeaPad Y550P
>> + *
>> + * Other models can be added easily. To test,
>> + * load with 'force' parameter set 'true'.
>> + *
>> + * LEDs blinking and input mode are managed via sysfs,
>> + * (hex, unsigned byte value):
>> + * /sys/devices/platform/ideapad_slidebar/slidebar_mode
>> + *
>> + * The value is in byte range, however, I only figured out
>> + * how bits 0b10011001 work. Some other bits, probably,
>> + * are meaningfull too.
>> + *
>> + * Possible states:
>> + *
>> + * STD_INT, ONMOV_INT, OFF_INT, LAST_POLL, OFF_POLL
>> + *
>> + * Meaning:
>> + * released touched
>> + * STD 'heartbeat' lights follow the finger
>> + * ONMOV no lights lights follow the finger
>> + * LAST at last pos lights follow the finger
>> + * OFF no lights no lights
>> + *
>> + * INT all input events are generated, interrupts are used
>> + * POLL no input events by default, to get them,
>> + * send 0b10000000 (read below)
>> + *
>> + * Commands: write
>> + *
>> + * All | 0b01001 -> STD_INT
>> + * possible | 0b10001 -> ONMOV_INT
>> + * states | 0b01000 -> OFF_INT
>> + *
>> + * | 0b0 -> LAST_POLL
>> + * STD_INT or ONMOV_INT |
>> + * | 0b1 -> STD_INT
>> + *
>> + * | 0b0 -> OFF_POLL
>> + * OFF_INT or OFF_POLL |
>> + * | 0b1 -> OFF_INT
>> + *
>> + * Any state | 0b10000000 -> if the slidebar has updated data,
>> + * produce one input event (last position),
>> + * switch to respective POLL mode
>> + * (like 0x0), if not in POLL mode yet.
>> + *
>> + * Get current state: read
>> + *
>> + * masked by 0x11 read value means:
>> + *
>> + * 0x00 LAST
>> + * 0x01 STD
>> + * 0x10 OFF
>> + * 0x11 ONMOV
>> + */
>> +
>> +#include <linux/module.h>
>> +#include <linux/kernel.h>
>> +#include <linux/dmi.h>
>> +#include <linux/spinlock.h>
>> +#include <linux/platform_device.h>
>> +#include <linux/input.h>
>> +#include <linux/io.h>
>> +#include <linux/i8042.h>
>> +
>> +static bool force;
>> +module_param(force, bool, 0);
>> +MODULE_PARM_DESC(force, "Force driver load, ignore DMI data");
>> +
>> +static spinlock_t sio_lock = __SPIN_LOCK_UNLOCKED(sio_lock);
>> +
>> +static struct input_dev *slidebar_input_dev;
>> +static struct platform_device *slidebar_platform_dev;
>> +
>> +/* Hardware interacting */
>> +static int slidebar_pos_get(void)
>> +{
>> + int res;
>> + unsigned long flags;
>
> A blank line between variables and code is appreciated.
>
>> + spin_lock_irqsave(&sio_lock, flags);
>> + outb(0xf4, 0xff29);
>> + outb(0xbf, 0xff2a);
>> + res = inb(0xff2b);
>> + spin_unlock_irqrestore(&sio_lock, flags);
>> + return res;
>> +}
>> +
>> +static unsigned char slidebar_mode_get(void)
>> +{
>> + int res;
>> + unsigned long flags;
>> + spin_lock_irqsave(&sio_lock, flags);
>> + outb(0xf7, 0xff29);
>> + outb(0x8b, 0xff2a);
>> + res = inb(0xff2b);
>> + spin_unlock_irqrestore(&sio_lock, flags);
>> + return res;
>> +}
>> +
>> +static void slidebar_mode_set(unsigned char mode)
>> +{
>> + unsigned long flags;
>> + spin_lock_irqsave(&sio_lock, flags);
>> + outb(0xf7, 0xff29);
>> + outb(0x8b, 0xff2a);
>> + outb(mode, 0xff2b);
>> + spin_unlock_irqrestore(&sio_lock, flags);
>> +}
>> +
>> +/* Listening the keyboard (i8042 filter) */
>> +static bool slidebar_i8042_filter(unsigned char data, unsigned char str,
>> + struct serio *port)
>> +{
>> + static bool extended = false, touched = false;
>> +
>> + /* Scancodes: e03b on move, e0bb on release */
>> + if (unlikely(data == 0xe0)) {
>> + extended = true;
>> + return false;
>> + } else if (unlikely(extended && (data == 0x3b))) {
>> + extended = false;
>> + if (!touched)
>> + input_report_key(slidebar_input_dev, BTN_TOUCH, 1);
>> + touched = true;
>> + input_report_abs(slidebar_input_dev, ABS_X, slidebar_pos_get());
>> + input_sync(slidebar_input_dev);
>> + return false;
>> + } else if (unlikely(extended && (data == 0xbb))) {
>> + touched = false;
>> + input_report_key(slidebar_input_dev, BTN_TOUCH, 0);
>> + input_sync(slidebar_input_dev);
>> + }
>> +
>> + return false;
>> +}
>> +
>> +/* Input device */
>> +static int setup_input_dev(void)
>> +{
>> + int err;
>> +
>> + slidebar_input_dev = input_allocate_device();
>> + if (!slidebar_input_dev) {
>> + pr_err("ideapad_slidebar: Not enough memory\n");
>> + return -ENOMEM;
>> + }
>> +
>> + slidebar_input_dev->name = "IdeaPad Slidebar";
>> + slidebar_input_dev->id.bustype = BUS_HOST;
>> + slidebar_input_dev->dev.parent = &slidebar_platform_dev->dev;
>> + input_set_capability(slidebar_input_dev, EV_KEY, BTN_TOUCH);
>> + input_set_capability(slidebar_input_dev, EV_ABS, ABS_X);
>> + input_alloc_absinfo(slidebar_input_dev);
>
> This call is not needed, it will be done for you.
>
>> + input_set_abs_params(slidebar_input_dev, ABS_X, 0, 0xff, 0, 0);
>> +
>> + err = input_register_device(slidebar_input_dev);
>> + if (err) {
>> + pr_err("ideapad_slidebar: Failed to register device\n");
>> + goto err_free_dev;
>> + }
>> +
>> + err = i8042_install_filter(slidebar_i8042_filter);
>> + if (err) {
>> + pr_err("ideapad_slidebar: Can't install i8042 filter \n");
>> + goto err_unregister_dev;
>> + }
>> + return 0;
>> +
>> +err_unregister_dev:
>> + input_unregister_device(slidebar_input_dev);
>> +err_free_dev:
>> + input_free_device(slidebar_input_dev);
>
> No free after unregister for input devices. Consider swapping
> registering device and installing the filter (allocated but not
> registered input device can take events emitted by the filter just
> fine).
>
>> + return err;
>> +}
>> +
>> +static void remove_input_dev(void)
>> +{
>> + i8042_remove_filter(slidebar_i8042_filter);
>> + input_unregister_device(slidebar_input_dev);
>> + input_free_device(slidebar_input_dev);
>
> No free after unregister for input devices.
>
>> +}
>> +
>> +/* Sysfs slidebar_mode interface */
>> +static ssize_t show_slidebar_mode(struct device *dev,
>> + struct device_attribute *attr,
>> + char *buf)
>> +{
>> + return sprintf(buf, "%x\n", slidebar_mode_get());
>> +}
>> +
>> +static ssize_t store_slidebar_mode(struct device *dev,
>> + struct device_attribute *attr,
>> + const char *buf, size_t count)
>> +{
>> + int mode;
>> + if (!count)
>> + return 0;
>> +
>> + if (sscanf(buf, "%x", &mode) != 1)
>> + return -EINVAL;
>> +
>> + slidebar_mode_set(mode);
>> + return count;
>> +}
>> +
>> +static DEVICE_ATTR(slidebar_mode, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH,
>> + show_slidebar_mode, store_slidebar_mode);
>> +
>> +static struct attribute *ideapad_attributes[] = {
>> + &dev_attr_slidebar_mode.attr,
>> + NULL
>> +};
>> +
>> +static struct attribute_group ideapad_attribute_group = {
>> + .attrs = ideapad_attributes
>> +};
>> +
>> +static int setup_slidebar_mode_dev(void)
>> +{
>> + return sysfs_create_group(&slidebar_platform_dev->dev.kobj,
>> + &ideapad_attribute_group);
>
> To avoid races between creating device and attributes you want to simply
> assign your group to the devices group pointer and then it will be
> created for you by device core.
>
>> +}
>> +
>> +static void remove_slidebar_mode_dev(void)
>> +{
>> + return sysfs_remove_group(&slidebar_platform_dev->dev.kobj,
>> + &ideapad_attribute_group);
>> +}
>> +
>> +/* Platform device */
>> +static int setup_platform_dev(void)
>> +{
>> + int err;
>> + slidebar_platform_dev = platform_device_alloc("ideapad_slidebar", -1);
>> + if (!slidebar_platform_dev) {
>> + pr_err("ideapad_slidebar: Not enough memory\n");
>> + return -ENOMEM;
>> + }
>> +
>> + err = platform_device_add(slidebar_platform_dev);
>> + if (err) {
>> + pr_err("ideapad_slidebar: Failed to register plarform device\n");
>> + goto err_free_platform_device;
>> + }
>> + return 0;
>> +
>> +err_free_platform_device:
>> + platform_device_put(slidebar_platform_dev);
>> + return err;
>> +}
>> +
>> +static void remove_platform_dev(void)
>> +{
>> + platform_device_del(slidebar_platform_dev);
>> + platform_device_put(slidebar_platform_dev);
>
> I do nto understand why you need all these wrappers when you can simply
> call platform_device_unregister().
>
>> +}
>> +
>> +/* Platform driver */
>> +static struct platform_driver slidebar_drv = {
>> + .driver = {
>> + .name = "ideapad_slidebar",
>> + .owner = THIS_MODULE,
>> + },
>> +};
>> +
>> +static int register_platform_drv(void)
>> +{
>> + int err;
>> + err = platform_driver_register(&slidebar_drv);
>> + if (err)
>> + pr_err("ideapad_slidebar: Failed to register platform driver\n");
>> +
>> + return err;
>> +}
>> +
>> +static void unregister_platform_drv(void)
>> +{
>> + platform_driver_unregister(&slidebar_drv);
>> +}
>> +
>> +/* DMI */
>> +static int ideapad_dmi_check(const struct dmi_system_id *id)
>> +{
>> + pr_info("ideapad_slidebar: Laptop model '%s'\n", id->ident);
>> + return 1;
>> +}
>> +
>> +static struct dmi_system_id ideapad_dmi_table[] __initconst = {
>> + {
>> + .ident = "Lenovo IdeaPad Y550",
>> + .matches = {
>> + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
>> + DMI_MATCH(DMI_PRODUCT_NAME, "20017"),
>> + DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo IdeaPad Y550")
>> + },
>> + .callback = ideapad_dmi_check
>> + },
>> + {
>> + .ident = "Lenovo IdeaPad Y550P",
>> + .matches = {
>> + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
>> + DMI_MATCH(DMI_PRODUCT_NAME, "20035"),
>> + DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo IdeaPad Y550P")
>> + },
>> + .callback = ideapad_dmi_check
>> + },
>> + { }
>> +};
>> +
>> +/* Init and cleanup */
>> +static int __init slidebar_init(void)
>> +{
>> + int err;
>> +
>> + if (!force && !dmi_check_system(ideapad_dmi_table))
>> + return -ENODEV;
>> +
>> + err = setup_platform_dev();
>> + if (err)
>> + return err;
>> +
>> + err = register_platform_drv();
>> + if (err)
>> + goto err_remove_platform_dev;
>> +
>> + err = setup_input_dev();
>> + if (err)
>> + goto err_unregister_platform_drv;
>> +
>> + err = setup_slidebar_mode_dev();
>> + if (err)
>> + goto err_remove_input_dev;
>> + return 0;
>> +
>> +err_remove_input_dev:
>> + remove_input_dev();
>> +err_unregister_platform_drv:
>> + unregister_platform_drv();
>> +err_remove_platform_dev:
>> + remove_platform_dev();
>> + return err;
>> +}
>> +
>> +static void __exit slidebar_exit(void)
>> +{
>> + remove_slidebar_mode_dev();
>> + remove_input_dev();
>> + remove_platform_dev();
>> + unregister_platform_drv();
>> +}
>> +
>> +module_init(slidebar_init);
>> +module_exit(slidebar_exit);
>> +
>> +MODULE_AUTHOR("Andrey Moiseev <o2g.org.ru@gmail.com>");
>> +MODULE_DESCRIPTION("Slidebar input support for some Lenovo IdeaPad laptops");
>> +MODULE_LICENSE("GPL");
>> +MODULE_VERSION("0.1");
>> +
>> +MODULE_ALIAS("dmi:*:svnLENOVO:pn20017:pvrLenovoIdeaPadY550:*");
>> +MODULE_ALIAS("dmi:*:svnLENOVO:pn20035:pvrLenovoIdeaPadY550P:*");
>
> Why not create MODULE_DEVICE_TABLE from dmi table instead of doing it by
> hand?
>
> Thanks.
>
> --
> Dmitry
^ permalink raw reply related
* [PATCH v6 resend] input: ideapad_slidebar: new input driver
From: Andrey Moiseev @ 2013-08-15 15:35 UTC (permalink / raw)
To: linux-input
Cc: dmitry.torokhov, arnoques, russianneuromancer, ike.pan,
linux-kernel
v6: fixed things addressed by Dmitry Torokhov to v5 (quoted message goes below the patch)
ideapad_slidebar is a new driver which enables slidebars on some
Lenovo IdeaPad laptops (the slidebars work with SlideNav/Desktop
Navigator under Windows)
Fixes this: https://bugzilla.kernel.org/show_bug.cgi?id=16004
Registers 'IdeaPad Slidebar' input device and
/sys/devices/platform/ideapad_slidebar/slidebar_mode
for switching slidebar's modes.
Now works on:
IdeaPad Y550, Y550P.
May work on (testing and adding new models is needed):
Ideapad Y560, Y460, Y450, Y650,
and, probably, some others.
Driver source: https://github.com/o2genum/ideapad-slidebar.git
Patch is generated against current mainline kernel.
Signed-off-by: Andrey Moiseev <o2g.org.ru@gmail.com>
diff --git a/MAINTAINERS b/MAINTAINERS
index defc053..2ff3dd8 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -4096,6 +4096,13 @@ W: http://launchpad.net/ideapad-laptop
S: Maintained
F: drivers/platform/x86/ideapad-laptop.c
+IDEAPAD LAPTOP SLIDEBAR DRIVER
+M: Andrey Moiseev <o2g.org.ru@gmail.com>
+L: linux-input@vger.kernel.org
+W: https://github.com/o2genum/ideapad-slidebar
+S: Maintained
+F: drivers/input/misc/ideapad_slidebar.c
+
IDE/ATAPI DRIVERS
M: Borislav Petkov <bp@alien8.de>
L: linux-ide@vger.kernel.org
diff --git a/drivers/input/misc/Kconfig b/drivers/input/misc/Kconfig
index 0b541cd..45729a9 100644
--- a/drivers/input/misc/Kconfig
+++ b/drivers/input/misc/Kconfig
@@ -647,4 +647,13 @@ config INPUT_SIRFSOC_ONKEY
If unsure, say N.
+config INPUT_IDEAPAD_SLIDEBAR
+ tristate "IdeaPad Laptop Slidebar"
+ depends on INPUT
+ help
+ Input driver for slidebars on some Lenovo IdeaPad laptops.
+
+ If you have an IdeaPad laptop with a slidebar, say Y or M here.
+ Module name is ideapad_slidebar.
+
endif
diff --git a/drivers/input/misc/Makefile b/drivers/input/misc/Makefile
index 829de43..0ebfb6d 100644
--- a/drivers/input/misc/Makefile
+++ b/drivers/input/misc/Makefile
@@ -61,3 +61,4 @@ obj-$(CONFIG_INPUT_WISTRON_BTNS) += wistron_btns.o
obj-$(CONFIG_INPUT_WM831X_ON) += wm831x-on.o
obj-$(CONFIG_INPUT_XEN_KBDDEV_FRONTEND) += xen-kbdfront.o
obj-$(CONFIG_INPUT_YEALINK) += yealink.o
+obj-$(CONFIG_INPUT_IDEAPAD_SLIDEBAR) += ideapad_slidebar.o
diff --git a/drivers/input/misc/ideapad_slidebar.c b/drivers/input/misc/ideapad_slidebar.c
new file mode 100644
index 0000000..3d97933
--- /dev/null
+++ b/drivers/input/misc/ideapad_slidebar.c
@@ -0,0 +1,348 @@
+/*
+ * Input driver for slidebars on some Lenovo IdeaPad laptops
+ *
+ * Copyright (C) 2013 Andrey Moiseev <o2g.org.ru@gmail.com>
+ *
+ * Reverse-engineered from Lenovo SlideNav software (SBarHook.dll).
+ *
+ * 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.
+ *
+ * Trademarks are the property of their respective owners.
+ */
+
+/*
+ * Currently tested and works on:
+ * Lenovo IdeaPad Y550
+ * Lenovo IdeaPad Y550P
+ *
+ * Other models can be added easily. To test,
+ * load with 'force' parameter set 'true'.
+ *
+ * LEDs blinking and input mode are managed via sysfs,
+ * (hex, unsigned byte value):
+ * /sys/devices/platform/ideapad_slidebar/slidebar_mode
+ *
+ * The value is in byte range, however, I only figured out
+ * how bits 0b10011001 work. Some other bits, probably,
+ * are meaningfull too.
+ *
+ * Possible states:
+ *
+ * STD_INT, ONMOV_INT, OFF_INT, LAST_POLL, OFF_POLL
+ *
+ * Meaning:
+ * released touched
+ * STD 'heartbeat' lights follow the finger
+ * ONMOV no lights lights follow the finger
+ * LAST at last pos lights follow the finger
+ * OFF no lights no lights
+ *
+ * INT all input events are generated, interrupts are used
+ * POLL no input events by default, to get them,
+ * send 0b10000000 (read below)
+ *
+ * Commands: write
+ *
+ * All | 0b01001 -> STD_INT
+ * possible | 0b10001 -> ONMOV_INT
+ * states | 0b01000 -> OFF_INT
+ *
+ * | 0b0 -> LAST_POLL
+ * STD_INT or ONMOV_INT |
+ * | 0b1 -> STD_INT
+ *
+ * | 0b0 -> OFF_POLL
+ * OFF_INT or OFF_POLL |
+ * | 0b1 -> OFF_INT
+ *
+ * Any state | 0b10000000 -> if the slidebar has updated data,
+ * produce one input event (last position),
+ * switch to respective POLL mode
+ * (like 0x0), if not in POLL mode yet.
+ *
+ * Get current state: read
+ *
+ * masked by 0x11 read value means:
+ *
+ * 0x00 LAST
+ * 0x01 STD
+ * 0x10 OFF
+ * 0x11 ONMOV
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/dmi.h>
+#include <linux/spinlock.h>
+#include <linux/platform_device.h>
+#include <linux/input.h>
+#include <linux/io.h>
+#include <linux/i8042.h>
+
+static bool force;
+module_param(force, bool, 0);
+MODULE_PARM_DESC(force, "Force driver load, ignore DMI data");
+
+static spinlock_t sio_lock = __SPIN_LOCK_UNLOCKED(sio_lock);
+
+static struct input_dev *slidebar_input_dev;
+static struct platform_device *slidebar_platform_dev;
+
+/* Hardware interacting */
+static unsigned char slidebar_pos_get(void)
+{
+ int res;
+ unsigned long flags;
+
+ spin_lock_irqsave(&sio_lock, flags);
+ outb(0xf4, 0xff29);
+ outb(0xbf, 0xff2a);
+ res = inb(0xff2b);
+ spin_unlock_irqrestore(&sio_lock, flags);
+ return res;
+}
+
+static unsigned char slidebar_mode_get(void)
+{
+ int res;
+ unsigned long flags;
+
+ spin_lock_irqsave(&sio_lock, flags);
+ outb(0xf7, 0xff29);
+ outb(0x8b, 0xff2a);
+ res = inb(0xff2b);
+ spin_unlock_irqrestore(&sio_lock, flags);
+ return res;
+}
+
+static void slidebar_mode_set(unsigned char mode)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&sio_lock, flags);
+ outb(0xf7, 0xff29);
+ outb(0x8b, 0xff2a);
+ outb(mode, 0xff2b);
+ spin_unlock_irqrestore(&sio_lock, flags);
+}
+
+/* Listening the keyboard (i8042 filter) */
+static bool slidebar_i8042_filter(unsigned char data, unsigned char str,
+ struct serio *port)
+{
+ static bool extended = false, touched = false;
+
+ /* Scancodes: e03b on move, e0bb on release */
+ if (unlikely(data == 0xe0)) {
+ extended = true;
+ return false;
+ } else if (unlikely(extended && (data == 0x3b))) {
+ extended = false;
+ if (!touched)
+ input_report_key(slidebar_input_dev, BTN_TOUCH, 1);
+ touched = true;
+ input_report_abs(slidebar_input_dev, ABS_X, slidebar_pos_get());
+ input_sync(slidebar_input_dev);
+ return false;
+ } else if (unlikely(extended && (data == 0xbb))) {
+ touched = false;
+ input_report_key(slidebar_input_dev, BTN_TOUCH, 0);
+ input_sync(slidebar_input_dev);
+ }
+ return false;
+}
+
+/* Sysfs slidebar_mode interface */
+static ssize_t show_slidebar_mode(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ return sprintf(buf, "%x\n", slidebar_mode_get());
+}
+
+static ssize_t store_slidebar_mode(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ int mode;
+
+ if (!count)
+ return 0;
+ if (sscanf(buf, "%x", &mode) != 1)
+ return -EINVAL;
+ slidebar_mode_set(mode);
+ return count;
+}
+
+static DEVICE_ATTR(slidebar_mode, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH,
+ show_slidebar_mode, store_slidebar_mode);
+
+static struct attribute *ideapad_attrs[] = {
+ &dev_attr_slidebar_mode.attr,
+ NULL
+};
+
+static struct attribute_group ideapad_attr_group = {
+ .attrs = ideapad_attrs
+};
+
+static const struct attribute_group *ideapad_attr_groups[] = {
+ &ideapad_attr_group,
+ NULL
+};
+
+/* Input device */
+static int __init setup_input_dev(void)
+{
+ int err;
+
+ slidebar_input_dev = input_allocate_device();
+ if (!slidebar_input_dev) {
+ pr_err("ideapad_slidebar: Not enough memory\n");
+ return -ENOMEM;
+ }
+
+ slidebar_input_dev->name = "IdeaPad Slidebar";
+ slidebar_input_dev->id.bustype = BUS_HOST;
+ slidebar_input_dev->dev.parent = &slidebar_platform_dev->dev;
+ input_set_capability(slidebar_input_dev, EV_KEY, BTN_TOUCH);
+ input_set_capability(slidebar_input_dev, EV_ABS, ABS_X);
+ input_set_abs_params(slidebar_input_dev, ABS_X, 0, 0xff, 0, 0);
+
+ err = i8042_install_filter(slidebar_i8042_filter);
+ if (err) {
+ pr_err("ideapad_slidebar: Can't install i8042 filter \n");
+ goto err_free_dev;
+ }
+ err = input_register_device(slidebar_input_dev);
+ if (err) {
+ pr_err("ideapad_slidebar: Failed to register device\n");
+ goto err_remove_filter;
+ }
+ return 0;
+
+err_remove_filter:
+ i8042_remove_filter(slidebar_i8042_filter);
+err_free_dev:
+ input_free_device(slidebar_input_dev);
+ return err;
+}
+
+static void remove_input_dev(void)
+{
+ i8042_remove_filter(slidebar_i8042_filter);
+ input_unregister_device(slidebar_input_dev);
+}
+
+/* Platform device */
+static int __init setup_platform_dev(void)
+{
+ int err;
+ slidebar_platform_dev = platform_device_alloc("ideapad_slidebar", -1);
+ slidebar_platform_dev->dev.groups = ideapad_attr_groups;
+ if (!slidebar_platform_dev) {
+ pr_err("ideapad_slidebar: Not enough memory\n");
+ return -ENOMEM;
+ }
+ err = platform_device_add(slidebar_platform_dev);
+ if (err) {
+ pr_err("ideapad_slidebar: Failed to register plarform device\n");
+ goto err_free_platform_device;
+ }
+ return 0;
+
+err_free_platform_device:
+ platform_device_put(slidebar_platform_dev);
+ return err;
+}
+
+/* Platform driver */
+static struct platform_driver slidebar_drv = {
+ .driver = {
+ .name = "ideapad_slidebar",
+ .owner = THIS_MODULE,
+ },
+};
+
+static int __init register_platform_drv(void)
+{
+ int err;
+
+ err = platform_driver_register(&slidebar_drv);
+ if (err)
+ pr_err("ideapad_slidebar: Failed to register platform driver\n");
+
+ return err;
+}
+
+/* DMI */
+static int __init ideapad_dmi_check(const struct dmi_system_id *id)
+{
+ pr_info("ideapad_slidebar: Laptop model '%s'\n", id->ident);
+ return 1;
+}
+
+static const struct dmi_system_id ideapad_dmi[] __initconst = {
+ {
+ .ident = "Lenovo IdeaPad Y550",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "20017"),
+ DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo IdeaPad Y550")
+ },
+ .callback = ideapad_dmi_check
+ },
+ {
+ .ident = "Lenovo IdeaPad Y550P",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "20035"),
+ DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo IdeaPad Y550P")
+ },
+ .callback = ideapad_dmi_check
+ },
+ { NULL, }
+};
+MODULE_DEVICE_TABLE(dmi, ideapad_dmi);
+
+/* Init and cleanup */
+static int __init slidebar_init(void)
+{
+ int err;
+
+ if (!force && !dmi_check_system(ideapad_dmi))
+ return -ENODEV;
+ err = setup_platform_dev();
+ if (err)
+ return err;
+ err = register_platform_drv();
+ if (err)
+ goto err_unregister_platform_dev;
+ err = setup_input_dev();
+ if (err)
+ goto err_unregister_platform_drv;
+ return 0;
+
+err_unregister_platform_drv:
+ platform_driver_unregister(&slidebar_drv);
+err_unregister_platform_dev:
+ platform_device_unregister(slidebar_platform_dev);
+ return err;
+}
+
+static void __exit slidebar_exit(void)
+{
+ remove_input_dev();
+ platform_device_unregister(slidebar_platform_dev);
+ platform_driver_unregister(&slidebar_drv);
+}
+
+module_init(slidebar_init);
+module_exit(slidebar_exit);
+
+MODULE_AUTHOR("Andrey Moiseev <o2g.org.ru@gmail.com>");
+MODULE_DESCRIPTION("Slidebar input support for some Lenovo IdeaPad laptops");
+MODULE_LICENSE("GPL");
--
1.8.3.4
---
Quoted Re: to v5:
> Hi Andrey,
>
> On Mon, Aug 12, 2013 at 02:23:23PM +0400, Andrey Moiseev wrote:
>> v5: __initconst goes after var name
>>
>> ideapad_slidebar is a new driver which enables slidebars on some
>> Lenovo IdeaPad laptops (the slidebars work with SlideNav/Desktop
>> Navigator under Windows)
>>
>> Fixes this: https://bugzilla.kernel.org/show_bug.cgi?id=16004
>>
>> Registers 'IdeaPad Slidebar' input device and
>> /sys/devices/platform/ideapad_slidebar/slidebar_mode
>> for switching slidebar's modes.
>>
>> Now works on:
>> IdeaPad Y550, Y550P.
>>
>> May work on (testing and adding new models is needed):
>> Ideapad Y560, Y460, Y450, Y650,
>> and, probably, some others.
>>
>> Driver source: https://github.com/o2genum/ideapad-slidebar.git
>>
>> Patch is generated against current mainline kernel.
>>
>> Signed-off-by: Andrey Moiseev <o2g.org.ru@gmail.com>
>> ---
>> MAINTAINERS | 7 +
>> drivers/input/misc/Kconfig | 9 +
>> drivers/input/misc/Makefile | 1 +
>> drivers/input/misc/ideapad_slidebar.c | 380 ++++++++++++++++++++++++++++++++++
>> 4 files changed, 397 insertions(+)
>> create mode 100644 drivers/input/misc/ideapad_slidebar.c
>>
>> diff --git a/MAINTAINERS b/MAINTAINERS
>> index defc053..2ff3dd8 100644
>> --- a/MAINTAINERS
>> +++ b/MAINTAINERS
>> @@ -4096,6 +4096,13 @@ W: http://launchpad.net/ideapad-laptop
>> S: Maintained
>> F: drivers/platform/x86/ideapad-laptop.c
>>
>> +IDEAPAD LAPTOP SLIDEBAR DRIVER
>> +M: Andrey Moiseev <o2g.org.ru@gmail.com>
>> +L: linux-input@vger.kernel.org
>> +W: https://github.com/o2genum/ideapad-slidebar
>> +S: Maintained
>> +F: drivers/input/misc/ideapad_slidebar.c
>> +
>> IDE/ATAPI DRIVERS
>> M: Borislav Petkov <bp@alien8.de>
>> L: linux-ide@vger.kernel.org
>> diff --git a/drivers/input/misc/Kconfig b/drivers/input/misc/Kconfig
>> index 0b541cd..45729a9 100644
>> --- a/drivers/input/misc/Kconfig
>> +++ b/drivers/input/misc/Kconfig
>> @@ -647,4 +647,13 @@ config INPUT_SIRFSOC_ONKEY
>>
>> If unsure, say N.
>>
>> +config INPUT_IDEAPAD_SLIDEBAR
>> + tristate "IdeaPad Laptop Slidebar"
>> + depends on INPUT
>> + help
>> + Input driver for slidebars on some Lenovo IdeaPad laptops.
>> +
>> + If you have an IdeaPad laptop with a slidebar, say Y or M here.
>> + Module name is ideapad_slidebar.
>> +
>> endif
>> diff --git a/drivers/input/misc/Makefile b/drivers/input/misc/Makefile
>> index 829de43..0ebfb6d 100644
>> --- a/drivers/input/misc/Makefile
>> +++ b/drivers/input/misc/Makefile
>> @@ -61,3 +61,4 @@ obj-$(CONFIG_INPUT_WISTRON_BTNS) += wistron_btns.o
>> obj-$(CONFIG_INPUT_WM831X_ON) += wm831x-on.o
>> obj-$(CONFIG_INPUT_XEN_KBDDEV_FRONTEND) += xen-kbdfront.o
>> obj-$(CONFIG_INPUT_YEALINK) += yealink.o
>> +obj-$(CONFIG_INPUT_IDEAPAD_SLIDEBAR) += ideapad_slidebar.o
>> diff --git a/drivers/input/misc/ideapad_slidebar.c b/drivers/input/misc/ideapad_slidebar.c
>> new file mode 100644
>> index 0000000..df437a4
>> --- /dev/null
>> +++ b/drivers/input/misc/ideapad_slidebar.c
>> @@ -0,0 +1,380 @@
>> +/*
>> + * Input driver for slidebars on some Lenovo IdeaPad laptops
>> + *
>> + * Copyright (C) 2013 Andrey Moiseev <o2g.org.ru@gmail.com>
>> + *
>> + * Reverse-engineered from Lenovo SlideNav software (SBarHook.dll).
>> + *
>> + * 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.
>> + *
>> + * Trademarks are the property of their respective owners.
>> + */
>> +
>> +/*
>> + * Currently tested and works on:
>> + * Lenovo IdeaPad Y550
>> + * Lenovo IdeaPad Y550P
>> + *
>> + * Other models can be added easily. To test,
>> + * load with 'force' parameter set 'true'.
>> + *
>> + * LEDs blinking and input mode are managed via sysfs,
>> + * (hex, unsigned byte value):
>> + * /sys/devices/platform/ideapad_slidebar/slidebar_mode
>> + *
>> + * The value is in byte range, however, I only figured out
>> + * how bits 0b10011001 work. Some other bits, probably,
>> + * are meaningfull too.
>> + *
>> + * Possible states:
>> + *
>> + * STD_INT, ONMOV_INT, OFF_INT, LAST_POLL, OFF_POLL
>> + *
>> + * Meaning:
>> + * released touched
>> + * STD 'heartbeat' lights follow the finger
>> + * ONMOV no lights lights follow the finger
>> + * LAST at last pos lights follow the finger
>> + * OFF no lights no lights
>> + *
>> + * INT all input events are generated, interrupts are used
>> + * POLL no input events by default, to get them,
>> + * send 0b10000000 (read below)
>> + *
>> + * Commands: write
>> + *
>> + * All | 0b01001 -> STD_INT
>> + * possible | 0b10001 -> ONMOV_INT
>> + * states | 0b01000 -> OFF_INT
>> + *
>> + * | 0b0 -> LAST_POLL
>> + * STD_INT or ONMOV_INT |
>> + * | 0b1 -> STD_INT
>> + *
>> + * | 0b0 -> OFF_POLL
>> + * OFF_INT or OFF_POLL |
>> + * | 0b1 -> OFF_INT
>> + *
>> + * Any state | 0b10000000 -> if the slidebar has updated data,
>> + * produce one input event (last position),
>> + * switch to respective POLL mode
>> + * (like 0x0), if not in POLL mode yet.
>> + *
>> + * Get current state: read
>> + *
>> + * masked by 0x11 read value means:
>> + *
>> + * 0x00 LAST
>> + * 0x01 STD
>> + * 0x10 OFF
>> + * 0x11 ONMOV
>> + */
>> +
>> +#include <linux/module.h>
>> +#include <linux/kernel.h>
>> +#include <linux/dmi.h>
>> +#include <linux/spinlock.h>
>> +#include <linux/platform_device.h>
>> +#include <linux/input.h>
>> +#include <linux/io.h>
>> +#include <linux/i8042.h>
>> +
>> +static bool force;
>> +module_param(force, bool, 0);
>> +MODULE_PARM_DESC(force, "Force driver load, ignore DMI data");
>> +
>> +static spinlock_t sio_lock = __SPIN_LOCK_UNLOCKED(sio_lock);
>> +
>> +static struct input_dev *slidebar_input_dev;
>> +static struct platform_device *slidebar_platform_dev;
>> +
>> +/* Hardware interacting */
>> +static int slidebar_pos_get(void)
>> +{
>> + int res;
>> + unsigned long flags;
>
> A blank line between variables and code is appreciated.
>
>> + spin_lock_irqsave(&sio_lock, flags);
>> + outb(0xf4, 0xff29);
>> + outb(0xbf, 0xff2a);
>> + res = inb(0xff2b);
>> + spin_unlock_irqrestore(&sio_lock, flags);
>> + return res;
>> +}
>> +
>> +static unsigned char slidebar_mode_get(void)
>> +{
>> + int res;
>> + unsigned long flags;
>> + spin_lock_irqsave(&sio_lock, flags);
>> + outb(0xf7, 0xff29);
>> + outb(0x8b, 0xff2a);
>> + res = inb(0xff2b);
>> + spin_unlock_irqrestore(&sio_lock, flags);
>> + return res;
>> +}
>> +
>> +static void slidebar_mode_set(unsigned char mode)
>> +{
>> + unsigned long flags;
>> + spin_lock_irqsave(&sio_lock, flags);
>> + outb(0xf7, 0xff29);
>> + outb(0x8b, 0xff2a);
>> + outb(mode, 0xff2b);
>> + spin_unlock_irqrestore(&sio_lock, flags);
>> +}
>> +
>> +/* Listening the keyboard (i8042 filter) */
>> +static bool slidebar_i8042_filter(unsigned char data, unsigned char str,
>> + struct serio *port)
>> +{
>> + static bool extended = false, touched = false;
>> +
>> + /* Scancodes: e03b on move, e0bb on release */
>> + if (unlikely(data == 0xe0)) {
>> + extended = true;
>> + return false;
>> + } else if (unlikely(extended && (data == 0x3b))) {
>> + extended = false;
>> + if (!touched)
>> + input_report_key(slidebar_input_dev, BTN_TOUCH, 1);
>> + touched = true;
>> + input_report_abs(slidebar_input_dev, ABS_X, slidebar_pos_get());
>> + input_sync(slidebar_input_dev);
>> + return false;
>> + } else if (unlikely(extended && (data == 0xbb))) {
>> + touched = false;
>> + input_report_key(slidebar_input_dev, BTN_TOUCH, 0);
>> + input_sync(slidebar_input_dev);
>> + }
>> +
>> + return false;
>> +}
>> +
>> +/* Input device */
>> +static int setup_input_dev(void)
>> +{
>> + int err;
>> +
>> + slidebar_input_dev = input_allocate_device();
>> + if (!slidebar_input_dev) {
>> + pr_err("ideapad_slidebar: Not enough memory\n");
>> + return -ENOMEM;
>> + }
>> +
>> + slidebar_input_dev->name = "IdeaPad Slidebar";
>> + slidebar_input_dev->id.bustype = BUS_HOST;
>> + slidebar_input_dev->dev.parent = &slidebar_platform_dev->dev;
>> + input_set_capability(slidebar_input_dev, EV_KEY, BTN_TOUCH);
>> + input_set_capability(slidebar_input_dev, EV_ABS, ABS_X);
>> + input_alloc_absinfo(slidebar_input_dev);
>
> This call is not needed, it will be done for you.
>
>> + input_set_abs_params(slidebar_input_dev, ABS_X, 0, 0xff, 0, 0);
>> +
>> + err = input_register_device(slidebar_input_dev);
>> + if (err) {
>> + pr_err("ideapad_slidebar: Failed to register device\n");
>> + goto err_free_dev;
>> + }
>> +
>> + err = i8042_install_filter(slidebar_i8042_filter);
>> + if (err) {
>> + pr_err("ideapad_slidebar: Can't install i8042 filter \n");
>> + goto err_unregister_dev;
>> + }
>> + return 0;
>> +
>> +err_unregister_dev:
>> + input_unregister_device(slidebar_input_dev);
>> +err_free_dev:
>> + input_free_device(slidebar_input_dev);
>
> No free after unregister for input devices. Consider swapping
> registering device and installing the filter (allocated but not
> registered input device can take events emitted by the filter just
> fine).
>
>> + return err;
>> +}
>> +
>> +static void remove_input_dev(void)
>> +{
>> + i8042_remove_filter(slidebar_i8042_filter);
>> + input_unregister_device(slidebar_input_dev);
>> + input_free_device(slidebar_input_dev);
>
> No free after unregister for input devices.
>
>> +}
>> +
>> +/* Sysfs slidebar_mode interface */
>> +static ssize_t show_slidebar_mode(struct device *dev,
>> + struct device_attribute *attr,
>> + char *buf)
>> +{
>> + return sprintf(buf, "%x\n", slidebar_mode_get());
>> +}
>> +
>> +static ssize_t store_slidebar_mode(struct device *dev,
>> + struct device_attribute *attr,
>> + const char *buf, size_t count)
>> +{
>> + int mode;
>> + if (!count)
>> + return 0;
>> +
>> + if (sscanf(buf, "%x", &mode) != 1)
>> + return -EINVAL;
>> +
>> + slidebar_mode_set(mode);
>> + return count;
>> +}
>> +
>> +static DEVICE_ATTR(slidebar_mode, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH,
>> + show_slidebar_mode, store_slidebar_mode);
>> +
>> +static struct attribute *ideapad_attributes[] = {
>> + &dev_attr_slidebar_mode.attr,
>> + NULL
>> +};
>> +
>> +static struct attribute_group ideapad_attribute_group = {
>> + .attrs = ideapad_attributes
>> +};
>> +
>> +static int setup_slidebar_mode_dev(void)
>> +{
>> + return sysfs_create_group(&slidebar_platform_dev->dev.kobj,
>> + &ideapad_attribute_group);
>
> To avoid races between creating device and attributes you want to simply
> assign your group to the devices group pointer and then it will be
> created for you by device core.
>
>> +}
>> +
>> +static void remove_slidebar_mode_dev(void)
>> +{
>> + return sysfs_remove_group(&slidebar_platform_dev->dev.kobj,
>> + &ideapad_attribute_group);
>> +}
>> +
>> +/* Platform device */
>> +static int setup_platform_dev(void)
>> +{
>> + int err;
>> + slidebar_platform_dev = platform_device_alloc("ideapad_slidebar", -1);
>> + if (!slidebar_platform_dev) {
>> + pr_err("ideapad_slidebar: Not enough memory\n");
>> + return -ENOMEM;
>> + }
>> +
>> + err = platform_device_add(slidebar_platform_dev);
>> + if (err) {
>> + pr_err("ideapad_slidebar: Failed to register plarform device\n");
>> + goto err_free_platform_device;
>> + }
>> + return 0;
>> +
>> +err_free_platform_device:
>> + platform_device_put(slidebar_platform_dev);
>> + return err;
>> +}
>> +
>> +static void remove_platform_dev(void)
>> +{
>> + platform_device_del(slidebar_platform_dev);
>> + platform_device_put(slidebar_platform_dev);
>
> I do nto understand why you need all these wrappers when you can simply
> call platform_device_unregister().
>
>> +}
>> +
>> +/* Platform driver */
>> +static struct platform_driver slidebar_drv = {
>> + .driver = {
>> + .name = "ideapad_slidebar",
>> + .owner = THIS_MODULE,
>> + },
>> +};
>> +
>> +static int register_platform_drv(void)
>> +{
>> + int err;
>> + err = platform_driver_register(&slidebar_drv);
>> + if (err)
>> + pr_err("ideapad_slidebar: Failed to register platform driver\n");
>> +
>> + return err;
>> +}
>> +
>> +static void unregister_platform_drv(void)
>> +{
>> + platform_driver_unregister(&slidebar_drv);
>> +}
>> +
>> +/* DMI */
>> +static int ideapad_dmi_check(const struct dmi_system_id *id)
>> +{
>> + pr_info("ideapad_slidebar: Laptop model '%s'\n", id->ident);
>> + return 1;
>> +}
>> +
>> +static struct dmi_system_id ideapad_dmi_table[] __initconst = {
>> + {
>> + .ident = "Lenovo IdeaPad Y550",
>> + .matches = {
>> + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
>> + DMI_MATCH(DMI_PRODUCT_NAME, "20017"),
>> + DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo IdeaPad Y550")
>> + },
>> + .callback = ideapad_dmi_check
>> + },
>> + {
>> + .ident = "Lenovo IdeaPad Y550P",
>> + .matches = {
>> + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
>> + DMI_MATCH(DMI_PRODUCT_NAME, "20035"),
>> + DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo IdeaPad Y550P")
>> + },
>> + .callback = ideapad_dmi_check
>> + },
>> + { }
>> +};
>> +
>> +/* Init and cleanup */
>> +static int __init slidebar_init(void)
>> +{
>> + int err;
>> +
>> + if (!force && !dmi_check_system(ideapad_dmi_table))
>> + return -ENODEV;
>> +
>> + err = setup_platform_dev();
>> + if (err)
>> + return err;
>> +
>> + err = register_platform_drv();
>> + if (err)
>> + goto err_remove_platform_dev;
>> +
>> + err = setup_input_dev();
>> + if (err)
>> + goto err_unregister_platform_drv;
>> +
>> + err = setup_slidebar_mode_dev();
>> + if (err)
>> + goto err_remove_input_dev;
>> + return 0;
>> +
>> +err_remove_input_dev:
>> + remove_input_dev();
>> +err_unregister_platform_drv:
>> + unregister_platform_drv();
>> +err_remove_platform_dev:
>> + remove_platform_dev();
>> + return err;
>> +}
>> +
>> +static void __exit slidebar_exit(void)
>> +{
>> + remove_slidebar_mode_dev();
>> + remove_input_dev();
>> + remove_platform_dev();
>> + unregister_platform_drv();
>> +}
>> +
>> +module_init(slidebar_init);
>> +module_exit(slidebar_exit);
>> +
>> +MODULE_AUTHOR("Andrey Moiseev <o2g.org.ru@gmail.com>");
>> +MODULE_DESCRIPTION("Slidebar input support for some Lenovo IdeaPad laptops");
>> +MODULE_LICENSE("GPL");
>> +MODULE_VERSION("0.1");
>> +
>> +MODULE_ALIAS("dmi:*:svnLENOVO:pn20017:pvrLenovoIdeaPadY550:*");
>> +MODULE_ALIAS("dmi:*:svnLENOVO:pn20035:pvrLenovoIdeaPadY550P:*");
>
> Why not create MODULE_DEVICE_TABLE from dmi table instead of doing it by
> hand?
>
> Thanks.
>
> --
> Dmitry
^ permalink raw reply related
* Re: [PATCH 45/51] Input: atmel_mxt_ts - Release touch state during suspend
From: Nick Dyer @ 2013-08-15 15:52 UTC (permalink / raw)
To: rydberg
Cc: Dmitry Torokhov, Daniel Kurtz, Joonyoung Shim, Alan Bowens,
linux-input, linux-kernel, Peter Meerwald, Benson Leung,
Olof Johansson
In-Reply-To: <20130718172953.GI32381@polaris.bitmath.org>
rydberg@euromail.se wrote:
> On Thu, Jun 27, 2013 at 01:49:20PM +0100, Nick Dyer wrote:
>> If fingers are down as the MXT chip goes into suspend it does not send a lift
>> message. In addition, it may not complete its final measurement cycle
>> immediately, which means touch messages may be received by the interrupt
>> handler after mxt_stop() has completed.
>
> How long is the window of possible stray interrupts? Could this be
> done with a small delay instead of keeping track of the suspend state?
The touch controller has its own acquisition scheduling which switches
between active/idle modes. Which mode it is in isn't explicitly
communicated to the driver.
If we could tell it was in active mode (which would involve some hairier
code than this patch involves), we might have to wait ~20ms on a modern
device. But the worst case is that we would have to wait for the idle scan
interval plus some margin, so a couple of hundred ms. I think that is too
long, right?
^ permalink raw reply
* Re: Atmel updates to atmel_mxt_ts touch controller driver - v6
From: Nick Dyer @ 2013-08-15 15:55 UTC (permalink / raw)
To: rydberg
Cc: Dmitry Torokhov, Daniel Kurtz, Joonyoung Shim, Alan Bowens,
linux-input, linux-kernel, Peter Meerwald, Benson Leung,
Olof Johansson
In-Reply-To: <20130718194739.GA559@polaris.bitmath.org>
rydberg@euromail.se wrote:
> First: thanks for the patches and you work on this driver.
Thank you for your time in looking at these changes.
> Now, I don't swear much, but I would like to emphasize line 161 of
> Documentation/SubmittingPatches:
>
> **Do not send more than 15 patches at once to the vger mailing lists!!!***
>
> One reason that should be obvious by now is that your work will be
> attended to much quicker. One may think that it is more efficient to
> send the whole backlog at once, but in fact, the time it takes to get
> a patchset accepted is inversely proportional to the length of the
> patchset. So please, keep it small and simple next time,
Apologies. I will split this lot up into several smaller series of patches.
^ permalink raw reply
* Re: [PATCH 35/36] hid: roccat: convert class code to use bin_attrs in groups
From: Stefan Achatz @ 2013-08-15 16:03 UTC (permalink / raw)
To: Greg Kroah-Hartman, Jiri Kosina, linux-input, linux-kernel
Hello Greg,
looking through the patch I found the following:
In hid-roccat-kone.c, PROFILE_ATTR macro:
The line
.private = &profile_numbers[number],
should read
.private = &profile_numbers[number-1],
Regarding the compiler warning about unused pyra_sysfs_write_settings:
The attribute is indeed readwrite, but the code to write is not standard
code. The read function was created with the PYRA_SYSFS_R macro and the
write function was written manually. Used were both by
PYRA_BIN_ATTRIBUTE_RW.
In this case I would keep the PYRA_SYSFS_* macros standalone so that the
calling code looks like this:
PYRA_SYSFS_W(control, CONTROL);
PYRA_SYSFS_RW(info, INFO);
PYRA_SYSFS_RW(profile_settings, PROFILE_SETTINGS);
PYRA_SYSFS_RW(profile_buttons, PROFILE_BUTTONS);
PYRA_SYSFS_R(settings, SETTINGS);
PYRA_BIN_ATTRIBUTE_W(control, CONTROL);
PYRA_BIN_ATTRIBUTE_RW(info, INFO);
PYRA_BIN_ATTRIBUTE_RW(profile_settings, PROFILE_SETTINGS);
PYRA_BIN_ATTRIBUTE_RW(profile_buttons, PROFILE_BUTTONS);
PYRA_BIN_ATTRIBUTE_RW(settings, SETTINGS);
Another thing: Why does nobody (besides me) use the get_maintainer.pl
script, so I have to search manually for patches regarding my code?
Have a nice day,
Stefan
^ permalink raw reply
* Re: Atmel updates to atmel_mxt_ts touch controller driver - v6
From: Dmitry Torokhov @ 2013-08-15 16:07 UTC (permalink / raw)
To: Nick Dyer
Cc: rydberg, Daniel Kurtz, Joonyoung Shim, Alan Bowens, linux-input,
linux-kernel, Peter Meerwald, Benson Leung, Olof Johansson
In-Reply-To: <520CFA0D.4090905@itdev.co.uk>
On Thu, Aug 15, 2013 at 04:55:57PM +0100, Nick Dyer wrote:
> rydberg@euromail.se wrote:
> > First: thanks for the patches and you work on this driver.
>
> Thank you for your time in looking at these changes.
>
> > Now, I don't swear much, but I would like to emphasize line 161 of
> > Documentation/SubmittingPatches:
> >
> > **Do not send more than 15 patches at once to the vger mailing lists!!!***
> >
> > One reason that should be obvious by now is that your work will be
> > attended to much quicker. One may think that it is more efficient to
> > send the whole backlog at once, but in fact, the time it takes to get
> > a patchset accepted is inversely proportional to the length of the
> > patchset. So please, keep it small and simple next time,
>
> Apologies. I will split this lot up into several smaller series of patches.
Wait, I am in the process of applying it actually...
--
Dmitry
^ permalink raw reply
* Re: [PATCH 37/51] Input: atmel_mxt_ts - Implement vector/orientation support
From: Nick Dyer @ 2013-08-15 16:18 UTC (permalink / raw)
To: rydberg
Cc: Dmitry Torokhov, Daniel Kurtz, Joonyoung Shim, Alan Bowens,
linux-input, linux-kernel, Peter Meerwald, Benson Leung,
Olof Johansson
In-Reply-To: <20130718172029.GF32381@polaris.bitmath.org>
rydberg@euromail.se wrote:
> On Thu, Jun 27, 2013 at 01:49:12PM +0100, Nick Dyer wrote:
>> The atmel touch messages contain orientation information as a byte in a packed
>> format which can be passed straight on to Android if the input device
>> configuration is correct, see
>> http://source.android.com/tech/input/touch-devices.html#touchorientationcalibration
Except they've changed the URL, should be:
https://source.android.com/devices/tech/input/touch-devices.html#touchorientationcalibration
The Atmel format is two 4-bit signed values packed into 1 byte, you use
inverse tan to work out the angle, and pythagoras theorem to work out the
magnitude of the vector (giving a confidence level)
>> This requires vector reports to be enabled in maXTouch config (zero DISVECT
>> bit in T9 CTRL field)
>>
>> Android converts the format in frameworks/base/services/input/Input.cpp,
>> search for ORIENTATION_CALIBRATION_VECTOR.
>
> How does this compare to the input mt documentation?
http://lxr.free-electrons.com/source/Documentation/input/multi-touch-protocol.txt#L263
So yes, we don't meet the documented format. Options:
1. Leave out this patch entirely and support out of tree
2. Update multi-touch-protocol.txt to include the Atmel format
3. Convert in driver to match documented protocol. Presumably via a LUT of
the 256 possible values. Although this loses the confidence level that is
implied.
4. Getting the firmware changed is not a valid option I'm afraid (too many
devices already out there).
Which do you prefer (I suspect 3) ?
^ permalink raw reply
* Re: [PATCH 35/36] hid: roccat: convert class code to use bin_attrs in groups
From: Greg Kroah-Hartman @ 2013-08-15 16:40 UTC (permalink / raw)
To: Stefan Achatz; +Cc: Jiri Kosina, linux-input, linux-kernel
In-Reply-To: <1376582626.2452.26.camel@neuromancer.tessier-ashpool>
On Thu, Aug 15, 2013 at 06:03:46PM +0200, Stefan Achatz wrote:
> Hello Greg,
> looking through the patch I found the following:
>
>
>
> In hid-roccat-kone.c, PROFILE_ATTR macro:
> The line
> .private = &profile_numbers[number],
> should read
> .private = &profile_numbers[number-1],
Ah, ok, thanks, will fix up.
> Regarding the compiler warning about unused pyra_sysfs_write_settings:
> The attribute is indeed readwrite, but the code to write is not standard
> code. The read function was created with the PYRA_SYSFS_R macro and the
> write function was written manually. Used were both by
> PYRA_BIN_ATTRIBUTE_RW.
> In this case I would keep the PYRA_SYSFS_* macros standalone so that the
> calling code looks like this:
>
> PYRA_SYSFS_W(control, CONTROL);
> PYRA_SYSFS_RW(info, INFO);
> PYRA_SYSFS_RW(profile_settings, PROFILE_SETTINGS);
> PYRA_SYSFS_RW(profile_buttons, PROFILE_BUTTONS);
> PYRA_SYSFS_R(settings, SETTINGS);
>
> PYRA_BIN_ATTRIBUTE_W(control, CONTROL);
> PYRA_BIN_ATTRIBUTE_RW(info, INFO);
> PYRA_BIN_ATTRIBUTE_RW(profile_settings, PROFILE_SETTINGS);
> PYRA_BIN_ATTRIBUTE_RW(profile_buttons, PROFILE_BUTTONS);
> PYRA_BIN_ATTRIBUTE_RW(settings, SETTINGS);
Hm, I'll look this over again and see if I can make it better.
But why are you using a "non standard" write function? What is special
here about this?
> Another thing: Why does nobody (besides me) use the get_maintainer.pl
> script, so I have to search manually for patches regarding my code?
I did use it:
$ ./scripts/get_maintainer.pl --file drivers/hid/hid-roccat-kone.c
Jiri Kosina <jkosina@suse.cz> (maintainer:HID CORE LAYER)
linux-input@vger.kernel.org (open list:HID CORE LAYER)
linux-kernel@vger.kernel.org (open list)
You aren't listed there, perhaps a MAINTAINERS entry needs to be added?
thanks,
greg k-h
^ permalink raw reply
* Re: [PATCH 1/3] HID: Use existing parser for pre-scanning the report descriptors
From: Benjamin Tissoires @ 2013-08-15 17:36 UTC (permalink / raw)
To: Alexander Holler
Cc: Benjamin Tissoires, Henrik Rydberg, Jiri Kosina, Stephane Chatty,
Mika Westerberg, linux-input, linux-kernel@vger.kernel.org
In-Reply-To: <520BE280.6010407@ahsoftware.de>
On Wed, Aug 14, 2013 at 10:03 PM, Alexander Holler <holler@ahsoftware.de> wrote:
> Am 14.08.2013 17:38, schrieb Benjamin Tissoires:
>
>
>>>> {
>>>> if (usage == HID_DG_CONTACTID)
>>>> - hid->group = HID_GROUP_MULTITOUCH;
>>>> + parser->flags |= HID_FLAG_MULTITOUCH;
>>>
>>>
>>> Did you consider reusing the group flags, e.g., parser->groups |= (1
>>> << HID_GROUP_MULTITOUCH)? This change could be made regardless of the
>>> parser logic.
>>
>>
>> If nobody is against changing the values of the different groups across
>> kernel version (which should be harmless), then I fully agree, we can
>> use the group item as a bit field (but we would be able to only have 16
>> different device groups).
>
>
> Hmm, that might become a problem. E.g. all the HID sensors might be used
> stand alone (without a sensor-hub, if someone modifies the drivers). But I
> agree that currently the flags are just confusing and would introduce them
> only if the number of groups reaches the limit.
>
>
>
>>>> - hid->group = HID_GROUP_GENERIC;
>>>> + parser = vzalloc(sizeof(struct hid_parser));
>>>
>>>
>>> Argh, I realize it is inevitable for this patch, but it still makes my
>>> eyes bleed. The parser takes quite a bit of memory...
>>
>>
>> Yep, my first attempt was to remove it, then I re-added it with a small
>> tear...
>
>
> So you actually create a new parser and the subject (that existing) of this
> patch is misleading.
Hi Alexander,
I think you misread what Henrik and I were discussing about:
Henrik complained about using the heap for something which is just
used in this function, and using the stack would have been better.
However, the size of the parser is too big that the compiler complains
when we declare it on the stack.
So this line is just a copy/paste from the function hid_open_report(),
and thus, you can agree that I did not create a new parser.
Cheers,
Benjamin
^ permalink raw reply
* Re: [PATCH v6 1/1] input: ideapad_slidebar: new input driver
From: Dmitry Torokhov @ 2013-08-15 16:06 UTC (permalink / raw)
To: Andrey Moiseev
Cc: linux-input, arnoques, russianneuromancer, ike.pan, linux-kernel
In-Reply-To: <520CEE7F.20205@gmail.com>
Hi Andrey,
On Thu, Aug 15, 2013 at 07:06:39PM +0400, Andrey Moiseev wrote:
> v6: fixed things addressed by Dmitry Torokhov to v5 (quoted message goes below the patch)
Thank you for making chnages, however I have still more comments.
>
> ideapad_slidebar is a new driver which enables slidebars on some
> Lenovo IdeaPad laptops (the slidebars work with SlideNav/Desktop
> Navigator under Windows)
>
> Fixes this: https://bugzilla.kernel.org/show_bug.cgi?id=16004
>
> Registers 'IdeaPad Slidebar' input device and
> /sys/devices/platform/ideapad_slidebar/input/inputX/slidebar_mode
> for switching slidebar's modes.
>
> Now works on:
> IdeaPad Y550, Y550P.
>
> May work on (testing and adding new models is needed):
> Ideapad Y560, Y460, Y450, Y650,
> and, probably, some others.
>
> Driver source: https://github.com/o2genum/ideapad-slidebar.git
>
> Patch is generated against current mainline kernel.
>
> Signed-off-by: Andrey Moiseev <o2g.org.ru@gmail.com>
> ---
> MAINTAINERS | 7 +
> drivers/input/misc/Kconfig | 9 +
> drivers/input/misc/Makefile | 1 +
> drivers/input/misc/ideapad_slidebar.c | 348 ++++++++++++++++++++++++++++++++++
> 4 files changed, 365 insertions(+)
> create mode 100644 drivers/input/misc/ideapad_slidebar.c
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index defc053..2ff3dd8 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -4096,6 +4096,13 @@ W: http://launchpad.net/ideapad-laptop
> S: Maintained
> F: drivers/platform/x86/ideapad-laptop.c
>
> +IDEAPAD LAPTOP SLIDEBAR DRIVER
> +M: Andrey Moiseev <o2g.org.ru@gmail.com>
> +L: linux-input@vger.kernel.org
> +W: https://github.com/o2genum/ideapad-slidebar
> +S: Maintained
> +F: drivers/input/misc/ideapad_slidebar.c
> +
> IDE/ATAPI DRIVERS
> M: Borislav Petkov <bp@alien8.de>
> L: linux-ide@vger.kernel.org
> diff --git a/drivers/input/misc/Kconfig b/drivers/input/misc/Kconfig
> index 0b541cd..45729a9 100644
> --- a/drivers/input/misc/Kconfig
> +++ b/drivers/input/misc/Kconfig
> @@ -647,4 +647,13 @@ config INPUT_SIRFSOC_ONKEY
>
> If unsure, say N.
>
> +config INPUT_IDEAPAD_SLIDEBAR
> + tristate "IdeaPad Laptop Slidebar"
> + depends on INPUT
> + help
> + Input driver for slidebars on some Lenovo IdeaPad laptops.
> +
> + If you have an IdeaPad laptop with a slidebar, say Y or M here.
> + Module name is ideapad_slidebar.
> +
> endif
> diff --git a/drivers/input/misc/Makefile b/drivers/input/misc/Makefile
> index 829de43..0ebfb6d 100644
> --- a/drivers/input/misc/Makefile
> +++ b/drivers/input/misc/Makefile
> @@ -61,3 +61,4 @@ obj-$(CONFIG_INPUT_WISTRON_BTNS) += wistron_btns.o
> obj-$(CONFIG_INPUT_WM831X_ON) += wm831x-on.o
> obj-$(CONFIG_INPUT_XEN_KBDDEV_FRONTEND) += xen-kbdfront.o
> obj-$(CONFIG_INPUT_YEALINK) += yealink.o
> +obj-$(CONFIG_INPUT_IDEAPAD_SLIDEBAR) += ideapad_slidebar.o
> diff --git a/drivers/input/misc/ideapad_slidebar.c b/drivers/input/misc/ideapad_slidebar.c
> new file mode 100644
> index 0000000..5725d88
> --- /dev/null
> +++ b/drivers/input/misc/ideapad_slidebar.c
> @@ -0,0 +1,348 @@
> +/*
> + * Input driver for slidebars on some Lenovo IdeaPad laptops
> + *
> + * Copyright (C) 2013 Andrey Moiseev <o2g.org.ru@gmail.com>
> + *
> + * Reverse-engineered from Lenovo SlideNav software (SBarHook.dll).
> + *
> + * 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.
> + *
> + * Trademarks are the property of their respective owners.
> + */
> +
> +/*
> + * Currently tested and works on:
> + * Lenovo IdeaPad Y550
> + * Lenovo IdeaPad Y550P
> + *
> + * Other models can be added easily. To test,
> + * load with 'force' parameter set 'true'.
> + *
> + * LEDs blinking and input mode are managed via sysfs,
> + * (hex, unsigned byte value):
> + * /sys/devices/platform/ideapad_slidebar/input/inputX/slidebar_mode
> + *
> + * The value is in byte range, however, I only figured out
> + * how bits 0b10011001 work. Some other bits, probably,
> + * are meaningfull too.
> + *
> + * Possible states:
> + *
> + * STD_INT, ONMOV_INT, OFF_INT, LAST_POLL, OFF_POLL
> + *
> + * Meaning:
> + * released touched
> + * STD 'heartbeat' lights follow the finger
> + * ONMOV no lights lights follow the finger
> + * LAST at last pos lights follow the finger
> + * OFF no lights no lights
> + *
> + * INT all input events are generated, interrupts are used
> + * POLL no input events by default, to get them,
> + * send 0b10000000 (read below)
> + *
> + * Commands: write
> + *
> + * All | 0b01001 -> STD_INT
> + * possible | 0b10001 -> ONMOV_INT
> + * states | 0b01000 -> OFF_INT
> + *
> + * | 0b0 -> LAST_POLL
> + * STD_INT or ONMOV_INT |
> + * | 0b1 -> STD_INT
> + *
> + * | 0b0 -> OFF_POLL
> + * OFF_INT or OFF_POLL |
> + * | 0b1 -> OFF_INT
> + *
> + * Any state | 0b10000000 -> if the slidebar has updated data,
> + * produce one input event (last position),
> + * switch to respective POLL mode
> + * (like 0x0), if not in POLL mode yet.
> + *
> + * Get current state: read
> + *
> + * masked by 0x11 read value means:
> + *
> + * 0x00 LAST
> + * 0x01 STD
> + * 0x10 OFF
> + * 0x11 ONMOV
> + */
> +
> +#include <linux/module.h>
> +#include <linux/kernel.h>
> +#include <linux/dmi.h>
> +#include <linux/spinlock.h>
> +#include <linux/platform_device.h>
> +#include <linux/input.h>
> +#include <linux/io.h>
> +#include <linux/i8042.h>
> +
> +static bool force;
> +module_param(force, bool, 0);
> +MODULE_PARM_DESC(force, "Force driver load, ignore DMI data");
> +
> +static spinlock_t sio_lock = __SPIN_LOCK_UNLOCKED(sio_lock);
static DEFINE_SPINLOCK(sio_lock);
> +
> +static struct input_dev *slidebar_input_dev;
> +static struct platform_device *slidebar_platform_dev;
> +
> +/* Hardware interacting */
> +static unsigned char slidebar_pos_get(void)
> +{
> + int res;
> + unsigned long flags;
> +
> + spin_lock_irqsave(&sio_lock, flags);
> + outb(0xf4, 0xff29);
> + outb(0xbf, 0xff2a);
> + res = inb(0xff2b);
You are banging on ports but not reserving them.
> + spin_unlock_irqrestore(&sio_lock, flags);
> + return res;
> +}
> +
> +static unsigned char slidebar_mode_get(void)
> +{
> + int res;
> + unsigned long flags;
> +
> + spin_lock_irqsave(&sio_lock, flags);
> + outb(0xf7, 0xff29);
> + outb(0x8b, 0xff2a);
> + res = inb(0xff2b);
> + spin_unlock_irqrestore(&sio_lock, flags);
> + return res;
> +}
> +
> +static void slidebar_mode_set(unsigned char mode)
> +{
> + unsigned long flags;
> +
> + spin_lock_irqsave(&sio_lock, flags);
> + outb(0xf7, 0xff29);
> + outb(0x8b, 0xff2a);
> + outb(mode, 0xff2b);
> + spin_unlock_irqrestore(&sio_lock, flags);
> +}
> +
> +/* Listening the keyboard (i8042 filter) */
> +static bool slidebar_i8042_filter(unsigned char data, unsigned char str,
> + struct serio *port)
> +{
> + static bool extended = false, touched = false;
> +
> + /* Scancodes: e03b on move, e0bb on release */
> + if (unlikely(data == 0xe0)) {
> + extended = true;
> + return false;
> + } else if (unlikely(extended && (data == 0x3b))) {
> + extended = false;
> + if (!touched)
> + input_report_key(slidebar_input_dev, BTN_TOUCH, 1);
> + touched = true;
Input core will filter out duplicate events for you so you do not really
need to do it here.
You probably want something like:
if (unlikely(extended && (data & 0x3b) == 0x3b)) {
input_report_key(slidebar_input_dev, BTN_TOUCH, data & 0x80);
input_report_abs(slidebar_input_dev, ABS_X, slidebar_pos_get());
input_sync(slidebar_input_dev);
}
> + input_report_abs(slidebar_input_dev, ABS_X, slidebar_pos_get());
> + input_sync(slidebar_input_dev);
> + return false;
> + } else if (unlikely(extended && (data == 0xbb))) {
> + touched = false;
> + input_report_key(slidebar_input_dev, BTN_TOUCH, 0);
> + input_sync(slidebar_input_dev);
> + }
> + return false;
> +}
> +
> +/* Sysfs slidebar_mode interface */
> +static ssize_t show_slidebar_mode(struct device *dev,
> + struct device_attribute *attr,
> + char *buf)
> +{
> + return sprintf(buf, "%x\n", slidebar_mode_get());
> +}
> +
> +static ssize_t store_slidebar_mode(struct device *dev,
> + struct device_attribute *attr,
> + const char *buf, size_t count)
> +{
> + int mode;
> +
> + if (!count)
> + return 0;
> + if (sscanf(buf, "%x", &mode) != 1)
> + return -EINVAL;
> + slidebar_mode_set(mode);
> + return count;
> +}
> +
> +static DEVICE_ATTR(slidebar_mode, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH,
> + show_slidebar_mode, store_slidebar_mode);
> +
> +static struct attribute *ideapad_attrs[] = {
> + &dev_attr_slidebar_mode.attr,
> + NULL
> +};
> +
> +static struct attribute_group ideapad_attr_group = {
> + .attrs = ideapad_attrs
> +};
> +
> +static const struct attribute_group *ideapad_attr_groups[] = {
> + &ideapad_attr_group,
> + NULL
> +};
> +
> +/* Input device */
> +static int __init setup_input_dev(void)
> +{
> + int err;
> +
> + slidebar_input_dev = input_allocate_device();
> + if (!slidebar_input_dev) {
> + pr_err("ideapad_slidebar: Not enough memory\n");
> + return -ENOMEM;
> + }
> +
> + slidebar_input_dev->name = "IdeaPad Slidebar";
> + slidebar_input_dev->id.bustype = BUS_HOST;
> + slidebar_input_dev->dev.parent = &slidebar_platform_dev->dev;
> + slidebar_input_dev->dev.groups = ideapad_attr_groups;
This should belong to the platform device, not input device, as I prefer
the latter to be generic and not have any custom attributes.
> + input_set_capability(slidebar_input_dev, EV_KEY, BTN_TOUCH);
> + input_set_capability(slidebar_input_dev, EV_ABS, ABS_X);
> + input_set_abs_params(slidebar_input_dev, ABS_X, 0, 0xff, 0, 0);
> +
> + err = i8042_install_filter(slidebar_i8042_filter);
> + if (err) {
> + pr_err("ideapad_slidebar: Can't install i8042 filter \n");
> + goto err_free_dev;
> + }
> + err = input_register_device(slidebar_input_dev);
> + if (err) {
> + pr_err("ideapad_slidebar: Failed to register device\n");
> + goto err_remove_filter;
> + }
> + return 0;
> +
> +err_remove_filter:
> + i8042_remove_filter(slidebar_i8042_filter);
> +err_free_dev:
> + input_free_device(slidebar_input_dev);
> + return err;
> +}
> +
> +static void remove_input_dev(void)
> +{
> + i8042_remove_filter(slidebar_i8042_filter);
> + input_unregister_device(slidebar_input_dev);
> +}
> +
> +/* Platform device */
> +static int __init setup_platform_dev(void)
> +{
> + int err;
> + slidebar_platform_dev = platform_device_alloc("ideapad_slidebar", -1);
> + if (!slidebar_platform_dev) {
> + pr_err("ideapad_slidebar: Not enough memory\n");
> + return -ENOMEM;
> + }
> + err = platform_device_add(slidebar_platform_dev);
> + if (err) {
> + pr_err("ideapad_slidebar: Failed to register plarform device\n");
> + goto err_free_platform_device;
> + }
> + return 0;
> +
> +err_free_platform_device:
> + platform_device_put(slidebar_platform_dev);
> + return err;
> +}
> +
> +/* Platform driver */
> +static struct platform_driver slidebar_drv = {
> + .driver = {
> + .name = "ideapad_slidebar",
> + .owner = THIS_MODULE,
> + },
> +};
> +
> +static int __init register_platform_drv(void)
> +{
> + int err;
> +
> + err = platform_driver_register(&slidebar_drv);
> + if (err)
> + pr_err("ideapad_slidebar: Failed to register platform driver\n");
> +
> + return err;
I still do not understand why you need these wrappers. I'd get rid of
register_platform_drv(), setup_platform_dev(). BTW, setup_input_dev() and
remove_input_dev() should become probe() and remove() methods of the
platform driver.
> +}
> +
> +/* DMI */
> +static int __init ideapad_dmi_check(const struct dmi_system_id *id)
> +{
> + pr_info("ideapad_slidebar: Laptop model '%s'\n", id->ident);
> + return 1;
> +}
> +
> +static const struct dmi_system_id ideapad_dmi[] __initconst = {
> + {
> + .ident = "Lenovo IdeaPad Y550",
> + .matches = {
> + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
> + DMI_MATCH(DMI_PRODUCT_NAME, "20017"),
> + DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo IdeaPad Y550")
> + },
> + .callback = ideapad_dmi_check
> + },
> + {
> + .ident = "Lenovo IdeaPad Y550P",
> + .matches = {
> + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
> + DMI_MATCH(DMI_PRODUCT_NAME, "20035"),
> + DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo IdeaPad Y550P")
> + },
> + .callback = ideapad_dmi_check
> + },
> + { NULL, }
> +};
> +MODULE_DEVICE_TABLE(dmi, ideapad_dmi);
> +
> +/* Init and cleanup */
> +static int __init slidebar_init(void)
> +{
> + int err;
> +
> + if (!force && !dmi_check_system(ideapad_dmi))
> + return -ENODEV;
> + err = setup_platform_dev();
> + if (err)
> + return err;
> + err = register_platform_drv();
> + if (err)
> + goto err_unregister_platform_dev;
> + err = setup_input_dev();
> + if (err)
> + goto err_unregister_platform_drv;
> + return 0;
> +
> +err_unregister_platform_drv:
> + platform_driver_unregister(&slidebar_drv);
> +err_unregister_platform_dev:
> + platform_device_unregister(slidebar_platform_dev);
> + return err;
> +}
> +
> +static void __exit slidebar_exit(void)
> +{
> + remove_input_dev();
> + platform_device_unregister(slidebar_platform_dev);
> + platform_driver_unregister(&slidebar_drv);
> +}
> +
> +module_init(slidebar_init);
> +module_exit(slidebar_exit);
> +
> +MODULE_AUTHOR("Andrey Moiseev <o2g.org.ru@gmail.com>");
> +MODULE_DESCRIPTION("Slidebar input support for some Lenovo IdeaPad laptops");
> +MODULE_LICENSE("GPL");
> --
> 1.8.3.4
Thanks!
--
Dmitry
^ permalink raw reply
* [PATCH v7 1/1] input: ideapad_slidebar: new input driver
From: Andrey Moiseev @ 2013-08-16 6:58 UTC (permalink / raw)
To: linux-input
Cc: dmitry.torokhov, arnoques, russianneuromancer, ike.pan,
linux-kernel
v7: fixed things addressed by Dmitry Torokhov to v7 (quoted message goes below the patch)
ideapad_slidebar is a new driver which enables slidebars on some
Lenovo IdeaPad laptops (the slidebars work with SlideNav/Desktop
Navigator under Windows)
Fixes this: https://bugzilla.kernel.org/show_bug.cgi?id=16004
Registers 'IdeaPad Slidebar' input device and
/sys/devices/platform/ideapad_slidebar/slidebar_mode
for switching slidebar's modes.
Now works on:
IdeaPad Y550, Y550P.
May work on (testing and adding new models is needed):
Ideapad Y560, Y460, Y450, Y650,
and, probably, some others.
Driver source: https://github.com/o2genum/ideapad-slidebar.git
Patch is generated against current mainline kernel.
Signed-off-by: Andrey Moiseev <o2g.org.ru@gmail.com>
---
MAINTAINERS | 7 +
drivers/input/misc/Kconfig | 9 +
drivers/input/misc/Makefile | 1 +
drivers/input/misc/ideapad_slidebar.c | 330 ++++++++++++++++++++++++++++++++++
4 files changed, 347 insertions(+)
create mode 100644 drivers/input/misc/ideapad_slidebar.c
diff --git a/MAINTAINERS b/MAINTAINERS
index defc053..2ff3dd8 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -4096,6 +4096,13 @@ W: http://launchpad.net/ideapad-laptop
S: Maintained
F: drivers/platform/x86/ideapad-laptop.c
+IDEAPAD LAPTOP SLIDEBAR DRIVER
+M: Andrey Moiseev <o2g.org.ru@gmail.com>
+L: linux-input@vger.kernel.org
+W: https://github.com/o2genum/ideapad-slidebar
+S: Maintained
+F: drivers/input/misc/ideapad_slidebar.c
+
IDE/ATAPI DRIVERS
M: Borislav Petkov <bp@alien8.de>
L: linux-ide@vger.kernel.org
diff --git a/drivers/input/misc/Kconfig b/drivers/input/misc/Kconfig
index 0b541cd..45729a9 100644
--- a/drivers/input/misc/Kconfig
+++ b/drivers/input/misc/Kconfig
@@ -647,4 +647,13 @@ config INPUT_SIRFSOC_ONKEY
If unsure, say N.
+config INPUT_IDEAPAD_SLIDEBAR
+ tristate "IdeaPad Laptop Slidebar"
+ depends on INPUT
+ help
+ Input driver for slidebars on some Lenovo IdeaPad laptops.
+
+ If you have an IdeaPad laptop with a slidebar, say Y or M here.
+ Module name is ideapad_slidebar.
+
endif
diff --git a/drivers/input/misc/Makefile b/drivers/input/misc/Makefile
index 829de43..0ebfb6d 100644
--- a/drivers/input/misc/Makefile
+++ b/drivers/input/misc/Makefile
@@ -61,3 +61,4 @@ obj-$(CONFIG_INPUT_WISTRON_BTNS) += wistron_btns.o
obj-$(CONFIG_INPUT_WM831X_ON) += wm831x-on.o
obj-$(CONFIG_INPUT_XEN_KBDDEV_FRONTEND) += xen-kbdfront.o
obj-$(CONFIG_INPUT_YEALINK) += yealink.o
+obj-$(CONFIG_INPUT_IDEAPAD_SLIDEBAR) += ideapad_slidebar.o
diff --git a/drivers/input/misc/ideapad_slidebar.c b/drivers/input/misc/ideapad_slidebar.c
new file mode 100644
index 0000000..572866f
--- /dev/null
+++ b/drivers/input/misc/ideapad_slidebar.c
@@ -0,0 +1,330 @@
+/*
+ * Input driver for slidebars on some Lenovo IdeaPad laptops
+ *
+ * Copyright (C) 2013 Andrey Moiseev <o2g.org.ru@gmail.com>
+ *
+ * Reverse-engineered from Lenovo SlideNav software (SBarHook.dll).
+ *
+ * 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.
+ *
+ * Trademarks are the property of their respective owners.
+ */
+
+/*
+ * Currently tested and works on:
+ * Lenovo IdeaPad Y550
+ * Lenovo IdeaPad Y550P
+ *
+ * Other models can be added easily. To test,
+ * load with 'force' parameter set 'true'.
+ *
+ * LEDs blinking and input mode are managed via sysfs,
+ * (hex, unsigned byte value):
+ * /sys/devices/platform/ideapad_slidebar/slidebar_mode
+ *
+ * The value is in byte range, however, I only figured out
+ * how bits 0b10011001 work. Some other bits, probably,
+ * are meaningfull too.
+ *
+ * Possible states:
+ *
+ * STD_INT, ONMOV_INT, OFF_INT, LAST_POLL, OFF_POLL
+ *
+ * Meaning:
+ * released touched
+ * STD 'heartbeat' lights follow the finger
+ * ONMOV no lights lights follow the finger
+ * LAST at last pos lights follow the finger
+ * OFF no lights no lights
+ *
+ * INT all input events are generated, interrupts are used
+ * POLL no input events by default, to get them,
+ * send 0b10000000 (read below)
+ *
+ * Commands: write
+ *
+ * All | 0b01001 -> STD_INT
+ * possible | 0b10001 -> ONMOV_INT
+ * states | 0b01000 -> OFF_INT
+ *
+ * | 0b0 -> LAST_POLL
+ * STD_INT or ONMOV_INT |
+ * | 0b1 -> STD_INT
+ *
+ * | 0b0 -> OFF_POLL
+ * OFF_INT or OFF_POLL |
+ * | 0b1 -> OFF_INT
+ *
+ * Any state | 0b10000000 -> if the slidebar has updated data,
+ * produce one input event (last position),
+ * switch to respective POLL mode
+ * (like 0x0), if not in POLL mode yet.
+ *
+ * Get current state: read
+ *
+ * masked by 0x11 read value means:
+ *
+ * 0x00 LAST
+ * 0x01 STD
+ * 0x10 OFF
+ * 0x11 ONMOV
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/dmi.h>
+#include <linux/spinlock.h>
+#include <linux/platform_device.h>
+#include <linux/input.h>
+#include <linux/io.h>
+#include <linux/ioport.h>
+#include <linux/i8042.h>
+
+static bool force;
+module_param(force, bool, 0);
+MODULE_PARM_DESC(force, "Force driver load, ignore DMI data");
+
+static DEFINE_SPINLOCK(io_lock);
+
+static struct input_dev *slidebar_input_dev;
+static struct platform_device *slidebar_platform_dev;
+
+static unsigned char slidebar_pos_get(void)
+{
+ int res;
+ unsigned long flags;
+
+ spin_lock_irqsave(&io_lock, flags);
+ outb(0xf4, 0xff29);
+ outb(0xbf, 0xff2a);
+ res = inb(0xff2b);
+ spin_unlock_irqrestore(&io_lock, flags);
+ return res;
+}
+
+static unsigned char slidebar_mode_get(void)
+{
+ int res;
+ unsigned long flags;
+
+ spin_lock_irqsave(&io_lock, flags);
+ outb(0xf7, 0xff29);
+ outb(0x8b, 0xff2a);
+ res = inb(0xff2b);
+ spin_unlock_irqrestore(&io_lock, flags);
+ return res;
+}
+
+static void slidebar_mode_set(unsigned char mode)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&io_lock, flags);
+ outb(0xf7, 0xff29);
+ outb(0x8b, 0xff2a);
+ outb(mode, 0xff2b);
+ spin_unlock_irqrestore(&io_lock, flags);
+}
+
+static bool slidebar_i8042_filter(unsigned char data, unsigned char str,
+ struct serio *port)
+{
+ static bool extended = false;
+
+ /* Scancodes: e03b on move, e0bb on release */
+ if (unlikely(data == 0xe0)) {
+ extended = true;
+ return false;
+ } else if (unlikely(extended && (data == 0x3b))) {
+ extended = false;
+ input_report_key(slidebar_input_dev, BTN_TOUCH, 1);
+ input_report_abs(slidebar_input_dev, ABS_X, slidebar_pos_get());
+ input_sync(slidebar_input_dev);
+ return false;
+ } else if (unlikely(extended && (data == 0xbb))) {
+ input_report_key(slidebar_input_dev, BTN_TOUCH, 0);
+ input_sync(slidebar_input_dev);
+ }
+ return false;
+}
+
+static ssize_t show_slidebar_mode(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ return sprintf(buf, "%x\n", slidebar_mode_get());
+}
+
+static ssize_t store_slidebar_mode(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ int mode;
+
+ if (!count)
+ return 0;
+ if (sscanf(buf, "%x", &mode) != 1)
+ return -EINVAL;
+ slidebar_mode_set(mode);
+ return count;
+}
+
+static DEVICE_ATTR(slidebar_mode, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH,
+ show_slidebar_mode, store_slidebar_mode);
+
+static struct attribute *ideapad_attrs[] = {
+ &dev_attr_slidebar_mode.attr,
+ NULL
+};
+
+static struct attribute_group ideapad_attr_group = {
+ .attrs = ideapad_attrs
+};
+
+static const struct attribute_group *ideapad_attr_groups[] = {
+ &ideapad_attr_group,
+ NULL
+};
+
+static int probe(struct platform_device* dev)
+{
+ int err;
+
+ if (!request_region(0xff29, 3, "ideapad_slidebar")) {
+ pr_err("ideapad_slidebar: IO ports are busy\n");
+ return -EBUSY;
+ }
+
+ slidebar_input_dev = input_allocate_device();
+ if (!slidebar_input_dev) {
+ pr_err("ideapad_slidebar: Not enough memory\n");
+ err = -ENOMEM;
+ goto err_release_ports;
+ }
+
+ slidebar_input_dev->name = "IdeaPad Slidebar";
+ slidebar_input_dev->id.bustype = BUS_HOST;
+ slidebar_input_dev->dev.parent = &slidebar_platform_dev->dev;
+ input_set_capability(slidebar_input_dev, EV_KEY, BTN_TOUCH);
+ input_set_capability(slidebar_input_dev, EV_ABS, ABS_X);
+ input_set_abs_params(slidebar_input_dev, ABS_X, 0, 0xff, 0, 0);
+
+ err = i8042_install_filter(slidebar_i8042_filter);
+ if (err) {
+ pr_err("ideapad_slidebar: Can't install i8042 filter \n");
+ goto err_free_dev;
+ }
+
+ err = input_register_device(slidebar_input_dev);
+ if (err) {
+ pr_err("ideapad_slidebar: Failed to register input device\n");
+ goto err_remove_filter;
+ }
+ return 0;
+
+err_remove_filter:
+ i8042_remove_filter(slidebar_i8042_filter);
+err_free_dev:
+ input_free_device(slidebar_input_dev);
+err_release_ports:
+ release_region(0xff29, 3);
+ return err;
+}
+
+static int remove(struct platform_device *dev)
+{
+ i8042_remove_filter(slidebar_i8042_filter);
+ input_unregister_device(slidebar_input_dev);
+ release_region(0xff29, 3);
+ return 0;
+}
+
+static struct platform_driver slidebar_drv = {
+ .driver = {
+ .name = "ideapad_slidebar",
+ .owner = THIS_MODULE,
+ },
+ .probe = probe,
+ .remove = remove
+};
+
+static int __init ideapad_dmi_check(const struct dmi_system_id *id)
+{
+ pr_info("ideapad_slidebar: Laptop model '%s'\n", id->ident);
+ return 1;
+}
+
+static const struct dmi_system_id ideapad_dmi[] __initconst = {
+ {
+ .ident = "Lenovo IdeaPad Y550",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "20017"),
+ DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo IdeaPad Y550")
+ },
+ .callback = ideapad_dmi_check
+ },
+ {
+ .ident = "Lenovo IdeaPad Y550P",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "20035"),
+ DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo IdeaPad Y550P")
+ },
+ .callback = ideapad_dmi_check
+ },
+ { NULL, }
+};
+MODULE_DEVICE_TABLE(dmi, ideapad_dmi);
+
+static int __init slidebar_init(void)
+{
+ int err;
+
+ if (!force && !dmi_check_system(ideapad_dmi)) {
+ pr_err("ideapad_slidebar: DMI didn't match\n");
+ return -ENODEV;
+ }
+
+ err = platform_driver_register(&slidebar_drv);
+ if (err) {
+ pr_err("ideapad_slidebar: Failed to register platform driver\n");
+ return err;
+ }
+
+ slidebar_platform_dev = platform_device_alloc("ideapad_slidebar", -1);
+ slidebar_platform_dev->dev.groups = ideapad_attr_groups;
+ if (!slidebar_platform_dev) {
+ pr_err("ideapad_slidebar: Not enough memory\n");
+ goto err_unregister_drv;
+ }
+
+ err = platform_device_add(slidebar_platform_dev);
+ if (err) {
+ pr_err("ideapad_slidebar: Failed to register plarform device\n");
+ goto err_free_dev;
+ }
+ return 0;
+
+err_free_dev:
+ platform_device_put(slidebar_platform_dev);
+err_unregister_drv:
+ platform_driver_unregister(&slidebar_drv);
+ return err;
+}
+
+static void __exit slidebar_exit(void)
+{
+ platform_device_unregister(slidebar_platform_dev);
+ platform_driver_unregister(&slidebar_drv);
+}
+
+module_init(slidebar_init);
+module_exit(slidebar_exit);
+
+MODULE_AUTHOR("Andrey Moiseev <o2g.org.ru@gmail.com>");
+MODULE_DESCRIPTION("Slidebar input support for some Lenovo IdeaPad laptops");
+MODULE_LICENSE("GPL");
--
1.8.3.4
---
> Hi Andrey,
>
> On Thu, Aug 15, 2013 at 07:06:39PM +0400, Andrey Moiseev wrote:
>> v6: fixed things addressed by Dmitry Torokhov to v5 (quoted message goes below the patch)
>
> Thank you for making chnages, however I have still more comments.
>
>>
>> ideapad_slidebar is a new driver which enables slidebars on some
>> Lenovo IdeaPad laptops (the slidebars work with SlideNav/Desktop
>> Navigator under Windows)
>>
>> Fixes this: https://bugzilla.kernel.org/show_bug.cgi?id=16004
>>
>> Registers 'IdeaPad Slidebar' input device and
>> /sys/devices/platform/ideapad_slidebar/input/inputX/slidebar_mode
>> for switching slidebar's modes.
>>
>> Now works on:
>> IdeaPad Y550, Y550P.
>>
>> May work on (testing and adding new models is needed):
>> Ideapad Y560, Y460, Y450, Y650,
>> and, probably, some others.
>>
>> Driver source: https://github.com/o2genum/ideapad-slidebar.git
>>
>> Patch is generated against current mainline kernel.
>>
>> Signed-off-by: Andrey Moiseev <o2g.org.ru@gmail.com>
>> ---
>> MAINTAINERS | 7 +
>> drivers/input/misc/Kconfig | 9 +
>> drivers/input/misc/Makefile | 1 +
>> drivers/input/misc/ideapad_slidebar.c | 348 ++++++++++++++++++++++++++++++++++
>> 4 files changed, 365 insertions(+)
>> create mode 100644 drivers/input/misc/ideapad_slidebar.c
>>
>> diff --git a/MAINTAINERS b/MAINTAINERS
>> index defc053..2ff3dd8 100644
>> --- a/MAINTAINERS
>> +++ b/MAINTAINERS
>> @@ -4096,6 +4096,13 @@ W: http://launchpad.net/ideapad-laptop
>> S: Maintained
>> F: drivers/platform/x86/ideapad-laptop.c
>>
>> +IDEAPAD LAPTOP SLIDEBAR DRIVER
>> +M: Andrey Moiseev <o2g.org.ru@gmail.com>
>> +L: linux-input@vger.kernel.org
>> +W: https://github.com/o2genum/ideapad-slidebar
>> +S: Maintained
>> +F: drivers/input/misc/ideapad_slidebar.c
>> +
>> IDE/ATAPI DRIVERS
>> M: Borislav Petkov <bp@alien8.de>
>> L: linux-ide@vger.kernel.org
>> diff --git a/drivers/input/misc/Kconfig b/drivers/input/misc/Kconfig
>> index 0b541cd..45729a9 100644
>> --- a/drivers/input/misc/Kconfig
>> +++ b/drivers/input/misc/Kconfig
>> @@ -647,4 +647,13 @@ config INPUT_SIRFSOC_ONKEY
>>
>> If unsure, say N.
>>
>> +config INPUT_IDEAPAD_SLIDEBAR
>> + tristate "IdeaPad Laptop Slidebar"
>> + depends on INPUT
>> + help
>> + Input driver for slidebars on some Lenovo IdeaPad laptops.
>> +
>> + If you have an IdeaPad laptop with a slidebar, say Y or M here.
>> + Module name is ideapad_slidebar.
>> +
>> endif
>> diff --git a/drivers/input/misc/Makefile b/drivers/input/misc/Makefile
>> index 829de43..0ebfb6d 100644
>> --- a/drivers/input/misc/Makefile
>> +++ b/drivers/input/misc/Makefile
>> @@ -61,3 +61,4 @@ obj-$(CONFIG_INPUT_WISTRON_BTNS) += wistron_btns.o
>> obj-$(CONFIG_INPUT_WM831X_ON) += wm831x-on.o
>> obj-$(CONFIG_INPUT_XEN_KBDDEV_FRONTEND) += xen-kbdfront.o
>> obj-$(CONFIG_INPUT_YEALINK) += yealink.o
>> +obj-$(CONFIG_INPUT_IDEAPAD_SLIDEBAR) += ideapad_slidebar.o
>> diff --git a/drivers/input/misc/ideapad_slidebar.c b/drivers/input/misc/ideapad_slidebar.c
>> new file mode 100644
>> index 0000000..5725d88
>> --- /dev/null
>> +++ b/drivers/input/misc/ideapad_slidebar.c
>> @@ -0,0 +1,348 @@
>> +/*
>> + * Input driver for slidebars on some Lenovo IdeaPad laptops
>> + *
>> + * Copyright (C) 2013 Andrey Moiseev <o2g.org.ru@gmail.com>
>> + *
>> + * Reverse-engineered from Lenovo SlideNav software (SBarHook.dll).
>> + *
>> + * 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.
>> + *
>> + * Trademarks are the property of their respective owners.
>> + */
>> +
>> +/*
>> + * Currently tested and works on:
>> + * Lenovo IdeaPad Y550
>> + * Lenovo IdeaPad Y550P
>> + *
>> + * Other models can be added easily. To test,
>> + * load with 'force' parameter set 'true'.
>> + *
>> + * LEDs blinking and input mode are managed via sysfs,
>> + * (hex, unsigned byte value):
>> + * /sys/devices/platform/ideapad_slidebar/input/inputX/slidebar_mode
>> + *
>> + * The value is in byte range, however, I only figured out
>> + * how bits 0b10011001 work. Some other bits, probably,
>> + * are meaningfull too.
>> + *
>> + * Possible states:
>> + *
>> + * STD_INT, ONMOV_INT, OFF_INT, LAST_POLL, OFF_POLL
>> + *
>> + * Meaning:
>> + * released touched
>> + * STD 'heartbeat' lights follow the finger
>> + * ONMOV no lights lights follow the finger
>> + * LAST at last pos lights follow the finger
>> + * OFF no lights no lights
>> + *
>> + * INT all input events are generated, interrupts are used
>> + * POLL no input events by default, to get them,
>> + * send 0b10000000 (read below)
>> + *
>> + * Commands: write
>> + *
>> + * All | 0b01001 -> STD_INT
>> + * possible | 0b10001 -> ONMOV_INT
>> + * states | 0b01000 -> OFF_INT
>> + *
>> + * | 0b0 -> LAST_POLL
>> + * STD_INT or ONMOV_INT |
>> + * | 0b1 -> STD_INT
>> + *
>> + * | 0b0 -> OFF_POLL
>> + * OFF_INT or OFF_POLL |
>> + * | 0b1 -> OFF_INT
>> + *
>> + * Any state | 0b10000000 -> if the slidebar has updated data,
>> + * produce one input event (last position),
>> + * switch to respective POLL mode
>> + * (like 0x0), if not in POLL mode yet.
>> + *
>> + * Get current state: read
>> + *
>> + * masked by 0x11 read value means:
>> + *
>> + * 0x00 LAST
>> + * 0x01 STD
>> + * 0x10 OFF
>> + * 0x11 ONMOV
>> + */
>> +
>> +#include <linux/module.h>
>> +#include <linux/kernel.h>
>> +#include <linux/dmi.h>
>> +#include <linux/spinlock.h>
>> +#include <linux/platform_device.h>
>> +#include <linux/input.h>
>> +#include <linux/io.h>
>> +#include <linux/i8042.h>
>> +
>> +static bool force;
>> +module_param(force, bool, 0);
>> +MODULE_PARM_DESC(force, "Force driver load, ignore DMI data");
>> +
>> +static spinlock_t sio_lock = __SPIN_LOCK_UNLOCKED(sio_lock);
>
> static DEFINE_SPINLOCK(sio_lock);
>
>> +
>> +static struct input_dev *slidebar_input_dev;
>> +static struct platform_device *slidebar_platform_dev;
>> +
>> +/* Hardware interacting */
>> +static unsigned char slidebar_pos_get(void)
>> +{
>> + int res;
>> + unsigned long flags;
>> +
>> + spin_lock_irqsave(&sio_lock, flags);
>> + outb(0xf4, 0xff29);
>> + outb(0xbf, 0xff2a);
>> + res = inb(0xff2b);
>
> You are banging on ports but not reserving them.
>
>> + spin_unlock_irqrestore(&sio_lock, flags);
>> + return res;
>> +}
>> +
>> +static unsigned char slidebar_mode_get(void)
>> +{
>> + int res;
>> + unsigned long flags;
>> +
>> + spin_lock_irqsave(&sio_lock, flags);
>> + outb(0xf7, 0xff29);
>> + outb(0x8b, 0xff2a);
>> + res = inb(0xff2b);
>> + spin_unlock_irqrestore(&sio_lock, flags);
>> + return res;
>> +}
>> +
>> +static void slidebar_mode_set(unsigned char mode)
>> +{
>> + unsigned long flags;
>> +
>> + spin_lock_irqsave(&sio_lock, flags);
>> + outb(0xf7, 0xff29);
>> + outb(0x8b, 0xff2a);
>> + outb(mode, 0xff2b);
>> + spin_unlock_irqrestore(&sio_lock, flags);
>> +}
>> +
>> +/* Listening the keyboard (i8042 filter) */
>> +static bool slidebar_i8042_filter(unsigned char data, unsigned char str,
>> + struct serio *port)
>> +{
>> + static bool extended = false, touched = false;
>> +
>> + /* Scancodes: e03b on move, e0bb on release */
>> + if (unlikely(data == 0xe0)) {
>> + extended = true;
>> + return false;
>> + } else if (unlikely(extended && (data == 0x3b))) {
>> + extended = false;
>> + if (!touched)
>> + input_report_key(slidebar_input_dev, BTN_TOUCH, 1);
>> + touched = true;
>
> Input core will filter out duplicate events for you so you do not really
> need to do it here.
>
> You probably want something like:
>
> if (unlikely(extended && (data & 0x3b) == 0x3b)) {
> input_report_key(slidebar_input_dev, BTN_TOUCH, data & 0x80);
> input_report_abs(slidebar_input_dev, ABS_X, slidebar_pos_get());
> input_sync(slidebar_input_dev);
> }
>
>
>> + input_report_abs(slidebar_input_dev, ABS_X, slidebar_pos_get());
>> + input_sync(slidebar_input_dev);
>> + return false;
>> + } else if (unlikely(extended && (data == 0xbb))) {
>> + touched = false;
>> + input_report_key(slidebar_input_dev, BTN_TOUCH, 0);
>> + input_sync(slidebar_input_dev);
>> + }
>> + return false;
>> +}
>> +
>> +/* Sysfs slidebar_mode interface */
>> +static ssize_t show_slidebar_mode(struct device *dev,
>> + struct device_attribute *attr,
>> + char *buf)
>> +{
>> + return sprintf(buf, "%x\n", slidebar_mode_get());
>> +}
>> +
>> +static ssize_t store_slidebar_mode(struct device *dev,
>> + struct device_attribute *attr,
>> + const char *buf, size_t count)
>> +{
>> + int mode;
>> +
>> + if (!count)
>> + return 0;
>> + if (sscanf(buf, "%x", &mode) != 1)
>> + return -EINVAL;
>> + slidebar_mode_set(mode);
>> + return count;
>> +}
>> +
>> +static DEVICE_ATTR(slidebar_mode, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH,
>> + show_slidebar_mode, store_slidebar_mode);
>> +
>> +static struct attribute *ideapad_attrs[] = {
>> + &dev_attr_slidebar_mode.attr,
>> + NULL
>> +};
>> +
>> +static struct attribute_group ideapad_attr_group = {
>> + .attrs = ideapad_attrs
>> +};
>> +
>> +static const struct attribute_group *ideapad_attr_groups[] = {
>> + &ideapad_attr_group,
>> + NULL
>> +};
>> +
>> +/* Input device */
>> +static int __init setup_input_dev(void)
>> +{
>> + int err;
>> +
>> + slidebar_input_dev = input_allocate_device();
>> + if (!slidebar_input_dev) {
>> + pr_err("ideapad_slidebar: Not enough memory\n");
>> + return -ENOMEM;
>> + }
>> +
>> + slidebar_input_dev->name = "IdeaPad Slidebar";
>> + slidebar_input_dev->id.bustype = BUS_HOST;
>> + slidebar_input_dev->dev.parent = &slidebar_platform_dev->dev;
>> + slidebar_input_dev->dev.groups = ideapad_attr_groups;
>
> This should belong to the platform device, not input device, as I prefer
> the latter to be generic and not have any custom attributes.
>
>> + input_set_capability(slidebar_input_dev, EV_KEY, BTN_TOUCH);
>> + input_set_capability(slidebar_input_dev, EV_ABS, ABS_X);
>> + input_set_abs_params(slidebar_input_dev, ABS_X, 0, 0xff, 0, 0);
>> +
>> + err = i8042_install_filter(slidebar_i8042_filter);
>> + if (err) {
>> + pr_err("ideapad_slidebar: Can't install i8042 filter \n");
>> + goto err_free_dev;
>> + }
>> + err = input_register_device(slidebar_input_dev);
>> + if (err) {
>> + pr_err("ideapad_slidebar: Failed to register device\n");
>> + goto err_remove_filter;
>> + }
>> + return 0;
>> +
>> +err_remove_filter:
>> + i8042_remove_filter(slidebar_i8042_filter);
>> +err_free_dev:
>> + input_free_device(slidebar_input_dev);
>> + return err;
>> +}
>> +
>> +static void remove_input_dev(void)
>> +{
>> + i8042_remove_filter(slidebar_i8042_filter);
>> + input_unregister_device(slidebar_input_dev);
>> +}
>> +
>> +/* Platform device */
>> +static int __init setup_platform_dev(void)
>> +{
>> + int err;
>> + slidebar_platform_dev = platform_device_alloc("ideapad_slidebar", -1);
>> + if (!slidebar_platform_dev) {
>> + pr_err("ideapad_slidebar: Not enough memory\n");
>> + return -ENOMEM;
>> + }
>> + err = platform_device_add(slidebar_platform_dev);
>> + if (err) {
>> + pr_err("ideapad_slidebar: Failed to register plarform device\n");
>> + goto err_free_platform_device;
>> + }
>> + return 0;
>> +
>> +err_free_platform_device:
>> + platform_device_put(slidebar_platform_dev);
>> + return err;
>> +}
>> +
>> +/* Platform driver */
>> +static struct platform_driver slidebar_drv = {
>> + .driver = {
>> + .name = "ideapad_slidebar",
>> + .owner = THIS_MODULE,
>> + },
>> +};
>> +
>> +static int __init register_platform_drv(void)
>> +{
>> + int err;
>> +
>> + err = platform_driver_register(&slidebar_drv);
>> + if (err)
>> + pr_err("ideapad_slidebar: Failed to register platform driver\n");
>> +
>> + return err;
>
> I still do not understand why you need these wrappers. I'd get rid of
> register_platform_drv(), setup_platform_dev(). BTW, setup_input_dev() and
> remove_input_dev() should become probe() and remove() methods of the
> platform driver.
>
>> +}
>> +
>> +/* DMI */
>> +static int __init ideapad_dmi_check(const struct dmi_system_id *id)
>> +{
>> + pr_info("ideapad_slidebar: Laptop model '%s'\n", id->ident);
>> + return 1;
>> +}
>> +
>> +static const struct dmi_system_id ideapad_dmi[] __initconst = {
>> + {
>> + .ident = "Lenovo IdeaPad Y550",
>> + .matches = {
>> + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
>> + DMI_MATCH(DMI_PRODUCT_NAME, "20017"),
>> + DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo IdeaPad Y550")
>> + },
>> + .callback = ideapad_dmi_check
>> + },
>> + {
>> + .ident = "Lenovo IdeaPad Y550P",
>> + .matches = {
>> + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
>> + DMI_MATCH(DMI_PRODUCT_NAME, "20035"),
>> + DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo IdeaPad Y550P")
>> + },
>> + .callback = ideapad_dmi_check
>> + },
>> + { NULL, }
>> +};
>> +MODULE_DEVICE_TABLE(dmi, ideapad_dmi);
>> +
>> +/* Init and cleanup */
>> +static int __init slidebar_init(void)
>> +{
>> + int err;
>> +
>> + if (!force && !dmi_check_system(ideapad_dmi))
>> + return -ENODEV;
>> + err = setup_platform_dev();
>> + if (err)
>> + return err;
>> + err = register_platform_drv();
>> + if (err)
>> + goto err_unregister_platform_dev;
>> + err = setup_input_dev();
>> + if (err)
>> + goto err_unregister_platform_drv;
>> + return 0;
>> +
>> +err_unregister_platform_drv:
>> + platform_driver_unregister(&slidebar_drv);
>> +err_unregister_platform_dev:
>> + platform_device_unregister(slidebar_platform_dev);
>> + return err;
>> +}
>> +
>> +static void __exit slidebar_exit(void)
>> +{
>> + remove_input_dev();
>> + platform_device_unregister(slidebar_platform_dev);
>> + platform_driver_unregister(&slidebar_drv);
>> +}
>> +
>> +module_init(slidebar_init);
>> +module_exit(slidebar_exit);
>> +
>> +MODULE_AUTHOR("Andrey Moiseev <o2g.org.ru@gmail.com>");
>> +MODULE_DESCRIPTION("Slidebar input support for some Lenovo IdeaPad laptops");
>> +MODULE_LICENSE("GPL");
>> --
>> 1.8.3.4
>
> Thanks!
>
> --
> Dmitry
^ permalink raw reply related
* Re: [PATCH 1/4] input: ti_am335x_tsc: correct step mask update after IRQ
From: Sebastian Andrzej Siewior @ 2013-08-16 8:53 UTC (permalink / raw)
To: Zubair Lutfullah
Cc: jic23, dmitry.torokhov, sameo, lee.jones, linux-iio, linux-input,
linux-kernel, gregkh, Russ.Dill
In-Reply-To: <1376412499-21007-2-git-send-email-zubair.lutfullah@gmail.com>
* Zubair Lutfullah | 2013-08-13 17:48:16 [+0100]:
>diff --git a/drivers/input/touchscreen/ti_am335x_tsc.c b/drivers/input/touchscreen/ti_am335x_tsc.c
>index e1c5300..e165fcb 100644
>--- a/drivers/input/touchscreen/ti_am335x_tsc.c
>+++ b/drivers/input/touchscreen/ti_am335x_tsc.c
>@@ -52,6 +52,7 @@ struct titsc {
> u32 config_inp[4];
> u32 bit_xp, bit_xn, bit_yp, bit_yn;
> u32 inp_xp, inp_xn, inp_yp, inp_yn;
>+ u32 step_mask;
> };
>
> static unsigned int titsc_readl(struct titsc *ts, unsigned int reg)
>@@ -196,7 +197,8 @@ static void titsc_step_config(struct titsc *ts_dev)
>
> /* The steps1 … end and bit 0 for TS_Charge */
> stepenable = (1 << (end_step + 2)) - 1;
>- am335x_tsc_se_set(ts_dev->mfd_tscadc, stepenable);
>+ ts_dev->step_mask = stepenable;
>+ am335x_tsc_se_set(ts_dev->mfd_tscadc, ts_dev->step_mask);
> }
>
> static void titsc_read_coordinates(struct titsc *ts_dev,
>@@ -316,7 +318,7 @@ static irqreturn_t titsc_irq(int irq, void *dev)
>
> if (irqclr) {
> titsc_writel(ts_dev, REG_IRQSTATUS, irqclr);
>- am335x_tsc_se_update(ts_dev->mfd_tscadc);
>+ am335x_tsc_se_set(ts_dev->mfd_tscadc, ts_dev->step_mask);
> return IRQ_HANDLED;
> }
> return IRQ_NONE;
titsc_step_config() computes the mask once since it does not change. It
is then assigned via am335x_tsc_se_set() to ->reg_se_cache() and later
always udpated via am335x_tsc_se_update(). This should ensure that ADC's
and TSC's bits are in sync and clear each other out.
Now you call am335x_tsc_se_set() in every irq which adds the TSC's mask
to ->reg_se_cache but why? It was never removed.
Sebastian
^ permalink raw reply
* Re: [PATCH 1/3] HID: Use existing parser for pre-scanning the report descriptors
From: Alexander Holler @ 2013-08-16 8:54 UTC (permalink / raw)
To: Benjamin Tissoires
Cc: Benjamin Tissoires, Henrik Rydberg, Jiri Kosina, Stephane Chatty,
Mika Westerberg, linux-input, linux-kernel@vger.kernel.org
In-Reply-To: <CAN+gG=FEHefW-4XrCTkDY2_Jfj+uWZ=g_uc-2BsgFdW2My=VvA@mail.gmail.com>
Am 15.08.2013 19:36, schrieb Benjamin Tissoires:
> On Wed, Aug 14, 2013 at 10:03 PM, Alexander Holler <holler@ahsoftware.de> wrote:
>>>>> - hid->group = HID_GROUP_GENERIC;
>>>>> + parser = vzalloc(sizeof(struct hid_parser));
>>>>
>>>>
>>>> Argh, I realize it is inevitable for this patch, but it still makes my
>>>> eyes bleed. The parser takes quite a bit of memory...
>>>
>>>
>>> Yep, my first attempt was to remove it, then I re-added it with a small
>>> tear...
>>
>>
>> So you actually create a new parser and the subject (that existing) of this
>> patch is misleading.
>
> Hi Alexander,
>
> I think you misread what Henrik and I were discussing about:
> Henrik complained about using the heap for something which is just
> used in this function, and using the stack would have been better.
> However, the size of the parser is too big that the compiler complains
> when we declare it on the stack.
>
> So this line is just a copy/paste from the function hid_open_report(),
> and thus, you can agree that I did not create a new parser.
Hmm, not really, you do instantiate a new parser, wherever that one lives.
Of course, the code for the parser already exists, but at first I've
read the subject such, that something will be used which already was in
use. That "existing" did mislead me.
I think "Use hid_parser for ..." would describe the change more verbose
as the patch changes runtime behaviour quite a bit (by dispatching
everything through the parser).
Regards,
Alexander Holler
^ permalink raw reply
* Re: [PATCH 2/4] input: ti_am335x_tsc: Increase sequencer delay time
From: Sebastian Andrzej Siewior @ 2013-08-16 9:04 UTC (permalink / raw)
To: Zubair Lutfullah
Cc: jic23, dmitry.torokhov, sameo, lee.jones, linux-iio, linux-input,
linux-kernel, gregkh, Russ.Dill
In-Reply-To: <1376412499-21007-3-git-send-email-zubair.lutfullah@gmail.com>
* Zubair Lutfullah | 2013-08-13 17:48:17 [+0100]:
>diff --git a/drivers/input/touchscreen/ti_am335x_tsc.c b/drivers/input/touchscreen/ti_am335x_tsc.c
>index e165fcb..766bc7e 100644
>--- a/drivers/input/touchscreen/ti_am335x_tsc.c
>+++ b/drivers/input/touchscreen/ti_am335x_tsc.c
>@@ -31,7 +31,7 @@
> #include <linux/mfd/ti_am335x_tscadc.h>
>
> #define ADCFSM_STEPID 0x10
>-#define SEQ_SETTLE 275
>+#define SEQ_SETTLE 350
> #define MAX_12BIT ((1 << 12) - 1)
Okay, if this is required then it is. But in the long turn we should
make this interrupt threaded since this delay in interrupt context is
way too long.
Sebastian
^ permalink raw reply
* Re: [PATCH 3/4] input: ti_tsc: Enable shared IRQ for TSC and add overrun, underflow checks
From: Sebastian Andrzej Siewior @ 2013-08-16 9:14 UTC (permalink / raw)
To: Zubair Lutfullah
Cc: jic23-KWPb1pKIrIJaa/9Udqfwiw,
dmitry.torokhov-Re5JQEeQqe8AvxtiuMwx3w,
sameo-VuQAYsv1563Yd54FQh9/CA, lee.jones-QSEj5FYQhm4dnm+yROfE0A,
linux-iio-u79uwXL29TY76Z2rM5mHXA,
linux-input-u79uwXL29TY76Z2rM5mHXA,
linux-kernel-u79uwXL29TY76Z2rM5mHXA,
gregkh-hQyY1W1yCW8ekmWlsbkhG0B+6BGkLq7r, Russ.Dill-l0cyMroinI0
In-Reply-To: <1376412499-21007-4-git-send-email-zubair.lutfullah-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
* Zubair Lutfullah | 2013-08-13 17:48:18 [+0100]:
>Enable shared IRQ to allow ADC to share IRQ line from
>parent MFD core. Only FIFO0 IRQs are for TSC and handled
>on the TSC side.
>
>Patch also adds overrun and underflow irq handlers.
>
>Russ Dill (TI) worked on overrun and underflow handlers.
>Rachna Patil (TI) laid ground work for shared IRQ.
>
>Signed-off-by: Zubair Lutfullah <zubair.lutfullah-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
>---
> drivers/input/touchscreen/ti_am335x_tsc.c | 37 +++++++++++++++++++++++++----
> include/linux/mfd/ti_am335x_tscadc.h | 2 ++
> 2 files changed, 34 insertions(+), 5 deletions(-)
>
>diff --git a/drivers/input/touchscreen/ti_am335x_tsc.c b/drivers/input/touchscreen/ti_am335x_tsc.c
>index 766bc7e..9c114b2 100644
>--- a/drivers/input/touchscreen/ti_am335x_tsc.c
>+++ b/drivers/input/touchscreen/ti_am335x_tsc.c
>@@ -256,14 +256,39 @@ static irqreturn_t titsc_irq(int irq, void *dev)
> {
> struct titsc *ts_dev = dev;
> struct input_dev *input_dev = ts_dev->input;
>- unsigned int status, irqclr = 0;
>+ unsigned int status, irqclr = 0, config = 0;
> unsigned int x = 0, y = 0;
> unsigned int z1, z2, z;
> unsigned int fsm;
>
> status = titsc_readl(ts_dev, REG_IRQSTATUS);
>- if (status & IRQENB_FIFO0THRES) {
>+ /*
>+ * ADC and touchscreen share the IRQ line.
>+ * FIFO1 threshold, FIFO1 Overrun and FIFO1 underflow
>+ * interrupts are used by ADC. Handle FIFO0 IRQs here only
>+ * and check if any IRQs left in case both fifos interrupt.
>+ * If any irq left, return none, else return handled.
>+ */
>+ if ((status & IRQENB_FIFO0OVRRUN) ||
>+ (status & IRQENB_FIFO0UNDRFLW)) {
>+
>+ config = titsc_readl(ts_dev, REG_CTRL);
>+ config &= ~(CNTRLREG_TSCSSENB);
>+ titsc_writel(ts_dev, REG_CTRL, config);
>+
>+ if (status & IRQENB_FIFO0UNDRFLW) {
>+ titsc_writel(ts_dev, REG_IRQSTATUS,
>+ (status | IRQENB_FIFO0UNDRFLW));
>+ irqclr |= IRQENB_FIFO0UNDRFLW;
>+ } else {
>+ titsc_writel(ts_dev, REG_IRQSTATUS,
>+ (status | IRQENB_FIFO0OVRRUN));
>+ irqclr |= IRQENB_FIFO0OVRRUN;
>+ }
You don't do anything on overflow / underflow. Is this due to the fact
once enabled for FIFO1 it also triggers for FIFO0?
>+ titsc_writel(ts_dev, REG_CTRL,
>+ (config | CNTRLREG_TSCSSENB));
>+ } else if (status & IRQENB_FIFO0THRES) {
> titsc_read_coordinates(ts_dev, &x, &y, &z1, &z2);
>
> if (ts_dev->pen_down && z1 != 0 && z2 != 0) {
>@@ -317,9 +342,11 @@ static irqreturn_t titsc_irq(int irq, void *dev)
> }
>
> if (irqclr) {
>- titsc_writel(ts_dev, REG_IRQSTATUS, irqclr);
>+ titsc_writel(ts_dev, REG_IRQSTATUS, (status | irqclr));
Shouldn't FIFO1UNDRFLW & OVRRUN be handled by the adc driver? Why do you
or the unhandled bits as well here?
> am335x_tsc_se_set(ts_dev->mfd_tscadc, ts_dev->step_mask);
>- return IRQ_HANDLED;
>+ status = titsc_readl(ts_dev, REG_IRQSTATUS);
>+ if (status == false)
>+ return IRQ_HANDLED;
And why this? If you something you handled it, if you didn't you return
NONE. Why does it depend on REG_IRQSTATUS?
> }
> return IRQ_NONE;
> }
Sebastian
^ permalink raw reply
* Re: [PATCH 4/4] iio: ti_am335x_adc: Add continuous sampling and trigger support
From: Sebastian Andrzej Siewior @ 2013-08-16 10:07 UTC (permalink / raw)
To: Jonathan Cameron
Cc: Zubair Lutfullah, jic23-KWPb1pKIrIJaa/9Udqfwiw,
dmitry.torokhov-Re5JQEeQqe8AvxtiuMwx3w,
linux-iio-u79uwXL29TY76Z2rM5mHXA,
linux-input-u79uwXL29TY76Z2rM5mHXA,
linux-kernel-u79uwXL29TY76Z2rM5mHXA,
gregkh-hQyY1W1yCW8ekmWlsbkhG0B+6BGkLq7r, Russ.Dill-l0cyMroinI0
In-Reply-To: <520CBEC6.8010400-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org>
* Jonathan Cameron | 2013-08-15 12:43:02 [+0100]:
>Hi,
Hi Jonathan,
>Whilst the below review is based on what we have here, I tried to apply it
>to the latest iio.git togreg branch (which for this driver should be
>much the same as staging-next and hence linux-next). It's not even
>close and I can't immediately spot where some of the changes came
>from. Have I missed a precursor patch or is this based on an
>older version of this driver?
I took the four patches, checkout iio/fixes-togreg and then
|git am -3 iio.mbox
|Applying: input: ti_am335x_tsc: correct step mask update after IRQ
|Applying: input: ti_am335x_tsc: Increase sequencer delay time
|Applying: input: ti_am335x_tsc: Enable shared IRQ for TSC, add overrun and underflow checks
|Applying: iio: ti_am335x_adc: Add continuous sampling and trigger support
It did apply with no trouble here.
Let me look at your comments…
Sebastian
^ permalink raw reply
* Re: [PATCH 4/4] iio: ti_am335x_adc: Add continuous sampling and trigger support
From: Sebastian Andrzej Siewior @ 2013-08-16 10:17 UTC (permalink / raw)
To: Jonathan Cameron
Cc: Zubair Lutfullah, jic23-KWPb1pKIrIJaa/9Udqfwiw,
dmitry.torokhov-Re5JQEeQqe8AvxtiuMwx3w,
linux-iio-u79uwXL29TY76Z2rM5mHXA,
linux-input-u79uwXL29TY76Z2rM5mHXA,
linux-kernel-u79uwXL29TY76Z2rM5mHXA,
gregkh-hQyY1W1yCW8ekmWlsbkhG0B+6BGkLq7r, Russ.Dill-l0cyMroinI0
In-Reply-To: <20130816100702.GD26895-hfZtesqFncYOwBW4kG4KsQ@public.gmane.org>
* Sebastian Andrzej Siewior | 2013-08-16 12:07:02 [+0200]:
>* Jonathan Cameron | 2013-08-15 12:43:02 [+0100]:
>
>It did apply with no trouble here.
and with
diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
index 93129ec..fdd9810 100644
--- a/drivers/iio/adc/Kconfig
+++ b/drivers/iio/adc/Kconfig
@@ -156,6 +156,7 @@ config TI_ADC081C
config TI_AM335X_ADC
tristate "TI's ADC driver"
depends on MFD_TI_AM335X_TSCADC
+ select IIO_TRIGGERED_BUFFER
help
Say yes here to build support for Texas Instruments ADC
driver which is also a MFD client.
it links :)
Sebastian
^ permalink raw reply related
* Re: [PATCH 4/4] iio: ti_am335x_adc: Add continuous sampling and trigger support
From: Sebastian Andrzej Siewior @ 2013-08-16 10:46 UTC (permalink / raw)
To: Jonathan Cameron
Cc: Zubair Lutfullah, jic23, dmitry.torokhov, linux-iio, linux-input,
linux-kernel, gregkh, Russ.Dill
In-Reply-To: <520E0E22.4000403@kernel.org>
On 08/16/2013 01:33 PM, Jonathan Cameron wrote:
> Ah, fixes-togreg is for this cycle, whereas new stuff like this needs
> to go on the togreg branch. Hence please rebase on the togreg branch
> instead.
But he needs "iio: ti_am335x_adc: Fix wrong samples received on 1st
read" from that branch. How should the rebase be done?
>
> That would explain it.
Sebastian
^ permalink raw reply
* Re: [PATCH 4/4] iio: ti_am335x_adc: Add continuous sampling and trigger support
From: Jonathan Cameron @ 2013-08-16 11:33 UTC (permalink / raw)
To: Sebastian Andrzej Siewior
Cc: Zubair Lutfullah, jic23-KWPb1pKIrIJaa/9Udqfwiw,
dmitry.torokhov-Re5JQEeQqe8AvxtiuMwx3w,
linux-iio-u79uwXL29TY76Z2rM5mHXA,
linux-input-u79uwXL29TY76Z2rM5mHXA,
linux-kernel-u79uwXL29TY76Z2rM5mHXA,
gregkh-hQyY1W1yCW8ekmWlsbkhG0B+6BGkLq7r, Russ.Dill-l0cyMroinI0
In-Reply-To: <20130816100702.GD26895-hfZtesqFncYOwBW4kG4KsQ@public.gmane.org>
On 08/16/13 11:07, Sebastian Andrzej Siewior wrote:
> * Jonathan Cameron | 2013-08-15 12:43:02 [+0100]:
>
>> Hi,
> Hi Jonathan,
>
>> Whilst the below review is based on what we have here, I tried to apply it
>> to the latest iio.git togreg branch (which for this driver should be
>> much the same as staging-next and hence linux-next). It's not even
>> close and I can't immediately spot where some of the changes came
>> from. Have I missed a precursor patch or is this based on an
>> older version of this driver?
>
> I took the four patches, checkout iio/fixes-togreg and then
>
> |git am -3 iio.mbox
> |Applying: input: ti_am335x_tsc: correct step mask update after IRQ
> |Applying: input: ti_am335x_tsc: Increase sequencer delay time
> |Applying: input: ti_am335x_tsc: Enable shared IRQ for TSC, add overrun and underflow checks
> |Applying: iio: ti_am335x_adc: Add continuous sampling and trigger support
>
> It did apply with no trouble here.
> Let me look at your comments…
Ah, fixes-togreg is for this cycle, whereas new stuff like this needs
to go on the togreg branch. Hence please rebase on the togreg branch
instead.
That would explain it.
^ permalink raw reply
* [PATCH] Input: add driver for Neonode zForce based touchscreens
From: Heiko Stübner @ 2013-08-16 11:59 UTC (permalink / raw)
To: Dmitry Torokhov; +Cc: Henrik Rydberg, linux-kernel, linux-input
This adds a driver for touchscreens using the zforce infrared
technology from Neonode connected via i2c to the host system.
It supports multitouch with up to two fingers and tracking of the
contacts in hardware.
Signed-off-by: Heiko Stuebner <heiko@sntech.de>
---
drivers/input/touchscreen/Kconfig | 13 +
drivers/input/touchscreen/Makefile | 1 +
drivers/input/touchscreen/zforce_ts.c | 837 +++++++++++++++++++++++++++++++++
include/linux/input/zforce_ts.h | 26 +
4 files changed, 877 insertions(+)
create mode 100644 drivers/input/touchscreen/zforce_ts.c
create mode 100644 include/linux/input/zforce_ts.h
diff --git a/drivers/input/touchscreen/Kconfig b/drivers/input/touchscreen/Kconfig
index 3b9758b..ade11b7 100644
--- a/drivers/input/touchscreen/Kconfig
+++ b/drivers/input/touchscreen/Kconfig
@@ -919,4 +919,17 @@ config TOUCHSCREEN_TPS6507X
To compile this driver as a module, choose M here: the
module will be called tps6507x_ts.
+config TOUCHSCREEN_ZFORCE
+ tristate "Neonode zForce infrared touchscreens"
+ depends on I2C
+ depends on GPIOLIB
+ help
+ Say Y here if you have a touchscreen using the zforce
+ infraread technology from Neonode.
+
+ If unsure, say N.
+
+ To compile this driver as a module, choose M here: the
+ module will be called zforce_ts.
+
endif
diff --git a/drivers/input/touchscreen/Makefile b/drivers/input/touchscreen/Makefile
index f5216c1..7587883 100644
--- a/drivers/input/touchscreen/Makefile
+++ b/drivers/input/touchscreen/Makefile
@@ -75,3 +75,4 @@ obj-$(CONFIG_TOUCHSCREEN_WM97XX_MAINSTONE) += mainstone-wm97xx.o
obj-$(CONFIG_TOUCHSCREEN_WM97XX_ZYLONITE) += zylonite-wm97xx.o
obj-$(CONFIG_TOUCHSCREEN_W90X900) += w90p910_ts.o
obj-$(CONFIG_TOUCHSCREEN_TPS6507X) += tps6507x-ts.o
+obj-$(CONFIG_TOUCHSCREEN_ZFORCE) += zforce_ts.o
diff --git a/drivers/input/touchscreen/zforce_ts.c b/drivers/input/touchscreen/zforce_ts.c
new file mode 100644
index 0000000..92af632
--- /dev/null
+++ b/drivers/input/touchscreen/zforce_ts.c
@@ -0,0 +1,837 @@
+/*
+ * Copyright (C) 2012-2013 MundoReader S.L.
+ * Author: Heiko Stuebner <heiko@sntech.de>
+ *
+ * based in parts on Nook zforce driver
+ *
+ * Copyright (C) 2010 Barnes & Noble, Inc.
+ * Author: Pieter Truter<ptruter@intrinsyc.com>
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * 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.
+ */
+
+#include <linux/module.h>
+#include <linux/hrtimer.h>
+#include <linux/slab.h>
+#include <linux/input.h>
+#include <linux/interrupt.h>
+#include <linux/i2c.h>
+#include <linux/delay.h>
+#include <linux/gpio.h>
+#include <linux/device.h>
+#include <linux/sysfs.h>
+#include <linux/input/zforce_ts.h>
+#include <linux/input/mt.h>
+
+#define WAIT_TIMEOUT msecs_to_jiffies(1000)
+
+#define FRAME_START 0xee
+
+/* Offsets of the different parts of the payload the controller sends */
+#define PAYLOAD_HEADER 0
+#define PAYLOAD_LENGTH 1
+#define PAYLOAD_BODY 2
+
+/* Response offsets */
+#define RESPONSE_ID 0
+#define RESPONSE_DATA 1
+
+/* Commands */
+#define COMMAND_DEACTIVATE 0x00
+#define COMMAND_INITIALIZE 0x01
+#define COMMAND_RESOLUTION 0x02
+#define COMMAND_SETCONFIG 0x03
+#define COMMAND_DATAREQUEST 0x04
+#define COMMAND_SCANFREQ 0x08
+#define COMMAND_STATUS 0X1e
+
+/*
+ * Responses the controller sends as a result of
+ * command requests
+ */
+#define RESPONSE_DEACTIVATE 0x00
+#define RESPONSE_INITIALIZE 0x01
+#define RESPONSE_RESOLUTION 0x02
+#define RESPONSE_SETCONFIG 0x03
+#define RESPONSE_SCANFREQ 0x08
+#define RESPONSE_STATUS 0X1e
+
+/*
+ * Notifications are send by the touch controller without
+ * being requested by the driver and include for example
+ * touch indications
+ */
+#define NOTIFICATION_TOUCH 0x04
+#define NOTIFICATION_BOOTCOMPLETE 0x07
+#define NOTIFICATION_OVERRUN 0x25
+#define NOTIFICATION_PROXIMITY 0x26
+#define NOTIFICATION_INVALID_COMMAND 0xfe
+
+#define ZFORCE_REPORT_POINTS 2
+#define ZFORCE_MAX_AREA 0xff
+
+#define STATE_DOWN 0
+#define STATE_MOVE 1
+#define STATE_UP 2
+
+#define SETCONFIG_DUALTOUCH (1 << 0)
+
+struct zforce_point {
+ int coord_x;
+ int coord_y;
+ int state;
+ int id;
+ int area_major;
+ int area_minor;
+ int orientation;
+ int pressure;
+ int prblty;
+};
+
+/*
+ * @client the i2c_client
+ * @input the input device
+ * @suspending in the process of going to suspend (don't emit wakeup
+ * events for commands executed to suspend the device)
+ * @suspended device suspended
+ * @access_mutex serialize i2c-access, to keep multipart reads together
+ * @command_done completion to wait for the command result
+ * @command_mutex serialize commands send to the ic
+ * @command_waiting the id of the command that that is currently waiting
+ * for a result
+ * @command_result returned result of the command
+ */
+struct zforce_ts {
+ struct i2c_client *client;
+ struct input_dev *input;
+ const struct zforce_ts_platdata *pdata;
+ char phys[32];
+
+ bool suspending;
+ bool suspended;
+ bool boot_complete;
+
+ /* Firmware version information */
+ u16 version_major;
+ u16 version_minor;
+ u16 version_build;
+ u16 version_rev;
+
+ struct mutex access_mutex;
+
+ struct completion command_done;
+ struct mutex command_mutex;
+ int command_waiting;
+ int command_result;
+};
+
+static int zforce_command(struct zforce_ts *ts, u8 cmd)
+{
+ struct i2c_client *client = ts->client;
+ char buf[3];
+ int ret;
+
+ dev_dbg(&client->dev, "%s: 0x%x\n", __func__, cmd);
+
+ buf[0] = FRAME_START;
+ buf[1] = 1; /* data size, command only */
+ buf[2] = cmd;
+
+ mutex_lock(&ts->access_mutex);
+ ret = i2c_master_send(client, &buf[0], ARRAY_SIZE(buf));
+ mutex_unlock(&ts->access_mutex);
+ if (ret < 0) {
+ dev_err(&client->dev, "i2c send data request error: %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int zforce_send_wait(struct zforce_ts *ts, const char *buf, int len)
+{
+ struct i2c_client *client = ts->client;
+ int ret;
+
+ ret = mutex_trylock(&ts->command_mutex);
+ if (!ret) {
+ dev_err(&client->dev, "already waiting for a command\n");
+ return -EBUSY;
+ }
+
+ dev_dbg(&client->dev, "sending %d bytes for command 0x%x\n",
+ buf[1], buf[2]);
+
+ ts->command_waiting = buf[2];
+
+ mutex_lock(&ts->access_mutex);
+ ret = i2c_master_send(client, buf, len);
+ mutex_unlock(&ts->access_mutex);
+ if (ret < 0) {
+ dev_err(&client->dev, "i2c send data request error: %d\n", ret);
+ goto unlock;
+ }
+
+ dev_dbg(&client->dev, "waiting for result for command 0x%x\n", buf[2]);
+
+ if (wait_for_completion_timeout(&ts->command_done, WAIT_TIMEOUT) == 0) {
+ ret = -ETIME;
+ goto unlock;
+ }
+
+ ret = ts->command_result;
+
+unlock:
+ mutex_unlock(&ts->command_mutex);
+ return ret;
+}
+
+static int zforce_command_wait(struct zforce_ts *ts, u8 cmd)
+{
+ struct i2c_client *client = ts->client;
+ char buf[3];
+ int ret;
+
+ dev_dbg(&client->dev, "%s: 0x%x\n", __func__, cmd);
+
+ buf[0] = FRAME_START;
+ buf[1] = 1; /* data size, command only */
+ buf[2] = cmd;
+
+ ret = zforce_send_wait(ts, &buf[0], ARRAY_SIZE(buf));
+ if (ret < 0) {
+ dev_err(&client->dev, "i2c send data request error: %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int zforce_resolution(struct zforce_ts *ts, u16 x, u16 y)
+{
+ struct i2c_client *client = ts->client;
+ char buf[7] = { FRAME_START, 5, COMMAND_RESOLUTION,
+ (x & 0xff), ((x >> 8) & 0xff),
+ (y & 0xff), ((y >> 8) & 0xff) };
+
+ dev_dbg(&client->dev, "set resolution to (%d,%d)\n", x, y);
+
+ return zforce_send_wait(ts, &buf[0], ARRAY_SIZE(buf));
+}
+
+static int zforce_scan_frequency(struct zforce_ts *ts, u16 idle, u16 finger,
+ u16 stylus)
+{
+ struct i2c_client *client = ts->client;
+ char buf[9] = { FRAME_START, 7, COMMAND_SCANFREQ,
+ (idle & 0xff), ((idle >> 8) & 0xff),
+ (finger & 0xff), ((finger >> 8) & 0xff),
+ (stylus & 0xff), ((stylus >> 8) & 0xff) };
+
+ dev_dbg(&client->dev, "set scan frequency to (idle: %d, finger: %d, stylus: %d)\n",
+ idle, finger, stylus);
+
+ return zforce_send_wait(ts, &buf[0], ARRAY_SIZE(buf));
+}
+
+static int zforce_setconfig(struct zforce_ts *ts, char b1)
+{
+ struct i2c_client *client = ts->client;
+ char buf[7] = { FRAME_START, 5, COMMAND_SETCONFIG,
+ b1, 0, 0, 0 };
+
+ dev_dbg(&client->dev, "set config to (%d)\n", b1);
+
+ return zforce_send_wait(ts, &buf[0], ARRAY_SIZE(buf));
+}
+
+static int zforce_start(struct zforce_ts *ts)
+{
+ struct i2c_client *client = ts->client;
+ const struct zforce_ts_platdata *pdata = client->dev.platform_data;
+ int ret;
+
+ dev_dbg(&client->dev, "starting device\n");
+
+ ret = zforce_command_wait(ts, COMMAND_INITIALIZE);
+ if (ret) {
+ dev_err(&client->dev, "Unable to initialize, %d\n", ret);
+ return ret;
+ }
+
+ ret = zforce_resolution(ts, pdata->x_max, pdata->y_max);
+ if (ret) {
+ dev_err(&client->dev, "Unable to set resolution, %d\n", ret);
+ goto error;
+ }
+
+ ret = zforce_scan_frequency(ts, 10, 50, 50);
+ if (ret) {
+ dev_err(&client->dev, "Unable to set scan frequency, %d\n",
+ ret);
+ goto error;
+ }
+
+ if (zforce_setconfig(ts, SETCONFIG_DUALTOUCH)) {
+ dev_err(&client->dev, "Unable to set config\n");
+ goto error;
+ }
+
+ /* start sending touch events */
+ ret = zforce_command(ts, COMMAND_DATAREQUEST);
+ if (ret) {
+ dev_err(&client->dev, "Unable to request data\n");
+ goto error;
+ }
+
+ /* Per NN, initial cal. take max. of 200msec.
+ * Allow time to complete this calibration
+ */
+ msleep(200);
+
+ return 0;
+
+error:
+ zforce_command_wait(ts, COMMAND_DEACTIVATE);
+ return ret;
+}
+
+static int zforce_stop(struct zforce_ts *ts)
+{
+ struct i2c_client *client = ts->client;
+ int ret;
+
+ dev_dbg(&client->dev, "stopping device\n");
+
+ /* deactivates touch sensing and puts the device into sleep */
+ ret = zforce_command_wait(ts, COMMAND_DEACTIVATE);
+ if (ret != 0) {
+ dev_err(&client->dev, "could not deactivate device, %d\n",
+ ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int zforce_touch_event(struct zforce_ts *ts, u8 *payload)
+{
+ struct i2c_client *client = ts->client;
+ struct zforce_point point[ZFORCE_REPORT_POINTS];
+ const struct zforce_ts_platdata *pdata = client->dev.platform_data;
+ int count, i;
+
+ count = payload[0];
+ if (count > ZFORCE_REPORT_POINTS) {
+ dev_warn(&client->dev, "to many coordinates %d, expected max %d\n",
+ count, ZFORCE_REPORT_POINTS);
+ count = ZFORCE_REPORT_POINTS;
+ }
+
+ for (i = 0; i < count; i++) {
+ point[i].coord_x =
+ payload[9 * i + 2] << 8 | payload[9 * i + 1];
+ point[i].coord_y =
+ payload[9 * i + 4] << 8 | payload[9 * i + 3];
+
+ if (point[i].coord_x > pdata->x_max ||
+ point[i].coord_y > pdata->y_max) {
+ dev_warn(&client->dev, "coordinates (%d,%d) invalid\n",
+ point[i].coord_x, point[i].coord_y);
+ point[i].coord_x = point[i].coord_y = 0;
+ }
+
+ point[i].state = payload[9 * i + 5] & 0x03;
+ point[i].id = (payload[9 * i + 5] & 0xfc) >> 2;
+
+ /* determine touch major, minor and orientation */
+ point[i].area_major = max(payload[9 * i + 6],
+ payload[9 * i + 7]);
+ point[i].area_minor = min(payload[9 * i + 6],
+ payload[9 * i + 7]);
+ point[i].orientation = payload[9 * i + 6] > payload[9 * i + 7];
+
+ point[i].pressure = payload[9 * i + 8];
+ point[i].prblty = payload[9 * i + 9];
+ }
+
+ for (i = 0; i < count; i++) {
+ dev_dbg(&client->dev, "point %d/%d: state %d, id %d, pressure %d, prblty %d, x %d, y %d, amajor %d, aminor %d, ori %d\n",
+ i, count, point[i].state, point[i].id,
+ point[i].pressure, point[i].prblty,
+ point[i].coord_x, point[i].coord_y,
+ point[i].area_major, point[i].area_minor,
+ point[i].orientation);
+
+ /* the zforce id starts with "1", so needs to be decreased */
+ input_mt_slot(ts->input, point[i].id - 1);
+
+ input_mt_report_slot_state(ts->input, MT_TOOL_FINGER,
+ point[i].state != STATE_UP);
+
+ if (point[i].state != STATE_UP) {
+ input_report_abs(ts->input, ABS_MT_POSITION_X,
+ point[i].coord_x);
+ input_report_abs(ts->input, ABS_MT_POSITION_Y,
+ point[i].coord_y);
+ input_report_abs(ts->input, ABS_MT_TOUCH_MAJOR,
+ point[i].area_major);
+ input_report_abs(ts->input, ABS_MT_TOUCH_MINOR,
+ point[i].area_minor);
+ input_report_abs(ts->input, ABS_MT_ORIENTATION,
+ point[i].orientation);
+ }
+ }
+
+ if (point[0].state != STATE_UP) {
+ input_report_abs(ts->input, ABS_X, point[0].coord_x);
+ input_report_abs(ts->input, ABS_Y, point[0].coord_y);
+ }
+
+ input_report_key(ts->input, BTN_TOUCH, point[0].state != STATE_UP);
+
+ input_sync(ts->input);
+
+ return 0;
+}
+
+static int zforce_read_packet(struct zforce_ts *ts, u8 *buf)
+{
+ struct i2c_client *client = ts->client;
+ int ret;
+
+ mutex_lock(&ts->access_mutex);
+
+ /* read 2 byte message header */
+ ret = i2c_master_recv(client, buf, 2);
+ if (ret < 0) {
+ dev_err(&client->dev, "error reading header: %d\n", ret);
+ goto unlock;
+ }
+
+ if (buf[PAYLOAD_HEADER] != FRAME_START) {
+ dev_err(&client->dev, "invalid frame start: %d\n", buf[0]);
+ ret = -EIO;
+ goto unlock;
+ }
+
+ if (buf[PAYLOAD_LENGTH] <= 0 || buf[PAYLOAD_LENGTH] > 255) {
+ dev_err(&client->dev, "invalid payload length: %d\n",
+ buf[PAYLOAD_LENGTH]);
+ ret = -EIO;
+ goto unlock;
+ }
+
+ /* read the message */
+ ret = i2c_master_recv(client, &buf[PAYLOAD_BODY], buf[PAYLOAD_LENGTH]);
+ if (ret < 0) {
+ dev_err(&client->dev, "error reading payload: %d\n", ret);
+ goto unlock;
+ }
+
+ dev_dbg(&client->dev, "read %d bytes for response command 0x%x\n",
+ buf[PAYLOAD_LENGTH], buf[PAYLOAD_BODY]);
+
+unlock:
+ mutex_unlock(&ts->access_mutex);
+ return ret;
+}
+
+static void zforce_complete(struct zforce_ts *ts, int cmd, int result)
+{
+ struct i2c_client *client = ts->client;
+
+ if (ts->command_waiting == cmd) {
+ dev_dbg(&client->dev, "completing command 0x%x\n", cmd);
+ ts->command_result = result;
+ complete(&ts->command_done);
+ } else {
+ dev_dbg(&client->dev, "command %d not for us\n", cmd);
+ }
+}
+
+static irqreturn_t zforce_interrupt(int irq, void *dev_id)
+{
+ struct zforce_ts *ts = dev_id;
+ struct i2c_client *client = ts->client;
+ const struct zforce_ts_platdata *pdata = client->dev.platform_data;
+ int ret;
+ u8 payload_buffer[512];
+ u8 *payload;
+
+ /*
+ * When suspended, emit a wakeup signal if necessary and return.
+ * Due to the level-interrupt we will get re-triggered later.
+ */
+ if (ts->suspended) {
+ if (device_may_wakeup(&client->dev))
+ pm_wakeup_event(&client->dev, 500);
+ msleep(20);
+ return IRQ_HANDLED;
+ }
+
+ dev_dbg(&client->dev, "handling interrupt\n");
+
+ /* Don't emit wakeup events from commands run by zforce_suspend */
+ if (!ts->suspending && device_may_wakeup(&client->dev))
+ pm_stay_awake(&client->dev);
+
+ while (!gpio_get_value(pdata->gpio_int)) {
+ ret = zforce_read_packet(ts, payload_buffer);
+ if (ret < 0) {
+ dev_err(&client->dev, "could not read packet, ret: %d\n",
+ ret);
+ break;
+ }
+
+ payload = &payload_buffer[PAYLOAD_BODY];
+
+ switch (payload[RESPONSE_ID]) {
+ case NOTIFICATION_TOUCH:
+ /* Always report touch-events received while
+ * suspending, when being a wakeup source
+ */
+ if (ts->suspending && device_may_wakeup(&client->dev))
+ pm_wakeup_event(&client->dev, 500);
+ zforce_touch_event(ts, &payload[RESPONSE_DATA]);
+ break;
+ case NOTIFICATION_BOOTCOMPLETE:
+ ts->boot_complete = payload[RESPONSE_DATA];
+ zforce_complete(ts, payload[RESPONSE_ID], 0);
+ break;
+ case RESPONSE_INITIALIZE:
+ case RESPONSE_DEACTIVATE:
+ case RESPONSE_SETCONFIG:
+ case RESPONSE_RESOLUTION:
+ case RESPONSE_SCANFREQ:
+ zforce_complete(ts, payload[RESPONSE_ID],
+ payload[RESPONSE_DATA]);
+ break;
+ case RESPONSE_STATUS:
+ /* Version Payload Results
+ * [2:major] [2:minor] [2:build] [2:rev]
+ */
+ ts->version_major = (payload[RESPONSE_DATA + 1] << 8) |
+ payload[RESPONSE_DATA];
+ ts->version_minor = (payload[RESPONSE_DATA + 3] << 8) |
+ payload[RESPONSE_DATA + 2];
+ ts->version_build = (payload[RESPONSE_DATA + 5] << 8) |
+ payload[RESPONSE_DATA + 4];
+ ts->version_rev = (payload[RESPONSE_DATA + 7] << 8) |
+ payload[RESPONSE_DATA + 6];
+ dev_dbg(&ts->client->dev, "Firmware Version %04x:%04x %04x:%04x\n",
+ ts->version_major, ts->version_minor,
+ ts->version_build, ts->version_rev);
+
+ zforce_complete(ts, payload[RESPONSE_ID], 0);
+ break;
+ case NOTIFICATION_INVALID_COMMAND:
+ dev_err(&ts->client->dev, "invalid command: 0x%x\n",
+ payload[RESPONSE_DATA]);
+ break;
+ default:
+ dev_err(&ts->client->dev, "unrecognized response id: 0x%x\n",
+ payload[RESPONSE_ID]);
+ break;
+ }
+ }
+
+ if (!ts->suspending && device_may_wakeup(&client->dev))
+ pm_relax(&client->dev);
+
+ dev_dbg(&client->dev, "finished interrupt\n");
+
+ return IRQ_HANDLED;
+}
+
+static int zforce_input_open(struct input_dev *dev)
+{
+ struct zforce_ts *ts = input_get_drvdata(dev);
+ int ret;
+
+ ret = zforce_start(ts);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static void zforce_input_close(struct input_dev *dev)
+{
+ struct zforce_ts *ts = input_get_drvdata(dev);
+ struct i2c_client *client = ts->client;
+ int ret;
+
+ ret = zforce_stop(ts);
+ if (ret)
+ dev_warn(&client->dev, "stopping zforce failed\n");
+
+ return;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int zforce_suspend(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct zforce_ts *ts = i2c_get_clientdata(client);
+ struct input_dev *input = ts->input;
+ int ret = 0;
+
+ mutex_lock(&input->mutex);
+ ts->suspending = true;
+
+ /* when configured as wakeup source, device should always wake system
+ * therefore start device if necessary
+ */
+ if (device_may_wakeup(&client->dev)) {
+ dev_dbg(&client->dev, "suspend while being a wakeup source\n");
+
+ /* need to start device if not open, to be wakeup source */
+ if (!input->users) {
+ ret = zforce_start(ts);
+ if (ret)
+ goto unlock;
+ }
+
+ enable_irq_wake(client->irq);
+ } else if (input->users) {
+ dev_dbg(&client->dev, "suspend without being a wakeup source\n");
+
+ ret = zforce_stop(ts);
+ if (ret)
+ goto unlock;
+
+ disable_irq(client->irq);
+ }
+
+ ts->suspended = true;
+
+unlock:
+ ts->suspending = false;
+ mutex_unlock(&input->mutex);
+
+ return ret;
+}
+
+static int zforce_resume(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct zforce_ts *ts = i2c_get_clientdata(client);
+ struct input_dev *input = ts->input;
+ int ret = 0;
+
+ mutex_lock(&input->mutex);
+
+ ts->suspended = false;
+
+ if (device_may_wakeup(&client->dev)) {
+ dev_dbg(&client->dev, "resume from being a wakeup source\n");
+
+ disable_irq_wake(client->irq);
+
+ /* need to stop device if it was not open on suspend */
+ if (!input->users) {
+ ret = zforce_stop(ts);
+ if (ret)
+ goto unlock;
+ }
+ } else if (input->users) {
+ dev_dbg(&client->dev, "resume without being a wakeup source\n");
+
+ enable_irq(client->irq);
+
+ ret = zforce_start(ts);
+ if (ret < 0)
+ goto unlock;
+ }
+
+unlock:
+ mutex_unlock(&input->mutex);
+
+ return ret;
+}
+#endif
+
+static SIMPLE_DEV_PM_OPS(zforce_pm_ops, zforce_suspend, zforce_resume);
+
+static void zforce_reset(void *data)
+{
+ struct zforce_ts *ts = data;
+
+ gpio_set_value(ts->pdata->gpio_rst, 0);
+}
+
+static int zforce_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ const struct zforce_ts_platdata *pdata = client->dev.platform_data;
+ struct zforce_ts *ts;
+ struct input_dev *input_dev;
+ int ret;
+
+ if (!pdata)
+ return -EINVAL;
+
+ ts = devm_kzalloc(&client->dev, sizeof(struct zforce_ts), GFP_KERNEL);
+ if (!ts)
+ return -ENOMEM;
+
+ ret = devm_gpio_request_one(&client->dev, pdata->gpio_int, GPIOF_IN,
+ "zforce_ts_int");
+ if (ret) {
+ dev_err(&client->dev, "request of gpio %d failed, %d\n",
+ pdata->gpio_int, ret);
+ return ret;
+ }
+
+ ret = devm_gpio_request_one(&client->dev, pdata->gpio_rst,
+ GPIOF_OUT_INIT_LOW, "zforce_ts_rst");
+ if (ret) {
+ dev_err(&client->dev, "request of gpio %d failed, %d\n",
+ pdata->gpio_rst, ret);
+ return ret;
+ }
+
+ ret = devm_add_action(&client->dev, zforce_reset, ts);
+ if (ret) {
+ dev_err(&client->dev, "failed to register reset action, %d\n",
+ ret);
+ return ret;
+ }
+
+ msleep(20);
+
+ snprintf(ts->phys, sizeof(ts->phys),
+ "%s/input0", dev_name(&client->dev));
+
+ input_dev = devm_input_allocate_device(&client->dev);
+ if (!input_dev) {
+ dev_err(&client->dev, "could not allocate input device\n");
+ return -ENOMEM;
+ }
+
+ mutex_init(&ts->access_mutex);
+ mutex_init(&ts->command_mutex);
+
+ ts->pdata = pdata;
+ ts->client = client;
+ ts->input = input_dev;
+
+ input_dev->name = "Neonode zForce touchscreen";
+ input_dev->phys = ts->phys;
+ input_dev->id.bustype = BUS_I2C;
+ input_dev->dev.parent = &client->dev;
+
+ input_dev->open = zforce_input_open;
+ input_dev->close = zforce_input_close;
+
+ set_bit(EV_KEY, input_dev->evbit);
+ set_bit(EV_SYN, input_dev->evbit);
+ set_bit(EV_ABS, input_dev->evbit);
+ set_bit(BTN_TOUCH, input_dev->keybit);
+
+ /* For single touch */
+ input_set_abs_params(input_dev, ABS_X, 0, pdata->x_max, 0, 0);
+ input_set_abs_params(input_dev, ABS_Y, 0, pdata->y_max, 0, 0);
+
+ /* For multi touch */
+ input_mt_init_slots(input_dev, ZFORCE_REPORT_POINTS, 0);
+ input_set_abs_params(input_dev, ABS_MT_POSITION_X, 0,
+ pdata->x_max, 0, 0);
+ input_set_abs_params(input_dev, ABS_MT_POSITION_Y, 0,
+ pdata->y_max, 0, 0);
+
+ input_set_abs_params(input_dev, ABS_MT_TOUCH_MAJOR, 0,
+ ZFORCE_MAX_AREA, 0, 0);
+ input_set_abs_params(input_dev, ABS_MT_TOUCH_MINOR, 0,
+ ZFORCE_MAX_AREA, 0, 0);
+ input_set_abs_params(input_dev, ABS_MT_ORIENTATION, 0, 1, 0, 0);
+
+ input_set_drvdata(ts->input, ts);
+
+ init_completion(&ts->command_done);
+
+ /* The zforce pulls the interrupt low when it has data ready.
+ * After it is triggered the isr thread runs until all the available
+ * packets have been read and the interrupt is high again.
+ * Therefore we can trigger the interrupt anytime it is low and do
+ * not need to limit it to the interrupt edge.
+ */
+ ret = devm_request_threaded_irq(&client->dev, client->irq, NULL,
+ zforce_interrupt,
+ IRQF_TRIGGER_LOW | IRQF_ONESHOT,
+ input_dev->name, ts);
+ if (ret) {
+ dev_err(&client->dev, "irq %d request failed\n", client->irq);
+ return ret;
+ }
+
+ i2c_set_clientdata(client, ts);
+
+ /* let the controller boot */
+ gpio_set_value(pdata->gpio_rst, 1);
+
+ ts->command_waiting = NOTIFICATION_BOOTCOMPLETE;
+ if (wait_for_completion_timeout(&ts->command_done, WAIT_TIMEOUT) == 0)
+ dev_warn(&client->dev, "bootcomplete timed out\n");
+
+ /* need to start device to get version information */
+ ret = zforce_command_wait(ts, COMMAND_INITIALIZE);
+ if (ret) {
+ dev_err(&client->dev, "unable to initialize, %d\n", ret);
+ return ret;
+ }
+
+ /* this gets the firmware version among other informations */
+ ret = zforce_command_wait(ts, COMMAND_STATUS);
+ if (ret < 0) {
+ dev_err(&client->dev, "couldn't get status, %d\n", ret);
+ zforce_stop(ts);
+ return ret;
+ }
+
+ /* stop device and put it into sleep until it is opened */
+ ret = zforce_stop(ts);
+ if (ret < 0)
+ return ret;
+
+ device_set_wakeup_capable(&client->dev, true);
+
+ ret = input_register_device(input_dev);
+ if (ret) {
+ dev_err(&client->dev, "could not register input device, %d\n",
+ ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static struct i2c_device_id zforce_idtable[] = {
+ { "zforce-ts", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, zforce_idtable);
+
+static struct i2c_driver zforce_driver = {
+ .driver = {
+ .owner = THIS_MODULE,
+ .name = "zforce-ts",
+ .pm = &zforce_pm_ops,
+ },
+ .probe = zforce_probe,
+ .id_table = zforce_idtable,
+};
+
+module_i2c_driver(zforce_driver);
+
+MODULE_AUTHOR("Heiko Stuebner <heiko@sntech.de>");
+MODULE_DESCRIPTION("zForce TouchScreen Driver");
+MODULE_LICENSE("GPL");
diff --git a/include/linux/input/zforce_ts.h b/include/linux/input/zforce_ts.h
new file mode 100644
index 0000000..0472ab2
--- /dev/null
+++ b/include/linux/input/zforce_ts.h
@@ -0,0 +1,26 @@
+/* drivers/input/touchscreen/zforce.c
+ *
+ * Copyright (C) 2012-2013 MundoReader S.L.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * 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.
+ */
+
+#ifndef _LINUX_INPUT_ZFORCE_TS_H
+#define _LINUX_INPUT_ZFORCE_TS_H
+
+struct zforce_ts_platdata {
+ int gpio_int;
+ int gpio_rst;
+
+ unsigned int x_max;
+ unsigned int y_max;
+};
+
+#endif /* _LINUX_INPUT_ZFORCE_TS_H */
--
1.7.10.4
^ permalink raw reply related
* Re: [PATCH 4/4] iio: ti_am335x_adc: Add continuous sampling and trigger support
From: Sebastian Andrzej Siewior @ 2013-08-16 12:53 UTC (permalink / raw)
To: Zubair Lutfullah
Cc: jic23-KWPb1pKIrIJaa/9Udqfwiw,
dmitry.torokhov-Re5JQEeQqe8AvxtiuMwx3w,
linux-iio-u79uwXL29TY76Z2rM5mHXA,
linux-input-u79uwXL29TY76Z2rM5mHXA,
linux-kernel-u79uwXL29TY76Z2rM5mHXA,
gregkh-hQyY1W1yCW8ekmWlsbkhG0B+6BGkLq7r, Russ.Dill-l0cyMroinI0
In-Reply-To: <1376424303-22740-5-git-send-email-zubair.lutfullah-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
* Zubair Lutfullah | 2013-08-13 21:05:03 [+0100]:
>diff --git a/drivers/iio/adc/ti_am335x_adc.c b/drivers/iio/adc/ti_am335x_adc.c
>index 3ceac3e..0d7e313 100644
>--- a/drivers/iio/adc/ti_am335x_adc.c
>+++ b/drivers/iio/adc/ti_am335x_adc.c
>@@ -24,16 +24,28 @@
…
>+static irqreturn_t tiadc_irq(int irq, void *private)
>+{
>+ struct iio_dev *idev = private;
>+ struct tiadc_device *adc_dev = iio_priv(idev);
>+ unsigned int status, config;
>+ status = tiadc_readl(adc_dev, REG_IRQSTATUS);
>+
>+ /* FIFO Overrun. Clear flag. Disable/Enable ADC to recover */
>+ if (status & IRQENB_FIFO1OVRRUN) {
>+ config = tiadc_readl(adc_dev, REG_CTRL);
>+ config &= ~(CNTRLREG_TSCSSENB);
>+ tiadc_writel(adc_dev, REG_CTRL, config);
>+ tiadc_writel(adc_dev, REG_IRQSTATUS, IRQENB_FIFO1OVRRUN |
>+ IRQENB_FIFO1UNDRFLW | IRQENB_FIFO1THRES);
>+ tiadc_writel(adc_dev, REG_CTRL, (config | CNTRLREG_TSCSSENB));
I have no idea how other handle this but shouldn't you somehow inform
*anyone* that you lost some samples due to this overrun?
>+ } else if (status & IRQENB_FIFO1THRES) {
>+ /* Wake adc_work that pushes FIFO data to iio buffer */
>+ tiadc_writel(adc_dev, REG_IRQCLR, IRQENB_FIFO1THRES);
>+ adc_dev->data_avail = 1;
>+ wake_up_interruptible(&adc_dev->wq_data_avail);
>+ } else
>+ return IRQ_NONE;
Sebastian
^ 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