Hi all, I'm working on a backlight driver which read/write commands through standard UART port, but got some issues. 1. The UART port defined in BIOS/DSDT as HID="DELL0501" and CID="PNP0501" and uses address 0x3F8 and IRQ 4, so the port will be created by 8250_pnp driver as ttyS0. Name (_HID, "DELL0501") // _HID: Hardware ID Name (_CID, EisaId ("PNP0501") /* 16550A-compatible COM Serial Port */) // _CID: Compatible ID Name (_DDN, "COM5") // _DDN: DOS Device Name 2. I can send/receive commands from userspace app by opening /dev/ttyS0 to change the backlight brightness. This makes sure the UART channel works. 3. What I want is to implement a driver which create a backlight interface, so userspace GUI can access it without changing any code. The driver is as attached. 4. The issue is that, I have to open and close the "/dev/ttyS0" before inserting my driver. It can be done by userspace app(on some system, systemd check/open every serial ports automatically after booting up) or done in the driver by filp_open(). If the port(ttyS0) doesn't be opened before inserting my driver, I can't read any data from UART. And after inserting my driver, the ttyS0 won't respond any commands I sent from userspace app. I have no idea why it doesn't work, and I'm not pretty sure if I did it correct. Please help me to check it. Thanks. Best regards, AceLan Kao.
/* * Dell AIO Serial Backlight Driver * * Copyright (C) 2017 AceLan Kao <acelan.kao@xxxxxxxxxxxxx> * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * */ #include <linux/kernel.h> #include <linux/module.h> #include <linux/platform_device.h> #include <linux/acpi.h> #include <linux/serial_8250.h> #include <linux/delay.h> #include <linux/backlight.h> #include <acpi/video.h> #include "dell-uart-backlight.h" struct dell_uart_backlight { struct backlight_device *dell_uart_bd; struct uart_8250_port uart; int line; int bl_power; }; static unsigned int io_serial_in(struct uart_port *p, int offset) { unsigned int val; offset = offset << p->regshift; val = inb(p->iobase + offset); return val; } static void io_serial_out(struct uart_port *p, int offset, int value) { offset = offset << p->regshift; outb(value&0xff, p->iobase + offset); } static int dell_uart_write(struct uart_8250_port *up, __u8 *buf, int len) { int fifo_size, actual = 0; int retry = 10; unsigned long flags; fifo_size = up->port.fifosize; spin_lock_irqsave(&up->port.lock, flags); while (retry-- > 0) { /* Tx FIFO should be empty */ if (!(io_serial_in(&up->port, UART_LSR) & UART_LSR_THRE)) { pr_debug("TX FIFO is not empty!!!\n"); msleep(20); } else break; } /* Fill FIFO with current frame */ while ((fifo_size-- > 0) && (actual < len)) { /* Transmit next byte */ io_serial_out(&up->port, UART_TX, buf[actual]); actual++; } spin_unlock_irqrestore(&up->port.lock, flags); return actual; } static int dell_uart_read(struct uart_8250_port *up, __u8 *buf, int len) { int i, retry; unsigned long flags; spin_lock_irqsave(&up->port.lock, flags); for (i = 0; i < len; i++) { retry = 10; while (!(io_serial_in(&up->port, UART_LSR) & UART_LSR_DR)) { if (--retry == 0) break; msleep(20); } if (retry == 0) break; buf[i] = io_serial_in(&up->port, UART_RX); } spin_unlock_irqrestore(&up->port.lock, flags); return i; } static void dell_uart_dump_cmd(const char *func, const char *prefix, const char *cmd, int len) { char buf[80]; snprintf(buf, 80, "dell_uart_backlight:%s:%s", func, prefix); print_hex_dump_debug(buf, DUMP_PREFIX_NONE, 16, 1, cmd, len, false); } /* * checksum: SUM(Length and Cmd and Data)xor 0xFF) */ static unsigned char dell_uart_checksum(unsigned char *buf, int len) { unsigned char val = 0; while (len-- > 0) val += buf[len]; return val ^ 0xff; } /* * There is no command to get backlight power status, * so we set the backlight power to "on" while initializing, * and then track and report its status by bl_power variable */ static int dell_uart_get_bl_power(struct dell_uart_backlight *dell_pdata) { return dell_pdata->bl_power; } static int dell_uart_set_bl_power(struct backlight_device *bd, int power) { struct dell_uart_bl_cmd *bl_cmd = &uart_cmd[DELL_UART_SET_BACKLIGHT_POWER]; struct dell_uart_backlight *dell_pdata = bl_get_data(bd); if (power != FB_BLANK_POWERDOWN) power = FB_BLANK_UNBLANK; dell_uart_dump_cmd(__func__, "tx: ", bl_cmd->cmd, bl_cmd->tx_len); bl_cmd->cmd[2] = power?0:1; bl_cmd->cmd[3] = dell_uart_checksum(bl_cmd->cmd, bl_cmd->tx_len - 1); dell_uart_write(&dell_pdata->uart, bl_cmd->cmd, bl_cmd->tx_len); dell_uart_read(&dell_pdata->uart, bl_cmd->ret, bl_cmd->rx_len); dell_uart_dump_cmd(__func__, "rx: ", bl_cmd->ret, bl_cmd->rx_len); bd->props.power = power; dell_pdata->bl_power = power; return 0; } static int dell_uart_get_brightness(struct backlight_device *bd) { struct dell_uart_bl_cmd *bl_cmd = &uart_cmd[DELL_UART_GET_BRIGHTNESS]; struct dell_uart_backlight *dell_pdata = bl_get_data(bd); int brightness = 0; dell_uart_dump_cmd(__func__, "tx: ", bl_cmd->cmd, bl_cmd->tx_len); dell_uart_write(&dell_pdata->uart, bl_cmd->cmd, bl_cmd->tx_len); dell_uart_read(&dell_pdata->uart, bl_cmd->ret, bl_cmd->rx_len); dell_uart_dump_cmd(__func__, "rx: ", bl_cmd->ret, bl_cmd->rx_len); brightness = (unsigned int)bl_cmd->ret[2]; return brightness; } static int dell_uart_update_status(struct backlight_device *bd) { struct dell_uart_bl_cmd *bl_cmd = &uart_cmd[DELL_UART_SET_BRIGHTNESS]; struct dell_uart_backlight *dell_pdata = bl_get_data(bd); bl_cmd->cmd[2] = bd->props.brightness; bl_cmd->cmd[3] = dell_uart_checksum(bl_cmd->cmd, bl_cmd->tx_len - 1); dell_uart_dump_cmd(__func__, "tx: ", bl_cmd->cmd, bl_cmd->tx_len); dell_uart_write(&dell_pdata->uart, bl_cmd->cmd, bl_cmd->tx_len); dell_uart_read(&dell_pdata->uart, bl_cmd->ret, bl_cmd->rx_len); dell_uart_dump_cmd(__func__, "rx: ", bl_cmd->ret, bl_cmd->rx_len); if (bd->props.power != dell_uart_get_bl_power(dell_pdata)) dell_uart_set_bl_power(bd, bd->props.power); return 0; } static void dell_uart_show_firmware_ver(struct dell_uart_backlight *dell_pdata) { struct dell_uart_bl_cmd *bl_cmd = &uart_cmd[DELL_UART_GET_FIRMWARE_VER]; int retry = 10; dell_uart_dump_cmd(__func__, "tx: ", bl_cmd->cmd, bl_cmd->tx_len); dell_uart_write(&dell_pdata->uart, bl_cmd->cmd, bl_cmd->tx_len); while (retry-- > 0) { /* first byte is data length */ dell_uart_read(&dell_pdata->uart, bl_cmd->ret, 1); if (bl_cmd->ret[0] > 80 || bl_cmd->ret[0] <= 0) { pr_debug("Failed to get firmware version\n"); if (retry == 0) return; msleep(1000); continue; } bl_cmd->rx_len = (int)bl_cmd->ret[0]; dell_uart_read(&dell_pdata->uart, bl_cmd->ret+1, (int)bl_cmd->ret[0]-1); } dell_uart_dump_cmd(__func__, "rx: ", bl_cmd->ret, bl_cmd->rx_len); pr_debug("Firmare str(%d)= %s\n", (int)bl_cmd->ret[0], bl_cmd->ret+2); } static const struct backlight_ops dell_uart_backlight_ops = { .get_brightness = dell_uart_get_brightness, .update_status = dell_uart_update_status, }; static int dell_uart_startup(struct dell_uart_backlight *dell_pdata) { struct uart_8250_port *uart = &dell_pdata->uart; struct uart_port *port = &uart->port; memset(uart, 0, sizeof(*uart)); port->uartclk = 9600*16; port->iobase = 0x3f8; port->irq = 4; port->iotype = UPIO_PORT; port->flags = UPF_BOOT_AUTOCONF | UPF_SKIP_TEST; port->fifosize = 16; dell_pdata->line = serial8250_register_8250_port(uart); /* * the sleep is necessary after serial8250_register_8250_port() * or it'll fail to initialize the UART port and the following * io access will be failed. */ msleep(1000); /* Clear FIFO */ io_serial_out(port, UART_FCR, UART_FCR_ENABLE_FIFO | UART_FCR_CLEAR_RCVR | UART_FCR_CLEAR_XMIT); /* initialize the UART */ io_serial_out(port, UART_LCR, UART_LCR_WLEN8); return 0; } static int dell_uart_bl_add(struct acpi_device *dev) { struct dell_uart_backlight *dell_pdata; struct backlight_properties props; struct backlight_device *dell_uart_bd; dell_pdata = kzalloc(sizeof(struct dell_uart_backlight), GFP_KERNEL); dell_uart_startup(dell_pdata); dev->driver_data = dell_pdata; memset(&props, 0, sizeof(struct backlight_properties)); props.type = BACKLIGHT_PLATFORM; props.max_brightness = 100; dell_uart_bd = backlight_device_register("dell_uart_backlight", &dev->dev, dell_pdata, &dell_uart_backlight_ops, &props); dell_pdata->dell_uart_bd = dell_uart_bd; dell_uart_show_firmware_ver(dell_pdata); dell_uart_set_bl_power(dell_uart_bd, FB_BLANK_UNBLANK); backlight_update_status(dell_uart_bd); /* unregister acpi backlight interface */ acpi_video_set_dmi_backlight_type(acpi_backlight_vendor); return 0; } static int dell_uart_bl_remove(struct acpi_device *dev) { struct dell_uart_backlight *dell_pdata = dev->driver_data; backlight_device_unregister(dell_pdata->dell_uart_bd); serial8250_unregister_port(dell_pdata->line); return 0; } static const struct acpi_device_id dell_uart_bl_ids[] = { {"DELL0501", 0}, {"", 0}, }; static struct acpi_driver dell_uart_backlight_driver = { .name = "Dell AIO serial backlight", .ids = dell_uart_bl_ids, .ops = { .add = dell_uart_bl_add, .remove = dell_uart_bl_remove, }, }; static int __init dell_uart_bl_init(void) { return acpi_bus_register_driver(&dell_uart_backlight_driver); } static void __exit dell_uart_bl_exit(void) { acpi_bus_unregister_driver(&dell_uart_backlight_driver); } module_init(dell_uart_bl_init); module_exit(dell_uart_bl_exit); MODULE_DEVICE_TABLE(acpi, dell_uart_bl_ids); MODULE_DESCRIPTION("Dell AIO Serial Backlight module"); MODULE_AUTHOR("AceLan Kao <acelan.kao@xxxxxxxxxxxxx>"); MODULE_LICENSE("GPL");
/* * Dell AIO Serial Backlight Driver * * Copyright (C) 2017 AceLan Kao <acelan.kao@xxxxxxxxxxxxx> * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * */ #ifndef _DELL_UART_BACKLIGHT_H_ #define _DELL_UART_BACKLIGHT_H_ enum { DELL_UART_GET_FIRMWARE_VER, DELL_UART_GET_BRIGHTNESS, DELL_UART_SET_BRIGHTNESS, DELL_UART_SET_BACKLIGHT_POWER, }; struct dell_uart_bl_cmd { unsigned char cmd[10]; unsigned char ret[80]; unsigned short tx_len; unsigned short rx_len; }; static struct dell_uart_bl_cmd uart_cmd[] = { /* * Get Firmware Version: command to get firmware version. * Command: 0x6A 0x06 0x8F * (Length: 3 (The 3 MSB(bit5 ~ bit7) indicate the data length) * Type: 0x0A, Cmd: 6, Checksum: 0x8F) * Return data: 0x0D 0x06 Data checksum * (Length: 13, * Cmd: 0x06, * Data: F/W version(APRILIA=APR27-VXXX,PHINE=PHI23-VXXX), * checksum: SUM(Length and Cmd and Data) xor 0xFF) */ [DELL_UART_GET_FIRMWARE_VER] = { .cmd = {0x6A, 0x06, 0x8F}, .tx_len = 3, }, /* * Get Brightness level: command for scaler to get brightness. * Command: 0x6A 0x0C 0x89 * (Length: 3 (The 3 MSB(bit5 ~ bit7) indicate the data length) * Type: 0x0A, Cmd: 0x0C, Checksum: 0x89) * Return data: 0x04 0x0C Data checksum * (Length: 4 * Cmd: 0x0C * Data: brightness level(ranges from 0~100) * checksum: SUM(Length and Cmd and Data)xor 0xFF) */ [DELL_UART_GET_BRIGHTNESS] = { .cmd = {0x6A, 0x0C, 0x89}, .ret = {0x04, 0x0C, 0x00, 0x00}, .tx_len = 3, .rx_len = 4, }, /* Set Brightness level: command for scaler to set brightness. * Command: 0x8A 0x0B Byte2 Checksum * (Length: 4, Type: 0x0A, Cmd: 0x0B) * where Byte2 is the brightness level which ranges from 0~100 * Return data: 0x03 0x0B 0xF1 * (Length: 3, Cmd: 0x0B, checksum: 0xF1) */ [DELL_UART_SET_BRIGHTNESS] = { .cmd = {0x8A, 0x0B, 0x0, 0x0}, .ret = {0x03, 0x0B, 0xF1}, .tx_len = 4, .rx_len = 3, }, /* * Screen ON/OFF Control: command to control screen ON or OFF. * Command: 0x8A 0x0E Byte2 Checksum * (Length: 4, Type: 0x0A, Cmd:0x0E) * Byte2 = 0 to turn OFF the screen. * Byte2 = 1 to turn ON the screen * Other value of Byte2 is reserved and invalid. * Return data: 0x03 0x0E 0xEE * (Length: 3, Cmd: 0x0E, checksum: 0xEE) */ [DELL_UART_SET_BACKLIGHT_POWER] = { .cmd = {0x8A, 0x0E, 0x00, 0x0}, .ret = {0x03, 0x0E, 0xEE}, .tx_len = 4, .rx_len = 3, }, }; #endif /* _DELL_UART_BACKLIGHT_H_ */