From owner-acpi-jp@jp.FreeBSD.org Tue Sep 23 16:25:27 2003
Received: (from daemon@localhost)
	by castle.jp.FreeBSD.org (8.11.6p2+3.4W/8.11.3) id h8N7PRo96371;
	Tue, 23 Sep 2003 16:25:27 +0900 (JST)
	(envelope-from owner-acpi-jp@jp.FreeBSD.org)
Received: from hermes.nixsys.be (postfix@hermes.nixsys.be [2001:ab8:2007:0:20c:6eff:fe4b:23f])
	by castle.jp.FreeBSD.org (8.11.6p2+3.4W/8.11.3) with ESMTP/inet6 id h8N7PMJ96364
	for <acpi-jp@jp.FreeBSD.org>; Tue, 23 Sep 2003 16:25:23 +0900 (JST)
	(envelope-from philip@nixsys.be)
Received: by hermes.nixsys.be (Postfix, from userid 1001)
	id A808F6D; Tue, 23 Sep 2003 09:25:16 +0200 (CEST)
From: Philip Paeps <philip+freebsd@paeps.cx>
To: acpi-jp@jp.FreeBSD.org
Message-ID: <20030923072516.GC645@hermes.nixsys.be>
Mail-Followup-To: acpi-jp@jp.FreeBSD.org
Mime-Version: 1.0
Content-Type: multipart/mixed; boundary="wq9mPyueHGvFACwf"
Content-Disposition: inline
X-Date-in-Rome: ante diem IX Kalendas Octobres MMDCCLVI ab Urbe Condida
X-PGP-Fingerprint: FA74 3C27 91A6 79D5 F6D3 FC53 BF4B D0E6 049D B879
X-Message-Flag: Get a proper mailclient!  Mutt: <http://www.mutt.org/>
User-Agent: Mutt/1.5.4i
Reply-To: acpi-jp@jp.FreeBSD.org
Precedence: list
Date: Tue, 23 Sep 2003 09:25:16 +0200
X-Sequence: acpi-jp 2691
Subject: [acpi-jp 2691] Driver for Asus extras
Sender: owner-acpi-jp@jp.FreeBSD.org
X-Originator: philip+freebsd@paeps.cx
X-Distribute: distribute version 2.1 (Alpha) patchlevel 24e+030902


--wq9mPyueHGvFACwf
Content-Type: text/plain; charset=us-ascii
Content-Disposition: inline

I found some time yesterday to clean and finish up things a little bit.
Attached is a driver which I found to function well on the three laptops it
currently supports.  

After I test this all some more, I'll add the other laptops currently
supported by the Linux driver.  Currently, I'm a bit hesitant about it, as I
can't think of any way to verify their information other than the 'it works
here' comments :-/

I'll write a little manual (asus(4)) later today, and also think about a way
to handle the events the machine notifies.

In the interests of simplicity, would it be a good idea to 'translate' the
device-specific notifies into some known constants?  Something like:

  #define ACPI_HOTK_BUTTON0     0x0001
  #define ACPI_HOTK_BUTTON1     0x0002

That adds a bit more code to the different drivers, but it makes the daemon a
lot easier to maintain and implement.  Any thoughts on that?

Work (known to be working) in progress attached.  Reviews and testing would be
much appreciated!

Cheers,

 - Philip

-- 
Philip Paeps                                          Please don't CC me, I am
                                                       subscribed to the list.

  Never admit anything.
  Never regret anything
  whatever it is, you're not responsible.

--wq9mPyueHGvFACwf
Content-Type: text/plain; charset=us-ascii
Content-Disposition: attachment; filename="acpi_asus.c"

/*-
 * Copyright (c) 2003 Philip Paeps <philip@paeps.cx>
 * 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 features -- hotkeys, leds, etc -- sported on recent
 * laptops from Asus.  Inspired by, but not based on, the acpi4asus project
 * which implements some of these features in the Linux kernel.
 *
 *   <http://sourceforge.net/projects/acpi4asus/>
 *
 * Currently supports toggling the mail/wifi leds, switching displays and
 * dealing with brightness.
 *
 * TODO: Write a little daemon to deal with notifies
 *       Verify the Linux data about more laptops
 *       Test :-)
 */

