diff options
author | Marshall Dawson <marshalldawson3rd@gmail.com> | 2019-05-02 17:27:57 -0600 |
---|---|---|
committer | Martin Roth <martinroth@google.com> | 2019-06-06 17:57:40 +0000 |
commit | 251d305e73f76ca3b63654273f3b2bb3de775457 (patch) | |
tree | 76cf206b9b73033c21569005f12f80f1df7bbcbf /src/soc/amd/common/block/gpio_banks | |
parent | eb5b0d05a71ec04d69699edebb6e71be2bb6ed09 (diff) |
soc/amd/stoneyridge: Move GPIO support to common
The banked GPIO functionality in the AcpiMmio block has been consistent
since the Mullins product. Move the basic support into a common
directory.
Each product's pin availability, MUXes, and other details must remain
specific to the product.
The relocated source also drops the weak configure_gevent_smi() that
reports SMI is not available. The stoneyridge port relies on SMI
to do its initialization, similar to modern soc/intel devices. This
is the plan for future soc/amd ports, so make a missing function a
build error instead of a runtime warning.
BUG=b:131682806
Change-Id: I9cda00210a74de2bd1308ad43e2b867d24a67845
Signed-off-by: Marshall Dawson <marshalldawson3rd@gmail.com>
Reviewed-on: https://review.coreboot.org/c/coreboot/+/32651
Tested-by: build bot (Jenkins) <no-reply@coreboot.org>
Reviewed-by: Martin Roth <martinroth@google.com>
Diffstat (limited to 'src/soc/amd/common/block/gpio_banks')
-rw-r--r-- | src/soc/amd/common/block/gpio_banks/Kconfig | 8 | ||||
-rw-r--r-- | src/soc/amd/common/block/gpio_banks/Makefile.inc | 6 | ||||
-rw-r--r-- | src/soc/amd/common/block/gpio_banks/gpio.c | 310 |
3 files changed, 324 insertions, 0 deletions
diff --git a/src/soc/amd/common/block/gpio_banks/Kconfig b/src/soc/amd/common/block/gpio_banks/Kconfig new file mode 100644 index 0000000000..115aa2cc52 --- /dev/null +++ b/src/soc/amd/common/block/gpio_banks/Kconfig @@ -0,0 +1,8 @@ +config SOC_AMD_COMMON_BLOCK_BANKED_GPIOS + bool + depends on SOC_AMD_COMMON_BLOCK_ACPIMMIO + default n + 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. diff --git a/src/soc/amd/common/block/gpio_banks/Makefile.inc b/src/soc/amd/common/block/gpio_banks/Makefile.inc new file mode 100644 index 0000000000..f1555b12b8 --- /dev/null +++ b/src/soc/amd/common/block/gpio_banks/Makefile.inc @@ -0,0 +1,6 @@ +bootblock-$(CONFIG_SOC_AMD_COMMON_BLOCK_BANKED_GPIOS) += gpio.c +verstage-$(CONFIG_SOC_AMD_COMMON_BLOCK_BANKED_GPIOS) += gpio.c +romstage-$(CONFIG_SOC_AMD_COMMON_BLOCK_BANKED_GPIOS) += gpio.c +postcar-$(CONFIG_SOC_AMD_COMMON_BLOCK_BANKED_GPIOS) += gpio.c +ramstage-$(CONFIG_SOC_AMD_COMMON_BLOCK_BANKED_GPIOS) += gpio.c +smm-$(CONFIG_SOC_AMD_COMMON_BLOCK_BANKED_GPIOS) += gpio.c diff --git a/src/soc/amd/common/block/gpio_banks/gpio.c b/src/soc/amd/common/block/gpio_banks/gpio.c new file mode 100644 index 0000000000..17e3de09e3 --- /dev/null +++ b/src/soc/amd/common/block/gpio_banks/gpio.c @@ -0,0 +1,310 @@ +/* + * This file is part of the coreboot project. + * + * Copyright (C) 2015 Google Inc. + * Copyright (C) 2015 Intel Corporation + * Copyright (C) 2017 Advanced Micro Devices, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <device/mmio.h> +#include <device/device.h> +#include <console/console.h> +#include <gpio.h> +#include <amdblocks/acpimmio.h> +#include <soc/gpio.h> +#include <soc/smi.h> +#include <assert.h> + +static int get_gpio_gevent(uint8_t gpio, const struct soc_amd_event *table, + size_t items) +{ + int i; + + for (i = 0; i < items; i++) { + if ((table + i)->gpio == gpio) + return (int)(table + i)->event; + } + return -1; +} + +static void mem_read_write32(uint32_t *address, uint32_t value, uint32_t mask) +{ + uint32_t reg32; + + value &= mask; + reg32 = read32(address); + reg32 &= ~mask; + reg32 |= value; + write32(address, reg32); +} + +static void program_smi(uint32_t flag, int gevent_num) +{ + uint32_t trigger; + + trigger = flag & FLAGS_TRIGGER_MASK; + /* + * Only level trigger is allowed for SMI. Trigger values are 0 + * through 3, with 0-1 being level trigger and 2-3 being edge + * trigger. GPIO_TRIGGER_EDGE_LOW is 2, so trigger has to be + * less than GPIO_TRIGGER_EDGE_LOW. + */ + assert(trigger < GPIO_TRIGGER_EDGE_LOW); + + if (trigger == GPIO_TRIGGER_LEVEL_HIGH) + configure_gevent_smi(gevent_num, SMI_MODE_SMI, + SMI_SCI_LVL_HIGH); + if (trigger == GPIO_TRIGGER_LEVEL_LOW) + configure_gevent_smi(gevent_num, SMI_MODE_SMI, + SMI_SCI_LVL_LOW); +} + +static void get_sci_config_bits(uint32_t flag, uint32_t *edge, uint32_t *level) +{ + uint32_t trigger; + + trigger = flag & FLAGS_TRIGGER_MASK; + switch (trigger) { + case GPIO_TRIGGER_LEVEL_LOW: + *edge = SCI_TRIGGER_LEVEL; + *level = 0; + break; + case GPIO_TRIGGER_LEVEL_HIGH: + *edge = SCI_TRIGGER_LEVEL; + *level = 1; + break; + case GPIO_TRIGGER_EDGE_LOW: + *edge = SCI_TRIGGER_EDGE; + *level = 0; + break; + case GPIO_TRIGGER_EDGE_HIGH: + *edge = SCI_TRIGGER_EDGE; + *level = 1; + break; + default: + break; + } +} + +uintptr_t gpio_get_address(gpio_t gpio_num) +{ + uintptr_t gpio_address; + + if (gpio_num < 64) + gpio_address = GPIO_BANK0_CONTROL(gpio_num); + else if (gpio_num < 128) + gpio_address = GPIO_BANK1_CONTROL(gpio_num); + else + gpio_address = GPIO_BANK2_CONTROL(gpio_num); + + return gpio_address; +} + +int gpio_get(gpio_t gpio_num) +{ + uint32_t reg; + uintptr_t gpio_address = gpio_get_address(gpio_num); + + reg = read32((void *)gpio_address); + + return !!(reg & GPIO_PIN_STS); +} + +void gpio_set(gpio_t gpio_num, int value) +{ + uint32_t reg; + uintptr_t gpio_address = gpio_get_address(gpio_num); + + reg = read32((void *)gpio_address); + reg &= ~GPIO_OUTPUT_MASK; + reg |= !!value << GPIO_OUTPUT_SHIFT; + write32((void *)gpio_address, reg); +} + +void gpio_input_pulldown(gpio_t gpio_num) +{ + uint32_t reg; + uintptr_t gpio_address = gpio_get_address(gpio_num); + + reg = read32((void *)gpio_address); + reg &= ~GPIO_PULLUP_ENABLE; + reg |= GPIO_PULLDOWN_ENABLE; + write32((void *)gpio_address, reg); +} + +void gpio_input_pullup(gpio_t gpio_num) +{ + uint32_t reg; + uintptr_t gpio_address = gpio_get_address(gpio_num); + + reg = read32((void *)gpio_address); + reg &= ~GPIO_PULLDOWN_ENABLE; + reg |= GPIO_PULLUP_ENABLE; + write32((void *)gpio_address, reg); +} + +void gpio_input(gpio_t gpio_num) +{ + uint32_t reg; + uintptr_t gpio_address = gpio_get_address(gpio_num); + + reg = read32((void *)gpio_address); + reg &= ~GPIO_OUTPUT_ENABLE; + write32((void *)gpio_address, reg); +} + +void gpio_output(gpio_t gpio_num, int value) +{ + uint32_t reg; + uintptr_t gpio_address = gpio_get_address(gpio_num); + + reg = read32((void *)gpio_address); + reg |= GPIO_OUTPUT_ENABLE; + write32((void *)gpio_address, reg); + gpio_set(gpio_num, value); +} + +const char *gpio_acpi_path(gpio_t gpio) +{ + return "\\_SB.GPIO"; +} + +uint16_t gpio_acpi_pin(gpio_t gpio) +{ + return gpio; +} + +__weak void soc_gpio_hook(uint8_t gpio, uint8_t mux) {} + +void program_gpios(const struct soc_amd_gpio *gpio_list_ptr, size_t size) +{ + uint32_t *gpio_ptr, *inter_master; + uint32_t control, control_flags, edge_level, direction; + uint32_t mask, bit_edge, bit_level; + uint8_t mux, index, gpio; + int gevent_num; + const struct soc_amd_event *gev_tbl; + size_t gev_items; + + inter_master = (uint32_t *)(uintptr_t)(ACPIMMIO_GPIO0_BASE + + GPIO_MASTER_SWITCH); + direction = 0; + edge_level = 0; + mask = 0; + + /* + * 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. + */ + mem_read_write32(inter_master, 0, GPIO_MASK_STS_EN | GPIO_INTERRUPT_EN); + + soc_get_gpio_event_table(&gev_tbl, &gev_items); + + for (index = 0; index < size; index++) { + gpio = gpio_list_ptr[index].gpio; + mux = gpio_list_ptr[index].function; + control = gpio_list_ptr[index].control; + control_flags = gpio_list_ptr[index].flags; + + iomux_write8(gpio, mux & AMD_GPIO_MUX_MASK); + iomux_read8(gpio); /* Flush posted write */ + + soc_gpio_hook(gpio, mux); + + gpio_ptr = (uint32_t *)gpio_get_address(gpio); + + if (control_flags & GPIO_SPECIAL_FLAG) { + gevent_num = get_gpio_gevent(gpio, gev_tbl, gev_items); + if (gevent_num < 0) { + printk(BIOS_WARNING, "Warning: GPIO pin %d has" + " no associated gevent!\n", gpio); + continue; + } + switch (control_flags & GPIO_SPECIAL_MASK) { + case GPIO_DEBOUNCE_FLAG: + mem_read_write32(gpio_ptr, control, + GPIO_DEBOUNCE_MASK); + break; + case GPIO_WAKE_FLAG: + mem_read_write32(gpio_ptr, control, + INT_WAKE_MASK); + break; + case GPIO_INT_FLAG: + mem_read_write32(gpio_ptr, control, + AMD_GPIO_CONTROL_MASK); + break; + case GPIO_SMI_FLAG: + mem_read_write32(gpio_ptr, control, + INT_SCI_SMI_MASK); + program_smi(control_flags, gevent_num); + break; + case GPIO_SCI_FLAG: + mem_read_write32(gpio_ptr, control, + INT_SCI_SMI_MASK); + get_sci_config_bits(control_flags, &bit_edge, + &bit_level); + edge_level |= bit_edge << gevent_num; + direction |= bit_level << gevent_num; + mask |= (1 << gevent_num); + soc_route_sci(gevent_num); + break; + default: + printk(BIOS_WARNING, "Error, flags 0x%08x\n", + control_flags); + break; + } + } else { + mem_read_write32(gpio_ptr, control, + AMD_GPIO_CONTROL_MASK); + } + } + + /* + * 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. + */ + mem_read_write32(inter_master, GPIO_INTERRUPT_EN, GPIO_INTERRUPT_EN); + + /* Set all SCI trigger direction (high/low) */ + mem_read_write32((uint32_t *) + (uintptr_t)(ACPIMMIO_SMI_BASE + SMI_SCI_TRIG), + direction, mask); + + /* Set all SCI trigger level (edge/level) */ + mem_read_write32((uint32_t *) + (uintptr_t)(ACPIMMIO_SMI_BASE + SMI_SCI_LEVEL), + edge_level, mask); +} + +int gpio_interrupt_status(gpio_t gpio) +{ + uintptr_t gpio_address = gpio_get_address(gpio); + uint32_t reg = read32((void *)gpio_address); + + if (reg & GPIO_INT_STATUS) { + /* Clear interrupt status, preserve wake status */ + reg &= ~GPIO_WAKE_STATUS; + write32((void *)gpio_address, reg); + return 1; + } + + return 0; +} |