/*
 * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#include <sys/conf.h>
#include <sys/ddi.h>
#include <sys/sunddi.h>
#include <sys/modctl.h>
#include <sys/stat.h>
#include <sys/sunldi.h>
#include <sys/file.h>
#include <sys/agpgart.h>
#include <sys/agp/agpdefs.h>
#include <sys/agp/agpamd64gart_io.h>

#define	MAX_GART_INSTS		8
#define	GETSOFTC(instance)	((amd64_gart_softstate_t *)	\
    ddi_get_soft_state(amd64_gart_glob_soft_handle, (instance)));
#define	DEV2INST(dev)		(getminor(dev))
#define	INST2NODENUM(inst)	(inst)

int amd64_debug_var = 0;
#define	AMD64DB_PRINT1(fmt)	if (amd64_debug_var == 1) cmn_err fmt
#define	AMD64DB_PRINT2(fmt)	if (amd64_debug_var >= 1) cmn_err fmt

typedef struct amd64_gart_softstate {
	dev_info_t		*gsoft_dip;
	ddi_acc_handle_t	gsoft_pcihdl;
	kmutex_t		gsoft_lock;
}amd64_gart_softstate_t;

static void *amd64_gart_glob_soft_handle;

static uint64_t
amd64_get_aperbase(amd64_gart_softstate_t *sc)
{
	uint32_t	value;
	uint64_t	aper_base;

	/* amd64 aperture base support 40 bits and 32M aligned */
	value = pci_config_get32(sc->gsoft_pcihdl,
	    AMD64_APERTURE_BASE) & AMD64_APERBASE_MASK;
	aper_base = (uint64_t)value << AMD64_APERBASE_SHIFT;
	return (aper_base);
}

static size_t
amd64_get_apersize(amd64_gart_softstate_t *sc)
{
	uint32_t	value;
	size_t		size;

	value = pci_config_get32(sc->gsoft_pcihdl, AMD64_APERTURE_CONTROL);

	value = (value & AMD64_APERSIZE_MASK) >> 1;

	/* aper size = 2^value x 32 */
	switch (value) {
		case  0x0:
			size = 32;
			break;
		case  0x1:
			size = 64;
			break;
		case  0x2:
			size = 128;
			break;
		case  0x3:
			size = 256;
			break;
		case  0x4:
			size = 512;
			break;
		case  0x5:
			size = 1024;
			break;
		case  0x6:
			size = 2048;
			break;
		default:		/* reserved */
			size = 0;
	};

	return (size);
}

static void
amd64_invalidate_gtlb(amd64_gart_softstate_t *sc)
{
	uint32_t value;

	value = pci_config_get32(sc->gsoft_pcihdl, AMD64_GART_CACHE_CTL);
	value |= AMD64_INVALID_CACHE;

	pci_config_put32(sc->gsoft_pcihdl, AMD64_GART_CACHE_CTL, value);
}

static void
amd64_enable_gart(amd64_gart_softstate_t *sc, int enable)
{
	uint32_t aper_ctl;
	uint32_t aper_base;
	uint32_t gart_ctl;
	uint32_t gart_base;

	aper_ctl = pci_config_get32(sc->gsoft_pcihdl, AMD64_APERTURE_CONTROL);
	AMD64DB_PRINT1((CE_NOTE, "before: aper_ctl = %x", aper_ctl));
	aper_base = pci_config_get32(sc->gsoft_pcihdl, AMD64_APERTURE_BASE);
	gart_ctl = pci_config_get32(sc->gsoft_pcihdl, AMD64_GART_CACHE_CTL);
	gart_base = pci_config_get32(sc->gsoft_pcihdl, AMD64_GART_BASE);
#ifdef lint
	aper_base = aper_base;
	gart_ctl = gart_ctl;
	gart_base = gart_base;
#endif /* lint */
	AMD64DB_PRINT1((CE_NOTE, "before: aper_base = %x", aper_base));
	AMD64DB_PRINT1((CE_NOTE, "before: gart_ctl = %x", gart_ctl));
	AMD64DB_PRINT1((CE_NOTE, "before: gart_base = %x", gart_base));
	if (enable) {
		aper_ctl |= AMD64_GARTEN;
		aper_ctl &= ~(AMD64_DISGARTCPU | AMD64_DISGARTIO);
	} else
		aper_ctl &= (~AMD64_GARTEN);

	pci_config_put32(sc->gsoft_pcihdl, AMD64_APERTURE_CONTROL, aper_ctl);
}

