/*-
 * Driver for Hifn HIPP-I/II chipset
 * Copyright (c) 2006 Michael Richardson <mcr@xelerance.com>
 *
 * 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.
 * 3. The name of the author may not be used to endorse or promote products
 *   derived from this software without specific prior written permission.
 *
 * 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.
 *
 * Effort sponsored by Hifn Inc.
 *
 */

/*
 * Driver for various Hifn encryption processors.
 */
#include <linux/version.h>
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,38) && !defined(AUTOCONF_INCLUDED)
#include <linux/config.h>
#endif
#include <linux/module.h>
#include <linux/init.h>
#include <linux/list.h>
#include <linux/slab.h>
#include <linux/wait.h>
#include <linux/sched.h>
#include <linux/pci.h>
#include <linux/delay.h>
#include <linux/interrupt.h>
#include <linux/spinlock.h>
#include <linux/random.h>
#include <linux/skbuff.h>
#include <linux/uio.h>
#include <linux/sysfs.h>
#include <linux/miscdevice.h>
#include <asm/io.h>

#include <cryptodev.h>

#include "hifnHIPPreg.h"
#include "hifnHIPPvar.h"

#if 1
#define	DPRINTF(a...)	if (hipp_debug) { \
							printk("%s: ", sc ? \
								device_get_nameunit(sc->sc_dev) : "hifn"); \
							printk(a); \
						} else
#else
#define	DPRINTF(a...)
#endif

typedef int bus_size_t;

static inline int
pci_get_revid(struct pci_dev *dev)
{
	u8 rid = 0;
	pci_read_config_byte(dev, PCI_REVISION_ID, &rid);
	return rid;
}

#define debug hipp_debug
int hipp_debug = 0;
module_param(hipp_debug, int, 0644);
MODULE_PARM_DESC(hipp_debug, "Enable debug");

int hipp_maxbatch = 1;
module_param(hipp_maxbatch, int, 0644);
MODULE_PARM_DESC(hipp_maxbatch, "max ops to batch w/o interrupt");

static	int  hipp_probe(struct pci_dev *dev, const struct pci_device_id *ent);
static	void hipp_remove(struct pci_dev *dev);
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,19)
static irqreturn_t hipp_intr(int irq, void *arg);
#else
static irqreturn_t hipp_intr(int irq, void *arg, struct pt_regs *regs);
#endif

static int hipp_num_chips = 0;
static struct hipp_softc *hipp_chip_idx[HIPP_MAX_CHIPS];

static	int hipp_newsession(device_t, u_int32_t *, struct cryptoini *);
static	int hipp_freesession(device_t, u_int64_t);
static	int hipp_process(device_t, struct cryptop *, int);

static device_method_t hipp_methods = {
	/* crypto device methods */
	DEVMETHOD(cryptodev_newsession,	hipp_newsession),
	DEVMETHOD(cryptodev_freesession,hipp_freesession),
	DEVMETHOD(cryptodev_process,	hipp_process),
};

static __inline u_int32_t
READ_REG(struct hipp_softc *sc, unsigned int barno, bus_size_t reg)
{
	u_int32_t v = readl(sc->sc_bar[barno] + reg);
	//sc->sc_bar0_lastreg = (bus_size_t) -1;
	return (v);
}
static __inline void
WRITE_REG(struct hipp_softc *sc, unsigned int barno, bus_size_t reg, u_int32_t val)
{
	writel(val, sc->sc_bar[barno] + reg);
}

#define READ_REG_0(sc, reg)         READ_REG(sc, 0, reg)
#define WRITE_REG_0(sc, reg, val)   WRITE_REG(sc,0, reg, val)
#define READ_REG_1(sc, reg)         READ_REG(sc, 1, reg)
#define WRITE_REG_1(sc, reg, val)   WRITE_REG(sc,1, reg, val)

static int
hipp_newsession(device_t dev, u_int32_t *sidp, struct cryptoini *cri)
{
	return EINVAL;
}

static int
hipp_freesession(device_t dev, u_int64_t tid)
{
	return EINVAL;
}

static int
hipp_process(device_t dev, struct cryptop *crp, int hint)
{
	return EINVAL;
}

