mirror of
https://github.com/Telecominfraproject/OpenCellular.git
synced 2025-12-30 10:31:02 +00:00
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:
147
chip/lm4/lpc.c
147
chip/lm4/lpc.c
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user