xref: /linux/drivers/platform/x86/dell/dell-uart-backlight.c (revision 9669b2499ea377764f8320dd562dd6cd4ea80a5d)
1484bae9eSHans de Goede // SPDX-License-Identifier: GPL-2.0-or-later
2484bae9eSHans de Goede /*
3484bae9eSHans de Goede  * Dell AIO Serial Backlight Driver
4484bae9eSHans de Goede  *
5484bae9eSHans de Goede  * Copyright (C) 2024 Hans de Goede <hansg@kernel.org>
6484bae9eSHans de Goede  * Copyright (C) 2017 AceLan Kao <acelan.kao@canonical.com>
7484bae9eSHans de Goede  */
8484bae9eSHans de Goede 
9484bae9eSHans de Goede #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
10484bae9eSHans de Goede 
11484bae9eSHans de Goede #include <linux/acpi.h>
12484bae9eSHans de Goede #include <linux/backlight.h>
13484bae9eSHans de Goede #include <linux/delay.h>
14484bae9eSHans de Goede #include <linux/device.h>
15484bae9eSHans de Goede #include <linux/err.h>
16484bae9eSHans de Goede #include <linux/module.h>
17484bae9eSHans de Goede #include <linux/mutex.h>
18484bae9eSHans de Goede #include <linux/platform_device.h>
19484bae9eSHans de Goede #include <linux/serdev.h>
20484bae9eSHans de Goede #include <linux/string.h>
21484bae9eSHans de Goede #include <linux/types.h>
22484bae9eSHans de Goede #include <linux/wait.h>
23b5f09430SHans de Goede #include <acpi/video.h>
24484bae9eSHans de Goede #include "../serdev_helpers.h"
25484bae9eSHans de Goede 
26484bae9eSHans de Goede /* The backlight controller must respond within 1 second */
27484bae9eSHans de Goede #define DELL_BL_TIMEOUT		msecs_to_jiffies(1000)
28484bae9eSHans de Goede #define DELL_BL_MAX_BRIGHTNESS	100
29484bae9eSHans de Goede 
30484bae9eSHans de Goede /* Defines for the commands send to the controller */
31484bae9eSHans de Goede 
32484bae9eSHans de Goede /* 1st byte Start Of Frame 3 MSB bits: cmd-len + 01010 SOF marker */
33484bae9eSHans de Goede #define DELL_SOF(len)			(((len) << 5) | 0x0a)
34484bae9eSHans de Goede #define GET_CMD_LEN			3
35484bae9eSHans de Goede #define SET_CMD_LEN			4
36484bae9eSHans de Goede 
37484bae9eSHans de Goede /* 2nd byte command */
38484bae9eSHans de Goede #define CMD_GET_VERSION			0x06
39484bae9eSHans de Goede #define CMD_SET_BRIGHTNESS		0x0b
40484bae9eSHans de Goede #define CMD_GET_BRIGHTNESS		0x0c
41484bae9eSHans de Goede #define CMD_SET_BL_POWER		0x0e
42484bae9eSHans de Goede 
43484bae9eSHans de Goede /* Indexes and other defines for response received from the controller */
44484bae9eSHans de Goede #define RESP_LEN			0
45484bae9eSHans de Goede #define RESP_CMD			1 /* Echo of CMD byte from command */
46484bae9eSHans de Goede #define RESP_DATA			2 /* Start of received data */
47484bae9eSHans de Goede 
48484bae9eSHans de Goede #define SET_RESP_LEN			3
49484bae9eSHans de Goede #define GET_RESP_LEN			4
50484bae9eSHans de Goede #define MIN_RESP_LEN			3
51484bae9eSHans de Goede #define MAX_RESP_LEN			80
52484bae9eSHans de Goede 
53484bae9eSHans de Goede struct dell_uart_backlight {
54484bae9eSHans de Goede 	struct mutex mutex;
55484bae9eSHans de Goede 	wait_queue_head_t wait_queue;
56484bae9eSHans de Goede 	struct device *dev;
57484bae9eSHans de Goede 	struct backlight_device *bl;
58484bae9eSHans de Goede 	u8 *resp;
59484bae9eSHans de Goede 	u8 resp_idx;
60484bae9eSHans de Goede 	u8 resp_len;
61484bae9eSHans de Goede 	u8 resp_max_len;
62484bae9eSHans de Goede 	u8 pending_cmd;
63484bae9eSHans de Goede 	int status;
64484bae9eSHans de Goede 	int power;
65484bae9eSHans de Goede };
66484bae9eSHans de Goede 
67484bae9eSHans de Goede /* Checksum: SUM(Length and Cmd and Data) xor 0xFF */
dell_uart_checksum(u8 * buf,int len)68484bae9eSHans de Goede static u8 dell_uart_checksum(u8 *buf, int len)
69484bae9eSHans de Goede {
70484bae9eSHans de Goede 	u8 val = 0;
71484bae9eSHans de Goede 
72484bae9eSHans de Goede 	while (len-- > 0)
73484bae9eSHans de Goede 		val += buf[len];
74484bae9eSHans de Goede 
75484bae9eSHans de Goede 	return val ^ 0xff;
76484bae9eSHans de Goede }
77484bae9eSHans de Goede 
dell_uart_bl_command(struct dell_uart_backlight * dell_bl,const u8 * cmd,int cmd_len,u8 * resp,int resp_max_len)78484bae9eSHans de Goede static int dell_uart_bl_command(struct dell_uart_backlight *dell_bl,
79484bae9eSHans de Goede 				const u8 *cmd, int cmd_len,
80484bae9eSHans de Goede 				u8 *resp, int resp_max_len)
81484bae9eSHans de Goede {
82484bae9eSHans de Goede 	int ret;
83484bae9eSHans de Goede 
84484bae9eSHans de Goede 	ret = mutex_lock_killable(&dell_bl->mutex);
85484bae9eSHans de Goede 	if (ret)
86484bae9eSHans de Goede 		return ret;
87484bae9eSHans de Goede 
88484bae9eSHans de Goede 	dell_bl->status = -EBUSY;
89484bae9eSHans de Goede 	dell_bl->resp = resp;
90484bae9eSHans de Goede 	dell_bl->resp_idx = 0;
91484bae9eSHans de Goede 	dell_bl->resp_len = -1; /* Invalid / unset */
92484bae9eSHans de Goede 	dell_bl->resp_max_len = resp_max_len;
93484bae9eSHans de Goede 	dell_bl->pending_cmd = cmd[1];
94484bae9eSHans de Goede 
95484bae9eSHans de Goede 	/* The TTY buffer should be big enough to take the entire cmd in one go */
96484bae9eSHans de Goede 	ret = serdev_device_write_buf(to_serdev_device(dell_bl->dev), cmd, cmd_len);
97484bae9eSHans de Goede 	if (ret != cmd_len) {
98484bae9eSHans de Goede 		dev_err(dell_bl->dev, "Error writing command: %d\n", ret);
99484bae9eSHans de Goede 		dell_bl->status = (ret < 0) ? ret : -EIO;
100484bae9eSHans de Goede 		goto out;
101484bae9eSHans de Goede 	}
102484bae9eSHans de Goede 
103484bae9eSHans de Goede 	ret = wait_event_timeout(dell_bl->wait_queue, dell_bl->status != -EBUSY,
104484bae9eSHans de Goede 				 DELL_BL_TIMEOUT);
105484bae9eSHans de Goede 	if (ret == 0) {
106484bae9eSHans de Goede 		dev_err(dell_bl->dev, "Timed out waiting for response.\n");
107484bae9eSHans de Goede 		/* Clear busy status to discard bytes received after this */
108484bae9eSHans de Goede 		dell_bl->status = -ETIMEDOUT;
109484bae9eSHans de Goede 	}
110484bae9eSHans de Goede 
111484bae9eSHans de Goede out:
112484bae9eSHans de Goede 	mutex_unlock(&dell_bl->mutex);
113484bae9eSHans de Goede 	return dell_bl->status;
114484bae9eSHans de Goede }
115484bae9eSHans de Goede 
dell_uart_set_brightness(struct dell_uart_backlight * dell_bl,int brightness)116484bae9eSHans de Goede static int dell_uart_set_brightness(struct dell_uart_backlight *dell_bl, int brightness)
117484bae9eSHans de Goede {
118484bae9eSHans de Goede 	u8 set_brightness[SET_CMD_LEN], resp[SET_RESP_LEN];
119484bae9eSHans de Goede 
120484bae9eSHans de Goede 	set_brightness[0] = DELL_SOF(SET_CMD_LEN);
121484bae9eSHans de Goede 	set_brightness[1] = CMD_SET_BRIGHTNESS;
122484bae9eSHans de Goede 	set_brightness[2] = brightness;
123484bae9eSHans de Goede 	set_brightness[3] = dell_uart_checksum(set_brightness, 3);
124484bae9eSHans de Goede 
125484bae9eSHans de Goede 	return dell_uart_bl_command(dell_bl, set_brightness, SET_CMD_LEN, resp, SET_RESP_LEN);
126484bae9eSHans de Goede }
127484bae9eSHans de Goede 
dell_uart_get_brightness(struct dell_uart_backlight * dell_bl)128484bae9eSHans de Goede static int dell_uart_get_brightness(struct dell_uart_backlight *dell_bl)
129484bae9eSHans de Goede {
130484bae9eSHans de Goede 	struct device *dev = dell_bl->dev;
131484bae9eSHans de Goede 	u8 get_brightness[GET_CMD_LEN], resp[GET_RESP_LEN];
132484bae9eSHans de Goede 	int ret;
133484bae9eSHans de Goede 
134484bae9eSHans de Goede 	get_brightness[0] = DELL_SOF(GET_CMD_LEN);
135484bae9eSHans de Goede 	get_brightness[1] = CMD_GET_BRIGHTNESS;
136484bae9eSHans de Goede 	get_brightness[2] = dell_uart_checksum(get_brightness, 2);
137484bae9eSHans de Goede 
138484bae9eSHans de Goede 	ret = dell_uart_bl_command(dell_bl, get_brightness, GET_CMD_LEN, resp, GET_RESP_LEN);
139484bae9eSHans de Goede 	if (ret)
140484bae9eSHans de Goede 		return ret;
141484bae9eSHans de Goede 
142484bae9eSHans de Goede 	if (resp[RESP_LEN] != GET_RESP_LEN) {
143484bae9eSHans de Goede 		dev_err(dev, "Unexpected get brightness response length: %d\n", resp[RESP_LEN]);
144484bae9eSHans de Goede 		return -EIO;
145484bae9eSHans de Goede 	}
146484bae9eSHans de Goede 
147484bae9eSHans de Goede 	if (resp[RESP_DATA] > DELL_BL_MAX_BRIGHTNESS) {
148484bae9eSHans de Goede 		dev_err(dev, "Unexpected get brightness response: %d\n", resp[RESP_DATA]);
149484bae9eSHans de Goede 		return -EIO;
150484bae9eSHans de Goede 	}
151484bae9eSHans de Goede 
152484bae9eSHans de Goede 	return resp[RESP_DATA];
153484bae9eSHans de Goede }
154484bae9eSHans de Goede 
dell_uart_set_bl_power(struct dell_uart_backlight * dell_bl,int power)155484bae9eSHans de Goede static int dell_uart_set_bl_power(struct dell_uart_backlight *dell_bl, int power)
156484bae9eSHans de Goede {
157484bae9eSHans de Goede 	u8 set_power[SET_CMD_LEN], resp[SET_RESP_LEN];
158484bae9eSHans de Goede 	int ret;
159484bae9eSHans de Goede 
160484bae9eSHans de Goede 	set_power[0] = DELL_SOF(SET_CMD_LEN);
161484bae9eSHans de Goede 	set_power[1] = CMD_SET_BL_POWER;
162a2ec08e4SThomas Zimmermann 	set_power[2] = (power == BACKLIGHT_POWER_ON) ? 1 : 0;
163484bae9eSHans de Goede 	set_power[3] = dell_uart_checksum(set_power, 3);
164484bae9eSHans de Goede 
165484bae9eSHans de Goede 	ret = dell_uart_bl_command(dell_bl, set_power, SET_CMD_LEN, resp, SET_RESP_LEN);
166484bae9eSHans de Goede 	if (ret)
167484bae9eSHans de Goede 		return ret;
168484bae9eSHans de Goede 
169484bae9eSHans de Goede 	dell_bl->power = power;
170484bae9eSHans de Goede 	return 0;
171484bae9eSHans de Goede }
172484bae9eSHans de Goede 
173484bae9eSHans de Goede /*
174484bae9eSHans de Goede  * There is no command to get backlight power status,
175484bae9eSHans de Goede  * so we set the backlight power to "on" while initializing,
176484bae9eSHans de Goede  * and then track and report its status by power variable.
177484bae9eSHans de Goede  */
dell_uart_get_bl_power(struct dell_uart_backlight * dell_bl)178484bae9eSHans de Goede static int dell_uart_get_bl_power(struct dell_uart_backlight *dell_bl)
179484bae9eSHans de Goede {
180484bae9eSHans de Goede 	return dell_bl->power;
181484bae9eSHans de Goede }
182484bae9eSHans de Goede 
dell_uart_update_status(struct backlight_device * bd)183484bae9eSHans de Goede static int dell_uart_update_status(struct backlight_device *bd)
184484bae9eSHans de Goede {
185484bae9eSHans de Goede 	struct dell_uart_backlight *dell_bl = bl_get_data(bd);
186484bae9eSHans de Goede 	int ret;
187484bae9eSHans de Goede 
188484bae9eSHans de Goede 	ret = dell_uart_set_brightness(dell_bl, bd->props.brightness);
189484bae9eSHans de Goede 	if (ret)
190484bae9eSHans de Goede 		return ret;
191484bae9eSHans de Goede 
192484bae9eSHans de Goede 	if (bd->props.power != dell_uart_get_bl_power(dell_bl))
193484bae9eSHans de Goede 		return dell_uart_set_bl_power(dell_bl, bd->props.power);
194484bae9eSHans de Goede 
195484bae9eSHans de Goede 	return 0;
196484bae9eSHans de Goede }
197484bae9eSHans de Goede 
dell_uart_get_brightness_op(struct backlight_device * bd)198484bae9eSHans de Goede static int dell_uart_get_brightness_op(struct backlight_device *bd)
199484bae9eSHans de Goede {
200484bae9eSHans de Goede 	return dell_uart_get_brightness(bl_get_data(bd));
201484bae9eSHans de Goede }
202484bae9eSHans de Goede 
203484bae9eSHans de Goede static const struct backlight_ops dell_uart_backlight_ops = {
204484bae9eSHans de Goede 	.update_status = dell_uart_update_status,
205484bae9eSHans de Goede 	.get_brightness = dell_uart_get_brightness_op,
206484bae9eSHans de Goede };
207484bae9eSHans de Goede 
dell_uart_bl_receive(struct serdev_device * serdev,const u8 * data,size_t len)208484bae9eSHans de Goede static size_t dell_uart_bl_receive(struct serdev_device *serdev, const u8 *data, size_t len)
209484bae9eSHans de Goede {
210484bae9eSHans de Goede 	struct dell_uart_backlight *dell_bl = serdev_device_get_drvdata(serdev);
211484bae9eSHans de Goede 	size_t i;
212484bae9eSHans de Goede 	u8 csum;
213484bae9eSHans de Goede 
214484bae9eSHans de Goede 	dev_dbg(dell_bl->dev, "Recv: %*ph\n", (int)len, data);
215484bae9eSHans de Goede 
216484bae9eSHans de Goede 	/* Throw away unexpected bytes / remainder of response after an error */
217484bae9eSHans de Goede 	if (dell_bl->status != -EBUSY) {
218484bae9eSHans de Goede 		dev_warn(dell_bl->dev, "Bytes received out of band, dropping them.\n");
219484bae9eSHans de Goede 		return len;
220484bae9eSHans de Goede 	}
221484bae9eSHans de Goede 
222484bae9eSHans de Goede 	i = 0;
223484bae9eSHans de Goede 	while (i < len && dell_bl->resp_idx != dell_bl->resp_len) {
224484bae9eSHans de Goede 		dell_bl->resp[dell_bl->resp_idx] = data[i++];
225484bae9eSHans de Goede 
226484bae9eSHans de Goede 		switch (dell_bl->resp_idx) {
227484bae9eSHans de Goede 		case RESP_LEN: /* Length byte */
228484bae9eSHans de Goede 			dell_bl->resp_len = dell_bl->resp[RESP_LEN];
229484bae9eSHans de Goede 			if (dell_bl->resp_len < MIN_RESP_LEN ||
230484bae9eSHans de Goede 			    dell_bl->resp_len > dell_bl->resp_max_len) {
231484bae9eSHans de Goede 				dev_err(dell_bl->dev, "Response length %d out if range %d - %d\n",
232484bae9eSHans de Goede 					dell_bl->resp_len, MIN_RESP_LEN, dell_bl->resp_max_len);
233484bae9eSHans de Goede 				dell_bl->status = -EIO;
234484bae9eSHans de Goede 				goto wakeup;
235484bae9eSHans de Goede 			}
236484bae9eSHans de Goede 			break;
237484bae9eSHans de Goede 		case RESP_CMD: /* CMD byte */
238484bae9eSHans de Goede 			if (dell_bl->resp[RESP_CMD] != dell_bl->pending_cmd) {
239484bae9eSHans de Goede 				dev_err(dell_bl->dev, "Response cmd 0x%02x != pending 0x%02x\n",
240484bae9eSHans de Goede 					dell_bl->resp[RESP_CMD], dell_bl->pending_cmd);
241484bae9eSHans de Goede 				dell_bl->status = -EIO;
242484bae9eSHans de Goede 				goto wakeup;
243484bae9eSHans de Goede 			}
244484bae9eSHans de Goede 			break;
245484bae9eSHans de Goede 		}
246484bae9eSHans de Goede 		dell_bl->resp_idx++;
247484bae9eSHans de Goede 	}
248484bae9eSHans de Goede 
249484bae9eSHans de Goede 	if (dell_bl->resp_idx != dell_bl->resp_len)
250484bae9eSHans de Goede 		return len; /* Response not complete yet */
251484bae9eSHans de Goede 
252484bae9eSHans de Goede 	csum = dell_uart_checksum(dell_bl->resp, dell_bl->resp_len - 1);
253484bae9eSHans de Goede 	if (dell_bl->resp[dell_bl->resp_len - 1] == csum) {
254484bae9eSHans de Goede 		dell_bl->status = 0; /* Success */
255484bae9eSHans de Goede 	} else {
256484bae9eSHans de Goede 		dev_err(dell_bl->dev, "Checksum mismatch got 0x%02x expected 0x%02x\n",
257484bae9eSHans de Goede 			dell_bl->resp[dell_bl->resp_len - 1], csum);
258484bae9eSHans de Goede 		dell_bl->status = -EIO;
259484bae9eSHans de Goede 	}
260484bae9eSHans de Goede wakeup:
261484bae9eSHans de Goede 	wake_up(&dell_bl->wait_queue);
262484bae9eSHans de Goede 	return i;
263484bae9eSHans de Goede }
264484bae9eSHans de Goede 
265484bae9eSHans de Goede static const struct serdev_device_ops dell_uart_bl_serdev_ops = {
266484bae9eSHans de Goede 	.receive_buf = dell_uart_bl_receive,
267484bae9eSHans de Goede 	.write_wakeup = serdev_device_write_wakeup,
268484bae9eSHans de Goede };
269484bae9eSHans de Goede 
dell_uart_bl_serdev_probe(struct serdev_device * serdev)270484bae9eSHans de Goede static int dell_uart_bl_serdev_probe(struct serdev_device *serdev)
271484bae9eSHans de Goede {
272484bae9eSHans de Goede 	u8 get_version[GET_CMD_LEN], resp[MAX_RESP_LEN];
273484bae9eSHans de Goede 	struct backlight_properties props = {};
274484bae9eSHans de Goede 	struct dell_uart_backlight *dell_bl;
275484bae9eSHans de Goede 	struct device *dev = &serdev->dev;
276484bae9eSHans de Goede 	int ret;
277484bae9eSHans de Goede 
278484bae9eSHans de Goede 	dell_bl = devm_kzalloc(dev, sizeof(*dell_bl), GFP_KERNEL);
279484bae9eSHans de Goede 	if (!dell_bl)
280484bae9eSHans de Goede 		return -ENOMEM;
281484bae9eSHans de Goede 
282484bae9eSHans de Goede 	mutex_init(&dell_bl->mutex);
283484bae9eSHans de Goede 	init_waitqueue_head(&dell_bl->wait_queue);
284484bae9eSHans de Goede 	dell_bl->dev = dev;
285484bae9eSHans de Goede 
2861b2128aaSChenyuan Yang 	serdev_device_set_drvdata(serdev, dell_bl);
2871b2128aaSChenyuan Yang 	serdev_device_set_client_ops(serdev, &dell_uart_bl_serdev_ops);
2881b2128aaSChenyuan Yang 
289484bae9eSHans de Goede 	ret = devm_serdev_device_open(dev, serdev);
290484bae9eSHans de Goede 	if (ret)
291484bae9eSHans de Goede 		return dev_err_probe(dev, ret, "opening UART device\n");
292484bae9eSHans de Goede 
293484bae9eSHans de Goede 	/* 9600 bps, no flow control, these are the default but set them to be sure */
294484bae9eSHans de Goede 	serdev_device_set_baudrate(serdev, 9600);
295484bae9eSHans de Goede 	serdev_device_set_flow_control(serdev, false);
296484bae9eSHans de Goede 
297484bae9eSHans de Goede 	get_version[0] = DELL_SOF(GET_CMD_LEN);
298484bae9eSHans de Goede 	get_version[1] = CMD_GET_VERSION;
299484bae9eSHans de Goede 	get_version[2] = dell_uart_checksum(get_version, 2);
300484bae9eSHans de Goede 
301484bae9eSHans de Goede 	ret = dell_uart_bl_command(dell_bl, get_version, GET_CMD_LEN, resp, MAX_RESP_LEN);
302484bae9eSHans de Goede 	if (ret)
303484bae9eSHans de Goede 		return dev_err_probe(dev, ret, "getting firmware version\n");
304484bae9eSHans de Goede 
305484bae9eSHans de Goede 	dev_dbg(dev, "Firmware version: %.*s\n", resp[RESP_LEN] - 3, resp + RESP_DATA);
306484bae9eSHans de Goede 
307484bae9eSHans de Goede 	/* Initialize bl_power to a known value */
308*cf685b38SThomas Zimmermann 	ret = dell_uart_set_bl_power(dell_bl, BACKLIGHT_POWER_ON);
309484bae9eSHans de Goede 	if (ret)
310484bae9eSHans de Goede 		return ret;
311484bae9eSHans de Goede 
312484bae9eSHans de Goede 	ret = dell_uart_get_brightness(dell_bl);
313484bae9eSHans de Goede 	if (ret < 0)
314484bae9eSHans de Goede 		return ret;
315484bae9eSHans de Goede 
316484bae9eSHans de Goede 	props.type = BACKLIGHT_PLATFORM;
317484bae9eSHans de Goede 	props.brightness = ret;
318484bae9eSHans de Goede 	props.max_brightness = DELL_BL_MAX_BRIGHTNESS;
319484bae9eSHans de Goede 	props.power = dell_bl->power;
320484bae9eSHans de Goede 
321484bae9eSHans de Goede 	dell_bl->bl = devm_backlight_device_register(dev, "dell_uart_backlight",
322484bae9eSHans de Goede 						     dev, dell_bl,
323484bae9eSHans de Goede 						     &dell_uart_backlight_ops,
324484bae9eSHans de Goede 						     &props);
325484bae9eSHans de Goede 	return PTR_ERR_OR_ZERO(dell_bl->bl);
326484bae9eSHans de Goede }
327484bae9eSHans de Goede 
3284878e0b1SIlpo Järvinen static struct serdev_device_driver dell_uart_bl_serdev_driver = {
329484bae9eSHans de Goede 	.probe = dell_uart_bl_serdev_probe,
330484bae9eSHans de Goede 	.driver = {
331484bae9eSHans de Goede 		.name = KBUILD_MODNAME,
332484bae9eSHans de Goede 	},
333484bae9eSHans de Goede };
334484bae9eSHans de Goede 
dell_uart_bl_pdev_probe(struct platform_device * pdev)335484bae9eSHans de Goede static int dell_uart_bl_pdev_probe(struct platform_device *pdev)
336484bae9eSHans de Goede {
337b5f09430SHans de Goede 	enum acpi_backlight_type bl_type;
338484bae9eSHans de Goede 	struct serdev_device *serdev;
339484bae9eSHans de Goede 	struct device *ctrl_dev;
340484bae9eSHans de Goede 	int ret;
341484bae9eSHans de Goede 
342b5f09430SHans de Goede 	bl_type = acpi_video_get_backlight_type();
343b5f09430SHans de Goede 	if (bl_type != acpi_backlight_dell_uart) {
344b5f09430SHans de Goede 		dev_dbg(&pdev->dev, "Not loading (ACPI backlight type = %d)\n", bl_type);
345b5f09430SHans de Goede 		return -ENODEV;
346b5f09430SHans de Goede 	}
347b5f09430SHans de Goede 
348484bae9eSHans de Goede 	ctrl_dev = get_serdev_controller("DELL0501", NULL, 0, "serial0");
349484bae9eSHans de Goede 	if (IS_ERR(ctrl_dev))
350484bae9eSHans de Goede 		return PTR_ERR(ctrl_dev);
351484bae9eSHans de Goede 
352484bae9eSHans de Goede 	serdev = serdev_device_alloc(to_serdev_controller(ctrl_dev));
353484bae9eSHans de Goede 	put_device(ctrl_dev);
354484bae9eSHans de Goede 	if (!serdev)
355484bae9eSHans de Goede 		return -ENOMEM;
356484bae9eSHans de Goede 
357484bae9eSHans de Goede 	ret = serdev_device_add(serdev);
358484bae9eSHans de Goede 	if (ret) {
359484bae9eSHans de Goede 		dev_err(&pdev->dev, "error %d adding serdev\n", ret);
360484bae9eSHans de Goede 		serdev_device_put(serdev);
361484bae9eSHans de Goede 		return ret;
362484bae9eSHans de Goede 	}
363484bae9eSHans de Goede 
364484bae9eSHans de Goede 	ret = serdev_device_driver_register(&dell_uart_bl_serdev_driver);
365484bae9eSHans de Goede 	if (ret)
366484bae9eSHans de Goede 		goto err_remove_serdev;
367484bae9eSHans de Goede 
368484bae9eSHans de Goede 	/*
369484bae9eSHans de Goede 	 * serdev device <-> driver matching relies on OF or ACPI matches and
370484bae9eSHans de Goede 	 * neither is available here, manually bind the driver.
371484bae9eSHans de Goede 	 */
372484bae9eSHans de Goede 	ret = device_driver_attach(&dell_uart_bl_serdev_driver.driver, &serdev->dev);
373484bae9eSHans de Goede 	if (ret)
374484bae9eSHans de Goede 		goto err_unregister_serdev_driver;
375484bae9eSHans de Goede 
376484bae9eSHans de Goede 	/* So that dell_uart_bl_pdev_remove() can remove the serdev */
377484bae9eSHans de Goede 	platform_set_drvdata(pdev, serdev);
378484bae9eSHans de Goede 	return 0;
379484bae9eSHans de Goede 
380484bae9eSHans de Goede err_unregister_serdev_driver:
381484bae9eSHans de Goede 	serdev_device_driver_unregister(&dell_uart_bl_serdev_driver);
382484bae9eSHans de Goede err_remove_serdev:
383484bae9eSHans de Goede 	serdev_device_remove(serdev);
384484bae9eSHans de Goede 	return ret;
385484bae9eSHans de Goede }
386484bae9eSHans de Goede 
dell_uart_bl_pdev_remove(struct platform_device * pdev)387484bae9eSHans de Goede static void dell_uart_bl_pdev_remove(struct platform_device *pdev)
388484bae9eSHans de Goede {
389484bae9eSHans de Goede 	struct serdev_device *serdev = platform_get_drvdata(pdev);
390484bae9eSHans de Goede 
391484bae9eSHans de Goede 	serdev_device_driver_unregister(&dell_uart_bl_serdev_driver);
392484bae9eSHans de Goede 	serdev_device_remove(serdev);
393484bae9eSHans de Goede }
394484bae9eSHans de Goede 
395484bae9eSHans de Goede static struct platform_driver dell_uart_bl_pdev_driver = {
396484bae9eSHans de Goede 	.probe = dell_uart_bl_pdev_probe,
3973ea5eb68SUwe Kleine-König 	.remove = dell_uart_bl_pdev_remove,
398484bae9eSHans de Goede 	.driver = {
399484bae9eSHans de Goede 		.name = "dell-uart-backlight",
400484bae9eSHans de Goede 	},
401484bae9eSHans de Goede };
402484bae9eSHans de Goede module_platform_driver(dell_uart_bl_pdev_driver);
403484bae9eSHans de Goede 
404484bae9eSHans de Goede MODULE_ALIAS("platform:dell-uart-backlight");
405484bae9eSHans de Goede MODULE_DESCRIPTION("Dell AIO Serial Backlight driver");
406484bae9eSHans de Goede MODULE_AUTHOR("Hans de Goede <hansg@kernel.org>");
407484bae9eSHans de Goede MODULE_LICENSE("GPL");
408