Current-Users archive

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index][Old Index]

New ACPI driver: acpismbus(4) - call for testers



At the request of some folks off-list, I have created a driver for ACPI's SMBus Control Method Interface. Basically, this is an acpi-based wrapper to provide device-independant access to i2c/SMBus controllers.
(See spec at http://smbus.org/specs/smbus_cmi10.pdf for further info.)

To see if your system can use this driver, check the output of acpidump for the existence of a Device(SMB0). If you have an SMB device, I'd appreciate it if you could help test this driver!

Testing is fairly simple:

1. Copy the attached file smbus_acpi.c to you src/sys/dev/acpi/
2. Add the following to your kernel configuration file (the first few
   lines will eventually be added to src/sys/dev/acpi/files.acip but
   for now, just put them in your kernel config file)

        # ACPI SMBus controller
        device  acpismbus: i2cbus
        attach  acpismbus at acpinodebus
        file    dev/acpi/smbus_acpi.c           acpismbus

        acpismbus* at acpi?
        iic* at acpismbus?

3. Build and boot the kernel.

Note that it is not currently possible for "native" i2c controllers' drivers to tell that acpi has "claimed" a device. So if you have a controller which is already supported by a native driver, you'll need to disable the native driver. Either remove it from your kernel, or use userconf at boot time (boot -c from the loader).

Please let me know of any issues you may find. It would be helpful to include your acpidump output with any bug reports!


-------------------------------------------------------------------------
|   Paul Goyette   | PGP DSS Key fingerprint: |  E-mail addresses:      |
| Customer Service | FA29 0E3B 35AF E8AE 6651 |  paul at whooppee.com   |
| Network Engineer | 0786 F758 55DE 53BA 7731 | pgoyette at juniper.net |
| Kernel Developer |                          | pgoyette at netbsd.org  |
-------------------------------------------------------------------------
/* $NetBSD$ */

/*-
 * 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$");

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/errno.h>
#include <sys/ioctl.h>
#include <sys/syslog.h>
#include <sys/device.h>
#include <sys/callout.h>
#include <sys/proc.h>
#include <sys/mutex.h>

#include <dev/acpi/acpica.h>
#include <dev/acpi/acpireg.h>
#include <dev/acpi/acpivar.h>

#include <dev/i2c/i2cvar.h>

#define _COMPONENT          ACPI_SMBUS_COMPONENT
ACPI_MODULE_NAME            ("acpi_smbus")

/*
 * 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_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, 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[] = {
/* XXX
 * XXX The first two of these are legacy IDs and probably won't work.
 * XXX The SMBus CMI spec refers to these as "the SMBus interface
 * XXX defined in the ACPI specification (which relies on a special
 * XXX SMBus operation region)".  They should probably not be used.
 * XXX
 */
        "ACPI0001",     /* SMBus v1.0 */
        "ACPI0005",     /* SMBus v2.0 */
        "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);

/*
 * acpi_smbus_match: autoconf(9) match routine
 */
static int
acpi_smbus_match(device_t parent, cfdata_t match, void *aux)
{
        struct acpi_attach_args *aa = aux;
        int r = 0;
        ACPI_STATUS rv;
        ACPI_BUFFER smi_buf;
        ACPI_OBJECT *e, *p;

        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;

        /* Ensure that device's CMI version is supported */
        smi_buf.Pointer = NULL;
        smi_buf.Length = ACPI_ALLOCATE_LOCAL_BUFFER;
        rv = AcpiEvaluateObject(aa->aa_node->ad_handle, "_SBI", NULL,
                                &smi_buf);
        if (ACPI_FAILURE(rv))
                goto done;

        p = smi_buf.Pointer;
        if (p != NULL && p->Type == ACPI_TYPE_PACKAGE &&
            p->Package.Count >= 1) {
                e = p->Package.Elements;
                if (e[0].Type == ACPI_TYPE_INTEGER &&
                    e[0].Integer.Value == 0x10)
                        r = 1;
        }
done:
        if (smi_buf.Pointer != NULL)
                ACPI_FREE(smi_buf.Pointer);

        return r;
}

/*
 * acpitz_attach: autoconf(9) attach routine
 */
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;
        ACPI_STATUS rv;
        ACPI_BUFFER smi_buf;
        ACPI_OBJECT *e, *p;
        struct SMB_INFO *info;

        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;

        /* Retrieve polling interval for SMBus Alerts */
        smi_buf.Pointer = NULL;
        smi_buf.Length = ACPI_ALLOCATE_LOCAL_BUFFER;
        rv = AcpiEvaluateObject(aa->aa_node->ad_handle, "_SBI", NULL,
                                &smi_buf);
        if (!ACPI_FAILURE(rv)) {
                p = smi_buf.Pointer;
                if (p != NULL && p->Type == ACPI_TYPE_PACKAGE &&
                    p->Package.Count >= 2) {
                        e = p->Package.Elements;
                        if (e[1].Type == ACPI_TYPE_BUFFER) {
                                info = (struct SMB_INFO *)(e[1].Buffer.Pointer);
                                sc->sc_poll_alert = info->poll_int;
                        }
                }
        }
        if (smi_buf.Pointer != NULL)
                ACPI_FREE(smi_buf.Pointer);

        /* Install notify handler if possible */
        rv = AcpiInstallNotifyHandler(sc->sc_devnode->ad_handle,
                ACPI_DEVICE_NOTIFY, acpi_smbus_notify_handler, self);
        if (ACPI_FAILURE(rv)) {
                aprint_error(": unable to install DEVICE NOTIFY handler: %s\n",
                    AcpiFormatException(rv));
                sc->sc_poll_alert = 2;  /* If failed, fall-back to polling */
        }

        callout_init(&sc->sc_callout, 0);
        callout_setfunc(&sc->sc_callout, acpi_smbus_tick, self);

        if (!pmf_device_register(self, NULL, NULL))
                aprint_error(": couldn't establish power handler\n");

        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");

        memset(&iba, 0, sizeof(iba));
        iba.iba_tag = &sc->sc_i2c_tag;
        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);
        ACPI_STATUS rv;
 
        pmf_device_deregister(self);

        callout_halt(&sc->sc_callout, NULL);

        rv = AcpiRemoveNotifyHandler(sc->sc_devnode->ad_handle,
            ACPI_DEVICE_NOTIFY, acpi_smbus_notify_handler);
        if (ACPI_FAILURE(rv)) {
                aprint_error_dev(self,
                    "unable to deregister DEVICE NOTIFY handler: %s\n",
                    AcpiFormatException(rv));
                return EBUSY;
        }

        mutex_destroy(&sc->sc_i2c_mutex);
   
        return 0;
}

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;
        int xlen;
        int r = 0;
        ACPI_BUFFER smbuf;
        ACPI_STATUS rv;
        ACPI_OBJECT_LIST args;
        ACPI_OBJECT arg[5];
        ACPI_OBJECT *p, *e;

        smbuf.Pointer = NULL;
        smbuf.Length = ACPI_ALLOCATE_LOCAL_BUFFER;
        args.Pointer = arg;
        arg[0].Type = ACPI_TYPE_INTEGER;        /* Protocol */
        arg[1].Type = ACPI_TYPE_INTEGER;        /* Slave Addr */
        arg[1].Integer.Value = addr;
        arg[2].Type = ACPI_TYPE_INTEGER;        /* Command */
        if (I2C_OP_READ_P(op)) {
                args.Count = 3;
                if (len == 0) {
                        arg[2].Integer.Value = 0;
                        if (cmdlen == 0)
                                arg[0].Integer.Value = ACPI_SMBUS_RD_QUICK;
                        else
                                arg[0].Integer.Value = ACPI_SMBUS_RCV_BYTE;
                } else
                        arg[2].Integer.Value = *c;
                if (len == 1)
                        arg[0].Integer.Value = ACPI_SMBUS_RD_BYTE;
                else if (len == 2)
                        arg[0].Integer.Value = ACPI_SMBUS_RD_WORD;
                else if (len > 2)
                        arg[0].Integer.Value = ACPI_SMBUS_RD_BLOCK;
                rv = AcpiEvaluateObject(sc->sc_devnode->ad_handle, "_SBR",
                                    &args, &smbuf);
        } else {
                args.Count = 5;
                arg[3].Type = ACPI_TYPE_INTEGER;        /* Data Len */
                arg[3].Integer.Value = len;
                arg[4].Type = ACPI_TYPE_INTEGER;        /* Data */
                if (len == 0) {
                        arg[4].Integer.Value = 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;
                        }
                } else
                        arg[2].Integer.Value = *c;
                if (len == 1) {
                        arg[0].Integer.Value = ACPI_SMBUS_WR_BYTE;
                        arg[4].Integer.Value = *b;
                } else if (len == 2) {
                        arg[0].Integer.Value = ACPI_SMBUS_WR_WORD;
                        arg[4].Integer.Value = *b++;
                        arg[4].Integer.Value += (*b--) << 8;
                } else if (len > 2) {
                        arg[0].Integer.Value = ACPI_SMBUS_WR_BLOCK;
                        arg[4].Type = ACPI_TYPE_BUFFER;
                        arg[4].Buffer.Pointer = buf;
                        arg[4].Buffer.Length = (len < 32?len:32);
                }
                rv = AcpiEvaluateObject(sc->sc_devnode->ad_handle, "_SBW",
                                    &args, &smbuf);
        }
        if (ACPI_FAILURE(rv))
                r = 1;
        else {
                p = smbuf.Pointer;
                if (p == NULL || p->Type != ACPI_TYPE_PACKAGE ||
                    p->Package.Count < 1)
                        r = 1;
                else {
                        e = p->Package.Elements;
                        if (e->Type == ACPI_TYPE_INTEGER)
                                r = e[0].Integer.Value;
                        else
                                r = 1;
                }
                if (r != 0)
                        r = 1;

                /* For read operations, copy data to user buffer */
                if (r == 0 && I2C_OP_READ_P(op)) {
                        if (p->Package.Count >= 3 &&
                            e[1].Type == ACPI_TYPE_INTEGER) {
                                xlen = e[1].Integer.Value;
                                if (xlen > len)
                                        xlen = len;
                                if (xlen != 0 &&
                                    e[2].Type == ACPI_TYPE_BUFFER) {
                                        xb = e[2].Buffer.Pointer;
                                        if (xb != NULL)
                                                memcpy(b, xb, xlen);
                                        else
                                                r = 1;
                                } else if (e[2].Type == ACPI_TYPE_INTEGER) {
                                        if (xlen > 0)
                                                *b++ = e[2].Integer.Value &
                                                        0xff;
                                        if (xlen > 1)
                                                *b = (e[2].Integer.Value >> 8);
                                } else
                                        r = 1;
                        } else
                                r = 1;
                }
        }
        if (smbuf.Pointer)
                ACPI_FREE(smbuf.Pointer);

        return r;
}

