diff options
Diffstat (limited to 'chip/lm4/lpc.c')
-rw-r--r-- | chip/lm4/lpc.c | 210 |
1 files changed, 210 insertions, 0 deletions
diff --git a/chip/lm4/lpc.c b/chip/lm4/lpc.c new file mode 100644 index 0000000000..a377890c41 --- /dev/null +++ b/chip/lm4/lpc.c @@ -0,0 +1,210 @@ +/* Copyright (c) 2011 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. + */ + +/* LPC module for Chrome EC */ + +#include "board.h" +#include "host_command.h" +#include "i8042.h" +#include "lpc.h" +#include "port80.h" +#include "registers.h" +#include "task.h" +#include "uart.h" + + +/* Configures GPIOs for module. */ +static void configure_gpio(void) +{ + volatile uint32_t scratch __attribute__((unused)); + + /* Enable clocks to GPIO modules L, M (p. 404) */ + LM4_SYSTEM_RCGCGPIO |= 0x0c00; + scratch = LM4_SYSTEM_RCGCGPIO; + + /* Set digital alternate function 15 for PL0:5, PM0:2, PM4:5 pins. */ + LM4_GPIO_AFSEL(L) |= 0x3f; + LM4_GPIO_AFSEL(M) |= 0x37; + LM4_GPIO_PCTL(L) |= 0x00ffffff; + LM4_GPIO_PCTL(M) |= 0x00ff0fff; + LM4_GPIO_DEN(L) |= 0x3f; + LM4_GPIO_DEN(M) |= 0x37; + + /* Set the drive strength to 8mA. */ + /* TODO: Only necessary on BDS because the cabling to the x86 + is long and flaky; remove this for Link. */ + LM4_GPIO_DR8R(L) |= 0x0000003f; + LM4_GPIO_DR8R(M) |= 0x00000037; +} + + +int lpc_init(void) +{ + volatile uint32_t scratch __attribute__((unused)); + + /* Enable RGCGLPC then delay a few clocks. */ + LM4_SYSTEM_RCGCLPC = 1; + scratch = LM4_SYSTEM_RCGCLPC; + + /* Configure GPIOs */ + configure_gpio(); + + /* Set LPC channel 0 to I/O address 0x62 (data) / 0x66 (command), + * single endpoint, offset 0 for host command/writes and 1 for EC + * data writes, pool bytes 0(data)/1(cmd) */ + LM4_LPC_ADR(0) = 0x62; + LM4_LPC_CTL(0) = (LPC_POOL_OFFS_KERNEL << (5 - 1)); + + /* Set LPC channel 1 to I/O address 0x80 (data), single endpoint, + * pool bytes 4(data)/5(cmd). */ + LM4_LPC_ADR(1) = 0x80; + LM4_LPC_CTL(1) = (LPC_POOL_OFFS_PORT80 << (5 - 1)); + + /* Set LPC channel 2 to I/O address 0x800, range endpoint, + * arbitration disabled, pool bytes 512-1023. To access this from + * x86, use the following commands to set GEN_LPC2 and GEN_LPC3: + * + * pci_write32 0 0x1f 0 0x88 0x007c0801 + * pci_write32 0 0x1f 0 0x8c 0x007c0901 + */ + LM4_LPC_ADR(2) = 0x800; + LM4_LPC_CTL(2) = 0x801D | (LPC_POOL_OFFS_CMD_DATA << (5 - 1)); + + /* Set LPC channel 3 to I/O address 0x60 (data) / 0x64 (command), + * single endpoint, offset 0 for host command/writes and 1 for EC + * data writes, pool bytes 0(data)/1(cmd) */ + LM4_LPC_ADR(3) = 0x60; + LM4_LPC_CTL(3) = (1 << 24/* IRQSEL1 */) | + (1 << 18/* IRQEN1 */) | + (LPC_POOL_OFFS_KEYBOARD << (5 - 1)); + LM4_LPC_ST(3) = 0; + + /* Set LPC channel 7 to I/O address 0x2F8 (COM2), bytes 8-15. + * Channel 7 ignores the TYPE bit. */ + LM4_LPC_ADR(7) = 0x2f8; + /* TODO: could configure IRQSELs and set IRQEN2/CX, and then the host + * can enable IRQs on its own. */ + LM4_LPC_CTL(7) = 0x0004 | (LPC_POOL_OFFS_COMX << (5 - 1)); + +#ifdef USE_LPC_COMx_DMA + /* TODO: haven't been able to get this to work yet */ + /* COMx UART DMA mode */ + LM4_LPC_LPCDMACX = 0x00070000; + + /* TODO: set up DMA */ + LM4_SYSTEM_RCGCDMA = 1; + /* Wait 3 clocks before accessing other DMA regs */ + LM4_SYSTEM_RCGCDMA = 1; + LM4_SYSTEM_RCGCDMA = 1; + LM4_SYSTEM_RCGCDMA = 1; + /* Enable master */ + LM4_DMA_DMACFG = 1; + /* TODO: hope we don't need the channel control structs; we're just + * throwing this somewhere in memory. Shouldn't need it if we leave + * all the channel disabled, though. */ + LM4_DMA_DMACTLBASE = 0x20004000; + /* Map UART and LPC DMA functions to channels */ + LM4_DMA_DMACHMAP0 = 0x00003000; /* Channel 3 encoding 3 = LPC0 Ch3 */ + LM4_DMA_DMACHMAP1 = 0x00000011; /* Channels 8,9 encoding 1 = UART1 */ +#else + /* Use our LPC interrupt handler to notify COMxIM on write-from-host */ + LM4_LPC_LPCDMACX = 0x00110000; + LM4_LPC_LPCIM |= 0x20000000; +#endif + + /* Unmask interrupt bits for channel 0 command write (CH0IM2), + * channel 1 data write (CH1IM1) and channel 3 data write (CH3IM3). */ + LM4_LPC_LPCIM |= 0x7024; + + /* Enable LPC channels 0, 1, 2, 3, 7 in LPCCTL */ + LM4_LPC_LPCCTL |= 0x8F; + + return EC_SUCCESS; +} + + +volatile uint8_t *lpc_get_host_range(void) +{ + return LPC_POOL_CMD_DATA; +} + + +void lpc_send_host_response(int status) +{ + /* Set status nibble (bits 7:4 from host side) and clear the busy + * bit (0x1000) (bit 2 from host side) */ + LM4_LPC_ST(0) = (LM4_LPC_ST(0) & 0xffffe0ff) | ((status & 0xf) << 8); + + /* Write dummy value to data byte. This sets the TOH bit in the + * status byte and triggers an IRQ on the host so the host can read + * the status. */ + /* TODO: or it would, if we actually set up host IRQs */ + LPC_POOL_KERNEL[1] = 0; +} + + +/* LPC interrupt handler */ +static void lpc_interrupt(void) +{ + uint32_t mis = LM4_LPC_LPCMIS; + + /* Clear the interrupt bits we're handling */ + LM4_LPC_LPCIC = mis; + + /* Handle port 66 command writes */ + if (mis & 0x04) { + /* Set the busy bit and clear the status */ + LM4_LPC_ST(0) = (LM4_LPC_ST(0) & 0xffffe0ff) | 0x1000; + + /* Read the command byte and pass to the host command handler. + * This clears the FRMH bit in the status byte. */ + host_command_received(LPC_POOL_KERNEL[0]); + } + + /* Handle port 80 writes (CH0MIS1) */ + if (mis & 0x20) + port_80_write(LPC_POOL_PORT80[0]); + + /* Handle port 60 command (CH3MIS2) and data (CH3MIS1) */ + if (mis & 0x2000) { + /* Read the data byte and pass to the i8042 handler. + * This clears the FRMH bit in the status byte. */ + i8042_receives_data(LPC_POOL_KEYBOARD[0]); + } + if (mis & 0x4000) { + /* Read the command byte and pass to the i8042 handler. + * This clears the FRMH bit in the status byte. */ + i8042_receives_command(LPC_POOL_KEYBOARD[0]); + } + if (mis & 0x1000) { + /* Host picks up the data, try to send remaining bytes */ + task_send_msg(TASK_ID_I8042CMD, TASK_ID_I8042CMD, 0); + } + + /* Handle COMx */ + if (mis & 0x20000000) { + uint32_t cis = LM4_LPC_LPCDMACX; + /* Clear the interrupt reasons we're handling */ + LM4_LPC_LPCDMACX = cis; + + /* Handle host writes */ + if (LM4_LPC_ST(7) & 0x02) { + if (LM4_UART_FR(1) & 0x20) { + /* FIFO is full, so enable transmit + * interrupt to let us know when it + * empties */ + LM4_UART_IM(1) |= 0x20; + } else { + /* Space in FIFO, so copy byte */ + LM4_UART_DR(1) = LPC_POOL_COMX[0]; + } + } + + /* TODO: if host read the from-host data, see if + * there's another byte still waiting on UART1. */ + } +} + +DECLARE_IRQ(108, lpc_interrupt, 2); |