summaryrefslogtreecommitdiff
path: root/src/soc/amd/common/block/gpio
diff options
context:
space:
mode:
authorFelix Held <felix.held@amd.corp-partner.google.com>2021-09-22 18:20:54 +0200
committerFelix Held <felix-coreboot@felixheld.de>2021-09-23 18:31:53 +0000
commit2876e4f49aec84288d2b8d24885e5b22241fe907 (patch)
treecb320bf453051cf4cc3265efb122a12286fc6e5d /src/soc/amd/common/block/gpio
parentef8654554f07f4c51130781dee3becbad1d2c618 (diff)
soc/amd/common/blocks: rename gpio_banks folder to gpio
This brings the AMD SoC GPIO code in line with the Intel SoC code and removes the not really needed suffix. Signed-off-by: Felix Held <felix-coreboot@felixheld.de> Change-Id: I3dfcca2f126eb49c962b5cc32cbcf72e04f3f170 Reviewed-on: https://review.coreboot.org/c/coreboot/+/57841 Tested-by: build bot (Jenkins) <no-reply@coreboot.org> Reviewed-by: Raul Rangel <rrangel@chromium.org>
Diffstat (limited to 'src/soc/amd/common/block/gpio')
-rw-r--r--src/soc/amd/common/block/gpio/Kconfig20
-rw-r--r--src/soc/amd/common/block/gpio/Makefile.inc6
-rw-r--r--src/soc/amd/common/block/gpio/gpio.c401
3 files changed, 427 insertions, 0 deletions
diff --git a/src/soc/amd/common/block/gpio/Kconfig b/src/soc/amd/common/block/gpio/Kconfig
new file mode 100644
index 0000000000..0a1bffee81
--- /dev/null
+++ b/src/soc/amd/common/block/gpio/Kconfig
@@ -0,0 +1,20 @@
+config SOC_AMD_COMMON_BLOCK_BANKED_GPIOS
+ bool
+ depends on SOC_AMD_COMMON_BLOCK_ACPIMMIO
+ help
+ Select this option to use the newer style banks of GPIO signals.
+ These are at offsets +0x1500, +0x1600, and +0x1700 from the AcpiMmio
+ base.
+
+if SOC_AMD_COMMON_BLOCK_BANKED_GPIOS
+
+config SOC_AMD_COMMON_BLOCK_BANKED_GPIOS_NON_SOC_CODEBASE
+ bool
+ help
+ Select this option when selecting the GPIO bank support from AMD
+ chipsets outside the soc/ subtree that only support a subset of the
+ features available on the chipsets inside the soc/ subtree. When this
+ option is selected, no SMI or SCI event can be configured by the GPIO
+ code.
+
+endif # SOC_AMD_COMMON_BLOCK_BANKED_GPIOS
diff --git a/src/soc/amd/common/block/gpio/Makefile.inc b/src/soc/amd/common/block/gpio/Makefile.inc
new file mode 100644
index 0000000000..616b58986d
--- /dev/null
+++ b/src/soc/amd/common/block/gpio/Makefile.inc
@@ -0,0 +1,6 @@
+ifeq ($(CONFIG_SOC_AMD_COMMON_BLOCK_BANKED_GPIOS),y)
+
+all-y += gpio.c
+smm-y += gpio.c
+
+endif # CONFIG_SOC_AMD_COMMON_BLOCK_BANKED_GPIOS
diff --git a/src/soc/amd/common/block/gpio/gpio.c b/src/soc/amd/common/block/gpio/gpio.c
new file mode 100644
index 0000000000..263757988a
--- /dev/null
+++ b/src/soc/amd/common/block/gpio/gpio.c
@@ -0,0 +1,401 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+
+#include <acpi/acpi_pm.h>
+#include <device/mmio.h>
+#include <device/device.h>
+#include <console/console.h>
+#include <elog.h>
+#include <gpio.h>
+#include <amdblocks/acpi.h>
+#include <amdblocks/acpimmio.h>
+#include <amdblocks/gpio_banks.h>
+#include <amdblocks/smi.h>
+#include <soc/gpio.h>
+#include <soc/smi.h>
+#include <assert.h>
+#include <string.h>
+#include <types.h>
+
+/*
+ * acpimmio_gpio0, acpimmio_remote_gpio and acpimmio_iomux are defined in
+ * soc/amd/common/block/acpimmio/mmio_util.c and declared as extern variables/constants in
+ * amdblocks/acpimmio.h which is included in this file.
+ */
+
+/* MMIO access of new-style GPIO bank configuration registers */
+static inline void *gpio_ctrl_ptr(gpio_t gpio_num)
+{
+ if (SOC_GPIO_TOTAL_PINS < AMD_GPIO_FIRST_REMOTE_GPIO_NUMBER ||
+ /* Verstage on PSP would need to map acpimmio_remote_gpio */
+ (CONFIG(VBOOT_STARTS_BEFORE_BOOTBLOCK) && ENV_SEPARATE_VERSTAGE) ||
+ gpio_num < AMD_GPIO_FIRST_REMOTE_GPIO_NUMBER)
+ return acpimmio_gpio0 + gpio_num * sizeof(uint32_t);
+ else
+ return acpimmio_remote_gpio +
+ (gpio_num - AMD_GPIO_FIRST_REMOTE_GPIO_NUMBER) * sizeof(uint32_t);
+}
+
+static inline uint32_t gpio_read32(gpio_t gpio_num)
+{
+ return read32(gpio_ctrl_ptr(gpio_num));
+}
+
+static inline void gpio_write32(gpio_t gpio_num, uint32_t value)
+{
+ write32(gpio_ctrl_ptr(gpio_num), value);
+}
+
+static inline void *gpio_mux_ptr(gpio_t gpio_num)
+{
+ if (SOC_GPIO_TOTAL_PINS < AMD_GPIO_FIRST_REMOTE_GPIO_NUMBER ||
+ /* Verstage on PSP would need to map acpimmio_remote_gpio */
+ (CONFIG(VBOOT_STARTS_BEFORE_BOOTBLOCK) && ENV_SEPARATE_VERSTAGE) ||
+ gpio_num < AMD_GPIO_FIRST_REMOTE_GPIO_NUMBER)
+ return acpimmio_iomux + gpio_num;
+ else
+ return acpimmio_remote_gpio + AMD_GPIO_REMOTE_GPIO_MUX_OFFSET +
+ (gpio_num - AMD_GPIO_FIRST_REMOTE_GPIO_NUMBER);
+}
+
+static uint8_t get_gpio_mux(gpio_t gpio_num)
+{
+ return read8(gpio_mux_ptr(gpio_num));
+}
+
+static void set_gpio_mux(gpio_t gpio_num, uint8_t function)
+{
+ write8(gpio_mux_ptr(gpio_num), function & AMD_GPIO_MUX_MASK);
+ get_gpio_mux(gpio_num); /* Flush posted write */
+}
+
+static int get_gpio_gevent(gpio_t gpio, const struct soc_amd_event *table,
+ size_t items)
+{
+ size_t i;
+
+ for (i = 0; i < items; i++) {
+ if ((table + i)->gpio == gpio)
+ return (int)(table + i)->event;
+ }
+ return -1;
+}
+
+static void program_smi(uint32_t flags, unsigned int gevent_num)
+{
+ uint8_t level;
+
+ if (!is_gpio_event_level_triggered(flags)) {
+ printk(BIOS_ERR, "ERROR: %s - Only level trigger allowed for SMI!\n", __func__);
+ BUG();
+ return;
+ }
+
+ if (is_gpio_event_active_high(flags))
+ level = SMI_SCI_LVL_HIGH;
+ else
+ level = SMI_SCI_LVL_LOW;
+
+ configure_gevent_smi(gevent_num, SMI_MODE_SMI, level);
+}
+
+/*
+ * For each general purpose event, GPE, the choice of edge/level triggered
+ * event is represented as a single bit in SMI_SCI_LEVEL register.
+ *
+ * In a similar fashion, polarity (rising/falling, hi/lo) of each GPE is
+ * represented as a single bit in SMI_SCI_TRIG register.
+ */
+static void program_sci(uint32_t flags, unsigned int gevent_num)
+{
+ struct sci_source sci;
+
+ sci.scimap = gevent_num;
+ sci.gpe = gevent_num;
+
+ if (is_gpio_event_level_triggered(flags))
+ sci.level = SMI_SCI_LVL;
+ else
+ sci.level = SMI_SCI_EDG;
+
+ if (is_gpio_event_active_high(flags))
+ sci.direction = SMI_SCI_LVL_HIGH;
+ else
+ sci.direction = SMI_SCI_LVL_LOW;
+
+ configure_scimap(&sci);
+}
+
+uintptr_t gpio_get_address(gpio_t gpio_num)
+{
+ return (uintptr_t)gpio_ctrl_ptr(gpio_num);
+}
+
+static void gpio_update32(gpio_t gpio_num, uint32_t mask, uint32_t or)
+{
+ uint32_t reg;
+
+ reg = gpio_read32(gpio_num);
+ reg &= mask;
+ reg |= or;
+ gpio_write32(gpio_num, reg);
+}
+
+/* Set specified bits of a register to match those of ctrl. */
+static void gpio_setbits32(gpio_t gpio_num, uint32_t mask, uint32_t ctrl)
+{
+ gpio_update32(gpio_num, ~mask, ctrl & mask);
+}
+
+static void gpio_and32(gpio_t gpio_num, uint32_t mask)
+{
+ gpio_update32(gpio_num, mask, 0);
+}
+
+static void gpio_or32(gpio_t gpio_num, uint32_t or)
+{
+ gpio_update32(gpio_num, -1UL, or);
+}
+
+static void master_switch_clr(uint32_t mask)
+{
+ const gpio_t master_reg = GPIO_MASTER_SWITCH / sizeof(uint32_t);
+ gpio_and32(master_reg, ~mask);
+}
+
+static void master_switch_set(uint32_t or)
+{
+ const gpio_t master_reg = GPIO_MASTER_SWITCH / sizeof(uint32_t);
+ gpio_or32(master_reg, or);
+}
+
+int gpio_get(gpio_t gpio_num)
+{
+ uint32_t reg;
+
+ reg = gpio_read32(gpio_num);
+ return !!(reg & GPIO_PIN_STS);
+}
+
+void gpio_set(gpio_t gpio_num, int value)
+{
+ gpio_setbits32(gpio_num, GPIO_OUTPUT_VALUE, value ? GPIO_OUTPUT_VALUE : 0);
+}
+
+void gpio_input_pulldown(gpio_t gpio_num)
+{
+ gpio_setbits32(gpio_num, GPIO_PULL_MASK | GPIO_OUTPUT_ENABLE, GPIO_PULLDOWN_ENABLE);
+}
+
+void gpio_input_pullup(gpio_t gpio_num)
+{
+ gpio_setbits32(gpio_num, GPIO_PULL_MASK | GPIO_OUTPUT_ENABLE, GPIO_PULLUP_ENABLE);
+}
+
+void gpio_input(gpio_t gpio_num)
+{
+ gpio_and32(gpio_num, ~(GPIO_PULL_MASK | GPIO_OUTPUT_ENABLE));
+}
+
+void gpio_output(gpio_t gpio_num, int value)
+{
+ /* set GPIO output value before setting the direction to output to avoid glitches */
+ gpio_set(gpio_num, value);
+ gpio_or32(gpio_num, GPIO_OUTPUT_ENABLE);
+}
+
+const char *gpio_acpi_path(gpio_t gpio)
+{
+ return "\\_SB.GPIO";
+}
+
+uint16_t gpio_acpi_pin(gpio_t gpio)
+{
+ return gpio;
+}
+
+void gpio_save_pin_registers(gpio_t gpio, struct soc_amd_gpio_register_save *save)
+{
+ save->mux_value = get_gpio_mux(gpio);
+ save->control_value = gpio_read32(gpio);
+}
+
+void gpio_restore_pin_registers(gpio_t gpio, struct soc_amd_gpio_register_save *save)
+{
+ set_gpio_mux(gpio, save->mux_value);
+ gpio_write32(gpio, save->control_value);
+ gpio_read32(gpio); /* Flush posted write */
+}
+
+static void set_single_gpio(const struct soc_amd_gpio *g)
+{
+ static const struct soc_amd_event *gev_tbl;
+ static size_t gev_items;
+ int gevent_num;
+ const bool can_set_smi_flags = !((CONFIG(VBOOT_STARTS_BEFORE_BOOTBLOCK) &&
+ ENV_SEPARATE_VERSTAGE) ||
+ CONFIG(SOC_AMD_COMMON_BLOCK_BANKED_GPIOS_NON_SOC_CODEBASE));
+
+ set_gpio_mux(g->gpio, g->function);
+
+ gpio_setbits32(g->gpio, PAD_CFG_MASK, g->control);
+ /* Clear interrupt and wake status (write 1-to-clear bits) */
+ gpio_or32(g->gpio, GPIO_INT_STATUS | GPIO_WAKE_STATUS);
+ if (g->flags == 0)
+ return;
+
+ /* Can't set SMI flags from PSP */
+ if (!can_set_smi_flags)
+ return;
+
+ if (gev_tbl == NULL)
+ soc_get_gpio_event_table(&gev_tbl, &gev_items);
+
+ gevent_num = get_gpio_gevent(g->gpio, gev_tbl, gev_items);
+ if (gevent_num < 0) {
+ printk(BIOS_WARNING, "Warning: GPIO pin %d has no associated gevent!\n",
+ g->gpio);
+ return;
+ }
+
+ if (g->flags & GPIO_FLAG_SMI) {
+ program_smi(g->flags, gevent_num);
+ } else if (g->flags & GPIO_FLAG_SCI) {
+ program_sci(g->flags, gevent_num);
+ }
+}
+
+void gpio_configure_pads_with_override(const struct soc_amd_gpio *base_cfg,
+ size_t base_num_pads,
+ const struct soc_amd_gpio *override_cfg,
+ size_t override_num_pads)
+{
+ const struct soc_amd_gpio *c;
+ size_t i, j;
+
+ if (!base_cfg || !base_num_pads)
+ return;
+
+ /*
+ * Disable blocking wake/interrupt status generation while updating
+ * debounce registers. Otherwise when a debounce register is updated
+ * the whole GPIO controller will zero out all interrupt enable status
+ * bits while the delay happens. This could cause us to drop the bits
+ * due to the read-modify-write that happens on each register.
+ *
+ * Additionally disable interrupt generation so we don't get any
+ * spurious interrupts while updating the registers.
+ */
+ master_switch_clr(GPIO_MASK_STS_EN | GPIO_INTERRUPT_EN);
+
+ for (i = 0; i < base_num_pads; i++) {
+ c = &base_cfg[i];
+ /* Check if override exist for GPIO from the base configuration */
+ for (j = 0; override_cfg && j < override_num_pads; j++) {
+ if (c->gpio == override_cfg[j].gpio) {
+ c = &override_cfg[j];
+ break;
+ }
+ }
+ set_single_gpio(c);
+ }
+
+ /*
+ * Re-enable interrupt status generation.
+ *
+ * We leave MASK_STATUS disabled because the kernel may reconfigure the
+ * debounce registers while the drivers load. This will cause interrupts
+ * to be missed during boot.
+ */
+ master_switch_set(GPIO_INTERRUPT_EN);
+}
+
+void gpio_configure_pads(const struct soc_amd_gpio *gpio_list_ptr, size_t size)
+{
+ gpio_configure_pads_with_override(gpio_list_ptr, size, NULL, 0);
+}
+
+int gpio_interrupt_status(gpio_t gpio)
+{
+ uint32_t reg = gpio_read32(gpio);
+
+ if (reg & GPIO_INT_STATUS) {
+ /* Clear interrupt status, preserve wake status */
+ reg &= ~GPIO_WAKE_STATUS;
+ gpio_write32(gpio, reg);
+ return 1;
+ }
+
+ return 0;
+}
+
+static void check_and_add_wake_gpio(gpio_t begin, gpio_t end, struct gpio_wake_state *state)
+{
+ gpio_t i;
+ uint32_t reg;
+
+ for (i = begin; i < end; i++) {
+ reg = gpio_read32(i);
+ if (!(reg & GPIO_WAKE_STATUS))
+ continue;
+ printk(BIOS_INFO, "GPIO %d woke system.\n", i);
+ if (state->num_valid_wake_gpios >= ARRAY_SIZE(state->wake_gpios))
+ continue;
+ state->wake_gpios[state->num_valid_wake_gpios++] = i;
+ }
+}
+
+static void check_gpios(uint32_t wake_stat, unsigned int bit_limit, gpio_t gpio_base,
+ struct gpio_wake_state *state)
+{
+ unsigned int i;
+ gpio_t begin;
+ gpio_t end;
+
+ for (i = 0; i < bit_limit; i++) {
+ if (!(wake_stat & BIT(i)))
+ continue;
+ /* Each wake status register bit is for 4 GPIOs that then will be checked */
+ begin = gpio_base + i * 4;
+ end = begin + 4;
+ /* There is no gpio 63. */
+ if (begin == 60)
+ end = 63;
+ check_and_add_wake_gpio(begin, end, state);
+ }
+}
+
+void gpio_fill_wake_state(struct gpio_wake_state *state)
+{
+ /* Turn the wake registers into "gpio" index to conform to existing API. */
+ const gpio_t stat0 = GPIO_WAKE_STAT_0 / sizeof(uint32_t);
+ const gpio_t stat1 = GPIO_WAKE_STAT_1 / sizeof(uint32_t);
+ const gpio_t control_switch = GPIO_MASTER_SWITCH / sizeof(uint32_t);
+
+ memset(state, 0, sizeof(*state));
+
+ state->control_switch = gpio_read32(control_switch);
+ state->wake_stat[0] = gpio_read32(stat0);
+ state->wake_stat[1] = gpio_read32(stat1);
+
+ printk(BIOS_INFO, "GPIO Control Switch: 0x%08x, Wake Stat 0: 0x%08x, Wake Stat 1: 0x%08x\n",
+ state->control_switch, state->wake_stat[0], state->wake_stat[1]);
+
+ check_gpios(state->wake_stat[0], 32, 0, state);
+ check_gpios(state->wake_stat[1], 14, 128, state);
+}
+
+void gpio_add_events(void)
+{
+ const struct chipset_power_state *ps;
+ const struct gpio_wake_state *state;
+ unsigned int i;
+ unsigned int end;
+
+ if (acpi_pm_state_for_elog(&ps) < 0)
+ return;
+ state = &ps->gpio_state;
+
+ end = MIN(state->num_valid_wake_gpios, ARRAY_SIZE(state->wake_gpios));
+ for (i = 0; i < end; i++)
+ elog_add_event_wake(ELOG_WAKE_SOURCE_GPIO, state->wake_gpios[i]);
+}