/*	$OpenBSD: voyager.c,v 1.4 2010/09/20 06:33:48 matthew Exp $	*/

/*
 * Copyright (c) 2010 Miodrag Vallat.
 *
 * 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.
 */

/*
 * Silicon Motion SM501/SM502 (VoyagerGX) master driver.
 */

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/device.h>
#include <sys/gpio.h>

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

#include <dev/gpio/gpiovar.h>

#include <dev/pci/pcireg.h>
#include <dev/pci/pcivar.h>
#include <dev/pci/pcidevs.h>

#include <loongson/dev/bonito_irq.h>	/* for BONITO_NINTS */
#include <loongson/dev/voyagerreg.h>
#include <loongson/dev/voyagervar.h>

struct voyager_softc {
	struct device		 sc_dev;

	bus_space_tag_t		 sc_fbt;
	bus_space_handle_t	 sc_fbh;
	bus_size_t		 sc_fbsize;

	bus_space_tag_t		 sc_mmiot;
	bus_space_handle_t	 sc_mmioh;
	bus_size_t		 sc_mmiosize;

	struct gpio_chipset_tag	 sc_gpio_tag;
	gpio_pin_t		 sc_gpio_pins[32 + 32];

	void			*sc_ih;
	struct intrhand		*sc_intr[32];
};

int	voyager_match(struct device *, void *, void *);
void	voyager_attach(struct device *, struct device *, void *);

const struct cfattach voyager_ca = {
	sizeof(struct voyager_softc), voyager_match, voyager_attach
};

struct cfdriver voyager_cd = {
	NULL, "voyager", DV_DULL
};

void	voyager_attach_gpio(struct voyager_softc *);
int	voyager_intr(void *);
int	voyager_print(void *, const char *);
int	voyager_search(struct device *, void *, void *);

const struct pci_matchid voyager_devices[] = {
	/*
	 * 502 shares the same device ID as 501, but uses a different
	 * revision number.
	 */
	{ PCI_VENDOR_SMI, PCI_PRODUCT_SMI_SM501 }
};

int
voyager_match(struct device *parent, void *vcf, void *aux)
{
	struct pci_attach_args *pa = (struct pci_attach_args *)aux;

	return pci_matchbyid(pa, voyager_devices, nitems(voyager_devices));
}

void
voyager_attach(struct device *parent, struct device *self, void *aux)
{
	struct voyager_softc *sc = (struct voyager_softc *)self;
	struct pci_attach_args *pa = (struct pci_attach_args *)aux;
	pci_intr_handle_t ih;
	const char *intrstr;

	printf(": ");

	/*
	 * Map registers.
	 */

	if (pci_mapreg_map(pa, PCI_MAPREG_START, PCI_MAPREG_TYPE_MEM,
	    BUS_SPACE_MAP_LINEAR, &sc->sc_fbt, &sc->sc_fbh,
	    NULL, &sc->sc_fbsize, 0) != 0) {
		printf("can't map frame buffer\n");
		return;
	}

	if (pci_mapreg_map(pa, PCI_MAPREG_START + 0x04, PCI_MAPREG_TYPE_MEM,
	    BUS_SPACE_MAP_LINEAR, &sc->sc_mmiot, &sc->sc_mmioh,
	    NULL, &sc->sc_mmiosize, 0) != 0) {
		printf("can't map mmio\n");
		goto fail1;
	}

	/*
	 * Setup interrut handling.
	 */

	bus_space_write_4(sc->sc_mmiot, sc->sc_mmioh, VOYAGER_RAW_ICR,
	    0xffffffff);
	bus_space_write_4(sc->sc_mmiot, sc->sc_mmioh, VOYAGER_IMR, 0);
	(void)bus_space_read_4(sc->sc_mmiot, sc->sc_mmioh, VOYAGER_IMR);

	if (pci_intr_map(pa, &ih) != 0) {
		printf("can't map interrupt\n");
		goto fail2;
	}
	intrstr = pci_intr_string(pa->pa_pc, ih);
	sc->sc_ih = pci_intr_establish(pa->pa_pc, ih, IPL_TTY, voyager_intr,
	    sc, self->dv_xname);
	if (sc->sc_ih == NULL) {
		printf("can't establish interrupt");
		if (intrstr != NULL)
			printf(" at %s", intrstr);
		printf("\n");
		goto fail2;
	}

	printf("%s\n", intrstr);

	/*
	 * Attach GPIO devices.
	 */
	voyager_attach_gpio(sc);

	/*
	 * Attach child devices.
	 */
	config_search(voyager_search, self, pa);

	return;
fail2:
	bus_space_unmap(sc->sc_mmiot, sc->sc_mmioh, sc->sc_mmiosize);
fail1:
	bus_space_unmap(sc->sc_fbt, sc->sc_fbh, sc->sc_fbsize);
}

