Files
OpenCellular/chip/it83xx/lpc.c
Dino Li afae611999 it8380dev: change PNPCFG base address to 4E/4F
Always reserved 2E/2F for super I/O.
This can avoid conflict with super I/O base address.

Signed-off-by: Dino Li <dino.li@ite.com.tw>

BRANCH=none
BUG=none
TEST=make buildall -j

Change-Id: I67a37355e320e289fb1f58c7356a1592f7645d21
Reviewed-on: https://chromium-review.googlesource.com/290087
Reviewed-by: Randall Spangler <rspangler@chromium.org>
Commit-Queue: Dino Li <dino.li@ite.com.tw>
Tested-by: Dino Li <dino.li@ite.com.tw>
2015-08-04 22:27:14 +00:00

693 lines
17 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 "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"
#include "irq_chip.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 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 *)host_cmd_memmap +
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 *)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;
}
/**
* 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(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);
}
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);
}
static void 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 (host_events & event_mask[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 (host_events & event_mask[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 */
*(uint32_t *)host_get_memmap(EC_MEMMAP_HOST_EVENTS) = host_events;
task_enable_irq(IT83XX_IRQ_PMC_IN);
/* 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();
}
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;
/*
* 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 &= 0xFC;
/*
* 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);
}
void lpc_keyboard_clear_buffer(void)
{
/* --- (not implemented yet) --- */
}
void lpc_keyboard_resume_irq(void)
{
if (lpc_keyboard_has_char()) {
/* 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;
task_clear_pending_irq(IT83XX_IRQ_KBC_OUT);
task_enable_irq(IT83XX_IRQ_KBC_OUT);
}
}
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)
{
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);
}
int lpc_get_pltrst_asserted(void)
{
return !gpio_get_level(GPIO_PCH_PLTRST_L);
}
/* 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);
}
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);
if (!(IT83XX_KBC_KBHICR & 0x01)) {
IT83XX_KBC_KBIRQR &= ~0x01;
IT83XX_KBC_KBHICR |= 0x01;
}
#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);
/* 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);
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);
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);
task_clear_pending_irq(IT83XX_IRQ_PMC2_IN);
}
void pm3_ibf_interrupt(void)
{
if (pm_get_status(LPC_HOST_PORT_80H) & EC_LPC_STATUS_FROM_HOST)
port_80_write(pm_get_data_in(LPC_HOST_PORT_80H));
task_clear_pending_irq(IT83XX_IRQ_PMC3_IN);
}
void pm4_ibf_interrupt(void)
{
task_clear_pending_irq(IT83XX_IRQ_PMC4_IN);
}
void pm5_ibf_interrupt(void)
{
task_clear_pending_irq(IT83XX_IRQ_PMC5_IN);
}
static void lpc_init(void)
{
/*
* DLM 52k~56k size select enable.
* For mapping LPC I/O cycle 800h ~ 9FFh to DLM 8D800 ~ 8D9FF.
*/
IT83XX_GCTRL_MCCR2 |= 0x10;
IT83XX_GPIO_GCR = 0x06;
/* 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.
*/
IT83XX_KBC_KBHICR |= 0x0C;
/* 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);
gpio_enable_interrupt(GPIO_PCH_PLTRST_L);
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);
task_enable_irq(IT83XX_IRQ_PMC_IN);
task_clear_pending_irq(IT83XX_IRQ_PMC2_IN);
task_enable_irq(IT83XX_IRQ_PMC2_IN);
task_clear_pending_irq(IT83XX_IRQ_PMC3_IN);
task_enable_irq(IT83XX_IRQ_PMC3_IN);
/* 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);
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");
}
/* 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);
}
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);
/* 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));