root/sys/dev/acpi/valz_acpi.c
/*      $NetBSD: valz_acpi.c,v 1.9 2021/01/29 15:49:55 thorpej Exp $    */

/*-
 * Copyright (c) 2002 The NetBSD Foundation, Inc.
 * All rights reserved.
 *
 * This code is derived from software contributed to The NetBSD Foundation
 * by Masanori Kanaoka.
 *
 * 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 NETBSD FOUNDATION, INC. 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 FOUNDATION 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.
 */

/*
 * Copyright 2001 Bill Sommerfeld.
 * All rights reserved.
 *
 * 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.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *      This product includes software developed for the NetBSD Project by
 *      Wasabi Systems, Inc.
 * 4. The name of Wasabi Systems, Inc. may not be used to endorse
 *    or promote products derived from this software without specific prior
 *    written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY WASABI SYSTEMS, INC. ``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 WASABI SYSTEMS, INC
 * 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.
 */

/*
 * ACPI VALZ Driver for Toshiba dynabook R63/PS
 *      This driver is based on vald_acpi.c
 */

/*
 * Obtain information of Toshiba "GHCI" Method from next URL.
 *           http://www.buzzard.me.uk/toshiba/docs.html
 *           http://memebeam.org/toys/ToshibaAcpiDriver
 */

#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: valz_acpi.c,v 1.9 2021/01/29 15:49:55 thorpej 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                ("valz_acpi")

#define METHOD_HCI              "GHCI"
#define METHOD_HCI_ENABLE       "ENAB"

/* Operations */
/* Get */
#define HCI_GET                 0xfe00
#define SCI_CHECK               0xf000
#define SCI_GET                 0xf300

/* Set */
#define HCI_SET                 0xff00
#define SCI_OPEN                0xf100
#define SCI_CLOSE               0xf200
#define SCI_SET                 0xf400

/* Return codes */
#define HCI_SUCCESS             0x0000
#define HCI_FAILURE             0x1000
#define HCI_NOT_SUPPORTED       0x8000
#define HCI_INPUT_ERROR         0x8300
#define HCI_FIFO_EMPTY          0x8c00

#define SCI_OPENCLOSE_OK        0x0044
#define SCI_NOT_SUPPORTED       0x8000
#define SCI_ALREADY_OPEN        0x8100
#define SCI_NOT_OPEN            0x8200
#define SCI_NOT_PRESENT         0x8600

/* Functions */
#define HCI_LCD_BACKLIGHT       0x0002
#define HCI_ACADAPTOR           0x0003
#define HCI_SYSTEM_EVENT_FIFO   0x0016
#define HCI_KBD_BACKLIGHT       0x0017
#define HCI_DISPLAY_DEV         0x001c
#define HCI_HOTKEY_EVENT        0x001e
#define HCI_LCD_BRIGHTNESS      0x002a
#define HCI_CPU_SPEED           0x0032

#define SCI_USB_OFF_CHARGE      0x0150
#define SCI_TOUCHPAD            0x050e
#define SCI_KBD_BACKLIGHT_STS   0x015c
#define SCI_KBD_BACKLIGHT       0x0095

#define SCI_KBD_BL_TIME_SHIFT   0x10

/* Field definitions */
#define HCI_LCD_BRIGHTNESS_BITS 3
#define HCI_LCD_BRIGHTNESS_SFT  (16 - HCI_LCD_BRIGHTNESS_BITS)
#define HCI_LCD_BRIGHTNESS_MIN  0
#define HCI_LCD_BRIGHTNESS_MAX  7
#define HCI_VIDEO_DEVICE_FLG    0x0100
#define HCI_CPU_SPEED_BITS      3
#define HCI_CPU_SPEED_SFT       (16 - HCI_CPU_SPEED_BITS)
#define HCI_CPU_SPEED_MAX       ((1 << HCI_CPU_SPEED_BITS) - 1)

/* Key press/release events */