#include "opt_acpi.h"
#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/bus.h>
#include <sys/sysctl.h>

#include "acpi.h"
#include <dev/acpica/acpivar.h>

#define _COMPONENT	ACPI_ASUS
ACPI_MODULE_NAME("ASUS");

struct acpi_asus_model {
	char	*name;		/* What INIT says */
	char	*descr;
	char	*bright_get;	/* Brightness */
	char	*bright_set;
	char	*disp_get;	/* Display */
	char	*disp_set;
	char	*mled_get;	/* Mail led */
	char	*mled_set;
	char	*wled_get;	/* WiFi led */
	char	*wled_set;
};

struct acpi_asus_softc {
	device_t	asus_dev;
	ACPI_HANDLE	asus_handle;

	struct acpi_asus_model *asus_model;

	int	asus_state_bright;
	int	asus_state_disp;	/* not working properly */
	int	asus_state_mled;
	int	asus_state_wled;
};

/*
 * So far, this is the only HID spotted
 */
#define	ASUS_HID	"ATK0100"

static struct acpi_asus_model acpi_asus_model_table[] = {
	{ "L2E", "Asus L2000E", "GPLV", "SPLV", "\\_SB.INFB", "SDSP", NULL,
		"MLED", NULL, "WLED" },
		
	{ "L3D", "Asus L3400D", "GPLV", "SPLV", "\\_SB.INFB", "SDSP", "\\MALD", 
		"MLED", NULL, "WLED" },

	{ "L3H", "Asus L3500H", "GPLV", "SPLV", "\\_SB.DAT3", "SDSP", NULL, 
		"MLED", NULL, "WLED" },

	{ NULL, NULL, NULL, NULL, NULL, NULL }
};

/*
 * Brightness up/down events.
 */
#define BRIGHT_UP	0x10
#define	BRIGHT_DOWN	0x20

static struct	sysctl_ctx_list	asus_sysctl_ctx;
static struct 	sysctl_oid	*asus_sysctl_tree;

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 int	acpi_asus_sysctl_bright(SYSCTL_HANDLER_ARGS);
static int	acpi_asus_sysctl_disp(SYSCTL_HANDLER_ARGS);
static int	acpi_asus_sysctl_led(SYSCTL_HANDLER_ARGS);

static void	acpi_asus_notify_handler(ACPI_HANDLE, UINT32, void *);

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;

static int
acpi_asus_probe(device_t dev)
{
	struct acpi_asus_model	*model;
	
	ACPI_BUFFER		buf;
	ACPI_OBJECT_LIST	ArgList;
	ACPI_OBJECT		*obj, Arg;
	
	ArgList.Count = 1;
	ArgList.Pointer = &Arg;
	
	if ((acpi_get_type(dev) == ACPI_TYPE_DEVICE) &&
			!acpi_disabled("asus") 	&&
			acpi_MatchHid(dev, ASUS_HID))
	{
		buf.Pointer = NULL;
		buf.Length = ACPI_ALLOCATE_BUFFER;

		Arg.Type = ACPI_TYPE_INTEGER;
		Arg.Integer.Value = 0;

		if (ACPI_SUCCESS(AcpiEvaluateObject(acpi_get_handle(dev), 
						"INIT", &ArgList, &buf)))
		{
			obj = buf.Pointer;
			ACPI_VPRINT(dev, acpi_device_get_parent_softc(dev),
					"Method INIT says %s\n", 
					obj->String.Pointer);

			for (model = acpi_asus_model_table; 
					model->name != NULL; model++)
			{
				if (strcmp(model->name, obj->String.Pointer) 
						== 0)
				{
					device_set_desc(dev, model->descr);
					
					((struct acpi_asus_softc *)
					 device_get_softc(dev))->asus_model 
						= model;

					AcpiOsFree(buf.Pointer);
					return (0);
				}
			}
			AcpiOsFree(buf.Pointer);
		}
	}
	return (ENXIO);
}

