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	&gt683r_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