diff options
author | Etienne Carriere <etienne.carriere@st.com> | 2019-02-11 13:25:25 +0100 |
---|---|---|
committer | Jérôme Forissier <jerome.forissier@linaro.org> | 2019-02-11 16:01:50 +0100 |
commit | 4b5e93ed8ac032fa14dcb4f3960e5fed014a722e (patch) | |
tree | 00c703cc4cd6d8496b289de6fb7d34fb4b920835 /core/drivers/stm32_gpio.c | |
parent | 4aa1f95a8fa074b89b429d8cf28d8e3a748948fc (diff) |
stm32_gpio: driver for GPIO and pin control
Driver is embedded upon CFG_STM32_GPIO=y.
STM32 GPIO driver API main functions:
- stm32_gpio_set_output_level() sets target output GPIO level,
- stm32_gpio_get_input_level() returns target input GPIO level,
- stm32_pinctrl_load_active_cfg() loads interface pin mux active state,
- stm32_pinctrl_load_standby_cfg() loads interface pin mux standby state,
- stm32_pinctrl_fdt_get_pinctrl() save pin configuration from DT content,
- stm32_gpio_set_secure_cfg() sets secure state for target GPIO/pin mux.
GPIO driver does not register to PM framework. It is the GPIO/pin owner
responsibility to call stm32_pinctrl_load_{active|standby}_cfg() on
peripherals power state transitions.
Signed-off-by: Etienne Carriere <etienne.carriere@st.com>
Signed-off-by: Mathieu Belou <mathieu.belou@st.com>
Signed-off-by: Nicolas Le Bayon <nicolas.le.bayon@st.com>
Signed-off-by: Yann Gautier <yann.gautier@st.com>
Acked-by: Jens Wiklander <jens.wiklander@linaro.org>
Diffstat (limited to 'core/drivers/stm32_gpio.c')
-rw-r--r-- | core/drivers/stm32_gpio.c | 419 |
1 files changed, 419 insertions, 0 deletions
diff --git a/core/drivers/stm32_gpio.c b/core/drivers/stm32_gpio.c new file mode 100644 index 00000000..e84a0e24 --- /dev/null +++ b/core/drivers/stm32_gpio.c @@ -0,0 +1,419 @@ +// SPDX-License-Identifier: BSD-3-Clause +/* + * Copyright (c) 2017-2019, STMicroelectronics + * + * STM32 GPIO driver is used as pin controller for stm32mp SoCs. + * The driver API is defined in header file stm32_gpio.h. + */ + +#include <assert.h> +#include <drivers/stm32_gpio.h> +#include <io.h> +#include <kernel/dt.h> +#include <kernel/generic_boot.h> +#include <kernel/panic.h> +#include <kernel/spinlock.h> +#include <mm/core_memprot.h> +#include <stdbool.h> +#include <stm32_util.h> +#include <trace.h> +#include <util.h> + +#ifdef CFG_DT +#include <libfdt.h> +#endif + +#define GPIO_PIN_MAX 15 + +#define GPIO_MODER_OFFSET 0x00 +#define GPIO_OTYPER_OFFSET 0x04 +#define GPIO_OSPEEDR_OFFSET 0x08 +#define GPIO_PUPDR_OFFSET 0x0c +#define GPIO_IDR_OFFSET 0x10 +#define GPIO_ODR_OFFSET 0x14 +#define GPIO_BSRR_OFFSET 0x18 +#define GPIO_AFRL_OFFSET 0x20 +#define GPIO_AFRH_OFFSET 0x24 +#define GPIO_SECR_OFFSET 0x30 + +#define GPIO_ALT_LOWER_LIMIT 0x8 + +#define GPIO_MODE_MASK GENMASK_32(1, 0) +#define GPIO_OSPEED_MASK GENMASK_32(1, 0) +#define GPIO_PUPD_PULL_MASK GENMASK_32(1, 0) +#define GPIO_ALTERNATE_MASK GENMASK_32(15, 0) + +#define DT_GPIO_BANK_SHIFT 12 +#define DT_GPIO_BANK_MASK GENMASK_32(16, 12) +#define DT_GPIO_PIN_SHIFT 8 +#define DT_GPIO_PIN_MASK GENMASK_32(11, 8) +#define DT_GPIO_MODE_MASK GENMASK_32(7, 0) + +static unsigned int gpio_lock; + +/* Save to output @cfg the current GPIO (@bank/@pin) configuration */ +static void get_gpio_cfg(uint32_t bank, uint32_t pin, struct gpio_cfg *cfg) +{ + uintptr_t base = stm32_get_gpio_bank_base(bank); + unsigned int clock = stm32_get_gpio_bank_clock(bank); + + stm32_clock_enable(clock); + + /* + * Save GPIO configuration bits spread over the few bank registers. + * 1bit fields are accessed at bit position being the pin index. + * 2bit fields are accessed at bit position being twice the pin index. + * 4bit fields are accessed at bit position being fourth the pin index + * but accessed from 2 32bit registers at incremental addresses. + */ + cfg->mode = (read32(base + GPIO_MODER_OFFSET) >> (pin << 1)) & + GPIO_MODE_MASK; + + cfg->otype = (read32(base + GPIO_OTYPER_OFFSET) >> pin) & 1; + + cfg->ospeed = (read32(base + GPIO_OSPEEDR_OFFSET) >> (pin << 1)) & + GPIO_OSPEED_MASK; + + cfg->pupd = (read32(base + GPIO_PUPDR_OFFSET) >> (pin << 1)) & + GPIO_PUPD_PULL_MASK; + + cfg->od = (read32(base + GPIO_ODR_OFFSET) >> (pin << 1)) & 1; + + if (pin < GPIO_ALT_LOWER_LIMIT) + cfg->af = (read32(base + GPIO_AFRL_OFFSET) >> (pin << 2)) & + GPIO_ALTERNATE_MASK; + else + cfg->af = (read32(base + GPIO_AFRH_OFFSET) >> + ((pin - GPIO_ALT_LOWER_LIMIT) << 2)) & + GPIO_ALTERNATE_MASK; + + stm32_clock_disable(clock); +} + +/* Apply GPIO (@bank/@pin) configuration described by @cfg */ +static void set_gpio_cfg(uint32_t bank, uint32_t pin, struct gpio_cfg *cfg) +{ + uintptr_t base = stm32_get_gpio_bank_base(bank); + unsigned int clock = stm32_get_gpio_bank_clock(bank); + uint32_t excep = cpu_spin_lock_xsave(&gpio_lock); + + stm32_clock_enable(clock); + + /* Load GPIO MODE value, 2bit value shifted by twice the pin number */ + io_clrsetbits32(base + GPIO_MODER_OFFSET, + GPIO_MODE_MASK << (pin << 1), + cfg->mode << (pin << 1)); + + /* Load GPIO Output TYPE value, 1bit shifted by pin number value */ + io_clrsetbits32(base + GPIO_OTYPER_OFFSET, BIT(pin), cfg->otype << pin); + + /* Load GPIO Output Speed confguration, 2bit value */ + io_clrsetbits32(base + GPIO_OSPEEDR_OFFSET, + GPIO_OSPEED_MASK << (pin << 1), + cfg->ospeed << (pin << 1)); + + /* Load GPIO pull configuration, 2bit value */ + io_clrsetbits32(base + GPIO_PUPDR_OFFSET, BIT(pin), + cfg->pupd << (pin << 1)); + + /* Load pin mux Alternate Function configuration, 4bit value */ + if (pin < GPIO_ALT_LOWER_LIMIT) { + io_clrsetbits32(base + GPIO_AFRL_OFFSET, + GPIO_ALTERNATE_MASK << (pin << 2), + cfg->af << (pin << 2)); + } else { + size_t shift = (pin - GPIO_ALT_LOWER_LIMIT) << 2; + + io_clrsetbits32(base + GPIO_AFRH_OFFSET, + GPIO_ALTERNATE_MASK << shift, + cfg->af << shift); + } + + /* Load GPIO Output direction confuguration, 1bit */ + io_clrsetbits32(base + GPIO_ODR_OFFSET, BIT(pin), cfg->od << pin); + + stm32_clock_disable(clock); + cpu_spin_unlock_xrestore(&gpio_lock, excep); +} + +void stm32_pinctrl_load_active_cfg(struct stm32_pinctrl *pinctrl, size_t cnt) +{ + size_t n; + + for (n = 0; n < cnt; n++) + set_gpio_cfg(pinctrl[n].bank, pinctrl[n].pin, + &pinctrl[n].active_cfg); +} + +void stm32_pinctrl_load_standby_cfg(struct stm32_pinctrl *pinctrl, size_t cnt) +{ + size_t n; + + for (n = 0; n < cnt; n++) + set_gpio_cfg(pinctrl[n].bank, pinctrl[n].pin, + &pinctrl[n].standby_cfg); +} + +void stm32_pinctrl_store_standby_cfg(struct stm32_pinctrl *pinctrl, size_t cnt) +{ + size_t n; + + for (n = 0; n < cnt; n++) + get_gpio_cfg(pinctrl[n].bank, pinctrl[n].pin, + &pinctrl[n].standby_cfg); +} + +#ifdef CFG_DT +/* Return GPIO bank node if valid and a negative libfdt error othewise */ +static void ckeck_gpio_bank(void *fdt, uint32_t bank, int pinctrl_node) +{ + int pinctrl_subnode; + + fdt_for_each_subnode(pinctrl_subnode, fdt, pinctrl_node) { + const fdt32_t *cuint; + + if (fdt_getprop(fdt, pinctrl_subnode, + "gpio-controller", NULL) == NULL) + continue; + + /* Check bank register offset matches platform assumptions */ + cuint = fdt_getprop(fdt, pinctrl_subnode, "reg", NULL); + if (!cuint) + panic(); + if (fdt32_to_cpu(*cuint) != stm32_get_gpio_bank_offset(bank)) + panic(); + + /* Check bank clock matches platform assumptions */ + cuint = fdt_getprop(fdt, pinctrl_subnode, "clocks", NULL); + if (!cuint) + panic(); + cuint++; + if (fdt32_to_cpu(*cuint) != stm32_get_gpio_bank_clock(bank)) + panic(); + + /* Check controller is enabled */ + if (_fdt_get_status(fdt, pinctrl_subnode) == DT_STATUS_DISABLED) + panic(); + + return; + } + + panic(); +} + +/* Count pins described in the DT node and get related data if possible */ +static int get_pinctrl_from_fdt(void *fdt, int node, + struct stm32_pinctrl *pinctrl, size_t count) +{ + const fdt32_t *cuint, *slewrate; + int len; + int pinctrl_node; + uint32_t i; + uint32_t speed = GPIO_OSPEED_LOW; + uint32_t pull = GPIO_PUPD_NO_PULL; + size_t found = 0; + + cuint = fdt_getprop(fdt, node, "pinmux", &len); + if (!cuint) + return -FDT_ERR_NOTFOUND; + + pinctrl_node = fdt_parent_offset(fdt, fdt_parent_offset(fdt, node)); + if (pinctrl_node < 0) + return -FDT_ERR_NOTFOUND; + + slewrate = fdt_getprop(fdt, node, "slew-rate", NULL); + if (slewrate) + speed = fdt32_to_cpu(*slewrate); + + if (fdt_getprop(fdt, node, "bias-pull-up", NULL)) + pull = GPIO_PUPD_PULL_UP; + if (fdt_getprop(fdt, node, "bias-pull-down", NULL)) + pull = GPIO_PUPD_PULL_DOWN; + + for (i = 0; i < ((uint32_t)len / sizeof(uint32_t)); i++) { + uint32_t pincfg; + uint32_t bank; + uint32_t pin; + uint32_t mode; + uint32_t alternate = 0; + bool opendrain = false; + + pincfg = fdt32_to_cpu(*cuint); + cuint++; + + bank = (pincfg & DT_GPIO_BANK_MASK) >> DT_GPIO_BANK_SHIFT; + + pin = (pincfg & DT_GPIO_PIN_MASK) >> DT_GPIO_PIN_SHIFT; + + mode = pincfg & DT_GPIO_MODE_MASK; + + switch (mode) { + case 0: + mode = GPIO_MODE_INPUT; + break; + case 1: + case 2: + case 3: + case 4: + case 5: + case 6: + case 7: + case 8: + case 9: + case 10: + case 11: + case 12: + case 13: + case 14: + case 15: + case 16: + alternate = mode - 1U; + mode = GPIO_MODE_ALTERNATE; + break; + case 17: + mode = GPIO_MODE_ANALOG; + break; + default: + mode = GPIO_MODE_OUTPUT; + break; + } + + if (fdt_getprop(fdt, node, "drive-open-drain", NULL)) + opendrain = true; + + /* Check GPIO bank clock/base address against platform */ + ckeck_gpio_bank(fdt, bank, pinctrl_node); + + if (found < count) { + struct stm32_pinctrl *ref = &pinctrl[found]; + + ref->bank = (uint8_t)bank; + ref->pin = (uint8_t)pin; + ref->active_cfg.mode = mode; + ref->active_cfg.otype = opendrain ? 1 : 0; + ref->active_cfg.ospeed = speed; + ref->active_cfg.pupd = pull; + ref->active_cfg.od = 0; + ref->active_cfg.af = alternate; + /* Default to analog mode for standby state */ + ref->standby_cfg.mode = GPIO_MODE_ANALOG; + ref->standby_cfg.pupd = GPIO_PUPD_NO_PULL; + } + + found++; + } + + return (int)found; +} + +int stm32_pinctrl_fdt_get_pinctrl(void *fdt, int device_node, + struct stm32_pinctrl *pinctrl, size_t count) +{ + const fdt32_t *cuint; + int lenp; + int i; + size_t found = 0; + + cuint = fdt_getprop(fdt, device_node, "pinctrl-0", &lenp); + if (!cuint) + return -FDT_ERR_NOTFOUND; + + for (i = 0; i < (lenp / 4); i++) { + int node; + int subnode; + + node = fdt_node_offset_by_phandle(fdt, fdt32_to_cpu(*cuint)); + if (node < 0) + return -FDT_ERR_NOTFOUND; + + fdt_for_each_subnode(subnode, fdt, node) { + size_t n; + int rc; + + if (count > found) + n = count - found; + else + n = 0; + + rc = get_pinctrl_from_fdt(fdt, subnode, + &pinctrl[found], n); + if (rc < 0) + return rc; + + found += (size_t)rc; + } + + cuint++; + } + + return (int)found; +} +#endif /*CFG_DT*/ + +static __maybe_unused bool valid_gpio_config(unsigned int bank, + unsigned int pin, bool input) +{ + uintptr_t base = stm32_get_gpio_bank_base(bank); + uint32_t mode = (read32(base + GPIO_MODER_OFFSET) >> (pin << 1)) & + GPIO_MODE_MASK; + + if (pin > GPIO_PIN_MAX) + return false; + + if (input) + return mode == GPIO_MODE_INPUT; + else + return mode == GPIO_MODE_OUTPUT; +} + +int stm32_gpio_get_input_level(unsigned int bank, unsigned int pin) +{ + uintptr_t base = stm32_get_gpio_bank_base(bank); + unsigned int clock = stm32_get_gpio_bank_clock(bank); + int rc = 0; + + assert(valid_gpio_config(bank, pin, true)); + + stm32_clock_enable(clock); + + if (read32(base + GPIO_IDR_OFFSET) == BIT(pin)) + rc = 1; + + stm32_clock_disable(clock); + + return rc; +} + +void stm32_gpio_set_output_level(unsigned int bank, unsigned int pin, int level) +{ + uintptr_t base = stm32_get_gpio_bank_base(bank); + unsigned int clock = stm32_get_gpio_bank_clock(bank); + + assert(valid_gpio_config(bank, pin, false)); + + stm32_clock_enable(clock); + + if (level) + write32(BIT(pin), base + GPIO_BSRR_OFFSET); + else + write32(BIT(pin + 16), base + GPIO_BSRR_OFFSET); + + stm32_clock_disable(clock); +} + +void stm32_gpio_set_secure_cfg(unsigned int bank, unsigned int pin, bool secure) +{ + uintptr_t base = stm32_get_gpio_bank_base(bank); + unsigned int clock = stm32_get_gpio_bank_clock(bank); + uint32_t excep = cpu_spin_lock_xsave(&gpio_lock); + + stm32_clock_enable(clock); + + if (secure) + io_setbits32(base + GPIO_SECR_OFFSET, BIT(pin)); + else + io_clrbits32(base + GPIO_SECR_OFFSET, BIT(pin)); + + stm32_clock_disable(clock); + cpu_spin_unlock_xrestore(&gpio_lock, excep); +} |