555 lines
12 KiB
C
555 lines
12 KiB
C
/* $NetBSD: smbus_acpi.c,v 1.13 2010/07/29 11:03:09 jruoho Exp $ */
|
|
|
|
/*-
|
|
* Copyright (c) 2009 The NetBSD Foundation, Inc.
|
|
* All rights reserved.
|
|
*
|
|
* This code is derived from software contributed to The NetBSD Foundation
|
|
* by Paul Goyette
|
|
*
|
|
* 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 NETBSD FOUNDATION, INC. 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 FOUNDATION 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.
|
|
*/
|
|
|
|
/*
|
|
* ACPI SMBus Controller driver
|
|
*
|
|
* See http://smbus.org/specs/smbus_cmi10.pdf for specifications
|
|
*/
|
|
|
|
#include <sys/cdefs.h>
|
|
__KERNEL_RCSID(0, "$NetBSD: smbus_acpi.c,v 1.13 2010/07/29 11:03:09 jruoho Exp $");
|
|
|
|
#include <sys/param.h>
|
|
#include <sys/device.h>
|
|
#include <sys/callout.h>
|
|
#include <sys/kernel.h>
|
|
#include <sys/mutex.h>
|
|
#include <sys/systm.h>
|
|
|
|
#include <dev/acpi/acpireg.h>
|
|
#include <dev/acpi/acpivar.h>
|
|
|
|
#include <dev/i2c/i2cvar.h>
|
|
|
|
#define _COMPONENT ACPI_BUS_COMPONENT
|
|
ACPI_MODULE_NAME ("smbus_acpi")
|
|
|
|
/*
|
|
* ACPI SMBus CMI protocol codes.
|
|
*/
|
|
#define ACPI_SMBUS_RD_QUICK 0x03
|
|
#define ACPI_SMBUS_RCV_BYTE 0x05
|
|
#define ACPI_SMBUS_RD_BYTE 0x07
|
|
#define ACPI_SMBUS_RD_WORD 0x09
|
|
#define ACPI_SMBUS_RD_BLOCK 0x0B
|
|
#define ACPI_SMBUS_WR_QUICK 0x02
|
|
#define ACPI_SMBUS_SND_BYTE 0x04
|
|
#define ACPI_SMBUS_WR_BYTE 0x06
|
|
#define ACPI_SMBUS_WR_WORD 0x08
|
|
#define ACPI_SMBUS_WR_BLOCK 0x0A
|
|
#define ACPI_SMBUS_PROCESS_CALL 0x0C
|
|
|
|
struct acpi_smbus_softc {
|
|
struct acpi_devnode *sc_devnode;
|
|
struct callout sc_callout;
|
|
struct i2c_controller sc_i2c_tag;
|
|
device_t sc_dv;
|
|
kmutex_t sc_i2c_mutex;
|
|
int sc_poll_alert;
|
|
};
|
|
|
|
static int acpi_smbus_match(device_t, cfdata_t, void *);
|
|
static void acpi_smbus_attach(device_t, device_t, void *);
|
|
static int acpi_smbus_detach(device_t, int);
|
|
static int acpi_smbus_poll_alert(ACPI_HANDLE, int *);
|
|
static int acpi_smbus_acquire_bus(void *, int);
|
|
static void acpi_smbus_release_bus(void *, int);
|
|
static int acpi_smbus_exec(void *, i2c_op_t, i2c_addr_t, const void *,
|
|
size_t, void *, size_t, int);
|
|
static void acpi_smbus_alerts(struct acpi_smbus_softc *);
|
|
static void acpi_smbus_tick(void *);
|
|
static void acpi_smbus_notify_handler(ACPI_HANDLE, uint32_t, void *);
|
|
|
|
struct SMB_UDID {
|
|
uint8_t dev_cap;
|
|
uint8_t vers_rev;
|
|
uint16_t vendor;
|
|
uint16_t device;
|
|
uint16_t interface;
|
|
uint16_t subsys_vendor;
|
|
uint16_t subsys_device;
|
|
uint8_t reserved[4];
|
|
};
|
|
|
|
struct SMB_DEVICE {
|
|
uint8_t slave_addr;
|
|
uint8_t reserved;
|
|
struct SMB_UDID dev_id;
|
|
};
|
|
|
|
struct SMB_INFO {
|
|
uint8_t struct_ver;
|
|
uint8_t spec_ver;
|
|
uint8_t hw_cap;
|
|
uint8_t poll_int;
|
|
uint8_t dev_count;
|
|
struct SMB_DEVICE device[1];
|
|
};
|
|
|
|
static const char * const smbus_acpi_ids[] = {
|
|
"SMBUS01", /* SMBus CMI v1.0 */
|
|
NULL
|
|
};
|
|
|
|
CFATTACH_DECL_NEW(acpismbus, sizeof(struct acpi_smbus_softc),
|
|
acpi_smbus_match, acpi_smbus_attach, acpi_smbus_detach, NULL);
|
|
|
|
static int
|
|
acpi_smbus_match(device_t parent, cfdata_t match, void *aux)
|
|
{
|
|
struct acpi_attach_args *aa = aux;
|
|
|
|
if (aa->aa_node->ad_type != ACPI_TYPE_DEVICE)
|
|
return 0;
|
|
|
|
if (acpi_match_hid(aa->aa_node->ad_devinfo, smbus_acpi_ids) == 0)
|
|
return 0;
|
|
|
|
return acpi_smbus_poll_alert(aa->aa_node->ad_handle, NULL);
|
|
}
|
|
|
|
static void
|
|
acpi_smbus_attach(device_t parent, device_t self, void *aux)
|
|
{
|
|
struct acpi_smbus_softc *sc = device_private(self);
|
|
struct acpi_attach_args *aa = aux;
|
|
struct i2cbus_attach_args iba;
|
|
|
|
aprint_naive("\n");
|
|
|
|
sc->sc_devnode = aa->aa_node;
|
|
sc->sc_dv = self;
|
|
sc->sc_poll_alert = 2;
|
|
|
|
/* Attach I2C bus. */
|
|
mutex_init(&sc->sc_i2c_mutex, MUTEX_DEFAULT, IPL_NONE);
|
|
|
|
sc->sc_i2c_tag.ic_cookie = sc;
|
|
sc->sc_i2c_tag.ic_acquire_bus = acpi_smbus_acquire_bus;
|
|
sc->sc_i2c_tag.ic_release_bus = acpi_smbus_release_bus;
|
|
sc->sc_i2c_tag.ic_exec = acpi_smbus_exec;
|
|
|
|
(void)acpi_smbus_poll_alert(aa->aa_node->ad_handle,&sc->sc_poll_alert);
|
|
|
|
/* If failed, fall-back to polling. */
|
|
if (acpi_register_notify(sc->sc_devnode,
|
|
acpi_smbus_notify_handler) != true)
|
|
sc->sc_poll_alert = 2;
|
|
|
|
callout_init(&sc->sc_callout, 0);
|
|
callout_setfunc(&sc->sc_callout, acpi_smbus_tick, self);
|
|
|
|
if (sc->sc_poll_alert != 0) {
|
|
aprint_debug(": alert_poll %d sec", sc->sc_poll_alert);
|
|
callout_schedule(&sc->sc_callout, sc->sc_poll_alert * hz);
|
|
}
|
|
|
|
aprint_normal("\n");
|
|
|
|
(void)memset(&iba, 0, sizeof(iba));
|
|
(void)pmf_device_register(self, NULL, NULL);
|
|
|
|
iba.iba_tag = &sc->sc_i2c_tag;
|
|
|
|
(void)config_found_ia(self, "i2cbus", &iba, iicbus_print);
|
|
}
|
|
|
|
static int
|
|
acpi_smbus_detach(device_t self, int flags)
|
|
{
|
|
struct acpi_smbus_softc *sc = device_private(self);
|
|
|
|
pmf_device_deregister(self);
|
|
acpi_deregister_notify(sc->sc_devnode);
|
|
|
|
callout_halt(&sc->sc_callout, NULL);
|
|
callout_destroy(&sc->sc_callout);
|
|
|
|
mutex_destroy(&sc->sc_i2c_mutex);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
acpi_smbus_poll_alert(ACPI_HANDLE hdl, int *alert)
|
|
{
|
|
struct SMB_INFO *info;
|
|
ACPI_BUFFER smi_buf;
|
|
ACPI_OBJECT *e, *p;
|
|
ACPI_STATUS rv;
|
|
|
|
/*
|
|
* Retrieve polling interval for SMBus Alerts.
|
|
*/
|
|
rv = acpi_eval_struct(hdl, "_SBI", &smi_buf);
|
|
|
|
if (ACPI_FAILURE(rv))
|
|
return 0;
|
|
|
|
p = smi_buf.Pointer;
|
|
|
|
if (p->Type != ACPI_TYPE_PACKAGE) {
|
|
rv = AE_TYPE;
|
|
goto out;
|
|
}
|
|
|
|
if (p->Package.Count == 0) {
|
|
rv = AE_LIMIT;
|
|
goto out;
|
|
}
|
|
|
|
e = p->Package.Elements;
|
|
|
|
if (e[0].Type != ACPI_TYPE_INTEGER) {
|
|
rv = AE_TYPE;
|
|
goto out;
|
|
}
|
|
|
|
/* Verify CMI version. */
|
|
if (e[0].Integer.Value != 0x10) {
|
|
rv = AE_SUPPORT;
|
|
goto out;
|
|
}
|
|
|
|
if (alert != NULL) {
|
|
|
|
if (p->Package.Count < 2)
|
|
goto out;
|
|
|
|
if (e[1].Type != ACPI_TYPE_BUFFER)
|
|
goto out;
|
|
|
|
info = (struct SMB_INFO *)(e[1].Buffer.Pointer);
|
|
*alert = info->poll_int;
|
|
}
|
|
|
|
out:
|
|
if (smi_buf.Pointer != NULL)
|
|
ACPI_FREE(smi_buf.Pointer);
|
|
|
|
return (ACPI_FAILURE(rv)) ? 0 : 1;
|
|
}
|
|
|
|
static int
|
|
acpi_smbus_acquire_bus(void *cookie, int flags)
|
|
{
|
|
struct acpi_smbus_softc *sc = cookie;
|
|
|
|
mutex_enter(&sc->sc_i2c_mutex);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
acpi_smbus_release_bus(void *cookie, int flags)
|
|
{
|
|
struct acpi_smbus_softc *sc = cookie;
|
|
|
|
mutex_exit(&sc->sc_i2c_mutex);
|
|
}
|
|
static int
|
|
acpi_smbus_exec(void *cookie, i2c_op_t op, i2c_addr_t addr,
|
|
const void *cmdbuf, size_t cmdlen, void *buf, size_t len, int flags)
|
|
{
|
|
struct acpi_smbus_softc *sc = cookie;
|
|
const uint8_t *c = cmdbuf;
|
|
uint8_t *b = buf, *xb;
|
|
const char *path;
|
|
ACPI_OBJECT_LIST args;
|
|
ACPI_OBJECT arg[5];
|
|
ACPI_OBJECT *p, *e;
|
|
ACPI_BUFFER smbuf;
|
|
ACPI_STATUS rv;
|
|
int i, r, xlen;
|
|
|
|
/*
|
|
* arg[0] : protocol
|
|
* arg[1] : slave address
|
|
* arg[2] : command
|
|
* arg[3] : data length
|
|
* arg[4] : data
|
|
*/
|
|
for (i = r = 0; i < __arraycount(arg); i++)
|
|
arg[i].Type = ACPI_TYPE_INTEGER;
|
|
|
|
args.Pointer = arg;
|
|
|
|
smbuf.Pointer = NULL;
|
|
smbuf.Length = ACPI_ALLOCATE_LOCAL_BUFFER;
|
|
|
|
arg[1].Integer.Value = addr;
|
|
|
|
if (I2C_OP_READ_P(op)) {
|
|
|
|
path = "_SBR";
|
|
args.Count = 3;
|
|
|
|
switch (len) {
|
|
|
|
case 0:
|
|
arg[0].Integer.Value = (cmdlen != 0) ?
|
|
ACPI_SMBUS_RCV_BYTE : ACPI_SMBUS_RD_QUICK;
|
|
|
|
arg[2].Integer.Value = 0;
|
|
break;
|
|
|
|
case 1:
|
|
arg[0].Integer.Value = ACPI_SMBUS_RD_BYTE;
|
|
arg[2].Integer.Value = *c;
|
|
break;
|
|
|
|
case 2:
|
|
arg[0].Integer.Value = ACPI_SMBUS_RD_WORD;
|
|
arg[2].Integer.Value = *c;
|
|
break;
|
|
|
|
default:
|
|
arg[0].Integer.Value = ACPI_SMBUS_RD_BLOCK;
|
|
arg[2].Integer.Value = *c;
|
|
break;
|
|
}
|
|
|
|
} else {
|
|
|
|
path = "_SBW";
|
|
args.Count = 5;
|
|
|
|
arg[3].Integer.Value = len;
|
|
|
|
switch (len) {
|
|
|
|
case 0:
|
|
if (cmdlen == 0) {
|
|
arg[2].Integer.Value = 0;
|
|
arg[0].Integer.Value = ACPI_SMBUS_WR_QUICK;
|
|
} else {
|
|
arg[2].Integer.Value = *c;
|
|
arg[0].Integer.Value = ACPI_SMBUS_SND_BYTE;
|
|
}
|
|
|
|
arg[4].Integer.Value = 0;
|
|
break;
|
|
|
|
case 1:
|
|
arg[0].Integer.Value = ACPI_SMBUS_WR_BYTE;
|
|
arg[2].Integer.Value = *c;
|
|
arg[4].Integer.Value = *b;
|
|
break;
|
|
|
|
case 2:
|
|
arg[0].Integer.Value = ACPI_SMBUS_WR_WORD;
|
|
arg[2].Integer.Value = *c;
|
|
arg[4].Integer.Value = *b++;
|
|
arg[4].Integer.Value += (*b--) << 8;
|
|
break;
|
|
|
|
default:
|
|
arg[0].Integer.Value = ACPI_SMBUS_WR_BLOCK;
|
|
arg[2].Integer.Value = *c;
|
|
arg[4].Type = ACPI_TYPE_BUFFER;
|
|
arg[4].Buffer.Pointer = buf;
|
|
arg[4].Buffer.Length = (len < 32) ? len : 32;
|
|
break;
|
|
}
|
|
}
|
|
|
|
rv = AcpiEvaluateObject(sc->sc_devnode->ad_handle, path, &args,&smbuf);
|
|
|
|
if (ACPI_FAILURE(rv))
|
|
goto out;
|
|
|
|
p = smbuf.Pointer;
|
|
|
|
if (p->Type != ACPI_TYPE_PACKAGE) {
|
|
rv = AE_TYPE;
|
|
goto out;
|
|
}
|
|
|
|
if (p->Package.Count < 1) {
|
|
rv = AE_LIMIT;
|
|
goto out;
|
|
}
|
|
|
|
e = p->Package.Elements;
|
|
|
|
if (e->Type != ACPI_TYPE_INTEGER) {
|
|
rv = AE_TYPE;
|
|
goto out;
|
|
}
|
|
|
|
ACPI_DEBUG_PRINT((ACPI_DB_DEBUG_OBJECT,
|
|
"return status: %"PRIu64"\n", e[0].Integer.Value));
|
|
|
|
if (e[0].Integer.Value != 0) {
|
|
rv = AE_BAD_VALUE;
|
|
goto out;
|
|
}
|
|
|
|
/*
|
|
* For read operations, copy data to user buffer.
|
|
*/
|
|
if (I2C_OP_READ_P(op)) {
|
|
|
|
if (p->Package.Count < 3) {
|
|
rv = AE_LIMIT;
|
|
goto out;
|
|
}
|
|
|
|
if (e[1].Type != ACPI_TYPE_INTEGER) {
|
|
rv = AE_TYPE;
|
|
goto out;
|
|
}
|
|
|
|
xlen = e[1].Integer.Value;
|
|
|
|
if (xlen > len)
|
|
xlen = len;
|
|
|
|
switch (e[2].Type) {
|
|
|
|
case ACPI_TYPE_BUFFER:
|
|
|
|
if (xlen == 0) {
|
|
rv = AE_LIMIT;
|
|
goto out;
|
|
}
|
|
|
|
xb = e[2].Buffer.Pointer;
|
|
|
|
if (xb == NULL) {
|
|
rv = AE_NULL_OBJECT;
|
|
goto out;
|
|
}
|
|
|
|
(void)memcpy(b, xb, xlen);
|
|
break;
|
|
|
|
case ACPI_TYPE_INTEGER:
|
|
|
|
if (xlen > 0)
|
|
*b++ = e[2].Integer.Value & 0xff;
|
|
|
|
if (xlen > 1)
|
|
*b = e[2].Integer.Value >> 8;
|
|
|
|
break;
|
|
|
|
default:
|
|
rv = AE_TYPE;
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
out:
|
|
if (smbuf.Pointer != NULL)
|
|
ACPI_FREE(smbuf.Pointer);
|
|
|
|
if (ACPI_SUCCESS(rv))
|
|
return 0;
|
|
|
|
ACPI_DEBUG_PRINT((ACPI_DB_DEBUG_OBJECT, "failed to "
|
|
"evaluate %s: %s\n", path, AcpiFormatException(rv)));
|
|
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* Whether triggered by periodic polling or a Notify(),
|
|
* retrieve all pending SMBus device alerts.
|
|
*/
|
|
static void
|
|
acpi_smbus_alerts(struct acpi_smbus_softc *sc)
|
|
{
|
|
const ACPI_HANDLE hdl = sc->sc_devnode->ad_handle;
|
|
ACPI_OBJECT *e, *p;
|
|
ACPI_BUFFER alert;
|
|
ACPI_STATUS rv;
|
|
int status = 0;
|
|
uint8_t addr;
|
|
|
|
do {
|
|
rv = acpi_eval_struct(hdl, "_SBA", &alert);
|
|
|
|
if (ACPI_FAILURE(rv)) {
|
|
status = 1;
|
|
goto done;
|
|
}
|
|
|
|
p = alert.Pointer;
|
|
|
|
if (p->Type == ACPI_TYPE_PACKAGE && p->Package.Count >= 2) {
|
|
|
|
status = 1;
|
|
|
|
e = p->Package.Elements;
|
|
|
|
if (e[0].Type == ACPI_TYPE_INTEGER)
|
|
status = e[0].Integer.Value;
|
|
|
|
if (status == 0 && e[1].Type == ACPI_TYPE_INTEGER) {
|
|
addr = e[1].Integer.Value;
|
|
|
|
aprint_debug_dev(sc->sc_dv,
|
|
"alert for 0x%x\n", addr);
|
|
|
|
(void)iic_smbus_intr(&sc->sc_i2c_tag);
|
|
}
|
|
}
|
|
done:
|
|
if (alert.Pointer != NULL)
|
|
ACPI_FREE(alert.Pointer);
|
|
|
|
} while (status == 0);
|
|
}
|
|
|
|
static void
|
|
acpi_smbus_tick(void *opaque)
|
|
{
|
|
device_t dv = opaque;
|
|
struct acpi_smbus_softc *sc = device_private(dv);
|
|
|
|
acpi_smbus_alerts(sc);
|
|
|
|
callout_schedule(&sc->sc_callout, sc->sc_poll_alert * hz);
|
|
}
|
|
|
|
static void
|
|
acpi_smbus_notify_handler(ACPI_HANDLE hdl, uint32_t notify, void *opaque)
|
|
{
|
|
device_t dv = opaque;
|
|
struct acpi_smbus_softc *sc = device_private(dv);
|
|
|
|
aprint_debug_dev(dv, "received notify message 0x%x\n", notify);
|
|
|
|
acpi_smbus_alerts(sc);
|
|
}
|