| /* Copyright 2017 The ChromiumOS Authors |
| * 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 "lpc_chip.h" |
| #include "registers.h" |
| #include "system.h" |
| #include "task.h" |
| #include "tfdp_chip.h" |
| #include "timer.h" |
| #include "util.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; |
| }; |
| |
| /* |
| * Mapping from GPIO port to GIRQ info |
| * MEC17xx 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[] = { { 11, 0 }, { 10, 1 }, |
| { 9, 2 }, { 8, 3 }, |
| { 12, 4 }, { 26, 5 } }; |
| BUILD_ASSERT(ARRAY_SIZE(int_map) == MCHP_GPIO_MAX_PORT); |
| |
| /* |
| * These pins default to BGPO functionality. BGPO overrides GPIO Control |
| * register programming. If the pin is in the GPIO list the user wants to |
| * use the pin as GPIO and we must disable BGPIO functionality for this pin. |
| */ |
| struct bgpo_pin { |
| uint16_t pin; |
| uint8_t bgpo_pos; |
| }; |
| |
| static const struct bgpo_pin bgpo_list[] = { |
| { 0101, 1 }, /* GPIO 0101 */ |
| { 0102, 2 }, /* GPIO 0102 */ |
| #if defined(CHIP_FAMILY_MEC152X) |
| { 0253, 0 }, /* GPIO 0253 */ |
| #elif defined(CHIP_FAMILY_MEC170X) |
| { 0172, 3 }, /* GPIO 0172 */ |
| #endif |
| }; |
| |
| static const uint32_t bgpo_map[] = { |
| #if defined(CHIP_FAMILY_MEC152X) |
| 0, 0, (BIT(1) | BIT(2)), 0, 0, BIT(11) |
| #elif defined(CHIP_FAMILY_MEC170X) |
| 0, 0, (BIT(1) | BIT(2)), BIT(26), 0, 0 |
| #else |
| 0, 0, 0, 0, 0, 0 |
| #endif |
| }; |
| BUILD_ASSERT(ARRAY_SIZE(bgpo_map) == MCHP_GPIO_MAX_PORT); |
| |
| /* Check for BGPO capable pins on this port and disable BGPO feature */ |
| static void disable_bgpo(uint32_t port, uint32_t mask) |
| { |
| int i, n; |
| uint32_t gpnum; |
| uint32_t m = bgpo_map[port] & mask; |
| |
| while (m) { |
| i = __builtin_ffs(m) - 1; |
| gpnum = (port * 32U) + i; |
| for (n = 0; n < ARRAY_SIZE(bgpo_list); n++) |
| if (gpnum == bgpo_list[n].pin) |
| MCHP_WKTIMER_BGPO_POWER &= |
| ~BIT(bgpo_list[n].bgpo_pos); |
| m &= ~BIT(i); |
| } |
| } |
| |
| /* |
| * 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, |
| enum gpio_alternate_func func) |
| { |
| int i; |
| uint32_t val; |
| |
| if (port >= MCHP_GPIO_MAX_PORT) |
| return; |
| |
| while (mask) { |
| i = __builtin_ffs(mask) - 1; |
| val = MCHP_GPIO_CTL(port, i); |
| val &= ~(BIT(12) | BIT(13)); |
| /* mux_control = DEFAULT, indicates GPIO */ |
| if (func > GPIO_ALT_FUNC_DEFAULT) |
| val |= (func & 0x3) << 12; |
| MCHP_GPIO_CTL(port, i) = val; |
| mask &= ~BIT(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 & BIT(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) |= BIT(16); |
| else |
| MCHP_GPIO_CTL(gpio_list[signal].port, i) &= ~BIT(16); |
| } |
| |
| /* |
| * Add support for new #ifdef CONFIG_CMD_GPIO_POWER_DOWN. |
| * If GPIO_POWER_DOWN 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. |
| * NOTE 2: MEC152x family implements input pad disable (bit[15]=1). |
| */ |
| void gpio_set_flags_by_mask(uint32_t port, uint32_t mask, uint32_t flags) |
| { |
| int i; |
| uint32_t val; |
| |
| if (port >= MCHP_GPIO_MAX_PORT) |
| return; |
| |
| while (mask) { |
| i = GPIO_MASK_TO_NUM(mask); |
| mask &= ~BIT(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_CTRL_DIS_INPUT_BIT); |
| |
| MCHP_GPIO_CTL(port, i) = val; |
| continue; |
| } |
| #endif |
| val &= ~(MCHP_GPIO_CTRL_PWR_MASK | |
| MCHP_GPIO_CTRL_DIS_INPUT_BIT); |
| |
| 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; |
| |
| if (port >= MCHP_GPIO_MAX_PORT) |
| return; |
| |
| while (mask) { |
| i = GPIO_MASK_TO_NUM(mask); |
| mask &= ~BIT(i); |
| MCHP_GPIO_CTL(port, i) = |
| (MCHP_GPIO_CTRL_PWR_OFF | MCHP_GPIO_INTDET_DISABLED | |
| MCHP_GPIO_CTRL_DIS_INPUT_BIT); |
| } |
| } |
| |
| 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 | |
| MCHP_GPIO_CTRL_DIS_INPUT_BIT); |
| |
| 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) = BIT(i); |
| MCHP_INT_BLK_EN |= BIT(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) = BIT(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) = BIT(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); |
| |
| disable_bgpo(g->port, g->mask); |
| |
| gpio_set_flags_by_mask(g->port, g->mask, flags); |
| |
| /* Use as GPIO, not alternate function */ |
| gpio_set_alternate_function(g->port, g->mask, |
| GPIO_ALT_FUNC_NONE); |
| } |
| } |
| |
| /* 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 zero based GPIO port number [0, 5] |
| * @note __builtin_ffs(x) returns bitpos+1 of least significant 1-bit |
| * in x or 0 if no bits are set. |
| */ |
| static void gpio_interrupt(int girq, int port) |
| { |
| int i, bit; |
| const struct gpio_info *g = gpio_list; |
| uint32_t sts = MCHP_INT_RESULT(girq); |
| |
| /* RW1C, no need for read-modify-write */ |
| MCHP_INT_SOURCE(girq) = sts; |
| |
| trace12(0, GPIO, 0, "GPIO GIRQ %d result = 0x%08x", girq, sts); |
| trace12(0, GPIO, 0, "GPIO ParIn[%d] = 0x%08x", port, |
| MCHP_GPIO_PARIN(port)); |
| |
| for (i = 0; (i < GPIO_IH_COUNT) && sts; ++i, ++g) { |
| if (g->port != port) |
| continue; |
| |
| bit = __builtin_ffs(g->mask); |
| if (bit) { |
| bit--; |
| if (sts & BIT(bit)) { |
| trace12(0, GPIO, 0, "Bit[%d]: handler @ 0x%08x", |
| bit, (uint32_t)gpio_irq_handlers[i]); |
| gpio_irq_handlers[i](i); |
| } |
| sts &= ~BIT(bit); |
| } |
| } |
| } |
| |
| #define GPIO_IRQ_FUNC(irqfunc, girq, port) \ |
| static void irqfunc(void) \ |
| { \ |
| gpio_interrupt(girq, port); \ |
| } |
| |
| GPIO_IRQ_FUNC(__girq_8_interrupt, 8, 3); |
| GPIO_IRQ_FUNC(__girq_9_interrupt, 9, 2); |
| GPIO_IRQ_FUNC(__girq_10_interrupt, 10, 1); |
| GPIO_IRQ_FUNC(__girq_11_interrupt, 11, 0); |
| GPIO_IRQ_FUNC(__girq_12_interrupt, 12, 4); |
| GPIO_IRQ_FUNC(__girq_26_interrupt, 26, 5); |
| |
| #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); |