#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: vald_acpi.c,v 1.8 2021/12/07 21:37:36 andvar Exp $");
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/device.h>
#include <dev/acpi/acpica.h>
#include <dev/acpi/acpireg.h>
#include <dev/acpi/acpivar.h>
#define _COMPONENT ACPI_RESOURCE_COMPONENT
ACPI_MODULE_NAME ("vald_acpi")
#define GHCI_WORDS 6
#define GHCI_FIFO_EMPTY 0x8c00
#define GHCI_NOT_SUPPORT 0x8000
#define GHCI_BACKLIGHT 0x0002
#define GHCI_ACADAPTOR 0x0003
#define GHCI_FAN 0x0004
#define GHCI_SYSTEM_EVENT_FIFO 0x0016
#define GHCI_DISPLAY_DEVICE 0x001C
#define GHCI_HOTKEY_EVENT 0x001E
#define GHCI_ON 0x0001
#define GHCI_OFF 0x0000
#define GHCI_ENABLE 0x0001
#define GHCI_DISABLE 0x0000
#define GHCI_CRT 0x0002
#define GHCI_LCD 0x0001
struct vald_acpi_softc {
device_t sc_dev;
struct acpi_devnode *sc_node;
int sc_flags;
ACPI_HANDLE lcd_handle;
int *lcd_level;
int lcd_num;
int lcd_index;
ACPI_INTEGER sc_ac_status;
};
static const struct device_compatible_entry compat_data[] = {
{ .compat = "TOS6200" },
DEVICE_COMPAT_EOL
};
#define LIBRIGHT_HOLD 0x00
#define LIBRIGHT_UP 0x01
#define LIBRIGHT_DOWN 0x02
static int vald_acpi_match(device_t, cfdata_t, void *);
static void vald_acpi_attach(device_t, device_t, void *);
static void vald_acpi_event(void *);
static void vald_acpi_notify_handler(ACPI_HANDLE, uint32_t, void *);
#define ACPI_NOTIFY_ValdStatusChanged 0x80
static ACPI_STATUS vald_acpi_ghci_get(struct vald_acpi_softc *, uint32_t,
uint32_t *, uint32_t *);
static ACPI_STATUS vald_acpi_ghci_set(struct vald_acpi_softc *, uint32_t,
uint32_t, uint32_t *);
static ACPI_STATUS vald_acpi_libright_get_bus(ACPI_HANDLE, uint32_t,
void *, void **);
static void vald_acpi_libright_get(struct vald_acpi_softc *);
static void vald_acpi_libright_set(struct vald_acpi_softc *, int);
static void vald_acpi_video_switch(struct vald_acpi_softc *);
static void vald_acpi_fan_switch(struct vald_acpi_softc *);
static ACPI_STATUS vald_acpi_bcm_set(ACPI_HANDLE, uint32_t);
static ACPI_STATUS vald_acpi_dssx_set(uint32_t);
CFATTACH_DECL_NEW(vald_acpi, sizeof(struct vald_acpi_softc),
vald_acpi_match, vald_acpi_attach, NULL, NULL);
static int
vald_acpi_match(device_t parent, cfdata_t match, void *aux)
{
struct acpi_attach_args *aa = aux;
return acpi_compatible_match(aa, compat_data);
}
static void
vald_acpi_attach(device_t parent, device_t self, void *aux)
{
struct vald_acpi_softc *sc = device_private(self);
struct acpi_attach_args *aa = aux;
ACPI_STATUS rv;
uint32_t value, result;
aprint_naive(": Toshiba VALD\n");
aprint_normal(": Toshiba VALD\n");
sc->sc_node = aa->aa_node;
sc->sc_dev = self;
rv = acpi_eval_integer(ACPI_ROOT_OBJECT, "\\_SB_.ADP1._PSR",
&sc->sc_ac_status);
if (ACPI_FAILURE(rv))
aprint_error_dev(self, "Unable to evaluate _PSR: %s\n",
AcpiFormatException(rv));
else
aprint_verbose_dev(self, "AC adaptor %sconnected\n",
(sc->sc_ac_status == 0 ? "not ": ""));
rv = vald_acpi_ghci_get(sc, GHCI_BACKLIGHT, &value, &result);
if (ACPI_SUCCESS(rv)) {
if (result != 0)
aprint_error_dev(self,
"can't get backlight status error=%d\n", result);
else
aprint_verbose_dev(self, "LCD backlight %s\n",
((value == GHCI_ON) ? "on" : "off"));
}
rv = vald_acpi_ghci_set(sc, GHCI_SYSTEM_EVENT_FIFO, GHCI_ENABLE,
&result);
if (ACPI_SUCCESS(rv) && result != 0)
aprint_error_dev(self,
"can't enable SystemEventFIFO error=%d\n", result);
rv = vald_acpi_ghci_set(sc, GHCI_HOTKEY_EVENT, GHCI_ENABLE, &result);
if (ACPI_SUCCESS(rv) && result != 0)
aprint_error_dev(self, "can't enable HotkeyEvent error=%d\n",
result);
vald_acpi_event(sc);
vald_acpi_libright_get(sc);
vald_acpi_libright_set(sc, LIBRIGHT_HOLD);
rv = AcpiEvaluateObject(sc->sc_node->ad_handle, "ENAB", NULL, NULL);
if (ACPI_SUCCESS(rv))
(void)acpi_register_notify(sc->sc_node,
vald_acpi_notify_handler);
}
static void
vald_acpi_notify_handler(ACPI_HANDLE handle, uint32_t notify, void *context)
{
struct vald_acpi_softc *sc;
device_t self = context;
sc = device_private(self);
switch (notify) {
case ACPI_NOTIFY_ValdStatusChanged:
(void)AcpiOsExecute(OSL_NOTIFY_HANDLER, vald_acpi_event, sc);
break;
default:
aprint_error_dev(sc->sc_dev,
"unknown notify 0x%02X\n", notify);
break;
}
}
static void
vald_acpi_event(void *arg)
{
struct vald_acpi_softc *sc = arg;
ACPI_STATUS rv;
uint32_t value, result;
while(1) {
rv = vald_acpi_ghci_get(sc, GHCI_SYSTEM_EVENT_FIFO, &value,
&result);
if (ACPI_SUCCESS(rv) && result == 0) {
switch (value) {
case 0x1c3:
break;
case 0x1c2:
vald_acpi_fan_switch(sc);
break;
case 0x1c1:
vald_acpi_libright_set(sc, LIBRIGHT_UP);
break;
case 0x1c0:
vald_acpi_libright_set(sc, LIBRIGHT_DOWN);
break;
case 0x1bf:
vald_acpi_video_switch(sc);
break;
default:
break;
}
}
if (ACPI_FAILURE(rv) || result == GHCI_FIFO_EMPTY)
break;
}
}
static ACPI_STATUS
vald_acpi_ghci_get(struct vald_acpi_softc *sc,
uint32_t reg, uint32_t *value, uint32_t *result)
{
ACPI_STATUS rv;
ACPI_OBJECT Arg[GHCI_WORDS];
ACPI_OBJECT_LIST ArgList;
ACPI_OBJECT *param, *PrtElement;
ACPI_BUFFER buf;
int i;
for (i = 0; i < GHCI_WORDS; i++) {
Arg[i].Type = ACPI_TYPE_INTEGER;
Arg[i].Integer.Value = 0;
}
Arg[0].Integer.Value = 0xfe00;
Arg[1].Integer.Value = reg;
Arg[2].Integer.Value = 0;
ArgList.Count = GHCI_WORDS;
ArgList.Pointer = Arg;
buf.Pointer = NULL;
buf.Length = ACPI_ALLOCATE_LOCAL_BUFFER;
rv = AcpiEvaluateObject(sc->sc_node->ad_handle,
"GHCI", &ArgList, &buf);
if (ACPI_FAILURE(rv)) {
aprint_error_dev(sc->sc_dev, "failed to evaluate GHCI: %s\n",
AcpiFormatException(rv));
return (rv);
}
*result = GHCI_NOT_SUPPORT;
*value = 0;
param = buf.Pointer;
if (param->Type == ACPI_TYPE_PACKAGE) {
PrtElement = param->Package.Elements;
if (PrtElement->Type == ACPI_TYPE_INTEGER)
*result = PrtElement->Integer.Value;
PrtElement++;
PrtElement++;
if (PrtElement->Type == ACPI_TYPE_INTEGER)
*value = PrtElement->Integer.Value;
}
if (buf.Pointer)
ACPI_FREE(buf.Pointer);
return (rv);
}
static ACPI_STATUS
vald_acpi_ghci_set(struct vald_acpi_softc *sc,
uint32_t reg, uint32_t value, uint32_t *result)
{
ACPI_STATUS rv;
ACPI_OBJECT Arg[GHCI_WORDS];
ACPI_OBJECT_LIST ArgList;
ACPI_OBJECT *param, *PrtElement;
ACPI_BUFFER buf;
int i;
for (i = 0; i < GHCI_WORDS; i++) {
Arg[i].Type = ACPI_TYPE_INTEGER;
Arg[i].Integer.Value = 0;
}
Arg[0].Integer.Value = 0xff00;
Arg[1].Integer.Value = reg;
Arg[2].Integer.Value = value;
ArgList.Count = GHCI_WORDS;
ArgList.Pointer = Arg;
buf.Pointer = NULL;
buf.Length = ACPI_ALLOCATE_LOCAL_BUFFER;
rv = AcpiEvaluateObject(sc->sc_node->ad_handle,
"GHCI", &ArgList, &buf);
if (ACPI_FAILURE(rv)) {
aprint_error_dev(sc->sc_dev, "failed to evaluate GHCI: %s\n",
AcpiFormatException(rv));
return (rv);
}
*result = GHCI_NOT_SUPPORT;
param = buf.Pointer;
if (param->Type == ACPI_TYPE_PACKAGE) {
PrtElement = param->Package.Elements;
if (PrtElement->Type == ACPI_TYPE_INTEGER)
*result = PrtElement->Integer.Value;
}
if (buf.Pointer)
ACPI_FREE(buf.Pointer);
return (rv);
}
static ACPI_STATUS
vald_acpi_libright_get_bus(ACPI_HANDLE handle, uint32_t level,
void *context, void **status)
{
struct vald_acpi_softc *sc = context;
ACPI_STATUS rv;
ACPI_BUFFER buf;
ACPI_OBJECT *param, *PrtElement;
int i, *pi;
rv = acpi_eval_struct(handle, "_BCL", &buf);
if (ACPI_FAILURE(rv))
return (AE_OK);
sc->lcd_handle = handle;
param = buf.Pointer;
if (param->Type == ACPI_TYPE_PACKAGE) {
printf("_BCL return: %d packages\n", param->Package.Count);
sc->lcd_num = param->Package.Count;
sc->lcd_level = ACPI_ALLOCATE(sizeof(int) * sc->lcd_num);
if (sc->lcd_level == NULL) {
if (buf.Pointer)
ACPI_FREE(buf.Pointer);
return (AE_NO_MEMORY);
}
PrtElement = param->Package.Elements;
pi = sc->lcd_level;
for (i = 0; i < param->Package.Count; i++) {
if (PrtElement->Type == ACPI_TYPE_INTEGER) {
*pi = (unsigned)PrtElement->Integer.Value;
PrtElement++;
pi++;
}
}
if (sc->sc_ac_status == 1)
sc->lcd_index = sc->lcd_num -1;
else
sc->lcd_index = 3;
#ifdef ACPI_DEBUG
pi = sc->lcd_level;
printf("\t Full Power Level: %d\n", *pi);
printf("\t on Battery Level: %d\n", *(pi+1));
printf("\t Possible Level: ");
for (i = 2;i < sc->lcd_num; i++)
printf(" %d", *(pi+i));
printf("\n");
#endif
}
if (buf.Pointer)
ACPI_FREE(buf.Pointer);
return (AE_OK);
}
static void
vald_acpi_libright_get(struct vald_acpi_softc *sc)
{
ACPI_HANDLE parent;
ACPI_STATUS rv;
aprint_verbose_dev(sc->sc_dev, "get LCD brightness via _BCL\n");
#ifdef ACPI_DEBUG
printf("acpi_libright_get: start\n");
#endif
rv = AcpiGetHandle(ACPI_ROOT_OBJECT, "\\_SB_", &parent);
if (ACPI_FAILURE(rv))
return;
AcpiWalkNamespace(ACPI_TYPE_DEVICE, parent, 100,
vald_acpi_libright_get_bus, NULL, sc, NULL);
}
static void
vald_acpi_libright_set(struct vald_acpi_softc *sc, int UpDown)
{
uint32_t backlight, backlight_new, result, bright;
ACPI_STATUS rv;
int *pi;
if (sc->lcd_handle == NULL)
return;
rv = vald_acpi_ghci_get(sc, GHCI_BACKLIGHT, &backlight, &result);
if (ACPI_FAILURE(rv) || result != 0)
return;
backlight_new = backlight;
if (UpDown == LIBRIGHT_UP) {
if (backlight == 1)
sc->lcd_index++;
else {
backlight_new = 1;
sc->lcd_index = 2;
}
} else if (UpDown == LIBRIGHT_DOWN) {
if ((backlight == 1) && (sc->lcd_index > 2))
sc->lcd_index--;
else {
backlight_new = 0;
sc->lcd_index = 2;
}
}
if (sc->lcd_index < 2)
sc->lcd_index = 2;
if (sc->lcd_index >= sc->lcd_num)
sc->lcd_index = sc->lcd_num - 1;
if (backlight_new != backlight) {
rv = vald_acpi_ghci_set(sc, GHCI_BACKLIGHT, backlight_new,
&result);
if (ACPI_SUCCESS(rv) && result != 0)
aprint_error_dev(sc->sc_dev,
"can't set LCD backlight %s error=%x\n",
((backlight_new == 1) ? "on" : "off"), result);
}
if (backlight_new == 1) {
pi = sc->lcd_level;
bright = *(pi + sc->lcd_index);
rv = vald_acpi_bcm_set(sc->lcd_handle, bright);
if (ACPI_FAILURE(rv))
aprint_error_dev(sc->sc_dev,
"unable to evaluate _BCM: %s\n",
AcpiFormatException(rv));
} else {
bright = 0;
}
#ifdef ACPI_DEBUG
printf("LCD bright");
printf(" %s", ((UpDown == LIBRIGHT_UP) ? "up":""));
printf("%s\n", ((UpDown == LIBRIGHT_DOWN) ? "down":""));
printf("\t acpi_libright_set: Set brightness to %d%%\n", bright);
#endif
}
static void
vald_acpi_video_switch(struct vald_acpi_softc *sc)
{
ACPI_STATUS rv;
uint32_t value, result;
rv = vald_acpi_ghci_get(sc, GHCI_DISPLAY_DEVICE, &value, &result);
if (ACPI_FAILURE(rv))
return;
if (result != 0) {
aprint_error_dev(sc->sc_dev,
"can't get video status error=%x\n", result);
return;
}
#ifdef ACPI_DEBUG
printf("Toggle LCD/CRT\n");
printf("\t Before switch, video status: %s",
(((value & GHCI_LCD) == GHCI_LCD) ? "LCD" : ""));
printf("%s\n", (((value & GHCI_CRT) == GHCI_CRT) ? "CRT": ""));
#endif
if (value & GHCI_LCD) {
value &= ~GHCI_LCD;
value |= GHCI_CRT;
} else if (value & GHCI_CRT){
value &= ~GHCI_CRT;
value |= GHCI_LCD;
}
rv = vald_acpi_dssx_set(value);
if (ACPI_FAILURE(rv))
aprint_error_dev(sc->sc_dev, "unable to evaluate DSSX: %s\n",
AcpiFormatException(rv));
}
static ACPI_STATUS
vald_acpi_bcm_set(ACPI_HANDLE handle, uint32_t bright)
{
ACPI_STATUS rv;
ACPI_OBJECT Arg;
ACPI_OBJECT_LIST ArgList;
ArgList.Count = 1;
ArgList.Pointer = &Arg;
Arg.Type = ACPI_TYPE_INTEGER;
Arg.Integer.Value = bright;
rv = AcpiEvaluateObject(handle, "_BCM", &ArgList, NULL);
return (rv);
}
static ACPI_STATUS
vald_acpi_dssx_set(uint32_t value)
{
return acpi_eval_set_integer(NULL, "\\_SB_.VALX.DSSX", value);
}
static void
vald_acpi_fan_switch(struct vald_acpi_softc *sc)
{
ACPI_STATUS rv;
uint32_t value, result;
rv = vald_acpi_ghci_get(sc, GHCI_FAN, &value, &result);
if (ACPI_FAILURE(rv))
return;
if (result != 0) {
aprint_error_dev(sc->sc_dev, "can't get FAN status error=%d\n",
result);
return;
}
#ifdef ACPI_DEBUG
printf("Toggle FAN on/off\n");
printf("\t Before toggle, FAN status %s\n",
(value == GHCI_OFF ? "off" : "on"));
#endif
if (value == GHCI_OFF)
value = GHCI_ON;
else
value = GHCI_OFF;
rv = vald_acpi_ghci_set(sc, GHCI_FAN, value, &result);
if (ACPI_FAILURE(rv))
return;
if (result != 0) {
aprint_error_dev(sc->sc_dev, "can't set FAN status error=%d\n",
result);
return;
}
#ifdef ACPI_DEBUG
printf("\t After toggle, FAN status %s\n",
(value == GHCI_OFF ? "off" : "on"));
#endif
}