Add support for ACPI read/write commands

This is needed to support the kernel keyboard backlight driver through ACPI.

Also adds a few other memory addresses for testing this interface -
version, test, and test-compliment.

BUG=chrome-os-partner:12001
TEST=manual

    - query next ACPI event
    io_write8 0x66 0x84
    io_read8 0x62
    0x00
    - read ACPI memmap version
    io_write8 0x66 0x80
    io_write8 0x62 0
    io_read8 0x62
    0x01
    - extra command writes shouldn't crash
    io_write8 0x66 0x80
    io_write8 0x66 0x80
    io_write8 0x62 1
    - extra data writes shouldn't crash either
    io_write8 0x62 1
    io_write8 0x62 1
    - write test address
    io_write8 0x66 0x81
    io_write8 0x62 1
    io_write8 0x62 0x2a
    - read it back
    io_write8 0x66 0x80
    io_write8 0x62 1
    io_read8 0x62
    0x2a
    - read back test compliment
    io_write8 0x66 0x80
    io_write8 0x62 2
    io_read8 0x62
    0xd5
    - set keyboard backlight to 50%
    io_write8 0x66 0x81
    io_write8 0x62 3
    io_write8 0x62 50
    - read it back
    io_write8 0x66 0x80
    io_write8 0x62 3
    io_read8 0x62
    0x32

Change-Id: I619fdbd322cdef8ffffbb882b3bbb587e364334d
Signed-off-by: Randall Spangler <rspangler@chromium.org>
Reviewed-on: https://gerrit.chromium.org/gerrit/28714
Reviewed-by: Duncan Laurie <dlaurie@chromium.org>
This commit is contained in:
Randall Spangler
2012-07-30 13:02:29 -07:00
committed by Gerrit
parent 7adb0aad16
commit 2da2e72dd3
3 changed files with 180 additions and 58 deletions

View File

