1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt |
3 | |
4 | #include <linux/module.h> |
5 | |
6 | #include <linux/platform_device.h> |
7 | #include <linux/err.h> |
8 | #include <linux/leds.h> |
9 | |
10 | #include <linux/io.h> |
11 | #include <linux/dmi.h> |
12 | |
13 | #include <linux/i8042.h> |
14 | |
15 | #define CLEVO_MAIL_LED_OFF 0x0084 |
16 | #define CLEVO_MAIL_LED_BLINK_1HZ 0x008A |
17 | #define CLEVO_MAIL_LED_BLINK_0_5HZ 0x0083 |
18 | |
19 | MODULE_AUTHOR("Márton Németh <nm127@freemail.hu>" ); |
20 | MODULE_DESCRIPTION("Clevo mail LED driver" ); |
21 | MODULE_LICENSE("GPL" ); |
22 | |
23 | static bool nodetect; |
24 | module_param_named(nodetect, nodetect, bool, 0); |
25 | MODULE_PARM_DESC(nodetect, "Skip DMI hardware detection" ); |
26 | |
27 | static struct platform_device *pdev; |
28 | |
29 | static int __init clevo_mail_led_dmi_callback(const struct dmi_system_id *id) |
30 | { |
31 | pr_info("'%s' found\n" , id->ident); |
32 | return 1; |
33 | } |
34 | |
35 | /* |
36 | * struct clevo_mail_led_dmi_table - List of known good models |
37 | * |
38 | * Contains the known good models this driver is compatible with. |
39 | * When adding a new model try to be as strict as possible. This |
40 | * makes it possible to keep the false positives (the model is |
41 | * detected as working, but in reality it is not) as low as |
42 | * possible. |
43 | */ |
44 | static const struct dmi_system_id clevo_mail_led_dmi_table[] __initconst = { |
45 | { |
46 | .callback = clevo_mail_led_dmi_callback, |
47 | .ident = "Clevo D410J" , |
48 | .matches = { |
49 | DMI_MATCH(DMI_SYS_VENDOR, "VIA" ), |
50 | DMI_MATCH(DMI_PRODUCT_NAME, "K8N800" ), |
51 | DMI_MATCH(DMI_PRODUCT_VERSION, "VT8204B" ) |
52 | } |
53 | }, |
54 | { |
55 | .callback = clevo_mail_led_dmi_callback, |
56 | .ident = "Clevo M5x0N" , |
57 | .matches = { |
58 | DMI_MATCH(DMI_SYS_VENDOR, "CLEVO Co." ), |
59 | DMI_MATCH(DMI_PRODUCT_NAME, "M5x0N" ) |
60 | } |
61 | }, |
62 | { |
63 | .callback = clevo_mail_led_dmi_callback, |
64 | .ident = "Clevo M5x0V" , |
65 | .matches = { |
66 | DMI_MATCH(DMI_BOARD_VENDOR, "CLEVO Co. " ), |
67 | DMI_MATCH(DMI_BOARD_NAME, "M5X0V " ), |
68 | DMI_MATCH(DMI_PRODUCT_VERSION, "VT6198" ) |
69 | } |
70 | }, |
71 | { |
72 | .callback = clevo_mail_led_dmi_callback, |
73 | .ident = "Clevo D400P" , |
74 | .matches = { |
75 | DMI_MATCH(DMI_BOARD_VENDOR, "Clevo" ), |
76 | DMI_MATCH(DMI_BOARD_NAME, "D400P" ), |
77 | DMI_MATCH(DMI_BOARD_VERSION, "Rev.A" ), |
78 | DMI_MATCH(DMI_PRODUCT_VERSION, "0106" ) |
79 | } |
80 | }, |
81 | { |
82 | .callback = clevo_mail_led_dmi_callback, |
83 | .ident = "Clevo D410V" , |
84 | .matches = { |
85 | DMI_MATCH(DMI_BOARD_VENDOR, "Clevo, Co." ), |
86 | DMI_MATCH(DMI_BOARD_NAME, "D400V/D470V" ), |
87 | DMI_MATCH(DMI_BOARD_VERSION, "SS78B" ), |
88 | DMI_MATCH(DMI_PRODUCT_VERSION, "Rev. A1" ) |
89 | } |
90 | }, |
91 | { } |
92 | }; |
93 | MODULE_DEVICE_TABLE(dmi, clevo_mail_led_dmi_table); |
94 | |
95 | static void clevo_mail_led_set(struct led_classdev *led_cdev, |
96 | enum led_brightness value) |
97 | { |
98 | i8042_lock_chip(); |
99 | |
100 | if (value == LED_OFF) |
101 | i8042_command(NULL, CLEVO_MAIL_LED_OFF); |
102 | else if (value <= LED_HALF) |
103 | i8042_command(NULL, CLEVO_MAIL_LED_BLINK_0_5HZ); |
104 | else |
105 | i8042_command(NULL, CLEVO_MAIL_LED_BLINK_1HZ); |
106 | |
107 | i8042_unlock_chip(); |
108 | |
109 | } |
110 | |
111 | static int clevo_mail_led_blink(struct led_classdev *led_cdev, |
112 | unsigned long *delay_on, |
113 | unsigned long *delay_off) |
114 | { |
115 | int status = -EINVAL; |
116 | |
117 | i8042_lock_chip(); |
118 | |
119 | if (*delay_on == 0 /* ms */ && *delay_off == 0 /* ms */) { |
120 | /* Special case: the leds subsystem requested us to |
121 | * chose one user friendly blinking of the LED, and |
122 | * start it. Let's blink the led slowly (0.5Hz). |
123 | */ |
124 | *delay_on = 1000; /* ms */ |
125 | *delay_off = 1000; /* ms */ |
126 | i8042_command(NULL, CLEVO_MAIL_LED_BLINK_0_5HZ); |
127 | status = 0; |
128 | |
129 | } else if (*delay_on == 500 /* ms */ && *delay_off == 500 /* ms */) { |
130 | /* blink the led with 1Hz */ |
131 | i8042_command(NULL, CLEVO_MAIL_LED_BLINK_1HZ); |
132 | status = 0; |
133 | |
134 | } else if (*delay_on == 1000 /* ms */ && *delay_off == 1000 /* ms */) { |
135 | /* blink the led with 0.5Hz */ |
136 | i8042_command(NULL, CLEVO_MAIL_LED_BLINK_0_5HZ); |
137 | status = 0; |
138 | |
139 | } else { |
140 | pr_debug("clevo_mail_led_blink(..., %lu, %lu)," |
141 | " returning -EINVAL (unsupported)\n" , |
142 | *delay_on, *delay_off); |
143 | } |
144 | |
145 | i8042_unlock_chip(); |
146 | |
147 | return status; |
148 | } |
149 | |
150 | static struct led_classdev clevo_mail_led = { |
151 | .name = "clevo::mail" , |
152 | .brightness_set = clevo_mail_led_set, |
153 | .blink_set = clevo_mail_led_blink, |
154 | .flags = LED_CORE_SUSPENDRESUME, |
155 | }; |
156 | |
157 | static int __init clevo_mail_led_probe(struct platform_device *pdev) |
158 | { |
159 | return led_classdev_register(parent: &pdev->dev, led_cdev: &clevo_mail_led); |
160 | } |
161 | |
162 | static void clevo_mail_led_remove(struct platform_device *pdev) |
163 | { |
164 | led_classdev_unregister(led_cdev: &clevo_mail_led); |
165 | } |
166 | |
167 | static struct platform_driver clevo_mail_led_driver = { |
168 | .remove_new = clevo_mail_led_remove, |
169 | .driver = { |
170 | .name = KBUILD_MODNAME, |
171 | }, |
172 | }; |
173 | |
174 | static int __init clevo_mail_led_init(void) |
175 | { |
176 | int error = 0; |
177 | int count = 0; |
178 | |
179 | /* Check with the help of DMI if we are running on supported hardware */ |
180 | if (!nodetect) { |
181 | count = dmi_check_system(list: clevo_mail_led_dmi_table); |
182 | } else { |
183 | count = 1; |
184 | pr_err("Skipping DMI detection. " |
185 | "If the driver works on your hardware please " |
186 | "report model and the output of dmidecode in tracker " |
187 | "at http://sourceforge.net/projects/clevo-mailled/\n" ); |
188 | } |
189 | |
190 | if (!count) |
191 | return -ENODEV; |
192 | |
193 | pdev = platform_device_register_simple(KBUILD_MODNAME, id: -1, NULL, num: 0); |
194 | if (!IS_ERR(ptr: pdev)) { |
195 | error = platform_driver_probe(&clevo_mail_led_driver, |
196 | clevo_mail_led_probe); |
197 | if (error) { |
198 | pr_err("Can't probe platform driver\n" ); |
199 | platform_device_unregister(pdev); |
200 | } |
201 | } else |
202 | error = PTR_ERR(ptr: pdev); |
203 | |
204 | return error; |
205 | } |
206 | |
207 | static void __exit clevo_mail_led_exit(void) |
208 | { |
209 | platform_device_unregister(pdev); |
210 | platform_driver_unregister(&clevo_mail_led_driver); |
211 | |
212 | clevo_mail_led_set(NULL, value: LED_OFF); |
213 | } |
214 | |
215 | module_init(clevo_mail_led_init); |
216 | module_exit(clevo_mail_led_exit); |
217 | |