root/sys/arch/evbppc/virtex/dev/pstwo.c
/*      $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 */
}