#include "opt_acpi.h"
#include <sys/param.h>
#include <sys/bus.h>
#include <sys/kernel.h>
#include <sys/module.h>
#include <sys/sysctl.h>
#include <contrib/dev/acpica/include/acpi.h>
#include <contrib/dev/acpica/include/accommon.h>
#include <dev/acpica/acpivar.h>
#include <dev/backlight/backlight.h>
#include "backlight_if.h"
#define _COMPONENT ACPI_OEM
ACPI_MODULE_NAME("system76")
static char *system76_ids[] = { "17761776", NULL };
ACPI_SERIAL_DECL(system76, "System76 ACPI management");
struct acpi_ctrl {
int val;
bool exists;
};
struct acpi_system76_softc {
device_t dev;
ACPI_HANDLE handle;
struct acpi_ctrl kbb,
kbc,
bctl,
bcth;
struct sysctl_ctx_list sysctl_ctx;
struct sysctl_oid *sysctl_tree;
struct cdev *kbb_bkl;
uint8_t backlight_level;
};
static int acpi_system76_probe(device_t);
static int acpi_system76_attach(device_t);
static int acpi_system76_detach(device_t);
static int acpi_system76_suspend(device_t);
static int acpi_system76_resume(device_t);
static int acpi_system76_shutdown(device_t);
static void acpi_system76_init(struct acpi_system76_softc *);
static struct acpi_ctrl *
acpi_system76_ctrl_map(struct acpi_system76_softc *, int);
static int acpi_system76_update(struct acpi_system76_softc *, int, bool);
static int acpi_system76_sysctl_handler(SYSCTL_HANDLER_ARGS);
static void acpi_system76_notify_handler(ACPI_HANDLE, uint32_t, void *);
static void acpi_system76_check(struct acpi_system76_softc *);
static int acpi_system76_backlight_update_status(device_t dev,
struct backlight_props *props);
static int acpi_system76_backlight_get_status(device_t dev,
struct backlight_props *props);
static int acpi_system76_backlight_get_info(device_t dev,
struct backlight_info *info);
enum {
S76_CTRL_KBB = 1,
S76_CTRL_KBC = 2,
S76_CTRL_BCTL = 3,
S76_CTRL_BCTH = 4,
};
#define S76_CTRL_MAX 5
struct s76_ctrl_table {
char *name;
char *get_method;
#define S76_CTRL_GKBB "\\_SB.S76D.GKBB"
#define S76_CTRL_GKBC "\\_SB.S76D.GKBC"
#define S76_CTRL_GBCT "\\_SB.PCI0.LPCB.EC0.GBCT"
char *set_method;
#define S76_CTRL_SKBB "\\_SB.S76D.SKBB"
#define S76_CTRL_SKBC "\\_SB.S76D.SKBC"
#define S76_CTRL_SBCT "\\_SB.PCI0.LPCB.EC0.SBCT"
char *desc;
};
static const struct s76_ctrl_table s76_sysctl_table[] = {
[S76_CTRL_KBB] = {
.name = "keyboard_backlight",
.get_method = S76_CTRL_GKBB,
.set_method = S76_CTRL_SKBB,
.desc = "Keyboard Backlight",
},
[S76_CTRL_KBC] = {
.name = "keyboard_color",
.get_method = S76_CTRL_GKBC,
.set_method = S76_CTRL_SKBC,
.desc = "Keyboard Color",
},
[S76_CTRL_BCTL] = {
.name = "battery_charge_min",
.get_method = S76_CTRL_GBCT,
.set_method = S76_CTRL_SBCT,
.desc = "Start charging the battery when this threshold is reached (percentage)",
},
[S76_CTRL_BCTH] = {
.name = "battery_charge_max",
.get_method = S76_CTRL_GBCT,
.set_method = S76_CTRL_SBCT,
.desc = "Stop charging the battery when this threshold is reached (percentage)",
},
};
static device_method_t acpi_system76_methods[] = {
DEVMETHOD(device_probe, acpi_system76_probe),
DEVMETHOD(device_attach, acpi_system76_attach),
DEVMETHOD(device_detach, acpi_system76_detach),
DEVMETHOD(device_suspend, acpi_system76_suspend),
DEVMETHOD(device_resume, acpi_system76_resume),
DEVMETHOD(device_shutdown, acpi_system76_shutdown),
DEVMETHOD(backlight_update_status, acpi_system76_backlight_update_status),
DEVMETHOD(backlight_get_status, acpi_system76_backlight_get_status),
DEVMETHOD(backlight_get_info, acpi_system76_backlight_get_info),
DEVMETHOD_END
};
#define ACPI_NOTIFY_BACKLIGHT_CHANGED 0x80
#define ACPI_NOTIFY_COLOR_TOGGLE 0x81
#define ACPI_NOTIFY_COLOR_DOWN 0x82
#define ACPI_NOTIFY_COLOR_UP 0x83
#define ACPI_NOTIFY_COLOR_CHANGED 0x84
static driver_t acpi_system76_driver = {
"acpi_system76",
acpi_system76_methods,
sizeof(struct acpi_system76_softc)
};
static const uint32_t acpi_system76_backlight_levels[] = {
0, 6, 12, 18, 24, 30, 36, 42,
48, 54, 60, 66, 72, 78, 84, 100
};
static inline uint32_t
devstate_to_backlight(uint32_t val)
{
return (acpi_system76_backlight_levels[val >> 4 & 0xf]);
}
static inline uint32_t
backlight_to_devstate(uint32_t bkl)
{
int i;
uint32_t val;
for (i = 0; i < nitems(acpi_system76_backlight_levels); i++) {
if (bkl < acpi_system76_backlight_levels[i])
break;
}
val = (i - 1) * 16;
if (val > 224)
val = 255;
return (val);
}
static struct acpi_ctrl *
acpi_system76_ctrl_map(struct acpi_system76_softc *sc, int method)
{
switch (method) {
case S76_CTRL_KBB:
return (&sc->kbb);
case S76_CTRL_KBC:
return (&sc->kbc);
case S76_CTRL_BCTL:
return (&sc->bctl);
case S76_CTRL_BCTH:
return (&sc->bcth);
default:
device_printf(sc->dev, "Driver received unknown method\n");
return (NULL);
}
}
static int
acpi_system76_update(struct acpi_system76_softc *sc, int method, bool set)
{
struct acpi_ctrl *ctrl;
ACPI_STATUS status;
ACPI_BUFFER Buf;
ACPI_OBJECT Arg[2], Obj;
ACPI_OBJECT_LIST Args;
ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__);
ACPI_SERIAL_ASSERT(system76);
if ((ctrl = acpi_system76_ctrl_map(sc, method)) == NULL)
return (EINVAL);
switch (method) {
case S76_CTRL_BCTL:
case S76_CTRL_BCTH:
Arg[0].Type = ACPI_TYPE_INTEGER;
Arg[0].Integer.Value = method == S76_CTRL_BCTH ? 1 : 0;
Args.Count = set ? 2 : 1;
Args.Pointer = Arg;
Buf.Length = sizeof(Obj);
Buf.Pointer = &Obj;
if (set) {
Arg[1].Type = ACPI_TYPE_INTEGER;
Arg[1].Integer.Value = ctrl->val;
status = AcpiEvaluateObject(sc->handle,
s76_sysctl_table[method].set_method, &Args, &Buf);
} else {
status = AcpiEvaluateObject(sc->handle,
s76_sysctl_table[method].get_method, &Args, &Buf);
if (ACPI_SUCCESS(status) &&
Obj.Type == ACPI_TYPE_INTEGER)
ctrl->val = Obj.Integer.Value;
}
break;
case S76_CTRL_KBB:
case S76_CTRL_KBC:
if (set)
status = acpi_SetInteger(sc->handle, s76_sysctl_table[method].set_method,
ctrl->val);
else
status = acpi_GetInteger(sc->handle, s76_sysctl_table[method].get_method,
&ctrl->val);
break;
}
if (ACPI_FAILURE(status)) {
device_printf(sc->dev, "Couldn't query method (%s)\n",
s76_sysctl_table[method].name);
return (status);
}
return (0);
}
static void
acpi_system76_notify_update(void *arg)
{
struct acpi_system76_softc *sc;
int method;
ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__);
sc = (struct acpi_system76_softc *)device_get_softc(arg);
ACPI_SERIAL_BEGIN(system76);
for (method = 1; method < S76_CTRL_MAX; method++) {
if (method == S76_CTRL_BCTL ||
method == S76_CTRL_BCTH)
continue;
acpi_system76_update(sc, method, false);
}
ACPI_SERIAL_END(system76);
if (sc->kbb_bkl != NULL)
sc->backlight_level = devstate_to_backlight(sc->kbb.val);
}
static void
acpi_system76_check(struct acpi_system76_softc *sc)
{
struct acpi_ctrl *ctrl;
int method;
ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__);
ACPI_SERIAL_ASSERT(system76);
for (method = 1; method < S76_CTRL_MAX; method++) {
if ((ctrl = acpi_system76_ctrl_map(sc, method)) == NULL)
continue;
if (method == S76_CTRL_BCTL ||
method == S76_CTRL_BCTH) {
ctrl->exists = true;
acpi_system76_update(sc, method, false);
continue;
}
if (ACPI_FAILURE(acpi_GetInteger(sc->handle,
s76_sysctl_table[method].get_method, &ctrl->val))) {
ctrl->exists = false;
device_printf(sc->dev, "Driver can't control %s\n",
s76_sysctl_table[method].desc);
} else {
ctrl->exists = true;
acpi_system76_update(sc, method, false);
}
}
}
static void
acpi_system76_notify_handler(ACPI_HANDLE handle, uint32_t notify, void *ctx)
{
ACPI_FUNCTION_TRACE_U32((char *)(uintptr_t)__func__, notify);
switch (notify) {
case ACPI_NOTIFY_BACKLIGHT_CHANGED:
case ACPI_NOTIFY_COLOR_TOGGLE:
case ACPI_NOTIFY_COLOR_DOWN:
case ACPI_NOTIFY_COLOR_UP:
case ACPI_NOTIFY_COLOR_CHANGED:
AcpiOsExecute(OSL_NOTIFY_HANDLER,
acpi_system76_notify_update, ctx);
break;
default:
break;
}
}
static int
acpi_system76_sysctl_handler(SYSCTL_HANDLER_ARGS)
{
struct acpi_ctrl *ctrl, *ctrl_cmp;
struct acpi_system76_softc *sc;
int val, method, error;
bool update;
ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__);
sc = (struct acpi_system76_softc *)oidp->oid_arg1;
method = oidp->oid_arg2;
if ((ctrl = acpi_system76_ctrl_map(sc, method)) == NULL)
return (EINVAL);
val = ctrl->val;
error = sysctl_handle_int(oidp, &val, 0, req);
if (error != 0) {
device_printf(sc->dev, "Driver query failed\n");
return (error);
}
if (req->newptr == NULL) {
if (method != S76_CTRL_BCTL && method != S76_CTRL_BCTH)
return (error);
update = false;
} else {
switch (method) {
case S76_CTRL_KBB:
if (val > UINT8_MAX || val < 0)
return (EINVAL);
if (sc->kbb_bkl != NULL)
sc->backlight_level = devstate_to_backlight(val);
break;
case S76_CTRL_KBC:
if (val >= (1 << 24) || val < 0)
return (EINVAL);
break;
case S76_CTRL_BCTL:
if ((ctrl_cmp = acpi_system76_ctrl_map(sc, S76_CTRL_BCTH)) == NULL)
return (EINVAL);
if (val > 100 || val < 0 || val >= ctrl_cmp->val)
return (EINVAL);
break;
case S76_CTRL_BCTH:
if ((ctrl_cmp = acpi_system76_ctrl_map(sc, S76_CTRL_BCTL)) == NULL)
return (EINVAL);
if (val > 100 || val < 0 || val <= ctrl_cmp->val)
return (EINVAL);
break;
}
ctrl->val = val;
update = true;
}
ACPI_SERIAL_BEGIN(system76);
error = acpi_system76_update(sc, method, update);
ACPI_SERIAL_END(system76);
return (error);
}
static void
acpi_system76_init(struct acpi_system76_softc *sc)
{
struct acpi_softc *acpi_sc;
struct acpi_ctrl *ctrl;
uint32_t method;
ACPI_SERIAL_ASSERT(system76);
acpi_system76_check(sc);
acpi_sc = acpi_device_get_parent_softc(sc->dev);
sysctl_ctx_init(&sc->sysctl_ctx);
sc->sysctl_tree = SYSCTL_ADD_NODE(&sc->sysctl_ctx,
SYSCTL_CHILDREN(acpi_sc->acpi_sysctl_tree), OID_AUTO, "s76",
CTLFLAG_RD | CTLFLAG_MPSAFE, 0, "system76 control");
for (method = 1; method < S76_CTRL_MAX; method++) {
if ((ctrl = acpi_system76_ctrl_map(sc, method)) == NULL)
continue;
if (!ctrl->exists)
continue;
if (method == S76_CTRL_KBB) {
sc->kbb_bkl = backlight_register("system76_keyboard", sc->dev);
if (sc->kbb_bkl == NULL)
device_printf(sc->dev, "Can not register backlight\n");
else
sc->backlight_level = devstate_to_backlight(sc->kbb.val);
}
SYSCTL_ADD_PROC(&sc->sysctl_ctx,
SYSCTL_CHILDREN(sc->sysctl_tree), OID_AUTO, s76_sysctl_table[method].name,
CTLTYPE_UINT | CTLFLAG_RW | CTLFLAG_ANYBODY | CTLFLAG_MPSAFE,
sc, method, acpi_system76_sysctl_handler, "IU", s76_sysctl_table[method].desc);
}
}
static int
acpi_system76_backlight_update_status(device_t dev, struct backlight_props
*props)
{
struct acpi_system76_softc *sc;
sc = device_get_softc(dev);
if (sc->kbb.val != backlight_to_devstate(props->brightness)) {
sc->kbb.val = backlight_to_devstate(props->brightness);
acpi_system76_update(sc, S76_CTRL_KBB, true);
}
sc->backlight_level = props->brightness;
return (0);
}
static int
acpi_system76_backlight_get_status(device_t dev, struct backlight_props *props)
{
struct acpi_system76_softc *sc;
sc = device_get_softc(dev);
props->brightness = sc->backlight_level;
props->nlevels = nitems(acpi_system76_backlight_levels);
memcpy(props->levels, acpi_system76_backlight_levels,
sizeof(acpi_system76_backlight_levels));
return (0);
}
static int
acpi_system76_backlight_get_info(device_t dev, struct backlight_info *info)
{
info->type = BACKLIGHT_TYPE_KEYBOARD;
strlcpy(info->name, "System76 Keyboard", BACKLIGHTMAXNAMELENGTH);
return (0);
}
static int
acpi_system76_attach(device_t dev)
{
struct acpi_system76_softc *sc;
ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__);
sc = device_get_softc(dev);
sc->dev = dev;
sc->handle = acpi_get_handle(dev);
AcpiInstallNotifyHandler(sc->handle, ACPI_DEVICE_NOTIFY,
acpi_system76_notify_handler, dev);
ACPI_SERIAL_BEGIN(system76);
acpi_system76_init(sc);
ACPI_SERIAL_END(system76);
return (0);
}
static int
acpi_system76_detach(device_t dev)
{
struct acpi_system76_softc *sc;
sc = device_get_softc(dev);
if (sysctl_ctx_free(&sc->sysctl_ctx) != 0)
return (EBUSY);
AcpiRemoveNotifyHandler(sc->handle, ACPI_SYSTEM_NOTIFY,
acpi_system76_notify_handler);
if (sc->kbb_bkl != NULL)
backlight_destroy(sc->kbb_bkl);
return (0);
}
static int
acpi_system76_suspend(device_t dev)
{
struct acpi_system76_softc *sc;
struct acpi_ctrl *ctrl;
sc = device_get_softc(dev);
if ((ctrl = acpi_system76_ctrl_map(sc, S76_CTRL_KBB)) != NULL) {
ctrl->val = 0;
acpi_system76_update(sc, S76_CTRL_KBB, true);
}
return (0);
}
static int
acpi_system76_resume(device_t dev)
{
struct acpi_system76_softc *sc;
struct acpi_ctrl *ctrl;
sc = device_get_softc(dev);
if ((ctrl = acpi_system76_ctrl_map(sc, S76_CTRL_KBB)) != NULL) {
ctrl->val = backlight_to_devstate(sc->backlight_level);
acpi_system76_update(sc, S76_CTRL_KBB, true);
}
return (0);
}
static int
acpi_system76_shutdown(device_t dev)
{
return (acpi_system76_detach(dev));
}
static int
acpi_system76_probe(device_t dev)
{
int rv;
if (acpi_disabled("system76") || device_get_unit(dev) > 1)
return (ENXIO);
rv = ACPI_ID_PROBE(device_get_parent(dev), dev, system76_ids, NULL);
if (rv > 0) {
return (rv);
}
return (BUS_PROBE_VENDOR);
}
DRIVER_MODULE(acpi_system76, acpi, acpi_system76_driver, 0, 0);
MODULE_VERSION(acpi_system76, 1);
MODULE_DEPEND(acpi_system76, acpi, 1, 1, 1);
MODULE_DEPEND(acpi_system76, backlight, 1, 1, 1);