]> Git Repo - linux.git/blob - drivers/platform/x86/dell/dell-uart-backlight.c
Linux 6.14-rc3
[linux.git] / drivers / platform / x86 / dell / dell-uart-backlight.c
1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3  * Dell AIO Serial Backlight Driver
4  *
5  * Copyright (C) 2024 Hans de Goede <[email protected]>
6  * Copyright (C) 2017 AceLan Kao <[email protected]>
7  */
8
9 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
10
11 #include <linux/acpi.h>
12 #include <linux/backlight.h>
13 #include <linux/delay.h>
14 #include <linux/device.h>
15 #include <linux/err.h>
16 #include <linux/module.h>
17 #include <linux/mutex.h>
18 #include <linux/platform_device.h>
19 #include <linux/serdev.h>
20 #include <linux/string.h>
21 #include <linux/types.h>
22 #include <linux/wait.h>
23 #include <acpi/video.h>
24 #include "../serdev_helpers.h"
25
26 /* The backlight controller must respond within 1 second */
27 #define DELL_BL_TIMEOUT         msecs_to_jiffies(1000)
28 #define DELL_BL_MAX_BRIGHTNESS  100
29
30 /* Defines for the commands send to the controller */
31
32 /* 1st byte Start Of Frame 3 MSB bits: cmd-len + 01010 SOF marker */
33 #define DELL_SOF(len)                   (((len) << 5) | 0x0a)
34 #define GET_CMD_LEN                     3
35 #define SET_CMD_LEN                     4
36
37 /* 2nd byte command */
38 #define CMD_GET_VERSION                 0x06
39 #define CMD_SET_BRIGHTNESS              0x0b
40 #define CMD_GET_BRIGHTNESS              0x0c
41 #define CMD_SET_BL_POWER                0x0e
42
43 /* Indexes and other defines for response received from the controller */
44 #define RESP_LEN                        0
45 #define RESP_CMD                        1 /* Echo of CMD byte from command */
46 #define RESP_DATA                       2 /* Start of received data */
47
48 #define SET_RESP_LEN                    3
49 #define GET_RESP_LEN                    4
50 #define MIN_RESP_LEN                    3
51 #define MAX_RESP_LEN                    80
52
53 struct dell_uart_backlight {
54         struct mutex mutex;
55         wait_queue_head_t wait_queue;
56         struct device *dev;
57         struct backlight_device *bl;
58         u8 *resp;
59         u8 resp_idx;
60         u8 resp_len;
61         u8 resp_max_len;
62         u8 pending_cmd;
63         int status;
64         int power;
65 };
66
67 /* Checksum: SUM(Length and Cmd and Data) xor 0xFF */
68 static u8 dell_uart_checksum(u8 *buf, int len)
69 {
70         u8 val = 0;
71
72         while (len-- > 0)
73                 val += buf[len];
74
75         return val ^ 0xff;
76 }
77
78 static int dell_uart_bl_command(struct dell_uart_backlight *dell_bl,
79                                 const u8 *cmd, int cmd_len,
80                                 u8 *resp, int resp_max_len)
81 {
82         int ret;
83
84         ret = mutex_lock_killable(&dell_bl->mutex);
85         if (ret)
86                 return ret;
87
88         dell_bl->status = -EBUSY;
89         dell_bl->resp = resp;
90         dell_bl->resp_idx = 0;
91         dell_bl->resp_len = -1; /* Invalid / unset */
92         dell_bl->resp_max_len = resp_max_len;
93         dell_bl->pending_cmd = cmd[1];
94
95         /* The TTY buffer should be big enough to take the entire cmd in one go */
96         ret = serdev_device_write_buf(to_serdev_device(dell_bl->dev), cmd, cmd_len);
97         if (ret != cmd_len) {
98                 dev_err(dell_bl->dev, "Error writing command: %d\n", ret);
99                 dell_bl->status = (ret < 0) ? ret : -EIO;
100                 goto out;
101         }
102
103         ret = wait_event_timeout(dell_bl->wait_queue, dell_bl->status != -EBUSY,
104                                  DELL_BL_TIMEOUT);
105         if (ret == 0) {
106                 dev_err(dell_bl->dev, "Timed out waiting for response.\n");
107                 /* Clear busy status to discard bytes received after this */
108                 dell_bl->status = -ETIMEDOUT;
109         }
110
111 out:
112         mutex_unlock(&dell_bl->mutex);
113         return dell_bl->status;
114 }
115
116 static int dell_uart_set_brightness(struct dell_uart_backlight *dell_bl, int brightness)
117 {
118         u8 set_brightness[SET_CMD_LEN], resp[SET_RESP_LEN];
119
120         set_brightness[0] = DELL_SOF(SET_CMD_LEN);
121         set_brightness[1] = CMD_SET_BRIGHTNESS;
122         set_brightness[2] = brightness;
123         set_brightness[3] = dell_uart_checksum(set_brightness, 3);
124
125         return dell_uart_bl_command(dell_bl, set_brightness, SET_CMD_LEN, resp, SET_RESP_LEN);
126 }
127
128 static int dell_uart_get_brightness(struct dell_uart_backlight *dell_bl)
129 {
130         struct device *dev = dell_bl->dev;
131         u8 get_brightness[GET_CMD_LEN], resp[GET_RESP_LEN];
132         int ret;
133
134         get_brightness[0] = DELL_SOF(GET_CMD_LEN);
135         get_brightness[1] = CMD_GET_BRIGHTNESS;
136         get_brightness[2] = dell_uart_checksum(get_brightness, 2);
137
138         ret = dell_uart_bl_command(dell_bl, get_brightness, GET_CMD_LEN, resp, GET_RESP_LEN);
139         if (ret)
140                 return ret;
141
142         if (resp[RESP_LEN] != GET_RESP_LEN) {
143                 dev_err(dev, "Unexpected get brightness response length: %d\n", resp[RESP_LEN]);
144                 return -EIO;
145         }
146
147         if (resp[RESP_DATA] > DELL_BL_MAX_BRIGHTNESS) {
148                 dev_err(dev, "Unexpected get brightness response: %d\n", resp[RESP_DATA]);
149                 return -EIO;
150         }
151
152         return resp[RESP_DATA];
153 }
154
155 static int dell_uart_set_bl_power(struct dell_uart_backlight *dell_bl, int power)
156 {
157         u8 set_power[SET_CMD_LEN], resp[SET_RESP_LEN];
158         int ret;
159
160         set_power[0] = DELL_SOF(SET_CMD_LEN);
161         set_power[1] = CMD_SET_BL_POWER;
162         set_power[2] = (power == BACKLIGHT_POWER_ON) ? 1 : 0;
163         set_power[3] = dell_uart_checksum(set_power, 3);
164
165         ret = dell_uart_bl_command(dell_bl, set_power, SET_CMD_LEN, resp, SET_RESP_LEN);
166         if (ret)
167                 return ret;
168
169         dell_bl->power = power;
170         return 0;
171 }
172
173 /*
174  * There is no command to get backlight power status,
175  * so we set the backlight power to "on" while initializing,
176  * and then track and report its status by power variable.
177  */
178 static int dell_uart_get_bl_power(struct dell_uart_backlight *dell_bl)
179 {
180         return dell_bl->power;
181 }
182
183 static int dell_uart_update_status(struct backlight_device *bd)
184 {
185         struct dell_uart_backlight *dell_bl = bl_get_data(bd);
186         int ret;
187
188         ret = dell_uart_set_brightness(dell_bl, bd->props.brightness);
189         if (ret)
190                 return ret;
191
192         if (bd->props.power != dell_uart_get_bl_power(dell_bl))
193                 return dell_uart_set_bl_power(dell_bl, bd->props.power);
194
195         return 0;
196 }
197
198 static int dell_uart_get_brightness_op(struct backlight_device *bd)
199 {
200         return dell_uart_get_brightness(bl_get_data(bd));
201 }
202
203 static const struct backlight_ops dell_uart_backlight_ops = {
204         .update_status = dell_uart_update_status,
205         .get_brightness = dell_uart_get_brightness_op,
206 };
207
208 static size_t dell_uart_bl_receive(struct serdev_device *serdev, const u8 *data, size_t len)
209 {
210         struct dell_uart_backlight *dell_bl = serdev_device_get_drvdata(serdev);
211         size_t i;
212         u8 csum;
213
214         dev_dbg(dell_bl->dev, "Recv: %*ph\n", (int)len, data);
215
216         /* Throw away unexpected bytes / remainder of response after an error */
217         if (dell_bl->status != -EBUSY) {
218                 dev_warn(dell_bl->dev, "Bytes received out of band, dropping them.\n");
219                 return len;
220         }
221
222         i = 0;
223         while (i < len && dell_bl->resp_idx != dell_bl->resp_len) {
224                 dell_bl->resp[dell_bl->resp_idx] = data[i++];
225
226                 switch (dell_bl->resp_idx) {
227                 case RESP_LEN: /* Length byte */
228                         dell_bl->resp_len = dell_bl->resp[RESP_LEN];
229                         if (dell_bl->resp_len < MIN_RESP_LEN ||
230                             dell_bl->resp_len > dell_bl->resp_max_len) {
231                                 dev_err(dell_bl->dev, "Response length %d out if range %d - %d\n",
232                                         dell_bl->resp_len, MIN_RESP_LEN, dell_bl->resp_max_len);
233                                 dell_bl->status = -EIO;
234                                 goto wakeup;
235                         }
236                         break;
237                 case RESP_CMD: /* CMD byte */
238                         if (dell_bl->resp[RESP_CMD] != dell_bl->pending_cmd) {
239                                 dev_err(dell_bl->dev, "Response cmd 0x%02x != pending 0x%02x\n",
240                                         dell_bl->resp[RESP_CMD], dell_bl->pending_cmd);
241                                 dell_bl->status = -EIO;
242                                 goto wakeup;
243                         }
244                         break;
245                 }
246                 dell_bl->resp_idx++;
247         }
248
249         if (dell_bl->resp_idx != dell_bl->resp_len)
250                 return len; /* Response not complete yet */
251
252         csum = dell_uart_checksum(dell_bl->resp, dell_bl->resp_len - 1);
253         if (dell_bl->resp[dell_bl->resp_len - 1] == csum) {
254                 dell_bl->status = 0; /* Success */
255         } else {
256                 dev_err(dell_bl->dev, "Checksum mismatch got 0x%02x expected 0x%02x\n",
257                         dell_bl->resp[dell_bl->resp_len - 1], csum);
258                 dell_bl->status = -EIO;
259         }
260 wakeup:
261         wake_up(&dell_bl->wait_queue);
262         return i;
263 }
264
265 static const struct serdev_device_ops dell_uart_bl_serdev_ops = {
266         .receive_buf = dell_uart_bl_receive,
267         .write_wakeup = serdev_device_write_wakeup,
268 };
269
270 static int dell_uart_bl_serdev_probe(struct serdev_device *serdev)
271 {
272         u8 get_version[GET_CMD_LEN], resp[MAX_RESP_LEN];
273         struct backlight_properties props = {};
274         struct dell_uart_backlight *dell_bl;
275         struct device *dev = &serdev->dev;
276         int ret;
277
278         dell_bl = devm_kzalloc(dev, sizeof(*dell_bl), GFP_KERNEL);
279         if (!dell_bl)
280                 return -ENOMEM;
281
282         mutex_init(&dell_bl->mutex);
283         init_waitqueue_head(&dell_bl->wait_queue);
284         dell_bl->dev = dev;
285
286         serdev_device_set_drvdata(serdev, dell_bl);
287         serdev_device_set_client_ops(serdev, &dell_uart_bl_serdev_ops);
288
289         ret = devm_serdev_device_open(dev, serdev);
290         if (ret)
291                 return dev_err_probe(dev, ret, "opening UART device\n");
292
293         /* 9600 bps, no flow control, these are the default but set them to be sure */
294         serdev_device_set_baudrate(serdev, 9600);
295         serdev_device_set_flow_control(serdev, false);
296
297         get_version[0] = DELL_SOF(GET_CMD_LEN);
298         get_version[1] = CMD_GET_VERSION;
299         get_version[2] = dell_uart_checksum(get_version, 2);
300
301         ret = dell_uart_bl_command(dell_bl, get_version, GET_CMD_LEN, resp, MAX_RESP_LEN);
302         if (ret)
303                 return dev_err_probe(dev, ret, "getting firmware version\n");
304
305         dev_dbg(dev, "Firmware version: %.*s\n", resp[RESP_LEN] - 3, resp + RESP_DATA);
306
307         /* Initialize bl_power to a known value */
308         ret = dell_uart_set_bl_power(dell_bl, FB_BLANK_UNBLANK);
309         if (ret)
310                 return ret;
311
312         ret = dell_uart_get_brightness(dell_bl);
313         if (ret < 0)
314                 return ret;
315
316         props.type = BACKLIGHT_PLATFORM;
317         props.brightness = ret;
318         props.max_brightness = DELL_BL_MAX_BRIGHTNESS;
319         props.power = dell_bl->power;
320
321         dell_bl->bl = devm_backlight_device_register(dev, "dell_uart_backlight",
322                                                      dev, dell_bl,
323                                                      &dell_uart_backlight_ops,
324                                                      &props);
325         return PTR_ERR_OR_ZERO(dell_bl->bl);
326 }
327
328 struct serdev_device_driver dell_uart_bl_serdev_driver = {
329         .probe = dell_uart_bl_serdev_probe,
330         .driver = {
331                 .name = KBUILD_MODNAME,
332         },
333 };
334
335 static int dell_uart_bl_pdev_probe(struct platform_device *pdev)
336 {
337         enum acpi_backlight_type bl_type;
338         struct serdev_device *serdev;
339         struct device *ctrl_dev;
340         int ret;
341
342         bl_type = acpi_video_get_backlight_type();
343         if (bl_type != acpi_backlight_dell_uart) {
344                 dev_dbg(&pdev->dev, "Not loading (ACPI backlight type = %d)\n", bl_type);
345                 return -ENODEV;
346         }
347
348         ctrl_dev = get_serdev_controller("DELL0501", NULL, 0, "serial0");
349         if (IS_ERR(ctrl_dev))
350                 return PTR_ERR(ctrl_dev);
351
352         serdev = serdev_device_alloc(to_serdev_controller(ctrl_dev));
353         put_device(ctrl_dev);
354         if (!serdev)
355                 return -ENOMEM;
356
357         ret = serdev_device_add(serdev);
358         if (ret) {
359                 dev_err(&pdev->dev, "error %d adding serdev\n", ret);
360                 serdev_device_put(serdev);
361                 return ret;
362         }
363
364         ret = serdev_device_driver_register(&dell_uart_bl_serdev_driver);
365         if (ret)
366                 goto err_remove_serdev;
367
368         /*
369          * serdev device <-> driver matching relies on OF or ACPI matches and
370          * neither is available here, manually bind the driver.
371          */
372         ret = device_driver_attach(&dell_uart_bl_serdev_driver.driver, &serdev->dev);
373         if (ret)
374                 goto err_unregister_serdev_driver;
375
376         /* So that dell_uart_bl_pdev_remove() can remove the serdev */
377         platform_set_drvdata(pdev, serdev);
378         return 0;
379
380 err_unregister_serdev_driver:
381         serdev_device_driver_unregister(&dell_uart_bl_serdev_driver);
382 err_remove_serdev:
383         serdev_device_remove(serdev);
384         return ret;
385 }
386
387 static void dell_uart_bl_pdev_remove(struct platform_device *pdev)
388 {
389         struct serdev_device *serdev = platform_get_drvdata(pdev);
390
391         serdev_device_driver_unregister(&dell_uart_bl_serdev_driver);
392         serdev_device_remove(serdev);
393 }
394
395 static struct platform_driver dell_uart_bl_pdev_driver = {
396         .probe = dell_uart_bl_pdev_probe,
397         .remove = dell_uart_bl_pdev_remove,
398         .driver = {
399                 .name = "dell-uart-backlight",
400         },
401 };
402 module_platform_driver(dell_uart_bl_pdev_driver);
403
404 MODULE_ALIAS("platform:dell-uart-backlight");
405 MODULE_DESCRIPTION("Dell AIO Serial Backlight driver");
406 MODULE_AUTHOR("Hans de Goede <[email protected]>");
407 MODULE_LICENSE("GPL");
This page took 0.054503 seconds and 4 git commands to generate.