/* $OpenBSD: crosec_kbd.c,v 1.3 2015/07/19 01:13:27 bmercer Exp $ */
/*
 * Copyright (c) 2013 Patrick Wildt <patrick@blueri.se>
 *
 * Permission to use, copy, modify, and 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 <sys/device.h>
#include <sys/sensors.h>
#include <sys/malloc.h>

#include <armv7/exynos/crosecvar.h>

#include <dev/wscons/wsconsio.h>
#include <dev/wscons/wskbdvar.h>
#include <dev/wscons/wsksymdef.h>
#include <dev/wscons/wsksymvar.h>

void cros_ec_add_task(void *);
void cros_ec_poll_keystate(void *);
int cros_ec_get_keystate(struct cros_ec_softc *);

int cros_ec_keyboard_enable(void *, int);
void cros_ec_keyboard_set_leds(void *, int);
int cros_ec_keyboard_ioctl(void *, u_long, caddr_t, int, struct proc *);

struct wskbd_accessops cros_ec_keyboard_accessops = {
	cros_ec_keyboard_enable,
	cros_ec_keyboard_set_leds,
	cros_ec_keyboard_ioctl,
};

void cros_ec_keyboard_cngetc(void *, u_int *, int *);
void cros_ec_keyboard_cnpollc(void *, int);

struct wskbd_consops cros_ec_keyboard_consops = {
	cros_ec_keyboard_cngetc,
	cros_ec_keyboard_cnpollc,
};


/* XXX: assumes 8 rows, 13 cols, FDT */
#define KC(n) KS_KEYCODE(n)
static const keysym_t cros_ec_keyboard_keydesc_us[] = {
	KC(1),	KS_Caps_Lock,
	KC(3),	KS_b,
	KC(6),	KS_n,
	KC(10),	KS_Alt_R,
	KC(14),	KS_Escape,
	KC(16),	KS_g,
	KC(19),	KS_h,
	KC(24),	KS_BackSpace,
	KC(26),	KS_Control_L,
	KC(27),	KS_Tab,
	KC(29),	KS_t,
	KC(32),	KS_y,
	KC(42),	KS_5,
	KC(45),	KS_6,
	KC(52),	KS_Control_R,
	KC(53),	KS_a,
	KC(54),	KS_d,
	KC(55),	KS_f,
	KC(56),	KS_s,
	KC(57),	KS_k,
	KC(58),	KS_j,
	KC(61),	KS_l,
	KC(63),	KS_Return,
	KC(66),	KS_z,
	KC(67),	KS_c,
	KC(68),	KS_v,
	KC(69),	KS_x,
	KC(70),	KS_comma,
	KC(71),	KS_m,
	KC(72),	KS_Shift_L,
	KC(74),	KS_period,
	KC(76),	KS_space,
	KC(79),	KS_1,
	KC(80),	KS_3,
	KC(81),	KS_4,
	KC(82),	KS_2,
	KC(83),	KS_8,
	KC(84),	KS_7,
	KC(86),	KS_0,
	KC(87),	KS_9,
	KC(88),	KS_Alt_L,
	KC(92),	KS_q,
	KC(93),	KS_e,
	KC(94),	KS_r,
	KC(95),	KS_w,
	KC(96),	KS_i,
	KC(97),	KS_u,
	KC(98),	KS_Shift_R,
	KC(99),	KS_p,
	KC(100),	KS_o,
};

#define KBD_MAP(name, base, map) \
	{ name, base, sizeof(map)/sizeof(keysym_t), map }
static const struct wscons_keydesc cros_ec_keyboard_keydesctab[] = {
	KBD_MAP(KB_US,			0,	cros_ec_keyboard_keydesc_us),
	{0, 0, 0, 0}
};
struct wskbd_mapdata cros_ec_keyboard_keymapdata = {
	cros_ec_keyboard_keydesctab,
	KB_US,
};