@@ -13,6 +13,7 @@
#include "i8042.h"
#include "lpc.h"
#include "port80.h"
#include "pwm.h"
#include "registers.h"
#include "system.h"
#include "task.h"
@@ -26,12 +27,10 @@
#define LPC_SYSJUMP_TAG 0x4c50 /* "LP" */
/* Bit masks for LPCCH?ST */
#define LPC_STATUS_MASK_BUSY (1 << 12)
#define LPC_STATUS_MASK_SMI (1 << 10)
#define LPC_STATUS_MASK_SCI (1 << 9)
#define LPC_STATUS_MASK_PRESENT (1 << 8)
#define LPC_STATUS_MASK_TOH (1 << 0) /* TO Host bit */
static uint8_t acpi_cmd; /* Last received ACPI command */
static uint8_t acpi_addr; /* First byte of data after ACPI command */
static int acpi_data_count; /* Number of data writes after command */
static uint8_t acpi_mem_test; /* Test byte in ACPI memory space */
static uint32_t host_events; /* Currently pending SCI/SMI events */
static uint32_t event_mask[3]; /* Event masks for each type */
@@ -56,7 +55,6 @@ static void configure_gpio(void)
gpio_set_alternate_function(LM4_GPIO_M, 0x33, 0x0f);
}
static void wait_irq_sent(void)
{
/* TODO: udelay() is not graceful. Since the SIRQRIS is almost not
@@ -190,14 +188,14 @@ static void lpc_send_response(struct host_cmd_handler_args *args)
/* Clear the busy bit */
task_disable_irq(LM4_IRQ_LPC);
LM4_LPC_ST(LPC_CH_CMD) &= ~LPC_STATUS_MASK_BUSY;
LM4_LPC_ST(LPC_CH_CMD) &= ~LM4_LPC_ST_BUSY;
task_enable_irq(LM4_IRQ_LPC);
}
/* Return true if the TOH is still set */
int lpc_keyboard_has_char(void)
{
return (LM4_LPC_ST(LPC_CH_KEYBOARD) & LPC_STATUS_MASK_TOH) ? 1 : 0;
return (LM4_LPC_ST(LPC_CH_KEYBOARD) & LM4_LPC_ST_TOH) ? 1 : 0;
}
@@ -217,7 +215,7 @@ 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) &= ~LPC_STATUS_MASK_TOH;
LM4_LPC_ST(LPC_CH_KEYBOARD) &= ~LM4_LPC_ST_TOH;
/* Ensure there is no TOH set in this period. */
wait_irq_sent();
@@ -269,18 +267,18 @@ static void update_host_event_status(void) {
if (host_events & event_mask[LPC_HOST_EVENT_SMI]) {
/* Only generate SMI for first event */
if (!(LM4_LPC_ST(LPC_CH_ACPI) & LPC_STATUS_MASK_SMI))
if (!(LM4_LPC_ST(LPC_CH_ACPI) & LM4_LPC_ST_SMI))
need_smi = 1;
LM4_LPC_ST(LPC_CH_ACPI) |= LPC_STATUS_MASK_SMI;
LM4_LPC_ST(LPC_CH_ACPI) |= LM4_LPC_ST_SMI;
} else
LM4_LPC_ST(LPC_CH_ACPI) &= ~LPC_STATUS_MASK_SMI;
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) |= LPC_STATUS_MASK_SCI;
LM4_LPC_ST(LPC_CH_ACPI) |= LM4_LPC_ST_SCI;
} else
LM4_LPC_ST(LPC_CH_ACPI) &= ~LPC_STATUS_MASK_SCI;
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;
@@ -321,44 +319,99 @@ uint32_t lpc_get_host_event_mask(enum lpc_host_event_type type)
return event_mask[type];
}
/* Handle an ACPI command */
static void handle_acpi_command(void)
/**
* Handle command (is_cmd=1) or data (is_cmd=0) writes to ACPI I/O ports.
*/
static void handle_acpi_write(int is_cmd)
{
int cmd;
int result = 0;
int i;
int data = 0;
/* Set the busy bit */
LM4_LPC_ST(LPC_CH_ACPI) |= LPC_STATUS_MASK_BUSY;
LM4_LPC_ST(LPC_CH_ACPI) |= LM4_LPC_ST_BUSY;
/*
* Read the command byte and pass to the host command handler.
* This clears the FRMH bit in the status byte.
*/
cmd = LPC_POOL_ACPI[0];
/* Read command/data; this clears the FRMH status bit. */
if (is_cmd) {
acpi_cmd = LPC_POOL_ACPI[0];
acpi_data_count = 0;
} else {
data = LPC_POOL_ACPI[0];
/*
* The first data byte is the ACPI memory address for
* read/write commands.
*/
if (!acpi_data_count++)
acpi_addr = data;
}
/* Process complete commands */
if (acpi_cmd == EC_CMD_ACPI_READ && acpi_data_count == 1) {
/* ACPI read cmd + addr */
int result = 0;
switch (acpi_addr) {
case EC_ACPI_MEM_VERSION:
result = EC_ACPI_MEM_VERSION_CURRENT;
break;
case EC_ACPI_MEM_TEST:
result = acpi_mem_test;
break;
case EC_ACPI_MEM_TEST_COMPLIMENT:
result = 0xff - acpi_mem_test;
break;
#ifdef CONFIG_TASK_PWM
case EC_ACPI_MEM_KEYBOARD_BACKLIGHT:
/*
* TODO: not very satisfying that LPC knows directly
* about the keyboard backlight, but for now this is
* good enough and less code than defining a new
* console command interface just for ACPI read/write.
*/
result = pwm_get_keyboard_backlight();
break;
#endif
default:
break;
}
/* Send the result byte */
CPRINTF("[%T ACPI read 0x%02x = 0x%02x]\n", acpi_addr, result);
LPC_POOL_ACPI[1] = result;
} else if (acpi_cmd == EC_CMD_ACPI_WRITE && acpi_data_count == 2) {
/* ACPI write cmd + addr + data */
CPRINTF("[%T ACPI write 0x%02x = 0x%02x]\n", acpi_addr, data);
switch (acpi_addr) {
case EC_ACPI_MEM_TEST:
acpi_mem_test = data;
break;
#ifdef CONFIG_TASK_PWM
case EC_ACPI_MEM_KEYBOARD_BACKLIGHT:
pwm_set_keyboard_backlight(data);
break;
#endif
default:
break;
}
} else if (acpi_cmd == EC_CMD_ACPI_QUERY_EVENT && !acpi_data_count) {
/* Clear and return the lowest host event */
int evt_index = 0;
int i;
/* Process the command */
switch (cmd) {
case EC_CMD_ACPI_QUERY_EVENT:
for (i = 0; i < 32; i++) {
if (host_events & (1 << i)) {
host_clear_events(1 << i);
result = i + 1; /* Events are 1-based */
evt_index = i + 1; /* Events are 1-based */
break;
}
}
break;
default:
/* Something we don't handle; ignore it */
break;
CPRINTF("[%T ACPI query = %d]\n", evt_index);
LPC_POOL_ACPI[1] = evt_index;
}
/* Write the response */
LPC_POOL_ACPI[1] = result;
/* Clear the busy bit */
LM4_LPC_ST(LPC_CH_ACPI) &= ~LPC_STATUS_MASK_BUSY;
LM4_LPC_ST(LPC_CH_ACPI) &= ~LM4_LPC_ST_BUSY;
/*
* ACPI 5.0-12.6.1: Generate SCI for Input Buffer Empty / Output Buffer
@@ -368,9 +421,11 @@ static void handle_acpi_command(void)
}
/**
* We have received an unexpected ACPI request on the normal command channel
* from an old firmware/kernel, try to somewhat answer it.
* Handle unexpected ACPI query request on the normal command channel from an
* old API firmware/kernel. No need to handle other ACPI commands on the
* normal command channel, because old firmware/kernel only supported query.
*/
/* TODO: remove when link EVT is deprecated. */
static int acpi_on_bad_channel(struct host_cmd_handler_args *args)
{
int i;
@@ -450,14 +505,16 @@ static void lpc_interrupt(void)
LM4_LPC_LPCIC = mis;
#ifdef CONFIG_TASK_HOSTCMD
/* Handle ACPI command writes */
/* Handle ACPI command and data writes */
if (mis & LM4_LPC_INT_MASK(LPC_CH_ACPI, 4))
handle_acpi_command();
handle_acpi_write(1);
if (mis & LM4_LPC_INT_MASK(LPC_CH_ACPI, 2))
handle_acpi_write(0);
/* Handle user command writes */
if (mis & LM4_LPC_INT_MASK(LPC_CH_CMD, 4)) {
/* Set the busy bit */
LM4_LPC_ST(LPC_CH_CMD) |= LPC_STATUS_MASK_BUSY;
LM4_LPC_ST(LPC_CH_CMD) |= LM4_LPC_ST_BUSY;
/*
* Read the command byte. This clears the FRMH bit in the
@@ -557,8 +614,8 @@ static int lpc_init(void)
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 writes */
LM4_LPC_LPCIM |= LM4_LPC_INT_MASK(LPC_CH_ACPI, 4);
/* 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,

View File

@@ -118,6 +118,12 @@ static inline int lm4_lpc_addr(int ch, int offset)
#define LM4LPCREG(ch, offset) LM4REG(lm4_lpc_addr(ch, offset))
#define LM4_LPC_CTL(ch) LM4LPCREG(ch, 0x000)
#define LM4_LPC_ST(ch) LM4LPCREG(ch, 0x004)
#define LM4_LPC_ST_TOH (1 << 0) /* TO Host bit */
#define LM4_LPC_ST_CMD (1 << 3) /* Last from-host byte was command */
#define LM4_LPC_ST_PRESENT (1 << 8)
#define LM4_LPC_ST_SCI (1 << 9)
#define LM4_LPC_ST_SMI (1 << 10)
#define LM4_LPC_ST_BUSY (1 << 12)
#define LM4_LPC_ADR(ch) LM4LPCREG(ch, 0x008)
#define LM4_LPC_POOL_BYTES 1024 /* Size of LPCPOOL in bytes */
#define LM4_LPC_LPCPOOL ((volatile unsigned char *)0x40080400)

View File

@@ -50,15 +50,14 @@
#define EC_LPC_ADDR_OLD_PARAM 0x880
#define EC_OLD_PARAM_SIZE 0x080 /* Size of param area in bytes */
/* EC command register bit functions */
#define EC_LPC_CMDR_DATA (1 << 0)
#define EC_LPC_CMDR_PENDING (1 << 1)
#define EC_LPC_CMDR_BUSY (1 << 2)
#define EC_LPC_CMDR_CMD (1 << 3)
#define EC_LPC_CMDR_ACPI_BRST (1 << 4)
#define EC_LPC_CMDR_SCI (1 << 5)
#define EC_LPC_CMDR_SMI (1 << 6)
#define EC_LPC_CMDR_DATA (1 << 0) /* Data ready for host to read */
#define EC_LPC_CMDR_PENDING (1 << 1) /* Write pending to EC */
#define EC_LPC_CMDR_BUSY (1 << 2) /* EC is busy processing a command */
#define EC_LPC_CMDR_CMD (1 << 3) /* Last host write was a command */
#define EC_LPC_CMDR_ACPI_BRST (1 << 4) /* Burst mode (not used) */
#define EC_LPC_CMDR_SCI (1 << 5) /* SCI event is pending */
#define EC_LPC_CMDR_SMI (1 << 6) /* SMI event is pending */
#define EC_LPC_ADDR_MEMMAP 0x900
#define EC_MEMMAP_SIZE 255 /* ACPI IO buffer max is 255 bytes */
@@ -806,6 +805,14 @@ struct ec_response_temp_sensor_get_info {
uint8_t sensor_type;
} __packed;
/*****************************************************************************/
/*
* Note: host commands 0x80 - 0x87 are reserved to avoid conflict with ACPI
* commands accidentally sent to the wrong interface. See the ACPI section
* below.
*/
/*****************************************************************************/
/* Host event commands */
@@ -940,23 +947,75 @@ struct ec_params_reboot_ec {
/*****************************************************************************/
/*
* Special commands
* ACPI commands
*
* These do not follow the normal rules for commands. See each command for
* details.
* These are valid ONLY on the ACPI command/data port.
*/
/*
* ACPI Read Embedded Controller
*
* This reads from ACPI memory space on the EC (EC_ACPI_MEM_*).
*
* Use the following sequence:
*
* - Write EC_CMD_ACPI_READ to EC_LPC_ADDR_ACPI_CMD
* - Wait for EC_LPC_CMDR_PENDING bit to clear
* - Write address to EC_LPC_ADDR_ACPI_DATA
* - Wait for EC_LPC_CMDR_DATA bit to set
* - Read value from EC_LPC_ADDR_ACPI_DATA
*/
#define EC_CMD_ACPI_READ 0x80
/*
* ACPI Write Embedded Controller
*
* This reads from ACPI memory space on the EC (EC_ACPI_MEM_*).
*
* Use the following sequence:
*
* - Write EC_CMD_ACPI_WRITE to EC_LPC_ADDR_ACPI_CMD
* - Wait for EC_LPC_CMDR_PENDING bit to clear
* - Write address to EC_LPC_ADDR_ACPI_DATA
* - Wait for EC_LPC_CMDR_PENDING bit to clear
* - Write value to EC_LPC_ADDR_ACPI_DATA
*/
#define EC_CMD_ACPI_WRITE 0x81
/*
* ACPI Query Embedded Controller
*
* This clears the lowest-order bit in the currently pending host events, and
* sets the result code to the 1-based index of the bit (event 0x00000001 = 1,
* event 0x80000000 = 32), or 0 if no event was pending.
*
* This command is valid ONLY on port 62/66.
*/
#define EC_CMD_ACPI_QUERY_EVENT 0x84
/* Valid addresses in ACPI memory space, for read/write commands */
/* Memory space version; set to EC_ACPI_MEM_VERSION_CURRENT */
#define EC_ACPI_MEM_VERSION 0x00
/*
* Test location; writing value here updates test compliment byte to (0xff -
* value).
*/
#define EC_ACPI_MEM_TEST 0x01
/* Test compliment; writes here are ignored. */
#define EC_ACPI_MEM_TEST_COMPLIMENT 0x02
/* Keyboard backlight brightness percent (0 - 100) */
#define EC_ACPI_MEM_KEYBOARD_BACKLIGHT 0x03
/* Current version of ACPI memory address space */
#define EC_ACPI_MEM_VERSION_CURRENT 1
/*****************************************************************************/
/*
* Special commands
*
* These do not follow the normal rules for commands. See each command for
* details.
*/
/*
* Reboot NOW
*