/*ARGSUSED*/
static int
amd64_gart_getinfo(dev_info_t *dip, ddi_info_cmd_t cmd,
    void *arg, void **resultp)
{
	amd64_gart_softstate_t *st;
	int instance, rval = DDI_FAILURE;
	dev_t dev;

	switch (cmd) {
	case DDI_INFO_DEVT2DEVINFO:
		dev = (dev_t)arg;
		instance = DEV2INST(dev);
		st = ddi_get_soft_state(amd64_gart_glob_soft_handle, instance);
		if (st != NULL) {
			mutex_enter(&st->gsoft_lock);
			*resultp = st->gsoft_dip;
			mutex_exit(&st->gsoft_lock);
			rval = DDI_SUCCESS;
		} else {
			*resultp = NULL;
		}

		break;
	case DDI_INFO_DEVT2INSTANCE:
		dev = (dev_t)arg;
		instance = DEV2INST(dev);
		*resultp = (void *)(uintptr_t)instance;
		rval = DDI_SUCCESS;
		break;
	default:
		break;
	}

	return (rval);
}

static int
amd64_gart_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
{
	int			instance;
	amd64_gart_softstate_t	*sc;
	int			status;
	char			buf[80];

	switch (cmd) {
	default:
		return (DDI_FAILURE);

	case DDI_RESUME:
		/* Nothing special is needed for resume. */
		return (DDI_SUCCESS);

	case DDI_ATTACH:
		break;
	}

	instance = ddi_get_instance(dip);

	if (ddi_soft_state_zalloc(amd64_gart_glob_soft_handle, instance) !=
	    DDI_SUCCESS)
		return (DDI_FAILURE);

	sc = ddi_get_soft_state(amd64_gart_glob_soft_handle, instance);
	mutex_init(&sc->gsoft_lock, NULL, MUTEX_DRIVER, NULL);
	sc->gsoft_dip = dip;
	status = pci_config_setup(dip, &sc->gsoft_pcihdl);
	if (status != DDI_SUCCESS) {
		ddi_soft_state_free(amd64_gart_glob_soft_handle, instance);
		return (DDI_FAILURE);
	}
	(void) sprintf(buf, "%s-%d", AMD64GART_NAME, instance);
	status = ddi_create_minor_node(dip, buf, S_IFCHR,
	    INST2NODENUM(instance), DDI_NT_AGP_CPUGART, 0);
	if (status != DDI_SUCCESS) {
		pci_config_teardown(&sc->gsoft_pcihdl);
		ddi_soft_state_free(amd64_gart_glob_soft_handle, instance);
		return (DDI_FAILURE);
	}
	return (DDI_SUCCESS);
}

/*ARGSUSED*/
static int
amd64_gart_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
{
	int			instance;
	amd64_gart_softstate_t	*sc;
	char			buf[80];

	switch (cmd) {
	default:
		return (DDI_FAILURE);

	case DDI_SUSPEND:
		/* Nothing special is needed for suspend */
		return (DDI_SUCCESS);

	case DDI_DETACH:
		break;
	}

	instance = ddi_get_instance(dip);
	sc = ddi_get_soft_state(amd64_gart_glob_soft_handle, instance);

	(void) sprintf(buf, "%s-%d", AMD64GART_NAME, instance);
	ddi_remove_minor_node(dip, buf);
	pci_config_teardown(&sc->gsoft_pcihdl);
	mutex_destroy(&sc->gsoft_lock);
	ddi_soft_state_free(amd64_gart_glob_soft_handle, instance);

	return (DDI_SUCCESS);
}

