linux/drivers/hid/hid-thingm.c
Vivien Didelot f70ed8a6f7 HID: thingm: refactor blink(1) support
This patch refactors the way the thingm driver registers a blink(1) LED.
In order to make the driver simpler and more standard, drop the "rgb"
sysfs attribute and create one instance of LED class per RGB channel.

Actually, the name of the LED class instance registered for a blink(1)
device is "blink1::ABCD", where ABCD is the last 4 chars of the serial
number. The driver now registers 3 instances per RGB chip, named
"thingmX:{red,green,blue}:ledY" where X is the hidraw minor number and Y
is the RGB chip number (as seen by the firmware).

This patch also uses work queues to defer calls with the device, which
now allows triggers to work as expected with this LED device.

Also remove the brightness structure field and the brightness_get
backend, as it is already handled by the LED class, and changes the
prefix of functions and structures to thingm_ to match the driver name.

Signed-off-by: Vivien Didelot <vivien.didelot@savoirfairelinux.com>
Signed-off-by: Jiri Kosina <jkosina@suse.cz>
2014-04-15 14:42:51 +02:00

308 lines
6.8 KiB
C

/*
* 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 <linux/workqueue.h>
#include "hid-ids.h"
#define REPORT_ID 1
#define REPORT_SIZE 9
/* Firmware major number of supported devices */
#define THINGM_MAJOR_MK1 '1'
struct thingm_fwinfo {
char major;
unsigned numrgb;
unsigned first;
};
const struct thingm_fwinfo thingm_fwinfo[] = {
{
.major = THINGM_MAJOR_MK1,
.numrgb = 1,
.first = 0,
}
};
/* 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;
struct work_struct work;
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]);
ret = hid_hw_raw_request(tdev->hdev, buf[0], buf, REPORT_SIZE,
HID_FEATURE_REPORT, HID_REQ_SET_REPORT);
return ret < 0 ? ret : 0;
}
static int thingm_recv(struct thingm_device *tdev, u8 buf[REPORT_SIZE])
{
int ret;
ret = hid_hw_raw_request(tdev->hdev, buf[0], buf, REPORT_SIZE,
HID_FEATURE_REPORT, HID_REQ_GET_REPORT);
if (ret < 0)
return 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]);
return 0;
}
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_send(tdev, buf);
if (err)
return 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, 'n', 0, 0, 0, 0, 0, 0, 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 void thingm_work(struct work_struct *work)
{
struct thingm_rgb *rgb = container_of(work, struct thingm_rgb, work);
mutex_lock(&rgb->tdev->lock);
if (thingm_write_color(rgb))
hid_err(rgb->tdev->hdev, "failed to write color\n");
mutex_unlock(&rgb->tdev->lock);
}
static void thingm_led_set(struct led_classdev *ldev,
enum led_brightness brightness)
{
struct thingm_led *led = container_of(ldev, struct thingm_led, ldev);
/* the ledclass has already stored the brightness value */
schedule_work(&led->rgb->work);
}
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 */
snprintf(rgb->red.name, sizeof(rgb->red.name),
"thingm%d:red:led%d", minor, rgb->num);
rgb->red.ldev.name = rgb->red.name;
rgb->red.ldev.max_brightness = 255;
rgb->red.ldev.brightness_set = thingm_led_set;
rgb->red.rgb = rgb;
err = led_classdev_register(&rgb->tdev->hdev->dev, &rgb->red.ldev);
if (err)
return err;
/* Register the green diode */
snprintf(rgb->green.name, sizeof(rgb->green.name),
"thingm%d:green:led%d", minor, rgb->num);
rgb->green.ldev.name = rgb->green.name;
rgb->green.ldev.max_brightness = 255;
rgb->green.ldev.brightness_set = thingm_led_set;
rgb->green.rgb = rgb;
err = led_classdev_register(&rgb->tdev->hdev->dev, &rgb->green.ldev);
if (err)
goto unregister_red;
/* Register the blue diode */
snprintf(rgb->blue.name, sizeof(rgb->blue.name),
"thingm%d:blue:led%d", minor, rgb->num);
rgb->blue.ldev.name = rgb->blue.name;
rgb->blue.ldev.max_brightness = 255;
rgb->blue.ldev.brightness_set = thingm_led_set;
rgb->blue.rgb = rgb;
err = led_classdev_register(&rgb->tdev->hdev->dev, &rgb->blue.ldev);
if (err)
goto unregister_green;
INIT_WORK(&rgb->work, thingm_work);
return 0;
unregister_green:
led_classdev_unregister(&rgb->green.ldev);
unregister_red:
led_classdev_unregister(&rgb->red.ldev);
return err;
}
static void thingm_remove_rgb(struct thingm_rgb *rgb)
{
flush_work(&rgb->work);
led_classdev_unregister(&rgb->red.ldev);
led_classdev_unregister(&rgb->green.ldev);
led_classdev_unregister(&rgb->blue.ldev);
}
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)
goto error;
err = hid_hw_start(hdev, HID_CONNECT_HIDRAW);
if (err)
goto error;
mutex_init(&tdev->lock);
err = thingm_version(tdev);
if (err)
goto stop;
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);
goto stop;
}
tdev->rgb = devm_kzalloc(&hdev->dev,
sizeof(struct thingm_rgb) * tdev->fwinfo->numrgb,
GFP_KERNEL);
if (!tdev->rgb) {
err = -ENOMEM;
goto stop;
}
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) {
while (--i >= 0)
thingm_remove_rgb(tdev->rgb + i);
goto stop;
}
}
return 0;
stop:
hid_hw_stop(hdev);
error:
return err;
}
static void thingm_remove(struct hid_device *hdev)
{
struct thingm_device *tdev = hid_get_drvdata(hdev);
int i;
for (i = 0; i < tdev->fwinfo->numrgb; ++i)
thingm_remove_rgb(tdev->rgb + i);
hid_hw_stop(hdev);
}
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,
.remove = thingm_remove,
.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");