#include "opt_acpi.h"
#include "opt_hid.h"
#include <sys/param.h>
#include <sys/bus.h>
#include <sys/kernel.h>
#include <sys/malloc.h>
#include <sys/mutex.h>
#include <sys/module.h>
#include <sys/rman.h>
#include <sys/sx.h>
#include <sys/taskqueue.h>
#include <contrib/dev/acpica/include/acpi.h>
#include <contrib/dev/acpica/include/accommon.h>
#include <contrib/dev/acpica/include/acevents.h>
#include <dev/acpica/acpivar.h>
#include <dev/acpica/acpiio.h>
#include <dev/backlight/backlight.h>
#include <dev/evdev/input.h>
#define HID_DEBUG_VAR atopcase_debug
#include <dev/hid/hid.h>
#include <dev/hid/hidquirk.h>
#include <dev/spibus/spi.h>
#include <dev/spibus/spibusvar.h>
#include "backlight_if.h"
#include "hid_if.h"
#include "atopcase_reg.h"
#include "atopcase_var.h"
static const char *atopcase_ids[] = { "APP000D", NULL };
static device_probe_t atopcase_acpi_probe;
static device_attach_t atopcase_acpi_attach;
static device_detach_t atopcase_acpi_detach;
static device_suspend_t atopcase_acpi_suspend;
static device_resume_t atopcase_acpi_resume;
static bool
acpi_is_atopcase(ACPI_HANDLE handle)
{
const char **ids;
UINT32 sta;
for (ids = atopcase_ids; *ids != NULL; ids++) {
if (acpi_MatchHid(handle, *ids))
break;
}
if (*ids == NULL)
return (false);
if (ACPI_FAILURE(acpi_GetInteger(handle, "_STA", &sta)) ||
ACPI_DEVICE_PRESENT(sta))
return (true);
return (false);
}
static int
atopcase_acpi_set_comm_enabled(struct atopcase_softc *sc, char *prop,
const bool on)
{
ACPI_OBJECT argobj;
ACPI_OBJECT_LIST args;
argobj.Type = ACPI_TYPE_INTEGER;
argobj.Integer.Value = on;
args.Count = 1;
args.Pointer = &argobj;
if (ACPI_FAILURE(
AcpiEvaluateObject(sc->sc_handle, prop, &args, NULL)))
return (ENXIO);
DELAY(100);
return (0);
}
static int
atopcase_acpi_test_comm_enabled(ACPI_HANDLE handle, char *prop, int *enabled)
{
if (ACPI_FAILURE(acpi_GetInteger(handle, prop, enabled)))
return (ENXIO);
return (0);
}
static void
atopcase_acpi_task(void *ctx, int pending __unused)
{
struct atopcase_softc *sc = ctx;
int err = EAGAIN;
DPRINTFN(ATOPCASE_LLEVEL_DEBUG, "Timer event\n");
sx_xlock(&sc->sc_write_sx);
err = atopcase_receive_packet(sc);
sx_xunlock(&sc->sc_write_sx);
taskqueue_enqueue_timeout(sc->sc_tq, &sc->sc_task,
hz / (err == EAGAIN ? 10 : 120));
}
static void
atopcase_acpi_gpe_task(void *ctx)
{
struct atopcase_softc *sc = ctx;
DPRINTFN(ATOPCASE_LLEVEL_DEBUG, "GPE event\n");
sx_xlock(&sc->sc_sx);
(void)atopcase_intr(sc);
sx_xunlock(&sc->sc_sx);
if (ACPI_FAILURE(AcpiFinishGpe(NULL, sc->sc_gpe_bit)))
device_printf(sc->sc_dev, "GPE rearm failed\n");
}
static UINT32
atopcase_acpi_notify(ACPI_HANDLE h __unused, UINT32 notify __unused, void *ctx)
{
AcpiOsExecute(OSL_GPE_HANDLER, atopcase_acpi_gpe_task, ctx);
return (0);
}
static void
atopcase_acpi_intr(void *ctx)
{
struct atopcase_softc *sc = ctx;
DPRINTFN(ATOPCASE_LLEVEL_DEBUG, "Interrupt event\n");
mtx_lock(&sc->sc_mtx);
sc->sc_intr_cnt++;
(void)atopcase_intr(sc);
mtx_unlock(&sc->sc_mtx);
}
static int
atopcase_acpi_probe(device_t dev)
{
ACPI_HANDLE handle;
int usb_enabled;
if (acpi_disabled("atopcase"))
return (ENXIO);
handle = acpi_get_handle(dev);
if (handle == NULL)
return (ENXIO);
if (!acpi_is_atopcase(handle))
return (ENXIO);
if (atopcase_acpi_test_comm_enabled(handle, "UIST", &usb_enabled) == 0
&& usb_enabled != 0)
return (ENXIO);
device_set_desc(dev, "Apple MacBook SPI Topcase");
return (BUS_PROBE_DEFAULT);
}
static int
atopcase_acpi_attach(device_t dev)
{
struct atopcase_softc *sc = device_get_softc(dev);
ACPI_DEVICE_INFO *device_info;
uint32_t cs_delay;
int spi_enabled, err;
sc->sc_dev = dev;
sc->sc_handle = acpi_get_handle(dev);
if (atopcase_acpi_test_comm_enabled(sc->sc_handle, "SIST",
&spi_enabled) != 0) {
device_printf(dev, "can't test SPI communication\n");
return (ENXIO);
}
if (spi_enabled != 0 &&
atopcase_acpi_set_comm_enabled(sc, "SIEN", false) != 0) {
device_printf(dev, "can't disable SPI communication\n");
return (ENXIO);
}
if (atopcase_acpi_set_comm_enabled(sc, "SIEN", true) != 0) {
device_printf(dev, "can't enable SPI communication\n");
return (ENXIO);
}
spibus_get_cs_delay(sc->sc_dev, &cs_delay);
if (cs_delay == 0)
spibus_set_cs_delay(sc->sc_dev, 10);
if (ACPI_FAILURE(AcpiGetObjectInfo(sc->sc_handle, &device_info)))
return (ENXIO);
if (device_info->Valid & ACPI_VALID_HID)
strlcpy(sc->sc_hid, device_info->HardwareId.String,
sizeof(sc->sc_hid));
AcpiOsFree(device_info);
sx_init(&sc->sc_write_sx, "atc_wr");
sx_init(&sc->sc_sx, "atc_sx");
mtx_init(&sc->sc_mtx, device_get_nameunit(dev), NULL, MTX_DEF);
err = ENXIO;
sc->sc_irq_res = bus_alloc_resource_any(sc->sc_dev,
SYS_RES_IRQ, &sc->sc_irq_rid, RF_ACTIVE);
if (sc->sc_irq_res != NULL) {
if (bus_setup_intr(dev, sc->sc_irq_res,
INTR_TYPE_MISC | INTR_MPSAFE, NULL,
atopcase_acpi_intr, sc, &sc->sc_irq_ih) != 0) {
device_printf(dev, "can't setup interrupt handler\n");
goto err;
}
device_printf(dev, "Using interrupts.\n");
pause("atopcase", hz / 5);
DPRINTF("interrupts asserted: %u\n", sc->sc_intr_cnt);
if (sc->sc_intr_cnt > 2 || sc->sc_intr_cnt == 0) {
bus_teardown_intr(dev, sc->sc_irq_res, sc->sc_irq_ih);
sc->sc_irq_ih = NULL;
device_printf(dev, "Interrupt storm detected. "
"Falling back to polling\n");
sc->sc_tq = taskqueue_create("atc_tq", M_WAITOK|M_ZERO,
taskqueue_thread_enqueue, &sc->sc_tq);
TIMEOUT_TASK_INIT(sc->sc_tq, &sc->sc_task, 0,
atopcase_acpi_task, sc);
taskqueue_start_threads(&sc->sc_tq, 1, PI_TTY,
"%s taskq", device_get_nameunit(dev));
}
if (sc->sc_intr_cnt == 0)
atopcase_receive_packet(sc);
} else {
if (bootverbose)
device_printf(dev, "can't allocate IRQ resource\n");
if (ACPI_FAILURE(acpi_GetInteger(sc->sc_handle, "_GPE",
&sc->sc_gpe_bit))) {
device_printf(dev, "can't allocate nor IRQ nor GPE\n");
goto err;
}
if (ACPI_FAILURE(AcpiInstallGpeHandler(NULL, sc->sc_gpe_bit,
ACPI_GPE_LEVEL_TRIGGERED, atopcase_acpi_notify, sc))) {
device_printf(dev, "can't install ACPI GPE handler\n");
goto err;
}
if (ACPI_FAILURE(AcpiEnableGpe(NULL, sc->sc_gpe_bit))) {
device_printf(dev, "can't enable ACPI notification\n");
goto err;
}
device_printf(dev, "Using ACPI GPE.\n");
if (bootverbose)
device_printf(dev, "GPE int %d\n", sc->sc_gpe_bit);
}
err = atopcase_init(sc);
err:
if (err != 0)
atopcase_acpi_detach(dev);
return (err);
}
static int
atopcase_acpi_detach(device_t dev)
{
struct atopcase_softc *sc = device_get_softc(dev);
int err;
err = atopcase_destroy(sc);
if (err != 0)
return (err);
if (sc->sc_irq_ih)
bus_teardown_intr(dev, sc->sc_irq_res, sc->sc_irq_ih);
if (sc->sc_irq_res != NULL)
bus_release_resource(dev, SYS_RES_IRQ,
sc->sc_irq_rid, sc->sc_irq_res);
if (sc->sc_tq != NULL) {
while (taskqueue_cancel_timeout(sc->sc_tq, &sc->sc_task, NULL))
taskqueue_drain_timeout(sc->sc_tq, &sc->sc_task);
taskqueue_free(sc->sc_tq);
}
if (sc->sc_gpe_bit != 0 && ACPI_FAILURE(AcpiRemoveGpeHandler(NULL,
sc->sc_gpe_bit, atopcase_acpi_notify)))
device_printf(dev, "can't remove ACPI GPE handler\n");
if (atopcase_acpi_set_comm_enabled(sc, "SIEN", false) != 0)
device_printf(dev, "can't disable SPI communication\n");
mtx_destroy(&sc->sc_mtx);
sx_destroy(&sc->sc_sx);
sx_destroy(&sc->sc_write_sx);
return (0);
}
static int
atopcase_acpi_suspend(device_t dev)
{
struct atopcase_softc *sc = device_get_softc(dev);
int err;
err = bus_generic_suspend(dev);
if (err)
return (err);
if (sc->sc_gpe_bit != 0)
AcpiDisableGpe(NULL, sc->sc_gpe_bit);
if (sc->sc_tq != NULL)
while (taskqueue_cancel_timeout(sc->sc_tq, &sc->sc_task, NULL))
taskqueue_drain_timeout(sc->sc_tq, &sc->sc_task);
if (atopcase_acpi_set_comm_enabled(sc, "SIEN", false) != 0)
device_printf(dev, "can't disable SPI communication\n");
return (0);
}
static int
atopcase_acpi_resume(device_t dev)
{
struct atopcase_softc *sc = device_get_softc(dev);
if (sc->sc_gpe_bit != 0)
AcpiEnableGpe(NULL, sc->sc_gpe_bit);
if (sc->sc_tq != NULL)
taskqueue_enqueue_timeout(sc->sc_tq, &sc->sc_task, hz / 120);
if (atopcase_acpi_set_comm_enabled(sc, "SIEN", true) != 0) {
device_printf(dev, "can't enable SPI communication\n");
return (ENXIO);
}
return (bus_generic_resume(dev));
}
static device_method_t atopcase_methods[] = {
DEVMETHOD(device_probe, atopcase_acpi_probe),
DEVMETHOD(device_attach, atopcase_acpi_attach),
DEVMETHOD(device_detach, atopcase_acpi_detach),
DEVMETHOD(device_suspend, atopcase_acpi_suspend),
DEVMETHOD(device_resume, atopcase_acpi_resume),
DEVMETHOD(hid_intr_setup, atopcase_intr_setup),
DEVMETHOD(hid_intr_unsetup, atopcase_intr_unsetup),
DEVMETHOD(hid_intr_start, atopcase_intr_start),
DEVMETHOD(hid_intr_stop, atopcase_intr_stop),
DEVMETHOD(hid_intr_poll, atopcase_intr_poll),
DEVMETHOD(hid_get_rdesc, atopcase_get_rdesc),
DEVMETHOD(hid_set_report, atopcase_set_report),
DEVMETHOD(backlight_update_status, atopcase_backlight_update_status),
DEVMETHOD(backlight_get_status, atopcase_backlight_get_status),
DEVMETHOD(backlight_get_info, atopcase_backlight_get_info),
DEVMETHOD_END
};
static driver_t atopcase_driver = {
"atopcase",
atopcase_methods,
sizeof(struct atopcase_softc),
};
DRIVER_MODULE(atopcase, spibus, atopcase_driver, 0, 0);
MODULE_DEPEND(atopcase, acpi, 1, 1, 1);
MODULE_DEPEND(atopcase, backlight, 1, 1, 1);
MODULE_DEPEND(atopcase, hid, 1, 1, 1);
MODULE_DEPEND(atopcase, hidbus, 1, 1, 1);
MODULE_DEPEND(atopcase, spibus, 1, 1, 1);
MODULE_VERSION(atopcase, 1);
SPIBUS_ACPI_PNP_INFO(atopcase_ids);