/*
* USB LED Driver for NVIDIA Shield
*
* Copyright (c) 2013-2014, NVIDIA Corporation. All Rights Reserved.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
#include
#include
#include
#include
#include
#include
#include
#define DRIVER_AUTHOR "Jun Yan, juyan@nvidia.com"
#define DRIVER_DESC "NVIDIA Shield USB LED Driver"
#define INTF_CLASS 0xFF
#define INTF_SUBCLASS 0x01
#define INTF_PROTOCOL 0x01
#define LED_STATE_NORMAL "normal"
#define LED_STATE_BLINK "blink"
#define LED_STATE_BREATHE "breathe"
#define LED_STATE_OFF "off"
#define LED_STATE_UNKNOW "unknow"
#define LED_STATE_MAXCHAR 8
#define LED_NUM 2
#define UC_COMM_ON 0x10
#define UC_COMM_BLINK 0x11
#define UC_COMM_BREATHE 0x12
#define UC_COMM_OFF 0x13
#define UC_COMM_PWR_LED 0x00
#define UC_COMM_TCH_LED 0x80
#define VID 0x0955
#define PID 0x7205
#define LED_NVBUTTON 0
#define LED_TOUCH 1
struct nvshield_led *g_dev;
static const struct usb_device_id nvshield_table[] = {
{ USB_DEVICE_AND_INTERFACE_INFO(VID, PID,
INTF_CLASS, INTF_SUBCLASS, INTF_PROTOCOL) },
{}
};
MODULE_DEVICE_TABLE(usb, nvshield_table);
enum led_state {
LED_NORMAL,
LED_BLINK,
LED_BREATHE,
LED_OFF,
};
struct nvshield_led {
struct usb_device *udev;
unsigned char brightness[LED_NUM];
enum led_state state[LED_NUM];
};
static unsigned char brightness_table[256] = {
0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2,
3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 6, 7, 7, 7, 8, 8, 9,
9, 9, 10, 10, 11, 11, 12, 12, 12, 13, 13, 14, 14, 15, 16, 16,
17, 17, 18, 18, 19, 19, 20, 21, 21, 22, 23, 23, 24, 24, 25, 26, 27,
27, 28, 29, 29, 30, 31, 32, 32, 33, 34, 35, 35, 36, 37, 38, 39, 39,
40, 41, 42, 43, 44, 45, 46, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55,
56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72,
73, 75, 76, 77, 78, 79, 80, 81, 82, 84, 85, 86, 87, 88, 90, 91, 92,
93, 94, 96, 97, 98, 99, 100, 102, 103, 104, 106, 107, 108, 109, 111,
112, 113, 115, 116, 117, 118, 120, 121, 122, 124, 125, 127, 128, 129,
131, 132, 133, 135, 136, 138, 139, 140, 142, 143, 145, 146, 147, 149,
150, 152, 153, 155, 156, 157, 159, 160, 162, 163, 165, 166, 168, 169,
171, 172, 174, 175, 177, 178, 180, 181, 183, 184, 186, 187, 189, 190,
192, 193, 195, 196, 198, 199, 201, 202, 204, 205, 207, 208, 210, 212,
213, 215, 216, 218, 219, 221, 222, 224, 226, 227, 229, 230, 232, 233,
235, 236, 238, 240, 241, 243, 244, 246, 247, 249, 251, 252, 254, 255
};
static void send_command(struct nvshield_led *led, int led_id)
{
int retval = 0;
unsigned char state;
unsigned char brightness;
if (led->state[led_id] == LED_BREATHE)
state = UC_COMM_BREATHE;
else if (led->state[led_id] == LED_BLINK)
state = UC_COMM_BLINK;
else if (led->state[led_id] == LED_NORMAL)
state = UC_COMM_ON;
else if (led->state[led_id] == LED_OFF)
state = UC_COMM_OFF;
else
return;
brightness = led->brightness[led_id];
if (led_id == LED_TOUCH)
state |= UC_COMM_TCH_LED;
else
brightness = brightness_table[(unsigned int)brightness];
retval = usb_control_msg(led->udev,
usb_sndctrlpipe(led->udev, 0),
0x00,
0x42,
cpu_to_le16(brightness),
cpu_to_le16(state),
NULL,
0,
2000);
}
static ssize_t show_brightness(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct usb_interface *intf = to_usb_interface(dev);
struct nvshield_led *led = usb_get_intfdata(intf);
return sprintf(buf, "%d\n", led->brightness[LED_NVBUTTON]);
}
static ssize_t show_brightness2(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct usb_interface *intf = to_usb_interface(dev);
struct nvshield_led *led = usb_get_intfdata(intf);
return sprintf(buf, "%d\n", led->brightness[LED_TOUCH]);
}
static ssize_t set_brightness(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct usb_interface *intf = to_usb_interface(dev);
struct nvshield_led *led = usb_get_intfdata(intf);
unsigned long brightness_val;
if (!kstrtoul(buf, 10, &brightness_val)) {
led->brightness[LED_NVBUTTON] = (unsigned char)brightness_val;
send_command(led, LED_NVBUTTON);
}
return count;
}
static ssize_t set_brightness2(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct usb_interface *intf = to_usb_interface(dev);
struct nvshield_led *led = usb_get_intfdata(intf);
unsigned long brightness_val;
if (!kstrtoul(buf, 10, &brightness_val)) {
led->brightness[LED_TOUCH] = (unsigned char)brightness_val;
send_command(led, LED_TOUCH);
}
return count;
}
static ssize_t show_ledstate(struct device *dev, struct device_attribute *attr,
char *buf)
{
struct usb_interface *intf = to_usb_interface(dev);
struct nvshield_led *led = usb_get_intfdata(intf);
switch (led->state[LED_NVBUTTON]) {
case LED_NORMAL:
return sprintf(buf, "%s\n", LED_STATE_NORMAL);
case LED_BLINK:
return sprintf(buf, "%s\n", LED_STATE_BLINK);
case LED_BREATHE:
return sprintf(buf, "%s\n", LED_STATE_BREATHE);
case LED_OFF:
return sprintf(buf, "%s\n", LED_STATE_OFF);
default:
return sprintf(buf, LED_STATE_UNKNOW);
}
}
static ssize_t set_ledstate(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{
struct usb_interface *intf = to_usb_interface(dev);
struct nvshield_led *led = usb_get_intfdata(intf);
char ledstate_name[LED_STATE_MAXCHAR];
size_t len;
ledstate_name[sizeof(ledstate_name) - 1] = '\0';
strncpy(ledstate_name, buf, sizeof(ledstate_name) - 1);
len = strlen(ledstate_name);
if (len && ledstate_name[len - 1] == '\n')
ledstate_name[len - 1] = '\0';
if (!strcmp(ledstate_name, LED_STATE_NORMAL))
led->state[LED_NVBUTTON] = LED_NORMAL;
else if (!strcmp(ledstate_name, LED_STATE_BLINK))
led->state[LED_NVBUTTON] = LED_BLINK;
else if (!strcmp(ledstate_name, LED_STATE_BREATHE))
led->state[LED_NVBUTTON] = LED_BREATHE;
else if (!strcmp(ledstate_name, LED_STATE_OFF))
led->state[LED_NVBUTTON] = LED_OFF;
else
return count;
send_command(led, LED_NVBUTTON);
return count;
}
static DEVICE_ATTR(brightness, S_IRUGO | S_IWUSR,
show_brightness, set_brightness);
static DEVICE_ATTR(state, S_IRUGO | S_IWUSR,
show_ledstate, set_ledstate);
static DEVICE_ATTR(brightness2, S_IRUGO | S_IWUSR,
show_brightness2, set_brightness2);
static int nvshieldled_reboot_callback(struct notifier_block *nb,
unsigned long code,
void *unused) {
if (!g_dev)
return NOTIFY_DONE;
g_dev->state[LED_NVBUTTON] = LED_NORMAL;
send_command(g_dev, LED_NVBUTTON);
return NOTIFY_DONE;
}
static struct notifier_block nvshieldled_notifier = {
.notifier_call = nvshieldled_reboot_callback,
};
static int nvshieldled_probe(struct usb_interface *interface,
const struct usb_device_id *id)
{
struct usb_device *udev = interface_to_usbdev(interface);
struct nvshield_led *dev = NULL;
int retval = -ENOMEM;
dev = kzalloc(sizeof(struct nvshield_led), GFP_KERNEL);
if (dev == NULL) {
dev_err(&interface->dev, "out of memory\n");
goto error_mem;
}
dev->udev = usb_get_dev(udev);
dev->state[LED_NVBUTTON] = LED_NORMAL;
dev->brightness[LED_NVBUTTON] = 255;
dev->state[LED_TOUCH] = LED_NORMAL;
dev->brightness[LED_TOUCH] = 255;
usb_set_intfdata(interface, dev);
g_dev = dev;
retval = device_create_file(&interface->dev, &dev_attr_brightness);
if (retval)
goto error;
retval = device_create_file(&interface->dev, &dev_attr_state);
if (retval)
goto error2;
retval = device_create_file(&interface->dev, &dev_attr_brightness2);
if (retval)
goto error3;
dev_info(&interface->dev, "Nvidia Shield LED attached\n");
retval = register_reboot_notifier(&nvshieldled_notifier);
if (retval)
goto error4;
return 0;
error4:
device_remove_file(&interface->dev, &dev_attr_brightness2);
error3:
device_remove_file(&interface->dev, &dev_attr_brightness);
error2:
device_remove_file(&interface->dev, &dev_attr_state);
error:
usb_set_intfdata(interface, NULL);
usb_put_dev(dev->udev);
kfree(dev);
error_mem:
return retval;
}
static void nvshieldled_disconnect(struct usb_interface *interface)
{
struct nvshield_led *dev;
dev = usb_get_intfdata(interface);
unregister_reboot_notifier(&nvshieldled_notifier);
device_remove_file(&interface->dev, &dev_attr_brightness);
device_remove_file(&interface->dev, &dev_attr_state);
device_remove_file(&interface->dev, &dev_attr_brightness2);
usb_set_intfdata(interface, NULL);
usb_put_dev(dev->udev);
kfree(dev);
dev_info(&interface->dev, "Nvidia Shield LED disconnected\n");
}
static int nvshieldled_suspend(struct usb_interface *interface,
pm_message_t notused)
{
return 0;
}
static int nvshieldled_resume(struct usb_interface *interface)
{
return 0;
}
static struct usb_driver shieldled_driver = {
.name = "nvshieldled",
.probe = nvshieldled_probe,
.disconnect = nvshieldled_disconnect,
.suspend = nvshieldled_suspend,
.resume = nvshieldled_resume,
.id_table = nvshield_table,
};
module_usb_driver(shieldled_driver);
MODULE_AUTHOR(DRIVER_AUTHOR);
MODULE_DESCRIPTION(DRIVER_DESC);
MODULE_LICENSE("GPL");