Commit 884316de authored by Linus Torvalds's avatar Linus Torvalds

Merge branch 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/jikos/hid

Pull HID updates from Jiri Kosina:

 - new hid-alps driver for ALPS Touchpad-Stick device, from Masaki Ota

 - much improved and generalized HID led handling, and merge of
   specialized hid-thingm driver into this generic hid-led one, from
   Heiner Kallweit

 - i2c-hid power management improvements from Fu Zhonghui and Guohua
   Zhong

 - uhid initialization race fix from Roderick Colenbrander

* 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/jikos/hid: (21 commits)
  HID: add usb device id for Apple Magic Keyboard
  HID: hid-led: fix Delcom support on big endian systems
  HID: hid-led: add support for Greynut Luxafor
  HID: hid-led: add support for Delcom Visual Signal Indicator G2
  HID: hid-led: remove report id from struct hidled_config
  HID: alps: a few cleanups
  HID: remove ThingM blink(1) driver
  HID: hid-led: add support for ThingM blink(1)
  HID: hid-led: add support for reading from LED devices
  HID: hid-led: add support for devices with multiple independent LEDs
  HID: i2c-hid: set power sleep before shutdown
  HID: alps: match alps devices in core
  HID: thingm: simplify debug output code
  HID: alps: pass correct sizes to hid_hw_raw_request()
  HID: alps: struct u1_dev *priv is internal to the driver
  HID: add Alps I2C HID Touchpad-Stick support
  HID: led: fix config
  usb: misc: remove outdated USB LED driver
  HID: migrate USB LED driver from usb misc to hid
  HID: i2c_hid: enable i2c-hid devices to suspend/resume asynchronously
  ...
