The patch implements TIOCSSERIAL ioctl call. The following fields of the
`struct serial_struct` are processed:
- flags: ASYNC_SPD_MASK bits are processed.
- baud_base: allow a user to specify arbitrary value less or equal the
maximum port speed. Use it later in conjunction with custom_divisor to
calculate the desired baud rate.
- custom_divisor: save a user supplied value, use it later for baud rate
calculation.
Custom baud rate may be applied using any combination of baud_base /
custom_divisor as follows:
# stty -F /dev/ttyUSBX 38400
# setserial /dev/ttyUSBX baud_base 1000 divisor 1 spd_cust # 1 kBaud
# setserial /dev/ttyUSBX baud_base 42000 divisor 42 spd_cust # 1 kBaud
The patch is based on the code from the drivers/usb/serial/ftdi_sio.c.
Signed-off-by: Peter Mamonov <[email protected]>
---
drivers/usb/serial/mos7840.c | 99 ++++++++++++++++++++++++++++++++++++++++++--
1 file changed, 95 insertions(+), 4 deletions(-)
diff --git a/drivers/usb/serial/mos7840.c b/drivers/usb/serial/mos7840.c
index e8669aae14b3..93093e27d6b5 100644
--- a/drivers/usb/serial/mos7840.c
+++ b/drivers/usb/serial/mos7840.c
@@ -244,6 +244,12 @@ struct moschip_port {
struct usb_ctrlrequest *led_dr;
unsigned long flags;
+
+ int serial_flags;
+ int baud_base;
+ int custom_divisor;
+
+ struct mutex cfg_lock;
};
/*
@@ -1554,6 +1560,7 @@ static int mos7840_tiocmset(struct tty_struct *tty,
* this function calculates the proper baud rate divisor for the specified
* baud rate.
*****************************************************************************/
+#define MAX_BAUD_RATE 3145728
static int mos7840_calc_baud_rate_divisor(struct usb_serial_port *port,
int baudRate, int *divisor,
__u16 *clk_sel_val)
@@ -1582,8 +1589,8 @@ static int mos7840_calc_baud_rate_divisor(struct
usb_serial_port *port,
} else if ((baudRate > 921600) && (baudRate <= 1572864)) {
*divisor = 1572864 / baudRate;
*clk_sel_val = 0x60;
- } else if ((baudRate > 1572864) && (baudRate <= 3145728)) {
- *divisor = 3145728 / baudRate;
+ } else if ((baudRate > 1572864) && (baudRate <= MAX_BAUD_RATE)) {
+ *divisor = MAX_BAUD_RATE / baudRate;
*clk_sel_val = 0x70;
}
return 0;
@@ -1733,6 +1740,8 @@ static void mos7840_change_port_settings(struct
tty_struct *tty,
return;
}
+ mutex_lock(&mos7840_port->cfg_lock);
+
lData = LCR_BITS_8;
lStop = LCR_STOP_1;
lParity = LCR_PAR_NONE;
@@ -1831,6 +1840,16 @@ static void mos7840_change_port_settings(struct
tty_struct *tty,
/* Determine divisor based on baud rate */
baud = tty_get_baud_rate(tty);
+ if (baud == 38400 &&
+ (mos7840_port->serial_flags & ASYNC_SPD_MASK) == ASYNC_SPD_CUST &&
+ mos7840_port->custom_divisor) {
+ baud = mos7840_port->baud_base / mos7840_port->custom_divisor;
+ dev_dbg(&port->dev, "Set custom baudrate (%d / %d) = %d",
+ mos7840_port->baud_base,
+ mos7840_port->custom_divisor,
+ baud);
+ }
+
if (!baud) {
/* pick a default, any default... */
dev_dbg(&port->dev, "%s", "Picked default baud...\n");
@@ -1855,6 +1874,8 @@ static void mos7840_change_port_settings(struct
tty_struct *tty,
}
dev_dbg(&port->dev, "%s - mos7840_port->shadowLCR is End %x\n",
__func__,
mos7840_port->shadowLCR);
+
+ mutex_unlock(&mos7840_port->cfg_lock);
}
/*****************************************************************************
@@ -1950,20 +1971,86 @@ static int mos7840_get_serial_info(struct moschip_port
*mos7840_port,
memset(&tmp, 0, sizeof(tmp));
+ mutex_lock(&mos7840_port->cfg_lock);
+
tmp.type = PORT_16550A;
tmp.line = mos7840_port->port->minor;
tmp.port = mos7840_port->port->port_number;
tmp.irq = 0;
+ tmp.flags = mos7840_port->serial_flags;
tmp.xmit_fifo_size = NUM_URBS * URB_TRANSFER_BUFFER_SIZE;
- tmp.baud_base = 9600;
+ tmp.baud_base = mos7840_port->baud_base;
+ tmp.custom_divisor = mos7840_port->custom_divisor;
tmp.close_delay = 5 * HZ;
tmp.closing_wait = 30 * HZ;
+ mutex_unlock(&mos7840_port->cfg_lock);
+
if (copy_to_user(retinfo, &tmp, sizeof(*retinfo)))
return -EFAULT;
return 0;
}
+static int mos7840_set_serial_info(struct tty_struct *tty,
+ struct moschip_port *priv,
+ struct serial_struct __user *newinfo)
+{
+ struct serial_struct new_serial;
+ int old_flags = priv->serial_flags;
+ int old_divisor = priv->custom_divisor;
+ int old_base = priv->baud_base;
+
+ if (copy_from_user(&new_serial, newinfo, sizeof(new_serial)))
+ return -EFAULT;
+ mutex_lock(&priv->cfg_lock);
+
+ if (!capable(CAP_SYS_ADMIN)) {
+ if (((new_serial.flags & ~ASYNC_USR_MASK) !=
+ (priv->serial_flags & ~ASYNC_USR_MASK))) {
+ mutex_unlock(&priv->cfg_lock);
+ return -EPERM;
+ }
+ priv->serial_flags = ((priv->serial_flags & ~ASYNC_USR_MASK) |
+ (new_serial.flags & ASYNC_USR_MASK));
+ priv->custom_divisor = new_serial.custom_divisor;
+ goto check_and_exit;
+ }
+
+ if (new_serial.baud_base > MAX_BAUD_RATE) {
+ mutex_unlock(&priv->cfg_lock);
+ return -EINVAL;
+ }
+ /* Save user supplied value, use it later to calculate the baudrate. */
+ priv->baud_base = new_serial.baud_base;
+
+ priv->serial_flags = ((priv->serial_flags & ~ASYNC_FLAGS) |
+ (new_serial.flags & ASYNC_FLAGS));
+ priv->custom_divisor = new_serial.custom_divisor;
+check_and_exit:
+ if ((priv->serial_flags & ASYNC_SPD_MASK) == ASYNC_SPD_HI)
+ tty->alt_speed = 57600;
+ else if ((priv->serial_flags & ASYNC_SPD_MASK) == ASYNC_SPD_VHI)
+ tty->alt_speed = 115200;
+ else if ((priv->serial_flags & ASYNC_SPD_MASK) == ASYNC_SPD_SHI)
+ tty->alt_speed = 230400;
+ else if ((priv->serial_flags & ASYNC_SPD_MASK) == ASYNC_SPD_WARP)
+ tty->alt_speed = 460800;
+ else
+ tty->alt_speed = 0;
+
+ if (((old_flags & ASYNC_SPD_MASK) !=
+ (priv->serial_flags & ASYNC_SPD_MASK)) ||
+ (((priv->serial_flags & ASYNC_SPD_MASK) == ASYNC_SPD_CUST) &&
+ ((old_divisor != priv->custom_divisor) ||
+ old_base != priv->baud_base))) {
+ mutex_unlock(&priv->cfg_lock);
+ mos7840_change_port_settings(tty, priv, &tty->termios);
+ }
+
+ mutex_unlock(&priv->cfg_lock);
+ return 0;
+}
+
/*****************************************************************************
* SerialIoctl
* this function handles any ioctl calls to the driver
@@ -1997,7 +2084,7 @@ static int mos7840_ioctl(struct tty_struct *tty,
case TIOCSSERIAL:
dev_dbg(&port->dev, "%s TIOCSSERIAL\n", __func__);
- break;
+ return mos7840_set_serial_info(tty, mos7840_port, argp);
default:
break;
}
@@ -2136,6 +2223,10 @@ static int mos7840_port_probe(struct usb_serial_port
*port)
if (!mos7840_port)
return -ENOMEM;
+ mutex_init(&mos7840_port->cfg_lock);
+ mos7840_port->baud_base = MAX_BAUD_RATE;
+ mos7840_port->custom_divisor = 1;
+
/* Initialize all port interrupt end point to port 0 int
* endpoint. Our device has only one interrupt end point
* common to all port */
--
2.11.0
--
To unsubscribe from this list: send the line "unsubscribe linux-usb" in
the body of a message to [email protected]
More majordomo info at http://vger.kernel.org/majordomo-info.html