blob: c141a2fd589d6c444d8d0a51b38595da5bd3739f [file] [log] [blame]
/* 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 */
static 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);