brintos

brintos / linux-shallow public Read only

0
0
Text · 11.0 KiB · 3995f90 Raw
407 lines · c
1// SPDX-License-Identifier: GPL-2.0-or-later2/*3 * Dell AIO Serial Backlight Driver4 *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 ": " fmt10 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	10029 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			335#define SET_CMD_LEN			436 37/* 2nd byte command */38#define CMD_GET_VERSION			0x0639#define CMD_SET_BRIGHTNESS		0x0b40#define CMD_GET_BRIGHTNESS		0x0c41#define CMD_SET_BL_POWER		0x0e42 43/* Indexes and other defines for response received from the controller */44#define RESP_LEN			045#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			349#define GET_RESP_LEN			450#define MIN_RESP_LEN			351#define MAX_RESP_LEN			8052 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 == 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 */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	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 327struct serdev_device_driver dell_uart_bl_serdev_driver = {328	.probe = dell_uart_bl_serdev_probe,329	.driver = {330		.name = KBUILD_MODNAME,331	},332};333 334static 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 and369	 * 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 379err_unregister_serdev_driver:380	serdev_device_driver_unregister(&dell_uart_bl_serdev_driver);381err_remove_serdev:382	serdev_device_remove(serdev);383	return ret;384}385 386static 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 394static 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};401module_platform_driver(dell_uart_bl_pdev_driver);402 403MODULE_ALIAS("platform:dell-uart-backlight");404MODULE_DESCRIPTION("Dell AIO Serial Backlight driver");405MODULE_AUTHOR("Hans de Goede <hansg@kernel.org>");406MODULE_LICENSE("GPL");407