2019-05-31 11:09:32 +03:00
// SPDX-License-Identifier: GPL-2.0-only
2016-06-17 22:20:46 +03:00
/*
* 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
*/
# 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 ,
2016-06-21 22:49:29 +03:00
THINGM ,
2016-07-04 22:45:54 +03:00
DELCOM ,
2016-07-04 22:47:52 +03:00
LUXAFOR ,
2016-06-17 22:20:46 +03:00
} ;
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)]
2016-07-04 22:45:54 +03:00
union delcom_packet {
__u8 data [ 8 ] ;
struct {
__u8 major_cmd ;
__u8 minor_cmd ;
__u8 data_lsb ;
__u8 data_msb ;
} tx ;
struct {
__u8 cmd ;
} rx ;
struct {
__le16 family_code ;
__le16 security_code ;
__u8 fw_version ;
} fw ;
} ;
# define DELCOM_GREEN_LED 0
# define DELCOM_RED_LED 1
# define DELCOM_BLUE_LED 2
2016-06-17 22:20:46 +03:00
struct hidled_device ;
2016-06-21 22:48:26 +03:00
struct hidled_rgb ;
2016-06-17 22:20:46 +03:00
struct hidled_config {
enum hidled_type type ;
const char * name ;
const char * short_name ;
enum led_brightness max_brightness ;
2016-06-21 22:48:26 +03:00
int num_leds ;
2016-06-17 22:20:46 +03:00
size_t report_size ;
enum hidled_report_type report_type ;
int ( * init ) ( struct hidled_device * ldev ) ;
int ( * write ) ( struct led_classdev * cdev , enum led_brightness br ) ;
} ;
struct hidled_led {
struct led_classdev cdev ;
2016-06-21 22:48:26 +03:00
struct hidled_rgb * rgb ;
2016-06-17 22:20:46 +03:00
char name [ 32 ] ;
} ;
2016-06-21 22:48:26 +03:00
struct hidled_rgb {
struct hidled_device * ldev ;
2016-06-17 22:20:46 +03:00
struct hidled_led red ;
struct hidled_led green ;
struct hidled_led blue ;
2016-06-21 22:48:26 +03:00
u8 num ;
} ;
struct hidled_device {
const struct hidled_config * config ;
2016-06-17 22:20:46 +03:00
struct hid_device * hdev ;
2016-06-21 22:48:26 +03:00
struct hidled_rgb * rgb ;
2016-10-03 22:21:42 +03:00
u8 * buf ;
2016-06-17 22:20:46 +03:00
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 ;
mutex_lock ( & ldev - > lock ) ;
2016-10-03 22:21:42 +03:00
/*
* buffer provided to hid_hw_raw_request must not be on the stack
* and must not be part of a data structure
*/
memcpy ( ldev - > buf , buf , ldev - > config - > report_size ) ;
2016-06-17 22:20:46 +03:00
if ( ldev - > config - > report_type = = RAW_REQUEST )
2016-10-03 22:21:42 +03:00
ret = hid_hw_raw_request ( ldev - > hdev , buf [ 0 ] , ldev - > buf ,
2016-06-17 22:20:46 +03:00
ldev - > config - > report_size ,
HID_FEATURE_REPORT ,
HID_REQ_SET_REPORT ) ;
else if ( ldev - > config - > report_type = = OUTPUT_REPORT )
2016-10-03 22:21:42 +03:00
ret = hid_hw_output_report ( ldev - > hdev , ldev - > buf ,
2016-06-17 22:20:46 +03:00
ldev - > config - > report_size ) ;
else
ret = - EINVAL ;
mutex_unlock ( & ldev - > lock ) ;
if ( ret < 0 )
return ret ;
return ret = = ldev - > config - > report_size ? 0 : - EMSGSIZE ;
}
2016-06-21 22:48:46 +03:00
/* reading data is supported for report type RAW_REQUEST only */
static int hidled_recv ( struct hidled_device * ldev , __u8 * buf )
{
int ret ;
if ( ldev - > config - > report_type ! = RAW_REQUEST )
return - EINVAL ;
mutex_lock ( & ldev - > lock ) ;
2016-10-03 22:21:42 +03:00
memcpy ( ldev - > buf , buf , ldev - > config - > report_size ) ;
ret = hid_hw_raw_request ( ldev - > hdev , buf [ 0 ] , ldev - > buf ,
2016-06-21 22:48:46 +03:00
ldev - > config - > report_size ,
HID_FEATURE_REPORT ,
HID_REQ_SET_REPORT ) ;
if ( ret < 0 )
goto err ;
2016-10-03 22:21:42 +03:00
ret = hid_hw_raw_request ( ldev - > hdev , buf [ 0 ] , ldev - > buf ,
2016-06-21 22:48:46 +03:00
ldev - > config - > report_size ,
HID_FEATURE_REPORT ,
HID_REQ_GET_REPORT ) ;
2016-10-03 22:21:42 +03:00
memcpy ( buf , ldev - > buf , ldev - > config - > report_size ) ;
2016-06-21 22:48:46 +03:00
err :
mutex_unlock ( & ldev - > lock ) ;
return ret < 0 ? ret : 0 ;
}
2016-06-21 22:48:26 +03:00
static u8 riso_kagaku_index ( struct hidled_rgb * rgb )
2016-06-17 22:20:46 +03:00
{
enum led_brightness r , g , b ;
2016-06-21 22:48:26 +03:00
r = rgb - > red . cdev . brightness ;
g = rgb - > green . cdev . brightness ;
b = rgb - > blue . cdev . brightness ;
2016-06-17 22:20:46 +03:00
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 ) ;
2016-06-21 22:48:26 +03:00
struct hidled_rgb * rgb = led - > rgb ;
2016-06-17 22:20:46 +03:00
__u8 buf [ MAX_REPORT_SIZE ] = { } ;
2016-06-21 22:48:26 +03:00
buf [ 1 ] = riso_kagaku_index ( rgb ) ;
2016-06-17 22:20:46 +03:00
2016-06-21 22:48:26 +03:00
return hidled_send ( rgb - > ldev , buf ) ;
2016-06-17 22:20:46 +03:00
}
static int dream_cheeky_write ( struct led_classdev * cdev , enum led_brightness br )
{
struct hidled_led * led = to_hidled_led ( cdev ) ;
2016-06-21 22:48:26 +03:00
struct hidled_rgb * rgb = led - > rgb ;
2016-06-17 22:20:46 +03:00
__u8 buf [ MAX_REPORT_SIZE ] = { } ;
2016-06-21 22:48:26 +03:00
buf [ 1 ] = rgb - > red . cdev . brightness ;
buf [ 2 ] = rgb - > green . cdev . brightness ;
buf [ 3 ] = rgb - > blue . cdev . brightness ;
2016-06-17 22:20:46 +03:00
buf [ 7 ] = 0x1a ;
buf [ 8 ] = 0x05 ;
2016-06-21 22:48:26 +03:00
return hidled_send ( rgb - > ldev , buf ) ;
2016-06-17 22:20:46 +03:00
}
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 ) ;
}
2016-06-21 22:49:29 +03:00
static int _thingm_write ( struct led_classdev * cdev , enum led_brightness br ,
u8 offset )
{
struct hidled_led * led = to_hidled_led ( cdev ) ;
2016-07-03 18:33:04 +03:00
__u8 buf [ MAX_REPORT_SIZE ] = { 1 , ' c ' } ;
2016-06-21 22:49:29 +03:00
buf [ 2 ] = led - > rgb - > red . cdev . brightness ;
buf [ 3 ] = led - > rgb - > green . cdev . brightness ;
buf [ 4 ] = led - > rgb - > blue . cdev . brightness ;
buf [ 7 ] = led - > rgb - > num + offset ;
return hidled_send ( led - > rgb - > ldev , buf ) ;
}
static int thingm_write_v1 ( struct led_classdev * cdev , enum led_brightness br )
{
return _thingm_write ( cdev , br , 0 ) ;
}
static int thingm_write ( struct led_classdev * cdev , enum led_brightness br )
{
return _thingm_write ( cdev , br , 1 ) ;
}
static const struct hidled_config hidled_config_thingm_v1 = {
. name = " ThingM blink(1) v1 " ,
. short_name = " thingm " ,
. max_brightness = 255 ,
. num_leds = 1 ,
. report_size = 9 ,
. report_type = RAW_REQUEST ,
. write = thingm_write_v1 ,
} ;
static int thingm_init ( struct hidled_device * ldev )
{
2016-07-03 18:33:04 +03:00
__u8 buf [ MAX_REPORT_SIZE ] = { 1 , ' v ' } ;
2016-06-21 22:49:29 +03:00
int ret ;
ret = hidled_recv ( ldev , buf ) ;
if ( ret )
return ret ;
/* Check for firmware major version 1 */
if ( buf [ 3 ] = = ' 1 ' )
ldev - > config = & hidled_config_thingm_v1 ;
return 0 ;
}
2016-07-04 22:45:54 +03:00
static inline int delcom_get_lednum ( const struct hidled_led * led )
{
if ( led = = & led - > rgb - > red )
return DELCOM_RED_LED ;
else if ( led = = & led - > rgb - > green )
return DELCOM_GREEN_LED ;
else
return DELCOM_BLUE_LED ;
}
static int delcom_enable_led ( struct hidled_led * led )
{
union delcom_packet dp = { . tx . major_cmd = 101 , . tx . minor_cmd = 12 } ;
dp . tx . data_lsb = 1 < < delcom_get_lednum ( led ) ;
dp . tx . data_msb = 0 ;
return hidled_send ( led - > rgb - > ldev , dp . data ) ;
}
static int delcom_set_pwm ( struct hidled_led * led )
{
union delcom_packet dp = { . tx . major_cmd = 101 , . tx . minor_cmd = 34 } ;
dp . tx . data_lsb = delcom_get_lednum ( led ) ;
dp . tx . data_msb = led - > cdev . brightness ;
return hidled_send ( led - > rgb - > ldev , dp . data ) ;
}
static int delcom_write ( struct led_classdev * cdev , enum led_brightness br )
{
struct hidled_led * led = to_hidled_led ( cdev ) ;
int ret ;
/*
* enable LED
* We can ' t do this in the init function already because the device
* is internally reset later .
*/
ret = delcom_enable_led ( led ) ;
if ( ret )
return ret ;
return delcom_set_pwm ( led ) ;
}
static int delcom_init ( struct hidled_device * ldev )
{
union delcom_packet dp = { . rx . cmd = 104 } ;
int ret ;
ret = hidled_recv ( ldev , dp . data ) ;
if ( ret )
return ret ;
/*
* Several Delcom devices share the same USB VID / PID
* Check for family id 2 for Visual Signal Indicator
*/
2016-07-08 07:58:37 +03:00
return le16_to_cpu ( dp . fw . family_code ) = = 2 ? 0 : - ENODEV ;
2016-07-04 22:45:54 +03:00
}
2016-07-04 22:47:52 +03:00
static int luxafor_write ( struct led_classdev * cdev , enum led_brightness br )
{
struct hidled_led * led = to_hidled_led ( cdev ) ;
__u8 buf [ MAX_REPORT_SIZE ] = { [ 1 ] = 1 } ;
buf [ 2 ] = led - > rgb - > num + 1 ;
buf [ 3 ] = led - > rgb - > red . cdev . brightness ;
buf [ 4 ] = led - > rgb - > green . cdev . brightness ;
buf [ 5 ] = led - > rgb - > blue . cdev . brightness ;
return hidled_send ( led - > rgb - > ldev , buf ) ;
}
2016-06-17 22:20:46 +03:00
static const struct hidled_config hidled_configs [ ] = {
{
. type = RISO_KAGAKU ,
. name = " Riso Kagaku Webmail Notifier " ,
. short_name = " riso_kagaku " ,
. max_brightness = 1 ,
2016-06-21 22:48:26 +03:00
. num_leds = 1 ,
2016-06-17 22:20:46 +03:00
. report_size = 6 ,
. report_type = OUTPUT_REPORT ,
. write = riso_kagaku_write ,
} ,
{
. type = DREAM_CHEEKY ,
. name = " Dream Cheeky Webmail Notifier " ,
. short_name = " dream_cheeky " ,
. max_brightness = 31 ,
2016-06-21 22:48:26 +03:00
. num_leds = 1 ,
2016-06-17 22:20:46 +03:00
. report_size = 9 ,
. report_type = RAW_REQUEST ,
. init = dream_cheeky_init ,
. write = dream_cheeky_write ,
} ,
2016-06-21 22:49:29 +03:00
{
. type = THINGM ,
. name = " ThingM blink(1) " ,
. short_name = " thingm " ,
. max_brightness = 255 ,
. num_leds = 2 ,
. report_size = 9 ,
. report_type = RAW_REQUEST ,
. init = thingm_init ,
. write = thingm_write ,
} ,
2016-07-04 22:45:54 +03:00
{
. type = DELCOM ,
. name = " Delcom Visual Signal Indicator G2 " ,
. short_name = " delcom " ,
. max_brightness = 100 ,
. num_leds = 1 ,
. report_size = 8 ,
. report_type = RAW_REQUEST ,
. init = delcom_init ,
. write = delcom_write ,
} ,
2016-07-04 22:47:52 +03:00
{
. type = LUXAFOR ,
. name = " Greynut Luxafor " ,
. short_name = " luxafor " ,
. max_brightness = 255 ,
. num_leds = 6 ,
. report_size = 9 ,
. report_type = OUTPUT_REPORT ,
. write = luxafor_write ,
} ,
2016-06-17 22:20:46 +03:00
} ;
static int hidled_init_led ( struct hidled_led * led , const char * color_name ,
2016-06-21 22:48:26 +03:00
struct hidled_rgb * rgb , unsigned int minor )
2016-06-17 22:20:46 +03:00
{
2016-06-21 22:48:26 +03:00
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 ) ;
2016-06-17 22:20:46 +03:00
led - > cdev . name = led - > name ;
2016-06-21 22:48:26 +03:00
led - > cdev . max_brightness = config - > max_brightness ;
led - > cdev . brightness_set_blocking = config - > write ;
2016-06-17 22:20:46 +03:00
led - > cdev . flags = LED_HW_PLUGGABLE ;
2016-06-21 22:48:26 +03:00
led - > rgb = rgb ;
2016-06-17 22:20:46 +03:00
2016-06-21 22:48:26 +03:00
return devm_led_classdev_register ( & rgb - > ldev - > hdev - > dev , & led - > cdev ) ;
2016-06-17 22:20:46 +03:00
}
2016-06-21 22:48:26 +03:00
static int hidled_init_rgb ( struct hidled_rgb * rgb , unsigned int minor )
2016-06-17 22:20:46 +03:00
{
int ret ;
/* Register the red diode */
2016-06-21 22:48:26 +03:00
ret = hidled_init_led ( & rgb - > red , " red " , rgb , minor ) ;
2016-06-17 22:20:46 +03:00
if ( ret )
return ret ;
/* Register the green diode */
2016-06-21 22:48:26 +03:00
ret = hidled_init_led ( & rgb - > green , " green " , rgb , minor ) ;
2016-06-17 22:20:46 +03:00
if ( ret )
return ret ;
/* Register the blue diode */
2016-06-21 22:48:26 +03:00
return hidled_init_led ( & rgb - > blue , " blue " , rgb , minor ) ;
2016-06-17 22:20:46 +03:00
}
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 ;
2016-10-03 22:21:42 +03:00
ldev - > buf = devm_kmalloc ( & hdev - > dev , MAX_REPORT_SIZE , GFP_KERNEL ) ;
if ( ! ldev - > buf )
return - ENOMEM ;
2016-06-17 22:20:46 +03:00
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 ;
}
2016-06-21 22:48:26 +03:00
ldev - > rgb = devm_kcalloc ( & hdev - > dev , ldev - > config - > num_leds ,
sizeof ( struct hidled_rgb ) , GFP_KERNEL ) ;
if ( ! ldev - > rgb )
return - ENOMEM ;
2016-06-17 22:20:46 +03:00
ret = hid_hw_start ( hdev , HID_CONNECT_HIDRAW ) ;
if ( ret )
return ret ;
minor = ( ( struct hidraw * ) hdev - > hidraw ) - > minor ;
2016-06-21 22:48:26 +03:00
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 ;
}
2016-06-17 22:20:46 +03:00
}
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 } ,
2016-06-21 22:49:29 +03:00
{ HID_USB_DEVICE ( USB_VENDOR_ID_THINGM ,
USB_DEVICE_ID_BLINK1 ) , . driver_data = THINGM } ,
2016-07-04 22:45:54 +03:00
{ HID_USB_DEVICE ( USB_VENDOR_ID_DELCOM ,
USB_DEVICE_ID_DELCOM_VISUAL_IND ) , . driver_data = DELCOM } ,
2016-07-04 22:47:52 +03:00
{ HID_USB_DEVICE ( USB_VENDOR_ID_MICROCHIP ,
USB_DEVICE_ID_LUXAFOR ) , . driver_data = LUXAFOR } ,
2016-06-17 22:20:46 +03:00
{ }
} ;
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 " ) ;