#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: smbus_acpi.c,v 1.18 2022/10/24 10:17:27 riastradh 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")
#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;
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_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 struct device_compatible_entry compat_data[] = {
{ .compat = "SMBUS01" },
DEVICE_COMPAT_EOL
};
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;
int ret;
ret = acpi_compatible_match(aa, compat_data);
if (ret == 0)
return 0;
return acpi_smbus_poll_alert(aa->aa_node->ad_handle, NULL) ? ret : 0;
}
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;
iic_tag_init(&sc->sc_i2c_tag);
sc->sc_i2c_tag.ic_cookie = sc;
sc->sc_i2c_tag.ic_exec = acpi_smbus_exec;
(void)acpi_smbus_poll_alert(aa->aa_node->ad_handle,&sc->sc_poll_alert);
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;
config_found(self, &iba, iicbus_print, CFARGS_NONE);
}
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);
iic_tag_fini(&sc->sc_i2c_tag);
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;
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;
}
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_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;
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;
}
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;
}
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);
}
}
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);
}