From 75a666aa952482d1c93a9ad04b94f3bb26d31441 Mon Sep 17 00:00:00 2001 From: Clemens Zeidler Date: Thu, 18 Jun 2009 18:30:06 +0000 Subject: [PATCH] Embedded Controller driver, used the BSD driver as base. Thanks to Ithamar for pointing out that I need this driver to read the acpi battery status which now works for me (driver coming soon). My laptop now really switch off on shutdown :-) git-svn-id: file:///srv/svn/repos/haiku/haiku/trunk@31102 a95241bf-73f2-0310-859d-f6bbb57e9c96 --- src/add-ons/kernel/drivers/power/Jamfile | 1 + .../power/acpi_embedded_controller/Jamfile | 11 + .../SmallResourceData.cpp | 114 +++ .../SmallResourceData.h | 61 ++ .../acpi_embedded_controller.cpp | 937 ++++++++++++++++++ .../acpi_embedded_controller.h | 314 ++++++ 6 files changed, 1438 insertions(+) create mode 100644 src/add-ons/kernel/drivers/power/acpi_embedded_controller/Jamfile create mode 100644 src/add-ons/kernel/drivers/power/acpi_embedded_controller/SmallResourceData.cpp create mode 100644 src/add-ons/kernel/drivers/power/acpi_embedded_controller/SmallResourceData.h create mode 100644 src/add-ons/kernel/drivers/power/acpi_embedded_controller/acpi_embedded_controller.cpp create mode 100644 src/add-ons/kernel/drivers/power/acpi_embedded_controller/acpi_embedded_controller.h diff --git a/src/add-ons/kernel/drivers/power/Jamfile b/src/add-ons/kernel/drivers/power/Jamfile index d01eaa904d..68c4087b0e 100644 --- a/src/add-ons/kernel/drivers/power/Jamfile +++ b/src/add-ons/kernel/drivers/power/Jamfile @@ -1,6 +1,7 @@ SubDir HAIKU_TOP src add-ons kernel drivers power ; SubInclude HAIKU_TOP src add-ons kernel drivers power acpi_button ; +SubInclude HAIKU_TOP src add-ons kernel drivers power acpi_embedded_controller ; SubInclude HAIKU_TOP src add-ons kernel drivers power acpi_lid ; SubInclude HAIKU_TOP src add-ons kernel drivers power acpi_thermal ; SubInclude HAIKU_TOP src add-ons kernel drivers power enhanced_speedstep ; diff --git a/src/add-ons/kernel/drivers/power/acpi_embedded_controller/Jamfile b/src/add-ons/kernel/drivers/power/acpi_embedded_controller/Jamfile new file mode 100644 index 0000000000..c557278417 --- /dev/null +++ b/src/add-ons/kernel/drivers/power/acpi_embedded_controller/Jamfile @@ -0,0 +1,11 @@ +SubDir HAIKU_TOP src add-ons kernel drivers power acpi_embedded_controller ; + + +UsePrivateHeaders kernel ; + +KernelAddon acpi_embedded_controller : + acpi_embedded_controller.cpp + SmallResourceData.cpp + ; + +Depends acpi_embedded_controller : acpi ; diff --git a/src/add-ons/kernel/drivers/power/acpi_embedded_controller/SmallResourceData.cpp b/src/add-ons/kernel/drivers/power/acpi_embedded_controller/SmallResourceData.cpp new file mode 100644 index 0000000000..dc6f91fa87 --- /dev/null +++ b/src/add-ons/kernel/drivers/power/acpi_embedded_controller/SmallResourceData.cpp @@ -0,0 +1,114 @@ +/* + * Copyright 2009, Haiku, Inc. All Rights Reserved. + * Distributed under the terms of the MIT License. + * + * Authors: + * Clemens Zeidler, haiku@clemens-zeidler.de + */ + +#include "SmallResourceData.h" + +#include + +//#define TRACE_SMALLRESOURCEDATA +#ifdef TRACE_SMALLRESOURCEDATA +# define TRACE(x...) dprintf("Small Resource Data: "x) +#else +# define TRACE(x...) +#endif + + +void +io_port::Print() +{ + dprintf("io_port:\n"); + int i = (deviceAddresses16Bit ? 1 : 0); + dprintf("deviceAddresses16Bit %i\n", i); + dprintf("minimumBase %i\n", minimumBase); + dprintf("maximumBase %i\n", maximumBase); + dprintf("minimumBaseAlignment %i\n", minimumBaseAlignment); + dprintf("contigiuousIOPorts %i\n", contigiuousIOPorts); +} + + +SmallResourceData::SmallResourceData(acpi_device_module_info* acpi, + acpi_device acpiCookie, char* method) +{ + acpi_data buffer; + buffer.pointer = NULL; + buffer.length = ACPI_ALLOCATE_BUFFER; + + fStatus = acpi->evaluate_method(acpiCookie, method, NULL, &buffer); + + if (fStatus != B_OK) + return; + + fBuffer = (acpi_object_type*)buffer.pointer; + + if (fBuffer[0].object_type != ACPI_TYPE_BUFFER) { + fStatus = B_ERROR; + return; + } + + fResourcePointer = (int8*)fBuffer[0].data.buffer.buffer; + fBufferSize = fBuffer[0].data.buffer.length; + fRemainingBufferSize = fBufferSize; + + // ToDo: Check checksum of the endtag. The sum of all databytes + checksum + // is zero. See section 6.4.2.8. +} + + +SmallResourceData::~SmallResourceData() +{ + if (InitCheck() == B_OK) + free(fBuffer); +} + + +status_t +SmallResourceData::InitCheck() +{ + return fStatus; +} + + +int8 +SmallResourceData::GetType() +{ + return *fResourcePointer; +} + + +status_t +SmallResourceData::ReadIOPort(io_port* ioPort) +{ + const size_t packageSize = 8; + + if (fRemainingBufferSize < packageSize) + return B_ERROR; + if (fResourcePointer[0] != kIOPort) + return B_ERROR; + + ioPort->deviceAddresses16Bit = (fResourcePointer[1] == 1); + + int16 tmp; + tmp = fResourcePointer[3]; + tmp = tmp << 8; + tmp |= fResourcePointer[2]; + ioPort->minimumBase = tmp; + + tmp = fResourcePointer[5]; + tmp = tmp << 8; + tmp |= fResourcePointer[4]; + ioPort->maximumBase = tmp; + + ioPort->minimumBaseAlignment = fResourcePointer[6]; + ioPort->contigiuousIOPorts = fResourcePointer[7]; + + fResourcePointer += packageSize; + fRemainingBufferSize -= packageSize; + TRACE("SmallResourceData: remaining buffer size %i\n", + int(fRemainingBufferSize)); + return B_OK; +} diff --git a/src/add-ons/kernel/drivers/power/acpi_embedded_controller/SmallResourceData.h b/src/add-ons/kernel/drivers/power/acpi_embedded_controller/SmallResourceData.h new file mode 100644 index 0000000000..028b2c525e --- /dev/null +++ b/src/add-ons/kernel/drivers/power/acpi_embedded_controller/SmallResourceData.h @@ -0,0 +1,61 @@ +/* + * Copyright 2009, Haiku, Inc. All Rights Reserved. + * Distributed under the terms of the MIT License. + * + * Authors: + * Clemens Zeidler, haiku@clemens-zeidler.de + */ + +#ifndef SMALLRESOURCEDATA_H +#define SMALLRESOURCEDATA_H + +#include +#include + + +enum resource_type +{ + kIOPort = 0x47, + kEndTag = 0x78 +}; + + +struct io_port +{ + void Print(); + //! The logical device decodes 16-bit addresses. + bool deviceAddresses16Bit; + + uint16 minimumBase; + uint16 maximumBase; + uint8 minimumBaseAlignment; + uint8 contigiuousIOPorts; +}; + + +/*! ToDo: implement also the other resource data, see acpi section 6.2.4 */ +class SmallResourceData +{ +public: + SmallResourceData(acpi_device_module_info* acpi, + acpi_device acpiCookie, char* method); + ~SmallResourceData(); + + status_t InitCheck(); + + int8 GetType(); + /*! Get resource data and jump to the next resource. */ + status_t ReadIOPort(io_port* ioPort); + +private: + acpi_object_type* fBuffer; + size_t fBufferSize; + size_t fRemainingBufferSize; + + int8* fResourcePointer; + + status_t fStatus; +}; + + +#endif diff --git a/src/add-ons/kernel/drivers/power/acpi_embedded_controller/acpi_embedded_controller.cpp b/src/add-ons/kernel/drivers/power/acpi_embedded_controller/acpi_embedded_controller.cpp new file mode 100644 index 0000000000..6b369cc7d9 --- /dev/null +++ b/src/add-ons/kernel/drivers/power/acpi_embedded_controller/acpi_embedded_controller.cpp @@ -0,0 +1,937 @@ +/*- + * Copyright (c) 2009 Clemens Zeidler + * Copyright (c) 2003-2007 Nate Lawson + * Copyright (c) 2000 Michael Smith + * Copyright (c) 2000 BSDi + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "acpi_embedded_controller.h" + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "SmallResourceData.h" + +#define ACPI_EC_DRIVER_NAME "drivers/power/acpi_embedded_controller/driver_v1" + +#define ACPI_EC_DEVICE_NAME "drivers/power/acpi_embedded_controller/device_v1" + +/* Base Namespace devices are published to */ +#define ACPI_EC_BASENAME "power/embedded_controller/%d" + +// name of pnp generator of path ids +#define ACPI_EC_PATHID_GENERATOR "embedded_controller/path_id" + +device_manager_info *gDeviceManager = NULL; + +pci_module_info *gPCIManager = NULL; + + +uint8 +bus_space_read_1(int address) +{ + return gPCIManager->read_io_8(address); +} + + +void +bus_space_write_1(int address, uint8 v) +{ + gPCIManager->write_io_8(address, v); +} + + +status_t +acpi_GetInteger(acpi_device_module_info* acpi, acpi_device& acpiCookie, + char* path, int* number) +{ + status_t status; + acpi_data buf; + acpi_object_type object; + buf.pointer = &object; + buf.length = sizeof(acpi_object_type); + /* + * Assume that what we've been pointed at is an Integer object, or + * a method that will return an Integer. + */ + status = acpi->evaluate_method(acpiCookie, path, NULL, &buf); + + if (status == B_OK) { + if (object.object_type == ACPI_TYPE_INTEGER) + *number = object.data.integer; + else + status = B_ERROR; + } + return status; +} + + +acpi_handle +acpi_GetReference(acpi_module_info* acpi, acpi_handle scope, + acpi_object_type *obj) +{ + acpi_handle h; + + if (obj == NULL) + return (NULL); + + switch (obj->object_type) { + case ACPI_TYPE_LOCAL_REFERENCE: + case ACPI_TYPE_ANY: + h = obj->data.reference.handle; + break; + case ACPI_TYPE_STRING: + /* + * The String object usually contains a fully-qualified path, so + * scope can be NULL. + * + * XXX This may not always be the case. + */ + if (acpi->get_handle(scope, obj->data.string.string, &h) != B_OK) + h = NULL; + break; + default: + h = NULL; + break; + } + + return (h); +} + + +int +acpi_PkgInt(acpi_object_type *res, int idx, int *dst) +{ + acpi_object_type *obj; + + obj = &res->data.package.objects[idx]; + if (obj == NULL || obj->object_type != ACPI_TYPE_INTEGER) + return (EINVAL); + *dst = obj->data.integer; + + return (0); +} + + +int +acpi_PkgInt32(acpi_object_type *res, int idx, uint32 *dst) +{ + int tmp; + int error; + + error = acpi_PkgInt(res, idx, &tmp); + if (error == 0) + *dst = (uint32)tmp; + + return (error); +} + + +static status_t +embedded_controller_open(void *initCookie, const char *path, int flags, void** cookie) +{ + acpi_ec_softc *device = (acpi_ec_softc*)initCookie; + *cookie = device; + + return B_OK; +} + + +static status_t +embedded_controller_close(void* cookie) +{ + return B_OK; +} + + +static status_t +embedded_controller_read(void* _cookie, off_t position, void *buffer, size_t* numBytes) +{ + return B_IO_ERROR; +} + + +static status_t +embedded_controller_write(void* cookie, off_t position, const void* buffer, size_t* numBytes) +{ + return B_IO_ERROR; +} + + +status_t +embedded_controller_control(void* _cookie, uint32 op, void* arg, size_t len) +{ + return B_ERROR; +} + + +static status_t +embedded_controller_free(void* cookie) +{ + return B_OK; +} + + +// #pragma mark - driver module API + +int32 +acpi_get_type(device_node* dev) +{ + const char *bus; + if (gDeviceManager->get_attr_string(dev, B_DEVICE_BUS, &bus, false)) + return -1; + + if (strcmp(bus, "acpi")) + return -1; + + uint32 deviceType; + if (gDeviceManager->get_attr_uint32(dev, ACPI_DEVICE_TYPE_ITEM, + &deviceType, false) != B_OK) + return -1; + + return deviceType; +} + + +static float +embedded_controller_support(device_node *dev) +{ + static char *ec_ids[] = { "PNP0C09", NULL }; + + /* Check that this is a device. */ + if (acpi_get_type(dev) != ACPI_TYPE_DEVICE) + return 0.; + + const char *name; + if (gDeviceManager->get_attr_string(dev, ACPI_DEVICE_HID_ITEM, &name, + false) != B_OK || strcmp(name, ec_ids[0])) + return 0.0; + + TRACE("supported device found %s\n", name); + return 0.6; +} + + +static status_t +embedded_controller_register_device(device_node *node) +{ + device_attr attrs[] = { + { B_DEVICE_PRETTY_NAME, B_STRING_TYPE, + { string: "ACPI embedded controller" }}, + { NULL } + }; + + return gDeviceManager->register_node(node, ACPI_EC_DRIVER_NAME, attrs, + NULL, NULL); +} + + +static status_t +embedded_controller_init_driver(device_node *dev, void **_driverCookie) +{ + TRACE("init driver\n"); + acpi_ec_softc *sc; + sc = (acpi_ec_softc*)malloc(sizeof(acpi_ec_softc)); + memset(sc, 0, sizeof(acpi_ec_softc)); + + *_driverCookie = sc; + sc->ec_dev = dev; + + sc->ec_condition_var.Init(NULL, "ec condition variable"); + + device_node *parent; + parent = gDeviceManager->get_parent_node(dev); + gDeviceManager->get_driver(parent, (driver_module_info **)&sc->ec_acpi, + (void **)&sc->ec_handle); + gDeviceManager->put_node(parent); + + SmallResourceData resourceData(sc->ec_acpi, sc->ec_handle, "_CRS"); + if (resourceData.InitCheck() != B_OK) { + TRACE("failed to read _CRS resource\n") ; + return B_ERROR; + } + io_port portData; + + if (get_module(B_ACPI_MODULE_NAME, (module_info**)&sc->ec_acpi_module) != B_OK) + return B_ERROR; + + // DPC module + if (gDPC == NULL && get_module(B_DPC_MODULE_NAME, + (module_info **)&gDPC) != B_OK) { + dprintf("failed to get dpc module for os execution\n"); + return B_ERROR; + } + + if (gDPCHandle == NULL) { + if (gDPC->new_dpc_queue(&gDPCHandle, "acpi_task", + B_NORMAL_PRIORITY) != B_OK) { + dprintf("failed to create os execution queue\n"); + return B_ERROR; + } + } + + acpi_data buf; + buf.pointer = NULL; + buf.length = ACPI_ALLOCATE_BUFFER; + + + /* + * Read the unit ID to check for duplicate attach and the + * global lock value to see if we should acquire it when + * accessing the EC. + */ + + status_t status; + status = acpi_GetInteger(sc->ec_acpi, sc->ec_handle, "_UID", &sc->ec_uid); + if (status != B_OK) + sc->ec_uid = 0; + status = acpi_GetInteger(sc->ec_acpi, sc->ec_handle, "_GLK", &sc->ec_glk); + if (status != B_OK) + sc->ec_glk = 0; + + /* + * Evaluate the _GPE method to find the GPE bit used by the EC to + * signal status (SCI). If it's a package, it contains a reference + * and GPE bit, similar to _PRW. + */ + status = sc->ec_acpi->evaluate_method(sc->ec_handle, "_GPE", NULL, &buf); + if (status != B_OK) { + TRACE("can't evaluate _GPE\n"); + goto error; + } + + acpi_object_type* obj; + obj = (acpi_object_type*)buf.pointer; + if (obj == NULL) + goto error; + + switch (obj->object_type) { + case ACPI_TYPE_INTEGER: + sc->ec_gpehandle = NULL; + sc->ec_gpebit = obj->data.integer; + break; + case ACPI_TYPE_PACKAGE: + if (!ACPI_PKG_VALID(obj, 2)) + goto error; + sc->ec_gpehandle = + acpi_GetReference(sc->ec_acpi_module, NULL, + &obj->data.package.objects[0]); + if (sc->ec_gpehandle == NULL || + acpi_PkgInt32(obj, 1, (uint32*)&sc->ec_gpebit) != 0) + goto error; + break; + default: + TRACE("_GPE has invalid type %i\n", int(obj->object_type)); + goto error; + } + + sc->ec_suspending = FALSE; + + /* Attach bus resources for data and command/status ports. */ + sc->ec_data_rid = 0; + if (resourceData.ReadIOPort(&portData) != B_OK) + goto error; + + sc->ec_data_pci_address = portData.minimumBase; + + sc->ec_csr_rid = 1; + if (resourceData.ReadIOPort(&portData) != B_OK) + goto error; + + sc->ec_csr_pci_address = portData.minimumBase; + + /* + * Install a handler for this EC's GPE bit. We want edge-triggered + * behavior. + */ + TRACE("attaching GPE handler\n"); + status = sc->ec_acpi_module->install_gpe_handler(sc->ec_gpehandle, + sc->ec_gpebit, ACPI_GPE_EDGE_TRIGGERED, &EcGpeHandler, sc); + if (status != B_OK) { + TRACE("can't install ec GPE handler\n"); + goto error; + } + + /* + * Install address space handler + */ + TRACE("attaching address space handler\n"); + status = sc->ec_acpi->install_address_space_handler(sc->ec_handle, + ACPI_ADR_SPACE_EC, &EcSpaceHandler, &EcSpaceSetup, sc); + if (status != B_OK) { + TRACE("can't install address space handler\n"); + goto error; + } + + /* Enable runtime GPEs for the handler. */ + status = sc->ec_acpi_module->set_gpe_type(sc->ec_gpehandle, sc->ec_gpebit, + ACPI_GPE_TYPE_RUNTIME); + if (status != B_OK) { + TRACE("AcpiSetGpeType failed.\n"); + goto error; + } + status = sc->ec_acpi_module->enable_gpe(sc->ec_gpehandle, sc->ec_gpebit, + ACPI_NOT_ISR); + if (status != B_OK) { + TRACE("AcpiEnableGpe failed.\n"); + goto error; + } + + return (0); + +error: + if (buf.pointer) + free(buf.pointer); + + sc->ec_acpi_module->remove_gpe_handler(sc->ec_gpehandle, sc->ec_gpebit, + &EcGpeHandler); + sc->ec_acpi->remove_address_space_handler(sc->ec_handle, ACPI_ADR_SPACE_EC, + EcSpaceHandler); + + return (ENXIO); +} + + +static void +embedded_controller_uninit_driver(void *driverCookie) +{ + acpi_ec_softc* sc = (struct acpi_ec_softc *)driverCookie; + free(sc); + put_module(B_ACPI_MODULE_NAME); + put_module(B_DPC_MODULE_NAME); +} + + +static status_t +embedded_controller_register_child_devices(void *_cookie) +{ + device_node *node = ((acpi_ec_softc*)_cookie)->ec_dev; + + int pathID = gDeviceManager->create_id(ACPI_EC_PATHID_GENERATOR); + if (pathID < 0) { + TRACE("register_child_device couldn't create a path_id\n"); + return B_ERROR; + } + + char name[128]; + snprintf(name, sizeof(name), ACPI_EC_BASENAME, pathID); + + return gDeviceManager->publish_device(node, name, ACPI_EC_DEVICE_NAME); +} + + +static status_t +embedded_controller_init_device(void *driverCookie, void **cookie) +{ + return B_ERROR; +} + + +static void +embedded_controller_uninit_device(void *_cookie) +{ + acpi_ec_softc *device = (acpi_ec_softc*)_cookie; + free(device); +} + + +module_dependency module_dependencies[] = { + { B_DEVICE_MANAGER_MODULE_NAME, (module_info **)&gDeviceManager }, + { B_PCI_MODULE_NAME, (module_info **)&gPCIManager}, + {} +}; + + +driver_module_info embedded_controller_driver_module = { + { + ACPI_EC_DRIVER_NAME, + 0, + NULL + }, + + embedded_controller_support, + embedded_controller_register_device, + embedded_controller_init_driver, + embedded_controller_uninit_driver, + embedded_controller_register_child_devices, + NULL, // rescan + NULL, // removed +}; + + +struct device_module_info embedded_controller_device_module = { + { + ACPI_EC_DEVICE_NAME, + 0, + NULL + }, + + embedded_controller_init_device, + embedded_controller_uninit_device, + NULL, + + embedded_controller_open, + embedded_controller_close, + embedded_controller_free, + embedded_controller_read, + embedded_controller_write, + NULL, + embedded_controller_control, + + NULL, + NULL +}; + + +module_info *modules[] = { + (module_info *)&embedded_controller_driver_module, + (module_info *)&embedded_controller_device_module, + NULL +}; + + +static void +EcGpeQueryHandler(void *context) +{ + struct acpi_ec_softc *sc = (struct acpi_ec_softc *)context; + uint8 data; + status_t status; + char qxx[5]; + + ASSERT(context != NULL);//, ("EcGpeQueryHandler called with NULL")); + + /* Serialize user access with EcSpaceHandler(). */ + status = EcLock(sc); + if (status != B_OK) { + TRACE("GpeQuery lock error.\n"); + return; + } + + /* + * Send a query command to the EC to find out which _Qxx call it + * wants to make. This command clears the SCI bit and also the + * interrupt source since we are edge-triggered. To prevent the GPE + * that may arise from running the query from causing another query + * to be queued, we clear the pending flag only after running it. + */ + status = EcCommand(sc, EC_COMMAND_QUERY); + sc->ec_sci_pend = FALSE; + if (status != B_OK) { + EcUnlock(sc); + TRACE("GPE query failed.\n"); + return; + } + data = EC_GET_DATA(sc); + + /* + * We have to unlock before running the _Qxx method below since that + * method may attempt to read/write from EC address space, causing + * recursive acquisition of the lock. + */ + EcUnlock(sc); + + /* Ignore the value for "no outstanding event". (13.3.5) */ + TRACE("ec query ok,%s running _Q%02X\n", Data ? "" : " not", data); + if (data == 0) + return; + + /* Evaluate _Qxx to respond to the controller. */ + snprintf(qxx, sizeof(qxx), "_Q%02X", data); + AcpiUtStrupr(qxx); + status = sc->ec_acpi->evaluate_method(sc->ec_handle, qxx, NULL, NULL); + if (status != B_OK) { + TRACE("evaluation of query method %s failed\n", qxx); + } +} + +/* + * The GPE handler is called when IBE/OBF or SCI events occur. We are + * called from an unknown lock context. + */ +static uint32 +EcGpeHandler(void *context) +{ + struct acpi_ec_softc *sc = (acpi_ec_softc*)context; + status_t status; + EC_STATUS EcStatus; + + ASSERT(context != NULL);//, ("EcGpeHandler called with NULL")); + TRACE("ec gpe handler start\n"); + + /* + * Notify EcWaitEvent() that the status register is now fresh. If we + * didn't do this, it wouldn't be possible to distinguish an old IBE + * from a new one, for example when doing a write transaction (writing + * address and then data values.) + */ + atomic_add(&sc->ec_gencount, 1); + sc->ec_condition_var.NotifyAll(); + /* + * If the EC_SCI bit of the status register is set, queue a query handler. + * It will run the query and _Qxx method later, under the lock. + */ + EcStatus = EC_GET_CSR(sc); + if ((EcStatus & EC_EVENT_SCI) && !sc->ec_sci_pend) { + TRACE("ec gpe queueing query handler\n"); + status = AcpiOsExecute(OSL_GPE_HANDLER, EcGpeQueryHandler, context); + if (status == B_OK) + sc->ec_sci_pend = TRUE; + else + dprintf("EcGpeHandler: queuing GPE query handler failed\n"); + } + return (0); +} + + +static status_t +EcSpaceSetup(acpi_handle region, uint32 function, void *context, + void **regionContext) +{ + /* + * If deactivating a region, always set the output to NULL. Otherwise, + * just pass the context through. + */ + if (function == ACPI_REGION_DEACTIVATE) + *regionContext = NULL; + else + *regionContext = context; + + return B_OK; +} + + +static status_t +EcSpaceHandler(uint32 function, acpi_physical_address address, uint32 width, + int *value, void *context, void *regionContext) +{ + TRACE("enter EcSpaceHandler\n"); + struct acpi_ec_softc *sc = (struct acpi_ec_softc *)context; + status_t status; + uint8 ecAddr, ecData; + uint32 i; + + if (width % 8 != 0 || value == NULL || context == NULL) + return B_BAD_VALUE; + if (address + (width / 8) - 1 > 0xFF) + return B_BAD_ADDRESS; + + if (function == ACPI_READ) + *value = 0; + ecAddr = address; + status = B_ERROR; + + /* + * If booting, check if we need to run the query handler. If so, we + * we call it directly here since our thread taskq is not active yet. + */ + /*if (cold || rebooting || sc->ec_suspending) { + if ((EC_GET_CSR(sc) & EC_EVENT_SCI)) { + //CTR0(KTR_ACPI, "ec running gpe handler directly"); + EcGpeQueryHandler(sc); + } + }*/ + + /* Serialize with EcGpeQueryHandler() at transaction granularity. */ + status = EcLock(sc); + if (status != B_OK) + return (status); + + /* Perform the transaction(s), based on width. */ + for (i = 0; i < width; i += 8, ecAddr++) { + switch (function) { + case ACPI_READ: + status = EcRead(sc, ecAddr, &ecData); + if (status == B_OK) + *value |= ((int)ecData) << i; + break; + case ACPI_WRITE: + ecData = (uint8)((*value) >> i); + status = EcWrite(sc, ecAddr, &ecData); + break; + default: + TRACE("invalid EcSpaceHandler function\n"); + status = B_BAD_VALUE; + break; + } + if (status != B_OK) + break; + } + + EcUnlock(sc); + return (status); +} + +static status_t +EcCheckStatus(struct acpi_ec_softc *sc, const char *msg, EC_EVENT event) +{ + status_t status = B_ERROR; + EC_STATUS ec_status; + + ec_status = EC_GET_CSR(sc); + if (sc->ec_burstactive && !(ec_status & EC_FLAG_BURST_MODE)) { + TRACE("ec burst disabled in waitevent (%s)\n", msg); + sc->ec_burstactive = false; + } + if (EVENT_READY(event, ec_status)) { + TRACE("ec %s wait ready, status %#x\n", msg, ec_status); + status = B_OK; + } + return (status); +} + +static status_t +EcWaitEvent(struct acpi_ec_softc *sc, EC_EVENT event, int32 gen_count) +{ + status_t status = B_ERROR; + int32 count, i; + + // int need_poll = cold || rebooting || ec_polled_mode || sc->ec_suspending; + int need_poll = ec_polled_mode || sc->ec_suspending; + + /* + * The main CPU should be much faster than the EC. So the status should + * be "not ready" when we start waiting. But if the main CPU is really + * slow, it's possible we see the current "ready" response. Since that + * can't be distinguished from the previous response in polled mode, + * this is a potential issue. We really should have interrupts enabled + * during boot so there is no ambiguity in polled mode. + * + * If this occurs, we add an additional delay before actually entering + * the status checking loop, hopefully to allow the EC to go to work + * and produce a non-stale status. + */ + if (need_poll) { + static int once; + + if (EcCheckStatus(sc, "pre-check", event) == B_OK) { + if (!once) { + TRACE("warning: EC done before starting event wait\n"); + once = 1; + } + spin(10); + } + } + + /* Wait for event by polling or GPE (interrupt). */ + if (need_poll) { + count = (ec_timeout * 1000) / EC_POLL_DELAY; + if (count == 0) + count = 1; + for (i = 0; i < count; i++) { + status = EcCheckStatus(sc, "poll", event); + if (status == B_OK) + break; + spin(EC_POLL_DELAY); + } + } else { + // ToDo: scale timeout for slow cpu see BSD code... + count = ec_timeout; + + /* + * Wait for the GPE to signal the status changed, checking the + * status register each time we get one. It's possible to get a + * GPE for an event we're not interested in here (i.e., SCI for + * EC query). + */ + for (i = 0; i < count; i++) { + if (gen_count != sc->ec_gencount) { + /* + * Record new generation count. It's possible the GPE was + * just to notify us that a query is needed and we need to + * wait for a second GPE to signal the completion of the + * event we are actually waiting for. + */ + gen_count = sc->ec_gencount; + status = EcCheckStatus(sc, "sleep", event); + if (status == B_OK) + break; + } + sc->ec_condition_var.Wait(); + } + + /* + * We finished waiting for the GPE and it never arrived. Try to + * read the register once and trust whatever value we got. This is + * the best we can do at this point. Then, force polled mode on + * since this system doesn't appear to generate GPEs. + */ + if (status != B_OK) { + status = EcCheckStatus(sc, "sleep_end", event); + TRACE("wait timed out (%sresponse), forcing polled mode\n", + Status == B_OK ? "" : "no "); + ec_polled_mode = TRUE; + } + } + if (status != B_OK) + TRACE("error: ec wait timed out\n"); + + return (status); +} + +static status_t +EcCommand(struct acpi_ec_softc *sc, EC_COMMAND cmd) +{ + status_t status; + EC_EVENT event; + EC_STATUS ec_status; + u_int gen_count; + + /* Don't use burst mode if user disabled it. */ + if (!ec_burst_mode && cmd == EC_COMMAND_BURST_ENABLE) + return (B_ERROR); + + /* Decide what to wait for based on command type. */ + switch (cmd) { + case EC_COMMAND_READ: + case EC_COMMAND_WRITE: + case EC_COMMAND_BURST_DISABLE: + event = EC_EVENT_INPUT_BUFFER_EMPTY; + break; + case EC_COMMAND_QUERY: + case EC_COMMAND_BURST_ENABLE: + event = EC_EVENT_OUTPUT_BUFFER_FULL; + break; + default: + TRACE("EcCommand: invalid command %#x\n", cmd); + return (B_BAD_VALUE); + } + + /* Run the command and wait for the chosen event. */ + TRACE("ec running command %#x\n", cmd); + gen_count = sc->ec_gencount; + EC_SET_CSR(sc, cmd); + status = EcWaitEvent(sc, event, gen_count); + if (status == B_OK) { + /* If we succeeded, burst flag should now be present. */ + if (cmd == EC_COMMAND_BURST_ENABLE) { + ec_status = EC_GET_CSR(sc); + if ((ec_status & EC_FLAG_BURST_MODE) == 0) + status = B_ERROR; + } + } else + TRACE("EcCommand: no response to %#x\n", cmd); + + return (status); +} + +static status_t +EcRead(struct acpi_ec_softc *sc, uint8 address, uint8 *readData) +{ + status_t status; + uint8 data; + u_int gen_count; + + TRACE("ec read from %#x\n", Address); + + /* If we can't start burst mode, continue anyway. */ + status = EcCommand(sc, EC_COMMAND_BURST_ENABLE); + if (status == B_OK) { + data = EC_GET_DATA(sc); + if (data == EC_BURST_ACK) { + TRACE("ec burst enabled\n"); + sc->ec_burstactive = TRUE; + } + } + + status = EcCommand(sc, EC_COMMAND_READ); + if (status != B_OK) + return (status); + + gen_count = sc->ec_gencount; + + EC_SET_DATA(sc, address); + status = EcWaitEvent(sc, EC_EVENT_OUTPUT_BUFFER_FULL, gen_count); + if (status != B_OK) { + TRACE("EcRead: failed waiting to get data\n"); + return (status); + } + *readData = EC_GET_DATA(sc); + + if (sc->ec_burstactive) { + sc->ec_burstactive = FALSE; + status = EcCommand(sc, EC_COMMAND_BURST_DISABLE); + if (status != B_OK) + return (status); + TRACE("ec disabled burst ok\n"); + } + + return (B_OK); +} + +static status_t +EcWrite(struct acpi_ec_softc *sc, uint8 address, uint8 *writeData) +{ + status_t status; + uint8 data; + u_int gen_count; + + /* If we can't start burst mode, continue anyway. */ + status = EcCommand(sc, EC_COMMAND_BURST_ENABLE); + if (status == B_OK) { + data = EC_GET_DATA(sc); + if (data == EC_BURST_ACK) { + TRACE("ec burst enabled\n"); + sc->ec_burstactive = TRUE; + } + } + + status = EcCommand(sc, EC_COMMAND_WRITE); + if (status != B_OK) + return (status); + + gen_count = sc->ec_gencount; + EC_SET_DATA(sc, address); + status = EcWaitEvent(sc, EC_EVENT_INPUT_BUFFER_EMPTY, gen_count); + if (status != B_OK) { + TRACE("EcRead: failed waiting for sent address\n"); + return (status); + } + + gen_count = sc->ec_gencount; + EC_SET_DATA(sc, *writeData); + status = EcWaitEvent(sc, EC_EVENT_INPUT_BUFFER_EMPTY, gen_count); + if (status != B_OK) { + TRACE("EcWrite: failed waiting for sent data\n"); + return (status); + } + + if (sc->ec_burstactive) { + sc->ec_burstactive = FALSE; + status = EcCommand(sc, EC_COMMAND_BURST_DISABLE); + if (status != B_OK) + return (status); + TRACE("ec disabled burst ok"); + } + + return (B_OK); +} + diff --git a/src/add-ons/kernel/drivers/power/acpi_embedded_controller/acpi_embedded_controller.h b/src/add-ons/kernel/drivers/power/acpi_embedded_controller/acpi_embedded_controller.h new file mode 100644 index 0000000000..8ec48dfe15 --- /dev/null +++ b/src/add-ons/kernel/drivers/power/acpi_embedded_controller/acpi_embedded_controller.h @@ -0,0 +1,314 @@ +/*- + * Copyright (c) 2009 Clemens Zeidler + * Copyright (c) 2003-2007 Nate Lawson + * Copyright (c) 2000 Michael Smith + * Copyright (c) 2000 BSDi + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef ACPI_BATTERY_H +#define ACPI_BATTERY_H + +#include + +#include +#include +#include +#include +#include + + +// #define TRACE_EMBEDDED_CONTROLLER +#ifdef TRACE_EMBEDDED_CONTROLLER +# define TRACE(x...) dprintf("EC: " x) +#else +# define TRACE(x...) +#endif + + +dpc_module_info *gDPC = NULL; +void *gDPCHandle; + + +#define ACPI_GPE_EDGE_TRIGGERED (uint8) 0x00 +#define ACPI_GPE_TYPE_RUNTIME (uint8) 0x04 /* Default */ + +#define ACPI_NOT_ISR 0x1 +#define ACPI_ISR 0x0 + +enum { + OSL_GLOBAL_LOCK_HANDLER, + OSL_NOTIFY_HANDLER, + OSL_GPE_HANDLER, + OSL_DEBUGGER_THREAD, + OSL_EC_POLL_HANDLER, + OSL_EC_BURST_HANDLER +}; + +typedef void (*ACPI_OSD_EXEC_CALLBACK)(void* Context); + + +// ToDo: Maybe also put this acpi function into the acpi module? +status_t +AcpiOsExecute(uint32 Type, ACPI_OSD_EXEC_CALLBACK Function, + void *Context) +{ + switch (Type) { + case OSL_GLOBAL_LOCK_HANDLER: + case OSL_NOTIFY_HANDLER: + case OSL_GPE_HANDLER: + case OSL_DEBUGGER_THREAD: + case OSL_EC_POLL_HANDLER: + case OSL_EC_BURST_HANDLER: + break; + } + + if (gDPC->queue_dpc(gDPCHandle, Function, Context) != B_OK) + return B_ERROR; + + return B_OK; +} + +/* copied from utmisc.c don't want to put this simple function into the acpi +module */ +void +AcpiUtStrupr(char *SrcString) +{ + char *String; + + if (!SrcString) + { + return; + } + + /* Walk entire string, uppercasing the letters */ + + for (String = SrcString; *String; String++) + { + *String = (char) toupper(*String); + } + + return; +} + + +#define ACPI_REGION_DEACTIVATE 1 + +#define ACPI_READ 0 +#define ACPI_WRITE 1 + + +#define EC_COMMAND_UNKNOWN ((EC_COMMAND) 0x00) +#define EC_COMMAND_READ ((EC_COMMAND) 0x80) +#define EC_COMMAND_WRITE ((EC_COMMAND) 0x81) +#define EC_COMMAND_BURST_ENABLE ((EC_COMMAND) 0x82) +#define EC_COMMAND_BURST_DISABLE ((EC_COMMAND) 0x83) +#define EC_COMMAND_QUERY ((EC_COMMAND) 0x84) + +/* + * EC_STATUS: + * ---------- + * The encoding of the EC status register is illustrated below. + * Note that a set bit (1) indicates the property is TRUE + * (e.g. if bit 0 is set then the output buffer is full). + * +-+-+-+-+-+-+-+-+ + * |7|6|5|4|3|2|1|0| + * +-+-+-+-+-+-+-+-+ + * | | | | | | | | + * | | | | | | | +- Output Buffer Full? + * | | | | | | +--- Input Buffer Full? + * | | | | | +----- + * | | | | +------- Data Register is Command Byte? + * | | | +--------- Burst Mode Enabled? + * | | +----------- SCI Event? + * | +------------- SMI Event? + * +--------------- + * + */ + +typedef uint8 EC_STATUS; + +#define EC_FLAG_OUTPUT_BUFFER ((uint8) 0x01) +#define EC_FLAG_INPUT_BUFFER ((uint8) 0x02) +#define EC_FLAG_DATA_IS_CMD ((uint8) 0x08) +#define EC_FLAG_BURST_MODE ((uint8) 0x10) + +/* + * EC_EVENT: + * --------- + */ + +#define EC_EVENT_UNKNOWN ((uint8) 0x00) +#define EC_EVENT_OUTPUT_BUFFER_FULL ((uint8) 0x01) +#define EC_EVENT_INPUT_BUFFER_EMPTY ((uint8) 0x02) +#define EC_EVENT_SCI ((uint8) 0x20) +#define EC_EVENT_SMI ((uint8) 0x40) + +/* Data byte returned after burst enable indicating it was successful. */ +#define EC_BURST_ACK 0x90 + +/* Total time in ms spent waiting for a response from EC. */ +#define EC_TIMEOUT 750 + +static int ec_burst_mode = 1; +static int ec_polled_mode = 0; + +static int ec_timeout = EC_TIMEOUT; + +/* + * Register access primitives + */ +#define EC_GET_DATA(sc) \ + bus_space_read_1((sc)->ec_data_pci_address) + +#define EC_SET_DATA(sc, v) \ + bus_space_write_1((sc)->ec_data_pci_address, (v)) + +#define EC_GET_CSR(sc) \ + bus_space_read_1((sc)->ec_csr_pci_address) + +#define EC_SET_CSR(sc, v) \ + bus_space_write_1((sc)->ec_csr_pci_address, (v)) + + +#define ACPI_PKG_VALID(pkg, size) \ + ((pkg) != NULL && (pkg)->object_type == ACPI_TYPE_PACKAGE && \ + (pkg)->data.package.count >= (size)) + +int32 acpi_get_type(device_node* dev); + +/* + * Driver softc. + */ +struct acpi_ec_softc { + device_node* ec_dev; + acpi_module_info* ec_acpi_module; + acpi_device_module_info* ec_acpi; + acpi_device ec_handle; + int ec_uid; + acpi_handle ec_gpehandle; + uint8 ec_gpebit; + + int ec_data_rid; + int ec_data_pci_address; + + int ec_csr_rid; + int ec_csr_pci_address; + + int ec_glk; + uint32 ec_glkhandle; + int ec_burstactive; + int ec_sci_pend; + vint32 ec_gencount; + ConditionVariable ec_condition_var; + int ec_suspending; +}; + + + +/* + * XXX njl + * I couldn't find it in the spec but other implementations also use a + * value of 1 ms for the time to acquire global lock. + */ +#define EC_LOCK_TIMEOUT 1000 + +/* Default delay in microseconds between each run of the status polling loop. */ +#define EC_POLL_DELAY 5 + +/* Total time in ms spent waiting for a response from EC. */ +#define EC_TIMEOUT 750 + +#define EVENT_READY(event, status) \ + (((event) == EC_EVENT_OUTPUT_BUFFER_FULL && \ + ((status) & EC_FLAG_OUTPUT_BUFFER) != 0) || \ + ((event) == EC_EVENT_INPUT_BUFFER_EMPTY && \ + ((status) & EC_FLAG_INPUT_BUFFER) == 0)) + + +static status_t +EcLock(struct acpi_ec_softc *sc) +{ + status_t status; + + /* If _GLK is non-zero, acquire the global lock. */ + status = B_OK; + if (sc->ec_glk) { + status = sc->ec_acpi_module->acquire_global_lock(EC_LOCK_TIMEOUT, + &sc->ec_glkhandle); + if (status != B_OK) + return status; + } + + return status; +} + + +static void +EcUnlock(struct acpi_ec_softc *sc) +{ + if (sc->ec_glk) + sc->ec_acpi_module->release_global_lock(sc->ec_glkhandle); +} + +typedef unsigned int EC_EVENT; +typedef unsigned int EC_COMMAND; + +static uint32 EcGpeHandler(void *context); +static status_t EcSpaceSetup(acpi_handle region, uint32 function, + void *context, void **return_Context); +static status_t EcSpaceHandler(uint32 function, + acpi_physical_address address, + uint32 width, int *value, + void *context, void *regionContext); +static status_t EcWaitEvent(struct acpi_ec_softc *sc, EC_EVENT event, + int32 gen_count); +static status_t EcCommand(struct acpi_ec_softc *sc, EC_COMMAND cmd); +static status_t EcRead(struct acpi_ec_softc *sc, uint8 address, + uint8 *readData); +static status_t EcWrite(struct acpi_ec_softc *sc, uint8 address, + uint8 *writeData); + + +/* + * XXX njl + * I couldn't find it in the spec but other implementations also use a + * value of 1 ms for the time to acquire global lock. + */ +#define EC_LOCK_TIMEOUT 1000 + +/* Default delay in microseconds between each run of the status polling loop. */ +#define EC_POLL_DELAY 5 + +/* Total time in ms spent waiting for a response from EC. */ +#define EC_TIMEOUT 750 + +#define EVENT_READY(event, status) \ + (((event) == EC_EVENT_OUTPUT_BUFFER_FULL && \ + ((status) & EC_FLAG_OUTPUT_BUFFER) != 0) || \ + ((event) == EC_EVENT_INPUT_BUFFER_EMPTY && \ + ((status) & EC_FLAG_INPUT_BUFFER) == 0)) + + +#endif