1 // SPDX-License-Identifier: GPL-2.0-only
2 /***************************************************************************
3  *   Copyright (C) 2010-2012 by Bruno Prémont <bonbons@linux-vserver.org>  *
4  *                                                                         *
5  *   Based on Logitech G13 driver (v0.4)                                   *
6  *     Copyright (C) 2009 by Rick L. Vinyard, Jr. <rvinyard@cs.nmsu.edu>   *
7  *                                                                         *
8  ***************************************************************************/
9 
10 #include <linux/hid.h>
11 #include <linux/hid-debug.h>
12 #include <linux/input.h>
13 #include "hid-ids.h"
14 
15 #include <linux/fb.h>
16 #include <linux/vmalloc.h>
17 #include <linux/backlight.h>
18 #include <linux/lcd.h>
19 
20 #include <linux/leds.h>
21 
22 #include <linux/seq_file.h>
23 #include <linux/debugfs.h>
24 
25 #include <linux/completion.h>
26 #include <linux/uaccess.h>
27 #include <linux/module.h>
28 
29 #include "hid-picolcd.h"
30 
31 
32 void picolcd_leds_set(struct picolcd_data *data)
33 {
34 	struct hid_report *report;
35 	unsigned long flags;
36 
37 	if (!data->led[0])
38 		return;
39 	report = picolcd_out_report(REPORT_LED_STATE, data->hdev);
40 	if (!report || report->maxfield != 1 || report->field[0]->report_count != 1)
41 		return;
42 
43 	spin_lock_irqsave(&data->lock, flags);
44 	hid_set_field(report->field[0], 0, data->led_state);
45 	if (!(data->status & PICOLCD_FAILED))
46 		hid_hw_request(data->hdev, report, HID_REQ_SET_REPORT);
47 	spin_unlock_irqrestore(&data->lock, flags);
48 }
49 
50 static void picolcd_led_set_brightness(struct led_classdev *led_cdev,
51 			enum led_brightness value)
52 {
53 	struct device *dev;
54 	struct hid_device *hdev;
55 	struct picolcd_data *data;
56 	int i, state = 0;
57 
58 	dev  = led_cdev->dev->parent;
59 	hdev = to_hid_device(dev);
60 	data = hid_get_drvdata(hdev);
61 	if (!data)
62 		return;
63 	for (i = 0; i < 8; i++) {
64 		if (led_cdev != data->led[i])
65 			continue;
66 		state = (data->led_state >> i) & 1;
67 		if (value == LED_OFF && state) {
68 			data->led_state &= ~(1 << i);
69 			picolcd_leds_set(data);
70 		} else if (value != LED_OFF && !state) {
71 			data->led_state |= 1 << i;
72 			picolcd_leds_set(data);
73 		}
74 		break;
75 	}
76 }
77 
78 static enum led_brightness picolcd_led_get_brightness(struct led_classdev *led_cdev)
79 {
80 	struct device *dev;
81 	struct hid_device *hdev;
82 	struct picolcd_data *data;
83 	int i, value = 0;
84 
85 	dev  = led_cdev->dev->parent;
86 	hdev = to_hid_device(dev);
87 	data = hid_get_drvdata(hdev);
88 	for (i = 0; i < 8; i++)
89 		if (led_cdev == data->led[i]) {
90 			value = (data->led_state >> i) & 1;
91 			break;
92 		}
93 	return value ? LED_FULL : LED_OFF;
94 }
95 
96 int picolcd_init_leds(struct picolcd_data *data, struct hid_report *report)
97 {
98 	struct device *dev = &data->hdev->dev;
99 	struct led_classdev *led;
100 	size_t name_sz = strlen(dev_name(dev)) + 8;
101 	char *name;
102 	int i, ret = 0;
103 
104 	if (!report)
105 		return -ENODEV;
106 	if (report->maxfield != 1 || report->field[0]->report_count != 1 ||
107 			report->field[0]->report_size != 8) {
108 		dev_err(dev, "unsupported LED_STATE report");
109 		return -EINVAL;
110 	}
111 
112 	for (i = 0; i < 8; i++) {
113 		led = kzalloc(sizeof(struct led_classdev)+name_sz, GFP_KERNEL);
114 		if (!led) {
115 			dev_err(dev, "can't allocate memory for LED %d\n", i);
116 			ret = -ENOMEM;
117 			goto err;
118 		}
119 		name = (void *)(&led[1]);
120 		snprintf(name, name_sz, "%s::GPO%d", dev_name(dev), i);
121 		led->name = name;
122 		led->brightness = 0;
123 		led->max_brightness = 1;
124 		led->brightness_get = picolcd_led_get_brightness;
125 		led->brightness_set = picolcd_led_set_brightness;
126 
127 		data->led[i] = led;
128 		ret = led_classdev_register(dev, data->led[i]);
129 		if (ret) {
130 			data->led[i] = NULL;
131 			kfree(led);
132 			dev_err(dev, "can't register LED %d\n", i);
133 			goto err;
134 		}
135 	}
136 	return 0;
137 err:
138 	for (i = 0; i < 8; i++)
139 		if (data->led[i]) {
140 			led = data->led[i];
141 			data->led[i] = NULL;
142 			led_classdev_unregister(led);
143 			kfree(led);
144 		}
145 	return ret;
146 }
147 
148 void picolcd_exit_leds(struct picolcd_data *data)
149 {
150 	struct led_classdev *led;
151 	int i;
152 
153 	for (i = 0; i < 8; i++) {
154 		led = data->led[i];
155 		data->led[i] = NULL;
156 		if (!led)
157 			continue;
158 		led_classdev_unregister(led);
159 		kfree(led);
160 	}
161 }
162 
163 
164