From 7648c227da9c6b7d2a74647e38ada310e79af12a Mon Sep 17 00:00:00 2001 From: Mikhail Malyshev Date: Wed, 14 Aug 2024 15:09:20 +0000 Subject: [PATCH] Enable RS485 mode for USB_SERIAL_XR_RS485 The driver never supported switching to RS485 mode. Following steps must be taken: 1. Change flow control register to set Half/Full duplex 2. Enable GPIO5 as RTS and keep TX HI New 'mode' module parameter is introduced to list ports that have to be switched to RS485. The format is following mode=,... where f - full duplex, h - half duplex. If the port is not specified it is switched to RS232 mode Signed-off-by: Mikhail Malyshev --- drivers/usb/serial/xr_usb_serial_common.c | 127 ++++++++++++++++++++++ drivers/usb/serial/xr_usb_serial_common.h | 11 +- drivers/usb/serial/xr_usb_serial_hal.c | 34 ++++++ 3 files changed, 169 insertions(+), 3 deletions(-) diff --git a/drivers/usb/serial/xr_usb_serial_common.c b/drivers/usb/serial/xr_usb_serial_common.c index c713984570bd..89be254ee410 100644 --- a/drivers/usb/serial/xr_usb_serial_common.c +++ b/drivers/usb/serial/xr_usb_serial_common.c @@ -64,12 +64,90 @@ #include #endif +#undef CONFIG_GPIOLIB + #include "xr_usb_serial_common.h" #include "xr_usb_serial_ioctl.h" #define DRIVER_AUTHOR "" #define DRIVER_DESC "Exar/MxL USB UART (serial port) driver version 1G" +#define MAX_PORTS XR_USB_SERIAL_TTY_MINORS // Maximum number of ports allowed + +struct port_config { + int minor; + int full_duplex; // '0' for half-duplex, '1' for full-duplex +}; + +// Dynamically allocated array for storing port configurations +static struct port_config *port_configs = NULL; +static int num_ports_in_param = 0; + +static char *mode = NULL; +module_param(mode, charp, 0000); +MODULE_PARM_DESC(mode, "RS485 port mode (format: xh,xf,... where x is port number (zero based), h/f is half/full duplex). RS232 if not specified"); + +// Helper function to parse and store port configurations +static int parse_ports(const char *mode_str) +{ + char *str, *token, *temp; + int i = 0; + + // Duplicate the mode string to avoid modifying the original + temp = kstrdup(mode_str, GFP_KERNEL); + if (!temp) + return -ENOMEM; + + // Allocate memory for port configurations (up to MAX_PORTS) + port_configs = kmalloc_array(MAX_PORTS, sizeof(struct port_config), GFP_KERNEL); + if (!port_configs) { + pr_err("Memory allocation failed for port configurations\n"); + kfree(temp); + return -ENOMEM; + } + + // Split the mode string by commas and parse each part + str = temp; + while ((token = strsep(&str, ",")) != NULL) { + int port_num; + char duplex_mode; + + // Parse the port number and duplex mode (xh or xf) + if (sscanf(token, "%d%c", &port_num, &duplex_mode) != 2) { + pr_err("Invalid port format: %s\n", token); + kfree(port_configs); + kfree(temp); + return -EINVAL; + } + + // Validate duplex mode + if (duplex_mode != 'h' && duplex_mode != 'f') { + pr_err("Invalid duplex mode for port %d: %c\n", port_num, duplex_mode); + kfree(port_configs); + kfree(temp); + return -EINVAL; + } + + // Store the parsed port configuration + port_configs[i].minor = port_num; + port_configs[i].full_duplex = duplex_mode == 'f' ? 1 : 0; + i++; + + // Ensure we don't exceed the maximum number of ports + if (i >= MAX_PORTS) { + pr_err("Exceeded maximum number of ports (%d)\n", MAX_PORTS); + break; + } + } + + // Update the number of parsed ports + num_ports_in_param = i; + + kfree(temp); + return 0; // Success +} + + static struct usb_driver xr_usb_serial_driver; static struct tty_driver *xr_usb_serial_tty_driver; static struct xr_usb_serial *xr_usb_serial_table[XR_USB_SERIAL_TTY_MINORS]; @@ -1193,6 +1271,7 @@ static void xr_usb_serial_tty_set_termios(struct tty_struct *tty, newline.bDataBits); xr_usb_serial_set_line(xr_usb_serial, &xr_usb_serial->line); } + xr_usb_serial_set_rs485_mode(xr_usb_serial); xr_usb_serial_enable(xr_usb_serial); } @@ -1314,6 +1393,28 @@ static int xr_usb_gpio_dir_output(struct gpio_chip *chip, } #endif +static int xr_usb_set_port_mode_from_param(struct xr_usb_serial *xr_usb_serial) +{ + int i; + int index = -1; + for (i = 0; i < num_ports_in_param; i++) { + if (port_configs[i].minor == xr_usb_serial->minor) { + ndex = i; + break; + } + } + if (index == -1) { + //configure as 232 + xr_usb_serial->is_rs485 = 0; + } + else + { + xr_usb_serial->is_rs485 = 1; + xr_usb_serial->is_rs485_full_duplex = port_configs[i].full_duplex; + } + return 0; +} + static int xr_usb_serial_probe(struct usb_interface *intf, const struct usb_device_id *id) { @@ -1560,6 +1661,9 @@ static int xr_usb_serial_probe(struct usb_interface *intf, xr_usb_serial->control = control_interface; xr_usb_serial->data = data_interface; xr_usb_serial->minor = minor; + + xr_usb_set_port_mode_from_param(xr_usb_serial); + xr_usb_serial->dev = usb_dev; xr_usb_serial->ctrl_caps = ac_management_function; if (quirks & NO_CAP_LINE) @@ -2059,6 +2163,29 @@ static const struct tty_operations xr_usb_serial_ops = { static int __init xr_usb_serial_init(void) { int retval; + int i; + + if (mode) { + retval = parse_ports(mode); + if (retval) { + pr_err("xr_usb_serial: failed to parse mode parameter `%s'\n", mode); + return retval; + } + + for (i = 0; i < num_ports_in_param; i++) + { + pr_info("ttyXRUSB%d, RS485, duplex = %s\n", + port_configs[i].minor, port_configs[i].full_duplex ? 'Full' : 'Half'); + } + } + pr_info("xr_usb_serial: driver initialized with %d ports.\n", num_ports_in_param); + } + else + { + pr_info("xr_usb_serial: no mode parameter specified, using default settings.\n"); + } + + #if LINUX_VERSION_CODE < KERNEL_VERSION(5, 15, 0) xr_usb_serial_tty_driver = alloc_tty_driver(XR_USB_SERIAL_TTY_MINORS); #else diff --git a/drivers/usb/serial/xr_usb_serial_common.h b/drivers/usb/serial/xr_usb_serial_common.h index ddf3238c9d54..894fa069d672 100644 --- a/drivers/usb/serial/xr_usb_serial_common.h +++ b/drivers/usb/serial/xr_usb_serial_common.h @@ -161,6 +161,8 @@ struct xr_usb_serial { struct gpio_chip xr_gpio; int rv_gpio_created; #endif + bool is_rs485; + bool is_rs485_full_duplex; }; #define CDC_DATA_INTERFACE_TYPE 0x0a @@ -186,12 +188,15 @@ struct xr_usb_serial { #define LOOPBACK_ENABLE_RTS_CTS 2 #define LOOPBACK_ENABLE_DTR_DSR 4 -#define UART_FLOW_MODE_NONE 0x0 -#define UART_FLOW_MODE_HW 0x1 +#define UART_FLOW_MODE_NONE 0x0 /* no flow control, no address matching */ +#define UART_FLOW_MODE_HW 0x1 /* HW flow control enabled. Auto RTS/CTS or DTR/DSR must be selected by GPIO_MODE.*/ #define UART_FLOW_MODE_SW 0x2 +#define UART_FLOW_MODE_FULL_DUPLEX 0x0 +#define UART_FLOW_MODE_HALF_DUPLEX 0x8 #define UART_GPIO_MODE_SEL_GPIO 0x0 #define UART_GPIO_MODE_SEL_RTS_CTS 0x1 +#define UART_GPIO_MODE_GPIO5_RS485 0x3 /* GPIO5 used for auto RS-485 half-duplex control */ +#define UART_GPIO_MODE_SEL_RS485_TX_HI 0x8 #define XR2280x_FUNC_MGR_OFFSET 0x40 - diff --git a/drivers/usb/serial/xr_usb_serial_hal.c b/drivers/usb/serial/xr_usb_serial_hal.c index 2dc196c28cd2..f91287b090bf 100644 --- a/drivers/usb/serial/xr_usb_serial_hal.c +++ b/drivers/usb/serial/xr_usb_serial_hal.c @@ -500,6 +500,40 @@ int xr_usb_serial_set_flow_mode(struct xr_usb_serial *xr_usb_serial, struct tty_ xr_usb_serial_set_reg(xr_usb_serial, xr_usb_serial->reg_map.uart_flow_addr, flow); xr_usb_serial_set_reg(xr_usb_serial, xr_usb_serial->reg_map.uart_gpio_mode_addr, gpio_mode); + + return 0; +} + +int xr_usb_serial_set_rs485_mode(struct xr_usb_serial *xr_usb_serial) +{ + unsigned int flow; + unsigned int gpio_mode; + + if (xr_usb_serial->is_rs485) { + dev_info(&xr_usb_serial->control->dev, "xr_usb_serial_set_rs485_mode: RS485 mode on\n"); + + if (!xr_usb_serial->is_rs485_full_duplex) { + dev_info(&xr_usb_serial->control->dev, "xr_usb_serial_set_rs485_mode: RS485 Half-duplex mode\n"); + flow = UART_FLOW_MODE_HALF_DUPLEX; + } + else + { + dev_info(&xr_usb_serial->control->dev, "xr_usb_serial_set_rs485_mode: RS485 Full-duplex mode\n"); + flow = UART_FLOW_MODE_FULL_DUPLEX; + } + gpio_mode = UART_GPIO_MODE_GPIO5_RS485 | UART_GPIO_MODE_SEL_RS485_TX_HI; + + dev_info(&xr_usb_serial->control->dev, "xr_usb_serial_set_rs485_mode: flow mode:0x%04x\n",flow); + dev_info(&xr_usb_serial->control->dev, "xr_usb_serial_set_rs485_mode: GPIO mode:0x%04x\n",gpio_mode); + + xr_usb_serial_set_reg(xr_usb_serial, xr_usb_serial->reg_map.uart_flow_addr, flow); + xr_usb_serial_set_reg(xr_usb_serial, xr_usb_serial->reg_map.uart_gpio_mode_addr, gpio_mode); + } + else + { + dev_info(&xr_usb_serial->control->dev, "xr_usb_serial_set_rs485_mode: RS485 mode off\n"); + } + return 0; }