/* Key press/release events */
#define FN_RELEASE_OFFSET       0x80
#  if 0
/* Not used */
#define FN_PRESS                0x01ff
#define FN_RELEASE              0x0100
#  endif
#define FN_ESC_PRESS            0x0101
#define FN_ESC_RELEASE          (FN_ESC_PRESS + FN_RELEASE_OFFSET)
#define FN_F1_PRESS             0x013b
#define FN_F1_RELEASE           (FN_F1_PRESS + FN_RELEASE_OFFSET)
#define FN_F2_PRESS             0x013c
#define FN_F2_RELEASE           (FN_F2_PRESS + FN_RELEASE_OFFSET)
#define FN_F3_PRESS             0x013d
#define FN_F3_RELEASE           (FN_F3_PRESS + FN_RELEASE_OFFSET)
#define FN_F4_PRESS             0x013e
#define FN_F4_RELEASE           (FN_F4_PRESS + FN_RELEASE_OFFSET)
#define FN_F5_PRESS             0x013f
#define FN_F5_RELEASE           (FN_F5_PRESS + FN_RELEASE_OFFSET)
#define FN_F6_PRESS             0x0140
#define FN_F6_RELEASE           (FN_F6_PRESS + FN_RELEASE_OFFSET)
#define FN_F7_PRESS             0x0141
#define FN_F7_RELEASE           (FN_F7_PRESS + FN_RELEASE_OFFSET)
#define FN_F8_PRESS             0x0142
#define FN_F8_RELEASE           (FN_F8_PRESS + FN_RELEASE_OFFSET)
#define FN_F9_PRESS             0x0143
#define FN_F9_RELEASE           (FN_F9_PRESS + FN_RELEASE_OFFSET)
/* Toggle, they are controlled by hardware */
#define FN_F10_ON               0x1bb0
#define FN_F10_OFF              0x1bb1
#define FN_F11_ON               0x1bb2
#define FN_F11_OFF              0x1bb3
/* Fn+F12 does not emit keycode */
/* dynabook R63/PS does not have KANJI keytop print */
#define FN_KNJ_PRESS            0x0129
#define FN_KNJ_RELEASE          (FN_KNJ_PRESS + FN_RELEASE_OFFSET)
#define FN_1_PRESS              0x0102
#define FN_1_RELEASE            (FN_1_PRESS + FN_RELEASE_OFFSET)
#define FN_2_PRESS              0x0103
#define FN_2_RELEASE            (FN_2_PRESS + FN_RELEASE_OFFSET)
/* Fn+3 and Fn+4 do not emit keybode */
#define FN_Z_PRESS              0x012c
#define FN_Z_RELEASE            (FN_1_PRESS + FN_RELEASE_OFFSET)
#define FN_SPACE_PRESS          0x0139
#define FN_SPACE_RELEASE        (FN_1_PRESS + FN_RELEASE_OFFSET)
#define FN_TAB_PRESS            0x010f
#define FN_TAB_RELEASE          (FN_TAB_PRESS + FN_RELEASE_OFFSET)
#define FN_CAPS_PRESS           0x013a
#define FN_CAPS_RELEASE         (FN_CAPS_PRESS + FN_RELEASE_OFFSET)
#define FN_BACKSPACE_PRESS      0x010e
#define FN_BACKSPACE_RELEASE    (FN_BACKSPACE_PRESS + FN_RELEASE_OFFSET)
#define FN_INS_PRESS            0x0152
#define FN_INS_RELEASE          (FN_INS_PRESS + FN_RELEASE_OFFSET)
#define FN_DEL_PRESS            0x0153
#define FN_DEL_RELEASE          (FN_DEL_PRESS + FN_RELEASE_OFFSET)
#define FN_PRTSC_PRESS          0x0137
#define FN_PRTSC_RELEASE        (FN_PRTSC_PRESS + FN_RELEASE_OFFSET)

/* HCI register definitions */
#define HCI_WORDS               6 /* number of registers */
#define HCI_REG_AX              0 /* Operation -> return value */
#define HCI_REG_BX              1 /* Function */
#define HCI_REG_CX              2 /* Argument (in or out) */
#define HCI_REG_DX              3 /* unused */
#define HCI_REG_SI              4 /* unused */
#define HCI_REG_DI              5 /* unused */

#define HCI_ON                  0x0001
#define HCI_OFF                 0x0000
#define HCI_ENABLE              0x0001
#define HCI_DISABLE             0x0000

#define HCI_LCD                 0x1
#define HCI_CRT                 0x2
#define HCI_TV                  0x4

#define SCI_KBD_BL_MODE_MASK    0x1f
#define SCI_KBD_BL_TIMO_SFT     0x10
#define SCI_KBD_BL_MODE_AUTO    0x2
#define SCI_KBD_BL_MODE_ON      0x8
#define SCI_KBD_BL_MODE_OFF     0x10