int
voyager_print(void *args, const char *parentname)
{
	struct voyager_attach_args *vaa = (struct voyager_attach_args *)args;

	if (parentname != NULL)
		printf("%s at %s", vaa->vaa_name, parentname);

	return UNCONF;
}

int
voyager_search(struct device *parent, void *vcf, void *args)
{
	struct voyager_softc *sc = (struct voyager_softc *)parent;
	struct cfdata *cf = (struct cfdata *)vcf;
	struct pci_attach_args *pa = (struct pci_attach_args *)args;
	struct voyager_attach_args vaa;

	/*
	 * Caller should have attached gpio already. If it didn't, bail
	 * out here.
	 */
	if (strcmp(cf->cf_driver->cd_name, "gpio") == 0)
		return 0;

	vaa.vaa_name = cf->cf_driver->cd_name;
	vaa.vaa_pa = pa;
	vaa.vaa_fbt = sc->sc_fbt;
	vaa.vaa_fbh = sc->sc_fbh;
	vaa.vaa_mmiot = sc->sc_mmiot;
	vaa.vaa_mmioh = sc->sc_mmioh;

	if (cf->cf_attach->ca_match(parent, cf, &vaa) == 0)
		return 0;

	config_attach(parent, cf, &vaa, voyager_print);
	return 1;
}

/*
 * Interrupt disatcher
 */

int
voyager_intr(void *vsc)
{
	struct voyager_softc *sc = (struct voyager_softc *)vsc;
	uint32_t isr, imr, mask, bitno;
	struct intrhand *ih;

	isr = bus_space_read_4(sc->sc_mmiot, sc->sc_mmioh, VOYAGER_ISR);
	imr = bus_space_read_4(sc->sc_mmiot, sc->sc_mmioh, VOYAGER_IMR);

	isr &= imr;
	if (isr == 0)
		return 0;

	for (bitno = 0, mask = 1 << 0; isr != 0; bitno++, mask <<= 1) {
		if ((isr & mask) == 0)
			continue;
		isr ^= mask;
		for (ih = sc->sc_intr[bitno]; ih != NULL; ih = ih->ih_next) {
			if ((*ih->ih_fun)(ih->ih_arg) != 0)
				ih->ih_count.ec_count++;
		}
	}

	return 1;
}

void *
voyager_intr_establish(void *cookie, int irq, int level, int (*fun)(void *),
    void *arg, const char *name)
{
	struct voyager_softc *sc = (struct voyager_softc *)cookie;
	struct intrhand *prevh, *nh;
	uint32_t imr;

#ifdef DIAGNOSTIC
	if (irq < 0 || irq >= nitems(sc->sc_intr))
		return NULL;
#endif

	nh = (struct intrhand *)malloc(sizeof *nh, M_DEVBUF, M_NOWAIT | M_ZERO);
	if (nh == NULL)
		return NULL;

	nh->ih_fun = fun;
	nh->ih_arg = arg;
	nh->ih_level = level;
	nh->ih_irq = irq + BONITO_NINTS;
	evcount_attach(&nh->ih_count, name, &nh->ih_irq);

	if (sc->sc_intr[irq] == NULL)
		sc->sc_intr[irq] = nh;
	else {
		/* insert at tail */
		for (prevh = sc->sc_intr[irq]; prevh->ih_next != NULL;
		    prevh = prevh->ih_next) ;
		prevh->ih_next = nh;
	}

	/* enable interrupt source */
	imr = bus_space_read_4(sc->sc_mmiot, sc->sc_mmioh, VOYAGER_IMR);
	imr |= 1 << irq;
	bus_space_write_4(sc->sc_mmiot, sc->sc_mmioh, VOYAGER_IMR, imr);
	(void)bus_space_read_4(sc->sc_mmiot, sc->sc_mmioh, VOYAGER_IMR);

	return nh;
}

const char *
voyager_intr_string(void *vih)
{
	struct intrhand *ih = (struct intrhand *)vih;
	static char intrstr[1 + 32];

	snprintf(intrstr, sizeof intrstr, "voyager irq %d",
	    ih->ih_irq - BONITO_NINTS);
	return intrstr;
}

/*
 * GPIO support code
 */

int	voyager_gpio_pin_read(void *, int);
void	voyager_gpio_pin_write(void *, int, int);
void	voyager_gpio_pin_ctl(void *, int, int);

static const struct gpio_chipset_tag voyager_gpio_tag = {
	.gp_pin_read = voyager_gpio_pin_read,
	.gp_pin_write = voyager_gpio_pin_write,
	.gp_pin_ctl = voyager_gpio_pin_ctl
};

