diff --git a/drivers/serial/uart_cc13xx_cc26xx.c b/drivers/serial/uart_cc13xx_cc26xx.c index fd2a4192659..0810af873c4 100644 --- a/drivers/serial/uart_cc13xx_cc26xx.c +++ b/drivers/serial/uart_cc13xx_cc26xx.c @@ -7,18 +7,30 @@ #include #include #include +#include #include #include #include #include +#include +#include + struct uart_cc13xx_cc26xx_data { struct uart_config uart_config; #ifdef CONFIG_UART_INTERRUPT_DRIVEN uart_irq_callback_user_data_t callback; void *user_data; #endif /* CONFIG_UART_INTERRUPT_DRIVEN */ +#ifdef CONFIG_SYS_POWER_MANAGEMENT + Power_NotifyObj postNotify; + bool tx_constrained; + bool rx_constrained; +#endif +#ifdef CONFIG_DEVICE_POWER_MANAGEMENT + u32_t pm_state; +#endif }; #ifdef CONFIG_UART_CC13XX_CC26XX_0 @@ -53,6 +65,12 @@ static int uart_cc13xx_cc26xx_poll_in(struct device *dev, unsigned char *c) static void uart_cc13xx_cc26xx_poll_out(struct device *dev, unsigned char c) { UARTCharPut(get_dev_conf(dev)->regs, c); + /* + * Need to wait for character to be transmitted to ensure cpu does not + * enter standby when uart is busy + */ + while (UARTBusy(get_dev_conf(dev)->regs) == true) { + } } static int uart_cc13xx_cc26xx_err_check(struct device *dev) @@ -144,6 +162,12 @@ static int uart_cc13xx_cc26xx_configure(struct device *dev, get_dev_conf(dev)->sys_clk_freq, cfg->baudrate, line_ctrl); + /* Clear all UART interrupts */ + UARTIntClear(get_dev_conf(dev)->regs, + UART_INT_OE | UART_INT_BE | UART_INT_PE | + UART_INT_FE | UART_INT_RT | UART_INT_TX | + UART_INT_RX | UART_INT_CTS); + if (flow_ctrl) { UARTHwFlowControlEnable(get_dev_conf(dev)->regs); } else { @@ -204,12 +228,36 @@ static int uart_cc13xx_cc26xx_fifo_read(struct device *dev, u8_t *buf, static void uart_cc13xx_cc26xx_irq_tx_enable(struct device *dev) { +#ifdef CONFIG_SYS_POWER_MANAGEMENT + if (!get_dev_data(dev)->tx_constrained) { + /* + * When tx irq is enabled, it is implicit that we are expecting + * to transmit using the uart, hence we should no longer go + * into standby. + * + * Instead of using device_busy_set(), which currently does + * not impact the PM policy, we specifically disable the + * standby mode instead, since it is the power state that + * would interfere with a transfer. + */ + sys_pm_ctrl_disable_state(SYS_POWER_STATE_SLEEP_2); + get_dev_data(dev)->tx_constrained = true; + } +#endif + UARTIntEnable(get_dev_conf(dev)->regs, UART_INT_TX); } static void uart_cc13xx_cc26xx_irq_tx_disable(struct device *dev) { UARTIntDisable(get_dev_conf(dev)->regs, UART_INT_TX); + +#ifdef CONFIG_SYS_POWER_MANAGEMENT + if (get_dev_data(dev)->tx_constrained) { + sys_pm_ctrl_enable_state(SYS_POWER_STATE_SLEEP_2); + get_dev_data(dev)->tx_constrained = false; + } +#endif } static int uart_cc13xx_cc26xx_irq_tx_ready(struct device *dev) @@ -219,11 +267,30 @@ static int uart_cc13xx_cc26xx_irq_tx_ready(struct device *dev) static void uart_cc13xx_cc26xx_irq_rx_enable(struct device *dev) { +#ifdef CONFIG_SYS_POWER_MANAGEMENT + /* + * When rx is enabled, it is implicit that we are expecting + * to receive from the uart, hence we can no longer go into + * standby. + */ + if (!get_dev_data(dev)->rx_constrained) { + sys_pm_ctrl_disable_state(SYS_POWER_STATE_SLEEP_2); + get_dev_data(dev)->rx_constrained = true; + } +#endif + UARTIntEnable(get_dev_conf(dev)->regs, UART_INT_RX); } static void uart_cc13xx_cc26xx_irq_rx_disable(struct device *dev) { +#ifdef CONFIG_SYS_POWER_MANAGEMENT + if (get_dev_data(dev)->rx_constrained) { + sys_pm_ctrl_enable_state(SYS_POWER_STATE_SLEEP_2); + get_dev_data(dev)->rx_constrained = false; + } +#endif + UARTIntDisable(get_dev_conf(dev)->regs, UART_INT_RX); } @@ -284,6 +351,118 @@ static void uart_cc13xx_cc26xx_isr(void *arg) #endif /* CONFIG_UART_INTERRUPT_DRIVEN */ +#ifdef CONFIG_SYS_POWER_MANAGEMENT +/* + * ======== postNotifyFxn ======== + * Called by Power module when waking up the CPU from Standby, to support + * the case when SYS_POWER_MANAGEMENT is set but DEVICE_POWER_MANAGEMENT is + * not. The uart needs to be reconfigured afterwards unless Zephyr's device + * PM turned it off, in which case it'd be responsible for turning it back + * on and reconfiguring it. + */ +static int postNotifyFxn(unsigned int eventType, uintptr_t eventArg, + uintptr_t clientArg) +{ + struct device *dev = (struct device *)clientArg; + int ret = Power_NOTIFYDONE; + s16_t res_id; + + /* Reconfigure the hardware if returning from standby */ + if (eventType == PowerCC26XX_AWAKE_STANDBY) { + if (get_dev_conf(dev)->regs == + DT_TI_CC13XX_CC26XX_UART_40001000_BASE_ADDRESS) { + res_id = PowerCC26XX_PERIPH_UART0; + } else { /* DT_TI_CC13XX_CC26XX_UART_4000B000_BASE_ADDRESS */ + res_id = PowerCC26X2_PERIPH_UART1; + } + + if (Power_getDependencyCount(res_id) != 0) { + /* + * Reconfigure and enable UART only if not + * actively powered down + */ + if (uart_cc13xx_cc26xx_configure(dev, + &get_dev_data(dev)->uart_config) != 0) { + ret = Power_NOTIFYERROR; + } + } + } + + return (ret); +} +#endif + +#ifdef CONFIG_DEVICE_POWER_MANAGEMENT +static int uart_cc13xx_cc26xx_set_power_state(struct device *dev, + u32_t new_state) +{ + int ret = 0; + + if ((new_state == DEVICE_PM_ACTIVE_STATE) && + (new_state != get_dev_data(dev)->pm_state)) { + if (get_dev_conf(dev)->regs == + DT_TI_CC13XX_CC26XX_UART_40001000_BASE_ADDRESS) { + Power_setDependency(PowerCC26XX_PERIPH_UART0); + } else { + Power_setDependency(PowerCC26X2_PERIPH_UART1); + } + /* Configure and enable UART */ + ret = uart_cc13xx_cc26xx_configure(dev, + &get_dev_data(dev)->uart_config); + if (ret == 0) { + get_dev_data(dev)->pm_state = new_state; + } + } else { + __ASSERT_NO_MSG(new_state == DEVICE_PM_LOW_POWER_STATE || + new_state == DEVICE_PM_SUSPEND_STATE || + new_state == DEVICE_PM_OFF_STATE); + + if (get_dev_data(dev)->pm_state == DEVICE_PM_ACTIVE_STATE) { + UARTDisable(get_dev_conf(dev)->regs); + /* + * Release power dependency - i.e. potentially power + * down serial domain. + */ + if (get_dev_conf(dev)->regs == + DT_TI_CC13XX_CC26XX_UART_40001000_BASE_ADDRESS) { + Power_releaseDependency( + PowerCC26XX_PERIPH_UART0); + } else { + Power_releaseDependency( + PowerCC26X2_PERIPH_UART1); + } + get_dev_data(dev)->pm_state = new_state; + } + } + + return ret; +} + +static int uart_cc13xx_cc26xx_pm_control(struct device *dev, u32_t ctrl_command, + void *context, device_pm_cb cb, void *arg) +{ + int ret = 0; + + if (ctrl_command == DEVICE_PM_SET_POWER_STATE) { + u32_t new_state = *((const u32_t *)context); + + if (new_state != get_dev_data(dev)->pm_state) { + ret = uart_cc13xx_cc26xx_set_power_state(dev, + new_state); + } + } else { + __ASSERT_NO_MSG(ctrl_command == DEVICE_PM_GET_POWER_STATE); + *((u32_t *)context) = get_dev_data(dev)->pm_state; + } + + if (cb) { + cb(dev, ret, context, arg); + } + + return ret; +} +#endif /* CONFIG_DEVICE_POWER_MANAGEMENT */ + static const struct uart_driver_api uart_cc13xx_cc26xx_driver_api = { .poll_in = uart_cc13xx_cc26xx_poll_in, .poll_out = uart_cc13xx_cc26xx_poll_out, @@ -313,6 +492,22 @@ static int uart_cc13xx_cc26xx_init_0(struct device *dev) { int ret; +#ifdef CONFIG_DEVICE_POWER_MANAGEMENT + get_dev_data(dev)->pm_state = DEVICE_PM_ACTIVE_STATE; +#endif + +#ifdef CONFIG_SYS_POWER_MANAGEMENT + get_dev_data(dev)->rx_constrained = false; + get_dev_data(dev)->tx_constrained = false; + + /* Set Power dependencies */ + Power_setDependency(PowerCC26XX_PERIPH_UART0); + + /* Register notification function */ + Power_registerNotify(&get_dev_data(dev)->postNotify, + PowerCC26XX_AWAKE_STANDBY, + postNotifyFxn, (uintptr_t)dev); +#else /* Enable UART power domain */ PRCMPowerDomainOn(PRCM_DOMAIN_SERIAL); @@ -331,7 +526,7 @@ static int uart_cc13xx_cc26xx_init_0(struct device *dev) PRCM_DOMAIN_POWER_ON) { continue; } - +#endif /* Configure IOC module to map UART signals to pins */ IOCPortConfigureSet(DT_TI_CC13XX_CC26XX_UART_40001000_TX_PIN, IOC_PORT_MCU_UART0_TX, IOC_STD_OUTPUT); @@ -378,12 +573,21 @@ static struct uart_cc13xx_cc26xx_data uart_cc13xx_cc26xx_data_0 = { #endif /* CONFIG_UART_INTERRUPT_DRIVEN */ }; +#ifdef CONFIG_DEVICE_POWER_MANAGEMENT +DEVICE_DEFINE(uart_cc13xx_cc26xx_0, DT_TI_CC13XX_CC26XX_UART_40001000_LABEL, + uart_cc13xx_cc26xx_init_0, + uart_cc13xx_cc26xx_pm_control, + &uart_cc13xx_cc26xx_data_0, &uart_cc13xx_cc26xx_config_0, + PRE_KERNEL_1, CONFIG_KERNEL_INIT_PRIORITY_DEVICE, + &uart_cc13xx_cc26xx_driver_api); +#else DEVICE_AND_API_INIT(uart_cc13xx_cc26xx_0, DT_TI_CC13XX_CC26XX_UART_40001000_LABEL, uart_cc13xx_cc26xx_init_0, &uart_cc13xx_cc26xx_data_0, &uart_cc13xx_cc26xx_config_0, PRE_KERNEL_1, CONFIG_KERNEL_INIT_PRIORITY_DEVICE, &uart_cc13xx_cc26xx_driver_api); +#endif #endif /* CONFIG_UART_CC13XX_CC26XX_0 */ #ifdef CONFIG_UART_CC13XX_CC26XX_1 @@ -391,6 +595,22 @@ static int uart_cc13xx_cc26xx_init_1(struct device *dev) { int ret; +#ifdef CONFIG_DEVICE_POWER_MANAGEMENT + get_dev_data(dev)->pm_state = DEVICE_PM_ACTIVE_STATE; +#endif + +#ifdef CONFIG_SYS_POWER_MANAGEMENT + get_dev_data(dev)->rx_constrained = false; + get_dev_data(dev)->tx_constrained = false; + + /* Set Power dependencies */ + Power_setDependency(PowerCC26XX_PERIPH_UART1); + + /* Register notification function */ + Power_registerNotify(&get_dev_data(dev)->postNotify, + PowerCC26XX_AWAKE_STANDBY, + postNotifyFxn, (uintptr_t)dev); +#else /* Enable UART power domain */ PRCMPowerDomainOn(PRCM_DOMAIN_PERIPH); @@ -408,6 +628,7 @@ static int uart_cc13xx_cc26xx_init_1(struct device *dev) PRCM_DOMAIN_POWER_ON) { continue; } +#endif /* Configure IOC module to map UART signals to pins */ IOCPortConfigureSet(DT_TI_CC13XX_CC26XX_UART_4000B000_TX_PIN, @@ -455,10 +676,21 @@ static struct uart_cc13xx_cc26xx_data uart_cc13xx_cc26xx_data_1 = { #endif /* CONFIG_UART_INTERRUPT_DRIVEN */ }; +#ifdef CONFIG_DEVICE_POWER_MANAGEMENT +DEVICE_DEFINE(uart_cc13xx_cc26xx_1, DT_TI_CC13XX_CC26XX_UART_4000B000_LABEL, + uart_cc13xx_cc26xx_init_1, + uart_cc13xx_cc26xx_pm_control, + &uart_cc13xx_cc26xx_data_1, &uart_cc13xx_cc26xx_config_1, + POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEVICE, + &uart_cc13xx_cc26xx_driver_api); +#else DEVICE_AND_API_INIT(uart_cc13xx_cc26xx_1, - DT_TI_CC13XX_CC26XX_UART_4000B000_LABEL, - uart_cc13xx_cc26xx_init_1, &uart_cc13xx_cc26xx_data_1, - &uart_cc13xx_cc26xx_config_1, POST_KERNEL, - CONFIG_KERNEL_INIT_PRIORITY_DEVICE, - &uart_cc13xx_cc26xx_driver_api); + DT_TI_CC13XX_CC26XX_UART_4000B000_LABEL, + uart_cc13xx_cc26xx_init_1, &uart_cc13xx_cc26xx_data_1, + &uart_cc13xx_cc26xx_config_1, POST_KERNEL, + CONFIG_KERNEL_INIT_PRIORITY_DEVICE, + &uart_cc13xx_cc26xx_driver_api); +#endif + + #endif /* CONFIG_UART_CC13XX_CC26XX_1 */