/* $OpenBSD: acpisony.c,v 1.6 2014/09/14 14:17:24 jsg Exp $ */
/*
 * Copyright (c) 2010 Paul Irofti <pirofti@openbsd.org>
 *
 * Permission to use, copy, modify, and/or distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#include <sys/param.h>
#include <sys/systm.h>

#include <dev/acpi/acpireg.h>
#include <dev/acpi/acpivar.h>
#include <dev/acpi/acpidev.h>
#include <dev/acpi/amltypes.h>
#include <dev/acpi/dsdt.h>

#include <machine/apmvar.h>

int	acpisony_match(struct device *, void *, void *);
void	acpisony_attach(struct device *, struct device *, void *);
int	acpisony_activate(struct device *, int);
int	acpisony_notify(struct aml_node *, int, void *);

#ifdef ACPISONY_DEBUG
#define DPRINTF(x)	printf x
#else
#define DPRINTF(x)
#endif

/* Notifications */
#define	SONY_NOTIFY_FN_KEY			0x90

#define	SONY_NOTIFY_BRIGHTNESS_DOWN_PRESSED	0x85
#define	SONY_NOTIFY_BRIGHTNESS_DOWN_RELEASED	0x05
#define	SONY_NOTIFY_BRIGHTNESS_UP_PRESSED	0x86
#define	SONY_NOTIFY_BRIGHTNESS_UP_RELEASED	0x06

#define	SONY_NOTIFY_DISPLAY_SWITCH_PRESSED	0x87
#define	SONY_NOTIFY_DISPLAY_SWITCH_RELEASED	0x07

#define	SONY_NOTIFY_ZOOM_OUT_PRESSED		0x89
#define	SONY_NOTIFY_ZOOM_OUT_RELEASED		0x09

#define	SONY_NOTIFY_ZOOM_IN_PRESSED		0x8a
#define	SONY_NOTIFY_ZOOM_IN_RELEASED		0x0a

#define	SONY_NOTIFY_SUSPEND_PRESSED		0x8c
#define	SONY_NOTIFY_SUSPEND_RELEASED		0x0c

struct acpisony_softc {
	struct device		sc_dev;

	bus_space_tag_t		sc_iot;
	bus_space_handle_t	sc_ioh;

	struct acpi_softc	*sc_acpi;
	struct aml_node		*sc_devnode;
};

struct cfattach acpisony_ca = {
	sizeof(struct acpisony_softc), acpisony_match, acpisony_attach,
	NULL, acpisony_activate
};

struct cfdriver acpisony_cd = {
	NULL, "acpisony", DV_DULL
};

void acpisony_notify_setup(struct acpisony_softc *);
int acpisony_set_hotkey(struct acpisony_softc *, int, int);
int acpisony_find_offset(struct acpisony_softc *, int);

void acpisony_brightness_down(struct acpisony_softc *);
int acpisony_get_brightness(struct acpisony_softc *);
void acpisony_set_brightness(struct acpisony_softc *, int);

int
acpisony_match(struct device *parent, void *match, void *aux)
{
	struct acpi_attach_args *aa = aux;
	struct cfdata		*cf = match;

	if (aa->aaa_name == NULL ||
	    strcmp(aa->aaa_name, cf->cf_driver->cd_name) != 0 ||
	    aa->aaa_table != NULL)
		return (0);

	return (1);
}

void
acpisony_attach(struct device *parent, struct device *self, void *aux)
{
	struct acpisony_softc	*sc = (struct acpisony_softc *)self;
	struct acpi_attach_args *aa = aux;

	sc->sc_acpi = (struct acpi_softc *)parent;
	sc->sc_devnode = aa->aaa_node;

	printf(": %s\n", sc->sc_devnode->name);

	/* Setup the notification masks */
	acpisony_notify_setup(sc);

	aml_register_notify(sc->sc_devnode, aa->aaa_dev,
	    acpisony_notify, sc, ACPIDEV_NOPOLL);
}

int
acpisony_activate(struct device *self, int act)
{
	struct acpisony_softc *sc = (struct acpisony_softc *)self;

	switch (act) {
	case DVACT_WAKEUP:
		acpisony_notify_setup(sc);
		break;
	}
	return 0;
}

