[PATCH] USB: serial: mos7840: add support for setting custom baud rate

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



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 <pmamonov@xxxxxxxxx>
---
 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 majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html



[Index of Archives]     [Linux Media]     [Linux Input]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]     [Old Linux USB Devel Archive]

  Powered by Linux