18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * MSI GT683R led driver 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Copyright (c) 2014 Janne Kanniainen <janne.kanniainen@gmail.com> 68c2ecf20Sopenharmony_ci */ 78c2ecf20Sopenharmony_ci 88c2ecf20Sopenharmony_ci#include <linux/device.h> 98c2ecf20Sopenharmony_ci#include <linux/hid.h> 108c2ecf20Sopenharmony_ci#include <linux/kernel.h> 118c2ecf20Sopenharmony_ci#include <linux/leds.h> 128c2ecf20Sopenharmony_ci#include <linux/module.h> 138c2ecf20Sopenharmony_ci 148c2ecf20Sopenharmony_ci#include "hid-ids.h" 158c2ecf20Sopenharmony_ci 168c2ecf20Sopenharmony_ci#define GT683R_BUFFER_SIZE 8 178c2ecf20Sopenharmony_ci 188c2ecf20Sopenharmony_ci/* 198c2ecf20Sopenharmony_ci * GT683R_LED_OFF: all LEDs are off 208c2ecf20Sopenharmony_ci * GT683R_LED_AUDIO: LEDs brightness depends on sound level 218c2ecf20Sopenharmony_ci * GT683R_LED_BREATHING: LEDs brightness varies at human breathing rate 228c2ecf20Sopenharmony_ci * GT683R_LED_NORMAL: LEDs are fully on when enabled 238c2ecf20Sopenharmony_ci */ 248c2ecf20Sopenharmony_cienum gt683r_led_mode { 258c2ecf20Sopenharmony_ci GT683R_LED_OFF = 0, 268c2ecf20Sopenharmony_ci GT683R_LED_AUDIO = 2, 278c2ecf20Sopenharmony_ci GT683R_LED_BREATHING = 3, 288c2ecf20Sopenharmony_ci GT683R_LED_NORMAL = 5 298c2ecf20Sopenharmony_ci}; 308c2ecf20Sopenharmony_ci 318c2ecf20Sopenharmony_cienum gt683r_panels { 328c2ecf20Sopenharmony_ci GT683R_LED_BACK = 0, 338c2ecf20Sopenharmony_ci GT683R_LED_SIDE = 1, 348c2ecf20Sopenharmony_ci GT683R_LED_FRONT = 2, 358c2ecf20Sopenharmony_ci GT683R_LED_COUNT, 368c2ecf20Sopenharmony_ci}; 378c2ecf20Sopenharmony_ci 388c2ecf20Sopenharmony_cistatic const char * const gt683r_panel_names[] = { 398c2ecf20Sopenharmony_ci "back", 408c2ecf20Sopenharmony_ci "side", 418c2ecf20Sopenharmony_ci "front", 428c2ecf20Sopenharmony_ci}; 438c2ecf20Sopenharmony_ci 448c2ecf20Sopenharmony_cistruct gt683r_led { 458c2ecf20Sopenharmony_ci struct hid_device *hdev; 468c2ecf20Sopenharmony_ci struct led_classdev led_devs[GT683R_LED_COUNT]; 478c2ecf20Sopenharmony_ci struct mutex lock; 488c2ecf20Sopenharmony_ci struct work_struct work; 498c2ecf20Sopenharmony_ci enum led_brightness brightnesses[GT683R_LED_COUNT]; 508c2ecf20Sopenharmony_ci enum gt683r_led_mode mode; 518c2ecf20Sopenharmony_ci}; 528c2ecf20Sopenharmony_ci 538c2ecf20Sopenharmony_cistatic const struct hid_device_id gt683r_led_id[] = { 548c2ecf20Sopenharmony_ci { HID_USB_DEVICE(USB_VENDOR_ID_MSI, USB_DEVICE_ID_MSI_GT683R_LED_PANEL) }, 558c2ecf20Sopenharmony_ci { } 568c2ecf20Sopenharmony_ci}; 578c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(hid, gt683r_led_id); 588c2ecf20Sopenharmony_ci 598c2ecf20Sopenharmony_cistatic void gt683r_brightness_set(struct led_classdev *led_cdev, 608c2ecf20Sopenharmony_ci enum led_brightness brightness) 618c2ecf20Sopenharmony_ci{ 628c2ecf20Sopenharmony_ci int i; 638c2ecf20Sopenharmony_ci struct device *dev = led_cdev->dev->parent; 648c2ecf20Sopenharmony_ci struct hid_device *hdev = to_hid_device(dev); 658c2ecf20Sopenharmony_ci struct gt683r_led *led = hid_get_drvdata(hdev); 668c2ecf20Sopenharmony_ci 678c2ecf20Sopenharmony_ci for (i = 0; i < GT683R_LED_COUNT; i++) { 688c2ecf20Sopenharmony_ci if (led_cdev == &led->led_devs[i]) 698c2ecf20Sopenharmony_ci break; 708c2ecf20Sopenharmony_ci } 718c2ecf20Sopenharmony_ci 728c2ecf20Sopenharmony_ci if (i < GT683R_LED_COUNT) { 738c2ecf20Sopenharmony_ci led->brightnesses[i] = brightness; 748c2ecf20Sopenharmony_ci schedule_work(&led->work); 758c2ecf20Sopenharmony_ci } 768c2ecf20Sopenharmony_ci} 778c2ecf20Sopenharmony_ci 788c2ecf20Sopenharmony_cistatic ssize_t mode_show(struct device *dev, 798c2ecf20Sopenharmony_ci struct device_attribute *attr, 808c2ecf20Sopenharmony_ci char *buf) 818c2ecf20Sopenharmony_ci{ 828c2ecf20Sopenharmony_ci u8 sysfs_mode; 838c2ecf20Sopenharmony_ci struct hid_device *hdev = to_hid_device(dev->parent); 848c2ecf20Sopenharmony_ci struct gt683r_led *led = hid_get_drvdata(hdev); 858c2ecf20Sopenharmony_ci 868c2ecf20Sopenharmony_ci if (led->mode == GT683R_LED_NORMAL) 878c2ecf20Sopenharmony_ci sysfs_mode = 0; 888c2ecf20Sopenharmony_ci else if (led->mode == GT683R_LED_AUDIO) 898c2ecf20Sopenharmony_ci sysfs_mode = 1; 908c2ecf20Sopenharmony_ci else 918c2ecf20Sopenharmony_ci sysfs_mode = 2; 928c2ecf20Sopenharmony_ci 938c2ecf20Sopenharmony_ci return scnprintf(buf, PAGE_SIZE, "%u\n", sysfs_mode); 948c2ecf20Sopenharmony_ci} 958c2ecf20Sopenharmony_ci 968c2ecf20Sopenharmony_cistatic ssize_t mode_store(struct device *dev, 978c2ecf20Sopenharmony_ci struct device_attribute *attr, 988c2ecf20Sopenharmony_ci const char *buf, size_t count) 998c2ecf20Sopenharmony_ci{ 1008c2ecf20Sopenharmony_ci u8 sysfs_mode; 1018c2ecf20Sopenharmony_ci struct hid_device *hdev = to_hid_device(dev->parent); 1028c2ecf20Sopenharmony_ci struct gt683r_led *led = hid_get_drvdata(hdev); 1038c2ecf20Sopenharmony_ci 1048c2ecf20Sopenharmony_ci 1058c2ecf20Sopenharmony_ci if (kstrtou8(buf, 10, &sysfs_mode) || sysfs_mode > 2) 1068c2ecf20Sopenharmony_ci return -EINVAL; 1078c2ecf20Sopenharmony_ci 1088c2ecf20Sopenharmony_ci mutex_lock(&led->lock); 1098c2ecf20Sopenharmony_ci 1108c2ecf20Sopenharmony_ci if (sysfs_mode == 0) 1118c2ecf20Sopenharmony_ci led->mode = GT683R_LED_NORMAL; 1128c2ecf20Sopenharmony_ci else if (sysfs_mode == 1) 1138c2ecf20Sopenharmony_ci led->mode = GT683R_LED_AUDIO; 1148c2ecf20Sopenharmony_ci else 1158c2ecf20Sopenharmony_ci led->mode = GT683R_LED_BREATHING; 1168c2ecf20Sopenharmony_ci 1178c2ecf20Sopenharmony_ci mutex_unlock(&led->lock); 1188c2ecf20Sopenharmony_ci schedule_work(&led->work); 1198c2ecf20Sopenharmony_ci 1208c2ecf20Sopenharmony_ci return count; 1218c2ecf20Sopenharmony_ci} 1228c2ecf20Sopenharmony_ci 1238c2ecf20Sopenharmony_cistatic int gt683r_led_snd_msg(struct gt683r_led *led, u8 *msg) 1248c2ecf20Sopenharmony_ci{ 1258c2ecf20Sopenharmony_ci int ret; 1268c2ecf20Sopenharmony_ci 1278c2ecf20Sopenharmony_ci ret = hid_hw_raw_request(led->hdev, msg[0], msg, GT683R_BUFFER_SIZE, 1288c2ecf20Sopenharmony_ci HID_FEATURE_REPORT, HID_REQ_SET_REPORT); 1298c2ecf20Sopenharmony_ci if (ret != GT683R_BUFFER_SIZE) { 1308c2ecf20Sopenharmony_ci hid_err(led->hdev, 1318c2ecf20Sopenharmony_ci "failed to send set report request: %i\n", ret); 1328c2ecf20Sopenharmony_ci if (ret < 0) 1338c2ecf20Sopenharmony_ci return ret; 1348c2ecf20Sopenharmony_ci return -EIO; 1358c2ecf20Sopenharmony_ci } 1368c2ecf20Sopenharmony_ci 1378c2ecf20Sopenharmony_ci return 0; 1388c2ecf20Sopenharmony_ci} 1398c2ecf20Sopenharmony_ci 1408c2ecf20Sopenharmony_cistatic int gt683r_leds_set(struct gt683r_led *led, u8 leds) 1418c2ecf20Sopenharmony_ci{ 1428c2ecf20Sopenharmony_ci int ret; 1438c2ecf20Sopenharmony_ci u8 *buffer; 1448c2ecf20Sopenharmony_ci 1458c2ecf20Sopenharmony_ci buffer = kzalloc(GT683R_BUFFER_SIZE, GFP_KERNEL); 1468c2ecf20Sopenharmony_ci if (!buffer) 1478c2ecf20Sopenharmony_ci return -ENOMEM; 1488c2ecf20Sopenharmony_ci 1498c2ecf20Sopenharmony_ci buffer[0] = 0x01; 1508c2ecf20Sopenharmony_ci buffer[1] = 0x02; 1518c2ecf20Sopenharmony_ci buffer[2] = 0x30; 1528c2ecf20Sopenharmony_ci buffer[3] = leds; 1538c2ecf20Sopenharmony_ci ret = gt683r_led_snd_msg(led, buffer); 1548c2ecf20Sopenharmony_ci 1558c2ecf20Sopenharmony_ci kfree(buffer); 1568c2ecf20Sopenharmony_ci return ret; 1578c2ecf20Sopenharmony_ci} 1588c2ecf20Sopenharmony_ci 1598c2ecf20Sopenharmony_cistatic int gt683r_mode_set(struct gt683r_led *led, u8 mode) 1608c2ecf20Sopenharmony_ci{ 1618c2ecf20Sopenharmony_ci int ret; 1628c2ecf20Sopenharmony_ci u8 *buffer; 1638c2ecf20Sopenharmony_ci 1648c2ecf20Sopenharmony_ci buffer = kzalloc(GT683R_BUFFER_SIZE, GFP_KERNEL); 1658c2ecf20Sopenharmony_ci if (!buffer) 1668c2ecf20Sopenharmony_ci return -ENOMEM; 1678c2ecf20Sopenharmony_ci 1688c2ecf20Sopenharmony_ci buffer[0] = 0x01; 1698c2ecf20Sopenharmony_ci buffer[1] = 0x02; 1708c2ecf20Sopenharmony_ci buffer[2] = 0x20; 1718c2ecf20Sopenharmony_ci buffer[3] = mode; 1728c2ecf20Sopenharmony_ci buffer[4] = 0x01; 1738c2ecf20Sopenharmony_ci ret = gt683r_led_snd_msg(led, buffer); 1748c2ecf20Sopenharmony_ci 1758c2ecf20Sopenharmony_ci kfree(buffer); 1768c2ecf20Sopenharmony_ci return ret; 1778c2ecf20Sopenharmony_ci} 1788c2ecf20Sopenharmony_ci 1798c2ecf20Sopenharmony_cistatic void gt683r_led_work(struct work_struct *work) 1808c2ecf20Sopenharmony_ci{ 1818c2ecf20Sopenharmony_ci int i; 1828c2ecf20Sopenharmony_ci u8 leds = 0; 1838c2ecf20Sopenharmony_ci u8 mode; 1848c2ecf20Sopenharmony_ci struct gt683r_led *led = container_of(work, struct gt683r_led, work); 1858c2ecf20Sopenharmony_ci 1868c2ecf20Sopenharmony_ci mutex_lock(&led->lock); 1878c2ecf20Sopenharmony_ci 1888c2ecf20Sopenharmony_ci for (i = 0; i < GT683R_LED_COUNT; i++) { 1898c2ecf20Sopenharmony_ci if (led->brightnesses[i]) 1908c2ecf20Sopenharmony_ci leds |= BIT(i); 1918c2ecf20Sopenharmony_ci } 1928c2ecf20Sopenharmony_ci 1938c2ecf20Sopenharmony_ci if (gt683r_leds_set(led, leds)) 1948c2ecf20Sopenharmony_ci goto fail; 1958c2ecf20Sopenharmony_ci 1968c2ecf20Sopenharmony_ci if (leds) 1978c2ecf20Sopenharmony_ci mode = led->mode; 1988c2ecf20Sopenharmony_ci else 1998c2ecf20Sopenharmony_ci mode = GT683R_LED_OFF; 2008c2ecf20Sopenharmony_ci 2018c2ecf20Sopenharmony_ci gt683r_mode_set(led, mode); 2028c2ecf20Sopenharmony_cifail: 2038c2ecf20Sopenharmony_ci mutex_unlock(&led->lock); 2048c2ecf20Sopenharmony_ci} 2058c2ecf20Sopenharmony_ci 2068c2ecf20Sopenharmony_cistatic DEVICE_ATTR_RW(mode); 2078c2ecf20Sopenharmony_ci 2088c2ecf20Sopenharmony_cistatic struct attribute *gt683r_led_attrs[] = { 2098c2ecf20Sopenharmony_ci &dev_attr_mode.attr, 2108c2ecf20Sopenharmony_ci NULL 2118c2ecf20Sopenharmony_ci}; 2128c2ecf20Sopenharmony_ci 2138c2ecf20Sopenharmony_cistatic const struct attribute_group gt683r_led_group = { 2148c2ecf20Sopenharmony_ci .name = "gt683r", 2158c2ecf20Sopenharmony_ci .attrs = gt683r_led_attrs, 2168c2ecf20Sopenharmony_ci}; 2178c2ecf20Sopenharmony_ci 2188c2ecf20Sopenharmony_cistatic const struct attribute_group *gt683r_led_groups[] = { 2198c2ecf20Sopenharmony_ci >683r_led_group, 2208c2ecf20Sopenharmony_ci NULL 2218c2ecf20Sopenharmony_ci}; 2228c2ecf20Sopenharmony_ci 2238c2ecf20Sopenharmony_cistatic int gt683r_led_probe(struct hid_device *hdev, 2248c2ecf20Sopenharmony_ci const struct hid_device_id *id) 2258c2ecf20Sopenharmony_ci{ 2268c2ecf20Sopenharmony_ci int i; 2278c2ecf20Sopenharmony_ci int ret; 2288c2ecf20Sopenharmony_ci int name_sz; 2298c2ecf20Sopenharmony_ci char *name; 2308c2ecf20Sopenharmony_ci struct gt683r_led *led; 2318c2ecf20Sopenharmony_ci 2328c2ecf20Sopenharmony_ci led = devm_kzalloc(&hdev->dev, sizeof(*led), GFP_KERNEL); 2338c2ecf20Sopenharmony_ci if (!led) 2348c2ecf20Sopenharmony_ci return -ENOMEM; 2358c2ecf20Sopenharmony_ci 2368c2ecf20Sopenharmony_ci mutex_init(&led->lock); 2378c2ecf20Sopenharmony_ci INIT_WORK(&led->work, gt683r_led_work); 2388c2ecf20Sopenharmony_ci 2398c2ecf20Sopenharmony_ci led->mode = GT683R_LED_NORMAL; 2408c2ecf20Sopenharmony_ci led->hdev = hdev; 2418c2ecf20Sopenharmony_ci hid_set_drvdata(hdev, led); 2428c2ecf20Sopenharmony_ci 2438c2ecf20Sopenharmony_ci ret = hid_parse(hdev); 2448c2ecf20Sopenharmony_ci if (ret) { 2458c2ecf20Sopenharmony_ci hid_err(hdev, "hid parsing failed\n"); 2468c2ecf20Sopenharmony_ci return ret; 2478c2ecf20Sopenharmony_ci } 2488c2ecf20Sopenharmony_ci 2498c2ecf20Sopenharmony_ci ret = hid_hw_start(hdev, HID_CONNECT_HIDRAW); 2508c2ecf20Sopenharmony_ci if (ret) { 2518c2ecf20Sopenharmony_ci hid_err(hdev, "hw start failed\n"); 2528c2ecf20Sopenharmony_ci return ret; 2538c2ecf20Sopenharmony_ci } 2548c2ecf20Sopenharmony_ci 2558c2ecf20Sopenharmony_ci for (i = 0; i < GT683R_LED_COUNT; i++) { 2568c2ecf20Sopenharmony_ci name_sz = strlen(dev_name(&hdev->dev)) + 2578c2ecf20Sopenharmony_ci strlen(gt683r_panel_names[i]) + 3; 2588c2ecf20Sopenharmony_ci 2598c2ecf20Sopenharmony_ci name = devm_kzalloc(&hdev->dev, name_sz, GFP_KERNEL); 2608c2ecf20Sopenharmony_ci if (!name) { 2618c2ecf20Sopenharmony_ci ret = -ENOMEM; 2628c2ecf20Sopenharmony_ci goto fail; 2638c2ecf20Sopenharmony_ci } 2648c2ecf20Sopenharmony_ci 2658c2ecf20Sopenharmony_ci snprintf(name, name_sz, "%s::%s", 2668c2ecf20Sopenharmony_ci dev_name(&hdev->dev), gt683r_panel_names[i]); 2678c2ecf20Sopenharmony_ci led->led_devs[i].name = name; 2688c2ecf20Sopenharmony_ci led->led_devs[i].max_brightness = 1; 2698c2ecf20Sopenharmony_ci led->led_devs[i].brightness_set = gt683r_brightness_set; 2708c2ecf20Sopenharmony_ci led->led_devs[i].groups = gt683r_led_groups; 2718c2ecf20Sopenharmony_ci 2728c2ecf20Sopenharmony_ci ret = led_classdev_register(&hdev->dev, &led->led_devs[i]); 2738c2ecf20Sopenharmony_ci if (ret) { 2748c2ecf20Sopenharmony_ci hid_err(hdev, "could not register led device\n"); 2758c2ecf20Sopenharmony_ci goto fail; 2768c2ecf20Sopenharmony_ci } 2778c2ecf20Sopenharmony_ci } 2788c2ecf20Sopenharmony_ci 2798c2ecf20Sopenharmony_ci return 0; 2808c2ecf20Sopenharmony_ci 2818c2ecf20Sopenharmony_cifail: 2828c2ecf20Sopenharmony_ci for (i = i - 1; i >= 0; i--) 2838c2ecf20Sopenharmony_ci led_classdev_unregister(&led->led_devs[i]); 2848c2ecf20Sopenharmony_ci hid_hw_stop(hdev); 2858c2ecf20Sopenharmony_ci return ret; 2868c2ecf20Sopenharmony_ci} 2878c2ecf20Sopenharmony_ci 2888c2ecf20Sopenharmony_cistatic void gt683r_led_remove(struct hid_device *hdev) 2898c2ecf20Sopenharmony_ci{ 2908c2ecf20Sopenharmony_ci int i; 2918c2ecf20Sopenharmony_ci struct gt683r_led *led = hid_get_drvdata(hdev); 2928c2ecf20Sopenharmony_ci 2938c2ecf20Sopenharmony_ci for (i = 0; i < GT683R_LED_COUNT; i++) 2948c2ecf20Sopenharmony_ci led_classdev_unregister(&led->led_devs[i]); 2958c2ecf20Sopenharmony_ci flush_work(&led->work); 2968c2ecf20Sopenharmony_ci hid_hw_stop(hdev); 2978c2ecf20Sopenharmony_ci} 2988c2ecf20Sopenharmony_ci 2998c2ecf20Sopenharmony_cistatic struct hid_driver gt683r_led_driver = { 3008c2ecf20Sopenharmony_ci .probe = gt683r_led_probe, 3018c2ecf20Sopenharmony_ci .remove = gt683r_led_remove, 3028c2ecf20Sopenharmony_ci .name = "gt683r_led", 3038c2ecf20Sopenharmony_ci .id_table = gt683r_led_id, 3048c2ecf20Sopenharmony_ci}; 3058c2ecf20Sopenharmony_ci 3068c2ecf20Sopenharmony_cimodule_hid_driver(gt683r_led_driver); 3078c2ecf20Sopenharmony_ci 3088c2ecf20Sopenharmony_ciMODULE_AUTHOR("Janne Kanniainen"); 3098c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("MSI GT683R led driver"); 3108c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL"); 311