int
voyager_gpio_pin_read(void *cookie, int pin)
{
	struct voyager_softc *sc = (struct voyager_softc *)cookie;
	bus_addr_t reg;
	int32_t data, mask;

	if (pin >= 32) {
		pin -= 32;
		reg = VOYAGER_GPIO_DATA_HIGH;
	} else {
		reg = VOYAGER_GPIO_DATA_LOW;
	}
	mask = 1 << pin;

	data = bus_space_read_4(sc->sc_mmiot, sc->sc_mmioh, reg);
	return data & mask ? GPIO_PIN_HIGH : GPIO_PIN_LOW;
}

void
voyager_gpio_pin_write(void *cookie, int pin, int val)
{
	struct voyager_softc *sc = (struct voyager_softc *)cookie;
	bus_addr_t reg;
	int32_t data, mask;

	if (pin >= 32) {
		pin -= 32;
		reg = VOYAGER_GPIO_DATA_HIGH;
	} else {
		reg = VOYAGER_GPIO_DATA_LOW;
	}
	mask = 1 << pin;
	data = bus_space_read_4(sc->sc_mmiot, sc->sc_mmioh, reg);
	if (val)
		data |= mask;
	else
		data &= ~mask;
	bus_space_write_4(sc->sc_mmiot, sc->sc_mmioh, reg, data);
	(void)bus_space_read_4(sc->sc_mmiot, sc->sc_mmioh, reg);
}

void
voyager_gpio_pin_ctl(void *cookie, int pin, int flags)
{
	struct voyager_softc *sc = (struct voyager_softc *)cookie;
	bus_addr_t reg;
	int32_t data, mask;

	if (pin >= 32) {
		pin -= 32;
		reg = VOYAGER_GPIO_DIR_HIGH;
	} else {
		reg = VOYAGER_GPIO_DIR_LOW;
	}
	mask = 1 << pin;
	data = bus_space_read_4(sc->sc_mmiot, sc->sc_mmioh, reg);
	if (ISSET(flags, GPIO_PIN_OUTPUT))
		data |= mask;
	else
		data &= ~mask;
	bus_space_write_4(sc->sc_mmiot, sc->sc_mmioh, reg, data);
	(void)bus_space_read_4(sc->sc_mmiot, sc->sc_mmioh, reg);
}

void
voyager_attach_gpio(struct voyager_softc *sc)
{
	struct gpiobus_attach_args gba;
	int pin;
	uint32_t control, value;

	bcopy(&voyager_gpio_tag, &sc->sc_gpio_tag, sizeof voyager_gpio_tag);
	sc->sc_gpio_tag.gp_cookie = sc;

	control = bus_space_read_4(sc->sc_mmiot, sc->sc_mmioh,
	    VOYAGER_GPIOL_CONTROL);
	value = bus_space_read_4(sc->sc_mmiot, sc->sc_mmioh,
	    VOYAGER_GPIO_DATA_LOW);
	for (pin = 0; pin < 32; pin++) {
		sc->sc_gpio_pins[pin].pin_num = pin;
		if ((control & 1) == 0) {
			sc->sc_gpio_pins[pin].pin_caps =
			    GPIO_PIN_INPUT | GPIO_PIN_OUTPUT;
			sc->sc_gpio_pins[pin].pin_state = value & 1;
		} else {
			/* disable control of taken over pins */
			sc->sc_gpio_pins[pin].pin_caps = 0;
			sc->sc_gpio_pins[pin].pin_state = 0;
		}
	}

	control = bus_space_read_4(sc->sc_mmiot, sc->sc_mmioh,
	    VOYAGER_GPIOH_CONTROL);
	value = bus_space_read_4(sc->sc_mmiot, sc->sc_mmioh,
	    VOYAGER_GPIO_DATA_HIGH);
	for (pin = 32 + 0; pin < 32 + 32; pin++) {
		sc->sc_gpio_pins[pin].pin_num = pin;
		if ((control & 1) == 0) {
			sc->sc_gpio_pins[pin].pin_caps =
			    GPIO_PIN_INPUT | GPIO_PIN_OUTPUT;
			sc->sc_gpio_pins[pin].pin_state = value & 1;
		} else {
			/* disable control of taken over pins */
			sc->sc_gpio_pins[pin].pin_caps = 0;
			sc->sc_gpio_pins[pin].pin_state = 0;
		}
	}

	gba.gba_name = "gpio";
	gba.gba_gc = &sc->sc_gpio_tag;
	gba.gba_pins = sc->sc_gpio_pins;
	gba.gba_npins = nitems(sc->sc_gpio_pins);

	config_found(&sc->sc_dev, &gba, voyager_print);
}