From 2da2e72dd31c592fd3946b03eb927e8616a62808 Mon Sep 17 00:00:00 2001 From: Randall Spangler Date: Mon, 30 Jul 2012 13:02:29 -0700 Subject: 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 Reviewed-on: https://gerrit.chromium.org/gerrit/28714 Reviewed-by: Duncan Laurie --- chip/lm4/lpc.c | 147 ++++++++++++++++++++++++++++++++++---------------- chip/lm4/registers.h | 6 +++ include/ec_commands.h | 85 ++++++++++++++++++++++++----- 3 files changed, 180 insertions(+), 58 deletions(-) diff --git a/chip/lm4/lpc.c b/chip/lm4/lpc.c index 91ce43a198..c7e1d988cf 100644 --- a/chip/lm4/lpc.c +++ b/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, diff --git a/chip/lm4/registers.h b/chip/lm4/registers.h index 4cef5af088..6578e104e7 100644 --- a/chip/lm4/registers.h +++ b/chip/lm4/registers.h @@ -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) diff --git a/include/ec_commands.h b/include/ec_commands.h index f6680ca8b0..c94fed5c96 100644 --- a/include/ec_commands.h +++ b/include/ec_commands.h @@ -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 * -- cgit v1.2.1