162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * MSI GT683R led driver 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Copyright (c) 2014 Janne Kanniainen <janne.kanniainen@gmail.com> 662306a36Sopenharmony_ci */ 762306a36Sopenharmony_ci 862306a36Sopenharmony_ci#include <linux/device.h> 962306a36Sopenharmony_ci#include <linux/hid.h> 1062306a36Sopenharmony_ci#include <linux/kernel.h> 1162306a36Sopenharmony_ci#include <linux/leds.h> 1262306a36Sopenharmony_ci#include <linux/module.h> 1362306a36Sopenharmony_ci 1462306a36Sopenharmony_ci#include "hid-ids.h" 1562306a36Sopenharmony_ci 1662306a36Sopenharmony_ci#define GT683R_BUFFER_SIZE 8 1762306a36Sopenharmony_ci 1862306a36Sopenharmony_ci/* 1962306a36Sopenharmony_ci * GT683R_LED_OFF: all LEDs are off 2062306a36Sopenharmony_ci * GT683R_LED_AUDIO: LEDs brightness depends on sound level 2162306a36Sopenharmony_ci * GT683R_LED_BREATHING: LEDs brightness varies at human breathing rate 2262306a36Sopenharmony_ci * GT683R_LED_NORMAL: LEDs are fully on when enabled 2362306a36Sopenharmony_ci */ 2462306a36Sopenharmony_cienum gt683r_led_mode { 2562306a36Sopenharmony_ci GT683R_LED_OFF = 0, 2662306a36Sopenharmony_ci GT683R_LED_AUDIO = 2, 2762306a36Sopenharmony_ci GT683R_LED_BREATHING = 3, 2862306a36Sopenharmony_ci GT683R_LED_NORMAL = 5 2962306a36Sopenharmony_ci}; 3062306a36Sopenharmony_ci 3162306a36Sopenharmony_cienum gt683r_panels { 3262306a36Sopenharmony_ci GT683R_LED_BACK = 0, 3362306a36Sopenharmony_ci GT683R_LED_SIDE = 1, 3462306a36Sopenharmony_ci GT683R_LED_FRONT = 2, 3562306a36Sopenharmony_ci GT683R_LED_COUNT, 3662306a36Sopenharmony_ci}; 3762306a36Sopenharmony_ci 3862306a36Sopenharmony_cistatic const char * const gt683r_panel_names[] = { 3962306a36Sopenharmony_ci "back", 4062306a36Sopenharmony_ci "side", 4162306a36Sopenharmony_ci "front", 4262306a36Sopenharmony_ci}; 4362306a36Sopenharmony_ci 4462306a36Sopenharmony_cistruct gt683r_led { 4562306a36Sopenharmony_ci struct hid_device *hdev; 4662306a36Sopenharmony_ci struct led_classdev led_devs[GT683R_LED_COUNT]; 4762306a36Sopenharmony_ci struct mutex lock; 4862306a36Sopenharmony_ci struct work_struct work; 4962306a36Sopenharmony_ci enum led_brightness brightnesses[GT683R_LED_COUNT]; 5062306a36Sopenharmony_ci enum gt683r_led_mode mode; 5162306a36Sopenharmony_ci}; 5262306a36Sopenharmony_ci 5362306a36Sopenharmony_cistatic const struct hid_device_id gt683r_led_id[] = { 5462306a36Sopenharmony_ci { HID_USB_DEVICE(USB_VENDOR_ID_MSI, USB_DEVICE_ID_MSI_GT683R_LED_PANEL) }, 5562306a36Sopenharmony_ci { } 5662306a36Sopenharmony_ci}; 5762306a36Sopenharmony_ciMODULE_DEVICE_TABLE(hid, gt683r_led_id); 5862306a36Sopenharmony_ci 5962306a36Sopenharmony_cistatic void gt683r_brightness_set(struct led_classdev *led_cdev, 6062306a36Sopenharmony_ci enum led_brightness brightness) 6162306a36Sopenharmony_ci{ 6262306a36Sopenharmony_ci int i; 6362306a36Sopenharmony_ci struct device *dev = led_cdev->dev->parent; 6462306a36Sopenharmony_ci struct hid_device *hdev = to_hid_device(dev); 6562306a36Sopenharmony_ci struct gt683r_led *led = hid_get_drvdata(hdev); 6662306a36Sopenharmony_ci 6762306a36Sopenharmony_ci for (i = 0; i < GT683R_LED_COUNT; i++) { 6862306a36Sopenharmony_ci if (led_cdev == &led->led_devs[i]) 6962306a36Sopenharmony_ci break; 7062306a36Sopenharmony_ci } 7162306a36Sopenharmony_ci 7262306a36Sopenharmony_ci if (i < GT683R_LED_COUNT) { 7362306a36Sopenharmony_ci led->brightnesses[i] = brightness; 7462306a36Sopenharmony_ci schedule_work(&led->work); 7562306a36Sopenharmony_ci } 7662306a36Sopenharmony_ci} 7762306a36Sopenharmony_ci 7862306a36Sopenharmony_cistatic ssize_t mode_show(struct device *dev, 7962306a36Sopenharmony_ci struct device_attribute *attr, 8062306a36Sopenharmony_ci char *buf) 8162306a36Sopenharmony_ci{ 8262306a36Sopenharmony_ci u8 sysfs_mode; 8362306a36Sopenharmony_ci struct hid_device *hdev = to_hid_device(dev->parent); 8462306a36Sopenharmony_ci struct gt683r_led *led = hid_get_drvdata(hdev); 8562306a36Sopenharmony_ci 8662306a36Sopenharmony_ci if (led->mode == GT683R_LED_NORMAL) 8762306a36Sopenharmony_ci sysfs_mode = 0; 8862306a36Sopenharmony_ci else if (led->mode == GT683R_LED_AUDIO) 8962306a36Sopenharmony_ci sysfs_mode = 1; 9062306a36Sopenharmony_ci else 9162306a36Sopenharmony_ci sysfs_mode = 2; 9262306a36Sopenharmony_ci 9362306a36Sopenharmony_ci return scnprintf(buf, PAGE_SIZE, "%u\n", sysfs_mode); 9462306a36Sopenharmony_ci} 9562306a36Sopenharmony_ci 9662306a36Sopenharmony_cistatic ssize_t mode_store(struct device *dev, 9762306a36Sopenharmony_ci struct device_attribute *attr, 9862306a36Sopenharmony_ci const char *buf, size_t count) 9962306a36Sopenharmony_ci{ 10062306a36Sopenharmony_ci u8 sysfs_mode; 10162306a36Sopenharmony_ci struct hid_device *hdev = to_hid_device(dev->parent); 10262306a36Sopenharmony_ci struct gt683r_led *led = hid_get_drvdata(hdev); 10362306a36Sopenharmony_ci 10462306a36Sopenharmony_ci 10562306a36Sopenharmony_ci if (kstrtou8(buf, 10, &sysfs_mode) || sysfs_mode > 2) 10662306a36Sopenharmony_ci return -EINVAL; 10762306a36Sopenharmony_ci 10862306a36Sopenharmony_ci mutex_lock(&led->lock); 10962306a36Sopenharmony_ci 11062306a36Sopenharmony_ci if (sysfs_mode == 0) 11162306a36Sopenharmony_ci led->mode = GT683R_LED_NORMAL; 11262306a36Sopenharmony_ci else if (sysfs_mode == 1) 11362306a36Sopenharmony_ci led->mode = GT683R_LED_AUDIO; 11462306a36Sopenharmony_ci else 11562306a36Sopenharmony_ci led->mode = GT683R_LED_BREATHING; 11662306a36Sopenharmony_ci 11762306a36Sopenharmony_ci mutex_unlock(&led->lock); 11862306a36Sopenharmony_ci schedule_work(&led->work); 11962306a36Sopenharmony_ci 12062306a36Sopenharmony_ci return count; 12162306a36Sopenharmony_ci} 12262306a36Sopenharmony_ci 12362306a36Sopenharmony_cistatic int gt683r_led_snd_msg(struct gt683r_led *led, u8 *msg) 12462306a36Sopenharmony_ci{ 12562306a36Sopenharmony_ci int ret; 12662306a36Sopenharmony_ci 12762306a36Sopenharmony_ci ret = hid_hw_raw_request(led->hdev, msg[0], msg, GT683R_BUFFER_SIZE, 12862306a36Sopenharmony_ci HID_FEATURE_REPORT, HID_REQ_SET_REPORT); 12962306a36Sopenharmony_ci if (ret != GT683R_BUFFER_SIZE) { 13062306a36Sopenharmony_ci hid_err(led->hdev, 13162306a36Sopenharmony_ci "failed to send set report request: %i\n", ret); 13262306a36Sopenharmony_ci if (ret < 0) 13362306a36Sopenharmony_ci return ret; 13462306a36Sopenharmony_ci return -EIO; 13562306a36Sopenharmony_ci } 13662306a36Sopenharmony_ci 13762306a36Sopenharmony_ci return 0; 13862306a36Sopenharmony_ci} 13962306a36Sopenharmony_ci 14062306a36Sopenharmony_cistatic int gt683r_leds_set(struct gt683r_led *led, u8 leds) 14162306a36Sopenharmony_ci{ 14262306a36Sopenharmony_ci int ret; 14362306a36Sopenharmony_ci u8 *buffer; 14462306a36Sopenharmony_ci 14562306a36Sopenharmony_ci buffer = kzalloc(GT683R_BUFFER_SIZE, GFP_KERNEL); 14662306a36Sopenharmony_ci if (!buffer) 14762306a36Sopenharmony_ci return -ENOMEM; 14862306a36Sopenharmony_ci 14962306a36Sopenharmony_ci buffer[0] = 0x01; 15062306a36Sopenharmony_ci buffer[1] = 0x02; 15162306a36Sopenharmony_ci buffer[2] = 0x30; 15262306a36Sopenharmony_ci buffer[3] = leds; 15362306a36Sopenharmony_ci ret = gt683r_led_snd_msg(led, buffer); 15462306a36Sopenharmony_ci 15562306a36Sopenharmony_ci kfree(buffer); 15662306a36Sopenharmony_ci return ret; 15762306a36Sopenharmony_ci} 15862306a36Sopenharmony_ci 15962306a36Sopenharmony_cistatic int gt683r_mode_set(struct gt683r_led *led, u8 mode) 16062306a36Sopenharmony_ci{ 16162306a36Sopenharmony_ci int ret; 16262306a36Sopenharmony_ci u8 *buffer; 16362306a36Sopenharmony_ci 16462306a36Sopenharmony_ci buffer = kzalloc(GT683R_BUFFER_SIZE, GFP_KERNEL); 16562306a36Sopenharmony_ci if (!buffer) 16662306a36Sopenharmony_ci return -ENOMEM; 16762306a36Sopenharmony_ci 16862306a36Sopenharmony_ci buffer[0] = 0x01; 16962306a36Sopenharmony_ci buffer[1] = 0x02; 17062306a36Sopenharmony_ci buffer[2] = 0x20; 17162306a36Sopenharmony_ci buffer[3] = mode; 17262306a36Sopenharmony_ci buffer[4] = 0x01; 17362306a36Sopenharmony_ci ret = gt683r_led_snd_msg(led, buffer); 17462306a36Sopenharmony_ci 17562306a36Sopenharmony_ci kfree(buffer); 17662306a36Sopenharmony_ci return ret; 17762306a36Sopenharmony_ci} 17862306a36Sopenharmony_ci 17962306a36Sopenharmony_cistatic void gt683r_led_work(struct work_struct *work) 18062306a36Sopenharmony_ci{ 18162306a36Sopenharmony_ci int i; 18262306a36Sopenharmony_ci u8 leds = 0; 18362306a36Sopenharmony_ci u8 mode; 18462306a36Sopenharmony_ci struct gt683r_led *led = container_of(work, struct gt683r_led, work); 18562306a36Sopenharmony_ci 18662306a36Sopenharmony_ci mutex_lock(&led->lock); 18762306a36Sopenharmony_ci 18862306a36Sopenharmony_ci for (i = 0; i < GT683R_LED_COUNT; i++) { 18962306a36Sopenharmony_ci if (led->brightnesses[i]) 19062306a36Sopenharmony_ci leds |= BIT(i); 19162306a36Sopenharmony_ci } 19262306a36Sopenharmony_ci 19362306a36Sopenharmony_ci if (gt683r_leds_set(led, leds)) 19462306a36Sopenharmony_ci goto fail; 19562306a36Sopenharmony_ci 19662306a36Sopenharmony_ci if (leds) 19762306a36Sopenharmony_ci mode = led->mode; 19862306a36Sopenharmony_ci else 19962306a36Sopenharmony_ci mode = GT683R_LED_OFF; 20062306a36Sopenharmony_ci 20162306a36Sopenharmony_ci gt683r_mode_set(led, mode); 20262306a36Sopenharmony_cifail: 20362306a36Sopenharmony_ci mutex_unlock(&led->lock); 20462306a36Sopenharmony_ci} 20562306a36Sopenharmony_ci 20662306a36Sopenharmony_cistatic DEVICE_ATTR_RW(mode); 20762306a36Sopenharmony_ci 20862306a36Sopenharmony_cistatic struct attribute *gt683r_led_attrs[] = { 20962306a36Sopenharmony_ci &dev_attr_mode.attr, 21062306a36Sopenharmony_ci NULL 21162306a36Sopenharmony_ci}; 21262306a36Sopenharmony_ci 21362306a36Sopenharmony_cistatic const struct attribute_group gt683r_led_group = { 21462306a36Sopenharmony_ci .name = "gt683r", 21562306a36Sopenharmony_ci .attrs = gt683r_led_attrs, 21662306a36Sopenharmony_ci}; 21762306a36Sopenharmony_ci 21862306a36Sopenharmony_cistatic const struct attribute_group *gt683r_led_groups[] = { 21962306a36Sopenharmony_ci >683r_led_group, 22062306a36Sopenharmony_ci NULL 22162306a36Sopenharmony_ci}; 22262306a36Sopenharmony_ci 22362306a36Sopenharmony_cistatic int gt683r_led_probe(struct hid_device *hdev, 22462306a36Sopenharmony_ci const struct hid_device_id *id) 22562306a36Sopenharmony_ci{ 22662306a36Sopenharmony_ci int i; 22762306a36Sopenharmony_ci int ret; 22862306a36Sopenharmony_ci int name_sz; 22962306a36Sopenharmony_ci char *name; 23062306a36Sopenharmony_ci struct gt683r_led *led; 23162306a36Sopenharmony_ci 23262306a36Sopenharmony_ci led = devm_kzalloc(&hdev->dev, sizeof(*led), GFP_KERNEL); 23362306a36Sopenharmony_ci if (!led) 23462306a36Sopenharmony_ci return -ENOMEM; 23562306a36Sopenharmony_ci 23662306a36Sopenharmony_ci mutex_init(&led->lock); 23762306a36Sopenharmony_ci INIT_WORK(&led->work, gt683r_led_work); 23862306a36Sopenharmony_ci 23962306a36Sopenharmony_ci led->mode = GT683R_LED_NORMAL; 24062306a36Sopenharmony_ci led->hdev = hdev; 24162306a36Sopenharmony_ci hid_set_drvdata(hdev, led); 24262306a36Sopenharmony_ci 24362306a36Sopenharmony_ci ret = hid_parse(hdev); 24462306a36Sopenharmony_ci if (ret) { 24562306a36Sopenharmony_ci hid_err(hdev, "hid parsing failed\n"); 24662306a36Sopenharmony_ci return ret; 24762306a36Sopenharmony_ci } 24862306a36Sopenharmony_ci 24962306a36Sopenharmony_ci ret = hid_hw_start(hdev, HID_CONNECT_HIDRAW); 25062306a36Sopenharmony_ci if (ret) { 25162306a36Sopenharmony_ci hid_err(hdev, "hw start failed\n"); 25262306a36Sopenharmony_ci return ret; 25362306a36Sopenharmony_ci } 25462306a36Sopenharmony_ci 25562306a36Sopenharmony_ci for (i = 0; i < GT683R_LED_COUNT; i++) { 25662306a36Sopenharmony_ci name_sz = strlen(dev_name(&hdev->dev)) + 25762306a36Sopenharmony_ci strlen(gt683r_panel_names[i]) + 3; 25862306a36Sopenharmony_ci 25962306a36Sopenharmony_ci name = devm_kzalloc(&hdev->dev, name_sz, GFP_KERNEL); 26062306a36Sopenharmony_ci if (!name) { 26162306a36Sopenharmony_ci ret = -ENOMEM; 26262306a36Sopenharmony_ci goto fail; 26362306a36Sopenharmony_ci } 26462306a36Sopenharmony_ci 26562306a36Sopenharmony_ci snprintf(name, name_sz, "%s::%s", 26662306a36Sopenharmony_ci dev_name(&hdev->dev), gt683r_panel_names[i]); 26762306a36Sopenharmony_ci led->led_devs[i].name = name; 26862306a36Sopenharmony_ci led->led_devs[i].max_brightness = 1; 26962306a36Sopenharmony_ci led->led_devs[i].brightness_set = gt683r_brightness_set; 27062306a36Sopenharmony_ci led->led_devs[i].groups = gt683r_led_groups; 27162306a36Sopenharmony_ci 27262306a36Sopenharmony_ci ret = led_classdev_register(&hdev->dev, &led->led_devs[i]); 27362306a36Sopenharmony_ci if (ret) { 27462306a36Sopenharmony_ci hid_err(hdev, "could not register led device\n"); 27562306a36Sopenharmony_ci goto fail; 27662306a36Sopenharmony_ci } 27762306a36Sopenharmony_ci } 27862306a36Sopenharmony_ci 27962306a36Sopenharmony_ci return 0; 28062306a36Sopenharmony_ci 28162306a36Sopenharmony_cifail: 28262306a36Sopenharmony_ci for (i = i - 1; i >= 0; i--) 28362306a36Sopenharmony_ci led_classdev_unregister(&led->led_devs[i]); 28462306a36Sopenharmony_ci hid_hw_stop(hdev); 28562306a36Sopenharmony_ci return ret; 28662306a36Sopenharmony_ci} 28762306a36Sopenharmony_ci 28862306a36Sopenharmony_cistatic void gt683r_led_remove(struct hid_device *hdev) 28962306a36Sopenharmony_ci{ 29062306a36Sopenharmony_ci int i; 29162306a36Sopenharmony_ci struct gt683r_led *led = hid_get_drvdata(hdev); 29262306a36Sopenharmony_ci 29362306a36Sopenharmony_ci for (i = 0; i < GT683R_LED_COUNT; i++) 29462306a36Sopenharmony_ci led_classdev_unregister(&led->led_devs[i]); 29562306a36Sopenharmony_ci flush_work(&led->work); 29662306a36Sopenharmony_ci hid_hw_stop(hdev); 29762306a36Sopenharmony_ci} 29862306a36Sopenharmony_ci 29962306a36Sopenharmony_cistatic struct hid_driver gt683r_led_driver = { 30062306a36Sopenharmony_ci .probe = gt683r_led_probe, 30162306a36Sopenharmony_ci .remove = gt683r_led_remove, 30262306a36Sopenharmony_ci .name = "gt683r_led", 30362306a36Sopenharmony_ci .id_table = gt683r_led_id, 30462306a36Sopenharmony_ci}; 30562306a36Sopenharmony_ci 30662306a36Sopenharmony_cimodule_hid_driver(gt683r_led_driver); 30762306a36Sopenharmony_ci 30862306a36Sopenharmony_ciMODULE_AUTHOR("Janne Kanniainen"); 30962306a36Sopenharmony_ciMODULE_DESCRIPTION("MSI GT683R led driver"); 31062306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 311