Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

platform/x86: Add new Dell UART backlight driver

Dell All In One (AIO) models released after 2017 use a backlight controller
board connected to an UART.

In DSDT this uart port will be defined as:

Name (_HID, "DELL0501")
Name (_CID, EisaId ("PNP0501")

Instead of having a separate ACPI device with an UartSerialBusV2() resource
to model the backlight-controller, which would be the standard way to do
this.

The acpi_quirk_skip_serdev_enumeration() has special handling for this
and it will make the serial port code create a serdev controller device
for the UART instead of a /dev/ttyS0 char-dev. It will also create
a dell-uart-backlight driver platform device for this driver to bind too.

This new kernel module contains 2 drivers for this:

1. A simple platform driver which creates the actual serdev device
(with the serdev controller device as parent)

2. A serdev driver for the created serdev device which exports
the backlight functionality uses a standard backlight class device.

Reported-by: Roman Bogoyev <roman@computercheck.com.au>
Tested-by: Roman Bogoyev <roman@computercheck.com.au>
Tested-by: Kai-Heng Feng <kai.heng.feng@canonical.com>
Co-developed-by: AceLan Kao <acelan.kao@canonical.com>
Signed-off-by: AceLan Kao <acelan.kao@canonical.com>
Signed-off-by: Hans de Goede <hdegoede@redhat.com>
Link: https://lore.kernel.org/r/20240513144603.93874-2-hdegoede@redhat.com

+414
+15
drivers/platform/x86/dell/Kconfig
··· 145 145 To compile this driver as a module, choose M here: the module will 146 146 be called dell-smo8800. 147 147 148 + config DELL_UART_BACKLIGHT 149 + tristate "Dell AIO UART Backlight driver" 150 + depends on ACPI 151 + depends on BACKLIGHT_CLASS_DEVICE 152 + depends on SERIAL_DEV_BUS 153 + help 154 + Say Y here if you want to support Dell AIO UART backlight interface. 155 + The Dell AIO machines released after 2017 come with a UART interface 156 + to communicate with the backlight scalar board. This driver creates 157 + a standard backlight interface and talks to the scalar board through 158 + UART to adjust the AIO screen brightness. 159 + 160 + To compile this driver as a module, choose M here: the module will 161 + be called dell_uart_backlight. 162 + 148 163 config DELL_WMI 149 164 tristate "Dell WMI notifications" 150 165 default m
+1
drivers/platform/x86/dell/Makefile
··· 14 14 dell-smbios-$(CONFIG_DELL_SMBIOS_WMI) += dell-smbios-wmi.o 15 15 dell-smbios-$(CONFIG_DELL_SMBIOS_SMM) += dell-smbios-smm.o 16 16 obj-$(CONFIG_DELL_SMO8800) += dell-smo8800.o 17 + obj-$(CONFIG_DELL_UART_BACKLIGHT) += dell-uart-backlight.o 17 18 obj-$(CONFIG_DELL_WMI) += dell-wmi.o 18 19 dell-wmi-objs := dell-wmi-base.o 19 20 dell-wmi-$(CONFIG_DELL_WMI_PRIVACY) += dell-wmi-privacy.o
+398
drivers/platform/x86/dell/dell-uart-backlight.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 "../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 + 52 + struct 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 */ 67 + static 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 + 77 + static 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 + 110 + out: 111 + mutex_unlock(&dell_bl->mutex); 112 + return dell_bl->status; 113 + } 114 + 115 + static 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 + 127 + static 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 + 154 + static 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 + */ 177 + static int dell_uart_get_bl_power(struct dell_uart_backlight *dell_bl) 178 + { 179 + return dell_bl->power; 180 + } 181 + 182 + static 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 + 197 + static int dell_uart_get_brightness_op(struct backlight_device *bd) 198 + { 199 + return dell_uart_get_brightness(bl_get_data(bd)); 200 + } 201 + 202 + static 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 + 207 + static 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 + } 259 + wakeup: 260 + wake_up(&dell_bl->wait_queue); 261 + return i; 262 + } 263 + 264 + static 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 + 269 + static 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 + 326 + struct serdev_device_driver dell_uart_bl_serdev_driver = { 327 + .probe = dell_uart_bl_serdev_probe, 328 + .driver = { 329 + .name = KBUILD_MODNAME, 330 + }, 331 + }; 332 + 333 + static 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 + 371 + err_unregister_serdev_driver: 372 + serdev_device_driver_unregister(&dell_uart_bl_serdev_driver); 373 + err_remove_serdev: 374 + serdev_device_remove(serdev); 375 + return ret; 376 + } 377 + 378 + static 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 + 386 + static 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 + }; 393 + module_platform_driver(dell_uart_bl_pdev_driver); 394 + 395 + MODULE_ALIAS("platform:dell-uart-backlight"); 396 + MODULE_DESCRIPTION("Dell AIO Serial Backlight driver"); 397 + MODULE_AUTHOR("Hans de Goede <hansg@kernel.org>"); 398 + MODULE_LICENSE("GPL");