/* $NetBSD: $ */

/*-
  * Copyright (c) 2009 UPMC/LIP6
  * All rights reserved.
  * This software is distributed under the following condiions
  * compliant with the NetBSD foundation policy.
  *
  * 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 NETBSD FOUNDATION, INC. 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 FOUNDATION 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.
  */

/*-
 * Copyright (c) 2007 Michael Lorenz
 * 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 NETBSD FOUNDATION, INC. 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 FOUNDATION 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.
 */

/* driver for soclib's framebusser device. Attach as a generic fb */

#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: machdep.c,v 1.223 2008/07/02 17:28:56 ad Exp $");

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

#include <machine/autoconf.h>
#include <machine/bus.h>

#include <dev/wsfb/genfbvar.h>

#include "vcifb_logo.c"

/* control registers */
#define DMA_SRC		0x0
#define DMA_LEN		0x4
#define DMA_RESET	0x8

static int genfb_vci_match(device_t, cfdata_t, void *);
static void genfb_vci_attach(device_t, device_t, void *);

struct genfb_vci_softc {
	struct genfb_softc sc_gen;
	paddr_t sc_p; /* mapping informations */
	vaddr_t sc_psize;
	paddr_t sc_pc;
	vaddr_t sc_pcsize;
	bus_space_tag_t sc_tag;
	bus_space_handle_t sc_handle;
	bus_space_handle_t sc_chandle;
	/* framebuffer-related information */
	void *sc_fbaddr;
};

CFATTACH_DECL_NEW(genfb_vci, sizeof(struct genfb_vci_softc),
    genfb_vci_match, genfb_vci_attach, NULL, NULL);

static int genfb_vci_ioctl(void *, void *, u_long, void *, int, struct lwp *);
static paddr_t genfb_vci_mmap(void *, void *, off_t, int);

struct wsdisplay_accessops genfb_vci_accessops = {
	genfb_vci_ioctl,
	genfb_vci_mmap,
	NULL,
	NULL,
	NULL,
	NULL
};


int
genfb_vci_match(device_t parent, cfdata_t match, void *aux)
{
	struct tsardevs_attach_args *tsd = aux;
	if (strcmp(tsd->tsd_devtype, "soclib:frame_buffer") == 0)
		return 1;
	if (strcmp(tsd->tsd_devtype, "vci:vga") == 0)
		return 1;
	return 0;
}

