From owner-acpi-jp@jp.FreeBSD.org Fri Sep 19 18:37:37 2003
Received: (from daemon@localhost)
	by castle.jp.FreeBSD.org (8.11.6p2+3.4W/8.11.3) id h8J9bbT58829;
	Fri, 19 Sep 2003 18:37:37 +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 h8J9bXJ58438
	for <acpi-jp@jp.FreeBSD.org>; Fri, 19 Sep 2003 18:37:34 +0900 (JST)
	(envelope-from philip@nixsys.be)
Received: by hermes.nixsys.be (Postfix, from userid 1001)
	id A676351; Fri, 19 Sep 2003 11:37:28 +0200 (CEST)
From: Philip Paeps <philip+freebsd@paeps.cx>
To: acpi-jp@jp.FreeBSD.org
Message-ID: <20030919093728.GC652@hermes.nixsys.be>
Mail-Followup-To: acpi-jp@jp.FreeBSD.org
References: <20030914225148.2e1a21f7.aizu@navi.org> <20030916161328.Q10247@root.org> <20030918231149.7e57383b.aizu@navi.org> <20030919.013121.43004805.iwasaki@jp.FreeBSD.org> <20030918102054.T15072@root.org>
Mime-Version: 1.0
Content-Type: multipart/mixed; boundary="1yeeQ81UyVL57Vl7"
Content-Disposition: inline
In-Reply-To: <20030918102054.T15072@root.org>
X-Date-in-Rome: ante diem XIII 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: Fri, 19 Sep 2003 11:37:28 +0200
X-Sequence: acpi-jp 2681
Subject: [acpi-jp 2681] Re: TOSHIBA HCI driver on ACPI.
Sender: owner-acpi-jp@jp.FreeBSD.org
X-Originator: philip+freebsd@paeps.cx
X-Distribute: distribute version 2.1 (Alpha) patchlevel 24e+030902


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

On 2003-09-18 10:29:25 (-0700), Nate Lawson <nate@root.org> wrote:
> On Fri, 19 Sep 2003, Mitsuru IWASAKI wrote:
> > Source file path:    sys/i386/acpi/acpi_toshiba.c
> > Module compile path: sys/modules/acpi/acpi_toshiba/
>
> I would prefer these paths:
> Src:  sys/dev/acpi_toshiba
> Modules:  sys/modules/acpi_toshiba

I've used those for the Asus bits (well, s/toshiba/asus/g, actually).

> Are either of you willing to take a look at the Linux ASUS driver and
> rewrite it in a similar manner?  Also, if anyone has an ASUS laptop and
> would be willing to test a new driver, that would be good.
>    linux/drivers/acpi/asus_acpi.c

I'm still working on this.  It's been on the backburner for a bit, sorry about
that.  Attached is what's currently working pretty well on my laptop (L3H) and
a friend's L3D.

I really want to add volume/mute/key support, but it's being a real pain at
the moment.  The display switching also only works 'occasionally'.

Must find a quiet few hours to spend on this. *sigh*

> > Adding to ports collecton (sysutils?) also would be possible.
> 
> I like your changes.  I'd rather have this imported than in a port since it
> is simple and will be easy to maintain.

I also really hate drivers in the ports tree (like the nvidia one).  I always
forget to rebuild the port after rebuilding a kernel, and end up without the
module loaded the first time I boot the new kernel.

Cheers,

 - Philip

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

  People will believe anything if you whisper it.

--1yeeQ81UyVL57Vl7
Content-Type: text/plain; charset=us-ascii
Content-Disposition: attachment; filename=Makefile

# $FreeBSD$

.PATH:	${.CURDIR}/../../dev/acpi_asus
       

KMOD=	acpi_asus

CFLAGS+=   -I${.CURDIR}/../../contrib/dev/acpica

SRCS=	acpi_asus.c
SRCS+=	device_if.h bus_if.h opt_acpi.h

.include <bsd.kmod.mk>

