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