void
genfb_vci_attach(device_t parent, device_t self, void *aux)
{
	struct genfb_vci_softc *sc = device_private(self);
	struct tsardevs_attach_args *tsd = aux;
	const struct boot_fdt_prop *prop;
	int x, y;
	int start_x;
	int start_y;
	uint32_t p;
	const uint8_t *buf;
	prop_dictionary_t dict;
	struct genfb_ops ops;
	bool is_console = false;

	aprint_normal(": SocLib Framebuffer Driver\n");

	sc->sc_gen.sc_dev = self;
	sc->sc_p = tsd->tsd_reg[0].reg_addr;
	sc->sc_psize = tsd->tsd_reg[0].reg_size;
	sc->sc_tag = tsd->tsd_tag;
	if (strcmp(tsd->tsd_devtype, "vci:vga") == 0) {
		sc->sc_pc = tsd->tsd_reg[1].reg_addr;
		sc->sc_pcsize = tsd->tsd_reg[1].reg_size;
		if (bus_space_map(sc->sc_tag, sc->sc_pc, sc->sc_pcsize, 0,
		    &sc->sc_chandle)) {
			aprint_error_dev(self,
			    "couldn't map control registers\n");
			return;
		}
		bus_space_write_4(sc->sc_tag, sc->sc_chandle, DMA_SRC,
		    sc->sc_p);
		bus_space_write_4(sc->sc_tag, sc->sc_chandle, DMA_LEN,
		    sc->sc_psize);
	}

	/* map the device */
	if (bus_space_map(sc->sc_tag, sc->sc_p, sc->sc_psize,
	    BUS_SPACE_MAP_LINEAR | BUS_SPACE_MAP_CACHEABLE, &sc->sc_handle)) {
		aprint_error_dev(self, "couldn't map framebuffer\n");
		return;
	}
	sc->sc_fbaddr = bus_space_vaddr(sc->sc_tag, sc->sc_handle);
	if (sc->sc_fbaddr == NULL) {
		aprint_error_dev(self,
		    "can't map framebuffer in virtual space\n");
		return;
	}
	prop = fdt_find_prop(tsd->tsd_node->fdt_node_data, "width");
	if (prop == NULL) {
		aprint_error_dev(self, "no width\n");
		return;
	}
	sc->sc_gen.sc_width = be32toh(prop->prop_value[0]);

	prop = fdt_find_prop(tsd->tsd_node->fdt_node_data, "height");
	if (prop == NULL) {
		aprint_error_dev(self, "no height\n");
		return;
	}
	sc->sc_gen.sc_height = be32toh(prop->prop_value[0]);

	prop = fdt_find_prop(tsd->tsd_node->fdt_node_data, "mode");
	if (prop == NULL) {
		aprint_error_dev(self, "no mode\n");
		return;
	}
	switch(be32toh(prop->prop_value[0]))
	{
	case 16:
		sc->sc_gen.sc_depth = 16;
		break;
	case 32:
		sc->sc_gen.sc_depth = 32;
		break;
	default:
		aprint_error_dev(self, "unknown mode %d\n",
		    be32toh(prop->prop_value[0]));
		return;
	}
	prop = fdt_find_prop(tsd->tsd_node->fdt_node_data, "stride");
	if (prop != NULL) {
		sc->sc_gen.sc_stride = be32toh(prop->prop_value[0]);
	} else {
		sc->sc_gen.sc_stride = (sc->sc_gen.sc_width * sc->sc_gen.sc_depth) >> 3;
	}
	sc->sc_gen.sc_fbsize = sc->sc_gen.sc_height * sc->sc_gen.sc_stride;
	sc->sc_gen.sc_fboffset = sc->sc_p;
	sc->sc_gen.sc_fbaddr = sc->sc_fbaddr;
	prop = fdt_find_prop(tsd->tsd_node->fdt_node_data, "console");
	if (prop != NULL && be32toh(prop->prop_value[0]) > 0)  {
		is_console = true;
	} 
	dict = device_properties(self);
	prop_dictionary_set_bool(dict, "is_console", is_console);
	aprint_normal_dev(self, "%dx%d %d bits\n", sc->sc_gen.sc_width,
	    sc->sc_gen.sc_height, sc->sc_gen.sc_depth);

	/* draw logo on screen */
	start_x = sc->sc_gen.sc_width / 2 - logo_width / 2;
	start_y = sc->sc_gen.sc_height / 2 - logo_height / 2;
	buf = logo;
	for (y = 0; y < logo_height; y++) {
		for (x = 0; x < logo_width; x++) {
		switch(sc->sc_gen.sc_depth) {
		case 16:
			p = ((uint16_t)(*buf) >> 3 ) << 11; /* R */
			buf++;
			p |= ((uint16_t)(*buf) >> 2) << 5; /* V */
			buf++;
			p |= ((uint16_t)(*buf) >> 3);      /* B */
			buf++;
			bus_space_write_2(sc->sc_tag, sc->sc_handle,
			    (
			     (y + start_y) * sc->sc_gen.sc_stride +
			     (x + start_x) * 2
			    ), p);
			break;
		case 32:
			p = (uint32_t)(*buf) << 16; /* R */
			buf++;
			p |= (uint32_t)(*buf) << 8; /* V */
			buf++;
			p |= (uint32_t)(*buf) << 0; /* B */
			buf++;
			bus_space_write_4(sc->sc_tag, sc->sc_handle,
			    (
			     (y + start_y) * sc->sc_gen.sc_stride +
			     (x + start_x) * 4
			    ), p);
			break;
		}
		}
	}
	memset(&ops, 0, sizeof(ops));
	ops.genfb_ioctl = genfb_vci_ioctl;
	ops.genfb_mmap = genfb_vci_mmap;
	genfb_attach(&sc->sc_gen, &ops);
}	

static int
genfb_vci_ioctl(void *v, void *vs, u_long cmd, void *data, int flag, struct lwp *l)
{
	switch (cmd) {
       case WSDISPLAYIO_GTYPE:
		*(u_int *)data = WSDISPLAY_TYPE_GENFB;
		return 0;
	}

	return EPASSTHROUGH;
}

static paddr_t
genfb_vci_mmap(void *v, void *vs, off_t offset, int prot)
{
	struct genfb_vci_softc *sc = v;

	/* framebuffer at offset 0 */
	if ((offset >= 0) && (offset < sc->sc_psize))
		return bus_space_mmap(sc->sc_tag, sc->sc_p, offset, prot,
		    BUS_SPACE_MAP_LINEAR | BUS_SPACE_MAP_CACHEABLE);
	return -1;
}
