/* 	$NetBSD: pstwo.c,v 1.5 2012/10/27 17:17:51 chs Exp $ */

/*
 * Copyright (c) 2006 Jachym Holecek
 * All rights reserved.
 *
 * Written for DFC Design, s.r.o.
 *
 * 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 ``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 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>
__KERNEL_RCSID(0, "$NetBSD: pstwo.c,v 1.5 2012/10/27 17:17:51 chs Exp $");

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/conf.h>
#include <sys/device.h>
#include <sys/file.h>
#include <sys/ioctl.h>
#include <sys/kernel.h>
#include <sys/proc.h>
#include <sys/time.h>
#include <sys/syslog.h>

#include <machine/intr.h>
#include <sys/bus.h>

#include <dev/pckbport/pckbportvar.h>

#include <evbppc/virtex/virtex.h>
#include <evbppc/virtex/dev/xcvbusvar.h>
#include <evbppc/virtex/dev/pstworeg.h>


#define PSTWO_RXBUF_SIZE 	32
#define NEXT(idx) 		(void)((idx) = ((idx) + 1) % PSTWO_RXBUF_SIZE)

struct pstwo_softc {
	device_t 		sc_dev;
	void 			*sc_ih;

	bus_space_tag_t 	sc_iot;
	bus_space_handle_t 	sc_ioh;

	pckbport_tag_t 		sc_pt;
	pckbport_slot_t 	sc_slot;
};

static int 	pstwo_intr(void *);
static void 	pstwo_reset(bus_space_tag_t, bus_space_handle_t, bool);
#if 0
static void 	pstwo_printreg(struct pstwo_softc *);
#endif

/* Interface to pckbport. */
static int 	pstwo_xt_translation(void *, pckbport_slot_t, int);
static int 	pstwo_send_devcmd(void *, pckbport_slot_t, u_char);
static int 	pstwo_poll_data1(void *, pckbport_slot_t);
static void 	pstwo_slot_enable(void *, pckbport_slot_t, int);
static void 	pstwo_intr_establish(void *, pckbport_slot_t);
static void 	pstwo_set_poll(void *, pckbport_slot_t, int);

/* Generic device. */
static void 	pstwo_attach(device_t, device_t, void *);

CFATTACH_DECL_NEW(pstwo, sizeof(struct pstwo_softc),
    xcvbus_child_match, pstwo_attach, NULL, NULL);

static struct pckbport_accessops pstwo_ops = {
	.t_xt_translation 	= pstwo_xt_translation,
	.t_send_devcmd 		= pstwo_send_devcmd,
	.t_poll_data1 		= pstwo_poll_data1,
	.t_slot_enable 		= pstwo_slot_enable,
	.t_intr_establish 	= pstwo_intr_establish,
	.t_set_poll 		= pstwo_set_poll,
};

static void
pstwo_attach(device_t parent, device_t self, void *aux)
{
	struct xcvbus_attach_args 	*vaa = aux;
	struct pstwo_softc 		*sc = device_private(self);
	int 				i;

	aprint_normal(": PS2 port\n");

	if ((sc->sc_ih = intr_establish(vaa->vaa_intr, IST_LEVEL, IPL_TTY,
	    pstwo_intr, sc)) == NULL) {
		aprint_error_dev(self, "could not establish interrupt\n");
		return;
	}

	sc->sc_dev = self;
	sc->sc_iot = vaa->vaa_iot;

	if (bus_space_map(vaa->vaa_iot, vaa->vaa_addr, PSTWO_SIZE, 0,
	    &sc->sc_ioh) != 0) {
		aprint_error_dev(self, "could not map registers\n");
		return ;
	}

	pstwo_reset(sc->sc_iot, sc->sc_ioh, 1);

	sc->sc_pt = pckbport_attach(sc, &pstwo_ops);

	/* Emulate the concept of "slot" to make pms(4)/pckbd(4) happy. */
	for (i = 0; i < PCKBPORT_NSLOTS; i++)
		if (pckbport_attach_slot(self, sc->sc_pt, i) != NULL) {
			sc->sc_slot = i;
			break; 			/* only one device allowed */
		}
}

