Commit 974e2f6a authored by Jarkko Sonninen's avatar Jarkko Sonninen Committed by Johan Hovold
Browse files

USB: serial: xr: add TIOCGRS485 and TIOCSRS485 ioctls



Exar devices like XR21B1411 can control an RS485 transceiver by
automatically asserting the RTS#/RS485 pin before sending data
and deasserting it when the last stop bit has been transmitted.
The polarity of the RST#/RS485 signal is configurable and the
hardware also supports half-duplex turn-around delay and
address matching mode.

Add support for enabling and disabling RS-485 mode and
configuring the RST#/RS485 signal polarity using the TIOCGRS485
and TIOCSRS485 ioctls. Support for half-duplex turn-around delay
and address matching mode are left unimplemented for now.

User enables RS-485 mode by setting SER_RS485_ENABLED flag in
struct serial_rs485 flags. User should also set either
SER_RS485_RTS_ON_SEND or SER_RS485_RTS_AFTER_SEND to select the
behaviour of the RTS#/RS485 pin. Setting SER_RS485_RTS_ON_SEND
will drive RTS#/RS485 low during transmission.

Signed-off-by: default avatarJarkko Sonninen <kasper@iki.fi>
[ johan: let SER_RS485_RTS_ON_SEND determine SER_RS485_RTS_AFTER_SEND ]
Signed-off-by: default avatarJohan Hovold <johan@kernel.org>
parent fdf0eaf1
Loading
Loading
Loading
Loading
+88 −1
Original line number Diff line number Diff line
@@ -93,6 +93,7 @@ struct xr_txrx_clk_mask {
#define XR_GPIO_MODE_SEL_DTR_DSR	0x2
#define XR_GPIO_MODE_SEL_RS485		0x3
#define XR_GPIO_MODE_SEL_RS485_ADDR	0x4
#define XR_GPIO_MODE_RS485_TX_H		0x8
#define XR_GPIO_MODE_TX_TOGGLE		0x100
#define XR_GPIO_MODE_RX_TOGGLE		0x200

@@ -237,6 +238,7 @@ static const struct xr_type xr_types[] = {
struct xr_data {
	const struct xr_type *type;
	u8 channel;			/* zero-based index or interface number */
	struct serial_rs485 rs485;
};

static int xr_set_reg(struct usb_serial_port *port, u8 channel, u16 reg, u16 val)
@@ -629,6 +631,7 @@ static void xr_set_flow_mode(struct tty_struct *tty,
	struct xr_data *data = usb_get_serial_port_data(port);
	const struct xr_type *type = data->type;
	u16 flow, gpio_mode;
	bool rs485_enabled;
	int ret;

	ret = xr_get_reg_uart(port, type->gpio_mode, &gpio_mode);
@@ -645,7 +648,17 @@ static void xr_set_flow_mode(struct tty_struct *tty,
	/* Set GPIO mode for controlling the pins manually by default. */
	gpio_mode &= ~XR_GPIO_MODE_SEL_MASK;

	if (C_CRTSCTS(tty) && C_BAUD(tty) != B0) {
	rs485_enabled = !!(data->rs485.flags & SER_RS485_ENABLED);
	if (rs485_enabled) {
		dev_dbg(&port->dev, "Enabling RS-485\n");
		gpio_mode |= XR_GPIO_MODE_SEL_RS485;
		if (data->rs485.flags & SER_RS485_RTS_ON_SEND)
			gpio_mode &= ~XR_GPIO_MODE_RS485_TX_H;
		else
			gpio_mode |= XR_GPIO_MODE_RS485_TX_H;
	}

	if (C_CRTSCTS(tty) && C_BAUD(tty) != B0 && !rs485_enabled) {
		dev_dbg(&port->dev, "Enabling hardware flow ctrl\n");
		gpio_mode |= XR_GPIO_MODE_SEL_RTS_CTS;
		flow = XR_UART_FLOW_MODE_HW;
@@ -809,6 +822,79 @@ static void xr_cdc_set_line_coding(struct tty_struct *tty,
	kfree(lc);
}

static void xr_sanitize_serial_rs485(struct serial_rs485 *rs485)
{
	if (!(rs485->flags & SER_RS485_ENABLED)) {
		memset(rs485, 0, sizeof(*rs485));
		return;
	}

	/* RTS always toggles after TX */
	if (rs485->flags & SER_RS485_RTS_ON_SEND)
		rs485->flags &= ~SER_RS485_RTS_AFTER_SEND;
	else
		rs485->flags |= SER_RS485_RTS_AFTER_SEND;

	/* Only the flags are implemented at the moment */
	rs485->flags &= SER_RS485_ENABLED | SER_RS485_RTS_ON_SEND |
			SER_RS485_RTS_AFTER_SEND;
	rs485->delay_rts_before_send = 0;
	rs485->delay_rts_after_send = 0;
	memset(rs485->padding, 0, sizeof(rs485->padding));
}

static int xr_get_rs485_config(struct tty_struct *tty,
			       struct serial_rs485 __user *argp)
{
	struct usb_serial_port *port = tty->driver_data;
	struct xr_data *data = usb_get_serial_port_data(port);

	down_read(&tty->termios_rwsem);
	if (copy_to_user(argp, &data->rs485, sizeof(data->rs485))) {
		up_read(&tty->termios_rwsem);
		return -EFAULT;
	}
	up_read(&tty->termios_rwsem);

	return 0;
}

static int xr_set_rs485_config(struct tty_struct *tty,
			       struct serial_rs485 __user *argp)
{
	struct usb_serial_port *port = tty->driver_data;
	struct xr_data *data = usb_get_serial_port_data(port);
	struct serial_rs485 rs485;

	if (copy_from_user(&rs485, argp, sizeof(rs485)))
		return -EFAULT;
	xr_sanitize_serial_rs485(&rs485);

	down_write(&tty->termios_rwsem);
	data->rs485 = rs485;
	xr_set_flow_mode(tty, port, NULL);
	up_write(&tty->termios_rwsem);

	if (copy_to_user(argp, &rs485, sizeof(rs485)))
		return -EFAULT;

	return 0;
}

static int xr_ioctl(struct tty_struct *tty, unsigned int cmd, unsigned long arg)
{
	void __user *argp = (void __user *)arg;

	switch (cmd) {
	case TIOCGRS485:
		return xr_get_rs485_config(tty, argp);
	case TIOCSRS485:
		return xr_set_rs485_config(tty, argp);
	}

	return -ENOIOCTLCMD;
}

static void xr_set_termios(struct tty_struct *tty,
			   struct usb_serial_port *port,
			   const struct ktermios *old_termios)
@@ -1010,6 +1096,7 @@ static struct usb_serial_driver xr_device = {
	.set_termios		= xr_set_termios,
	.tiocmget		= xr_tiocmget,
	.tiocmset		= xr_tiocmset,
	.ioctl			= xr_ioctl,
	.dtr_rts		= xr_dtr_rts
};