struct valz_acpi_softc {
        device_t sc_dev;                /* base device glue */
        struct acpi_devnode *sc_node;   /* our ACPI devnode */
};

static const struct device_compatible_entry compat_data[] = {
        { .compat = "TOS6208" },
        DEVICE_COMPAT_EOL
};

static int      valz_acpi_match(device_t, cfdata_t, void *);
static void     valz_acpi_attach(device_t, device_t, void *);

static void     valz_acpi_event(void *);
static void     valz_acpi_notify_handler(ACPI_HANDLE, uint32_t, void *);

#define ACPI_NOTIFY_ValzHotkeyPressed   0x80
#define ACPI_NOTIFY_ValzLidClosed       0x8f
#define ACPI_NOTIFY_ValzKbdBLChanges    0x92

/* HCI manipulation */
static ACPI_STATUS      hci_op(struct valz_acpi_softc *,
                                uint32_t *, uint32_t *);
static ACPI_STATUS      valz_acpi_hci_get(struct valz_acpi_softc *, uint32_t,
                                        uint32_t, uint32_t *, uint32_t *);
static ACPI_STATUS      valz_acpi_hci_set(struct valz_acpi_softc *, uint32_t,
                                        uint32_t, uint32_t, uint32_t *);

static ACPI_STATUS      sci_open(struct valz_acpi_softc *);
static ACPI_STATUS      sci_close(struct valz_acpi_softc *);

static ACPI_STATUS      valz_acpi_touchpad_toggle(struct valz_acpi_softc *);
static ACPI_STATUS      valz_acpi_lcd_backlight_toggle(
                                        struct valz_acpi_softc *sc);

CFATTACH_DECL_NEW(valz_acpi, sizeof(struct valz_acpi_softc),
    valz_acpi_match, valz_acpi_attach, NULL, NULL);

/*
 * valz_acpi_match:
 *
 *      Autoconfiguration `match' routine.
 */
static int
valz_acpi_match(device_t parent, cfdata_t match, void *aux)
{
        struct acpi_attach_args *aa = aux;

        return acpi_compatible_match(aa, compat_data);
}

/*
 * valz_acpi_attach:
 *
 *      Autoconfiguration `attach' routine.
 */
static void
valz_acpi_attach(device_t parent, device_t self, void *aux)
{
        struct valz_acpi_softc *sc = device_private(self);
        struct acpi_attach_args *aa = aux;
        ACPI_STATUS rv;

        aprint_naive(": Toshiba VALZ\n");
        aprint_normal(": Toshiba VALZ\n");

        sc->sc_node = aa->aa_node;
        sc->sc_dev = self;

        /* enable valz notify */
        rv = AcpiEvaluateObject(sc->sc_node->ad_handle, METHOD_HCI_ENABLE,
                                NULL, NULL);
        if (ACPI_FAILURE(rv)) {
                aprint_error("Cannot enable VALZ.\n");
        } else {
                (void)acpi_register_notify(sc->sc_node,
                    valz_acpi_notify_handler);
        }
}

/*
 * valz_acpi_notify_handler:
 *
 *      Notify handler.
 */
static void
valz_acpi_notify_handler(ACPI_HANDLE handle, uint32_t notify, void *context)
{
        struct valz_acpi_softc *sc;
        device_t self = context;

        sc = device_private(self);

        switch (notify) {
        case ACPI_NOTIFY_ValzHotkeyPressed:
                (void)AcpiOsExecute(OSL_NOTIFY_HANDLER, valz_acpi_event, sc);
                break;

        case ACPI_NOTIFY_ValzLidClosed:
                /* Lid closed */
                break;

        case ACPI_NOTIFY_ValzKbdBLChanges:
                /* Keyboard backlight mode changed */
                break;

        default:
                aprint_debug_dev(sc->sc_dev,
                    "unknown notify 0x%02X\n", notify);
                break;
        }
}

/*
 * valz_acpi_event:
 *
 *      Check hotkey event and do it, if event occur.
 */
