summaryrefslogtreecommitdiff
path: root/chip/mchp/gpio.c
diff options
context:
space:
mode:
authorScott Worley <scott.worley@microchip.corp-partner.google.com>2017-12-20 17:08:09 -0500
committerchrome-bot <chrome-bot@chromium.org>2017-12-28 12:35:07 -0800
commit4e9588ddcffb9315b0a74ac62121efb76b7b2202 (patch)
treed1ce8cea735b68c5e43f7631af2e90026006df58 /chip/mchp/gpio.c
parentc334f648bd644f5e72e841458fdac6796efa1ceb (diff)
downloadchrome-ec-4e9588ddcffb9315b0a74ac62121efb76b7b2202.tar.gz
ec_chip_mchp: Add MCHP chip folder
BRANCH=none BUG= TEST=Review only. Committing small pieces until all code passes review. Change-Id: I9d16f95314a7c97b11c4fe61602c6db2621e6024 Signed-off-by: Scott Worley <scott.worley@microchip.corp-partner.google.com>
Diffstat (limited to 'chip/mchp/gpio.c')
-rw-r--r--chip/mchp/gpio.c419
1 files changed, 419 insertions, 0 deletions
diff --git a/chip/mchp/gpio.c b/chip/mchp/gpio.c
new file mode 100644
index 0000000000..8a89619fbc
--- /dev/null
+++ b/chip/mchp/gpio.c
@@ -0,0 +1,419 @@
+/* Copyright 2017 The Chromium OS Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+/* GPIO module for MCHP MEC */
+
+#include "common.h"
+#include "gpio.h"
+#include "hooks.h"
+#include "registers.h"
+#include "system.h"
+#include "task.h"
+#include "timer.h"
+#include "util.h"
+#include "lpc_chip.h"
+#include "tfdp_chip.h"
+
+/* Console output macros */
+#define CPUTS(outstr) cputs(CC_LPC, outstr)
+#define CPRINTS(format, args...) cprints(CC_LPC, format, ## args)
+
+
+struct gpio_int_mapping {
+ int8_t girq_id;
+ int8_t port_offset;
+};
+
+/* TODO will this mapping change for other MEC chips?
+ * Mapping from GPIO port to GIRQ info
+ * MEC1701 each bank contains 32 GPIO's. Pin Id is the bit position [0:31]
+ * Bank GPIO's GIRQ
+ * 0 0000 - 0036 11
+ * 1 0040 - 0076 10
+ * 2 0100 - 0135 9
+ * 3 0140 - 0175 8
+ * 4 0200 - 0235 12
+ * 5 0240 - 0276 26
+ */
+static const struct gpio_int_mapping int_map[6] = {
+ { 11, 0 }, { 10, 1 }, { 9, 2 },
+ { 8, 3 }, { 12, 4 }, { 26, 5 }
+};
+
+
+
+/*
+ * NOTE: GCC __builtin_ffs(val) returns (index + 1) of least significant
+ * 1-bit of val or if val == 0 returns 0
+ */
+void gpio_set_alternate_function(uint32_t port, uint32_t mask, int func)
+{
+ int i;
+ uint32_t val;
+
+ while (mask) {
+ i = __builtin_ffs(mask) - 1;
+ val = MCHP_GPIO_CTL(port, i);
+ val &= ~((1 << 12) | (1 << 13));
+ /* mux_control = 0 indicates GPIO */
+ if (func > 0)
+ val |= (func & 0x3) << 12;
+ MCHP_GPIO_CTL(port, i) = val;
+ mask &= ~(1 << i);
+ }
+}
+
+test_mockable int gpio_get_level(enum gpio_signal signal)
+{
+ uint32_t mask = gpio_list[signal].mask;
+ int i;
+ uint32_t val;
+
+ if (mask == 0)
+ return 0;
+ i = GPIO_MASK_TO_NUM(mask);
+ val = MCHP_GPIO_CTL(gpio_list[signal].port, i);
+
+ return (val & (1 << 24)) ? 1 : 0;
+}
+
+void gpio_set_level(enum gpio_signal signal, int value)
+{
+ uint32_t mask = gpio_list[signal].mask;
+ int i;
+
+ if (mask == 0)
+ return;
+ i = GPIO_MASK_TO_NUM(mask);
+
+ if (value)
+ MCHP_GPIO_CTL(gpio_list[signal].port, i) |= (1 << 16);
+ else
+ MCHP_GPIO_CTL(gpio_list[signal].port, i) &= ~(1 << 16);
+}
+
+/*
+ * Add support for new #ifdef CONFIG_CMD_GPIO_POWER_DOWN.
+ * If GPIO_POWER_DONW flag is set force GPIO Control to
+ * GPIO input, interrupt detect disabled, power control field
+ * in bits[3:2]=10b.
+ * NOTE: if interrupt detect is enabled when pin is powered down
+ * then a false edge may be detected.
+ *
+ */
+void gpio_set_flags_by_mask(uint32_t port, uint32_t mask, uint32_t flags)
+{
+ int i;
+ uint32_t val;
+
+ while (mask) {
+ i = GPIO_MASK_TO_NUM(mask);
+ mask &= ~(1 << i);
+ val = MCHP_GPIO_CTL(port, i);
+
+#ifdef CONFIG_GPIO_POWER_DOWN
+ if (flags & GPIO_POWER_DOWN) {
+ val = (MCHP_GPIO_CTRL_PWR_OFF +
+ MCHP_GPIO_INTDET_DISABLED);
+ MCHP_GPIO_CTL(port, i) = val;
+ continue;
+ }
+#endif
+ val &= ~(MCHP_GPIO_CTRL_PWR_MASK);
+ val |= MCHP_GPIO_CTRL_PWR_VTR;
+
+ /*
+ * Select open drain first, so that we don't
+ * glitch the signal when changing the line to
+ * an output.
+ */
+ if (flags & GPIO_OPEN_DRAIN)
+ val |= (MCHP_GPIO_OPEN_DRAIN);
+ else
+ val &= ~(MCHP_GPIO_OPEN_DRAIN);
+
+ if (flags & GPIO_OUTPUT) {
+ val |= (MCHP_GPIO_OUTPUT);
+ val &= ~(MCHP_GPIO_OUTSEL_PAR);
+ } else {
+ val &= ~(MCHP_GPIO_OUTPUT);
+ val |= (MCHP_GPIO_OUTSEL_PAR);
+ }
+
+ /* Handle pull-up / pull-down */
+ val &= ~(MCHP_GPIO_CTRL_PUD_MASK);
+ if (flags & GPIO_PULL_UP)
+ val |= MCHP_GPIO_CTRL_PUD_PU;
+ else if (flags & GPIO_PULL_DOWN)
+ val |= MCHP_GPIO_CTRL_PUD_PD;
+ else
+ val |= MCHP_GPIO_CTRL_PUD_NONE;
+
+ /* Set up interrupt */
+ val &= ~(MCHP_GPIO_INTDET_MASK);
+ switch (flags & GPIO_INT_ANY) {
+ case GPIO_INT_F_RISING:
+ val |= MCHP_GPIO_INTDET_EDGE_RIS;
+ break;
+ case GPIO_INT_F_FALLING:
+ val |= MCHP_GPIO_INTDET_EDGE_FALL;
+ break;
+ case GPIO_INT_BOTH: /* both edges */
+ val |= MCHP_GPIO_INTDET_EDGE_BOTH;
+ break;
+ case GPIO_INT_F_LOW:
+ val |= MCHP_GPIO_INTDET_LVL_LO;
+ break;
+ case GPIO_INT_F_HIGH:
+ val |= MCHP_GPIO_INTDET_LVL_HI;
+ break;
+ default:
+ val |= MCHP_GPIO_INTDET_DISABLED;
+ break;
+ }
+
+ /* Set up level */
+ if (flags & GPIO_HIGH)
+ val |= (MCHP_GPIO_CTRL_OUT_LVL);
+ else if (flags & GPIO_LOW)
+ val &= ~(MCHP_GPIO_CTRL_OUT_LVL);
+
+ MCHP_GPIO_CTL(port, i) = val;
+ }
+}
+
+void gpio_power_off_by_mask(uint32_t port, uint32_t mask)
+{
+ int i;
+
+ while (mask) {
+ i = GPIO_MASK_TO_NUM(mask);
+ mask &= ~(1 << i);
+
+ MCHP_GPIO_CTL(port, i) = (MCHP_GPIO_CTRL_PWR_OFF +
+ MCHP_GPIO_INTDET_DISABLED);
+ }
+}
+
+int gpio_power_off(enum gpio_signal signal)
+{
+ int i, port;
+
+ if (gpio_list[signal].mask == 0)
+ return EC_ERROR_INVAL;
+
+ i = GPIO_MASK_TO_NUM(gpio_list[signal].mask);
+ port = gpio_list[signal].port;
+
+ MCHP_GPIO_CTL(port, i) = (MCHP_GPIO_CTRL_PWR_OFF +
+ MCHP_GPIO_INTDET_DISABLED);
+
+ return EC_SUCCESS;
+}
+
+/*
+ * gpio_list[signal].port = [0, 6] each port contains up to 32 pins
+ * gpio_list[signal].mask = bit mask in 32-bit port
+ * NOTE: MCHP GPIO are always aggregated not direct connected to NVIC.
+ * GPIO's are aggregated into banks of 32 pins.
+ * Each bank/port are connected to a GIRQ.
+ * int_map[port].girq_id is the GIRQ ID
+ * The bit number in the GIRQ registers is the same as the bit number
+ * in the GPIO bank.
+ */
+int gpio_enable_interrupt(enum gpio_signal signal)
+{
+ int i, port, girq_id;
+
+ if (gpio_list[signal].mask == 0)
+ return EC_SUCCESS;
+
+ i = GPIO_MASK_TO_NUM(gpio_list[signal].mask);
+ port = gpio_list[signal].port;
+ girq_id = int_map[port].girq_id;
+
+ MCHP_INT_ENABLE(girq_id) = (1 << i);
+ MCHP_INT_BLK_EN |= (1 << girq_id);
+
+ return EC_SUCCESS;
+}
+
+int gpio_disable_interrupt(enum gpio_signal signal)
+{
+ int i, port, girq_id;
+
+ if (gpio_list[signal].mask == 0)
+ return EC_SUCCESS;
+
+ i = GPIO_MASK_TO_NUM(gpio_list[signal].mask);
+ port = gpio_list[signal].port;
+ girq_id = int_map[port].girq_id;
+
+
+ MCHP_INT_DISABLE(girq_id) = (1 << i);
+
+ return EC_SUCCESS;
+}
+
+/*
+ * MCHP Interrupt Source is R/W1C no need for read-modify-write.
+ * GPIO's are aggregated meaning the NVIC Pending bit may be
+ * set for another GPIO in the GIRQ. You can clear NVIC pending
+ * and the hardware should re-assert it within one Cortex-M4 clock.
+ * If the Cortex-M4 is clocked slower than AHB then the Cortex-M4
+ * will take longer to register the interrupt. Not clearing NVIC
+ * pending leave a pending status if only the GPIO this routine
+ * clears is pending.
+ * NVIC (system control) register space is strongly-ordered
+ * Interrupt Aggregator is in Device space (system bus connected
+ * to AHB) with the Cortex-M4 write buffer.
+ * We need to insure the write to aggregator register in device
+ * AHB space completes before NVIC pending is cleared.
+ * The Cortex-M4 memory ordering rules imply Device access
+ * comes before strongly ordered access. Cortex-M4 will not re-order
+ * the writes. Due to the presence of the write buffer a DSB will
+ * not guarantee the clearing of the device status completes. Add
+ * a read back before clearing NVIC pending.
+ * GIRQ 8, 9, 10, 11, 12, 26 map to NVIC inputs 0, 1, 2, 3, 4, and 18.
+ */
+int gpio_clear_pending_interrupt(enum gpio_signal signal)
+{
+ int i, port, girq_id;
+
+ if (gpio_list[signal].mask == 0)
+ return EC_SUCCESS;
+
+ i = GPIO_MASK_TO_NUM(gpio_list[signal].mask);
+ port = gpio_list[signal].port;
+ girq_id = int_map[port].girq_id;
+
+ /* Clear interrupt source sticky status bit even if not enabled */
+ MCHP_INT_SOURCE(girq_id) = (1 << i);
+ i = MCHP_INT_SOURCE(girq_id);
+ task_clear_pending_irq(girq_id - 8);
+
+ return EC_SUCCESS;
+}
+
+/*
+ * MCHP NOTE - called from main before scheduler started
+ */
+void gpio_pre_init(void)
+{
+ int i;
+ int flags;
+ int is_warm = system_is_reboot_warm();
+ const struct gpio_info *g = gpio_list;
+
+
+ for (i = 0; i < GPIO_COUNT; i++, g++) {
+ flags = g->flags;
+
+ if (flags & GPIO_DEFAULT)
+ continue;
+
+ /*
+ * If this is a warm reboot, don't set the output levels or
+ * we'll shut off the AP.
+ */
+ if (is_warm)
+ flags &= ~(GPIO_LOW | GPIO_HIGH);
+
+ gpio_set_flags_by_mask(g->port, g->mask, flags);
+
+ /* Use as GPIO, not alternate function */
+ gpio_set_alternate_function(g->port, g->mask, -1);
+ }
+}
+
+/* Clear any interrupt flags before enabling GPIO interrupt
+ * Original code has flaws.
+ * Writing result register to source only clears bits that have their
+ * enable and sources bits set.
+ * We must clear the NVIC pending R/W bit before setting NVIC enable.
+ * NVIC Pending is only cleared by the NVIC HW on ISR entry.
+ * Modifications are:
+ * 1. Clear all status bits in each GPIO GIRQ. This assumes any edges
+ * will occur after gpio_init. The old code is also making this
+ * assumption for the GPIO's that have been enabled.
+ * 2. Clear NVIC pending to prevent ISR firing on false edge.
+ */
+#define ENABLE_GPIO_GIRQ(x) \
+ do { \
+ MCHP_INT_SOURCE(x) = 0xfffffffful; \
+ task_clear_pending_irq(MCHP_IRQ_GIRQ ## x); \
+ task_enable_irq(MCHP_IRQ_GIRQ ## x); \
+ } while (0)
+
+
+static void gpio_init(void)
+{
+ ENABLE_GPIO_GIRQ(8);
+ ENABLE_GPIO_GIRQ(9);
+ ENABLE_GPIO_GIRQ(10);
+ ENABLE_GPIO_GIRQ(11);
+ ENABLE_GPIO_GIRQ(12);
+ ENABLE_GPIO_GIRQ(26);
+}
+DECLARE_HOOK(HOOK_INIT, gpio_init, HOOK_PRIO_DEFAULT);
+
+/************************************************************************/
+/* Interrupt handlers */
+
+
+/**
+ * Handler for each GIRQ interrupt. This reads and clears the interrupt
+ * bits for the GIRQ interrupt, then finds and calls the corresponding
+ * GPIO interrupt handlers.
+ *
+ * @param girq GIRQ index
+ * @param port_offset GPIO port offset for the given GIRQ
+ */
+static void gpio_interrupt(int girq)
+{
+ int i, bit;
+ const struct gpio_info *g = gpio_list;
+ uint32_t sts = MCHP_INT_RESULT(girq);
+
+ /* CPRINTS("MEC1701 GPIO GIRQ %d result = 0x%08x", girq, sts); */
+ trace12(0, GPIO, 0, "GPIO GIRQ %d result = 0x%08x", girq, sts);
+
+ /* RW1C, no need for read-modify-write */
+ MCHP_INT_SOURCE(girq) = sts;
+
+ for (i = 0; i < GPIO_IH_COUNT && sts; ++i, ++g) {
+ bit = __builtin_ffs(g->mask) - 1;
+ if (sts & (1 << bit))
+ gpio_irq_handlers[i](i);
+ sts &= ~(1 << bit);
+ }
+}
+
+#define GPIO_IRQ_FUNC(irqfunc, girq) \
+ void irqfunc(void) \
+ { \
+ gpio_interrupt(girq); \
+ }
+
+GPIO_IRQ_FUNC(__girq_8_interrupt, 8);
+GPIO_IRQ_FUNC(__girq_9_interrupt, 9);
+GPIO_IRQ_FUNC(__girq_10_interrupt, 10);
+GPIO_IRQ_FUNC(__girq_11_interrupt, 11);
+GPIO_IRQ_FUNC(__girq_12_interrupt, 12);
+GPIO_IRQ_FUNC(__girq_26_interrupt, 26);
+
+#undef GPIO_IRQ_FUNC
+
+/*
+ * Declare IRQs. Nesting this macro inside the GPIO_IRQ_FUNC macro works
+ * poorly because DECLARE_IRQ() stringizes its inputs.
+ */
+DECLARE_IRQ(MCHP_IRQ_GIRQ8, __girq_8_interrupt, 1);
+DECLARE_IRQ(MCHP_IRQ_GIRQ9, __girq_9_interrupt, 1);
+DECLARE_IRQ(MCHP_IRQ_GIRQ10, __girq_10_interrupt, 1);
+DECLARE_IRQ(MCHP_IRQ_GIRQ11, __girq_11_interrupt, 1);
+DECLARE_IRQ(MCHP_IRQ_GIRQ12, __girq_12_interrupt, 1);
+DECLARE_IRQ(MCHP_IRQ_GIRQ26, __girq_26_interrupt, 1);
+