/*
 * acpi_smbus_alerts
 *
 * Whether triggered by periodic polling or an AcpiNotify, retrieve
 * all pending SMBus device alerts
 */
static void
acpi_smbus_alerts(struct acpi_smbus_softc *sc)
{
        int status = 0;
        uint8_t slave_addr;
        ACPI_STATUS rv;
        ACPI_BUFFER alert;
        ACPI_OBJECT *e, *p;

        do {
                alert.Pointer = NULL;
                alert.Length = ACPI_ALLOCATE_LOCAL_BUFFER;
                rv = AcpiEvaluateObject(sc->sc_devnode->ad_handle, "_SBA", NULL,
                                        &alert);
                if (ACPI_FAILURE(rv)) {
                        status = 1;
                        goto done;
                }

                p = alert.Pointer;
                if (p != NULL && p->Type == ACPI_TYPE_PACKAGE &&
                    p->Package.Count >= 2) {
                        e = p->Package.Elements;
                        if (e[0].Type == ACPI_TYPE_INTEGER)
                                status = e[0].Integer.Value;
                        else
                                status = 1;
                        if (status == 0x0 && e[1].Type == ACPI_TYPE_INTEGER) {
                                slave_addr = e[1].Integer.Value;
                                aprint_debug_dev(sc->sc_dv, "Alert for 0x%x\n",
                                                 slave_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 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);
}


Home | Main Index | Thread Index | Old Index