static void
valz_acpi_event(void *arg)
{
        struct valz_acpi_softc *sc = arg;
        ACPI_STATUS rv;
        uint32_t value, result;

        for (;;) {
                rv = valz_acpi_hci_get(sc, HCI_GET, HCI_SYSTEM_EVENT_FIFO,
                        &value, &result);
                if (ACPI_SUCCESS(rv) && result == 0) {
                        switch (value) {
                        case FN_F9_PRESS:
                                valz_acpi_touchpad_toggle(sc);
                                break;
                        case FN_TAB_PRESS:
                                valz_acpi_lcd_backlight_toggle(sc);
                                break;

                        default:
                                /* Many unused buttons */
                                aprint_debug("Pressed: 0x%x\n", value);
                                break;
                        }
                }
                if (ACPI_FAILURE(rv) || result == HCI_NOT_SUPPORTED ||
                        result == HCI_FIFO_EMPTY)
                        break;
        }
}

/*
 * HCI/SCI operation
 */
static ACPI_STATUS
hci_op(struct valz_acpi_softc *sc, uint32_t *input, uint32_t *output)
{
        ACPI_STATUS rv;
        ACPI_OBJECT Arg[HCI_WORDS];
        ACPI_OBJECT_LIST ArgList;
        ACPI_OBJECT *param, *PrtElement;
        ACPI_BUFFER buf;
        int             i;

        for (i = 0; i < HCI_WORDS; i++) {
                Arg[i].Type = ACPI_TYPE_INTEGER;
                Arg[i].Integer.Value = 0;
        }

        for (i = 0; i < HCI_WORDS; i++) {
                Arg[i].Integer.Value = input[i];
        }

        ArgList.Count = HCI_WORDS;
        ArgList.Pointer = Arg;

        buf.Pointer = NULL;
        buf.Length = ACPI_ALLOCATE_BUFFER;

        rv = AcpiEvaluateObjectTyped(sc->sc_node->ad_handle,
            METHOD_HCI, &ArgList, &buf, ACPI_TYPE_PACKAGE);
        if (ACPI_FAILURE(rv)) {
                aprint_error_dev(sc->sc_dev, "failed to evaluate GHCI: %s\n",
                    AcpiFormatException(rv));
                return rv;
        }

        param = (ACPI_OBJECT *)buf.Pointer;
        PrtElement = param->Package.Elements;
        for (i = 0; i < HCI_WORDS; i++) {
                output[i] = PrtElement[i].Type == ACPI_TYPE_INTEGER ?
                    PrtElement[i].Integer.Value : 0;
        }

        ACPI_FREE(buf.Pointer);

        return rv;
}

/*
 * valz_acpi_hci_get:
 *
 *      Get value via "GHCI" Method.
 */
static ACPI_STATUS
valz_acpi_hci_get(struct valz_acpi_softc *sc, uint32_t function,
    uint32_t reg, uint32_t *value, uint32_t *result)
{
        ACPI_STATUS rv;

        uint32_t input[HCI_WORDS];
        uint32_t output[HCI_WORDS];

        input[HCI_REG_AX] = function;
        input[HCI_REG_BX] = reg;
        input[HCI_REG_CX] = 0;
        input[HCI_REG_DX] = 0;
        input[HCI_REG_SI] = 0;
        input[HCI_REG_DI] = 0;

        rv = hci_op(sc, input, output);

        *result = output[HCI_REG_AX];
        *value = output[HCI_REG_CX];

        return rv;
}

/*
 * valz_acpi_hci_set:
 *
 *      Set value via "GHCI" Method.
 */
static ACPI_STATUS
valz_acpi_hci_set(struct valz_acpi_softc *sc, uint32_t function,
    uint32_t reg, uint32_t value, uint32_t *result)
{
        ACPI_STATUS rv;

        uint32_t input[HCI_WORDS];
        uint32_t output[HCI_WORDS];

        input[HCI_REG_AX] = function;
        input[HCI_REG_BX] = reg;
        input[HCI_REG_CX] = value;
        input[HCI_REG_DX] = 0;
        input[HCI_REG_SI] = 0;
        input[HCI_REG_DI] = 0;

        rv = hci_op(sc, input, output);

        *result = output[HCI_REG_AX];

        return rv;
}

/*
 * Open SCI
 */