#if 0
static void
pstwo_printreg(struct pstwo_softc *sc)
{
#define PRINTREG(name, reg) \
	printf("%s: [0x%08x] %s -> 0x%08x\n", device_xname(sc->sc_dev), \
	    reg, name, bus_space_read_4(sc->sc_iot, sc->sc_ioh, reg))

	PRINTREG("status   ", PSTWO_STAT);
	PRINTREG("recv byte", PSTWO_RX_DATA);
	PRINTREG("intr stat", PSTWO_INTR_STAT);
	PRINTREG("intr mask", PSTWO_INTR_MSET);

#undef PRINTREG
}
#endif

static int
pstwo_intr(void *arg)
{
	struct pstwo_softc 	*sc = arg;
	uint32_t 		stat;

	stat = bus_space_read_4(sc->sc_iot, sc->sc_ioh, PSTWO_INTR_STAT);
	bus_space_write_4(sc->sc_iot, sc->sc_ioh, PSTWO_INTR_ACK, stat);

	if (stat & INTR_RX_FULL) {
		uint32_t 	val;

		val = bus_space_read_4(sc->sc_iot, sc->sc_ioh, PSTWO_RX_DATA);
		pckbportintr(sc->sc_pt, sc->sc_slot, DATA_RECV(val));
	}
	return (0);
}

static void
pstwo_reset(bus_space_tag_t iot, bus_space_handle_t ioh, bool restart)
{
	bus_space_write_4(iot, ioh, PSTWO_CTRL, CTRL_RESET);

	delay(10);

	if (restart) {
		bus_space_write_4(iot, ioh, PSTWO_CTRL, ~CTRL_RESET);
		bus_space_write_4(iot, ioh, PSTWO_INTR_MSET, INTR_ANY);
	}
}

/*
 * pstwo_wait:
 *
 * 	Wait while status bits indicated by ${mask} have the value ${set}.
 * 	Return zero on success, one on timeout.
 */
static int
pstwo_wait(struct pstwo_softc *sc, uint32_t mask, bool set)
{
	uint32_t 		val = (set ? mask : 0);
	int 			i = 1000;

	while (i--) {
		if ((bus_space_read_4(sc->sc_iot, sc->sc_ioh, PSTWO_STAT) &
		    mask) == val) {
			delay(10);
			continue;
		}

		return (0);
	}

	return (1);
}

/*
 * pckbport
 */

static int
pstwo_xt_translation(void *arg, pckbport_slot_t port, int enable)
{
	/* Can't do translation, so disable succeeds & enable fails. */
	return (!enable);
}

static int
pstwo_send_devcmd(void *arg, pckbport_slot_t slot, u_char byte)
{
	struct pstwo_softc 	*sc = arg;

	if (pstwo_wait(sc, STAT_TX_BUSY, 1))
		return (0);

	bus_space_write_4(sc->sc_iot, sc->sc_ioh, PSTWO_TX_DATA,
	    DATA_SEND(byte));
	return (1);
}

static int
pstwo_poll_data1(void *arg, pckbport_slot_t slot)
{
	struct pstwo_softc 	*sc = arg;
	uint32_t 		val;

	if (pstwo_wait(sc, STAT_RX_DONE, 0))
		return (-1);

	val = bus_space_read_4(sc->sc_iot, sc->sc_ioh, PSTWO_RX_DATA);
	return DATA_RECV(val);
}

static void
pstwo_slot_enable(void *arg, pckbport_slot_t slot, int enable)
{
	/* Nothing to do */
}

static void
pstwo_intr_establish(void *arg, pckbport_slot_t slot)
{
	/* XXX no-op because we don't support polled mode */
}

static void
pstwo_set_poll(void *arg, pckbport_slot_t slot, int enable)
{
	/* XXX only needed by console */
}