--1yeeQ81UyVL57Vl7
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: Figure out how the volume-keys work
 *       Find a clean way to implement the hotkeys
 *       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", "\\INFB", "SDSP" ,NULL,     "MLED", NULL, "WLED" },
	{ "L3D", "Asus L3400D", "GPLV", "SPLV", "\\INFB", "SDSP" ,"\\MALD", "MLED", NULL, "WLED" },
	{ "L3H", "Asus L3500H", "GPLV", "SPLV", "\\INFB", "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_read_int(ACPI_HANDLE h, char *path, int *outint);
static int	acpi_asus_write_int(ACPI_HANDLE h, char *path, int arg, ACPI_BUFFER *outbuf);

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)
{
	ACPI_BUFFER	buf;
	ACPI_OBJECT	*obj;

	struct acpi_asus_model	*model;
	
	if ((acpi_get_type(dev) == ACPI_TYPE_DEVICE) &&
			!acpi_disabled("asus") 	&&
			acpi_MatchHid(dev, ASUS_HID))
	{
		buf.Pointer = NULL;
		buf.Length = ACPI_ALLOCATE_BUFFER;

		if (acpi_asus_write_int(acpi_get_handle(dev), "INIT", 0, &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;
	
	/* Initialise the brightness */
	if (!acpi_asus_read_int(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_asus_read_int(sc->asus_handle, sc->asus_model->disp_get, &sc->asus_state_disp))
	{
		device_printf(dev, "Couldn't get display state\n");
		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_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)
	{
		if (!acpi_asus_write_int(sc->asus_handle, sc->asus_model->mled_set, 1, NULL))
			device_printf(sc->asus_dev, "Unable to extinguish MLED\n");
	}

	if (sc->asus_model->wled_set != NULL)
	{
		if (!acpi_asus_write_int(sc->asus_handle, sc->asus_model->wled_set, 0, 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);
}

/*
 * Execute a method which taking an integral argument and returning either a
 * buffer or nothing.  If nothing is expected to be returned, outbuf should be
 * NULL.
 *
 * Returns 1 on success, 0 on failure.
 */
static int
acpi_asus_write_int(ACPI_HANDLE h, char *path, int arg, ACPI_BUFFER *outbuf)
{
	ACPI_STATUS		error;
	ACPI_OBJECT_LIST	ArgList;
	ACPI_OBJECT		Arg;
	
	ACPI_LOCK_DECL;
	
	ACPI_LOCK;

	ACPI_MEMSET(&ArgList, 0, sizeof(ArgList));
	ArgList.Count = 1;
	ArgList.Pointer = &Arg;
	
	ACPI_MEMSET(&Arg, 0, sizeof(Arg));
	Arg.Type = ACPI_TYPE_INTEGER;
	Arg.Integer.Value = arg;

	error = AcpiEvaluateObject(h, path, &ArgList, outbuf);

	ACPI_UNLOCK;

	return (error == AE_OK);
}

/*
 * Execute a method taking no arguments and returning an int.
 *
 * Returns 1 on success, 0 on failure.
 */
static int
acpi_asus_read_int(ACPI_HANDLE h, char *path, int *outint)
{
	ACPI_STATUS		error;
	ACPI_BUFFER		buf;
	ACPI_OBJECT		res;
	
	ACPI_LOCK_DECL;
	
	ACPI_LOCK;

	buf.Pointer = &res;
	buf.Length = sizeof(res);

	if (ACPI_SUCCESS(error = AcpiEvaluateObject(h, path, NULL, &buf)))
	{
		if (res.Type == ACPI_TYPE_INTEGER)
			*outint = res.Integer.Value;
		else
			error = AE_TYPE;
	}

	ACPI_UNLOCK;

	return (error == AE_OK);
}


/*
 * 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_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;

	if (!acpi_asus_write_int(sc->asus_handle, sc->asus_model->bright_set, bright, NULL))
		device_printf(sc->asus_dev, "Unable to set brightness\n");

	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_LOCK_DECL;

	ACPI_LOCK;

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

	/*
	 * XXX: Setting displays is very broken!
	 */

	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_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 */

		if (!acpi_asus_write_int(sc->asus_handle, sc->asus_model->mled_set, led, NULL))
			device_printf(sc->asus_dev, "Unable to set MLED\n");
	}
	else
	{
		sc->asus_state_wled = led;
		led = led & 1;
		
		if (!acpi_asus_write_int(sc->asus_handle, sc->asus_model->wled_set, led, 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!
	 */
	ACPI_VPRINT(dev, acpi_device_get_parent_softc(dev),
		"Notify %x\n", notify);
}

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

--1yeeQ81UyVL57Vl7--
