From: Piotr Dymacz Date: Fri, 13 Nov 2015 01:17:48 +0000 (+0100) Subject: Add new code/drivers for QC/A WiSoCs X-Git-Url: https://git.librecmc.org/?a=commitdiff_plain;h=a6682f4e7fdc76d5713d9e5dc52f84a583577245;p=oweals%2Fu-boot_mod.git Add new code/drivers for QC/A WiSoCs 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) --- diff --git a/u-boot/cpu/mips/ar7240/qca_clocks.c b/u-boot/cpu/mips/ar7240/qca_clocks.c new file mode 100644 index 0000000..e52be09 --- /dev/null +++ b/u-boot/cpu/mips/ar7240/qca_clocks.c @@ -0,0 +1,338 @@ +/* + * Qualcomm/Atheros system clocks related functions + * + * Copyright (C) 2015 Piotr Dymacz + * + * Partially based on: + * Linux/arch/mips/ath79/clock.c + * + * SPDX-License-Identifier:GPL-2.0 + */ + +#include +#include +#include +#include +#include + +/* 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 index 0000000..e878617 --- /dev/null +++ b/u-boot/cpu/mips/ar7240/qca_common.c @@ -0,0 +1,87 @@ +/* + * Qualcomm/Atheros common/helper functions + * + * Copyright (C) 2015 Piotr Dymacz + * + * Partially based on: + * Linux/arch/mips/ath79/setup.c + * + * SPDX-License-Identifier:GPL-2.0 + */ + +#include +#include +#include +#include + +/* + * 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 index 0000000..7d094fc --- /dev/null +++ b/u-boot/cpu/mips/ar7240/qca_hs_uart.c @@ -0,0 +1,182 @@ +/* + * Qualcomm/Atheros High-Speed UART driver + * + * Copyright (C) 2015 Piotr Dymacz + * Copyright (C) 2014 Mantas Pucka + * 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 +#include +#include +#include + +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 index 0000000..7edf608 --- /dev/null +++ b/u-boot/cpu/mips/ar7240/qca_ls_uart.c @@ -0,0 +1,113 @@ +/* + * Qualcomm/Atheros Low-Speed UART driver + * + * Copyright (C) 2015 Piotr Dymacz + * Copyright (C) 2008-2010 Atheros Communications Inc. + * + * SPDX-License-Identifier:GPL-2.0 + */ + +#include +#include +#include +#include + +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++); +}