static int
acpi_asus_attach(device_t dev)
{
	struct acpi_asus_softc	*sc;

	ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__);

	sc = device_get_softc(dev);

	sc->asus_dev = dev;
	sc->asus_handle = acpi_get_handle(dev);
	
	/* Leds are off at boot */
	sc->asus_state_mled = 0;
	sc->asus_state_mled = 0;

	/* Activate hotkeys */
	if (ACPI_FAILURE(AcpiEvaluateObject(sc->asus_handle, "BSTS", NULL,
					NULL)))
		ACPI_VPRINT(dev, acpi_device_get_parent_softc(dev),
				"BSTS failed; nothing to worry about\n");
	else
		ACPI_VPRINT(dev, acpi_device_get_parent_softc(dev),
				"BSTS worked, and there was much rejoicing\n");

	/* Initialise the brightness */
	if (ACPI_FAILURE(acpi_EvaluateInteger(sc->asus_handle, 
					sc->asus_model->bright_get, 
					&sc->asus_state_bright)))
	{
		device_printf(dev, "Couldn't get initial brightness\n");
		sc->asus_model->bright_get = NULL;
	}
	else
	{
		ACPI_VPRINT(dev, acpi_device_get_parent_softc(dev),
				"Display brightness: %d\n", 
				sc->asus_state_bright);
	}

	/* Initialize the display */
	if (ACPI_FAILURE(acpi_EvaluateInteger(sc->asus_handle,
					sc->asus_model->disp_get,
					&sc->asus_state_disp)))
	{
		device_printf(dev, "Couldn't get display state: %d\n", 
				sc->asus_state_disp);
		sc->asus_model->disp_get = NULL;
	}
	else
	{
		ACPI_VPRINT(dev, acpi_device_get_parent_softc(dev),
				"Display state: %d\n", sc->asus_state_disp);
	}

	/* Create sysctl nodes */
	sysctl_ctx_init(&asus_sysctl_ctx);

	asus_sysctl_tree = SYSCTL_ADD_NODE(&asus_sysctl_ctx, 
			SYSCTL_CHILDREN((acpi_device_get_parent_softc(dev))
				->acpi_sysctl_tree), 
			OID_AUTO, "asus", CTLFLAG_RD, 0, "");

	if (sc->asus_model->bright_get != NULL)
	{
		SYSCTL_ADD_PROC(&asus_sysctl_ctx, 
				SYSCTL_CHILDREN(asus_sysctl_tree), OID_AUTO,
				"bright", CTLTYPE_INT | CTLFLAG_RW, sc, 0, 
				acpi_asus_sysctl_bright, "I", 
				"brightness of the display");
	}

	if (sc->asus_model->disp_get != NULL)
	{
		SYSCTL_ADD_PROC(&asus_sysctl_ctx, 
				SYSCTL_CHILDREN(asus_sysctl_tree), OID_AUTO, 
				"disp", CTLTYPE_INT | CTLFLAG_RW, sc, 0, 
				acpi_asus_sysctl_disp, "I", 
				"display output state");
	}
	
	if (sc->asus_model->mled_set != NULL)
	{
		SYSCTL_ADD_PROC(&asus_sysctl_ctx, 
				SYSCTL_CHILDREN(asus_sysctl_tree), OID_AUTO, 
				"mled", CTLTYPE_INT | CTLFLAG_RW, sc, 0, 
				acpi_asus_sysctl_led, "I", 
				"current state of the mail led");
	}

	if (sc->asus_model->wled_set != NULL)
	{
		SYSCTL_ADD_PROC(&asus_sysctl_ctx, 
				SYSCTL_CHILDREN(asus_sysctl_tree), OID_AUTO, 
				"wled", CTLTYPE_INT | CTLFLAG_RW, sc, 0, 
				acpi_asus_sysctl_led, "I", 
				"current state of the wifi led");
	}

    
	/* Install a notify handler to keep track of things */
	AcpiInstallNotifyHandler(sc->asus_handle, ACPI_SYSTEM_NOTIFY,
			     acpi_asus_notify_handler, dev);

	return (0);
}

