From 60591598e544a0b444f9bb09430acb332f8669b2 Mon Sep 17 00:00:00 2001 From: Maciej Sobkowski Date: Mon, 3 Jul 2023 16:00:15 +0200 Subject: [PATCH] drivers: serial: pl011: Add support for Ambiq UART UART controller present in Ambiq SoCs is mostly compatible with PL011, but requires some quirks that are implemented in this commit: - the peripheral needs to be powered on first, via the PWRCTRL core, - peripheral clock needs to be enabled and configured via the CLKEN/CLKSEL. registers. The quirks mechanism was inspired by support for STM32F4 SoC in the usb_dc_dw driver (fce0b85ecaed71f43c018391909a23cefb747ac6). Co-authored-by: Mateusz Sierszulski Signed-off-by: Maciej Sobkowski Signed-off-by: Mateusz Sierszulski --- drivers/serial/uart_pl011.c | 33 +++++++++++++- drivers/serial/uart_pl011_ambiq.h | 75 +++++++++++++++++++++++++++++++ 2 files changed, 107 insertions(+), 1 deletion(-) create mode 100644 drivers/serial/uart_pl011_ambiq.h diff --git a/drivers/serial/uart_pl011.c b/drivers/serial/uart_pl011.c index af1c5e820b8..a480f892e91 100644 --- a/drivers/serial/uart_pl011.c +++ b/drivers/serial/uart_pl011.c @@ -26,6 +26,7 @@ #endif #include "uart_pl011_registers.h" +#include "uart_pl011_ambiq.h" struct pl011_config { DEVICE_MMIO_ROM; @@ -36,6 +37,8 @@ struct pl011_config { #ifdef CONFIG_UART_INTERRUPT_DRIVEN uart_irq_config_func_t irq_config_func; #endif + int (*clk_enable_func)(const struct device *dev, uint32_t clk); + int (*pwr_on_func)(void); }; /* Device data structure */ @@ -304,10 +307,23 @@ static int pl011_init(const struct device *dev) return ret; } #endif + /* Call vendor-specific function to power on the peripheral */ + if (config->pwr_on_func != NULL) { + ret = config->pwr_on_func(); + } + /* disable the uart */ pl011_disable(dev); pl011_disable_fifo(dev); + /* Call vendor-specific function to enable clock for the peripheral */ + if (config->clk_enable_func != NULL) { + ret = config->clk_enable_func(dev, config->sys_clk_freq); + if (ret) { + return ret; + } + } + /* Set baud rate */ ret = pl011_set_baudrate(dev, config->sys_clk_freq, data->baud_rate); @@ -358,6 +374,18 @@ static int pl011_init(const struct device *dev) #define PINCTRL_INIT(n) #endif /* CONFIG_PINCTRL */ +#define PL011_GET_COMPAT_QUIRK_NONE(n) NULL + +#define PL011_GET_COMPAT_CLK_QUIRK_0(n) \ + COND_CODE_1(DT_NODE_HAS_COMPAT(DT_DRV_INST(n), ambiq_uart), \ + (clk_enable_ambiq_uart), \ + PL011_GET_COMPAT_QUIRK_NONE(n)) + +#define PL011_GET_COMPAT_PWR_QUIRK_0(n) \ + COND_CODE_1(DT_NODE_HAS_COMPAT(DT_DRV_INST(n), ambiq_uart), \ + (pwr_on_ambiq_uart_##n), \ + PL011_GET_COMPAT_QUIRK_NONE(n)) + #ifdef CONFIG_UART_INTERRUPT_DRIVEN void pl011_isr(const struct device *dev) { @@ -393,6 +421,8 @@ void pl011_isr(const struct device *dev) .sys_clk_freq = DT_INST_PROP_BY_PHANDLE(n, clocks, clock_frequency), \ PINCTRL_INIT(n) \ .irq_config_func = pl011_irq_config_func_##n, \ + .clk_enable_func = PL011_GET_COMPAT_CLK_QUIRK_0(n), \ + .pwr_on_func = PL011_GET_COMPAT_PWR_QUIRK_0(n), \ }; #else #define PL011_CONFIG_PORT(n) \ @@ -404,7 +434,8 @@ void pl011_isr(const struct device *dev) #endif /* CONFIG_UART_INTERRUPT_DRIVEN */ #define PL011_INIT(n) \ - PINCTRL_DEFINE(n) \ + PINCTRL_DEFINE(n) \ + PL011_QUIRK_AMBIQ_UART_DEFINE(n) \ PL011_CONFIG_PORT(n) \ \ static struct pl011_data pl011_data_port_##n = { \ diff --git a/drivers/serial/uart_pl011_ambiq.h b/drivers/serial/uart_pl011_ambiq.h new file mode 100644 index 00000000000..8cc36419385 --- /dev/null +++ b/drivers/serial/uart_pl011_ambiq.h @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2023 Antmicro + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_DRIVERS_SERIAL_UART_PL011_AMBIQ_H_ +#define ZEPHYR_DRIVERS_SERIAL_UART_PL011_AMBIQ_H_ + +#include +#include + +#include "uart_pl011_registers.h" + +#define PWRCTRL_MAX_WAIT_US 5 + +static inline void pl011_ambiq_enable_clk(const struct device *dev) +{ + get_uart(dev)->cr |= PL011_CR_AMBIQ_CLKEN; +} + +static inline int pl011_ambiq_clk_set(const struct device *dev, uint32_t clk) +{ + uint8_t clksel; + + switch (clk) { + case 3000000: + clksel = PL011_CR_AMBIQ_CLKSEL_3MHZ; + break; + case 6000000: + clksel = PL011_CR_AMBIQ_CLKSEL_6MHZ; + break; + case 12000000: + clksel = PL011_CR_AMBIQ_CLKSEL_12MHZ; + break; + case 24000000: + clksel = PL011_CR_AMBIQ_CLKSEL_24MHZ; + break; + case 48000000: + clksel = PL011_CR_AMBIQ_CLKSEL_48MHZ; + break; + default: + return -EINVAL; + } + + get_uart(dev)->cr |= FIELD_PREP(PL011_CR_AMBIQ_CLKSEL, clksel); + return 0; +} + +static inline int clk_enable_ambiq_uart(const struct device *dev, uint32_t clk) +{ + pl011_ambiq_enable_clk(dev); + return pl011_ambiq_clk_set(dev, clk); +} + +/* Problem: writes to pwrcfg reg take at most PWCTRL_MAX_WAIT_US time to propagate. + * Solution: busy wait for PWCTRL_MAX_WAIT_US microseconds to ensure that register + * writes have propagated. + */ + +#define QUIRK_AMBIQ_UART_DEFINE(n) \ + static int pwr_on_ambiq_uart_##n(void) \ + { \ + uint32_t addr = DT_REG_ADDR(DT_INST_PHANDLE(n, ambiq_pwrcfg)) + \ + DT_INST_PHA(n, ambiq_pwrcfg, offset); \ + sys_write32((sys_read32(addr) | DT_INST_PHA(n, ambiq_pwrcfg, mask)), addr); \ + k_busy_wait(PWRCTRL_MAX_WAIT_US); \ + return 0; \ + } + +#define PL011_QUIRK_AMBIQ_UART_DEFINE(n) \ + COND_CODE_1(DT_NODE_HAS_COMPAT(DT_DRV_INST(n), ambiq_uart), (QUIRK_AMBIQ_UART_DEFINE(n)), \ + ()) + +#endif /* ZEPHYR_DRIVERS_SERIAL_UART_PL011_AMBIQ_H_ */