mirror of
https://github.com/Telecominfraproject/OpenCellular.git
synced 2026-01-11 18:35:28 +00:00
With the upcoming change to add a new command to get/set/clear host events and masks, it seems to be the right time to bump up the host events and masks to 64-bit. We are already out of available host events. This change opens up at least 32 bits for new host events. Old EC commands to operate on host events/masks will still deal with lower 32-bits of the events/mask. On the other hand, the new command being added will take care of the entire 64-bit events/masks. This ensures that old BIOS and kernel versions can still work with the newer EC versions. BUG=b:69329196 BRANCH=None TEST=make -j buildall. Verified: 1. hostevent set 0x4000 ==> Sets correct bit in host events 2. hostevent clear 0x4000 ==> Clears correct bit in host events 3. Kernel is able to query and read correct host event bits from EC. Verified using evtest. 4. Coreboot is able to read correct wake reason from EC. Verified using mosys eventlog list. Change-Id: Idcb24ea364ac6c491efc2f8dd9e29a9df6149e07 Signed-off-by: Furquan Shaikh <furquan@chromium.org> Reviewed-on: https://chromium-review.googlesource.com/770925 Reviewed-by: Randall Spangler <rspangler@chromium.org>
762 lines
18 KiB
C
762 lines
18 KiB
C
/* Copyright (c) 2014 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 "chipset.h"
|
|
#include "clock.h"
|
|
#include "common.h"
|
|
#include "console.h"
|
|
#include "ec2i_chip.h"
|
|
#include "espi.h"
|
|
#include "gpio.h"
|
|
#include "hooks.h"
|
|
#include "host_command.h"
|
|
#include "intc.h"
|
|
#include "irq_chip.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"
|
|
|
|
/* Console output macros */
|
|
#define CPUTS(outstr) cputs(CC_LPC, outstr)
|
|
#define CPRINTS(format, args...) cprints(CC_LPC, format, ## args)
|
|
|
|
/* LPC PM channels */
|
|
enum lpc_pm_ch {
|
|
LPC_PM1 = 0,
|
|
LPC_PM2,
|
|
LPC_PM3,
|
|
LPC_PM4,
|
|
LPC_PM5,
|
|
};
|
|
|
|
enum pm_ctrl_mask {
|
|
/* Input Buffer Full Interrupt Enable. */
|
|
PM_CTRL_IBFIE = 0x01,
|
|
/* Output Buffer Empty Interrupt Enable. */
|
|
PM_CTRL_OBEIE = 0x02,
|
|
};
|
|
|
|
#define LPC_ACPI_CMD LPC_PM1 /* ACPI commands 62h/66h port */
|
|
#define LPC_HOST_CMD LPC_PM2 /* Host commands 200h/204h port */
|
|
#define LPC_HOST_PORT_80H LPC_PM3 /* Host 80h port */
|
|
|
|
static uint8_t acpi_ec_memmap[EC_MEMMAP_SIZE]
|
|
__attribute__((section(".h2ram.pool.acpiec")));
|
|
static uint8_t host_cmd_memmap[256]
|
|
__attribute__((section(".h2ram.pool.hostcmd")));
|
|
|
|
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 int p80l_index;
|
|
|
|
static struct ec_lpc_host_args * const lpc_host_args =
|
|
(struct ec_lpc_host_args *)host_cmd_memmap;
|
|
|
|
static void pm_set_ctrl(enum lpc_pm_ch ch, enum pm_ctrl_mask ctrl, int set)
|
|
{
|
|
if (set)
|
|
IT83XX_PMC_PMCTL(ch) |= ctrl;
|
|
else
|
|
IT83XX_PMC_PMCTL(ch) &= ~ctrl;
|
|
}
|
|
|
|
static void pm_set_status(enum lpc_pm_ch ch, uint8_t status, int set)
|
|
{
|
|
if (set)
|
|
IT83XX_PMC_PMSTS(ch) |= status;
|
|
else
|
|
IT83XX_PMC_PMSTS(ch) &= ~status;
|
|
}
|
|
|
|
static uint8_t pm_get_status(enum lpc_pm_ch ch)
|
|
{
|
|
return IT83XX_PMC_PMSTS(ch);
|
|
}
|
|
|
|
static uint8_t pm_get_data_in(enum lpc_pm_ch ch)
|
|
{
|
|
return IT83XX_PMC_PMDI(ch);
|
|
}
|
|
|
|
static void pm_put_data_out(enum lpc_pm_ch ch, uint8_t out)
|
|
{
|
|
IT83XX_PMC_PMDO(ch) = out;
|
|
}
|
|
|
|
static void pm_clear_ibf(enum lpc_pm_ch ch)
|
|
{
|
|
/* bit7, write-1 clear IBF */
|
|
IT83XX_PMC_PMIE(ch) |= (1 << 7);
|
|
}
|
|
|
|
#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.
|
|
*/
|
|
gpio_set_level(CONFIG_KEYBOARD_IRQ_GPIO, 1);
|
|
udelay(4);
|
|
/* Generate a falling edge */
|
|
gpio_set_level(CONFIG_KEYBOARD_IRQ_GPIO, 0);
|
|
udelay(4);
|
|
|
|
/* Set signal high, now that we've generated the edge */
|
|
gpio_set_level(CONFIG_KEYBOARD_IRQ_GPIO, 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)
|
|
{
|
|
gpio_set_level(GPIO_PCH_SMI_L, 0);
|
|
udelay(65);
|
|
gpio_set_level(GPIO_PCH_SMI_L, 1);
|
|
}
|
|
|
|
static void lpc_generate_sci(void)
|
|
{
|
|
gpio_set_level(GPIO_PCH_SCI_L, 0);
|
|
udelay(65);
|
|
gpio_set_level(GPIO_PCH_SCI_L, 1);
|
|
}
|
|
|
|
/**
|
|
* Update the level-sensitive wake signal to the AP.
|
|
*
|
|
* @param wake_events Currently asserted wake events
|
|
*/
|
|
static void lpc_update_wake(host_event_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);
|
|
}
|
|
|
|
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 OBF status bit. */
|
|
pm_put_data_out(LPC_HOST_CMD, args->result);
|
|
|
|
/* Clear the busy bit, so the host knows the EC is done. */
|
|
pm_set_status(LPC_HOST_CMD, EC_LPC_STATUS_PROCESSING, 0);
|
|
}
|
|
|
|
void lpc_update_host_event_status(void)
|
|
{
|
|
int need_sci = 0;
|
|
int need_smi = 0;
|
|
|
|
if (!init_done)
|
|
return;
|
|
|
|
/* Disable PMC1 interrupt while updating status register */
|
|
task_disable_irq(IT83XX_IRQ_PMC_IN);
|
|
|
|
if (lpc_get_host_events_by_type(LPC_HOST_EVENT_SMI)) {
|
|
/* Only generate SMI for first event */
|
|
if (!(pm_get_status(LPC_ACPI_CMD) & EC_LPC_STATUS_SMI_PENDING))
|
|
need_smi = 1;
|
|
pm_set_status(LPC_ACPI_CMD, EC_LPC_STATUS_SMI_PENDING, 1);
|
|
} else {
|
|
pm_set_status(LPC_ACPI_CMD, EC_LPC_STATUS_SMI_PENDING, 0);
|
|
}
|
|
|
|
if (lpc_get_host_events_by_type(LPC_HOST_EVENT_SCI)) {
|
|
/* Generate SCI for every event */
|
|
need_sci = 1;
|
|
pm_set_status(LPC_ACPI_CMD, EC_LPC_STATUS_SCI_PENDING, 1);
|
|
} else {
|
|
pm_set_status(LPC_ACPI_CMD, EC_LPC_STATUS_SCI_PENDING, 0);
|
|
}
|
|
|
|
/* Copy host events to mapped memory */
|
|
*(host_event_t *)host_get_memmap(EC_MEMMAP_HOST_EVENTS) =
|
|
lpc_get_host_events();
|
|
|
|
task_enable_irq(IT83XX_IRQ_PMC_IN);
|
|
|
|
/* Process the wake events. */
|
|
lpc_update_wake(lpc_get_host_events_by_type(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();
|
|
}
|
|
|
|
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. */
|
|
pm_put_data_out(LPC_HOST_CMD, pkt->driver_result);
|
|
|
|
/* Clear the busy bit, so the host knows the EC is done. */
|
|
pm_set_status(LPC_HOST_CMD, EC_LPC_STATUS_PROCESSING, 0);
|
|
}
|
|
|
|
uint8_t *lpc_get_memmap_range(void)
|
|
{
|
|
return (uint8_t *)acpi_ec_memmap;
|
|
}
|
|
|
|
int lpc_keyboard_has_char(void)
|
|
{
|
|
/* OBE or OBF */
|
|
return IT83XX_KBC_KBHISR & 0x01;
|
|
}
|
|
|
|
int lpc_keyboard_input_pending(void)
|
|
{
|
|
/* IBE or IBF */
|
|
return IT83XX_KBC_KBHISR & 0x02;
|
|
}
|
|
|
|
void lpc_keyboard_put_char(uint8_t chr, int send_irq)
|
|
{
|
|
/* Clear programming data bit 7-4 */
|
|
IT83XX_KBC_KBHISR &= 0x0F;
|
|
|
|
/* keyboard */
|
|
IT83XX_KBC_KBHISR |= 0x10;
|
|
|
|
#ifdef CONFIG_KEYBOARD_IRQ_GPIO
|
|
task_clear_pending_irq(IT83XX_IRQ_KBC_OUT);
|
|
/* The data output to the KBC Data Output Register. */
|
|
IT83XX_KBC_KBHIKDOR = chr;
|
|
task_enable_irq(IT83XX_IRQ_KBC_OUT);
|
|
if (send_irq)
|
|
keyboard_irq_assert();
|
|
#else
|
|
/*
|
|
* bit0 = 0, The IRQ1 is controlled by the IRQ1B bit in KBIRQR.
|
|
* bit1 = 0, The IRQ12 is controlled by the IRQ12B bit in KBIRQR.
|
|
*/
|
|
IT83XX_KBC_KBHICR &= 0x3C;
|
|
|
|
/*
|
|
* Enable the interrupt to keyboard driver in the host processor
|
|
* via SERIRQ when the output buffer is full.
|
|
*/
|
|
if (send_irq)
|
|
IT83XX_KBC_KBHICR |= 0x01;
|
|
|
|
udelay(16);
|
|
|
|
task_clear_pending_irq(IT83XX_IRQ_KBC_OUT);
|
|
/* The data output to the KBC Data Output Register. */
|
|
IT83XX_KBC_KBHIKDOR = chr;
|
|
task_enable_irq(IT83XX_IRQ_KBC_OUT);
|
|
#endif
|
|
}
|
|
|
|
void lpc_keyboard_clear_buffer(void)
|
|
{
|
|
uint32_t int_mask = get_int_mask();
|
|
interrupt_disable();
|
|
/* bit6, write-1 clear OBF */
|
|
IT83XX_KBC_KBHICR |= (1 << 6);
|
|
IT83XX_KBC_KBHICR &= ~(1 << 6);
|
|
set_int_mask(int_mask);
|
|
}
|
|
|
|
void lpc_keyboard_resume_irq(void)
|
|
{
|
|
if (lpc_keyboard_has_char()) {
|
|
#ifdef CONFIG_KEYBOARD_IRQ_GPIO
|
|
keyboard_irq_assert();
|
|
#else
|
|
/* The IRQ1 is controlled by the IRQ1B bit in KBIRQR. */
|
|
IT83XX_KBC_KBHICR &= ~0x01;
|
|
|
|
/*
|
|
* When the OBFKIE bit in KBC Host Interface Control Register
|
|
* (KBHICR) is 0, the bit directly controls the IRQ1 signal.
|
|
*/
|
|
IT83XX_KBC_KBIRQR |= 0x01;
|
|
#endif
|
|
|
|
task_clear_pending_irq(IT83XX_IRQ_KBC_OUT);
|
|
|
|
task_enable_irq(IT83XX_IRQ_KBC_OUT);
|
|
}
|
|
}
|
|
|
|
void lpc_set_acpi_status_mask(uint8_t mask)
|
|
{
|
|
pm_set_status(LPC_ACPI_CMD, mask, 1);
|
|
}
|
|
|
|
void lpc_clear_acpi_status_mask(uint8_t mask)
|
|
{
|
|
pm_set_status(LPC_ACPI_CMD, mask, 0);
|
|
}
|
|
|
|
#ifndef CONFIG_ESPI_VW_SIGNALS
|
|
int lpc_get_pltrst_asserted(void)
|
|
{
|
|
return !gpio_get_level(GPIO_PCH_PLTRST_L);
|
|
}
|
|
#endif
|
|
|
|
/* KBC and PMC control modules */
|
|
void lpc_kbc_ibf_interrupt(void)
|
|
{
|
|
if (lpc_keyboard_input_pending()) {
|
|
keyboard_host_write(IT83XX_KBC_KBHIDIR,
|
|
(IT83XX_KBC_KBHISR & 0x08) ? 1 : 0);
|
|
/* bit7, write-1 clear IBF */
|
|
IT83XX_KBC_KBHICR |= (1 << 7);
|
|
IT83XX_KBC_KBHICR &= ~(1 << 7);
|
|
}
|
|
|
|
task_clear_pending_irq(IT83XX_IRQ_KBC_IN);
|
|
|
|
#ifdef HAS_TASK_KEYPROTO
|
|
task_wake(TASK_ID_KEYPROTO);
|
|
#endif
|
|
}
|
|
|
|
void lpc_kbc_obe_interrupt(void)
|
|
{
|
|
task_disable_irq(IT83XX_IRQ_KBC_OUT);
|
|
|
|
task_clear_pending_irq(IT83XX_IRQ_KBC_OUT);
|
|
|
|
#ifndef CONFIG_KEYBOARD_IRQ_GPIO
|
|
if (!(IT83XX_KBC_KBHICR & 0x01)) {
|
|
IT83XX_KBC_KBIRQR &= ~0x01;
|
|
|
|
IT83XX_KBC_KBHICR |= 0x01;
|
|
}
|
|
#endif
|
|
|
|
#ifdef HAS_TASK_KEYPROTO
|
|
task_wake(TASK_ID_KEYPROTO);
|
|
#endif
|
|
}
|
|
|
|
void pm1_ibf_interrupt(void)
|
|
{
|
|
int is_cmd;
|
|
uint8_t value, result;
|
|
|
|
if (pm_get_status(LPC_ACPI_CMD) & EC_LPC_STATUS_FROM_HOST) {
|
|
/* Set the busy bit */
|
|
pm_set_status(LPC_ACPI_CMD, EC_LPC_STATUS_PROCESSING, 1);
|
|
|
|
/* data from command port or data port */
|
|
is_cmd = pm_get_status(LPC_ACPI_CMD) & EC_LPC_STATUS_LAST_CMD;
|
|
|
|
/* Get command or data */
|
|
value = pm_get_data_in(LPC_ACPI_CMD);
|
|
|
|
/* Handle whatever this was. */
|
|
if (acpi_ap_to_ec(is_cmd, value, &result))
|
|
pm_put_data_out(LPC_ACPI_CMD, result);
|
|
|
|
pm_clear_ibf(LPC_ACPI_CMD);
|
|
|
|
/* Clear the busy bit */
|
|
pm_set_status(LPC_ACPI_CMD, EC_LPC_STATUS_PROCESSING, 0);
|
|
|
|
/*
|
|
* ACPI 5.0-12.6.1: Generate SCI for Input Buffer Empty
|
|
* Output Buffer Full condition on the kernel channel.
|
|
*/
|
|
lpc_generate_sci();
|
|
}
|
|
|
|
task_clear_pending_irq(IT83XX_IRQ_PMC_IN);
|
|
}
|
|
|
|
void pm2_ibf_interrupt(void)
|
|
{
|
|
uint8_t value __attribute__((unused)) = 0;
|
|
uint8_t status;
|
|
|
|
status = pm_get_status(LPC_HOST_CMD);
|
|
/* IBE */
|
|
if (!(status & EC_LPC_STATUS_FROM_HOST)) {
|
|
task_clear_pending_irq(IT83XX_IRQ_PMC2_IN);
|
|
return;
|
|
}
|
|
|
|
/* IBF and data port */
|
|
if (!(status & EC_LPC_STATUS_LAST_CMD)) {
|
|
/* R/C IBF*/
|
|
value = pm_get_data_in(LPC_HOST_CMD);
|
|
pm_clear_ibf(LPC_HOST_CMD);
|
|
task_clear_pending_irq(IT83XX_IRQ_PMC2_IN);
|
|
return;
|
|
}
|
|
|
|
/* Set the busy bit */
|
|
pm_set_status(LPC_HOST_CMD, EC_LPC_STATUS_PROCESSING, 1);
|
|
|
|
/*
|
|
* Read the command byte. This clears the FRMH bit in
|
|
* the status byte.
|
|
*/
|
|
host_cmd_args.command = pm_get_data_in(LPC_HOST_CMD);
|
|
|
|
host_cmd_args.result = EC_RES_SUCCESS;
|
|
if (host_cmd_args.command != EC_COMMAND_PROTOCOL_3)
|
|
host_cmd_args.send_response = lpc_send_response;
|
|
host_cmd_flags = lpc_host_args->flags;
|
|
|
|
/* We only support new style command (v3) now */
|
|
if (host_cmd_args.command == EC_COMMAND_PROTOCOL_3) {
|
|
lpc_packet.send_response = lpc_send_response_packet;
|
|
|
|
lpc_packet.request = (const void *)host_cmd_memmap;
|
|
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 *)host_cmd_memmap;
|
|
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);
|
|
|
|
pm_clear_ibf(LPC_HOST_CMD);
|
|
task_clear_pending_irq(IT83XX_IRQ_PMC2_IN);
|
|
return;
|
|
} 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);
|
|
|
|
pm_clear_ibf(LPC_HOST_CMD);
|
|
task_clear_pending_irq(IT83XX_IRQ_PMC2_IN);
|
|
}
|
|
|
|
void pm3_ibf_interrupt(void)
|
|
{
|
|
int new_p80_idx, i;
|
|
enum ec2i_message ec2i_r;
|
|
|
|
/* set LDN */
|
|
if (ec2i_write(HOST_INDEX_LDN, LDN_RTCT) == EC2I_WRITE_SUCCESS) {
|
|
/* get P80L current index */
|
|
ec2i_r = ec2i_read(HOST_INDEX_DSLDC6);
|
|
/* clear IBF */
|
|
pm_clear_ibf(LPC_HOST_PORT_80H);
|
|
/* read OK */
|
|
if ((ec2i_r & 0xff00) == EC2I_READ_SUCCESS) {
|
|
new_p80_idx = ec2i_r & P80L_BRAM_BANK1_SIZE_MASK;
|
|
for (i = 0; i < (P80L_P80LE - P80L_P80LB + 1); i++) {
|
|
if (++p80l_index > P80L_P80LE)
|
|
p80l_index = P80L_P80LB;
|
|
port_80_write(IT83XX_BRAM_BANK1(p80l_index));
|
|
if (p80l_index == new_p80_idx)
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
pm_clear_ibf(LPC_HOST_PORT_80H);
|
|
}
|
|
|
|
task_clear_pending_irq(IT83XX_IRQ_PMC3_IN);
|
|
}
|
|
|
|
void pm4_ibf_interrupt(void)
|
|
{
|
|
pm_clear_ibf(LPC_PM4);
|
|
task_clear_pending_irq(IT83XX_IRQ_PMC4_IN);
|
|
}
|
|
|
|
void pm5_ibf_interrupt(void)
|
|
{
|
|
pm_clear_ibf(LPC_PM5);
|
|
task_clear_pending_irq(IT83XX_IRQ_PMC5_IN);
|
|
}
|
|
|
|
static void lpc_init(void)
|
|
{
|
|
enum ec2i_message ec2i_r;
|
|
|
|
/* SPI slave interface is disabled */
|
|
IT83XX_GCTRL_SSCR = 0;
|
|
/*
|
|
* DLM 52k~56k size select enable.
|
|
* For mapping LPC I/O cycle 800h ~ 9FFh to DLM 8D800 ~ 8D9FF.
|
|
*/
|
|
IT83XX_GCTRL_MCCR2 |= 0x10;
|
|
|
|
/* The register pair to access PNPCFG is 004Eh and 004Fh */
|
|
IT83XX_GCTRL_BADRSEL = 0x01;
|
|
|
|
/* Disable KBC IRQ */
|
|
IT83XX_KBC_KBIRQR = 0x00;
|
|
|
|
/*
|
|
* bit2, Output Buffer Empty CPU Interrupt Enable.
|
|
* bit3, Input Buffer Full CPU Interrupt Enable.
|
|
* bit5, IBF/OBF EC clear mode.
|
|
* 0b: IBF cleared if EC read data register, EC reset, or host reset.
|
|
* OBF cleared if host read data register, or EC reset.
|
|
* 1b: IBF cleared if EC write-1 to bit7 at related registers,
|
|
* EC reset, or host reset.
|
|
* OBF cleared if host read data register, EC write-1 to bit6 at
|
|
* related registers, or EC reset.
|
|
*/
|
|
IT83XX_KBC_KBHICR |= 0x2C;
|
|
|
|
/* PM1 Input Buffer Full Interrupt Enable for 62h/66 port */
|
|
pm_set_ctrl(LPC_ACPI_CMD, PM_CTRL_IBFIE, 1);
|
|
|
|
/* PM2 Input Buffer Full Interrupt Enable for 200h/204 port */
|
|
pm_set_ctrl(LPC_HOST_CMD, PM_CTRL_IBFIE, 1);
|
|
|
|
memset(lpc_get_memmap_range(), 0, EC_MEMMAP_SIZE);
|
|
memset(lpc_host_args, 0, sizeof(*lpc_host_args));
|
|
|
|
/* Host LPC I/O cycle mapping to RAM */
|
|
/*
|
|
* bit[4], H2RAM through LPC IO cycle.
|
|
* bit[1], H2RAM window 1 enabled.
|
|
* bit[0], H2RAM window 0 enabled.
|
|
*/
|
|
IT83XX_SMFI_HRAMWC |= 0x13;
|
|
|
|
/*
|
|
* bit[7:6]
|
|
* Host RAM Window[x] Read Protect Enable
|
|
* 00b: Disabled
|
|
* 01b: Lower half of RAM window protected
|
|
* 10b: Upper half of RAM window protected
|
|
* 11b: All protected
|
|
*
|
|
* bit[5:4]
|
|
* Host RAM Window[x] Write Protect Enable
|
|
* 00b: Disabled
|
|
* 01b: Lower half of RAM window protected
|
|
* 10b: Upper half of RAM window protected
|
|
* 11b: All protected
|
|
*
|
|
* bit[2:0]
|
|
* Host RAM Window 1 Size (HRAMW1S)
|
|
* 0h: 16 bytes
|
|
* 1h: 32 bytes
|
|
* 2h: 64 bytes
|
|
* 3h: 128 bytes
|
|
* 4h: 256 bytes
|
|
* 5h: 512 bytes
|
|
* 6h: 1024 bytes
|
|
* 7h: 2048 bytes
|
|
*/
|
|
|
|
/* H2RAM Win 0 Base Address 800h allow r/w for host_cmd_memmap */
|
|
IT83XX_SMFI_HRAMW0BA = 0x80;
|
|
IT83XX_SMFI_HRAMW0AAS = 0x04;
|
|
|
|
/* H2RAM Win 1 Base Address 900h allow r for acpi_ec_memmap */
|
|
IT83XX_SMFI_HRAMW1BA = 0x90;
|
|
IT83XX_SMFI_HRAMW1AAS = 0x34;
|
|
|
|
/* 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;
|
|
|
|
/*
|
|
* bit[5], Dedicated interrupt
|
|
* INT3: PMC1 Output Buffer Empty Int
|
|
* INT25: PMC1 Input Buffer Full Int
|
|
* INT26: PMC2 Output Buffer Empty Int
|
|
* INT27: PMC2 Input Buffer Full Int
|
|
*/
|
|
IT83XX_PMC_MBXCTRL |= 0x20;
|
|
|
|
/* PM3 Input Buffer Full Interrupt Enable for 80h port */
|
|
pm_set_ctrl(LPC_HOST_PORT_80H, PM_CTRL_IBFIE, 1);
|
|
|
|
p80l_index = P80L_P80LC;
|
|
if (ec2i_write(HOST_INDEX_LDN, LDN_RTCT) == EC2I_WRITE_SUCCESS) {
|
|
/* get P80L current index */
|
|
ec2i_r = ec2i_read(HOST_INDEX_DSLDC6);
|
|
/* read OK */
|
|
if ((ec2i_r & 0xff00) == EC2I_READ_SUCCESS)
|
|
p80l_index = ec2i_r & P80L_BRAM_BANK1_SIZE_MASK;
|
|
}
|
|
|
|
/*
|
|
* bit[7], enable P80L function.
|
|
* bit[6], accept port 80h cycle.
|
|
* bit[1-0], 10b: I2EC is read-only.
|
|
*/
|
|
IT83XX_GCTRL_SPCTRL1 |= 0xC2;
|
|
|
|
#ifndef CONFIG_ESPI_VW_SIGNALS
|
|
gpio_enable_interrupt(GPIO_PCH_PLTRST_L);
|
|
#endif
|
|
|
|
task_clear_pending_irq(IT83XX_IRQ_KBC_OUT);
|
|
task_disable_irq(IT83XX_IRQ_KBC_OUT);
|
|
|
|
task_clear_pending_irq(IT83XX_IRQ_KBC_IN);
|
|
task_enable_irq(IT83XX_IRQ_KBC_IN);
|
|
|
|
task_clear_pending_irq(IT83XX_IRQ_PMC_IN);
|
|
pm_set_status(LPC_ACPI_CMD, EC_LPC_STATUS_PROCESSING, 0);
|
|
task_enable_irq(IT83XX_IRQ_PMC_IN);
|
|
|
|
task_clear_pending_irq(IT83XX_IRQ_PMC2_IN);
|
|
pm_set_status(LPC_HOST_CMD, EC_LPC_STATUS_PROCESSING, 0);
|
|
task_enable_irq(IT83XX_IRQ_PMC2_IN);
|
|
|
|
task_clear_pending_irq(IT83XX_IRQ_PMC3_IN);
|
|
task_enable_irq(IT83XX_IRQ_PMC3_IN);
|
|
|
|
#ifdef CONFIG_ESPI
|
|
espi_init();
|
|
#endif
|
|
/* Sufficiently initialized */
|
|
init_done = 1;
|
|
|
|
/* Update host events now that we can copy them to memmap */
|
|
lpc_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);
|
|
|
|
void lpc_host_reset(void)
|
|
{
|
|
/* Host Reset Control will assert RCIN# */
|
|
#ifdef CONFIG_ESPI_VW_SIGNALS
|
|
espi_vw_set_wire(VW_RCIN_L, 0);
|
|
udelay(10);
|
|
espi_vw_set_wire(VW_RCIN_L, 1);
|
|
#else
|
|
gpio_set_level(GPIO_PCH_RCIN_L, 0);
|
|
udelay(10);
|
|
gpio_set_level(GPIO_PCH_RCIN_L, 1);
|
|
#endif
|
|
}
|
|
|
|
#ifndef CONFIG_ESPI_VW_SIGNALS
|
|
void lpcrst_interrupt(enum gpio_signal signal)
|
|
{
|
|
if (lpc_get_pltrst_asserted())
|
|
/* Store port 80 reset event */
|
|
port_80_write(PORT_80_EVENT_RESET);
|
|
|
|
CPRINTS("LPC RESET# %sasserted",
|
|
lpc_get_pltrst_asserted() ? "" : "de");
|
|
}
|
|
#endif
|
|
|
|
/* Enable LPC ACPI-EC interrupts */
|
|
void lpc_enable_acpi_interrupts(void)
|
|
{
|
|
task_enable_irq(IT83XX_IRQ_PMC_IN);
|
|
}
|
|
|
|
/* Disable LPC ACPI-EC interrupts */
|
|
void lpc_disable_acpi_interrupts(void)
|
|
{
|
|
task_disable_irq(IT83XX_IRQ_PMC_IN);
|
|
}
|
|
|
|
/* 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 << 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));
|