hid-thingm.c revision aee114fd3c94f1be0f95af84d6ed25cd47702c41
1/* 2 * ThingM blink(1) USB RGB LED driver 3 * 4 * Copyright 2013 Savoir-faire Linux Inc. 5 * Vivien Didelot <vivien.didelot@savoirfairelinux.com> 6 * 7 * This program is free software; you can redistribute it and/or 8 * modify it under the terms of the GNU General Public License as 9 * published by the Free Software Foundation, version 2. 10 */ 11 12#include <linux/hid.h> 13#include <linux/leds.h> 14#include <linux/module.h> 15 16#include "hid-ids.h" 17 18#define BLINK1_CMD_SIZE 9 19 20#define blink1_rgb_to_r(rgb) ((rgb & 0xFF0000) >> 16) 21#define blink1_rgb_to_g(rgb) ((rgb & 0x00FF00) >> 8) 22#define blink1_rgb_to_b(rgb) ((rgb & 0x0000FF) >> 0) 23 24/** 25 * struct blink1_data - blink(1) device specific data 26 * @hdev: HID device. 27 * @led_cdev: LED class instance. 28 * @rgb: 8-bit per channel RGB notation. 29 * @brightness: brightness coefficient. 30 */ 31struct blink1_data { 32 struct hid_device *hdev; 33 struct led_classdev led_cdev; 34 u32 rgb; 35 u8 brightness; 36}; 37 38static int blink1_send_command(struct blink1_data *data, 39 u8 buf[BLINK1_CMD_SIZE]) 40{ 41 int ret; 42 43 hid_dbg(data->hdev, "command: %d%c%.2x%.2x%.2x%.2x%.2x%.2x%.2x\n", 44 buf[0], buf[1], buf[2], buf[3], buf[4], 45 buf[5], buf[6], buf[7], buf[8]); 46 47 ret = hid_hw_raw_request(data->hdev, buf[0], buf, BLINK1_CMD_SIZE, 48 HID_FEATURE_REPORT, HID_REQ_SET_REPORT); 49 50 return ret < 0 ? ret : 0; 51} 52 53static int blink1_update_color(struct blink1_data *data) 54{ 55 u8 buf[BLINK1_CMD_SIZE] = { 1, 'n', 0, 0, 0, 0, 0, 0, 0 }; 56 57 if (data->brightness) { 58 unsigned int coef = DIV_ROUND_CLOSEST(255, data->brightness); 59 60 buf[2] = DIV_ROUND_CLOSEST(blink1_rgb_to_r(data->rgb), coef); 61 buf[3] = DIV_ROUND_CLOSEST(blink1_rgb_to_g(data->rgb), coef); 62 buf[4] = DIV_ROUND_CLOSEST(blink1_rgb_to_b(data->rgb), coef); 63 } 64 65 return blink1_send_command(data, buf); 66} 67 68static void blink1_led_set(struct led_classdev *led_cdev, 69 enum led_brightness brightness) 70{ 71 struct blink1_data *data = dev_get_drvdata(led_cdev->dev->parent); 72 73 data->brightness = brightness; 74 if (blink1_update_color(data)) 75 hid_err(data->hdev, "failed to update color\n"); 76} 77 78static enum led_brightness blink1_led_get(struct led_classdev *led_cdev) 79{ 80 struct blink1_data *data = dev_get_drvdata(led_cdev->dev->parent); 81 82 return data->brightness; 83} 84 85static ssize_t blink1_show_rgb(struct device *dev, 86 struct device_attribute *attr, char *buf) 87{ 88 struct blink1_data *data = dev_get_drvdata(dev->parent); 89 90 return sprintf(buf, "%.6X\n", data->rgb); 91} 92 93static ssize_t blink1_store_rgb(struct device *dev, 94 struct device_attribute *attr, const char *buf, size_t count) 95{ 96 struct blink1_data *data = dev_get_drvdata(dev->parent); 97 long unsigned int rgb; 98 int ret; 99 100 ret = kstrtoul(buf, 16, &rgb); 101 if (ret) 102 return ret; 103 104 /* RGB triplet notation is 24-bit hexadecimal */ 105 if (rgb > 0xFFFFFF) 106 return -EINVAL; 107 108 data->rgb = rgb; 109 ret = blink1_update_color(data); 110 111 return ret ? ret : count; 112} 113 114static DEVICE_ATTR(rgb, S_IRUGO | S_IWUSR, blink1_show_rgb, blink1_store_rgb); 115 116static const struct attribute_group blink1_sysfs_group = { 117 .attrs = (struct attribute *[]) { 118 &dev_attr_rgb.attr, 119 NULL 120 }, 121}; 122 123static int thingm_probe(struct hid_device *hdev, const struct hid_device_id *id) 124{ 125 struct blink1_data *data; 126 struct led_classdev *led; 127 char led_name[13]; 128 int ret; 129 130 data = devm_kzalloc(&hdev->dev, sizeof(struct blink1_data), GFP_KERNEL); 131 if (!data) 132 return -ENOMEM; 133 134 hid_set_drvdata(hdev, data); 135 data->hdev = hdev; 136 data->rgb = 0xFFFFFF; /* set a default white color */ 137 138 ret = hid_parse(hdev); 139 if (ret) 140 goto error; 141 142 ret = hid_hw_start(hdev, HID_CONNECT_HIDRAW); 143 if (ret) 144 goto error; 145 146 /* blink(1) serial numbers range is 0x1A001000 to 0x1A002FFF */ 147 led = &data->led_cdev; 148 snprintf(led_name, sizeof(led_name), "blink1::%s", hdev->uniq + 4); 149 led->name = led_name; 150 led->brightness_set = blink1_led_set; 151 led->brightness_get = blink1_led_get; 152 ret = led_classdev_register(&hdev->dev, led); 153 if (ret) 154 goto stop; 155 156 ret = sysfs_create_group(&led->dev->kobj, &blink1_sysfs_group); 157 if (ret) 158 goto remove_led; 159 160 return 0; 161 162remove_led: 163 led_classdev_unregister(led); 164stop: 165 hid_hw_stop(hdev); 166error: 167 return ret; 168} 169 170static void thingm_remove(struct hid_device *hdev) 171{ 172 struct blink1_data *data = hid_get_drvdata(hdev); 173 struct led_classdev *led = &data->led_cdev; 174 175 sysfs_remove_group(&led->dev->kobj, &blink1_sysfs_group); 176 led_classdev_unregister(led); 177 hid_hw_stop(hdev); 178} 179 180static const struct hid_device_id thingm_table[] = { 181 { HID_USB_DEVICE(USB_VENDOR_ID_THINGM, USB_DEVICE_ID_BLINK1) }, 182 { } 183}; 184MODULE_DEVICE_TABLE(hid, thingm_table); 185 186static struct hid_driver thingm_driver = { 187 .name = "thingm", 188 .probe = thingm_probe, 189 .remove = thingm_remove, 190 .id_table = thingm_table, 191}; 192 193module_hid_driver(thingm_driver); 194 195MODULE_LICENSE("GPL"); 196MODULE_AUTHOR("Vivien Didelot <vivien.didelot@savoirfairelinux.com>"); 197MODULE_DESCRIPTION("ThingM blink(1) USB RGB LED driver"); 198