Skip to content

Commit

Permalink
Enable RS485 mode for USB_SERIAL_XR_RS485
Browse files Browse the repository at this point in the history
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=<port index><f|h>,...  where f - full duplex, h - half duplex.
If the port is not specified it is switched to RS232 mode

Signed-off-by: Mikhail Malyshev <[email protected]>
  • Loading branch information
rucoder committed Sep 10, 2024
1 parent 190de40 commit 02642fe
Show file tree
Hide file tree
Showing 3 changed files with 167 additions and 3 deletions.
125 changes: 125 additions & 0 deletions drivers/usb/serial/xr_usb_serial_common.c
Original file line number Diff line number Diff line change
Expand Up @@ -64,12 +64,90 @@
#include <linux/compat.h>
#endif

#undef CONFIG_GPIOLIB

#include "xr_usb_serial_common.h"
#include "xr_usb_serial_ioctl.h"

#define DRIVER_AUTHOR "<[email protected]>"
#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];
Expand Down Expand Up @@ -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);
}

Expand Down Expand Up @@ -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) {
index = 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)
{
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -2059,6 +2163,27 @@ 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");
}
}
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
Expand Down
11 changes: 8 additions & 3 deletions drivers/usb/serial/xr_usb_serial_common.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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

34 changes: 34 additions & 0 deletions drivers/usb/serial/xr_usb_serial_hal.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down

0 comments on commit 02642fe

Please sign in to comment.