Input: wacom - add Intuos4 LED and OLED control

This commit enables control of the LEDs and OLED displays found on the
Wacom Intuos4 M, L, and XL. For this purpose, a new "wacom_led" attribute
group is added to the sysfs entry of the USB device.

This "wacom_led" group only shows up when the correct device (M, L, or XL)
is detected. The attributes are described in
 Documentation/ABI/testing/sysfs-wacom

Signed-off-by: Eduard Hasenleithner <eduard@hasenleithner.at>
Signed-off-by: Dmitry Torokhov <dtor@mail.ru>
This commit is contained in:
Eduard Hasenleithner 2011-09-07 14:08:54 -07:00 committed by Dmitry Torokhov
parent 7e66eaf14e
commit 5d7e7d4798
4 changed files with 341 additions and 40 deletions

View File

@ -0,0 +1,64 @@
What: /sys/class/hidraw/hidraw*/device/speed
Date: April 2010
Kernel Version: 2.6.35
Contact: linux-bluetooth@vger.kernel.org
Description:
The /sys/class/hidraw/hidraw*/device/speed file controls
reporting speed of Wacom bluetooth tablet. Reading from
this file returns 1 if tablet reports in high speed mode
or 0 otherwise. Writing to this file one of these values
switches reporting speed.
What: /sys/bus/usb/devices/<busnum>-<devnum>:<cfg>.<intf>/wacom_led/led
Date: August 2011
Contact: linux-input@vger.kernel.org
Description:
Attribute group for control of the status LEDs and the OLED
displays found on the Wacom Intuos 4 M, L, and XL tablets. This
attribute group is not available for other Wacom tablets.
Therefore its presence implicitly signifies the presence of
said LEDs and OLED displays on the tablet device.
What: /sys/bus/usb/devices/<busnum>-<devnum>:<cfg>.<intf>/wacom_led/status0_luminance
Date: August 2011
Contact: linux-input@vger.kernel.org
Description:
Writing to this file sets the status LED luminance (0..127)
when the stylus does not touch the tablet surface, and no
button is pressed on the stylus.
What: /sys/bus/usb/devices/<busnum>-<devnum>:<cfg>.<intf>/wacom_led/status1_luminance
Date: August 2011
Contact: linux-input@vger.kernel.org
Description:
Writing to this file sets the status LED luminance (0..127)
when the stylus touches the tablet surface, or any button is
pressed on the stylus.
What: /sys/bus/usb/devices/<busnum>-<devnum>:<cfg>.<intf>/wacom_led/status_led_select
Date: August 2011
Contact: linux-input@vger.kernel.org
Description:
Writing to this file sets which one of the four status LEDs is
active (0..3). The other three LEDs are always inactive. By
means of specifying "-1" it is possible to set all status LEDs
to inactive.
What: /sys/bus/usb/devices/<busnum>-<devnum>:<cfg>.<intf>/wacom_led/buttons_luminance
Date: August 2011
Contact: linux-input@vger.kernel.org
Description:
Writing to this file sets the overall luminance level (0..15)
of all eight button OLED displays.
What: /sys/bus/usb/devices/<busnum>-<devnum>:<cfg>.<intf>/wacom_led/button<n>_rawimg
Date: August 2011
Contact: linux-input@vger.kernel.org
Description:
When writing a 1024 byte raw image in Wacom Intuos 4
interleaving format to the file, the image shows up on Button N
of the device. The image is a 64x32 pixel 4-bit gray image. The
1024 byte binary is split up into 16x 64 byte chunks. Each 64
byte chunk encodes the image data for two consecutive lines on
the display. The low nibble of each byte contains the first
line, and the high nibble contains the second line.

View File

@ -1,10 +0,0 @@
What: /sys/class/hidraw/hidraw*/device/speed
Date: April 2010
Kernel Version: 2.6.35
Contact: linux-bluetooth@vger.kernel.org
Description:
The /sys/class/hidraw/hidraw*/device/speed file controls
reporting speed of wacom bluetooth tablet. Reading from
this file returns 1 if tablet reports in high speed mode
or 0 otherwise. Writing to this file one of these values
switches reporting speed.

View File

@ -114,6 +114,12 @@ struct wacom {
struct mutex lock;
bool open;
char phys[32];
struct wacom_led {
u8 select; /* status led selector (0..3, -1=none) */
u8 llv; /* status led brightness no button */
u8 hlv; /* status led brightness button pressed */
u8 img_lum; /* OLED matrix display brightness */
} led;
};
extern const struct usb_device_id wacom_ids[];