parents 69c42894 8c2f421c
ALPS HID Touchpad Protocol
----------------------
Introduction
------------
Currently ALPS HID driver supports U1 Touchpad device.
U1 devuce basic information.
Vender ID 0x044E
Product ID 0x120B
Version ID 0x0121
HID Descriptor
------------
Byte Field Value Notes
0 wHIDDescLength 001E Length of HID Descriptor : 30 bytes
2 bcdVersion 0100 Compliant with Version 1.00
4 wReportDescLength 00B2 Report Descriptor is 178 Bytes (0x00B2)
6 wReportDescRegister 0002 Identifier to read Report Descriptor
8 wInputRegister 0003 Identifier to read Input Report
10 wMaxInputLength 0053 Input Report is 80 Bytes + 2
12 wOutputRegister 0000 Identifier to read Output Report
14 wMaxOutputLength 0000 No Output Reports
16 wCommandRegister 0005 Identifier for Command Register
18 wDataRegister 0006 Identifier for Data Register
20 wVendorID 044E Vendor ID 0x044E
22 wProductID 120B Product ID 0x120B
24 wVersionID 0121 Version 01.21
26 RESERVED 0000 RESERVED
Report ID
------------
ReportID-1 (Input Reports) (HIDUsage-Mouse) for TP&SP
ReportID-2 (Input Reports) (HIDUsage-keyboard) for TP
ReportID-3 (Input Reports) (Vendor Usage: Max 10 finger data) for TP
ReportID-4 (Input Reports) (Vendor Usage: ON bit data) for GP
ReportID-5 (Feature Reports) Feature Reports
ReportID-6 (Input Reports) (Vendor Usage: StickPointer data) for SP
ReportID-7 (Feature Reports) Flash update (Bootloader)
Data pattern
------------
Case1 ReportID_1 TP/SP Relative/Relative
Case2 ReportID_3 TP Absolute
ReportID_6 SP Absolute
Command Read/Write
------------------
To read/write to RAM, need to send a commands to the device.
The command format is as below.
DataByte(SET_REPORT)
Byte1 Command Byte
Byte2 Address - Byte 0 (LSB)
Byte3 Address - Byte 1
Byte4 Address - Byte 2
Byte5 Address - Byte 3 (MSB)
Byte6 Value Byte
Byte7 Checksum
Command Byte is read=0xD1/write=0xD2 .
Address is read/write RAM address.
Value Byte is writing data when you send the write commands.
When you read RAM, there is no meaning.
DataByte(GET_REPORT)
Byte1 Response Byte
Byte2 Address - Byte 0 (LSB)
Byte3 Address - Byte 1
Byte4 Address - Byte 2
Byte5 Address - Byte 3 (MSB)
Byte6 Value Byte
Byte7 Checksum
Read value is stored in Value Byte.
Packet Format
Touchpad data byte
------------------
b7 b6 b5 b4 b3 b2 b1 b0
1 0 0 SW6 SW5 SW4 SW3 SW2 SW1
2 0 0 0 Fcv Fn3 Fn2 Fn1 Fn0
3 Xa0_7 Xa0_6 Xa0_5 Xa0_4 Xa0_3 Xa0_2 Xa0_1 Xa0_0
4 Xa0_15 Xa0_14 Xa0_13 Xa0_12 Xa0_11 Xa0_10 Xa0_9 Xa0_8
5 Ya0_7 Ya0_6 Ya0_5 Ya0_4 Ya0_3 Ya0_2 Ya0_1 Ya0_0
6 Ya0_15 Ya0_14 Ya0_13 Ya0_12 Ya0_11 Ya0_10 Ya0_9 Ya0_8
7 LFB0 Zs0_6 Zs0_5 Zs0_4 Zs0_3 Zs0_2 Zs0_1 Zs0_0
8 Xa1_7 Xa1_6 Xa1_5 Xa1_4 Xa1_3 Xa1_2 Xa1_1 Xa1_0
9 Xa1_15 Xa1_14 Xa1_13 Xa1_12 Xa1_11 Xa1_10 Xa1_9 Xa1_8
10 Ya1_7 Ya1_6 Ya1_5 Ya1_4 Ya1_3 Ya1_2 Ya1_1 Ya1_0
11 Ya1_15 Ya1_14 Ya1_13 Ya1_12 Ya1_11 Ya1_10 Ya1_9 Ya1_8
12 LFB1 Zs1_6 Zs1_5 Zs1_4 Zs1_3 Zs1_2 Zs1_1 Zs1_0
13 Xa2_7 Xa2_6 Xa2_5 Xa2_4 Xa2_3 Xa2_2 Xa2_1 Xa2_0
14 Xa2_15 Xa2_14 Xa2_13 Xa2_12 Xa2_11 Xa2_10 Xa2_9 Xa2_8
15 Ya2_7 Ya2_6 Ya2_5 Ya2_4 Ya2_3 Ya2_2 Ya2_1 Ya2_0
16 Ya2_15 Ya2_14 Ya2_13 Ya2_12 Ya2_11 Ya2_10 Ya2_9 Ya2_8
17 LFB2 Zs2_6 Zs2_5 Zs2_4 Zs2_3 Zs2_2 Zs2_1 Zs2_0
18 Xa3_7 Xa3_6 Xa3_5 Xa3_4 Xa3_3 Xa3_2 Xa3_1 Xa3_0
19 Xa3_15 Xa3_14 Xa3_13 Xa3_12 Xa3_11 Xa3_10 Xa3_9 Xa3_8
20 Ya3_7 Ya3_6 Ya3_5 Ya3_4 Ya3_3 Ya3_2 Ya3_1 Ya3_0
21 Ya3_15 Ya3_14 Ya3_13 Ya3_12 Ya3_11 Ya3_10 Ya3_9 Ya3_8
22 LFB3 Zs3_6 Zs3_5 Zs3_4 Zs3_3 Zs3_2 Zs3_1 Zs3_0
23 Xa4_7 Xa4_6 Xa4_5 Xa4_4 Xa4_3 Xa4_2 Xa4_1 Xa4_0
24 Xa4_15 Xa4_14 Xa4_13 Xa4_12 Xa4_11 Xa4_10 Xa4_9 Xa4_8
25 Ya4_7 Ya4_6 Ya4_5 Ya4_4 Ya4_3 Ya4_2 Ya4_1 Ya4_0
26 Ya4_15 Ya4_14 Ya4_13 Ya4_12 Ya4_11 Ya4_10 Ya4_9 Ya4_8
27 LFB4 Zs4_6 Zs4_5 Zs4_4 Zs4_3 Zs4_2 Zs4_1 Zs4_0
SW1-SW6: SW ON/OFF status
Xan_15-0(16bit):X Absolute data of the "n"th finger
Yan_15-0(16bit):Y Absolute data of the "n"th finger
Zsn_6-0(7bit): Operation area of the "n"th finger
StickPointer data byte
------------------
b7 b6 b5 b4 b3 b2 b1 b0
Byte1 1 1 1 0 1 SW3 SW2 SW1
Byte2 X7 X6 X5 X4 X3 X2 X1 X0
Byte3 X15 X14 X13 X12 X11 X10 X9 X8
Byte4 Y7 Y6 Y5 Y4 Y3 Y2 Y1 Y0
Byte5 Y15 Y14 Y13 Y12 Y11 Y10 Y9 Y8
Byte6 Z7 Z6 Z5 Z4 Z3 Z2 Z1 Z0
Byte7 T&P Z14 Z13 Z12 Z11 Z10 Z9 Z8
SW1-SW3: SW ON/OFF status
Xn_15-0(16bit):X Absolute data
Yn_15-0(16bit):Y Absolute data
Zn_14-0(15bit):Z
......@@ -11440,11 +11440,6 @@ F: Documentation/thermal/cpu-cooling-api.txt
F: drivers/thermal/cpu_cooling.c
F: include/linux/cpu_cooling.h
THINGM BLINK(1) USB RGB LED DRIVER
M: Vivien Didelot <vivien.didelot@savoirfairelinux.com>
S: Maintained
F: drivers/hid/hid-thingm.c
THINKPAD ACPI EXTRAS DRIVER
M: Henrique de Moraes Holschuh <ibm-acpi@hmh.eng.br>
L: ibm-acpi-devel@lists.sourceforge.net
......
......@@ -388,6 +388,21 @@ config HID_LCPOWER
---help---
Support for LC-Power RC1000MCE RF remote control.
config HID_LED
tristate "Simple RGB LED support"
depends on HID
depends on LEDS_CLASS
---help---
Support for simple RGB LED devices. Currently supported are:
- Riso Kagaku Webmail Notifier
- Dream Cheeky Webmail Notifier and Friends Alert
- ThingM blink(1)
- Delcom Visual Signal Indicator Generation 2
- Greynut Luxafor
To compile this driver as a module, choose M here: the
module will be called hid-led.
config HID_LENOVO
tristate "Lenovo / Thinkpad devices"
depends on HID
......@@ -819,11 +834,11 @@ config HID_THINGM
tristate "ThingM blink(1) USB RGB LED"
depends on HID
depends on LEDS_CLASS
select HID_LED
---help---
Support for the ThingM blink(1) USB RGB LED. This driver registers a
Linux LED class instance, plus additional sysfs attributes to control
RGB colors, fade time and playing. The device is exposed through hidraw
to access other functions.
Support for the ThingM blink(1) USB RGB LED. This driver has been
merged into the generic hid led driver. Config symbol HID_THINGM
just selects HID_LED and will be removed soon.
config HID_THRUSTMASTER
tristate "ThrustMaster devices support"
......@@ -936,6 +951,14 @@ config HID_SENSOR_CUSTOM_SENSOR
standard sensors.
Select this config option for custom/generic sensor support.
config HID_ALPS
tristate "Alps HID device support"
depends on HID
---help---
Support for Alps I2C HID touchpads and StickPointer.
Say Y here if you have a Alps touchpads over i2c-hid or usbhid
and want support for its special functionalities.
endmenu
endif # HID
......
......@@ -21,6 +21,7 @@ hid-wiimote-y := hid-wiimote-core.o hid-wiimote-modules.o
hid-wiimote-$(CONFIG_DEBUG_FS) += hid-wiimote-debug.o
obj-$(CONFIG_HID_A4TECH) += hid-a4tech.o
obj-$(CONFIG_HID_ALPS) += hid-alps.o
obj-$(CONFIG_HID_ACRUX) += hid-axff.o
obj-$(CONFIG_HID_APPLE) += hid-apple.o
obj-$(CONFIG_HID_APPLEIR) += hid-appleir.o
......@@ -90,12 +91,12 @@ obj-$(CONFIG_HID_SPEEDLINK) += hid-speedlink.o
obj-$(CONFIG_HID_STEELSERIES) += hid-steelseries.o
obj-$(CONFIG_HID_SUNPLUS) += hid-sunplus.o
obj-$(CONFIG_HID_GREENASIA) += hid-gaff.o
obj-$(CONFIG_HID_THINGM) += hid-thingm.o
obj-$(CONFIG_HID_THRUSTMASTER) += hid-tmff.o
obj-$(CONFIG_HID_TIVO) += hid-tivo.o
obj-$(CONFIG_HID_TOPSEED) += hid-topseed.o
obj-$(CONFIG_HID_TWINHAN) += hid-twinhan.o
obj-$(CONFIG_HID_UCLOGIC) += hid-uclogic.o
obj-$(CONFIG_HID_LED) += hid-led.o
obj-$(CONFIG_HID_XINMO) += hid-xinmo.o
obj-$(CONFIG_HID_ZEROPLUS) += hid-zpff.o
obj-$(CONFIG_HID_ZYDACRON) += hid-zydacron.o
......
/*
* Copyright (c) 2016 Masaki Ota <masaki.ota@jp.alps.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.
*/
#include <linux/kernel.h>
#include <linux/hid.h>
#include <linux/input.h>
#include <linux/input/mt.h>
#include <linux/module.h>
#include <asm/unaligned.h>
#include "hid-ids.h"
/* ALPS Device Product ID */
#define HID_PRODUCT_ID_T3_BTNLESS 0xD0C0
#define HID_PRODUCT_ID_COSMO 0x1202
#define HID_PRODUCT_ID_U1_PTP_1 0x1207
#define HID_PRODUCT_ID_U1 0x1209
#define HID_PRODUCT_ID_U1_PTP_2 0x120A
#define HID_PRODUCT_ID_U1_DUAL 0x120B
#define HID_PRODUCT_ID_T4_BTNLESS 0x120C
#define DEV_SINGLEPOINT 0x01
#define DEV_DUALPOINT 0x02
#define U1_MOUSE_REPORT_ID 0x01 /* Mouse data ReportID */
#define U1_ABSOLUTE_REPORT_ID 0x03 /* Absolute data ReportID */
#define U1_FEATURE_REPORT_ID 0x05 /* Feature ReportID */
#define U1_SP_ABSOLUTE_REPORT_ID 0x06 /* Feature ReportID */
#define U1_FEATURE_REPORT_LEN 0x08 /* Feature Report Length */
#define U1_FEATURE_REPORT_LEN_ALL 0x0A
#define U1_CMD_REGISTER_READ 0xD1
#define U1_CMD_REGISTER_WRITE 0xD2
#define U1_DEVTYPE_SP_SUPPORT 0x10 /* SP Support */
#define U1_DISABLE_DEV 0x01
#define U1_TP_ABS_MODE 0x02
#define U1_SP_ABS_MODE 0x80
#define ADDRESS_U1_DEV_CTRL_1 0x00800040
#define ADDRESS_U1_DEVICE_TYP 0x00800043
#define ADDRESS_U1_NUM_SENS_X 0x00800047
#define ADDRESS_U1_NUM_SENS_Y 0x00800048
#define ADDRESS_U1_PITCH_SENS_X 0x00800049
#define ADDRESS_U1_PITCH_SENS_Y 0x0080004A
#define ADDRESS_U1_RESO_DWN_ABS 0x0080004E
#define ADDRESS_U1_PAD_BTN 0x00800052
#define ADDRESS_U1_SP_BTN 0x0080009F
#define MAX_TOUCHES 5
/**
* struct u1_data
*
* @input: pointer to the kernel input device
* @input2: pointer to the kernel input2 device
* @hdev: pointer to the struct hid_device
*
* @dev_ctrl: device control parameter
* @dev_type: device type
* @sen_line_num_x: number of sensor line of X
* @sen_line_num_y: number of sensor line of Y
* @pitch_x: sensor pitch of X
* @pitch_y: sensor pitch of Y
* @resolution: resolution
* @btn_info: button information
* @x_active_len_mm: active area length of X (mm)
* @y_active_len_mm: active area length of Y (mm)
* @x_max: maximum x coordinate value
* @y_max: maximum y coordinate value
* @btn_cnt: number of buttons
* @sp_btn_cnt: number of stick buttons
*/
struct u1_dev {
struct input_dev *input;
struct input_dev *input2;
struct hid_device *hdev;
u8 dev_ctrl;
u8 dev_type;
u8 sen_line_num_x;
u8 sen_line_num_y;
u8 pitch_x;
u8 pitch_y;
u8 resolution;
u8 btn_info;
u8 sp_btn_info;
u32 x_active_len_mm;
u32 y_active_len_mm;
u32 x_max;
u32 y_max;
u32 btn_cnt;
u32 sp_btn_cnt;
};
static int u1_read_write_register(struct hid_device *hdev, u32 address,
u8 *read_val, u8 write_val, bool read_flag)
{
int ret, i;
u8 check_sum;
u8 *input;
u8 *readbuf;
input = kzalloc(U1_FEATURE_REPORT_LEN, GFP_KERNEL);
if (!input)
return -ENOMEM;
input[0] = U1_FEATURE_REPORT_ID;
if (read_flag) {
input[1] = U1_CMD_REGISTER_READ;
input[6] = 0x00;
} else {
input[1] = U1_CMD_REGISTER_WRITE;
input[6] = write_val;
}
put_unaligned_le32(address, input + 2);
/* Calculate the checksum */
check_sum = U1_FEATURE_REPORT_LEN_ALL;
for (i = 0; i < U1_FEATURE_REPORT_LEN - 1; i++)
check_sum += input[i];
input[7] = check_sum;
ret = hid_hw_raw_request(hdev, U1_FEATURE_REPORT_ID, input,
U1_FEATURE_REPORT_LEN,
HID_FEATURE_REPORT, HID_REQ_SET_REPORT);
if (ret < 0) {
dev_err(&hdev->dev, "failed to read command (%d)\n", ret);
goto exit;
}
if (read_flag) {
readbuf = kzalloc(U1_FEATURE_REPORT_LEN, GFP_KERNEL);
if (!readbuf) {
kfree(input);
return -ENOMEM;
}
ret = hid_hw_raw_request(hdev, U1_FEATURE_REPORT_ID, readbuf,
U1_FEATURE_REPORT_LEN,
HID_FEATURE_REPORT, HID_REQ_GET_REPORT);
if (ret < 0) {
dev_err(&hdev->dev, "failed read register (%d)\n", ret);
goto exit;
}
*read_val = readbuf[6];
kfree(readbuf);
}
ret = 0;
exit:
kfree(input);
return ret;
}
static int alps_raw_event(struct hid_device *hdev,
struct hid_report *report, u8 *data, int size)
{
unsigned int x, y, z;
int i;
short sp_x, sp_y;
struct u1_dev *hdata = hid_get_drvdata(hdev);
switch (data[0]) {
case U1_MOUSE_REPORT_ID:
break;
case U1_FEATURE_REPORT_ID:
break;
case U1_ABSOLUTE_REPORT_ID:
for (i = 0; i < MAX_TOUCHES; i++) {
u8 *contact = &data[i * 5];
x = get_unaligned_le16(contact + 3);
y = get_unaligned_le16(contact + 5);
z = contact[7] & 0x7F;
input_mt_slot(hdata->input, i);
if (z != 0) {
input_mt_report_slot_state(hdata->input,
MT_TOOL_FINGER, 1);
} else {
input_mt_report_slot_state(hdata->input,
MT_TOOL_FINGER, 0);
break;
}
input_report_abs(hdata->input, ABS_MT_POSITION_X, x);
input_report_abs(hdata->input, ABS_MT_POSITION_Y, y);
input_report_abs(hdata->input, ABS_MT_PRESSURE, z);
}
input_mt_sync_frame(hdata->input);
input_report_key(hdata->input, BTN_LEFT,
data[1] & 0x1);
input_report_key(hdata->input, BTN_RIGHT,
(data[1] & 0x2));
input_report_key(hdata->input, BTN_MIDDLE,
(data[1] & 0x4));
input_sync(hdata->input);
return 1;
case U1_SP_ABSOLUTE_REPORT_ID:
sp_x = get_unaligned_le16(data+2);
sp_y = get_unaligned_le16(data+4);
sp_x = sp_x / 8;
sp_y = sp_y / 8;
input_report_rel(hdata->input2, REL_X, sp_x);
input_report_rel(hdata->input2, REL_Y, sp_y);
input_report_key(hdata->input2, BTN_LEFT,
data[1] & 0x1);
input_report_key(hdata->input2, BTN_RIGHT,
(data[1] & 0x2));
input_report_key(hdata->input2, BTN_MIDDLE,
(data[1] & 0x4));
input_sync(hdata->input2);
return 1;
}
return 0;
}
#ifdef CONFIG_PM
static int alps_post_reset(struct hid_device *hdev)
{
return u1_read_write_register(hdev, ADDRESS_U1_DEV_CTRL_1,
NULL, U1_TP_ABS_MODE, false);
}
static int alps_post_resume(struct hid_device *hdev)
{
return u1_read_write_register(hdev, ADDRESS_U1_DEV_CTRL_1,
NULL, U1_TP_ABS_MODE, false);
}
#endif /* CONFIG_PM */
static int alps_input_configured(struct hid_device *hdev, struct hid_input *hi)
{
struct u1_dev *data = hid_get_drvdata(hdev);
struct input_dev *input = hi->input, *input2;
struct u1_dev devInfo;
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 ret;
/* Allow incoming hid reports */
hid_device_io_start(hdev);
/* Device initialization */
ret = u1_read_write_register(hdev, ADDRESS_U1_DEV_CTRL_1,
&devInfo.dev_ctrl, 0, true);
if (ret < 0) {
dev_err(&hdev->dev, "failed U1_DEV_CTRL_1 (%d)\n", ret);
goto exit;
}
devInfo.dev_ctrl &= ~U1_DISABLE_DEV;
devInfo.dev_ctrl |= U1_TP_ABS_MODE;
ret = u1_read_write_register(hdev, ADDRESS_U1_DEV_CTRL_1,
NULL, devInfo.dev_ctrl, false);
if (ret < 0) {
dev_err(&hdev->dev, "failed to change TP mode (%d)\n", ret);
goto exit;
}
ret = u1_read_write_register(hdev, ADDRESS_U1_NUM_SENS_X,
&devInfo.sen_line_num_x, 0, true);
if (ret < 0) {
dev_err(&hdev->dev, "failed U1_NUM_SENS_X (%d)\n", ret);
goto exit;
}
ret = u1_read_write_register(hdev, ADDRESS_U1_NUM_SENS_Y,
&devInfo.sen_line_num_y, 0, true);
if (ret < 0) {
dev_err(&hdev->dev, "failed U1_NUM_SENS_Y (%d)\n", ret);
goto exit;
}
ret = u1_read_write_register(hdev, ADDRESS_U1_PITCH_SENS_X,
&devInfo.pitch_x, 0, true);
if (ret < 0) {
dev_err(&hdev->dev, "failed U1_PITCH_SENS_X (%d)\n", ret);
goto exit;
}
ret = u1_read_write_register(hdev, ADDRESS_U1_PITCH_SENS_Y,
&devInfo.pitch_y, 0, true);
if (ret < 0) {
dev_err(&hdev->dev, "failed U1_PITCH_SENS_Y (%d)\n", ret);
goto exit;
}
ret = u1_read_write_register(hdev, ADDRESS_U1_RESO_DWN_ABS,
&devInfo.resolution, 0, true);
if (ret < 0) {
dev_err(&hdev->dev, "failed U1_RESO_DWN_ABS (%d)\n", ret);
goto exit;
}
ret = u1_read_write_register(hdev, ADDRESS_U1_PAD_BTN,
&devInfo.btn_info, 0, true);
if (ret < 0) {
dev_err(&hdev->dev, "failed U1_PAD_BTN (%d)\n", ret);
goto exit;
}
/* Check StickPointer device */
ret = u1_read_write_register(hdev, ADDRESS_U1_DEVICE_TYP,
&devInfo.dev_type, 0, true);
if (ret < 0) {
dev_err(&hdev->dev, "failed U1_DEVICE_TYP (%d)\n", ret);
goto exit;
}
devInfo.x_active_len_mm =
(devInfo.pitch_x * (devInfo.sen_line_num_x - 1)) / 10;
devInfo.y_active_len_mm =
(devInfo.pitch_y * (devInfo.sen_line_num_y - 1)) / 10;
devInfo.x_max =
(devInfo.resolution << 2) * (devInfo.sen_line_num_x - 1);
devInfo.y_max =
(devInfo.resolution << 2) * (devInfo.sen_line_num_y - 1);
__set_bit(EV_ABS, input->evbit);
input_set_abs_params(input, ABS_MT_POSITION_X, 1, devInfo.x_max, 0, 0);
input_set_abs_params(input, ABS_MT_POSITION_Y, 1, devInfo.y_max, 0, 0);
if (devInfo.x_active_len_mm && devInfo.y_active_len_mm) {
res_x = (devInfo.x_max - 1) / devInfo.x_active_len_mm;
res_y = (devInfo.y_max - 1) / devInfo.y_active_len_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_PRESSURE, 0, 64, 0, 0);
input_mt_init_slots(input, MAX_TOUCHES, INPUT_MT_POINTER);
__set_bit(EV_KEY, input->evbit);
if ((devInfo.btn_info & 0x0F) == (devInfo.btn_info & 0xF0) >> 4) {
devInfo.btn_cnt = (devInfo.btn_info & 0x0F);
} else {
/* Button pad */
devInfo.btn_cnt = 1;
__set_bit(INPUT_PROP_BUTTONPAD, input->propbit);
}
for (i = 0; i < devInfo.btn_cnt; i++)
__set_bit(BTN_LEFT + i, input->keybit);
/* Stick device initialization */
if (devInfo.dev_type & U1_DEVTYPE_SP_SUPPORT) {
input2 = input_allocate_device();
if (!input2) {
input_free_device(input2);
goto exit;
}
data->input2 = input2;
devInfo.dev_ctrl |= U1_SP_ABS_MODE;
ret = u1_read_write_register(hdev, ADDRESS_U1_DEV_CTRL_1,
NULL, devInfo.dev_ctrl, false);
if (ret < 0) {
dev_err(&hdev->dev, "failed SP mode (%d)\n", ret);
input_free_device(input2);
goto exit;
}
ret = u1_read_write_register(hdev, ADDRESS_U1_SP_BTN,
&devInfo.sp_btn_info, 0, true);
if (ret < 0) {
dev_err(&hdev->dev, "failed U1_SP_BTN (%d)\n", ret);
input_free_device(input2);
goto exit;
}
input2->phys = input->phys;
input2->name = "DualPoint Stick";
input2->id.bustype = BUS_I2C;
input2->id.vendor = input->id.vendor;
input2->id.product = input->id.product;
input2->id.version = input->id.version;
input2->dev.parent = input->dev.parent;
__set_bit(EV_KEY, input2->evbit);
devInfo.sp_btn_cnt = (devInfo.sp_btn_info & 0x0F);
for (i = 0; i < devInfo.sp_btn_cnt; i++)
__set_bit(BTN_LEFT + i, input2->keybit);
__set_bit(EV_REL, input2->evbit);
__set_bit(REL_X, input2->relbit);
__set_bit(REL_Y, input2->relbit);
__set_bit(INPUT_PROP_POINTER, input2->propbit);
__set_bit(INPUT_PROP_POINTING_STICK, input2->propbit);
if (input_register_device(data->input2)) {
input_free_device(input2);
goto exit;
}
}
exit:
hid_device_io_stop(hdev);
hid_hw_close(hdev);
return ret;
}
static int alps_input_mapping(struct hid_device *hdev,
struct hid_input *hi, struct hid_field *field,
struct hid_usage *usage, unsigned long **bit, int *max)
{
return -1;
}
static int alps_probe(struct hid_device *hdev, const struct hid_device_id *id)
{
struct u1_dev *data = NULL;
int ret;
data = devm_kzalloc(&hdev->dev, sizeof(struct u1_dev), GFP_KERNEL);
if (!data)
return -ENOMEM;
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;
}
ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
if (ret) {
hid_err(hdev, "hw start failed\n");
return ret;
}
return 0;
}
static void alps_remove(struct hid_device *hdev)
{
hid_hw_stop(hdev);
}
static const struct hid_device_id alps_id[] = {
{ HID_DEVICE(HID_BUS_ANY, HID_GROUP_ANY,
USB_VENDOR_ID_ALPS_JP, HID_DEVICE_ID_ALPS_U1_DUAL) },
{ }
};
MODULE_DEVICE_TABLE(hid, alps_id);
static struct hid_driver alps_driver = {
.name = "hid-alps",
.id_table = alps_id,
.probe = alps_probe,
.remove = alps_remove,
.raw_event = alps_raw_event,
.input_mapping = alps_input_mapping,
.input_configured = alps_input_configured,
#ifdef CONFIG_PM
.resume = alps_post_resume,
.reset_resume = alps_post_reset,
#endif
};
module_hid_driver(alps_driver);
MODULE_AUTHOR("Masaki Ota <masaki.ota@jp.alps.com>");
MODULE_DESCRIPTION("ALPS HID driver");
MODULE_LICENSE("GPL");
......@@ -474,6 +474,8 @@ static const struct hid_device_id apple_devices[] = {
.driver_data = APPLE_NUMLOCK_EMULATION | APPLE_HAS_FN },
{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_ALU_WIRELESS_JIS),
.driver_data = APPLE_NUMLOCK_EMULATION | APPLE_HAS_FN },
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_ANSI),
.driver_data = APPLE_HAS_FN },
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING_ANSI),
.driver_data = APPLE_HAS_FN },
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING_ISO),
......
......@@ -1772,6 +1772,7 @@ static const struct hid_device_id hid_have_special_driver[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_A4TECH, USB_DEVICE_ID_A4TECH_RP_649) },
{ HID_USB_DEVICE(USB_VENDOR_ID_ACRUX, 0x0802) },
{ HID_USB_DEVICE(USB_VENDOR_ID_ACRUX, 0xf705) },
{ HID_DEVICE(HID_BUS_ANY, HID_GROUP_ANY, USB_VENDOR_ID_ALPS_JP, HID_DEVICE_ID_ALPS_U1_DUAL) },
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_MIGHTYMOUSE) },
{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_MAGICMOUSE) },
{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_MAGICTRACKPAD) },
......@@ -1851,6 +1852,7 @@ static const struct hid_device_id hid_have_special_driver[] = {
{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_ALU_WIRELESS_2011_ANSI) },
{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_ALU_WIRELESS_2011_ISO) },
{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_ALU_WIRELESS_2011_JIS) },
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_ANSI) },
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_FOUNTAIN_TP_ONLY) },
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_GEYSER1_TP_ONLY) },
{ HID_I2C_DEVICE(USB_VENDOR_ID_ASUSTEK, USB_DEVICE_ID_ASUSTEK_NOTEBOOK_KEYBOARD) },
......@@ -1877,8 +1879,11 @@ static const struct hid_device_id hid_have_special_driver[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_CYPRESS, USB_DEVICE_ID_CYPRESS_BARCODE_3) },
{ HID_USB_DEVICE(USB_VENDOR_ID_CYPRESS, USB_DEVICE_ID_CYPRESS_BARCODE_4) },
{ HID_USB_DEVICE(USB_VENDOR_ID_CYPRESS, USB_DEVICE_ID_CYPRESS_MOUSE) },
{ HID_USB_DEVICE(USB_VENDOR_ID_DELCOM, USB_DEVICE_ID_DELCOM_VISUAL_IND) },
{ HID_USB_DEVICE(USB_VENDOR_ID_DRAGONRISE, 0x0006) },
{ HID_USB_DEVICE(USB_VENDOR_ID_DRAGONRISE, 0x0011) },
{ HID_USB_DEVICE(USB_VENDOR_ID_DREAM_CHEEKY, USB_DEVICE_ID_DREAM_CHEEKY_WN) },
{ HID_USB_DEVICE(USB_VENDOR_ID_DREAM_CHEEKY, USB_DEVICE_ID_DREAM_CHEEKY_FA) },
{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_BM084) },
{ HID_USB_DEVICE(USB_VENDOR_ID_ELO, 0x0009) },
{ HID_USB_DEVICE(USB_VENDOR_ID_ELO, 0x0030) },
......@@ -1962,6 +1967,7 @@ static const struct hid_device_id hid_have_special_driver[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_SPACENAVIGATOR) },
{ HID_USB_DEVICE(USB_VENDOR_ID_MICROCHIP, USB_DEVICE_ID_PICOLCD) },
{ HID_USB_DEVICE(USB_VENDOR_ID_MICROCHIP, USB_DEVICE_ID_PICOLCD_BOOTLOADER) },
{ HID_USB_DEVICE(USB_VENDOR_ID_MICROCHIP, USB_DEVICE_ID_LUXAFOR) },
{ HID_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_COMFORT_MOUSE_4500) },
{ HID_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_COMFORT_KEYBOARD) },
{ HID_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_SIDEWINDER_GV) },
......@@ -2008,6 +2014,7 @@ static const struct hid_device_id hid_have_special_driver[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_PETALYNX, USB_DEVICE_ID_PETALYNX_MAXTER_REMOTE) },
{ HID_USB_DEVICE(USB_VENDOR_ID_PLANTRONICS, HID_ANY_ID) },
{ HID_USB_DEVICE(USB_VENDOR_ID_PRIMAX, USB_DEVICE_ID_PRIMAX_KEYBOARD) },
{ HID_USB_DEVICE(USB_VENDOR_ID_RISO_KAGAKU, USB_DEVICE_ID_RI_KA_WEBMAIL) },
#if IS_ENABLED(CONFIG_HID_ROCCAT)
{ HID_USB_DEVICE(USB_VENDOR_ID_ROCCAT, USB_DEVICE_ID_ROCCAT_ARVO) },
{ HID_USB_DEVICE(USB_VENDOR_ID_ROCCAT, USB_DEVICE_ID_ROCCAT_ISKU) },
......@@ -2348,8 +2355,6 @@ static const struct hid_device_id hid_ignore_list[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_DEALEXTREAME, USB_DEVICE_ID_DEALEXTREAME_RADIO_SI4701) },
{ HID_USB_DEVICE(USB_VENDOR_ID_DELORME, USB_DEVICE_ID_DELORME_EARTHMATE) },
{ HID_USB_DEVICE(USB_VENDOR_ID_DELORME, USB_DEVICE_ID_DELORME_EM_LT20) },
{ HID_USB_DEVICE(USB_VENDOR_ID_DREAM_CHEEKY, 0x0004) },
{ HID_USB_DEVICE(USB_VENDOR_ID_DREAM_CHEEKY, 0x000a) },
{ HID_I2C_DEVICE(USB_VENDOR_ID_ELAN, 0x0400) },
{ HID_I2C_DEVICE(USB_VENDOR_ID_ELAN, 0x0401) },
{ HID_USB_DEVICE(USB_VENDOR_ID_ESSENTIAL_REALITY, USB_DEVICE_ID_ESSENTIAL_REALITY_P5) },
......@@ -2486,7 +2491,6 @@ static const struct hid_device_id hid_ignore_list[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_SYNAPTICS, USB_DEVICE_ID_SYNAPTICS_DPAD) },
#endif
{ HID_USB_DEVICE(USB_VENDOR_ID_YEALINK, USB_DEVICE_ID_YEALINK_P1K_P4K_B2K) },
{ HID_USB_DEVICE(USB_VENDOR_ID_RISO_KAGAKU, USB_DEVICE_ID_RI_KA_WEBMAIL) },
{ }
};
......
......@@ -70,6 +70,9 @@
#define USB_VENDOR_ID_ALPS 0x0433
#define USB_DEVICE_ID_IBM_GAMEPAD 0x1101
#define USB_VENDOR_ID_ALPS_JP 0x044E
#define HID_DEVICE_ID_ALPS_U1_DUAL 0x120B
#define USB_VENDOR_ID_ANTON 0x1130
#define USB_DEVICE_ID_ANTON_TOUCH_PAD 0x3101
......@@ -142,6 +145,7 @@
#define USB_DEVICE_ID_APPLE_ALU_WIRELESS_2011_ANSI 0x0255
#define USB_DEVICE_ID_APPLE_ALU_WIRELESS_2011_ISO 0x0256
#define USB_DEVICE_ID_APPLE_ALU_WIRELESS_2011_JIS 0x0257
#define USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_ANSI 0x0267
#define USB_DEVICE_ID_APPLE_WELLSPRING8_ANSI 0x0290
#define USB_DEVICE_ID_APPLE_WELLSPRING8_ISO 0x0291
#define USB_DEVICE_ID_APPLE_WELLSPRING8_JIS 0x0292
......@@ -296,6 +300,9 @@
#define USB_VENDOR_ID_DEALEXTREAME 0x10c5
#define USB_DEVICE_ID_DEALEXTREAME_RADIO_SI4701 0x819a
#define USB_VENDOR_ID_DELCOM 0x0fc5
#define USB_DEVICE_ID_DELCOM_VISUAL_IND 0xb080
#define USB_VENDOR_ID_DELORME 0x1163
#define USB_DEVICE_ID_DELORME_EARTHMATE 0x0100
#define USB_DEVICE_ID_DELORME_EM_LT20 0x0200
......@@ -334,6 +341,8 @@
#define USB_DEVICE_ID_ELECOM_BM084 0x0061
#define USB_VENDOR_ID_DREAM_CHEEKY 0x1d34
#define USB_DEVICE_ID_DREAM_CHEEKY_WN 0x0004
#define USB_DEVICE_ID_DREAM_CHEEKY_FA 0x000a
#define USB_VENDOR_ID_ELITEGROUP 0x03fc
#define USB_DEVICE_ID_ELITEGROUP_05D8 0x05d8
......@@ -680,6 +689,7 @@
#define USB_DEVICE_ID_PICOLCD_BOOTLOADER 0xf002
#define USB_DEVICE_ID_PICK16F1454 0x0042
#define USB_DEVICE_ID_PICK16F1454_V2 0xf2f7
#define USB_DEVICE_ID_LUXAFOR 0xf372
#define USB_VENDOR_ID_MICROSOFT 0x045e
#define USB_DEVICE_ID_SIDEWINDER_GV 0x003b
......
/*
* Simple USB RGB LED driver
*
* Copyright 2016 Heiner Kallweit <hkallweit1@gmail.com>
* Based on drivers/hid/hid-thingm.c and
* drivers/usb/misc/usbled.c
*
* 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, version 2.
*/
#include <linux/hid.h>
#include <linux/hidraw.h>
#include <linux/leds.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include "hid-ids.h"
enum hidled_report_type {
RAW_REQUEST,
OUTPUT_REPORT
};
enum hidled_type {
RISO_KAGAKU,
DREAM_CHEEKY,
THINGM,
DELCOM,
LUXAFOR,
};
static unsigned const char riso_kagaku_tbl[] = {
/* R+2G+4B -> riso kagaku color index */
[0] = 0, /* black */
[1] = 2, /* red */
[2] = 1, /* green */
[3] = 5, /* yellow */
[4] = 3, /* blue */
[5] = 6, /* magenta */
[6] = 4, /* cyan */
[7] = 7 /* white */
};
#define RISO_KAGAKU_IX(r, g, b) riso_kagaku_tbl[((r)?1:0)+((g)?2:0)+((b)?4:0)]
union delcom_packet {
__u8 data[8];
struct {
__u8 major_cmd;
__u8 minor_cmd;
__u8 data_lsb;
__u8 data_msb;
} tx;
struct {
__u8 cmd;
} rx;
struct {
__le16 family_code;
__le16 security_code;
__u8 fw_version;
} fw;
};
#define DELCOM_GREEN_LED 0
#define DELCOM_RED_LED 1
#define DELCOM_BLUE_LED 2
struct hidled_device;
struct hidled_rgb;
struct hidled_config {
enum hidled_type type;
const char *name;
const char *short_name;
enum led_brightness max_brightness;
int num_leds;
size_t report_size;
enum hidled_report_type report_type;
int (*init)(struct hidled_device *ldev);
int (*write)(struct led_classdev *cdev, enum led_brightness br);
};
struct hidled_led {
struct led_classdev cdev;
struct hidled_rgb *rgb;
char name[32];
};
struct hidled_rgb {
struct hidled_device *ldev;
struct hidled_led red;
struct hidled_led green;
struct hidled_led blue;
u8 num;
};
struct hidled_device {
const struct hidled_config *config;
struct hid_device *hdev;
struct hidled_rgb *rgb;
struct mutex lock;
};
#define MAX_REPORT_SIZE 16
#define to_hidled_led(arg) container_of(arg, struct hidled_led, cdev)
static bool riso_kagaku_switch_green_blue;
module_param(riso_kagaku_switch_green_blue, bool, S_IRUGO | S_IWUSR);
MODULE_PARM_DESC(riso_kagaku_switch_green_blue,
"switch green and blue RGB component for Riso Kagaku devices");
static int hidled_send(struct hidled_device *ldev, __u8 *buf)
{
int ret;
mutex_lock(&ldev->lock);
if (ldev->config->report_type == RAW_REQUEST)
ret = hid_hw_raw_request(ldev->hdev, buf[0], buf,
ldev->config->report_size,
HID_FEATURE_REPORT,
HID_REQ_SET_REPORT);
else if (ldev->config->report_type == OUTPUT_REPORT)
ret = hid_hw_output_report(ldev->hdev, buf,
ldev->config->report_size);
else
ret = -EINVAL;
mutex_unlock(&ldev->lock);
if (ret < 0)
return ret;
return ret == ldev->config->report_size ? 0 : -EMSGSIZE;
}
/* reading data is supported for report type RAW_REQUEST only */
static int hidled_recv(struct hidled_device *ldev, __u8 *buf)
{
int ret;
if (ldev->config->report_type != RAW_REQUEST)
return -EINVAL;
mutex_lock(&ldev->lock);
ret = hid_hw_raw_request(ldev->hdev, buf[0], buf,
ldev->config->report_size,
HID_FEATURE_REPORT,
HID_REQ_SET_REPORT);
if (ret < 0)
goto err;
ret = hid_hw_raw_request(ldev->hdev, buf[0], buf,
ldev->config->report_size,
HID_FEATURE_REPORT,
HID_REQ_GET_REPORT);
err:
mutex_unlock(&ldev->lock);
return ret < 0 ? ret : 0;
}
static u8 riso_kagaku_index(struct hidled_rgb *rgb)
{
enum led_brightness r, g, b;
r = rgb->red.cdev.brightness;
g = rgb->green.cdev.brightness;
b = rgb->blue.cdev.brightness;
if (riso_kagaku_switch_green_blue)
return RISO_KAGAKU_IX(r, b, g);
else
return RISO_KAGAKU_IX(r, g, b);
}
static int riso_kagaku_write(struct led_classdev *cdev, enum led_brightness br)
{
struct hidled_led *led = to_hidled_led(cdev);
struct hidled_rgb *rgb = led->rgb;
__u8 buf[MAX_REPORT_SIZE] = {};
buf[1] = riso_kagaku_index(rgb);
return hidled_send(rgb->ldev, buf);
}
static int dream_cheeky_write(struct led_classdev *cdev, enum led_brightness br)
{
struct hidled_led *led = to_hidled_led(cdev);
struct hidled_rgb *rgb = led->rgb;
__u8 buf[MAX_REPORT_SIZE] = {};
buf[1] = rgb->red.cdev.brightness;
buf[2] = rgb->green.cdev.brightness;
buf[3] = rgb->blue.cdev.brightness;
buf[7] = 0x1a;
buf[8] = 0x05;
return hidled_send(rgb->ldev, buf);
}
static int dream_cheeky_init(struct hidled_device *ldev)
{
__u8 buf[MAX_REPORT_SIZE] = {};
/* Dream Cheeky magic */
buf[1] = 0x1f;
buf[2] = 0x02;
buf[4] = 0x5f;
buf[7] = 0x1a;
buf[8] = 0x03;
return hidled_send(ldev, buf);
}
static int _thingm_write(struct led_classdev *cdev, enum led_brightness br,
u8 offset)
{
struct hidled_led *led = to_hidled_led(cdev);
__u8 buf[MAX_REPORT_SIZE] = { 1, 'c' };
buf[2] = led->rgb->red.cdev.brightness;
buf[3] = led->rgb->green.cdev.brightness;
buf[4] = led->rgb->blue.cdev.brightness;
buf[7] = led->rgb->num + offset;
return hidled_send(led->rgb->ldev, buf);
}
static int thingm_write_v1(struct led_classdev *cdev, enum led_brightness br)
{
return _thingm_write(cdev, br, 0);
}
static int thingm_write(struct led_classdev *cdev, enum led_brightness br)
{
return _thingm_write(cdev, br, 1);
}
static const struct hidled_config hidled_config_thingm_v1 = {
.name = "ThingM blink(1) v1",
.short_name = "thingm",
.max_brightness = 255,
.num_leds = 1,
.report_size = 9,
.report_type = RAW_REQUEST,
.write = thingm_write_v1,
};
static int thingm_init(struct hidled_device *ldev)
{
__u8 buf[MAX_REPORT_SIZE] = { 1, 'v' };
int ret;
ret = hidled_recv(ldev, buf);
if (ret)
return ret;
/* Check for firmware major version 1 */
if (buf[3] == '1')
ldev->config = &hidled_config_thingm_v1;
return 0;
}
static inline int delcom_get_lednum(const struct hidled_led *led)
{
if (led == &led->rgb->red)
return DELCOM_RED_LED;
else if (led == &led->rgb->green)
return DELCOM_GREEN_LED;
else
return DELCOM_BLUE_LED;
}
static int delcom_enable_led(struct hidled_led *led)
{
union delcom_packet dp = { .tx.major_cmd = 101, .tx.minor_cmd = 12 };
dp.tx.data_lsb = 1 << delcom_get_lednum(led);
dp.tx.data_msb = 0;
return hidled_send(led->rgb->ldev, dp.data);
}
static int delcom_set_pwm(struct hidled_led *led)
{
union delcom_packet dp = { .tx.major_cmd = 101, .tx.minor_cmd = 34 };
dp.tx.data_lsb = delcom_get_lednum(led);
dp.tx.data_msb = led->cdev.brightness;
return hidled_send(led->rgb->ldev, dp.data);
}
static int delcom_write(struct led_classdev *cdev, enum led_brightness br)
{
struct hidled_led *led = to_hidled_led(cdev);
int ret;
/*
* enable LED
* We can't do this in the init function already because the device
* is internally reset later.
*/
ret = delcom_enable_led(led);
if (ret)
return ret;
return delcom_set_pwm(led);
}
static int delcom_init(struct hidled_device *ldev)
{
union delcom_packet dp = { .rx.cmd = 104 };
int ret;
ret = hidled_recv(ldev, dp.data);
if (ret)
return ret;
/*
* Several Delcom devices share the same USB VID/PID
* Check for family id 2 for Visual Signal Indicator
*/
return le16_to_cpu(dp.fw.family_code) == 2 ? 0 : -ENODEV;
}
static int luxafor_write(struct led_classdev *cdev, enum led_brightness br)
{
struct hidled_led *led = to_hidled_led(cdev);
__u8 buf[MAX_REPORT_SIZE] = { [1] = 1 };
buf[2] = led->rgb->num + 1;
buf[3] = led->rgb->red.cdev.brightness;
buf[4] = led->rgb->green.cdev.brightness;
buf[5] = led->rgb->blue.cdev.brightness;
return hidled_send(led->rgb->ldev, buf);
}
static const struct hidled_config hidled_configs[] = {
{
.type = RISO_KAGAKU,
.name = "Riso Kagaku Webmail Notifier",
.short_name = "riso_kagaku",
.max_brightness = 1,
.num_leds = 1,
.report_size = 6,
.report_type = OUTPUT_REPORT,
.write = riso_kagaku_write,
},
{
.type = DREAM_CHEEKY,
.name = "Dream Cheeky Webmail Notifier",
.short_name = "dream_cheeky",
.max_brightness = 31,
.num_leds = 1,
.report_size = 9,
.report_type = RAW_REQUEST,
.init = dream_cheeky_init,
.write = dream_cheeky_write,
},
{
.type = THINGM,
.name = "ThingM blink(1)",
.short_name = "thingm",
.max_brightness = 255,
.num_leds = 2,
.report_size = 9,
.report_type = RAW_REQUEST,
.init = thingm_init,
.write = thingm_write,
},
{
.type = DELCOM,
.name = "Delcom Visual Signal Indicator G2",
.short_name = "delcom",
.max_brightness = 100,
.num_leds = 1,
.report_size = 8,
.report_type = RAW_REQUEST,
.init = delcom_init,
.write = delcom_write,
},
{
.type = LUXAFOR,
.name = "Greynut Luxafor",
.short_name = "luxafor",
.max_brightness = 255,
.num_leds = 6,
.report_size = 9,
.report_type = OUTPUT_REPORT,
.write = luxafor_write,
},
};
static int hidled_init_led(struct hidled_led *led, const char *color_name,
struct hidled_rgb *rgb, unsigned int minor)
{
const struct hidled_config *config = rgb->ldev->config;
if (config->num_leds > 1)
snprintf(led->name, sizeof(led->name), "%s%u:%s:led%u",
config->short_name, minor, color_name, rgb->num);
else
snprintf(led->name, sizeof(led->name), "%s%u:%s",
config->short_name, minor, color_name);
led->cdev.name = led->name;
led->cdev.max_brightness = config->max_brightness;
led->cdev.brightness_set_blocking = config->write;
led->cdev.flags = LED_HW_PLUGGABLE;
led->rgb = rgb;
return devm_led_classdev_register(&rgb->ldev->hdev->dev, &led->cdev);
}
static int hidled_init_rgb(struct hidled_rgb *rgb, unsigned int minor)
{
int ret;
/* Register the red diode */
ret = hidled_init_led(&rgb->red, "red", rgb, minor);
if (ret)
return ret;
/* Register the green diode */
ret = hidled_init_led(&rgb->green, "green", rgb, minor);
if (ret)
return ret;
/* Register the blue diode */
return hidled_init_led(&rgb->blue, "blue", rgb, minor);
}
static int hidled_probe(struct hid_device *hdev, const struct hid_device_id *id)
{
struct hidled_device *ldev;
unsigned int minor;
int ret, i;
ldev = devm_kzalloc(&hdev->dev, sizeof(*ldev), GFP_KERNEL);
if (!ldev)
return -ENOMEM;
ret = hid_parse(hdev);
if (ret)
return ret;
ldev->hdev = hdev;
mutex_init(&ldev->lock);
for (i = 0; !ldev->config && i < ARRAY_SIZE(hidled_configs); i++)
if (hidled_configs[i].type == id->driver_data)
ldev->config = &hidled_configs[i];
if (!ldev->config)
return -EINVAL;
if (ldev->config->init) {
ret = ldev->config->init(ldev);
if (ret)
return ret;
}
ldev->rgb = devm_kcalloc(&hdev->dev, ldev->config->num_leds,
sizeof(struct hidled_rgb), GFP_KERNEL);
if (!ldev->rgb)
return -ENOMEM;
ret = hid_hw_start(hdev, HID_CONNECT_HIDRAW);
if (ret)
return ret;
minor = ((struct hidraw *) hdev->hidraw)->minor;
for (i = 0; i < ldev->config->num_leds; i++) {
ldev->rgb[i].ldev = ldev;
ldev->rgb[i].num = i;
ret = hidled_init_rgb(&ldev->rgb[i], minor);
if (ret) {
hid_hw_stop(hdev);
return ret;
}
}
hid_info(hdev, "%s initialized\n", ldev->config->name);
return 0;
}
static const struct hid_device_id hidled_table[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_RISO_KAGAKU,
USB_DEVICE_ID_RI_KA_WEBMAIL), .driver_data = RISO_KAGAKU },
{ HID_USB_DEVICE(USB_VENDOR_ID_DREAM_CHEEKY,
USB_DEVICE_ID_DREAM_CHEEKY_WN), .driver_data = DREAM_CHEEKY },
{ HID_USB_DEVICE(USB_VENDOR_ID_DREAM_CHEEKY,
USB_DEVICE_ID_DREAM_CHEEKY_FA), .driver_data = DREAM_CHEEKY },
{ HID_USB_DEVICE(USB_VENDOR_ID_THINGM,
USB_DEVICE_ID_BLINK1), .driver_data = THINGM },
{ HID_USB_DEVICE(USB_VENDOR_ID_DELCOM,
USB_DEVICE_ID_DELCOM_VISUAL_IND), .driver_data = DELCOM },
{ HID_USB_DEVICE(USB_VENDOR_ID_MICROCHIP,
USB_DEVICE_ID_LUXAFOR), .driver_data = LUXAFOR },
{ }
};
MODULE_DEVICE_TABLE(hid, hidled_table);
static struct hid_driver hidled_driver = {
.name = "hid-led",
.probe = hidled_probe,
.id_table = hidled_table,
};
module_hid_driver(hidled_driver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Heiner Kallweit <hkallweit1@gmail.com>");
MODULE_DESCRIPTION("Simple USB RGB LED driver");
/*
* ThingM blink(1) USB RGB LED driver
*
* Copyright 2013-2014 Savoir-faire Linux Inc.
* Vivien Didelot <vivien.didelot@savoirfairelinux.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, version 2.
*/
#include <linux/hid.h>
#include <linux/hidraw.h>
#include <linux/leds.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include "hid-ids.h"
#define REPORT_ID 1
#define REPORT_SIZE 9
/* Firmware major number of supported devices */
#define THINGM_MAJOR_MK1 '1'
#define THINGM_MAJOR_MK2 '2'
struct thingm_fwinfo {
char major;
unsigned numrgb;
unsigned first;
};
static const struct thingm_fwinfo thingm_fwinfo[] = {
{
.major = THINGM_MAJOR_MK1,
.numrgb = 1,
.first = 0,
}, {
.major = THINGM_MAJOR_MK2,
.numrgb = 2,
.first = 1,
}
};
/* A red, green or blue channel, part of an RGB chip */
struct thingm_led {
struct thingm_rgb *rgb;
struct led_classdev ldev;
char name[32];
};
/* Basically a WS2812 5050 RGB LED chip */
struct thingm_rgb {
struct thingm_device *tdev;
struct thingm_led red;
struct thingm_led green;
struct thingm_led blue;
u8 num;
};
struct thingm_device {
struct hid_device *hdev;
struct {
char major;
char minor;
} version;
const struct thingm_fwinfo *fwinfo;
struct mutex lock;
struct thingm_rgb *rgb;
};
static int thingm_send(struct thingm_device *tdev, u8 buf[REPORT_SIZE])
{
int ret;
hid_dbg(tdev->hdev, "-> %d %c %02hhx %02hhx %02hhx %02hhx %02hhx %02hhx %02hhx\n",
buf[0], buf[1], buf[2], buf[3], buf[4],
buf[5], buf[6], buf[7], buf[8]);
mutex_lock(&tdev->lock);
ret = hid_hw_raw_request(tdev->hdev, buf[0], buf, REPORT_SIZE,
HID_FEATURE_REPORT, HID_REQ_SET_REPORT);
mutex_unlock(&tdev->lock);
return ret < 0 ? ret : 0;
}
static int thingm_recv(struct thingm_device *tdev, u8 buf[REPORT_SIZE])
{
int ret;
/*
* A read consists of two operations: sending the read command
* and the actual read from the device. Use the mutex to protect
* the full sequence of both operations.
*/
mutex_lock(&tdev->lock);
ret = hid_hw_raw_request(tdev->hdev, buf[0], buf, REPORT_SIZE,
HID_FEATURE_REPORT, HID_REQ_SET_REPORT);
if (ret < 0)
goto err;
ret = hid_hw_raw_request(tdev->hdev, buf[0], buf, REPORT_SIZE,
HID_FEATURE_REPORT, HID_REQ_GET_REPORT);
if (ret < 0)
goto err;
ret = 0;
hid_dbg(tdev->hdev, "<- %d %c %02hhx %02hhx %02hhx %02hhx %02hhx %02hhx %02hhx\n",
buf[0], buf[1], buf[2], buf[3], buf[4],
buf[5], buf[6], buf[7], buf[8]);
err:
mutex_unlock(&tdev->lock);
return ret;
}
static int thingm_version(struct thingm_device *tdev)
{
u8 buf[REPORT_SIZE] = { REPORT_ID, 'v', 0, 0, 0, 0, 0, 0, 0 };
int err;
err = thingm_recv(tdev, buf);
if (err)
return err;
tdev->version.major = buf[3];
tdev->version.minor = buf[4];
return 0;
}
static int thingm_write_color(struct thingm_rgb *rgb)
{
u8 buf[REPORT_SIZE] = { REPORT_ID, 'c', 0, 0, 0, 0, 0, rgb->num, 0 };
buf[2] = rgb->red.ldev.brightness;
buf[3] = rgb->green.ldev.brightness;
buf[4] = rgb->blue.ldev.brightness;
return thingm_send(rgb->tdev, buf);
}
static int thingm_led_set(struct led_classdev *ldev,
enum led_brightness brightness)
{
struct thingm_led *led = container_of(ldev, struct thingm_led, ldev);
return thingm_write_color(led->rgb);
}
static int thingm_init_led(struct thingm_led *led, const char *color_name,
struct thingm_rgb *rgb, int minor)
{
snprintf(led->name, sizeof(led->name), "thingm%d:%s:led%d",
minor, color_name, rgb->num);
led->ldev.name = led->name;
led->ldev.max_brightness = 255;
led->ldev.brightness_set_blocking = thingm_led_set;
led->ldev.flags = LED_HW_PLUGGABLE;
led->rgb = rgb;
return devm_led_classdev_register(&rgb->tdev->hdev->dev, &led->ldev);
}
static int thingm_init_rgb(struct thingm_rgb *rgb)
{
const int minor = ((struct hidraw *) rgb->tdev->hdev->hidraw)->minor;
int err;
/* Register the red diode */
err = thingm_init_led(&rgb->red, "red", rgb, minor);
if (err)
return err;
/* Register the green diode */
err = thingm_init_led(&rgb->green, "green", rgb, minor);
if (err)
return err;
/* Register the blue diode */
return thingm_init_led(&rgb->blue, "blue", rgb, minor);
}
static int thingm_probe(struct hid_device *hdev, const struct hid_device_id *id)
{
struct thingm_device *tdev;
int i, err;
tdev = devm_kzalloc(&hdev->dev, sizeof(struct thingm_device),
GFP_KERNEL);
if (!tdev)
return -ENOMEM;
tdev->hdev = hdev;
hid_set_drvdata(hdev, tdev);
err = hid_parse(hdev);
if (err)
return err;
mutex_init(&tdev->lock);
err = thingm_version(tdev);
if (err)
return err;
hid_dbg(hdev, "firmware version: %c.%c\n",
tdev->version.major, tdev->version.minor);
for (i = 0; i < ARRAY_SIZE(thingm_fwinfo) && !tdev->fwinfo; ++i)
if (thingm_fwinfo[i].major == tdev->version.major)
tdev->fwinfo = &thingm_fwinfo[i];
if (!tdev->fwinfo) {
hid_err(hdev, "unsupported firmware %c\n", tdev->version.major);
return -ENODEV;
}
tdev->rgb = devm_kzalloc(&hdev->dev,
sizeof(struct thingm_rgb) * tdev->fwinfo->numrgb,
GFP_KERNEL);
if (!tdev->rgb)
return -ENOMEM;
err = hid_hw_start(hdev, HID_CONNECT_HIDRAW);
if (err)
return err;
for (i = 0; i < tdev->fwinfo->numrgb; ++i) {
struct thingm_rgb *rgb = tdev->rgb + i;
rgb->tdev = tdev;
rgb->num = tdev->fwinfo->first + i;
err = thingm_init_rgb(rgb);
if (err) {
hid_hw_stop(hdev);
return err;
}
}
return 0;
}
static const struct hid_device_id thingm_table[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_THINGM, USB_DEVICE_ID_BLINK1) },
{ }
};
MODULE_DEVICE_TABLE(hid, thingm_table);
static struct hid_driver thingm_driver = {
.name = "thingm",
.probe = thingm_probe,
.id_table = thingm_table,
};
module_hid_driver(thingm_driver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Vivien Didelot <vivien.didelot@savoirfairelinux.com>");
MODULE_DESCRIPTION("ThingM blink(1) USB RGB LED driver");
......@@ -1020,6 +1020,7 @@ static int i2c_hid_probe(struct i2c_client *client,
pm_runtime_get_noresume(&client->dev);
pm_runtime_set_active(&client->dev);
pm_runtime_enable(&client->dev);
device_enable_async_suspend(&client->dev);
ret = i2c_hid_fetch_hid_descriptor(ihid);
if (ret < 0)
......@@ -1106,6 +1107,14 @@ static int i2c_hid_remove(struct i2c_client *client)
return 0;
}
static void i2c_hid_shutdown(struct i2c_client *client)
{
struct i2c_hid *ihid = i2c_get_clientdata(client);
i2c_hid_set_power(client, I2C_HID_PWR_SLEEP);
free_irq(client->irq, ihid);
}
#ifdef CONFIG_PM_SLEEP
static int i2c_hid_suspend(struct device *dev)
{
......@@ -1230,7 +1239,7 @@ static struct i2c_driver i2c_hid_driver = {
.probe = i2c_hid_probe,
.remove = i2c_hid_remove,
.shutdown = i2c_hid_shutdown,
.id_table = i2c_hid_id_table,
};
......
......@@ -51,10 +51,26 @@ struct uhid_device {
u32 report_id;
u32 report_type;
struct uhid_event report_buf;
struct work_struct worker;
};
static struct miscdevice uhid_misc;
static void uhid_device_add_worker(struct work_struct *work)
{
struct uhid_device *uhid = container_of(work, struct uhid_device, worker);
int ret;
ret = hid_add_device(uhid->hid);
if (ret) {
hid_err(uhid->hid, "Cannot register HID device: error %d\n", ret);
hid_destroy_device(uhid->hid);
uhid->hid = NULL;
uhid->running = false;
}
}
static void uhid_queue(struct uhid_device *uhid, struct uhid_event *ev)
{
__u8 newhead;
......@@ -498,18 +514,14 @@ static int uhid_dev_create2(struct uhid_device *uhid,
uhid->hid = hid;
uhid->running = true;
ret = hid_add_device(hid);
if (ret) {
hid_err(hid, "Cannot register HID device\n");
goto err_hid;
}
/* Adding of a HID device is done through a worker, to allow HID drivers
* which use feature requests during .probe to work, without they would
* be blocked on devlock, which is held by uhid_char_write.
*/
schedule_work(&uhid->worker);
return 0;
err_hid:
hid_destroy_device(hid);
uhid->hid = NULL;
uhid->running = false;
err_free:
kfree(uhid->rd_data);
uhid->rd_data = NULL;
......@@ -550,6 +562,8 @@ static int uhid_dev_destroy(struct uhid_device *uhid)
uhid->running = false;
wake_up_interruptible(&uhid->report_wait);
cancel_work_sync(&uhid->worker);
hid_destroy_device(uhid->hid);
kfree(uhid->rd_data);
......@@ -612,6 +626,7 @@ static int uhid_char_open(struct inode *inode, struct file *file)
init_waitqueue_head(&uhid->waitq);
init_waitqueue_head(&uhid->report_wait);
uhid->running = false;
INIT_WORK(&uhid->worker, uhid_device_add_worker);
file->private_data = uhid;
nonseekable_open(inode, file);
......
......@@ -79,15 +79,6 @@ config USB_LCD
To compile this driver as a module, choose M here: the
module will be called usblcd.
config USB_LED
tristate "USB LED driver support"
help
Say Y here if you want to connect an USBLED device to your
computer's USB port.
To compile this driver as a module, choose M here: the
module will be called usbled.
config USB_CYPRESS_CY7C63
tristate "Cypress CY7C63xxx USB driver support"
help
......
......@@ -15,7 +15,6 @@ obj-$(CONFIG_USB_IOWARRIOR) += iowarrior.o
obj-$(CONFIG_USB_ISIGHTFW) += isight_firmware.o
obj-$(CONFIG_USB_LCD) += usblcd.o
obj-$(CONFIG_USB_LD) += ldusb.o
obj-$(CONFIG_USB_LED) += usbled.o
obj-$(CONFIG_USB_LEGOTOWER) += legousbtower.o
obj-$(CONFIG_USB_RIO500) += rio500.o
obj-$(CONFIG_USB_TEST) += usbtest.o
......
/*
* USB LED driver
*
* Copyright (C) 2004 Greg Kroah-Hartman (greg@kroah.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, version 2.
*
*/
#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/usb.h>
#define DRIVER_AUTHOR "Greg Kroah-Hartman, greg@kroah.com"
#define DRIVER_DESC "USB LED Driver"
enum led_type {
DELCOM_VISUAL_SIGNAL_INDICATOR,
DREAM_CHEEKY_WEBMAIL_NOTIFIER,
RISO_KAGAKU_LED
};
/* the Webmail LED made by RISO KAGAKU CORP. decodes a color index
internally, we want to keep the red+green+blue sysfs api, so we decode
from 1-bit RGB to the riso kagaku color index according to this table... */
static unsigned const char riso_kagaku_tbl[] = {
/* R+2G+4B -> riso kagaku color index */
[0] = 0, /* black */
[1] = 2, /* red */
[2] = 1, /* green */
[3] = 5, /* yellow */
[4] = 3, /* blue */
[5] = 6, /* magenta */
[6] = 4, /* cyan */
[7] = 7 /* white */
};
#define RISO_KAGAKU_IX(r,g,b) riso_kagaku_tbl[((r)?1:0)+((g)?2:0)+((b)?4:0)]
/* table of devices that work with this driver */
static const struct usb_device_id id_table[] = {
{ USB_DEVICE(0x0fc5, 0x1223),
.driver_info = DELCOM_VISUAL_SIGNAL_INDICATOR },
{ USB_DEVICE(0x1d34, 0x0004),
.driver_info = DREAM_CHEEKY_WEBMAIL_NOTIFIER },
{ USB_DEVICE(0x1d34, 0x000a),
.driver_info = DREAM_CHEEKY_WEBMAIL_NOTIFIER },
{ USB_DEVICE(0x1294, 0x1320),
.driver_info = RISO_KAGAKU_LED },
{ },
};
MODULE_DEVICE_TABLE(usb, id_table);
struct usb_led {
struct usb_device *udev;
unsigned char blue;
unsigned char red;
unsigned char green;
enum led_type type;
};
static void change_color(struct usb_led *led)
{
int retval = 0;
unsigned char *buffer;
int actlength;
buffer = kmalloc(8, GFP_KERNEL);
if (!buffer) {
dev_err(&led->udev->dev, "out of memory\n");
return;
}
switch (led->type) {
case DELCOM_VISUAL_SIGNAL_INDICATOR: {
unsigned char color = 0x07;
if (led->blue)
color &= ~0x04;
if (led->red)
color &= ~0x02;
if (led->green)
color &= ~0x01;
dev_dbg(&led->udev->dev,
"blue = %d, red = %d, green = %d, color = %.2x\n",
led->blue, led->red, led->green, color);
retval = usb_control_msg(led->udev,
usb_sndctrlpipe(led->udev, 0),
0x12,
0xc8,
(0x02 * 0x100) + 0x0a,
(0x00 * 0x100) + color,
buffer,
8,
2000);
break;
}
case DREAM_CHEEKY_WEBMAIL_NOTIFIER:
dev_dbg(&led->udev->dev,
"red = %d, green = %d, blue = %d\n",
led->red, led->green, led->blue);
buffer[0] = led->red;
buffer[1] = led->green;
buffer[2] = led->blue;
buffer[3] = buffer[4] = buffer[5] = 0;
buffer[6] = 0x1a;
buffer[7] = 0x05;
retval = usb_control_msg(led->udev,
usb_sndctrlpipe(led->udev, 0),
0x09,
0x21,
0x200,
0,
buffer,
8,
2000);
break;
case RISO_KAGAKU_LED:
buffer[0] = RISO_KAGAKU_IX(led->red, led->green, led->blue);
buffer[1] = 0;
buffer[2] = 0;
buffer[3] = 0;
buffer[4] = 0;
retval = usb_interrupt_msg(led->udev,
usb_sndctrlpipe(led->udev, 2),
buffer, 5, &actlength, 1000 /*ms timeout*/);
break;
default:
dev_err(&led->udev->dev, "unknown device type %d\n", led->type);
}
if (retval)
dev_dbg(&led->udev->dev, "retval = %d\n", retval);
kfree(buffer);
}
#define show_set(value) \
static ssize_t show_##value(struct device *dev, struct device_attribute *attr,\
char *buf) \
{ \
struct usb_interface *intf = to_usb_interface(dev); \
struct usb_led *led = usb_get_intfdata(intf); \
\
return sprintf(buf, "%d\n", led->value); \
} \
static ssize_t set_##value(struct device *dev, struct device_attribute *attr,\
const char *buf, size_t count) \
{ \
struct usb_interface *intf = to_usb_interface(dev); \
struct usb_led *led = usb_get_intfdata(intf); \
int temp = simple_strtoul(buf, NULL, 10); \
\
led->value = temp; \
change_color(led); \
return count; \
} \
static DEVICE_ATTR(value, S_IRUGO | S_IWUSR, show_##value, set_##value);
show_set(blue);
show_set(red);
show_set(green);
static int led_probe(struct usb_interface *interface,
const struct usb_device_id *id)
{
struct usb_device *udev = interface_to_usbdev(interface);
struct usb_led *dev = NULL;
int retval = -ENOMEM;
dev = kzalloc(sizeof(struct usb_led), GFP_KERNEL);
if (dev == NULL) {
dev_err(&interface->dev, "out of memory\n");
goto error_mem;
}
dev->udev = usb_get_dev(udev);
dev->type = id->driver_info;
usb_set_intfdata(interface, dev);
retval = device_create_file(&interface->dev, &dev_attr_blue);
if (retval)
goto error;
retval = device_create_file(&interface->dev, &dev_attr_red);
if (retval)
goto error;
retval = device_create_file(&interface->dev, &dev_attr_green);
if (retval)
goto error;
if (dev->type == DREAM_CHEEKY_WEBMAIL_NOTIFIER) {
unsigned char *enable;
enable = kmemdup("\x1f\x02\0\x5f\0\0\x1a\x03", 8, GFP_KERNEL);
if (!enable) {
dev_err(&interface->dev, "out of memory\n");
retval = -ENOMEM;
goto error;
}
retval = usb_control_msg(udev,
usb_sndctrlpipe(udev, 0),
0x09,
0x21,
0x200,
0,
enable,
8,
2000);
kfree(enable);
if (retval != 8)
goto error;
}
dev_info(&interface->dev, "USB LED device now attached\n");
return 0;
error:
device_remove_file(&interface->dev, &dev_attr_blue);
device_remove_file(&interface->dev, &dev_attr_red);
device_remove_file(&interface->dev, &dev_attr_green);
usb_set_intfdata(interface, NULL);
usb_put_dev(dev->udev);
kfree(dev);
error_mem:
return retval;
}
static void led_disconnect(struct usb_interface *interface)
{
struct usb_led *dev;
dev = usb_get_intfdata(interface);
device_remove_file(&interface->dev, &dev_attr_blue);
device_remove_file(&interface->dev, &dev_attr_red);
device_remove_file(&interface->dev, &dev_attr_green);
/* first remove the files, then set the pointer to NULL */
usb_set_intfdata(interface, NULL);
usb_put_dev(dev->udev);
kfree(dev);
dev_info(&interface->dev, "USB LED now disconnected\n");
}
static struct usb_driver led_driver = {
.name = "usbled",
.probe = led_probe,
.disconnect = led_disconnect,
.id_table = id_table,
};
module_usb_driver(led_driver);
MODULE_AUTHOR(DRIVER_AUTHOR);
MODULE_DESCRIPTION(DRIVER_DESC);
MODULE_LICENSE("GPL");
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment