diff options
author | Felix Held <felix.held@amd.corp-partner.google.com> | 2021-09-22 18:20:54 +0200 |
---|---|---|
committer | Felix Held <felix-coreboot@felixheld.de> | 2021-09-23 18:31:53 +0000 |
commit | 2876e4f49aec84288d2b8d24885e5b22241fe907 (patch) | |
tree | cb320bf453051cf4cc3265efb122a12286fc6e5d /src/soc/amd/common/block/gpio | |
parent | ef8654554f07f4c51130781dee3becbad1d2c618 (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/Kconfig | 20 | ||||
-rw-r--r-- | src/soc/amd/common/block/gpio/Makefile.inc | 6 | ||||
-rw-r--r-- | src/soc/amd/common/block/gpio/gpio.c | 401 |
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]); +} |