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
This commit is contained in:
Clemens Zeidler
2009-06-18 18:30:06 +00:00
parent 5f6906e1eb
commit 75a666aa95
6 changed files with 1438 additions and 0 deletions
+1
View File
@@ -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 ;
@@ -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 ;
@@ -0,0 +1,114 @@
/*
* Copyright 2009, Haiku, Inc. All Rights Reserved.
* Distributed under the terms of the MIT License.
*
* Authors:
* Clemens Zeidler, [email protected]
*/
#include "SmallResourceData.h"
#include <stdlib.h>
//#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;
}
@@ -0,0 +1,61 @@
/*
* Copyright 2009, Haiku, Inc. All Rights Reserved.
* Distributed under the terms of the MIT License.
*
* Authors:
* Clemens Zeidler, [email protected]
*/
#ifndef SMALLRESOURCEDATA_H
#define SMALLRESOURCEDATA_H
#include <ACPI.h>
#include <Errors.h>
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
@@ -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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ACPI.h>
#include <condition_variable.h>
#include <Errors.h>
#include <KernelExport.h>
#include <drivers/PCI.h>
#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);
}
@@ -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 <ctype.h>
#include <ACPI.h>
#include <condition_variable.h>
#include <dpc.h>
#include <Drivers.h>
#include <KernelExport.h>
// #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?
* | | | | | +----- <reserved>
* | | | | +------- Data Register is Command Byte?
* | | | +--------- Burst Mode Enabled?
* | | +----------- SCI Event?
* | +------------- SMI Event?
* +--------------- <reserved>
*
*/
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