static ACPI_STATUS
sci_open(struct valz_acpi_softc *sc)
{
        ACPI_STATUS rv;
        uint32_t result;

        rv = valz_acpi_hci_set(sc, SCI_OPEN, 0, 0, &result);
        if (ACPI_FAILURE(rv)) {
                aprint_error("SCI: ACPI set error\n");
        } else {
                switch (result) {
                case SCI_OPENCLOSE_OK:
                        aprint_debug("Opening SCI\n");
                        break;
                case SCI_ALREADY_OPEN:
                        aprint_error("SCI already open\n");
                        break;
                case SCI_NOT_SUPPORTED:
                        aprint_error("SCI is not supported\n");
                        break;
                case SCI_NOT_PRESENT:
                        aprint_error("SCI is not present\n");
                        break;
                default:
                        aprint_error("SCI: undefined behavior\n");
                        break;
                }
        }

        return rv;
}

/*
 * Close SCI
 */
static ACPI_STATUS
sci_close(struct valz_acpi_softc *sc)
{
        ACPI_STATUS rv;
        uint32_t result;

        rv = valz_acpi_hci_set(sc, SCI_CLOSE, 0, 0, &result);
        if (ACPI_FAILURE(rv)) {
                aprint_error("SCI: ACPI set error\n");
        } else {
                switch (result) {
                case SCI_OPENCLOSE_OK:
                        aprint_debug("Closing SCI\n");
                        break;
                case SCI_NOT_OPEN:
                        aprint_error("SCI is not opened\n");
                        break;
                case SCI_NOT_SUPPORTED:
                        aprint_error("SCI is not supported\n");
                        break;
                case SCI_NOT_PRESENT:
                        aprint_error("SCI is not present\n");
                        break;
                default:
                        aprint_error("SCI: undefined behavior\n");
                        break;
                }
        }

        return rv;
}

/*
 * Enable/disable touchpad and trackpoint with HCI_ENABLE/HCI_DISABLE
 */
static ACPI_STATUS
valz_acpi_touchpad_toggle(struct valz_acpi_softc *sc)
{
        ACPI_STATUS rv;
        uint32_t result, status, value;

        rv = sci_open(sc);
        if (ACPI_FAILURE(rv))
                aprint_error_dev(sc->sc_dev,
                                "Cannot open SCI: %s\n",
                                AcpiFormatException(rv));

        rv = valz_acpi_hci_get(sc, SCI_GET, SCI_TOUCHPAD, &value, &result);
        if (ACPI_FAILURE(rv))
                aprint_error_dev(sc->sc_dev,
                                "Cannot get SCI touchpad status: %s\n",
                                AcpiFormatException(rv));

        switch (value) {
        case HCI_ENABLE:
                status = HCI_DISABLE;
                break;
        case HCI_DISABLE:
                status = HCI_ENABLE;
                break;
        default:
                status = HCI_ENABLE;
                break;
        }

        rv = valz_acpi_hci_set(sc, SCI_SET, SCI_TOUCHPAD, status, &result);
        if (ACPI_FAILURE(rv))
                aprint_error_dev(sc->sc_dev,
                                "Cannot set SCI touchpad status: %s\n",
                                AcpiFormatException(rv));

        rv = sci_close(sc);
        if (ACPI_FAILURE(rv))
                aprint_error_dev(sc->sc_dev,
                                "Cannot close SCI: %s\n",
                                AcpiFormatException(rv));

        return rv;
}

/*
 * Enable/disable LCD backlight with HCI_ENABLE/HCI_DISABLE
 */
static ACPI_STATUS
valz_acpi_lcd_backlight_toggle(struct valz_acpi_softc *sc)
{
        ACPI_STATUS rv;
        uint32_t result, status, value;

        rv = valz_acpi_hci_get(sc, HCI_GET, HCI_LCD_BACKLIGHT, &value, &result);
        if (ACPI_FAILURE(rv))
                aprint_error_dev(sc->sc_dev,
                                "Cannot get HCI LCD backlight status: %s\n",
                                AcpiFormatException(rv));

        switch (value) {
        case HCI_ON:
                status = HCI_OFF;
                break;
        case HCI_OFF:
                status = HCI_ON;
                break;
        default:
                status = HCI_ON;
                break;
        }

        rv = valz_acpi_hci_set(sc, HCI_SET, HCI_LCD_BACKLIGHT, status, &result);
        if (ACPI_FAILURE(rv))
                aprint_error_dev(sc->sc_dev,
                                "Cannot set HCI LCD backlight status: %s\n",
                                AcpiFormatException(rv));

        return rv;
}