/*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2021-2023 Val Packett * Copyright (c) 2023 Vladimir Kondratyev * * 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 "opt_acpi.h" #include "opt_hid.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define HID_DEBUG_VAR atopcase_debug #include #include #include #include #include "backlight_if.h" #include "hid_if.h" #include "atopcase_reg.h" #include "atopcase_var.h" /* * XXX: The Linux driver only supports ACPI GPEs, but we only receive * interrupts in this driver on a MacBookPro 12,1 and 14,1. This is because * Linux responds to _OSI("Darwin") while we don't! * * ACPI GPE is enabled on FreeBSD by addition of following lines to * /boot/loader.conf: * hw.acpi.install_interface="Darwin" * hw.acpi.remove_interface="Windows 2009, Windows 2012" */ 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 no _STA method or if it failed, then assume that * the device is present. */ 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); /* Rearm timer */ 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); /* Rearm GPE */ 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 USB interface exists and is enabled, use USB driver */ 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); } /* Turn SPI off if enabled to force "booted" packet to appear */ 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); } /* * Apple encodes a CS delay in ACPI properties, but * - they're encoded in a non-standard way that predates _DSD, and * - they're only exported if you respond to _OSI(Darwin) which we don't * - because that has more side effects than we're prepared to handle * - Apple makes a Windows driver and Windows is not Darwin * - so presumably that one uses hardcoded values too */ spibus_get_cs_delay(sc->sc_dev, &cs_delay); if (cs_delay == 0) spibus_set_cs_delay(sc->sc_dev, 10); /* Retrieve ACPI _HID */ 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"); /* * On some hardware interrupts are not acked by SPI read for * unknown reasons that leads to interrupt storm due to level * triggering. GPE does not suffer from this problem. * * TODO: Find out what Windows driver does to ack IRQ. */ 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)); } /* * Interrupts does not work at all. It may happen if kernel * erroneously detected stray irq at bus_teardown_intr() and * completelly disabled it after than. * Fetch "booted" packet manually to pass communication check. */ 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[] = { /* Device interface */ 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), /* HID interrupt interface */ 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), /* HID interface */ DEVMETHOD(hid_get_rdesc, atopcase_get_rdesc), DEVMETHOD(hid_set_report, atopcase_set_report), /* Backlight interface */ 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);