/*ARGSUSED*/
static int
amd64_gart_ioctl(dev_t dev, int cmd, intptr_t data, int mode,
    cred_t *cred, int *rval)
{
	int instance;
	amd64_gart_softstate_t *sc;
	static char kernel_only[] =
	    "amd64_gart_ioctl: is a kernel only ioctl";

	if (!(mode & FKIOCTL)) {
		AMD64DB_PRINT2((CE_CONT, kernel_only));
		return (ENXIO);
	}
	instance = DEV2INST(dev);
	sc = GETSOFTC(instance);

	if (sc == NULL)
		return (ENXIO);
	mutex_enter(&sc->gsoft_lock);

	switch (cmd) {
	case AMD64_GET_INFO:
	{
		amdgart_info_t info;

		info.cgart_aperbase = amd64_get_aperbase(sc);
		info.cgart_apersize = amd64_get_apersize(sc);

		if (ddi_copyout(&info, (void *)data,
		    sizeof (amdgart_info_t), mode)) {
			mutex_exit(&sc->gsoft_lock);
			return (EFAULT);
		}
		break;
	}
	case AMD64_SET_GART_ADDR:
	{
		uint32_t addr;

		if (ddi_copyin((void *)data, &addr, sizeof (uint32_t), mode)) {
			mutex_exit(&sc->gsoft_lock);
			return (EFAULT);
		}

		pci_config_put32(sc->gsoft_pcihdl, AMD64_GART_BASE, addr);
		amd64_enable_gart(sc, 1);

		break;
	}
	case AMD64_FLUSH_GTLB:
	{
		amd64_invalidate_gtlb(sc);

		break;
	}
	case AMD64_CONFIGURE:
	{
		/* reserved */
		break;
	}
	case AMD64_UNCONFIG:
	{
		amd64_enable_gart(sc, 0);
		pci_config_put32(sc->gsoft_pcihdl, AMD64_GART_BASE, 0x00000000);

		break;
	}
	default:
		mutex_exit(&sc->gsoft_lock);
		return (ENXIO);

	}

	mutex_exit(&sc->gsoft_lock);

	return (0);
}

/*ARGSUSED*/
static int
amd64_gart_open(dev_t *dev, int flag, int otyp, cred_t *cred)
{
	int			instance;
	amd64_gart_softstate_t	*sc;

	if (!(flag & FKLYR))
		return (ENXIO);

	instance = DEV2INST(*dev);
	sc = GETSOFTC(instance);

	if (sc == NULL)
		return (ENXIO);

	return (0);
}

/*ARGSUSED*/
static int
amd64_gart_close(dev_t dev, int flag, int otyp, cred_t *cred)
{
	int			instance;
	amd64_gart_softstate_t	*sc;

	instance = DEV2INST(dev);
	sc = GETSOFTC(instance);

	if (sc == NULL)
		return (ENXIO);

	return (0);
}

static  struct  cb_ops  amd64_gart_cb_ops = {
	amd64_gart_open,	/* cb_open() */
	amd64_gart_close,	/* cb_close() */
	nodev,			/* cb_strategy() */
	nodev,			/* cb_print */
	nodev,			/* cb_dump */
	nodev,			/* cb_read() */
	nodev,			/* cb_write() */
	amd64_gart_ioctl,	/* cb_ioctl */
	nodev,			/* cb_devmap */
	nodev,			/* cb_mmap */
	nodev,			/* cb_segmap */
	nochpoll,		/* cb_chpoll */
	ddi_prop_op,		/* cb_prop_op */
	0,			/* cb_stream */
	D_NEW | D_MP,		/* cb_flag */
	CB_REV,			/* cb_ops version? */
	nodev,			/* cb_aread() */
	nodev,			/* cb_awrite() */
};

/* device operations */
static struct dev_ops amd64_gart_ops = {
	DEVO_REV,		/* devo_rev */
	0,			/* devo_refcnt */
	amd64_gart_getinfo,	/* devo_getinfo */
	nulldev,		/* devo_identify */
	nulldev,		/* devo_probe */
	amd64_gart_attach,	/* devo_attach */
	amd64_gart_detach,	/* devo_detach */
	nodev,			/* devo_reset */
	&amd64_gart_cb_ops,	/* devo_cb_ops */
	0,			/* devo_bus_ops */
	0,			/* devo_power */
	ddi_quiesce_not_needed,	/* devo_quiesce */
};

static  struct modldrv modldrv = {
	&mod_driverops,
	"AGP AMD gart driver",
	&amd64_gart_ops,
};

static  struct modlinkage modlinkage = {
	MODREV_1,		/* MODREV_1 is indicated by manual */
	&modldrv,
	NULL
};


int
_init(void)
{
	int ret = DDI_SUCCESS;

	ret = ddi_soft_state_init(&amd64_gart_glob_soft_handle,
	    sizeof (amd64_gart_softstate_t),
	    MAX_GART_INSTS);

	if (ret)
		return (ret);
	if ((ret = mod_install(&modlinkage)) != 0) {
		ddi_soft_state_fini(&amd64_gart_glob_soft_handle);
		return (ret);
	}
	return (DDI_SUCCESS);
}

int
_info(struct  modinfo *modinfop)
{
	return (mod_info(&modlinkage, modinfop));
}

int
_fini(void)
{
	int ret;
	if ((ret = mod_remove(&modlinkage)) == 0) {
		ddi_soft_state_fini(&amd64_gart_glob_soft_handle);
	}
	return (ret);
}