[PATCH u-boot-marvell 4/5] serial: a37xx: Switch to XTAL clock when booting Linux kernel

Marek Behún marek.behun at nic.cz
Tue May 25 19:42:41 CEST 2021


From: Pali Rohár <pali at kernel.org>

Unfortunately the UART driver in current Linux for Armada 3700 expects
UART's parent clock to be XTAL and calculats baudrate divisor according
to XTAL clock. Therefore we must switch back to XTAL clock before
booting kernel.

Implement .remove method for this driver with DM_FLAG_OS_PREPARE flag
set.

If current baudrate is unsuitable for XTAL clock then we do not change
anything. This can only happen if the user either configured unsupported
settings or knows what they are doing and has kernel patches which allow
usage of non-XTAL parent clock.

Signed-off-by: Pali Rohár <pali at kernel.org>
Reviewed-by: Marek Behún <marek.behun at nic.cz>
---
 drivers/serial/serial_mvebu_a3700.c | 67 +++++++++++++++++++++++++++++
 1 file changed, 67 insertions(+)

diff --git a/drivers/serial/serial_mvebu_a3700.c b/drivers/serial/serial_mvebu_a3700.c
index ba2ac5917f..c7e66fef87 100644
--- a/drivers/serial/serial_mvebu_a3700.c
+++ b/drivers/serial/serial_mvebu_a3700.c
@@ -204,6 +204,71 @@ static int mvebu_serial_probe(struct udevice *dev)
 	return 0;
 }
 
+static int mvebu_serial_remove(struct udevice *dev)
+{
+	struct mvebu_plat *plat = dev_get_plat(dev);
+	void __iomem *base = plat->base;
+	ulong new_parent_rate, parent_rate;
+	u32 new_divider, divider;
+	u32 new_oversampling;
+	u32 oversampling;
+	u32 d1, d2;
+
+	/*
+	 * Switch UART base clock back to XTAL because older Linux kernel
+	 * expects it. Otherwise it does not calculate UART divisor correctly
+	 * and therefore UART does not work in kernel.
+	 */
+	divider = readl(base + UART_BAUD_REG);
+	if (!(divider & BIT(19))) /* UART already uses XTAL */
+		return 0;
+
+	/* Read current divisors settings */
+	d1 = (divider >> 15) & 7;
+	d2 = (divider >> 12) & 7;
+	parent_rate = plat->tbg_rate;
+	divider &= 1023;
+	oversampling = readl(base + UART_POSSR_REG) & 63;
+	if (!oversampling)
+		oversampling = 16;
+
+	/* Calculate new divisor against XTAL clock without changing baudrate */
+	new_oversampling = 0;
+	new_parent_rate = get_ref_clk() * 1000000;
+	new_divider = DIV_ROUND_CLOSEST(new_parent_rate * divider * d1 * d2 *
+					oversampling, parent_rate * 16);
+
+	/*
+	 * UART does not work reliably when XTAL divisor is smaller than 4.
+	 * In this case we do not switch UART parent to XTAL. User either
+	 * configured unsupported settings or has newer kernel with patches
+	 * which allow usage of non-XTAL clock as a parent clock.
+	 */
+	if (new_divider < 4)
+		return 0;
+
+	/*
+	 * If new divisor is larger than maximal supported, try to switch
+	 * from default x16 scheme to oversampling with maximal factor 63.
+	 */
+	if (new_divider > 1023) {
+		new_oversampling = 63;
+		new_divider = DIV_ROUND_CLOSEST(new_parent_rate * divider * d1 *
+						d2 * oversampling,
+						parent_rate * new_oversampling);
+		if (new_divider < 4 || new_divider > 1023)
+			return 0;
+	}
+
+	while (!(readl(base + UART_STATUS_REG) & UART_STATUS_TX_EMPTY))
+		;
+
+	writel(new_divider, base + UART_BAUD_REG);
+	writel(new_oversampling, base + UART_POSSR_REG);
+
+	return 0;
+}
+
 static int mvebu_serial_of_to_plat(struct udevice *dev)
 {
 	struct mvebu_plat *plat = dev_get_plat(dev);
@@ -232,6 +297,8 @@ U_BOOT_DRIVER(serial_mvebu) = {
 	.of_to_plat = mvebu_serial_of_to_plat,
 	.plat_auto	= sizeof(struct mvebu_plat),
 	.probe	= mvebu_serial_probe,
+	.remove	= mvebu_serial_remove,
+	.flags	= DM_FLAG_OS_PREPARE,
 	.ops	= &mvebu_serial_ops,
 };
 
-- 
2.26.3



More information about the U-Boot mailing list