|
/*- |
|
* SPDX-License-Identifier: BSD-2-Clause |
|
* |
|
* Copyright (c) 2026 The cyan_kbdgpio contributors |
|
* Copyright (c) 2017 Tom Jones <tj@enoti.me> |
|
* Copyright (c) 2016 Mark Kettenis |
|
* |
|
* Portions of the register definitions and controller logic are derived |
|
* from FreeBSD's chvgpio(4), which in turn incorporates the OpenBSD driver. |
|
* The original copyright and permission notices are reproduced in LICENSE. |
|
*/ |
|
|
|
/* |
|
* Deliver an ACPI GpioInt() from a Cherryview INT33FF GPIO controller to |
|
* FreeBSD's existing atkbd instance. This is intentionally a small, |
|
* machine-specific bridge: it claims only the GPIO controller referenced by |
|
* a PNP0303/PNP030B keyboard resource and does not replace the atkbd driver. |
|
*/ |
|
|
|
#include "opt_acpi.h" |
|
|
|
#include <sys/param.h> |
|
#include <sys/systm.h> |
|
#include <sys/bus.h> |
|
#include <sys/kernel.h> |
|
#include <sys/kbio.h> |
|
#include <sys/module.h> |
|
#include <sys/mutex.h> |
|
#include <sys/rman.h> |
|
|
|
#include <machine/bus.h> |
|
#include <machine/resource.h> |
|
|
|
#include <contrib/dev/acpica/include/acpi.h> |
|
|
|
#include <dev/acpica/acpivar.h> |
|
#include <dev/kbd/kbdreg.h> |
|
|
|
#include "acpi_if.h" |
|
#include "bus_if.h" |
|
#include "device_if.h" |
|
|
|
#define CYAN_NINTR 16 |
|
|
|
#define CYAN_INTERRUPT_STATUS 0x0300 |
|
#define CYAN_INTERRUPT_MASK 0x0380 |
|
#define CYAN_PAD_CFG0 0x4400 |
|
|
|
#define CYAN_PAD_CFG0_INTSEL_MASK 0xf0000000U |
|
#define CYAN_PAD_CFG0_INTSEL_SHIFT 28 |
|
#define CYAN_PAD_CFG0_GPIOCFG_MASK (7U << 8) |
|
#define CYAN_PAD_CFG0_GPIOCFG_GPI (2U << 8) |
|
|
|
#define CYAN_PAD_CFG1_INTWAKECFG_MASK 0x00000007U |
|
#define CYAN_PAD_CFG1_INTWAKECFG_FALLING 0x00000001U |
|
#define CYAN_PAD_CFG1_INTWAKECFG_RISING 0x00000002U |
|
#define CYAN_PAD_CFG1_INTWAKECFG_BOTH 0x00000003U |
|
#define CYAN_PAD_CFG1_INTWAKECFG_LEVEL 0x00000004U |
|
#define CYAN_PAD_CFG1_INVRXTX_MASK 0x000000f0U |
|
#define CYAN_PAD_CFG1_INVRXTX_RXDATA 0x00000040U |
|
|
|
#define CYAN_LOCK(sc) mtx_lock_spin(&(sc)->mtx) |
|
#define CYAN_UNLOCK(sc) mtx_unlock_spin(&(sc)->mtx) |
|
|
|
/* Number of valid pads in each 15-pad register group, indexed by _UID. */ |
|
static const u_int cyan_sw_pins[] = { 8, 8, 8, 8, 8, 8, 8 }; |
|
static const u_int cyan_n_pins[] = { 9, 13, 12, 12, 13 }; |
|
static const u_int cyan_e_pins[] = { 12, 12 }; |
|
static const u_int cyan_se_pins[] = { 8, 12, 6, 8, 10, 11 }; |
|
|
|
struct cyan_gpio_intr { |
|
ACPI_HANDLE consumer; |
|
uint16_t pin; |
|
uint8_t triggering; |
|
uint8_t polarity; |
|
}; |
|
|
|
struct cyan_find_ctx { |
|
ACPI_HANDLE controller; |
|
ACPI_HANDLE candidate; |
|
struct cyan_gpio_intr intr; |
|
bool found; |
|
}; |
|
|
|
struct cyan_softc { |
|
device_t dev; |
|
ACPI_HANDLE handle; |
|
struct mtx mtx; |
|
|
|
int mem_rid; |
|
struct resource *mem_res; |
|
int irq_rid; |
|
struct resource *irq_res; |
|
void *irq_cookie; |
|
|
|
const u_int *pins; |
|
u_int ngroups; |
|
struct cyan_gpio_intr gpio; |
|
|
|
keyboard_t *kbd; |
|
u_int line; |
|
bool configured; |
|
|
|
struct intr_config_hook config_hook; |
|
bool hook_established; |
|
}; |
|
|
|
static int cyan_probe(device_t dev); |
|
static int cyan_attach(device_t dev); |
|
static int cyan_detach(device_t dev); |
|
static int cyan_suspend(device_t dev); |
|
static int cyan_resume(device_t dev); |
|
static void cyan_intr(void *arg); |
|
static void cyan_config_hook(void *arg); |
|
|
|
static char *cyan_hids[] = { |
|
"INT33FF", |
|
NULL |
|
}; |
|
|
|
static bool |
|
cyan_is_at_keyboard(ACPI_HANDLE handle) |
|
{ |
|
|
|
return (acpi_MatchHid(handle, "PNP0303") != |
|
ACPI_MATCHHID_NOMATCH || |
|
acpi_MatchHid(handle, "PNP030B") != |
|
ACPI_MATCHHID_NOMATCH); |
|
} |
|
|
|
static ACPI_STATUS |
|
cyan_find_resource(ACPI_RESOURCE *res, void *context) |
|
{ |
|
struct cyan_find_ctx *ctx; |
|
ACPI_RESOURCE_GPIO *gpio; |
|
ACPI_HANDLE controller; |
|
ACPI_STATUS status; |
|
u_int i; |
|
|
|
ctx = context; |
|
if (res->Type != ACPI_RESOURCE_TYPE_GPIO) |
|
return (AE_OK); |
|
gpio = &res->Data.Gpio; |
|
if (gpio->ConnectionType != ACPI_RESOURCE_GPIO_TYPE_INT || |
|
gpio->PinTable == NULL || gpio->PinTableLength == 0 || |
|
gpio->ResourceSource.StringPtr == NULL) |
|
return (AE_OK); |
|
|
|
status = AcpiGetHandle(ctx->candidate, |
|
gpio->ResourceSource.StringPtr, &controller); |
|
if (ACPI_FAILURE(status) || controller != ctx->controller) |
|
return (AE_OK); |
|
|
|
/* One interrupt pin is expected, but accepting the first is sufficient. */ |
|
for (i = 0; i < gpio->PinTableLength; i++) { |
|
ctx->intr.consumer = ctx->candidate; |
|
ctx->intr.pin = gpio->PinTable[i]; |
|
ctx->intr.triggering = gpio->Triggering; |
|
ctx->intr.polarity = gpio->Polarity; |
|
ctx->found = true; |
|
return (AE_CTRL_TERMINATE); |
|
} |
|
return (AE_OK); |
|
} |
|
|
|
static ACPI_STATUS |
|
cyan_find_device(ACPI_HANDLE handle, UINT32 level, void *context, |
|
void **return_value) |
|
{ |
|
struct cyan_find_ctx *ctx; |
|
UINT32 sta; |
|
|
|
(void)level; |
|
(void)return_value; |
|
ctx = context; |
|
if (!ACPI_FAILURE(acpi_GetInteger(handle, "_STA", &sta)) && |
|
!ACPI_DEVICE_PRESENT(sta)) |
|
return (AE_OK); |
|
if (!cyan_is_at_keyboard(handle)) |
|
return (AE_OK); |
|
|
|
ctx->candidate = handle; |
|
(void)AcpiWalkResources(handle, "_CRS", cyan_find_resource, ctx); |
|
return (ctx->found ? AE_CTRL_TERMINATE : AE_OK); |
|
} |
|
|
|
static int |
|
cyan_find_keyboard_gpio(ACPI_HANDLE controller, struct cyan_gpio_intr *intr) |
|
{ |
|
struct cyan_find_ctx ctx; |
|
ACPI_STATUS status; |
|
|
|
bzero(&ctx, sizeof(ctx)); |
|
ctx.controller = controller; |
|
status = AcpiWalkNamespace(ACPI_TYPE_DEVICE, ACPI_ROOT_OBJECT, |
|
ACPI_UINT32_MAX, cyan_find_device, NULL, &ctx, NULL); |
|
if (ctx.found) { |
|
*intr = ctx.intr; |
|
return (0); |
|
} |
|
return (ACPI_FAILURE(status) ? ENXIO : ENOENT); |
|
} |
|
|
|
static int |
|
cyan_select_bank(struct cyan_softc *sc) |
|
{ |
|
UINT32 uid; |
|
ACPI_STATUS status; |
|
|
|
status = acpi_GetInteger(sc->handle, "_UID", &uid); |
|
if (ACPI_FAILURE(status)) |
|
return (ENXIO); |
|
|
|
switch (uid) { |
|
case 1: |
|
sc->pins = cyan_sw_pins; |
|
sc->ngroups = nitems(cyan_sw_pins); |
|
break; |
|
case 2: |
|
sc->pins = cyan_n_pins; |
|
sc->ngroups = nitems(cyan_n_pins); |
|
break; |
|
case 3: |
|
sc->pins = cyan_e_pins; |
|
sc->ngroups = nitems(cyan_e_pins); |
|
break; |
|
case 4: |
|
sc->pins = cyan_se_pins; |
|
sc->ngroups = nitems(cyan_se_pins); |
|
break; |
|
default: |
|
return (ENXIO); |
|
} |
|
return (0); |
|
} |
|
|
|
static int |
|
cyan_valid_pin(struct cyan_softc *sc, u_int pin) |
|
{ |
|
u_int group; |
|
|
|
group = pin / 15; |
|
if (group >= sc->ngroups || pin % 15 >= sc->pins[group]) |
|
return (EINVAL); |
|
return (0); |
|
} |
|
|
|
static bus_size_t |
|
cyan_pad_cfg0_offset(u_int pin) |
|
{ |
|
|
|
return (CYAN_PAD_CFG0 + 1024 * (pin / 15) + 8 * (pin % 15)); |
|
} |
|
|
|
static uint32_t |
|
cyan_read_pad_cfg0(struct cyan_softc *sc, u_int pin) |
|
{ |
|
|
|
return (bus_read_4(sc->mem_res, cyan_pad_cfg0_offset(pin))); |
|
} |
|
|
|
static void |
|
cyan_write_pad_cfg0(struct cyan_softc *sc, u_int pin, uint32_t value) |
|
{ |
|
|
|
bus_write_4(sc->mem_res, cyan_pad_cfg0_offset(pin), value); |
|
} |
|
|
|
static uint32_t |
|
cyan_read_pad_cfg1(struct cyan_softc *sc, u_int pin) |
|
{ |
|
|
|
return (bus_read_4(sc->mem_res, cyan_pad_cfg0_offset(pin) + 4)); |
|
} |
|
|
|
static void |
|
cyan_write_pad_cfg1(struct cyan_softc *sc, u_int pin, uint32_t value) |
|
{ |
|
|
|
bus_write_4(sc->mem_res, cyan_pad_cfg0_offset(pin) + 4, value); |
|
} |
|
|
|
static int |
|
cyan_interrupt_config(const struct cyan_gpio_intr *intr, uint32_t *config) |
|
{ |
|
uint32_t value; |
|
|
|
value = 0; |
|
switch (intr->triggering) { |
|
case ACPI_LEVEL_SENSITIVE: |
|
if (intr->polarity == ACPI_ACTIVE_LOW) |
|
value = CYAN_PAD_CFG1_INTWAKECFG_LEVEL | |
|
CYAN_PAD_CFG1_INVRXTX_RXDATA; |
|
else if (intr->polarity == ACPI_ACTIVE_HIGH) |
|
value = CYAN_PAD_CFG1_INTWAKECFG_LEVEL; |
|
else |
|
return (EINVAL); |
|
break; |
|
case ACPI_EDGE_SENSITIVE: |
|
switch (intr->polarity) { |
|
case ACPI_ACTIVE_LOW: |
|
value = CYAN_PAD_CFG1_INTWAKECFG_FALLING; |
|
break; |
|
case ACPI_ACTIVE_HIGH: |
|
value = CYAN_PAD_CFG1_INTWAKECFG_RISING; |
|
break; |
|
case ACPI_ACTIVE_BOTH: |
|
value = CYAN_PAD_CFG1_INTWAKECFG_BOTH; |
|
break; |
|
default: |
|
return (EINVAL); |
|
} |
|
break; |
|
default: |
|
return (EINVAL); |
|
} |
|
*config = value; |
|
return (0); |
|
} |
|
|
|
/* |
|
* Resolve the already-attached atkbd and program its GPIO route. Giant is |
|
* acquired unconditionally here and released on the same lexical path. |
|
*/ |
|
static int |
|
cyan_configure(struct cyan_softc *sc) |
|
{ |
|
device_t atkbd, atkbdc; |
|
keyboard_t *kbd; |
|
uint32_t config, line, reg; |
|
int error, index, unit; |
|
|
|
error = cyan_interrupt_config(&sc->gpio, &config); |
|
if (error != 0) |
|
return (error); |
|
|
|
mtx_lock(&Giant); |
|
atkbdc = acpi_get_device(sc->gpio.consumer); |
|
if (atkbdc == NULL) { |
|
error = ENXIO; |
|
goto out; |
|
} |
|
atkbd = device_find_child(atkbdc, "atkbd", -1); |
|
unit = atkbd != NULL ? device_get_unit(atkbd) : |
|
device_get_unit(atkbdc); |
|
index = kbd_find_keyboard("atkbd", unit); |
|
if (index < 0 || (kbd = kbd_get_keyboard(index)) == NULL || |
|
!KBD_IS_CONFIGURED(kbd)) { |
|
error = ENXIO; |
|
goto out; |
|
} |
|
|
|
CYAN_LOCK(sc); |
|
if (sc->configured) { |
|
reg = bus_read_4(sc->mem_res, CYAN_INTERRUPT_MASK); |
|
bus_write_4(sc->mem_res, CYAN_INTERRUPT_MASK, |
|
reg & ~(1U << sc->line)); |
|
bus_write_4(sc->mem_res, CYAN_INTERRUPT_STATUS, |
|
1U << sc->line); |
|
} |
|
reg = cyan_read_pad_cfg0(sc, sc->gpio.pin); |
|
line = (reg & CYAN_PAD_CFG0_INTSEL_MASK) >> |
|
CYAN_PAD_CFG0_INTSEL_SHIFT; |
|
KASSERT(line < CYAN_NINTR, ("invalid interrupt line %u", line)); |
|
|
|
/* Preserve the firmware-selected interrupt line and select GPIO input. */ |
|
reg &= ~CYAN_PAD_CFG0_GPIOCFG_MASK; |
|
reg |= CYAN_PAD_CFG0_GPIOCFG_GPI; |
|
cyan_write_pad_cfg0(sc, sc->gpio.pin, reg); |
|
|
|
reg = cyan_read_pad_cfg1(sc, sc->gpio.pin); |
|
reg &= ~(CYAN_PAD_CFG1_INTWAKECFG_MASK | |
|
CYAN_PAD_CFG1_INVRXTX_MASK); |
|
reg |= config; |
|
cyan_write_pad_cfg1(sc, sc->gpio.pin, reg); |
|
|
|
/* Publish the callback before acknowledging and unmasking the line. */ |
|
sc->kbd = kbd; |
|
sc->line = line; |
|
sc->configured = true; |
|
bus_write_4(sc->mem_res, CYAN_INTERRUPT_STATUS, 1U << line); |
|
reg = bus_read_4(sc->mem_res, CYAN_INTERRUPT_MASK); |
|
bus_write_4(sc->mem_res, CYAN_INTERRUPT_MASK, |
|
reg | (1U << line)); |
|
CYAN_UNLOCK(sc); |
|
error = 0; |
|
out: |
|
mtx_unlock(&Giant); |
|
return (error); |
|
} |
|
|
|
static void |
|
cyan_config_hook(void *arg) |
|
{ |
|
struct cyan_softc *sc; |
|
int error; |
|
|
|
sc = arg; |
|
error = cyan_configure(sc); |
|
if (error != 0) |
|
device_printf(sc->dev, |
|
"unable to connect GPIO interrupt to atkbd: %d\n", error); |
|
else |
|
device_printf(sc->dev, |
|
"keyboard GpioInt pin %u routed through line %u\n", |
|
sc->gpio.pin, sc->line); |
|
|
|
/* Keep this as the final operation; detach drains this hook. */ |
|
config_intrhook_disestablish(&sc->config_hook); |
|
} |
|
|
|
static int |
|
cyan_probe(device_t dev) |
|
{ |
|
struct cyan_gpio_intr intr; |
|
int rv; |
|
|
|
if (acpi_disabled("cyan_kbdgpio")) |
|
return (ENXIO); |
|
rv = ACPI_ID_PROBE(device_get_parent(dev), dev, cyan_hids, NULL); |
|
if (rv > 0) |
|
return (rv); |
|
if (cyan_find_keyboard_gpio(acpi_get_handle(dev), &intr) != 0) |
|
return (ENXIO); |
|
|
|
device_set_desc(dev, "Cherryview keyboard GPIO interrupt bridge"); |
|
return (BUS_PROBE_SPECIFIC); |
|
} |
|
|
|
static int |
|
cyan_attach(device_t dev) |
|
{ |
|
struct cyan_softc *sc; |
|
bus_size_t end; |
|
int error; |
|
|
|
sc = device_get_softc(dev); |
|
sc->dev = dev; |
|
sc->handle = acpi_get_handle(dev); |
|
error = cyan_find_keyboard_gpio(sc->handle, &sc->gpio); |
|
if (error != 0) |
|
return (error); |
|
error = cyan_select_bank(sc); |
|
if (error != 0) { |
|
device_printf(dev, "unsupported or missing _UID\n"); |
|
return (error); |
|
} |
|
if (cyan_valid_pin(sc, sc->gpio.pin) != 0) { |
|
device_printf(dev, "invalid GpioInt pin %u\n", sc->gpio.pin); |
|
return (EINVAL); |
|
} |
|
|
|
mtx_init(&sc->mtx, device_get_nameunit(dev), "cyan-kbdgpio", |
|
MTX_SPIN); |
|
sc->mem_rid = 0; |
|
sc->mem_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, |
|
&sc->mem_rid, RF_ACTIVE); |
|
if (sc->mem_res == NULL) { |
|
device_printf(dev, "cannot allocate memory resource\n"); |
|
error = ENOMEM; |
|
goto fail_mtx; |
|
} |
|
end = cyan_pad_cfg0_offset(sc->gpio.pin) + 8; |
|
if (end > rman_get_size(sc->mem_res)) { |
|
device_printf(dev, "GpioInt pin register is outside resource\n"); |
|
error = EINVAL; |
|
goto fail_mem; |
|
} |
|
|
|
/* Prevent a stale status bit from firing before atkbd is resolved. */ |
|
CYAN_LOCK(sc); |
|
bus_write_4(sc->mem_res, CYAN_INTERRUPT_MASK, 0); |
|
bus_write_4(sc->mem_res, CYAN_INTERRUPT_STATUS, 0xffffU); |
|
CYAN_UNLOCK(sc); |
|
|
|
sc->irq_rid = 0; |
|
sc->irq_res = bus_alloc_resource_any(dev, SYS_RES_IRQ, |
|
&sc->irq_rid, RF_ACTIVE); |
|
if (sc->irq_res == NULL) { |
|
device_printf(dev, "cannot allocate interrupt resource\n"); |
|
error = ENOMEM; |
|
goto fail_mem; |
|
} |
|
|
|
/* |
|
* Deliberately omit INTR_MPSAFE. The interrupt framework therefore |
|
* holds Giant while cyan_intr() calls the legacy keyboard stack. |
|
*/ |
|
error = bus_setup_intr(dev, sc->irq_res, INTR_TYPE_TTY, NULL, |
|
cyan_intr, sc, &sc->irq_cookie); |
|
if (error != 0) { |
|
device_printf(dev, "cannot set up parent interrupt: %d\n", error); |
|
goto fail_irq; |
|
} |
|
|
|
sc->config_hook.ich_func = cyan_config_hook; |
|
sc->config_hook.ich_arg = sc; |
|
error = config_intrhook_establish(&sc->config_hook); |
|
if (error == 0) |
|
sc->hook_established = true; |
|
else { |
|
/* A duplicate hook is impossible normally; retain a safe fallback. */ |
|
error = cyan_configure(sc); |
|
if (error != 0) |
|
device_printf(dev, |
|
"unable to connect GPIO interrupt to atkbd: %d\n", |
|
error); |
|
} |
|
return (0); |
|
|
|
fail_irq: |
|
bus_release_resource(dev, SYS_RES_IRQ, sc->irq_rid, sc->irq_res); |
|
fail_mem: |
|
bus_release_resource(dev, SYS_RES_MEMORY, sc->mem_rid, sc->mem_res); |
|
fail_mtx: |
|
mtx_destroy(&sc->mtx); |
|
return (error); |
|
} |
|
|
|
static void |
|
cyan_intr(void *arg) |
|
{ |
|
struct cyan_softc *sc; |
|
keyboard_t *kbd; |
|
uint32_t pending, status; |
|
|
|
sc = arg; |
|
mtx_assert(&Giant, MA_OWNED); |
|
|
|
CYAN_LOCK(sc); |
|
status = bus_read_4(sc->mem_res, CYAN_INTERRUPT_STATUS); |
|
pending = status & bus_read_4(sc->mem_res, CYAN_INTERRUPT_MASK); |
|
if (status != 0) |
|
bus_write_4(sc->mem_res, CYAN_INTERRUPT_STATUS, status); |
|
if (sc->configured && (pending & (1U << sc->line)) != 0) |
|
kbd = sc->kbd; |
|
else |
|
kbd = NULL; |
|
CYAN_UNLOCK(sc); |
|
|
|
/* The callback may enter the console/keyboard stack; do not hold spin. */ |
|
if (kbd != NULL) |
|
kbdd_intr(kbd, NULL); |
|
} |
|
|
|
static int |
|
cyan_suspend(device_t dev) |
|
{ |
|
struct cyan_softc *sc; |
|
uint32_t reg; |
|
|
|
sc = device_get_softc(dev); |
|
mtx_lock(&Giant); |
|
CYAN_LOCK(sc); |
|
if (sc->configured) { |
|
reg = bus_read_4(sc->mem_res, CYAN_INTERRUPT_MASK); |
|
bus_write_4(sc->mem_res, CYAN_INTERRUPT_MASK, |
|
reg & ~(1U << sc->line)); |
|
bus_write_4(sc->mem_res, CYAN_INTERRUPT_STATUS, |
|
1U << sc->line); |
|
} |
|
CYAN_UNLOCK(sc); |
|
mtx_unlock(&Giant); |
|
return (0); |
|
} |
|
|
|
static int |
|
cyan_resume(device_t dev) |
|
{ |
|
struct cyan_softc *sc; |
|
int error; |
|
|
|
sc = device_get_softc(dev); |
|
error = cyan_configure(sc); |
|
if (error != 0) |
|
device_printf(dev, "cannot restore keyboard GpioInt: %d\n", error); |
|
return (error); |
|
} |
|
|
|
static int |
|
cyan_detach(device_t dev) |
|
{ |
|
struct cyan_softc *sc; |
|
|
|
sc = device_get_softc(dev); |
|
if (sc->hook_established) |
|
(void)config_intrhook_drain(&sc->config_hook); |
|
|
|
/* Serialize state removal with the non-MPSAFE interrupt handler. */ |
|
mtx_lock(&Giant); |
|
CYAN_LOCK(sc); |
|
bus_write_4(sc->mem_res, CYAN_INTERRUPT_MASK, 0); |
|
bus_write_4(sc->mem_res, CYAN_INTERRUPT_STATUS, 0xffffU); |
|
sc->configured = false; |
|
sc->kbd = NULL; |
|
CYAN_UNLOCK(sc); |
|
mtx_unlock(&Giant); |
|
|
|
if (sc->irq_cookie != NULL) |
|
bus_teardown_intr(dev, sc->irq_res, sc->irq_cookie); |
|
if (sc->irq_res != NULL) |
|
bus_release_resource(dev, SYS_RES_IRQ, sc->irq_rid, |
|
sc->irq_res); |
|
if (sc->mem_res != NULL) |
|
bus_release_resource(dev, SYS_RES_MEMORY, sc->mem_rid, |
|
sc->mem_res); |
|
mtx_destroy(&sc->mtx); |
|
return (0); |
|
} |
|
|
|
static device_method_t cyan_methods[] = { |
|
DEVMETHOD(device_probe, cyan_probe), |
|
DEVMETHOD(device_attach, cyan_attach), |
|
DEVMETHOD(device_detach, cyan_detach), |
|
DEVMETHOD(device_suspend, cyan_suspend), |
|
DEVMETHOD(device_resume, cyan_resume), |
|
|
|
DEVMETHOD_END |
|
}; |
|
|
|
static driver_t cyan_driver = { |
|
.name = "cyan_kbdgpio", |
|
.methods = cyan_methods, |
|
.size = sizeof(struct cyan_softc), |
|
}; |
|
|
|
DRIVER_MODULE(cyan_kbdgpio, acpi, cyan_driver, NULL, NULL); |
|
MODULE_DEPEND(cyan_kbdgpio, acpi, 1, 1, 1); |
|
MODULE_VERSION(cyan_kbdgpio, 1); |