hid-led.c 7.2 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
/*
 * 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,
};

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)]

struct hidled_device;
46
struct hidled_rgb;
47 48 49 50 51 52

struct hidled_config {
	enum hidled_type	type;
	const char		*name;
	const char		*short_name;
	enum led_brightness	max_brightness;
53
	int			num_leds;
54 55 56 57 58 59 60 61 62
	size_t			report_size;
	enum hidled_report_type	report_type;
	u8			report_id;
	int (*init)(struct hidled_device *ldev);
	int (*write)(struct led_classdev *cdev, enum led_brightness br);
};

struct hidled_led {
	struct led_classdev	cdev;
63
	struct hidled_rgb	*rgb;
64 65 66
	char			name[32];
};

67 68
struct hidled_rgb {
	struct hidled_device	*ldev;
69 70 71
	struct hidled_led	red;
	struct hidled_led	green;
	struct hidled_led	blue;
72 73 74 75 76
	u8			num;
};

struct hidled_device {
	const struct hidled_config *config;
77
	struct hid_device       *hdev;
78
	struct hidled_rgb	*rgb;
79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117
	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;

	buf[0] = ldev->config->report_id;

	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;
}

118
static u8 riso_kagaku_index(struct hidled_rgb *rgb)
119 120 121
{
	enum led_brightness r, g, b;

122 123 124
	r = rgb->red.cdev.brightness;
	g = rgb->green.cdev.brightness;
	b = rgb->blue.cdev.brightness;
125 126 127 128 129 130 131 132 133 134

	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);
135
	struct hidled_rgb *rgb = led->rgb;
136 137
	__u8 buf[MAX_REPORT_SIZE] = {};

138
	buf[1] = riso_kagaku_index(rgb);
139

140
	return hidled_send(rgb->ldev, buf);
141 142 143 144 145
}

static int dream_cheeky_write(struct led_classdev *cdev, enum led_brightness br)
{
	struct hidled_led *led = to_hidled_led(cdev);
146
	struct hidled_rgb *rgb = led->rgb;
147 148
	__u8 buf[MAX_REPORT_SIZE] = {};

149 150 151
	buf[1] = rgb->red.cdev.brightness;
	buf[2] = rgb->green.cdev.brightness;
	buf[3] = rgb->blue.cdev.brightness;
152 153 154
	buf[7] = 0x1a;
	buf[8] = 0x05;

155
	return hidled_send(rgb->ldev, buf);
156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177
}

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 const struct hidled_config hidled_configs[] = {
	{
		.type = RISO_KAGAKU,
		.name = "Riso Kagaku Webmail Notifier",
		.short_name = "riso_kagaku",
		.max_brightness = 1,
178
		.num_leds = 1,
179 180 181 182 183 184 185 186 187 188
		.report_size = 6,
		.report_type = OUTPUT_REPORT,
		.report_id = 0,
		.write = riso_kagaku_write,
	},
	{
		.type = DREAM_CHEEKY,
		.name = "Dream Cheeky Webmail Notifier",
		.short_name = "dream_cheeky",
		.max_brightness = 31,
189
		.num_leds = 1,
190 191 192 193 194 195 196 197 198
		.report_size = 9,
		.report_type = RAW_REQUEST,
		.report_id = 0,
		.init = dream_cheeky_init,
		.write = dream_cheeky_write,
	},
};

static int hidled_init_led(struct hidled_led *led, const char *color_name,
199
			   struct hidled_rgb *rgb, unsigned int minor)
200
{
201 202 203 204 205 206 207 208
	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);
209
	led->cdev.name = led->name;
210 211
	led->cdev.max_brightness = config->max_brightness;
	led->cdev.brightness_set_blocking = config->write;
212
	led->cdev.flags = LED_HW_PLUGGABLE;
213
	led->rgb = rgb;
214

215
	return devm_led_classdev_register(&rgb->ldev->hdev->dev, &led->cdev);
216 217
}

218
static int hidled_init_rgb(struct hidled_rgb *rgb, unsigned int minor)
219 220 221 222
{
	int ret;

	/* Register the red diode */
223
	ret = hidled_init_led(&rgb->red, "red", rgb, minor);
224 225 226 227
	if (ret)
		return ret;

	/* Register the green diode */
228
	ret = hidled_init_led(&rgb->green, "green", rgb, minor);
229 230 231 232
	if (ret)
		return ret;

	/* Register the blue diode */
233
	return hidled_init_led(&rgb->blue, "blue", rgb, minor);
234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265
}

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;
	}

266 267 268 269 270
	ldev->rgb = devm_kcalloc(&hdev->dev, ldev->config->num_leds,
				 sizeof(struct hidled_rgb), GFP_KERNEL);
	if (!ldev->rgb)
		return -ENOMEM;

271 272 273 274 275 276
	ret = hid_hw_start(hdev, HID_CONNECT_HIDRAW);
	if (ret)
		return ret;

	minor = ((struct hidraw *) hdev->hidraw)->minor;

277 278 279 280 281 282 283 284
	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;
		}
285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313
	}

	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 },
	{ }
};
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");