int
acpisony_notify(struct aml_node *node, int notify, void *arg)
{
	struct acpisony_softc *sc = arg;
	int val, key = 0;

	if (notify == SONY_NOTIFY_FN_KEY) {
		notify -= 0x90;
		DPRINTF(("notify = %X", notify));

		if (notify == acpisony_find_offset(sc, 0x100)) {
			DPRINTF(("key = 0x100\n"));
			key = 0x100;
		}
		if (notify == acpisony_find_offset(sc, 0x127)) {
			DPRINTF(("key = 0x127\n"));
			key = 0x127;
		}

		if (key) {
			val = acpisony_set_hotkey(sc, key, 0x200);
			if (val < 0) {
				printf("returned val = %X", val);
				return 1;
			}
			notify = val & 0xff;

			DPRINTF(("Treat %X events, notify %X\n", key, notify));
		} else
			DPRINTF(("rfkill update, notify %X\n", notify));
	}

	switch (notify) {
	case SONY_NOTIFY_BRIGHTNESS_DOWN_PRESSED:
		DPRINTF(("br-down-pressed\n"));
		acpisony_brightness_down(sc);
		break;
	case SONY_NOTIFY_BRIGHTNESS_DOWN_RELEASED:
		DPRINTF(("br-down-released\n"));
		break;
	case SONY_NOTIFY_BRIGHTNESS_UP_PRESSED:
		DPRINTF(("br-up-pressed\n"));
		break;
	case SONY_NOTIFY_BRIGHTNESS_UP_RELEASED:
		DPRINTF(("br-up-released\n"));
		break;
	case SONY_NOTIFY_DISPLAY_SWITCH_PRESSED:
		DPRINTF(("display-pressed\n"));
		break;
	case SONY_NOTIFY_DISPLAY_SWITCH_RELEASED:
		DPRINTF(("display-released\n"));
		break;
	case SONY_NOTIFY_ZOOM_IN_PRESSED:
		DPRINTF(("zoom-in-pressed\n"));
		break;
	case SONY_NOTIFY_ZOOM_IN_RELEASED:
		DPRINTF(("zoom-in-released\n"));
		break;
	case SONY_NOTIFY_ZOOM_OUT_PRESSED:
		DPRINTF(("zoom-out-pressed\n"));
		break;
	case SONY_NOTIFY_ZOOM_OUT_RELEASED:
		DPRINTF(("zoom-out-released\n"));
		break;
	case SONY_NOTIFY_SUSPEND_PRESSED:
		DPRINTF(("suspend-pressed\n"));
#ifndef SMALL_KERNEL
		if (acpi_record_event(sc->sc_acpi, APM_USER_SUSPEND_REQ))
			acpi_addtask(sc->sc_acpi, acpi_sleep_task,
			    sc->sc_acpi, ACPI_STATE_S3);
#endif
		break;
	case SONY_NOTIFY_SUSPEND_RELEASED:
		DPRINTF(("suspend-released\n"));
		break;
	default:
		printf("%s: unknown event 0x%02x\n", DEVNAME(sc), notify);
		break;
	}

	return 0;
}

void
acpisony_notify_setup(struct acpisony_softc *sc)
{
	struct aml_value arg;

	bzero(&arg, sizeof(arg));
	arg.type = AML_OBJTYPE_INTEGER;

	arg.v_integer = 1;
	aml_evalname(sc->sc_acpi, sc->sc_devnode, "ECON", 1, &arg, NULL);

	/* Enable all events */
	arg.v_integer = 0xffff;
	aml_evalname(sc->sc_acpi, sc->sc_devnode, "SN02", 1, &arg, NULL);

	/* Enable hotkeys */
	arg.v_integer = 0x04;
	aml_evalname(sc->sc_acpi, sc->sc_devnode, "SN02", 1, &arg, NULL);
	arg.v_integer = 0x02;
	aml_evalname(sc->sc_acpi, sc->sc_devnode, "SN07", 1, &arg, NULL);
	arg.v_integer = 0x10;
	aml_evalname(sc->sc_acpi, sc->sc_devnode, "SN02", 1, &arg, NULL);
	arg.v_integer = 0x00;
	aml_evalname(sc->sc_acpi, sc->sc_devnode, "SN07", 1, &arg, NULL);
	arg.v_integer = 0x02;
	aml_evalname(sc->sc_acpi, sc->sc_devnode, "SN03", 1, &arg, NULL);
	arg.v_integer = 0x101;
	aml_evalname(sc->sc_acpi, sc->sc_devnode, "SN07", 1, &arg, NULL);
}

int
acpisony_find_offset(struct acpisony_softc *sc, int key)
{
	struct aml_value arg, res;
	int val;

	bzero(&arg, sizeof(arg));
	arg.type = AML_OBJTYPE_INTEGER;

	for (arg.v_integer = 0x20; arg.v_integer < 0x30; arg.v_integer++) {
		aml_evalname(sc->sc_acpi, sc->sc_devnode, "SN00", 1, &arg, &res);
		val = aml_val2int(&res);
		aml_freevalue(&res);
		if (val == key) {
			DPRINTF(("Matched key %X\n", val));
			return arg.v_integer - 0x20;
		}
	}

	return -1;
}

int
acpisony_set_hotkey(struct acpisony_softc *sc, int key, int val)
{
	int off, rc = -1;
	struct aml_value res, arg;

	bzero(&arg, sizeof(arg));
	arg.type = AML_OBJTYPE_INTEGER;

	off = acpisony_find_offset(sc, key);
	DPRINTF(("off = %X\n", off));
	if (off < 0)
		return rc;

	arg.v_integer = off | val;
	aml_evalname(sc->sc_acpi, sc->sc_devnode, "SN07", 1, &arg, &res);
	rc = aml_val2int(&res);
	aml_freevalue(&res);

	return rc;
}

void
acpisony_brightness_down(struct acpisony_softc *sc)
{
	int val;

	val = acpisony_get_brightness(sc);
	DPRINTF(("current value = %X", val));
	if (val > 0)
		val--;
	else
		val = 0;
	DPRINTF(("next value = %X", val));
	acpisony_set_brightness(sc, val);
}

int
acpisony_get_brightness(struct acpisony_softc *sc)
{
	struct aml_value res;
	int val;

	aml_evalname(sc->sc_acpi, sc->sc_devnode, "GBRT", 0, NULL, &res);
	val = aml_val2int(&res);
	aml_freevalue(&res);

	return val;
}

void
acpisony_set_brightness(struct acpisony_softc *sc, int level)
{
	struct aml_value arg;

	bzero(&arg, sizeof(arg));
	arg.type = AML_OBJTYPE_INTEGER;
	arg.v_integer = level;
	aml_evalname(sc->sc_acpi, sc->sc_devnode, "SBRT", 1, &arg, NULL);
	aml_freevalue(&arg);
}
