aboutsummaryrefslogtreecommitdiff
path: root/src/soc/amd/common/block/gpio_banks/gpio.c
diff options
context:
space:
mode:
authorMarshall Dawson <marshalldawson3rd@gmail.com>2019-05-02 17:27:57 -0600
committerMartin Roth <martinroth@google.com>2019-06-06 17:57:40 +0000
commit251d305e73f76ca3b63654273f3b2bb3de775457 (patch)
tree76cf206b9b73033c21569005f12f80f1df7bbcbf /src/soc/amd/common/block/gpio_banks/gpio.c
parenteb5b0d05a71ec04d69699edebb6e71be2bb6ed09 (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/gpio.c')
-rw-r--r--src/soc/amd/common/block/gpio_banks/gpio.c310
1 files changed, 310 insertions, 0 deletions
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;
+}