zephyr/drivers/gpio/gpio_smartbond.c
Andrzej Kaczmarek 422092f2d3 drivers: gpio: smartbond: Add GPIO latching for PM
This adds automatic GPIO latching before going to extended sleep and
restoring state after wakeup.

Mode and state for each pin is stored, then ports are latched to retain
state when PD_COM is disabled during sleep. On wakeup mode and state for
each pin is restored and ports are unlatched to make it work again.

Signed-off-by: Andrzej Kaczmarek <andrzej.kaczmarek@codecoup.pl>
Signed-off-by: Jerzy Kasenberg <jerzy.kasenberg@codecoup.pl>
2024-04-25 16:17:53 +02:00

442 lines
12 KiB
C

/*
* Copyright (c) 2022, Renesas Electronics Corporation
*
* SPDX-License-Identifier: Apache-2.0
*/
#define DT_DRV_COMPAT renesas_smartbond_gpio
#include <zephyr/drivers/gpio/gpio_utils.h>
#include <stdint.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/irq.h>
#include <zephyr/pm/device.h>
#include <DA1469xAB.h>
#include <da1469x_pdc.h>
#include <da1469x_pd.h>
#define GPIO_MODE_RESET 0x200
#define GPIO_PUPD_INPUT 0
#define GPIO_PUPD_INPUT_PU 1
#define GPIO_PUPD_INPUT_PD 2
#define GPIO_PUPD_OUTPUT 3
/* GPIO P0 and P1 share single GPIO and WKUP peripheral instance with separate
* set registers for P0 and P1 interleaved. The starting registers for direct
* data access, bit access, mode, latch and wake-up controller are defined in
* device tree.
*/
struct gpio_smartbond_data_regs {
uint32_t data;
uint32_t _reserved0;
uint32_t set;
uint32_t _reserved1;
uint32_t reset;
};
struct gpio_smartbond_latch_regs {
uint32_t latch;
uint32_t set;
uint32_t reset;
};
struct gpio_smartbond_wkup_regs {
uint32_t select;
uint32_t _reserved0[4];
uint32_t pol;
uint32_t _reserved1[4];
uint32_t status;
uint32_t _reserved2[2];
uint32_t clear;
uint32_t _reserved3[2];
uint32_t sel;
};
struct gpio_smartbond_data {
/* gpio_driver_data needs to be first */
struct gpio_driver_data common;
/* Pins that are configured for both edges (handled by software) */
gpio_port_pins_t both_edges_pins;
sys_slist_t callbacks;
#if CONFIG_PM_DEVICE
/*
* Saved state consist of:
* 1 word for GPIO output port state
* GPIOx_NGPIOS words for each pin mode
*/
uint32_t *gpio_saved_state;
#endif
};
struct gpio_smartbond_config {
/* gpio_driver_config needs to be first */
struct gpio_driver_config common;
volatile struct gpio_smartbond_data_regs *data_regs;
volatile uint32_t *mode_regs;
volatile struct gpio_smartbond_latch_regs *latch_regs;
volatile struct gpio_smartbond_wkup_regs *wkup_regs;
/* Value of TRIG_SELECT for PDC_CTRLx_REG entry */
uint8_t wkup_trig_select;
#if CONFIG_PM_DEVICE
uint8_t ngpios;
#endif
};
static void gpio_smartbond_wkup_init(void)
{
static bool wkup_init;
/* Wakeup controller is shared for both GPIO ports and should
* be initialized only once.
*/
if (!wkup_init) {
WAKEUP->WKUP_CTRL_REG = 0;
WAKEUP->WKUP_CLEAR_P0_REG = 0xffffffff;
WAKEUP->WKUP_CLEAR_P1_REG = 0xffffffff;
WAKEUP->WKUP_SELECT_P0_REG = 0;
WAKEUP->WKUP_SELECT_P1_REG = 0;
WAKEUP->WKUP_SEL_GPIO_P0_REG = 0;
WAKEUP->WKUP_SEL_GPIO_P1_REG = 0;
WAKEUP->WKUP_RESET_IRQ_REG = 0;
CRG_TOP->CLK_TMR_REG |= CRG_TOP_CLK_TMR_REG_WAKEUPCT_ENABLE_Msk;
WAKEUP->WKUP_CTRL_REG = WAKEUP_WKUP_CTRL_REG_WKUP_ENABLE_IRQ_Msk;
wkup_init = true;
}
}
static int gpio_smartbond_pin_configure(const struct device *dev,
gpio_pin_t pin, gpio_flags_t flags)
{
const struct gpio_smartbond_config *config = dev->config;
if (flags == GPIO_DISCONNECTED) {
/* Reset to default value */
config->mode_regs[pin] = GPIO_MODE_RESET;
return 0;
}
if ((flags & GPIO_INPUT) && (flags & GPIO_OUTPUT)) {
/* Simultaneous in/out is not supported */
return -ENOTSUP;
}
if (flags & GPIO_OUTPUT) {
config->mode_regs[pin] = GPIO_PUPD_OUTPUT << GPIO_P0_00_MODE_REG_PUPD_Pos;
if (flags & GPIO_OUTPUT_INIT_LOW) {
config->data_regs->reset = BIT(pin);
} else if (flags & GPIO_OUTPUT_INIT_HIGH) {
config->data_regs->set = BIT(pin);
}
return 0;
}
if (flags & GPIO_PULL_DOWN) {
config->mode_regs[pin] = GPIO_PUPD_INPUT_PD << GPIO_P0_00_MODE_REG_PUPD_Pos;
} else if (flags & GPIO_PULL_UP) {
config->mode_regs[pin] = GPIO_PUPD_INPUT_PU << GPIO_P0_00_MODE_REG_PUPD_Pos;
} else {
config->mode_regs[pin] = GPIO_PUPD_INPUT << GPIO_P0_00_MODE_REG_PUPD_Pos;
}
return 0;
}
static int gpio_smartbond_port_get_raw(const struct device *dev,
gpio_port_value_t *value)
{
const struct gpio_smartbond_config *config = dev->config;
*value = config->data_regs->data;
return 0;
}
static int gpio_smartbond_port_set_masked_raw(const struct device *dev,
gpio_port_pins_t mask,
gpio_port_value_t value)
{
const struct gpio_smartbond_config *config = dev->config;
config->data_regs->data = value & mask;
return 0;
}
static int gpio_smartbond_port_set_bits_raw(const struct device *dev,
gpio_port_pins_t pins)
{
const struct gpio_smartbond_config *config = dev->config;
config->data_regs->set = pins;
return 0;
}
static int gpio_smartbond_port_clear_bits_raw(const struct device *dev,
gpio_port_pins_t pins)
{
const struct gpio_smartbond_config *config = dev->config;
config->data_regs->reset = pins;
return 0;
}
static int gpio_smartbond_port_toggle_bits(const struct device *dev,
gpio_port_pins_t mask)
{
const struct gpio_smartbond_config *config = dev->config;
volatile uint32_t *reg = &config->data_regs->data;
*reg = *reg ^ mask;
return 0;
}
static void gpio_smartbond_arm_next_edge_interrupt(const struct device *dev,
uint32_t pin_mask)
{
const struct gpio_smartbond_config *config = dev->config;
uint32_t pin_value;
do {
pin_value = config->data_regs->data & pin_mask;
if (pin_value) {
config->wkup_regs->pol |= pin_mask;
} else {
config->wkup_regs->pol &= ~pin_mask;
}
} while (pin_value != (config->data_regs->data & pin_mask));
}
static int gpio_smartbond_pin_interrupt_configure(const struct device *dev,
gpio_pin_t pin,
enum gpio_int_mode mode,
enum gpio_int_trig trig)
{
const struct gpio_smartbond_config *config = dev->config;
struct gpio_smartbond_data *data = dev->data;
uint32_t pin_mask = BIT(pin);
#if CONFIG_PM
int trig_select_id = (config->wkup_trig_select << 5) | pin;
int pdc_ix;
#endif
/* Not supported by hardware */
if (mode == GPIO_INT_MODE_LEVEL) {
return -ENOTSUP;
}
#if CONFIG_PM
pdc_ix = da1469x_pdc_find(trig_select_id, MCU_PDC_MASTER_M33, MCU_PDC_EN_XTAL);
#endif
if (mode == GPIO_INT_MODE_DISABLED) {
config->wkup_regs->sel &= ~pin_mask;
config->wkup_regs->clear = pin_mask;
data->both_edges_pins &= ~pin_mask;
#if CONFIG_PM
da1469x_pdc_del(pdc_ix);
#endif
} else {
if (trig == GPIO_INT_TRIG_BOTH) {
/* Not supported by hardware */
data->both_edges_pins |= pin_mask;
gpio_smartbond_arm_next_edge_interrupt(dev, pin_mask);
} else if (trig == GPIO_INT_TRIG_HIGH) {
config->wkup_regs->pol &= ~pin_mask;
} else {
config->wkup_regs->pol |= pin_mask;
}
config->wkup_regs->sel |= pin_mask;
#if CONFIG_PM
if (pdc_ix < 0) {
pdc_ix = da1469x_pdc_add(trig_select_id, MCU_PDC_MASTER_M33,
MCU_PDC_EN_XTAL);
}
if (pdc_ix < 0) {
return -ENOMEM;
}
#endif
}
return 0;
}
static int gpio_smartbond_manage_callback(const struct device *dev,
struct gpio_callback *callback, bool set)
{
struct gpio_smartbond_data *data = dev->data;
return gpio_manage_callback(&data->callbacks, callback, set);
}
static void gpio_smartbond_isr(const struct device *dev)
{
const struct gpio_smartbond_config *config = dev->config;
struct gpio_smartbond_data *data = dev->data;
uint32_t stat;
uint32_t two_edge_triggered;
WAKEUP->WKUP_RESET_IRQ_REG = WAKEUP_WKUP_RESET_IRQ_REG_WKUP_IRQ_RST_Msk;
stat = config->wkup_regs->status;
two_edge_triggered = stat & data->both_edges_pins;
while (two_edge_triggered) {
int pos = find_lsb_set(two_edge_triggered) - 1;
two_edge_triggered &= ~BIT(pos);
/* Re-arm for other edge */
gpio_smartbond_arm_next_edge_interrupt(dev, BIT(pos));
}
config->wkup_regs->clear = stat;
gpio_fire_callbacks(&data->callbacks, dev, stat);
}
#ifdef CONFIG_PM_DEVICE
static void gpio_latch_inst(mem_addr_t data_reg, mem_addr_t mode_reg, mem_addr_t latch_reg,
uint8_t ngpios, uint32_t *data, uint32_t *mode)
{
uint8_t idx;
*data = sys_read32(data_reg);
for (idx = 0; idx < ngpios; idx++, mode_reg += 4) {
mode[idx] = sys_read32(mode_reg);
}
sys_write32(BIT_MASK(ngpios), latch_reg);
}
static void gpio_unlatch_inst(mem_addr_t data_reg, mem_addr_t mode_reg, mem_addr_t latch_reg,
uint8_t ngpios, uint32_t data, uint32_t *mode)
{
uint8_t idx;
sys_write32(data, data_reg);
for (idx = 0; idx < ngpios; idx++, mode_reg += 4) {
sys_write32(mode[idx], mode_reg);
}
sys_write32(BIT_MASK(ngpios), latch_reg);
}
static void gpio_latch(const struct device *dev)
{
const struct gpio_smartbond_config *config = dev->config;
const struct gpio_smartbond_data *data = dev->data;
gpio_latch_inst((mem_addr_t)&config->data_regs->data,
(mem_addr_t)config->mode_regs,
(mem_addr_t)&config->latch_regs->reset,
config->ngpios, data->gpio_saved_state, data->gpio_saved_state + 1);
}
static void gpio_unlatch(const struct device *dev)
{
const struct gpio_smartbond_config *config = dev->config;
const struct gpio_smartbond_data *data = dev->data;
gpio_unlatch_inst((mem_addr_t)&config->data_regs->data,
(mem_addr_t)config->mode_regs,
(mem_addr_t)&config->latch_regs->set,
config->ngpios, data->gpio_saved_state[0], data->gpio_saved_state + 1);
}
static int gpio_smartbond_pm_action(const struct device *dev,
enum pm_device_action action)
{
int ret = 0;
switch (action) {
case PM_DEVICE_ACTION_RESUME:
da1469x_pd_acquire(MCU_PD_DOMAIN_COM);
gpio_unlatch(dev);
break;
case PM_DEVICE_ACTION_SUSPEND:
gpio_latch(dev);
da1469x_pd_release(MCU_PD_DOMAIN_COM);
break;
default:
ret = -ENOTSUP;
}
return ret;
}
#endif /* CONFIG_PM_DEVICE */
/* GPIO driver registration */
static const struct gpio_driver_api gpio_smartbond_drv_api_funcs = {
.pin_configure = gpio_smartbond_pin_configure,
.port_get_raw = gpio_smartbond_port_get_raw,
.port_set_masked_raw = gpio_smartbond_port_set_masked_raw,
.port_set_bits_raw = gpio_smartbond_port_set_bits_raw,
.port_clear_bits_raw = gpio_smartbond_port_clear_bits_raw,
.port_toggle_bits = gpio_smartbond_port_toggle_bits,
.pin_interrupt_configure = gpio_smartbond_pin_interrupt_configure,
.manage_callback = gpio_smartbond_manage_callback,
};
#define GPIO_SAVED_STATE(id) gpio_smartbond_saved_state_##id
#define GPIO_PM_DEVICE_CFG(fld, val) \
COND_CODE_1(CONFIG_PM_DEVICE, (fld = val,), ())
#define GPIO_PM_DEVICE_STATE(id, ngpios) \
COND_CODE_1(CONFIG_PM_DEVICE, (static uint32_t GPIO_SAVED_STATE(id)[1 + ngpios];), ())
#define GPIO_SMARTBOND_DEVICE(id) \
GPIO_PM_DEVICE_STATE(id, DT_INST_PROP(id, ngpios)) \
static const struct gpio_smartbond_config gpio_smartbond_config_##id = { \
.common = { \
.port_pin_mask = \
GPIO_PORT_PIN_MASK_FROM_DT_INST(id), \
}, \
.data_regs = (volatile struct gpio_smartbond_data_regs *) \
DT_INST_REG_ADDR_BY_NAME(id, data), \
.mode_regs = (volatile uint32_t *)DT_INST_REG_ADDR_BY_NAME(id, mode), \
.latch_regs = (volatile struct gpio_smartbond_latch_regs *) \
DT_INST_REG_ADDR_BY_NAME(id, latch), \
.wkup_regs = (volatile struct gpio_smartbond_wkup_regs *) \
DT_INST_REG_ADDR_BY_NAME(id, wkup), \
.wkup_trig_select = id, \
GPIO_PM_DEVICE_CFG(.ngpios, DT_INST_PROP(id, ngpios)) \
}; \
\
static struct gpio_smartbond_data gpio_smartbond_data_##id = { \
GPIO_PM_DEVICE_CFG(.gpio_saved_state, GPIO_SAVED_STATE(id)) \
}; \
\
static int gpio_smartbond_init_##id(const struct device *dev) \
{ \
da1469x_pd_acquire(MCU_PD_DOMAIN_COM); \
gpio_smartbond_wkup_init(); \
IRQ_CONNECT(DT_INST_IRQN(id), \
DT_INST_IRQ(id, priority), \
gpio_smartbond_isr, \
DEVICE_DT_INST_GET(id), 0); \
irq_enable(DT_INST_IRQN(id)); \
return 0; \
} \
\
PM_DEVICE_DEFINE(id, gpio_smartbond_pm_action); \
DEVICE_DT_INST_DEFINE(id, gpio_smartbond_init_##id, \
PM_DEVICE_GET(id), \
&gpio_smartbond_data_##id, \
&gpio_smartbond_config_##id, \
PRE_KERNEL_1, \
CONFIG_GPIO_INIT_PRIORITY, \
&gpio_smartbond_drv_api_funcs);
DT_INST_FOREACH_STATUS_OKAY(GPIO_SMARTBOND_DEVICE)