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