2013-01-22 21:01:21 +04:00
/*
* ThingM blink ( 1 ) USB RGB LED driver
*
2014-04-15 00:50:19 +04:00
* Copyright 2013 - 2014 Savoir - faire Linux Inc .
2013-01-22 21:01:21 +04:00
* 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>
2014-04-15 00:50:19 +04:00
# include <linux/hidraw.h>
2013-01-22 21:01:21 +04:00
# include <linux/leds.h>
# include <linux/module.h>
2014-04-15 00:50:19 +04:00
# include <linux/mutex.h>
# include <linux/workqueue.h>
2013-01-22 21:01:21 +04:00
# include "hid-ids.h"
2014-04-15 00:50:19 +04:00
# define REPORT_ID 1
# define REPORT_SIZE 9
2013-01-22 21:01:21 +04:00
2014-04-15 00:50:19 +04:00
/* Firmware major number of supported devices */
# define THINGM_MAJOR_MK1 '1'
2014-04-15 00:50:20 +04:00
# define THINGM_MAJOR_MK2 '2'
2013-01-22 21:01:21 +04:00
2014-04-15 00:50:19 +04:00
struct thingm_fwinfo {
char major ;
unsigned numrgb ;
unsigned first ;
} ;
2014-06-03 15:29:38 +04:00
static const struct thingm_fwinfo thingm_fwinfo [ ] = {
2014-04-15 00:50:19 +04:00
{
. major = THINGM_MAJOR_MK1 ,
. numrgb = 1 ,
. first = 0 ,
2014-04-15 00:50:20 +04:00
} , {
. major = THINGM_MAJOR_MK2 ,
. numrgb = 2 ,
. first = 1 ,
2014-04-15 00:50:19 +04:00
}
} ;
/* 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 {
2013-01-22 21:01:21 +04:00
struct hid_device * hdev ;
2014-04-15 00:50:19 +04:00
struct {
char major ;
char minor ;
} version ;
const struct thingm_fwinfo * fwinfo ;
struct mutex lock ;
struct thingm_rgb * rgb ;
2013-01-22 21:01:21 +04:00
} ;
2014-04-15 00:50:19 +04:00
static int thingm_send ( struct thingm_device * tdev , u8 buf [ REPORT_SIZE ] )
2013-01-22 21:01:21 +04:00
{
int ret ;
2014-04-15 00:50:19 +04:00
hid_dbg ( tdev - > hdev , " -> %d %c %02hhx %02hhx %02hhx %02hhx %02hhx %02hhx %02hhx \n " ,
2013-01-22 21:01:21 +04:00
buf [ 0 ] , buf [ 1 ] , buf [ 2 ] , buf [ 3 ] , buf [ 4 ] ,
buf [ 5 ] , buf [ 6 ] , buf [ 7 ] , buf [ 8 ] ) ;
2014-04-15 00:50:19 +04:00
ret = hid_hw_raw_request ( tdev - > hdev , buf [ 0 ] , buf , REPORT_SIZE ,
HID_FEATURE_REPORT , HID_REQ_SET_REPORT ) ;
2013-01-22 21:01:21 +04:00
return ret < 0 ? ret : 0 ;
}
2014-04-15 00:50:19 +04:00
static int thingm_recv ( struct thingm_device * tdev , u8 buf [ REPORT_SIZE ] )
2013-01-22 21:01:21 +04:00
{
2014-04-15 00:50:19 +04:00
int ret ;
2013-01-22 21:01:21 +04:00
2014-04-15 00:50:19 +04:00
ret = hid_hw_raw_request ( tdev - > hdev , buf [ 0 ] , buf , REPORT_SIZE ,
HID_FEATURE_REPORT , HID_REQ_GET_REPORT ) ;
if ( ret < 0 )
return ret ;
2013-01-22 21:01:21 +04:00
2014-04-15 00:50:19 +04:00
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 ] ) ;
2013-01-22 21:01:21 +04:00
2014-04-15 00:50:19 +04:00
return 0 ;
2013-01-22 21:01:21 +04:00
}
2014-04-15 00:50:19 +04:00
static int thingm_version ( struct thingm_device * tdev )
2013-01-22 21:01:21 +04:00
{
2014-04-15 00:50:19 +04:00
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 ;
2013-01-22 21:01:21 +04:00
2014-04-15 00:50:19 +04:00
tdev - > version . major = buf [ 3 ] ;
tdev - > version . minor = buf [ 4 ] ;
return 0 ;
2013-01-22 21:01:21 +04:00
}
2014-04-15 00:50:19 +04:00
static int thingm_write_color ( struct thingm_rgb * rgb )
2013-01-22 21:01:21 +04:00
{
2014-04-15 00:50:20 +04:00
u8 buf [ REPORT_SIZE ] = { REPORT_ID , ' c ' , 0 , 0 , 0 , 0 , 0 , rgb - > num , 0 } ;
2014-04-15 00:50:19 +04:00
buf [ 2 ] = rgb - > red . ldev . brightness ;
buf [ 3 ] = rgb - > green . ldev . brightness ;
buf [ 4 ] = rgb - > blue . ldev . brightness ;
2013-01-22 21:01:21 +04:00
2014-04-15 00:50:19 +04:00
return thingm_send ( rgb - > tdev , buf ) ;
2013-01-22 21:01:21 +04:00
}
2014-04-15 00:50:19 +04:00
static void thingm_work ( struct work_struct * work )
2013-01-22 21:01:21 +04:00
{
2014-04-15 00:50:19 +04:00
struct thingm_rgb * rgb = container_of ( work , struct thingm_rgb , work ) ;
2013-01-22 21:01:21 +04:00
2014-04-15 00:50:19 +04:00
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 ) ;
2013-01-22 21:01:21 +04:00
}
2014-04-15 00:50:19 +04:00
static void thingm_led_set ( struct led_classdev * ldev ,
enum led_brightness brightness )
2013-01-22 21:01:21 +04:00
{
2014-04-15 00:50:19 +04:00
struct thingm_led * led = container_of ( ldev , struct thingm_led , ldev ) ;
2013-01-22 21:01:21 +04:00
2014-04-15 00:50:19 +04:00
/* the ledclass has already stored the brightness value */
schedule_work ( & led - > rgb - > work ) ;
}
2013-01-22 21:01:21 +04:00
2014-04-15 00:50:19 +04:00
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 ) ;
2013-01-22 21:01:21 +04:00
2014-04-15 00:50:19 +04:00
return 0 ;
2013-01-22 21:01:21 +04:00
2014-04-15 00:50:19 +04:00
unregister_green :
led_classdev_unregister ( & rgb - > green . ldev ) ;
2013-01-22 21:01:21 +04:00
2014-04-15 00:50:19 +04:00
unregister_red :
led_classdev_unregister ( & rgb - > red . ldev ) ;
2013-01-22 21:01:21 +04:00
2014-04-15 00:50:19 +04:00
return err ;
}
static void thingm_remove_rgb ( struct thingm_rgb * rgb )
{
led_classdev_unregister ( & rgb - > red . ldev ) ;
led_classdev_unregister ( & rgb - > green . ldev ) ;
led_classdev_unregister ( & rgb - > blue . ldev ) ;
2014-09-04 10:56:06 +04:00
flush_work ( & rgb - > work ) ;
2014-04-15 00:50:19 +04:00
}
2013-01-22 21:01:21 +04:00
static int thingm_probe ( struct hid_device * hdev , const struct hid_device_id * id )
{
2014-04-15 00:50:19 +04:00
struct thingm_device * tdev ;
int i , err ;
2013-01-22 21:01:21 +04:00
2014-04-15 00:50:19 +04:00
tdev = devm_kzalloc ( & hdev - > dev , sizeof ( struct thingm_device ) ,
GFP_KERNEL ) ;
if ( ! tdev )
2013-01-22 21:01:21 +04:00
return - ENOMEM ;
2014-04-15 00:50:19 +04:00
tdev - > hdev = hdev ;
hid_set_drvdata ( hdev , tdev ) ;
2013-01-22 21:01:21 +04:00
2014-04-15 00:50:19 +04:00
err = hid_parse ( hdev ) ;
if ( err )
2013-01-22 21:01:21 +04:00
goto error ;
2014-04-15 00:50:19 +04:00
err = hid_hw_start ( hdev , HID_CONNECT_HIDRAW ) ;
if ( err )
2013-01-22 21:01:21 +04:00
goto error ;
2014-04-15 00:50:19 +04:00
mutex_init ( & tdev - > lock ) ;
err = thingm_version ( tdev ) ;
if ( err )
2013-01-22 21:01:21 +04:00
goto stop ;
2014-04-15 00:50:19 +04:00
hid_dbg ( hdev , " firmware version: %c.%c \n " ,
tdev - > version . major , tdev - > version . minor ) ;
2013-01-22 21:01:21 +04:00
2014-04-15 00:50:19 +04:00
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 ) ;
2014-09-02 23:50:43 +04:00
err = - ENODEV ;
2014-04-15 00:50:19 +04:00
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 ;
}
}
2013-01-22 21:01:21 +04:00
2014-04-15 00:50:19 +04:00
return 0 ;
2013-01-22 21:01:21 +04:00
stop :
hid_hw_stop ( hdev ) ;
error :
2014-04-15 00:50:19 +04:00
return err ;
2013-01-22 21:01:21 +04:00
}
static void thingm_remove ( struct hid_device * hdev )
{
2014-04-15 00:50:19 +04:00
struct thingm_device * tdev = hid_get_drvdata ( hdev ) ;
int i ;
2014-09-04 10:56:06 +04:00
hid_hw_stop ( hdev ) ;
2014-04-15 00:50:19 +04:00
for ( i = 0 ; i < tdev - > fwinfo - > numrgb ; + + i )
thingm_remove_rgb ( tdev - > rgb + i ) ;
2013-01-22 21:01:21 +04:00
}
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 " ) ;