/*-
 * Copyright (C) 2012 Margarida Gouveia
 * 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.
 */
#include <sys/cdefs.h>
__FBSDID("$FreeBSD: head/sys/powerpc/wii/wii_pic.c 249826 2013-04-24 01:36:35Z rpaulo $");

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/module.h>
#include <sys/bus.h>
#include <sys/conf.h>
#include <sys/kernel.h>
#include <sys/malloc.h>
#include <sys/rman.h>
#include <sys/reboot.h>

#include <machine/bus.h>
#include <machine/platform.h>
#include <machine/intr_machdep.h>
#include <machine/resource.h>

#include <powerpc/wii/wii_picreg.h>

#include "pic_if.h"

static int	wiipic_probe(device_t);
static int	wiipic_attach(device_t);
static void	wiipic_dispatch(device_t, struct trapframe *);
static void	wiipic_enable(device_t, unsigned int, unsigned int);
static void	wiipic_eoi(device_t, unsigned int);
static void	wiipic_mask(device_t, unsigned int);
static void	wiipic_unmask(device_t, unsigned int);
static void	wiipic_intr(void *);

struct wiipic_softc {
	device_t		 sc_dev;
	struct resource		*sc_rres;
	bus_space_tag_t		 sc_bt;
	bus_space_handle_t	 sc_bh;
	int			 sc_rrid;
	int			 sc_irqid;
	struct resource		*sc_irq;
	void			*sc_irqctx;
	int			 sc_vector[WIIPIC_NIRQ];
};

static device_method_t wiipic_methods[] = {
	/* Device interface */
	DEVMETHOD(device_probe,		wiipic_probe),
	DEVMETHOD(device_attach,	wiipic_attach),

	/* PIC interface */
	DEVMETHOD(pic_dispatch,		wiipic_dispatch),
	DEVMETHOD(pic_enable,		wiipic_enable),
	DEVMETHOD(pic_eoi,		wiipic_eoi),
	DEVMETHOD(pic_mask,		wiipic_mask),
	DEVMETHOD(pic_unmask,		wiipic_unmask),

        DEVMETHOD_END
};

static driver_t wiipic_driver = {
	"wiipic",
	wiipic_methods,
	sizeof(struct wiipic_softc)
};

static devclass_t wiipic_devclass;

DRIVER_MODULE(wiipic, wiibus, wiipic_driver, wiipic_devclass, 0, 0);

static __inline uint32_t
wiipic_imr_read(struct wiipic_softc *sc)
{

	return (bus_space_read_4(sc->sc_bt, sc->sc_bh, WIIPIC_IMR));
}

static __inline void
wiipic_imr_write(struct wiipic_softc *sc, uint32_t imr)
{

	bus_space_write_4(sc->sc_bt, sc->sc_bh, WIIPIC_IMR, imr);
}

static __inline uint32_t
wiipic_icr_read(struct wiipic_softc *sc)
{

	return (bus_space_read_4(sc->sc_bt, sc->sc_bh, WIIPIC_ICR));
}

static __inline void
wiipic_icr_write(struct wiipic_softc *sc, uint32_t icr)
{

	bus_space_write_4(sc->sc_bt, sc->sc_bh, WIIPIC_ICR, icr);
}

static int
wiipic_probe(device_t dev)
{
        device_set_desc(dev, "Nintendo Wii PIC");

        return (BUS_PROBE_NOWILDCARD);
}

static int
wiipic_attach(device_t dev)
{
	struct wiipic_softc *sc;

	sc = device_get_softc(dev);
	sc->sc_dev = dev;

	sc->sc_rrid = 0;
	sc->sc_rres = bus_alloc_resource_any(dev, SYS_RES_MEMORY,
	    &sc->sc_rrid, RF_ACTIVE);
	if (sc->sc_rres == NULL) {
		device_printf(dev, "could not alloc mem resource\n");
		return (ENXIO);
	}
	sc->sc_bt = rman_get_bustag(sc->sc_rres);
	sc->sc_bh = rman_get_bushandle(sc->sc_rres);

	/* Turn off all interrupts */
	wiipic_imr_write(sc, 0x00000000);
	wiipic_icr_write(sc, 0xffffffff);

	powerpc_register_pic(dev, 0, WIIPIC_NIRQ, 0, FALSE);

	/*
	 * Setup the interrupt handler.
	 */
	sc->sc_irqid = 0;
	sc->sc_irq = bus_alloc_resource_any(dev, SYS_RES_IRQ, &sc->sc_irqid,
	    RF_ACTIVE);
	if (sc->sc_irq == NULL) {
		device_printf(dev, "could not alloc IRQ resource\n");
		return (ENXIO);
	}
	bus_setup_intr(dev, sc->sc_irq, INTR_TYPE_MISC | INTR_MPSAFE,
	    NULL, wiipic_intr, sc, &sc->sc_irqctx);

	return (0);
}

static void
wiipic_dispatch(device_t dev, struct trapframe *tf)
{
	struct wiipic_softc *sc;
	uint32_t irq;

	sc = device_get_softc(dev);
	irq = wiipic_icr_read(sc) & wiipic_imr_read(sc);
	if (irq == 0)
		return;
	irq = ffs(irq) - 1;
	KASSERT(irq < WIIPIC_NIRQ, ("bogus irq %d", irq));
	powerpc_dispatch_intr(sc->sc_vector[irq], tf);
}

static void
wiipic_enable(device_t dev, unsigned int irq, unsigned int vector)
{
	struct wiipic_softc *sc;

	KASSERT(irq < WIIPIC_NIRQ, ("bogus irq %d", irq));
	sc = device_get_softc(dev);
	sc->sc_vector[irq] = vector;
	wiipic_unmask(dev, irq);
}

static void
wiipic_eoi(device_t dev, unsigned int irq)
{
	struct wiipic_softc *sc;
	uint32_t icr;

	sc = device_get_softc(dev);
	icr = wiipic_icr_read(sc);
	icr |= (1 << irq);
	wiipic_icr_write(sc, icr);
}

static void
wiipic_mask(device_t dev, unsigned int irq)
{
	struct wiipic_softc *sc;
	uint32_t imr;

	sc = device_get_softc(dev);
	imr = wiipic_imr_read(sc);
	imr &= ~(1 << irq);
	wiipic_imr_write(sc, imr);
}

static void
wiipic_unmask(device_t dev, unsigned int irq)
{
	struct wiipic_softc *sc;
	uint32_t imr;

	sc = device_get_softc(dev);
	imr = wiipic_imr_read(sc);
	imr |= (1 << irq);
	wiipic_imr_write(sc, imr);
}

/*
 * Reset button interrupt.
 */
static void
wiipic_intr(void *xsc)
{
	struct wiipic_softc *sc;

	sc = (struct wiipic_softc *)xsc;
	if (wiipic_icr_read(sc) & WIIPIC_RBS)
		shutdown_nice(RB_AUTOBOOT);
}

