2014-04-07 17:39:55

by Benjamin Tissoires

[permalink] [raw]
Subject: [PATCH] HID: rmi: introduce RMI driver for Synaptics touchpads

This driver add support for RMI4 over USB or I2C.
The current state is that it uses its own RMI4 implementation, but once
RMI4 is merged upstream, the driver will be a transport driver for the
RMI4 library.

Signed-off-by: Andrew Duggan <[email protected]>
Signed-off-by: Benjamin Tissoires <[email protected]>
---
drivers/hid/Kconfig | 8 +
drivers/hid/Makefile | 1 +
drivers/hid/hid-core.c | 2 +
drivers/hid/hid-rmi.c | 889 +++++++++++++++++++++++++++++++++++++++++++++++++
include/linux/hid.h | 2 +
5 files changed, 902 insertions(+)
create mode 100644 drivers/hid/hid-rmi.c

diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
index 7af9d0b..762f15d 100644
--- a/drivers/hid/Kconfig
+++ b/drivers/hid/Kconfig
@@ -657,6 +657,14 @@ config HID_SUNPLUS
---help---
Support for Sunplus wireless desktop.

+config HID_RMI
+ tristate "Synaptics RMI4 device support"
+ depends on HID
+ ---help---
+ Support for Synaptics RMI4 touchpads.
+ Say Y here if you have a Synaptics RMI4 touchpads over i2c-hid or usbhid
+ and want support for its special functionalities.
+
config HID_GREENASIA
tristate "GreenAsia (Product ID 0x12) game controller support"
depends on HID
diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile
index fc712dd..a6fa6ba 100644
--- a/drivers/hid/Makefile
+++ b/drivers/hid/Makefile
@@ -97,6 +97,7 @@ obj-$(CONFIG_HID_ROCCAT) += hid-roccat.o hid-roccat-common.o \
hid-roccat-arvo.o hid-roccat-isku.o hid-roccat-kone.o \
hid-roccat-koneplus.o hid-roccat-konepure.o hid-roccat-kovaplus.o \
hid-roccat-lua.o hid-roccat-pyra.o hid-roccat-ryos.o hid-roccat-savu.o
+obj-$(CONFIG_HID_RMI) += hid-rmi.o
obj-$(CONFIG_HID_SAITEK) += hid-saitek.o
obj-$(CONFIG_HID_SAMSUNG) += hid-samsung.o
obj-$(CONFIG_HID_SMARTJOYPLUS) += hid-sjoy.o
diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c
index abaa0a5..c97ece8 100644
--- a/drivers/hid/hid-core.c
+++ b/drivers/hid/hid-core.c
@@ -1879,6 +1879,8 @@ static const struct hid_device_id hid_have_special_driver[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_VAIO_VGP_MOUSE) },
{ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_SRWS1) },
{ HID_USB_DEVICE(USB_VENDOR_ID_SUNPLUS, USB_DEVICE_ID_SUNPLUS_WDESKTOP) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_SYNAPTICS, HID_ANY_ID) },
+ { HID_I2C_DEVICE(USB_VENDOR_ID_SYNAPTICS, HID_ANY_ID) },
{ HID_USB_DEVICE(USB_VENDOR_ID_THINGM, USB_DEVICE_ID_BLINK1) },
{ HID_USB_DEVICE(USB_VENDOR_ID_THRUSTMASTER, 0xb300) },
{ HID_USB_DEVICE(USB_VENDOR_ID_THRUSTMASTER, 0xb304) },
diff --git a/drivers/hid/hid-rmi.c b/drivers/hid/hid-rmi.c
new file mode 100644
index 0000000..699d631
--- /dev/null
+++ b/drivers/hid/hid-rmi.c
@@ -0,0 +1,889 @@
+/*
+ * Copyright (c) 2013 Andrew Duggan <[email protected]>
+ * Copyright (c) 2013 Synaptics Incorporated
+ * Copyright (c) 2014 Benjamin Tissoires <[email protected]>
+ * Copyright (c) 2014 Red Hat, Inc
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ */
+
+#include <linux/kernel.h>
+#include <linux/hid.h>
+#include <linux/input.h>
+#include <linux/input/mt.h>
+#include <linux/module.h>
+#include <linux/pm.h>
+#include <linux/slab.h>
+#include <linux/wait.h>
+#include <linux/sched.h>
+#include "hid-ids.h"
+
+#define RMI_MOUSE_REPORT_ID 0x01 /* Mouse emulation Report */
+#define RMI_WRITE_REPORT_ID 0x09 /* Output Report */
+#define RMI_READ_ADDR_REPORT_ID 0x0a /* Output Report */
+#define RMI_READ_DATA_REPORT_ID 0x0b /* Input Report */
+#define RMI_ATTN_REPORT_ID 0x0c /* Input Report */
+#define RMI_SET_RMI_MODE_REPORT_ID 0x0f /* Feature Report */
+
+/* flags */
+#define RMI_READ_REQUEST_PENDING BIT(0)
+#define RMI_READ_DATA_PENDING BIT(1)
+#define RMI_STARTED BIT(2)
+
+enum rmi_mode_type {
+ RMI_MODE_OFF = 0,
+ RMI_MODE_ATTN_REPORTS = 1,
+ RMI_MODE_NO_PACKED_ATTN_REPORTS = 2,
+};
+
+struct rmi_function {
+ unsigned page; /* page of the function */
+ u16 query_base_addr; /* base address for queries */
+ u16 command_base_addr; /* base address for commands */
+ u16 control_base_addr; /* base address for controls */
+ u16 data_base_addr; /* base address for datas */
+ unsigned int interrupt_base; /* cross-function interrupt number
+ * (uniq in the device)*/
+ unsigned int interrupt_count; /* number of interrupts */
+ unsigned int report_size; /* size of a report */
+ unsigned long irq_mask; /* mask of the interrupts
+ * (to be applied against ATTN IRQ) */
+};
+
+/**
+ * struct rmi_data - stores information for hid communication
+ *
+ * @page_mutex: Locks current page to avoid changing pages in unexpected ways.
+ * @page: Keeps track of the current virtual page
+ *
+ * @wait: Used for waiting for read data
+ *
+ * @writeReport: output buffer when writing RMI registers
+ * @readReport: input buffer when reading RMI registers
+ *
+ * @input_report_size: size of an input report (advertised by HID)
+ * @output_report_size: size of an output report (advertised by HID)
+ *
+ * @flags: flags for the current device (started, reading, etc...)
+ *
+ * @f11: placeholder of internal RMI function F11 description
+ * @f30: placeholder of internal RMI function F30 description
+ *
+ * @max_fingers: maximum finger count reported by the device
+ * @max_x: maximum x value reported by the device
+ * @max_y: maximum y value reported by the device
+ *
+ * @gpio_led_count: count of GPIOs + LEDs reported by F30
+ * @button_count: actual physical buttons count
+ * @button_mask: button mask used to decode GPIO ATTN reports
+ * @button_state_mask: pull state of the buttons
+ *
+ * @input: pointer to the kernel input device
+ *
+ * @reset_work: worker which will be called in case of a mouse report
+ * @hdev: pointer to the struct hid_device
+ */
+struct rmi_data {
+ struct mutex page_mutex;
+ int page;
+
+ wait_queue_head_t wait;
+
+ u8 *writeReport;
+ u8 *readReport;
+
+ int input_report_size;
+ int output_report_size;
+
+ unsigned long flags;
+
+ struct rmi_function f11;
+ struct rmi_function f30;
+
+ unsigned int max_fingers;
+ unsigned int max_x;
+ unsigned int max_y;
+ unsigned int x_size_mm;
+ unsigned int y_size_mm;
+
+ unsigned int gpio_led_count;
+ unsigned int button_count;
+ unsigned long button_mask;
+ unsigned long button_state_mask;
+
+ struct input_dev *input;
+
+ struct work_struct reset_work;
+ struct hid_device *hdev;
+};
+
+#define RMI_PAGE(addr) (((addr) >> 8) & 0xff)
+
+static int rmi_write_report(struct hid_device *hdev, u8 *report, int len);
+
+/**
+ * rmi_set_page - Set RMI page
+ * @hdev: The pointer to the hid_device struct
+ * @page: The new page address.
+ *
+ * RMI devices have 16-bit addressing, but some of the physical
+ * implementations (like SMBus) only have 8-bit addressing. So RMI implements
+ * a page address at 0xff of every page so we can reliable page addresses
+ * every 256 registers.
+ *
+ * The page_mutex lock must be held when this function is entered.
+ *
+ * Returns zero on success, non-zero on failure.
+ */
+static int rmi_set_page(struct hid_device *hdev, u8 page)
+{
+ struct rmi_data *data = hid_get_drvdata(hdev);
+ int retval;
+
+ data->writeReport[0] = RMI_WRITE_REPORT_ID;
+ data->writeReport[1] = 1;
+ data->writeReport[2] = 0xFF;
+ data->writeReport[4] = page;
+
+ retval = rmi_write_report(hdev, data->writeReport,
+ data->output_report_size);
+ if (retval != data->output_report_size) {
+ dev_err(&hdev->dev,
+ "%s: set page failed: %d.", __func__, retval);
+ return retval;
+ }
+
+ data->page = page;
+ return 0;
+}
+
+static int rmi_set_mode(struct hid_device *hdev, u8 mode)
+{
+ int ret;
+ u8 txbuf[2] = {RMI_SET_RMI_MODE_REPORT_ID, mode};
+
+ ret = hid_hw_raw_request(hdev, RMI_SET_RMI_MODE_REPORT_ID, txbuf,
+ sizeof(txbuf), HID_FEATURE_REPORT, HID_REQ_SET_REPORT);
+ if (ret < 0) {
+ dev_err(&hdev->dev, "unable to set rmi mode to %d (%d)\n", mode,
+ ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int rmi_write_report(struct hid_device *hdev, u8 *report, int len)
+{
+ int ret;
+
+ ret = hid_hw_output_report(hdev, (void *)report, len);
+ if (ret < 0) {
+ dev_err(&hdev->dev, "failed to write hid report (%d)\n", ret);
+ return ret;
+ }
+
+ return ret;
+}
+
+static int rmi_read_block(struct hid_device *hdev, u16 addr, void *buf,
+ const int len)
+{
+ struct rmi_data *data = hid_get_drvdata(hdev);
+ int ret;
+ int bytes_read;
+ int bytes_needed;
+ int retries;
+ int read_input_count;
+
+ mutex_lock(&data->page_mutex);
+
+ if (RMI_PAGE(addr) != data->page) {
+ ret = rmi_set_page(hdev, RMI_PAGE(addr));
+ if (ret < 0)
+ goto exit;
+ }
+
+ for (retries = 5; retries > 0; retries--) {
+ data->writeReport[0] = RMI_READ_ADDR_REPORT_ID;
+ data->writeReport[1] = 0; /* old 1 byte read count */
+ data->writeReport[2] = addr & 0xFF;
+ data->writeReport[3] = (addr >> 8) & 0xFF;
+ data->writeReport[4] = len & 0xFF;
+ data->writeReport[5] = (len >> 8) & 0xFF;
+
+ set_bit(RMI_READ_REQUEST_PENDING, &data->flags);
+
+ ret = rmi_write_report(hdev, data->writeReport,
+ data->output_report_size);
+ if (ret != data->output_report_size) {
+ clear_bit(RMI_READ_REQUEST_PENDING, &data->flags);
+ dev_err(&hdev->dev,
+ "failed to write request output report (%d)\n",
+ ret);
+ goto exit;
+ }
+
+ bytes_read = 0;
+ bytes_needed = len;
+ while (bytes_read < len) {
+ if (!wait_event_timeout(data->wait,
+ test_bit(RMI_READ_DATA_PENDING, &data->flags),
+ msecs_to_jiffies(1000))) {
+ hid_warn(hdev, "%s: timeout elapsed\n",
+ __func__);
+ ret = -EAGAIN;
+ break;
+ }
+
+ read_input_count = data->readReport[1];
+ memcpy(buf + bytes_read, &data->readReport[2],
+ read_input_count < bytes_needed ?
+ read_input_count : bytes_needed);
+
+ bytes_read += read_input_count;
+ bytes_needed -= read_input_count;
+ clear_bit(RMI_READ_DATA_PENDING, &data->flags);
+ }
+
+ if (ret >= 0) {
+ ret = 0;
+ break;
+ }
+ }
+
+exit:
+ clear_bit(RMI_READ_REQUEST_PENDING, &data->flags);
+ mutex_unlock(&data->page_mutex);
+ return ret;
+}
+
+static inline int rmi_read(struct hid_device *hdev, u16 addr, void *buf)
+{
+ return rmi_read_block(hdev, addr, buf, 1);
+}
+
+static void rmi_f11_process_touch(struct rmi_data *hdata, int slot,
+ u8 finger_state, u8 *touch_data)
+{
+ int x, y, wx, wy;
+ int wide, major, minor;
+ int z;
+
+ input_mt_slot(hdata->input, slot);
+ input_mt_report_slot_state(hdata->input, MT_TOOL_FINGER,
+ finger_state == 0x01);
+ if (finger_state == 0x01) {
+ x = (touch_data[0] << 4) | (touch_data[2] & 0x07);
+ y = (touch_data[1] << 4) | (touch_data[2] >> 4);
+ wx = touch_data[3] & 0x07;
+ wy = touch_data[3] >> 4;
+ wide = (wx > wy);
+ major = max(wx, wy);
+ minor = min(wx, wy);
+ z = touch_data[4];
+
+ /* y is inverted */
+ y = hdata->max_y - y;
+
+ input_event(hdata->input, EV_ABS, ABS_MT_POSITION_X, x);
+ input_event(hdata->input, EV_ABS, ABS_MT_POSITION_Y, y);
+ input_event(hdata->input, EV_ABS, ABS_MT_ORIENTATION, wide);
+ input_event(hdata->input, EV_ABS, ABS_MT_PRESSURE, z);
+ input_event(hdata->input, EV_ABS, ABS_MT_TOUCH_MAJOR, major);
+ input_event(hdata->input, EV_ABS, ABS_MT_TOUCH_MINOR, minor);
+ }
+}
+
+static void rmi_reset_work(struct work_struct *work)
+{
+ struct rmi_data *hdata = container_of(work, struct rmi_data,
+ reset_work);
+
+ /* switch the device to RMI if we receive a generic mouse report */
+ rmi_set_mode(hdata->hdev, RMI_MODE_ATTN_REPORTS);
+}
+
+static inline int rmi_schedule_reset(struct hid_device *hdev)
+{
+ struct rmi_data *hdata = hid_get_drvdata(hdev);
+ return schedule_work(&hdata->reset_work);
+}
+
+static int rmi_f11_input_event(struct hid_device *hdev, u8 irq, u8 *data,
+ int size)
+{
+ struct rmi_data *hdata = hid_get_drvdata(hdev);
+ int offset;
+ int i;
+
+ if (size < hdata->f11.report_size)
+ return 0;
+
+ if (!(irq & hdata->f11.irq_mask))
+ return 0;
+
+ offset = (hdata->max_fingers >> 2) + 1;
+ for (i = 0; i < hdata->max_fingers; i++) {
+ int fs_byte_position = i >> 2;
+ int fs_bit_position = (i & 0x3) << 1;
+ int finger_state = (data[fs_byte_position] >> fs_bit_position) &
+ 0x03;
+
+ rmi_f11_process_touch(hdata, i, finger_state,
+ &data[offset + 5 * i]);
+ }
+ input_mt_sync_frame(hdata->input);
+ input_sync(hdata->input);
+ return hdata->f11.report_size;
+}
+
+static int rmi_f30_input_event(struct hid_device *hdev, u8 irq, u8 *data,
+ int size)
+{
+ struct rmi_data *hdata = hid_get_drvdata(hdev);
+ int i;
+ int button = 0;
+ bool value;
+
+ if (!(irq & hdata->f30.irq_mask))
+ return 0;
+
+ for (i = 0; i < hdata->gpio_led_count; i++) {
+ if (test_bit(i, &hdata->button_mask)) {
+ value = (data[i / 8] >> (i & 0x07)) & BIT(0);
+ if (test_bit(i, &hdata->button_state_mask))
+ value = !value;
+ input_event(hdata->input, EV_KEY, BTN_LEFT + button++,
+ value);
+ }
+ }
+ return hdata->f30.report_size;
+}
+
+static int rmi_input_event(struct hid_device *hdev, u8 *data, int size)
+{
+ struct rmi_data *hdata = hid_get_drvdata(hdev);
+ unsigned long irq_mask = 0;
+ unsigned index = 2;
+
+ if (!(test_bit(RMI_STARTED, &hdata->flags)))
+ return 0;
+
+ irq_mask |= hdata->f11.irq_mask;
+ irq_mask |= hdata->f30.irq_mask;
+
+ if (data[1] & ~irq_mask)
+ hid_warn(hdev, "unknown intr source:%02lx %s:%d\n",
+ data[1] & ~irq_mask, __FILE__, __LINE__);
+
+ if (hdata->f11.interrupt_base < hdata->f30.interrupt_base) {
+ index += rmi_f11_input_event(hdev, data[1], &data[index],
+ size - index);
+ index += rmi_f30_input_event(hdev, data[1], &data[index],
+ size - index);
+ } else {
+ index += rmi_f30_input_event(hdev, data[1], &data[index],
+ size - index);
+ index += rmi_f11_input_event(hdev, data[1], &data[index],
+ size - index);
+ }
+
+ return 1;
+}
+
+static int rmi_read_data_event(struct hid_device *hdev, u8 *data, int size)
+{
+ struct rmi_data *hdata = hid_get_drvdata(hdev);
+
+ if (!test_bit(RMI_READ_REQUEST_PENDING, &hdata->flags)) {
+ hid_err(hdev, "no read request pending\n");
+ return 0;
+ }
+
+ memcpy(hdata->readReport, data, size < hdata->input_report_size ?
+ size : hdata->input_report_size);
+ set_bit(RMI_READ_DATA_PENDING, &hdata->flags);
+ wake_up(&hdata->wait);
+
+ return 1;
+}
+
+static int rmi_raw_event(struct hid_device *hdev,
+ struct hid_report *report, u8 *data, int size)
+{
+ switch (data[0]) {
+ case RMI_READ_DATA_REPORT_ID:
+ return rmi_read_data_event(hdev, data, size);
+ case RMI_ATTN_REPORT_ID:
+ return rmi_input_event(hdev, data, size);
+ case RMI_MOUSE_REPORT_ID:
+ rmi_schedule_reset(hdev);
+ break;
+ }
+
+ return 0;
+}
+
+static int rmi_post_reset(struct hid_device *hdev)
+{
+ return rmi_set_mode(hdev, RMI_MODE_ATTN_REPORTS);
+}
+
+static int rmi_post_resume(struct hid_device *hdev)
+{
+ return rmi_set_mode(hdev, RMI_MODE_ATTN_REPORTS);
+}
+
+#define RMI4_MAX_PAGE 0xff
+#define RMI4_PAGE_SIZE 0x0100
+
+#define PDT_START_SCAN_LOCATION 0x00e9
+#define PDT_END_SCAN_LOCATION 0x0005
+#define RMI4_END_OF_PDT(id) ((id) == 0x00 || (id) == 0xff)
+
+struct pdt_entry {
+ u8 query_base_addr:8;
+ u8 command_base_addr:8;
+ u8 control_base_addr:8;
+ u8 data_base_addr:8;
+ u8 interrupt_source_count:3;
+ u8 bits3and4:2;
+ u8 function_version:2;
+ u8 bit7:1;
+ u8 function_number:8;
+} __attribute__((__packed__));
+
+static inline unsigned long rmi_gen_mask(unsigned irq_base, unsigned irq_count)
+{
+ return GENMASK(irq_count + irq_base - 1, irq_base);
+}
+
+static void rmi_register_function(struct rmi_data *data,
+ struct pdt_entry *pdt_entry, int page, unsigned interrupt_count)
+{
+ struct rmi_function *f = NULL;
+ u16 page_base = page << 8;
+
+ switch (pdt_entry->function_number) {
+ case 0x11:
+ f = &data->f11;
+ break;
+ case 0x30:
+ f = &data->f30;
+ break;
+ }
+
+ if (f) {
+ f->page = page;
+ f->query_base_addr = page_base | pdt_entry->query_base_addr;
+ f->command_base_addr = page_base | pdt_entry->command_base_addr;
+ f->control_base_addr = page_base | pdt_entry->control_base_addr;
+ f->data_base_addr = page_base | pdt_entry->data_base_addr;
+ f->interrupt_base = interrupt_count;
+ f->interrupt_count = pdt_entry->interrupt_source_count;
+ f->irq_mask = rmi_gen_mask(f->interrupt_base,
+ f->interrupt_count);
+ }
+}
+
+static int rmi_scan_pdt(struct hid_device *hdev)
+{
+ struct rmi_data *data = hid_get_drvdata(hdev);
+ struct pdt_entry entry;
+ int page;
+ bool page_has_function;
+ int i;
+ int retval;
+ int interrupt = 0;
+ u16 page_start, pdt_start , pdt_end;
+
+ hid_info(hdev, "Scanning PDT...\n");
+
+ for (page = 0; (page <= RMI4_MAX_PAGE); page++) {
+ page_start = RMI4_PAGE_SIZE * page;
+ pdt_start = page_start + PDT_START_SCAN_LOCATION;
+ pdt_end = page_start + PDT_END_SCAN_LOCATION;
+
+ page_has_function = false;
+ for (i = pdt_start; i >= pdt_end; i -= sizeof(entry)) {
+ retval = rmi_read_block(hdev, i, &entry, sizeof(entry));
+ if (retval) {
+ hid_err(hdev,
+ "Read of PDT entry at %#06x failed.\n",
+ i);
+ goto error_exit;
+ }
+
+ if (RMI4_END_OF_PDT(entry.function_number))
+ break;
+
+ page_has_function = true;
+
+ hid_info(hdev, "Found F%02X on page %#04x\n",
+ entry.function_number, page);
+
+ rmi_register_function(data, &entry, page, interrupt);
+ interrupt += entry.interrupt_source_count;
+ }
+
+ if (!page_has_function)
+ break;
+ }
+
+ hid_info(hdev, "%s: Done with PDT scan.\n", __func__);
+ retval = 0;
+
+error_exit:
+ return retval;
+}
+
+static int rmi_populate_f11(struct hid_device *hdev)
+{
+ struct rmi_data *data = hid_get_drvdata(hdev);
+ u8 buf[20];
+ int ret;
+ bool has_query12;
+ bool has_physical_props;
+ unsigned x_size, y_size;
+
+ if (!data->f11.query_base_addr) {
+ hid_err(hdev, "No 2D sensor found, giving up.\n");
+ return -ENODEV;
+ }
+
+ /* query 0 contains some useful information */
+ ret = rmi_read(hdev, data->f11.query_base_addr, buf);
+ if (ret) {
+ hid_err(hdev, "can not get query 0: %d.\n", ret);
+ return ret;
+ }
+ has_query12 = !!(buf[0] & BIT(5));
+
+ /* query 1 to get the max number of fingers */
+ ret = rmi_read(hdev, data->f11.query_base_addr + 1, buf);
+ if (ret) {
+ hid_err(hdev, "can not get NumberOfFingers: %d.\n", ret);
+ return ret;
+ }
+ data->max_fingers = (buf[0] & 0x07) + 1;
+ if (data->max_fingers > 5)
+ data->max_fingers = 10;
+
+ data->f11.report_size = data->max_fingers * 5 +
+ DIV_ROUND_UP(data->max_fingers, 4);
+
+ if (!(buf[0] & BIT(4))) {
+ hid_err(hdev, "No absolute events, giving up.\n");
+ return -ENODEV;
+ }
+
+ /*
+ * query 12 to know if the physical properties are reported
+ * (query 12 is at offset 10 for HID devices)
+ */
+ if (has_query12) {
+ ret = rmi_read(hdev, data->f11.query_base_addr + 10, buf);
+ if (ret) {
+ hid_err(hdev, "can not get query 12: %d.\n", ret);
+ return ret;
+ }
+ has_physical_props = !!(buf[0] & BIT(5));
+
+ if (has_physical_props) {
+ ret = rmi_read_block(hdev,
+ data->f11.query_base_addr + 11, buf, 4);
+ if (ret) {
+ hid_err(hdev, "can not read query 15-18: %d.\n",
+ ret);
+ return ret;
+ }
+
+ x_size = buf[0] | (buf[1] << 8);
+ y_size = buf[2] | (buf[3] << 8);
+
+ data->x_size_mm = DIV_ROUND_CLOSEST(x_size, 10);
+ data->y_size_mm = DIV_ROUND_CLOSEST(y_size, 10);
+
+ hid_info(hdev, "%s: size in mm: %d x %d\n",
+ __func__, data->x_size_mm, data->y_size_mm);
+ }
+ }
+
+ /* retrieve the ctrl registers */
+ ret = rmi_read_block(hdev, data->f11.control_base_addr, buf, 20);
+ if (ret) {
+ hid_err(hdev, "can not read ctrl block of size 20: %d.\n", ret);
+ return ret;
+ }
+
+ data->max_x = buf[6] | (buf[7] << 8);
+ data->max_y = buf[8] | (buf[9] << 8);
+
+ return 0;
+}
+
+static int rmi_populate_f30(struct hid_device *hdev)
+{
+ struct rmi_data *data = hid_get_drvdata(hdev);
+ u8 buf[20];
+ int ret;
+ bool has_gpio, has_led;
+ unsigned bytes_per_ctrl;
+ u8 ctrl2_addr;
+ int ctrl2_3_length;
+ int i;
+
+ /* function F30 is for physical buttons */
+ if (!data->f30.query_base_addr) {
+ hid_err(hdev, "No GPIO/LEDs found, giving up.\n");
+ return -ENODEV;
+ }
+
+ ret = rmi_read_block(hdev, data->f30.query_base_addr, buf, 2);
+ if (ret) {
+ hid_err(hdev, "can not get F30 query registers: %d.\n", ret);
+ return ret;
+ }
+
+ has_gpio = !!(buf[0] & BIT(3));
+ has_led = !!(buf[0] & BIT(2));
+ data->gpio_led_count = buf[1] & 0x1f;
+
+ /* retrieve ctrl 2 & 3 registers */
+ bytes_per_ctrl = (data->gpio_led_count + 7) / 8;
+ /* Ctrl0 is present only if both has_gpio and has_led are set*/
+ ctrl2_addr = (has_gpio && has_led) ? bytes_per_ctrl : 0;
+ /* Ctrl1 is always be present */
+ ctrl2_addr += bytes_per_ctrl;
+ ctrl2_3_length = 2 * bytes_per_ctrl;
+
+ data->f30.report_size = bytes_per_ctrl;
+
+ ret = rmi_read_block(hdev, data->f30.control_base_addr + ctrl2_addr,
+ buf, ctrl2_3_length);
+ if (ret) {
+ hid_err(hdev, "can not read ctrl 2&3 block of size %d: %d.\n",
+ ctrl2_3_length, ret);
+ return ret;
+ }
+
+ for (i = 0; i < data->gpio_led_count; i++) {
+ int byte_position = i >> 3;
+ int bit_position = i & 0x07;
+ u8 dir_byte = buf[byte_position];
+ u8 data_byte = buf[byte_position + bytes_per_ctrl];
+ bool dir = (dir_byte >> bit_position) & BIT(0);
+ bool dat = (data_byte >> bit_position) & BIT(0);
+
+ if (dir == 0) {
+ /* input mode */
+ if (dat) {
+ /* actual buttons have pull up resistor */
+ data->button_count++;
+ set_bit(i, &data->button_mask);
+ set_bit(i, &data->button_state_mask);
+ }
+ }
+
+ }
+
+ return 0;
+}
+
+static int rmi_populate(struct hid_device *hdev)
+{
+ int ret;
+
+ ret = rmi_scan_pdt(hdev);
+ if (ret) {
+ hid_err(hdev, "PDT scan failed with code %d.\n", ret);
+ return ret;
+ }
+
+ ret = rmi_populate_f11(hdev);
+ if (ret) {
+ hid_err(hdev, "Error while initializing F11 (%d).\n", ret);
+ return ret;
+ }
+
+ ret = rmi_populate_f30(hdev);
+ if (ret)
+ hid_warn(hdev, "Error while initializing F30 (%d).\n", ret);
+
+ return 0;
+}
+
+static void rmi_input_configured(struct hid_device *hdev, struct hid_input *hi)
+{
+ struct rmi_data *data = hid_get_drvdata(hdev);
+ struct input_dev *input = hi->input;
+ int ret;
+ int res_x, res_y, i;
+
+ data->input = input;
+
+ hid_dbg(hdev, "Opening low level driver\n");
+ ret = hid_hw_open(hdev);
+ if (ret)
+ return;
+
+ /* Allow incoming hid reports */
+ hid_device_io_start(hdev);
+
+ ret = rmi_set_mode(hdev, RMI_MODE_ATTN_REPORTS);
+ if (ret < 0) {
+ dev_err(&hdev->dev, "failed to set rmi mode\n");
+ goto exit;
+ }
+
+ ret = rmi_set_page(hdev, 0);
+ if (ret < 0) {
+ dev_err(&hdev->dev, "failed to set page select to 0.\n");
+ goto exit;
+ }
+
+ ret = rmi_populate(hdev);
+ if (ret)
+ goto exit;
+
+ __set_bit(EV_ABS, input->evbit);
+ input_set_abs_params(input, ABS_MT_POSITION_X, 1, data->max_x, 0, 0);
+ input_set_abs_params(input, ABS_MT_POSITION_Y, 1, data->max_y, 0, 0);
+
+ if (data->x_size_mm && data->x_size_mm) {
+ res_x = (data->max_x - 1) / data->x_size_mm;
+ res_y = (data->max_y - 1) / data->x_size_mm;
+
+ input_abs_set_res(input, ABS_MT_POSITION_X, res_x);
+ input_abs_set_res(input, ABS_MT_POSITION_Y, res_y);
+ }
+
+ input_set_abs_params(input, ABS_MT_ORIENTATION, 0, 1, 0, 0);
+ input_set_abs_params(input, ABS_MT_PRESSURE, 0, 0xff, 0, 0);
+ input_set_abs_params(input, ABS_MT_TOUCH_MAJOR, 0, 0x0f, 0, 0);
+ input_set_abs_params(input, ABS_MT_TOUCH_MINOR, 0, 0x0f, 0, 0);
+
+ input_mt_init_slots(input, data->max_fingers, INPUT_MT_POINTER);
+
+ if (data->button_count) {
+ __set_bit(EV_KEY, input->evbit);
+ for (i = 0; i < data->button_count; i++)
+ __set_bit(BTN_LEFT + i, input->keybit);
+
+ if (data->button_count == 1)
+ __set_bit(INPUT_PROP_BUTTONPAD, input->propbit);
+ }
+
+ set_bit(RMI_STARTED, &data->flags);
+
+exit:
+ hid_device_io_stop(hdev);
+ hid_hw_close(hdev);
+}
+
+static int rmi_input_mapping(struct hid_device *hdev,
+ struct hid_input *hi, struct hid_field *field,
+ struct hid_usage *usage, unsigned long **bit, int *max)
+{
+ /* we want to make HID ignore the advertised HID collection */
+ return -1;
+}
+
+static int rmi_probe(struct hid_device *hdev, const struct hid_device_id *id)
+{
+ struct rmi_data *data = NULL;
+ int ret;
+ size_t alloc_size;
+
+ data = devm_kzalloc(&hdev->dev, sizeof(struct rmi_data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ INIT_WORK(&data->reset_work, rmi_reset_work);
+ data->hdev = hdev;
+
+ hid_set_drvdata(hdev, data);
+
+ hdev->quirks |= HID_QUIRK_NO_INIT_REPORTS;
+
+ ret = hid_parse(hdev);
+ if (ret) {
+ hid_err(hdev, "parse failed\n");
+ return ret;
+ }
+
+ data->input_report_size = (hdev->report_enum[HID_INPUT_REPORT]
+ .report_id_hash[RMI_ATTN_REPORT_ID]->size >> 3)
+ + 1 /* report id */;
+ data->output_report_size = (hdev->report_enum[HID_OUTPUT_REPORT]
+ .report_id_hash[RMI_WRITE_REPORT_ID]->size >> 3)
+ + 1 /* report id */;
+
+ alloc_size = data->output_report_size + data->input_report_size;
+
+ data->writeReport = devm_kzalloc(&hdev->dev, alloc_size, GFP_KERNEL);
+ if (!data->writeReport) {
+ ret = -ENOMEM;
+ return ret;
+ }
+
+ data->readReport = data->writeReport + data->output_report_size;
+
+ init_waitqueue_head(&data->wait);
+
+ mutex_init(&data->page_mutex);
+
+ ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
+ if (ret) {
+ hid_err(hdev, "hw start failed\n");
+ return ret;
+ }
+
+ if (!test_bit(RMI_STARTED, &data->flags)) {
+ hid_hw_stop(hdev);
+ return -EIO;
+ }
+
+ hid_hw_stop(hdev);
+ return 0;
+}
+
+static void rmi_remove(struct hid_device *hdev)
+{
+ struct rmi_data *hdata = hid_get_drvdata(hdev);
+
+ clear_bit(RMI_STARTED, &hdata->flags);
+
+ hid_hw_stop(hdev);
+}
+
+static const struct hid_device_id rmi_id[] = {
+ { HID_I2C_DEVICE(USB_VENDOR_ID_SYNAPTICS, HID_ANY_ID) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_SYNAPTICS, HID_ANY_ID) },
+ { }
+};
+MODULE_DEVICE_TABLE(hid, rmi_id);
+
+static struct hid_driver rmi_driver = {
+ .name = "hid-rmi",
+ .id_table = rmi_id,
+ .probe = rmi_probe,
+ .remove = rmi_remove,
+ .raw_event = rmi_raw_event,
+ .input_mapping = rmi_input_mapping,
+ .input_configured = rmi_input_configured,
+#ifdef CONFIG_PM
+ .resume = rmi_post_resume,
+ .reset_resume = rmi_post_reset,
+#endif
+};
+
+module_hid_driver(rmi_driver);
+
+MODULE_AUTHOR("Andrew Duggan <[email protected]>");
+MODULE_DESCRIPTION("RMI HID driver");
+MODULE_LICENSE("GPL");
diff --git a/include/linux/hid.h b/include/linux/hid.h
index 720e3a1..54f855b 100644
--- a/include/linux/hid.h
+++ b/include/linux/hid.h
@@ -570,6 +570,8 @@ struct hid_descriptor {
.bus = BUS_USB, .vendor = (ven), .product = (prod)
#define HID_BLUETOOTH_DEVICE(ven, prod) \
.bus = BUS_BLUETOOTH, .vendor = (ven), .product = (prod)
+#define HID_I2C_DEVICE(ven, prod) \
+ .bus = BUS_I2C, .vendor = (ven), .product = (prod)

#define HID_REPORT_ID(rep) \
.report_type = (rep)
--
1.9.0


2014-04-07 17:43:25

by Benjamin Tissoires

[permalink] [raw]
Subject: Re: [PATCH] HID: rmi: introduce RMI driver for Synaptics touchpads

On Mon, Apr 7, 2014 at 1:39 PM, Benjamin Tissoires
<[email protected]> wrote:
> This driver add support for RMI4 over USB or I2C.
> The current state is that it uses its own RMI4 implementation, but once
> RMI4 is merged upstream, the driver will be a transport driver for the
> RMI4 library.
>
> Signed-off-by: Andrew Duggan <[email protected]>
> Signed-off-by: Benjamin Tissoires <[email protected]>
> ---

Of course, I did not triple checked things here, and the version I
sent was missing the comments:

Hi,

well, part of this driver should be considered as temporary. Most of the
RMI4 processing and input handling will be deleted at some point.

I based my work on Andrew's regarding its port of RMI4 over HID (see
https://github.com/mightybigcar/synaptics-rmi4/tree/rmihid )
This repo presents how the driver may looks like at the end:
https://github.com/mightybigcar/synaptics-rmi4/blob/rmihid/drivers/input/rmi4/rmi_hid.c

Andrew validated the changes, so I kept his S-o-b and the module author.

Without this temporary solution, the workaround we gave to users
is to disable i2c-hid, which leads to disabling the touchscreen on the
XPS 11 and 12 (Haswell generation).

Cheers,
Benjamin

Related bugs:
https://bugzilla.redhat.com/show_bug.cgi?id=1048314
https://bugs.launchpad.net/ubuntu/+source/linux/+bug/1218973



> drivers/hid/Kconfig | 8 +
> drivers/hid/Makefile | 1 +
> drivers/hid/hid-core.c | 2 +
> drivers/hid/hid-rmi.c | 889 +++++++++++++++++++++++++++++++++++++++++++++++++
> include/linux/hid.h | 2 +
> 5 files changed, 902 insertions(+)
> create mode 100644 drivers/hid/hid-rmi.c
>
> diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
> index 7af9d0b..762f15d 100644
> --- a/drivers/hid/Kconfig
> +++ b/drivers/hid/Kconfig
> @@ -657,6 +657,14 @@ config HID_SUNPLUS
> ---help---
> Support for Sunplus wireless desktop.
>
> +config HID_RMI
> + tristate "Synaptics RMI4 device support"
> + depends on HID
> + ---help---
> + Support for Synaptics RMI4 touchpads.
> + Say Y here if you have a Synaptics RMI4 touchpads over i2c-hid or usbhid
> + and want support for its special functionalities.
> +
> config HID_GREENASIA
> tristate "GreenAsia (Product ID 0x12) game controller support"
> depends on HID
> diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile
> index fc712dd..a6fa6ba 100644
> --- a/drivers/hid/Makefile
> +++ b/drivers/hid/Makefile
> @@ -97,6 +97,7 @@ obj-$(CONFIG_HID_ROCCAT) += hid-roccat.o hid-roccat-common.o \
> hid-roccat-arvo.o hid-roccat-isku.o hid-roccat-kone.o \
> hid-roccat-koneplus.o hid-roccat-konepure.o hid-roccat-kovaplus.o \
> hid-roccat-lua.o hid-roccat-pyra.o hid-roccat-ryos.o hid-roccat-savu.o
> +obj-$(CONFIG_HID_RMI) += hid-rmi.o
> obj-$(CONFIG_HID_SAITEK) += hid-saitek.o
> obj-$(CONFIG_HID_SAMSUNG) += hid-samsung.o
> obj-$(CONFIG_HID_SMARTJOYPLUS) += hid-sjoy.o
> diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c
> index abaa0a5..c97ece8 100644
> --- a/drivers/hid/hid-core.c
> +++ b/drivers/hid/hid-core.c
> @@ -1879,6 +1879,8 @@ static const struct hid_device_id hid_have_special_driver[] = {
> { HID_USB_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_VAIO_VGP_MOUSE) },
> { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_SRWS1) },
> { HID_USB_DEVICE(USB_VENDOR_ID_SUNPLUS, USB_DEVICE_ID_SUNPLUS_WDESKTOP) },
> + { HID_USB_DEVICE(USB_VENDOR_ID_SYNAPTICS, HID_ANY_ID) },
> + { HID_I2C_DEVICE(USB_VENDOR_ID_SYNAPTICS, HID_ANY_ID) },
> { HID_USB_DEVICE(USB_VENDOR_ID_THINGM, USB_DEVICE_ID_BLINK1) },
> { HID_USB_DEVICE(USB_VENDOR_ID_THRUSTMASTER, 0xb300) },
> { HID_USB_DEVICE(USB_VENDOR_ID_THRUSTMASTER, 0xb304) },
> diff --git a/drivers/hid/hid-rmi.c b/drivers/hid/hid-rmi.c
> new file mode 100644
> index 0000000..699d631
> --- /dev/null
> +++ b/drivers/hid/hid-rmi.c
> @@ -0,0 +1,889 @@
> +/*
> + * Copyright (c) 2013 Andrew Duggan <[email protected]>
> + * Copyright (c) 2013 Synaptics Incorporated
> + * Copyright (c) 2014 Benjamin Tissoires <[email protected]>
> + * Copyright (c) 2014 Red Hat, Inc
> + *
> + * This program is free software; you can redistribute it and/or modify it
> + * under the terms of the GNU General Public License as published by the Free
> + * Software Foundation; either version 2 of the License, or (at your option)
> + * any later version.
> + */
> +
> +#include <linux/kernel.h>
> +#include <linux/hid.h>
> +#include <linux/input.h>
> +#include <linux/input/mt.h>
> +#include <linux/module.h>
> +#include <linux/pm.h>
> +#include <linux/slab.h>
> +#include <linux/wait.h>
> +#include <linux/sched.h>
> +#include "hid-ids.h"
> +
> +#define RMI_MOUSE_REPORT_ID 0x01 /* Mouse emulation Report */
> +#define RMI_WRITE_REPORT_ID 0x09 /* Output Report */
> +#define RMI_READ_ADDR_REPORT_ID 0x0a /* Output Report */
> +#define RMI_READ_DATA_REPORT_ID 0x0b /* Input Report */
> +#define RMI_ATTN_REPORT_ID 0x0c /* Input Report */
> +#define RMI_SET_RMI_MODE_REPORT_ID 0x0f /* Feature Report */
> +
> +/* flags */
> +#define RMI_READ_REQUEST_PENDING BIT(0)
> +#define RMI_READ_DATA_PENDING BIT(1)
> +#define RMI_STARTED BIT(2)
> +
> +enum rmi_mode_type {
> + RMI_MODE_OFF = 0,
> + RMI_MODE_ATTN_REPORTS = 1,
> + RMI_MODE_NO_PACKED_ATTN_REPORTS = 2,
> +};
> +
> +struct rmi_function {
> + unsigned page; /* page of the function */
> + u16 query_base_addr; /* base address for queries */
> + u16 command_base_addr; /* base address for commands */
> + u16 control_base_addr; /* base address for controls */
> + u16 data_base_addr; /* base address for datas */
> + unsigned int interrupt_base; /* cross-function interrupt number
> + * (uniq in the device)*/
> + unsigned int interrupt_count; /* number of interrupts */
> + unsigned int report_size; /* size of a report */
> + unsigned long irq_mask; /* mask of the interrupts
> + * (to be applied against ATTN IRQ) */
> +};
> +
> +/**
> + * struct rmi_data - stores information for hid communication
> + *
> + * @page_mutex: Locks current page to avoid changing pages in unexpected ways.
> + * @page: Keeps track of the current virtual page
> + *
> + * @wait: Used for waiting for read data
> + *
> + * @writeReport: output buffer when writing RMI registers
> + * @readReport: input buffer when reading RMI registers
> + *
> + * @input_report_size: size of an input report (advertised by HID)
> + * @output_report_size: size of an output report (advertised by HID)
> + *
> + * @flags: flags for the current device (started, reading, etc...)
> + *
> + * @f11: placeholder of internal RMI function F11 description
> + * @f30: placeholder of internal RMI function F30 description
> + *
> + * @max_fingers: maximum finger count reported by the device
> + * @max_x: maximum x value reported by the device
> + * @max_y: maximum y value reported by the device
> + *
> + * @gpio_led_count: count of GPIOs + LEDs reported by F30
> + * @button_count: actual physical buttons count
> + * @button_mask: button mask used to decode GPIO ATTN reports
> + * @button_state_mask: pull state of the buttons
> + *
> + * @input: pointer to the kernel input device
> + *
> + * @reset_work: worker which will be called in case of a mouse report
> + * @hdev: pointer to the struct hid_device
> + */
> +struct rmi_data {
> + struct mutex page_mutex;
> + int page;
> +
> + wait_queue_head_t wait;
> +
> + u8 *writeReport;
> + u8 *readReport;
> +
> + int input_report_size;
> + int output_report_size;
> +
> + unsigned long flags;
> +
> + struct rmi_function f11;
> + struct rmi_function f30;
> +
> + unsigned int max_fingers;
> + unsigned int max_x;
> + unsigned int max_y;
> + unsigned int x_size_mm;
> + unsigned int y_size_mm;
> +
> + unsigned int gpio_led_count;
> + unsigned int button_count;
> + unsigned long button_mask;
> + unsigned long button_state_mask;
> +
> + struct input_dev *input;
> +
> + struct work_struct reset_work;
> + struct hid_device *hdev;
> +};
> +
> +#define RMI_PAGE(addr) (((addr) >> 8) & 0xff)
> +
> +static int rmi_write_report(struct hid_device *hdev, u8 *report, int len);
> +
> +/**
> + * rmi_set_page - Set RMI page
> + * @hdev: The pointer to the hid_device struct
> + * @page: The new page address.
> + *
> + * RMI devices have 16-bit addressing, but some of the physical
> + * implementations (like SMBus) only have 8-bit addressing. So RMI implements
> + * a page address at 0xff of every page so we can reliable page addresses
> + * every 256 registers.
> + *
> + * The page_mutex lock must be held when this function is entered.
> + *
> + * Returns zero on success, non-zero on failure.
> + */
> +static int rmi_set_page(struct hid_device *hdev, u8 page)
> +{
> + struct rmi_data *data = hid_get_drvdata(hdev);
> + int retval;
> +
> + data->writeReport[0] = RMI_WRITE_REPORT_ID;
> + data->writeReport[1] = 1;
> + data->writeReport[2] = 0xFF;
> + data->writeReport[4] = page;
> +
> + retval = rmi_write_report(hdev, data->writeReport,
> + data->output_report_size);
> + if (retval != data->output_report_size) {
> + dev_err(&hdev->dev,
> + "%s: set page failed: %d.", __func__, retval);
> + return retval;
> + }
> +
> + data->page = page;
> + return 0;
> +}
> +
> +static int rmi_set_mode(struct hid_device *hdev, u8 mode)
> +{
> + int ret;
> + u8 txbuf[2] = {RMI_SET_RMI_MODE_REPORT_ID, mode};
> +
> + ret = hid_hw_raw_request(hdev, RMI_SET_RMI_MODE_REPORT_ID, txbuf,
> + sizeof(txbuf), HID_FEATURE_REPORT, HID_REQ_SET_REPORT);
> + if (ret < 0) {
> + dev_err(&hdev->dev, "unable to set rmi mode to %d (%d)\n", mode,
> + ret);
> + return ret;
> + }
> +
> + return 0;
> +}
> +
> +static int rmi_write_report(struct hid_device *hdev, u8 *report, int len)
> +{
> + int ret;
> +
> + ret = hid_hw_output_report(hdev, (void *)report, len);
> + if (ret < 0) {
> + dev_err(&hdev->dev, "failed to write hid report (%d)\n", ret);
> + return ret;
> + }
> +
> + return ret;
> +}
> +
> +static int rmi_read_block(struct hid_device *hdev, u16 addr, void *buf,
> + const int len)
> +{
> + struct rmi_data *data = hid_get_drvdata(hdev);
> + int ret;
> + int bytes_read;
> + int bytes_needed;
> + int retries;
> + int read_input_count;
> +
> + mutex_lock(&data->page_mutex);
> +
> + if (RMI_PAGE(addr) != data->page) {
> + ret = rmi_set_page(hdev, RMI_PAGE(addr));
> + if (ret < 0)
> + goto exit;
> + }
> +
> + for (retries = 5; retries > 0; retries--) {
> + data->writeReport[0] = RMI_READ_ADDR_REPORT_ID;
> + data->writeReport[1] = 0; /* old 1 byte read count */
> + data->writeReport[2] = addr & 0xFF;
> + data->writeReport[3] = (addr >> 8) & 0xFF;
> + data->writeReport[4] = len & 0xFF;
> + data->writeReport[5] = (len >> 8) & 0xFF;
> +
> + set_bit(RMI_READ_REQUEST_PENDING, &data->flags);
> +
> + ret = rmi_write_report(hdev, data->writeReport,
> + data->output_report_size);
> + if (ret != data->output_report_size) {
> + clear_bit(RMI_READ_REQUEST_PENDING, &data->flags);
> + dev_err(&hdev->dev,
> + "failed to write request output report (%d)\n",
> + ret);
> + goto exit;
> + }
> +
> + bytes_read = 0;
> + bytes_needed = len;
> + while (bytes_read < len) {
> + if (!wait_event_timeout(data->wait,
> + test_bit(RMI_READ_DATA_PENDING, &data->flags),
> + msecs_to_jiffies(1000))) {
> + hid_warn(hdev, "%s: timeout elapsed\n",
> + __func__);
> + ret = -EAGAIN;
> + break;
> + }
> +
> + read_input_count = data->readReport[1];
> + memcpy(buf + bytes_read, &data->readReport[2],
> + read_input_count < bytes_needed ?
> + read_input_count : bytes_needed);
> +
> + bytes_read += read_input_count;
> + bytes_needed -= read_input_count;
> + clear_bit(RMI_READ_DATA_PENDING, &data->flags);
> + }
> +
> + if (ret >= 0) {
> + ret = 0;
> + break;
> + }
> + }
> +
> +exit:
> + clear_bit(RMI_READ_REQUEST_PENDING, &data->flags);
> + mutex_unlock(&data->page_mutex);
> + return ret;
> +}
> +
> +static inline int rmi_read(struct hid_device *hdev, u16 addr, void *buf)
> +{
> + return rmi_read_block(hdev, addr, buf, 1);
> +}
> +
> +static void rmi_f11_process_touch(struct rmi_data *hdata, int slot,
> + u8 finger_state, u8 *touch_data)
> +{
> + int x, y, wx, wy;
> + int wide, major, minor;
> + int z;
> +
> + input_mt_slot(hdata->input, slot);
> + input_mt_report_slot_state(hdata->input, MT_TOOL_FINGER,
> + finger_state == 0x01);
> + if (finger_state == 0x01) {
> + x = (touch_data[0] << 4) | (touch_data[2] & 0x07);
> + y = (touch_data[1] << 4) | (touch_data[2] >> 4);
> + wx = touch_data[3] & 0x07;
> + wy = touch_data[3] >> 4;
> + wide = (wx > wy);
> + major = max(wx, wy);
> + minor = min(wx, wy);
> + z = touch_data[4];
> +
> + /* y is inverted */
> + y = hdata->max_y - y;
> +
> + input_event(hdata->input, EV_ABS, ABS_MT_POSITION_X, x);
> + input_event(hdata->input, EV_ABS, ABS_MT_POSITION_Y, y);
> + input_event(hdata->input, EV_ABS, ABS_MT_ORIENTATION, wide);
> + input_event(hdata->input, EV_ABS, ABS_MT_PRESSURE, z);
> + input_event(hdata->input, EV_ABS, ABS_MT_TOUCH_MAJOR, major);
> + input_event(hdata->input, EV_ABS, ABS_MT_TOUCH_MINOR, minor);
> + }
> +}
> +
> +static void rmi_reset_work(struct work_struct *work)
> +{
> + struct rmi_data *hdata = container_of(work, struct rmi_data,
> + reset_work);
> +
> + /* switch the device to RMI if we receive a generic mouse report */
> + rmi_set_mode(hdata->hdev, RMI_MODE_ATTN_REPORTS);
> +}
> +
> +static inline int rmi_schedule_reset(struct hid_device *hdev)
> +{
> + struct rmi_data *hdata = hid_get_drvdata(hdev);
> + return schedule_work(&hdata->reset_work);
> +}
> +
> +static int rmi_f11_input_event(struct hid_device *hdev, u8 irq, u8 *data,
> + int size)
> +{
> + struct rmi_data *hdata = hid_get_drvdata(hdev);
> + int offset;
> + int i;
> +
> + if (size < hdata->f11.report_size)
> + return 0;
> +
> + if (!(irq & hdata->f11.irq_mask))
> + return 0;
> +
> + offset = (hdata->max_fingers >> 2) + 1;
> + for (i = 0; i < hdata->max_fingers; i++) {
> + int fs_byte_position = i >> 2;
> + int fs_bit_position = (i & 0x3) << 1;
> + int finger_state = (data[fs_byte_position] >> fs_bit_position) &
> + 0x03;
> +
> + rmi_f11_process_touch(hdata, i, finger_state,
> + &data[offset + 5 * i]);
> + }
> + input_mt_sync_frame(hdata->input);
> + input_sync(hdata->input);
> + return hdata->f11.report_size;
> +}
> +
> +static int rmi_f30_input_event(struct hid_device *hdev, u8 irq, u8 *data,
> + int size)
> +{
> + struct rmi_data *hdata = hid_get_drvdata(hdev);
> + int i;
> + int button = 0;
> + bool value;
> +
> + if (!(irq & hdata->f30.irq_mask))
> + return 0;
> +
> + for (i = 0; i < hdata->gpio_led_count; i++) {
> + if (test_bit(i, &hdata->button_mask)) {
> + value = (data[i / 8] >> (i & 0x07)) & BIT(0);
> + if (test_bit(i, &hdata->button_state_mask))
> + value = !value;
> + input_event(hdata->input, EV_KEY, BTN_LEFT + button++,
> + value);
> + }
> + }
> + return hdata->f30.report_size;
> +}
> +
> +static int rmi_input_event(struct hid_device *hdev, u8 *data, int size)
> +{
> + struct rmi_data *hdata = hid_get_drvdata(hdev);
> + unsigned long irq_mask = 0;
> + unsigned index = 2;
> +
> + if (!(test_bit(RMI_STARTED, &hdata->flags)))
> + return 0;
> +
> + irq_mask |= hdata->f11.irq_mask;
> + irq_mask |= hdata->f30.irq_mask;
> +
> + if (data[1] & ~irq_mask)
> + hid_warn(hdev, "unknown intr source:%02lx %s:%d\n",
> + data[1] & ~irq_mask, __FILE__, __LINE__);
> +
> + if (hdata->f11.interrupt_base < hdata->f30.interrupt_base) {
> + index += rmi_f11_input_event(hdev, data[1], &data[index],
> + size - index);
> + index += rmi_f30_input_event(hdev, data[1], &data[index],
> + size - index);
> + } else {
> + index += rmi_f30_input_event(hdev, data[1], &data[index],
> + size - index);
> + index += rmi_f11_input_event(hdev, data[1], &data[index],
> + size - index);
> + }
> +
> + return 1;
> +}
> +
> +static int rmi_read_data_event(struct hid_device *hdev, u8 *data, int size)
> +{
> + struct rmi_data *hdata = hid_get_drvdata(hdev);
> +
> + if (!test_bit(RMI_READ_REQUEST_PENDING, &hdata->flags)) {
> + hid_err(hdev, "no read request pending\n");
> + return 0;
> + }
> +
> + memcpy(hdata->readReport, data, size < hdata->input_report_size ?
> + size : hdata->input_report_size);
> + set_bit(RMI_READ_DATA_PENDING, &hdata->flags);
> + wake_up(&hdata->wait);
> +
> + return 1;
> +}
> +
> +static int rmi_raw_event(struct hid_device *hdev,
> + struct hid_report *report, u8 *data, int size)
> +{
> + switch (data[0]) {
> + case RMI_READ_DATA_REPORT_ID:
> + return rmi_read_data_event(hdev, data, size);
> + case RMI_ATTN_REPORT_ID:
> + return rmi_input_event(hdev, data, size);
> + case RMI_MOUSE_REPORT_ID:
> + rmi_schedule_reset(hdev);
> + break;
> + }
> +
> + return 0;
> +}
> +
> +static int rmi_post_reset(struct hid_device *hdev)
> +{
> + return rmi_set_mode(hdev, RMI_MODE_ATTN_REPORTS);
> +}
> +
> +static int rmi_post_resume(struct hid_device *hdev)
> +{
> + return rmi_set_mode(hdev, RMI_MODE_ATTN_REPORTS);
> +}
> +
> +#define RMI4_MAX_PAGE 0xff
> +#define RMI4_PAGE_SIZE 0x0100
> +
> +#define PDT_START_SCAN_LOCATION 0x00e9
> +#define PDT_END_SCAN_LOCATION 0x0005
> +#define RMI4_END_OF_PDT(id) ((id) == 0x00 || (id) == 0xff)
> +
> +struct pdt_entry {
> + u8 query_base_addr:8;
> + u8 command_base_addr:8;
> + u8 control_base_addr:8;
> + u8 data_base_addr:8;
> + u8 interrupt_source_count:3;
> + u8 bits3and4:2;
> + u8 function_version:2;
> + u8 bit7:1;
> + u8 function_number:8;
> +} __attribute__((__packed__));
> +
> +static inline unsigned long rmi_gen_mask(unsigned irq_base, unsigned irq_count)
> +{
> + return GENMASK(irq_count + irq_base - 1, irq_base);
> +}
> +
> +static void rmi_register_function(struct rmi_data *data,
> + struct pdt_entry *pdt_entry, int page, unsigned interrupt_count)
> +{
> + struct rmi_function *f = NULL;
> + u16 page_base = page << 8;
> +
> + switch (pdt_entry->function_number) {
> + case 0x11:
> + f = &data->f11;
> + break;
> + case 0x30:
> + f = &data->f30;
> + break;
> + }
> +
> + if (f) {
> + f->page = page;
> + f->query_base_addr = page_base | pdt_entry->query_base_addr;
> + f->command_base_addr = page_base | pdt_entry->command_base_addr;
> + f->control_base_addr = page_base | pdt_entry->control_base_addr;
> + f->data_base_addr = page_base | pdt_entry->data_base_addr;
> + f->interrupt_base = interrupt_count;
> + f->interrupt_count = pdt_entry->interrupt_source_count;
> + f->irq_mask = rmi_gen_mask(f->interrupt_base,
> + f->interrupt_count);
> + }
> +}
> +
> +static int rmi_scan_pdt(struct hid_device *hdev)
> +{
> + struct rmi_data *data = hid_get_drvdata(hdev);
> + struct pdt_entry entry;
> + int page;
> + bool page_has_function;
> + int i;
> + int retval;
> + int interrupt = 0;
> + u16 page_start, pdt_start , pdt_end;
> +
> + hid_info(hdev, "Scanning PDT...\n");
> +
> + for (page = 0; (page <= RMI4_MAX_PAGE); page++) {
> + page_start = RMI4_PAGE_SIZE * page;
> + pdt_start = page_start + PDT_START_SCAN_LOCATION;
> + pdt_end = page_start + PDT_END_SCAN_LOCATION;
> +
> + page_has_function = false;
> + for (i = pdt_start; i >= pdt_end; i -= sizeof(entry)) {
> + retval = rmi_read_block(hdev, i, &entry, sizeof(entry));
> + if (retval) {
> + hid_err(hdev,
> + "Read of PDT entry at %#06x failed.\n",
> + i);
> + goto error_exit;
> + }
> +
> + if (RMI4_END_OF_PDT(entry.function_number))
> + break;
> +
> + page_has_function = true;
> +
> + hid_info(hdev, "Found F%02X on page %#04x\n",
> + entry.function_number, page);
> +
> + rmi_register_function(data, &entry, page, interrupt);
> + interrupt += entry.interrupt_source_count;
> + }
> +
> + if (!page_has_function)
> + break;
> + }
> +
> + hid_info(hdev, "%s: Done with PDT scan.\n", __func__);
> + retval = 0;
> +
> +error_exit:
> + return retval;
> +}
> +
> +static int rmi_populate_f11(struct hid_device *hdev)
> +{
> + struct rmi_data *data = hid_get_drvdata(hdev);
> + u8 buf[20];
> + int ret;
> + bool has_query12;
> + bool has_physical_props;
> + unsigned x_size, y_size;
> +
> + if (!data->f11.query_base_addr) {
> + hid_err(hdev, "No 2D sensor found, giving up.\n");
> + return -ENODEV;
> + }
> +
> + /* query 0 contains some useful information */
> + ret = rmi_read(hdev, data->f11.query_base_addr, buf);
> + if (ret) {
> + hid_err(hdev, "can not get query 0: %d.\n", ret);
> + return ret;
> + }
> + has_query12 = !!(buf[0] & BIT(5));
> +
> + /* query 1 to get the max number of fingers */
> + ret = rmi_read(hdev, data->f11.query_base_addr + 1, buf);
> + if (ret) {
> + hid_err(hdev, "can not get NumberOfFingers: %d.\n", ret);
> + return ret;
> + }
> + data->max_fingers = (buf[0] & 0x07) + 1;
> + if (data->max_fingers > 5)
> + data->max_fingers = 10;
> +
> + data->f11.report_size = data->max_fingers * 5 +
> + DIV_ROUND_UP(data->max_fingers, 4);
> +
> + if (!(buf[0] & BIT(4))) {
> + hid_err(hdev, "No absolute events, giving up.\n");
> + return -ENODEV;
> + }
> +
> + /*
> + * query 12 to know if the physical properties are reported
> + * (query 12 is at offset 10 for HID devices)
> + */
> + if (has_query12) {
> + ret = rmi_read(hdev, data->f11.query_base_addr + 10, buf);
> + if (ret) {
> + hid_err(hdev, "can not get query 12: %d.\n", ret);
> + return ret;
> + }
> + has_physical_props = !!(buf[0] & BIT(5));
> +
> + if (has_physical_props) {
> + ret = rmi_read_block(hdev,
> + data->f11.query_base_addr + 11, buf, 4);
> + if (ret) {
> + hid_err(hdev, "can not read query 15-18: %d.\n",
> + ret);
> + return ret;
> + }
> +
> + x_size = buf[0] | (buf[1] << 8);
> + y_size = buf[2] | (buf[3] << 8);
> +
> + data->x_size_mm = DIV_ROUND_CLOSEST(x_size, 10);
> + data->y_size_mm = DIV_ROUND_CLOSEST(y_size, 10);
> +
> + hid_info(hdev, "%s: size in mm: %d x %d\n",
> + __func__, data->x_size_mm, data->y_size_mm);
> + }
> + }
> +
> + /* retrieve the ctrl registers */
> + ret = rmi_read_block(hdev, data->f11.control_base_addr, buf, 20);
> + if (ret) {
> + hid_err(hdev, "can not read ctrl block of size 20: %d.\n", ret);
> + return ret;
> + }
> +
> + data->max_x = buf[6] | (buf[7] << 8);
> + data->max_y = buf[8] | (buf[9] << 8);
> +
> + return 0;
> +}
> +
> +static int rmi_populate_f30(struct hid_device *hdev)
> +{
> + struct rmi_data *data = hid_get_drvdata(hdev);
> + u8 buf[20];
> + int ret;
> + bool has_gpio, has_led;
> + unsigned bytes_per_ctrl;
> + u8 ctrl2_addr;
> + int ctrl2_3_length;
> + int i;
> +
> + /* function F30 is for physical buttons */
> + if (!data->f30.query_base_addr) {
> + hid_err(hdev, "No GPIO/LEDs found, giving up.\n");
> + return -ENODEV;
> + }
> +
> + ret = rmi_read_block(hdev, data->f30.query_base_addr, buf, 2);
> + if (ret) {
> + hid_err(hdev, "can not get F30 query registers: %d.\n", ret);
> + return ret;
> + }
> +
> + has_gpio = !!(buf[0] & BIT(3));
> + has_led = !!(buf[0] & BIT(2));
> + data->gpio_led_count = buf[1] & 0x1f;
> +
> + /* retrieve ctrl 2 & 3 registers */
> + bytes_per_ctrl = (data->gpio_led_count + 7) / 8;
> + /* Ctrl0 is present only if both has_gpio and has_led are set*/
> + ctrl2_addr = (has_gpio && has_led) ? bytes_per_ctrl : 0;
> + /* Ctrl1 is always be present */
> + ctrl2_addr += bytes_per_ctrl;
> + ctrl2_3_length = 2 * bytes_per_ctrl;
> +
> + data->f30.report_size = bytes_per_ctrl;
> +
> + ret = rmi_read_block(hdev, data->f30.control_base_addr + ctrl2_addr,
> + buf, ctrl2_3_length);
> + if (ret) {
> + hid_err(hdev, "can not read ctrl 2&3 block of size %d: %d.\n",
> + ctrl2_3_length, ret);
> + return ret;
> + }
> +
> + for (i = 0; i < data->gpio_led_count; i++) {
> + int byte_position = i >> 3;
> + int bit_position = i & 0x07;
> + u8 dir_byte = buf[byte_position];
> + u8 data_byte = buf[byte_position + bytes_per_ctrl];
> + bool dir = (dir_byte >> bit_position) & BIT(0);
> + bool dat = (data_byte >> bit_position) & BIT(0);
> +
> + if (dir == 0) {
> + /* input mode */
> + if (dat) {
> + /* actual buttons have pull up resistor */
> + data->button_count++;
> + set_bit(i, &data->button_mask);
> + set_bit(i, &data->button_state_mask);
> + }
> + }
> +
> + }
> +
> + return 0;
> +}
> +
> +static int rmi_populate(struct hid_device *hdev)
> +{
> + int ret;
> +
> + ret = rmi_scan_pdt(hdev);
> + if (ret) {
> + hid_err(hdev, "PDT scan failed with code %d.\n", ret);
> + return ret;
> + }
> +
> + ret = rmi_populate_f11(hdev);
> + if (ret) {
> + hid_err(hdev, "Error while initializing F11 (%d).\n", ret);
> + return ret;
> + }
> +
> + ret = rmi_populate_f30(hdev);
> + if (ret)
> + hid_warn(hdev, "Error while initializing F30 (%d).\n", ret);
> +
> + return 0;
> +}
> +
> +static void rmi_input_configured(struct hid_device *hdev, struct hid_input *hi)
> +{
> + struct rmi_data *data = hid_get_drvdata(hdev);
> + struct input_dev *input = hi->input;
> + int ret;
> + int res_x, res_y, i;
> +
> + data->input = input;
> +
> + hid_dbg(hdev, "Opening low level driver\n");
> + ret = hid_hw_open(hdev);
> + if (ret)
> + return;
> +
> + /* Allow incoming hid reports */
> + hid_device_io_start(hdev);
> +
> + ret = rmi_set_mode(hdev, RMI_MODE_ATTN_REPORTS);
> + if (ret < 0) {
> + dev_err(&hdev->dev, "failed to set rmi mode\n");
> + goto exit;
> + }
> +
> + ret = rmi_set_page(hdev, 0);
> + if (ret < 0) {
> + dev_err(&hdev->dev, "failed to set page select to 0.\n");
> + goto exit;
> + }
> +
> + ret = rmi_populate(hdev);
> + if (ret)
> + goto exit;
> +
> + __set_bit(EV_ABS, input->evbit);
> + input_set_abs_params(input, ABS_MT_POSITION_X, 1, data->max_x, 0, 0);
> + input_set_abs_params(input, ABS_MT_POSITION_Y, 1, data->max_y, 0, 0);
> +
> + if (data->x_size_mm && data->x_size_mm) {
> + res_x = (data->max_x - 1) / data->x_size_mm;
> + res_y = (data->max_y - 1) / data->x_size_mm;
> +
> + input_abs_set_res(input, ABS_MT_POSITION_X, res_x);
> + input_abs_set_res(input, ABS_MT_POSITION_Y, res_y);
> + }
> +
> + input_set_abs_params(input, ABS_MT_ORIENTATION, 0, 1, 0, 0);
> + input_set_abs_params(input, ABS_MT_PRESSURE, 0, 0xff, 0, 0);
> + input_set_abs_params(input, ABS_MT_TOUCH_MAJOR, 0, 0x0f, 0, 0);
> + input_set_abs_params(input, ABS_MT_TOUCH_MINOR, 0, 0x0f, 0, 0);
> +
> + input_mt_init_slots(input, data->max_fingers, INPUT_MT_POINTER);
> +
> + if (data->button_count) {
> + __set_bit(EV_KEY, input->evbit);
> + for (i = 0; i < data->button_count; i++)
> + __set_bit(BTN_LEFT + i, input->keybit);
> +
> + if (data->button_count == 1)
> + __set_bit(INPUT_PROP_BUTTONPAD, input->propbit);
> + }
> +
> + set_bit(RMI_STARTED, &data->flags);
> +
> +exit:
> + hid_device_io_stop(hdev);
> + hid_hw_close(hdev);
> +}
> +
> +static int rmi_input_mapping(struct hid_device *hdev,
> + struct hid_input *hi, struct hid_field *field,
> + struct hid_usage *usage, unsigned long **bit, int *max)
> +{
> + /* we want to make HID ignore the advertised HID collection */
> + return -1;
> +}
> +
> +static int rmi_probe(struct hid_device *hdev, const struct hid_device_id *id)
> +{
> + struct rmi_data *data = NULL;
> + int ret;
> + size_t alloc_size;
> +
> + data = devm_kzalloc(&hdev->dev, sizeof(struct rmi_data), GFP_KERNEL);
> + if (!data)
> + return -ENOMEM;
> +
> + INIT_WORK(&data->reset_work, rmi_reset_work);
> + data->hdev = hdev;
> +
> + hid_set_drvdata(hdev, data);
> +
> + hdev->quirks |= HID_QUIRK_NO_INIT_REPORTS;
> +
> + ret = hid_parse(hdev);
> + if (ret) {
> + hid_err(hdev, "parse failed\n");
> + return ret;
> + }
> +
> + data->input_report_size = (hdev->report_enum[HID_INPUT_REPORT]
> + .report_id_hash[RMI_ATTN_REPORT_ID]->size >> 3)
> + + 1 /* report id */;
> + data->output_report_size = (hdev->report_enum[HID_OUTPUT_REPORT]
> + .report_id_hash[RMI_WRITE_REPORT_ID]->size >> 3)
> + + 1 /* report id */;
> +
> + alloc_size = data->output_report_size + data->input_report_size;
> +
> + data->writeReport = devm_kzalloc(&hdev->dev, alloc_size, GFP_KERNEL);
> + if (!data->writeReport) {
> + ret = -ENOMEM;
> + return ret;
> + }
> +
> + data->readReport = data->writeReport + data->output_report_size;
> +
> + init_waitqueue_head(&data->wait);
> +
> + mutex_init(&data->page_mutex);
> +
> + ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
> + if (ret) {
> + hid_err(hdev, "hw start failed\n");
> + return ret;
> + }
> +
> + if (!test_bit(RMI_STARTED, &data->flags)) {
> + hid_hw_stop(hdev);
> + return -EIO;
> + }
> +
> + hid_hw_stop(hdev);
> + return 0;
> +}
> +
> +static void rmi_remove(struct hid_device *hdev)
> +{
> + struct rmi_data *hdata = hid_get_drvdata(hdev);
> +
> + clear_bit(RMI_STARTED, &hdata->flags);
> +
> + hid_hw_stop(hdev);
> +}
> +
> +static const struct hid_device_id rmi_id[] = {
> + { HID_I2C_DEVICE(USB_VENDOR_ID_SYNAPTICS, HID_ANY_ID) },
> + { HID_USB_DEVICE(USB_VENDOR_ID_SYNAPTICS, HID_ANY_ID) },
> + { }
> +};
> +MODULE_DEVICE_TABLE(hid, rmi_id);
> +
> +static struct hid_driver rmi_driver = {
> + .name = "hid-rmi",
> + .id_table = rmi_id,
> + .probe = rmi_probe,
> + .remove = rmi_remove,
> + .raw_event = rmi_raw_event,
> + .input_mapping = rmi_input_mapping,
> + .input_configured = rmi_input_configured,
> +#ifdef CONFIG_PM
> + .resume = rmi_post_resume,
> + .reset_resume = rmi_post_reset,
> +#endif
> +};
> +
> +module_hid_driver(rmi_driver);
> +
> +MODULE_AUTHOR("Andrew Duggan <[email protected]>");
> +MODULE_DESCRIPTION("RMI HID driver");
> +MODULE_LICENSE("GPL");
> diff --git a/include/linux/hid.h b/include/linux/hid.h
> index 720e3a1..54f855b 100644
> --- a/include/linux/hid.h
> +++ b/include/linux/hid.h
> @@ -570,6 +570,8 @@ struct hid_descriptor {
> .bus = BUS_USB, .vendor = (ven), .product = (prod)
> #define HID_BLUETOOTH_DEVICE(ven, prod) \
> .bus = BUS_BLUETOOTH, .vendor = (ven), .product = (prod)
> +#define HID_I2C_DEVICE(ven, prod) \
> + .bus = BUS_I2C, .vendor = (ven), .product = (prod)
>
> #define HID_REPORT_ID(rep) \
> .report_type = (rep)
> --
> 1.9.0
>
> --
> To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
> the body of a message to [email protected]
> More majordomo info at http://vger.kernel.org/majordomo-info.html
> Please read the FAQ at http://www.tux.org/lkml/

2014-04-09 14:03:28

by Jiri Kosina

[permalink] [raw]
Subject: Re: [PATCH] HID: rmi: introduce RMI driver for Synaptics touchpads

On Mon, 7 Apr 2014, Benjamin Tissoires wrote:

> This driver add support for RMI4 over USB or I2C.
> The current state is that it uses its own RMI4 implementation, but once
> RMI4 is merged upstream, the driver will be a transport driver for the
> RMI4 library.

Queuing for 3.16, thanks.

--
Jiri Kosina
SUSE Labs