mirror of
https://github.com/AuxXxilium/linux_dsm_epyc7002.git
synced 2024-12-28 11:18:45 +07:00
82023bb7f7
- ACPI-based device hotplug fixes for issues introduced recently and a fix for an older error code path bug in the ACPI PCI host bridge driver. - Fix for recently broken OMAP cpufreq build from Viresh Kumar. - Fix for a recent hibernation regression related to s2disk. - Fix for a locking-related regression in the ACPI EC driver from Puneet Kumar. - System suspend error code path fix related to runtime PM and runtime PM documentation update from Ulf Hansson. - cpufreq's conservative governor fix from Xiaoguang Chen. - New processor IDs for intel_idle and turbostat and removal of an obsolete Kconfig option from Len Brown. - New device IDs for the ACPI LPSS (Low-Power Subsystem) driver and ACPI-based PCI hotplug (ACPIPHP) cleanup from Mika Westerberg. - Removal of several ACPI video DMI blacklist entries that are not necessary any more from Aaron Lu. - Rework of the ACPI companion representation in struct device and code cleanup related to that change from Rafael J Wysocki, Lan Tianyu and Jarkko Nikula. - Fixes for assigning names to ACPI-enumerated I2C and SPI devices from Jarkko Nikula. / -----BEGIN PGP SIGNATURE----- Version: GnuPG v2.0.19 (GNU/Linux) iQIcBAABCAAGBQJSjLYNAAoJEILEb/54YlRxkEQP/1pmFWNwSsxLtTHd+PEs0Xbo QccqvjQrnw/c8GcmK4eZrz6/xyuepmmjy9kfRKj2ENZniy0NEsSFqkTdSO3vYlva 8HKWUj7MV3evhFERXAF6Tu0b4Enx4jOP7VMtmYxJo3qrSnKRUcUzc6DGv/ACsUT1 Nkj0Lhdsg053Z+YzIXrl50w0tCDEMhVmWlMHBtYgr+dMNVnkfPBGkqMblMkKCXT2 w/yHvauZlxQHtI+8bVqTuGgNN0CPzdlpFGiuUF+5mDf6dRX8zlSn56Ia+Wyw1k9X dQp4jYQOgPRo03rNKqQPDiPxUdc7T0RAHRvDB51Ncweuh5PfZGguQe71p6/LKY2W i6zblZ0f/vc13hTiMrP+qzKcwZvgPB5DH7SfnHr61JKV7GNFCdYAqoceS5hYMzR9 d2Fd+txgm763IHWewXfDS/G2cU492R5qr4jpmUIACBQKWDZcqmSRDwRj83t56Ltb jgFBMbg4vZxG7IARhind74xsALxdhsgmFjPmx+0qPWjYxcU8otQZpXbgGNI9iOuW pxIQv5WPQW0tTmwO4HSuVCOwDPLPz5R0jkev7SvSj3Ek3TeD7He4LmnK055CATiC puq+6dp1FISPOPJYk+0DI61qN/CB/qNwRp8LU3ctZwudPVhznIE9FFQ3iN1FdBg2 X8VDcT9t7VvVuxSBjgkj =QMp+ -----END PGP SIGNATURE----- Merge tag 'pm+acpi-2-3.13-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/rafael/linux-pm Pull more ACPI and power management updates from Rafael Wysocki: - ACPI-based device hotplug fixes for issues introduced recently and a fix for an older error code path bug in the ACPI PCI host bridge driver - Fix for recently broken OMAP cpufreq build from Viresh Kumar - Fix for a recent hibernation regression related to s2disk - Fix for a locking-related regression in the ACPI EC driver from Puneet Kumar - System suspend error code path fix related to runtime PM and runtime PM documentation update from Ulf Hansson - cpufreq's conservative governor fix from Xiaoguang Chen - New processor IDs for intel_idle and turbostat and removal of an obsolete Kconfig option from Len Brown - New device IDs for the ACPI LPSS (Low-Power Subsystem) driver and ACPI-based PCI hotplug (ACPIPHP) cleanup from Mika Westerberg - Removal of several ACPI video DMI blacklist entries that are not necessary any more from Aaron Lu - Rework of the ACPI companion representation in struct device and code cleanup related to that change from Rafael J Wysocki, Lan Tianyu and Jarkko Nikula - Fixes for assigning names to ACPI-enumerated I2C and SPI devices from Jarkko Nikula * tag 'pm+acpi-2-3.13-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/rafael/linux-pm: (24 commits) PCI / hotplug / ACPI: Drop unused acpiphp_debug declaration ACPI / scan: Set flags.match_driver in acpi_bus_scan_fixed() ACPI / PCI root: Clear driver_data before failing enumeration ACPI / hotplug: Fix PCI host bridge hot removal ACPI / hotplug: Fix acpi_bus_get_device() return value check cpufreq: governor: Remove fossil comment in the cpufreq_governor_dbs() ACPI / video: clean up DMI table for initial black screen problem ACPI / EC: Ensure lock is acquired before accessing ec struct members PM / Hibernate: Do not crash kernel in free_basic_memory_bitmaps() ACPI / AC: Remove struct acpi_device pointer from struct acpi_ac spi: Use stable dev_name for ACPI enumerated SPI slaves i2c: Use stable dev_name for ACPI enumerated I2C slaves ACPI: Provide acpi_dev_name accessor for struct acpi_device device name ACPI / bind: Use (put|get)_device() on ACPI device objects too ACPI: Eliminate the DEVICE_ACPI_HANDLE() macro ACPI / driver core: Store an ACPI device pointer in struct acpi_dev_node cpufreq: OMAP: Fix compilation error 'r & ret undeclared' PM / Runtime: Fix error path for prepare PM / Runtime: Update documentation around probe|remove|suspend cpufreq: conservative: set requested_freq to policy max when it is over policy max ...
1118 lines
27 KiB
C
1118 lines
27 KiB
C
/*
|
|
* HID over I2C protocol implementation
|
|
*
|
|
* Copyright (c) 2012 Benjamin Tissoires <benjamin.tissoires@gmail.com>
|
|
* Copyright (c) 2012 Ecole Nationale de l'Aviation Civile, France
|
|
* Copyright (c) 2012 Red Hat, Inc
|
|
*
|
|
* This code is partly based on "USB HID support for Linux":
|
|
*
|
|
* Copyright (c) 1999 Andreas Gal
|
|
* Copyright (c) 2000-2005 Vojtech Pavlik <vojtech@suse.cz>
|
|
* Copyright (c) 2005 Michael Haboustak <mike-@cinci.rr.com> for Concept2, Inc
|
|
* Copyright (c) 2007-2008 Oliver Neukum
|
|
* Copyright (c) 2006-2010 Jiri Kosina
|
|
*
|
|
* This file is subject to the terms and conditions of the GNU General Public
|
|
* License. See the file COPYING in the main directory of this archive for
|
|
* more details.
|
|
*/
|
|
|
|
#include <linux/module.h>
|
|
#include <linux/i2c.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/input.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/pm.h>
|
|
#include <linux/device.h>
|
|
#include <linux/wait.h>
|
|
#include <linux/err.h>
|
|
#include <linux/string.h>
|
|
#include <linux/list.h>
|
|
#include <linux/jiffies.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/hid.h>
|
|
#include <linux/mutex.h>
|
|
#include <linux/acpi.h>
|
|
#include <linux/of.h>
|
|
|
|
#include <linux/i2c/i2c-hid.h>
|
|
|
|
/* flags */
|
|
#define I2C_HID_STARTED (1 << 0)
|
|
#define I2C_HID_RESET_PENDING (1 << 1)
|
|
#define I2C_HID_READ_PENDING (1 << 2)
|
|
|
|
#define I2C_HID_PWR_ON 0x00
|
|
#define I2C_HID_PWR_SLEEP 0x01
|
|
|
|
/* debug option */
|
|
static bool debug;
|
|
module_param(debug, bool, 0444);
|
|
MODULE_PARM_DESC(debug, "print a lot of debug information");
|
|
|
|
#define i2c_hid_dbg(ihid, fmt, arg...) \
|
|
do { \
|
|
if (debug) \
|
|
dev_printk(KERN_DEBUG, &(ihid)->client->dev, fmt, ##arg); \
|
|
} while (0)
|
|
|
|
struct i2c_hid_desc {
|
|
__le16 wHIDDescLength;
|
|
__le16 bcdVersion;
|
|
__le16 wReportDescLength;
|
|
__le16 wReportDescRegister;
|
|
__le16 wInputRegister;
|
|
__le16 wMaxInputLength;
|
|
__le16 wOutputRegister;
|
|
__le16 wMaxOutputLength;
|
|
__le16 wCommandRegister;
|
|
__le16 wDataRegister;
|
|
__le16 wVendorID;
|
|
__le16 wProductID;
|
|
__le16 wVersionID;
|
|
__le32 reserved;
|
|
} __packed;
|
|
|
|
struct i2c_hid_cmd {
|
|
unsigned int registerIndex;
|
|
__u8 opcode;
|
|
unsigned int length;
|
|
bool wait;
|
|
};
|
|
|
|
union command {
|
|
u8 data[0];
|
|
struct cmd {
|
|
__le16 reg;
|
|
__u8 reportTypeID;
|
|
__u8 opcode;
|
|
} __packed c;
|
|
};
|
|
|
|
#define I2C_HID_CMD(opcode_) \
|
|
.opcode = opcode_, .length = 4, \
|
|
.registerIndex = offsetof(struct i2c_hid_desc, wCommandRegister)
|
|
|
|
/* fetch HID descriptor */
|
|
static const struct i2c_hid_cmd hid_descr_cmd = { .length = 2 };
|
|
/* fetch report descriptors */
|
|
static const struct i2c_hid_cmd hid_report_descr_cmd = {
|
|
.registerIndex = offsetof(struct i2c_hid_desc,
|
|
wReportDescRegister),
|
|
.opcode = 0x00,
|
|
.length = 2 };
|
|
/* commands */
|
|
static const struct i2c_hid_cmd hid_reset_cmd = { I2C_HID_CMD(0x01),
|
|
.wait = true };
|
|
static const struct i2c_hid_cmd hid_get_report_cmd = { I2C_HID_CMD(0x02) };
|
|
static const struct i2c_hid_cmd hid_set_report_cmd = { I2C_HID_CMD(0x03) };
|
|
static const struct i2c_hid_cmd hid_set_power_cmd = { I2C_HID_CMD(0x08) };
|
|
static const struct i2c_hid_cmd hid_no_cmd = { .length = 0 };
|
|
|
|
/*
|
|
* These definitions are not used here, but are defined by the spec.
|
|
* Keeping them here for documentation purposes.
|
|
*
|
|
* static const struct i2c_hid_cmd hid_get_idle_cmd = { I2C_HID_CMD(0x04) };
|
|
* static const struct i2c_hid_cmd hid_set_idle_cmd = { I2C_HID_CMD(0x05) };
|
|
* static const struct i2c_hid_cmd hid_get_protocol_cmd = { I2C_HID_CMD(0x06) };
|
|
* static const struct i2c_hid_cmd hid_set_protocol_cmd = { I2C_HID_CMD(0x07) };
|
|
*/
|
|
|
|
static DEFINE_MUTEX(i2c_hid_open_mut);
|
|
|
|
/* The main device structure */
|
|
struct i2c_hid {
|
|
struct i2c_client *client; /* i2c client */
|
|
struct hid_device *hid; /* pointer to corresponding HID dev */
|
|
union {
|
|
__u8 hdesc_buffer[sizeof(struct i2c_hid_desc)];
|
|
struct i2c_hid_desc hdesc; /* the HID Descriptor */
|
|
};
|
|
__le16 wHIDDescRegister; /* location of the i2c
|
|
* register of the HID
|
|
* descriptor. */
|
|
unsigned int bufsize; /* i2c buffer size */
|
|
char *inbuf; /* Input buffer */
|
|
char *cmdbuf; /* Command buffer */
|
|
char *argsbuf; /* Command arguments buffer */
|
|
|
|
unsigned long flags; /* device flags */
|
|
|
|
wait_queue_head_t wait; /* For waiting the interrupt */
|
|
|
|
struct i2c_hid_platform_data pdata;
|
|
};
|
|
|
|
static int __i2c_hid_command(struct i2c_client *client,
|
|
const struct i2c_hid_cmd *command, u8 reportID,
|
|
u8 reportType, u8 *args, int args_len,
|
|
unsigned char *buf_recv, int data_len)
|
|
{
|
|
struct i2c_hid *ihid = i2c_get_clientdata(client);
|
|
union command *cmd = (union command *)ihid->cmdbuf;
|
|
int ret;
|
|
struct i2c_msg msg[2];
|
|
int msg_num = 1;
|
|
|
|
int length = command->length;
|
|
bool wait = command->wait;
|
|
unsigned int registerIndex = command->registerIndex;
|
|
|
|
/* special case for hid_descr_cmd */
|
|
if (command == &hid_descr_cmd) {
|
|
cmd->c.reg = ihid->wHIDDescRegister;
|
|
} else {
|
|
cmd->data[0] = ihid->hdesc_buffer[registerIndex];
|
|
cmd->data[1] = ihid->hdesc_buffer[registerIndex + 1];
|
|
}
|
|
|
|
if (length > 2) {
|
|
cmd->c.opcode = command->opcode;
|
|
cmd->c.reportTypeID = reportID | reportType << 4;
|
|
}
|
|
|
|
memcpy(cmd->data + length, args, args_len);
|
|
length += args_len;
|
|
|
|
i2c_hid_dbg(ihid, "%s: cmd=%*ph\n", __func__, length, cmd->data);
|
|
|
|
msg[0].addr = client->addr;
|
|
msg[0].flags = client->flags & I2C_M_TEN;
|
|
msg[0].len = length;
|
|
msg[0].buf = cmd->data;
|
|
if (data_len > 0) {
|
|
msg[1].addr = client->addr;
|
|
msg[1].flags = client->flags & I2C_M_TEN;
|
|
msg[1].flags |= I2C_M_RD;
|
|
msg[1].len = data_len;
|
|
msg[1].buf = buf_recv;
|
|
msg_num = 2;
|
|
set_bit(I2C_HID_READ_PENDING, &ihid->flags);
|
|
}
|
|
|
|
if (wait)
|
|
set_bit(I2C_HID_RESET_PENDING, &ihid->flags);
|
|
|
|
ret = i2c_transfer(client->adapter, msg, msg_num);
|
|
|
|
if (data_len > 0)
|
|
clear_bit(I2C_HID_READ_PENDING, &ihid->flags);
|
|
|
|
if (ret != msg_num)
|
|
return ret < 0 ? ret : -EIO;
|
|
|
|
ret = 0;
|
|
|
|
if (wait) {
|
|
i2c_hid_dbg(ihid, "%s: waiting...\n", __func__);
|
|
if (!wait_event_timeout(ihid->wait,
|
|
!test_bit(I2C_HID_RESET_PENDING, &ihid->flags),
|
|
msecs_to_jiffies(5000)))
|
|
ret = -ENODATA;
|
|
i2c_hid_dbg(ihid, "%s: finished.\n", __func__);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int i2c_hid_command(struct i2c_client *client,
|
|
const struct i2c_hid_cmd *command,
|
|
unsigned char *buf_recv, int data_len)
|
|
{
|
|
return __i2c_hid_command(client, command, 0, 0, NULL, 0,
|
|
buf_recv, data_len);
|
|
}
|
|
|
|
static int i2c_hid_get_report(struct i2c_client *client, u8 reportType,
|
|
u8 reportID, unsigned char *buf_recv, int data_len)
|
|
{
|
|
struct i2c_hid *ihid = i2c_get_clientdata(client);
|
|
u8 args[3];
|
|
int ret;
|
|
int args_len = 0;
|
|
u16 readRegister = le16_to_cpu(ihid->hdesc.wDataRegister);
|
|
|
|
i2c_hid_dbg(ihid, "%s\n", __func__);
|
|
|
|
if (reportID >= 0x0F) {
|
|
args[args_len++] = reportID;
|
|
reportID = 0x0F;
|
|
}
|
|
|
|
args[args_len++] = readRegister & 0xFF;
|
|
args[args_len++] = readRegister >> 8;
|
|
|
|
ret = __i2c_hid_command(client, &hid_get_report_cmd, reportID,
|
|
reportType, args, args_len, buf_recv, data_len);
|
|
if (ret) {
|
|
dev_err(&client->dev,
|
|
"failed to retrieve report from device.\n");
|
|
return ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int i2c_hid_set_report(struct i2c_client *client, u8 reportType,
|
|
u8 reportID, unsigned char *buf, size_t data_len)
|
|
{
|
|
struct i2c_hid *ihid = i2c_get_clientdata(client);
|
|
u8 *args = ihid->argsbuf;
|
|
const struct i2c_hid_cmd * hidcmd = &hid_set_report_cmd;
|
|
int ret;
|
|
u16 dataRegister = le16_to_cpu(ihid->hdesc.wDataRegister);
|
|
u16 outputRegister = le16_to_cpu(ihid->hdesc.wOutputRegister);
|
|
u16 maxOutputLength = le16_to_cpu(ihid->hdesc.wMaxOutputLength);
|
|
|
|
/* hidraw already checked that data_len < HID_MAX_BUFFER_SIZE */
|
|
u16 size = 2 /* size */ +
|
|
(reportID ? 1 : 0) /* reportID */ +
|
|
data_len /* buf */;
|
|
int args_len = (reportID >= 0x0F ? 1 : 0) /* optional third byte */ +
|
|
2 /* dataRegister */ +
|
|
size /* args */;
|
|
int index = 0;
|
|
|
|
i2c_hid_dbg(ihid, "%s\n", __func__);
|
|
|
|
if (reportID >= 0x0F) {
|
|
args[index++] = reportID;
|
|
reportID = 0x0F;
|
|
}
|
|
|
|
/*
|
|
* use the data register for feature reports or if the device does not
|
|
* support the output register
|
|
*/
|
|
if (reportType == 0x03 || maxOutputLength == 0) {
|
|
args[index++] = dataRegister & 0xFF;
|
|
args[index++] = dataRegister >> 8;
|
|
} else {
|
|
args[index++] = outputRegister & 0xFF;
|
|
args[index++] = outputRegister >> 8;
|
|
hidcmd = &hid_no_cmd;
|
|
}
|
|
|
|
args[index++] = size & 0xFF;
|
|
args[index++] = size >> 8;
|
|
|
|
if (reportID)
|
|
args[index++] = reportID;
|
|
|
|
memcpy(&args[index], buf, data_len);
|
|
|
|
ret = __i2c_hid_command(client, hidcmd, reportID,
|
|
reportType, args, args_len, NULL, 0);
|
|
if (ret) {
|
|
dev_err(&client->dev, "failed to set a report to device.\n");
|
|
return ret;
|
|
}
|
|
|
|
return data_len;
|
|
}
|
|
|
|
static int i2c_hid_set_power(struct i2c_client *client, int power_state)
|
|
{
|
|
struct i2c_hid *ihid = i2c_get_clientdata(client);
|
|
int ret;
|
|
|
|
i2c_hid_dbg(ihid, "%s\n", __func__);
|
|
|
|
ret = __i2c_hid_command(client, &hid_set_power_cmd, power_state,
|
|
0, NULL, 0, NULL, 0);
|
|
if (ret)
|
|
dev_err(&client->dev, "failed to change power setting.\n");
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int i2c_hid_hwreset(struct i2c_client *client)
|
|
{
|
|
struct i2c_hid *ihid = i2c_get_clientdata(client);
|
|
int ret;
|
|
|
|
i2c_hid_dbg(ihid, "%s\n", __func__);
|
|
|
|
ret = i2c_hid_set_power(client, I2C_HID_PWR_ON);
|
|
if (ret)
|
|
return ret;
|
|
|
|
i2c_hid_dbg(ihid, "resetting...\n");
|
|
|
|
ret = i2c_hid_command(client, &hid_reset_cmd, NULL, 0);
|
|
if (ret) {
|
|
dev_err(&client->dev, "failed to reset device.\n");
|
|
i2c_hid_set_power(client, I2C_HID_PWR_SLEEP);
|
|
return ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void i2c_hid_get_input(struct i2c_hid *ihid)
|
|
{
|
|
int ret, ret_size;
|
|
int size = le16_to_cpu(ihid->hdesc.wMaxInputLength);
|
|
|
|
ret = i2c_master_recv(ihid->client, ihid->inbuf, size);
|
|
if (ret != size) {
|
|
if (ret < 0)
|
|
return;
|
|
|
|
dev_err(&ihid->client->dev, "%s: got %d data instead of %d\n",
|
|
__func__, ret, size);
|
|
return;
|
|
}
|
|
|
|
ret_size = ihid->inbuf[0] | ihid->inbuf[1] << 8;
|
|
|
|
if (!ret_size) {
|
|
/* host or device initiated RESET completed */
|
|
if (test_and_clear_bit(I2C_HID_RESET_PENDING, &ihid->flags))
|
|
wake_up(&ihid->wait);
|
|
return;
|
|
}
|
|
|
|
if (ret_size > size) {
|
|
dev_err(&ihid->client->dev, "%s: incomplete report (%d/%d)\n",
|
|
__func__, size, ret_size);
|
|
return;
|
|
}
|
|
|
|
i2c_hid_dbg(ihid, "input: %*ph\n", ret_size, ihid->inbuf);
|
|
|
|
if (test_bit(I2C_HID_STARTED, &ihid->flags))
|
|
hid_input_report(ihid->hid, HID_INPUT_REPORT, ihid->inbuf + 2,
|
|
ret_size - 2, 1);
|
|
|
|
return;
|
|
}
|
|
|
|
static irqreturn_t i2c_hid_irq(int irq, void *dev_id)
|
|
{
|
|
struct i2c_hid *ihid = dev_id;
|
|
|
|
if (test_bit(I2C_HID_READ_PENDING, &ihid->flags))
|
|
return IRQ_HANDLED;
|
|
|
|
i2c_hid_get_input(ihid);
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static int i2c_hid_get_report_length(struct hid_report *report)
|
|
{
|
|
return ((report->size - 1) >> 3) + 1 +
|
|
report->device->report_enum[report->type].numbered + 2;
|
|
}
|
|
|
|
static void i2c_hid_init_report(struct hid_report *report, u8 *buffer,
|
|
size_t bufsize)
|
|
{
|
|
struct hid_device *hid = report->device;
|
|
struct i2c_client *client = hid->driver_data;
|
|
struct i2c_hid *ihid = i2c_get_clientdata(client);
|
|
unsigned int size, ret_size;
|
|
|
|
size = i2c_hid_get_report_length(report);
|
|
if (i2c_hid_get_report(client,
|
|
report->type == HID_FEATURE_REPORT ? 0x03 : 0x01,
|
|
report->id, buffer, size))
|
|
return;
|
|
|
|
i2c_hid_dbg(ihid, "report (len=%d): %*ph\n", size, size, ihid->inbuf);
|
|
|
|
ret_size = buffer[0] | (buffer[1] << 8);
|
|
|
|
if (ret_size != size) {
|
|
dev_err(&client->dev, "error in %s size:%d / ret_size:%d\n",
|
|
__func__, size, ret_size);
|
|
return;
|
|
}
|
|
|
|
/* hid->driver_lock is held as we are in probe function,
|
|
* we just need to setup the input fields, so using
|
|
* hid_report_raw_event is safe. */
|
|
hid_report_raw_event(hid, report->type, buffer + 2, size - 2, 1);
|
|
}
|
|
|
|
/*
|
|
* Initialize all reports
|
|
*/
|
|
static void i2c_hid_init_reports(struct hid_device *hid)
|
|
{
|
|
struct hid_report *report;
|
|
struct i2c_client *client = hid->driver_data;
|
|
struct i2c_hid *ihid = i2c_get_clientdata(client);
|
|
u8 *inbuf = kzalloc(ihid->bufsize, GFP_KERNEL);
|
|
|
|
if (!inbuf) {
|
|
dev_err(&client->dev, "can not retrieve initial reports\n");
|
|
return;
|
|
}
|
|
|
|
list_for_each_entry(report,
|
|
&hid->report_enum[HID_FEATURE_REPORT].report_list, list)
|
|
i2c_hid_init_report(report, inbuf, ihid->bufsize);
|
|
|
|
kfree(inbuf);
|
|
}
|
|
|
|
/*
|
|
* Traverse the supplied list of reports and find the longest
|
|
*/
|
|
static void i2c_hid_find_max_report(struct hid_device *hid, unsigned int type,
|
|
unsigned int *max)
|
|
{
|
|
struct hid_report *report;
|
|
unsigned int size;
|
|
|
|
/* We should not rely on wMaxInputLength, as some devices may set it to
|
|
* a wrong length. */
|
|
list_for_each_entry(report, &hid->report_enum[type].report_list, list) {
|
|
size = i2c_hid_get_report_length(report);
|
|
if (*max < size)
|
|
*max = size;
|
|
}
|
|
}
|
|
|
|
static void i2c_hid_free_buffers(struct i2c_hid *ihid)
|
|
{
|
|
kfree(ihid->inbuf);
|
|
kfree(ihid->argsbuf);
|
|
kfree(ihid->cmdbuf);
|
|
ihid->inbuf = NULL;
|
|
ihid->cmdbuf = NULL;
|
|
ihid->argsbuf = NULL;
|
|
ihid->bufsize = 0;
|
|
}
|
|
|
|
static int i2c_hid_alloc_buffers(struct i2c_hid *ihid, size_t report_size)
|
|
{
|
|
/* the worst case is computed from the set_report command with a
|
|
* reportID > 15 and the maximum report length */
|
|
int args_len = sizeof(__u8) + /* optional ReportID byte */
|
|
sizeof(__u16) + /* data register */
|
|
sizeof(__u16) + /* size of the report */
|
|
report_size; /* report */
|
|
|
|
ihid->inbuf = kzalloc(report_size, GFP_KERNEL);
|
|
ihid->argsbuf = kzalloc(args_len, GFP_KERNEL);
|
|
ihid->cmdbuf = kzalloc(sizeof(union command) + args_len, GFP_KERNEL);
|
|
|
|
if (!ihid->inbuf || !ihid->argsbuf || !ihid->cmdbuf) {
|
|
i2c_hid_free_buffers(ihid);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
ihid->bufsize = report_size;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int i2c_hid_get_raw_report(struct hid_device *hid,
|
|
unsigned char report_number, __u8 *buf, size_t count,
|
|
unsigned char report_type)
|
|
{
|
|
struct i2c_client *client = hid->driver_data;
|
|
struct i2c_hid *ihid = i2c_get_clientdata(client);
|
|
size_t ret_count, ask_count;
|
|
int ret;
|
|
|
|
if (report_type == HID_OUTPUT_REPORT)
|
|
return -EINVAL;
|
|
|
|
/* +2 bytes to include the size of the reply in the query buffer */
|
|
ask_count = min(count + 2, (size_t)ihid->bufsize);
|
|
|
|
ret = i2c_hid_get_report(client,
|
|
report_type == HID_FEATURE_REPORT ? 0x03 : 0x01,
|
|
report_number, ihid->inbuf, ask_count);
|
|
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
ret_count = ihid->inbuf[0] | (ihid->inbuf[1] << 8);
|
|
|
|
if (ret_count <= 2)
|
|
return 0;
|
|
|
|
ret_count = min(ret_count, ask_count);
|
|
|
|
/* The query buffer contains the size, dropping it in the reply */
|
|
count = min(count, ret_count - 2);
|
|
memcpy(buf, ihid->inbuf + 2, count);
|
|
|
|
return count;
|
|
}
|
|
|
|
static int i2c_hid_output_raw_report(struct hid_device *hid, __u8 *buf,
|
|
size_t count, unsigned char report_type)
|
|
{
|
|
struct i2c_client *client = hid->driver_data;
|
|
int report_id = buf[0];
|
|
int ret;
|
|
|
|
if (report_type == HID_INPUT_REPORT)
|
|
return -EINVAL;
|
|
|
|
if (report_id) {
|
|
buf++;
|
|
count--;
|
|
}
|
|
|
|
ret = i2c_hid_set_report(client,
|
|
report_type == HID_FEATURE_REPORT ? 0x03 : 0x02,
|
|
report_id, buf, count);
|
|
|
|
if (report_id && ret >= 0)
|
|
ret++; /* add report_id to the number of transfered bytes */
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void i2c_hid_request(struct hid_device *hid, struct hid_report *rep,
|
|
int reqtype)
|
|
{
|
|
struct i2c_client *client = hid->driver_data;
|
|
char *buf;
|
|
int ret;
|
|
int len = i2c_hid_get_report_length(rep) - 2;
|
|
|
|
buf = kzalloc(len, GFP_KERNEL);
|
|
if (!buf)
|
|
return;
|
|
|
|
switch (reqtype) {
|
|
case HID_REQ_GET_REPORT:
|
|
ret = i2c_hid_get_raw_report(hid, rep->id, buf, len, rep->type);
|
|
if (ret < 0)
|
|
dev_err(&client->dev, "%s: unable to get report: %d\n",
|
|
__func__, ret);
|
|
else
|
|
hid_input_report(hid, rep->type, buf, ret, 0);
|
|
break;
|
|
case HID_REQ_SET_REPORT:
|
|
hid_output_report(rep, buf);
|
|
i2c_hid_output_raw_report(hid, buf, len, rep->type);
|
|
break;
|
|
}
|
|
|
|
kfree(buf);
|
|
}
|
|
|
|
static int i2c_hid_parse(struct hid_device *hid)
|
|
{
|
|
struct i2c_client *client = hid->driver_data;
|
|
struct i2c_hid *ihid = i2c_get_clientdata(client);
|
|
struct i2c_hid_desc *hdesc = &ihid->hdesc;
|
|
unsigned int rsize;
|
|
char *rdesc;
|
|
int ret;
|
|
int tries = 3;
|
|
|
|
i2c_hid_dbg(ihid, "entering %s\n", __func__);
|
|
|
|
rsize = le16_to_cpu(hdesc->wReportDescLength);
|
|
if (!rsize || rsize > HID_MAX_DESCRIPTOR_SIZE) {
|
|
dbg_hid("weird size of report descriptor (%u)\n", rsize);
|
|
return -EINVAL;
|
|
}
|
|
|
|
do {
|
|
ret = i2c_hid_hwreset(client);
|
|
if (ret)
|
|
msleep(1000);
|
|
} while (tries-- > 0 && ret);
|
|
|
|
if (ret)
|
|
return ret;
|
|
|
|
rdesc = kzalloc(rsize, GFP_KERNEL);
|
|
|
|
if (!rdesc) {
|
|
dbg_hid("couldn't allocate rdesc memory\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
i2c_hid_dbg(ihid, "asking HID report descriptor\n");
|
|
|
|
ret = i2c_hid_command(client, &hid_report_descr_cmd, rdesc, rsize);
|
|
if (ret) {
|
|
hid_err(hid, "reading report descriptor failed\n");
|
|
kfree(rdesc);
|
|
return -EIO;
|
|
}
|
|
|
|
i2c_hid_dbg(ihid, "Report Descriptor: %*ph\n", rsize, rdesc);
|
|
|
|
ret = hid_parse_report(hid, rdesc, rsize);
|
|
kfree(rdesc);
|
|
if (ret) {
|
|
dbg_hid("parsing report descriptor failed\n");
|
|
return ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int i2c_hid_start(struct hid_device *hid)
|
|
{
|
|
struct i2c_client *client = hid->driver_data;
|
|
struct i2c_hid *ihid = i2c_get_clientdata(client);
|
|
int ret;
|
|
unsigned int bufsize = HID_MIN_BUFFER_SIZE;
|
|
|
|
i2c_hid_find_max_report(hid, HID_INPUT_REPORT, &bufsize);
|
|
i2c_hid_find_max_report(hid, HID_OUTPUT_REPORT, &bufsize);
|
|
i2c_hid_find_max_report(hid, HID_FEATURE_REPORT, &bufsize);
|
|
|
|
if (bufsize > ihid->bufsize) {
|
|
i2c_hid_free_buffers(ihid);
|
|
|
|
ret = i2c_hid_alloc_buffers(ihid, bufsize);
|
|
|
|
if (ret)
|
|
return ret;
|
|
}
|
|
|
|
if (!(hid->quirks & HID_QUIRK_NO_INIT_REPORTS))
|
|
i2c_hid_init_reports(hid);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void i2c_hid_stop(struct hid_device *hid)
|
|
{
|
|
struct i2c_client *client = hid->driver_data;
|
|
struct i2c_hid *ihid = i2c_get_clientdata(client);
|
|
|
|
hid->claimed = 0;
|
|
|
|
i2c_hid_free_buffers(ihid);
|
|
}
|
|
|
|
static int i2c_hid_open(struct hid_device *hid)
|
|
{
|
|
struct i2c_client *client = hid->driver_data;
|
|
struct i2c_hid *ihid = i2c_get_clientdata(client);
|
|
int ret = 0;
|
|
|
|
mutex_lock(&i2c_hid_open_mut);
|
|
if (!hid->open++) {
|
|
ret = i2c_hid_set_power(client, I2C_HID_PWR_ON);
|
|
if (ret) {
|
|
hid->open--;
|
|
goto done;
|
|
}
|
|
set_bit(I2C_HID_STARTED, &ihid->flags);
|
|
}
|
|
done:
|
|
mutex_unlock(&i2c_hid_open_mut);
|
|
return ret;
|
|
}
|
|
|
|
static void i2c_hid_close(struct hid_device *hid)
|
|
{
|
|
struct i2c_client *client = hid->driver_data;
|
|
struct i2c_hid *ihid = i2c_get_clientdata(client);
|
|
|
|
/* protecting hid->open to make sure we don't restart
|
|
* data acquistion due to a resumption we no longer
|
|
* care about
|
|
*/
|
|
mutex_lock(&i2c_hid_open_mut);
|
|
if (!--hid->open) {
|
|
clear_bit(I2C_HID_STARTED, &ihid->flags);
|
|
|
|
/* Save some power */
|
|
i2c_hid_set_power(client, I2C_HID_PWR_SLEEP);
|
|
}
|
|
mutex_unlock(&i2c_hid_open_mut);
|
|
}
|
|
|
|
static int i2c_hid_power(struct hid_device *hid, int lvl)
|
|
{
|
|
struct i2c_client *client = hid->driver_data;
|
|
struct i2c_hid *ihid = i2c_get_clientdata(client);
|
|
int ret = 0;
|
|
|
|
i2c_hid_dbg(ihid, "%s lvl:%d\n", __func__, lvl);
|
|
|
|
switch (lvl) {
|
|
case PM_HINT_FULLON:
|
|
ret = i2c_hid_set_power(client, I2C_HID_PWR_ON);
|
|
break;
|
|
case PM_HINT_NORMAL:
|
|
ret = i2c_hid_set_power(client, I2C_HID_PWR_SLEEP);
|
|
break;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static struct hid_ll_driver i2c_hid_ll_driver = {
|
|
.parse = i2c_hid_parse,
|
|
.start = i2c_hid_start,
|
|
.stop = i2c_hid_stop,
|
|
.open = i2c_hid_open,
|
|
.close = i2c_hid_close,
|
|
.power = i2c_hid_power,
|
|
.request = i2c_hid_request,
|
|
};
|
|
|
|
static int i2c_hid_init_irq(struct i2c_client *client)
|
|
{
|
|
struct i2c_hid *ihid = i2c_get_clientdata(client);
|
|
int ret;
|
|
|
|
dev_dbg(&client->dev, "Requesting IRQ: %d\n", client->irq);
|
|
|
|
ret = request_threaded_irq(client->irq, NULL, i2c_hid_irq,
|
|
IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
|
|
client->name, ihid);
|
|
if (ret < 0) {
|
|
dev_warn(&client->dev,
|
|
"Could not register for %s interrupt, irq = %d,"
|
|
" ret = %d\n",
|
|
client->name, client->irq, ret);
|
|
|
|
return ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int i2c_hid_fetch_hid_descriptor(struct i2c_hid *ihid)
|
|
{
|
|
struct i2c_client *client = ihid->client;
|
|
struct i2c_hid_desc *hdesc = &ihid->hdesc;
|
|
unsigned int dsize;
|
|
int ret;
|
|
|
|
/* Fetch the length of HID description, retrieve the 4 first bytes:
|
|
* bytes 0-1 -> length
|
|
* bytes 2-3 -> bcdVersion (has to be 1.00) */
|
|
ret = i2c_hid_command(client, &hid_descr_cmd, ihid->hdesc_buffer, 4);
|
|
|
|
i2c_hid_dbg(ihid, "%s, ihid->hdesc_buffer: %4ph\n", __func__,
|
|
ihid->hdesc_buffer);
|
|
|
|
if (ret) {
|
|
dev_err(&client->dev,
|
|
"unable to fetch the size of HID descriptor (ret=%d)\n",
|
|
ret);
|
|
return -ENODEV;
|
|
}
|
|
|
|
dsize = le16_to_cpu(hdesc->wHIDDescLength);
|
|
/*
|
|
* the size of the HID descriptor should at least contain
|
|
* its size and the bcdVersion (4 bytes), and should not be greater
|
|
* than sizeof(struct i2c_hid_desc) as we directly fill this struct
|
|
* through i2c_hid_command.
|
|
*/
|
|
if (dsize < 4 || dsize > sizeof(struct i2c_hid_desc)) {
|
|
dev_err(&client->dev, "weird size of HID descriptor (%u)\n",
|
|
dsize);
|
|
return -ENODEV;
|
|
}
|
|
|
|
/* check bcdVersion == 1.0 */
|
|
if (le16_to_cpu(hdesc->bcdVersion) != 0x0100) {
|
|
dev_err(&client->dev,
|
|
"unexpected HID descriptor bcdVersion (0x%04hx)\n",
|
|
le16_to_cpu(hdesc->bcdVersion));
|
|
return -ENODEV;
|
|
}
|
|
|
|
i2c_hid_dbg(ihid, "Fetching the HID descriptor\n");
|
|
|
|
ret = i2c_hid_command(client, &hid_descr_cmd, ihid->hdesc_buffer,
|
|
dsize);
|
|
if (ret) {
|
|
dev_err(&client->dev, "hid_descr_cmd Fail\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
i2c_hid_dbg(ihid, "HID Descriptor: %*ph\n", dsize, ihid->hdesc_buffer);
|
|
|
|
return 0;
|
|
}
|
|
|
|
#ifdef CONFIG_ACPI
|
|
static int i2c_hid_acpi_pdata(struct i2c_client *client,
|
|
struct i2c_hid_platform_data *pdata)
|
|
{
|
|
static u8 i2c_hid_guid[] = {
|
|
0xF7, 0xF6, 0xDF, 0x3C, 0x67, 0x42, 0x55, 0x45,
|
|
0xAD, 0x05, 0xB3, 0x0A, 0x3D, 0x89, 0x38, 0xDE,
|
|
};
|
|
union acpi_object params[4];
|
|
struct acpi_object_list input;
|
|
struct acpi_device *adev;
|
|
unsigned long long value;
|
|
acpi_handle handle;
|
|
|
|
handle = ACPI_HANDLE(&client->dev);
|
|
if (!handle || acpi_bus_get_device(handle, &adev))
|
|
return -ENODEV;
|
|
|
|
input.count = ARRAY_SIZE(params);
|
|
input.pointer = params;
|
|
|
|
params[0].type = ACPI_TYPE_BUFFER;
|
|
params[0].buffer.length = sizeof(i2c_hid_guid);
|
|
params[0].buffer.pointer = i2c_hid_guid;
|
|
params[1].type = ACPI_TYPE_INTEGER;
|
|
params[1].integer.value = 1;
|
|
params[2].type = ACPI_TYPE_INTEGER;
|
|
params[2].integer.value = 1; /* HID function */
|
|
params[3].type = ACPI_TYPE_PACKAGE;
|
|
params[3].package.count = 0;
|
|
params[3].package.elements = NULL;
|
|
|
|
if (ACPI_FAILURE(acpi_evaluate_integer(handle, "_DSM", &input,
|
|
&value))) {
|
|
dev_err(&client->dev, "device _DSM execution failed\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
pdata->hid_descriptor_address = value;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct acpi_device_id i2c_hid_acpi_match[] = {
|
|
{"ACPI0C50", 0 },
|
|
{"PNP0C50", 0 },
|
|
{ },
|
|
};
|
|
MODULE_DEVICE_TABLE(acpi, i2c_hid_acpi_match);
|
|
#else
|
|
static inline int i2c_hid_acpi_pdata(struct i2c_client *client,
|
|
struct i2c_hid_platform_data *pdata)
|
|
{
|
|
return -ENODEV;
|
|
}
|
|
#endif
|
|
|
|
#ifdef CONFIG_OF
|
|
static int i2c_hid_of_probe(struct i2c_client *client,
|
|
struct i2c_hid_platform_data *pdata)
|
|
{
|
|
struct device *dev = &client->dev;
|
|
u32 val;
|
|
int ret;
|
|
|
|
ret = of_property_read_u32(dev->of_node, "hid-descr-addr", &val);
|
|
if (ret) {
|
|
dev_err(&client->dev, "HID register address not provided\n");
|
|
return -ENODEV;
|
|
}
|
|
if (val >> 16) {
|
|
dev_err(&client->dev, "Bad HID register address: 0x%08x\n",
|
|
val);
|
|
return -EINVAL;
|
|
}
|
|
pdata->hid_descriptor_address = val;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct of_device_id i2c_hid_of_match[] = {
|
|
{ .compatible = "hid-over-i2c" },
|
|
{},
|
|
};
|
|
MODULE_DEVICE_TABLE(of, i2c_hid_of_match);
|
|
#else
|
|
static inline int i2c_hid_of_probe(struct i2c_client *client,
|
|
struct i2c_hid_platform_data *pdata)
|
|
{
|
|
return -ENODEV;
|
|
}
|
|
#endif
|
|
|
|
static int i2c_hid_probe(struct i2c_client *client,
|
|
const struct i2c_device_id *dev_id)
|
|
{
|
|
int ret;
|
|
struct i2c_hid *ihid;
|
|
struct hid_device *hid;
|
|
__u16 hidRegister;
|
|
struct i2c_hid_platform_data *platform_data = client->dev.platform_data;
|
|
|
|
dbg_hid("HID probe called for i2c 0x%02x\n", client->addr);
|
|
|
|
if (!client->irq) {
|
|
dev_err(&client->dev,
|
|
"HID over i2c has not been provided an Int IRQ\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
ihid = kzalloc(sizeof(struct i2c_hid), GFP_KERNEL);
|
|
if (!ihid)
|
|
return -ENOMEM;
|
|
|
|
if (client->dev.of_node) {
|
|
ret = i2c_hid_of_probe(client, &ihid->pdata);
|
|
if (ret)
|
|
goto err;
|
|
} else if (!platform_data) {
|
|
ret = i2c_hid_acpi_pdata(client, &ihid->pdata);
|
|
if (ret) {
|
|
dev_err(&client->dev,
|
|
"HID register address not provided\n");
|
|
goto err;
|
|
}
|
|
} else {
|
|
ihid->pdata = *platform_data;
|
|
}
|
|
|
|
i2c_set_clientdata(client, ihid);
|
|
|
|
ihid->client = client;
|
|
|
|
hidRegister = ihid->pdata.hid_descriptor_address;
|
|
ihid->wHIDDescRegister = cpu_to_le16(hidRegister);
|
|
|
|
init_waitqueue_head(&ihid->wait);
|
|
|
|
/* we need to allocate the command buffer without knowing the maximum
|
|
* size of the reports. Let's use HID_MIN_BUFFER_SIZE, then we do the
|
|
* real computation later. */
|
|
ret = i2c_hid_alloc_buffers(ihid, HID_MIN_BUFFER_SIZE);
|
|
if (ret < 0)
|
|
goto err;
|
|
|
|
ret = i2c_hid_fetch_hid_descriptor(ihid);
|
|
if (ret < 0)
|
|
goto err;
|
|
|
|
ret = i2c_hid_init_irq(client);
|
|
if (ret < 0)
|
|
goto err;
|
|
|
|
hid = hid_allocate_device();
|
|
if (IS_ERR(hid)) {
|
|
ret = PTR_ERR(hid);
|
|
goto err_irq;
|
|
}
|
|
|
|
ihid->hid = hid;
|
|
|
|
hid->driver_data = client;
|
|
hid->ll_driver = &i2c_hid_ll_driver;
|
|
hid->hid_get_raw_report = i2c_hid_get_raw_report;
|
|
hid->hid_output_raw_report = i2c_hid_output_raw_report;
|
|
hid->dev.parent = &client->dev;
|
|
ACPI_COMPANION_SET(&hid->dev, ACPI_COMPANION(&client->dev));
|
|
hid->bus = BUS_I2C;
|
|
hid->version = le16_to_cpu(ihid->hdesc.bcdVersion);
|
|
hid->vendor = le16_to_cpu(ihid->hdesc.wVendorID);
|
|
hid->product = le16_to_cpu(ihid->hdesc.wProductID);
|
|
|
|
snprintf(hid->name, sizeof(hid->name), "%s %04hX:%04hX",
|
|
client->name, hid->vendor, hid->product);
|
|
|
|
ret = hid_add_device(hid);
|
|
if (ret) {
|
|
if (ret != -ENODEV)
|
|
hid_err(client, "can't add hid device: %d\n", ret);
|
|
goto err_mem_free;
|
|
}
|
|
|
|
return 0;
|
|
|
|
err_mem_free:
|
|
hid_destroy_device(hid);
|
|
|
|
err_irq:
|
|
free_irq(client->irq, ihid);
|
|
|
|
err:
|
|
i2c_hid_free_buffers(ihid);
|
|
kfree(ihid);
|
|
return ret;
|
|
}
|
|
|
|
static int i2c_hid_remove(struct i2c_client *client)
|
|
{
|
|
struct i2c_hid *ihid = i2c_get_clientdata(client);
|
|
struct hid_device *hid;
|
|
|
|
hid = ihid->hid;
|
|
hid_destroy_device(hid);
|
|
|
|
free_irq(client->irq, ihid);
|
|
|
|
if (ihid->bufsize)
|
|
i2c_hid_free_buffers(ihid);
|
|
|
|
kfree(ihid);
|
|
|
|
return 0;
|
|
}
|
|
|
|
#ifdef CONFIG_PM_SLEEP
|
|
static int i2c_hid_suspend(struct device *dev)
|
|
{
|
|
struct i2c_client *client = to_i2c_client(dev);
|
|
|
|
if (device_may_wakeup(&client->dev))
|
|
enable_irq_wake(client->irq);
|
|
|
|
/* Save some power */
|
|
i2c_hid_set_power(client, I2C_HID_PWR_SLEEP);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int i2c_hid_resume(struct device *dev)
|
|
{
|
|
int ret;
|
|
struct i2c_client *client = to_i2c_client(dev);
|
|
|
|
ret = i2c_hid_hwreset(client);
|
|
if (ret)
|
|
return ret;
|
|
|
|
if (device_may_wakeup(&client->dev))
|
|
disable_irq_wake(client->irq);
|
|
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
static SIMPLE_DEV_PM_OPS(i2c_hid_pm, i2c_hid_suspend, i2c_hid_resume);
|
|
|
|
static const struct i2c_device_id i2c_hid_id_table[] = {
|
|
{ "hid", 0 },
|
|
{ },
|
|
};
|
|
MODULE_DEVICE_TABLE(i2c, i2c_hid_id_table);
|
|
|
|
|
|
static struct i2c_driver i2c_hid_driver = {
|
|
.driver = {
|
|
.name = "i2c_hid",
|
|
.owner = THIS_MODULE,
|
|
.pm = &i2c_hid_pm,
|
|
.acpi_match_table = ACPI_PTR(i2c_hid_acpi_match),
|
|
.of_match_table = of_match_ptr(i2c_hid_of_match),
|
|
},
|
|
|
|
.probe = i2c_hid_probe,
|
|
.remove = i2c_hid_remove,
|
|
|
|
.id_table = i2c_hid_id_table,
|
|
};
|
|
|
|
module_i2c_driver(i2c_hid_driver);
|
|
|
|
MODULE_DESCRIPTION("HID over I2C core driver");
|
|
MODULE_AUTHOR("Benjamin Tissoires <benjamin.tissoires@gmail.com>");
|
|
MODULE_LICENSE("GPL");
|