/* Copyright (c) 2013 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 Chrome EC */ #include "common.h" #include "console.h" #include "gpio.h" #include "hooks.h" #include "registers.h" #include "task.h" #include "util.h" /* Console output macros */ #define CPUTS(outstr) cputs(CC_GPIO, outstr) #define CPRINTF(format, args...) cprintf(CC_GPIO, format, ## args) /* For each EXTI bit, record which GPIO entry is using it */ static const struct gpio_info *exti_events[16]; static uint32_t expand_to_2bit_mask(uint32_t mask) { uint32_t mask_out = 0; while (mask) { int bit = get_next_bit(&mask); mask_out |= 3 << (bit * 2); } return mask_out; } void gpio_set_flags_by_mask(uint32_t port, uint32_t mask, uint32_t flags) { /* Bitmask for registers with 2 bits per GPIO pin */ const uint32_t mask2 = expand_to_2bit_mask(mask); uint32_t val; /* Set up pullup / pulldown */ val = STM32_GPIO_PUPDR(port) & ~mask2; if (flags & GPIO_PULL_UP) val |= 0x55555555 & mask2; /* Pull Up = 01 */ else if (flags & GPIO_PULL_DOWN) val |= 0xaaaaaaaa & mask2; /* Pull Down = 10 */ STM32_GPIO_PUPDR(port) = val; /* * Select open drain first, so that we don't glitch the signal when * changing the line to an output. */ if (flags & GPIO_OPEN_DRAIN) STM32_GPIO_OTYPER(port) |= mask; val = STM32_GPIO_MODER(port) & ~mask2; if (flags & GPIO_OUTPUT) { /* * Set pin level first to avoid glitching. This is harmless on * STM32L because the set/reset register isn't connected to the * output drivers until the pin is made an output. */ if (flags & GPIO_HIGH) STM32_GPIO_BSRR(port) = mask; else if (flags & GPIO_LOW) STM32_GPIO_BSRR(port) = mask << 16; /* General purpose, MODE = 01 */ val |= 0x55555555 & mask2; STM32_GPIO_MODER(port) = val; } else if (flags & GPIO_INPUT) { /* Input, MODE=00 */ STM32_GPIO_MODER(port) = val; } /* Set up interrupts if necessary */ ASSERT(!(flags & (GPIO_INT_F_LOW | GPIO_INT_F_HIGH))); if (flags & GPIO_INT_F_RISING) STM32_EXTI_RTSR |= mask; if (flags & GPIO_INT_F_FALLING) STM32_EXTI_FTSR |= mask; /* Interrupt is enabled by gpio_enable_interrupt() */ } void gpio_pre_init(void) { const struct gpio_info *g = gpio_list; int is_warm = 0; int i; /* Required to configure external IRQ lines (SYSCFG_EXTICRn) */ STM32_RCC_APB2ENR |= 1 << 0; if ((STM32_RCC_AHBENR & 0x3f) == 0x3f) { /* This is a warm reboot */ is_warm = 1; } else { /* * Enable all GPIOs clocks * * TODO(crosbug.com/p/23770): only enable the banks we need to, * and support disabling some of them in low-power idle. */ STM32_RCC_AHBENR |= 0x3f; } /* Set all GPIOs to defaults */ for (i = 0; i < GPIO_COUNT; i++, g++) { int 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); /* Set up GPIO based on flags */ gpio_set_flags_by_mask(g->port, g->mask, flags); } } static void gpio_init(void) { /* Enable IRQs now that pins are set up */ task_enable_irq(STM32_IRQ_EXTI0); task_enable_irq(STM32_IRQ_EXTI1); task_enable_irq(STM32_IRQ_EXTI2); task_enable_irq(STM32_IRQ_EXTI3); task_enable_irq(STM32_IRQ_EXTI4); task_enable_irq(STM32_IRQ_EXTI9_5); task_enable_irq(STM32_IRQ_EXTI15_10); } DECLARE_HOOK(HOOK_INIT, gpio_init, HOOK_PRIO_DEFAULT); void gpio_set_alternate_function(uint32_t port, uint32_t mask, int func) { int bit; uint8_t half; uint32_t afr; uint32_t moder = STM32_GPIO_MODER(port); if (func < 0) { /* Return to normal GPIO function, defaulting to input. */ while (mask) { bit = get_next_bit(&mask); moder &= ~(0x3 << (bit * 2)); } STM32_GPIO_MODER(port) = moder; return; } /* Low half of the GPIO bank */ half = mask & 0xff; afr = STM32_GPIO_AFRL(port); while (half) { bit = 31 - __builtin_clz(half); afr &= ~(0xf << (bit * 4)); afr |= func << (bit * 4); moder &= ~(0x3 << (bit * 2 + 0)); moder |= 0x2 << (bit * 2 + 0); half &= ~(1 << bit); } STM32_GPIO_AFRL(port) = afr; /* High half of the GPIO bank */ half = mask >> 8; afr = STM32_GPIO_AFRH(port); while (half) { bit = 31 - __builtin_clz(half); afr &= ~(0xf << (bit * 4)); afr |= func << (bit * 4); moder &= ~(0x3 << (bit * 2 + 16)); moder |= 0x2 << (bit * 2 + 16); half &= ~(1 << bit); } STM32_GPIO_AFRH(port) = afr; STM32_GPIO_MODER(port) = moder; } test_mockable int gpio_get_level(enum gpio_signal signal) { return !!(STM32_GPIO_IDR(gpio_list[signal].port) & gpio_list[signal].mask); } uint16_t *gpio_get_level_reg(enum gpio_signal signal, uint32_t *mask) { *mask = gpio_list[signal].mask; return (uint16_t *)&STM32_GPIO_IDR(gpio_list[signal].port); } void gpio_set_level(enum gpio_signal signal, int value) { STM32_GPIO_BSRR(gpio_list[signal].port) = gpio_list[signal].mask << (value ? 0 : 16); } int gpio_enable_interrupt(enum gpio_signal signal) { const struct gpio_info *g = gpio_list + signal; uint32_t bit, group, shift, bank; /* Fail if not implemented or no interrupt handler */ if (!g->mask || !g->irq_handler) return EC_ERROR_INVAL; bit = 31 - __builtin_clz(g->mask); if (exti_events[bit]) { CPRINTF("Overriding %s with %s on EXTI%d\n", exti_events[bit]->name, g->name, bit); } exti_events[bit] = g; group = bit / 4; shift = (bit % 4) * 4; bank = (g->port - STM32_GPIOA_BASE) / 0x400; STM32_SYSCFG_EXTICR(group) = (STM32_SYSCFG_EXTICR(group) & ~(0xF << shift)) | (bank << shift); STM32_EXTI_IMR |= g->mask; return EC_SUCCESS; } /*****************************************************************************/ /* Interrupt handler */ void gpio_interrupt(void) { int bit; const struct gpio_info *g; uint32_t pending = STM32_EXTI_PR; STM32_EXTI_PR = pending; while (pending) { bit = 31 - __builtin_clz(pending); g = exti_events[bit]; if (g && g->irq_handler) g->irq_handler(g - gpio_list); pending &= ~(1 << bit); } } DECLARE_IRQ(STM32_IRQ_EXTI0, gpio_interrupt, 1); DECLARE_IRQ(STM32_IRQ_EXTI1, gpio_interrupt, 1); DECLARE_IRQ(STM32_IRQ_EXTI2, gpio_interrupt, 1); DECLARE_IRQ(STM32_IRQ_EXTI3, gpio_interrupt, 1); DECLARE_IRQ(STM32_IRQ_EXTI4, gpio_interrupt, 1); DECLARE_IRQ(STM32_IRQ_EXTI9_5, gpio_interrupt, 1); DECLARE_IRQ(STM32_IRQ_EXTI15_10, gpio_interrupt, 1);