int
cros_ec_init_keyboard(struct cros_ec_softc *sc)
{
	struct ec_response_cros_ec_info info;
	struct wskbddev_attach_args a;

	if (cros_ec_info(sc, &info)) {
		printf("%s: could not read KBC info\n", __func__);
		return (-1);
	}

	sc->keyboard.rows = info.rows;
	sc->keyboard.cols = info.cols;
	sc->keyboard.switches = info.switches;
	sc->keyboard.state = (uint8_t *)malloc(info.rows*info.cols,
			M_DEVBUF, M_WAITOK|M_ZERO);
	if (sc->keyboard.state == NULL)
		panic("%s: no memory available for keyboard states", __func__);

	/* FIXME: interrupt driven, please. */
	sc->keyboard.taskq = taskq_create("crosec-keyb", 1, IPL_TTY, 0);
	task_set(&sc->keyboard.task, cros_ec_poll_keystate, sc);
	timeout_set(&sc->keyboard.timeout, cros_ec_add_task, sc);

	/* XXX: ghosting */

	wskbd_cnattach(&cros_ec_keyboard_consops, sc, &cros_ec_keyboard_keymapdata);
	a.console = 1;

	a.keymap = &cros_ec_keyboard_keymapdata;
	a.accessops = &cros_ec_keyboard_accessops;
	a.accesscookie = sc;

	sc->keyboard.wskbddev = config_found((void *)sc, &a, wskbddevprint);

	timeout_add_sec(&sc->keyboard.timeout, 10);

	return 0;
}

void
cros_ec_add_task(void *arg)
{
	struct cros_ec_softc *sc = (struct cros_ec_softc *)arg;
	task_add(sc->keyboard.taskq, &sc->keyboard.task);
	timeout_add_msec(&sc->keyboard.timeout, 100);
}

void
cros_ec_poll_keystate(void *arg)
{
	struct cros_ec_softc *sc = (struct cros_ec_softc *)arg;
	cros_ec_get_keystate(sc);
}

int
cros_ec_get_keystate(struct cros_ec_softc *sc)
{
	int col, row, s;
	uint8_t state[sc->keyboard.cols];
	cros_ec_scan_keyboard(sc, state, sc->keyboard.cols);
	for (col = 0; col < sc->keyboard.cols; col++) {
		for (row = 0; row < sc->keyboard.rows; row++) {
			int off = row*sc->keyboard.cols;
			int pressed = !!(state[col] & (1 << row));
			if (pressed && !sc->keyboard.state[off+col]) {
				//printf("row %d col %d id %d pressed\n", row, col, off+col);
				sc->keyboard.state[off+col] = 1;
				if (sc->keyboard.polling)
					return off+col;
				s = spltty();
				wskbd_input(sc->keyboard.wskbddev, WSCONS_EVENT_KEY_DOWN, off+col);
				splx(s);
			} else if (!pressed && sc->keyboard.state[off+col]) {
				//printf("row %d col %d id %d released\n", row, col, off+col);
				sc->keyboard.state[off+col] = 0;
				if (sc->keyboard.polling)
					return off+col;
				s = spltty();
				wskbd_input(sc->keyboard.wskbddev, WSCONS_EVENT_KEY_UP, off+col);
				splx(s);
			} else if (sc->keyboard.state[off+col]) {
				//printf("row %d col %d id %d repeated\n", row, col, off+col);
			}
		}
	}
	return (-1);
}

void
cros_ec_keyboard_cngetc(void *v, u_int *type, int *data)
{
	struct cros_ec_softc *sc = v;
	int key;

	sc->keyboard.polling = 1;
	while ((key = cros_ec_get_keystate(sc)) == -1) {
		delay(10000);
	}
	sc->keyboard.polling = 0;

	*data = key;
	*type = sc->keyboard.state[key] ? WSCONS_EVENT_KEY_UP : WSCONS_EVENT_KEY_DOWN;
}

void
cros_ec_keyboard_cnpollc(void *v, int on)
{
}

int
cros_ec_keyboard_enable(void *v, int on)
{
	return 0;
}

void
cros_ec_keyboard_set_leds(void *v, int on)
{
}

int
cros_ec_keyboard_ioctl(void *v, u_long cmd, caddr_t data, int flag, struct proc *p)
{
#ifdef WSDISPLAY_COMPAT_RAWKBD
	struct cros_ec_softc *sc = v;
#endif

	switch (cmd) {

	case WSKBDIO_GTYPE:
		*(int *)data = WSKBD_TYPE_ZAURUS;
		return 0;
	case WSKBDIO_SETLEDS:
		return 0;
	case WSKBDIO_GETLEDS:
		*(int *)data = 0;
		return 0;
#ifdef WSDISPLAY_COMPAT_RAWKBD
	case WSKBDIO_SETMODE:
		sc->keyboard.rawkbd = *(int *)data == WSKBD_RAW;
		return (0);
#endif

	}
	/* kbdioctl(...); */

	return -1;
}
