xref: /linux/drivers/platform/x86/dell/dell-uart-backlight.c (revision b09f6ca99c46e4a561ac943253aca9beae8c1146)
1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3  * Dell AIO Serial Backlight Driver
4  *
5  * Copyright (C) 2024 Hans de Goede <hansg@kernel.org>
6  * Copyright (C) 2017 AceLan Kao <acelan.kao@canonical.com>
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 */
dell_uart_checksum(u8 * buf,int len)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 
dell_uart_bl_command(struct dell_uart_backlight * dell_bl,const u8 * cmd,int cmd_len,u8 * resp,int resp_max_len)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 
dell_uart_set_brightness(struct dell_uart_backlight * dell_bl,int brightness)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 
dell_uart_get_brightness(struct dell_uart_backlight * dell_bl)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 
dell_uart_set_bl_power(struct dell_uart_backlight * dell_bl,int power)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 == FB_BLANK_UNBLANK) ? 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  */
dell_uart_get_bl_power(struct dell_uart_backlight * dell_bl)178 static int dell_uart_get_bl_power(struct dell_uart_backlight *dell_bl)
179 {
180 	return dell_bl->power;
181 }
182 
dell_uart_update_status(struct backlight_device * bd)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 
dell_uart_get_brightness_op(struct backlight_device * bd)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 
dell_uart_bl_receive(struct serdev_device * serdev,const u8 * data,size_t len)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 
dell_uart_bl_serdev_probe(struct serdev_device * serdev)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 	ret = devm_serdev_device_open(dev, serdev);
287 	if (ret)
288 		return dev_err_probe(dev, ret, "opening UART device\n");
289 
290 	/* 9600 bps, no flow control, these are the default but set them to be sure */
291 	serdev_device_set_baudrate(serdev, 9600);
292 	serdev_device_set_flow_control(serdev, false);
293 	serdev_device_set_drvdata(serdev, dell_bl);
294 	serdev_device_set_client_ops(serdev, &dell_uart_bl_serdev_ops);
295 
296 	get_version[0] = DELL_SOF(GET_CMD_LEN);
297 	get_version[1] = CMD_GET_VERSION;
298 	get_version[2] = dell_uart_checksum(get_version, 2);
299 
300 	ret = dell_uart_bl_command(dell_bl, get_version, GET_CMD_LEN, resp, MAX_RESP_LEN);
301 	if (ret)
302 		return dev_err_probe(dev, ret, "getting firmware version\n");
303 
304 	dev_dbg(dev, "Firmware version: %.*s\n", resp[RESP_LEN] - 3, resp + RESP_DATA);
305 
306 	/* Initialize bl_power to a known value */
307 	ret = dell_uart_set_bl_power(dell_bl, FB_BLANK_UNBLANK);
308 	if (ret)
309 		return ret;
310 
311 	ret = dell_uart_get_brightness(dell_bl);
312 	if (ret < 0)
313 		return ret;
314 
315 	props.type = BACKLIGHT_PLATFORM;
316 	props.brightness = ret;
317 	props.max_brightness = DELL_BL_MAX_BRIGHTNESS;
318 	props.power = dell_bl->power;
319 
320 	dell_bl->bl = devm_backlight_device_register(dev, "dell_uart_backlight",
321 						     dev, dell_bl,
322 						     &dell_uart_backlight_ops,
323 						     &props);
324 	return PTR_ERR_OR_ZERO(dell_bl->bl);
325 }
326 
327 struct serdev_device_driver dell_uart_bl_serdev_driver = {
328 	.probe = dell_uart_bl_serdev_probe,
329 	.driver = {
330 		.name = KBUILD_MODNAME,
331 	},
332 };
333 
dell_uart_bl_pdev_probe(struct platform_device * pdev)334 static int dell_uart_bl_pdev_probe(struct platform_device *pdev)
335 {
336 	enum acpi_backlight_type bl_type;
337 	struct serdev_device *serdev;
338 	struct device *ctrl_dev;
339 	int ret;
340 
341 	bl_type = acpi_video_get_backlight_type();
342 	if (bl_type != acpi_backlight_dell_uart) {
343 		dev_dbg(&pdev->dev, "Not loading (ACPI backlight type = %d)\n", bl_type);
344 		return -ENODEV;
345 	}
346 
347 	ctrl_dev = get_serdev_controller("DELL0501", NULL, 0, "serial0");
348 	if (IS_ERR(ctrl_dev))
349 		return PTR_ERR(ctrl_dev);
350 
351 	serdev = serdev_device_alloc(to_serdev_controller(ctrl_dev));
352 	put_device(ctrl_dev);
353 	if (!serdev)
354 		return -ENOMEM;
355 
356 	ret = serdev_device_add(serdev);
357 	if (ret) {
358 		dev_err(&pdev->dev, "error %d adding serdev\n", ret);
359 		serdev_device_put(serdev);
360 		return ret;
361 	}
362 
363 	ret = serdev_device_driver_register(&dell_uart_bl_serdev_driver);
364 	if (ret)
365 		goto err_remove_serdev;
366 
367 	/*
368 	 * serdev device <-> driver matching relies on OF or ACPI matches and
369 	 * neither is available here, manually bind the driver.
370 	 */
371 	ret = device_driver_attach(&dell_uart_bl_serdev_driver.driver, &serdev->dev);
372 	if (ret)
373 		goto err_unregister_serdev_driver;
374 
375 	/* So that dell_uart_bl_pdev_remove() can remove the serdev */
376 	platform_set_drvdata(pdev, serdev);
377 	return 0;
378 
379 err_unregister_serdev_driver:
380 	serdev_device_driver_unregister(&dell_uart_bl_serdev_driver);
381 err_remove_serdev:
382 	serdev_device_remove(serdev);
383 	return ret;
384 }
385 
dell_uart_bl_pdev_remove(struct platform_device * pdev)386 static void dell_uart_bl_pdev_remove(struct platform_device *pdev)
387 {
388 	struct serdev_device *serdev = platform_get_drvdata(pdev);
389 
390 	serdev_device_driver_unregister(&dell_uart_bl_serdev_driver);
391 	serdev_device_remove(serdev);
392 }
393 
394 static struct platform_driver dell_uart_bl_pdev_driver = {
395 	.probe = dell_uart_bl_pdev_probe,
396 	.remove_new = dell_uart_bl_pdev_remove,
397 	.driver = {
398 		.name = "dell-uart-backlight",
399 	},
400 };
401 module_platform_driver(dell_uart_bl_pdev_driver);
402 
403 MODULE_ALIAS("platform:dell-uart-backlight");
404 MODULE_DESCRIPTION("Dell AIO Serial Backlight driver");
405 MODULE_AUTHOR("Hans de Goede <hansg@kernel.org>");
406 MODULE_LICENSE("GPL");
407