* Re: [PATCH] Input: evdev - Fix incorrect kfree of err_free_client after vzalloc
From: David Rientjes @ 2014-06-12 6:49 UTC (permalink / raw)
To: Yongtaek Lee; +Cc: rydberg, dmitry.torokhov, daniels, linux-input, linux-kernel
In-Reply-To: <1402537537-17945-1-git-send-email-ytk.lee@samsung.com>
On Thu, 12 Jun 2014, Yongtaek Lee wrote:
> This bug was introduced by commit 92eb77d ("Input: evdev - fall back
> to vmalloc for client event buffer").
>
> vzalloc is used to alloc memory as fallback in case of failure
> of kzalloc. But err_free_client was not considered on below case.
> 1. kzalloc fail
> 2. vzalloc success
> 3. evdev_open_device fail
> 4. kfree
>
> So that address checking is needed to call correct free function.
>
> Signed-off-by: Yongtaek Lee <ytk.lee@samsung.com>
> Reviewed-by: Daniel Stone <daniels@collabora.com>
Acked-by: David Rientjes <rientjes@google.com>
^ permalink raw reply
* [PATCH] Input: evdev - Fix incorrect kfree of err_free_client after vzalloc
From: Yongtaek Lee @ 2014-06-12 1:45 UTC (permalink / raw)
To: rydberg, dmitry.torokhov, daniels; +Cc: linux-input, linux-kernel, ytk.lee
This bug was introduced by commit 92eb77d ("Input: evdev - fall back
to vmalloc for client event buffer").
vzalloc is used to alloc memory as fallback in case of failure
of kzalloc. But err_free_client was not considered on below case.
1. kzalloc fail
2. vzalloc success
3. evdev_open_device fail
4. kfree
So that address checking is needed to call correct free function.
Signed-off-by: Yongtaek Lee <ytk.lee@samsung.com>
Reviewed-by: Daniel Stone <daniels@collabora.com>
---
drivers/input/evdev.c | 5 ++++-
1 files changed, 4 insertions(+), 1 deletions(-)
diff --git a/drivers/input/evdev.c b/drivers/input/evdev.c
index ce953d8..f60daa0 100644
--- a/drivers/input/evdev.c
+++ b/drivers/input/evdev.c
@@ -422,7 +422,10 @@ static int evdev_open(struct inode *inode, struct file *file)
err_free_client:
evdev_detach_client(evdev, client);
- kfree(client);
+ if (is_vmalloc_addr(client))
+ vfree(client);
+ else
+ kfree(client);
return error;
}
--
1.7.1
^ permalink raw reply related
* [PATCH v4] leds: USB: HID: Add support for MSI GT683R led panels
From: Janne Kanniainen @ 2014-06-11 22:48 UTC (permalink / raw)
To: jkosina
Cc: cooloney, johan, linux-kernel, linux-leds, linux-usb, linux-input,
Janne Kanniainen
In-Reply-To: <20140611173416.GJ10256@localhost>
This driver adds support for USB controlled led panels that exists in MSI GT683R laptop
Signed-off-by: Janne Kanniainen <janne.kanniainen@gmail.com>
---
Changes in v2:
- sorted headers to alphabetic order
- using devm_kzalloc
- using BIT(n)
- using usb_control_msg instead of usb_submit_urb
- removing unneeded code
Changes in v3:
- implemented as HID device
- some cleanups and bug fixes
Changes in v4:
- more cleanups
- support for selecting leds
- support for selecting status
drivers/hid/Kconfig | 11 ++
drivers/hid/Makefile | 1 +
drivers/hid/hid-core.c | 1 +
drivers/hid/hid-gt683r.c | 320 ++++++++++++++++++++++++++++++++++++++++
drivers/hid/hid-ids.h | 2 +-
drivers/hid/usbhid/hid-quirks.c | 2 +-
6 files changed, 335 insertions(+), 2 deletions(-)
create mode 100644 drivers/hid/hid-gt683r.c
diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
index 7af9d0b..d93e0ae 100644
--- a/drivers/hid/Kconfig
+++ b/drivers/hid/Kconfig
@@ -261,6 +261,17 @@ config HOLTEK_FF
Say Y here if you have a Holtek On Line Grip based game controller
and want to have force feedback support for it.
+config HID_GT683R
+ tristate "MSI GT68xR LED support"
+ depends on LEDS_CLASS && USB_HID
+ ---help---
+ Say Y here if you want to enable support for the MSI GT68xR LEDS
+
+ This driver support following states normal, breathing and audio.
+ You can also select which leds you want to enable.
+ Currently the following devices are know to be supported:
+ - MSI GT683R
+
config HID_HUION
tristate "Huion tablets"
depends on USB_HID
diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile
index fc712dd..7129311 100644
--- a/drivers/hid/Makefile
+++ b/drivers/hid/Makefile
@@ -48,6 +48,7 @@ obj-$(CONFIG_HID_EMS_FF) += hid-emsff.o
obj-$(CONFIG_HID_ELECOM) += hid-elecom.o
obj-$(CONFIG_HID_ELO) += hid-elo.o
obj-$(CONFIG_HID_EZKEY) += hid-ezkey.o
+obj-$(CONFIG_HID_GT683R) += hid-gt683r.o
obj-$(CONFIG_HID_GYRATION) += hid-gyration.o
obj-$(CONFIG_HID_HOLTEK) += hid-holtek-kbd.o
obj-$(CONFIG_HID_HOLTEK) += hid-holtek-mouse.o
diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c
index da52279..ec88fdb 100644
--- a/drivers/hid/hid-core.c
+++ b/drivers/hid/hid-core.c
@@ -1827,6 +1827,7 @@ static const struct hid_device_id hid_have_special_driver[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_WIRELESS_OPTICAL_DESKTOP_3_0) },
{ HID_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_OFFICE_KB) },
{ HID_USB_DEVICE(USB_VENDOR_ID_MONTEREY, USB_DEVICE_ID_GENIUS_KB29E) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_MSI, USB_DEVICE_ID_MSI_GT683R_LED_PANEL) },
{ HID_USB_DEVICE(USB_VENDOR_ID_NTRIG, USB_DEVICE_ID_NTRIG_TOUCH_SCREEN) },
{ HID_USB_DEVICE(USB_VENDOR_ID_NTRIG, USB_DEVICE_ID_NTRIG_TOUCH_SCREEN_1) },
{ HID_USB_DEVICE(USB_VENDOR_ID_NTRIG, USB_DEVICE_ID_NTRIG_TOUCH_SCREEN_2) },
diff --git a/drivers/hid/hid-gt683r.c b/drivers/hid/hid-gt683r.c
new file mode 100644
index 0000000..04e4cc2
--- /dev/null
+++ b/drivers/hid/hid-gt683r.c
@@ -0,0 +1,320 @@
+/*
+ * MSI GT683R led driver
+ *
+ * Copyright (c) 2014 Janne Kanniainen <janne.kanniainen@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#include <linux/device.h>
+#include <linux/hid.h>
+#include <linux/kernel.h>
+#include <linux/leds.h>
+#include <linux/module.h>
+
+#include "hid-ids.h"
+
+#define GT683R_LED_BACK BIT(0)
+#define GT683R_LED_SIDE BIT(1)
+#define GT683R_LED_FRONT BIT(2)
+
+#define GT683R_BUFFER_SIZE 8
+
+/*
+ * GT683R_LED_OFF: all LEDs are off
+ * GT683R_LED_AUDIO: the status of LEDs depends
+ * on sound level
+ * GT683R_LED_BREATHING: LEDs brightness varies
+ * at human breathing rate
+ * GT683R_LED_NORMAL: LEDs are on
+ */
+enum gt683r_led_state {
+ GT683R_LED_OFF = 0,
+ GT683R_LED_AUDIO = 2,
+ GT683R_LED_BREATHING = 3,
+ GT683R_LED_NORMAL = 5
+};
+
+struct gt683r_led {
+ struct hid_device *hdev;
+ struct led_classdev led_dev_back;
+ struct led_classdev led_dev_side;
+ struct led_classdev led_dev_front;
+ struct mutex lock;
+ struct mutex state_lock;
+ struct work_struct work;
+ enum led_brightness brightness_back;
+ enum led_brightness brightness_side;
+ enum led_brightness brightness_front;
+ enum gt683r_led_state state;
+};
+
+static const struct hid_device_id gt683r_led_id[] = {
+ { HID_USB_DEVICE(USB_VENDOR_ID_MSI, USB_DEVICE_ID_MSI_GT683R_LED_PANEL) },
+ { }
+};
+
+#define GT683R_BRIGHTNESS_SET(name) \
+static void gt683r_brightness_set_##name(struct led_classdev *led_cdev, \
+ enum led_brightness brightness) \
+{ \
+ struct gt683r_led *led = \
+ container_of(led_cdev, struct gt683r_led, \
+ led_dev_##name); \
+ \
+ led->brightness_##name = brightness; \
+ \
+ schedule_work(&led->work); \
+}
+
+GT683R_BRIGHTNESS_SET(back);
+GT683R_BRIGHTNESS_SET(side);
+GT683R_BRIGHTNESS_SET(front);
+
+static ssize_t gt683r_show_state(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct hid_device *hdev =
+ container_of(dev, struct hid_device, dev);
+ struct gt683r_led *led = hid_get_drvdata(hdev);
+
+ if (led->state == GT683R_LED_OFF)
+ return sprintf(buf, "off\n");
+ else if (led->state == GT683R_LED_AUDIO)
+ return sprintf(buf, "audio\n");
+ else if (led->state == GT683R_LED_BREATHING)
+ return sprintf(buf, "breathing\n");
+ else
+ return sprintf(buf, "normal\n");
+}
+
+static ssize_t gt683r_store_state(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct hid_device *hdev =
+ container_of(dev, struct hid_device, dev);
+ struct gt683r_led *led = hid_get_drvdata(hdev);
+
+ mutex_lock(&led->state_lock);
+ if (!strncmp("off", buf, strlen("off"))) {
+ led->state = GT683R_LED_OFF;
+ } else if (!strncmp("audio", buf, strlen("audio"))) {
+ led->state = GT683R_LED_AUDIO;
+ } else if (!strncmp("breathing", buf, strlen("breathing"))) {
+ led->state = GT683R_LED_BREATHING;
+ } else if (!strncmp("normal", buf, strlen("normal"))) {
+ led->state = GT683R_LED_NORMAL;
+ } else {
+ count = -EINVAL;
+ goto fail;
+ }
+
+ schedule_work(&led->work);
+
+fail:
+ mutex_unlock(&led->state_lock);
+
+ return count;
+}
+
+static int gt683r_led_snd_msg(struct gt683r_led *led, char *msg)
+{
+ int ret;
+
+ ret = hid_hw_raw_request(led->hdev, msg[0], msg, GT683R_BUFFER_SIZE,
+ HID_FEATURE_REPORT, HID_REQ_SET_REPORT);
+ if (ret != GT683R_BUFFER_SIZE) {
+ hid_err(led->hdev,
+ "failed to send set report request: %i\n", ret);
+ return ret < 0 ? ret : -EIO;
+ }
+
+ return 0;
+}
+
+static void gt683r_led_set(struct gt683r_led *led, char leds, char state)
+{
+ char *buffer;
+
+ buffer = kzalloc(GT683R_BUFFER_SIZE, GFP_KERNEL);
+ if (!buffer)
+ return;
+
+ buffer[0] = 0x01;
+ buffer[1] = 0x02;
+ buffer[2] = 0x30;
+ buffer[3] = leds;
+ if (gt683r_led_snd_msg(led, buffer))
+ goto fail;
+
+ buffer[2] = 0x20;
+ buffer[3] = state;
+ buffer[4] = 0x01;
+ gt683r_led_snd_msg(led, buffer);
+
+fail:
+ kfree(buffer);
+}
+
+static void gt683r_led_work(struct work_struct *work)
+{
+ struct gt683r_led *led =
+ container_of(work, struct gt683r_led, work);
+ char leds = 0;
+
+ mutex_lock(&led->lock);
+
+ if (led->brightness_back)
+ leds |= GT683R_LED_BACK;
+
+ if (led->brightness_side)
+ leds |= GT683R_LED_SIDE;
+
+ if (led->brightness_front)
+ leds |= GT683R_LED_FRONT;
+
+ if (leds)
+ gt683r_led_set(led, leds, led->state);
+ else
+ gt683r_led_set(led, leds, GT683R_LED_OFF);
+
+ mutex_unlock(&led->lock);
+}
+
+static struct led_classdev gt683r_led_dev_back = {
+ .name = "gt683r-led:back",
+ .brightness_set = gt683r_brightness_set_back,
+ .max_brightness = 1,
+ .flags = LED_CORE_SUSPENDRESUME,
+};
+
+static struct led_classdev gt683r_led_dev_side = {
+ .name = "gt683r-led:side",
+ .brightness_set = gt683r_brightness_set_side,
+ .max_brightness = 1,
+ .flags = LED_CORE_SUSPENDRESUME,
+};
+
+static struct led_classdev gt683r_led_dev_front = {
+ .name = "gt683r-led:front",
+ .brightness_set = gt683r_brightness_set_front,
+ .max_brightness = 1,
+ .flags = LED_CORE_SUSPENDRESUME,
+};
+
+static struct device_attribute gt683r_led_state_attribute = {
+ .attr = {
+ .name = "state",
+ .mode = 0644,
+ },
+ .show = gt683r_show_state,
+ .store = gt683r_store_state,
+};
+
+static int gt683r_led_probe(struct hid_device *hdev,
+ const struct hid_device_id *id)
+{
+ int ret;
+ struct gt683r_led *led;
+
+ led = devm_kzalloc(&hdev->dev, sizeof(struct gt683r_led), GFP_KERNEL);
+ if (!led)
+ return -ENOMEM;
+
+ led->hdev = hdev;
+ hid_set_drvdata(hdev, led);
+
+ ret = hid_parse(hdev);
+ if (ret) {
+ hid_err(hdev, "hid parsing failed\n");
+ goto fail;
+ }
+
+ ret = hid_hw_start(hdev, HID_CONNECT_HIDRAW);
+ if (ret) {
+ hid_err(hdev, "hw start failed\n");
+ goto fail;
+ }
+
+ led->led_dev_back = gt683r_led_dev_back;
+ ret = led_classdev_register(&hdev->dev, &led->led_dev_back);
+ if (ret) {
+ hid_err(hdev, "could not register led device\n");
+ goto fail_back;
+ }
+
+ led->led_dev_side = gt683r_led_dev_side;
+ ret = led_classdev_register(&hdev->dev, &led->led_dev_side);
+ if (ret) {
+ hid_err(hdev, "could not register led device\n");
+ goto fail_side;
+ }
+
+ led->led_dev_front = gt683r_led_dev_front;
+ ret = led_classdev_register(&hdev->dev, &led->led_dev_front);
+ if (ret) {
+ hid_err(hdev, "could not register led device\n");
+ goto fail_front;
+ }
+
+ ret = device_create_file(&led->hdev->dev,
+ >683r_led_state_attribute);
+ if (ret) {
+ hid_err(hdev, "could not make state attribute file\n");
+ goto fail_create_file;
+ }
+
+ mutex_init(&led->lock);
+ mutex_init(&led->state_lock);
+ INIT_WORK(&led->work, gt683r_led_work);
+
+ return 0;
+
+fail_create_file:
+ led_classdev_unregister(&led->led_dev_front);
+fail_front:
+ led_classdev_unregister(&led->led_dev_side);
+fail_side:
+ led_classdev_unregister(&led->led_dev_back);
+fail_back:
+ hid_hw_stop(hdev);
+fail:
+ return ret;
+}
+
+static void gt683r_led_remove(struct hid_device *hdev)
+{
+ struct gt683r_led *led = hid_get_drvdata(hdev);
+
+ led_classdev_unregister(&led->led_dev_side);
+ led_classdev_unregister(&led->led_dev_back);
+ led_classdev_unregister(&led->led_dev_front);
+ cancel_work_sync(&led->work);
+ device_remove_file(&hdev->dev,
+ >683r_led_state_attribute);
+ hid_hw_stop(hdev);
+}
+
+static struct hid_driver gt683r_led_driver = {
+ .probe = gt683r_led_probe,
+ .remove = gt683r_led_remove,
+ .name = "gt683r_led",
+ .id_table = gt683r_led_id,
+};
+
+module_hid_driver(gt683r_led_driver);
+
+MODULE_AUTHOR("Janne Kanniainen");
+MODULE_DESCRIPTION("MSI GT683R led driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h
index 34bb220..3692d37 100644
--- a/drivers/hid/hid-ids.h
+++ b/drivers/hid/hid-ids.h
@@ -641,7 +641,7 @@
#define USB_DEVICE_ID_GENIUS_KB29E 0x3004
#define USB_VENDOR_ID_MSI 0x1770
-#define USB_DEVICE_ID_MSI_GX680R_LED_PANEL 0xff00
+#define USB_DEVICE_ID_MSI_GT683R_LED_PANEL 0xff00
#define USB_VENDOR_ID_NATIONAL_SEMICONDUCTOR 0x0400
#define USB_DEVICE_ID_N_S_HARMONY 0xc359
diff --git a/drivers/hid/usbhid/hid-quirks.c b/drivers/hid/usbhid/hid-quirks.c
index 8e4ddb3..c640e1d 100644
--- a/drivers/hid/usbhid/hid-quirks.c
+++ b/drivers/hid/usbhid/hid-quirks.c
@@ -73,7 +73,7 @@ static const struct hid_blacklist {
{ USB_VENDOR_ID_FORMOSA, USB_DEVICE_ID_FORMOSA_IR_RECEIVER, HID_QUIRK_NO_INIT_REPORTS },
{ USB_VENDOR_ID_FREESCALE, USB_DEVICE_ID_FREESCALE_MX28, HID_QUIRK_NOGET },
{ USB_VENDOR_ID_MGE, USB_DEVICE_ID_MGE_UPS, HID_QUIRK_NOGET },
- { USB_VENDOR_ID_MSI, USB_DEVICE_ID_MSI_GX680R_LED_PANEL, HID_QUIRK_NO_INIT_REPORTS },
+ { USB_VENDOR_ID_MSI, USB_DEVICE_ID_MSI_GT683R_LED_PANEL, HID_QUIRK_NO_INIT_REPORTS },
{ USB_VENDOR_ID_NEXIO, USB_DEVICE_ID_NEXIO_MULTITOUCH_PTI0750, HID_QUIRK_NO_INIT_REPORTS },
{ USB_VENDOR_ID_NOVATEK, USB_DEVICE_ID_NOVATEK_MOUSE, HID_QUIRK_NO_INIT_REPORTS },
{ USB_VENDOR_ID_PIXART, USB_DEVICE_ID_PIXART_OPTICAL_TOUCH_SCREEN, HID_QUIRK_NO_INIT_REPORTS },
--
1.9.2
^ permalink raw reply related
* [PATCH] Input: st-keyscan - Protect PM-only functions by #ifdef CONFIG_PM_SLEEP
From: Geert Uytterhoeven @ 2014-06-11 19:04 UTC (permalink / raw)
To: Dmitry Torokhov, Gabriel FERNANDEZ
Cc: linux-input, linux-kernel, Geert Uytterhoeven
If CONFIG_PM_SLEEP=n:
drivers/input/keyboard/st-keyscan.c:219: warning: ‘keyscan_suspend’ defined but not used
drivers/input/keyboard/st-keyscan.c:236: warning: ‘keyscan_resume’ defined but not used
Signed-off-by: Geert Uytterhoeven <geert@linux-m68k.org>
---
drivers/input/keyboard/st-keyscan.c | 2 ++
1 file changed, 2 insertions(+)
diff --git a/drivers/input/keyboard/st-keyscan.c b/drivers/input/keyboard/st-keyscan.c
index 758b48731415..668e3cf4b491 100644
--- a/drivers/input/keyboard/st-keyscan.c
+++ b/drivers/input/keyboard/st-keyscan.c
@@ -215,6 +215,7 @@ static int keyscan_probe(struct platform_device *pdev)
return 0;
}
+#ifdef CONFIG_PM_SLEEP
static int keyscan_suspend(struct device *dev)
{
struct platform_device *pdev = to_platform_device(dev);
@@ -249,6 +250,7 @@ static int keyscan_resume(struct device *dev)
mutex_unlock(&input->mutex);
return retval;
}
+#endif /* CONFIG_PM_SLEEP */
static SIMPLE_DEV_PM_OPS(keyscan_dev_pm_ops, keyscan_suspend, keyscan_resume);
--
1.9.1
--
To unsubscribe from this list: send the line "unsubscribe linux-input" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
^ permalink raw reply related
* [PATCH] HID: rmi: Protect PM-only functions by #ifdef CONFIG_PM
From: Geert Uytterhoeven @ 2014-06-11 19:03 UTC (permalink / raw)
To: Jiri Kosina; +Cc: linux-input, linux-kernel, Geert Uytterhoeven
If CONFIG_PM=n:
drivers/hid/hid-rmi.c:432: warning: ‘rmi_post_reset’ defined but not used
drivers/hid/hid-rmi.c:437: warning: ‘rmi_post_resume’ defined but not used
Signed-off-by: Geert Uytterhoeven <geert@linux-m68k.org>
---
drivers/hid/hid-rmi.c | 2 ++
1 file changed, 2 insertions(+)
diff --git a/drivers/hid/hid-rmi.c b/drivers/hid/hid-rmi.c
index 2451c7e5febd..578bbe65902b 100644
--- a/drivers/hid/hid-rmi.c
+++ b/drivers/hid/hid-rmi.c
@@ -428,6 +428,7 @@ static int rmi_raw_event(struct hid_device *hdev,
return 0;
}
+#ifdef CONFIG_PM
static int rmi_post_reset(struct hid_device *hdev)
{
return rmi_set_mode(hdev, RMI_MODE_ATTN_REPORTS);
@@ -437,6 +438,7 @@ static int rmi_post_resume(struct hid_device *hdev)
{
return rmi_set_mode(hdev, RMI_MODE_ATTN_REPORTS);
}
+#endif /* CONFIG_PM */
#define RMI4_MAX_PAGE 0xff
#define RMI4_PAGE_SIZE 0x0100
--
1.9.1
--
To unsubscribe from this list: send the line "unsubscribe linux-input" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
^ permalink raw reply related
* Re: [PATCH 0/4] Input: atmel_mxt_ts - make it work on Tegra
From: Stephen Warren @ 2014-06-11 18:17 UTC (permalink / raw)
To: Nick Dyer
Cc: Dmitry Torokhov, Benson Leung, Yufeng Shen, Daniel Kurtz,
linux-input@vger.kernel.org, Stephen Warren, Bowens, Alan
In-Reply-To: <537B8087.1040306@itdev.co.uk>
On 05/20/2014 10:19 AM, Nick Dyer wrote:
> Stephen Warren wrote:
>> On 05/16/2014 10:21 AM, Nick Dyer wrote:
>>> Thanks for this. Would you be happy for me to pick these changes up and
>>> include them along with the other work I am sending to Dmitry? I am just
>>> beginning to do various updates to the whole series, one of the things I
>>> need to sort out is the device tree support.
>>
>> That would be fine. I assume you'd only take the 2 Atmel driver patches.
>> I'll send the Tegra patches separately once the driver is merged.
>
> Great! Dmitry has merged some of the patches I sent now, so I'm just
> working on updating to take account of that and adding the device tree
> changes, and taking account of a couple of other review comments.
...
>>> I will need to add device tree parameters for the touchscreen as well as
>>> the touchpad, of course.
>>>
>>> By the way, the driver should work without any firmware file and just use
>>> the firmware and configuration from NVRAM - request_firmware() returns a
>>> failure and it continues through mxt_initialize().
>>
>> Hmmm. I couldn't get that to work after applying the patches you posted.
>> However, it did with what's already in linux-next plus the patches I sent.
>
> I will test this on my setup and see if I can figure out what is causing
> the problem.
Nick, did you get anywhere reproducing my problem? Unfortunately with
your recent patches merged, I can't get the touchpad working at all,
whereas before I had a few simple patches that made it work:-(
^ permalink raw reply
* Re: [PATCH v3] leds: USB: HID: Add support for MSI GT683R led panels
From: Johan Hovold @ 2014-06-11 17:34 UTC (permalink / raw)
To: Janne Kanniainen
Cc: jkosina, cooloney, linux-kernel, linux-leds, linux-usb,
linux-input, johan
In-Reply-To: <20140611140542.GG10256@localhost>
On Wed, Jun 11, 2014 at 04:05:42PM +0200, Johan Hovold wrote:
>
On Wed, Jun 11, 2014 at 12:21:39AM +0300, Janne Kanniainen wrote:
> > +static int gt683r_led_snd_msg(struct gt683r_led *led, char *msg)
> > +{
> > + int ret;
> > +
> > + ret = hid_hw_raw_request(led->hdev, 0x01, msg, GT683R_BUFFER_SIZE,
> > + HID_FEATURE_REPORT, HID_REQ_SET_REPORT);
> > + if (ret < 0) {
> > + hid_err(led->hdev,
> > + "failed to send set report request: %i\n", ret);
And here's one more: You need to check if ret != GT683R_BUFFER_SIZE and
make sure to return an error (e.g. -EIO) even if ret >= 0 in that case.
> > + return ret;
> > + }
> > +
> > + return 0;
> > +}
Johan
^ permalink raw reply
* Re: [PATCH v3] leds: USB: HID: Add support for MSI GT683R led panels
From: Johan Hovold @ 2014-06-11 15:30 UTC (permalink / raw)
To: Janne Kanniainen
Cc: jkosina, cooloney, linux-kernel, linux-leds, linux-usb,
linux-input, johan
In-Reply-To: <20140611140542.GG10256@localhost>
On Wed, Jun 11, 2014 at 04:05:42PM +0200, Johan Hovold wrote:
> On Wed, Jun 11, 2014 at 12:21:39AM +0300, Janne Kanniainen wrote:
> > +static const char gt683r_led_select_leds[GT683R_BUFFER_SIZE] = { 0x01, 0x02, 0x30, 0x00,
> > + 0x00, 0x00, 0x00, 0x00 };
> > +static const char gt683r_led_select_type[GT683R_BUFFER_SIZE] = { 0x01, 0x02, 0x20, 0x00,
> > + 0x01, 0x00, 0x00, 0x00 };
>
> 80 char limit.
>
> Perhaps move these to gt683r_led_set, which is the only place where they
> are used?
Or, as I hinted earlier, just allocate the 8-byte buffer using kzalloc
and only initialise the non-zero bytes directly. The first byte should
be the report id (0x01). I noticed that some hid-drivers use this fact
when sending the raw request (see below).
> > +
> > +static void gt683r_brightness_set(struct led_classdev *led_cdev,
> > + enum led_brightness brightness)
> > +{
> > + struct gt683r_led *led =
> > + container_of(led_cdev, struct gt683r_led, led_dev);
> > +
> > + led->brightness = brightness;
> > +
> > + schedule_work(&led->work);
> > +}
> > +
> > +static int gt683r_led_snd_msg(struct gt683r_led *led, char *msg)
> > +{
> > + int ret;
> > +
> > + ret = hid_hw_raw_request(led->hdev, 0x01, msg, GT683R_BUFFER_SIZE,
> > + HID_FEATURE_REPORT, HID_REQ_SET_REPORT);
That is, you could use msg[0] here instead of 0x01.
> > + if (ret < 0) {
> > + hid_err(led->hdev,
> > + "failed to send set report request: %i\n", ret);
> > + return ret;
> > + }
> > +
> > + return 0;
> > +}
Johan
^ permalink raw reply
* Re: [PATCH v3] leds: USB: HID: Add support for MSI GT683R led panels
From: Johan Hovold @ 2014-06-11 14:06 UTC (permalink / raw)
To: Jiri Kosina
Cc: Janne Kanniainen, cooloney, linux-kernel, linux-leds, linux-usb,
linux-input, johan
In-Reply-To: <alpine.LNX.2.00.1406111325000.1321@pobox.suse.cz>
On Wed, Jun 11, 2014 at 01:25:49PM +0200, Jiri Kosina wrote:
> On Wed, 11 Jun 2014, Janne Kanniainen wrote:
>
> > This driver adds support for USB controlled led panels that exists in MSI GT683R laptop
> >
> > Changes in v2:
> > - sorted headers to alphabetic order
> > - using devm_kzalloc
> > - using BIT(n)
> > - using usb_control_msg instead of usb_submit_urb
> > - removing unneeded code
> >
> > Changes in v3:
> > - implemented as HID device
> > - some cleanups and bug fixes
> >
> > Signed-off-by: Janne Kanniainen <janne.kanniainen@gmail.com>
>
> Thanks for the driver. Johan, as you did a very good job reviewing this
> before I managed to get to it, I'd be glad to put your Reviewed-by: to the
> commit before commiting it, if you are going to provide it.
I had a few more comments to v3, but I'll make sure to reply with a
Reviewed-by-tag before you apply.
Thanks,
Johan
^ permalink raw reply
* Re: [PATCH v3] leds: USB: HID: Add support for MSI GT683R led panels
From: Johan Hovold @ 2014-06-11 14:05 UTC (permalink / raw)
To: Janne Kanniainen
Cc: jkosina-AlSwsSmVLrQ, cooloney-Re5JQEeQqe8AvxtiuMwx3w,
linux-kernel-u79uwXL29TY76Z2rM5mHXA,
linux-leds-u79uwXL29TY76Z2rM5mHXA,
linux-usb-u79uwXL29TY76Z2rM5mHXA,
linux-input-u79uwXL29TY76Z2rM5mHXA, johan-DgEjT+Ai2ygdnm+yROfE0A
In-Reply-To: <1402435299-16410-1-git-send-email-janne.kanniainen-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
On Wed, Jun 11, 2014 at 12:21:39AM +0300, Janne Kanniainen wrote:
> This driver adds support for USB controlled led panels that exists in MSI GT683R laptop
Can you break this line by 72 columns or so as well?
> Changes in v2:
> - sorted headers to alphabetic order
> - using devm_kzalloc
> - using BIT(n)
> - using usb_control_msg instead of usb_submit_urb
> - removing unneeded code
>
> Changes in v3:
> - implemented as HID device
> - some cleanups and bug fixes
Thanks for the update, Janne. It looks really good now.
Please put the changelog entries after the cut-off line below as it's
not really needed in the git logs.
You should also always run your patches through scripts/checkpatch.pl
before submitting. It currently reports 8 warnings of which you should
fix all but possible the ones about device id entries exceeding 80 chars
(which is usually considered acceptable).
A few more comments follow below.
> Signed-off-by: Janne Kanniainen <janne.kanniainen-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
> ---
> drivers/hid/Kconfig | 6 ++
> drivers/hid/Makefile | 1 +
> drivers/hid/hid-core.c | 1 +
> drivers/hid/hid-gt683r.c | 186 ++++++++++++++++++++++++++++++++++++++++
> drivers/hid/hid-ids.h | 1 +
> drivers/hid/usbhid/hid-quirks.c | 1 +
> 6 files changed, 196 insertions(+)
> create mode 100644 drivers/hid/hid-gt683r.c
>
> diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
> index 7af9d0b..6ecc527 100644
> --- a/drivers/hid/Kconfig
> +++ b/drivers/hid/Kconfig
> @@ -210,6 +210,12 @@ config DRAGONRISE_FF
> Say Y here if you want to enable force feedback support for DragonRise Inc.
> game controllers.
>
> +config HID_GT683R
> + tristate "LED support for the MSI GT683R"
How about rephrasing this description as "MSI GT683R LED support" as this
should make entry easier to find in Kconfig.
As there appears to be more models that could use this driver (and uses
the same PID) perhaps you should use "MSI GT68xR" or similar depending
on what models there are. We now of at least GX680R (having the same
PID, at least).
There's no need to rename the driver and every function or variable
below though (i.e. a gt683r prefix is still perfectly fine).
> + depends on LEDS_CLASS && USB_HID
> + ---help---
> + Say Y here if you want to enable support for the MSI GT683R LEDS
> +
checkpatch.pl suggests adding descriptive paragraph here.
> config HID_EMS_FF
> tristate "EMS Production Inc. force feedback support"
> depends on HID
> diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile
> index fc712dd..111304e 100644
> --- a/drivers/hid/Makefile
> +++ b/drivers/hid/Makefile
> @@ -44,6 +44,7 @@ obj-$(CONFIG_HID_CHICONY) += hid-chicony.o
> obj-$(CONFIG_HID_CP2112) += hid-cp2112.o
> obj-$(CONFIG_HID_CYPRESS) += hid-cypress.o
> obj-$(CONFIG_HID_DRAGONRISE) += hid-dr.o
> +obj-$(CONFIG_HID_GT683R) += hid-gt683r.o
Keep the entries sorted?
> obj-$(CONFIG_HID_EMS_FF) += hid-emsff.o
> obj-$(CONFIG_HID_ELECOM) += hid-elecom.o
> obj-$(CONFIG_HID_ELO) += hid-elo.o
> diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c
> index da52279..ec88fdb 100644
> --- a/drivers/hid/hid-core.c
> +++ b/drivers/hid/hid-core.c
> @@ -1827,6 +1827,7 @@ static const struct hid_device_id hid_have_special_driver[] = {
> { HID_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_WIRELESS_OPTICAL_DESKTOP_3_0) },
> { HID_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_OFFICE_KB) },
> { HID_USB_DEVICE(USB_VENDOR_ID_MONTEREY, USB_DEVICE_ID_GENIUS_KB29E) },
> + { HID_USB_DEVICE(USB_VENDOR_ID_MSI, USB_DEVICE_ID_MSI_GT683R_LED_PANEL) },
> { HID_USB_DEVICE(USB_VENDOR_ID_NTRIG, USB_DEVICE_ID_NTRIG_TOUCH_SCREEN) },
> { HID_USB_DEVICE(USB_VENDOR_ID_NTRIG, USB_DEVICE_ID_NTRIG_TOUCH_SCREEN_1) },
> { HID_USB_DEVICE(USB_VENDOR_ID_NTRIG, USB_DEVICE_ID_NTRIG_TOUCH_SCREEN_2) },
> diff --git a/drivers/hid/hid-gt683r.c b/drivers/hid/hid-gt683r.c
> new file mode 100644
> index 0000000..4baaa36
> --- /dev/null
> +++ b/drivers/hid/hid-gt683r.c
> @@ -0,0 +1,186 @@
> +/*
> + * MSI GT683R led driver
> + *
> + * Copyright (c) 2014 Janne Kanniainen <janne.kanniainen-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
> + *
> + * This program is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU General Public License as
> + * published by the Free Software Foundation; either version 2 of
> + * the License, or (at your option) any later version.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
> + * GNU General Public License for more details.
> + *
> + */
> +
> +#include <linux/hid.h>
> +#include <linux/kernel.h>
> +#include <linux/leds.h>
> +#include <linux/module.h>
> +
> +#include "hid-ids.h"
> +
> +#define GT683R_LED_BACK BIT(0)
> +#define GT683R_LED_SIDE BIT(1)
> +#define GT683R_LED_FRONT BIT(2)
> +#define GT683R_LED_ALL (GT683R_LED_BACK | GT683R_LED_SIDE | GT683R_LED_FRONT)
Since it seems you are able to control these three LEDs independently,
shouldn't you consider registering three LEDs?
> +
> +#define GT683R_LED_OFF 0
> +#define GT683R_LED_AUDIO 2
> +#define GT683R_LED_BREATHING 3
> +#define GT683R_LED_NORMAL 5
What are AUDIO and BREATHING for? Perhaps add a descriptive comment?
> +
> +#define GT683R_BUFFER_SIZE 8
> +
> +struct gt683r_led {
> + struct hid_device *hdev;
> + struct led_classdev led_dev;
> + struct mutex lock;
> + struct work_struct work;
> + enum led_brightness brightness;
> +};
> +
> +static const struct hid_device_id gt683r_led_id[] = {
> + { HID_USB_DEVICE(USB_VENDOR_ID_MSI, USB_DEVICE_ID_MSI_GT683R_LED_PANEL) },
> + { }
> +};
> +
> +static const char gt683r_led_select_leds[GT683R_BUFFER_SIZE] = { 0x01, 0x02, 0x30, 0x00,
> + 0x00, 0x00, 0x00, 0x00 };
> +static const char gt683r_led_select_type[GT683R_BUFFER_SIZE] = { 0x01, 0x02, 0x20, 0x00,
> + 0x01, 0x00, 0x00, 0x00 };
80 char limit.
Perhaps move these to gt683r_led_set, which is the only place where they
are used?
> +
> +static void gt683r_brightness_set(struct led_classdev *led_cdev,
> + enum led_brightness brightness)
> +{
> + struct gt683r_led *led =
> + container_of(led_cdev, struct gt683r_led, led_dev);
> +
> + led->brightness = brightness;
> +
> + schedule_work(&led->work);
> +}
> +
> +static int gt683r_led_snd_msg(struct gt683r_led *led, char *msg)
> +{
> + int ret;
> +
> + ret = hid_hw_raw_request(led->hdev, 0x01, msg, GT683R_BUFFER_SIZE,
> + HID_FEATURE_REPORT, HID_REQ_SET_REPORT);
> + if (ret < 0) {
> + hid_err(led->hdev,
> + "failed to send set report request: %i\n", ret);
> + return ret;
> + }
> +
> + return 0;
> +}
> +
> +static void gt683r_led_set(struct gt683r_led *led, char type)
> +{
> + char *buffer;
> +
> + buffer = kmalloc(GT683R_BUFFER_SIZE, GFP_KERNEL);
> + if (!buffer) {
> + hid_err(led->hdev, "can\'t allocate buffer\n");
No need to log OOM messages as this would already have been taken care
of by the memory subsystem.
> + return;
> + }
> +
> + memcpy(buffer, gt683r_led_select_leds, GT683R_BUFFER_SIZE);
> + buffer[3] = GT683R_LED_ALL;
> + gt683r_led_snd_msg(led, buffer);
Error handling? Perhaps there's no point in sending the "type" report
if select_leds failed?
> +
> + memcpy(buffer, gt683r_led_select_type, GT683R_BUFFER_SIZE);
> + buffer[3] = type;
> + gt683r_led_snd_msg(led, buffer);
You don't have to change this if you don't want, but wouldn't
"set_state" be more descriptive than "select_type"?
> +
> + kfree(buffer);
> +}
> +
> +static void gt683r_led_work(struct work_struct *work)
> +{
> + struct gt683r_led *led =
> + container_of(work, struct gt683r_led, work);
> +
> + mutex_lock(&led->lock);
> +
> + if (led->brightness)
> + gt683r_led_set(led, GT683R_LED_NORMAL);
> + else
> + gt683r_led_set(led, GT683R_LED_OFF);
> +
> + mutex_unlock(&led->lock);
> +}
> +
> +static struct led_classdev gt683r_led_dev = {
> + .name = "gt683r-led",
> + .brightness_set = gt683r_brightness_set,
> + .max_brightness = 1,
> + .flags = LED_CORE_SUSPENDRESUME,
> +};
> +
> +static int gt683r_led_probe(struct hid_device *hdev,
> + const struct hid_device_id *id)
> +{
> + int ret;
> + struct gt683r_led *led;
> +
> + led = devm_kzalloc(&hdev->dev, sizeof(struct gt683r_led), GFP_KERNEL);
> + if (!led)
> + return -ENOMEM;
> +
> + led->hdev = hdev;
> + hid_set_drvdata(hdev, led);
> +
> + ret = hid_parse(hdev);
> + if (ret) {
> + hid_err(hdev, "hid parsing failed\n");
> + goto fail;
> + }
> +
> + ret = hid_hw_start(hdev, HID_CONNECT_HIDRAW);
> + if (ret) {
> + hid_err(hdev, "hw start failed\n");
> + goto fail;
> + }
> +
> + led->led_dev = gt683r_led_dev;
> + ret = led_classdev_register(&hdev->dev, &led->led_dev);
> + if (ret) {
> + hid_err(hdev, "could not register led device\n");
> + goto stop;
> + }
> +
> + mutex_init(&led->lock);
> + INIT_WORK(&led->work, gt683r_led_work);
> +
> + return 0;
> +stop:
> + hid_hw_stop(hdev);
> +fail:
> + return ret;
> +}
> +
> +static void gt683r_led_remove(struct hid_device *hdev)
> +{
> + struct gt683r_led *led = hid_get_drvdata(hdev);
> +
> + led_classdev_unregister(&led->led_dev);
> + cancel_work_sync(&led->work);
> + hid_hw_stop(hdev);
> +}
> +
> +static struct hid_driver gt683r_led_driver = {
> + .probe = gt683r_led_probe,
> + .remove = gt683r_led_remove,
> + .name = "gt683r_led",
> + .id_table = gt683r_led_id,
> +};
> +
> +module_hid_driver(gt683r_led_driver);
> +
> +MODULE_AUTHOR("Janne Kanniainen");
> +MODULE_DESCRIPTION("MSI GT683R led driver");
> +MODULE_LICENSE("GPL");
> diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h
> index 34bb220..e2097f7 100644
> --- a/drivers/hid/hid-ids.h
> +++ b/drivers/hid/hid-ids.h
> @@ -641,6 +641,7 @@
> #define USB_DEVICE_ID_GENIUS_KB29E 0x3004
>
> #define USB_VENDOR_ID_MSI 0x1770
> +#define USB_DEVICE_ID_MSI_GT683R_LED_PANEL 0xff00
> #define USB_DEVICE_ID_MSI_GX680R_LED_PANEL 0xff00
As these device have the same PID you shouldn't be adding a duplicate
entry to the blacklist_table below. Perhaps you can rename the original
one as GC68XR instead (or just remove the old one).
>
> #define USB_VENDOR_ID_NATIONAL_SEMICONDUCTOR 0x0400
> diff --git a/drivers/hid/usbhid/hid-quirks.c b/drivers/hid/usbhid/hid-quirks.c
> index 8e4ddb3..927e7be 100644
> --- a/drivers/hid/usbhid/hid-quirks.c
> +++ b/drivers/hid/usbhid/hid-quirks.c
> @@ -73,6 +73,7 @@ static const struct hid_blacklist {
> { USB_VENDOR_ID_FORMOSA, USB_DEVICE_ID_FORMOSA_IR_RECEIVER, HID_QUIRK_NO_INIT_REPORTS },
> { USB_VENDOR_ID_FREESCALE, USB_DEVICE_ID_FREESCALE_MX28, HID_QUIRK_NOGET },
> { USB_VENDOR_ID_MGE, USB_DEVICE_ID_MGE_UPS, HID_QUIRK_NOGET },
> + { USB_VENDOR_ID_MSI, USB_DEVICE_ID_MSI_GT683R_LED_PANEL, HID_QUIRK_NO_INIT_REPORTS },
> { USB_VENDOR_ID_MSI, USB_DEVICE_ID_MSI_GX680R_LED_PANEL, HID_QUIRK_NO_INIT_REPORTS },
> { USB_VENDOR_ID_NEXIO, USB_DEVICE_ID_NEXIO_MULTITOUCH_PTI0750, HID_QUIRK_NO_INIT_REPORTS },
> { USB_VENDOR_ID_NOVATEK, USB_DEVICE_ID_NOVATEK_MOUSE, HID_QUIRK_NO_INIT_REPORTS },
Thanks,
Johan
--
To unsubscribe from this list: send the line "unsubscribe linux-usb" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
^ permalink raw reply
* Re: [RFC PATCH] input, touch, atmel_mxt_ts: use atmel driver
From: Heiko Schocher @ 2014-06-11 12:15 UTC (permalink / raw)
To: Nick Dyer
Cc: devicetree, Tomasz Figa, Dmitry Torokhov, Kyungmin Park,
Henrik Rydberg, linux-input, Benson Leung, linux-arm-kernel
In-Reply-To: <5398465B.7060101@itdev.co.uk>
Hello Nick,
Am 11.06.2014 14:06, schrieb Nick Dyer:
> Heiko Schocher wrote:
>> try to bring the driver from
>> https://github.com/ndyer/linux for-next
>>
>> to mainline. Post this as a RFC, to see if this is possible...
>>
>> based on:
>> commit dda0a5570574cfe467c1c794cf7a17e1d742ec02 ("Input:
>> atmel_mxt_ts - implement improved debug message interface")
>>
>> from Nick Dyer<nick.dyer@itdev.co.uk>
>>
>> added:
>> - remove some (not all) checkpatch warnings
>> - Add support for voltage regulator
>> - Add support for Device Tree support
>
> My thinking has been that the maintainers will more likely accept a series
> of small patches than to make such a huge change to the entire driver, my
> for-next tree is where I'm working on this, and I plan to post another set
> of 15 patches soon, when I've fixed up some issues to do with probe/remove
> cycles.
Sound good!
> You should note that my for-next branch has moved on slightly since you
> have taken this, because I have implemented device tree support and changed
> the config download to use the request_firmware_async() function.
Oh, I take a look at this, thanks!
> Your device tree stuff for mach-goni.c looks really useful, however - if
> you can send me a patch for that against my current for-next branch I can
> include it with my version of the device tree support.
Ok, I send you this ASAP.
Thanks for this info.
bye,
Heiko
--
DENX Software Engineering GmbH, MD: Wolfgang Denk & Detlev Zundel
HRB 165235 Munich, Office: Kirchenstr.5, D-82194 Groebenzell, Germany
^ permalink raw reply
* Re: [RFC PATCH] input, touch, atmel_mxt_ts: use atmel driver
From: Nick Dyer @ 2014-06-11 12:06 UTC (permalink / raw)
To: Heiko Schocher, linux-arm-kernel, devicetree
Cc: Tomasz Figa, Kyungmin Park, Henrik Rydberg, linux-input,
Benson Leung, Dmitry Torokhov
In-Reply-To: <1402468526-9165-1-git-send-email-hs@denx.de>
Heiko Schocher wrote:
> try to bring the driver from
> https://github.com/ndyer/linux for-next
>
> to mainline. Post this as a RFC, to see if this is possible...
>
> based on:
> commit dda0a5570574cfe467c1c794cf7a17e1d742ec02 ("Input:
> atmel_mxt_ts - implement improved debug message interface")
>
> from Nick Dyer <nick.dyer@itdev.co.uk>
>
> added:
> - remove some (not all) checkpatch warnings
> - Add support for voltage regulator
> - Add support for Device Tree support
My thinking has been that the maintainers will more likely accept a series
of small patches than to make such a huge change to the entire driver, my
for-next tree is where I'm working on this, and I plan to post another set
of 15 patches soon, when I've fixed up some issues to do with probe/remove
cycles.
You should note that my for-next branch has moved on slightly since you
have taken this, because I have implemented device tree support and changed
the config download to use the request_firmware_async() function.
Your device tree stuff for mach-goni.c looks really useful, however - if
you can send me a patch for that against my current for-next branch I can
include it with my version of the device tree support.
all the best
Nick
^ permalink raw reply
* Re: [PATCH v3] leds: USB: HID: Add support for MSI GT683R led panels
From: Jiri Kosina @ 2014-06-11 11:25 UTC (permalink / raw)
To: Janne Kanniainen
Cc: cooloney, linux-kernel, linux-leds, linux-usb, linux-input, johan
In-Reply-To: <1402435299-16410-1-git-send-email-janne.kanniainen@gmail.com>
On Wed, 11 Jun 2014, Janne Kanniainen wrote:
> This driver adds support for USB controlled led panels that exists in MSI GT683R laptop
>
> Changes in v2:
> - sorted headers to alphabetic order
> - using devm_kzalloc
> - using BIT(n)
> - using usb_control_msg instead of usb_submit_urb
> - removing unneeded code
>
> Changes in v3:
> - implemented as HID device
> - some cleanups and bug fixes
>
> Signed-off-by: Janne Kanniainen <janne.kanniainen@gmail.com>
Thanks for the driver. Johan, as you did a very good job reviewing this
before I managed to get to it, I'd be glad to put your Reviewed-by: to the
commit before commiting it, if you are going to provide it.
Thanks,
--
Jiri Kosina
SUSE Labs
^ permalink raw reply
* Re: [PATCH v2 0/2] Add support for Lenovo Compact Keyboard
From: Jiri Kosina @ 2014-06-11 10:57 UTC (permalink / raw)
To: Jamie Lentin; +Cc: linux-input, linux-kernel
In-Reply-To: <1402439094-25464-1-git-send-email-jm@lentin.co.uk>
On Tue, 10 Jun 2014, Jamie Lentin wrote:
> This patchset follows on from my previous attempt[1] to add support for
> these keyboards from Lenovo.
Jamie,
thanks for the driver. Please re-send v3 with Antonio's comments
addressed, and I'll be happy to apply it.
--
Jiri Kosina
SUSE Labs
^ permalink raw reply
* Re: [PATCH] drivers: Kconfig: Add HAS_IOMEM dependency for allmodconfig under score architecture
From: Jiri Kosina @ 2014-06-11 8:53 UTC (permalink / raw)
To: Chen Gang; +Cc: kishon, linux-input, linux-next, liqin.linux, lennox.wu
In-Reply-To: <5396F7F2.7080202@gmail.com>
On Tue, 10 Jun 2014, Chen Gang wrote:
> > When NO_IOMEM is enabled (e.g. score architecture), some drivers which
> > need HAS_IOMEM need notice about it, or it will report related warning:
> >
> > scripts/kconfig/conf --allmodconfig Kconfig
> > kernel/time/Kconfig:162:warning: range is invalid
> ^
> Oh, sorry, this line above need be removed, it is not for this patch, if
> still necessary to send patch v2 for it, please let me know, thanks.
I have fixed it up (together with adding a proper subject) and applied.
--
Jiri Kosina
SUSE Labs
^ permalink raw reply
* Re: [PATCH] HID: drop remove Unicode non-breaking space from Kconfig
From: Jiri Kosina @ 2014-06-11 8:50 UTC (permalink / raw)
To: Brian Norris; +Cc: Linux Kernel, linux-input
In-Reply-To: <1402436136-17918-1-git-send-email-computersforpeace@gmail.com>
On Tue, 10 Jun 2014, Brian Norris wrote:
> There is a UTF-8 non-breaking space character (0xc2 0xa0) after the "Y"
> in "Say Y here". This is probably not intentional. Replace it with a
> standard ASCII space (0x20).
>
> If you can't see a difference here, I don't blame you :)
>
> Signed-off-by: Brian Norris <computersforpeace@gmail.com>
> Cc: Jiri Kosina <jkosina@suse.cz>
> Cc: linux-input@vger.kernel.org
> ---
> drivers/hid/Kconfig | 2 +-
> 1 file changed, 1 insertion(+), 1 deletion(-)
>
> diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
> index 800c8b60f7a2..08c9693e6f83 100644
> --- a/drivers/hid/Kconfig
> +++ b/drivers/hid/Kconfig
> @@ -785,7 +785,7 @@ config HID_XINMO
> depends on HID
> ---help---
> Support for Xin-Mo devices that are not fully compliant with the HID
> - standard. Currently only supports the Xin-Mo Dual Arcade. Say Y here
> + standard. Currently only supports the Xin-Mo Dual Arcade. Say Y here
> if you have a Xin-Mo Dual Arcade controller.
Applied, thanks.
--
Jiri Kosina
SUSE Labs
^ permalink raw reply
* Re: [PATCH v2 2/2] Add support for Compact (Bluetooth|USB) keyboard with Trackpoint
From: Antonio Ospite @ 2014-06-11 8:36 UTC (permalink / raw)
To: Jamie Lentin; +Cc: Jiri Kosina, linux-input, linux-kernel
In-Reply-To: <1402439094-25464-3-git-send-email-jm@lentin.co.uk>
On Tue, 10 Jun 2014 23:24:54 +0100
Jamie Lentin <jm@lentin.co.uk> wrote:
> Signed-off-by: Jamie Lentin <jm@lentin.co.uk>
Some minor comments here too.
> ---
> drivers/hid/hid-core.c | 2 +
> drivers/hid/hid-ids.h | 2 +
> drivers/hid/hid-lenovo-tpkbd.c | 203 +++++++++++++++++++++++++++++++++++++++++
> include/linux/hid.h | 1 +
> 4 files changed, 208 insertions(+)
>
> diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c
> index 8a5384c..6591f4e 100644
> --- a/drivers/hid/hid-core.c
> +++ b/drivers/hid/hid-core.c
> @@ -1738,6 +1738,8 @@ static const struct hid_device_id hid_have_special_driver[] = {
> { HID_USB_DEVICE(USB_VENDOR_ID_LCPOWER, USB_DEVICE_ID_LCPOWER_LC1000 ) },
> #if IS_ENABLED(CONFIG_HID_LENOVO_TPKBD)
> { HID_USB_DEVICE(USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_TPKBD) },
> + { HID_USB_DEVICE(USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_CUSBKBD) },
> + { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_CBTKBD) },
> #endif
> { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_MX3000_RECEIVER) },
> { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_S510_RECEIVER) },
> diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h
> index 6e12cd0..1763a07 100644
> --- a/drivers/hid/hid-ids.h
> +++ b/drivers/hid/hid-ids.h
> @@ -551,6 +551,8 @@
>
> #define USB_VENDOR_ID_LENOVO 0x17ef
> #define USB_DEVICE_ID_LENOVO_TPKBD 0x6009
> +#define USB_DEVICE_ID_LENOVO_CUSBKBD 0x6047
> +#define USB_DEVICE_ID_LENOVO_CBTKBD 0x6048
>
> #define USB_VENDOR_ID_LG 0x1fd2
> #define USB_DEVICE_ID_LG_MULTITOUCH 0x0064
> diff --git a/drivers/hid/hid-lenovo-tpkbd.c b/drivers/hid/hid-lenovo-tpkbd.c
> index 3bec9f5..1671e7a 100644
> --- a/drivers/hid/hid-lenovo-tpkbd.c
> +++ b/drivers/hid/hid-lenovo-tpkbd.c
> @@ -1,8 +1,11 @@
> /*
> * HID driver for Lenovo:-
> * * ThinkPad USB Keyboard with TrackPoint
> + * * ThinkPad Compact Bluetooth Keyboard with TrackPoint
> + * * ThinkPad Compact USB Keyboard with TrackPoint
Use - as the bullet.
> *
> * Copyright (c) 2012 Bernhard Seibold
> + * Copyright (c) 2014 Jamie Lentin <jm@lentin.co.uk>
> */
>
> /*
> @@ -34,6 +37,10 @@ struct tpkbd_data_pointer {
> int press_speed;
> };
>
> +struct tpcompactkbd_sc {
> + unsigned int fn_lock;
> +};
> +
> #define map_key_clear(c) hid_map_usage_clear(hi, usage, bit, max, EV_KEY, (c))
>
> static int tpkbd_input_mapping_tp(struct hid_device *hdev,
> @@ -49,17 +56,154 @@ static int tpkbd_input_mapping_tp(struct hid_device *hdev,
> return 0;
> }
>
> +static int tpcompactkbd_input_mapping(struct hid_device *hdev,
Maybe name these functions like tpkbd_input_mapping_compact()?
This way the namespace is more consistent, and follows the logic of
patch 1/2 more closely.
Use this scheme at least for functions which have a _tp() counterpart.
> + struct hid_input *hi, struct hid_field *field,
> + struct hid_usage *usage, unsigned long **bit, int *max)
> +{
> + /* HID_UP_LNVENDOR = USB, HID_UP_MSVENDOR = BT */
> + if ((usage->hid & HID_USAGE_PAGE) == HID_UP_MSVENDOR ||
> + (usage->hid & HID_USAGE_PAGE) == HID_UP_LNVENDOR) {
> + set_bit(EV_REP, hi->input->evbit);
> + switch (usage->hid & HID_USAGE) {
> + case 0x00f1: /* Fn-F4: Mic mute */
> + map_key_clear(KEY_MICMUTE);
> + return 1;
> + case 0x00f2: /* Fn-F5: Brightness down */
> + map_key_clear(KEY_BRIGHTNESSDOWN);
> + return 1;
> + case 0x00f3: /* Fn-F6: Brightness up */
> + map_key_clear(KEY_BRIGHTNESSUP);
> + return 1;
> + case 0x00f4: /* Fn-F7: External display (projector) */
> + map_key_clear(KEY_SWITCHVIDEOMODE);
> + return 1;
> + case 0x00f5: /* Fn-F8: Wireless */
> + map_key_clear(KEY_WLAN);
> + return 1;
> + case 0x00f6: /* Fn-F9: Control panel */
> + map_key_clear(KEY_CONFIG);
> + return 1;
> + case 0x00f8: /* Fn-F11: View open applications (3 boxes) */
> + map_key_clear(KEY_FN_F11);
> + return 1;
> + case 0x00fa: /* Fn-Esc: Fn-lock toggle */
> + map_key_clear(KEY_FN_ESC);
> + return 1;
> + case 0x00fb: /* Fn-F12: Open My computer (6 boxes) USB-only */
> + /* NB: This mapping is invented in raw_event below */
> + map_key_clear(KEY_FILE);
> + return 1;
> + }
> + }
> +
> + return 0;
> +}
> +
> static int tpkbd_input_mapping(struct hid_device *hdev,
> struct hid_input *hi, struct hid_field *field,
> struct hid_usage *usage, unsigned long **bit, int *max)
> {
> if (hdev->product == USB_DEVICE_ID_LENOVO_TPKBD)
> return tpkbd_input_mapping_tp(hdev, hi, field, usage, bit, max);
> + if (hdev->product == USB_DEVICE_ID_LENOVO_CUSBKBD)
> + return tpcompactkbd_input_mapping(hdev, hi, field, usage, bit, max);
> + if (hdev->product == USB_DEVICE_ID_LENOVO_CBTKBD)
What about combining the checks?
if (hdev->product == USB_DEVICE_ID_LENOVO_CUSBKBD ||
hdev->product == USB_DEVICE_ID_LENOVO_CBTKBD)
> + return tpcompactkbd_input_mapping(hdev, hi, field, usage, bit, max);
> return 0;
> }
>
> #undef map_key_clear
>
> +/* Send a config command to the keyboard */
> +static int tpcompactkbd_send_cmd(struct hid_device *hdev,
> + unsigned char byte2, unsigned char byte3)
> +{
> + int ret;
> + unsigned char buf[] = {0x18, byte2, byte3};
> + unsigned char report_type = HID_OUTPUT_REPORT;
> +
> + /* The USB keyboard accepts commands via SET_FEATURE */
> + if (hdev->product == USB_DEVICE_ID_LENOVO_CUSBKBD) {
> + buf[0] = 0x13;
> + report_type = HID_FEATURE_REPORT;
> + }
> +
> + ret = hdev->hid_output_raw_report(hdev, buf, sizeof(buf), report_type);
> + return ret < 0 ? ret : 0; /* BT returns 0, USB returns sizeof(buf) */
> +}
> +
> +static void tpcompactkbd_features_set(struct hid_device *hdev)
> +{
> + struct tpcompactkbd_sc *tpcsc = hid_get_drvdata(hdev);
> +
> + if (tpcompactkbd_send_cmd(hdev, 0x05, tpcsc->fn_lock ? 0x01 : 0x00))
> + hid_err(hdev, "Fn-lock setting failed\n");
> +}
> +
> +static ssize_t tpcompactkbd_fn_lock_show(struct device *dev,
> + struct device_attribute *attr,
> + char *buf)
> +{
> + struct hid_device *hdev = container_of(dev, struct hid_device, dev);
> + struct tpcompactkbd_sc *tpcsc = hid_get_drvdata(hdev);
> +
> + return snprintf(buf, PAGE_SIZE, "%u\n", tpcsc->fn_lock);
> +}
> +
> +static ssize_t tpcompactkbd_fn_lock_store(struct device *dev,
> + struct device_attribute *attr,
> + const char *buf,
> + size_t count)
> +{
> + struct hid_device *hdev = container_of(dev, struct hid_device, dev);
> + struct tpcompactkbd_sc *tpcsc = hid_get_drvdata(hdev);
> + int value;
> +
> + if (kstrtoint(buf, 10, &value))
> + return -EINVAL;
> + if (value < 0 || value > 1)
> + return -EINVAL;
> +
> + tpcsc->fn_lock = value;
> + tpcompactkbd_features_set(hdev);
> +
> + return count;
> +}
> +
> +static struct device_attribute dev_attr_pointer_fn_lock =
> + __ATTR(fn_lock, S_IWUSR | S_IRUGO,
> + tpcompactkbd_fn_lock_show,
> + tpcompactkbd_fn_lock_store);
> +
> +static struct attribute *tpcompactkbd_attributes[] = {
> + &dev_attr_pointer_fn_lock.attr,
> + NULL
> +};
> +
> +static const struct attribute_group tpcompactkbd_attr_group = {
> + .attrs = tpcompactkbd_attributes,
> +};
> +
> +static int tpkbd_raw_event(struct hid_device *hdev,
> + struct hid_report *report, u8 *data, int size)
> +{
> + /*
> + * USB keyboard's Fn-F12 report holds down many other keys, and it's
> + * own key is outside the usage page range. Remove extra keypresses and
> + * remap to inside usage page.
> + */
> + if (unlikely(hdev->product == USB_DEVICE_ID_LENOVO_CUSBKBD
> + && size == 3
> + && data[0] == 0x15
> + && data[1] == 0x94
> + && data[2] == 0x01)) {
> + data[1] = 0x0;
> + data[2] = 0x4;
> + }
> +
> + return 0;
> +}
> +
> static int tpkbd_features_set(struct hid_device *hdev)
> {
> struct hid_report *report;
> @@ -406,6 +550,46 @@ static int tpkbd_probe_tp(struct hid_device *hdev)
> return 0;
> }
>
> +static int tpcompactkbd_probe(struct hid_device *hdev,
> + const struct hid_device_id *id)
> +{
> + int ret;
> + struct tpcompactkbd_sc *tpcsc;
> +
> + tpcsc = devm_kzalloc(&hdev->dev, sizeof(*tpcsc), GFP_KERNEL);
> + if (tpcsc == NULL) {
> + hid_err(hdev, "can't alloc keyboard descriptor\n");
> + return -ENOMEM;
> + }
> + hid_set_drvdata(hdev, tpcsc);
> +
> + /* All the custom action happens on the mouse device for USB */
> + if (hdev->product == USB_DEVICE_ID_LENOVO_CUSBKBD
> + && hdev->type != HID_TYPE_USBMOUSE) {
> + pr_debug("Ignoring keyboard half of device\n");
> + return 0;
> + }
> +
> + /*
> + * Tell the keyboard a driver understands it, and turn F7, F9, F11 into
> + * regular keys
> + */
> + ret = tpcompactkbd_send_cmd(hdev, 0x01, 0x03);
> + if (ret)
> + hid_warn(hdev, "Failed to switch F7/9/11 into regular keys\n");
> +
> + /* Turn Fn-Lock on by default */
> + tpcsc->fn_lock = 1;
> + tpcompactkbd_features_set(hdev);
> +
> + if (sysfs_create_group(&hdev->dev.kobj,
> + &tpcompactkbd_attr_group)) {
Use:
ret = sysfs_create_group()
if (ret) {
> + hid_warn(hdev, "Could not create sysfs group\n");
> + }
with only one statement in the if body curly braces are not needed.
> +
> + return 0;
> +}
> +
> static int tpkbd_probe(struct hid_device *hdev,
> const struct hid_device_id *id)
> {
> @@ -426,6 +610,12 @@ static int tpkbd_probe(struct hid_device *hdev,
> if (hdev->product == USB_DEVICE_ID_LENOVO_TPKBD
> && tpkbd_probe_tp(hdev))
> goto err_hid;
> + if (hdev->product == USB_DEVICE_ID_LENOVO_CUSBKBD
> + && tpcompactkbd_probe(hdev, id))
> + goto err_hid;
> + if (hdev->product == USB_DEVICE_ID_LENOVO_CBTKBD
> + && tpcompactkbd_probe(hdev, id))
Again, what about combining the checks?
Calling the function in the if body (and checking the return value of
course :P).
> + goto err_hid;
>
> return 0;
> err_hid:
> @@ -447,6 +637,12 @@ static void tpkbd_remove_tp(struct hid_device *hdev)
> hid_set_drvdata(hdev, NULL);
> }
>
> +static void tpcompactkbd_remove(struct hid_device *hdev)
> +{
> + sysfs_remove_group(&hdev->dev.kobj,
> + &tpcompactkbd_attr_group);
> +}
> +
> static void tpkbd_remove(struct hid_device *hdev)
> {
> if (!hid_get_drvdata(hdev))
> @@ -454,12 +650,18 @@ static void tpkbd_remove(struct hid_device *hdev)
>
> if (hdev->product == USB_DEVICE_ID_LENOVO_TPKBD)
> tpkbd_remove_tp(hdev);
> + if (hdev->product == USB_DEVICE_ID_LENOVO_CUSBKBD)
> + tpcompactkbd_remove(hdev);
> + if (hdev->product == USB_DEVICE_ID_LENOVO_CBTKBD)
Here too.
> + tpcompactkbd_remove(hdev);
>
> hid_hw_stop(hdev);
> }
>
> static const struct hid_device_id tpkbd_devices[] = {
> { HID_USB_DEVICE(USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_TPKBD) },
> + { HID_USB_DEVICE(USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_CUSBKBD) },
> + { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_CBTKBD) },
> { }
> };
>
> @@ -471,6 +673,7 @@ static struct hid_driver tpkbd_driver = {
> .input_mapping = tpkbd_input_mapping,
> .probe = tpkbd_probe,
> .remove = tpkbd_remove,
> + .raw_event = tpkbd_raw_event,
> };
> module_hid_driver(tpkbd_driver);
>
> diff --git a/include/linux/hid.h b/include/linux/hid.h
> index 31b9d29..ed23d6a 100644
> --- a/include/linux/hid.h
> +++ b/include/linux/hid.h
> @@ -167,6 +167,7 @@ struct hid_item {
> #define HID_UP_MSVENDOR 0xff000000
> #define HID_UP_CUSTOM 0x00ff0000
> #define HID_UP_LOGIVENDOR 0xffbc0000
> +#define HID_UP_LNVENDOR 0xffa00000
> #define HID_UP_SENSOR 0x00200000
>
> #define HID_USAGE 0x0000ffff
> --
> 2.0.0.rc2
>
> --
> To unsubscribe from this list: send the line "unsubscribe linux-input" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at http://vger.kernel.org/majordomo-info.html
>
Ciao ciao,
Antonio
--
Antonio Ospite
http://ao2.it
A: Because it messes up the order in which people normally read text.
See http://en.wikipedia.org/wiki/Posting_style
Q: Why is top-posting such a bad thing?
^ permalink raw reply
* Re: [PATCH v2 1/2] Loosen seams to allow support of other keyboards
From: Antonio Ospite @ 2014-06-11 7:41 UTC (permalink / raw)
To: Jamie Lentin; +Cc: Jiri Kosina, linux-input, linux-kernel
In-Reply-To: <1402439094-25464-2-git-send-email-jm@lentin.co.uk>
Hi Jamie,
some few ideas in case there will be a v3, most of them are really just
nitpicks.
On Tue, 10 Jun 2014 23:24:53 +0100
Jamie Lentin <jm@lentin.co.uk> wrote:
> Signed-off-by: Jamie Lentin <jm@lentin.co.uk>
> ---
> drivers/hid/hid-lenovo-tpkbd.c | 33 ++++++++++++++++++++++++---------
> 1 file changed, 24 insertions(+), 9 deletions(-)
>
> diff --git a/drivers/hid/hid-lenovo-tpkbd.c b/drivers/hid/hid-lenovo-tpkbd.c
> index 2d25b6c..3bec9f5 100644
> --- a/drivers/hid/hid-lenovo-tpkbd.c
> +++ b/drivers/hid/hid-lenovo-tpkbd.c
> @@ -1,5 +1,6 @@
> /*
> - * HID driver for Lenovo ThinkPad USB Keyboard with TrackPoint
> + * HID driver for Lenovo:-
> + * * ThinkPad USB Keyboard with TrackPoint
> *
Remove the - after the : and use the - as the bullet.
The asterisk is used for the comment already.
> * Copyright (c) 2012 Bernhard Seibold
> */
> @@ -35,7 +36,7 @@ struct tpkbd_data_pointer {
>
> #define map_key_clear(c) hid_map_usage_clear(hi, usage, bit, max, EV_KEY, (c))
>
> -static int tpkbd_input_mapping(struct hid_device *hdev,
> +static int tpkbd_input_mapping_tp(struct hid_device *hdev,
> struct hid_input *hi, struct hid_field *field,
> struct hid_usage *usage, unsigned long **bit, int *max)
> {
> @@ -48,6 +49,15 @@ static int tpkbd_input_mapping(struct hid_device *hdev,
> return 0;
> }
>
> +static int tpkbd_input_mapping(struct hid_device *hdev,
> + struct hid_input *hi, struct hid_field *field,
> + struct hid_usage *usage, unsigned long **bit, int *max)
> +{
> + if (hdev->product == USB_DEVICE_ID_LENOVO_TPKBD)
> + return tpkbd_input_mapping_tp(hdev, hi, field, usage, bit, max);
> + return 0;
> +}
> +
> #undef map_key_clear
>
> static int tpkbd_features_set(struct hid_device *hdev)
> @@ -338,6 +348,11 @@ static int tpkbd_probe_tp(struct hid_device *hdev)
> char *name_mute, *name_micmute;
> int i;
>
> + /* Ignore unless tpkbd_input_mapping_tp marked it as a pointer */
> + if (!hid_get_drvdata(hdev))
> + return 0;
> + hid_set_drvdata(hdev, NULL);
> +
Maybe add a blank line before hid_set_drvdata().
JFTR this logic, already in the original code, relies on the fact that
the input_mapping callback is invoked before tpkbd_probe_tp():
hid_hw_start()
hid_connect()
hidinput_connect()
hidinput_configure_usage()
device->driver->input_mapping()
tpkbd_probe_tp()
which was not completely trivial to an occasional reader like me.
> /* Validate required reports. */
> for (i = 0; i < 4; i++) {
> if (!hid_validate_values(hdev, HID_FEATURE_REPORT, 4, i, 1))
> @@ -408,12 +423,9 @@ static int tpkbd_probe(struct hid_device *hdev,
> goto err;
> }
>
> - if (hid_get_drvdata(hdev)) {
> - hid_set_drvdata(hdev, NULL);
> - ret = tpkbd_probe_tp(hdev);
> - if (ret)
> - goto err_hid;
> - }
> + if (hdev->product == USB_DEVICE_ID_LENOVO_TPKBD
> + && tpkbd_probe_tp(hdev))
> + goto err_hid;
>
I'd avoid calling functions in conditions, especially when mixed with
other checks, but that's mostly personal taste. If you decide to call
the function in the if body you also get that the hdev->product check
will be exactly the same as in the symmetric one in tpkbd_remove().
> return 0;
> err_hid:
> @@ -437,7 +449,10 @@ static void tpkbd_remove_tp(struct hid_device *hdev)
>
> static void tpkbd_remove(struct hid_device *hdev)
> {
> - if (hid_get_drvdata(hdev))
> + if (!hid_get_drvdata(hdev))
> + return;
> +
I think this check can just become a:
if (data_pointer == NULL)
return;
early in tpkbd_remove_tp().
Also, I don't think you could just return in tpkbd_remove() like
you are doing as hid_hw_stop() must be always called.
> + if (hdev->product == USB_DEVICE_ID_LENOVO_TPKBD)
> tpkbd_remove_tp(hdev);
>
> hid_hw_stop(hdev);
> --
> 2.0.0.rc2
>
> --
> To unsubscribe from this list: send the line "unsubscribe linux-input" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at http://vger.kernel.org/majordomo-info.html
Ciao Ciao,
Antonio
--
Antonio Ospite
http://ao2.it
A: Because it messes up the order in which people normally read text.
See http://en.wikipedia.org/wiki/Posting_style
Q: Why is top-posting such a bad thing?
^ permalink raw reply
* [RFC PATCH] input, touch, atmel_mxt_ts: use atmel driver
From: Heiko Schocher @ 2014-06-11 6:35 UTC (permalink / raw)
To: linux-arm-kernel, devicetree
Cc: Heiko Schocher, Tomasz Figa, Kyungmin Park, Nick Dyer,
Henrik Rydberg, linux-input
try to bring the driver from
https://github.com/ndyer/linux for-next
to mainline. Post this as a RFC, to see if this is possible...
based on:
commit dda0a5570574cfe467c1c794cf7a17e1d742ec02 ("Input:
atmel_mxt_ts - implement improved debug message interface")
from Nick Dyer <nick.dyer@itdev.co.uk>
added:
- remove some (not all) checkpatch warnings
- Add support for voltage regulator
- Add support for Device Tree support
Signed-off-by: Heiko Schocher <hs@denx.de>
Cc: Tomasz Figa <t.figa@samsung.com>
Cc: Kyungmin Park <kyungmin.park@samsung.com>
Cc: Nick Dyer <nick.dyer@itdev.co.uk>
Cc: Henrik Rydberg <rydberg@euromail.se>
Cc: linux-input@vger.kernel.org
---
.../bindings/input/touchscreen/atmel_mxt_ts.txt | 27 +
arch/arm/mach-s5pv210/mach-goni.c | 27 +
drivers/input/touchscreen/atmel_mxt_ts.c | 2666 ++++++++++++++++----
include/linux/i2c/atmel_mxt_ts.h | 8 +-
4 files changed, 2245 insertions(+), 483 deletions(-)
create mode 100644 Documentation/devicetree/bindings/input/touchscreen/atmel_mxt_ts.txt
diff --git a/Documentation/devicetree/bindings/input/touchscreen/atmel_mxt_ts.txt b/Documentation/devicetree/bindings/input/touchscreen/atmel_mxt_ts.txt
new file mode 100644
index 0000000..e025169
--- /dev/null
+++ b/Documentation/devicetree/bindings/input/touchscreen/atmel_mxt_ts.txt
@@ -0,0 +1,27 @@
+* Atmel maXtouch touchscreen controller
+
+Required properties:
+- compatible: must be "atmel,maxtouch"
+- reg: I2C address of the chip
+- interrupt-parent: interrupt controller which provides the interrupt
+- interrupts: interrupt signal to which the chip is connected
+
+Optional properties:
+- vdd-supply: voltage regulator used for power control and reading
+ operating voltage
+
+Example:
+
+ i2c@00000000 {
+ /* ... */
+
+ tsp@4a {
+ compatible = "atmel,maxtouch";
+ reg = <0x4a>;
+ interrupt-parent = <&gpe1>;
+ interrupts = <7 2>;
+ vdd-supply = <&tsp_reg>;
+ };
+
+ /* ... */
+ };
diff --git a/arch/arm/mach-s5pv210/mach-goni.c b/arch/arm/mach-s5pv210/mach-goni.c
index c1ce921..07d5006 100644
--- a/arch/arm/mach-s5pv210/mach-goni.c
+++ b/arch/arm/mach-s5pv210/mach-goni.c
@@ -233,6 +233,32 @@ static void __init goni_radio_init(void)
}
/* TSP */
+static struct regulator_consumer_supply tsp_fixed_consumer =
+ REGULATOR_SUPPLY("vdd", "2-004a");
+
+static struct regulator_init_data tsp_fixed_voltage_init_data = {
+ .constraints = {
+ .name = "TSP_2.8V",
+ },
+ .num_consumer_supplies = 1,
+ .consumer_supplies = &tsp_fixed_consumer,
+};
+
+static struct fixed_voltage_config tsp_fixed_voltage_config = {
+ .supply_name = "TSP_VDD",
+ .microvolts = 2800000,
+ .gpio = -EINVAL,
+ .init_data = &tsp_fixed_voltage_init_data,
+};
+
+static struct platform_device tsp_fixed_voltage = {
+ .name = "reg-fixed-voltage",
+ .id = 3,
+ .dev = {
+ .platform_data = &tsp_fixed_voltage_config,
+ },
+};
+
static struct mxt_platform_data qt602240_platform_data = {
.irqflags = IRQF_TRIGGER_FALLING,
};
@@ -832,6 +858,7 @@ static struct platform_device *goni_devices[] __initdata = {
&s3c_device_usb_hsotg,
&samsung_device_keypad,
&s3c_device_i2c1,
+ &tsp_fixed_voltage,
&s3c_device_i2c2,
&wm8994_fixed_voltage0,
&wm8994_fixed_voltage1,
diff --git a/drivers/input/touchscreen/atmel_mxt_ts.c b/drivers/input/touchscreen/atmel_mxt_ts.c
index 6e0b4a2..47fbd3a 100644
--- a/drivers/input/touchscreen/atmel_mxt_ts.c
+++ b/drivers/input/touchscreen/atmel_mxt_ts.c
@@ -3,6 +3,7 @@
*
* Copyright (C) 2010 Samsung Electronics Co.Ltd
* Copyright (C) 2012 Google, Inc.
+ * Copyright (C) 2011-2014 Atmel Corporation
*
* Author: Joonyoung Shim <jy0922.shim@samsung.com>
*
@@ -14,7 +15,6 @@
*/
#include <linux/module.h>
-#include <linux/init.h>
#include <linux/completion.h>
#include <linux/delay.h>
#include <linux/firmware.h>
@@ -23,27 +23,17 @@
#include <linux/input/mt.h>
#include <linux/interrupt.h>
#include <linux/slab.h>
+#include <linux/regulator/consumer.h>
+#include <linux/gpio.h>
-/* Version */
-#define MXT_VER_20 20
-#define MXT_VER_21 21
-#define MXT_VER_22 22
-
-/* Firmware */
-#define MXT_FW_NAME "maxtouch.fw"
+/* Configuration file */
+#define MXT_CFG_MAGIC "OBP_RAW V1"
/* Registers */
-#define MXT_INFO 0x00
-#define MXT_FAMILY_ID 0x00
-#define MXT_VARIANT_ID 0x01
-#define MXT_VERSION 0x02
-#define MXT_BUILD 0x03
-#define MXT_MATRIX_X_SIZE 0x04
-#define MXT_MATRIX_Y_SIZE 0x05
-#define MXT_OBJECT_NUM 0x06
#define MXT_OBJECT_START 0x07
-
#define MXT_OBJECT_SIZE 6
+#define MXT_INFO_CHECKSUM_SIZE 3
+#define MXT_MAX_BLOCK_WRITE 256
/* Object types */
#define MXT_DEBUG_DIAGNOSTIC_T37 37
@@ -73,6 +63,11 @@
#define MXT_SPT_DIGITIZER_T43 43
#define MXT_SPT_MESSAGECOUNT_T44 44
#define MXT_SPT_CTECONFIG_T46 46
+#define MXT_PROCI_ACTIVE_STYLUS_T63 63
+#define MXT_TOUCH_MULTITOUCHSCREEN_T100 100
+
+/* MXT_GEN_MESSAGE_T5 object */
+#define MXT_RPTID_NOMSG 0xff
/* MXT_GEN_COMMAND_T6 field */
#define MXT_COMMAND_RESET 0
@@ -83,23 +78,22 @@
/* Define for T6 status byte */
#define MXT_T6_STATUS_RESET (1 << 7)
+#define MXT_T6_STATUS_OFL (1 << 6)
+#define MXT_T6_STATUS_SIGERR (1 << 5)
+#define MXT_T6_STATUS_CAL (1 << 4)
+#define MXT_T6_STATUS_CFGERR (1 << 3)
+#define MXT_T6_STATUS_COMSERR (1 << 2)
/* MXT_GEN_POWER_T7 field */
-#define MXT_POWER_IDLEACQINT 0
-#define MXT_POWER_ACTVACQINT 1
-#define MXT_POWER_ACTV2IDLETO 2
-
-/* MXT_GEN_ACQUIRE_T8 field */
-#define MXT_ACQUIRE_CHRGTIME 0
-#define MXT_ACQUIRE_TCHDRIFT 2
-#define MXT_ACQUIRE_DRIFTST 3
-#define MXT_ACQUIRE_TCHAUTOCAL 4
-#define MXT_ACQUIRE_SYNC 5
-#define MXT_ACQUIRE_ATCHCALST 6
-#define MXT_ACQUIRE_ATCHCALSTHR 7
+struct t7_config {
+ u8 idle;
+ u8 active;
+} __packed;
+
+#define MXT_POWER_CFG_RUN 0
+#define MXT_POWER_CFG_DEEPSLEEP 1
/* MXT_TOUCH_MULTI_T9 field */
-#define MXT_TOUCH_CTRL 0
#define MXT_T9_ORIENT 9
#define MXT_T9_RANGE 18
@@ -121,56 +115,52 @@ struct t9_range {
/* MXT_TOUCH_MULTI_T9 orient */
#define MXT_T9_ORIENT_SWITCH (1 << 0)
-/* MXT_PROCI_GRIPFACE_T20 field */
-#define MXT_GRIPFACE_CTRL 0
-#define MXT_GRIPFACE_XLOGRIP 1
-#define MXT_GRIPFACE_XHIGRIP 2
-#define MXT_GRIPFACE_YLOGRIP 3
-#define MXT_GRIPFACE_YHIGRIP 4
-#define MXT_GRIPFACE_MAXTCHS 5
-#define MXT_GRIPFACE_SZTHR1 7
-#define MXT_GRIPFACE_SZTHR2 8
-#define MXT_GRIPFACE_SHPTHR1 9
-#define MXT_GRIPFACE_SHPTHR2 10
-#define MXT_GRIPFACE_SUPEXTTO 11
-
-/* MXT_PROCI_NOISE field */
-#define MXT_NOISE_CTRL 0
-#define MXT_NOISE_OUTFLEN 1
-#define MXT_NOISE_GCAFUL_LSB 3
-#define MXT_NOISE_GCAFUL_MSB 4
-#define MXT_NOISE_GCAFLL_LSB 5
-#define MXT_NOISE_GCAFLL_MSB 6
-#define MXT_NOISE_ACTVGCAFVALID 7
-#define MXT_NOISE_NOISETHR 8
-#define MXT_NOISE_FREQHOPSCALE 10
-#define MXT_NOISE_FREQ0 11
-#define MXT_NOISE_FREQ1 12
-#define MXT_NOISE_FREQ2 13
-#define MXT_NOISE_FREQ3 14
-#define MXT_NOISE_FREQ4 15
-#define MXT_NOISE_IDLEGCAFVALID 16
-
/* MXT_SPT_COMMSCONFIG_T18 */
#define MXT_COMMS_CTRL 0
#define MXT_COMMS_CMD 1
-
-/* MXT_SPT_CTECONFIG_T28 field */
-#define MXT_CTE_CTRL 0
-#define MXT_CTE_CMD 1
-#define MXT_CTE_MODE 2
-#define MXT_CTE_IDLEGCAFDEPTH 3
-#define MXT_CTE_ACTVGCAFDEPTH 4
-#define MXT_CTE_VOLTAGE 5
-
-#define MXT_VOLTAGE_DEFAULT 2700000
-#define MXT_VOLTAGE_STEP 10000
+#define MXT_COMMS_RETRIGEN (1 << 6)
/* Define for MXT_GEN_COMMAND_T6 */
#define MXT_BOOT_VALUE 0xa5
#define MXT_RESET_VALUE 0x01
#define MXT_BACKUP_VALUE 0x55
+/* Define for MXT_PROCI_TOUCHSUPPRESSION_T42 */
+#define MXT_T42_MSG_TCHSUP (1 << 0)
+
+/* T47 Stylus */
+#define MXT_TOUCH_MAJOR_T47_STYLUS 1
+
+/* T63 Stylus */
+#define MXT_T63_STYLUS_PRESS (1 << 0)
+#define MXT_T63_STYLUS_RELEASE (1 << 1)
+#define MXT_T63_STYLUS_MOVE (1 << 2)
+#define MXT_T63_STYLUS_SUPPRESS (1 << 3)
+
+#define MXT_T63_STYLUS_DETECT (1 << 4)
+#define MXT_T63_STYLUS_TIP (1 << 5)
+#define MXT_T63_STYLUS_ERASER (1 << 6)
+#define MXT_T63_STYLUS_BARREL (1 << 7)
+
+#define MXT_T63_STYLUS_PRESSURE_MASK 0x3F
+
+/* T100 Multiple Touch Touchscreen */
+#define MXT_T100_CTRL 0
+#define MXT_T100_CFG1 1
+#define MXT_T100_TCHAUX 3
+#define MXT_T100_XRANGE 13
+#define MXT_T100_YRANGE 24
+
+#define MXT_T100_CFG_SWITCHXY (1 << 5)
+
+#define MXT_T100_TCHAUX_VECT (1 << 0)
+#define MXT_T100_TCHAUX_AMPL (1 << 1)
+#define MXT_T100_TCHAUX_AREA (1 << 2)
+
+#define MXT_T100_DETECT (1 << 7)
+#define MXT_T100_TYPE_MASK 0x70
+#define MXT_T100_TYPE_STYLUS 0x20
+
/* Delay times */
#define MXT_BACKUP_TIME 50 /* msec */
#define MXT_RESET_TIME 200 /* msec */
@@ -178,6 +168,10 @@ struct t9_range {
#define MXT_CRC_TIMEOUT 1000 /* msec */
#define MXT_FW_RESET_TIME 3000 /* msec */
#define MXT_FW_CHG_TIMEOUT 300 /* msec */
+#define MXT_WAKEUP_TIME 25 /* msec */
+#define MXT_REGULATOR_DELAY 150 /* msec */
+#define MXT_CHG_DELAY 100 /* msec */
+#define MXT_POWERON_DELAY 150 /* msec */
/* Command to unlock bootloader */
#define MXT_UNLOCK_CMD_MSB 0xaa
@@ -199,6 +193,8 @@ struct t9_range {
#define MXT_PIXELS_PER_MM 20
+#define DEBUG_MSG_MAX 200
+
struct mxt_info {
u8 family_id;
u8 variant_id;
@@ -217,32 +213,69 @@ struct mxt_object {
u8 num_report_ids;
} __packed;
-struct mxt_message {
- u8 reportid;
- u8 message[7];
-};
-
/* Each client has this additional data */
struct mxt_data {
struct i2c_client *client;
struct input_dev *input_dev;
char phys[64]; /* device physical location */
- const struct mxt_platform_data *pdata;
+ struct mxt_platform_data *pdata;
struct mxt_object *object_table;
- struct mxt_info info;
+ struct mxt_info *info;
+ void *raw_info_block;
unsigned int irq;
unsigned int max_x;
unsigned int max_y;
bool in_bootloader;
+ u16 mem_size;
+ u8 t100_aux_ampl;
+ u8 t100_aux_area;
+ u8 t100_aux_vect;
+ struct bin_attribute mem_access_attr;
+ bool debug_enabled;
+ bool debug_v2_enabled;
+ u8 *debug_msg_data;
+ u16 debug_msg_count;
+ struct bin_attribute debug_msg_attr;
+ struct mutex debug_msg_lock;
+ u8 max_reportid;
u32 config_crc;
+ u32 info_crc;
u8 bootloader_addr;
+ struct t7_config t7_cfg;
+ u8 *msg_buf;
+ u8 t6_status;
+ bool update_input;
+ u8 last_message_count;
+ u8 num_touchids;
+ u8 num_stylusids;
+ unsigned long t15_keystatus;
+ bool use_retrigen_workaround;
+ bool use_regulator;
+ struct regulator *reg_vdd;
+ struct regulator *reg_avdd;
+ char *fw_name;
+ char *cfg_name;
/* Cached parameters from object table */
+ u16 T5_address;
+ u8 T5_msg_size;
u8 T6_reportid;
u16 T6_address;
+ u16 T7_address;
u8 T9_reportid_min;
u8 T9_reportid_max;
+ u8 T15_reportid_min;
+ u8 T15_reportid_max;
+ u16 T18_address;
u8 T19_reportid;
+ u8 T42_reportid_min;
+ u8 T42_reportid_max;
+ u16 T44_address;
+ u8 T48_reportid;
+ u8 T63_reportid_min;
+ u8 T63_reportid_max;
+ u8 T100_reportid_min;
+ u8 T100_reportid_max;
/* for fw update in bootloader */
struct completion bl_completion;
@@ -252,6 +285,12 @@ struct mxt_data {
/* for config update handling */
struct completion crc_completion;
+
+ /* Indicates whether device is in suspend */
+ bool suspended;
+
+ /* Indicates whether device is updating configuration */
+ bool updating_config;
};
static size_t mxt_obj_size(const struct mxt_object *obj)
@@ -297,42 +336,143 @@ static bool mxt_object_readable(unsigned int type)
}
}
-static bool mxt_object_writable(unsigned int type)
+static void mxt_dump_message(struct mxt_data *data, u8 *message)
{
- switch (type) {
- case MXT_GEN_COMMAND_T6:
- case MXT_GEN_POWER_T7:
- case MXT_GEN_ACQUIRE_T8:
- case MXT_TOUCH_MULTI_T9:
- case MXT_TOUCH_KEYARRAY_T15:
- case MXT_TOUCH_PROXIMITY_T23:
- case MXT_TOUCH_PROXKEY_T52:
- case MXT_PROCI_GRIPFACE_T20:
- case MXT_PROCG_NOISE_T22:
- case MXT_PROCI_ONETOUCH_T24:
- case MXT_PROCI_TWOTOUCH_T27:
- case MXT_PROCI_GRIP_T40:
- case MXT_PROCI_PALM_T41:
- case MXT_PROCI_TOUCHSUPPRESSION_T42:
- case MXT_PROCI_STYLUS_T47:
- case MXT_PROCG_NOISESUPPRESSION_T48:
- case MXT_SPT_COMMSCONFIG_T18:
- case MXT_SPT_GPIOPWM_T19:
- case MXT_SPT_SELFTEST_T25:
- case MXT_SPT_CTECONFIG_T28:
- case MXT_SPT_DIGITIZER_T43:
- case MXT_SPT_CTECONFIG_T46:
- return true;
- default:
- return false;
+ dev_dbg(&data->client->dev, "MXT MSG: %*ph\n",
+ data->T5_msg_size, message);
+}
+
+static void mxt_debug_msg_enable(struct mxt_data *data)
+{
+ struct device *dev = &data->client->dev;
+
+ if (data->debug_v2_enabled)
+ return;
+
+ mutex_lock(&data->debug_msg_lock);
+
+ data->debug_msg_data = kcalloc(DEBUG_MSG_MAX,
+ data->T5_msg_size, GFP_KERNEL);
+ if (!data->debug_msg_data) {
+ dev_err(&data->client->dev, "Failed to allocate buffer\n");
+ return;
+ }
+
+ data->debug_v2_enabled = true;
+ mutex_unlock(&data->debug_msg_lock);
+
+ dev_info(dev, "Enabled message output\n");
+}
+
+static void mxt_debug_msg_disable(struct mxt_data *data)
+{
+ struct device *dev = &data->client->dev;
+
+ if (!data->debug_v2_enabled)
+ return;
+
+ dev_info(dev, "disabling message output\n");
+ data->debug_v2_enabled = false;
+
+ mutex_lock(&data->debug_msg_lock);
+ kfree(data->debug_msg_data);
+ data->debug_msg_data = NULL;
+ data->debug_msg_count = 0;
+ mutex_unlock(&data->debug_msg_lock);
+ dev_info(dev, "Disabled message output\n");
+}
+
+static void mxt_debug_msg_add(struct mxt_data *data, u8 *msg)
+{
+ struct device *dev = &data->client->dev;
+
+ mutex_lock(&data->debug_msg_lock);
+
+ if (!data->debug_msg_data) {
+ dev_err(dev, "No buffer!\n");
+ return;
+ }
+
+ if (data->debug_msg_count < DEBUG_MSG_MAX) {
+ memcpy(data->debug_msg_data +
+ data->debug_msg_count * data->T5_msg_size,
+ msg,
+ data->T5_msg_size);
+ data->debug_msg_count++;
+ } else {
+ dev_dbg(dev, "Discarding %u messages\n", data->debug_msg_count);
+ data->debug_msg_count = 0;
+ }
+
+ mutex_unlock(&data->debug_msg_lock);
+
+ sysfs_notify(&data->client->dev.kobj, NULL, "debug_notify");
+}
+
+static ssize_t mxt_debug_msg_write(struct file *filp, struct kobject *kobj,
+ struct bin_attribute *bin_attr, char *buf, loff_t off,
+ size_t count)
+{
+ return -EIO;
+}
+
+static ssize_t mxt_debug_msg_read(struct file *filp, struct kobject *kobj,
+ struct bin_attribute *bin_attr, char *buf, loff_t off, size_t bytes)
+{
+ struct device *dev = container_of(kobj, struct device, kobj);
+ struct mxt_data *data = dev_get_drvdata(dev);
+ int count;
+ size_t bytes_read;
+
+ if (!data->debug_msg_data) {
+ dev_err(dev, "No buffer!\n");
+ return 0;
+ }
+
+ count = bytes / data->T5_msg_size;
+
+ if (count > DEBUG_MSG_MAX)
+ count = DEBUG_MSG_MAX;
+
+ mutex_lock(&data->debug_msg_lock);
+
+ if (count > data->debug_msg_count)
+ count = data->debug_msg_count;
+
+ bytes_read = count * data->T5_msg_size;
+
+ memcpy(buf, data->debug_msg_data, bytes_read);
+ data->debug_msg_count = 0;
+
+ mutex_unlock(&data->debug_msg_lock);
+
+ return bytes_read;
+}
+
+static int mxt_debug_msg_init(struct mxt_data *data)
+{
+ sysfs_bin_attr_init(&data->debug_msg_attr);
+ data->debug_msg_attr.attr.name = "debug_msg";
+ data->debug_msg_attr.attr.mode = 0666;
+ data->debug_msg_attr.read = mxt_debug_msg_read;
+ data->debug_msg_attr.write = mxt_debug_msg_write;
+ data->debug_msg_attr.size = data->T5_msg_size * DEBUG_MSG_MAX;
+
+ if (sysfs_create_bin_file(&data->client->dev.kobj,
+ &data->debug_msg_attr) < 0) {
+ dev_err(&data->client->dev, "Failed to create %s\n",
+ data->debug_msg_attr.attr.name);
+ return -EINVAL;
}
+
+ return 0;
}
-static void mxt_dump_message(struct device *dev,
- struct mxt_message *message)
+static void mxt_debug_msg_remove(struct mxt_data *data)
{
- dev_dbg(dev, "reportid: %u\tmessage: %*ph\n",
- message->reportid, 7, message->message);
+ if (data->debug_msg_attr.attr.name)
+ sysfs_remove_bin_file(&data->client->dev.kobj,
+ &data->debug_msg_attr);
}
static int mxt_wait_for_completion(struct mxt_data *data,
@@ -401,14 +541,21 @@ static int mxt_bootloader_write(struct mxt_data *data,
return ret;
}
-static int mxt_lookup_bootloader_address(struct mxt_data *data)
+static int mxt_lookup_bootloader_address(struct mxt_data *data, bool retry)
{
u8 appmode = data->client->addr;
u8 bootloader;
+ u8 family_id = data->info ? data->info->family_id : 0;
switch (appmode) {
case 0x4a:
case 0x4b:
+ /* Chips after 1664S use different scheme */
+ if (retry || family_id >= 0xa2) {
+ bootloader = appmode - 0x24;
+ break;
+ }
+ /* Fall through for normal case */
case 0x4c:
case 0x4d:
case 0x5a:
@@ -426,6 +573,30 @@ static int mxt_lookup_bootloader_address(struct mxt_data *data)
return 0;
}
+static int mxt_probe_bootloader(struct mxt_data *data, bool retry)
+{
+ struct device *dev = &data->client->dev;
+ int ret;
+ u8 val;
+ bool crc_failure;
+
+ ret = mxt_lookup_bootloader_address(data, retry);
+ if (ret)
+ return ret;
+
+ ret = mxt_bootloader_read(data, &val, 1);
+ if (ret)
+ return ret;
+
+ /* Check app crc fail mode */
+ crc_failure = (val & ~MXT_BOOT_STATUS_MASK) == MXT_APP_CRC_FAIL;
+
+ dev_err(dev, "Detected bootloader, status:%02X%s\n",
+ val, crc_failure ? ", APP_CRC_FAIL" : "");
+
+ return 0;
+}
+
static u8 mxt_get_bootloader_version(struct mxt_data *data, u8 val)
{
struct device *dev = &data->client->dev;
@@ -434,7 +605,7 @@ static u8 mxt_get_bootloader_version(struct mxt_data *data, u8 val)
if (val & MXT_BOOT_EXTENDED_ID) {
if (mxt_bootloader_read(data, &buf[0], 3) != 0) {
dev_err(dev, "%s: i2c failure\n", __func__);
- return val;
+ return -EIO;
}
dev_dbg(dev, "Bootloader ID:%d Version:%d\n", buf[1], buf[2]);
@@ -447,14 +618,15 @@ static u8 mxt_get_bootloader_version(struct mxt_data *data, u8 val)
}
}
-static int mxt_check_bootloader(struct mxt_data *data, unsigned int state)
+static int mxt_check_bootloader(struct mxt_data *data, unsigned int state,
+ bool wait)
{
struct device *dev = &data->client->dev;
u8 val;
int ret;
recheck:
- if (state != MXT_WAITING_BOOTLOAD_CMD) {
+ if (wait) {
/*
* In application update mode, the interrupt
* line signals state transitions. We must wait for the
@@ -485,6 +657,7 @@ recheck:
switch (state) {
case MXT_WAITING_BOOTLOAD_CMD:
case MXT_WAITING_FRAME_DATA:
+ case MXT_APP_CRC_FAIL:
val &= ~MXT_BOOT_STATUS_MASK;
break;
case MXT_FRAME_CRC_PASS:
@@ -508,13 +681,18 @@ recheck:
return 0;
}
-static int mxt_unlock_bootloader(struct mxt_data *data)
+static int mxt_send_bootloader_cmd(struct mxt_data *data, bool unlock)
{
int ret;
u8 buf[2];
- buf[0] = MXT_UNLOCK_CMD_LSB;
- buf[1] = MXT_UNLOCK_CMD_MSB;
+ if (unlock) {
+ buf[0] = MXT_UNLOCK_CMD_LSB;
+ buf[1] = MXT_UNLOCK_CMD_MSB;
+ } else {
+ buf[0] = 0x01;
+ buf[1] = 0x01;
+ }
ret = mxt_bootloader_write(data, buf, 2);
if (ret)
@@ -529,6 +707,7 @@ static int __mxt_read_reg(struct i2c_client *client,
struct i2c_msg xfer[2];
u8 buf[2];
int ret;
+ bool retry = false;
buf[0] = reg & 0xff;
buf[1] = (reg >> 8) & 0xff;
@@ -545,17 +724,22 @@ static int __mxt_read_reg(struct i2c_client *client,
xfer[1].len = len;
xfer[1].buf = val;
- ret = i2c_transfer(client->adapter, xfer, 2);
- if (ret == 2) {
- ret = 0;
- } else {
- if (ret >= 0)
- ret = -EIO;
- dev_err(&client->dev, "%s: i2c transfer failed (%d)\n",
- __func__, ret);
+retry_read:
+ ret = i2c_transfer(client->adapter, xfer, ARRAY_SIZE(xfer));
+ if (ret != ARRAY_SIZE(xfer)) {
+ if (!retry) {
+ dev_dbg(&client->dev, "%s: i2c retry\n", __func__);
+ msleep(MXT_WAKEUP_TIME);
+ retry = true;
+ goto retry_read;
+ } else {
+ dev_err(&client->dev, "%s: i2c transfer failed (%d)\n",
+ __func__, ret);
+ return -EIO;
+ }
}
- return ret;
+ return 0;
}
static int __mxt_write_reg(struct i2c_client *client, u16 reg, u16 len,
@@ -564,6 +748,7 @@ static int __mxt_write_reg(struct i2c_client *client, u16 reg, u16 len,
u8 *buf;
size_t count;
int ret;
+ bool retry = false;
count = len + 2;
buf = kmalloc(count, GFP_KERNEL);
@@ -574,14 +759,21 @@ static int __mxt_write_reg(struct i2c_client *client, u16 reg, u16 len,
buf[1] = (reg >> 8) & 0xff;
memcpy(&buf[2], val, len);
+retry_write:
ret = i2c_master_send(client, buf, count);
- if (ret == count) {
- ret = 0;
- } else {
- if (ret >= 0)
+ if (ret != count) {
+ if (!retry) {
+ dev_dbg(&client->dev, "%s: i2c retry\n", __func__);
+ msleep(MXT_WAKEUP_TIME);
+ retry = true;
+ goto retry_write;
+ } else {
+ dev_err(&client->dev, "%s: i2c send failed (%d)\n",
+ __func__, ret);
ret = -EIO;
- dev_err(&client->dev, "%s: i2c send failed (%d)\n",
- __func__, ret);
+ }
+ } else {
+ ret = 0;
}
kfree(buf);
@@ -599,46 +791,50 @@ mxt_get_object(struct mxt_data *data, u8 type)
struct mxt_object *object;
int i;
- for (i = 0; i < data->info.object_num; i++) {
+ for (i = 0; i < data->info->object_num; i++) {
object = data->object_table + i;
if (object->type == type)
return object;
}
- dev_err(&data->client->dev, "Invalid object type T%u\n", type);
+ dev_warn(&data->client->dev, "Invalid object type T%u\n", type);
return NULL;
}
-static int mxt_read_message(struct mxt_data *data,
- struct mxt_message *message)
+static void mxt_proc_t6_messages(struct mxt_data *data, u8 *msg)
{
- struct mxt_object *object;
- u16 reg;
-
- object = mxt_get_object(data, MXT_GEN_MESSAGE_T5);
- if (!object)
- return -EINVAL;
-
- reg = object->start_address;
- return __mxt_read_reg(data->client, reg,
- sizeof(struct mxt_message), message);
-}
+ struct device *dev = &data->client->dev;
+ u8 status = msg[1];
+ u32 crc = msg[2] | (msg[3] << 8) | (msg[4] << 16);
-static int mxt_write_object(struct mxt_data *data,
- u8 type, u8 offset, u8 val)
-{
- struct mxt_object *object;
- u16 reg;
+ complete(&data->crc_completion);
- object = mxt_get_object(data, type);
- if (!object || offset >= mxt_obj_size(object))
- return -EINVAL;
+ if (crc != data->config_crc) {
+ data->config_crc = crc;
+ dev_dbg(dev, "T6 Config Checksum: 0x%06X\n", crc);
+ }
- reg = object->start_address;
- return mxt_write_reg(data->client, reg + offset, val);
+ /* Detect reset */
+ if (status & MXT_T6_STATUS_RESET)
+ complete(&data->reset_completion);
+
+ /* Output debug if status has changed */
+ if (status != data->t6_status)
+ dev_dbg(dev, "T6 Status 0x%02X%s%s%s%s%s%s%s\n",
+ status,
+ status == 0 ? " OK" : "",
+ status & MXT_T6_STATUS_RESET ? " RESET" : "",
+ status & MXT_T6_STATUS_OFL ? " OFL" : "",
+ status & MXT_T6_STATUS_SIGERR ? " SIGERR" : "",
+ status & MXT_T6_STATUS_CAL ? " CAL" : "",
+ status & MXT_T6_STATUS_CFGERR ? " CFGERR" : "",
+ status & MXT_T6_STATUS_COMSERR ? " COMSERR" : "");
+
+ /* Save current status */
+ data->t6_status = status;
}
-static void mxt_input_button(struct mxt_data *data, struct mxt_message *message)
+static void mxt_input_button(struct mxt_data *data, u8 *message)
{
struct input_dev *input = data->input_dev;
const struct mxt_platform_data *pdata = data->pdata;
@@ -649,7 +845,7 @@ static void mxt_input_button(struct mxt_data *data, struct mxt_message *message)
for (i = 0; i < pdata->t19_num_keys; i++) {
if (pdata->t19_keymap[i] == KEY_RESERVED)
continue;
- button = !(message->message[0] & (1 << i));
+ button = !(message[1] & (1 << i));
input_report_key(input, pdata->t19_keymap[i], button);
}
}
@@ -660,19 +856,23 @@ static void mxt_input_sync(struct input_dev *input_dev)
input_sync(input_dev);
}
-static void mxt_input_touchevent(struct mxt_data *data,
- struct mxt_message *message, int id)
+static void mxt_proc_t9_message(struct mxt_data *data, u8 *message)
{
struct device *dev = &data->client->dev;
- u8 status = message->message[0];
struct input_dev *input_dev = data->input_dev;
+ int id;
+ u8 status;
int x;
int y;
int area;
int amplitude;
+ u8 vector;
+ int tool;
- x = (message->message[1] << 4) | ((message->message[3] >> 4) & 0xf);
- y = (message->message[2] << 4) | ((message->message[3] & 0xf));
+ id = message[0] - data->T9_reportid_min;
+ status = message[1];
+ x = (message[2] << 4) | ((message[4] >> 4) & 0xf);
+ y = (message[3] << 4) | ((message[4] & 0xf));
/* Handle 10/12 bit switching */
if (data->max_x < 1024)
@@ -680,11 +880,13 @@ static void mxt_input_touchevent(struct mxt_data *data,
if (data->max_y < 1024)
y >>= 2;
- area = message->message[4];
- amplitude = message->message[5];
+ area = message[5];
+
+ amplitude = message[6];
+ vector = message[7];
dev_dbg(dev,
- "[%u] %c%c%c%c%c%c%c%c x: %5u y: %5u area: %3u amp: %3u\n",
+ "[%u] %c%c%c%c%c%c%c%c x: %5u y: %5u area: %3u amp: %3u vector: %02X\n",
id,
(status & MXT_T9_DETECT) ? 'D' : '.',
(status & MXT_T9_PRESS) ? 'P' : '.',
@@ -694,7 +896,7 @@ static void mxt_input_touchevent(struct mxt_data *data,
(status & MXT_T9_AMP) ? 'A' : '.',
(status & MXT_T9_SUPPRESS) ? 'S' : '.',
(status & MXT_T9_UNGRIP) ? 'U' : '.',
- x, y, area, amplitude);
+ x, y, area, amplitude, vector);
input_mt_slot(input_dev, id);
@@ -710,316 +912,1161 @@ static void mxt_input_touchevent(struct mxt_data *data,
mxt_input_sync(input_dev);
}
+ /* A size of zero indicates touch is from a linked T47 Stylus */
+ if (area == 0) {
+ area = MXT_TOUCH_MAJOR_T47_STYLUS;
+ tool = MT_TOOL_PEN;
+ } else {
+ tool = MT_TOOL_FINGER;
+ }
+
/* Touch active */
- input_mt_report_slot_state(input_dev, MT_TOOL_FINGER, 1);
+ input_mt_report_slot_state(input_dev, tool, 1);
input_report_abs(input_dev, ABS_MT_POSITION_X, x);
input_report_abs(input_dev, ABS_MT_POSITION_Y, y);
input_report_abs(input_dev, ABS_MT_PRESSURE, amplitude);
input_report_abs(input_dev, ABS_MT_TOUCH_MAJOR, area);
+ input_report_abs(input_dev, ABS_MT_ORIENTATION, vector);
} else {
/* Touch no longer active, close out slot */
input_mt_report_slot_state(input_dev, MT_TOOL_FINGER, 0);
}
-}
-static u16 mxt_extract_T6_csum(const u8 *csum)
-{
- return csum[0] | (csum[1] << 8) | (csum[2] << 16);
-}
-
-static bool mxt_is_T9_message(struct mxt_data *data, struct mxt_message *msg)
-{
- u8 id = msg->reportid;
- return (id >= data->T9_reportid_min && id <= data->T9_reportid_max);
+ data->update_input = true;
}
-static irqreturn_t mxt_process_messages_until_invalid(struct mxt_data *data)
+static void mxt_proc_t100_message(struct mxt_data *data, u8 *message)
{
- struct mxt_message message;
- const u8 *payload = &message.message[0];
struct device *dev = &data->client->dev;
- u8 reportid;
- bool update_input = false;
- u32 crc;
+ struct input_dev *input_dev = data->input_dev;
+ int id;
+ u8 status;
+ int x;
+ int y;
+ int tool;
- do {
- if (mxt_read_message(data, &message)) {
- dev_err(dev, "Failed to read message\n");
- return IRQ_NONE;
- }
+ id = message[0] - data->T100_reportid_min - 2;
- reportid = message.reportid;
+ /* ignore SCRSTATUS events */
+ if (id < 0)
+ return;
- if (reportid == data->T6_reportid) {
- u8 status = payload[0];
+ status = message[1];
+ x = (message[3] << 8) | message[2];
+ y = (message[5] << 8) | message[4];
- crc = mxt_extract_T6_csum(&payload[1]);
- if (crc != data->config_crc) {
- data->config_crc = crc;
- complete(&data->crc_completion);
- }
+ dev_dbg(dev,
+ "[%u] status:%02X x:%u y:%u area:%02X amp:%02X vec:%02X\n",
+ id,
+ status,
+ x, y,
+ data->t100_aux_area ? message[data->t100_aux_area] : 0,
+ data->t100_aux_ampl ? message[data->t100_aux_ampl] : 0,
+ data->t100_aux_vect ? message[data->t100_aux_vect] : 0);
- dev_dbg(dev, "Status: %02x Config Checksum: %06x\n",
- status, data->config_crc);
-
- if (status & MXT_T6_STATUS_RESET)
- complete(&data->reset_completion);
- } else if (mxt_is_T9_message(data, &message)) {
- int id = reportid - data->T9_reportid_min;
- mxt_input_touchevent(data, &message, id);
- update_input = true;
- } else if (message.reportid == data->T19_reportid) {
- mxt_input_button(data, &message);
- update_input = true;
- } else {
- mxt_dump_message(dev, &message);
+ input_mt_slot(input_dev, id);
+
+ if (status & MXT_T100_DETECT) {
+ if ((status & MXT_T100_TYPE_MASK) == MXT_T100_TYPE_STYLUS)
+ tool = MT_TOOL_PEN;
+ else
+ tool = MT_TOOL_FINGER;
+
+ /* Touch active */
+ input_mt_report_slot_state(input_dev, tool, 1);
+ input_report_abs(input_dev, ABS_MT_POSITION_X, x);
+ input_report_abs(input_dev, ABS_MT_POSITION_Y, y);
+
+ if (data->t100_aux_ampl)
+ input_report_abs(input_dev, ABS_MT_PRESSURE,
+ message[data->t100_aux_ampl]);
+
+ if (data->t100_aux_area) {
+ if (tool == MT_TOOL_PEN)
+ input_report_abs(input_dev, ABS_MT_TOUCH_MAJOR,
+ MXT_TOUCH_MAJOR_T47_STYLUS);
+ else
+ input_report_abs(input_dev, ABS_MT_TOUCH_MAJOR,
+ message[data->t100_aux_area]);
}
- } while (reportid != 0xff);
- if (update_input)
- mxt_input_sync(data->input_dev);
+ if (data->t100_aux_vect)
+ input_report_abs(input_dev, ABS_MT_ORIENTATION,
+ message[data->t100_aux_vect]);
+ } else {
+ /* Touch no longer active, close out slot */
+ input_mt_report_slot_state(input_dev, MT_TOOL_FINGER, 0);
+ }
- return IRQ_HANDLED;
+ data->update_input = true;
}
-static irqreturn_t mxt_interrupt(int irq, void *dev_id)
-{
- struct mxt_data *data = dev_id;
- if (data->in_bootloader) {
- /* bootloader state transition completion */
- complete(&data->bl_completion);
- return IRQ_HANDLED;
+static void mxt_proc_t15_messages(struct mxt_data *data, u8 *msg)
+{
+ struct input_dev *input_dev = data->input_dev;
+ struct device *dev = &data->client->dev;
+ int key;
+ bool curr_state, new_state;
+ bool sync = false;
+ unsigned long keystates = le32_to_cpu(msg[2]);
+
+ for (key = 0; key < data->pdata->t15_num_keys; key++) {
+ curr_state = test_bit(key, &data->t15_keystatus);
+ new_state = test_bit(key, &keystates);
+
+ if (!curr_state && new_state) {
+ dev_dbg(dev, "T15 key press: %u\n", key);
+ __set_bit(key, &data->t15_keystatus);
+ input_event(input_dev, EV_KEY,
+ data->pdata->t15_keymap[key], 1);
+ sync = true;
+ } else if (curr_state && !new_state) {
+ dev_dbg(dev, "T15 key release: %u\n", key);
+ __clear_bit(key, &data->t15_keystatus);
+ input_event(input_dev, EV_KEY,
+ data->pdata->t15_keymap[key], 0);
+ sync = true;
+ }
}
- return mxt_process_messages_until_invalid(data);
+ if (sync)
+ input_sync(input_dev);
}
-static int mxt_t6_command(struct mxt_data *data, u16 cmd_offset,
- u8 value, bool wait)
+static void mxt_proc_t42_messages(struct mxt_data *data, u8 *msg)
{
- u16 reg;
- u8 command_register;
- int timeout_counter = 0;
- int ret;
-
- reg = data->T6_address + cmd_offset;
+ struct device *dev = &data->client->dev;
+ u8 status = msg[1];
- ret = mxt_write_reg(data->client, reg, value);
- if (ret)
- return ret;
+ if (status & MXT_T42_MSG_TCHSUP)
+ dev_info(dev, "T42 suppress\n");
+ else
+ dev_info(dev, "T42 normal\n");
+}
- if (!wait)
- return 0;
+static int mxt_proc_t48_messages(struct mxt_data *data, u8 *msg)
+{
+ struct device *dev = &data->client->dev;
+ u8 status, state;
- do {
- msleep(20);
- ret = __mxt_read_reg(data->client, reg, 1, &command_register);
- if (ret)
- return ret;
- } while (command_register != 0 && timeout_counter++ <= 100);
+ status = msg[1];
+ state = msg[4];
- if (timeout_counter > 100) {
- dev_err(&data->client->dev, "Command failed!\n");
- return -EIO;
- }
+ dev_dbg(dev, "T48 state %d status %02X %s%s%s%s%s\n", state, status,
+ status & 0x01 ? "FREQCHG " : "",
+ status & 0x02 ? "APXCHG " : "",
+ status & 0x04 ? "ALGOERR " : "",
+ status & 0x10 ? "STATCHG " : "",
+ status & 0x20 ? "NLVLCHG " : "");
return 0;
}
-static int mxt_soft_reset(struct mxt_data *data)
+static void mxt_proc_t63_messages(struct mxt_data *data, u8 *msg)
{
struct device *dev = &data->client->dev;
- int ret = 0;
+ struct input_dev *input_dev = data->input_dev;
+ u8 id;
+ u16 x, y;
+ u8 pressure;
- dev_info(dev, "Resetting chip\n");
+ /* stylus slots come after touch slots */
+ id = data->num_touchids + (msg[0] - data->T63_reportid_min);
- reinit_completion(&data->reset_completion);
+ if (id < 0 || id > (data->num_touchids + data->num_stylusids)) {
+ dev_err(dev, "invalid stylus id %d, max slot is %d\n",
+ id, data->num_stylusids);
+ return;
+ }
- ret = mxt_t6_command(data, MXT_COMMAND_RESET, MXT_RESET_VALUE, false);
- if (ret)
- return ret;
+ x = msg[3] | (msg[4] << 8);
+ y = msg[5] | (msg[6] << 8);
+ pressure = msg[7] & MXT_T63_STYLUS_PRESSURE_MASK;
- ret = mxt_wait_for_completion(data, &data->reset_completion,
- MXT_RESET_TIMEOUT);
- if (ret)
- return ret;
+ dev_dbg(dev,
+ "[%d] %c%c%c%c x: %d y: %d pressure: %d stylus:%c%c%c%c\n",
+ id,
+ msg[1] & MXT_T63_STYLUS_SUPPRESS ? 'S' : '.',
+ msg[1] & MXT_T63_STYLUS_MOVE ? 'M' : '.',
+ msg[1] & MXT_T63_STYLUS_RELEASE ? 'R' : '.',
+ msg[1] & MXT_T63_STYLUS_PRESS ? 'P' : '.',
+ x, y, pressure,
+ msg[2] & MXT_T63_STYLUS_BARREL ? 'B' : '.',
+ msg[2] & MXT_T63_STYLUS_ERASER ? 'E' : '.',
+ msg[2] & MXT_T63_STYLUS_TIP ? 'T' : '.',
+ msg[2] & MXT_T63_STYLUS_DETECT ? 'D' : '.');
- return 0;
+ input_mt_slot(input_dev, id);
+
+ if (msg[2] & MXT_T63_STYLUS_DETECT) {
+ input_mt_report_slot_state(input_dev, MT_TOOL_PEN, 1);
+ input_report_abs(input_dev, ABS_MT_POSITION_X, x);
+ input_report_abs(input_dev, ABS_MT_POSITION_Y, y);
+ input_report_abs(input_dev, ABS_MT_PRESSURE, pressure);
+ } else {
+ input_mt_report_slot_state(input_dev, MT_TOOL_PEN, 0);
+ }
+
+ input_report_key(input_dev, BTN_STYLUS,
+ (msg[2] & MXT_T63_STYLUS_ERASER));
+ input_report_key(input_dev, BTN_STYLUS2,
+ (msg[2] & MXT_T63_STYLUS_BARREL));
+
+ mxt_input_sync(input_dev);
}
-static void mxt_update_crc(struct mxt_data *data, u8 cmd, u8 value)
+static int mxt_proc_message(struct mxt_data *data, u8 *message)
{
- /*
- * On failure, CRC is set to 0 and config will always be
- * downloaded.
- */
- data->config_crc = 0;
- reinit_completion(&data->crc_completion);
+ u8 report_id = message[0];
+ bool dump = data->debug_enabled;
- mxt_t6_command(data, cmd, value, true);
+ if (report_id == MXT_RPTID_NOMSG)
+ return 0;
- /*
+ if (report_id == data->T6_reportid) {
+ mxt_proc_t6_messages(data, message);
+ } else if (report_id >= data->T42_reportid_min
+ && report_id <= data->T42_reportid_max) {
+ mxt_proc_t42_messages(data, message);
+ } else if (report_id == data->T48_reportid) {
+ mxt_proc_t48_messages(data, message);
+ } else if (!data->input_dev || data->suspended) {
+ /*
+ * do not report events if input device is not
+ * yet registered or returning from suspend
+ */
+ mxt_dump_message(data, message);
+ } else if (report_id >= data->T9_reportid_min
+ && report_id <= data->T9_reportid_max) {
+ mxt_proc_t9_message(data, message);
+ } else if (report_id >= data->T100_reportid_min
+ && report_id <= data->T100_reportid_max) {
+ mxt_proc_t100_message(data, message);
+ } else if (report_id == data->T19_reportid) {
+ mxt_input_button(data, message);
+ data->update_input = true;
+ } else if (report_id >= data->T63_reportid_min
+ && report_id <= data->T63_reportid_max) {
+ mxt_proc_t63_messages(data, message);
+ } else if (report_id >= data->T15_reportid_min
+ && report_id <= data->T15_reportid_max) {
+ mxt_proc_t15_messages(data, message);
+ } else {
+ dump = true;
+ }
+
+ if (dump)
+ mxt_dump_message(data, message);
+
+ if (data->debug_v2_enabled)
+ mxt_debug_msg_add(data, message);
+
+ return 1;
+}
+
+static int mxt_read_and_process_messages(struct mxt_data *data, u8 count)
+{
+ struct device *dev = &data->client->dev;
+ int ret;
+ int i;
+ u8 num_valid = 0;
+
+ /* Safety check for msg_buf */
+ if (count > data->max_reportid)
+ return -EINVAL;
+
+ /* Process remaining messages if necessary */
+ ret = __mxt_read_reg(data->client, data->T5_address,
+ data->T5_msg_size * count, data->msg_buf);
+ if (ret) {
+ dev_err(dev, "Failed to read %u messages (%d)\n", count, ret);
+ return ret;
+ }
+
+ for (i = 0; i < count; i++) {
+ ret = mxt_proc_message(data,
+ data->msg_buf + data->T5_msg_size * i);
+
+ if (ret == 1)
+ num_valid++;
+ }
+
+ /* return number of messages read */
+ return num_valid;
+}
+
+static irqreturn_t mxt_process_messages_t44(struct mxt_data *data)
+{
+ struct device *dev = &data->client->dev;
+ int ret;
+ u8 count, num_left;
+
+ /* Read T44 and T5 together */
+ ret = __mxt_read_reg(data->client, data->T44_address,
+ data->T5_msg_size + 1, data->msg_buf);
+ if (ret) {
+ dev_err(dev, "Failed to read T44 and T5 (%d)\n", ret);
+ return IRQ_NONE;
+ }
+
+ count = data->msg_buf[0];
+
+ if (count == 0) {
+ dev_warn(dev, "Interrupt triggered but zero messages\n");
+ return IRQ_NONE;
+ } else if (count > data->max_reportid) {
+ dev_err(dev, "T44 count %d exceeded max report id\n", count);
+ count = data->max_reportid;
+ }
+
+ /* Process first message */
+ ret = mxt_proc_message(data, data->msg_buf + 1);
+ if (ret < 0) {
+ dev_warn(dev, "Unexpected invalid message\n");
+ return IRQ_NONE;
+ }
+
+ num_left = count - 1;
+
+ /* Process remaining messages if necessary */
+ if (num_left) {
+ ret = mxt_read_and_process_messages(data, num_left);
+ if (ret < 0)
+ goto end;
+ else if (ret != num_left)
+ dev_warn(dev, "Unexpected invalid message\n");
+ }
+
+end:
+ if (data->update_input) {
+ mxt_input_sync(data->input_dev);
+ data->update_input = false;
+ }
+
+ return IRQ_HANDLED;
+}
+
+static int mxt_process_messages_until_invalid(struct mxt_data *data)
+{
+ struct device *dev = &data->client->dev;
+ int count, read;
+ u8 tries = 2;
+
+ count = data->max_reportid;
+
+ /* Read messages until we force an invalid */
+ do {
+ read = mxt_read_and_process_messages(data, count);
+ if (read < count)
+ return 0;
+ } while (--tries);
+
+ if (data->update_input) {
+ mxt_input_sync(data->input_dev);
+ data->update_input = false;
+ }
+
+ dev_err(dev, "CHG pin isn't cleared\n");
+ return -EBUSY;
+}
+
+static irqreturn_t mxt_process_messages(struct mxt_data *data)
+{
+ int total_handled, num_handled;
+ u8 count = data->last_message_count;
+
+ if (count < 1 || count > data->max_reportid)
+ count = 1;
+
+ /* include final invalid message */
+ total_handled = mxt_read_and_process_messages(data, count + 1);
+ if (total_handled < 0)
+ return IRQ_NONE;
+ /* if there were invalid messages, then we are done */
+ else if (total_handled <= count)
+ goto update_count;
+
+ /* keep reading two msgs until one is invalid or reportid limit */
+ do {
+ num_handled = mxt_read_and_process_messages(data, 2);
+ if (num_handled < 0)
+ return IRQ_NONE;
+
+ total_handled += num_handled;
+
+ if (num_handled < 2)
+ break;
+ } while (total_handled < data->num_touchids);
+
+update_count:
+ data->last_message_count = total_handled;
+
+ if (data->update_input) {
+ mxt_input_sync(data->input_dev);
+ data->update_input = false;
+ }
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t mxt_interrupt(int irq, void *dev_id)
+{
+ struct mxt_data *data = dev_id;
+
+ if (data->in_bootloader) {
+ /* bootloader state transition completion */
+ complete(&data->bl_completion);
+ return IRQ_HANDLED;
+ }
+
+ if (!data->object_table)
+ return IRQ_HANDLED;
+
+ if (data->T44_address)
+ return mxt_process_messages_t44(data);
+ else
+ return mxt_process_messages(data);
+}
+
+static int mxt_t6_command(struct mxt_data *data, u16 cmd_offset,
+ u8 value, bool wait)
+{
+ u16 reg;
+ u8 command_register;
+ int timeout_counter = 0;
+ int ret;
+
+ reg = data->T6_address + cmd_offset;
+
+ ret = mxt_write_reg(data->client, reg, value);
+ if (ret)
+ return ret;
+
+ if (!wait)
+ return 0;
+
+ do {
+ msleep(20);
+ ret = __mxt_read_reg(data->client, reg, 1, &command_register);
+ if (ret)
+ return ret;
+ } while (command_register != 0 && timeout_counter++ <= 100);
+
+ if (timeout_counter > 100) {
+ dev_err(&data->client->dev, "Command failed!\n");
+ return -EIO;
+ }
+
+ return 0;
+}
+
+static int mxt_soft_reset(struct mxt_data *data)
+{
+ struct device *dev = &data->client->dev;
+ int ret = 0;
+
+ dev_info(dev, "Resetting chip\n");
+
+ reinit_completion(&data->reset_completion);
+
+ ret = mxt_t6_command(data, MXT_COMMAND_RESET, MXT_RESET_VALUE, false);
+ if (ret)
+ return ret;
+
+ ret = mxt_wait_for_completion(data, &data->reset_completion,
+ MXT_RESET_TIMEOUT);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static void mxt_update_crc(struct mxt_data *data, u8 cmd, u8 value)
+{
+ /*
+ * On failure, CRC is set to 0 and config will always be
+ * downloaded.
+ */
+ data->config_crc = 0;
+ reinit_completion(&data->crc_completion);
+
+ mxt_t6_command(data, cmd, value, true);
+
+ /*
* Wait for crc message. On failure, CRC is set to 0 and config will
* always be downloaded.
*/
mxt_wait_for_completion(data, &data->crc_completion, MXT_CRC_TIMEOUT);
}
+static void mxt_calc_crc24(u32 *crc, u8 firstbyte, u8 secondbyte)
+{
+ static const unsigned int crcpoly = 0x80001B;
+ u32 result;
+ u32 data_word;
+
+ data_word = (secondbyte << 8) | firstbyte;
+ result = ((*crc << 1) ^ data_word);
+
+ if (result & 0x1000000)
+ result ^= crcpoly;
+
+ *crc = result;
+}
+
+static u32 mxt_calculate_crc(u8 *base, off_t start_off, off_t end_off)
+{
+ u32 crc = 0;
+ u8 *ptr = base + start_off;
+ u8 *last_val = base + end_off - 1;
+
+ if (end_off < start_off)
+ return -EINVAL;
+
+ while (ptr < last_val) {
+ mxt_calc_crc24(&crc, *ptr, *(ptr + 1));
+ ptr += 2;
+ }
+
+ /* if len is odd, fill the last byte with 0 */
+ if (ptr == last_val)
+ mxt_calc_crc24(&crc, *ptr, 0);
+
+ /* Mask to 24-bit */
+ crc &= 0x00FFFFFF;
+
+ return crc;
+}
+
+static int mxt_check_retrigen(struct mxt_data *data)
+{
+ struct i2c_client *client = data->client;
+ int error;
+ int val;
+
+ if (data->pdata->irqflags & IRQF_TRIGGER_LOW)
+ return 0;
+
+ if (data->T18_address) {
+ error = __mxt_read_reg(client,
+ data->T18_address + MXT_COMMS_CTRL,
+ 1, &val);
+ if (error)
+ return error;
+
+ if (val & MXT_COMMS_RETRIGEN)
+ return 0;
+ }
+
+ dev_warn(&client->dev, "Enabling RETRIGEN workaround\n");
+ data->use_retrigen_workaround = true;
+ return 0;
+}
+
+static int mxt_init_t7_power_cfg(struct mxt_data *data);
+
+/*
+ * mxt_check_reg_init - download configuration to chip
+ *
+ * Atmel Raw Config File Format
+ *
+ * The first four lines of the raw config file contain:
+ * 1) Version
+ * 2) Chip ID Information (first 7 bytes of device memory)
+ * 3) Chip Information Block 24-bit CRC Checksum
+ * 4) Chip Configuration 24-bit CRC Checksum
+ *
+ * The rest of the file consists of one line per object instance:
+ * <TYPE> <INSTANCE> <SIZE> <CONTENTS>
+ *
+ * <TYPE> - 2-byte object type as hex
+ * <INSTANCE> - 2-byte object instance number as hex
+ * <SIZE> - 2-byte object size as hex
+ * <CONTENTS> - array of <SIZE> 1-byte hex values
+ */
static int mxt_check_reg_init(struct mxt_data *data)
{
- const struct mxt_platform_data *pdata = data->pdata;
- struct mxt_object *object;
struct device *dev = &data->client->dev;
- int index = 0;
- int i, size;
+ struct mxt_info cfg_info;
+ struct mxt_object *object;
+ const struct firmware *cfg = NULL;
int ret;
+ int offset;
+ int data_pos;
+ int byte_offset;
+ int i;
+ int cfg_start_ofs;
+ u32 info_crc, config_crc, calculated_crc;
+ u8 *config_mem;
+ size_t config_mem_size;
+ unsigned int type, instance, size;
+ u8 val;
+ u16 reg;
+
+ if (!data->cfg_name) {
+ dev_dbg(dev, "Skipping cfg download\n");
+ return 0;
+ }
- if (!pdata->config) {
- dev_dbg(dev, "No cfg data defined, skipping reg init\n");
+ ret = request_firmware(&cfg, data->cfg_name, dev);
+ if (ret < 0) {
+ dev_err(dev, "Failure to request config file %s\n",
+ data->cfg_name);
return 0;
}
mxt_update_crc(data, MXT_COMMAND_REPORTALL, 1);
- if (data->config_crc == pdata->config_crc) {
- dev_info(dev, "Config CRC 0x%06X: OK\n", data->config_crc);
- return 0;
+ if (strncmp(cfg->data, MXT_CFG_MAGIC, strlen(MXT_CFG_MAGIC))) {
+ dev_err(dev, "Unrecognised config file\n");
+ ret = -EINVAL;
+ goto release;
}
- dev_info(dev, "Config CRC 0x%06X: does not match 0x%06X\n",
- data->config_crc, pdata->config_crc);
+ data_pos = strlen(MXT_CFG_MAGIC);
+
+ /* Load information block and check */
+ for (i = 0; i < sizeof(struct mxt_info); i++) {
+ ret = sscanf(cfg->data + data_pos, "%hhx%n",
+ (unsigned char *)&cfg_info + i,
+ &offset);
+ if (ret != 1) {
+ dev_err(dev, "Bad format\n");
+ ret = -EINVAL;
+ goto release;
+ }
+
+ data_pos += offset;
+ }
- for (i = 0; i < data->info.object_num; i++) {
- object = data->object_table + i;
+ if (cfg_info.family_id != data->info->family_id) {
+ dev_err(dev, "Family ID mismatch!\n");
+ ret = -EINVAL;
+ goto release;
+ }
+
+ if (cfg_info.variant_id != data->info->variant_id) {
+ dev_err(dev, "Variant ID mismatch!\n");
+ ret = -EINVAL;
+ goto release;
+ }
+
+ /* Read CRCs */
+ ret = sscanf(cfg->data + data_pos, "%x%n", &info_crc, &offset);
+ if (ret != 1) {
+ dev_err(dev, "Bad format: failed to parse Info CRC\n");
+ ret = -EINVAL;
+ goto release;
+ }
+ data_pos += offset;
+
+ ret = sscanf(cfg->data + data_pos, "%x%n", &config_crc, &offset);
+ if (ret != 1) {
+ dev_err(dev, "Bad format: failed to parse Config CRC\n");
+ ret = -EINVAL;
+ goto release;
+ }
+ data_pos += offset;
+
+ /*
+ * The Info Block CRC is calculated over mxt_info and the object
+ * table. If it does not match then we are trying to load the
+ * configuration from a different chip or firmware version, so
+ * the configuration CRC is invalid anyway.
+ */
+ if (info_crc == data->info_crc) {
+ if (config_crc == 0 || data->config_crc == 0) {
+ dev_info(dev, "CRC zero, attempting to apply config\n");
+ } else if (config_crc == data->config_crc) {
+ dev_dbg(dev, "Config CRC 0x%06X: OK\n",
+ data->config_crc);
+ ret = 0;
+ goto release;
+ } else {
+ dev_info(dev, "Config CRC 0x%06X: does not match file 0x%06X\n",
+ data->config_crc, config_crc);
+ }
+ } else {
+ dev_warn(dev,
+ "Warning: Info CRC error - device=0x%06X file=0x%06X\n",
+ data->info_crc, info_crc);
+ }
+
+ /* Malloc memory to store configuration */
+ cfg_start_ofs = MXT_OBJECT_START +
+ data->info->object_num * sizeof(struct mxt_object) +
+ MXT_INFO_CHECKSUM_SIZE;
+ config_mem_size = data->mem_size - cfg_start_ofs;
+ config_mem = kzalloc(config_mem_size, GFP_KERNEL);
+ if (!config_mem) {
+ dev_err(dev, "Failed to allocate memory\n");
+ ret = -ENOMEM;
+ goto release;
+ }
- if (!mxt_object_writable(object->type))
+ while (data_pos < cfg->size) {
+ /* Read type, instance, length */
+ ret = sscanf(cfg->data + data_pos, "%x %x %x%n",
+ &type, &instance, &size, &offset);
+ if (ret == 0) {
+ /* EOF */
+ break;
+ } else if (ret != 3) {
+ dev_err(dev, "Bad format: failed to parse object\n");
+ ret = -EINVAL;
+ goto release_mem;
+ }
+ data_pos += offset;
+
+ object = mxt_get_object(data, type);
+ if (!object) {
+ /* Skip object */
+ for (i = 0; i < size; i++) {
+ ret = sscanf(cfg->data + data_pos, "%hhx%n",
+ &val,
+ &offset);
+ data_pos += offset;
+ }
continue;
+ }
- size = mxt_obj_size(object) * mxt_obj_instances(object);
- if (index + size > pdata->config_length) {
- dev_err(dev, "Not enough config data!\n");
- return -EINVAL;
+ if (size > mxt_obj_size(object)) {
+ /*
+ * Either we are in fallback mode due to wrong
+ * config or config from a later fw version,
+ * or the file is corrupt or hand-edited.
+ */
+ dev_warn(dev, "Discarding %zu byte(s) in T%u\n",
+ size - mxt_obj_size(object), type);
+ } else if (mxt_obj_size(object) > size) {
+ /*
+ * If firmware is upgraded, new bytes may be added to
+ * end of objects. It is generally forward compatible
+ * to zero these bytes - previous behaviour will be
+ * retained. However this does invalidate the CRC and
+ * will force fallback mode until the configuration is
+ * updated. We warn here but do nothing else - the
+ * malloc has zeroed the entire configuration.
+ */
+ dev_warn(dev, "Zeroing %zu byte(s) in T%d\n",
+ mxt_obj_size(object) - size, type);
}
- ret = __mxt_write_reg(data->client, object->start_address,
- size, &pdata->config[index]);
- if (ret)
- return ret;
- index += size;
+ if (instance >= mxt_obj_instances(object)) {
+ dev_err(dev, "Object instances exceeded!\n");
+ ret = -EINVAL;
+ goto release_mem;
+ }
+
+ reg = object->start_address + mxt_obj_size(object) * instance;
+
+ for (i = 0; i < size; i++) {
+ ret = sscanf(cfg->data + data_pos, "%hhx%n",
+ &val,
+ &offset);
+ if (ret != 1) {
+ dev_err(dev, "Bad format in T%d\n", type);
+ ret = -EINVAL;
+ goto release_mem;
+ }
+ data_pos += offset;
+
+ if (i > mxt_obj_size(object))
+ continue;
+
+ byte_offset = reg + i - cfg_start_ofs;
+
+ if ((byte_offset >= 0)
+ && (byte_offset <= config_mem_size)) {
+ *(config_mem + byte_offset) = val;
+ } else {
+ dev_err(dev, "Bad object: reg:%d, T%d, ofs=%d\n",
+ reg, object->type, byte_offset);
+ ret = -EINVAL;
+ goto release_mem;
+ }
+ }
+ }
+
+ /* Calculate crc of the received configs (not the raw config file) */
+ if (data->T7_address < cfg_start_ofs) {
+ dev_err(dev, "Bad T7 address, T7addr = %x, config offset %x\n",
+ data->T7_address, cfg_start_ofs);
+ ret = 0;
+ goto release_mem;
+ }
+
+ calculated_crc = mxt_calculate_crc(config_mem,
+ data->T7_address - cfg_start_ofs,
+ config_mem_size);
+
+ if (config_crc > 0 && (config_crc != calculated_crc))
+ dev_warn(dev, "Config CRC error, calculated=%06X, file=%06X\n",
+ calculated_crc, config_crc);
+
+ /* Write configuration as blocks */
+ byte_offset = 0;
+ while (byte_offset < config_mem_size) {
+ size = config_mem_size - byte_offset;
+
+ if (size > MXT_MAX_BLOCK_WRITE)
+ size = MXT_MAX_BLOCK_WRITE;
+
+ ret = __mxt_write_reg(data->client,
+ cfg_start_ofs + byte_offset,
+ size, config_mem + byte_offset);
+ if (ret != 0) {
+ dev_err(dev, "Config write error, ret=%d\n", ret);
+ goto release_mem;
+ }
+
+ byte_offset += size;
}
mxt_update_crc(data, MXT_COMMAND_BACKUPNV, MXT_BACKUP_VALUE);
+ ret = mxt_check_retrigen(data);
+ if (ret)
+ goto release_mem;
+
ret = mxt_soft_reset(data);
if (ret)
- return ret;
+ goto release_mem;
dev_info(dev, "Config successfully updated\n");
- return 0;
+ /* T7 config may have changed */
+ mxt_init_t7_power_cfg(data);
+
+release_mem:
+ kfree(config_mem);
+release:
+ release_firmware(cfg);
+ return ret;
}
-static int mxt_make_highchg(struct mxt_data *data)
+static int mxt_set_t7_power_cfg(struct mxt_data *data, u8 sleep)
{
struct device *dev = &data->client->dev;
- struct mxt_message message;
- int count = 10;
int error;
+ struct t7_config *new_config;
+ struct t7_config deepsleep = { .active = 0, .idle = 0 };
- /* Read dummy message to make high CHG pin */
- do {
- error = mxt_read_message(data, &message);
- if (error)
- return error;
- } while (message.reportid != 0xff && --count);
+ if (sleep == MXT_POWER_CFG_DEEPSLEEP)
+ new_config = &deepsleep;
+ else
+ new_config = &data->t7_cfg;
- if (!count) {
- dev_err(dev, "CHG pin isn't cleared\n");
- return -EBUSY;
- }
+ error = __mxt_write_reg(data->client, data->T7_address,
+ sizeof(data->t7_cfg), new_config);
+ if (error)
+ return error;
+
+ dev_dbg(dev, "Set T7 ACTV:%d IDLE:%d\n",
+ new_config->active, new_config->idle);
return 0;
}
-static int mxt_get_info(struct mxt_data *data)
+static int mxt_init_t7_power_cfg(struct mxt_data *data)
{
- struct i2c_client *client = data->client;
- struct mxt_info *info = &data->info;
+ struct device *dev = &data->client->dev;
int error;
+ bool retry = false;
- /* Read 7-byte info block starting at address 0 */
- error = __mxt_read_reg(client, MXT_INFO, sizeof(*info), info);
+recheck:
+ error = __mxt_read_reg(data->client, data->T7_address,
+ sizeof(data->t7_cfg), &data->t7_cfg);
if (error)
return error;
+ if (data->t7_cfg.active == 0 || data->t7_cfg.idle == 0) {
+ if (!retry) {
+ dev_dbg(dev, "T7 cfg zero, resetting\n");
+ mxt_soft_reset(data);
+ retry = true;
+ goto recheck;
+ } else {
+ dev_dbg(dev, "T7 cfg zero after reset, overriding\n");
+ data->t7_cfg.active = 20;
+ data->t7_cfg.idle = 100;
+ return mxt_set_t7_power_cfg(data, MXT_POWER_CFG_RUN);
+ }
+ }
+
+ dev_dbg(dev, "Initialized power cfg: ACTV %d, IDLE %d\n",
+ data->t7_cfg.active, data->t7_cfg.idle);
return 0;
}
-static int mxt_get_object_table(struct mxt_data *data)
+static int mxt_acquire_irq(struct mxt_data *data)
{
- struct i2c_client *client = data->client;
- size_t table_size;
int error;
+
+ enable_irq(data->irq);
+
+ if (data->use_retrigen_workaround) {
+ error = mxt_process_messages_until_invalid(data);
+ if (error)
+ return error;
+ }
+
+ return 0;
+}
+
+static void mxt_free_input_device(struct mxt_data *data)
+{
+ if (data->input_dev) {
+ input_unregister_device(data->input_dev);
+ data->input_dev = NULL;
+ }
+}
+
+static void mxt_free_object_table(struct mxt_data *data)
+{
+ mxt_debug_msg_remove(data);
+
+ kfree(data->raw_info_block);
+ data->object_table = NULL;
+ data->info = NULL;
+ data->raw_info_block = NULL;
+ kfree(data->msg_buf);
+ data->msg_buf = NULL;
+
+ mxt_free_input_device(data);
+
+ data->T5_address = 0;
+ data->T5_msg_size = 0;
+ data->T6_reportid = 0;
+ data->T7_address = 0;
+ data->T9_reportid_min = 0;
+ data->T9_reportid_max = 0;
+ data->T15_reportid_min = 0;
+ data->T15_reportid_max = 0;
+ data->T18_address = 0;
+ data->T19_reportid = 0;
+ data->T42_reportid_min = 0;
+ data->T42_reportid_max = 0;
+ data->T44_address = 0;
+ data->T48_reportid = 0;
+ data->T63_reportid_min = 0;
+ data->T63_reportid_max = 0;
+ data->T100_reportid_min = 0;
+ data->T100_reportid_max = 0;
+ data->max_reportid = 0;
+}
+
+static int mxt_parse_object_table(struct mxt_data *data,
+ struct mxt_object *object_table)
+{
+ struct i2c_client *client = data->client;
int i;
u8 reportid;
+ u16 end_address;
+
+ /* Valid Report IDs start counting from 1 */
+ reportid = 1;
+ data->mem_size = 0;
+ for (i = 0; i < data->info->object_num; i++) {
+ struct mxt_object *object = object_table + i;
+ u8 min_id, max_id;
+
+ le16_to_cpus(&object->start_address);
+
+ if (object->num_report_ids) {
+ min_id = reportid;
+ reportid += object->num_report_ids *
+ mxt_obj_instances(object);
+ max_id = reportid - 1;
+ } else {
+ min_id = 0;
+ max_id = 0;
+ }
+
+ dev_dbg(&data->client->dev,
+ "T%u Start:%u Size:%zu Instances:%zu Report IDs:%u-%u\n",
+ object->type, object->start_address,
+ mxt_obj_size(object), mxt_obj_instances(object),
+ min_id, max_id);
+
+ switch (object->type) {
+ case MXT_GEN_MESSAGE_T5:
+ if (data->info->family_id == 0x80) {
+ /*
+ * On mXT224 read and discard unused CRC byte
+ * otherwise DMA reads are misaligned
+ */
+ data->T5_msg_size = mxt_obj_size(object);
+ } else {
+ /* CRC not enabled, so skip last byte */
+ data->T5_msg_size = mxt_obj_size(object) - 1;
+ }
+ data->T5_address = object->start_address;
+ case MXT_GEN_COMMAND_T6:
+ data->T6_reportid = min_id;
+ data->T6_address = object->start_address;
+ break;
+ case MXT_GEN_POWER_T7:
+ data->T7_address = object->start_address;
+ break;
+ case MXT_TOUCH_MULTI_T9:
+ /* Only handle messages from first T9 instance */
+ data->T9_reportid_min = min_id;
+ data->T9_reportid_max = min_id +
+ object->num_report_ids - 1;
+ data->num_touchids = object->num_report_ids;
+ break;
+ case MXT_TOUCH_KEYARRAY_T15:
+ data->T15_reportid_min = min_id;
+ data->T15_reportid_max = max_id;
+ break;
+ case MXT_SPT_COMMSCONFIG_T18:
+ data->T18_address = object->start_address;
+ break;
+ case MXT_PROCI_TOUCHSUPPRESSION_T42:
+ data->T42_reportid_min = min_id;
+ data->T42_reportid_max = max_id;
+ break;
+ case MXT_SPT_MESSAGECOUNT_T44:
+ data->T44_address = object->start_address;
+ break;
+ case MXT_SPT_GPIOPWM_T19:
+ data->T19_reportid = min_id;
+ break;
+ case MXT_PROCG_NOISESUPPRESSION_T48:
+ data->T48_reportid = min_id;
+ break;
+ case MXT_PROCI_ACTIVE_STYLUS_T63:
+ /* Only handle messages from first T63 instance */
+ data->T63_reportid_min = min_id;
+ data->T63_reportid_max = min_id;
+ data->num_stylusids = 1;
+ break;
+ case MXT_TOUCH_MULTITOUCHSCREEN_T100:
+ data->T100_reportid_min = min_id;
+ data->T100_reportid_max = max_id;
+ /* first two report IDs reserved */
+ data->num_touchids = object->num_report_ids - 2;
+ break;
+ }
+
+ end_address = object->start_address
+ + mxt_obj_size(object) * mxt_obj_instances(object) - 1;
+
+ if (end_address >= data->mem_size)
+ data->mem_size = end_address + 1;
+ }
+
+ /* Store maximum reportid */
+ data->max_reportid = reportid;
+
+ /* If T44 exists, T5 position has to be directly after */
+ if (data->T44_address && (data->T5_address != data->T44_address + 1)) {
+ dev_err(&client->dev, "Invalid T44 position\n");
+ return -EINVAL;
+ }
+
+ data->msg_buf = kcalloc(data->max_reportid,
+ data->T5_msg_size, GFP_KERNEL);
+ if (!data->msg_buf) {
+ dev_err(&client->dev, "Failed to allocate message buffer\n");
+ return -ENOMEM;
+ }
+
+ return 0;
+}
+
+static int mxt_read_info_block(struct mxt_data *data)
+{
+ struct i2c_client *client = data->client;
+ int error;
+ size_t size;
+ void *id_buf, *buf;
+ uint8_t num_objects;
+ u32 calculated_crc;
+ u8 *crc_ptr;
+
+ /* If info block already allocated, free it */
+ if (data->raw_info_block != NULL)
+ mxt_free_object_table(data);
+
+ /* Read 7-byte ID information block starting at address 0 */
+ size = sizeof(struct mxt_info);
+ id_buf = kzalloc(size, GFP_KERNEL);
+ if (!id_buf) {
+ dev_err(&client->dev, "Failed to allocate memory\n");
+ return -ENOMEM;
+ }
+
+ error = __mxt_read_reg(client, 0, size, id_buf);
+ if (error) {
+ kfree(id_buf);
+ return error;
+ }
+
+ /* Resize buffer to give space for rest of info block */
+ num_objects = ((struct mxt_info *)id_buf)->object_num;
+ size += (num_objects * sizeof(struct mxt_object))
+ + MXT_INFO_CHECKSUM_SIZE;
+
+ buf = krealloc(id_buf, size, GFP_KERNEL);
+ if (!buf) {
+ dev_err(&client->dev, "Failed to allocate memory\n");
+ error = -ENOMEM;
+ goto err_free_mem;
+ }
- table_size = data->info.object_num * sizeof(struct mxt_object);
- error = __mxt_read_reg(client, MXT_OBJECT_START, table_size,
- data->object_table);
+ /* Read rest of info block */
+ error = __mxt_read_reg(client, MXT_OBJECT_START,
+ size - MXT_OBJECT_START,
+ buf + MXT_OBJECT_START);
if (error)
- return error;
+ goto err_free_mem;
- /* Valid Report IDs start counting from 1 */
- reportid = 1;
- for (i = 0; i < data->info.object_num; i++) {
- struct mxt_object *object = data->object_table + i;
- u8 min_id, max_id;
+ /* Extract & calculate checksum */
+ crc_ptr = buf + size - MXT_INFO_CHECKSUM_SIZE;
+ data->info_crc = crc_ptr[0] | (crc_ptr[1] << 8) | (crc_ptr[2] << 16);
- le16_to_cpus(&object->start_address);
+ calculated_crc = mxt_calculate_crc(buf, 0,
+ size - MXT_INFO_CHECKSUM_SIZE);
- if (object->num_report_ids) {
- min_id = reportid;
- reportid += object->num_report_ids *
- mxt_obj_instances(object);
- max_id = reportid - 1;
- } else {
- min_id = 0;
- max_id = 0;
- }
+ /*
+ * CRC mismatch can be caused by data corruption due to I2C comms
+ * issue or else device is not using Object Based Protocol (eg i2c-hid)
+ */
+ if ((data->info_crc == 0) || (data->info_crc != calculated_crc)) {
+ dev_err(&client->dev,
+ "Info Block CRC error calculated=0x%06X read=0x%06X\n",
+ data->info_crc, calculated_crc);
+ error = -EIO;
+ goto err_free_mem;
+ }
- dev_dbg(&data->client->dev,
- "T%u Start:%u Size:%zu Instances:%zu Report IDs:%u-%u\n",
- object->type, object->start_address,
- mxt_obj_size(object), mxt_obj_instances(object),
- min_id, max_id);
+ data->raw_info_block = buf;
+ data->info = (struct mxt_info *)buf;
- switch (object->type) {
- case MXT_GEN_COMMAND_T6:
- data->T6_reportid = min_id;
- data->T6_address = object->start_address;
- break;
- case MXT_TOUCH_MULTI_T9:
- data->T9_reportid_min = min_id;
- data->T9_reportid_max = max_id;
- break;
- case MXT_SPT_GPIOPWM_T19:
- data->T19_reportid = min_id;
- break;
- }
+ dev_info(&client->dev,
+ "Family: %u Variant: %u Firmware V%u.%u.%02X Objects: %u\n",
+ data->info->family_id, data->info->variant_id,
+ data->info->version >> 4, data->info->version & 0xf,
+ data->info->build, data->info->object_num);
+
+ /* Parse object table information */
+ error = mxt_parse_object_table(data, buf + MXT_OBJECT_START);
+ if (error) {
+ dev_err(&client->dev, "Error %d parsing object table\n", error);
+ mxt_free_object_table(data);
+ return error;
}
+ data->object_table = (struct mxt_object *)(buf + MXT_OBJECT_START);
+
return 0;
-}
-static void mxt_free_object_table(struct mxt_data *data)
-{
- kfree(data->object_table);
- data->object_table = NULL;
- data->T6_reportid = 0;
- data->T9_reportid_min = 0;
- data->T9_reportid_max = 0;
- data->T19_reportid = 0;
+err_free_mem:
+ kfree(buf);
+ return error;
}
static int mxt_read_t9_resolution(struct mxt_data *data)
@@ -1040,8 +2087,8 @@ static int mxt_read_t9_resolution(struct mxt_data *data)
if (error)
return error;
- le16_to_cpus(&range.x);
- le16_to_cpus(&range.y);
+ le16_to_cpus(range.x);
+ le16_to_cpus(range.y);
error = __mxt_read_reg(client,
object->start_address + MXT_T9_ORIENT,
@@ -1070,55 +2117,351 @@ static int mxt_read_t9_resolution(struct mxt_data *data)
return 0;
}
-static int mxt_initialize(struct mxt_data *data)
+static void mxt_regulator_enable(struct mxt_data *data)
+{
+ int error;
+
+ gpio_set_value(data->pdata->gpio_reset, 0);
+
+ error = regulator_enable(data->reg_vdd);
+ if (error)
+ return;
+
+ error = regulator_enable(data->reg_avdd);
+ if (error)
+ return;
+
+ msleep(MXT_REGULATOR_DELAY);
+ gpio_set_value(data->pdata->gpio_reset, 1);
+ msleep(MXT_CHG_DELAY);
+
+retry_wait:
+ reinit_completion(&data->bl_completion);
+ data->in_bootloader = true;
+ error = mxt_wait_for_completion(data, &data->bl_completion,
+ MXT_POWERON_DELAY);
+ if (error == -EINTR)
+ goto retry_wait;
+
+ data->in_bootloader = false;
+}
+
+static void mxt_regulator_disable(struct mxt_data *data)
+{
+ regulator_disable(data->reg_vdd);
+ regulator_disable(data->reg_avdd);
+}
+
+static void mxt_probe_regulators(struct mxt_data *data)
+{
+ struct device *dev = &data->client->dev;
+ int error;
+
+ /*
+ * According to maXTouch power sequencing specification, RESET line
+ * must be kept low until some time after regulators come up to
+ * voltage
+ */
+ if (!data->pdata->gpio_reset) {
+ dev_warn(dev, "Must have reset GPIO to use regulator support\n");
+ goto fail;
+ }
+
+ data->reg_vdd = regulator_get(dev, "vdd");
+ if (IS_ERR(data->reg_vdd)) {
+ error = PTR_ERR(data->reg_vdd);
+ dev_err(dev, "Error %d getting vdd regulator\n", error);
+ goto fail;
+ }
+
+ data->reg_avdd = regulator_get(dev, "avdd");
+ if (IS_ERR(data->reg_vdd)) {
+ error = PTR_ERR(data->reg_vdd);
+ dev_err(dev, "Error %d getting avdd regulator\n", error);
+ goto fail_release;
+ }
+
+ data->use_regulator = true;
+ mxt_regulator_enable(data);
+
+ dev_dbg(dev, "Initialised regulators\n");
+ return;
+
+fail_release:
+ regulator_put(data->reg_vdd);
+fail:
+ data->reg_vdd = NULL;
+ data->reg_avdd = NULL;
+ data->use_regulator = false;
+}
+
+static int mxt_read_t100_config(struct mxt_data *data)
{
struct i2c_client *client = data->client;
- struct mxt_info *info = &data->info;
int error;
+ struct mxt_object *object;
+ u16 range_x, range_y;
+ u8 cfg, tchaux;
+ u8 aux;
+ int ret;
+
+ object = mxt_get_object(data, MXT_TOUCH_MULTITOUCHSCREEN_T100);
+ if (!object)
+ return -EINVAL;
+
+ error = __mxt_read_reg(client,
+ object->start_address + MXT_T100_XRANGE,
+ sizeof(range_x), &range_x);
+ if (error)
+ return error;
+
+ le16_to_cpus(range_x);
- error = mxt_get_info(data);
+ error = __mxt_read_reg(client,
+ object->start_address + MXT_T100_YRANGE,
+ sizeof(range_y), &range_y);
if (error)
return error;
- data->object_table = kcalloc(info->object_num,
- sizeof(struct mxt_object),
- GFP_KERNEL);
- if (!data->object_table) {
- dev_err(&client->dev, "Failed to allocate memory\n");
+ le16_to_cpus(range_y);
+
+ error = __mxt_read_reg(client,
+ object->start_address + MXT_T100_CFG1,
+ 1, &cfg);
+ if (error)
+ return error;
+
+ error = __mxt_read_reg(client,
+ object->start_address + MXT_T100_TCHAUX,
+ 1, &tchaux);
+ if (error)
+ return error;
+
+ /* Handle default values */
+ if (range_x == 0)
+ range_x = 1023;
+
+ /* Handle default values */
+ if (range_x == 0)
+ range_x = 1023;
+
+ if (range_y == 0)
+ range_y = 1023;
+
+ if (cfg & MXT_T100_CFG_SWITCHXY) {
+ data->max_x = range_y;
+ data->max_y = range_x;
+ } else {
+ data->max_x = range_x;
+ data->max_y = range_y;
+ }
+
+ /* allocate aux bytes */
+ aux = 6;
+
+ if (tchaux & MXT_T100_TCHAUX_VECT)
+ data->t100_aux_vect = aux++;
+
+ if (tchaux & MXT_T100_TCHAUX_AMPL)
+ data->t100_aux_ampl = aux++;
+
+ if (tchaux & MXT_T100_TCHAUX_AREA)
+ data->t100_aux_area = aux++;
+
+ dev_info(&client->dev,
+ "T100 Touchscreen size X%uY%u\n", data->max_x, data->max_y);
+
+ mxt_update_crc(data, MXT_COMMAND_BACKUPNV, MXT_BACKUP_VALUE);
+
+ ret = mxt_soft_reset(data);
+ if (ret)
+ return ret;
+
+ dev_info(&client->dev, "Config successfully updated\n");
+
+ return 0;
+}
+
+static int mxt_input_open(struct input_dev *dev);
+static void mxt_input_close(struct input_dev *dev);
+
+static int mxt_initialize_t100_input_device(struct mxt_data *data)
+{
+ struct device *dev = &data->client->dev;
+ struct input_dev *input_dev;
+ int error;
+
+ error = mxt_read_t100_config(data);
+ if (error)
+ dev_warn(dev, "Failed to initialize T9 resolution\n");
+
+ input_dev = input_allocate_device();
+ if (!data || !input_dev) {
+ dev_err(dev, "Failed to allocate memory\n");
return -ENOMEM;
}
- /* Get object table information */
- error = mxt_get_object_table(data);
+ if (data->pdata->input_name)
+ input_dev->name = data->pdata->input_name;
+ else
+ input_dev->name = "atmel_mxt_ts T100 touchscreen";
+
+ input_dev->phys = data->phys;
+ input_dev->id.bustype = BUS_I2C;
+ input_dev->dev.parent = &data->client->dev;
+ input_dev->open = mxt_input_open;
+ input_dev->close = mxt_input_close;
+
+ set_bit(EV_ABS, input_dev->evbit);
+ input_set_capability(input_dev, EV_KEY, BTN_TOUCH);
+
+ /* For single touch */
+ input_set_abs_params(input_dev, ABS_X,
+ 0, data->max_x, 0, 0);
+ input_set_abs_params(input_dev, ABS_Y,
+ 0, data->max_y, 0, 0);
+
+ if (data->t100_aux_ampl)
+ input_set_abs_params(input_dev, ABS_PRESSURE,
+ 0, 255, 0, 0);
+
+ /* For multi touch */
+ error = input_mt_init_slots(input_dev, data->num_touchids, 0);
if (error) {
- dev_err(&client->dev, "Error %d reading object table\n", error);
- goto err_free_object_table;
+ dev_err(dev, "Error %d initialising slots\n", error);
+ goto err_free_mem;
}
- /* Check register init values */
- error = mxt_check_reg_init(data);
+ input_set_abs_params(input_dev, ABS_MT_TOOL_TYPE, 0, MT_TOOL_MAX, 0, 0);
+ input_set_abs_params(input_dev, ABS_MT_POSITION_X,
+ 0, data->max_x, 0, 0);
+ input_set_abs_params(input_dev, ABS_MT_POSITION_Y,
+ 0, data->max_y, 0, 0);
+
+ if (data->t100_aux_area)
+ input_set_abs_params(input_dev, ABS_MT_TOUCH_MAJOR,
+ 0, MXT_MAX_AREA, 0, 0);
+
+ if (data->t100_aux_ampl)
+ input_set_abs_params(input_dev, ABS_MT_PRESSURE,
+ 0, 255, 0, 0);
+
+ if (data->t100_aux_vect)
+ input_set_abs_params(input_dev, ABS_MT_ORIENTATION,
+ 0, 255, 0, 0);
+
+ input_set_drvdata(input_dev, data);
+
+ error = input_register_device(input_dev);
if (error) {
- dev_err(&client->dev, "Error %d initializing configuration\n",
- error);
- goto err_free_object_table;
+ dev_err(dev, "Error %d registering input device\n", error);
+ goto err_free_mem;
}
- error = mxt_read_t9_resolution(data);
+ data->input_dev = input_dev;
+
+ return 0;
+
+err_free_mem:
+ input_free_device(input_dev);
+ return error;
+}
+
+static int mxt_initialize_t9_input_device(struct mxt_data *data);
+static int mxt_configure_objects(struct mxt_data *data);
+
+static int mxt_initialize(struct mxt_data *data)
+{
+ struct i2c_client *client = data->client;
+ int error;
+ bool alt_bootloader_addr = false;
+ bool retry = false;
+
+retry_info:
+ error = mxt_read_info_block(data);
if (error) {
- dev_err(&client->dev, "Failed to initialize T9 resolution\n");
- goto err_free_object_table;
+retry_bootloader:
+ error = mxt_probe_bootloader(data, alt_bootloader_addr);
+ if (error) {
+ if (alt_bootloader_addr) {
+ /* Chip is not in appmode or bootloader mode */
+ return error;
+ }
+
+ dev_info(&client->dev, "Trying alternate bootloader address\n");
+ alt_bootloader_addr = true;
+ goto retry_bootloader;
+ } else {
+ if (retry) {
+ dev_err(&client->dev,
+ "Could not recover device from bootloader mode\n");
+ /*
+ * We can reflash from this state, so do not
+ * abort init
+ */
+ data->in_bootloader = true;
+ return 0;
+ }
+
+ /* Attempt to exit bootloader into app mode */
+ mxt_send_bootloader_cmd(data, false);
+ msleep(MXT_FW_RESET_TIME);
+ retry = true;
+ goto retry_info;
+ }
}
- dev_info(&client->dev,
- "Family: %u Variant: %u Firmware V%u.%u.%02X Objects: %u\n",
- info->family_id, info->variant_id, info->version >> 4,
- info->version & 0xf, info->build, info->object_num);
+ error = mxt_check_retrigen(data);
+ if (error)
+ return error;
+
+ error = mxt_acquire_irq(data);
+ if (error)
+ return error;
+
+ error = mxt_debug_msg_init(data);
+ if (error)
+ return error;
+
+ error = mxt_configure_objects(data);
+ if (error)
+ return error;
return 0;
+}
-err_free_object_table:
- mxt_free_object_table(data);
- return error;
+static int mxt_configure_objects(struct mxt_data *data)
+{
+ struct i2c_client *client = data->client;
+ int error;
+
+ error = mxt_init_t7_power_cfg(data);
+ if (error) {
+ dev_err(&client->dev, "Failed to initialize power cfg\n");
+ return error;
+ }
+
+ /* Check register init values */
+ error = mxt_check_reg_init(data);
+ if (error) {
+ dev_err(&client->dev, "Error %d initialising configuration\n",
+ error);
+ return error;
+ }
+
+ if (data->T9_reportid_min) {
+ error = mxt_initialize_t9_input_device(data);
+ if (error)
+ return error;
+ } else if (data->T100_reportid_min) {
+ error = mxt_initialize_t100_input_device(data);
+ if (error)
+ return error;
+ } else {
+ dev_warn(&client->dev, "No touch object detected\n");
+ }
+
+ return 0;
}
/* Firmware Version is returned as Major.Minor.Build */
@@ -1126,9 +2469,9 @@ static ssize_t mxt_fw_version_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct mxt_data *data = dev_get_drvdata(dev);
- struct mxt_info *info = &data->info;
return scnprintf(buf, PAGE_SIZE, "%u.%u.%02X\n",
- info->version >> 4, info->version & 0xf, info->build);
+ data->info->version >> 4, data->info->version & 0xf,
+ data->info->build);
}
/* Hardware Version is returned as FamilyID.VariantID */
@@ -1136,9 +2479,8 @@ static ssize_t mxt_hw_version_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct mxt_data *data = dev_get_drvdata(dev);
- struct mxt_info *info = &data->info;
return scnprintf(buf, PAGE_SIZE, "%u.%u\n",
- info->family_id, info->variant_id);
+ data->info->family_id, data->info->variant_id);
}
static ssize_t mxt_show_instance(char *buf, int count,
@@ -1175,7 +2517,7 @@ static ssize_t mxt_object_show(struct device *dev,
return -ENOMEM;
error = 0;
- for (i = 0; i < data->info.object_num; i++) {
+ for (i = 0; i < data->info->object_num; i++) {
object = data->object_table + i;
if (!mxt_object_readable(object->type))
@@ -1225,7 +2567,7 @@ static int mxt_check_firmware_format(struct device *dev,
return -EINVAL;
}
-static int mxt_load_fw(struct device *dev, const char *fn)
+static int mxt_load_fw(struct device *dev)
{
struct mxt_data *data = dev_get_drvdata(dev);
const struct firmware *fw = NULL;
@@ -1235,9 +2577,9 @@ static int mxt_load_fw(struct device *dev, const char *fn)
unsigned int frame = 0;
int ret;
- ret = request_firmware(&fw, fn, dev);
+ ret = request_firmware(&fw, data->fw_name, dev);
if (ret) {
- dev_err(dev, "Unable to open firmware %s\n", fn);
+ dev_err(dev, "Unable to open firmware %s\n", data->fw_name);
return ret;
}
@@ -1246,30 +2588,53 @@ static int mxt_load_fw(struct device *dev, const char *fn)
if (ret)
goto release_firmware;
- ret = mxt_lookup_bootloader_address(data);
- if (ret)
- goto release_firmware;
+ if (data->suspended) {
+ if (data->use_regulator)
+ mxt_regulator_enable(data);
- /* Change to the bootloader mode */
- data->in_bootloader = true;
+ enable_irq(data->irq);
+ data->suspended = false;
+ }
- ret = mxt_t6_command(data, MXT_COMMAND_RESET, MXT_BOOT_VALUE, false);
- if (ret)
- goto release_firmware;
+ if (!data->in_bootloader) {
+ /* Change to the bootloader mode */
+ data->in_bootloader = true;
- msleep(MXT_RESET_TIME);
+ ret = mxt_t6_command(data, MXT_COMMAND_RESET,
+ MXT_BOOT_VALUE, false);
+ if (ret)
+ goto release_firmware;
+
+ msleep(MXT_RESET_TIME);
+
+ /* Do not need to scan since we know family ID */
+ ret = mxt_lookup_bootloader_address(data, 0);
+ if (ret)
+ goto release_firmware;
+ } else {
+ enable_irq(data->irq);
+ }
+ mxt_free_object_table(data);
reinit_completion(&data->bl_completion);
- ret = mxt_check_bootloader(data, MXT_WAITING_BOOTLOAD_CMD);
- if (ret)
- goto disable_irq;
+ ret = mxt_check_bootloader(data, MXT_WAITING_BOOTLOAD_CMD, false);
+ if (ret) {
+ /* Bootloader may still be unlocked from previous attempt */
+ ret = mxt_check_bootloader(data, MXT_WAITING_FRAME_DATA, false);
+ if (ret)
+ goto disable_irq;
+ } else {
+ dev_info(dev, "Unlocking bootloader\n");
- /* Unlock bootloader */
- mxt_unlock_bootloader(data);
+ /* Unlock bootloader */
+ ret = mxt_send_bootloader_cmd(data, true);
+ if (ret)
+ goto disable_irq;
+ }
while (pos < fw->size) {
- ret = mxt_check_bootloader(data, MXT_WAITING_FRAME_DATA);
+ ret = mxt_check_bootloader(data, MXT_WAITING_FRAME_DATA, true);
if (ret)
goto disable_irq;
@@ -1283,7 +2648,7 @@ static int mxt_load_fw(struct device *dev, const char *fn)
if (ret)
goto disable_irq;
- ret = mxt_check_bootloader(data, MXT_FRAME_CRC_PASS);
+ ret = mxt_check_bootloader(data, MXT_FRAME_CRC_PASS, true);
if (ret) {
retry++;
@@ -1329,6 +2694,35 @@ release_firmware:
return ret;
}
+static int mxt_update_file_name(struct device *dev, char **file_name,
+ const char *buf, size_t count)
+{
+ char *file_name_tmp;
+
+ /* Simple sanity check */
+ if (count > 64) {
+ dev_warn(dev, "File name too long\n");
+ return -EINVAL;
+ }
+
+ file_name_tmp = krealloc(*file_name, count + 1, GFP_KERNEL);
+ if (!file_name_tmp) {
+ dev_warn(dev, "no memory\n");
+ return -ENOMEM;
+ }
+
+ *file_name = file_name_tmp;
+ memcpy(*file_name, buf, count);
+
+ /* Echo into the sysfs entry may append newline at the end of buf */
+ if (buf[count - 1] == '\n')
+ (*file_name)[count - 1] = '\0';
+ else
+ (*file_name)[count] = '\0';
+
+ return 0;
+}
+
static ssize_t mxt_update_fw_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
@@ -1336,20 +2730,20 @@ static ssize_t mxt_update_fw_store(struct device *dev,
struct mxt_data *data = dev_get_drvdata(dev);
int error;
- error = mxt_load_fw(dev, MXT_FW_NAME);
+ error = mxt_update_file_name(dev, &data->fw_name, buf, count);
+ if (error)
+ return error;
+
+ error = mxt_load_fw(dev);
if (error) {
dev_err(dev, "The firmware update failed(%d)\n", error);
count = error;
} else {
dev_info(dev, "The firmware update succeeded\n");
- mxt_free_object_table(data);
-
- mxt_initialize(data);
-
- enable_irq(data->irq);
+ data->suspended = false;
- error = mxt_make_highchg(data);
+ error = mxt_initialize(data);
if (error)
return error;
}
@@ -1357,16 +2751,170 @@ static ssize_t mxt_update_fw_store(struct device *dev,
return count;
}
+static ssize_t mxt_update_cfg_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct mxt_data *data = dev_get_drvdata(dev);
+ int ret;
+
+ if (data->in_bootloader) {
+ dev_err(dev, "Not in appmode\n");
+ return -EINVAL;
+ }
+
+ ret = mxt_update_file_name(dev, &data->cfg_name, buf, count);
+ if (ret)
+ return ret;
+
+ data->updating_config = true;
+
+ mxt_free_input_device(data);
+
+ if (data->suspended) {
+ if (data->use_regulator) {
+ enable_irq(data->irq);
+ mxt_regulator_enable(data);
+ } else {
+ mxt_set_t7_power_cfg(data, MXT_POWER_CFG_RUN);
+ mxt_acquire_irq(data);
+ }
+
+ data->suspended = false;
+ }
+
+ ret = mxt_configure_objects(data);
+ if (ret)
+ goto out;
+
+ ret = count;
+out:
+ data->updating_config = false;
+ return ret;
+}
+
+static ssize_t mxt_debug_enable_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct mxt_data *data = dev_get_drvdata(dev);
+ char c;
+
+ c = data->debug_enabled ? '1' : '0';
+ return scnprintf(buf, PAGE_SIZE, "%c\n", c);
+}
+
+static ssize_t mxt_debug_notify_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ return sprintf(buf, "0\n");
+}
+
+static ssize_t mxt_debug_v2_enable_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ struct mxt_data *data = dev_get_drvdata(dev);
+ int i;
+
+ if (sscanf(buf, "%u", &i) == 1 && i < 2) {
+ if (i == 1)
+ mxt_debug_msg_enable(data);
+ else
+ mxt_debug_msg_disable(data);
+
+ return count;
+ } else {
+ dev_dbg(dev, "debug_enabled write error\n");
+ return -EINVAL;
+ }
+}
+
+static ssize_t mxt_debug_enable_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ struct mxt_data *data = dev_get_drvdata(dev);
+ int i;
+
+ if (sscanf(buf, "%u", &i) == 1 && i < 2) {
+ data->debug_enabled = (i == 1);
+
+ dev_dbg(dev, "%s\n", i ? "debug enabled" : "debug disabled");
+ return count;
+ } else {
+ dev_dbg(dev, "debug_enabled write error\n");
+ return -EINVAL;
+ }
+}
+
+static int mxt_check_mem_access_params(struct mxt_data *data, loff_t off,
+ size_t *count)
+{
+ if (off >= data->mem_size)
+ return -EIO;
+
+ if (off + *count > data->mem_size)
+ *count = data->mem_size - off;
+
+ if (*count > MXT_MAX_BLOCK_WRITE)
+ *count = MXT_MAX_BLOCK_WRITE;
+
+ return 0;
+}
+
+static ssize_t mxt_mem_access_read(struct file *filp, struct kobject *kobj,
+ struct bin_attribute *bin_attr, char *buf, loff_t off, size_t count)
+{
+ struct device *dev = container_of(kobj, struct device, kobj);
+ struct mxt_data *data = dev_get_drvdata(dev);
+ int ret = 0;
+
+ ret = mxt_check_mem_access_params(data, off, &count);
+ if (ret < 0)
+ return ret;
+
+ if (count > 0)
+ ret = __mxt_read_reg(data->client, off, count, buf);
+
+ return ret == 0 ? count : ret;
+}
+
+static ssize_t mxt_mem_access_write(struct file *filp, struct kobject *kobj,
+ struct bin_attribute *bin_attr, char *buf, loff_t off,
+ size_t count)
+{
+ struct device *dev = container_of(kobj, struct device, kobj);
+ struct mxt_data *data = dev_get_drvdata(dev);
+ int ret = 0;
+
+ ret = mxt_check_mem_access_params(data, off, &count);
+ if (ret < 0)
+ return ret;
+
+ if (count > 0)
+ ret = __mxt_write_reg(data->client, off, count, buf);
+
+ return ret == 0 ? count : ret;
+}
+
static DEVICE_ATTR(fw_version, S_IRUGO, mxt_fw_version_show, NULL);
static DEVICE_ATTR(hw_version, S_IRUGO, mxt_hw_version_show, NULL);
static DEVICE_ATTR(object, S_IRUGO, mxt_object_show, NULL);
static DEVICE_ATTR(update_fw, S_IWUSR, NULL, mxt_update_fw_store);
+static DEVICE_ATTR(update_cfg, S_IWUSR, NULL, mxt_update_cfg_store);
+static DEVICE_ATTR(debug_v2_enable, S_IWUSR | S_IRUSR, NULL,
+ mxt_debug_v2_enable_store);
+static DEVICE_ATTR(debug_notify, S_IRUGO, mxt_debug_notify_show, NULL);
+static DEVICE_ATTR(debug_enable, S_IWUSR | S_IRUSR, mxt_debug_enable_show,
+ mxt_debug_enable_store);
static struct attribute *mxt_attrs[] = {
&dev_attr_fw_version.attr,
&dev_attr_hw_version.attr,
&dev_attr_object.attr,
&dev_attr_update_fw.attr,
+ &dev_attr_update_cfg.attr,
+ &dev_attr_debug_enable.attr,
+ &dev_attr_debug_v2_enable.attr,
+ &dev_attr_debug_notify.attr,
NULL
};
@@ -1374,18 +2922,63 @@ static const struct attribute_group mxt_attr_group = {
.attrs = mxt_attrs,
};
+static void mxt_reset_slots(struct mxt_data *data)
+{
+ struct input_dev *input_dev = data->input_dev;
+ unsigned int num_mt_slots;
+ int id;
+
+ num_mt_slots = data->num_touchids + data->num_stylusids;
+
+ for (id = 0; id < num_mt_slots; id++) {
+ input_mt_slot(input_dev, id);
+ input_mt_report_slot_state(input_dev, MT_TOOL_FINGER, 0);
+ }
+
+ mxt_input_sync(input_dev);
+}
+
static void mxt_start(struct mxt_data *data)
{
- /* Touch enable */
- mxt_write_object(data,
- MXT_TOUCH_MULTI_T9, MXT_TOUCH_CTRL, 0x83);
+ if (!data->suspended || data->in_bootloader)
+ return;
+
+ if (data->use_regulator) {
+ enable_irq(data->irq);
+
+ mxt_regulator_enable(data);
+ } else {
+ /*
+ * Discard any messages still in message buffer
+ * from before chip went to sleep
+ */
+ mxt_process_messages_until_invalid(data);
+
+ mxt_set_t7_power_cfg(data, MXT_POWER_CFG_RUN);
+
+ /* Recalibrate since chip has been in deep sleep */
+ mxt_t6_command(data, MXT_COMMAND_CALIBRATE, 1, false);
+
+ mxt_acquire_irq(data);
+ }
+
+ data->suspended = false;
}
static void mxt_stop(struct mxt_data *data)
{
- /* Touch disable */
- mxt_write_object(data,
- MXT_TOUCH_MULTI_T9, MXT_TOUCH_CTRL, 0);
+ if (data->suspended || data->in_bootloader || data->updating_config)
+ return;
+
+ disable_irq(data->irq);
+
+ if (data->use_regulator)
+ mxt_regulator_disable(data);
+ else
+ mxt_set_t7_power_cfg(data, MXT_POWER_CFG_DEEPSLEEP);
+
+ mxt_reset_slots(data);
+ data->suspended = true;
}
static int mxt_input_open(struct input_dev *dev)
@@ -1404,52 +2997,83 @@ static void mxt_input_close(struct input_dev *dev)
mxt_stop(data);
}
-static int mxt_probe(struct i2c_client *client,
- const struct i2c_device_id *id)
+#ifdef CONFIG_OF
+static struct of_device_id mxt_dt_match[] = {
+ { .compatible = "atmel,maxtouch" },
+ {}
+};
+MODULE_DEVICE_TABLE(of, mxt_dt_match);
+
+static struct mxt_platform_data *mxt_parse_dt(struct device *dev)
{
- const struct mxt_platform_data *pdata = dev_get_platdata(&client->dev);
- struct mxt_data *data;
+ struct mxt_platform_data *pd;
+
+ pd = devm_kzalloc(dev, sizeof(*pd), GFP_KERNEL);
+ if (!pd) {
+ dev_err(dev, "Failed to allocate platform data\n");
+ return NULL;
+ }
+
+ pd->irqflags = IRQF_TRIGGER_FALLING;
+
+ return pd;
+}
+#else
+static struct mxt_platform_data *mxt_parse_dt(struct device *dev)
+{
+ return NULL;
+}
+#endif
+
+static int mxt_handle_pdata(struct i2c_client *client, struct mxt_data *data)
+{
+ data->pdata = dev_get_platdata(&data->client->dev);
+
+ if (!data->pdata && client->dev.of_node)
+ data->pdata = mxt_parse_dt(&client->dev);
+
+ if (!data->pdata)
+ return -EINVAL;
+
+ data->pdata = kzalloc(sizeof(*data->pdata), GFP_KERNEL);
+ if (!data->pdata) {
+ dev_err(&data->client->dev, "Failed to allocate pdata\n");
+ return -ENOMEM;
+ }
+
+ /* Set default parameters */
+ data->pdata->irqflags = IRQF_TRIGGER_FALLING;
+
+ return 0;
+}
+
+static int mxt_initialize_t9_input_device(struct mxt_data *data)
+{
+ struct device *dev = &data->client->dev;
+ const struct mxt_platform_data *pdata = data->pdata;
struct input_dev *input_dev;
int error;
unsigned int num_mt_slots;
unsigned int mt_flags = 0;
int i;
- if (!pdata)
- return -EINVAL;
+ error = mxt_read_t9_resolution(data);
+ if (error)
+ dev_warn(dev, "Failed to initialize T9 resolution\n");
- data = kzalloc(sizeof(struct mxt_data), GFP_KERNEL);
input_dev = input_allocate_device();
- if (!data || !input_dev) {
- dev_err(&client->dev, "Failed to allocate memory\n");
- error = -ENOMEM;
- goto err_free_mem;
+ if (!input_dev) {
+ dev_err(dev, "Failed to allocate memory\n");
+ return -ENOMEM;
}
input_dev->name = "Atmel maXTouch Touchscreen";
- snprintf(data->phys, sizeof(data->phys), "i2c-%u-%04x/input0",
- client->adapter->nr, client->addr);
-
input_dev->phys = data->phys;
-
input_dev->id.bustype = BUS_I2C;
- input_dev->dev.parent = &client->dev;
+ input_dev->dev.parent = dev;
input_dev->open = mxt_input_open;
input_dev->close = mxt_input_close;
- data->client = client;
- data->input_dev = input_dev;
- data->pdata = pdata;
- data->irq = client->irq;
-
- init_completion(&data->bl_completion);
- init_completion(&data->reset_completion);
- init_completion(&data->crc_completion);
-
- error = mxt_initialize(data);
- if (error)
- goto err_free_mem;
-
__set_bit(EV_ABS, input_dev->evbit);
__set_bit(EV_KEY, input_dev->evbit);
__set_bit(BTN_TOUCH, input_dev->keybit);
@@ -1483,10 +3107,13 @@ static int mxt_probe(struct i2c_client *client,
0, 255, 0, 0);
/* For multi touch */
- num_mt_slots = data->T9_reportid_max - data->T9_reportid_min + 1;
+ num_mt_slots = data->num_touchids + data->num_stylusids;
error = input_mt_init_slots(input_dev, num_mt_slots, mt_flags);
- if (error)
- goto err_free_object;
+ if (error) {
+ dev_err(dev, "Error %d initialising slots\n", error);
+ goto err_free_mem;
+ }
+
input_set_abs_params(input_dev, ABS_MT_TOUCH_MAJOR,
0, MXT_MAX_AREA, 0, 0);
input_set_abs_params(input_dev, ABS_MT_POSITION_X,
@@ -1495,47 +3122,120 @@ static int mxt_probe(struct i2c_client *client,
0, data->max_y, 0, 0);
input_set_abs_params(input_dev, ABS_MT_PRESSURE,
0, 255, 0, 0);
+ input_set_abs_params(input_dev, ABS_MT_ORIENTATION,
+ 0, 255, 0, 0);
+
+ /* For T63 active stylus */
+ if (data->T63_reportid_min) {
+ input_set_capability(input_dev, EV_KEY, BTN_STYLUS);
+ input_set_capability(input_dev, EV_KEY, BTN_STYLUS2);
+ input_set_abs_params(input_dev, ABS_MT_TOOL_TYPE,
+ 0, MT_TOOL_MAX, 0, 0);
+ }
+
+ /* For T15 key array */
+ if (data->T15_reportid_min) {
+ data->t15_keystatus = 0;
+
+ for (i = 0; i < data->pdata->t15_num_keys; i++)
+ input_set_capability(input_dev, EV_KEY,
+ data->pdata->t15_keymap[i]);
+ }
input_set_drvdata(input_dev, data);
+
+ error = input_register_device(input_dev);
+ if (error) {
+ dev_err(dev, "Error %d registering input device\n", error);
+ goto err_free_mem;
+ }
+
+ data->input_dev = input_dev;
+
+ return 0;
+
+err_free_mem:
+ input_free_device(input_dev);
+ return error;
+}
+
+static int mxt_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct mxt_data *data;
+ int error;
+
+ data = kzalloc(sizeof(struct mxt_data), GFP_KERNEL);
+ if (!data) {
+ dev_err(&client->dev, "Failed to allocate memory\n");
+ return -ENOMEM;
+ }
+
+ snprintf(data->phys, sizeof(data->phys), "i2c-%u-%04x/input0",
+ client->adapter->nr, client->addr);
+
+ data->client = client;
+ data->irq = client->irq;
i2c_set_clientdata(client, data);
- error = request_threaded_irq(client->irq, NULL, mxt_interrupt,
- pdata->irqflags | IRQF_ONESHOT,
+ error = mxt_handle_pdata(client, data);
+ if (error)
+ goto err_free_mem;
+
+ init_completion(&data->bl_completion);
+ init_completion(&data->reset_completion);
+ init_completion(&data->crc_completion);
+ mutex_init(&data->debug_msg_lock);
+
+ error = request_threaded_irq(data->irq, NULL, mxt_interrupt,
+ data->pdata->irqflags | IRQF_ONESHOT,
client->name, data);
if (error) {
dev_err(&client->dev, "Failed to register interrupt\n");
- goto err_free_object;
+ goto err_free_pdata;
}
- error = mxt_make_highchg(data);
- if (error)
- goto err_free_irq;
+ mxt_probe_regulators(data);
- error = input_register_device(input_dev);
- if (error) {
- dev_err(&client->dev, "Error %d registering input device\n",
- error);
+ disable_irq(data->irq);
+
+ error = mxt_initialize(data);
+ if (error)
goto err_free_irq;
- }
error = sysfs_create_group(&client->dev.kobj, &mxt_attr_group);
if (error) {
dev_err(&client->dev, "Failure %d creating sysfs group\n",
error);
- goto err_unregister_device;
+ goto err_free_object;
+ }
+
+ sysfs_bin_attr_init(&data->mem_access_attr);
+ data->mem_access_attr.attr.name = "mem_access";
+ data->mem_access_attr.attr.mode = S_IRUGO | S_IWUSR;
+ data->mem_access_attr.read = mxt_mem_access_read;
+ data->mem_access_attr.write = mxt_mem_access_write;
+ data->mem_access_attr.size = data->mem_size;
+
+ if (sysfs_create_bin_file(&client->dev.kobj,
+ &data->mem_access_attr) < 0) {
+ dev_err(&client->dev, "Failed to create %s\n",
+ data->mem_access_attr.attr.name);
+ goto err_remove_sysfs_group;
}
return 0;
-err_unregister_device:
- input_unregister_device(input_dev);
- input_dev = NULL;
-err_free_irq:
- free_irq(client->irq, data);
+err_remove_sysfs_group:
+ sysfs_remove_group(&client->dev.kobj, &mxt_attr_group);
err_free_object:
- kfree(data->object_table);
+ mxt_free_object_table(data);
+err_free_irq:
+ free_irq(data->irq, data);
+err_free_pdata:
+ if (!dev_get_platdata(&data->client->dev))
+ kfree(data->pdata);
err_free_mem:
- input_free_device(input_dev);
kfree(data);
return error;
}
@@ -1544,10 +3244,17 @@ static int mxt_remove(struct i2c_client *client)
{
struct mxt_data *data = i2c_get_clientdata(client);
+ if (data->mem_access_attr.attr.name)
+ sysfs_remove_bin_file(&client->dev.kobj,
+ &data->mem_access_attr);
+
sysfs_remove_group(&client->dev.kobj, &mxt_attr_group);
free_irq(data->irq, data);
- input_unregister_device(data->input_dev);
- kfree(data->object_table);
+ regulator_put(data->reg_avdd);
+ regulator_put(data->reg_vdd);
+ mxt_free_object_table(data);
+ if (!dev_get_platdata(&data->client->dev))
+ kfree(data->pdata);
kfree(data);
return 0;
@@ -1576,8 +3283,6 @@ static int mxt_resume(struct device *dev)
struct mxt_data *data = i2c_get_clientdata(client);
struct input_dev *input_dev = data->input_dev;
- mxt_soft_reset(data);
-
mutex_lock(&input_dev->mutex);
if (input_dev->users)
@@ -1605,6 +3310,7 @@ static struct i2c_driver mxt_driver = {
.name = "atmel_mxt_ts",
.owner = THIS_MODULE,
.pm = &mxt_pm_ops,
+ .of_match_table = of_match_ptr(mxt_dt_match),
},
.probe = mxt_probe,
.remove = mxt_remove,
diff --git a/include/linux/i2c/atmel_mxt_ts.h b/include/linux/i2c/atmel_mxt_ts.h
index 3891dc1..bc74c3f 100644
--- a/include/linux/i2c/atmel_mxt_ts.h
+++ b/include/linux/i2c/atmel_mxt_ts.h
@@ -17,12 +17,14 @@
/* The platform data for the Atmel maXTouch touchscreen driver */
struct mxt_platform_data {
- const u8 *config;
- size_t config_length;
- u32 config_crc;
unsigned long irqflags;
u8 t19_num_keys;
const unsigned int *t19_keymap;
+ int t15_num_keys;
+ const unsigned int *t15_keymap;
+ unsigned long gpio_reset;
+ const char *cfg_name;
+ const char *input_name;
};
#endif /* __LINUX_ATMEL_MXT_TS_H */
--
1.8.3.1
^ permalink raw reply related
* [PATCH v2 1/2] Loosen seams to allow support of other keyboards
From: Jamie Lentin @ 2014-06-10 22:24 UTC (permalink / raw)
To: Jiri Kosina; +Cc: linux-input, linux-kernel, Jamie Lentin
In-Reply-To: <1402439094-25464-1-git-send-email-jm@lentin.co.uk>
Signed-off-by: Jamie Lentin <jm@lentin.co.uk>
---
drivers/hid/hid-lenovo-tpkbd.c | 33 ++++++++++++++++++++++++---------
1 file changed, 24 insertions(+), 9 deletions(-)
diff --git a/drivers/hid/hid-lenovo-tpkbd.c b/drivers/hid/hid-lenovo-tpkbd.c
index 2d25b6c..3bec9f5 100644
--- a/drivers/hid/hid-lenovo-tpkbd.c
+++ b/drivers/hid/hid-lenovo-tpkbd.c
@@ -1,5 +1,6 @@
/*
- * HID driver for Lenovo ThinkPad USB Keyboard with TrackPoint
+ * HID driver for Lenovo:-
+ * * ThinkPad USB Keyboard with TrackPoint
*
* Copyright (c) 2012 Bernhard Seibold
*/
@@ -35,7 +36,7 @@ struct tpkbd_data_pointer {
#define map_key_clear(c) hid_map_usage_clear(hi, usage, bit, max, EV_KEY, (c))
-static int tpkbd_input_mapping(struct hid_device *hdev,
+static int tpkbd_input_mapping_tp(struct hid_device *hdev,
struct hid_input *hi, struct hid_field *field,
struct hid_usage *usage, unsigned long **bit, int *max)
{
@@ -48,6 +49,15 @@ static int tpkbd_input_mapping(struct hid_device *hdev,
return 0;
}
+static int tpkbd_input_mapping(struct hid_device *hdev,
+ struct hid_input *hi, struct hid_field *field,
+ struct hid_usage *usage, unsigned long **bit, int *max)
+{
+ if (hdev->product == USB_DEVICE_ID_LENOVO_TPKBD)
+ return tpkbd_input_mapping_tp(hdev, hi, field, usage, bit, max);
+ return 0;
+}
+
#undef map_key_clear
static int tpkbd_features_set(struct hid_device *hdev)
@@ -338,6 +348,11 @@ static int tpkbd_probe_tp(struct hid_device *hdev)
char *name_mute, *name_micmute;
int i;
+ /* Ignore unless tpkbd_input_mapping_tp marked it as a pointer */
+ if (!hid_get_drvdata(hdev))
+ return 0;
+ hid_set_drvdata(hdev, NULL);
+
/* Validate required reports. */
for (i = 0; i < 4; i++) {
if (!hid_validate_values(hdev, HID_FEATURE_REPORT, 4, i, 1))
@@ -408,12 +423,9 @@ static int tpkbd_probe(struct hid_device *hdev,
goto err;
}
- if (hid_get_drvdata(hdev)) {
- hid_set_drvdata(hdev, NULL);
- ret = tpkbd_probe_tp(hdev);
- if (ret)
- goto err_hid;
- }
+ if (hdev->product == USB_DEVICE_ID_LENOVO_TPKBD
+ && tpkbd_probe_tp(hdev))
+ goto err_hid;
return 0;
err_hid:
@@ -437,7 +449,10 @@ static void tpkbd_remove_tp(struct hid_device *hdev)
static void tpkbd_remove(struct hid_device *hdev)
{
- if (hid_get_drvdata(hdev))
+ if (!hid_get_drvdata(hdev))
+ return;
+
+ if (hdev->product == USB_DEVICE_ID_LENOVO_TPKBD)
tpkbd_remove_tp(hdev);
hid_hw_stop(hdev);
--
2.0.0.rc2
^ permalink raw reply related
* [PATCH v2 2/2] Add support for Compact (Bluetooth|USB) keyboard with Trackpoint
From: Jamie Lentin @ 2014-06-10 22:24 UTC (permalink / raw)
To: Jiri Kosina; +Cc: linux-input, linux-kernel, Jamie Lentin
In-Reply-To: <1402439094-25464-1-git-send-email-jm@lentin.co.uk>
Signed-off-by: Jamie Lentin <jm@lentin.co.uk>
---
drivers/hid/hid-core.c | 2 +
drivers/hid/hid-ids.h | 2 +
drivers/hid/hid-lenovo-tpkbd.c | 203 +++++++++++++++++++++++++++++++++++++++++
include/linux/hid.h | 1 +
4 files changed, 208 insertions(+)
diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c
index 8a5384c..6591f4e 100644
--- a/drivers/hid/hid-core.c
+++ b/drivers/hid/hid-core.c
@@ -1738,6 +1738,8 @@ static const struct hid_device_id hid_have_special_driver[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_LCPOWER, USB_DEVICE_ID_LCPOWER_LC1000 ) },
#if IS_ENABLED(CONFIG_HID_LENOVO_TPKBD)
{ HID_USB_DEVICE(USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_TPKBD) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_CUSBKBD) },
+ { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_CBTKBD) },
#endif
{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_MX3000_RECEIVER) },
{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_S510_RECEIVER) },
diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h
index 6e12cd0..1763a07 100644
--- a/drivers/hid/hid-ids.h
+++ b/drivers/hid/hid-ids.h
@@ -551,6 +551,8 @@
#define USB_VENDOR_ID_LENOVO 0x17ef
#define USB_DEVICE_ID_LENOVO_TPKBD 0x6009
+#define USB_DEVICE_ID_LENOVO_CUSBKBD 0x6047
+#define USB_DEVICE_ID_LENOVO_CBTKBD 0x6048
#define USB_VENDOR_ID_LG 0x1fd2
#define USB_DEVICE_ID_LG_MULTITOUCH 0x0064
diff --git a/drivers/hid/hid-lenovo-tpkbd.c b/drivers/hid/hid-lenovo-tpkbd.c
index 3bec9f5..1671e7a 100644
--- a/drivers/hid/hid-lenovo-tpkbd.c
+++ b/drivers/hid/hid-lenovo-tpkbd.c
@@ -1,8 +1,11 @@
/*
* HID driver for Lenovo:-
* * ThinkPad USB Keyboard with TrackPoint
+ * * ThinkPad Compact Bluetooth Keyboard with TrackPoint
+ * * ThinkPad Compact USB Keyboard with TrackPoint
*
* Copyright (c) 2012 Bernhard Seibold
+ * Copyright (c) 2014 Jamie Lentin <jm@lentin.co.uk>
*/
/*
@@ -34,6 +37,10 @@ struct tpkbd_data_pointer {
int press_speed;
};
+struct tpcompactkbd_sc {
+ unsigned int fn_lock;
+};
+
#define map_key_clear(c) hid_map_usage_clear(hi, usage, bit, max, EV_KEY, (c))
static int tpkbd_input_mapping_tp(struct hid_device *hdev,
@@ -49,17 +56,154 @@ static int tpkbd_input_mapping_tp(struct hid_device *hdev,
return 0;
}
+static int tpcompactkbd_input_mapping(struct hid_device *hdev,
+ struct hid_input *hi, struct hid_field *field,
+ struct hid_usage *usage, unsigned long **bit, int *max)
+{
+ /* HID_UP_LNVENDOR = USB, HID_UP_MSVENDOR = BT */
+ if ((usage->hid & HID_USAGE_PAGE) == HID_UP_MSVENDOR ||
+ (usage->hid & HID_USAGE_PAGE) == HID_UP_LNVENDOR) {
+ set_bit(EV_REP, hi->input->evbit);
+ switch (usage->hid & HID_USAGE) {
+ case 0x00f1: /* Fn-F4: Mic mute */
+ map_key_clear(KEY_MICMUTE);
+ return 1;
+ case 0x00f2: /* Fn-F5: Brightness down */
+ map_key_clear(KEY_BRIGHTNESSDOWN);
+ return 1;
+ case 0x00f3: /* Fn-F6: Brightness up */
+ map_key_clear(KEY_BRIGHTNESSUP);
+ return 1;
+ case 0x00f4: /* Fn-F7: External display (projector) */
+ map_key_clear(KEY_SWITCHVIDEOMODE);
+ return 1;
+ case 0x00f5: /* Fn-F8: Wireless */
+ map_key_clear(KEY_WLAN);
+ return 1;
+ case 0x00f6: /* Fn-F9: Control panel */
+ map_key_clear(KEY_CONFIG);
+ return 1;
+ case 0x00f8: /* Fn-F11: View open applications (3 boxes) */
+ map_key_clear(KEY_FN_F11);
+ return 1;
+ case 0x00fa: /* Fn-Esc: Fn-lock toggle */
+ map_key_clear(KEY_FN_ESC);
+ return 1;
+ case 0x00fb: /* Fn-F12: Open My computer (6 boxes) USB-only */
+ /* NB: This mapping is invented in raw_event below */
+ map_key_clear(KEY_FILE);
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
static int tpkbd_input_mapping(struct hid_device *hdev,
struct hid_input *hi, struct hid_field *field,
struct hid_usage *usage, unsigned long **bit, int *max)
{
if (hdev->product == USB_DEVICE_ID_LENOVO_TPKBD)
return tpkbd_input_mapping_tp(hdev, hi, field, usage, bit, max);
+ if (hdev->product == USB_DEVICE_ID_LENOVO_CUSBKBD)
+ return tpcompactkbd_input_mapping(hdev, hi, field, usage, bit, max);
+ if (hdev->product == USB_DEVICE_ID_LENOVO_CBTKBD)
+ return tpcompactkbd_input_mapping(hdev, hi, field, usage, bit, max);
return 0;
}
#undef map_key_clear
+/* Send a config command to the keyboard */
+static int tpcompactkbd_send_cmd(struct hid_device *hdev,
+ unsigned char byte2, unsigned char byte3)
+{
+ int ret;
+ unsigned char buf[] = {0x18, byte2, byte3};
+ unsigned char report_type = HID_OUTPUT_REPORT;
+
+ /* The USB keyboard accepts commands via SET_FEATURE */
+ if (hdev->product == USB_DEVICE_ID_LENOVO_CUSBKBD) {
+ buf[0] = 0x13;
+ report_type = HID_FEATURE_REPORT;
+ }
+
+ ret = hdev->hid_output_raw_report(hdev, buf, sizeof(buf), report_type);
+ return ret < 0 ? ret : 0; /* BT returns 0, USB returns sizeof(buf) */
+}
+
+static void tpcompactkbd_features_set(struct hid_device *hdev)
+{
+ struct tpcompactkbd_sc *tpcsc = hid_get_drvdata(hdev);
+
+ if (tpcompactkbd_send_cmd(hdev, 0x05, tpcsc->fn_lock ? 0x01 : 0x00))
+ hid_err(hdev, "Fn-lock setting failed\n");
+}
+
+static ssize_t tpcompactkbd_fn_lock_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct hid_device *hdev = container_of(dev, struct hid_device, dev);
+ struct tpcompactkbd_sc *tpcsc = hid_get_drvdata(hdev);
+
+ return snprintf(buf, PAGE_SIZE, "%u\n", tpcsc->fn_lock);
+}
+
+static ssize_t tpcompactkbd_fn_lock_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf,
+ size_t count)
+{
+ struct hid_device *hdev = container_of(dev, struct hid_device, dev);
+ struct tpcompactkbd_sc *tpcsc = hid_get_drvdata(hdev);
+ int value;
+
+ if (kstrtoint(buf, 10, &value))
+ return -EINVAL;
+ if (value < 0 || value > 1)
+ return -EINVAL;
+
+ tpcsc->fn_lock = value;
+ tpcompactkbd_features_set(hdev);
+
+ return count;
+}
+
+static struct device_attribute dev_attr_pointer_fn_lock =
+ __ATTR(fn_lock, S_IWUSR | S_IRUGO,
+ tpcompactkbd_fn_lock_show,
+ tpcompactkbd_fn_lock_store);
+
+static struct attribute *tpcompactkbd_attributes[] = {
+ &dev_attr_pointer_fn_lock.attr,
+ NULL
+};
+
+static const struct attribute_group tpcompactkbd_attr_group = {
+ .attrs = tpcompactkbd_attributes,
+};
+
+static int tpkbd_raw_event(struct hid_device *hdev,
+ struct hid_report *report, u8 *data, int size)
+{
+ /*
+ * USB keyboard's Fn-F12 report holds down many other keys, and it's
+ * own key is outside the usage page range. Remove extra keypresses and
+ * remap to inside usage page.
+ */
+ if (unlikely(hdev->product == USB_DEVICE_ID_LENOVO_CUSBKBD
+ && size == 3
+ && data[0] == 0x15
+ && data[1] == 0x94
+ && data[2] == 0x01)) {
+ data[1] = 0x0;
+ data[2] = 0x4;
+ }
+
+ return 0;
+}
+
static int tpkbd_features_set(struct hid_device *hdev)
{
struct hid_report *report;
@@ -406,6 +550,46 @@ static int tpkbd_probe_tp(struct hid_device *hdev)
return 0;
}
+static int tpcompactkbd_probe(struct hid_device *hdev,
+ const struct hid_device_id *id)
+{
+ int ret;
+ struct tpcompactkbd_sc *tpcsc;
+
+ tpcsc = devm_kzalloc(&hdev->dev, sizeof(*tpcsc), GFP_KERNEL);
+ if (tpcsc == NULL) {
+ hid_err(hdev, "can't alloc keyboard descriptor\n");
+ return -ENOMEM;
+ }
+ hid_set_drvdata(hdev, tpcsc);
+
+ /* All the custom action happens on the mouse device for USB */
+ if (hdev->product == USB_DEVICE_ID_LENOVO_CUSBKBD
+ && hdev->type != HID_TYPE_USBMOUSE) {
+ pr_debug("Ignoring keyboard half of device\n");
+ return 0;
+ }
+
+ /*
+ * Tell the keyboard a driver understands it, and turn F7, F9, F11 into
+ * regular keys
+ */
+ ret = tpcompactkbd_send_cmd(hdev, 0x01, 0x03);
+ if (ret)
+ hid_warn(hdev, "Failed to switch F7/9/11 into regular keys\n");
+
+ /* Turn Fn-Lock on by default */
+ tpcsc->fn_lock = 1;
+ tpcompactkbd_features_set(hdev);
+
+ if (sysfs_create_group(&hdev->dev.kobj,
+ &tpcompactkbd_attr_group)) {
+ hid_warn(hdev, "Could not create sysfs group\n");
+ }
+
+ return 0;
+}
+
static int tpkbd_probe(struct hid_device *hdev,
const struct hid_device_id *id)
{
@@ -426,6 +610,12 @@ static int tpkbd_probe(struct hid_device *hdev,
if (hdev->product == USB_DEVICE_ID_LENOVO_TPKBD
&& tpkbd_probe_tp(hdev))
goto err_hid;
+ if (hdev->product == USB_DEVICE_ID_LENOVO_CUSBKBD
+ && tpcompactkbd_probe(hdev, id))
+ goto err_hid;
+ if (hdev->product == USB_DEVICE_ID_LENOVO_CBTKBD
+ && tpcompactkbd_probe(hdev, id))
+ goto err_hid;
return 0;
err_hid:
@@ -447,6 +637,12 @@ static void tpkbd_remove_tp(struct hid_device *hdev)
hid_set_drvdata(hdev, NULL);
}
+static void tpcompactkbd_remove(struct hid_device *hdev)
+{
+ sysfs_remove_group(&hdev->dev.kobj,
+ &tpcompactkbd_attr_group);
+}
+
static void tpkbd_remove(struct hid_device *hdev)
{
if (!hid_get_drvdata(hdev))
@@ -454,12 +650,18 @@ static void tpkbd_remove(struct hid_device *hdev)
if (hdev->product == USB_DEVICE_ID_LENOVO_TPKBD)
tpkbd_remove_tp(hdev);
+ if (hdev->product == USB_DEVICE_ID_LENOVO_CUSBKBD)
+ tpcompactkbd_remove(hdev);
+ if (hdev->product == USB_DEVICE_ID_LENOVO_CBTKBD)
+ tpcompactkbd_remove(hdev);
hid_hw_stop(hdev);
}
static const struct hid_device_id tpkbd_devices[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_TPKBD) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_CUSBKBD) },
+ { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_CBTKBD) },
{ }
};
@@ -471,6 +673,7 @@ static struct hid_driver tpkbd_driver = {
.input_mapping = tpkbd_input_mapping,
.probe = tpkbd_probe,
.remove = tpkbd_remove,
+ .raw_event = tpkbd_raw_event,
};
module_hid_driver(tpkbd_driver);
diff --git a/include/linux/hid.h b/include/linux/hid.h
index 31b9d29..ed23d6a 100644
--- a/include/linux/hid.h
+++ b/include/linux/hid.h
@@ -167,6 +167,7 @@ struct hid_item {
#define HID_UP_MSVENDOR 0xff000000
#define HID_UP_CUSTOM 0x00ff0000
#define HID_UP_LOGIVENDOR 0xffbc0000
+#define HID_UP_LNVENDOR 0xffa00000
#define HID_UP_SENSOR 0x00200000
#define HID_USAGE 0x0000ffff
--
2.0.0.rc2
^ permalink raw reply related
* [PATCH v2 0/2] Add support for Lenovo Compact Keyboard
From: Jamie Lentin @ 2014-06-10 22:24 UTC (permalink / raw)
To: Jiri Kosina; +Cc: linux-input, linux-kernel, Jamie Lentin
This patchset follows on from my previous attempt[1] to add support for
these keyboards from Lenovo.
Changes since v1:
* Merge driver into hid-lenovo-tpkbd.c instead of creating our own
driver for the hardware [Jiri Kosina]
* Remove key mappings which are now supported by standard
* Use KEY_FILE for Fn-F12 (opens My Computer on Windows)
* Support the USB variant as well as Bluetooth
* Expose the Fn Lock setting as a sysfs attribute instead of trying to
build a mechanism to toggle into the kernel
I got rid of the Fn-lock toggle code since it didn't play nicely with
the USB keyboard; from what I could ascertain, hid_output_raw_report
wants to sleep when sending a report to a USB keyboard, which isn't
allowed. The same was true when using an input handler.
A sysfs attribute is more flexible anyway, and one could trivially
script a user-space toggling mechanism around it. I suspect in
reality so long as Fn-Lock is left on nobody will really care what the
Fn-Lock key does :)
Applies and tested against 3.14.5.
Cheers,
[1] https://lkml.org/lkml/2014/3/25/535
Jamie Lentin (2):
Loosen seams to allow support of other keyboards
Add support for Compact (Bluetooth|USB) keyboard with Trackpoint
drivers/hid/hid-core.c | 2 +
drivers/hid/hid-ids.h | 2 +
drivers/hid/hid-lenovo-tpkbd.c | 236 +++++++++++++++++++++++++++++++++++++++--
include/linux/hid.h | 1 +
4 files changed, 232 insertions(+), 9 deletions(-)
--
2.0.0.rc2
^ permalink raw reply
* [PATCH] HID: drop remove Unicode non-breaking space from Kconfig
From: Brian Norris @ 2014-06-10 21:35 UTC (permalink / raw)
To: Jiri Kosina; +Cc: Brian Norris, Linux Kernel, linux-input
There is a UTF-8 non-breaking space character (0xc2 0xa0) after the "Y"
in "Say Y here". This is probably not intentional. Replace it with a
standard ASCII space (0x20).
If you can't see a difference here, I don't blame you :)
Signed-off-by: Brian Norris <computersforpeace@gmail.com>
Cc: Jiri Kosina <jkosina@suse.cz>
Cc: linux-input@vger.kernel.org
---
drivers/hid/Kconfig | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
index 800c8b60f7a2..08c9693e6f83 100644
--- a/drivers/hid/Kconfig
+++ b/drivers/hid/Kconfig
@@ -785,7 +785,7 @@ config HID_XINMO
depends on HID
---help---
Support for Xin-Mo devices that are not fully compliant with the HID
- standard. Currently only supports the Xin-Mo Dual Arcade. Say Y here
+ standard. Currently only supports the Xin-Mo Dual Arcade. Say Y here
if you have a Xin-Mo Dual Arcade controller.
config HID_ZEROPLUS
--
1.9.1
--
To unsubscribe from this list: send the line "unsubscribe linux-input" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
^ permalink raw reply related
* [PATCH v3] leds: USB: HID: Add support for MSI GT683R led panels
From: Janne Kanniainen @ 2014-06-10 21:21 UTC (permalink / raw)
To: jkosina
Cc: cooloney, linux-kernel, linux-leds, linux-usb, linux-input, johan,
Janne Kanniainen
In-Reply-To: <CAEEjW06BnCiLyDDL82fjhFtwg5LyaGzKP+swzLFNKKwmZcvViQ@mail.gmail.com>
This driver adds support for USB controlled led panels that exists in MSI GT683R laptop
Changes in v2:
- sorted headers to alphabetic order
- using devm_kzalloc
- using BIT(n)
- using usb_control_msg instead of usb_submit_urb
- removing unneeded code
Changes in v3:
- implemented as HID device
- some cleanups and bug fixes
Signed-off-by: Janne Kanniainen <janne.kanniainen@gmail.com>
---
drivers/hid/Kconfig | 6 ++
drivers/hid/Makefile | 1 +
drivers/hid/hid-core.c | 1 +
drivers/hid/hid-gt683r.c | 186 ++++++++++++++++++++++++++++++++++++++++
drivers/hid/hid-ids.h | 1 +
drivers/hid/usbhid/hid-quirks.c | 1 +
6 files changed, 196 insertions(+)
create mode 100644 drivers/hid/hid-gt683r.c
diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
index 7af9d0b..6ecc527 100644
--- a/drivers/hid/Kconfig
+++ b/drivers/hid/Kconfig
@@ -210,6 +210,12 @@ config DRAGONRISE_FF
Say Y here if you want to enable force feedback support for DragonRise Inc.
game controllers.
+config HID_GT683R
+ tristate "LED support for the MSI GT683R"
+ depends on LEDS_CLASS && USB_HID
+ ---help---
+ Say Y here if you want to enable support for the MSI GT683R LEDS
+
config HID_EMS_FF
tristate "EMS Production Inc. force feedback support"
depends on HID
diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile
index fc712dd..111304e 100644
--- a/drivers/hid/Makefile
+++ b/drivers/hid/Makefile
@@ -44,6 +44,7 @@ obj-$(CONFIG_HID_CHICONY) += hid-chicony.o
obj-$(CONFIG_HID_CP2112) += hid-cp2112.o
obj-$(CONFIG_HID_CYPRESS) += hid-cypress.o
obj-$(CONFIG_HID_DRAGONRISE) += hid-dr.o
+obj-$(CONFIG_HID_GT683R) += hid-gt683r.o
obj-$(CONFIG_HID_EMS_FF) += hid-emsff.o
obj-$(CONFIG_HID_ELECOM) += hid-elecom.o
obj-$(CONFIG_HID_ELO) += hid-elo.o
diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c
index da52279..ec88fdb 100644
--- a/drivers/hid/hid-core.c
+++ b/drivers/hid/hid-core.c
@@ -1827,6 +1827,7 @@ static const struct hid_device_id hid_have_special_driver[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_WIRELESS_OPTICAL_DESKTOP_3_0) },
{ HID_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_OFFICE_KB) },
{ HID_USB_DEVICE(USB_VENDOR_ID_MONTEREY, USB_DEVICE_ID_GENIUS_KB29E) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_MSI, USB_DEVICE_ID_MSI_GT683R_LED_PANEL) },
{ HID_USB_DEVICE(USB_VENDOR_ID_NTRIG, USB_DEVICE_ID_NTRIG_TOUCH_SCREEN) },
{ HID_USB_DEVICE(USB_VENDOR_ID_NTRIG, USB_DEVICE_ID_NTRIG_TOUCH_SCREEN_1) },
{ HID_USB_DEVICE(USB_VENDOR_ID_NTRIG, USB_DEVICE_ID_NTRIG_TOUCH_SCREEN_2) },
diff --git a/drivers/hid/hid-gt683r.c b/drivers/hid/hid-gt683r.c
new file mode 100644
index 0000000..4baaa36
--- /dev/null
+++ b/drivers/hid/hid-gt683r.c
@@ -0,0 +1,186 @@
+/*
+ * MSI GT683R led driver
+ *
+ * Copyright (c) 2014 Janne Kanniainen <janne.kanniainen@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#include <linux/hid.h>
+#include <linux/kernel.h>
+#include <linux/leds.h>
+#include <linux/module.h>
+
+#include "hid-ids.h"
+
+#define GT683R_LED_BACK BIT(0)
+#define GT683R_LED_SIDE BIT(1)
+#define GT683R_LED_FRONT BIT(2)
+#define GT683R_LED_ALL (GT683R_LED_BACK | GT683R_LED_SIDE | GT683R_LED_FRONT)
+
+#define GT683R_LED_OFF 0
+#define GT683R_LED_AUDIO 2
+#define GT683R_LED_BREATHING 3
+#define GT683R_LED_NORMAL 5
+
+#define GT683R_BUFFER_SIZE 8
+
+struct gt683r_led {
+ struct hid_device *hdev;
+ struct led_classdev led_dev;
+ struct mutex lock;
+ struct work_struct work;
+ enum led_brightness brightness;
+};
+
+static const struct hid_device_id gt683r_led_id[] = {
+ { HID_USB_DEVICE(USB_VENDOR_ID_MSI, USB_DEVICE_ID_MSI_GT683R_LED_PANEL) },
+ { }
+};
+
+static const char gt683r_led_select_leds[GT683R_BUFFER_SIZE] = { 0x01, 0x02, 0x30, 0x00,
+ 0x00, 0x00, 0x00, 0x00 };
+static const char gt683r_led_select_type[GT683R_BUFFER_SIZE] = { 0x01, 0x02, 0x20, 0x00,
+ 0x01, 0x00, 0x00, 0x00 };
+
+static void gt683r_brightness_set(struct led_classdev *led_cdev,
+ enum led_brightness brightness)
+{
+ struct gt683r_led *led =
+ container_of(led_cdev, struct gt683r_led, led_dev);
+
+ led->brightness = brightness;
+
+ schedule_work(&led->work);
+}
+
+static int gt683r_led_snd_msg(struct gt683r_led *led, char *msg)
+{
+ int ret;
+
+ ret = hid_hw_raw_request(led->hdev, 0x01, msg, GT683R_BUFFER_SIZE,
+ HID_FEATURE_REPORT, HID_REQ_SET_REPORT);
+ if (ret < 0) {
+ hid_err(led->hdev,
+ "failed to send set report request: %i\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static void gt683r_led_set(struct gt683r_led *led, char type)
+{
+ char *buffer;
+
+ buffer = kmalloc(GT683R_BUFFER_SIZE, GFP_KERNEL);
+ if (!buffer) {
+ hid_err(led->hdev, "can\'t allocate buffer\n");
+ return;
+ }
+
+ memcpy(buffer, gt683r_led_select_leds, GT683R_BUFFER_SIZE);
+ buffer[3] = GT683R_LED_ALL;
+ gt683r_led_snd_msg(led, buffer);
+
+ memcpy(buffer, gt683r_led_select_type, GT683R_BUFFER_SIZE);
+ buffer[3] = type;
+ gt683r_led_snd_msg(led, buffer);
+
+ kfree(buffer);
+}
+
+static void gt683r_led_work(struct work_struct *work)
+{
+ struct gt683r_led *led =
+ container_of(work, struct gt683r_led, work);
+
+ mutex_lock(&led->lock);
+
+ if (led->brightness)
+ gt683r_led_set(led, GT683R_LED_NORMAL);
+ else
+ gt683r_led_set(led, GT683R_LED_OFF);
+
+ mutex_unlock(&led->lock);
+}
+
+static struct led_classdev gt683r_led_dev = {
+ .name = "gt683r-led",
+ .brightness_set = gt683r_brightness_set,
+ .max_brightness = 1,
+ .flags = LED_CORE_SUSPENDRESUME,
+};
+
+static int gt683r_led_probe(struct hid_device *hdev,
+ const struct hid_device_id *id)
+{
+ int ret;
+ struct gt683r_led *led;
+
+ led = devm_kzalloc(&hdev->dev, sizeof(struct gt683r_led), GFP_KERNEL);
+ if (!led)
+ return -ENOMEM;
+
+ led->hdev = hdev;
+ hid_set_drvdata(hdev, led);
+
+ ret = hid_parse(hdev);
+ if (ret) {
+ hid_err(hdev, "hid parsing failed\n");
+ goto fail;
+ }
+
+ ret = hid_hw_start(hdev, HID_CONNECT_HIDRAW);
+ if (ret) {
+ hid_err(hdev, "hw start failed\n");
+ goto fail;
+ }
+
+ led->led_dev = gt683r_led_dev;
+ ret = led_classdev_register(&hdev->dev, &led->led_dev);
+ if (ret) {
+ hid_err(hdev, "could not register led device\n");
+ goto stop;
+ }
+
+ mutex_init(&led->lock);
+ INIT_WORK(&led->work, gt683r_led_work);
+
+ return 0;
+stop:
+ hid_hw_stop(hdev);
+fail:
+ return ret;
+}
+
+static void gt683r_led_remove(struct hid_device *hdev)
+{
+ struct gt683r_led *led = hid_get_drvdata(hdev);
+
+ led_classdev_unregister(&led->led_dev);
+ cancel_work_sync(&led->work);
+ hid_hw_stop(hdev);
+}
+
+static struct hid_driver gt683r_led_driver = {
+ .probe = gt683r_led_probe,
+ .remove = gt683r_led_remove,
+ .name = "gt683r_led",
+ .id_table = gt683r_led_id,
+};
+
+module_hid_driver(gt683r_led_driver);
+
+MODULE_AUTHOR("Janne Kanniainen");
+MODULE_DESCRIPTION("MSI GT683R led driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h
index 34bb220..e2097f7 100644
--- a/drivers/hid/hid-ids.h
+++ b/drivers/hid/hid-ids.h
@@ -641,6 +641,7 @@
#define USB_DEVICE_ID_GENIUS_KB29E 0x3004
#define USB_VENDOR_ID_MSI 0x1770
+#define USB_DEVICE_ID_MSI_GT683R_LED_PANEL 0xff00
#define USB_DEVICE_ID_MSI_GX680R_LED_PANEL 0xff00
#define USB_VENDOR_ID_NATIONAL_SEMICONDUCTOR 0x0400
diff --git a/drivers/hid/usbhid/hid-quirks.c b/drivers/hid/usbhid/hid-quirks.c
index 8e4ddb3..927e7be 100644
--- a/drivers/hid/usbhid/hid-quirks.c
+++ b/drivers/hid/usbhid/hid-quirks.c
@@ -73,6 +73,7 @@ static const struct hid_blacklist {
{ USB_VENDOR_ID_FORMOSA, USB_DEVICE_ID_FORMOSA_IR_RECEIVER, HID_QUIRK_NO_INIT_REPORTS },
{ USB_VENDOR_ID_FREESCALE, USB_DEVICE_ID_FREESCALE_MX28, HID_QUIRK_NOGET },
{ USB_VENDOR_ID_MGE, USB_DEVICE_ID_MGE_UPS, HID_QUIRK_NOGET },
+ { USB_VENDOR_ID_MSI, USB_DEVICE_ID_MSI_GT683R_LED_PANEL, HID_QUIRK_NO_INIT_REPORTS },
{ USB_VENDOR_ID_MSI, USB_DEVICE_ID_MSI_GX680R_LED_PANEL, HID_QUIRK_NO_INIT_REPORTS },
{ USB_VENDOR_ID_NEXIO, USB_DEVICE_ID_NEXIO_MULTITOUCH_PTI0750, HID_QUIRK_NO_INIT_REPORTS },
{ USB_VENDOR_ID_NOVATEK, USB_DEVICE_ID_NOVATEK_MOUSE, HID_QUIRK_NO_INIT_REPORTS },
--
1.9.2
^ permalink raw reply related
* Re: [PATCH v2] leds: USB: Add support for MSI GT683R led panels
From: Janne Kanniainen @ 2014-06-10 21:10 UTC (permalink / raw)
To: Johan Hovold
Cc: Bryan Wu, rpurdie, linux-kernel, linux-leds, linux-usb,
Jiri Kosina, linux-input
In-Reply-To: <20140609114222.GC9307@localhost>
>> > Where did you get these (HID report) values from by the way?
>>
>> I got them by reverse engineering.
>
> Traffic sniffing?
>
Yes.
> In fact, it seems you can get away with not adding any locking here.
> Just do the (mutex) locking in gt683r_led_set (or gt683r_led_work).
Ok I will fix it.
Janne
^ 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