blob: b8dfe47afea895af54a07516e4200c57c5da681e [file] [log] [blame]
// Copyright 2016 The Fuchsia 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 "ec.h"
#include <threads.h>
#include <acpica/acpi.h>
#include <hw/inout.h>
#include <magenta/syscalls.h>
#include <magenta/types.h>
#include <mxio/debug.h>
#define MXDEBUG 0
/* EC commands */
#define EC_CMD_QUERY 0x84
/* EC status register bits */
#define EC_SC_SCI_EVT (1 << 5)
#define EC_SC_IBF (1 << 1)
#define EC_SC_OBF (1 << 0)
static mx_handle_t pending_sci_evt;
static struct {
ACPI_HANDLE handle;
uint16_t data_port;
uint16_t cmd_port;
} ec_info;
static int acpi_ec_thread(void* arg) {
while (1) {
mx_status_t mx_status = mx_object_wait_one(pending_sci_evt,
MX_EVENT_SIGNALED,
MX_TIME_INFINITE,
NULL);
if (mx_status != MX_OK) {
break;
}
mx_object_signal(pending_sci_evt, MX_EVENT_SIGNALED, 0);
UINT32 global_lock;
while (AcpiAcquireGlobalLock(0xFFFF, &global_lock) != AE_OK)
;
uint8_t status;
do {
status = inp(ec_info.cmd_port);
/* Read the status out of the command/status port */
if (!(status & EC_SC_SCI_EVT)) {
break;
}
/* Query EC command */
outp(ec_info.cmd_port, EC_CMD_QUERY);
/* Wait for EC to read the command */
while (!((status = inp(ec_info.cmd_port)) & EC_SC_IBF))
;
/* Wait for EC to respond */
while (!((status = inp(ec_info.cmd_port)) & EC_SC_OBF))
;
while ((status = inp(ec_info.cmd_port)) & EC_SC_OBF) {
/* Read until the output buffer is empty */
uint8_t event_code = inp(ec_info.data_port);
char method[5] = {0};
snprintf(method, sizeof(method), "_Q%02x", event_code);
xprintf("Invoking method %s\n", method);
AcpiEvaluateObject(ec_info.handle, method, NULL, NULL);
xprintf("Invoked method %s\n", method);
}
} while (status & EC_SC_SCI_EVT);
AcpiReleaseGlobalLock(global_lock);
}
printf("acpi ec thread terminated\n");
return 0;
}
static uint32_t raw_ec_event_gpe_handler(ACPI_HANDLE gpe_dev, uint32_t gpe_num, void* ctx) {
mx_object_signal(pending_sci_evt, 0, MX_EVENT_SIGNALED);
return ACPI_REENABLE_GPE;
}
static ACPI_STATUS get_ec_handle(ACPI_HANDLE, UINT32, void*, void**);
static ACPI_STATUS get_ec_gpe_info(ACPI_HANDLE, ACPI_HANDLE*, UINT32*);
static ACPI_STATUS get_ec_ports(ACPI_HANDLE, uint16_t*, uint16_t*);
void ec_init(void) {
mx_status_t err = mx_event_create(0, &pending_sci_evt);
if (err < 0) {
xprintf("Failed to create event: %d\n", err);
return;
}
/* PNP0C09 devices are defined in section 12.11 of ACPI v6.1 */
ACPI_STATUS status = AcpiGetDevices(
(char*)"PNP0C09",
get_ec_handle,
&ec_info.handle,
NULL);
if (status != AE_OK || ec_info.handle == NULL) {
xprintf("Failed to find EC: %d\n", status);
return;
}
ACPI_HANDLE gpe_block = NULL;
UINT32 gpe = 0;
status = get_ec_gpe_info(ec_info.handle, &gpe_block, &gpe);
if (status != AE_OK) {
xprintf("Failed to decode EC GPE info: %d\n", status);
return;
}
status = get_ec_ports(
ec_info.handle, &ec_info.data_port, &ec_info.cmd_port);
if (status != AE_OK) {
xprintf("Failed to decode EC comm info: %d\n", status);
return;
}
/* Setup GPE handling */
status = AcpiInstallGpeHandler(
gpe_block, gpe, ACPI_GPE_EDGE_TRIGGERED,
raw_ec_event_gpe_handler, NULL);
if (status != AE_OK) {
xprintf("Failed to install GPE %d: %x\n", gpe, status);
goto bailout;
}
status = AcpiEnableGpe(gpe_block, gpe);
if (status != AE_OK) {
xprintf("Failed to enable GPE %d: %x\n", gpe, status);
goto bailout;
}
thrd_t thread;
int ret = thrd_create(&thread, acpi_ec_thread, NULL);
if (ret != thrd_success) {
xprintf("Failed to create ACPI EC thread\n");
goto bailout;
}
thrd_detach(thread);
return;
bailout:
AcpiDisableGpe(gpe_block, gpe);
AcpiRemoveGpeHandler(gpe_block, gpe, raw_ec_event_gpe_handler);
}
static ACPI_STATUS get_ec_handle(
ACPI_HANDLE object,
UINT32 nesting_level,
void* context,
void** ret) {
*(ACPI_HANDLE*)context = object;
return AE_OK;
}
static ACPI_STATUS get_ec_gpe_info(
ACPI_HANDLE ec_handle, ACPI_HANDLE* gpe_block, UINT32* gpe) {
ACPI_BUFFER buffer = {
.Length = ACPI_ALLOCATE_BUFFER,
.Pointer = NULL,
};
ACPI_STATUS status = AcpiEvaluateObject(
ec_handle, (char*)"_GPE", NULL, &buffer);
if (status != AE_OK) {
return status;
}
/* According to section 12.11 of ACPI v6.1, a _GPE object on this device
* evaluates to either an integer specifying bit in the GPEx_STS blocks
* to use, or a package specifying which GPE block and which bit inside
* that block to use. */
ACPI_OBJECT* gpe_obj = buffer.Pointer;
if (gpe_obj->Type == ACPI_TYPE_INTEGER) {
*gpe_block = NULL;
*gpe = gpe_obj->Integer.Value;
} else if (gpe_obj->Type == ACPI_TYPE_PACKAGE) {
if (gpe_obj->Package.Count != 2) {
goto bailout;
}
ACPI_OBJECT* block_obj = &gpe_obj->Package.Elements[0];
ACPI_OBJECT* gpe_num_obj = &gpe_obj->Package.Elements[1];
if (block_obj->Type != ACPI_TYPE_LOCAL_REFERENCE) {
goto bailout;
}
if (gpe_num_obj->Type != ACPI_TYPE_INTEGER) {
goto bailout;
}
*gpe_block = block_obj->Reference.Handle;
*gpe = gpe_num_obj->Integer.Value;
} else {
goto bailout;
}
ACPI_FREE(buffer.Pointer);
return AE_OK;
bailout:
xprintf("Failed to intepret EC GPE number");
ACPI_FREE(buffer.Pointer);
return AE_BAD_DATA;
}
struct ec_ports_callback_ctx {
uint16_t* data_port;
uint16_t* cmd_port;
unsigned int resource_num;
};
static ACPI_STATUS get_ec_ports_callback(
ACPI_RESOURCE* Resource, void* Context) {
struct ec_ports_callback_ctx* ctx = Context;
if (Resource->Type == ACPI_RESOURCE_TYPE_END_TAG) {
return AE_OK;
}
/* The spec says there will be at most 3 resources */
if (ctx->resource_num >= 3) {
return AE_BAD_DATA;
}
/* The third resource only exists on HW-Reduced platforms, which we don't
* support at the moment. */
if (ctx->resource_num == 2) {
xprintf("RESOURCE TYPE %d\n", Resource->Type);
return AE_NOT_IMPLEMENTED;
}
/* The two resources we're expecting are both address regions. First the
* data one, then the command one. We assume they're single IO ports. */
if (Resource->Type != ACPI_RESOURCE_TYPE_IO) {
return AE_SUPPORT;
}
if (Resource->Data.Io.Maximum != Resource->Data.Io.Minimum) {
return AE_SUPPORT;
}
uint16_t port = Resource->Data.Io.Minimum;
if (ctx->resource_num == 0) {
*ctx->data_port = port;
} else {
*ctx->cmd_port = port;
}
ctx->resource_num++;
return AE_OK;
}
static ACPI_STATUS get_ec_ports(
ACPI_HANDLE ec_handle, uint16_t* data_port, uint16_t* cmd_port) {
struct ec_ports_callback_ctx ctx = {
.data_port = data_port,
.cmd_port = cmd_port,
.resource_num = 0,
};
return AcpiWalkResources(ec_handle, (char*)"_CRS", get_ec_ports_callback, &ctx);
}