#include <sys/file.h>
#include <sys/errno.h>
#include <sys/conf.h>
#include <sys/modctl.h>
#include <sys/open.h>
#include <sys/stat.h>
#include <sys/ddi.h>
#include <sys/sunddi.h>
#include <sys/note.h>
#include <sys/atomic.h>
#include <sys/acpi/acpi.h>
#include <sys/acpica.h>
#define EC_OBF (0x01)
#define EC_IBF (0x02)
#define EC_DRC (0x08)
#define EC_BME (0x10)
#define EC_SCI (0x20)
#define EC_SMI (0x40)
#define EC_RD (0x80)
#define EC_WR (0x81)
#define EC_BE (0x82)
#define EC_BD (0x83)
#define EC_QR (0x84)
#define IO_PORT_DES (0x47)
static struct ec_softstate {
uint8_t ec_ok;
uint16_t ec_base;
uint16_t ec_sc;
ACPI_HANDLE ec_dev_hdl;
ACPI_HANDLE ec_gpe_hdl;
ACPI_INTEGER ec_gpe_bit;
kmutex_t ec_mutex;
} ec;
typedef struct io_port_des {
uint8_t type;
uint8_t decode;
uint8_t min_base_lo;
uint8_t min_base_hi;
uint8_t max_base_lo;
uint8_t max_base_hi;
uint8_t align;
uint8_t len;
} io_port_des_t;
int ec_ignore_ecdt = 0;
int ibf_clear_timeout = 100000;
int obf_set_timeout = 100000;
static int
ec_wait_ibf_clear(int sc_addr)
{
int cnt;
cnt = ibf_clear_timeout;
while (inb(sc_addr) & EC_IBF) {
if (cnt-- <= 0)
return (-1);
drv_usecwait(10);
}
return (0);
}
static int
ec_wait_obf_set(int sc_addr)
{
int cnt;
cnt = obf_set_timeout;
while (!(inb(sc_addr) & EC_OBF)) {
if (cnt-- <= 0)
return (-1);
drv_usecwait(10);
}
return (0);
}
static int
ec_rd(int addr)
{
int cnt, rv;
uint8_t sc;
mutex_enter(&ec.ec_mutex);
sc = inb(ec.ec_sc);
#ifdef DEBUG
if (sc & EC_IBF) {
cmn_err(CE_NOTE, "!ec_rd: IBF already set");
}
if (sc & EC_OBF) {
cmn_err(CE_NOTE, "!ec_rd: OBF already set");
}
#endif
outb(ec.ec_sc, EC_RD);
if (ec_wait_ibf_clear(ec.ec_sc) < 0) {
cmn_err(CE_NOTE, "!ec_rd:1: timed-out waiting "
"for IBF to clear");
mutex_exit(&ec.ec_mutex);
return (-1);
}
outb(ec.ec_base, addr);
if (ec_wait_ibf_clear(ec.ec_sc) < 0) {
cmn_err(CE_NOTE, "!ec_rd:2: timed-out waiting "
"for IBF to clear");
mutex_exit(&ec.ec_mutex);
return (-1);
}
if (ec_wait_obf_set(ec.ec_sc) < 0) {
cmn_err(CE_NOTE, "!ec_rd:1: timed-out waiting "
"for OBF to set");
mutex_exit(&ec.ec_mutex);
return (-1);
}
rv = inb(ec.ec_base);
mutex_exit(&ec.ec_mutex);
return (rv);
}
static int
ec_wr(int addr, uint8_t val)
{
int cnt;
uint8_t sc;
mutex_enter(&ec.ec_mutex);
sc = inb(ec.ec_sc);
#ifdef DEBUG
if (sc & EC_IBF) {
cmn_err(CE_NOTE, "!ec_wr: IBF already set");
}
if (sc & EC_OBF) {
cmn_err(CE_NOTE, "!ec_wr: OBF already set");
}
#endif
outb(ec.ec_sc, EC_WR);
if (ec_wait_ibf_clear(ec.ec_sc) < 0) {
cmn_err(CE_NOTE, "!ec_wr:1: timed-out waiting "
"for IBF to clear");
mutex_exit(&ec.ec_mutex);
return (-1);
}
outb(ec.ec_base, addr);
if (ec_wait_ibf_clear(ec.ec_sc) < 0) {
cmn_err(CE_NOTE, "!ec_wr:2: timed-out waiting "
"for IBF to clear");
mutex_exit(&ec.ec_mutex);
return (-1);
}
outb(ec.ec_base, val);
if (ec_wait_ibf_clear(ec.ec_sc) < 0) {
cmn_err(CE_NOTE, "!ec_wr:3: timed-out waiting "
"for IBF to clear");
mutex_exit(&ec.ec_mutex);
return (-1);
}
mutex_exit(&ec.ec_mutex);
return (0);
}
static int
ec_query(void)
{
int cnt, rv;
uint8_t sc;
mutex_enter(&ec.ec_mutex);
outb(ec.ec_sc, EC_QR);
if (ec_wait_ibf_clear(ec.ec_sc) < 0) {
cmn_err(CE_NOTE, "!ec_query:1: timed-out waiting "
"for IBF to clear");
mutex_exit(&ec.ec_mutex);
return (-1);
}
if (ec_wait_obf_set(ec.ec_sc) < 0) {
cmn_err(CE_NOTE, "!ec_query:1: timed-out waiting "
"for OBF to set");
mutex_exit(&ec.ec_mutex);
return (-1);
}
rv = inb(ec.ec_base);
mutex_exit(&ec.ec_mutex);
return (rv);
}
static ACPI_STATUS
ec_handler(UINT32 func, ACPI_PHYSICAL_ADDRESS addr, UINT32 width,
UINT64 *val, void *context, void *regcontext)
{
_NOTE(ARGUNUSED(context, regcontext))
int i, tw, tmp;
if (ec.ec_ok == 0)
return (AE_ERROR);
if ((width % 8) != 0) {
cmn_err(CE_NOTE, "!ec_handler: invalid width %d", width);
return (AE_BAD_PARAMETER);
}
if (val == NULL) {
cmn_err(CE_NOTE, "!ec_handler: NULL value pointer");
return (AE_BAD_PARAMETER);
}
while (width > 0) {
tw = min(width, 64);
if (func == ACPI_READ)
*val = 0;
for (i = 0; i < tw; i += 8, addr++) {
switch (func) {
case ACPI_READ:
tmp = ec_rd(addr);
if (tmp < 0)
return (AE_ERROR);
*val |= ((UINT64)tmp) << i;
break;
case ACPI_WRITE:
tmp = ((*val) >> i) & 0xFF;
if (ec_wr(addr, (uint8_t)tmp) < 0)
return (AE_ERROR);
break;
default:
return (AE_ERROR);
}
}
val++;
width -= tw;
}
return (AE_OK);
}
static void
ec_gpe_callback(void *ctx)
{
_NOTE(ARGUNUSED(ctx))
char query_str[5];
int query;
if (!(inb(ec.ec_sc) & EC_SCI))
goto out;
query = ec_query();
if (query < 0)
goto out;
(void) snprintf(query_str, 5, "_Q%02X", (uint8_t)query);
(void) AcpiEvaluateObject(ec.ec_dev_hdl, query_str, NULL, NULL);
out:
AcpiFinishGpe(ec.ec_gpe_hdl, ec.ec_gpe_bit);
}
static UINT32
ec_gpe_handler(ACPI_HANDLE GpeDevice, UINT32 GpeNumber, void *ctx)
{
_NOTE(ARGUNUSED(GpeDevice))
_NOTE(ARGUNUSED(GpeNumber))
_NOTE(ARGUNUSED(ctx))
if (ec.ec_ok == 0)
return (ACPI_REENABLE_GPE);
AcpiOsExecute(OSL_GPE_HANDLER, ec_gpe_callback, NULL);
return (0);
}
static ACPI_STATUS
ec_probe_ecdt(void)
{
ACPI_TABLE_HEADER *th;
ACPI_TABLE_ECDT *ecdt;
ACPI_HANDLE dev_hdl;
ACPI_STATUS status;
status = AcpiGetTable(ACPI_SIG_ECDT, 1, &th);
#ifndef DEBUG
if (status == AE_NOT_FOUND)
return (status);
#endif
if (ACPI_FAILURE(status)) {
cmn_err(CE_NOTE, "!acpica: ECDT not found");
return (status);
}
if (ec_ignore_ecdt) {
cmn_err(CE_NOTE, "!acpica: ECDT ignored");
return (AE_NOT_FOUND);
}
ecdt = (ACPI_TABLE_ECDT *)th;
if (ecdt->Control.BitWidth != 8 ||
ecdt->Data.BitWidth != 8) {
cmn_err(CE_NOTE, "!acpica: bad ECDT I/O width");
return (AE_BAD_VALUE);
}
status = AcpiGetHandle(NULL, (char *)ecdt->Id, &dev_hdl);
if (ACPI_FAILURE(status)) {
cmn_err(CE_NOTE, "!acpica: no ECDT device handle");
return (status);
}
ec.ec_base = ecdt->Data.Address;
ec.ec_sc = ecdt->Control.Address;
ec.ec_dev_hdl = dev_hdl;
ec.ec_gpe_hdl = NULL;
ec.ec_gpe_bit = ecdt->Gpe;
ec.ec_ok = 1;
#ifdef DEBUG
cmn_err(CE_NOTE, "!acpica:ec_probe_ecdt: success");
#endif
return (0);
}
static ACPI_STATUS
ec_find(ACPI_HANDLE obj, UINT32 nest, void *context, void **rv)
{
_NOTE(ARGUNUSED(nest, rv))
*((ACPI_HANDLE *)context) = obj;
return (AE_OK);
}
static ACPI_STATUS
ec_probe_ns(void)
{
ACPI_HANDLE dev_hdl;
ACPI_BUFFER buf, crs;
ACPI_OBJECT *gpe_obj;
ACPI_HANDLE gpe_hdl;
ACPI_INTEGER gpe_bit;
ACPI_STATUS status;
int i, io_port_cnt;
uint16_t ec_sc, ec_base;
dev_hdl = NULL;
(void) AcpiGetDevices("PNP0C09", &ec_find, (void *)&dev_hdl, NULL);
if (dev_hdl == NULL) {
#ifdef DEBUG
cmn_err(CE_WARN, "!acpica:ec_probe_ns: "
"PNP0C09 not found");
#endif
return (AE_NOT_FOUND);
}
crs.Length = ACPI_ALLOCATE_BUFFER;
status = AcpiEvaluateObjectTyped(dev_hdl, "_CRS", NULL, &crs,
ACPI_TYPE_BUFFER);
if (ACPI_FAILURE(status)) {
cmn_err(CE_WARN, "!acpica:ec_probe_ns: "
"_CRS object evaluate failed");
return (status);
}
for (i = 0, io_port_cnt = 0;
i < ((ACPI_OBJECT *)crs.Pointer)->Buffer.Length; i++) {
io_port_des_t *io_port;
uint8_t *tmp;
tmp = ((ACPI_OBJECT *)crs.Pointer)->Buffer.Pointer + i;
if (*tmp != IO_PORT_DES)
continue;
io_port = (io_port_des_t *)tmp;
if (io_port_cnt == 0)
ec_base = (io_port->min_base_hi << 8) |
io_port->min_base_lo;
if (io_port_cnt == 1)
ec_sc = (io_port->min_base_hi << 8) |
io_port->min_base_lo;
io_port_cnt++;
i += 7;
}
AcpiOsFree(crs.Pointer);
if (io_port_cnt < 2) {
cmn_err(CE_WARN, "!acpica:ec_probe_ns: "
"_CRS parse failed");
return (AE_BAD_VALUE);
}
buf.Length = ACPI_ALLOCATE_BUFFER;
status = AcpiEvaluateObject(dev_hdl, "_GPE", NULL, &buf);
if (ACPI_FAILURE(status)) {
cmn_err(CE_WARN, "!acpica:ec_probe_ns: "
"_GPE object evaluate");
return (status);
}
gpe_obj = (ACPI_OBJECT *)buf.Pointer;
switch (gpe_obj->Type) {
case ACPI_TYPE_INTEGER:
gpe_hdl = NULL;
gpe_bit = gpe_obj->Integer.Value;
break;
case ACPI_TYPE_PACKAGE:
if (gpe_obj->Package.Count != 2)
goto bad_gpe;
gpe_obj = gpe_obj->Package.Elements;
if (gpe_obj[1].Type != ACPI_TYPE_INTEGER)
goto bad_gpe;
gpe_hdl = gpe_obj[0].Reference.Handle;
gpe_bit = gpe_obj[1].Integer.Value;
break;
bad_gpe:
default:
status = AE_BAD_VALUE;
break;
}
AcpiOsFree(buf.Pointer);
if (ACPI_FAILURE(status)) {
cmn_err(CE_WARN, "!acpica:ec_probe_ns: "
"_GPE parse failed");
return (status);
}
ec.ec_base = ec_base;
ec.ec_sc = ec_sc;
ec.ec_dev_hdl = dev_hdl;
ec.ec_gpe_hdl = gpe_hdl;
ec.ec_gpe_bit = gpe_bit;
ec.ec_ok = 1;
#ifdef DEBUG
cmn_err(CE_NOTE, "!acpica:ec_probe_ns: success");
#endif
return (0);
}
static void
ec_init(void)
{
ACPI_STATUS rc;
int x;
if (ec.ec_ok == 0)
return;
if (inb(ec.ec_sc) & EC_OBF) {
x = inb(ec.ec_base);
#ifdef DEBUG
cmn_err(CE_NOTE, "!EC had something: 0x%x", x);
#endif
}
rc = AcpiInstallAddressSpaceHandler(ACPI_ROOT_OBJECT,
ACPI_ADR_SPACE_EC, &ec_handler, NULL, NULL);
if (rc != AE_OK) {
cmn_err(CE_WARN, "!acpica:ec_init: "
"install AS handler, rc=0x%x", rc);
return;
}
#ifdef DEBUG
cmn_err(CE_NOTE, "!acpica:ec_init: success");
#endif
}
static void
ec_attach(void)
{
ACPI_STATUS rc;
if (ec.ec_ok == 0) {
cmn_err(CE_WARN, "!acpica:ec_attach: "
"no EC device found");
return;
}
rc = AcpiInstallGpeHandler(ec.ec_gpe_hdl, ec.ec_gpe_bit,
ACPI_GPE_EDGE_TRIGGERED, ec_gpe_handler, NULL);
if (rc != AE_OK) {
cmn_err(CE_WARN, "!acpica:ec_attach: "
"install GPE handler, rc=0x%x", rc);
goto errout;
}
rc = AcpiEnableGpe(ec.ec_gpe_hdl, ec.ec_gpe_bit);
if (rc != AE_OK) {
cmn_err(CE_WARN, "!acpica:ec_attach: "
"enable GPE handler, rc=0x%x", rc);
goto errout;
}
#ifdef DEBUG
cmn_err(CE_NOTE, "!acpica:ec_attach: success");
#endif
return;
errout:
AcpiRemoveGpeHandler(ec.ec_gpe_hdl, ec.ec_gpe_bit,
ec_gpe_handler);
}
static void
smbus_attach(void)
{
#ifdef DEBUG
ACPI_HANDLE obj;
obj = NULL;
(void) AcpiGetDevices("ACPI0001", &ec_find, (void *)&obj, NULL);
if (obj != NULL) {
cmn_err(CE_NOTE, "!acpica: found an SMBC Version 1.0");
}
obj = NULL;
(void) AcpiGetDevices("ACPI0005", &ec_find, (void *)&obj, NULL);
if (obj != NULL) {
cmn_err(CE_NOTE, "!acpica: found an SMBC Version 2.0");
}
#endif
}
void
acpica_ec_init(void)
{
ACPI_STATUS rc;
mutex_init(&ec.ec_mutex, NULL, MUTEX_DRIVER, NULL);
rc = ec_probe_ecdt();
if (ACPI_FAILURE(rc))
rc = ec_probe_ns();
if (ACPI_SUCCESS(rc)) {
ec_init();
ec_attach();
}
smbus_attach();
}