Add new code/drivers for QC/A WiSoCs
authorPiotr Dymacz <pepe2k@gmail.com>
Fri, 13 Nov 2015 01:17:48 +0000 (02:17 +0100)
committerPiotr Dymacz <pepe2k@gmail.com>
Fri, 13 Nov 2015 01:17:48 +0000 (02:17 +0100)
New, more universal and clean code for:
- High Speed UART (old AR933x UART driver)
- Low Speed UART (for AR934x and QCA95xx)
- system clocks calculation
- SOC version and revision

Work still in progress (some TODO left in code)

u-boot/cpu/mips/ar7240/qca_clocks.c [new file with mode: 0644]
u-boot/cpu/mips/ar7240/qca_common.c [new file with mode: 0644]
u-boot/cpu/mips/ar7240/qca_hs_uart.c [new file with mode: 0644]
u-boot/cpu/mips/ar7240/qca_ls_uart.c [new file with mode: 0644]

diff --git a/u-boot/cpu/mips/ar7240/qca_clocks.c b/u-boot/cpu/mips/ar7240/qca_clocks.c
new file mode 100644 (file)
index 0000000..e52be09
--- /dev/null
@@ -0,0 +1,338 @@
+/*
+ * Qualcomm/Atheros system clocks related functions
+ *
+ * Copyright (C) 2015 Piotr Dymacz <piotr@dymacz.pl>
+ *
+ * Partially based on:
+ * Linux/arch/mips/ath79/clock.c
+ *
+ * SPDX-License-Identifier:GPL-2.0
+ */
+
+#include <config.h>
+#include <common.h>
+#include <asm/io.h>
+#include <asm/addrspace.h>
+#include <soc/qca_soc_common.h>
+
+/* QCA system clocks */
+static u32 qca_cpu_clk;
+static u32 qca_ddr_clk;
+static u32 qca_ahb_clk;
+static u32 qca_spi_clk;
+static u32 qca_ref_clk;
+
+/*
+ * Calculates and returns PLL value
+ * TODO: check for overflow!
+ */
+static u32 qca_get_pll(u32 ref_clk,
+                                          u32 refdiv,
+                                          u32 nfrac,
+                                          u32 nfracdiv,
+                                          u32 nint,
+                                          u32 outdiv)
+{
+       u64 pll_mul;
+       u64 pll_div;
+
+       pll_mul = ref_clk;
+       pll_div = refdiv;
+
+       if (pll_div == 0)
+               pll_div = 1;
+
+       if (nfrac > 0) {
+               pll_mul = pll_mul * ((nint * nfracdiv) + nfrac);
+               pll_div = pll_div * nfracdiv;
+       } else {
+               pll_mul = pll_mul * nint;
+       }
+
+       pll_div = pll_div << outdiv;
+
+       return (u32)(pll_mul / pll_div);
+}
+
+/*
+ * Get CPU, RAM, AHB and SPI clocks
+ * TODO: confirm nfracdiv values
+ */
+void qca_sys_clocks(u32 *cpu_clk,
+                                       u32 *ddr_clk,
+                                       u32 *ahb_clk,
+                                       u32 *spi_clk,
+                                       u32 *ref_clk)
+{
+       u32 cpu_pll;
+#if (SOC_TYPE != QCA_AR933X_SOC)
+       u32 ddr_pll;
+#endif
+       u32 outdiv;
+       u32 refdiv;
+       u32 reg_val;
+       u32 temp;
+       u32 nfrac;
+       u32 nfracdiv;
+       u32 nint;
+
+       if (qca_xtal_is_40mhz() == 1) {
+               qca_ref_clk = VAL_40MHz;
+       } else {
+               qca_ref_clk = VAL_25MHz;
+       }
+
+#if (SOC_TYPE == QCA_AR933X_SOC)
+       /*
+        * Main AR933x CPU PLL clock calculation:
+        *
+        * 1. If CPU PLL DITHER is disabled:
+        *    VCO_OUT = (REF_CLK / REF_DIV) * (NINT + (NFRAC_MIN / 1024))
+        *    CPU_PLL_OUT = VCO_OUT / (2^OUT_DIV)
+        *
+        * 2. If CPU PLL DITHER is enabled:
+        *    VCO_OUT = (REF_CLK / REF_DIV) * (NINT + (NFRAC / 1024))
+        *    CPU_PLL_OUT = VCO_OUT / (2^OUT_DIV)
+        *
+        *    TODO: NFRAC does not exist in AR9331 datasheet,
+        *          but exist in many other QC/A WiSOC datasheets,
+        *          we should somehow (scope?) check and confirm it
+        */
+
+       /* Read CPU CLock Control Register (CLOCK_CONTROL) value */
+       reg_val = qca_soc_reg_read(QCA_PLL_CPU_CLK_CTRL_REG);
+
+       if (reg_val & QCA_PLL_CPU_CLK_CTRL_BYPASS_MASK) {
+               /* PLL is bypassed, so all clocks are == reference clock */
+               *cpu_clk = qca_ref_clk;
+               *ddr_clk = qca_ref_clk;
+               *ahb_clk = qca_ref_clk;
+       } else {
+               reg_val = qca_soc_reg_read(QCA_PLL_PLL_DITHER_REG);
+
+               if (reg_val & QCA_PLL_PLL_DITHER_DITHER_EN_MASK) {
+                       reg_val = qca_soc_reg_read(QCA_PLL_CPU_PLL_CFG_REG);
+                       nfrac = (reg_val & QCA_PLL_CPU_PLL_CFG_NFRAC_MASK)
+                                       >> QCA_PLL_CPU_PLL_CFG_NFRAC_SHIFT;
+               } else {
+                       /* NFRAC = NFRAC_MIN if DITHER_EN is 0 */
+                       reg_val = qca_soc_reg_read(QCA_PLL_PLL_DITHER_FRAC_REG);
+                       nfrac = (reg_val & QCA_PLL_PLL_DITHER_FRAC_NFRAC_MIN_MASK)
+                                       >> QCA_PLL_PLL_DITHER_FRAC_NFRAC_MIN_SHIFT;
+               }
+
+               nfracdiv = 1 << 10;
+
+               reg_val = qca_soc_reg_read(QCA_PLL_CPU_PLL_CFG_REG);
+
+               nint = (reg_val & QCA_PLL_CPU_PLL_CFG_NINT_MASK)
+                          >> QCA_PLL_CPU_PLL_CFG_NINT_SHIFT;
+
+               refdiv = (reg_val & QCA_PLL_CPU_PLL_CFG_REFDIV_MASK)
+                                >> QCA_PLL_CPU_PLL_CFG_REFDIV_SHIFT;
+
+               outdiv = (reg_val & QCA_PLL_CPU_PLL_CFG_OUTDIV_MASK)
+                                >> QCA_PLL_CPU_PLL_CFG_OUTDIV_SHIFT;
+
+               /* TODO: need confirmation that OUTDIV == 0 is not supported for AR933x */
+               if (outdiv == 0)
+                       outdiv = 1;
+
+               /* Final CPU PLL value */
+               cpu_pll = qca_get_pll(qca_ref_clk, refdiv,
+                                                         nfrac, nfracdiv, nint, outdiv);
+
+               /* CPU, DDR and AHB clock dividers */
+               reg_val = qca_soc_reg_read(QCA_PLL_CPU_CLK_CTRL_REG);
+
+               temp = ((reg_val & QCA_PLL_CPU_CLK_CTRL_CPU_POST_DIV_MASK)
+                               >> QCA_PLL_CPU_CLK_CTRL_CPU_POST_DIV_SHIFT) + 1;
+               qca_cpu_clk = cpu_pll / temp;
+
+               temp = ((reg_val & QCA_PLL_CPU_CLK_CTRL_DDR_POST_DIV_MASK)
+                               >> QCA_PLL_CPU_CLK_CTRL_DDR_POST_DIV_SHIFT) + 1;
+               qca_ddr_clk = cpu_pll / temp;
+
+               temp = ((reg_val & QCA_PLL_CPU_CLK_CTRL_AHB_POST_DIV_MASK)
+                               >> QCA_PLL_CPU_CLK_CTRL_AHB_POST_DIV_SHIFT) + 1;
+               qca_ahb_clk = cpu_pll / temp;
+       }
+#else
+       /*
+        * Main AR934x/QCA95xx CPU/DDR PLL clock calculation
+        */
+
+       /*
+        * CPU PLL
+        */
+       reg_val = qca_soc_reg_read(QCA_PLL_SRIF_CPU_DPLL2_REG);
+
+       /* CPU PLL settings from SRIF CPU DPLL2? */
+       if (reg_val & QCA_PLL_SRIF_DPLL2_LOCAL_PLL_MASK) {
+               outdiv = (reg_val & QCA_PLL_SRIF_DPLL2_OUTDIV_MASK)
+                                >> QCA_PLL_SRIF_DPLL2_OUTDIV_SHIFT;
+
+               reg_val = qca_soc_reg_read(QCA_PLL_SRIF_CPU_DPLL1_REG);
+
+               nfrac = (reg_val & QCA_PLL_SRIF_DPLL1_NFRAC_MASK)
+                               >> QCA_PLL_SRIF_DPLL1_NFRAC_SHIFT;
+
+               nfracdiv = 1 << 18;
+
+               nint = (reg_val & QCA_PLL_SRIF_DPLL1_NINT_MASK)
+                          >> QCA_PLL_SRIF_DPLL1_NINT_SHIFT;
+
+               refdiv = (reg_val & QCA_PLL_SRIF_DPLL1_REFDIV_MASK)
+                                >> QCA_PLL_SRIF_DPLL1_REFDIV_SHIFT;
+       } else {
+               reg_val = qca_soc_reg_read(QCA_PLL_CPU_PLL_DITHER_REG);
+
+               if (reg_val & QCA_PLL_CPU_PLL_DITHER_DITHER_EN_MASK) {
+                       reg_val = qca_soc_reg_read(QCA_PLL_CPU_PLL_CFG_REG);
+                       nfrac = (reg_val & QCA_PLL_CPU_PLL_CFG_NFRAC_MASK)
+                                       >> QCA_PLL_CPU_PLL_CFG_NFRAC_SHIFT;
+               } else {
+                       /* NFRAC = NFRAC_MIN if DITHER_EN is 0 */
+                       nfrac = (reg_val & QCA_PLL_CPU_PLL_DITHER_NFRAC_MIN_MASK)
+                                       >> QCA_PLL_CPU_PLL_DITHER_NFRAC_MIN_SHIFT;
+               }
+
+               nfracdiv = 1 << 6;
+
+               reg_val = qca_soc_reg_read(QCA_PLL_CPU_PLL_CFG_REG);
+
+               nint = (reg_val & QCA_PLL_CPU_PLL_CFG_NINT_MASK)
+                          >> QCA_PLL_CPU_PLL_CFG_NINT_SHIFT;
+
+               refdiv = (reg_val & QCA_PLL_CPU_PLL_CFG_REFDIV_MASK)
+                                >> QCA_PLL_CPU_PLL_CFG_REFDIV_SHIFT;
+
+               outdiv = (reg_val & QCA_PLL_CPU_PLL_CFG_OUTDIV_MASK)
+                                >> QCA_PLL_CPU_PLL_CFG_OUTDIV_SHIFT;
+       }
+
+       /* Final CPU PLL value */
+       cpu_pll = qca_get_pll(qca_ref_clk, refdiv,
+                                                 nfrac, nfracdiv, nint, outdiv);
+
+       /*
+        * DDR PLL
+        */
+       reg_val = qca_soc_reg_read(QCA_PLL_SRIF_DDR_DPLL2_REG);
+
+       /* DDR PLL settings from SRIF DDR DPLL2? */
+       if (reg_val & QCA_PLL_SRIF_DPLL2_LOCAL_PLL_MASK) {
+               outdiv = (reg_val & QCA_PLL_SRIF_DPLL2_OUTDIV_MASK)
+                                >> QCA_PLL_SRIF_DPLL2_OUTDIV_SHIFT;
+
+               reg_val = qca_soc_reg_read(QCA_PLL_SRIF_DDR_DPLL1_REG);
+
+               nfrac = (reg_val & QCA_PLL_SRIF_DPLL1_NFRAC_MASK)
+                               >> QCA_PLL_SRIF_DPLL1_NFRAC_SHIFT;
+
+               nfracdiv = 1 << 18;
+
+               nint = (reg_val & QCA_PLL_SRIF_DPLL1_NINT_MASK)
+                          >> QCA_PLL_SRIF_DPLL1_NINT_SHIFT;
+
+               refdiv = (reg_val & QCA_PLL_SRIF_DPLL1_REFDIV_MASK)
+                                >> QCA_PLL_SRIF_DPLL1_REFDIV_SHIFT;
+       } else {
+               reg_val = qca_soc_reg_read(QCA_PLL_DDR_PLL_DITHER_REG);
+
+               if (reg_val & QCA_PLL_DDR_PLL_DITHER_DITHER_EN_MASK) {
+                       reg_val = qca_soc_reg_read(QCA_PLL_DDR_PLL_CFG_REG);
+                       nfrac = (reg_val & QCA_PLL_DDR_PLL_CFG_NFRAC_MASK)
+                                       >> QCA_PLL_DDR_PLL_CFG_NFRAC_SHIFT;
+               } else {
+                       /* NFRAC = NFRAC_MIN if DITHER_EN is 0 */
+                       nfrac = (reg_val & QCA_PLL_DDR_PLL_DITHER_NFRAC_MIN_MASK)
+                                       >> QCA_PLL_DDR_PLL_DITHER_NFRAC_MIN_SHIFT;
+               }
+
+               nfracdiv = 1 << 10;
+
+               reg_val = qca_soc_reg_read(QCA_PLL_DDR_PLL_CFG_REG);
+
+               nint = (reg_val & QCA_PLL_DDR_PLL_CFG_NINT_MASK)
+                          >> QCA_PLL_DDR_PLL_CFG_NINT_SHIFT;
+
+               refdiv = (reg_val & QCA_PLL_DDR_PLL_CFG_REFDIV_MASK)
+                                >> QCA_PLL_DDR_PLL_CFG_REFDIV_SHIFT;
+
+               outdiv = (reg_val & QCA_PLL_DDR_PLL_CFG_OUTDIV_MASK)
+                                >> QCA_PLL_DDR_PLL_CFG_OUTDIV_SHIFT;
+       }
+
+       /* Final DDR PLL value */
+       ddr_pll = qca_get_pll(qca_ref_clk, refdiv,
+                                                 nfrac, nfracdiv, nint, outdiv);
+
+       /* CPU clock divider */
+       reg_val = qca_soc_reg_read(QCA_PLL_CPU_DDR_CLK_CTRL_REG);
+
+       temp = ((reg_val & QCA_PLL_CPU_DDR_CLK_CTRL_CPU_POST_DIV_MASK)
+                       >> QCA_PLL_CPU_DDR_CLK_CTRL_CPU_POST_DIV_SHIFT) + 1;
+
+       if (reg_val & QCA_PLL_CPU_DDR_CLK_CTRL_CPU_PLL_BYPASS_MASK) {
+               qca_cpu_clk = qca_ref_clk;
+       } else if (reg_val & QCA_PLL_CPU_DDR_CLK_CTRL_CPUCLK_FROM_CPUPLL_MASK) {
+               qca_cpu_clk = cpu_pll / temp;
+       } else {
+               qca_cpu_clk = ddr_pll / temp;
+       }
+
+       /* DDR clock divider */
+       temp = ((reg_val & QCA_PLL_CPU_DDR_CLK_CTRL_DDR_POST_DIV_MASK)
+                       >> QCA_PLL_CPU_DDR_CLK_CTRL_DDR_POST_DIV_SHIFT) + 1;
+
+       if (reg_val & QCA_PLL_CPU_DDR_CLK_CTRL_DDR_PLL_BYPASS_MASK) {
+               qca_ddr_clk = qca_ref_clk;
+       } else if (reg_val & QCA_PLL_CPU_DDR_CLK_CTRL_DDRCLK_FROM_DDRPLL_MASK) {
+               qca_ddr_clk = ddr_pll / temp;
+       } else {
+               qca_ddr_clk = cpu_pll / temp;
+       }
+
+       /* AHB clock divider */
+       temp = ((reg_val & QCA_PLL_CPU_DDR_CLK_CTRL_AHB_POST_DIV_MASK)
+                       >> QCA_PLL_CPU_DDR_CLK_CTRL_AHB_POST_DIV_SHIFT) + 1;
+
+       if (reg_val & QCA_PLL_CPU_DDR_CLK_CTRL_AHB_PLL_BYPASS_MASK) {
+               qca_ahb_clk = qca_ref_clk;
+       } else if (reg_val & QCA_PLL_CPU_DDR_CLK_CTRL_AHBCLK_FROM_DDRPLL_MASK) {
+               qca_ahb_clk = ddr_pll / temp;
+       } else {
+               qca_ahb_clk = cpu_pll / temp;
+       }
+#endif
+       /* Calculate SPI FLASH clock - first disable SPI */
+       qca_soc_reg_read_set(QCA_SPI_FUNC_SEL_REG,
+                                                QCA_SPI_FUNC_SEL_FUNC_SEL_MASK);
+
+       /* SPI clock = AHB clock / ((SPI clock divider + 1) * 2) */
+       reg_val = (qca_soc_reg_read(QCA_SPI_CTRL_REG) & QCA_SPI_CTRL_CLK_DIV_MASK)
+                         >> QCA_SPI_CTRL_CLK_DIV_SHIFT;
+
+       qca_spi_clk = qca_ahb_clk / ((reg_val + 1) * 2);
+
+       /* Re-enable SPI */
+       qca_soc_reg_read_clear(QCA_SPI_FUNC_SEL_REG,
+                                                  QCA_SPI_FUNC_SEL_FUNC_SEL_MASK);
+
+       /* Return values */
+       if (cpu_clk != NULL)
+               *cpu_clk = qca_cpu_clk;
+
+       if (ddr_clk != NULL)
+               *ddr_clk = qca_ddr_clk;
+
+       if (ahb_clk != NULL)
+               *ahb_clk = qca_ahb_clk;
+
+       if (spi_clk != NULL)
+               *spi_clk = qca_spi_clk;
+
+       if (ref_clk != NULL)
+               *ref_clk = qca_ref_clk;
+}
diff --git a/u-boot/cpu/mips/ar7240/qca_common.c b/u-boot/cpu/mips/ar7240/qca_common.c
new file mode 100644 (file)
index 0000000..e878617
--- /dev/null
@@ -0,0 +1,87 @@
+/*
+ * Qualcomm/Atheros common/helper functions
+ *
+ * Copyright (C) 2015 Piotr Dymacz <piotr@dymacz.pl>
+ *
+ * Partially based on:
+ * Linux/arch/mips/ath79/setup.c
+ *
+ * SPDX-License-Identifier:GPL-2.0
+ */
+
+#include <config.h>
+#include <common.h>
+#include <asm/addrspace.h>
+#include <soc/qca_soc_common.h>
+
+/*
+ * Returns 1 if reference clock is 40 MHz
+ */
+inline u32 qca_xtal_is_40mhz(void)
+{
+       return ((qca_soc_reg_read(QCA_RST_BOOTSTRAP_REG) &
+                       QCA_RST_BOOTSTRAP_REF_CLK_MASK) >> QCA_RST_BOOTSTRAP_REF_CLK_SHIFT);
+}
+
+/*
+ * Return memory type value from BOOT_STRAP register
+ */
+inline u32 qca_mem_type(void)
+{
+       return ((qca_soc_reg_read(QCA_RST_BOOTSTRAP_REG) &
+                       QCA_RST_BOOTSTRAP_MEM_TYPE_MASK) >> QCA_RST_BOOTSTRAP_MEM_TYPE_SHIFT);
+}
+
+/*
+ * Put QCA SOC name, version and revision in buffer
+ */
+void qca_soc_name_rev(char *buf)
+{
+       u32 id;
+       u32 major;
+       u32 rev = 0;
+
+       /* Get revision ID value */
+       id = qca_soc_reg_read(QCA_RST_REVISION_ID_REG);
+
+       major = id & QCA_RST_REVISION_ID_MAJOR_MASK;
+       rev = id & QCA_RST_REVISION_ID_REV_MASK;
+
+       switch (major) {
+#if (SOC_TYPE == QCA_AR933X_SOC)
+       case QCA_RST_REVISION_ID_MAJOR_AR9330_VAL:
+               sprintf(buf, "AR9330 rev. %d", rev);
+               break;
+       case QCA_RST_REVISION_ID_MAJOR_AR9331_VAL:
+               sprintf(buf, "AR9331 rev. %d", rev);
+               break;
+#endif
+#if (SOC_TYPE == QCA_AR9341_SOC)
+       case QCA_RST_REVISION_ID_MAJOR_AR9341_VAL:
+               sprintf(buf, "AR9341 rev. %d", rev);
+               break;
+#endif
+#if (SOC_TYPE == QCA_AR9344_SOC)
+       case QCA_RST_REVISION_ID_MAJOR_AR9344_VAL:
+               sprintf(buf, "AR9344 rev. %d", rev);
+               break;
+#endif
+#if (SOC_TYPE == QCA_QCA9531_SOC || SOC_TYPE == QCA_QCA9533_SOC)
+       case QCA_RST_REVISION_ID_MAJOR_QCA9531_VAL:
+       case QCA_RST_REVISION_ID_MAJOR_QCA9533_VAL:
+               sprintf(buf, "QCA953x ver. 1 rev. %d", rev);
+               break;
+       case QCA_RST_REVISION_ID_MAJOR_QCA9533_V2_VAL:
+               sprintf(buf, "QCA953x ver. 2 rev. %d", rev);
+               break;
+#endif
+#if (SOC_TYPE == QCA_QCA9558_SOC)
+       case QCA_RST_REVISION_ID_MAJOR_QCA9558_VAL:
+               sprintf(buf, "QCA9558 rev. %d", rev);
+               break;
+#endif
+       default:
+               sprintf(buf, "Unknown");
+               break;
+       }
+}
diff --git a/u-boot/cpu/mips/ar7240/qca_hs_uart.c b/u-boot/cpu/mips/ar7240/qca_hs_uart.c
new file mode 100644 (file)
index 0000000..7d094fc
--- /dev/null
@@ -0,0 +1,182 @@
+/*
+ * Qualcomm/Atheros High-Speed UART driver
+ *
+ * Copyright (C) 2015 Piotr Dymacz <piotr@dymacz.pl>
+ * Copyright (C) 2014 Mantas Pucka <mantas@8devices.com>
+ * Copyright (C) 2008-2010 Atheros Communications Inc.
+ *
+ * Values for UART_SCALE and UART_STEP:
+ * https://www.mail-archive.com/openwrt-devel@lists.openwrt.org/msg22371.html
+ *
+ * Partially based on:
+ * Linux/drivers/tty/serial/ar933x_uart.c
+ *
+ * SPDX-License-Identifier:GPL-2.0
+ */
+
+#include <config.h>
+#include <common.h>
+#include <asm/addrspace.h>
+#include <soc/qca_soc_common.h>
+
+DECLARE_GLOBAL_DATA_PTR;
+
+/* HS UART baudrate = (REF_CLK / (CLOCK_SCALE + 1)) * (CLOCK_STEP * (1 / 2^17)) */
+static u32 qca_hsuart_get_baud(u32 ref_clk, u32 uart_scale, u32 uart_step)
+{
+       u64 baudrate;
+       u32 div;
+
+       div = (uart_scale + 1) * (2 << 16);
+
+       baudrate = (ref_clk * uart_step) + (div / 2);
+       baudrate = baudrate / div;
+
+       return (u32)baudrate;
+}
+
+static void qca_hsuart_get_scale_step(u32 baudrate,
+                                                                         u32 *uart_scale,
+                                                                         u32 *uart_step)
+{
+       s32 diff;
+       u32 ref_clk;
+       u32 tscale;
+       u64 tstep;
+       s32 min_diff;
+
+       *uart_scale = 0;
+       *uart_step = 0;
+
+       min_diff = baudrate;
+
+       if (qca_xtal_is_40mhz() == 1) {
+               ref_clk = VAL_40MHz;
+       } else {
+               ref_clk = VAL_25MHz;
+       }
+
+       for (tscale = 0; tscale < QCA_HSUART_CLK_SCALE_MAX_VAL; tscale++) {
+               tstep = baudrate * (tscale + 1);
+               tstep = tstep * (2 << 16);
+               tstep = tstep / ref_clk;
+
+               if (tstep > QCA_HSUART_CLK_STEP_MAX_VAL)
+                       break;
+
+               diff = qca_hsuart_get_baud(ref_clk, tscale, tstep) - baudrate;
+
+               if (diff < 0)
+                       diff = -1 * diff;
+
+               if (diff < min_diff) {
+                       min_diff = diff;
+                       *uart_scale = tscale;
+                       *uart_step = tstep;
+               }
+       }
+}
+
+void serial_setbrg(void)
+{
+       u32 uart_clock;
+       u32 uart_scale;
+       u32 uart_step;
+
+       qca_hsuart_get_scale_step(gd->baudrate, &uart_scale, &uart_step);
+
+       uart_clock  = (uart_scale << QCA_HSUART_CLK_SCALE_SHIFT);
+       uart_clock |= (uart_step  << QCA_HSUART_CLK_STEP_SHIFT);
+
+       qca_soc_reg_write(QCA_HSUART_CLK_REG, uart_clock);
+}
+
+int serial_init(void)
+{
+       u32 uart_cs;
+
+#if (SOC_TYPE == QCA_AR933X_SOC)
+       /*
+        * Set GPIO10 (UART_SO) as output and enable UART,
+        * BIT(15) in GPIO_FUNCTION_1 register must be written with 1
+        */
+       qca_soc_reg_read_set(QCA_GPIO_OE_REG, GPIO10);
+
+       qca_soc_reg_read_set(QCA_GPIO_FUNC_1_REG,
+                                                QCA_GPIO_FUNC_1_UART_EN_MASK | BIT(15));
+#else
+       #error "Missing GPIO configuration for HS UART"
+#endif
+
+       /*
+        * High-Speed UART controller configuration:
+        * - no DMA
+        * - no interrupt
+        * - no parity
+        * - DCE mode
+        * - no flow control
+        * - set RX ready oride
+        * - set TX ready oride
+        */
+       uart_cs = (0 << QCA_HSUART_CS_DMA_EN_SHIFT) |
+               (0 << QCA_HSUART_CS_HOST_INT_EN_SHIFT) |
+               (1 << QCA_HSUART_CS_RX_READY_ORIDE_SHIFT) |
+               (1 << QCA_HSUART_CS_TX_READY_ORIDE_SHIFT) |
+               (QCA_HSUART_CS_PAR_MODE_NO_VAL << QCA_HSUART_CS_PAR_MODE_SHIFT) |
+               (QCA_HSUART_CS_IFACE_MODE_DCE_VAL << QCA_HSUART_CS_IFACE_MODE_SHIFT) |
+               (QCA_HSUART_CS_FLOW_MODE_NO_VAL << QCA_HSUART_CS_FLOW_MODE_SHIFT);
+
+       qca_soc_reg_write(QCA_HSUART_CS_REG, uart_cs);
+
+       serial_setbrg();
+
+       return 0;
+}
+
+void serial_putc(const char c)
+{
+       u32 uart_data;
+
+       if (c == '\n')
+               serial_putc('\r');
+
+       /* Wait for FIFO */
+       do {
+               uart_data = qca_soc_reg_read(QCA_HSUART_DATA_REG);
+       } while (((uart_data & QCA_HSUART_DATA_TX_CSR_MASK)
+                         >> QCA_HSUART_DATA_TX_CSR_SHIFT)  == 0);
+
+       /* Put data in buffer and set CSR bit */
+       uart_data  = (u32)c | (1 << QCA_HSUART_DATA_TX_CSR_SHIFT);
+
+       qca_soc_reg_write(QCA_HSUART_DATA_REG, uart_data);
+}
+
+int serial_getc(void)
+{
+       u32 uart_data;
+
+       while (!serial_tstc())
+               ;
+
+       uart_data = qca_soc_reg_read(QCA_HSUART_DATA_REG);
+
+       qca_soc_reg_write(QCA_HSUART_DATA_REG,
+                                         (1 << QCA_HSUART_DATA_RX_CSR_SHIFT));
+
+       return (uart_data & QCA_HSUART_DATA_TX_RX_DATA_MASK);
+}
+
+int serial_tstc(void)
+{
+       u32 uart_data = qca_soc_reg_read(QCA_HSUART_DATA_REG);
+
+       return ((uart_data & QCA_HSUART_DATA_RX_CSR_MASK)
+                       >> QCA_HSUART_DATA_RX_CSR_SHIFT);
+}
+
+void serial_puts(const char *s)
+{
+       while (*s)
+               serial_putc(*s++);
+}
diff --git a/u-boot/cpu/mips/ar7240/qca_ls_uart.c b/u-boot/cpu/mips/ar7240/qca_ls_uart.c
new file mode 100644 (file)
index 0000000..7edf608
--- /dev/null
@@ -0,0 +1,113 @@
+/*
+ * Qualcomm/Atheros Low-Speed UART driver
+ *
+ * Copyright (C) 2015 Piotr Dymacz <piotr@dymacz.pl>
+ * Copyright (C) 2008-2010 Atheros Communications Inc.
+ *
+ * SPDX-License-Identifier:GPL-2.0
+ */
+
+#include <config.h>
+#include <common.h>
+#include <asm/addrspace.h>
+#include <soc/qca_soc_common.h>
+
+DECLARE_GLOBAL_DATA_PTR;
+
+void serial_setbrg(void)
+{
+       u32 div;
+
+       /*
+        * TODO: prepare list of supported range of baudrate values
+        * For 40 MHz ref_clk, successfully tested up to 1152000 on AR9344
+        *
+        * TODO: support 100 MHz reference clocks on AR934x and QCA955x
+        */
+
+       /* Round to closest, final baudrate = ref_clk / (16 * div) */
+       if (qca_xtal_is_40mhz() == 1) {
+               div = (VAL_40MHz + (8 * gd->baudrate)) / (16 * gd->baudrate);
+       } else {
+               div = (VAL_25MHz + (8 * gd->baudrate)) / (16 * gd->baudrate);
+       }
+
+       /* Set DLAB bit in LCR register unlocks DLL/DLH registers */
+       qca_soc_reg_read_set(QCA_LSUART_LCR_REG, QCA_LSUART_LCR_DLAB_MASK);
+
+       /* Write div into DLL and DLH registers */
+       qca_soc_reg_write(QCA_LSUART_DLL_REG, (div & 0xFF));
+       qca_soc_reg_write(QCA_LSUART_DLH_REG, ((div >> 8) & 0xFF));
+
+       /* Clear DLAB bit in LCR register */
+       qca_soc_reg_read_clear(QCA_LSUART_LCR_REG, QCA_LSUART_LCR_DLAB_MASK);
+}
+
+int serial_init(void)
+{
+       u32 uart_lcr;
+
+       serial_setbrg();
+
+       /* No interrupt */
+       qca_soc_reg_write(QCA_LSUART_IER_REG, 0x0);
+
+       /* No FIFO/DMA */
+       qca_soc_reg_write(QCA_LSUART_FCR_REG, 0x0);
+
+       /*
+        * Low-Speed UART controller configuration:
+        * - data: 8bits
+        * - stop: 1bit
+        * - parity: no
+        */
+       uart_lcr = (QCA_LSUART_LCR_CLS_8BIT_VAL << QCA_LSUART_LCR_CLS_SHIFT)
+                          | (0 << QCA_LSUART_LCR_STOP_SHIFT)
+                          | (0 << QCA_LSUART_LCR_PEN_SHIFT);
+
+       qca_soc_reg_write(QCA_LSUART_LCR_REG, uart_lcr);
+
+       return 0;
+}
+
+void serial_putc(const char c)
+{
+       u32 line_status;
+
+       if (c == '\n')
+               serial_putc('\r');
+
+       /* Wait for empty THR */
+       do {
+               line_status = qca_soc_reg_read(QCA_LSUART_LSR_REG);
+       } while (((line_status & QCA_LSUART_LSR_THRE_MASK)
+                         >> QCA_LSUART_LSR_THRE_SHIFT)  == 0);
+
+       /* Put data in THR */
+       qca_soc_reg_write(QCA_LSUART_THR_REG, (u32)c);
+}
+
+int serial_getc(void)
+{
+       while (!serial_tstc())
+               ;
+
+       /* Get data from RBR */
+       return (qca_soc_reg_read(QCA_LSUART_RBR_REG)
+                  & QCA_LSUART_RBR_RBR_MASK);
+}
+
+int serial_tstc(void)
+{
+       u32 uart_data = qca_soc_reg_read(QCA_LSUART_LSR_REG);
+
+       /* Check data ready bit */
+       return ((uart_data & QCA_LSUART_LSR_DR_MASK)
+                       >> QCA_LSUART_LSR_DR_SHIFT);
+}
+
+void serial_puts(const char *s)
+{
+       while (*s)
+               serial_putc(*s++);
+}