From 03a1fe6cd7970912df0250c4450e20923bbcae77 Mon Sep 17 00:00:00 2001 From: Zhengwei Wang Date: Thu, 25 Jan 2024 11:36:41 +0800 Subject: [PATCH] drivers: serial: pm: Add power management support for Apollo3 SoCs UART Add power management support for Apollo3/Apollo3P UART, and automatically enables device runtime power management Signed-off-by: Zhengwei Wang --- drivers/serial/uart_pl011.c | 61 +++++++------ drivers/serial/uart_pl011_ambiq.h | 113 ++++++++++++++++++++++++- dts/arm/ambiq/ambiq_apollo3_blue.dtsi | 2 + dts/arm/ambiq/ambiq_apollo3p_blue.dtsi | 2 + 4 files changed, 150 insertions(+), 28 deletions(-) diff --git a/drivers/serial/uart_pl011.c b/drivers/serial/uart_pl011.c index d28d75b3e46..02238e84ac2 100644 --- a/drivers/serial/uart_pl011.c +++ b/drivers/serial/uart_pl011.c @@ -33,7 +33,17 @@ #endif #include "uart_pl011_registers.h" + +#if defined(CONFIG_SOC_FAMILY_AMBIQ) #include "uart_pl011_ambiq.h" +#endif + +#if defined(CONFIG_SOC_SERIES_APOLLO3X) +#define PM_INST_GET(n) PM_DEVICE_DT_INST_GET(n) +#else +#define PM_INST_GET(n) NULL +#endif + #include "uart_pl011_raspberrypi_pico.h" struct pl011_config { @@ -648,33 +658,30 @@ void pl011_isr(const struct device *dev) }; #endif /* CONFIG_UART_INTERRUPT_DRIVEN */ -#define PL011_INIT(n) \ - PINCTRL_DEFINE(n) \ - COMPAT_SPECIFIC_DEFINE(n) \ - PL011_CONFIG_PORT(n) \ - \ - static struct pl011_data pl011_data_port_##n = { \ - .uart_cfg = { \ - .baudrate = DT_INST_PROP(n, current_speed), \ - .parity = UART_CFG_PARITY_NONE, \ - .stop_bits = UART_CFG_STOP_BITS_1, \ - .data_bits = UART_CFG_DATA_BITS_8, \ - .flow_ctrl = DT_INST_PROP(n, hw_flow_control) \ - ? UART_CFG_FLOW_CTRL_RTS_CTS \ - : UART_CFG_FLOW_CTRL_NONE, \ - }, \ - .clk_freq = COND_CODE_1( \ - DT_NODE_HAS_COMPAT(DT_INST_CLOCKS_CTLR(n), fixed_clock), \ - (DT_INST_PROP_BY_PHANDLE(n, clocks, clock_frequency)), (0)), \ - }; \ - \ - DEVICE_DT_INST_DEFINE(n, pl011_init, \ - NULL, \ - &pl011_data_port_##n, \ - &pl011_cfg_port_##n, \ - PRE_KERNEL_1, \ - CONFIG_SERIAL_INIT_PRIORITY, \ - &pl011_driver_api); +#define PL011_INIT(n) \ + PINCTRL_DEFINE(n) \ + COMPAT_SPECIFIC_DEFINE(n) \ + PL011_CONFIG_PORT(n) \ + \ + static struct pl011_data pl011_data_port_##n = { \ + .uart_cfg = \ + { \ + .baudrate = DT_INST_PROP(n, current_speed), \ + .parity = UART_CFG_PARITY_NONE, \ + .stop_bits = UART_CFG_STOP_BITS_1, \ + .data_bits = UART_CFG_DATA_BITS_8, \ + .flow_ctrl = DT_INST_PROP(n, hw_flow_control) \ + ? UART_CFG_FLOW_CTRL_RTS_CTS \ + : UART_CFG_FLOW_CTRL_NONE, \ + }, \ + .clk_freq = \ + COND_CODE_1(DT_NODE_HAS_COMPAT(DT_INST_CLOCKS_CTLR(n), fixed_clock), \ + (DT_INST_PROP_BY_PHANDLE(n, clocks, clock_frequency)), (0)), \ + }; \ + \ + DEVICE_DT_INST_DEFINE(n, pl011_init, PM_INST_GET(n), &pl011_data_port_##n, \ + &pl011_cfg_port_##n, PRE_KERNEL_1, CONFIG_SERIAL_INIT_PRIORITY, \ + &pl011_driver_api); DT_INST_FOREACH_STATUS_OKAY(PL011_INIT) diff --git a/drivers/serial/uart_pl011_ambiq.h b/drivers/serial/uart_pl011_ambiq.h index 62d6d929123..5866ff730f5 100644 --- a/drivers/serial/uart_pl011_ambiq.h +++ b/drivers/serial/uart_pl011_ambiq.h @@ -9,8 +9,11 @@ #include #include +#include +#include #include "uart_pl011_registers.h" +#include #define PWRCTRL_MAX_WAIT_US 5 @@ -50,6 +53,113 @@ static inline int clk_enable_ambiq_uart(const struct device *dev, uint32_t clk) return pl011_ambiq_clk_set(dev, clk); } +#ifdef CONFIG_PM_DEVICE + +/* Register status record. + * The register status will be preserved to this variable before entering sleep mode, + * and they will be restored after wake up. + */ +typedef struct { + bool bValid; + uint32_t regILPR; + uint32_t regIBRD; + uint32_t regFBRD; + uint32_t regLCRH; + uint32_t regCR; + uint32_t regIFLS; + uint32_t regIER; +} uart_register_state_t; +static uart_register_state_t sRegState[2]; + +static int uart_ambiq_pm_action(const struct device *dev, enum pm_device_action action) +{ + int key; + + /*Uart module number*/ + uint32_t ui32Module = ((uint32_t)get_uart(dev) == UART0_BASE) ? 0 : 1; + + /*Uart Power module*/ + am_hal_pwrctrl_periph_e eUARTPowerModule = + ((am_hal_pwrctrl_periph_e)(AM_HAL_PWRCTRL_PERIPH_UART0 + ui32Module)); + + /*Uart register status*/ + uart_register_state_t *pRegisterStatus = &sRegState[ui32Module]; + + /* Decode the requested power state and update UART operation accordingly.*/ + switch (action) { + + /* Turn on the UART. */ + case PM_DEVICE_ACTION_RESUME: + + /* Make sure we don't try to restore an invalid state.*/ + if (!pRegisterStatus->bValid) { + return -EPERM; + } + + /*The resume and suspend actions may be executed back-to-back, + * so we add a busy wait here for stabilization. + */ + k_busy_wait(100); + + /* Enable power control.*/ + am_hal_pwrctrl_periph_enable(eUARTPowerModule); + + /* Restore UART registers*/ + key = irq_lock(); + + UARTn(ui32Module)->ILPR = pRegisterStatus->regILPR; + UARTn(ui32Module)->IBRD = pRegisterStatus->regIBRD; + UARTn(ui32Module)->FBRD = pRegisterStatus->regFBRD; + UARTn(ui32Module)->LCRH = pRegisterStatus->regLCRH; + UARTn(ui32Module)->CR = pRegisterStatus->regCR; + UARTn(ui32Module)->IFLS = pRegisterStatus->regIFLS; + UARTn(ui32Module)->IER = pRegisterStatus->regIER; + pRegisterStatus->bValid = false; + + irq_unlock(key); + + return 0; + case PM_DEVICE_ACTION_SUSPEND: + + while ((get_uart(dev)->fr & PL011_FR_BUSY) != 0) + ; + + /* Preserve UART registers*/ + key = irq_lock(); + + pRegisterStatus->regILPR = UARTn(ui32Module)->ILPR; + pRegisterStatus->regIBRD = UARTn(ui32Module)->IBRD; + pRegisterStatus->regFBRD = UARTn(ui32Module)->FBRD; + pRegisterStatus->regLCRH = UARTn(ui32Module)->LCRH; + pRegisterStatus->regCR = UARTn(ui32Module)->CR; + pRegisterStatus->regIFLS = UARTn(ui32Module)->IFLS; + pRegisterStatus->regIER = UARTn(ui32Module)->IER; + pRegisterStatus->bValid = true; + + irq_unlock(key); + + /* Clear all interrupts before sleeping as having a pending UART + * interrupt burns power. + */ + UARTn(ui32Module)->IEC = 0xFFFFFFFF; + + /* If the user is going to sleep, certain bits of the CR register + * need to be 0 to be low power and have the UART shut off. + * Since the user either wishes to retain state which takes place + * above or the user does not wish to retain state, it is acceptable + * to set the entire CR register to 0. + */ + UARTn(ui32Module)->CR = 0; + + /* Disable power control.*/ + am_hal_pwrctrl_periph_disable(eUARTPowerModule); + return 0; + default: + return -ENOTSUP; + } +} +#endif /* CONFIG_PM_DEVICE */ + /* Problem: writes to power configure register takes some time to take effective. * Solution: Check device's power status to ensure that register has taken effective. * Note: busy wait is not allowed to use here due to UART is initiated before timer starts. @@ -57,7 +167,8 @@ static inline int clk_enable_ambiq_uart(const struct device *dev, uint32_t clk) #if defined(CONFIG_SOC_SERIES_APOLLO3X) #define DEVPWRSTATUS_OFFSET 0x10 #define HCPA_MASK 0x4 -#define AMBIQ_UART_DEFINE(n) \ +#define AMBIQ_UART_DEFINE(n) \ + PM_DEVICE_DT_INST_DEFINE(n, uart_ambiq_pm_action); \ static int pwr_on_ambiq_uart_##n(void) \ { \ uint32_t addr = DT_REG_ADDR(DT_INST_PHANDLE(n, ambiq_pwrcfg)) + \ diff --git a/dts/arm/ambiq/ambiq_apollo3_blue.dtsi b/dts/arm/ambiq/ambiq_apollo3_blue.dtsi index 3b62b23ba0e..034727ae6e5 100644 --- a/dts/arm/ambiq/ambiq_apollo3_blue.dtsi +++ b/dts/arm/ambiq/ambiq_apollo3_blue.dtsi @@ -175,6 +175,7 @@ status = "disabled"; clocks = <&uartclk>; ambiq,pwrcfg = <&pwrcfg 0x8 0x80>; + zephyr,pm-device-runtime-auto; }; uart1: uart@4001d000 { @@ -185,6 +186,7 @@ status = "disabled"; clocks = <&uartclk>; ambiq,pwrcfg = <&pwrcfg 0x8 0x100>; + zephyr,pm-device-runtime-auto; }; spi0: spi@50004000 { diff --git a/dts/arm/ambiq/ambiq_apollo3p_blue.dtsi b/dts/arm/ambiq/ambiq_apollo3p_blue.dtsi index 783b7678f12..840f91b00d8 100644 --- a/dts/arm/ambiq/ambiq_apollo3p_blue.dtsi +++ b/dts/arm/ambiq/ambiq_apollo3p_blue.dtsi @@ -193,6 +193,7 @@ status = "disabled"; clocks = <&uartclk>; ambiq,pwrcfg = <&pwrcfg 0x8 0x80>; + zephyr,pm-device-runtime-auto; }; uart1: uart@4001d000 { @@ -203,6 +204,7 @@ status = "disabled"; clocks = <&uartclk>; ambiq,pwrcfg = <&pwrcfg 0x8 0x100>; + zephyr,pm-device-runtime-auto; }; spi0: spi@50004000 {