426 lines
11 KiB
C
426 lines
11 KiB
C
/* Copyright 2013 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.
|
|
*/
|
|
|
|
#include "acpi.h"
|
|
#include "battery.h"
|
|
#include "common.h"
|
|
#include "console.h"
|
|
#include "dptf.h"
|
|
#include "gpio.h"
|
|
#include "hooks.h"
|
|
#include "host_command.h"
|
|
#include "keyboard_backlight.h"
|
|
#include "lpc.h"
|
|
#include "ec_commands.h"
|
|
#include "tablet_mode.h"
|
|
#include "pwm.h"
|
|
#include "timer.h"
|
|
#include "usb_charge.h"
|
|
#include "util.h"
|
|
|
|
/* Console output macros */
|
|
#define CPUTS(outstr) cputs(CC_LPC, outstr)
|
|
#define CPRINTF(format, args...) cprintf(CC_LPC, format, ## args)
|
|
#define CPRINTS(format, args...) cprints(CC_LPC, format, ## args)
|
|
|
|
/* Last received ACPI command */
|
|
static uint8_t __bss_slow acpi_cmd;
|
|
/* First byte of data after ACPI command */
|
|
static uint8_t __bss_slow acpi_addr;
|
|
/* Number of data writes after command */
|
|
static int __bss_slow acpi_data_count;
|
|
/* Test byte in ACPI memory space */
|
|
static uint8_t __bss_slow acpi_mem_test;
|
|
|
|
#ifdef CONFIG_DPTF
|
|
static int __bss_slow dptf_temp_sensor_id; /* last sensor ID written */
|
|
static int __bss_slow dptf_temp_threshold; /* last threshold written */
|
|
|
|
/*
|
|
* Current DPTF profile number.
|
|
* This is by default initialized to 1 if multi-profile DPTF is not supported.
|
|
* If multi-profile DPTF is supported, this is by default initialized to 2 under
|
|
* the assumption that profile #2 corresponds to lower thresholds and is a safer
|
|
* profile to use until board or some EC driver sets the appropriate profile for
|
|
* device mode.
|
|
*/
|
|
static int current_dptf_profile = DPTF_PROFILE_DEFAULT;
|
|
|
|
#endif
|
|
|
|
#ifdef CONFIG_USB_PORT_POWER_DUMB
|
|
extern const int usb_port_enable[USB_PORT_COUNT];
|
|
#endif
|
|
|
|
/*
|
|
* Keep a read cache of four bytes when burst mode is enabled, which is the
|
|
* size of the largest non-string memmap data type.
|
|
*/
|
|
#define ACPI_READ_CACHE_SIZE 4
|
|
|
|
/* Start address that indicates read cache is flushed. */
|
|
#define ACPI_READ_CACHE_FLUSHED (EC_ACPI_MEM_MAPPED_BEGIN - 1)
|
|
|
|
/* Calculate size of valid cache based upon end of memmap data. */
|
|
#define ACPI_VALID_CACHE_SIZE(addr) (MIN( \
|
|
EC_ACPI_MEM_MAPPED_SIZE + EC_ACPI_MEM_MAPPED_BEGIN - (addr), \
|
|
ACPI_READ_CACHE_SIZE))
|
|
|
|
/*
|
|
* In burst mode, read the requested memmap data and the data immediately
|
|
* following it into a cache. For future reads in burst mode, try to grab
|
|
* data from the cache. This ensures the continuity of multi-byte reads,
|
|
* which is important when dealing with data types > 8 bits.
|
|
*/
|
|
static struct {
|
|
int enabled;
|
|
uint8_t start_addr;
|
|
uint8_t data[ACPI_READ_CACHE_SIZE];
|
|
} acpi_read_cache;
|
|
|
|
/*
|
|
* Deferred function to ensure that ACPI burst mode doesn't remain enabled
|
|
* indefinitely.
|
|
*/
|
|
static void acpi_disable_burst_deferred(void)
|
|
{
|
|
acpi_read_cache.enabled = 0;
|
|
lpc_clear_acpi_status_mask(EC_LPC_STATUS_BURST_MODE);
|
|
CPUTS("ACPI missed burst disable?");
|
|
}
|
|
DECLARE_DEFERRED(acpi_disable_burst_deferred);
|
|
|
|
#ifdef CONFIG_DPTF
|
|
|
|
static int acpi_dptf_is_profile_valid(int n)
|
|
{
|
|
#ifdef CONFIG_DPTF_MULTI_PROFILE
|
|
if ((n < DPTF_PROFILE_VALID_FIRST) || (n > DPTF_PROFILE_VALID_LAST))
|
|
return EC_ERROR_INVAL;
|
|
#else
|
|
if (n != DPTF_PROFILE_DEFAULT)
|
|
return EC_ERROR_INVAL;
|
|
#endif
|
|
|
|
return EC_SUCCESS;
|
|
}
|
|
|
|
int acpi_dptf_set_profile_num(int n)
|
|
{
|
|
int ret = acpi_dptf_is_profile_valid(n);
|
|
|
|
if (ret == EC_SUCCESS) {
|
|
current_dptf_profile = n;
|
|
if (IS_ENABLED(CONFIG_DPTF_MULTI_PROFILE) &&
|
|
IS_ENABLED(CONFIG_HOSTCMD_EVENTS)) {
|
|
/* Notify kernel to update DPTF profile */
|
|
host_set_single_event(EC_HOST_EVENT_MODE_CHANGE);
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
int acpi_dptf_get_profile_num(void)
|
|
{
|
|
return current_dptf_profile;
|
|
}
|
|
|
|
#endif
|
|
|
|
/* Read memmapped data, returns read data or 0xff on error. */
|
|
static int acpi_read(uint8_t addr)
|
|
{
|
|
uint8_t *memmap_addr = (uint8_t *)(lpc_get_memmap_range() + addr -
|
|
EC_ACPI_MEM_MAPPED_BEGIN);
|
|
|
|
/* Check for out-of-range read. */
|
|
if (addr < EC_ACPI_MEM_MAPPED_BEGIN ||
|
|
addr >= EC_ACPI_MEM_MAPPED_BEGIN + EC_ACPI_MEM_MAPPED_SIZE) {
|
|
CPRINTS("ACPI read 0x%02x (ignored)",
|
|
acpi_addr);
|
|
return 0xff;
|
|
}
|
|
|
|
/* Read from cache if enabled (burst mode). */
|
|
if (acpi_read_cache.enabled) {
|
|
/* Fetch to cache on miss. */
|
|
if (acpi_read_cache.start_addr == ACPI_READ_CACHE_FLUSHED ||
|
|
acpi_read_cache.start_addr > addr ||
|
|
addr - acpi_read_cache.start_addr >=
|
|
ACPI_READ_CACHE_SIZE) {
|
|
memcpy(acpi_read_cache.data,
|
|
memmap_addr,
|
|
ACPI_VALID_CACHE_SIZE(addr));
|
|
acpi_read_cache.start_addr = addr;
|
|
}
|
|
/* Return data from cache. */
|
|
return acpi_read_cache.data[addr - acpi_read_cache.start_addr];
|
|
} else {
|
|
/* Read directly from memmap data. */
|
|
return *memmap_addr;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* This handles AP writes to the EC via the ACPI I/O port. There are only a few
|
|
* ACPI commands (EC_CMD_ACPI_*), but they are all handled here.
|
|
*/
|
|
int acpi_ap_to_ec(int is_cmd, uint8_t value, uint8_t *resultptr)
|
|
{
|
|
int data = 0;
|
|
int retval = 0;
|
|
int result = 0xff; /* value for bogus read */
|
|
|
|
/* Read command/data; this clears the FRMH status bit. */
|
|
if (is_cmd) {
|
|
acpi_cmd = value;
|
|
acpi_data_count = 0;
|
|
} else {
|
|
data = value;
|
|
/*
|
|
* 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 */
|
|
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_KEYBOARD_BACKLIGHT
|
|
case EC_ACPI_MEM_KEYBOARD_BACKLIGHT:
|
|
result = kblight_get();
|
|
break;
|
|
#endif
|
|
#ifdef CONFIG_FANS
|
|
case EC_ACPI_MEM_FAN_DUTY:
|
|
result = dptf_get_fan_duty_target();
|
|
break;
|
|
#endif
|
|
#ifdef CONFIG_DPTF
|
|
case EC_ACPI_MEM_TEMP_ID:
|
|
result = dptf_query_next_sensor_event();
|
|
break;
|
|
#endif
|
|
#ifdef CONFIG_CHARGER
|
|
case EC_ACPI_MEM_CHARGING_LIMIT:
|
|
result = dptf_get_charging_current_limit();
|
|
if (result >= 0)
|
|
result /= EC_ACPI_MEM_CHARGING_LIMIT_STEP_MA;
|
|
else
|
|
result = EC_ACPI_MEM_CHARGING_LIMIT_DISABLED;
|
|
break;
|
|
#endif
|
|
|
|
case EC_ACPI_MEM_DEVICE_ORIENTATION:
|
|
result = 0;
|
|
|
|
#ifdef CONFIG_TABLET_MODE
|
|
result = tablet_get_mode() << EC_ACPI_MEM_TBMD_SHIFT;
|
|
#endif
|
|
|
|
#ifdef CONFIG_DPTF
|
|
result |= (acpi_dptf_get_profile_num() &
|
|
EC_ACPI_MEM_DDPN_MASK)
|
|
<< EC_ACPI_MEM_DDPN_SHIFT;
|
|
#endif
|
|
break;
|
|
|
|
case EC_ACPI_MEM_DEVICE_FEATURES0:
|
|
case EC_ACPI_MEM_DEVICE_FEATURES1:
|
|
case EC_ACPI_MEM_DEVICE_FEATURES2:
|
|
case EC_ACPI_MEM_DEVICE_FEATURES3: {
|
|
int off = acpi_addr - EC_ACPI_MEM_DEVICE_FEATURES0;
|
|
uint32_t val = get_feature_flags0();
|
|
|
|
/* Flush EC_FEATURE_LIMITED bit. Having it reset to 0
|
|
* means that FEATURES[0-3] are supported in the first
|
|
* place, and the other bits are valid.
|
|
*/
|
|
val &= ~1;
|
|
|
|
result = val >> (8 * off);
|
|
break;
|
|
}
|
|
case EC_ACPI_MEM_DEVICE_FEATURES4:
|
|
case EC_ACPI_MEM_DEVICE_FEATURES5:
|
|
case EC_ACPI_MEM_DEVICE_FEATURES6:
|
|
case EC_ACPI_MEM_DEVICE_FEATURES7: {
|
|
int off = acpi_addr - EC_ACPI_MEM_DEVICE_FEATURES4;
|
|
uint32_t val = get_feature_flags1();
|
|
|
|
result = val >> (8 * off);
|
|
break;
|
|
}
|
|
|
|
#ifdef CONFIG_USB_PORT_POWER_DUMB
|
|
case EC_ACPI_MEM_USB_PORT_POWER: {
|
|
int i;
|
|
const int port_count = MIN(8, USB_PORT_COUNT);
|
|
|
|
/*
|
|
* Convert each USB port power GPIO signal to a bit
|
|
* field with max size 8 bits. USB port ID (index) 0 is
|
|
* the least significant bit.
|
|
*/
|
|
result = 0;
|
|
for (i = 0; i < port_count; ++i) {
|
|
if (gpio_get_level(usb_port_enable[i]) != 0)
|
|
result |= 1 << i;
|
|
}
|
|
break;
|
|
}
|
|
#endif
|
|
|
|
default:
|
|
result = acpi_read(acpi_addr);
|
|
break;
|
|
}
|
|
|
|
/* Send the result byte */
|
|
*resultptr = result;
|
|
retval = 1;
|
|
|
|
} else if (acpi_cmd == EC_CMD_ACPI_WRITE && acpi_data_count == 2) {
|
|
/* ACPI write cmd + addr + data */
|
|
switch (acpi_addr) {
|
|
case EC_ACPI_MEM_TEST:
|
|
acpi_mem_test = data;
|
|
break;
|
|
#ifdef CONFIG_BATTERY_V2
|
|
case EC_ACPI_MEM_BATTERY_INDEX:
|
|
CPRINTS("ACPI battery %d", data);
|
|
battery_memmap_set_index(data);
|
|
break;
|
|
#endif
|
|
#ifdef CONFIG_KEYBOARD_BACKLIGHT
|
|
case EC_ACPI_MEM_KEYBOARD_BACKLIGHT:
|
|
/*
|
|
* Debug output with CR not newline, because the host
|
|
* does a lot of keyboard backlights and it scrolls the
|
|
* debug console.
|
|
*/
|
|
CPRINTF("\r[%T ACPI kblight %d]", data);
|
|
kblight_set(data);
|
|
break;
|
|
#endif
|
|
#ifdef CONFIG_FANS
|
|
case EC_ACPI_MEM_FAN_DUTY:
|
|
dptf_set_fan_duty_target(data);
|
|
break;
|
|
#endif
|
|
#ifdef CONFIG_DPTF
|
|
case EC_ACPI_MEM_TEMP_ID:
|
|
dptf_temp_sensor_id = data;
|
|
break;
|
|
case EC_ACPI_MEM_TEMP_THRESHOLD:
|
|
dptf_temp_threshold = data + EC_TEMP_SENSOR_OFFSET;
|
|
break;
|
|
case EC_ACPI_MEM_TEMP_COMMIT:
|
|
{
|
|
int idx = data & EC_ACPI_MEM_TEMP_COMMIT_SELECT_MASK;
|
|
int enable = data & EC_ACPI_MEM_TEMP_COMMIT_ENABLE_MASK;
|
|
dptf_set_temp_threshold(dptf_temp_sensor_id,
|
|
dptf_temp_threshold,
|
|
idx, enable);
|
|
break;
|
|
}
|
|
#endif
|
|
#ifdef CONFIG_CHARGER
|
|
case EC_ACPI_MEM_CHARGING_LIMIT:
|
|
if (data == EC_ACPI_MEM_CHARGING_LIMIT_DISABLED) {
|
|
dptf_set_charging_current_limit(-1);
|
|
} else {
|
|
data *= EC_ACPI_MEM_CHARGING_LIMIT_STEP_MA;
|
|
dptf_set_charging_current_limit(data);
|
|
}
|
|
break;
|
|
#endif
|
|
|
|
#ifdef CONFIG_USB_PORT_POWER_DUMB
|
|
case EC_ACPI_MEM_USB_PORT_POWER: {
|
|
int i;
|
|
int mode_field = data;
|
|
const int port_count = MIN(8, USB_PORT_COUNT);
|
|
|
|
/*
|
|
* Read the port power bit field (with max size 8 bits)
|
|
* and set the charge mode of each USB port accordingly.
|
|
* USB port ID 0 is the least significant bit.
|
|
*/
|
|
for (i = 0; i < port_count; ++i) {
|
|
int mode = USB_CHARGE_MODE_DISABLED;
|
|
|
|
if (mode_field & 1)
|
|
mode = USB_CHARGE_MODE_ENABLED;
|
|
|
|
if (usb_charge_set_mode(i, mode,
|
|
USB_ALLOW_SUSPEND_CHARGE)) {
|
|
CPRINTS("ERROR: could not set charge "
|
|
"mode of USB port p%d to %d",
|
|
i, mode);
|
|
}
|
|
mode_field >>= 1;
|
|
}
|
|
break;
|
|
}
|
|
#endif
|
|
|
|
default:
|
|
CPRINTS("ACPI write 0x%02x = 0x%02x (ignored)",
|
|
acpi_addr, data);
|
|
break;
|
|
}
|
|
} else if (acpi_cmd == EC_CMD_ACPI_QUERY_EVENT && !acpi_data_count) {
|
|
/* Clear and return the lowest host event */
|
|
int evt_index = lpc_get_next_host_event();
|
|
CPRINTS("ACPI query = %d", evt_index);
|
|
*resultptr = evt_index;
|
|
retval = 1;
|
|
} else if (acpi_cmd == EC_CMD_ACPI_BURST_ENABLE && !acpi_data_count) {
|
|
/*
|
|
* TODO: The kernel only enables BURST when doing multi-byte
|
|
* value reads over the ACPI port. We don't do such reads
|
|
* when our memmap data can be accessed directly over LPC,
|
|
* so on LM4, for example, this is dead code. We might want
|
|
* to add a config to skip this code for certain chips.
|
|
*/
|
|
acpi_read_cache.enabled = 1;
|
|
acpi_read_cache.start_addr = ACPI_READ_CACHE_FLUSHED;
|
|
|
|
/* Enter burst mode */
|
|
lpc_set_acpi_status_mask(EC_LPC_STATUS_BURST_MODE);
|
|
|
|
/*
|
|
* Disable from deferred function in case burst mode is enabled
|
|
* for an extremely long time (ex. kernel bug / crash).
|
|
*/
|
|
hook_call_deferred(&acpi_disable_burst_deferred_data, 1*SECOND);
|
|
|
|
/* ACPI 5.0-12.3.3: Burst ACK */
|
|
*resultptr = 0x90;
|
|
retval = 1;
|
|
} else if (acpi_cmd == EC_CMD_ACPI_BURST_DISABLE && !acpi_data_count) {
|
|
acpi_read_cache.enabled = 0;
|
|
|
|
/* Leave burst mode */
|
|
hook_call_deferred(&acpi_disable_burst_deferred_data, -1);
|
|
lpc_clear_acpi_status_mask(EC_LPC_STATUS_BURST_MODE);
|
|
}
|
|
|
|
return retval;
|
|
}
|