File Index
Files and directories
Sign in
FILE

dell-uart-backlight.c

File preview with syntax highlighting.

linux-6.19.14/drivers/platform/x86/dell/dell-uart-backlight.c 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 <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
 53struct 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 */
 68static 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
 78static 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
111out:
112	mutex_unlock(&dell_bl->mutex);
113	return dell_bl->status;
114}
115
116static 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
128static 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
155static 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 */
178static int dell_uart_get_bl_power(struct dell_uart_backlight *dell_bl)
179{
180	return dell_bl->power;
181}
182
183static 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
198static int dell_uart_get_brightness_op(struct backlight_device *bd)
199{
200	return dell_uart_get_brightness(bl_get_data(bd));
201}
202
203static 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
208static 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	}
260wakeup:
261	wake_up(&dell_bl->wait_queue);
262	return i;
263}
264
265static 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
270static 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, BACKLIGHT_POWER_ON);
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
328static 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
335static 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
380err_unregister_serdev_driver:
381	serdev_device_driver_unregister(&dell_uart_bl_serdev_driver);
382err_remove_serdev:
383	serdev_device_remove(serdev);
384	return ret;
385}
386
387static 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
395static 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};
402module_platform_driver(dell_uart_bl_pdev_driver);
403
404MODULE_ALIAS("platform:dell-uart-backlight");
405MODULE_DESCRIPTION("Dell AIO Serial Backlight driver");
406MODULE_AUTHOR("Hans de Goede <hansg@kernel.org>");
407MODULE_LICENSE("GPL");
~/ Read-only file preview.