View File

@ -48,27 +48,49 @@ struct hid_descriptor {
/* defines to get/set USB message */
#define USB_REQ_GET_REPORT 0x01
#define USB_REQ_SET_REPORT 0x09
#define WAC_HID_FEATURE_REPORT 0x03
#define WAC_MSG_RETRIES 5
static int usb_get_report(struct usb_interface *intf, unsigned char type,
unsigned char id, void *buf, int size)
#define WAC_CMD_LED_CONTROL 0x20
#define WAC_CMD_ICON_START 0x21
#define WAC_CMD_ICON_XFER 0x23
#define WAC_CMD_RETRIES 10
static int wacom_get_report(struct usb_interface *intf, u8 type, u8 id,
void *buf, size_t size, unsigned int retries)
{
return usb_control_msg(interface_to_usbdev(intf),
usb_rcvctrlpipe(interface_to_usbdev(intf), 0),
USB_REQ_GET_REPORT, USB_TYPE_CLASS | USB_RECIP_INTERFACE,
(type << 8) + id, intf->altsetting[0].desc.bInterfaceNumber,
buf, size, 100);
struct usb_device *dev = interface_to_usbdev(intf);
int retval;
do {
retval = usb_control_msg(dev, usb_rcvctrlpipe(dev, 0),
USB_REQ_GET_REPORT,
USB_TYPE_CLASS | USB_RECIP_INTERFACE,
(type << 8) + id,
intf->altsetting[0].desc.bInterfaceNumber,
buf, size, 100);
} while ((retval == -ETIMEDOUT || retval == -EPIPE) && --retries);
return retval;
}
static int usb_set_report(struct usb_interface *intf, unsigned char type,
unsigned char id, void *buf, int size)
static int wacom_set_report(struct usb_interface *intf, u8 type, u8 id,
void *buf, size_t size, unsigned int retries)
{
return usb_control_msg(interface_to_usbdev(intf),
usb_sndctrlpipe(interface_to_usbdev(intf), 0),
USB_REQ_SET_REPORT, USB_TYPE_CLASS | USB_RECIP_INTERFACE,
(type << 8) + id, intf->altsetting[0].desc.bInterfaceNumber,
buf, size, 1000);
struct usb_device *dev = interface_to_usbdev(intf);
int retval;
do {
retval = usb_control_msg(dev, usb_sndctrlpipe(dev, 0),
USB_REQ_SET_REPORT,
USB_TYPE_CLASS | USB_RECIP_INTERFACE,
(type << 8) + id,
intf->altsetting[0].desc.bInterfaceNumber,
buf, size, 1000);
} while ((retval == -ETIMEDOUT || retval == -EPIPE) && --retries);
return retval;
}
static void wacom_sys_irq(struct urb *urb)
@ -333,23 +355,23 @@ static int wacom_query_tablet_data(struct usb_interface *intf, struct wacom_feat
rep_data[2] = 0;
rep_data[3] = 0;
report_id = 3;
error = usb_set_report(intf, WAC_HID_FEATURE_REPORT,
report_id, rep_data, 4);
error = wacom_set_report(intf, WAC_HID_FEATURE_REPORT,
report_id, rep_data, 4, 1);
if (error >= 0)
error = usb_get_report(intf,
WAC_HID_FEATURE_REPORT, report_id,
rep_data, 4);
error = wacom_get_report(intf,
WAC_HID_FEATURE_REPORT,
report_id, rep_data, 4, 1);
} while ((error < 0 || rep_data[1] != 4) && limit++ < WAC_MSG_RETRIES);
} else if (features->type != TABLETPC) {
do {
rep_data[0] = 2;
rep_data[1] = 2;
error = usb_set_report(intf, WAC_HID_FEATURE_REPORT,
report_id, rep_data, 2);
error = wacom_set_report(intf, WAC_HID_FEATURE_REPORT,
report_id, rep_data, 2, 1);
if (error >= 0)
error = usb_get_report(intf,
WAC_HID_FEATURE_REPORT, report_id,
rep_data, 2);
error = wacom_get_report(intf,
WAC_HID_FEATURE_REPORT,
report_id, rep_data, 2, 1);
} while ((error < 0 || rep_data[1] != 2) && limit++ < WAC_MSG_RETRIES);
}
@ -468,6 +490,220 @@ static void wacom_remove_shared_data(struct wacom_wac *wacom)
}
}
static int wacom_led_control(struct wacom *wacom)
{
unsigned char *buf;
int retval;
buf = kzalloc(9, GFP_KERNEL);
if (!buf)
return -ENOMEM;
buf[0] = WAC_CMD_LED_CONTROL;
buf[1] = wacom->led.select >= 0 ? wacom->led.select | 4 : 0;
buf[2] = wacom->led.llv;
buf[3] = wacom->led.hlv;
buf[4] = wacom->led.img_lum;
retval = wacom_set_report(wacom->intf, 0x03, WAC_CMD_LED_CONTROL,
buf, 9, WAC_CMD_RETRIES);
kfree(buf);
return retval;
}
static int wacom_led_putimage(struct wacom *wacom, int button_id, const void *img)
{
unsigned char *buf;
int i, retval;
buf = kzalloc(259, GFP_KERNEL);
if (!buf)
return -ENOMEM;
/* Send 'start' command */
buf[0] = WAC_CMD_ICON_START;
buf[1] = 1;
retval = wacom_set_report(wacom->intf, 0x03, WAC_CMD_ICON_START,
buf, 2, WAC_CMD_RETRIES);
if (retval < 0)
goto out;
buf[0] = WAC_CMD_ICON_XFER;
buf[1] = button_id & 0x07;
for (i = 0; i < 4; i++) {
buf[2] = i;
memcpy(buf + 3, img + i * 256, 256);
retval = wacom_set_report(wacom->intf, 0x03, WAC_CMD_ICON_XFER,
buf, 259, WAC_CMD_RETRIES);
if (retval < 0)
break;
}
/* Send 'stop' */
buf[0] = WAC_CMD_ICON_START;
buf[1] = 0;
wacom_set_report(wacom->intf, 0x03, WAC_CMD_ICON_START,
buf, 2, WAC_CMD_RETRIES);
out:
kfree(buf);
return retval;
}
static ssize_t wacom_led_select_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct wacom *wacom = dev_get_drvdata(dev);
unsigned int id;
int err;
err = kstrtouint(buf, 10, &id);
if (err)
return err;
mutex_lock(&wacom->lock);
wacom->led.select = id;
err = wacom_led_control(wacom);
mutex_unlock(&wacom->lock);
return err < 0 ? err : count;
}
static DEVICE_ATTR(status_led_select, S_IWUSR, NULL, wacom_led_select_store);
static ssize_t wacom_luminance_store(struct wacom *wacom, u8 *dest,
const char *buf, size_t count)
{
unsigned int value;
int err;
err = kstrtouint(buf, 10, &value);
if (err)
return err;
mutex_lock(&wacom->lock);
*dest = value & 0x7f;
err = wacom_led_control(wacom);
mutex_unlock(&wacom->lock);
return err < 0 ? err : count;
}
#define DEVICE_LUMINANCE_ATTR(name, field) \
static ssize_t wacom_##name##_luminance_store(struct device *dev, \
struct device_attribute *attr, const char *buf, size_t count) \
{ \
struct wacom *wacom = dev_get_drvdata(dev); \
\
return wacom_luminance_store(wacom, &wacom->led.field, \
buf, count); \
} \
static DEVICE_ATTR(name##_luminance, S_IWUSR, \
NULL, wacom_##name##_luminance_store)
DEVICE_LUMINANCE_ATTR(status0, llv);
DEVICE_LUMINANCE_ATTR(status1, hlv);
DEVICE_LUMINANCE_ATTR(buttons, img_lum);
static ssize_t wacom_button_image_store(struct device *dev, int button_id,
const char *buf, size_t count)
{
struct wacom *wacom = dev_get_drvdata(dev);
int err;
if (count != 1024)
return -EINVAL;
mutex_lock(&wacom->lock);
err = wacom_led_putimage(wacom, button_id, buf);
mutex_unlock(&wacom->lock);
return err < 0 ? err : count;
}
#define DEVICE_BTNIMG_ATTR(BUTTON_ID) \
static ssize_t wacom_btnimg##BUTTON_ID##_store(struct device *dev, \
struct device_attribute *attr, const char *buf, size_t count) \
{ \
return wacom_button_image_store(dev, BUTTON_ID, buf, count); \
} \
static DEVICE_ATTR(button##BUTTON_ID##_rawimg, S_IWUSR, \
NULL, wacom_btnimg##BUTTON_ID##_store)
DEVICE_BTNIMG_ATTR(0);
DEVICE_BTNIMG_ATTR(1);
DEVICE_BTNIMG_ATTR(2);
DEVICE_BTNIMG_ATTR(3);
DEVICE_BTNIMG_ATTR(4);
DEVICE_BTNIMG_ATTR(5);
DEVICE_BTNIMG_ATTR(6);
DEVICE_BTNIMG_ATTR(7);
static struct attribute *wacom_led_attrs[] = {
&dev_attr_status0_luminance.attr,
&dev_attr_status1_luminance.attr,
&dev_attr_status_led_select.attr,
&dev_attr_buttons_luminance.attr,
&dev_attr_button0_rawimg.attr,
&dev_attr_button1_rawimg.attr,
&dev_attr_button2_rawimg.attr,
&dev_attr_button3_rawimg.attr,
&dev_attr_button4_rawimg.attr,
&dev_attr_button5_rawimg.attr,
&dev_attr_button6_rawimg.attr,
&dev_attr_button7_rawimg.attr,
NULL
};
static struct attribute_group wacom_led_attr_group = {
.name = "wacom_led",
.attrs = wacom_led_attrs,
};
static int wacom_initialize_leds(struct wacom *wacom)
{
int error;
if (wacom->wacom_wac.features.type >= INTUOS4 &&
wacom->wacom_wac.features.type <= INTUOS4L) {
/* Initialize default values */
wacom->led.select = 0;
wacom->led.llv = 30;
wacom->led.hlv = 20;
wacom->led.img_lum = 10;
wacom_led_control(wacom);
error = sysfs_create_group(&wacom->intf->dev.kobj,
&wacom_led_attr_group);
if (error) {
dev_err(&wacom->intf->dev,
"cannot create sysfs group err: %d\n", error);
return error;
}
}
return 0;
}
static void wacom_destroy_leds(struct wacom *wacom)
{
if (wacom->wacom_wac.features.type >= INTUOS4 &&
wacom->wacom_wac.features.type <= INTUOS4L) {
sysfs_remove_group(&wacom->intf->dev.kobj,
&wacom_led_attr_group);
}
}
static int wacom_probe(struct usb_interface *intf, const struct usb_device_id *id)
{
struct usb_device *dev = interface_to_usbdev(intf);
@ -556,16 +792,21 @@ static int wacom_probe(struct usb_interface *intf, const struct usb_device_id *i
wacom->irq->transfer_dma = wacom->data_dma;
wacom->irq->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
error = input_register_device(input_dev);
error = wacom_initialize_leds(wacom);
if (error)
goto fail4;
error = input_register_device(input_dev);
if (error)
goto fail5;
/* Note that if query fails it is not a hard failure */
wacom_query_tablet_data(intf, features);
usb_set_intfdata(intf, wacom);
return 0;
fail5: wacom_destroy_leds(wacom);
fail4: wacom_remove_shared_data(wacom_wac);
fail3: usb_free_urb(wacom->irq);
fail2: usb_free_coherent(dev, WACOM_PKGLEN_MAX, wacom_wac->data, wacom->data_dma);
@ -582,6 +823,7 @@ static void wacom_disconnect(struct usb_interface *intf)
usb_kill_urb(wacom->irq);
input_unregister_device(wacom->wacom_wac.input);
wacom_destroy_leds(wacom);
usb_free_urb(wacom->irq);
usb_free_coherent(interface_to_usbdev(intf), WACOM_PKGLEN_MAX,
wacom->wacom_wac.data, wacom->data_dma);
@ -604,17 +846,16 @@ static int wacom_resume(struct usb_interface *intf)
{
struct wacom *wacom = usb_get_intfdata(intf);
struct wacom_features *features = &wacom->wacom_wac.features;
int rv;
int rv = 0;
mutex_lock(&wacom->lock);
/* switch to wacom mode first */
wacom_query_tablet_data(intf, features);
wacom_led_control(wacom);
if (wacom->open)
rv = usb_submit_urb(wacom->irq, GFP_NOIO);
else
rv = 0;
if (wacom->open && usb_submit_urb(wacom->irq, GFP_NOIO) < 0)
rv = -EIO;
mutex_unlock(&wacom->lock);