linux/drivers/hid/hid-thingm.c
<<
>>
Prefs
   1/*
   2 * ThingM blink(1) USB RGB LED driver
   3 *
   4 * Copyright 2013-2014 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/hidraw.h>
  14#include <linux/leds.h>
  15#include <linux/module.h>
  16#include <linux/mutex.h>
  17#include <linux/workqueue.h>
  18
  19#include "hid-ids.h"
  20
  21#define REPORT_ID       1
  22#define REPORT_SIZE     9
  23
  24/* Firmware major number of supported devices */
  25#define THINGM_MAJOR_MK1        '1'
  26#define THINGM_MAJOR_MK2        '2'
  27
  28struct thingm_fwinfo {
  29        char major;
  30        unsigned numrgb;
  31        unsigned first;
  32};
  33
  34static const struct thingm_fwinfo thingm_fwinfo[] = {
  35        {
  36                .major = THINGM_MAJOR_MK1,
  37                .numrgb = 1,
  38                .first = 0,
  39        }, {
  40                .major = THINGM_MAJOR_MK2,
  41                .numrgb = 2,
  42                .first = 1,
  43        }
  44};
  45
  46/* A red, green or blue channel, part of an RGB chip */
  47struct thingm_led {
  48        struct thingm_rgb *rgb;
  49        struct led_classdev ldev;
  50        char name[32];
  51};
  52
  53/* Basically a WS2812 5050 RGB LED chip */
  54struct thingm_rgb {
  55        struct thingm_device *tdev;
  56        struct thingm_led red;
  57        struct thingm_led green;
  58        struct thingm_led blue;
  59        struct work_struct work;
  60        u8 num;
  61};
  62
  63struct thingm_device {
  64        struct hid_device *hdev;
  65        struct {
  66                char major;
  67                char minor;
  68        } version;
  69        const struct thingm_fwinfo *fwinfo;
  70        struct mutex lock;
  71        struct thingm_rgb *rgb;
  72};
  73
  74static int thingm_send(struct thingm_device *tdev, u8 buf[REPORT_SIZE])
  75{
  76        int ret;
  77
  78        hid_dbg(tdev->hdev, "-> %d %c %02hhx %02hhx %02hhx %02hhx %02hhx %02hhx %02hhx\n",
  79                        buf[0], buf[1], buf[2], buf[3], buf[4],
  80                        buf[5], buf[6], buf[7], buf[8]);
  81
  82        ret = hid_hw_raw_request(tdev->hdev, buf[0], buf, REPORT_SIZE,
  83                        HID_FEATURE_REPORT, HID_REQ_SET_REPORT);
  84
  85        return ret < 0 ? ret : 0;
  86}
  87
  88static int thingm_recv(struct thingm_device *tdev, u8 buf[REPORT_SIZE])
  89{
  90        int ret;
  91
  92        ret = hid_hw_raw_request(tdev->hdev, buf[0], buf, REPORT_SIZE,
  93                        HID_FEATURE_REPORT, HID_REQ_GET_REPORT);
  94        if (ret < 0)
  95                return ret;
  96
  97        hid_dbg(tdev->hdev, "<- %d %c %02hhx %02hhx %02hhx %02hhx %02hhx %02hhx %02hhx\n",
  98                        buf[0], buf[1], buf[2], buf[3], buf[4],
  99                        buf[5], buf[6], buf[7], buf[8]);
 100
 101        return 0;
 102}
 103
 104static int thingm_version(struct thingm_device *tdev)
 105{
 106        u8 buf[REPORT_SIZE] = { REPORT_ID, 'v', 0, 0, 0, 0, 0, 0, 0 };
 107        int err;
 108
 109        err = thingm_send(tdev, buf);
 110        if (err)
 111                return err;
 112
 113        err = thingm_recv(tdev, buf);
 114        if (err)
 115                return err;
 116
 117        tdev->version.major = buf[3];
 118        tdev->version.minor = buf[4];
 119
 120        return 0;
 121}
 122
 123static int thingm_write_color(struct thingm_rgb *rgb)
 124{
 125        u8 buf[REPORT_SIZE] = { REPORT_ID, 'c', 0, 0, 0, 0, 0, rgb->num, 0 };
 126
 127        buf[2] = rgb->red.ldev.brightness;
 128        buf[3] = rgb->green.ldev.brightness;
 129        buf[4] = rgb->blue.ldev.brightness;
 130
 131        return thingm_send(rgb->tdev, buf);
 132}
 133
 134static void thingm_work(struct work_struct *work)
 135{
 136        struct thingm_rgb *rgb = container_of(work, struct thingm_rgb, work);
 137
 138        mutex_lock(&rgb->tdev->lock);
 139
 140        if (thingm_write_color(rgb))
 141                hid_err(rgb->tdev->hdev, "failed to write color\n");
 142
 143        mutex_unlock(&rgb->tdev->lock);
 144}
 145
 146static void thingm_led_set(struct led_classdev *ldev,
 147                enum led_brightness brightness)
 148{
 149        struct thingm_led *led = container_of(ldev, struct thingm_led, ldev);
 150
 151        /* the ledclass has already stored the brightness value */
 152        schedule_work(&led->rgb->work);
 153}
 154
 155static int thingm_init_rgb(struct thingm_rgb *rgb)
 156{
 157        const int minor = ((struct hidraw *) rgb->tdev->hdev->hidraw)->minor;
 158        int err;
 159
 160        /* Register the red diode */
 161        snprintf(rgb->red.name, sizeof(rgb->red.name),
 162                        "thingm%d:red:led%d", minor, rgb->num);
 163        rgb->red.ldev.name = rgb->red.name;
 164        rgb->red.ldev.max_brightness = 255;
 165        rgb->red.ldev.brightness_set = thingm_led_set;
 166        rgb->red.rgb = rgb;
 167
 168        err = led_classdev_register(&rgb->tdev->hdev->dev, &rgb->red.ldev);
 169        if (err)
 170                return err;
 171
 172        /* Register the green diode */
 173        snprintf(rgb->green.name, sizeof(rgb->green.name),
 174                        "thingm%d:green:led%d", minor, rgb->num);
 175        rgb->green.ldev.name = rgb->green.name;
 176        rgb->green.ldev.max_brightness = 255;
 177        rgb->green.ldev.brightness_set = thingm_led_set;
 178        rgb->green.rgb = rgb;
 179
 180        err = led_classdev_register(&rgb->tdev->hdev->dev, &rgb->green.ldev);
 181        if (err)
 182                goto unregister_red;
 183
 184        /* Register the blue diode */
 185        snprintf(rgb->blue.name, sizeof(rgb->blue.name),
 186                        "thingm%d:blue:led%d", minor, rgb->num);
 187        rgb->blue.ldev.name = rgb->blue.name;
 188        rgb->blue.ldev.max_brightness = 255;
 189        rgb->blue.ldev.brightness_set = thingm_led_set;
 190        rgb->blue.rgb = rgb;
 191
 192        err = led_classdev_register(&rgb->tdev->hdev->dev, &rgb->blue.ldev);
 193        if (err)
 194                goto unregister_green;
 195
 196        INIT_WORK(&rgb->work, thingm_work);
 197
 198        return 0;
 199
 200unregister_green:
 201        led_classdev_unregister(&rgb->green.ldev);
 202
 203unregister_red:
 204        led_classdev_unregister(&rgb->red.ldev);
 205
 206        return err;
 207}
 208
 209static void thingm_remove_rgb(struct thingm_rgb *rgb)
 210{
 211        led_classdev_unregister(&rgb->red.ldev);
 212        led_classdev_unregister(&rgb->green.ldev);
 213        led_classdev_unregister(&rgb->blue.ldev);
 214        flush_work(&rgb->work);
 215}
 216
 217static int thingm_probe(struct hid_device *hdev, const struct hid_device_id *id)
 218{
 219        struct thingm_device *tdev;
 220        int i, err;
 221
 222        tdev = devm_kzalloc(&hdev->dev, sizeof(struct thingm_device),
 223                        GFP_KERNEL);
 224        if (!tdev)
 225                return -ENOMEM;
 226
 227        tdev->hdev = hdev;
 228        hid_set_drvdata(hdev, tdev);
 229
 230        err = hid_parse(hdev);
 231        if (err)
 232                goto error;
 233
 234        err = hid_hw_start(hdev, HID_CONNECT_HIDRAW);
 235        if (err)
 236                goto error;
 237
 238        mutex_init(&tdev->lock);
 239
 240        err = thingm_version(tdev);
 241        if (err)
 242                goto stop;
 243
 244        hid_dbg(hdev, "firmware version: %c.%c\n",
 245                        tdev->version.major, tdev->version.minor);
 246
 247        for (i = 0; i < ARRAY_SIZE(thingm_fwinfo) && !tdev->fwinfo; ++i)
 248                if (thingm_fwinfo[i].major == tdev->version.major)
 249                        tdev->fwinfo = &thingm_fwinfo[i];
 250
 251        if (!tdev->fwinfo) {
 252                hid_err(hdev, "unsupported firmware %c\n", tdev->version.major);
 253                err = -ENODEV;
 254                goto stop;
 255        }
 256
 257        tdev->rgb = devm_kzalloc(&hdev->dev,
 258                        sizeof(struct thingm_rgb) * tdev->fwinfo->numrgb,
 259                        GFP_KERNEL);
 260        if (!tdev->rgb) {
 261                err = -ENOMEM;
 262                goto stop;
 263        }
 264
 265        for (i = 0; i < tdev->fwinfo->numrgb; ++i) {
 266                struct thingm_rgb *rgb = tdev->rgb + i;
 267
 268                rgb->tdev = tdev;
 269                rgb->num = tdev->fwinfo->first + i;
 270                err = thingm_init_rgb(rgb);
 271                if (err) {
 272                        while (--i >= 0)
 273                                thingm_remove_rgb(tdev->rgb + i);
 274                        goto stop;
 275                }
 276        }
 277
 278        return 0;
 279stop:
 280        hid_hw_stop(hdev);
 281error:
 282        return err;
 283}
 284
 285static void thingm_remove(struct hid_device *hdev)
 286{
 287        struct thingm_device *tdev = hid_get_drvdata(hdev);
 288        int i;
 289
 290        hid_hw_stop(hdev);
 291
 292        for (i = 0; i < tdev->fwinfo->numrgb; ++i)
 293                thingm_remove_rgb(tdev->rgb + i);
 294}
 295
 296static const struct hid_device_id thingm_table[] = {
 297        { HID_USB_DEVICE(USB_VENDOR_ID_THINGM, USB_DEVICE_ID_BLINK1) },
 298        { }
 299};
 300MODULE_DEVICE_TABLE(hid, thingm_table);
 301
 302static struct hid_driver thingm_driver = {
 303        .name = "thingm",
 304        .probe = thingm_probe,
 305        .remove = thingm_remove,
 306        .id_table = thingm_table,
 307};
 308
 309module_hid_driver(thingm_driver);
 310
 311MODULE_LICENSE("GPL");
 312MODULE_AUTHOR("Vivien Didelot <vivien.didelot@savoirfairelinux.com>");
 313MODULE_DESCRIPTION("ThingM blink(1) USB RGB LED driver");
 314