From 4e9588ddcffb9315b0a74ac62121efb76b7b2202 Mon Sep 17 00:00:00 2001 From: Scott Worley Date: Wed, 20 Dec 2017 17:08:09 -0500 Subject: 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 --- chip/mchp/gpio.c | 419 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 419 insertions(+) create mode 100644 chip/mchp/gpio.c (limited to 'chip/mchp/gpio.c') 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); + -- cgit v1.2.1