summaryrefslogtreecommitdiff
path: root/chip/lm4/lpc.c
diff options
context:
space:
mode:
Diffstat (limited to 'chip/lm4/lpc.c')
-rw-r--r--chip/lm4/lpc.c210
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);