
Add new functions which returns the nearest baudrate and use them instead of hardcoded and incomplete CONFIG_SYS_BAUDRATE_TABLE compile time option.
Add implementation of rounding function for serial_mvebu_a3700 driver and also for A3720 Espressobin board which has integrated pl2303 USB<->UART converter, which basically limits baudrates which can user set.
Completely remove CONFIG_SYS_BAUDRATE_TABLE defines from all A3720 boards as now with rounding functions it is not used anymore.
NOTE: This is just an example how to kill CONFIG_SYS_BAUDRATE_TABLE compile time definitions. I tested it that it works on A3720 Turris Mox board. I have not tested A3720 Espressobin board yet.
More discussion on this approach is required, so take this just as RFC change.
Signed-off-by: Pali Rohár pali@kernel.org --- board/Marvell/mvebu_armada-37xx/board.c | 28 ++++++++++ drivers/serial/serial-uclass.c | 71 +++++++++++++++++++++---- drivers/serial/serial_mvebu_a3700.c | 39 ++++++++++++++ include/configs/mvebu_armada-37xx.h | 8 --- include/configs/turris_mox.h | 8 --- include/serial.h | 24 +++++++++ 6 files changed, 152 insertions(+), 26 deletions(-)
diff --git a/board/Marvell/mvebu_armada-37xx/board.c b/board/Marvell/mvebu_armada-37xx/board.c index fdc873b1952f..0cd9ea6a1515 100644 --- a/board/Marvell/mvebu_armada-37xx/board.c +++ b/board/Marvell/mvebu_armada-37xx/board.c @@ -435,3 +435,31 @@ int ft_board_setup(void *blob, struct bd_info *bd) return 0; } #endif + +int board_round_baudrate(int baudrate) +{ + /* + * Espressobin has on-board pl2303 connected to A3720 UART. + * So calculate the final real baudrate supported by pl2303. + * Code from linux kernel function pl2303_encode_baud_rate_divisor() + * Exact formula is: baudrate = 12M * 32 / (mantissa * 4^exponent) + */ + unsigned int baseline, mantissa, exponent; + + baseline = 12000000 * 32; + mantissa = baseline / baudrate; + if (mantissa == 0) + mantissa = 1; + exponent = 0; + while (mantissa >= 512) { + if (exponent < 7) { + mantissa >>= 2; + exponent++; + } else { + mantissa = 511; + break; + } + } + + return (baseline / mantissa) >> (exponent << 1); +} diff --git a/drivers/serial/serial-uclass.c b/drivers/serial/serial-uclass.c index 8171b17faf88..0d725ed764e6 100644 --- a/drivers/serial/serial-uclass.c +++ b/drivers/serial/serial-uclass.c @@ -299,6 +299,20 @@ int serial_tstc(void) return _serial_tstc(gd->cur_serial_dev); }
+int serial_round_baudrate(int baudrate) +{ + struct dm_serial_ops *ops; + + if (!gd->cur_serial_dev) + return 0; + + ops = serial_get_ops(gd->cur_serial_dev); + if (!ops->round_baudrate) + return 0; + + return ops->round_baudrate(gd->cur_serial_dev, baudrate); +} + void serial_setbrg(void) { struct dm_serial_ops *ops; @@ -378,6 +392,11 @@ static int serial_stub_tstc(struct stdio_dev *sdev) } #endif
+int __weak board_round_baudrate(int baudrate) +{ + return 0; +} + /** * on_baudrate() - Update the actual baudrate when the env var changes * @@ -388,6 +407,8 @@ static int on_baudrate(const char *name, const char *value, enum env_op op, { int i; int baudrate; + int real_baudrate; + int board_baudrate;
switch (op) { case env_op_create: @@ -397,20 +418,50 @@ static int on_baudrate(const char *name, const char *value, enum env_op op, */ baudrate = dectoul(value, NULL);
- /* Not actually changing */ - if (gd->baudrate == baudrate) - return 0; + real_baudrate = serial_round_baudrate(baudrate);
- for (i = 0; i < ARRAY_SIZE(baudrate_table); ++i) { - if (baudrate == baudrate_table[i]) - break; - } - if (i == ARRAY_SIZE(baudrate_table)) { - if ((flags & H_FORCE) == 0) + if (real_baudrate) { + /* Baudrate is supported if is within 3% tolerance */ + if (100 * real_baudrate < baudrate * (100 - 3) || + 100 * real_baudrate > baudrate * (100 + 3)) { printf("## Baudrate %d bps not supported\n", baudrate); - return 1; + return 1; + } + baudrate = real_baudrate; + } else { + /* + * If round_baudrate() callback is unsupported then + * check supported baudrate against baudrate_table[] + */ + for (i = 0; i < ARRAY_SIZE(baudrate_table); ++i) { + if (baudrate == baudrate_table[i]) + break; + } + if (i == ARRAY_SIZE(baudrate_table)) { + if ((flags & H_FORCE) == 0) + printf("## Baudrate %d bps not supported\n", + baudrate); + return 1; + } + } + + board_baudrate = board_round_baudrate(baudrate); + + if (board_baudrate) { + /* Baudrate is supported if is within 3% tolerance */ + if (100 * board_baudrate < baudrate * (100 - 3) || + 100 * board_baudrate > baudrate * (100 + 3)) { + printf("## Baudrate %d bps not supported by the board\n", + baudrate); + return 1; + } } + + /* Not actually changing */ + if (gd->baudrate == baudrate) + return 0; + if ((flags & H_INTERACTIVE) != 0) { printf("## Switch baudrate to %d bps and press ENTER ...\n", baudrate); diff --git a/drivers/serial/serial_mvebu_a3700.c b/drivers/serial/serial_mvebu_a3700.c index 8dc4e5880e6b..d8bf3e97c486 100644 --- a/drivers/serial/serial_mvebu_a3700.c +++ b/drivers/serial/serial_mvebu_a3700.c @@ -147,6 +147,44 @@ static int mvebu_serial_setbrg(struct udevice *dev, int baudrate) return 0; }
+static int mvebu_serial_round_baudrate(struct udevice *dev, int baudrate) +{ + struct mvebu_plat *plat = dev_get_plat(dev); + u32 divider, d1, d2, m; + + m = 16; + d1 = d2 = 1; + divider = DIV_ROUND_CLOSEST(plat->tbg_rate, baudrate * d1 * d2 * m); + + if (divider < 1) + divider = 1; + else if (divider > 1023) { + d1 = 6; + divider = DIV_ROUND_CLOSEST(plat->tbg_rate, + baudrate * d1 * d2 * m); + if (divider < 1) + divider = 1; + else if (divider > 1023) { + d2 = 6; + divider = DIV_ROUND_CLOSEST(plat->tbg_rate, + baudrate * d1 * d2 * m); + if (divider < 1) + divider = 1; + else if (divider > 1023) { + m = 63; + divider = DIV_ROUND_CLOSEST(plat->tbg_rate, + baudrate * d1 * d2 * m); + if (divider < 1) + divider = 1; + else if (divider > 1023) + divider = 1023; + } + } + } + + return DIV_ROUND_CLOSEST(plat->tbg_rate, d1 * d2 * m * divider); +} + static int mvebu_serial_probe(struct udevice *dev) { struct mvebu_plat *plat = dev_get_plat(dev); @@ -335,6 +373,7 @@ static const struct dm_serial_ops mvebu_serial_ops = { .pending = mvebu_serial_pending, .getc = mvebu_serial_getc, .setbrg = mvebu_serial_setbrg, + .round_baudrate = mvebu_serial_round_baudrate, };
static const struct udevice_id mvebu_serial_ids[] = { diff --git a/include/configs/mvebu_armada-37xx.h b/include/configs/mvebu_armada-37xx.h index c8c34d7d92dd..6f7279f2ae4a 100644 --- a/include/configs/mvebu_armada-37xx.h +++ b/include/configs/mvebu_armada-37xx.h @@ -17,14 +17,6 @@
#define CONFIG_SYS_BOOTM_LEN SZ_64M /* Increase max gunzip size */
-#define CONFIG_SYS_BAUDRATE_TABLE { 300, 600, 1200, 1800, 2400, 4800, \ - 9600, 19200, 38400, 57600, 115200, \ - 230400, 460800, 500000, 576000, \ - 921600, 1000000, 1152000, 1500000, \ - 2000000, 2500000, 3000000, 3500000, \ - 4000000, 4500000, 5000000, 5500000, \ - 6000000 } - /* * For booting Linux, the board info and command line data * have to be in the first 8 MB of memory, since this is diff --git a/include/configs/turris_mox.h b/include/configs/turris_mox.h index 671283982356..03813613e193 100644 --- a/include/configs/turris_mox.h +++ b/include/configs/turris_mox.h @@ -22,14 +22,6 @@
/* auto boot */
-#define CONFIG_SYS_BAUDRATE_TABLE { 300, 600, 1200, 1800, 2400, 4800, \ - 9600, 19200, 38400, 57600, 115200, \ - 230400, 460800, 500000, 576000, \ - 921600, 1000000, 1152000, 1500000, \ - 2000000, 2500000, 3000000, 3500000, \ - 4000000, 4500000, 5000000, 5500000, \ - 6000000 } - /* * For booting Linux, the board info and command line data * have to be in the first 8 MB of memory, since this is diff --git a/include/serial.h b/include/serial.h index 6d1e62c6770c..5e8a7501b9a6 100644 --- a/include/serial.h +++ b/include/serial.h @@ -176,6 +176,30 @@ struct dm_serial_ops { * @return 0 if OK, -ve on error */ int (*setbrg)(struct udevice *dev, int baudrate); + /** + * round_baudrate() - Return the nearest available baudrate + * + * Return the exact baudrate value which would be set by setbrg() + * if is called with this @baudrate argument. So this function + * should return the nearest available baudrate. + * + * This function must not change baudrate generator. Its purpose + * is just to test if particular baudrate value is supported + * and to calculate baudrate tolerance. + * + * Caller may specify number 0 to retrieve the smallest possible + * supported baudrate and INT_MAX number to retrive the highest + * possible supported baudrate. + * + * Returning zero value is same as if this function is not + * implemented at all. Meaning that driver cannot predict what is + * the real final baudrate value. + * + * @dev: Device pointer + * @baudrate: Baudrate to round + * @return real baudrate value (see above) + */ + int (*round_baudrate)(struct udevice *dev, int baudrate); /** * getc() - Read a character and return it *