| /* 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. |
| */ |
| |
| /* LPC module for Chrome EC */ |
| |
| #include "acpi.h" |
| #include "clock.h" |
| #include "common.h" |
| #include "console.h" |
| #include "gpio.h" |
| #include "hooks.h" |
| #include "host_command.h" |
| #include "keyboard_protocol.h" |
| #include "lpc.h" |
| #include "port80.h" |
| #include "pwm.h" |
| #include "registers.h" |
| #include "system.h" |
| #include "task.h" |
| #include "timer.h" |
| #include "uart.h" |
| #include "util.h" |
| |
| /* LPC channels */ |
| #define LPC_CH_ACPI 0 /* ACPI commands */ |
| #define LPC_CH_PORT80 1 /* Port 80 debug output */ |
| #define LPC_CH_CMD_DATA 2 /* Data for host commands (args/params/response) */ |
| #define LPC_CH_KEYBOARD 3 /* 8042 keyboard emulation */ |
| #define LPC_CH_CMD 4 /* Host commands */ |
| #define LPC_CH_MEMMAP 5 /* Memory-mapped data */ |
| #define LPC_CH_COMX 7 /* UART emulation */ |
| /* LPC pool offsets */ |
| #define LPC_POOL_OFFS_ACPI 0 /* ACPI commands - 0=in, 1=out */ |
| #define LPC_POOL_OFFS_PORT80 4 /* Port 80 - 4=in, 5=out */ |
| #define LPC_POOL_OFFS_COMX 8 /* UART emulation range - 8-15 */ |
| #define LPC_POOL_OFFS_KEYBOARD 16 /* Keyboard - 16=in, 17=out */ |
| #define LPC_POOL_OFFS_CMD 20 /* Host commands - 20=in, 21=out */ |
| #define LPC_POOL_OFFS_CMD_DATA 512 /* Data range for host commands - 512-767 */ |
| #define LPC_POOL_OFFS_MEMMAP 768 /* Memory-mapped data - 768-1023 */ |
| /* LPC pool data pointers */ |
| #define LPC_POOL_ACPI (LM4_LPC_LPCPOOL + LPC_POOL_OFFS_ACPI) |
| #define LPC_POOL_PORT80 (LM4_LPC_LPCPOOL + LPC_POOL_OFFS_PORT80) |
| #define LPC_POOL_COMX (LM4_LPC_LPCPOOL + LPC_POOL_OFFS_COMX) |
| #define LPC_POOL_KEYBOARD (LM4_LPC_LPCPOOL + LPC_POOL_OFFS_KEYBOARD) |
| #define LPC_POOL_CMD (LM4_LPC_LPCPOOL + LPC_POOL_OFFS_CMD) |
| #define LPC_POOL_CMD_DATA (LM4_LPC_LPCPOOL + LPC_POOL_OFFS_CMD_DATA) |
| #define LPC_POOL_MEMMAP (LM4_LPC_LPCPOOL + LPC_POOL_OFFS_MEMMAP) |
| /* LPC COMx I/O address (in x86 I/O address space) */ |
| #define LPC_COMX_ADDR 0x3f8 /* COM1 */ |
| |
| /* Console output macros */ |
| #define CPUTS(outstr) cputs(CC_LPC, outstr) |
| #define CPRINTS(format, args...) cprints(CC_LPC, format, ## args) |
| |
| #define LPC_SYSJUMP_TAG 0x4c50 /* "LP" */ |
| |
| static uint32_t host_events; /* Currently pending SCI/SMI events */ |
| static uint32_t event_mask[3]; /* Event masks for each type */ |
| static struct host_packet lpc_packet; |
| static struct host_cmd_handler_args host_cmd_args; |
| static uint8_t host_cmd_flags; /* Flags from host command */ |
| |
| /* Params must be 32-bit aligned */ |
| static uint8_t params_copy[EC_LPC_HOST_PACKET_SIZE] __aligned(4); |
| static int init_done; |
| |
| static uint8_t * const cmd_params = (uint8_t *)LPC_POOL_CMD_DATA + |
| EC_LPC_ADDR_HOST_PARAM - EC_LPC_ADDR_HOST_ARGS; |
| static struct ec_lpc_host_args * const lpc_host_args = |
| (struct ec_lpc_host_args *)LPC_POOL_CMD_DATA; |
| |
| static void wait_irq_sent(void) |
| { |
| /* |
| * A hard-coded delay here isn't very elegant, but it's the best we can |
| * manage (and it's a short delay, so it's not that horrible). We need |
| * this because SIRQRIS isn't cleared in continuous mode, and the EC |
| * has trouble sending more than 1 frame in quiet mode. Waiting 4 us = |
| * 2 SERIRQ frames ensures the IRQ has been sent out. |
| */ |
| udelay(4); |
| } |
| |
| #ifdef CONFIG_KEYBOARD_IRQ_GPIO |
| static void keyboard_irq_assert(void) |
| { |
| /* |
| * Enforce signal-high for long enough for the signal to be pulled high |
| * by the external pullup resistor. This ensures the host will see the |
| * following falling edge, regardless of the line state before this |
| * function call. |
| */ |
| uint64_t tstop = get_time().val + MSEC; |
| gpio_set_level(CONFIG_KEYBOARD_IRQ_GPIO, 1); |
| udelay(4); |
| /* Generate a falling edge */ |
| gpio_set_level(CONFIG_KEYBOARD_IRQ_GPIO, 0); |
| /* Wait for host senses the interrupt and gets the char. */ |
| do { |
| if (get_time().val > tstop) |
| break; |
| } while (lpc_keyboard_has_char()); |
| /* Set signal high, now that we've generated the edge */ |
| gpio_set_level(CONFIG_KEYBOARD_IRQ_GPIO, 1); |
| } |
| #else |
| static void wait_send_serirq(uint32_t lpcirqctl) |
| { |
| LM4_LPC_LPCIRQCTL = lpcirqctl; |
| wait_irq_sent(); |
| } |
| |
| /** |
| * Manually generate an IRQ to host (edge-trigger). |
| * |
| * @param irq_num IRQ number to generate. Pass 0 to set the AH |
| * (active high) bit. |
| * |
| * For SERIRQ quite mode, we need to set LM4_LPC_LPCIRQCTL twice. |
| * The first one is to assert IRQ (pull low), and then the second one is |
| * to de-assert it. This generates a pulse (high-low-high) for an IRQ. |
| */ |
| static void lpc_manual_irq(int irq_num) |
| { |
| uint32_t common_bits = |
| 0x00000004 | /* PULSE */ |
| 0x00000002 | /* ONCHG - for quiet mode */ |
| 0x00000001; /* SND - send immediately */ |
| |
| /* Send out the IRQ first. */ |
| wait_send_serirq((1 << (irq_num + 16)) | common_bits); |
| |
| /* Generate a all-high frame to simulate a rising edge. */ |
| wait_send_serirq(common_bits); |
| } |
| |
| static inline void keyboard_irq_assert(void) |
| { |
| /* Use serirq method. */ |
| lpc_manual_irq(1); /* IRQ#1 */ |
| } |
| #endif |
| |
| /** |
| * Generate SMI pulse to the host chipset via GPIO. |
| * |
| * If the x86 is in S0, SMI# is sampled at 33MHz, so minimum pulse length is |
| * 60ns. If the x86 is in S3, SMI# is sampled at 32.768KHz, so we need pulse |
| * length >61us. Both are short enough and events are infrequent, so just |
| * delay for 65us. |
| */ |
| static void lpc_generate_smi(void) |
| { |
| /* Enforce signal-high for long enough to debounce high */ |
| gpio_set_level(GPIO_PCH_SMI_L, 1); |
| udelay(65); |
| /* Generate a falling edge */ |
| gpio_set_level(GPIO_PCH_SMI_L, 0); |
| udelay(65); |
| /* Set signal high, now that we've generated the edge */ |
| gpio_set_level(GPIO_PCH_SMI_L, 1); |
| |
| if (host_events & event_mask[LPC_HOST_EVENT_SMI]) |
| CPRINTS("smi 0x%08x", |
| host_events & event_mask[LPC_HOST_EVENT_SMI]); |
| } |
| |
| /** |
| * Generate SCI pulse to the host chipset via LPC0SCI. |
| */ |
| static void lpc_generate_sci(void) |
| { |
| #ifdef CONFIG_SCI_GPIO |
| /* Enforce signal-high for long enough to debounce high */ |
| gpio_set_level(CONFIG_SCI_GPIO, 1); |
| udelay(65); |
| /* Generate a falling edge */ |
| gpio_set_level(CONFIG_SCI_GPIO, 0); |
| udelay(65); |
| /* Set signal high, now that we've generated the edge */ |
| gpio_set_level(CONFIG_SCI_GPIO, 1); |
| #else |
| LM4_LPC_LPCCTL |= LM4_LPC_SCI_START; |
| #endif |
| |
| if (host_events & event_mask[LPC_HOST_EVENT_SCI]) |
| CPRINTS("sci 0x%08x", |
| host_events & event_mask[LPC_HOST_EVENT_SCI]); |
| } |
| |
| /** |
| * Update the level-sensitive wake signal to the AP. |
| * |
| * @param wake_events Currently asserted wake events |
| */ |
| static void lpc_update_wake(uint32_t wake_events) |
| { |
| /* |
| * Mask off power button event, since the AP gets that through a |
| * separate dedicated GPIO. |
| */ |
| wake_events &= ~EC_HOST_EVENT_MASK(EC_HOST_EVENT_POWER_BUTTON); |
| |
| /* Signal is asserted low when wake events is non-zero */ |
| gpio_set_level(GPIO_PCH_WAKE_L, !wake_events); |
| } |
| |
| uint8_t *lpc_get_memmap_range(void) |
| { |
| return (uint8_t *)LPC_POOL_MEMMAP; |
| } |
| |
| static void lpc_send_response(struct host_cmd_handler_args *args) |
| { |
| uint8_t *out; |
| int size = args->response_size; |
| int csum; |
| int i; |
| |
| /* Ignore in-progress on LPC since interface is synchronous anyway */ |
| if (args->result == EC_RES_IN_PROGRESS) |
| return; |
| |
| /* Handle negative size */ |
| if (size < 0) { |
| args->result = EC_RES_INVALID_RESPONSE; |
| size = 0; |
| } |
| |
| /* New-style response */ |
| lpc_host_args->flags = |
| (host_cmd_flags & ~EC_HOST_ARGS_FLAG_FROM_HOST) | |
| EC_HOST_ARGS_FLAG_TO_HOST; |
| |
| lpc_host_args->data_size = size; |
| |
| csum = args->command + lpc_host_args->flags + |
| lpc_host_args->command_version + |
| lpc_host_args->data_size; |
| |
| for (i = 0, out = (uint8_t *)args->response; i < size; i++, out++) |
| csum += *out; |
| |
| lpc_host_args->checksum = (uint8_t)csum; |
| |
| /* Fail if response doesn't fit in the param buffer */ |
| if (size > EC_PROTO2_MAX_PARAM_SIZE) |
| args->result = EC_RES_INVALID_RESPONSE; |
| |
| /* Write result to the data byte. This sets the TOH status bit. */ |
| LPC_POOL_CMD[1] = args->result; |
| |
| /* Clear the busy bit, so the host knows the EC is done. */ |
| task_disable_irq(LM4_IRQ_LPC); |
| LM4_LPC_ST(LPC_CH_CMD) &= ~LM4_LPC_ST_BUSY; |
| task_enable_irq(LM4_IRQ_LPC); |
| } |
| |
| static void lpc_send_response_packet(struct host_packet *pkt) |
| { |
| /* Ignore in-progress on LPC since interface is synchronous anyway */ |
| if (pkt->driver_result == EC_RES_IN_PROGRESS) |
| return; |
| |
| /* Write result to the data byte. This sets the TOH status bit. */ |
| LPC_POOL_CMD[1] = pkt->driver_result; |
| |
| /* Clear the busy bit, so the host knows the EC is done. */ |
| task_disable_irq(LM4_IRQ_LPC); |
| LM4_LPC_ST(LPC_CH_CMD) &= ~LM4_LPC_ST_BUSY; |
| task_enable_irq(LM4_IRQ_LPC); |
| } |
| |
| int lpc_keyboard_has_char(void) |
| { |
| return (LM4_LPC_ST(LPC_CH_KEYBOARD) & LM4_LPC_ST_TOH) ? 1 : 0; |
| } |
| |
| /* Return true if the FRMH is set */ |
| int lpc_keyboard_input_pending(void) |
| { |
| return (LM4_LPC_ST(LPC_CH_KEYBOARD) & LM4_LPC_ST_FRMH) ? 1 : 0; |
| } |
| |
| /* Put a char to host buffer and send IRQ if specified. */ |
| void lpc_keyboard_put_char(uint8_t chr, int send_irq) |
| { |
| LPC_POOL_KEYBOARD[1] = chr; |
| if (send_irq) |
| keyboard_irq_assert(); |
| } |
| |
| void lpc_keyboard_clear_buffer(void) |
| { |
| /* Make sure the previous TOH and IRQ has been sent out. */ |
| wait_irq_sent(); |
| |
| LM4_LPC_ST(LPC_CH_KEYBOARD) &= ~LM4_LPC_ST_TOH; |
| |
| /* Ensure there is no TOH set in this period. */ |
| wait_irq_sent(); |
| } |
| |
| void lpc_keyboard_resume_irq(void) |
| { |
| if (lpc_keyboard_has_char()) |
| keyboard_irq_assert(); |
| } |
| |
| #ifdef CONFIG_UART_HOST |
| |
| int lpc_comx_has_char(void) |
| { |
| return LM4_LPC_ST(LPC_CH_COMX) & LM4_LPC_ST_FRMH; |
| } |
| |
| int lpc_comx_get_char(void) |
| { |
| return LPC_POOL_COMX[0]; |
| } |
| |
| void lpc_comx_put_char(int c) |
| { |
| LPC_POOL_COMX[1] = c; |
| |
| /* |
| * We could in theory manually trigger an IRQ, like we do for the 8042 |
| * keyboard interface, but neither the kernel nor BIOS seems to require |
| * this. |
| */ |
| } |
| |
| #endif /* CONFIG_UART_HOST */ |
| |
| /** |
| * Update the host event status. |
| * |
| * Sends a pulse if masked event status becomes non-zero: |
| * - SMI pulse via EC_SMI_L GPIO |
| * - SCI pulse via LPC0SCI |
| */ |
| static void update_host_event_status(void) |
| { |
| int need_sci = 0; |
| int need_smi = 0; |
| |
| if (!init_done) |
| return; |
| |
| /* Disable LPC interrupt while updating status register */ |
| task_disable_irq(LM4_IRQ_LPC); |
| |
| if (host_events & event_mask[LPC_HOST_EVENT_SMI]) { |
| /* Only generate SMI for first event */ |
| if (!(LM4_LPC_ST(LPC_CH_ACPI) & LM4_LPC_ST_SMI)) |
| need_smi = 1; |
| LM4_LPC_ST(LPC_CH_ACPI) |= LM4_LPC_ST_SMI; |
| } else |
| LM4_LPC_ST(LPC_CH_ACPI) &= ~LM4_LPC_ST_SMI; |
| |
| if (host_events & event_mask[LPC_HOST_EVENT_SCI]) { |
| /* Generate SCI for every event */ |
| need_sci = 1; |
| LM4_LPC_ST(LPC_CH_ACPI) |= LM4_LPC_ST_SCI; |
| } else |
| LM4_LPC_ST(LPC_CH_ACPI) &= ~LM4_LPC_ST_SCI; |
| |
| /* Copy host events to mapped memory */ |
| *(uint32_t *)host_get_memmap(EC_MEMMAP_HOST_EVENTS) = host_events; |
| |
| task_enable_irq(LM4_IRQ_LPC); |
| |
| /* Process the wake events. */ |
| lpc_update_wake(host_events & event_mask[LPC_HOST_EVENT_WAKE]); |
| |
| /* Send pulse on SMI signal if needed */ |
| if (need_smi) |
| lpc_generate_smi(); |
| |
| /* ACPI 5.0-12.6.1: Generate SCI for SCI_EVT=1. */ |
| if (need_sci) |
| lpc_generate_sci(); |
| } |
| |
| void lpc_set_host_event_state(uint32_t mask) |
| { |
| if (mask != host_events) { |
| host_events = mask; |
| update_host_event_status(); |
| } |
| } |
| |
| int lpc_query_host_event_state(void) |
| { |
| const uint32_t any_mask = event_mask[0] | event_mask[1] | event_mask[2]; |
| int evt_index = 0; |
| int i; |
| |
| for (i = 0; i < 32; i++) { |
| const uint32_t e = (1 << i); |
| |
| if (host_events & e) { |
| host_clear_events(e); |
| |
| /* |
| * If host hasn't unmasked this event, drop it. We do |
| * this at query time rather than event generation time |
| * so that the host has a chance to unmask events |
| * before they're dropped by a query. |
| */ |
| if (!(e & any_mask)) |
| continue; |
| |
| evt_index = i + 1; /* Events are 1-based */ |
| break; |
| } |
| } |
| |
| return evt_index; |
| } |
| |
| void lpc_set_host_event_mask(enum lpc_host_event_type type, uint32_t mask) |
| { |
| event_mask[type] = mask; |
| update_host_event_status(); |
| } |
| |
| uint32_t lpc_get_host_event_mask(enum lpc_host_event_type type) |
| { |
| return event_mask[type]; |
| } |
| |
| void lpc_set_acpi_status_mask(uint8_t mask) |
| { |
| uint32_t set_mask = 0; |
| if (mask & EC_LPC_STATUS_BURST_MODE) |
| set_mask |= LM4_LPC_ST_BURST; |
| |
| LM4_LPC_ST(LPC_CH_ACPI) |= set_mask; |
| } |
| |
| void lpc_clear_acpi_status_mask(uint8_t mask) |
| { |
| uint32_t clear_mask = 0; |
| if (mask & EC_LPC_STATUS_BURST_MODE) |
| clear_mask |= LM4_LPC_ST_BURST; |
| |
| LM4_LPC_ST(LPC_CH_ACPI) &= ~clear_mask; |
| } |
| |
| int lpc_get_pltrst_asserted(void) |
| { |
| return (LM4_LPC_LPCSTS & (1<<10)) ? 1 : 0; |
| } |
| |
| /** |
| * Handle write to ACPI I/O port |
| * |
| * @param is_cmd Is write command (is_cmd=1) or data (is_cmd=0) |
| */ |
| static void handle_acpi_write(int is_cmd) |
| { |
| uint8_t value, result; |
| |
| /* Set the busy bit */ |
| LM4_LPC_ST(LPC_CH_ACPI) |= LM4_LPC_ST_BUSY; |
| |
| /* Read command/data; this clears the FRMH status bit. */ |
| value = LPC_POOL_ACPI[0]; |
| |
| /* Handle whatever this was. */ |
| if (acpi_ap_to_ec(is_cmd, value, &result)) |
| LPC_POOL_ACPI[1] = result; |
| |
| /* Clear the busy bit */ |
| LM4_LPC_ST(LPC_CH_ACPI) &= ~LM4_LPC_ST_BUSY; |
| |
| /* |
| * ACPI 5.0-12.6.1: Generate SCI for Input Buffer Empty / Output Buffer |
| * Full condition on the kernel channel. |
| */ |
| lpc_generate_sci(); |
| } |
| |
| /** |
| * Handle write to host command I/O ports. |
| * |
| * @param is_cmd Is write command (1) or data (0)? |
| */ |
| static void handle_host_write(int is_cmd) |
| { |
| /* Ignore data writes or overlapping commands from host */ |
| uint32_t is_overlapping = LM4_LPC_ST(LPC_CH_CMD) & LM4_LPC_ST_BUSY; |
| if (!is_cmd || is_overlapping) { |
| if (is_overlapping) |
| CPRINTS("LPC Ignoring overlapping HC"); |
| LM4_LPC_ST(LPC_CH_CMD) &= ~LM4_LPC_ST_FRMH; |
| return; |
| } |
| |
| /* Set the busy bit */ |
| LM4_LPC_ST(LPC_CH_CMD) |= LM4_LPC_ST_BUSY; |
| |
| /* |
| * Read the command byte. This clears the FRMH bit in |
| * the status byte. |
| */ |
| host_cmd_args.command = LPC_POOL_CMD[0]; |
| |
| host_cmd_args.result = EC_RES_SUCCESS; |
| host_cmd_args.send_response = lpc_send_response; |
| host_cmd_flags = lpc_host_args->flags; |
| |
| /* See if we have an old or new style command */ |
| if (host_cmd_args.command == EC_COMMAND_PROTOCOL_3) { |
| lpc_packet.send_response = lpc_send_response_packet; |
| |
| lpc_packet.request = (const void *)LPC_POOL_CMD_DATA; |
| lpc_packet.request_temp = params_copy; |
| lpc_packet.request_max = sizeof(params_copy); |
| /* Don't know the request size so pass in the entire buffer */ |
| lpc_packet.request_size = EC_LPC_HOST_PACKET_SIZE; |
| |
| lpc_packet.response = (void *)LPC_POOL_CMD_DATA; |
| lpc_packet.response_max = EC_LPC_HOST_PACKET_SIZE; |
| lpc_packet.response_size = 0; |
| |
| lpc_packet.driver_result = EC_RES_SUCCESS; |
| host_packet_receive(&lpc_packet); |
| return; |
| |
| } else if (host_cmd_flags & EC_HOST_ARGS_FLAG_FROM_HOST) { |
| /* Version 2 (link) style command */ |
| int size = lpc_host_args->data_size; |
| int csum, i; |
| |
| host_cmd_args.version = lpc_host_args->command_version; |
| host_cmd_args.params = params_copy; |
| host_cmd_args.params_size = size; |
| host_cmd_args.response = cmd_params; |
| host_cmd_args.response_max = EC_PROTO2_MAX_PARAM_SIZE; |
| host_cmd_args.response_size = 0; |
| |
| /* Verify params size */ |
| if (size > EC_PROTO2_MAX_PARAM_SIZE) { |
| host_cmd_args.result = EC_RES_INVALID_PARAM; |
| } else { |
| const uint8_t *src = cmd_params; |
| uint8_t *copy = params_copy; |
| |
| /* |
| * Verify checksum and copy params out of LPC space. |
| * This ensures the data acted on by the host command |
| * handler can't be changed by host writes after the |
| * checksum is verified. |
| */ |
| csum = host_cmd_args.command + |
| host_cmd_flags + |
| host_cmd_args.version + |
| host_cmd_args.params_size; |
| |
| for (i = 0; i < size; i++) { |
| csum += *src; |
| *(copy++) = *(src++); |
| } |
| |
| if ((uint8_t)csum != lpc_host_args->checksum) |
| host_cmd_args.result = EC_RES_INVALID_CHECKSUM; |
| } |
| } else { |
| /* Old style command, now unsupported */ |
| host_cmd_args.result = EC_RES_INVALID_COMMAND; |
| } |
| |
| /* Hand off to host command handler */ |
| host_command_received(&host_cmd_args); |
| } |
| |
| #ifdef CONFIG_CHIPSET_RESET_HOOK |
| static void lpc_chipset_reset(void) |
| { |
| hook_notify(HOOK_CHIPSET_RESET); |
| } |
| DECLARE_DEFERRED(lpc_chipset_reset); |
| #endif |
| |
| /** |
| * LPC interrupt handler |
| */ |
| void lpc_interrupt(void) |
| { |
| uint32_t mis = LM4_LPC_LPCMIS; |
| uint32_t st; |
| |
| /* Clear the interrupt bits we're handling */ |
| LM4_LPC_LPCIC = mis; |
| |
| #ifdef HAS_TASK_HOSTCMD |
| /* Handle ACPI command and data writes */ |
| st = LM4_LPC_ST(LPC_CH_ACPI); |
| if (st & LM4_LPC_ST_FRMH) |
| handle_acpi_write(st & LM4_LPC_ST_CMD); |
| |
| /* Handle user command writes */ |
| st = LM4_LPC_ST(LPC_CH_CMD); |
| if (st & LM4_LPC_ST_FRMH) |
| handle_host_write(st & LM4_LPC_ST_CMD); |
| #endif |
| |
| /* |
| * Handle port 80 writes (CH0MIS1). Due to crosbug.com/p/12349 the |
| * interrupt status (mis & LM4_LPC_INT_MASK(LPC_CH_PORT80, 2)) |
| * apparently gets lost on back-to-back writes to port 80, so check the |
| * FRMH bit in the channel status register to see if a write is |
| * pending. Loop to handle bursts of back-to-back writes. |
| */ |
| while (LM4_LPC_ST(LPC_CH_PORT80) & LM4_LPC_ST_FRMH) |
| port_80_write(LPC_POOL_PORT80[0]); |
| |
| #ifdef HAS_TASK_KEYPROTO |
| /* Handle keyboard interface writes */ |
| st = LM4_LPC_ST(LPC_CH_KEYBOARD); |
| if (st & LM4_LPC_ST_FRMH) |
| keyboard_host_write(LPC_POOL_KEYBOARD[0], st & LM4_LPC_ST_CMD); |
| |
| if (mis & LM4_LPC_INT_MASK(LPC_CH_KEYBOARD, 1)) { |
| /* Host read data; wake up task to send remaining bytes */ |
| task_wake(TASK_ID_KEYPROTO); |
| } |
| #endif |
| |
| #ifdef CONFIG_UART_HOST |
| /* Handle COMx */ |
| if (lpc_comx_has_char()) { |
| /* Copy a character to the UART if there's space */ |
| if (uart_comx_putc_ok()) |
| uart_comx_putc(lpc_comx_get_char()); |
| } |
| #endif |
| |
| /* Debugging: print changes to LPC0RESET */ |
| if (mis & (1 << 31)) { |
| if (LM4_LPC_LPCSTS & (1 << 10)) { |
| int i; |
| |
| /* Store port 80 reset event */ |
| port_80_write(PORT_80_EVENT_RESET); |
| |
| /* |
| * Workaround for crosbug.com/p/12349; clear all FRMH |
| * bits so host writes will trigger interrupts. |
| */ |
| for (i = 0; i < 8; i++) |
| LM4_LPC_ST(i) &= ~LM4_LPC_ST_FRMH; |
| |
| #ifdef CONFIG_CHIPSET_RESET_HOOK |
| /* Notify HOOK_CHIPSET_RESET */ |
| hook_call_deferred(&lpc_chipset_reset_data, MSEC); |
| #endif |
| } |
| |
| CPRINTS("LPC RESET# %sasserted", |
| lpc_get_pltrst_asserted() ? "" : "de"); |
| } |
| } |
| DECLARE_IRQ(LM4_IRQ_LPC, lpc_interrupt, 2); |
| |
| /** |
| * Preserve event masks across a sysjump. |
| */ |
| static void lpc_sysjump(void) |
| { |
| system_add_jump_tag(LPC_SYSJUMP_TAG, 1, |
| sizeof(event_mask), event_mask); |
| } |
| DECLARE_HOOK(HOOK_SYSJUMP, lpc_sysjump, HOOK_PRIO_DEFAULT); |
| |
| /** |
| * Restore event masks after a sysjump. |
| */ |
| static void lpc_post_sysjump(void) |
| { |
| const uint32_t *prev_mask; |
| int size, version; |
| |
| prev_mask = (const uint32_t *)system_get_jump_tag(LPC_SYSJUMP_TAG, |
| &version, &size); |
| if (!prev_mask || version != 1 || size != sizeof(event_mask)) |
| return; |
| |
| memcpy(event_mask, prev_mask, sizeof(event_mask)); |
| } |
| |
| /* Enable LPC ACPI-EC interrupts */ |
| void lpc_enable_acpi_interrupts(void) |
| { |
| LM4_LPC_LPCIM |= LM4_LPC_INT_MASK(LPC_CH_ACPI, 6); |
| } |
| |
| /* Disable LPC ACPI-EC interrupts */ |
| void lpc_disable_acpi_interrupts(void) |
| { |
| LM4_LPC_LPCIM &= ~(LM4_LPC_INT_MASK(LPC_CH_ACPI, 6)); |
| } |
| |
| static void lpc_init(void) |
| { |
| /* Enable LPC clock in run and sleep modes. */ |
| clock_enable_peripheral(CGC_OFFSET_LPC, 0x1, |
| CGC_MODE_RUN | CGC_MODE_SLEEP); |
| |
| LM4_LPC_LPCIM = 0; |
| LM4_LPC_LPCCTL = 0; |
| LM4_LPC_LPCIRQCTL = 0; |
| |
| /* Configure GPIOs */ |
| gpio_config_module(MODULE_LPC, 1); |
| |
| /* |
| * 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(LPC_CH_ACPI) = EC_LPC_ADDR_ACPI_DATA; |
| LM4_LPC_CTL(LPC_CH_ACPI) = (LPC_POOL_OFFS_ACPI << (5 - 1)); |
| LM4_LPC_ST(LPC_CH_ACPI) = 0; |
| /* Unmask interrupt for host command and data writes */ |
| LM4_LPC_LPCIM |= LM4_LPC_INT_MASK(LPC_CH_ACPI, 6); |
| |
| /* |
| * Set LPC channel 1 to I/O address 0x80 (data), single endpoint, |
| * pool bytes 4(data)/5(cmd). |
| */ |
| LM4_LPC_ADR(LPC_CH_PORT80) = 0x80; |
| LM4_LPC_CTL(LPC_CH_PORT80) = (LPC_POOL_OFFS_PORT80 << (5 - 1)); |
| /* Unmask interrupt for host data writes */ |
| LM4_LPC_LPCIM |= LM4_LPC_INT_MASK(LPC_CH_PORT80, 2); |
| |
| /* |
| * Set LPC channel 2 to I/O address 0x880, range endpoint, |
| * arbitration disabled, pool bytes 512-639. To access this from |
| * x86, use the following command to set GEN_LPC2: |
| * |
| * pci_write32 0 0x1f 0 0x88 0x007c0801 |
| */ |
| LM4_LPC_ADR(LPC_CH_CMD_DATA) = EC_LPC_ADDR_HOST_ARGS; |
| LM4_LPC_CTL(LPC_CH_CMD_DATA) = 0x8019 | |
| (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(LPC_CH_KEYBOARD) = 0x60; |
| LM4_LPC_CTL(LPC_CH_KEYBOARD) = (1 << 24/* IRQSEL1 */) | |
| (0 << 18/* IRQEN1 */) | (LPC_POOL_OFFS_KEYBOARD << (5 - 1)); |
| LM4_LPC_ST(LPC_CH_KEYBOARD) = 0; |
| /* Unmask interrupt for host command/data writes and data reads */ |
| LM4_LPC_LPCIM |= LM4_LPC_INT_MASK(LPC_CH_KEYBOARD, 7); |
| |
| /* |
| * Set LPC channel 4 to I/O address 0x200 (data) / 0x204 (command), |
| * single endpoint, offset 0 for host command/writes and 1 for EC |
| * data writes, pool bytes 0(data)/1(cmd) |
| */ |
| LM4_LPC_ADR(LPC_CH_CMD) = EC_LPC_ADDR_HOST_DATA; |
| LM4_LPC_CTL(LPC_CH_CMD) = (LPC_POOL_OFFS_CMD << (5 - 1)); |
| /* |
| * Initialize status bits to 0. We never set the ACPI burst status bit, |
| * so this guarantees that at least one status bit will always be 0. |
| * This is used by comm_lpc.c to detect that the EC is present on the |
| * LPC bus. See crosbug.com/p/10963. |
| */ |
| LM4_LPC_ST(LPC_CH_CMD) = 0; |
| /* Unmask interrupt for host command writes */ |
| LM4_LPC_LPCIM |= LM4_LPC_INT_MASK(LPC_CH_CMD, 4); |
| |
| /* |
| * Set LPC channel 5 to I/O address 0x900, range endpoint, |
| * arbitration enabled, pool bytes 768-1023. To access this from |
| * x86, use the following command to set GEN_LPC3: |
| * |
| * pci_write32 0 0x1f 0 0x8c 0x007c0901 |
| */ |
| LM4_LPC_ADR(LPC_CH_MEMMAP) = EC_LPC_ADDR_MEMMAP; |
| LM4_LPC_CTL(LPC_CH_MEMMAP) = 0x0019 | (LPC_POOL_OFFS_MEMMAP << (5 - 1)); |
| |
| #ifdef CONFIG_UART_HOST |
| /* |
| * Set LPC channel 7 to COM port I/O address. Note that channel 7 |
| * ignores the TYPE bit and is always an 8-byte range. |
| */ |
| LM4_LPC_ADR(LPC_CH_COMX) = LPC_COMX_ADDR; |
| /* |
| * In theory we could configure IRQSELs and set IRQEN2/CX, and then the |
| * host could enable IRQs on its own. So far that hasn't been |
| * necessary, and due to the issues with IRQs (see wait_irq_sent() |
| * above) it might not work anyway. |
| */ |
| LM4_LPC_CTL(LPC_CH_COMX) = 0x0004 | (LPC_POOL_OFFS_COMX << (5 - 1)); |
| /* Enable COMx emulation for reads and writes. */ |
| LM4_LPC_LPCDMACX = 0x00310000; |
| /* |
| * Unmask interrupt for host data writes. We don't need interrupts for |
| * reads, because there's no flow control in that direction; LPC is |
| * much faster than the UART, and the UART doesn't have anywhere |
| * sensible to buffer input anyway. |
| */ |
| LM4_LPC_LPCIM |= LM4_LPC_INT_MASK(LPC_CH_COMX, 2); |
| #endif /* CONFIG_UART_HOST */ |
| |
| /* |
| * Unmaksk LPC bus reset interrupt. This lets us monitor the PCH |
| * PLTRST# signal for debugging. |
| */ |
| LM4_LPC_LPCIM |= (1 << 31); |
| |
| /* Enable LPC channels */ |
| LM4_LPC_LPCCTL = LM4_LPC_SCI_CLK_1 | |
| (1 << LPC_CH_ACPI) | |
| (1 << LPC_CH_PORT80) | |
| (1 << LPC_CH_CMD_DATA) | |
| (1 << LPC_CH_KEYBOARD) | |
| (1 << LPC_CH_CMD) | |
| (1 << LPC_CH_MEMMAP); |
| |
| #ifdef CONFIG_UART_HOST |
| LM4_LPC_LPCCTL |= 1 << LPC_CH_COMX; |
| #endif |
| |
| /* |
| * Ensure the EC (slave) has control of the memory-mapped I/O space. |
| * Once the EC has won arbtration for the memory-mapped space, it will |
| * keep control of it until it writes the last byte in the space. |
| * (That never happens; we can't use the last byte in the space because |
| * ACPI can't see it anyway.) |
| */ |
| while (!(LM4_LPC_ST(LPC_CH_MEMMAP) & 0x10)) { |
| /* Clear HW1ST */ |
| LM4_LPC_ST(LPC_CH_MEMMAP) &= ~0x40; |
| /* Do a dummy slave write; this should cause SW1ST to be set */ |
| *LPC_POOL_MEMMAP = *LPC_POOL_MEMMAP; |
| } |
| |
| /* Initialize host args and memory map to all zero */ |
| memset(lpc_host_args, 0, sizeof(*lpc_host_args)); |
| memset(lpc_get_memmap_range(), 0, EC_MEMMAP_SIZE); |
| |
| /* We support LPC args and version 3 protocol */ |
| *(lpc_get_memmap_range() + EC_MEMMAP_HOST_CMD_FLAGS) = |
| EC_HOST_CMD_FLAG_LPC_ARGS_SUPPORTED | |
| EC_HOST_CMD_FLAG_VERSION_3; |
| |
| /* Enable LPC interrupt */ |
| task_enable_irq(LM4_IRQ_LPC); |
| |
| #ifdef CONFIG_UART_HOST |
| /* Enable COMx UART */ |
| uart_comx_enable(); |
| #endif |
| |
| /* Restore event masks if needed */ |
| lpc_post_sysjump(); |
| |
| /* Sufficiently initialized */ |
| init_done = 1; |
| |
| /* Update host events now that we can copy them to memmap */ |
| update_host_event_status(); |
| } |
| /* |
| * Set prio to higher than default; this way LPC memory mapped data is ready |
| * before other inits try to initialize their memmap data. |
| */ |
| DECLARE_HOOK(HOOK_INIT, lpc_init, HOOK_PRIO_INIT_LPC); |
| |
| static void lpc_resume(void) |
| { |
| /* Mask all host events until the host unmasks them itself. */ |
| lpc_set_host_event_mask(LPC_HOST_EVENT_SMI, 0); |
| lpc_set_host_event_mask(LPC_HOST_EVENT_SCI, 0); |
| lpc_set_host_event_mask(LPC_HOST_EVENT_WAKE, 0); |
| |
| /* Store port 80 event so we know where resume happened */ |
| port_80_write(PORT_80_EVENT_RESUME); |
| } |
| DECLARE_HOOK(HOOK_CHIPSET_RESUME, lpc_resume, HOOK_PRIO_DEFAULT); |
| |
| static void lpc_tick(void) |
| { |
| /* |
| * Make sure pending LPC interrupts have been processed. |
| * This works around a LM4 bug where host writes sometimes |
| * don't trigger interrupts. See crosbug.com/p/13965. |
| */ |
| task_trigger_irq(LM4_IRQ_LPC); |
| } |
| DECLARE_HOOK(HOOK_TICK, lpc_tick, HOOK_PRIO_DEFAULT); |
| |
| /** |
| * Get protocol information |
| */ |
| static int lpc_get_protocol_info(struct host_cmd_handler_args *args) |
| { |
| struct ec_response_get_protocol_info *r = args->response; |
| |
| memset(r, 0, sizeof(*r)); |
| r->protocol_versions = (1 << 2) | (1 << 3); |
| r->max_request_packet_size = EC_LPC_HOST_PACKET_SIZE; |
| r->max_response_packet_size = EC_LPC_HOST_PACKET_SIZE; |
| r->flags = 0; |
| |
| args->response_size = sizeof(*r); |
| |
| return EC_SUCCESS; |
| } |
| DECLARE_HOST_COMMAND(EC_CMD_GET_PROTOCOL_INFO, |
| lpc_get_protocol_info, |
| EC_VER_MASK(0)); |