diff options
Diffstat (limited to 'chip/lm4/gpio.c')
-rw-r--r-- | chip/lm4/gpio.c | 260 |
1 files changed, 260 insertions, 0 deletions
diff --git a/chip/lm4/gpio.c b/chip/lm4/gpio.c new file mode 100644 index 0000000000..4c11e30d58 --- /dev/null +++ b/chip/lm4/gpio.c @@ -0,0 +1,260 @@ +/* Copyright (c) 2012 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 "board.h" +#include "gpio.h" +#include "power_button.h" +#include "registers.h" +#include "task.h" +#include "timer.h" +#include "uart.h" +#include "util.h" + + +/* 0-terminated list of GPIO bases */ +const uint32_t gpio_bases[] = { + LM4_GPIO_A, LM4_GPIO_B, LM4_GPIO_C, LM4_GPIO_D, + LM4_GPIO_E, LM4_GPIO_F, LM4_GPIO_G, LM4_GPIO_H, + LM4_GPIO_J, LM4_GPIO_K, LM4_GPIO_L, LM4_GPIO_M, + LM4_GPIO_N, LM4_GPIO_P, LM4_GPIO_Q, 0 +}; + + +/* Signal information from board.c. Must match order from enum gpio_signal. */ +extern const struct gpio_info gpio_list[GPIO_COUNT]; + + +/* Find the index of a GPIO port base address (LM4_GPIO_[A-Q]); this is used by + * the clock gating registers. Returns the index, or -1 if no match. */ +static int find_gpio_port_index(uint32_t port_base) +{ + int i; + for (i = 0; gpio_bases[i]; i++) { + if (gpio_bases[i] == port_base) + return i; + } + return -1; +} + + +int gpio_pre_init(void) +{ + volatile uint32_t scratch __attribute__((unused)); + const struct gpio_info *g = gpio_list; + int i; + + /* Enable clocks to all the GPIO blocks (since we use all of them as + * GPIOs) */ + LM4_SYSTEM_RCGCGPIO |= 0x7fff; + scratch = LM4_SYSTEM_RCGCGPIO; /* Delay a few clocks */ + + /* Disable GPIO commit control for PD7 and PF0, since we don't use the + * NMI pin function. */ + LM4_GPIO_LOCK(LM4_GPIO_D) = LM4_GPIO_LOCK_UNLOCK; + LM4_GPIO_CR(LM4_GPIO_D) |= 0x80; + LM4_GPIO_LOCK(LM4_GPIO_D) = 0; + LM4_GPIO_LOCK(LM4_GPIO_F) = LM4_GPIO_LOCK_UNLOCK; + LM4_GPIO_CR(LM4_GPIO_F) |= 0x01; + LM4_GPIO_LOCK(LM4_GPIO_F) = 0; + + /* Clear SSI0 alternate function on PA2:5 */ + LM4_GPIO_AFSEL(LM4_GPIO_A) &= ~0x3c; + + /* Set all GPIOs to defaults */ + for (i = 0; i < GPIO_COUNT; i++, g++) { + + /* Use as GPIO, not alternate function */ + gpio_set_alternate_function(g->port, g->mask, 0); + + /* Handle GPIO direction */ + if (g->flags & GPIO_OUTPUT) { + /* Output with default level */ + LM4_GPIO_DIR(g->port) |= g->mask; + /* Must set level after direction; writes to GPIO_DATA + * before direction is output appear to be ignored. */ + gpio_set_level(i, g->flags & GPIO_HIGH); + } else { + /* Input */ + if (g->flags & GPIO_PULL) { + /* With pull up/down */ + if (g->flags & GPIO_HIGH) + LM4_GPIO_PUR(g->port) |= g->mask; + else + LM4_GPIO_PDR(g->port) |= g->mask; + } + } + + /* Set up interrupts if necessary */ + if (g->flags & GPIO_INT_LEVEL) + LM4_GPIO_IS(g->port) |= g->mask; + if (g->flags & (GPIO_INT_RISING | GPIO_INT_HIGH)) + LM4_GPIO_IEV(g->port) |= g->mask; + if (g->flags & GPIO_INT_BOTH) + LM4_GPIO_IBE(g->port) |= g->mask; + /* Interrupt is enabled by gpio_enable_interrupt() */ + } + + /* Enable IRQs now that pins are set up */ + task_enable_irq(LM4_IRQ_GPIOA); + task_enable_irq(LM4_IRQ_GPIOB); + task_enable_irq(LM4_IRQ_GPIOC); + task_enable_irq(LM4_IRQ_GPIOD); + task_enable_irq(LM4_IRQ_GPIOE); + task_enable_irq(LM4_IRQ_GPIOF); + task_enable_irq(LM4_IRQ_GPIOG); +#if (KB_SCAN_ROW_IRQ != LM4_IRQ_GPIOH) + task_enable_irq(LM4_IRQ_GPIOH); +#endif + task_enable_irq(LM4_IRQ_GPIOJ); + task_enable_irq(LM4_IRQ_GPIOK); + task_enable_irq(LM4_IRQ_GPIOL); + task_enable_irq(LM4_IRQ_GPIOM); +#if (KB_SCAN_ROW_IRQ != LM4_IRQ_GPION) + task_enable_irq(LM4_IRQ_GPION); +#endif + task_enable_irq(LM4_IRQ_GPIOP); + task_enable_irq(LM4_IRQ_GPIOQ); + + return EC_SUCCESS; +} + + +void gpio_set_alternate_function(int port, int mask, int func) +{ + int port_index = find_gpio_port_index(port); + int cgmask; + + if (port_index < 0) + return; /* TODO: assert */ + + /* Enable the GPIO port if necessary */ + cgmask = 1 << port_index; + if ((LM4_SYSTEM_RCGCGPIO & cgmask) != cgmask) { + volatile uint32_t scratch __attribute__((unused)); + LM4_SYSTEM_RCGCGPIO |= cgmask; + /* Delay a few clocks before accessing GPIO registers on that + * port. */ + scratch = LM4_SYSTEM_RCGCGPIO; + } + + if (func) { + int pctlmask = 0; + int i; + /* Expand mask from bits to nibbles */ + for (i = 0; i < 8; i++) { + if (mask & (1 << i)) + pctlmask |= 1 << (4 * i); + } + + LM4_GPIO_PCTL(port) = + (LM4_GPIO_PCTL(port) & ~(pctlmask * 0xf)) | + (pctlmask * func); + LM4_GPIO_AFSEL(port) |= mask; + } else { + LM4_GPIO_AFSEL(port) &= ~mask; + } + LM4_GPIO_DEN(port) |= mask; +} + + +int gpio_get_level(enum gpio_signal signal) +{ + return LM4_GPIO_DATA(gpio_list[signal].port, + gpio_list[signal].mask) ? 1 : 0; +} + + +int gpio_set_level(enum gpio_signal signal, int value) +{ + /* Ok to write 0xff becuase LM4_GPIO_DATA bit-masks only the bit + * we care about. */ + LM4_GPIO_DATA(gpio_list[signal].port, + gpio_list[signal].mask) = (value ? 0xff : 0); + return EC_SUCCESS; +} + + +int gpio_enable_interrupt(enum gpio_signal signal) +{ + const struct gpio_info *g = gpio_list + signal; + + /* Fail if no interrupt handler */ + if (!g->irq_handler) + return EC_ERROR_UNKNOWN; + + LM4_GPIO_IM(g->port) |= g->mask; + return EC_SUCCESS; +} + +/*****************************************************************************/ +/* Interrupt handlers */ + +static void gpio_interrupt(int port, uint32_t mis) +{ + int i = 0; + const struct gpio_info *g = gpio_list; + + for (i = 0; i < GPIO_COUNT; i++, g++) { + if (port == g->port && (mis & g->mask) && g->irq_handler) + g->irq_handler(i); + } +} + +/* Handlers for each GPIO port. These read and clear the interrupt bits for + * the port, then call the master handler above. */ +#define GPIO_IRQ_FUNC(irqfunc, gpiobase) \ + static void irqfunc(void) \ + { \ + uint32_t mis = LM4_GPIO_MIS(gpiobase); \ + LM4_GPIO_ICR(gpiobase) = mis; \ + gpio_interrupt(gpiobase, mis); \ + } + +GPIO_IRQ_FUNC(__gpio_a_interrupt, LM4_GPIO_A); +GPIO_IRQ_FUNC(__gpio_b_interrupt, LM4_GPIO_B); +GPIO_IRQ_FUNC(__gpio_c_interrupt, LM4_GPIO_C); +GPIO_IRQ_FUNC(__gpio_d_interrupt, LM4_GPIO_D); +GPIO_IRQ_FUNC(__gpio_e_interrupt, LM4_GPIO_E); +GPIO_IRQ_FUNC(__gpio_f_interrupt, LM4_GPIO_F); +GPIO_IRQ_FUNC(__gpio_g_interrupt, LM4_GPIO_G); +#if (KB_SCAN_ROW_GPIO != LM4_GPIO_H) +GPIO_IRQ_FUNC(__gpio_h_interrupt, LM4_GPIO_H); +#endif +GPIO_IRQ_FUNC(__gpio_j_interrupt, LM4_GPIO_J); +GPIO_IRQ_FUNC(__gpio_k_interrupt, LM4_GPIO_K); +GPIO_IRQ_FUNC(__gpio_l_interrupt, LM4_GPIO_L); +GPIO_IRQ_FUNC(__gpio_m_interrupt, LM4_GPIO_M); +#if (KB_SCAN_ROW_GPIO != LM4_GPIO_N) +GPIO_IRQ_FUNC(__gpio_n_interrupt, LM4_GPIO_N); +#endif +GPIO_IRQ_FUNC(__gpio_p_interrupt, LM4_GPIO_P); +GPIO_IRQ_FUNC(__gpio_q_interrupt, LM4_GPIO_Q); + +#undef GPIO_IRQ_FUNC + +/* Declare IRQs */ +/* TODO: nesting this macro inside the GPIO_IRQ_FUNC macro works poorly because + * DECLARE_IRQ() stringizes its inputs. */ +DECLARE_IRQ(LM4_IRQ_GPIOA, __gpio_a_interrupt, 1); +DECLARE_IRQ(LM4_IRQ_GPIOB, __gpio_b_interrupt, 1); +DECLARE_IRQ(LM4_IRQ_GPIOC, __gpio_c_interrupt, 1); +DECLARE_IRQ(LM4_IRQ_GPIOD, __gpio_d_interrupt, 1); +DECLARE_IRQ(LM4_IRQ_GPIOE, __gpio_e_interrupt, 1); +DECLARE_IRQ(LM4_IRQ_GPIOF, __gpio_f_interrupt, 1); +DECLARE_IRQ(LM4_IRQ_GPIOG, __gpio_g_interrupt, 1); +#if (KB_SCAN_ROW_IRQ != LM4_IRQ_GPIOH) +DECLARE_IRQ(LM4_IRQ_GPIOH, __gpio_h_interrupt, 1); +#endif +DECLARE_IRQ(LM4_IRQ_GPIOJ, __gpio_j_interrupt, 1); +DECLARE_IRQ(LM4_IRQ_GPIOK, __gpio_k_interrupt, 1); +DECLARE_IRQ(LM4_IRQ_GPIOL, __gpio_l_interrupt, 1); +DECLARE_IRQ(LM4_IRQ_GPIOM, __gpio_m_interrupt, 1); +#if (KB_SCAN_ROW_IRQ != LM4_IRQ_GPION) +DECLARE_IRQ(LM4_IRQ_GPION, __gpio_n_interrupt, 1); +#endif +DECLARE_IRQ(LM4_IRQ_GPIOP, __gpio_p_interrupt, 1); +DECLARE_IRQ(LM4_IRQ_GPIOQ, __gpio_q_interrupt, 1); |