/*	$OpenBSD: z8530ms.c,v 1.1 2009/05/20 18:22:33 miod Exp $	*/
/*	$NetBSD: ms.c,v 1.12 1997/07/17 01:17:47 jtk Exp $	*/

/*
 * Copyright (c) 2002, 2009, Miodrag Vallat
 * 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 ``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.
 *
 *
 * Copyright (c) 1992, 1993
 *	The Regents of the University of California.  All rights reserved.
 *
 * This software was developed by the Computer Systems Engineering group
 * at Lawrence Berkeley Laboratory under DARPA contract BG 91-66 and
 * contributed to Berkeley.
 *
 * All advertising materials mentioning features or use of this software
 * must display the following acknowledgement:
 *	This product includes software developed by the University of
 *	California, Lawrence Berkeley Laboratory.
 *
 * 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.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *	This product includes software developed by the University of
 *	California, Berkeley and its contributors.
 * 4. Neither the name of the University nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 REGENTS 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.
 *
 *	@(#)ms.c	8.1 (Berkeley) 6/11/93
 */

/*
 * Zilog Z8530 Dual UART driver (mouse interface)
 *
 * This is the "slave" driver that will be attached to
 * the "zsc" driver for a Sun mouse.
 */

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

#ifndef __sparc64__	/* until zs driver is unified... */
#include <sparc/dev/z8530reg.h>
#else
#include <sparc64/dev/z8530reg.h>
#endif
#include <machine/z8530var.h>

#include <dev/wscons/wsmousevar.h>
#include <dev/sun/sunmsvar.h>

/*
 * How many input characters we can buffer.
 * Note: must be a power of two!
 */
#define	MS_RX_RING_SIZE	256
#define MS_RX_RING_MASK (MS_RX_RING_SIZE-1)

struct zsms_softc {
	struct	sunms_softc sc_base;
	struct	zs_chanstate *sc_cs;

	/* Flags to communicate with zsms_softint() */
	volatile int sc_intr_flags;
#define	INTR_RX_OVERRUN 	0x01
#define INTR_TX_EMPTY   	0x02
#define INTR_ST_CHECK   	0x04
#define	INTR_BPS_CHANGE		0x08

	/*
	 * The receive ring buffer.
	 */
	uint	sc_rbget;		/* ring buffer `get' index */
	volatile uint	sc_rbput;	/* ring buffer `put' index */
	uint16_t sc_rbuf[MS_RX_RING_SIZE]; /* rr1, data pairs */
};

/*
 * autoconf glue.
 */

int	zsms_match(struct device *, void *, void *);
void	zsms_attach(struct device *, struct device *, void *);

const struct cfattach zsms_ca = {
	sizeof(struct zsms_softc), zsms_match, zsms_attach
};

struct cfdriver zsms_cd = {
	NULL, "zsms", DV_DULL
};

/*
 * wsmouse accessops.
 */

void	zsms_disable(void *);
int	zsms_enable(void *);

const struct wsmouse_accessops zsms_accessops = {
	zsms_enable,
	sunms_ioctl,
	zsms_disable
};

/*
 * zs glue.
 */

void	zsms_rxint(struct zs_chanstate *);
void	zsms_softint(struct zs_chanstate *);
void	zsms_stint(struct zs_chanstate *, int);
void	zsms_txint(struct zs_chanstate *);

struct zsops zsops_ms = {
	zsms_rxint,	/* receive char available */
	zsms_stint,	/* external/status */
	zsms_txint,	/* xmit buffer empty */
	zsms_softint	/* process software interrupt */
};

void	zsms_speed_change(void *, uint);

/*
 * autoconf glue.
 */

int
zsms_match(struct device *parent, void *vcf, void *aux)
{
	struct cfdata *cf = vcf;
	struct zsc_attach_args *args = aux;
	int rc;

	/* If we're not looking for a mouse, just exit */
	if (strcmp(args->type, "mouse") != 0)
		return 0;

	rc = 10;

	/* Exact match is better than wildcard. */
	if (cf->cf_loc[ZSCCF_CHANNEL] == args->channel)
		rc += 2;

	/* This driver accepts wildcard. */
	if (cf->cf_loc[ZSCCF_CHANNEL] == ZSCCF_CHANNEL_DEFAULT)
		rc += 1;

	return rc;
}

