aboutsummaryrefslogtreecommitdiff
path: root/core/drivers/stm32_gpio.c
diff options
context:
space:
mode:
authorEtienne Carriere <etienne.carriere@st.com>2019-02-11 13:25:25 +0100
committerJérôme Forissier <jerome.forissier@linaro.org>2019-02-11 16:01:50 +0100
commit4b5e93ed8ac032fa14dcb4f3960e5fed014a722e (patch)
tree00c703cc4cd6d8496b289de6fb7d34fb4b920835 /core/drivers/stm32_gpio.c
parent4aa1f95a8fa074b89b429d8cf28d8e3a748948fc (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.c419
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);
+}