diff options
Diffstat (limited to 'sys/dev/acpi_support')
| -rw-r--r-- | sys/dev/acpi_support/acpi_aiboost.c | 338 | ||||
| -rw-r--r-- | sys/dev/acpi_support/acpi_asus.c | 1297 | ||||
| -rw-r--r-- | sys/dev/acpi_support/acpi_fujitsu.c | 716 | ||||
| -rw-r--r-- | sys/dev/acpi_support/acpi_ibm.c | 948 | ||||
| -rw-r--r-- | sys/dev/acpi_support/acpi_panasonic.c | 514 | ||||
| -rw-r--r-- | sys/dev/acpi_support/acpi_sony.c | 170 | ||||
| -rw-r--r-- | sys/dev/acpi_support/acpi_toshiba.c | 566 |
7 files changed, 4549 insertions, 0 deletions
diff --git a/sys/dev/acpi_support/acpi_aiboost.c b/sys/dev/acpi_support/acpi_aiboost.c new file mode 100644 index 000000000000..00bf9cb1a34d --- /dev/null +++ b/sys/dev/acpi_support/acpi_aiboost.c @@ -0,0 +1,338 @@ +/*- + * Copyright (c) 2006 Takanori Watanabe + * 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. + * + * 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 <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include "opt_acpi.h" +#include <sys/param.h> +#include <sys/kernel.h> +#include <sys/bus.h> +#include <contrib/dev/acpica/acpi.h> +#include "acpi_if.h" +#include <sys/module.h> +#include <dev/acpica/acpivar.h> +#include <sys/sysctl.h> +#include <sys/malloc.h> +#include <sys/sysctl.h> + +#define _COMPONENT ACPI_OEM +ACPI_MODULE_NAME("AIBOOST") + +#define DESCSTRLEN 32 +struct acpi_aiboost_element{ + ACPI_HANDLE h; + uint32_t id; + char desc[DESCSTRLEN]; +}; +ACPI_SERIAL_DECL(aiboost, "ACPI AIBOOST"); +/**/ +struct acpi_aiboost_component{ + unsigned int num; + struct acpi_aiboost_element elem[1]; +}; + +struct acpi_aiboost_softc { + int pid; + struct acpi_aiboost_component *temp; + struct acpi_aiboost_component *volt; + struct acpi_aiboost_component *fan; +}; + +static int acpi_aiboost_probe(device_t dev); +static int acpi_aiboost_attach(device_t dev); +static int acpi_aiboost_detach(device_t dev); + +static device_method_t acpi_aiboost_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, acpi_aiboost_probe), + DEVMETHOD(device_attach, acpi_aiboost_attach), + DEVMETHOD(device_detach, acpi_aiboost_detach), + + {0, 0} +}; + +static driver_t acpi_aiboost_driver = { + "acpi_aiboost", + acpi_aiboost_methods, + sizeof(struct acpi_aiboost_softc), +}; + +static devclass_t acpi_aiboost_devclass; + +DRIVER_MODULE(acpi_aiboost, acpi, acpi_aiboost_driver, acpi_aiboost_devclass, + 0, 0); +MODULE_DEPEND(acpi_aiboost, acpi, 1, 1, 1); +static char *abs_id[] = {"ATK0110", NULL}; + +/*VSIF, RVLT, SVLT, TSIF, RTMP, STMP FSIF, RFAN, SFAN */ + +static ACPI_STATUS acpi_aiboost_getcomponent(device_t dev, char *name, struct acpi_aiboost_component **comp) +{ + ACPI_BUFFER buf, buf2; + ACPI_OBJECT *o,*elem,*subobj; + ACPI_STATUS status; + struct acpi_aiboost_component *c = NULL; + + int i; + + buf.Pointer = NULL; + buf.Length = ACPI_ALLOCATE_BUFFER; + buf2.Pointer = NULL; + + status = AcpiEvaluateObject(acpi_get_handle(dev), name, NULL, &buf); + + if(ACPI_FAILURE(status)) + return status; + + o = buf.Pointer; + if(o->Type != ACPI_TYPE_PACKAGE) + goto error; + + elem = o->Package.Elements; + if(elem->Type != ACPI_TYPE_INTEGER) + goto error; + + c = malloc(sizeof(struct acpi_aiboost_component) + + sizeof(struct acpi_aiboost_element) + * (elem->Integer.Value -1), + M_DEVBUF, M_ZERO|M_WAITOK); + *comp = c; + c->num = elem->Integer.Value; + + for(i = 1 ; i < o->Package.Count; i++){ + elem = &o->Package.Elements[i]; + if(elem->Type != ACPI_TYPE_ANY){ + printf("NOREF\n"); + goto error; + } + c->elem[ i - 1].h = elem->Reference.Handle; + + buf2.Pointer = NULL; + buf2.Length = ACPI_ALLOCATE_BUFFER; + + status = AcpiEvaluateObject(c->elem[i - 1].h, NULL, NULL, + &buf2); + if(ACPI_FAILURE(status)){ + printf("FETCH OBJECT\n"); + goto error; + } + subobj = buf2.Pointer; + if(ACPI_FAILURE(acpi_PkgInt32(subobj,0, &c->elem[i -1].id))){ + printf("ID FAILED\n"); + goto error; + } + status = acpi_PkgStr(subobj, 1, c->elem[i - 1].desc, + sizeof(c->elem[i - 1].desc)); + if(ACPI_FAILURE(status)){ + if(status == E2BIG){ + c->elem[i-1].desc[DESCSTRLEN-1] = 0; + }else{ + printf("DESC FAILED %d\n", i-1); + goto error; + } + } + + if(buf2.Pointer) + AcpiOsFree(buf2.Pointer); + } + + if(buf.Pointer) + AcpiOsFree(buf.Pointer); + + return 0; + + error: + printf("BAD DATA\n"); + if(buf.Pointer) + AcpiOsFree(buf.Pointer); + if(buf2.Pointer) + AcpiOsFree(buf2.Pointer); + if(c) + free(c, M_DEVBUF); + return AE_BAD_DATA; +} + +static int +acpi_aiboost_get_value(ACPI_HANDLE handle, char *path, UINT32 number) +{ + ACPI_OBJECT arg1, *ret; + ACPI_OBJECT_LIST args; + ACPI_BUFFER buf; + buf.Length = ACPI_ALLOCATE_BUFFER; + buf.Pointer = 0; + int val; + + arg1.Type = ACPI_TYPE_INTEGER; + arg1.Integer.Value = number; + args.Count = 1; + args.Pointer = &arg1; + + if(ACPI_FAILURE(AcpiEvaluateObject(handle, path, &args, &buf))){ + return -1; + } + + ret = buf.Pointer; + val = (ret->Type == ACPI_TYPE_INTEGER)? ret->Integer.Value : -1; + + AcpiOsFree(buf.Pointer); + return val; +} + + +static int acpi_aiboost_temp_sysctl(SYSCTL_HANDLER_ARGS) +{ + device_t dev = arg1; + int function = oidp->oid_arg2; + int error = 0, val; + ACPI_SERIAL_BEGIN(aiboost); + val = acpi_aiboost_get_value(acpi_get_handle(dev), "RTMP",function ); + error = sysctl_handle_int(oidp, &val, 0 , req); + ACPI_SERIAL_END(aiboost); + + return 0; +} + +static int acpi_aiboost_volt_sysctl(SYSCTL_HANDLER_ARGS) +{ + device_t dev = arg1; + int function = oidp->oid_arg2; + int error = 0, val; + ACPI_SERIAL_BEGIN(aiboost); + val = acpi_aiboost_get_value(acpi_get_handle(dev), "RVLT", function); + error = sysctl_handle_int(oidp, &val, 0 , req); + ACPI_SERIAL_END(aiboost); + + return 0; +} + +static int acpi_aiboost_fan_sysctl(SYSCTL_HANDLER_ARGS) +{ + device_t dev = arg1; + int function = oidp->oid_arg2; + int error = 0, val; + ACPI_SERIAL_BEGIN(aiboost); + val = acpi_aiboost_get_value(acpi_get_handle(dev), "RFAN", function); + error = sysctl_handle_int(oidp, &val, 0 , req); + ACPI_SERIAL_END(aiboost); + + return 0; +} + +static int +acpi_aiboost_probe(device_t dev) +{ + int ret = ENXIO; + + if (ACPI_ID_PROBE(device_get_parent(dev), dev, abs_id)) { + device_set_desc(dev, "ASUStek AIBOOSTER"); + ret = 0; + } + return (ret); +} + +static int +acpi_aiboost_attach(device_t dev) +{ + struct acpi_aiboost_softc *sc; + char nambuf[]="tempXXX"; + int i; + + sc = device_get_softc(dev); + if(ACPI_FAILURE(acpi_aiboost_getcomponent(dev, "TSIF", &sc->temp))) + goto error; + for(i= 0; i < sc->temp->num; i++){ + sprintf(nambuf,"temp%d", i); + SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), + SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), + OID_AUTO, nambuf, + CTLTYPE_INT|CTLFLAG_RD, dev, + sc->temp->elem[i].id, + acpi_aiboost_temp_sysctl, + "I", sc->temp->elem[i].desc); + } + if(ACPI_FAILURE(acpi_aiboost_getcomponent(dev, "VSIF", &sc->volt))) + goto error; + + for(i= 0; i < sc->volt->num; i++){ + sprintf(nambuf,"volt%d", i); + SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), + SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), + OID_AUTO, nambuf, + CTLTYPE_INT|CTLFLAG_RD, dev, + sc->volt->elem[i].id, + acpi_aiboost_volt_sysctl, + "I", sc->volt->elem[i].desc); + } + + if(ACPI_FAILURE(acpi_aiboost_getcomponent(dev, "FSIF", &sc->fan))) + goto error; + + for(i= 0; i < sc->fan->num; i++){ + sprintf(nambuf,"fan%d", i); + SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), + SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), + OID_AUTO, nambuf, + CTLTYPE_INT|CTLFLAG_RD, dev, + sc->fan->elem[i].id, + acpi_aiboost_fan_sysctl, + "I", sc->fan->elem[i].desc); + } + + + return (0); + error: + return EINVAL; +} + +static int +acpi_aiboost_detach(device_t dev) +{ + struct acpi_aiboost_softc *sc = device_get_softc(dev); + + if(sc->temp) + free(sc->temp, M_DEVBUF); + if(sc->volt) + free(sc->volt, M_DEVBUF); + if(sc->fan) + free(sc->fan, M_DEVBUF); + return (0); +} + +#if 0 +static int +acpi_aiboost_suspend(device_t dev) +{ + struct acpi_aiboost_softc *sc = device_get_softc(dev); + return (0); +} + +static int +acpi_aiboost_resume(device_t dev) +{ + return (0); +} +#endif diff --git a/sys/dev/acpi_support/acpi_asus.c b/sys/dev/acpi_support/acpi_asus.c new file mode 100644 index 000000000000..28f9e7c982c3 --- /dev/null +++ b/sys/dev/acpi_support/acpi_asus.c @@ -0,0 +1,1297 @@ +/*- + * Copyright (c) 2004, 2005 Philip Paeps <philip@FreeBSD.org> + * 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. + * + * 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 <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +/* + * Driver for extra ACPI-controlled gadgets (hotkeys, leds, etc) found on + * recent Asus (and Medion) laptops. Inspired by the acpi4asus project which + * implements these features in the Linux kernel. + * + * <http://sourceforge.net/projects/acpi4asus/> + * + * Currently should support most features, but could use some more testing. + * Particularly the display-switching stuff is a bit hairy. If you have an + * Asus laptop which doesn't appear to be supported, or strange things happen + * when using this driver, please report to <acpi@FreeBSD.org>. + */ + +#include "opt_acpi.h" +#include <sys/param.h> +#include <sys/kernel.h> +#include <sys/module.h> +#include <sys/bus.h> +#include <sys/sbuf.h> + +#include <contrib/dev/acpica/acpi.h> +#include <dev/acpica/acpivar.h> +#include <dev/led/led.h> + +/* Methods */ +#define ACPI_ASUS_METHOD_BRN 1 +#define ACPI_ASUS_METHOD_DISP 2 +#define ACPI_ASUS_METHOD_LCD 3 +#define ACPI_ASUS_METHOD_CAMERA 4 +#define ACPI_ASUS_METHOD_CARDRD 5 +#define ACPI_ASUS_METHOD_WLAN 6 + +#define _COMPONENT ACPI_OEM +ACPI_MODULE_NAME("ASUS") + +struct acpi_asus_model { + char *name; + + char *bled_set; + char *dled_set; + char *gled_set; + char *mled_set; + char *tled_set; + char *wled_set; + + char *brn_get; + char *brn_set; + char *brn_up; + char *brn_dn; + + char *lcd_get; + char *lcd_set; + + char *disp_get; + char *disp_set; + + char *cam_get; + char *cam_set; + + char *crd_get; + char *crd_set; + + char *wlan_get; + char *wlan_set; + + void (*n_func)(ACPI_HANDLE, UINT32, void *); + + char *lcdd; + void (*lcdd_n_func)(ACPI_HANDLE, UINT32, void *); +}; + +struct acpi_asus_led { + struct acpi_asus_softc *sc; + struct cdev *cdev; + int busy; + int state; + enum { + ACPI_ASUS_LED_BLED, + ACPI_ASUS_LED_DLED, + ACPI_ASUS_LED_GLED, + ACPI_ASUS_LED_MLED, + ACPI_ASUS_LED_TLED, + ACPI_ASUS_LED_WLED, + } type; +}; + +struct acpi_asus_softc { + device_t dev; + ACPI_HANDLE handle; + ACPI_HANDLE lcdd_handle; + + struct acpi_asus_model *model; + struct sysctl_ctx_list sysctl_ctx; + struct sysctl_oid *sysctl_tree; + + struct acpi_asus_led s_bled; + struct acpi_asus_led s_dled; + struct acpi_asus_led s_gled; + struct acpi_asus_led s_mled; + struct acpi_asus_led s_tled; + struct acpi_asus_led s_wled; + + int s_brn; + int s_disp; + int s_lcd; + int s_cam; + int s_crd; + int s_wlan; +}; + +static void acpi_asus_lcdd_notify(ACPI_HANDLE h, UINT32 notify, + void *context); + +/* + * We can identify Asus laptops from the string they return + * as a result of calling the ATK0100 'INIT' method. + */ +static struct acpi_asus_model acpi_asus_models[] = { + { + .name = "xxN", + .mled_set = "MLED", + .wled_set = "WLED", + .lcd_get = "\\BKLT", + .lcd_set = "\\_SB.PCI0.SBRG.EC0._Q10", + .brn_get = "GPLV", + .brn_set = "SPLV", + .disp_get = "\\ADVG", + .disp_set = "SDSP" + }, + { + .name = "A1x", + .mled_set = "MLED", + .lcd_get = "\\BKLI", + .lcd_set = "\\_SB.PCI0.ISA.EC0._Q10", + .brn_up = "\\_SB.PCI0.ISA.EC0._Q0E", + .brn_dn = "\\_SB.PCI0.ISA.EC0._Q0F" + }, + { + .name = "A2x", + .mled_set = "MLED", + .wled_set = "WLED", + .lcd_get = "\\BAOF", + .lcd_set = "\\Q10", + .brn_get = "GPLV", + .brn_set = "SPLV", + .disp_get = "\\INFB", + .disp_set = "SDSP" + }, + { + .name = "A3E", + .mled_set = "MLED", + .wled_set = "WLED", + .lcd_get = "\\_SB.PCI0.SBRG.EC0.RPIN(0x67)", + .lcd_set = "\\_SB.PCI0.SBRG.EC0._Q10", + .brn_get = "GPLV", + .brn_set = "SPLV", + .disp_get = "\\_SB.PCI0.P0P2.VGA.GETD", + .disp_set = "SDSP" + }, + { + .name = "A3F", + .mled_set = "MLED", + .wled_set = "WLED", + .bled_set = "BLED", + .lcd_get = "\\_SB.PCI0.SBRG.EC0.RPIN(0x11)", + .lcd_set = "\\_SB.PCI0.SBRG.EC0._Q10", + .brn_get = "GPLV", + .brn_set = "SPLV", + .disp_get = "\\SSTE", + .disp_set = "SDSP" + }, + { + .name = "A3N", + .mled_set = "MLED", + .bled_set = "BLED", + .wled_set = "WLED", + .lcd_get = "\\BKLT", + .lcd_set = "\\_SB.PCI0.SBRG.EC0._Q10", + .brn_get = "GPLV", + .brn_set = "SPLV", + .disp_get = "\\_SB.PCI0.P0P3.VGA.GETD", + .disp_set = "SDSP" + }, + { + .name = "A4D", + .mled_set = "MLED", + .brn_up = "\\_SB_.PCI0.SBRG.EC0._Q0E", + .brn_dn = "\\_SB_.PCI0.SBRG.EC0._Q0F", + .brn_get = "GPLV", + .brn_set = "SPLV", +#ifdef notyet + .disp_get = "\\_SB_.PCI0.SBRG.EC0._Q10", + .disp_set = "\\_SB_.PCI0.SBRG.EC0._Q11" +#endif + }, + { + .name = "A6V", + .bled_set = "BLED", + .mled_set = "MLED", + .wled_set = "WLED", + .lcd_get = NULL, + .lcd_set = "\\_SB.PCI0.SBRG.EC0._Q10", + .brn_get = "GPLV", + .brn_set = "SPLV", + .disp_get = "\\_SB.PCI0.P0P3.VGA.GETD", + .disp_set = "SDSP" + }, + { + .name = "A8SR", + .bled_set = "BLED", + .mled_set = "MLED", + .wled_set = "WLED", + .lcd_get = NULL, + .lcd_set = "\\_SB.PCI0.SBRG.EC0._Q10", + .brn_get = "GPLV", + .brn_set = "SPLV", + .disp_get = "\\_SB.PCI0.P0P1.VGA.GETD", + .disp_set = "SDSP", + .lcdd = "\\_SB.PCI0.P0P1.VGA.LCDD", + .lcdd_n_func = acpi_asus_lcdd_notify + }, + { + .name = "D1x", + .mled_set = "MLED", + .lcd_get = "\\GP11", + .lcd_set = "\\Q0D", + .brn_up = "\\Q0C", + .brn_dn = "\\Q0B", + .disp_get = "\\INFB", + .disp_set = "SDSP" + }, + { + .name = "G2K", + .bled_set = "BLED", + .dled_set = "DLED", + .gled_set = "GLED", + .mled_set = "MLED", + .tled_set = "TLED", + .wled_set = "WLED", + .brn_get = "GPLV", + .brn_set = "SPLV", + .lcd_get = "\\_SB.PCI0.SBRG.EC0.RPIN", + .lcd_set = "\\_SB.PCI0.SBRG.EC0._Q10", + .disp_get = "\\_SB.PCI0.PCE2.VGA.GETD", + .disp_set = "SDSP", + }, + { + .name = "L2D", + .mled_set = "MLED", + .wled_set = "WLED", + .brn_up = "\\Q0E", + .brn_dn = "\\Q0F", + .lcd_get = "\\SGP0", + .lcd_set = "\\Q10" + }, + { + .name = "L3C", + .mled_set = "MLED", + .wled_set = "WLED", + .brn_get = "GPLV", + .brn_set = "SPLV", + .lcd_get = "\\GL32", + .lcd_set = "\\_SB.PCI0.PX40.ECD0._Q10" + }, + { + .name = "L3D", + .mled_set = "MLED", + .wled_set = "WLED", + .brn_get = "GPLV", + .brn_set = "SPLV", + .lcd_get = "\\BKLG", + .lcd_set = "\\Q10" + }, + { + .name = "L3H", + .mled_set = "MLED", + .wled_set = "WLED", + .brn_get = "GPLV", + .brn_set = "SPLV", + .lcd_get = "\\_SB.PCI0.PM.PBC", + .lcd_set = "EHK", + .disp_get = "\\_SB.INFB", + .disp_set = "SDSP" + }, + { + .name = "L4R", + .mled_set = "MLED", + .wled_set = "WLED", + .brn_get = "GPLV", + .brn_set = "SPLV", + .lcd_get = "\\_SB.PCI0.SBSM.SEO4", + .lcd_set = "\\_SB.PCI0.SBRG.EC0._Q10", + .disp_get = "\\_SB.PCI0.P0P1.VGA.GETD", + .disp_set = "SDSP" + }, + { + .name = "L5x", + .mled_set = "MLED", + .tled_set = "TLED", + .lcd_get = "\\BAOF", + .lcd_set = "\\Q0D", + .brn_get = "GPLV", + .brn_set = "SPLV", + .disp_get = "\\INFB", + .disp_set = "SDSP" + }, + { + .name = "L8L" + /* Only has hotkeys, apparently */ + }, + { + .name = "M1A", + .mled_set = "MLED", + .brn_up = "\\_SB.PCI0.PX40.EC0.Q0E", + .brn_dn = "\\_SB.PCI0.PX40.EC0.Q0F", + .lcd_get = "\\PNOF", + .lcd_set = "\\_SB.PCI0.PX40.EC0.Q10" + }, + { + .name = "M2E", + .mled_set = "MLED", + .wled_set = "WLED", + .brn_get = "GPLV", + .brn_set = "SPLV", + .lcd_get = "\\GP06", + .lcd_set = "\\Q10" + }, + { + .name = "M6N", + .mled_set = "MLED", + .wled_set = "WLED", + .lcd_set = "\\_SB.PCI0.SBRG.EC0._Q10", + .lcd_get = "\\_SB.BKLT", + .brn_set = "SPLV", + .brn_get = "GPLV", + .disp_set = "SDSP", + .disp_get = "\\SSTE" + }, + { + .name = "M6R", + .mled_set = "MLED", + .wled_set = "WLED", + .brn_get = "GPLV", + .brn_set = "SPLV", + .lcd_get = "\\_SB.PCI0.SBSM.SEO4", + .lcd_set = "\\_SB.PCI0.SBRG.EC0._Q10", + .disp_get = "\\SSTE", + .disp_set = "SDSP" + }, + { + .name = "S1x", + .mled_set = "MLED", + .wled_set = "WLED", + .lcd_get = "\\PNOF", + .lcd_set = "\\_SB.PCI0.PX40.Q10", + .brn_get = "GPLV", + .brn_set = "SPLV" + }, + { + .name = "S2x", + .mled_set = "MLED", + .lcd_get = "\\BKLI", + .lcd_set = "\\_SB.PCI0.ISA.EC0._Q10", + .brn_up = "\\_SB.PCI0.ISA.EC0._Q0B", + .brn_dn = "\\_SB.PCI0.ISA.EC0._Q0A" + }, + { + .name = "V6V", + .bled_set = "BLED", + .tled_set = "TLED", + .wled_set = "WLED", + .lcd_get = "\\BKLT", + .lcd_set = "\\_SB.PCI0.SBRG.EC0._Q10", + .brn_get = "GPLV", + .brn_set = "SPLV", + .disp_get = "\\_SB.PCI0.P0P1.VGA.GETD", + .disp_set = "SDSP" + }, + { + .name = "W5A", + .bled_set = "BLED", + .lcd_get = "\\BKLT", + .lcd_set = "\\_SB.PCI0.SBRG.EC0._Q10", + .brn_get = "GPLV", + .brn_set = "SPLV", + .disp_get = "\\_SB.PCI0.P0P2.VGA.GETD", + .disp_set = "SDSP" + }, + + { .name = NULL } +}; + +/* + * Samsung P30/P35 laptops have an Asus ATK0100 gadget interface, + * but they can't be probed quite the same way as Asus laptops. + */ +static struct acpi_asus_model acpi_samsung_models[] = { + { + .name = "P30", + .wled_set = "WLED", + .brn_up = "\\_SB.PCI0.LPCB.EC0._Q68", + .brn_dn = "\\_SB.PCI0.LPCB.EC0._Q69", + .lcd_get = "\\BKLT", + .lcd_set = "\\_SB.PCI0.LPCB.EC0._Q0E" + }, + + { .name = NULL } +}; + +static void acpi_asus_eeepc_notify(ACPI_HANDLE h, UINT32 notify, void *context); + +/* + * EeePC have an Asus ASUS010 gadget interface, + * but they can't be probed quite the same way as Asus laptops. + */ +static struct acpi_asus_model acpi_eeepc_models[] = { + { + .name = "EEE", + .brn_get = "\\_SB.ATKD.PBLG", + .brn_set = "\\_SB.ATKD.PBLS", + .cam_get = "\\_SB.ATKD.CAMG", + .cam_set = "\\_SB.ATKD.CAMS", + .crd_set = "\\_SB.ATKD.CRDS", + .crd_get = "\\_SB.ATKD.CRDG", + .wlan_get = "\\_SB.ATKD.WLDG", + .wlan_set = "\\_SB.ATKD.WLDS", + .n_func = acpi_asus_eeepc_notify + }, + + { .name = NULL } +}; + +static struct { + char *name; + char *description; + int method; + int flags; +} acpi_asus_sysctls[] = { + { + .name = "lcd_backlight", + .method = ACPI_ASUS_METHOD_LCD, + .description = "state of the lcd backlight", + .flags = CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_ANYBODY + }, + { + .name = "lcd_brightness", + .method = ACPI_ASUS_METHOD_BRN, + .description = "brightness of the lcd panel", + .flags = CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_ANYBODY + }, + { + .name = "video_output", + .method = ACPI_ASUS_METHOD_DISP, + .description = "display output state", + .flags = CTLTYPE_INT | CTLFLAG_RW + }, + { + .name = "camera", + .method = ACPI_ASUS_METHOD_CAMERA, + .description = "internal camera state", + .flags = CTLTYPE_INT | CTLFLAG_RW + }, + { + .name = "cardreader", + .method = ACPI_ASUS_METHOD_CARDRD, + .description = "internal card reader state", + .flags = CTLTYPE_INT | CTLFLAG_RW + }, + { + .name = "wlan", + .method = ACPI_ASUS_METHOD_WLAN, + .description = "wireless lan state", + .flags = CTLTYPE_INT | CTLFLAG_RW + }, + + { .name = NULL } +}; + +ACPI_SERIAL_DECL(asus, "ACPI ASUS extras"); + +/* Function prototypes */ +static int acpi_asus_probe(device_t dev); +static int acpi_asus_attach(device_t dev); +static int acpi_asus_detach(device_t dev); + +static void acpi_asus_led(struct acpi_asus_led *led, int state); +static void acpi_asus_led_task(struct acpi_asus_led *led, int pending __unused); + +static int acpi_asus_sysctl(SYSCTL_HANDLER_ARGS); +static int acpi_asus_sysctl_init(struct acpi_asus_softc *sc, int method); +static int acpi_asus_sysctl_get(struct acpi_asus_softc *sc, int method); +static int acpi_asus_sysctl_set(struct acpi_asus_softc *sc, int method, int val); + +static void acpi_asus_notify(ACPI_HANDLE h, UINT32 notify, void *context); + +static device_method_t acpi_asus_methods[] = { + DEVMETHOD(device_probe, acpi_asus_probe), + DEVMETHOD(device_attach, acpi_asus_attach), + DEVMETHOD(device_detach, acpi_asus_detach), + + { 0, 0 } +}; + +static driver_t acpi_asus_driver = { + "acpi_asus", + acpi_asus_methods, + sizeof(struct acpi_asus_softc) +}; + +static devclass_t acpi_asus_devclass; + +DRIVER_MODULE(acpi_asus, acpi, acpi_asus_driver, acpi_asus_devclass, 0, 0); +MODULE_DEPEND(acpi_asus, acpi, 1, 1, 1); + +static int +acpi_asus_probe(device_t dev) +{ + struct acpi_asus_model *model; + struct acpi_asus_softc *sc; + struct sbuf *sb; + ACPI_BUFFER Buf; + ACPI_OBJECT Arg, *Obj; + ACPI_OBJECT_LIST Args; + static char *asus_ids[] = { "ATK0100", "ASUS010", NULL }; + char *rstr; + + ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); + + if (acpi_disabled("asus")) + return (ENXIO); + rstr = ACPI_ID_PROBE(device_get_parent(dev), dev, asus_ids); + if (rstr == NULL) { + return (ENXIO); + } + + sc = device_get_softc(dev); + sc->dev = dev; + sc->handle = acpi_get_handle(dev); + + Arg.Type = ACPI_TYPE_INTEGER; + Arg.Integer.Value = 0; + + Args.Count = 1; + Args.Pointer = &Arg; + + Buf.Pointer = NULL; + Buf.Length = ACPI_ALLOCATE_BUFFER; + + AcpiEvaluateObject(sc->handle, "INIT", &Args, &Buf); + Obj = Buf.Pointer; + + /* + * The Samsung P30 returns a null-pointer from INIT, we + * can identify it from the 'ODEM' string in the DSDT. + */ + if (Obj->String.Pointer == NULL) { + ACPI_STATUS status; + ACPI_TABLE_HEADER th; + + status = AcpiGetTableHeader(ACPI_SIG_DSDT, 0, &th); + if (ACPI_FAILURE(status)) { + device_printf(dev, "Unsupported (Samsung?) laptop\n"); + AcpiOsFree(Buf.Pointer); + return (ENXIO); + } + + if (strncmp("ODEM", th.OemTableId, 4) == 0) { + sc->model = &acpi_samsung_models[0]; + device_set_desc(dev, "Samsung P30 Laptop Extras"); + AcpiOsFree(Buf.Pointer); + return (0); + } + + /* EeePC */ + if (strncmp("ASUS010", rstr, 7) == 0) { + sc->model = &acpi_eeepc_models[0]; + device_set_desc(dev, "ASUS EeePC"); + AcpiOsFree(Buf.Pointer); + return (0); + } + } + + sb = sbuf_new_auto(); + if (sb == NULL) + return (ENOMEM); + + /* + * Asus laptops are simply identified by name, easy! + */ + for (model = acpi_asus_models; model->name != NULL; model++) { + if (strncmp(Obj->String.Pointer, model->name, 3) == 0) { + +good: + sbuf_printf(sb, "Asus %s Laptop Extras", + Obj->String.Pointer); + sbuf_finish(sb); + + sc->model = model; + device_set_desc_copy(dev, sbuf_data(sb)); + + sbuf_delete(sb); + AcpiOsFree(Buf.Pointer); + return (0); + } + + /* + * Some models look exactly the same as other models, but have + * their own ids. If we spot these, set them up with the same + * details as the models they're like, possibly dealing with + * small differences. + * + * XXX: there must be a prettier way to do this! + */ + else if (strncmp(model->name, "xxN", 3) == 0 && + (strncmp(Obj->String.Pointer, "M3N", 3) == 0 || + strncmp(Obj->String.Pointer, "S1N", 3) == 0)) + goto good; + else if (strncmp(model->name, "A1x", 3) == 0 && + strncmp(Obj->String.Pointer, "A1", 2) == 0) + goto good; + else if (strncmp(model->name, "A2x", 3) == 0 && + strncmp(Obj->String.Pointer, "A2", 2) == 0) + goto good; + else if (strncmp(model->name, "A3F", 3) == 0 && + strncmp(Obj->String.Pointer, "A6F", 3) == 0) + goto good; + else if (strncmp(model->name, "D1x", 3) == 0 && + strncmp(Obj->String.Pointer, "D1", 2) == 0) + goto good; + else if (strncmp(model->name, "L3H", 3) == 0 && + strncmp(Obj->String.Pointer, "L2E", 3) == 0) + goto good; + else if (strncmp(model->name, "L5x", 3) == 0 && + strncmp(Obj->String.Pointer, "L5", 2) == 0) + goto good; + else if (strncmp(model->name, "M2E", 3) == 0 && + (strncmp(Obj->String.Pointer, "M2", 2) == 0 || + strncmp(Obj->String.Pointer, "L4E", 3) == 0)) + goto good; + else if (strncmp(model->name, "S1x", 3) == 0 && + (strncmp(Obj->String.Pointer, "L8", 2) == 0 || + strncmp(Obj->String.Pointer, "S1", 2) == 0)) + goto good; + else if (strncmp(model->name, "S2x", 3) == 0 && + (strncmp(Obj->String.Pointer, "J1", 2) == 0 || + strncmp(Obj->String.Pointer, "S2", 2) == 0)) + goto good; + + /* L2B is like L3C but has no lcd_get method */ + else if (strncmp(model->name, "L3C", 3) == 0 && + strncmp(Obj->String.Pointer, "L2B", 3) == 0) { + model->lcd_get = NULL; + goto good; + } + + /* A3G is like M6R but with a different lcd_get method */ + else if (strncmp(model->name, "M6R", 3) == 0 && + strncmp(Obj->String.Pointer, "A3G", 3) == 0) { + model->lcd_get = "\\BLFG"; + goto good; + } + + /* M2N and W1N are like xxN with added WLED */ + else if (strncmp(model->name, "xxN", 3) == 0 && + (strncmp(Obj->String.Pointer, "M2N", 3) == 0 || + strncmp(Obj->String.Pointer, "W1N", 3) == 0)) { + model->wled_set = "WLED"; + goto good; + } + + /* M5N and S5N are like xxN without MLED */ + else if (strncmp(model->name, "xxN", 3) == 0 && + (strncmp(Obj->String.Pointer, "M5N", 3) == 0 || + strncmp(Obj->String.Pointer, "S5N", 3) == 0)) { + model->mled_set = NULL; + goto good; + } + } + + sbuf_printf(sb, "Unsupported Asus laptop: %s\n", Obj->String.Pointer); + sbuf_finish(sb); + + device_printf(dev, sbuf_data(sb)); + + sbuf_delete(sb); + AcpiOsFree(Buf.Pointer); + + return (ENXIO); +} + +static int +acpi_asus_attach(device_t dev) +{ + struct acpi_asus_softc *sc; + struct acpi_softc *acpi_sc; + + ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); + + sc = device_get_softc(dev); + acpi_sc = acpi_device_get_parent_softc(dev); + + /* Build sysctl tree */ + sysctl_ctx_init(&sc->sysctl_ctx); + sc->sysctl_tree = SYSCTL_ADD_NODE(&sc->sysctl_ctx, + SYSCTL_CHILDREN(acpi_sc->acpi_sysctl_tree), + OID_AUTO, "asus", CTLFLAG_RD, 0, ""); + + /* Hook up nodes */ + for (int i = 0; acpi_asus_sysctls[i].name != NULL; i++) { + if (!acpi_asus_sysctl_init(sc, acpi_asus_sysctls[i].method)) + continue; + + SYSCTL_ADD_PROC(&sc->sysctl_ctx, + SYSCTL_CHILDREN(sc->sysctl_tree), OID_AUTO, + acpi_asus_sysctls[i].name, + acpi_asus_sysctls[i].flags, + sc, i, acpi_asus_sysctl, "I", + acpi_asus_sysctls[i].description); + } + + /* Attach leds */ + if (sc->model->bled_set) { + sc->s_bled.busy = 0; + sc->s_bled.sc = sc; + sc->s_bled.type = ACPI_ASUS_LED_BLED; + sc->s_bled.cdev = + led_create_state((led_t *)acpi_asus_led, &sc->s_bled, + "bled", 1); + } + + if (sc->model->dled_set) { + sc->s_dled.busy = 0; + sc->s_dled.sc = sc; + sc->s_dled.type = ACPI_ASUS_LED_DLED; + sc->s_dled.cdev = + led_create((led_t *)acpi_asus_led, &sc->s_dled, "dled"); + } + + if (sc->model->gled_set) { + sc->s_gled.busy = 0; + sc->s_gled.sc = sc; + sc->s_gled.type = ACPI_ASUS_LED_GLED; + sc->s_gled.cdev = + led_create((led_t *)acpi_asus_led, &sc->s_gled, "gled"); + } + + if (sc->model->mled_set) { + sc->s_mled.busy = 0; + sc->s_mled.sc = sc; + sc->s_mled.type = ACPI_ASUS_LED_MLED; + sc->s_mled.cdev = + led_create((led_t *)acpi_asus_led, &sc->s_mled, "mled"); + } + + if (sc->model->tled_set) { + sc->s_tled.busy = 0; + sc->s_tled.sc = sc; + sc->s_tled.type = ACPI_ASUS_LED_TLED; + sc->s_tled.cdev = + led_create_state((led_t *)acpi_asus_led, &sc->s_tled, + "tled", 1); + } + + if (sc->model->wled_set) { + sc->s_wled.busy = 0; + sc->s_wled.sc = sc; + sc->s_wled.type = ACPI_ASUS_LED_WLED; + sc->s_wled.cdev = + led_create_state((led_t *)acpi_asus_led, &sc->s_wled, + "wled", 1); + } + + /* Activate hotkeys */ + AcpiEvaluateObject(sc->handle, "BSTS", NULL, NULL); + + /* Handle notifies */ + if (sc->model->n_func == NULL) + sc->model->n_func = acpi_asus_notify; + + AcpiInstallNotifyHandler(sc->handle, ACPI_SYSTEM_NOTIFY, + sc->model->n_func, dev); + + /* Find and hook the 'LCDD' object */ + if (sc->model->lcdd != NULL && sc->model->lcdd_n_func != NULL) { + ACPI_STATUS res; + + sc->lcdd_handle = NULL; + res = AcpiGetHandle((sc->model->lcdd[0] == '\\' ? + NULL : sc->handle), sc->model->lcdd, &(sc->lcdd_handle)); + if (ACPI_SUCCESS(res)) { + AcpiInstallNotifyHandler((sc->lcdd_handle), + ACPI_DEVICE_NOTIFY, sc->model->lcdd_n_func, dev); + } else { + printf("%s: unable to find LCD device '%s'\n", + __func__, sc->model->lcdd); + } + } + + return (0); +} + +static int +acpi_asus_detach(device_t dev) +{ + struct acpi_asus_softc *sc; + + ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); + + sc = device_get_softc(dev); + + /* Turn the lights off */ + if (sc->model->bled_set) + led_destroy(sc->s_bled.cdev); + + if (sc->model->dled_set) + led_destroy(sc->s_dled.cdev); + + if (sc->model->gled_set) + led_destroy(sc->s_gled.cdev); + + if (sc->model->mled_set) + led_destroy(sc->s_mled.cdev); + + if (sc->model->tled_set) + led_destroy(sc->s_tled.cdev); + + if (sc->model->wled_set) + led_destroy(sc->s_wled.cdev); + + /* Remove notify handler */ + AcpiRemoveNotifyHandler(sc->handle, ACPI_SYSTEM_NOTIFY, + acpi_asus_notify); + + if (sc->lcdd_handle) { + KASSERT(sc->model->lcdd_n_func != NULL, + ("model->lcdd_n_func is NULL, but lcdd_handle is non-zero")); + AcpiRemoveNotifyHandler((sc->lcdd_handle), + ACPI_DEVICE_NOTIFY, sc->model->lcdd_n_func); + } + + /* Free sysctl tree */ + sysctl_ctx_free(&sc->sysctl_ctx); + + return (0); +} + +static void +acpi_asus_led_task(struct acpi_asus_led *led, int pending __unused) +{ + struct acpi_asus_softc *sc; + char *method; + int state; + + ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); + + sc = led->sc; + + switch (led->type) { + case ACPI_ASUS_LED_BLED: + method = sc->model->bled_set; + state = led->state; + break; + case ACPI_ASUS_LED_DLED: + method = sc->model->dled_set; + state = led->state; + break; + case ACPI_ASUS_LED_GLED: + method = sc->model->gled_set; + state = led->state + 1; /* 1: off, 2: on */ + break; + case ACPI_ASUS_LED_MLED: + method = sc->model->mled_set; + state = !led->state; /* inverted */ + break; + case ACPI_ASUS_LED_TLED: + method = sc->model->tled_set; + state = led->state; + break; + case ACPI_ASUS_LED_WLED: + method = sc->model->wled_set; + state = led->state; + break; + default: + printf("acpi_asus_led: invalid LED type %d\n", + (int)led->type); + return; + } + + acpi_SetInteger(sc->handle, method, state); + led->busy = 0; +} + +static void +acpi_asus_led(struct acpi_asus_led *led, int state) +{ + + ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); + + if (led->busy) + return; + + led->busy = 1; + led->state = state; + + AcpiOsExecute(OSL_NOTIFY_HANDLER, (void *)acpi_asus_led_task, led); +} + +static int +acpi_asus_sysctl(SYSCTL_HANDLER_ARGS) +{ + struct acpi_asus_softc *sc; + int arg; + int error = 0; + int function; + int method; + + ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); + + sc = (struct acpi_asus_softc *)oidp->oid_arg1; + function = oidp->oid_arg2; + method = acpi_asus_sysctls[function].method; + + ACPI_SERIAL_BEGIN(asus); + arg = acpi_asus_sysctl_get(sc, method); + error = sysctl_handle_int(oidp, &arg, 0, req); + + /* Sanity check */ + if (error != 0 || req->newptr == NULL) + goto out; + + /* Update */ + error = acpi_asus_sysctl_set(sc, method, arg); + +out: + ACPI_SERIAL_END(asus); + return (error); +} + +static int +acpi_asus_sysctl_get(struct acpi_asus_softc *sc, int method) +{ + int val = 0; + + ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); + ACPI_SERIAL_ASSERT(asus); + + switch (method) { + case ACPI_ASUS_METHOD_BRN: + val = sc->s_brn; + break; + case ACPI_ASUS_METHOD_DISP: + val = sc->s_disp; + break; + case ACPI_ASUS_METHOD_LCD: + val = sc->s_lcd; + break; + case ACPI_ASUS_METHOD_CAMERA: + val = sc->s_cam; + break; + case ACPI_ASUS_METHOD_CARDRD: + val = sc->s_crd; + break; + case ACPI_ASUS_METHOD_WLAN: + val = sc->s_wlan; + break; + } + + return (val); +} + +static int +acpi_asus_sysctl_set(struct acpi_asus_softc *sc, int method, int arg) +{ + ACPI_STATUS status = AE_OK; + ACPI_OBJECT_LIST acpiargs; + ACPI_OBJECT acpiarg[1]; + + ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); + ACPI_SERIAL_ASSERT(asus); + + acpiargs.Count = 1; + acpiargs.Pointer = acpiarg; + acpiarg[0].Type = ACPI_TYPE_INTEGER; + acpiarg[0].Integer.Value = arg; + + switch (method) { + case ACPI_ASUS_METHOD_BRN: + if (arg < 0 || arg > 15) + return (EINVAL); + + if (sc->model->brn_set) + status = acpi_SetInteger(sc->handle, + sc->model->brn_set, arg); + else { + while (arg != 0) { + status = AcpiEvaluateObject(sc->handle, + (arg > 0) ? sc->model->brn_up : + sc->model->brn_dn, NULL, NULL); + (arg > 0) ? arg-- : arg++; + } + } + + if (ACPI_SUCCESS(status)) + sc->s_brn = arg; + + break; + case ACPI_ASUS_METHOD_DISP: + if (arg < 0 || arg > 7) + return (EINVAL); + + status = acpi_SetInteger(sc->handle, + sc->model->disp_set, arg); + + if (ACPI_SUCCESS(status)) + sc->s_disp = arg; + + break; + case ACPI_ASUS_METHOD_LCD: + if (arg < 0 || arg > 1) + return (EINVAL); + + if (strncmp(sc->model->name, "L3H", 3) != 0) + status = AcpiEvaluateObject(sc->handle, + sc->model->lcd_set, NULL, NULL); + else + status = acpi_SetInteger(sc->handle, + sc->model->lcd_set, 0x7); + + if (ACPI_SUCCESS(status)) + sc->s_lcd = arg; + + break; + case ACPI_ASUS_METHOD_CAMERA: + if (arg < 0 || arg > 1) + return (EINVAL); + + status = AcpiEvaluateObject(sc->handle, + sc->model->cam_set, &acpiargs, NULL); + + if (ACPI_SUCCESS(status)) + sc->s_cam = arg; + break; + case ACPI_ASUS_METHOD_CARDRD: + if (arg < 0 || arg > 1) + return (EINVAL); + + status = AcpiEvaluateObject(sc->handle, + sc->model->crd_set, &acpiargs, NULL); + + if (ACPI_SUCCESS(status)) + sc->s_crd = arg; + break; + case ACPI_ASUS_METHOD_WLAN: + if (arg < 0 || arg > 1) + return (EINVAL); + + status = AcpiEvaluateObject(sc->handle, + sc->model->wlan_set, &acpiargs, NULL); + + if (ACPI_SUCCESS(status)) + sc->s_wlan = arg; + break; + } + + return (0); +} + +static int +acpi_asus_sysctl_init(struct acpi_asus_softc *sc, int method) +{ + ACPI_STATUS status; + + switch (method) { + case ACPI_ASUS_METHOD_BRN: + if (sc->model->brn_get) { + /* GPLV/SPLV models */ + status = acpi_GetInteger(sc->handle, + sc->model->brn_get, &sc->s_brn); + if (ACPI_SUCCESS(status)) + return (TRUE); + } else if (sc->model->brn_up) { + /* Relative models */ + status = AcpiEvaluateObject(sc->handle, + sc->model->brn_up, NULL, NULL); + if (ACPI_FAILURE(status)) + return (FALSE); + + status = AcpiEvaluateObject(sc->handle, + sc->model->brn_dn, NULL, NULL); + if (ACPI_FAILURE(status)) + return (FALSE); + + return (TRUE); + } + return (FALSE); + case ACPI_ASUS_METHOD_DISP: + if (sc->model->disp_get) { + status = acpi_GetInteger(sc->handle, + sc->model->disp_get, &sc->s_disp); + if (ACPI_SUCCESS(status)) + return (TRUE); + } + return (FALSE); + case ACPI_ASUS_METHOD_LCD: + if (sc->model->lcd_get) { + if (strncmp(sc->model->name, "G2K", 3) == 0) { + ACPI_BUFFER Buf; + ACPI_OBJECT Arg, Obj; + ACPI_OBJECT_LIST Args; + + Arg.Type = ACPI_TYPE_INTEGER; + Arg.Integer.Value = 0x11; + Args.Count = 1; + Args.Pointer = &Arg; + Buf.Length = sizeof(Obj); + Buf.Pointer = &Obj; + + status = AcpiEvaluateObject(sc->handle, + sc->model->lcd_get, &Args, &Buf); + if (ACPI_SUCCESS(status) && + Obj.Type == ACPI_TYPE_INTEGER) { + sc->s_lcd = Obj.Integer.Value; + return (TRUE); + } + } else if (strncmp(sc->model->name, "L3H", 3) == 0) { + ACPI_BUFFER Buf; + ACPI_OBJECT Arg[2], Obj; + ACPI_OBJECT_LIST Args; + + /* L3H is a bit special */ + Arg[0].Type = ACPI_TYPE_INTEGER; + Arg[0].Integer.Value = 0x02; + Arg[1].Type = ACPI_TYPE_INTEGER; + Arg[1].Integer.Value = 0x03; + + Args.Count = 2; + Args.Pointer = Arg; + + Buf.Length = sizeof(Obj); + Buf.Pointer = &Obj; + + status = AcpiEvaluateObject(sc->handle, + sc->model->lcd_get, &Args, &Buf); + if (ACPI_SUCCESS(status) && + Obj.Type == ACPI_TYPE_INTEGER) { + sc->s_lcd = Obj.Integer.Value >> 8; + return (TRUE); + } + } else { + status = acpi_GetInteger(sc->handle, + sc->model->lcd_get, &sc->s_lcd); + if (ACPI_SUCCESS(status)) + return (TRUE); + } + } + return (FALSE); + case ACPI_ASUS_METHOD_CAMERA: + if (sc->model->cam_get) { + status = acpi_GetInteger(sc->handle, + sc->model->cam_get, &sc->s_cam); + if (ACPI_SUCCESS(status)) + return (TRUE); + } + return (FALSE); + case ACPI_ASUS_METHOD_CARDRD: + if (sc->model->crd_get) { + status = acpi_GetInteger(sc->handle, + sc->model->crd_get, &sc->s_crd); + if (ACPI_SUCCESS(status)) + return (TRUE); + } + return (FALSE); + case ACPI_ASUS_METHOD_WLAN: + if (sc->model->wlan_get) { + status = acpi_GetInteger(sc->handle, + sc->model->wlan_get, &sc->s_wlan); + if (ACPI_SUCCESS(status)) + return (TRUE); + } + return (FALSE); + } + return (FALSE); +} + +static void +acpi_asus_notify(ACPI_HANDLE h, UINT32 notify, void *context) +{ + struct acpi_asus_softc *sc; + struct acpi_softc *acpi_sc; + + ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); + + sc = device_get_softc((device_t)context); + acpi_sc = acpi_device_get_parent_softc(sc->dev); + + ACPI_SERIAL_BEGIN(asus); + if ((notify & ~0x10) <= 15) { + sc->s_brn = notify & ~0x10; + ACPI_VPRINT(sc->dev, acpi_sc, "Brightness increased\n"); + } else if ((notify & ~0x20) <= 15) { + sc->s_brn = notify & ~0x20; + ACPI_VPRINT(sc->dev, acpi_sc, "Brightness decreased\n"); + } else if (notify == 0x33) { + sc->s_lcd = 1; + ACPI_VPRINT(sc->dev, acpi_sc, "LCD turned on\n"); + } else if (notify == 0x34) { + sc->s_lcd = 0; + ACPI_VPRINT(sc->dev, acpi_sc, "LCD turned off\n"); + } else if (notify == 0x86) { + acpi_asus_sysctl_set(sc, ACPI_ASUS_METHOD_BRN, sc->s_brn-1); + ACPI_VPRINT(sc->dev, acpi_sc, "Brightness decreased\n"); + } else if (notify == 0x87) { + acpi_asus_sysctl_set(sc, ACPI_ASUS_METHOD_BRN, sc->s_brn+1); + ACPI_VPRINT(sc->dev, acpi_sc, "Brightness increased\n"); + } else { + /* Notify devd(8) */ + acpi_UserNotify("ASUS", h, notify); + } + ACPI_SERIAL_END(asus); +} + +static void +acpi_asus_lcdd_notify(ACPI_HANDLE h, UINT32 notify, void *context) +{ + struct acpi_asus_softc *sc; + struct acpi_softc *acpi_sc; + + ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); + + sc = device_get_softc((device_t)context); + acpi_sc = acpi_device_get_parent_softc(sc->dev); + + ACPI_SERIAL_BEGIN(asus); + switch (notify) { + case 0x87: + acpi_asus_sysctl_set(sc, ACPI_ASUS_METHOD_BRN, sc->s_brn-1); + ACPI_VPRINT(sc->dev, acpi_sc, "Brightness decreased\n"); + break; + case 0x86: + acpi_asus_sysctl_set(sc, ACPI_ASUS_METHOD_BRN, sc->s_brn+1); + ACPI_VPRINT(sc->dev, acpi_sc, "Brightness increased\n"); + break; + } + ACPI_SERIAL_END(asus); +} + +static void +acpi_asus_eeepc_notify(ACPI_HANDLE h, UINT32 notify, void *context) +{ + struct acpi_asus_softc *sc; + struct acpi_softc *acpi_sc; + + ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); + + sc = device_get_softc((device_t)context); + acpi_sc = acpi_device_get_parent_softc(sc->dev); + + ACPI_SERIAL_BEGIN(asus); + if ((notify & ~0x20) <= 15) { + sc->s_brn = notify & ~0x20; + ACPI_VPRINT(sc->dev, acpi_sc, + "Brightness increased/decreased\n"); + } else { + /* Notify devd(8) */ + acpi_UserNotify("ASUS-Eee", h, notify); + } + ACPI_SERIAL_END(asus); +} diff --git a/sys/dev/acpi_support/acpi_fujitsu.c b/sys/dev/acpi_support/acpi_fujitsu.c new file mode 100644 index 000000000000..5de365a060ce --- /dev/null +++ b/sys/dev/acpi_support/acpi_fujitsu.c @@ -0,0 +1,716 @@ +/*- + * Copyright (c) 2002 Sean Bullington <seanATstalker.org> + * 2003-2006 Anish Mistry <amistry@am-productions.biz> + * 2004 Mark Santcroos <marks@ripe.net> + * 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. + * + * 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 <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include "opt_acpi.h" +#include <sys/param.h> +#include <sys/kernel.h> +#include <sys/bus.h> +#include <sys/module.h> +#include <sys/sysctl.h> + +#include <contrib/dev/acpica/acpi.h> +#include <dev/acpica/acpivar.h> + +/* Hooks for the ACPI CA debugging infrastructure */ +#define _COMPONENT ACPI_OEM +ACPI_MODULE_NAME("Fujitsu") + +/* Change and update bits for the hotkeys */ +#define VOLUME_MUTE_BIT 0x40000000 + +/* Values of settings */ +#define GENERAL_SETTING_BITS 0x0fffffff +#define MOUSE_SETTING_BITS GENERAL_SETTING_BITS +#define VOLUME_SETTING_BITS GENERAL_SETTING_BITS +#define BRIGHTNESS_SETTING_BITS GENERAL_SETTING_BITS + +/* Possible state changes */ +/* + * These are NOT arbitrary values. They are the + * GHKS return value from the device that says which + * hotkey is active. They should match up with a bit + * from the GSIF bitmask. + */ +#define BRIGHT_CHANGED 0x01 +#define VOLUME_CHANGED 0x04 +#define MOUSE_CHANGED 0x08 +/* + * It is unknown which hotkey this bit is supposed to indicate, but + * according to values from GSIF this is a valid flag. + */ +#define UNKNOWN_CHANGED 0x10 + +/* sysctl values */ +#define FN_MUTE 0 +#define FN_POINTER_ENABLE 1 +#define FN_LCD_BRIGHTNESS 2 +#define FN_VOLUME 3 + +/* Methods */ +#define METHOD_GBLL 1 +#define METHOD_GMOU 2 +#define METHOD_GVOL 3 +#define METHOD_MUTE 4 +#define METHOD_RBLL 5 +#define METHOD_RVOL 6 +#define METHOD_GSIF 7 +#define METHOD_GHKS 8 + +/* Notify event */ +#define ACPI_NOTIFY_STATUS_CHANGED 0x80 + +/* + * Holds a control method name and its associated integer value. + * Only used for no-argument control methods which return a value. + */ +struct int_nameval { + char *name; + int value; + int exists; +}; + +/* + * Driver extension for the FUJITSU ACPI driver. + */ +struct acpi_fujitsu_softc { + device_t dev; + ACPI_HANDLE handle; + + /* Control methods */ + struct int_nameval _sta, /* unused */ + gbll, /* brightness */ + ghks, /* hotkey selector */ + gbuf, /* unused (buffer?) */ + gmou, /* mouse */ + gsif, /* function key bitmask */ + gvol, /* volume */ + rbll, /* number of brightness levels (radix) */ + rvol; /* number of volume levels (radix) */ + + /* State variables */ + uint8_t bIsMuted; /* Is volume muted */ + uint8_t bIntPtrEnabled; /* Is internal ptr enabled */ + uint32_t lastValChanged; /* The last value updated */ + + /* sysctl tree */ + struct sysctl_ctx_list sysctl_ctx; + struct sysctl_oid *sysctl_tree; +}; + +/* Driver entry point forward declarations. */ +static int acpi_fujitsu_probe(device_t dev); +static int acpi_fujitsu_attach(device_t dev); +static int acpi_fujitsu_detach(device_t dev); +static int acpi_fujitsu_suspend(device_t dev); +static int acpi_fujitsu_resume(device_t dev); + +static void acpi_fujitsu_notify_status_changed(void *arg); +static void acpi_fujitsu_notify_handler(ACPI_HANDLE h, uint32_t notify, void *context); +static int acpi_fujitsu_sysctl(SYSCTL_HANDLER_ARGS); + +/* Utility function declarations */ +static uint8_t acpi_fujitsu_update(struct acpi_fujitsu_softc *sc); +static uint8_t acpi_fujitsu_init(struct acpi_fujitsu_softc *sc); +static uint8_t acpi_fujitsu_check_hardware(struct acpi_fujitsu_softc *sc); + +/* Driver/Module specific structure definitions. */ +static device_method_t acpi_fujitsu_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, acpi_fujitsu_probe), + DEVMETHOD(device_attach, acpi_fujitsu_attach), + DEVMETHOD(device_detach, acpi_fujitsu_detach), + DEVMETHOD(device_suspend, acpi_fujitsu_suspend), + DEVMETHOD(device_resume, acpi_fujitsu_resume), + {0, 0} +}; + +static driver_t acpi_fujitsu_driver = { + "acpi_fujitsu", + acpi_fujitsu_methods, + sizeof(struct acpi_fujitsu_softc), +}; + +/* Prototype for function hotkeys for getting/setting a value. */ +static int acpi_fujitsu_method_get(struct acpi_fujitsu_softc *sc, int method); +static int acpi_fujitsu_method_set(struct acpi_fujitsu_softc *sc, int method, int value); + +static char *fujitsu_ids[] = { "FUJ02B1", NULL }; + +ACPI_SERIAL_DECL(fujitsu, "Fujitsu Function Hotkeys"); + +/* sysctl names and function calls */ +static struct { + char *name; + int method; + char *description; +} sysctl_table[] = { + { + .name = "mute", + .method = METHOD_MUTE, + .description = "Speakers/headphones mute status" + }, + { + .name = "pointer_enable", + .method = METHOD_GMOU, + .description = "Enable and disable the internal pointer" + }, + { + .name = "lcd_brightness", + .method = METHOD_GBLL, + .description = "Brightness level of the LCD panel" + }, + { + .name = "volume", + .method = METHOD_GVOL, + .description = "Speakers/headphones volume level" + }, + { + .name = "volume_radix", + .method = METHOD_RVOL, + .description = "Number of volume level steps" + }, + { + .name = "lcd_brightness_radix", + .method = METHOD_RBLL, + .description = "Number of brightness level steps" + }, + + { NULL, 0, NULL } +}; + +static devclass_t acpi_fujitsu_devclass; +DRIVER_MODULE(acpi_fujitsu, acpi, acpi_fujitsu_driver, + acpi_fujitsu_devclass, 0, 0); +MODULE_DEPEND(acpi_fujitsu, acpi, 1, 1, 1); +MODULE_VERSION(acpi_fujitsu, 1); + +static int +acpi_fujitsu_probe(device_t dev) +{ + char *name; + char buffer[64]; + + name = ACPI_ID_PROBE(device_get_parent(dev), dev, fujitsu_ids); + if (acpi_disabled("fujitsu") || name == NULL || + device_get_unit(dev) > 1) + return (ENXIO); + + sprintf(buffer, "Fujitsu Function Hotkeys %s", name); + device_set_desc_copy(dev, buffer); + + return (0); +} + +static int +acpi_fujitsu_attach(device_t dev) +{ + struct acpi_fujitsu_softc *sc; + + ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); + + sc = device_get_softc(dev); + sc->dev = dev; + sc->handle = acpi_get_handle(dev); + + /* Install notification handler */ + AcpiInstallNotifyHandler(sc->handle, ACPI_DEVICE_NOTIFY, + acpi_fujitsu_notify_handler, sc); + + /* Snag our default values for the hotkeys / hotkey states. */ + ACPI_SERIAL_BEGIN(fujitsu); + if (!acpi_fujitsu_init(sc)) + device_printf(dev, "Couldn't initialize hotkey states!\n"); + ACPI_SERIAL_END(fujitsu); + + return (0); +} + +/* + * Called when the system is being suspended, simply + * set an event to be signalled when we wake up. + */ +static int +acpi_fujitsu_suspend(device_t dev) +{ + + return (0); +} + +static int +acpi_fujitsu_resume(device_t dev) +{ + struct acpi_fujitsu_softc *sc; + ACPI_STATUS status; + + sc = device_get_softc(dev); + + /* + * The pointer needs to be re-enabled for + * some revisions of the P series (2120). + */ + ACPI_SERIAL_BEGIN(fujitsu); + + if(sc->gmou.exists) { + status = acpi_SetInteger(sc->handle, "SMOU", 1); + if (ACPI_FAILURE(status)) + device_printf(sc->dev, "Couldn't enable pointer\n"); + } + ACPI_SERIAL_END(fujitsu); + + return (0); +} + +static void +acpi_fujitsu_notify_status_changed(void *arg) +{ + struct acpi_fujitsu_softc *sc; + + ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); + + sc = (struct acpi_fujitsu_softc *)arg; + + /* + * Since our notify function is called, we know something has + * happened. So the only reason for acpi_fujitsu_update to fail + * is if we can't find what has changed or an error occurs. + */ + ACPI_SERIAL_BEGIN(fujitsu); + acpi_fujitsu_update(sc); + ACPI_SERIAL_END(fujitsu); +} + + +static void +acpi_fujitsu_notify_handler(ACPI_HANDLE h, uint32_t notify, void *context) +{ + struct acpi_fujitsu_softc *sc; + + ACPI_FUNCTION_TRACE_U32((char *)(uintptr_t)__func__, notify); + + sc = (struct acpi_fujitsu_softc *)context; + + switch (notify) { + case ACPI_NOTIFY_STATUS_CHANGED: + AcpiOsExecute(OSL_NOTIFY_HANDLER, + acpi_fujitsu_notify_status_changed, sc); + break; + default: + /* unknown notification value */ + break; + } +} + +static int +acpi_fujitsu_detach(device_t dev) +{ + struct acpi_fujitsu_softc *sc; + + sc = device_get_softc(dev); + AcpiRemoveNotifyHandler(sc->handle, ACPI_DEVICE_NOTIFY, + acpi_fujitsu_notify_handler); + + sysctl_ctx_free(&sc->sysctl_ctx); + + return (0); +} + +/* + * Initializes the names of the ACPI control methods and grabs + * the current state of all of the ACPI hotkeys into the softc. + */ +static uint8_t +acpi_fujitsu_init(struct acpi_fujitsu_softc *sc) +{ + struct acpi_softc *acpi_sc; + int i, exists; + + ACPI_SERIAL_ASSERT(fujitsu); + + /* Setup all of the names for each control method */ + sc->_sta.name = "_STA"; + sc->gbll.name = "GBLL"; + sc->ghks.name = "GHKS"; + sc->gmou.name = "GMOU"; + sc->gsif.name = "GSIF"; + sc->gvol.name = "GVOL"; + sc->ghks.name = "GHKS"; + sc->gsif.name = "GSIF"; + sc->rbll.name = "RBLL"; + sc->rvol.name = "RVOL"; + + /* Determine what hardware functionality is available */ + acpi_fujitsu_check_hardware(sc); + + /* Build the sysctl tree */ + 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, "fujitsu", CTLFLAG_RD, 0, ""); + + for (i = 0; sysctl_table[i].name != NULL; i++) { + exists = 0; + switch(sysctl_table[i].method) { + case METHOD_GMOU: + exists = sc->gmou.exists; + break; + case METHOD_GBLL: + exists = sc->gbll.exists; + break; + case METHOD_GVOL: + case METHOD_MUTE: + exists = sc->gvol.exists; + break; + case METHOD_RVOL: + exists = sc->rvol.exists; + break; + case METHOD_RBLL: + exists = sc->rbll.exists; + break; + default: + /* Allow by default */ + exists = 1; + break; + } + if(!exists) + continue; + SYSCTL_ADD_PROC(&sc->sysctl_ctx, + SYSCTL_CHILDREN(sc->sysctl_tree), OID_AUTO, + sysctl_table[i].name, + CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_ANYBODY, + sc, i, acpi_fujitsu_sysctl, "I", + sysctl_table[i].description); + } + + + /* Set the hotkeys to their initial states */ + if (!acpi_fujitsu_update(sc)) { + device_printf(sc->dev, "Couldn't init hotkey states\n"); + return (FALSE); + } + + return (TRUE); +} + +static int +acpi_fujitsu_sysctl(SYSCTL_HANDLER_ARGS) +{ + struct acpi_fujitsu_softc *sc; + int method; + int arg; + int function_num, error = 0; + + sc = (struct acpi_fujitsu_softc *)oidp->oid_arg1; + function_num = oidp->oid_arg2; + method = sysctl_table[function_num].method; + + ACPI_SERIAL_BEGIN(fujitsu); + + /* Get the current value */ + arg = acpi_fujitsu_method_get(sc, method); + error = sysctl_handle_int(oidp, &arg, 0, req); + + if (error != 0 || req->newptr == NULL) + goto out; + + /* Update the value */ + error = acpi_fujitsu_method_set(sc, method, arg); + +out: + ACPI_SERIAL_END(fujitsu); + return (error); +} + +static int +acpi_fujitsu_method_get(struct acpi_fujitsu_softc *sc, int method) +{ + struct int_nameval nv; + ACPI_STATUS status; + + ACPI_SERIAL_ASSERT(fujitsu); + + switch (method) { + case METHOD_GBLL: + nv = sc->gbll; + break; + case METHOD_GMOU: + nv = sc->gmou; + break; + case METHOD_GVOL: + case METHOD_MUTE: + nv = sc->gvol; + break; + case METHOD_GHKS: + nv = sc->ghks; + break; + case METHOD_GSIF: + nv = sc->gsif; + break; + case METHOD_RBLL: + nv = sc->rbll; + break; + case METHOD_RVOL: + nv = sc->rvol; + break; + default: + return (FALSE); + } + + if(!nv.exists) + return (EINVAL); + + status = acpi_GetInteger(sc->handle, nv.name, &nv.value); + if (ACPI_FAILURE(status)) { + device_printf(sc->dev, "Couldn't query method (%s)\n", nv.name); + return (FALSE); + } + + if (method == METHOD_MUTE) { + sc->bIsMuted = (uint8_t)((nv.value & VOLUME_MUTE_BIT) != 0); + return (sc->bIsMuted); + } + + nv.value &= GENERAL_SETTING_BITS; + return (nv.value); +} + +static int +acpi_fujitsu_method_set(struct acpi_fujitsu_softc *sc, int method, int value) +{ + struct int_nameval nv; + ACPI_STATUS status; + char *control; + int changed; + + ACPI_SERIAL_ASSERT(fujitsu); + + switch (method) { + case METHOD_GBLL: + changed = BRIGHT_CHANGED; + control = "SBLL"; + nv = sc->gbll; + break; + case METHOD_GMOU: + changed = MOUSE_CHANGED; + control = "SMOU"; + nv = sc->gmou; + break; + case METHOD_GVOL: + case METHOD_MUTE: + changed = VOLUME_CHANGED; + control = "SVOL"; + nv = sc->gvol; + break; + default: + return (EINVAL); + } + + if(!nv.exists) + return (EINVAL); + + if (method == METHOD_MUTE) { + if (value == 1) + value = nv.value | VOLUME_MUTE_BIT; + else if (value == 0) + value = nv.value & ~VOLUME_MUTE_BIT; + else + return (EINVAL); + } + + status = acpi_SetInteger(sc->handle, control, value); + if (ACPI_FAILURE(status)) { + device_printf(sc->dev, "Couldn't update %s\n", control); + return (FALSE); + } + + sc->lastValChanged = changed; + return (0); +} + +/* + * Query the get methods to determine what functionality is available + * from the hardware function hotkeys. + */ +static uint8_t +acpi_fujitsu_check_hardware(struct acpi_fujitsu_softc *sc) +{ + int val; + struct acpi_softc *acpi_sc; + + acpi_sc = acpi_device_get_parent_softc(sc->dev); + + ACPI_SERIAL_ASSERT(fujitsu); + /* save the hotkey bitmask */ + if (ACPI_FAILURE(acpi_GetInteger(sc->handle, + sc->gsif.name, &(sc->gsif.value)))) { + sc->gsif.exists = 0; + device_printf(sc->dev, "Couldn't query bitmask value\n"); + } else { + sc->gsif.exists = 1; + } + + /* System Volume Level */ + if (ACPI_FAILURE(acpi_GetInteger(sc->handle, + sc->gvol.name, &val))) { + sc->gvol.exists = 0; + } else { + sc->gvol.exists = 1; + } + + if (ACPI_FAILURE(acpi_GetInteger(sc->handle, + sc->gbll.name, &val))) { + sc->gbll.exists = 0; + } else { + sc->gbll.exists = 1; + } + + if (ACPI_FAILURE(acpi_GetInteger(sc->handle, + sc->ghks.name, &val))) { + sc->ghks.exists = 0; + } else { + sc->ghks.exists = 1; + } + + if (ACPI_FAILURE(acpi_GetInteger(sc->handle, + sc->gmou.name, &val))) { + sc->gmou.exists = 0; + } else { + sc->gmou.exists = 1; + } + + if (ACPI_FAILURE(acpi_GetInteger(sc->handle, + sc->rbll.name, &val))) { + sc->rbll.exists = 0; + } else { + sc->rbll.exists = 1; + } + + if (ACPI_FAILURE(acpi_GetInteger(sc->handle, + sc->rvol.name, &val))) { + sc->rvol.exists = 0; + } else { + sc->rvol.exists = 1; + } + + return (TRUE); +} + +/* + * Query each of the ACPI control methods that contain information we're + * interested in. We check the return values from the control methods and + * adjust any state variables if they should be adjusted. + */ +static uint8_t +acpi_fujitsu_update(struct acpi_fujitsu_softc *sc) +{ + int changed; + struct acpi_softc *acpi_sc; + + acpi_sc = acpi_device_get_parent_softc(sc->dev); + + ACPI_SERIAL_ASSERT(fujitsu); + if(sc->gsif.exists) + changed = sc->gsif.value & acpi_fujitsu_method_get(sc,METHOD_GHKS); + else + changed = 0; + + /* System Volume Level */ + if(sc->gvol.exists) { + if (ACPI_FAILURE(acpi_GetInteger(sc->handle, + sc->gvol.name, &(sc->gvol.value)))) { + device_printf(sc->dev, "Couldn't query volume level\n"); + return (FALSE); + } + + if (changed & VOLUME_CHANGED) { + sc->bIsMuted = + (uint8_t)((sc->gvol.value & VOLUME_MUTE_BIT) != 0); + + /* Clear the modification bit */ + sc->gvol.value &= VOLUME_SETTING_BITS; + + if (sc->bIsMuted) { + acpi_UserNotify("FUJITSU", sc->handle, FN_MUTE); + ACPI_VPRINT(sc->dev, acpi_sc, "Volume is now mute\n"); + } else + ACPI_VPRINT(sc->dev, acpi_sc, "Volume is now %d\n", + sc->gvol.value); + + acpi_UserNotify("FUJITSU", sc->handle, FN_VOLUME); + } + } + + /* Internal mouse pointer (eraserhead) */ + if(sc->gmou.exists) { + if (ACPI_FAILURE(acpi_GetInteger(sc->handle, + sc->gmou.name, &(sc->gmou.value)))) { + device_printf(sc->dev, "Couldn't query pointer state\n"); + return (FALSE); + } + + if (changed & MOUSE_CHANGED) { + sc->bIntPtrEnabled = (uint8_t)(sc->gmou.value & 0x1); + + /* Clear the modification bit */ + sc->gmou.value &= MOUSE_SETTING_BITS; + + acpi_UserNotify("FUJITSU", sc->handle, FN_POINTER_ENABLE); + + ACPI_VPRINT(sc->dev, acpi_sc, "Internal pointer is now %s\n", + (sc->bIntPtrEnabled) ? "enabled" : "disabled"); + } + } + + /* Screen Brightness Level */ + if(sc->gbll.exists) { + if (ACPI_FAILURE(acpi_GetInteger(sc->handle, + sc->gbll.name, &(sc->gbll.value)))) { + device_printf(sc->dev, "Couldn't query brightness level\n"); + return (FALSE); + } + + if (changed & BRIGHT_CHANGED) { + /* No state to record here. */ + + /* Clear the modification bit */ + sc->gbll.value &= BRIGHTNESS_SETTING_BITS; + + acpi_UserNotify("FUJITSU", sc->handle, FN_LCD_BRIGHTNESS); + + ACPI_VPRINT(sc->dev, acpi_sc, "Brightness level is now %d\n", + sc->gbll.value); + } + } + + sc->lastValChanged = changed; + return (TRUE); +} diff --git a/sys/dev/acpi_support/acpi_ibm.c b/sys/dev/acpi_support/acpi_ibm.c new file mode 100644 index 000000000000..ca67716ed8d6 --- /dev/null +++ b/sys/dev/acpi_support/acpi_ibm.c @@ -0,0 +1,948 @@ +/*- + * Copyright (c) 2004 Takanori Watanabe + * Copyright (c) 2005 Markus Brueffer <markus@FreeBSD.org> + * 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. + * + * 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 <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +/* + * Driver for extra ACPI-controlled gadgets found on IBM ThinkPad laptops. + * Inspired by the ibm-acpi and tpb projects which implement these features + * on Linux. + * + * acpi-ibm: <http://ibm-acpi.sourceforge.net/> + * tpb: <http://www.nongnu.org/tpb/> + */ + +#include "opt_acpi.h" +#include <sys/param.h> +#include <sys/kernel.h> +#include <sys/bus.h> +#include <machine/cpufunc.h> +#include <contrib/dev/acpica/acpi.h> +#include "acpi_if.h" +#include <sys/module.h> +#include <dev/acpica/acpivar.h> +#include <dev/led/led.h> +#include <sys/sysctl.h> +#include <isa/rtc.h> + +#define _COMPONENT ACPI_OEM +ACPI_MODULE_NAME("IBM") + +/* Internal methods */ +#define ACPI_IBM_METHOD_EVENTS 1 +#define ACPI_IBM_METHOD_EVENTMASK 2 +#define ACPI_IBM_METHOD_HOTKEY 3 +#define ACPI_IBM_METHOD_BRIGHTNESS 4 +#define ACPI_IBM_METHOD_VOLUME 5 +#define ACPI_IBM_METHOD_MUTE 6 +#define ACPI_IBM_METHOD_THINKLIGHT 7 +#define ACPI_IBM_METHOD_BLUETOOTH 8 +#define ACPI_IBM_METHOD_WLAN 9 +#define ACPI_IBM_METHOD_FANSPEED 10 +#define ACPI_IBM_METHOD_FANLEVEL 11 +#define ACPI_IBM_METHOD_FANSTATUS 12 +#define ACPI_IBM_METHOD_THERMAL 13 + +/* Hotkeys/Buttons */ +#define IBM_RTC_HOTKEY1 0x64 +#define IBM_RTC_MASK_HOME (1 << 0) +#define IBM_RTC_MASK_SEARCH (1 << 1) +#define IBM_RTC_MASK_MAIL (1 << 2) +#define IBM_RTC_MASK_WLAN (1 << 5) +#define IBM_RTC_HOTKEY2 0x65 +#define IBM_RTC_MASK_THINKPAD (1 << 3) +#define IBM_RTC_MASK_ZOOM (1 << 5) +#define IBM_RTC_MASK_VIDEO (1 << 6) +#define IBM_RTC_MASK_HIBERNATE (1 << 7) +#define IBM_RTC_THINKLIGHT 0x66 +#define IBM_RTC_MASK_THINKLIGHT (1 << 4) +#define IBM_RTC_SCREENEXPAND 0x67 +#define IBM_RTC_MASK_SCREENEXPAND (1 << 5) +#define IBM_RTC_BRIGHTNESS 0x6c +#define IBM_RTC_MASK_BRIGHTNESS (1 << 5) +#define IBM_RTC_VOLUME 0x6e +#define IBM_RTC_MASK_VOLUME (1 << 7) + +/* Embedded Controller registers */ +#define IBM_EC_BRIGHTNESS 0x31 +#define IBM_EC_MASK_BRI 0x7 +#define IBM_EC_VOLUME 0x30 +#define IBM_EC_MASK_VOL 0xf +#define IBM_EC_MASK_MUTE (1 << 6) +#define IBM_EC_FANSTATUS 0x2F +#define IBM_EC_MASK_FANLEVEL 0x3f +#define IBM_EC_MASK_FANDISENGAGED (1 << 6) +#define IBM_EC_MASK_FANSTATUS (1 << 7) +#define IBM_EC_FANSPEED 0x84 + +/* CMOS Commands */ +#define IBM_CMOS_VOLUME_DOWN 0 +#define IBM_CMOS_VOLUME_UP 1 +#define IBM_CMOS_VOLUME_MUTE 2 +#define IBM_CMOS_BRIGHTNESS_UP 4 +#define IBM_CMOS_BRIGHTNESS_DOWN 5 + +/* ACPI methods */ +#define IBM_NAME_KEYLIGHT "KBLT" +#define IBM_NAME_WLAN_BT_GET "GBDC" +#define IBM_NAME_WLAN_BT_SET "SBDC" +#define IBM_NAME_MASK_BT (1 << 1) +#define IBM_NAME_MASK_WLAN (1 << 2) +#define IBM_NAME_THERMAL_GET "TMP7" +#define IBM_NAME_THERMAL_UPDT "UPDT" + +#define IBM_NAME_EVENTS_STATUS_GET "DHKC" +#define IBM_NAME_EVENTS_MASK_GET "DHKN" +#define IBM_NAME_EVENTS_STATUS_SET "MHKC" +#define IBM_NAME_EVENTS_MASK_SET "MHKM" +#define IBM_NAME_EVENTS_GET "MHKP" +#define IBM_NAME_EVENTS_AVAILMASK "MHKA" + +#define ABS(x) (((x) < 0)? -(x) : (x)) + +struct acpi_ibm_softc { + device_t dev; + ACPI_HANDLE handle; + + /* Embedded controller */ + device_t ec_dev; + ACPI_HANDLE ec_handle; + + /* CMOS */ + ACPI_HANDLE cmos_handle; + + /* Fan status */ + ACPI_HANDLE fan_handle; + int fan_levels; + + /* Keylight commands and states */ + ACPI_HANDLE light_handle; + int light_cmd_on; + int light_cmd_off; + int light_val; + int light_get_supported; + int light_set_supported; + + /* led(4) interface */ + struct cdev *led_dev; + int led_busy; + int led_state; + + int wlan_bt_flags; + int thermal_updt_supported; + + unsigned int events_availmask; + unsigned int events_initialmask; + int events_mask_supported; + int events_enable; + + struct sysctl_ctx_list *sysctl_ctx; + struct sysctl_oid *sysctl_tree; +}; + +static struct { + char *name; + int method; + char *description; + int access; +} acpi_ibm_sysctls[] = { + { + .name = "events", + .method = ACPI_IBM_METHOD_EVENTS, + .description = "ACPI events enable", + .access = CTLTYPE_INT | CTLFLAG_RW + }, + { + .name = "eventmask", + .method = ACPI_IBM_METHOD_EVENTMASK, + .description = "ACPI eventmask", + .access = CTLTYPE_INT | CTLFLAG_RW + }, + { + .name = "hotkey", + .method = ACPI_IBM_METHOD_HOTKEY, + .description = "Key Status", + .access = CTLTYPE_INT | CTLFLAG_RD + }, + { + .name = "lcd_brightness", + .method = ACPI_IBM_METHOD_BRIGHTNESS, + .description = "LCD Brightness", + .access = CTLTYPE_INT | CTLFLAG_RW + }, + { + .name = "volume", + .method = ACPI_IBM_METHOD_VOLUME, + .description = "Volume", + .access = CTLTYPE_INT | CTLFLAG_RW + }, + { + .name = "mute", + .method = ACPI_IBM_METHOD_MUTE, + .description = "Mute", + .access = CTLTYPE_INT | CTLFLAG_RW + }, + { + .name = "thinklight", + .method = ACPI_IBM_METHOD_THINKLIGHT, + .description = "Thinklight enable", + .access = CTLTYPE_INT | CTLFLAG_RW + }, + { + .name = "bluetooth", + .method = ACPI_IBM_METHOD_BLUETOOTH, + .description = "Bluetooth enable", + .access = CTLTYPE_INT | CTLFLAG_RW + }, + { + .name = "wlan", + .method = ACPI_IBM_METHOD_WLAN, + .description = "WLAN enable", + .access = CTLTYPE_INT | CTLFLAG_RD + }, + { + .name = "fan_speed", + .method = ACPI_IBM_METHOD_FANSPEED, + .description = "Fan speed", + .access = CTLTYPE_INT | CTLFLAG_RD + }, + { + .name = "fan_level", + .method = ACPI_IBM_METHOD_FANLEVEL, + .description = "Fan level", + .access = CTLTYPE_INT | CTLFLAG_RW + }, + { + .name = "fan", + .method = ACPI_IBM_METHOD_FANSTATUS, + .description = "Fan enable", + .access = CTLTYPE_INT | CTLFLAG_RW + }, + + { NULL, 0, NULL, 0 } +}; + +ACPI_SERIAL_DECL(ibm, "ACPI IBM extras"); + +static int acpi_ibm_probe(device_t dev); +static int acpi_ibm_attach(device_t dev); +static int acpi_ibm_detach(device_t dev); + +static void ibm_led(void *softc, int onoff); +static void ibm_led_task(struct acpi_ibm_softc *sc, int pending __unused); + +static int acpi_ibm_sysctl(SYSCTL_HANDLER_ARGS); +static int acpi_ibm_sysctl_init(struct acpi_ibm_softc *sc, int method); +static int acpi_ibm_sysctl_get(struct acpi_ibm_softc *sc, int method); +static int acpi_ibm_sysctl_set(struct acpi_ibm_softc *sc, int method, int val); + +static int acpi_ibm_eventmask_set(struct acpi_ibm_softc *sc, int val); +static int acpi_ibm_thermal_sysctl(SYSCTL_HANDLER_ARGS); +static void acpi_ibm_notify(ACPI_HANDLE h, UINT32 notify, void *context); + +static device_method_t acpi_ibm_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, acpi_ibm_probe), + DEVMETHOD(device_attach, acpi_ibm_attach), + DEVMETHOD(device_detach, acpi_ibm_detach), + + {0, 0} +}; + +static driver_t acpi_ibm_driver = { + "acpi_ibm", + acpi_ibm_methods, + sizeof(struct acpi_ibm_softc), +}; + +static devclass_t acpi_ibm_devclass; + +DRIVER_MODULE(acpi_ibm, acpi, acpi_ibm_driver, acpi_ibm_devclass, + 0, 0); +MODULE_DEPEND(acpi_ibm, acpi, 1, 1, 1); +static char *ibm_ids[] = {"IBM0057", "IBM0068", NULL}; + +static void +ibm_led(void *softc, int onoff) +{ + struct acpi_ibm_softc* sc = (struct acpi_ibm_softc*) softc; + + ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); + + if (sc->led_busy) + return; + + sc->led_busy = 1; + sc->led_state = onoff; + + AcpiOsExecute(OSL_NOTIFY_HANDLER, (void *)ibm_led_task, sc); +} + +static void +ibm_led_task(struct acpi_ibm_softc *sc, int pending __unused) +{ + ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); + + ACPI_SERIAL_BEGIN(ibm); + acpi_ibm_sysctl_set(sc, ACPI_IBM_METHOD_THINKLIGHT, sc->led_state); + ACPI_SERIAL_END(ibm); + + sc->led_busy = 0; +} + +static int +acpi_ibm_probe(device_t dev) +{ + if (acpi_disabled("ibm") || + ACPI_ID_PROBE(device_get_parent(dev), dev, ibm_ids) == NULL || + device_get_unit(dev) != 0) + return (ENXIO); + + device_set_desc(dev, "IBM ThinkPad ACPI Extras"); + + return (0); +} + +static int +acpi_ibm_attach(device_t dev) +{ + struct acpi_ibm_softc *sc; + devclass_t ec_devclass; + + ACPI_FUNCTION_TRACE((char *)(uintptr_t) __func__); + + sc = device_get_softc(dev); + sc->dev = dev; + sc->handle = acpi_get_handle(dev); + + /* Look for the first embedded controller */ + if (!(ec_devclass = devclass_find ("acpi_ec"))) { + if (bootverbose) + device_printf(dev, "Couldn't find acpi_ec devclass\n"); + return (EINVAL); + } + if (!(sc->ec_dev = devclass_get_device(ec_devclass, 0))) { + if (bootverbose) + device_printf(dev, "Couldn't find acpi_ec device\n"); + return (EINVAL); + } + sc->ec_handle = acpi_get_handle(sc->ec_dev); + + ACPI_SERIAL_BEGIN(ibm); + + /* Get the sysctl tree */ + sc->sysctl_ctx = device_get_sysctl_ctx(dev); + sc->sysctl_tree = device_get_sysctl_tree(dev); + + /* Look for event mask and hook up the nodes */ + sc->events_mask_supported = ACPI_SUCCESS(acpi_GetInteger(sc->handle, + IBM_NAME_EVENTS_MASK_GET, &sc->events_initialmask)); + + if (sc->events_mask_supported) { + SYSCTL_ADD_INT(sc->sysctl_ctx, + SYSCTL_CHILDREN(sc->sysctl_tree), OID_AUTO, + "initialmask", CTLFLAG_RD, + &sc->events_initialmask, 0, "Initial eventmask"); + + /* The availmask is the bitmask of supported events */ + if (ACPI_FAILURE(acpi_GetInteger(sc->handle, + IBM_NAME_EVENTS_AVAILMASK, &sc->events_availmask))) + sc->events_availmask = 0xffffffff; + + SYSCTL_ADD_INT(sc->sysctl_ctx, + SYSCTL_CHILDREN(sc->sysctl_tree), OID_AUTO, + "availmask", CTLFLAG_RD, + &sc->events_availmask, 0, "Mask of supported events"); + } + + /* Hook up proc nodes */ + for (int i = 0; acpi_ibm_sysctls[i].name != NULL; i++) { + if (!acpi_ibm_sysctl_init(sc, acpi_ibm_sysctls[i].method)) + continue; + + SYSCTL_ADD_PROC(sc->sysctl_ctx, + SYSCTL_CHILDREN(sc->sysctl_tree), OID_AUTO, + acpi_ibm_sysctls[i].name, acpi_ibm_sysctls[i].access, + sc, i, acpi_ibm_sysctl, "I", + acpi_ibm_sysctls[i].description); + } + + /* Hook up thermal node */ + if (acpi_ibm_sysctl_init(sc, ACPI_IBM_METHOD_THERMAL)) { + SYSCTL_ADD_PROC(sc->sysctl_ctx, + SYSCTL_CHILDREN(sc->sysctl_tree), OID_AUTO, + "thermal", CTLTYPE_STRING | CTLFLAG_RD, + sc, 0, acpi_ibm_thermal_sysctl, "I", + "Thermal zones"); + } + + ACPI_SERIAL_END(ibm); + + /* Handle notifies */ + AcpiInstallNotifyHandler(sc->handle, ACPI_DEVICE_NOTIFY, + acpi_ibm_notify, dev); + + /* Hook up light to led(4) */ + if (sc->light_set_supported) + sc->led_dev = led_create_state(ibm_led, sc, "thinklight", sc->light_val); + + return (0); +} + +static int +acpi_ibm_detach(device_t dev) +{ + ACPI_FUNCTION_TRACE((char *)(uintptr_t) __func__); + + struct acpi_ibm_softc *sc = device_get_softc(dev); + + /* Disable events and restore eventmask */ + ACPI_SERIAL_BEGIN(ibm); + acpi_ibm_sysctl_set(sc, ACPI_IBM_METHOD_EVENTS, 0); + acpi_ibm_sysctl_set(sc, ACPI_IBM_METHOD_EVENTMASK, sc->events_initialmask); + ACPI_SERIAL_END(ibm); + + AcpiRemoveNotifyHandler(sc->handle, ACPI_DEVICE_NOTIFY, acpi_ibm_notify); + + if (sc->led_dev != NULL) + led_destroy(sc->led_dev); + + return (0); +} + +static int +acpi_ibm_eventmask_set(struct acpi_ibm_softc *sc, int val) +{ + ACPI_OBJECT arg[2]; + ACPI_OBJECT_LIST args; + ACPI_STATUS status; + + ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); + ACPI_SERIAL_ASSERT(ibm); + + args.Count = 2; + args.Pointer = arg; + arg[0].Type = ACPI_TYPE_INTEGER; + arg[1].Type = ACPI_TYPE_INTEGER; + + for (int i = 0; i < 32; ++i) { + arg[0].Integer.Value = i+1; + arg[1].Integer.Value = (((1 << i) & val) != 0); + status = AcpiEvaluateObject(sc->handle, + IBM_NAME_EVENTS_MASK_SET, &args, NULL); + + if (ACPI_FAILURE(status)) + return (status); + } + + return (0); +} + +static int +acpi_ibm_sysctl(SYSCTL_HANDLER_ARGS) +{ + struct acpi_ibm_softc *sc; + int arg; + int error = 0; + int function; + int method; + + ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); + + sc = (struct acpi_ibm_softc *)oidp->oid_arg1; + function = oidp->oid_arg2; + method = acpi_ibm_sysctls[function].method; + + ACPI_SERIAL_BEGIN(ibm); + arg = acpi_ibm_sysctl_get(sc, method); + error = sysctl_handle_int(oidp, &arg, 0, req); + + /* Sanity check */ + if (error != 0 || req->newptr == NULL) + goto out; + + /* Update */ + error = acpi_ibm_sysctl_set(sc, method, arg); + +out: + ACPI_SERIAL_END(ibm); + return (error); +} + +static int +acpi_ibm_sysctl_get(struct acpi_ibm_softc *sc, int method) +{ + ACPI_INTEGER val_ec; + int val = 0, key; + + ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); + ACPI_SERIAL_ASSERT(ibm); + + switch (method) { + case ACPI_IBM_METHOD_EVENTS: + acpi_GetInteger(sc->handle, IBM_NAME_EVENTS_STATUS_GET, &val); + break; + + case ACPI_IBM_METHOD_EVENTMASK: + if (sc->events_mask_supported) + acpi_GetInteger(sc->handle, IBM_NAME_EVENTS_MASK_GET, &val); + break; + + case ACPI_IBM_METHOD_HOTKEY: + /* + * Construct the hotkey as a bitmask as illustrated below. + * Note that whenever a key was pressed, the respecting bit + * toggles and nothing else changes. + * +--+--+-+-+-+-+-+-+-+-+-+-+ + * |11|10|9|8|7|6|5|4|3|2|1|0| + * +--+--+-+-+-+-+-+-+-+-+-+-+ + * | | | | | | | | | | | | + * | | | | | | | | | | | +- Home Button + * | | | | | | | | | | +--- Search Button + * | | | | | | | | | +----- Mail Button + * | | | | | | | | +------- Thinkpad Button + * | | | | | | | +--------- Zoom (Fn + Space) + * | | | | | | +----------- WLAN Button + * | | | | | +------------- Video Button + * | | | | +--------------- Hibernate Button + * | | | +----------------- Thinklight Button + * | | +------------------- Screen expand (Fn + F8) + * | +--------------------- Brightness + * +------------------------ Volume/Mute + */ + key = rtcin(IBM_RTC_HOTKEY1); + val = (IBM_RTC_MASK_HOME | IBM_RTC_MASK_SEARCH | IBM_RTC_MASK_MAIL | IBM_RTC_MASK_WLAN) & key; + key = rtcin(IBM_RTC_HOTKEY2); + val |= (IBM_RTC_MASK_THINKPAD | IBM_RTC_MASK_VIDEO | IBM_RTC_MASK_HIBERNATE) & key; + val |= (IBM_RTC_MASK_ZOOM & key) >> 1; + key = rtcin(IBM_RTC_THINKLIGHT); + val |= (IBM_RTC_MASK_THINKLIGHT & key) << 4; + key = rtcin(IBM_RTC_SCREENEXPAND); + val |= (IBM_RTC_MASK_THINKLIGHT & key) << 4; + key = rtcin(IBM_RTC_BRIGHTNESS); + val |= (IBM_RTC_MASK_BRIGHTNESS & key) << 5; + key = rtcin(IBM_RTC_VOLUME); + val |= (IBM_RTC_MASK_VOLUME & key) << 4; + break; + + case ACPI_IBM_METHOD_BRIGHTNESS: + ACPI_EC_READ(sc->ec_dev, IBM_EC_BRIGHTNESS, &val_ec, 1); + val = val_ec & IBM_EC_MASK_BRI; + break; + + case ACPI_IBM_METHOD_VOLUME: + ACPI_EC_READ(sc->ec_dev, IBM_EC_VOLUME, &val_ec, 1); + val = val_ec & IBM_EC_MASK_VOL; + break; + + case ACPI_IBM_METHOD_MUTE: + ACPI_EC_READ(sc->ec_dev, IBM_EC_VOLUME, &val_ec, 1); + val = ((val_ec & IBM_EC_MASK_MUTE) == IBM_EC_MASK_MUTE); + break; + + case ACPI_IBM_METHOD_THINKLIGHT: + if (sc->light_get_supported) + acpi_GetInteger(sc->ec_handle, IBM_NAME_KEYLIGHT, &val); + else + val = sc->light_val; + break; + + case ACPI_IBM_METHOD_BLUETOOTH: + acpi_GetInteger(sc->handle, IBM_NAME_WLAN_BT_GET, &val); + sc->wlan_bt_flags = val; + val = ((val & IBM_NAME_MASK_BT) != 0); + break; + + case ACPI_IBM_METHOD_WLAN: + acpi_GetInteger(sc->handle, IBM_NAME_WLAN_BT_GET, &val); + sc->wlan_bt_flags = val; + val = ((val & IBM_NAME_MASK_WLAN) != 0); + break; + + case ACPI_IBM_METHOD_FANSPEED: + if (sc->fan_handle) { + if(ACPI_FAILURE(acpi_GetInteger(sc->fan_handle, NULL, &val))) + val = -1; + } + else { + ACPI_EC_READ(sc->ec_dev, IBM_EC_FANSPEED, &val_ec, 2); + val = val_ec; + } + break; + + case ACPI_IBM_METHOD_FANLEVEL: + /* + * The IBM_EC_FANSTATUS register works as follows: + * Bit 0-5 indicate the level at which the fan operates. Only + * values between 0 and 7 have an effect. Everything + * above 7 is treated the same as level 7 + * Bit 6 overrides the fan speed limit if set to 1 + * Bit 7 indicates at which mode the fan operates: + * manual (0) or automatic (1) + */ + if (!sc->fan_handle) { + ACPI_EC_READ(sc->ec_dev, IBM_EC_FANSTATUS, &val_ec, 1); + val = val_ec & IBM_EC_MASK_FANLEVEL; + } + break; + + case ACPI_IBM_METHOD_FANSTATUS: + if (!sc->fan_handle) { + ACPI_EC_READ(sc->ec_dev, IBM_EC_FANSTATUS, &val_ec, 1); + val = (val_ec & IBM_EC_MASK_FANSTATUS) == IBM_EC_MASK_FANSTATUS; + } + else + val = -1; + break; + } + + return (val); +} + +static int +acpi_ibm_sysctl_set(struct acpi_ibm_softc *sc, int method, int arg) +{ + int val, step; + ACPI_INTEGER val_ec; + ACPI_OBJECT Arg; + ACPI_OBJECT_LIST Args; + ACPI_STATUS status; + + ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); + ACPI_SERIAL_ASSERT(ibm); + + switch (method) { + case ACPI_IBM_METHOD_EVENTS: + if (arg < 0 || arg > 1) + return (EINVAL); + + status = acpi_SetInteger(sc->handle, IBM_NAME_EVENTS_STATUS_SET, arg); + if (ACPI_FAILURE(status)) + return (status); + if (sc->events_mask_supported) + return acpi_ibm_eventmask_set(sc, sc->events_availmask); + break; + + case ACPI_IBM_METHOD_EVENTMASK: + if (sc->events_mask_supported) + return acpi_ibm_eventmask_set(sc, arg); + break; + + case ACPI_IBM_METHOD_BRIGHTNESS: + if (arg < 0 || arg > 7) + return (EINVAL); + + if (sc->cmos_handle) { + /* Read the current brightness */ + status = ACPI_EC_READ(sc->ec_dev, IBM_EC_BRIGHTNESS, &val_ec, 1); + if (ACPI_FAILURE(status)) + return (status); + val = val_ec & IBM_EC_MASK_BRI; + + Args.Count = 1; + Args.Pointer = &Arg; + Arg.Type = ACPI_TYPE_INTEGER; + Arg.Integer.Value = (arg > val) ? IBM_CMOS_BRIGHTNESS_UP : IBM_CMOS_BRIGHTNESS_DOWN; + + step = (arg > val) ? 1 : -1; + for (int i = val; i != arg; i += step) { + status = AcpiEvaluateObject(sc->cmos_handle, NULL, &Args, NULL); + if (ACPI_FAILURE(status)) + break; + } + } + return ACPI_EC_WRITE(sc->ec_dev, IBM_EC_BRIGHTNESS, arg, 1); + break; + + case ACPI_IBM_METHOD_VOLUME: + if (arg < 0 || arg > 14) + return (EINVAL); + + status = ACPI_EC_READ(sc->ec_dev, IBM_EC_VOLUME, &val_ec, 1); + if (ACPI_FAILURE(status)) + return (status); + + if (sc->cmos_handle) { + val = val_ec & IBM_EC_MASK_VOL; + + Args.Count = 1; + Args.Pointer = &Arg; + Arg.Type = ACPI_TYPE_INTEGER; + Arg.Integer.Value = (arg > val) ? IBM_CMOS_VOLUME_UP : IBM_CMOS_VOLUME_DOWN; + + step = (arg > val) ? 1 : -1; + for (int i = val; i != arg; i += step) { + status = AcpiEvaluateObject(sc->cmos_handle, NULL, &Args, NULL); + if (ACPI_FAILURE(status)) + break; + } + } + return ACPI_EC_WRITE(sc->ec_dev, IBM_EC_VOLUME, arg + (val_ec & (~IBM_EC_MASK_VOL)), 1); + break; + + case ACPI_IBM_METHOD_MUTE: + if (arg < 0 || arg > 1) + return (EINVAL); + + status = ACPI_EC_READ(sc->ec_dev, IBM_EC_VOLUME, &val_ec, 1); + if (ACPI_FAILURE(status)) + return (status); + + if (sc->cmos_handle) { + val = val_ec & IBM_EC_MASK_VOL; + + Args.Count = 1; + Args.Pointer = &Arg; + Arg.Type = ACPI_TYPE_INTEGER; + Arg.Integer.Value = IBM_CMOS_VOLUME_MUTE; + + status = AcpiEvaluateObject(sc->cmos_handle, NULL, &Args, NULL); + if (ACPI_FAILURE(status)) + break; + } + return ACPI_EC_WRITE(sc->ec_dev, IBM_EC_VOLUME, (arg==1) ? val_ec | IBM_EC_MASK_MUTE : val_ec & (~IBM_EC_MASK_MUTE), 1); + break; + + case ACPI_IBM_METHOD_THINKLIGHT: + if (arg < 0 || arg > 1) + return (EINVAL); + + if (sc->light_set_supported) { + Args.Count = 1; + Args.Pointer = &Arg; + Arg.Type = ACPI_TYPE_INTEGER; + Arg.Integer.Value = arg ? sc->light_cmd_on : sc->light_cmd_off; + + status = AcpiEvaluateObject(sc->light_handle, NULL, &Args, NULL); + if (ACPI_SUCCESS(status)) + sc->light_val = arg; + return (status); + } + break; + + case ACPI_IBM_METHOD_BLUETOOTH: + if (arg < 0 || arg > 1) + return (EINVAL); + + val = (arg == 1) ? sc->wlan_bt_flags | IBM_NAME_MASK_BT : sc->wlan_bt_flags & (~IBM_NAME_MASK_BT); + return acpi_SetInteger(sc->handle, IBM_NAME_WLAN_BT_SET, val); + break; + + case ACPI_IBM_METHOD_FANLEVEL: + if (arg < 0 || arg > 7) + return (EINVAL); + + if (!sc->fan_handle) { + /* Read the current fanstatus */ + ACPI_EC_READ(sc->ec_dev, IBM_EC_FANSTATUS, &val_ec, 1); + val = val_ec & (~IBM_EC_MASK_FANLEVEL); + + return ACPI_EC_WRITE(sc->ec_dev, IBM_EC_FANSTATUS, val | arg, 1); + } + break; + + case ACPI_IBM_METHOD_FANSTATUS: + if (arg < 0 || arg > 1) + return (EINVAL); + + if (!sc->fan_handle) { + /* Read the current fanstatus */ + ACPI_EC_READ(sc->ec_dev, IBM_EC_FANSTATUS, &val_ec, 1); + + return ACPI_EC_WRITE(sc->ec_dev, IBM_EC_FANSTATUS, + (arg == 1) ? (val_ec | IBM_EC_MASK_FANSTATUS) : (val_ec & (~IBM_EC_MASK_FANSTATUS)), 1); + } + break; + } + + return (0); +} + +static int +acpi_ibm_sysctl_init(struct acpi_ibm_softc *sc, int method) +{ + int dummy; + ACPI_OBJECT_TYPE cmos_t; + ACPI_HANDLE ledb_handle; + + switch (method) { + case ACPI_IBM_METHOD_EVENTS: + /* Events are disabled by default */ + return (TRUE); + + case ACPI_IBM_METHOD_EVENTMASK: + return (sc->events_mask_supported); + + case ACPI_IBM_METHOD_HOTKEY: + case ACPI_IBM_METHOD_BRIGHTNESS: + case ACPI_IBM_METHOD_VOLUME: + case ACPI_IBM_METHOD_MUTE: + /* EC is required here, which was aready checked before */ + return (TRUE); + + case ACPI_IBM_METHOD_THINKLIGHT: + sc->cmos_handle = NULL; + sc->light_get_supported = ACPI_SUCCESS(acpi_GetInteger( + sc->ec_handle, IBM_NAME_KEYLIGHT, &sc->light_val)); + + if ((ACPI_SUCCESS(AcpiGetHandle(sc->handle, "\\UCMS", &sc->light_handle)) || + ACPI_SUCCESS(AcpiGetHandle(sc->handle, "\\CMOS", &sc->light_handle)) || + ACPI_SUCCESS(AcpiGetHandle(sc->handle, "\\CMS", &sc->light_handle))) && + ACPI_SUCCESS(AcpiGetType(sc->light_handle, &cmos_t)) && + cmos_t == ACPI_TYPE_METHOD) { + sc->light_cmd_on = 0x0c; + sc->light_cmd_off = 0x0d; + sc->cmos_handle = sc->light_handle; + } + else if (ACPI_SUCCESS(AcpiGetHandle(sc->handle, "\\LGHT", &sc->light_handle))) { + sc->light_cmd_on = 1; + sc->light_cmd_off = 0; + } + else + sc->light_handle = NULL; + + sc->light_set_supported = (sc->light_handle && + ACPI_FAILURE(AcpiGetHandle(sc->ec_handle, "LEDB", &ledb_handle))); + + if (sc->light_get_supported) + return (TRUE); + + if (sc->light_set_supported) { + sc->light_val = 0; + return (TRUE); + } + + return (FALSE); + + case ACPI_IBM_METHOD_BLUETOOTH: + case ACPI_IBM_METHOD_WLAN: + if (ACPI_SUCCESS(acpi_GetInteger(sc->handle, IBM_NAME_WLAN_BT_GET, &dummy))) + return (TRUE); + return (FALSE); + + case ACPI_IBM_METHOD_FANSPEED: + /* + * Some models report the fan speed in levels from 0-7 + * Newer models report it contiguously + */ + sc->fan_levels = + (ACPI_SUCCESS(AcpiGetHandle(sc->handle, "GFAN", &sc->fan_handle)) || + ACPI_SUCCESS(AcpiGetHandle(sc->handle, "\\FSPD", &sc->fan_handle))); + return (TRUE); + + case ACPI_IBM_METHOD_FANLEVEL: + case ACPI_IBM_METHOD_FANSTATUS: + /* + * Fan status is only supported on those models, + * which report fan RPM contiguously, not in levels + */ + if (sc->fan_levels) + return (FALSE); + return (TRUE); + + case ACPI_IBM_METHOD_THERMAL: + if (ACPI_SUCCESS(acpi_GetInteger(sc->ec_handle, IBM_NAME_THERMAL_GET, &dummy))) { + sc->thermal_updt_supported = ACPI_SUCCESS(acpi_GetInteger(sc->ec_handle, IBM_NAME_THERMAL_UPDT, &dummy)); + return (TRUE); + } + return (FALSE); + } + return (FALSE); +} + +static int +acpi_ibm_thermal_sysctl(SYSCTL_HANDLER_ARGS) +{ + struct acpi_ibm_softc *sc; + int error = 0; + char temp_cmd[] = "TMP0"; + int temp[8]; + + ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); + + sc = (struct acpi_ibm_softc *)oidp->oid_arg1; + + ACPI_SERIAL_BEGIN(ibm); + + for (int i = 0; i < 8; ++i) { + temp_cmd[3] = '0' + i; + + /* + * The TMPx methods seem to return +/- 128 or 0 + * when the respecting sensor is not available + */ + if (ACPI_FAILURE(acpi_GetInteger(sc->ec_handle, temp_cmd, + &temp[i])) || ABS(temp[i]) == 128 || temp[i] == 0) + temp[i] = -1; + else if (sc->thermal_updt_supported) + /* Temperature is reported in tenth of Kelvin */ + temp[i] = (temp[i] - 2732 + 5) / 10; + } + + error = sysctl_handle_opaque(oidp, &temp, 8*sizeof(int), req); + + ACPI_SERIAL_END(ibm); + return (error); +} + +static void +acpi_ibm_notify(ACPI_HANDLE h, UINT32 notify, void *context) +{ + int event, arg, type; + device_t dev = context; + struct acpi_ibm_softc *sc = device_get_softc(dev); + + ACPI_FUNCTION_TRACE_U32((char *)(uintptr_t)__func__, notify); + + if (notify != 0x80) + device_printf(dev, "Unknown notify\n"); + + for (;;) { + acpi_GetInteger(acpi_get_handle(dev), IBM_NAME_EVENTS_GET, &event); + + if (event == 0) + break; + + + type = (event >> 12) & 0xf; + arg = event & 0xfff; + switch (type) { + case 1: + if (!(sc->events_availmask & (1 << (arg - 1)))) { + device_printf(dev, "Unknown key %d\n", arg); + break; + } + + /* Notify devd(8) */ + acpi_UserNotify("IBM", h, (arg & 0xff)); + break; + default: + break; + } + } +} diff --git a/sys/dev/acpi_support/acpi_panasonic.c b/sys/dev/acpi_support/acpi_panasonic.c new file mode 100644 index 000000000000..f922233092a3 --- /dev/null +++ b/sys/dev/acpi_support/acpi_panasonic.c @@ -0,0 +1,514 @@ +/*- + * Copyright (c) 2003 OGAWA Takaya <t-ogawa@triaez.kaisei.org> + * Copyright (c) 2004 TAKAHASHI Yoshihiro <nyan@FreeBSD.org> + * 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. + * + * 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 <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include "opt_acpi.h" +#include <sys/param.h> +#include <sys/kernel.h> +#include <sys/malloc.h> +#include <sys/module.h> +#include <sys/bus.h> +#include <sys/power.h> + +#include <contrib/dev/acpica/acpi.h> +#include <dev/acpica/acpivar.h> + +#define _COMPONENT ACPI_OEM +ACPI_MODULE_NAME("Panasonic") + +/* Debug */ +#undef ACPI_PANASONIC_DEBUG + +/* Operations */ +#define HKEY_SET 0 +#define HKEY_GET 1 + +/* Functions */ +#define HKEY_REG_LCD_BRIGHTNESS_MAX_AC 0x02 +#define HKEY_REG_LCD_BRIGHTNESS_MIN_AC 0x03 +#define HKEY_REG_LCD_BRIGHTNESS_AC 0x04 +#define HKEY_REG_LCD_BRIGHTNESS_MAX_DC 0x05 +#define HKEY_REG_LCD_BRIGHTNESS_MIN_DC 0x06 +#define HKEY_REG_LCD_BRIGHTNESS_DC 0x07 +#define HKEY_REG_SOUND_MUTE 0x08 + +/* Field definitions */ +#define HKEY_LCD_BRIGHTNESS_BITS 4 +#define HKEY_LCD_BRIGHTNESS_DIV ((1 << HKEY_LCD_BRIGHTNESS_BITS) - 1) + +struct acpi_panasonic_softc { + device_t dev; + ACPI_HANDLE handle; + + struct sysctl_ctx_list sysctl_ctx; + struct sysctl_oid *sysctl_tree; + + eventhandler_tag power_evh; +}; + +/* Prototype for HKEY functions for getting/setting a value. */ +typedef int hkey_fn_t(ACPI_HANDLE, int, UINT32 *); + +static int acpi_panasonic_probe(device_t dev); +static int acpi_panasonic_attach(device_t dev); +static int acpi_panasonic_detach(device_t dev); +static int acpi_panasonic_shutdown(device_t dev); +static int acpi_panasonic_sysctl(SYSCTL_HANDLER_ARGS); +static ACPI_INTEGER acpi_panasonic_sinf(ACPI_HANDLE h, ACPI_INTEGER index); +static void acpi_panasonic_sset(ACPI_HANDLE h, ACPI_INTEGER index, + ACPI_INTEGER val); +static int acpi_panasonic_hkey_event(struct acpi_panasonic_softc *sc, + ACPI_HANDLE h, UINT32 *arg); +static void acpi_panasonic_hkey_action(struct acpi_panasonic_softc *sc, + ACPI_HANDLE h, UINT32 key); +static void acpi_panasonic_notify(ACPI_HANDLE h, UINT32 notify, + void *context); +static void acpi_panasonic_power_profile(void *arg); + +static hkey_fn_t hkey_lcd_brightness_max; +static hkey_fn_t hkey_lcd_brightness_min; +static hkey_fn_t hkey_lcd_brightness; +static hkey_fn_t hkey_sound_mute; +ACPI_SERIAL_DECL(panasonic, "ACPI Panasonic extras"); + +/* Table of sysctl names and HKEY functions to call. */ +static struct { + char *name; + hkey_fn_t *handler; +} sysctl_table[] = { + /* name, handler */ + {"lcd_brightness_max", hkey_lcd_brightness_max}, + {"lcd_brightness_min", hkey_lcd_brightness_min}, + {"lcd_brightness", hkey_lcd_brightness}, + {"sound_mute", hkey_sound_mute}, + {NULL, NULL} +}; + +static device_method_t acpi_panasonic_methods[] = { + DEVMETHOD(device_probe, acpi_panasonic_probe), + DEVMETHOD(device_attach, acpi_panasonic_attach), + DEVMETHOD(device_detach, acpi_panasonic_detach), + DEVMETHOD(device_shutdown, acpi_panasonic_shutdown), + + {0, 0} +}; + +static driver_t acpi_panasonic_driver = { + "acpi_panasonic", + acpi_panasonic_methods, + sizeof(struct acpi_panasonic_softc), +}; + +static devclass_t acpi_panasonic_devclass; + +DRIVER_MODULE(acpi_panasonic, acpi, acpi_panasonic_driver, + acpi_panasonic_devclass, 0, 0); +MODULE_DEPEND(acpi_panasonic, acpi, 1, 1, 1); + +static int +acpi_panasonic_probe(device_t dev) +{ + static char *mat_ids[] = { "MAT0019", NULL }; + + if (acpi_disabled("panasonic") || + ACPI_ID_PROBE(device_get_parent(dev), dev, mat_ids) == NULL || + device_get_unit(dev) != 0) + return (ENXIO); + + device_set_desc(dev, "Panasonic Notebook Hotkeys"); + return (0); +} + +static int +acpi_panasonic_attach(device_t dev) +{ + struct acpi_panasonic_softc *sc; + struct acpi_softc *acpi_sc; + ACPI_STATUS status; + int i; + + sc = device_get_softc(dev); + sc->dev = dev; + sc->handle = acpi_get_handle(dev); + + acpi_sc = acpi_device_get_parent_softc(dev); + + /* Build sysctl tree */ + sysctl_ctx_init(&sc->sysctl_ctx); + sc->sysctl_tree = SYSCTL_ADD_NODE(&sc->sysctl_ctx, + SYSCTL_CHILDREN(acpi_sc->acpi_sysctl_tree), OID_AUTO, + "panasonic", CTLFLAG_RD, 0, ""); + for (i = 0; sysctl_table[i].name != NULL; i++) { + SYSCTL_ADD_PROC(&sc->sysctl_ctx, + SYSCTL_CHILDREN(sc->sysctl_tree), OID_AUTO, + sysctl_table[i].name, + CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_ANYBODY, + sc, i, acpi_panasonic_sysctl, "I", ""); + } + +#if 0 + /* Activate hotkeys */ + status = AcpiEvaluateObject(sc->handle, "", NULL, NULL); + if (ACPI_FAILURE(status)) { + device_printf(dev, "enable FN keys failed\n"); + sysctl_ctx_free(&sc->sysctl_ctx); + return (ENXIO); + } +#endif + + /* Handle notifies */ + status = AcpiInstallNotifyHandler(sc->handle, ACPI_DEVICE_NOTIFY, + acpi_panasonic_notify, sc); + if (ACPI_FAILURE(status)) { + device_printf(dev, "couldn't install notify handler - %s\n", + AcpiFormatException(status)); + sysctl_ctx_free(&sc->sysctl_ctx); + return (ENXIO); + } + + /* Install power profile event handler */ + sc->power_evh = EVENTHANDLER_REGISTER(power_profile_change, + acpi_panasonic_power_profile, sc->handle, 0); + + return (0); +} + +static int +acpi_panasonic_detach(device_t dev) +{ + struct acpi_panasonic_softc *sc; + + sc = device_get_softc(dev); + + /* Remove power profile event handler */ + EVENTHANDLER_DEREGISTER(power_profile_change, sc->power_evh); + + /* Remove notify handler */ + AcpiRemoveNotifyHandler(sc->handle, ACPI_DEVICE_NOTIFY, + acpi_panasonic_notify); + + /* Free sysctl tree */ + sysctl_ctx_free(&sc->sysctl_ctx); + + return (0); +} + +static int +acpi_panasonic_shutdown(device_t dev) +{ + struct acpi_panasonic_softc *sc; + int mute; + + /* Mute the main audio during reboot to prevent static burst to speaker. */ + sc = device_get_softc(dev); + mute = 1; + hkey_sound_mute(sc->handle, HKEY_SET, &mute); + return (0); +} + +static int +acpi_panasonic_sysctl(SYSCTL_HANDLER_ARGS) +{ + struct acpi_panasonic_softc *sc; + UINT32 arg; + int function, error; + hkey_fn_t *handler; + + sc = (struct acpi_panasonic_softc *)oidp->oid_arg1; + function = oidp->oid_arg2; + handler = sysctl_table[function].handler; + + /* Get the current value from the appropriate function. */ + ACPI_SERIAL_BEGIN(panasonic); + error = handler(sc->handle, HKEY_GET, &arg); + if (error != 0) + goto out; + + /* Send the current value to the user and return if no new value. */ + error = sysctl_handle_int(oidp, &arg, 0, req); + if (error != 0 || req->newptr == NULL) + goto out; + + /* Set the new value via the appropriate function. */ + error = handler(sc->handle, HKEY_SET, &arg); + +out: + ACPI_SERIAL_END(panasonic); + return (error); +} + +static ACPI_INTEGER +acpi_panasonic_sinf(ACPI_HANDLE h, ACPI_INTEGER index) +{ + ACPI_BUFFER buf; + ACPI_OBJECT *res; + ACPI_INTEGER ret; + + ACPI_SERIAL_ASSERT(panasonic); + ret = -1; + buf.Length = ACPI_ALLOCATE_BUFFER; + buf.Pointer = NULL; + AcpiEvaluateObject(h, "SINF", NULL, &buf); + res = (ACPI_OBJECT *)buf.Pointer; + if (res->Type == ACPI_TYPE_PACKAGE) + ret = res->Package.Elements[index].Integer.Value; + AcpiOsFree(buf.Pointer); + + return (ret); +} + +static void +acpi_panasonic_sset(ACPI_HANDLE h, ACPI_INTEGER index, ACPI_INTEGER val) +{ + ACPI_OBJECT_LIST args; + ACPI_OBJECT obj[2]; + + ACPI_SERIAL_ASSERT(panasonic); + obj[0].Type = ACPI_TYPE_INTEGER; + obj[0].Integer.Value = index; + obj[1].Type = ACPI_TYPE_INTEGER; + obj[1].Integer.Value = val; + args.Count = 2; + args.Pointer = obj; + AcpiEvaluateObject(h, "SSET", &args, NULL); +} + +static int +hkey_lcd_brightness_max(ACPI_HANDLE h, int op, UINT32 *val) +{ + int reg; + + ACPI_SERIAL_ASSERT(panasonic); + reg = (power_profile_get_state() == POWER_PROFILE_PERFORMANCE) ? + HKEY_REG_LCD_BRIGHTNESS_MAX_AC : HKEY_REG_LCD_BRIGHTNESS_MAX_DC; + + switch (op) { + case HKEY_SET: + return (EPERM); + break; + case HKEY_GET: + *val = acpi_panasonic_sinf(h, reg); + break; + } + + return (0); +} + +static int +hkey_lcd_brightness_min(ACPI_HANDLE h, int op, UINT32 *val) +{ + int reg; + + ACPI_SERIAL_ASSERT(panasonic); + reg = (power_profile_get_state() == POWER_PROFILE_PERFORMANCE) ? + HKEY_REG_LCD_BRIGHTNESS_MIN_AC : HKEY_REG_LCD_BRIGHTNESS_MIN_DC; + + switch (op) { + case HKEY_SET: + return (EPERM); + break; + case HKEY_GET: + *val = acpi_panasonic_sinf(h, reg); + break; + } + + return (0); +} + +static int +hkey_lcd_brightness(ACPI_HANDLE h, int op, UINT32 *val) +{ + int reg; + UINT32 max, min; + + reg = (power_profile_get_state() == POWER_PROFILE_PERFORMANCE) ? + HKEY_REG_LCD_BRIGHTNESS_AC : HKEY_REG_LCD_BRIGHTNESS_DC; + + ACPI_SERIAL_ASSERT(panasonic); + switch (op) { + case HKEY_SET: + hkey_lcd_brightness_max(h, HKEY_GET, &max); + hkey_lcd_brightness_min(h, HKEY_GET, &min); + if (*val < min || *val > max) + return (EINVAL); + acpi_panasonic_sset(h, reg, *val); + break; + case HKEY_GET: + *val = acpi_panasonic_sinf(h, reg); + break; + } + + return (0); +} + +static int +hkey_sound_mute(ACPI_HANDLE h, int op, UINT32 *val) +{ + + ACPI_SERIAL_ASSERT(panasonic); + switch (op) { + case HKEY_SET: + if (*val != 0 && *val != 1) + return (EINVAL); + acpi_panasonic_sset(h, HKEY_REG_SOUND_MUTE, *val); + break; + case HKEY_GET: + *val = acpi_panasonic_sinf(h, HKEY_REG_SOUND_MUTE); + break; + } + + return (0); +} + +static int +acpi_panasonic_hkey_event(struct acpi_panasonic_softc *sc, ACPI_HANDLE h, + UINT32 *arg) +{ + ACPI_BUFFER buf; + ACPI_OBJECT *res; + ACPI_INTEGER val; + int status; + + ACPI_SERIAL_ASSERT(panasonic); + status = ENXIO; + + buf.Length = ACPI_ALLOCATE_BUFFER; + buf.Pointer = NULL; + AcpiEvaluateObject(h, "HINF", NULL, &buf); + res = (ACPI_OBJECT *)buf.Pointer; + if (res->Type != ACPI_TYPE_INTEGER) { + device_printf(sc->dev, "HINF returned non-integer\n"); + goto end; + } + val = res->Integer.Value; +#ifdef ACPI_PANASONIC_DEBUG + device_printf(sc->dev, "%s button Fn+F%d\n", + (val & 0x80) ? "Pressed" : "Released", + (int)(val & 0x7f)); +#endif + if ((val & 0x7f) > 0 && (val & 0x7f) < 11) { + *arg = val; + status = 0; + } +end: + if (buf.Pointer) + AcpiOsFree(buf.Pointer); + + return (status); +} + +static void +acpi_panasonic_hkey_action(struct acpi_panasonic_softc *sc, ACPI_HANDLE h, + UINT32 key) +{ + struct acpi_softc *acpi_sc; + int arg, max, min; + + acpi_sc = acpi_device_get_parent_softc(sc->dev); + + ACPI_SERIAL_ASSERT(panasonic); + switch (key) { + case 1: + /* Decrease LCD brightness. */ + hkey_lcd_brightness_max(h, HKEY_GET, &max); + hkey_lcd_brightness_min(h, HKEY_GET, &min); + hkey_lcd_brightness(h, HKEY_GET, &arg); + arg -= max / HKEY_LCD_BRIGHTNESS_DIV; + if (arg < min) + arg = min; + else if (arg > max) + arg = max; + hkey_lcd_brightness(h, HKEY_SET, &arg); + break; + case 2: + /* Increase LCD brightness. */ + hkey_lcd_brightness_max(h, HKEY_GET, &max); + hkey_lcd_brightness_min(h, HKEY_GET, &min); + hkey_lcd_brightness(h, HKEY_GET, &arg); + arg += max / HKEY_LCD_BRIGHTNESS_DIV; + if (arg < min) + arg = min; + else if (arg > max) + arg = max; + hkey_lcd_brightness(h, HKEY_SET, &arg); + break; + case 4: + /* Toggle sound mute. */ + hkey_sound_mute(h, HKEY_GET, &arg); + if (arg) + arg = 0; + else + arg = 1; + hkey_sound_mute(h, HKEY_SET, &arg); + break; + case 7: + /* Suspend. */ + acpi_event_sleep_button_sleep(acpi_sc); + break; + } +} + +static void +acpi_panasonic_notify(ACPI_HANDLE h, UINT32 notify, void *context) +{ + struct acpi_panasonic_softc *sc; + UINT32 key = 0; + + sc = (struct acpi_panasonic_softc *)context; + + switch (notify) { + case 0x80: + ACPI_SERIAL_BEGIN(panasonic); + if (acpi_panasonic_hkey_event(sc, h, &key) == 0) { + acpi_panasonic_hkey_action(sc, h, key); + acpi_UserNotify("Panasonic", h, (uint8_t)key); + } + ACPI_SERIAL_END(panasonic); + break; + default: + device_printf(sc->dev, "unknown notify: %#x\n", notify); + break; + } +} + +static void +acpi_panasonic_power_profile(void *arg) +{ + ACPI_HANDLE handle; + UINT32 brightness; + + handle = (ACPI_HANDLE)arg; + + /* Reset current brightness according to new power state. */ + ACPI_SERIAL_BEGIN(panasonic); + hkey_lcd_brightness(handle, HKEY_GET, &brightness); + hkey_lcd_brightness(handle, HKEY_SET, &brightness); + ACPI_SERIAL_END(panasonic); +} diff --git a/sys/dev/acpi_support/acpi_sony.c b/sys/dev/acpi_support/acpi_sony.c new file mode 100644 index 000000000000..88195c2d7836 --- /dev/null +++ b/sys/dev/acpi_support/acpi_sony.c @@ -0,0 +1,170 @@ +/*- + * Copyright (c) 2004 Takanori Watanabe + * 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. + * + * 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 <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include "opt_acpi.h" +#include <sys/param.h> +#include <sys/kernel.h> +#include <sys/bus.h> +#include <contrib/dev/acpica/acpi.h> +#include "acpi_if.h" +#include <sys/module.h> +#include <dev/acpica/acpivar.h> +#include <sys/sysctl.h> + +#define _COMPONENT ACPI_OEM +ACPI_MODULE_NAME("Sony") + +#define ACPI_SONY_GET_BRIGHTNESS "GBRT" +#define ACPI_SONY_SET_BRIGHTNESS "SBRT" +#define ACPI_SONY_GET_PID "GPID" + +/* + * SNY5001 + * [GS]BRT [GS]PBR [GS]CTR [GS]PCR [GS]CMI [CDPW GCDP]? GWDP PWAK PWRN + * + */ + +struct acpi_sony_softc { + int pid; +}; +static struct acpi_sony_name_list +{ + char *nodename; + char *getmethod; + char *setmethod; + char *comment; +} acpi_sony_oids[] = { + { "brightness", "GBRT", "SBRT", "Display Brightness"}, + { "ctr", "GCTR", "SCTR", "??"}, + { "pcr", "GPCR", "SPCR", "???"}, +#if 0 + { "cmi", "GCMI", "SCMI", "????"}, +#endif + { "wdp", "GWDP", NULL, "?????"}, + { "cdp", "GCDP", "CDPW", "CD Power"}, /*shares [\GL03]&0x8 flag*/ + { "azp", "GAZP", "AZPW", "Audio Power"}, + { NULL, NULL, NULL } +}; + +static int acpi_sony_probe(device_t dev); +static int acpi_sony_attach(device_t dev); +static int acpi_sony_detach(device_t dev); +static int sysctl_acpi_sony_gen_handler(SYSCTL_HANDLER_ARGS); + +static device_method_t acpi_sony_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, acpi_sony_probe), + DEVMETHOD(device_attach, acpi_sony_attach), + DEVMETHOD(device_detach, acpi_sony_detach), + + {0, 0} +}; + +static driver_t acpi_sony_driver = { + "acpi_sony", + acpi_sony_methods, + sizeof(struct acpi_sony_softc), +}; + +static devclass_t acpi_sony_devclass; + +DRIVER_MODULE(acpi_sony, acpi, acpi_sony_driver, acpi_sony_devclass, + 0, 0); +MODULE_DEPEND(acpi_sony, acpi, 1, 1, 1); +static char *sny_id[] = {"SNY5001", NULL}; + +static int +acpi_sony_probe(device_t dev) +{ + int ret = ENXIO; + + if (ACPI_ID_PROBE(device_get_parent(dev), dev, sny_id)) { + device_set_desc(dev, "Sony notebook controller"); + ret = 0; + } + return (ret); +} + +static int +acpi_sony_attach(device_t dev) +{ + struct acpi_sony_softc *sc; + int i; + + sc = device_get_softc(dev); + acpi_GetInteger(acpi_get_handle(dev), ACPI_SONY_GET_PID, &sc->pid); + device_printf(dev, "PID %x\n", sc->pid); + for (i = 0 ; acpi_sony_oids[i].nodename != NULL; i++){ + SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), + SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), + i, acpi_sony_oids[i].nodename , CTLTYPE_INT | + ((acpi_sony_oids[i].setmethod)? CTLFLAG_RW: CTLFLAG_RD), + dev, i, sysctl_acpi_sony_gen_handler, "I", + acpi_sony_oids[i].comment); + } + return (0); +} + +static int +acpi_sony_detach(device_t dev) +{ + return (0); +} + +#if 0 +static int +acpi_sony_suspend(device_t dev) +{ + struct acpi_sony_softc *sc = device_get_softc(dev); + return (0); +} + +static int +acpi_sony_resume(device_t dev) +{ + return (0); +} +#endif + +static int +sysctl_acpi_sony_gen_handler(SYSCTL_HANDLER_ARGS) +{ + device_t dev = arg1; + int function = oidp->oid_arg2; + int error = 0, val; + + acpi_GetInteger(acpi_get_handle(dev), + acpi_sony_oids[function].getmethod, &val); + error = sysctl_handle_int(oidp, &val, 0, req); + if (error || !req->newptr || !acpi_sony_oids[function].setmethod) + return (error); + acpi_SetInteger(acpi_get_handle(dev), + acpi_sony_oids[function].setmethod, val); + return (0); +} diff --git a/sys/dev/acpi_support/acpi_toshiba.c b/sys/dev/acpi_support/acpi_toshiba.c new file mode 100644 index 000000000000..d15fe7e4ae4a --- /dev/null +++ b/sys/dev/acpi_support/acpi_toshiba.c @@ -0,0 +1,566 @@ +/*- + * Copyright (c) 2003 Hiroyuki Aizu <aizu@navi.org> + * 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. + * + * 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 <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include "opt_acpi.h" +#include <sys/param.h> +#include <sys/kernel.h> +#include <sys/module.h> +#include <sys/bus.h> + +#include <contrib/dev/acpica/acpi.h> +#include <dev/acpica/acpivar.h> + +#define _COMPONENT ACPI_OEM +ACPI_MODULE_NAME("Toshiba") + +/* + * Toshiba HCI interface definitions + * + * HCI is Toshiba's "Hardware Control Interface" which is supposed to + * be uniform across all their models. Ideally we would just call + * dedicated ACPI methods instead of using this primitive interface. + * However, the ACPI methods seem to be incomplete in some areas (for + * example they allow setting, but not reading, the LCD brightness + * value), so this is still useful. + */ + +#define METHOD_HCI "GHCI" +#define METHOD_HCI_ENABLE "ENAB" +#define METHOD_VIDEO "DSSX" + +/* Operations */ +#define HCI_SET 0xFF00 +#define HCI_GET 0xFE00 + +/* Return codes */ +#define HCI_SUCCESS 0x0000 +#define HCI_FAILURE 0x1000 +#define HCI_NOT_SUPPORTED 0x8000 +#define HCI_EMPTY 0x8C00 + +/* Functions */ +#define HCI_REG_LCD_BACKLIGHT 0x0002 +#define HCI_REG_FAN 0x0004 +#define HCI_REG_SYSTEM_EVENT 0x0016 +#define HCI_REG_VIDEO_OUTPUT 0x001C +#define HCI_REG_HOTKEY_EVENT 0x001E +#define HCI_REG_LCD_BRIGHTNESS 0x002A +#define HCI_REG_CPU_SPEED 0x0032 + +/* Field definitions */ +#define HCI_FAN_SHIFT 7 +#define HCI_LCD_BRIGHTNESS_BITS 3 +#define HCI_LCD_BRIGHTNESS_SHIFT (16 - HCI_LCD_BRIGHTNESS_BITS) +#define HCI_LCD_BRIGHTNESS_MAX ((1 << HCI_LCD_BRIGHTNESS_BITS) - 1) +#define HCI_VIDEO_OUTPUT_FLAG 0x0100 +#define HCI_VIDEO_OUTPUT_LCD 0x1 +#define HCI_VIDEO_OUTPUT_CRT 0x2 +#define HCI_VIDEO_OUTPUT_TV 0x4 +#define HCI_CPU_SPEED_BITS 3 +#define HCI_CPU_SPEED_SHIFT (16 - HCI_CPU_SPEED_BITS) +#define HCI_CPU_SPEED_MAX ((1 << HCI_CPU_SPEED_BITS) - 1) + +/* Key press/release events. */ +#define FN_F1_PRESS 0x013B +#define FN_F1_RELEASE 0x01BB +#define FN_F2_PRESS 0x013C +#define FN_F2_RELEASE 0x01BC +#define FN_F3_PRESS 0x013D +#define FN_F3_RELEASE 0x01BD +#define FN_F4_PRESS 0x013E +#define FN_F4_RELEASE 0x01BE +#define FN_F5_PRESS 0x013F +#define FN_F5_RELEASE 0x01BF +#define FN_F6_PRESS 0x0140 +#define FN_F6_RELEASE 0x01C0 +#define FN_F7_PRESS 0x0141 +#define FN_F7_RELEASE 0x01C1 +#define FN_F8_PRESS 0x0142 +#define FN_F8_RELEASE 0x01C2 +#define FN_F9_PRESS 0x0143 +#define FN_F9_RELEASE 0x01C3 +#define FN_BS_PRESS 0x010E +#define FN_BS_RELEASE 0x018E +#define FN_ESC_PRESS 0x0101 +#define FN_ESC_RELEASE 0x0181 +#define FN_KNJ_PRESS 0x0129 +#define FN_KNJ_RELEASE 0x01A9 + +/* HCI register definitions. */ +#define HCI_WORDS 6 /* Number of registers */ +#define HCI_REG_AX 0 /* Operation, then 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? */ + +struct acpi_toshiba_softc { + device_t dev; + ACPI_HANDLE handle; + ACPI_HANDLE video_handle; + struct sysctl_ctx_list sysctl_ctx; + struct sysctl_oid *sysctl_tree; +}; + +/* Prototype for HCI functions for getting/setting a value. */ +typedef int hci_fn_t(ACPI_HANDLE, int, UINT32 *); + +static int acpi_toshiba_probe(device_t dev); +static int acpi_toshiba_attach(device_t dev); +static int acpi_toshiba_detach(device_t dev); +static int acpi_toshiba_sysctl(SYSCTL_HANDLER_ARGS); +static hci_fn_t hci_force_fan; +static hci_fn_t hci_video_output; +static hci_fn_t hci_lcd_brightness; +static hci_fn_t hci_lcd_backlight; +static hci_fn_t hci_cpu_speed; +static int hci_call(ACPI_HANDLE h, int op, int function, UINT32 *arg); +static void hci_key_action(struct acpi_toshiba_softc *sc, ACPI_HANDLE h, + UINT32 key); +static void acpi_toshiba_notify(ACPI_HANDLE h, UINT32 notify, + void *context); +static int acpi_toshiba_video_probe(device_t dev); +static int acpi_toshiba_video_attach(device_t dev); + +ACPI_SERIAL_DECL(toshiba, "ACPI Toshiba Extras"); + +/* Table of sysctl names and HCI functions to call. */ +static struct { + char *name; + hci_fn_t *handler; +} sysctl_table[] = { + /* name, handler */ + {"force_fan", hci_force_fan}, + {"video_output", hci_video_output}, + {"lcd_brightness", hci_lcd_brightness}, + {"lcd_backlight", hci_lcd_backlight}, + {"cpu_speed", hci_cpu_speed}, + {NULL, NULL} +}; + +static device_method_t acpi_toshiba_methods[] = { + DEVMETHOD(device_probe, acpi_toshiba_probe), + DEVMETHOD(device_attach, acpi_toshiba_attach), + DEVMETHOD(device_detach, acpi_toshiba_detach), + + {0, 0} +}; + +static driver_t acpi_toshiba_driver = { + "acpi_toshiba", + acpi_toshiba_methods, + sizeof(struct acpi_toshiba_softc), +}; + +static devclass_t acpi_toshiba_devclass; +DRIVER_MODULE(acpi_toshiba, acpi, acpi_toshiba_driver, acpi_toshiba_devclass, + 0, 0); +MODULE_DEPEND(acpi_toshiba, acpi, 1, 1, 1); + +static device_method_t acpi_toshiba_video_methods[] = { + DEVMETHOD(device_probe, acpi_toshiba_video_probe), + DEVMETHOD(device_attach, acpi_toshiba_video_attach), + + {0, 0} +}; + +static driver_t acpi_toshiba_video_driver = { + "acpi_toshiba_video", + acpi_toshiba_video_methods, + 0, +}; + +static devclass_t acpi_toshiba_video_devclass; +DRIVER_MODULE(acpi_toshiba_video, acpi, acpi_toshiba_video_driver, + acpi_toshiba_video_devclass, 0, 0); +MODULE_DEPEND(acpi_toshiba_video, acpi, 1, 1, 1); + +static int enable_fn_keys = 1; +TUNABLE_INT("hw.acpi.toshiba.enable_fn_keys", &enable_fn_keys); + +/* + * HID Model + * ------------------------------------- + * TOS6200 Libretto L Series + * Dynabook Satellite 2455 + * Dynabook SS 3500 + * TOS6207 Dynabook SS2110 Series + * TOS6208 SPA40 + */ +static int +acpi_toshiba_probe(device_t dev) +{ + static char *tosh_ids[] = { "TOS6200", "TOS6207", "TOS6208", NULL }; + + if (acpi_disabled("toshiba") || + ACPI_ID_PROBE(device_get_parent(dev), dev, tosh_ids) == NULL || + device_get_unit(dev) != 0) + return (ENXIO); + + device_set_desc(dev, "Toshiba HCI Extras"); + return (0); +} + +static int +acpi_toshiba_attach(device_t dev) +{ + struct acpi_toshiba_softc *sc; + struct acpi_softc *acpi_sc; + ACPI_STATUS status; + int i; + + sc = device_get_softc(dev); + sc->dev = dev; + sc->handle = acpi_get_handle(dev); + + acpi_sc = acpi_device_get_parent_softc(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, + "toshiba", CTLFLAG_RD, 0, ""); + + for (i = 0; sysctl_table[i].name != NULL; i++) { + SYSCTL_ADD_PROC(&sc->sysctl_ctx, + SYSCTL_CHILDREN(sc->sysctl_tree), OID_AUTO, + sysctl_table[i].name, + CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_ANYBODY, + sc, i, acpi_toshiba_sysctl, "I", ""); + } + + if (enable_fn_keys != 0) { + status = AcpiEvaluateObject(sc->handle, METHOD_HCI_ENABLE, + NULL, NULL); + if (ACPI_FAILURE(status)) { + device_printf(dev, "enable FN keys failed\n"); + sysctl_ctx_free(&sc->sysctl_ctx); + return (ENXIO); + } + AcpiInstallNotifyHandler(sc->handle, ACPI_DEVICE_NOTIFY, + acpi_toshiba_notify, sc); + } + + return (0); +} + +static int +acpi_toshiba_detach(device_t dev) +{ + struct acpi_toshiba_softc *sc; + + sc = device_get_softc(dev); + if (enable_fn_keys != 0) { + AcpiRemoveNotifyHandler(sc->handle, ACPI_DEVICE_NOTIFY, + acpi_toshiba_notify); + } + sysctl_ctx_free(&sc->sysctl_ctx); + + return (0); +} + +static int +acpi_toshiba_sysctl(SYSCTL_HANDLER_ARGS) +{ + struct acpi_toshiba_softc *sc; + UINT32 arg; + int function, error = 0; + hci_fn_t *handler; + + sc = (struct acpi_toshiba_softc *)oidp->oid_arg1; + function = oidp->oid_arg2; + handler = sysctl_table[function].handler; + + /* Get the current value from the appropriate function. */ + ACPI_SERIAL_BEGIN(toshiba); + error = handler(sc->handle, HCI_GET, &arg); + if (error != 0) + goto out; + + /* Send the current value to the user and return if no new value. */ + error = sysctl_handle_int(oidp, &arg, 0, req); + if (error != 0 || req->newptr == NULL) + goto out; + + /* Set the new value via the appropriate function. */ + error = handler(sc->handle, HCI_SET, &arg); + +out: + ACPI_SERIAL_END(toshiba); + return (error); +} + +static int +hci_force_fan(ACPI_HANDLE h, int op, UINT32 *state) +{ + int ret; + + ACPI_SERIAL_ASSERT(toshiba); + if (op == HCI_SET) { + if (*state < 0 || *state > 1) + return (EINVAL); + *state <<= HCI_FAN_SHIFT; + } + ret = hci_call(h, op, HCI_REG_FAN, state); + if (ret == 0 && op == HCI_GET) + *state >>= HCI_FAN_SHIFT; + return (ret); +} + +static int +hci_video_output(ACPI_HANDLE h, int op, UINT32 *video_output) +{ + int ret; + ACPI_STATUS status; + + ACPI_SERIAL_ASSERT(toshiba); + if (op == HCI_SET) { + if (*video_output < 1 || *video_output > 7) + return (EINVAL); + if (h == NULL) + return (ENXIO); + *video_output |= HCI_VIDEO_OUTPUT_FLAG; + status = acpi_SetInteger(h, METHOD_VIDEO, *video_output); + if (ACPI_SUCCESS(status)) + ret = 0; + else + ret = ENXIO; + } else { + ret = hci_call(h, op, HCI_REG_VIDEO_OUTPUT, video_output); + if (ret == 0) + *video_output &= 0xff; + } + + return (ret); +} + +static int +hci_lcd_brightness(ACPI_HANDLE h, int op, UINT32 *brightness) +{ + int ret; + + ACPI_SERIAL_ASSERT(toshiba); + if (op == HCI_SET) { + if (*brightness < 0 || *brightness > HCI_LCD_BRIGHTNESS_MAX) + return (EINVAL); + *brightness <<= HCI_LCD_BRIGHTNESS_SHIFT; + } + ret = hci_call(h, op, HCI_REG_LCD_BRIGHTNESS, brightness); + if (ret == 0 && op == HCI_GET) + *brightness >>= HCI_LCD_BRIGHTNESS_SHIFT; + return (ret); +} + +static int +hci_lcd_backlight(ACPI_HANDLE h, int op, UINT32 *backlight) +{ + + ACPI_SERIAL_ASSERT(toshiba); + if (op == HCI_SET) { + if (*backlight < 0 || *backlight > 1) + return (EINVAL); + } + return (hci_call(h, op, HCI_REG_LCD_BACKLIGHT, backlight)); +} + +static int +hci_cpu_speed(ACPI_HANDLE h, int op, UINT32 *speed) +{ + int ret; + + ACPI_SERIAL_ASSERT(toshiba); + if (op == HCI_SET) { + if (*speed < 0 || *speed > HCI_CPU_SPEED_MAX) + return (EINVAL); + *speed <<= HCI_CPU_SPEED_SHIFT; + } + ret = hci_call(h, op, HCI_REG_CPU_SPEED, speed); + if (ret == 0 && op == HCI_GET) + *speed >>= HCI_CPU_SPEED_SHIFT; + return (ret); +} + +static int +hci_call(ACPI_HANDLE h, int op, int function, UINT32 *arg) +{ + ACPI_OBJECT_LIST args; + ACPI_BUFFER results; + ACPI_OBJECT obj[HCI_WORDS]; + ACPI_OBJECT *res; + int status, i, ret; + + ACPI_SERIAL_ASSERT(toshiba); + status = ENXIO; + + for (i = 0; i < HCI_WORDS; i++) { + obj[i].Type = ACPI_TYPE_INTEGER; + obj[i].Integer.Value = 0; + } + obj[HCI_REG_AX].Integer.Value = op; + obj[HCI_REG_BX].Integer.Value = function; + if (op == HCI_SET) + obj[HCI_REG_CX].Integer.Value = *arg; + + args.Count = HCI_WORDS; + args.Pointer = obj; + results.Pointer = NULL; + results.Length = ACPI_ALLOCATE_BUFFER; + if (ACPI_FAILURE(AcpiEvaluateObject(h, METHOD_HCI, &args, &results))) + goto end; + res = (ACPI_OBJECT *)results.Pointer; + if (!ACPI_PKG_VALID(res, HCI_WORDS)) { + printf("toshiba: invalid package!\n"); + return (ENXIO); + } + + acpi_PkgInt32(res, HCI_REG_AX, &ret); + if (ret == HCI_SUCCESS) { + if (op == HCI_GET) + acpi_PkgInt32(res, HCI_REG_CX, arg); + status = 0; + } else if (function == HCI_REG_SYSTEM_EVENT && op == HCI_GET && + ret == HCI_NOT_SUPPORTED) { + /* + * Sometimes system events are disabled without us requesting + * it. This workaround attempts to re-enable them. + * + * XXX This call probably shouldn't be recursive. Queueing + * a task via AcpiOsQueueForExecution() might be better. + */ + i = 1; + hci_call(h, HCI_SET, HCI_REG_SYSTEM_EVENT, &i); + } + +end: + if (results.Pointer != NULL) + AcpiOsFree(results.Pointer); + + return (status); +} + +/* + * Perform a few actions based on the keypress. Users can extend this + * functionality by reading the keystrokes we send to devd(8). + */ +static void +hci_key_action(struct acpi_toshiba_softc *sc, ACPI_HANDLE h, UINT32 key) +{ + UINT32 arg; + + ACPI_SERIAL_ASSERT(toshiba); + switch (key) { + case FN_F6_RELEASE: + /* Decrease LCD brightness. */ + hci_lcd_brightness(h, HCI_GET, &arg); + if (arg-- == 0) + arg = 0; + else + hci_lcd_brightness(h, HCI_SET, &arg); + break; + case FN_F7_RELEASE: + /* Increase LCD brightness. */ + hci_lcd_brightness(h, HCI_GET, &arg); + if (arg++ == 7) + arg = 7; + else + hci_lcd_brightness(h, HCI_SET, &arg); + break; + case FN_F5_RELEASE: + /* Cycle through video outputs. */ + hci_video_output(h, HCI_GET, &arg); + arg = (arg + 1) % 7; + hci_video_output(sc->video_handle, HCI_SET, &arg); + break; + case FN_F8_RELEASE: + /* Toggle LCD backlight. */ + hci_lcd_backlight(h, HCI_GET, &arg); + arg = (arg != 0) ? 0 : 1; + hci_lcd_backlight(h, HCI_SET, &arg); + break; + case FN_ESC_RELEASE: + /* Toggle forcing fan on. */ + hci_force_fan(h, HCI_GET, &arg); + arg = (arg != 0) ? 0 : 1; + hci_force_fan(h, HCI_SET, &arg); + break; + } +} + +static void +acpi_toshiba_notify(ACPI_HANDLE h, UINT32 notify, void *context) +{ + struct acpi_toshiba_softc *sc; + UINT32 key; + + sc = (struct acpi_toshiba_softc *)context; + + if (notify == 0x80) { + ACPI_SERIAL_BEGIN(toshiba); + while (hci_call(h, HCI_GET, HCI_REG_SYSTEM_EVENT, &key) == 0) { + hci_key_action(sc, h, key); + acpi_UserNotify("TOSHIBA", h, (uint8_t)key); + } + ACPI_SERIAL_END(toshiba); + } else + device_printf(sc->dev, "unknown notify: 0x%x\n", notify); +} + +/* + * Toshiba video pseudo-device to provide the DSSX method. + * + * HID Model + * ------------------------------------- + * TOS6201 Libretto L Series + */ +static int +acpi_toshiba_video_probe(device_t dev) +{ + static char *vid_ids[] = { "TOS6201", NULL }; + + if (acpi_disabled("toshiba") || + ACPI_ID_PROBE(device_get_parent(dev), dev, vid_ids) == NULL || + device_get_unit(dev) != 0) + return (ENXIO); + + device_quiet(dev); + device_set_desc(dev, "Toshiba Video"); + return (0); +} + +static int +acpi_toshiba_video_attach(device_t dev) +{ + struct acpi_toshiba_softc *sc; + + sc = devclass_get_softc(acpi_toshiba_devclass, 0); + if (sc == NULL) + return (ENXIO); + sc->video_handle = acpi_get_handle(dev); + return (0); +} |