static int
acpi_asus_detach(device_t dev)
{
	struct acpi_asus_softc	*sc;

	ACPI_OBJECT_LIST	ArgList;
	ACPI_OBJECT		Arg;
		
	ArgList.Count = 1;
	ArgList.Pointer = &Arg;

	ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__);
	
	sc = device_get_softc(dev);

	/* If there are lights, turn them off */
	if (sc->asus_model->mled_set != NULL)
	{
		Arg.Type = ACPI_TYPE_INTEGER;
		Arg.Integer.Value = 1;

		if (ACPI_FAILURE(AcpiEvaluateObject(sc->asus_handle, 
						sc->asus_model->mled_set,
						&ArgList, NULL)))
			device_printf(sc->asus_dev, 
					"Unable to extinguish MLED\n");
	}

	if (sc->asus_model->wled_set != NULL)
	{
		Arg.Type = ACPI_TYPE_INTEGER;
		Arg.Integer.Value = 0;

		if (ACPI_FAILURE(AcpiEvaluateObject(sc->asus_handle,
						sc->asus_model->wled_set,
						&ArgList, NULL)))
			device_printf(sc->asus_dev, 
					"Unable to extinguish WLED\n");
	}

	/* Detach notify handler */
	AcpiRemoveNotifyHandler(sc->asus_handle, ACPI_SYSTEM_NOTIFY,
			     acpi_asus_notify_handler);
	
	/* Free sysctl tree */
	sysctl_ctx_free(&asus_sysctl_ctx);

	return (0);
}

/*
 * Handler for sysctl to get and set the brightness
 */
static int
acpi_asus_sysctl_bright(SYSCTL_HANDLER_ARGS)
{
	struct acpi_asus_softc	*sc;

	int	bright;
	int	error;

	ACPI_OBJECT_LIST	ArgList;
	ACPI_OBJECT		Arg;
	
	ArgList.Count = 1;
	ArgList.Pointer = &Arg;

	ACPI_LOCK_DECL;
	
	ACPI_LOCK;

	sc = (struct acpi_asus_softc *)oidp->oid_arg1;
	bright = sc->asus_state_bright;
	error = sysctl_handle_int(oidp, &bright, 0, req);

	/* No-op or weirdness */
	if ((error != 0) || (req->newptr == NULL))
	{
		ACPI_UNLOCK;
		return (error);
	}
    
	/* Out of range */
	if ((bright < 0) || (bright > 15)) 
	{
		error = EINVAL;
		ACPI_UNLOCK;
		return (error);
	}
	
	/* Keep track of status and update */
	sc->asus_state_bright = bright;

	Arg.Type = ACPI_TYPE_INTEGER;
	Arg.Integer.Value = bright;

	if (ACPI_FAILURE(AcpiEvaluateObject(sc->asus_handle, 
					sc->asus_model->bright_set,
					&ArgList, NULL)))
		device_printf(sc->asus_dev, "Unable to set brightness\n");
	else
		ACPI_VPRINT(sc->asus_dev, 
				acpi_device_get_parent_softc(sc->asus_dev),
				"Brightness set to %d\n", bright);

	ACPI_UNLOCK;
	return (error);
}

/* 
 * Handler for sysctl to fiddle with the display
 */

static int
acpi_asus_sysctl_disp(SYSCTL_HANDLER_ARGS)
{
	struct acpi_asus_softc	*sc;

	int	disp;
	int	error;
	
	ACPI_OBJECT_LIST	ArgList;
	ACPI_OBJECT		Arg;
	
	ArgList.Count = 1;
	ArgList.Pointer = &Arg;

	ACPI_LOCK_DECL;

	ACPI_LOCK;

	sc = (struct acpi_asus_softc *)oidp->oid_arg1;

	/* XXX: Needs more testing! */
	acpi_EvaluateInteger(sc->asus_handle, sc->asus_model->disp_get, &disp);

	error = sysctl_handle_int(oidp, &disp, 0, req);

	/* No-op or weirdness */
	if ((error != 0) || (req->newptr == NULL))
	{
		ACPI_UNLOCK;
		return (error);
	}
    
	/* Out of range */
	if ((disp < 0) || (disp > 7)) 
	{
		error = EINVAL;
		ACPI_UNLOCK;
		return (error);
	}
	
	/* Keep track of status and update */
	sc->asus_state_disp = disp;

	Arg.Type = ACPI_TYPE_INTEGER;
	Arg.Integer.Value = disp;

	if (ACPI_FAILURE(AcpiEvaluateObject(sc->asus_handle, 
					sc->asus_model->disp_set,
					&ArgList, NULL)))
		device_printf(sc->asus_dev, "Unable to set display\n");
	else
		ACPI_VPRINT(sc->asus_dev, 
				acpi_device_get_parent_softc(sc->asus_dev),
				"Display set to %d\n", disp);

	ACPI_UNLOCK;
	return (error);
}