void
zsms_attach(struct device *parent, struct device *self, void *aux)
{
	struct zsc_softc *zsc = (struct zsc_softc *)parent;
	struct zsms_softc *sc = (struct zsms_softc *)self;
	struct zsc_attach_args *args = aux;
	struct zs_chanstate *cs;
	int channel;
	int s;

	channel = args->channel;
#ifndef __sparc64__	/* until driver is unified... */
	cs = &zsc->zsc_cs[channel];
#else
	cs = zsc->zsc_cs[channel];
#endif
	cs->cs_private = sc;
	cs->cs_ops = &zsops_ms;
	sc->sc_cs = cs;

	/* Initialize hardware. */
	s = splzs();
	zs_write_reg(cs, 9, channel == 0 ?  ZSWR9_A_RESET : ZSWR9_B_RESET);
	/* disable interrupts until the mouse is enabled */
	CLR(cs->cs_preg[1], ZSWR1_RIE | ZSWR1_SIE | ZSWR1_TIE);
	/* 8 data bits is already our default */
	/* no parity, 2 stop bits */
	CLR(cs->cs_preg[4], ZSWR4_SBMASK | ZSWR4_PARMASK);
	SET(cs->cs_preg[4], ZSWR4_TWOSB);
	(void)zs_set_speed(cs, INIT_SPEED);
	zs_loadchannelregs(cs);
	splx(s);

	sc->sc_base.sc_speed_change = zsms_speed_change;

	sunms_attach(&sc->sc_base, &zsms_accessops);
}

/*
 * wsmouse accessops.
 */

void
zsms_disable(void *v)
{
	struct zsms_softc *sc = v;
	struct zs_chanstate *cs = sc->sc_cs;
	int s;

	s = splzs();
	/* disable RX and status change interrupts */
	CLR(cs->cs_preg[1], ZSWR1_RIE | ZSWR1_SIE);
	zs_loadchannelregs(cs);
	splx(s);
}

int
zsms_enable(void *v)
{
	struct zsms_softc *sc = v;
	struct zs_chanstate *cs = sc->sc_cs;
	int s;

	s = splzs();
	/* enable RX and status change interrupts */
	SET(cs->cs_preg[1], ZSWR1_RIE | ZSWR1_SIE);
	zs_loadchannelregs(cs);
	splx(s);

	return 0;
}

/*
 * zs glue.
 */

void
zsms_rxint(struct zs_chanstate *cs)
{
	struct zsms_softc *sc = cs->cs_private;
	int put, put_next;
	u_char rr0, rr1, c;

	put = sc->sc_rbput;

	for (;;) {
		/*
		 * First read the status, because reading the received char
		 * destroys the status of this char.
		 */
		rr1 = zs_read_reg(cs, 1);
		c = zs_read_data(cs);

		/*
		 * Note that we do not try to change speed upon encountering
		 * framing errors, as this is not as reliable as breaks.
		 */
		if (ISSET(rr1, ZSRR1_FE | ZSRR1_DO | ZSRR1_PE)) {
			/* Clear the receive error. */
			zs_write_csr(cs, ZSWR0_RESET_ERRORS);
		}

		if (sc->sc_base.sc_state != STATE_RATE_CHANGE) {
			sc->sc_rbuf[put] = (c << 8) | rr1;
			put_next = (put + 1) & MS_RX_RING_MASK;

			/* Would overrun if increment makes (put==get). */
			if (put_next == sc->sc_rbget) {
				sc->sc_intr_flags |= INTR_RX_OVERRUN;
				break;
			} else {
				/* OK, really increment. */
				put = put_next;
			}
		}

		rr0 = zs_read_csr(cs);
		if (!ISSET(rr0, ZSRR0_RX_READY))
			break;
	}

	/* Done reading. */
	sc->sc_rbput = put;

	cs->cs_softreq = 1;
}

void
zsms_txint(struct zs_chanstate *cs)
{
	/*
	 * This function should never be invoked as we don't accept TX
	 * interrupts.  If someone alters our configuration behind our
	 * back, just disable TX interrupts again.
	 */
	zs_write_csr(cs, ZSWR0_RESET_TXINT);

	/* disable tx interrupts */
	CLR(cs->cs_preg[1], ZSWR1_TIE);
	zs_loadchannelregs(cs);
}