static const char*
hipp_partname(struct hipp_softc *sc, char buf[128], size_t blen)
{
	char *n = NULL;

	switch (pci_get_vendor(sc->sc_pcidev)) {
	case PCI_VENDOR_HIFN:
		switch (pci_get_device(sc->sc_pcidev)) {
		case PCI_PRODUCT_HIFN_7855:	n = "Hifn 7855";
		case PCI_PRODUCT_HIFN_8155:	n = "Hifn 8155";
		case PCI_PRODUCT_HIFN_6500:	n = "Hifn 6500";
		}
	}

	if(n==NULL) {
		snprintf(buf, blen, "VID=%02x,PID=%02x",
			 pci_get_vendor(sc->sc_pcidev),
			 pci_get_device(sc->sc_pcidev));
	} else {
		buf[0]='\0';
		strncat(buf, n, blen);
	}
	return buf;
}

struct hipp_fs_entry {
	struct attribute attr;
	/* other stuff */
};


static ssize_t
cryptoid_show(struct device *dev,
	      struct device_attribute *attr,
	      char *buf)						
{								
	struct hipp_softc *sc;					

	sc = pci_get_drvdata(to_pci_dev (dev));
	return sprintf (buf, "%d\n", sc->sc_cid);
}

struct device_attribute hipp_dev_cryptoid = __ATTR_RO(cryptoid);

/*
 * Attach an interface that successfully probed.
 */
static int
hipp_probe(struct pci_dev *dev, const struct pci_device_id *ent)
{
	struct hipp_softc *sc = NULL;
	int i;
	//char rbase;
	//u_int16_t ena;
	int rev;
	//int rseg;
	int rc;

	DPRINTF("%s()\n", __FUNCTION__);

	if (pci_enable_device(dev) < 0)
		return(-ENODEV);

	if (pci_set_mwi(dev))
		return(-ENODEV);

	if (!dev->irq) {
		printk("hifn: found device with no IRQ assigned. check BIOS settings!");
		pci_disable_device(dev);
		return(-ENODEV);
	}

	sc = (struct hipp_softc *) kmalloc(sizeof(*sc), GFP_KERNEL);
	if (!sc)
		return(-ENOMEM);
	memset(sc, 0, sizeof(*sc));

	softc_device_init(sc, "hifn-hipp", hipp_num_chips, hipp_methods);

	sc->sc_pcidev = dev;
	sc->sc_irq = -1;
	sc->sc_cid = -1;
	sc->sc_num = hipp_num_chips++;

	if (sc->sc_num < HIPP_MAX_CHIPS)
		hipp_chip_idx[sc->sc_num] = sc;

	pci_set_drvdata(sc->sc_pcidev, sc);

	spin_lock_init(&sc->sc_mtx);

	/*
	 * Setup PCI resources.
	 * The READ_REG_0, WRITE_REG_0, READ_REG_1,
	 * and WRITE_REG_1 macros throughout the driver are used
	 * to permit better debugging.
	 */
	for(i=0; i<4; i++) {
		unsigned long mem_start, mem_len;
		mem_start = pci_resource_start(sc->sc_pcidev, i);
		mem_len   = pci_resource_len(sc->sc_pcidev, i);
		sc->sc_barphy[i] = (caddr_t)mem_start;
		sc->sc_bar[i] = (ocf_iomem_t) ioremap(mem_start, mem_len);
		if (!sc->sc_bar[i]) {
			device_printf(sc->sc_dev, "cannot map bar%d register space\n", i);
			goto fail;
		}
	}

	//hipp_reset_board(sc, 0);
	pci_set_master(sc->sc_pcidev);

	/*
	 * Arrange the interrupt line.
	 */
	rc = request_irq(dev->irq, hipp_intr, IRQF_SHARED, "hifn", sc);
	if (rc) {
		device_printf(sc->sc_dev, "could not map interrupt: %d\n", rc);
		goto fail;
	}
	sc->sc_irq = dev->irq;

	rev = READ_REG_1(sc, HIPP_1_REVID) & 0xffff;

	{
		char b[32];
		device_printf(sc->sc_dev, "%s, rev %u",
			      hipp_partname(sc, b, sizeof(b)), rev);
	}

#if 0
	if (sc->sc_flags & HIFN_IS_7956)
		printf(", pll=0x%x<%s clk, %ux mult>",
			sc->sc_pllconfig,
			sc->sc_pllconfig & HIFN_PLL_REF_SEL ? "ext" : "pci",
			2 + 2*((sc->sc_pllconfig & HIFN_PLL_ND) >> 11));
#endif
	printf("\n");

	sc->sc_cid = crypto_get_driverid(softc_get_device(sc),CRYPTOCAP_F_HARDWARE);
	if (sc->sc_cid < 0) {
		device_printf(sc->sc_dev, "could not get crypto driver id\n");
		goto fail;
	}

#if 0 /* cannot work with a non-GPL module */
	/* make a sysfs entry to let the world know what entry we got */
	sysfs_create_file(&sc->sc_pcidev->dev.kobj, &hipp_dev_cryptoid.attr);
#endif

#if 0
	init_timer(&sc->sc_tickto);
	sc->sc_tickto.function = hifn_tick;
	sc->sc_tickto.data = (unsigned long) sc->sc_num;
	mod_timer(&sc->sc_tickto, jiffies + HZ);
#endif

#if 0 /* no code here yet ?? */
	crypto_register(sc->sc_cid, CRYPTO_3DES_CBC, 0, 0);
#endif

	return (0);

fail:
	if (sc->sc_cid >= 0)
		crypto_unregister_all(sc->sc_cid);
	if (sc->sc_irq != -1)
		free_irq(sc->sc_irq, sc);
	
#if 0
	if (sc->sc_dma) {
		/* Turn off DMA polling */
		WRITE_REG_1(sc, HIFN_1_DMA_CNFG, HIFN_DMACNFG_MSTRESET |
			    HIFN_DMACNFG_DMARESET | HIFN_DMACNFG_MODE);
		
		pci_free_consistent(sc->sc_pcidev,
				    sizeof(*sc->sc_dma),
				    sc->sc_dma, sc->sc_dma_physaddr);
	}
#endif
	kfree(sc);
	return (-ENXIO);
}