/*
 * Handler for sysctl to turn extra leds on or off.
 */
static int
acpi_asus_sysctl_led(SYSCTL_HANDLER_ARGS)
{
	struct acpi_asus_softc	*sc;

	int	led;
	int	error;

	ACPI_OBJECT_LIST	ArgList;
	ACPI_OBJECT		Arg;
		
	ArgList.Count = 1;
	ArgList.Pointer = &Arg;

	ACPI_LOCK_DECL;
	
	ACPI_LOCK;

	sc = (struct acpi_asus_softc *)oidp->oid_arg1;
			
	if (strncmp(oidp->oid_name, "mled", 4) == 0)
		led = sc->asus_state_mled;
	else 
		led = sc->asus_state_wled;

	error = sysctl_handle_int(oidp, &led, 0, req);

	/* No-op or weirdness */
	if ((error != 0) || (req->newptr == NULL))
	{
		ACPI_UNLOCK;
		return (error);
	}
    
	/* Out of range */
	if ((led < 0) || (led > 1)) 
	{
		error = EINVAL;
		ACPI_UNLOCK;
		return (error);
	}
	
	/* Keep track of status and update */
	if (strncmp(oidp->oid_name, "mled", 4) == 0)
	{
		sc->asus_state_mled = led;
		led = ~led & 1;	/* yes, this is weird */

		Arg.Type = ACPI_TYPE_INTEGER;
		Arg.Integer.Value = led;

		if (ACPI_FAILURE(AcpiEvaluateObject(sc->asus_handle,
						sc->asus_model->mled_set,
						&ArgList, NULL)))
			device_printf(sc->asus_dev, "Unable to set MLED\n");
	}
	else
	{
		sc->asus_state_wled = led;
		led = led & 1;
		
		Arg.Type = ACPI_TYPE_INTEGER;
		Arg.Integer.Value = led;
		
		if (ACPI_FAILURE(AcpiEvaluateObject(sc->asus_handle,
						sc->asus_model->wled_set,
						&ArgList, NULL)))
			device_printf(sc->asus_dev, "Unable to set WLED\n");
	}

	ACPI_UNLOCK;
	return (error);
}

/*
 * Keep track of what's going on
 */
static void
acpi_asus_notify_handler(ACPI_HANDLE h, UINT32 notify, void *context)
{
	device_t dev = context;

	struct acpi_asus_softc *sc = device_get_softc(dev);

	if ((notify & ~BRIGHT_UP) <= 15)
	{
		sc->asus_state_bright = (notify & ~BRIGHT_UP);
		ACPI_VPRINT(dev, acpi_device_get_parent_softc(dev),
				"Brightness increased\n");
	}
	else if ((notify & ~BRIGHT_DOWN) <= 15)
	{
		sc->asus_state_bright = (notify & ~BRIGHT_DOWN);
		ACPI_VPRINT(dev, acpi_device_get_parent_softc(dev),
				"Brightness decreased\n");
	}

	/*
	 * Very noisy!
	 *
	 * XXX: Where shall we handle these?
	 */
	ACPI_VPRINT(dev, acpi_device_get_parent_softc(dev),
		"Notify 0x%02x\n", notify);
}

DRIVER_MODULE(acpi_asus, acpi, acpi_asus_driver, acpi_asus_devclass, 0, 0);
MODULE_DEPEND(acpi_asus, acpi, 100, 100, 100);

--wq9mPyueHGvFACwf--