void
zsms_stint(struct zs_chanstate *cs, int force)
{
	struct zsms_softc *sc = cs->cs_private;
	uint8_t rr0, delta;

	rr0 = zs_read_csr(cs);
	zs_write_csr(cs, ZSWR0_RESET_STATUS);

	/*
	 * A break can occur if the speed is not correct.
	 * However, we do not change speed until we get the second
	 * break, for switching speed when the mouse is unplugged
	 * will trigger a break and thus we'd loop changing speeds
	 * until the mouse is plugged again.
	 */
	if (!force && ISSET(rr0, ZSRR0_BREAK)) {
		if (sc->sc_base.sc_state != STATE_RATE_CHANGE &&
		    ++sc->sc_base.sc_brk > 1) {
			sc->sc_intr_flags |= INTR_BPS_CHANGE;
			sc->sc_base.sc_state = STATE_RATE_CHANGE;
			cs->cs_softreq = 1;
#ifdef DEBUG
			printf("%s: break detected, changing speed\n",
			    sc->sc_base.sc_dev.dv_xname);
#endif
		}
	}

	if (!force)
		delta = rr0 ^ cs->cs_rr0;
	else
		delta = cs->cs_rr0_mask;
	cs->cs_rr0 = rr0;

	if (ISSET(delta, cs->cs_rr0_mask)) {
		SET(cs->cs_rr0_delta, delta);
		
		sc->sc_intr_flags |= INTR_ST_CHECK;
		cs->cs_softreq = 1;
	}
}

void
zsms_softint(struct zs_chanstate *cs)
{
	struct zsms_softc *sc;
	int get, c, s, s2;
	int intr_flags;
	u_short ring_data;

	sc = cs->cs_private;

	/* Atomically get and clear flags. */
	s = spltty();
	s2 = splzs();
	intr_flags = sc->sc_intr_flags;
	sc->sc_intr_flags = 0;
	/* Now lower to spltty for the rest. */
	splx(s2);

	/*
	 * If we have a baud rate change pending, do it now.
	 * This will reset the rx ring, so we can proceed safely.
	 */
	if (ISSET(intr_flags, INTR_BPS_CHANGE)) {
		CLR(intr_flags, INTR_RX_OVERRUN);
		sunms_speed_change(&sc->sc_base);
		splx(s);
	}

	/*
	 * Copy data from the receive ring, if any, to the event layer.
	 */
	get = sc->sc_rbget;
	while (get != sc->sc_rbput) {
		ring_data = sc->sc_rbuf[get];
		get = (get + 1) & MS_RX_RING_MASK;

		/* low byte of ring_data is rr1 */
		c = (ring_data >> 8) & 0xff;

		if (ring_data & ZSRR1_DO)
			SET(intr_flags, INTR_RX_OVERRUN);

		/* Pass this up to the "middle" layer. */
		sunms_input(&sc->sc_base, c);
	}
	if (ISSET(intr_flags, INTR_RX_OVERRUN))
		log(LOG_ERR, "%s: input overrun\n",
		    sc->sc_base.sc_dev.dv_xname);
	sc->sc_rbget = get;

	/*
	 * Status line change.  Not expected except for break conditions.
	 */
	if (ISSET(intr_flags, INTR_ST_CHECK)) {
		cs->cs_rr0_delta = 0;
	}

	splx(s);
}

/*
 * Reinitialize the line to a different speed.  Invoked at spltty().
 */
void
zsms_speed_change(void *v, uint bps)
{
	struct zsms_softc *sc = v;
	struct zs_chanstate *cs = sc->sc_cs;
	uint8_t rr0;
	int s;

	s = splzs();

	/*
	 * Eat everything on the line.
	 */
	for (;;) {
		rr0 = zs_read_csr(cs);
		if (!ISSET(rr0, ZSRR0_RX_READY))
			break;
		(void)zs_read_data(cs);
	}

	(void)zs_set_speed(cs, sc->sc_base.sc_bps);
	zs_loadchannelregs(cs);
	zsms_stint(cs, 1);

	sc->sc_rbget = sc->sc_rbput = 0;

	splx(s);
}