/*
 * Detach an interface that successfully probed.
 */
static void
hipp_remove(struct pci_dev *dev)
{
	struct hipp_softc *sc = pci_get_drvdata(dev);
	unsigned long l_flags;

	DPRINTF("%s()\n", __FUNCTION__);

	/* disable interrupts */
	HIPP_LOCK(sc);

#if 0
	WRITE_REG_1(sc, HIFN_1_DMA_IER, 0);
	HIFN_UNLOCK(sc);

	/*XXX other resources */
	del_timer_sync(&sc->sc_tickto);

	/* Turn off DMA polling */
	WRITE_REG_1(sc, HIFN_1_DMA_CNFG, HIFN_DMACNFG_MSTRESET |
	    HIFN_DMACNFG_DMARESET | HIFN_DMACNFG_MODE);
#endif

	crypto_unregister_all(sc->sc_cid);

	free_irq(sc->sc_irq, sc);

#if 0
	pci_free_consistent(sc->sc_pcidev, sizeof(*sc->sc_dma),
                sc->sc_dma, sc->sc_dma_physaddr);
#endif
}

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,19)
static irqreturn_t hipp_intr(int irq, void *arg)
#else
static irqreturn_t hipp_intr(int irq, void *arg, struct pt_regs *regs)
#endif
{
	struct hipp_softc *sc = arg;

	sc = sc; /* shut up compiler */

	return IRQ_HANDLED;
}

static struct pci_device_id hipp_pci_tbl[] = {
	{ PCI_VENDOR_HIFN, PCI_PRODUCT_HIFN_7855,
	  PCI_ANY_ID, PCI_ANY_ID, 0, 0, },
	{ PCI_VENDOR_HIFN, PCI_PRODUCT_HIFN_8155,
	  PCI_ANY_ID, PCI_ANY_ID, 0, 0, },
	{ 0 }, /* terminating entry */
};
MODULE_DEVICE_TABLE(pci, hipp_pci_tbl);

static struct pci_driver hipp_driver = {
	.name         = "hipp",
	.id_table     = hipp_pci_tbl,
	.probe        =	hipp_probe,
	.remove       = hipp_remove,
	/* add PM stuff here one day */
};

static int __init hipp_init (void)
{
	struct hipp_softc *sc = NULL;
	int rc;

	DPRINTF("%s(%p)\n", __FUNCTION__, hipp_init);

	rc = pci_register_driver(&hipp_driver);
	pci_register_driver_compat(&hipp_driver, rc);

	return rc;
}

static void __exit hipp_exit (void)
{
	pci_unregister_driver(&hipp_driver);
}

module_init(hipp_init);
module_exit(hipp_exit);

MODULE_LICENSE("BSD");
MODULE_AUTHOR("Michael Richardson <mcr@xelerance.com>");
MODULE_DESCRIPTION("OCF driver for hifn HIPP-I/II PCI crypto devices");