/*
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  You need a userspace library to cooperate with this driver. It (and other
 *  info) may be obtained here:
 *  http://www.fi.muni.cz/~xslaby/phantom.html
 *  or alternatively, you might use OpenHaptics provided by Sensable.
 */

#include <linux/compat.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/device.h>
#include <linux/pci.h>
#include <linux/fs.h>
#include <linux/poll.h>
#include <linux/interrupt.h>
#include <linux/cdev.h>
#include <linux/slab.h>
#include <linux/phantom.h>
#include <linux/sched.h>
#include <linux/smp_lock.h>

#include <asm/atomic.h>
#include <asm/io.h>

#define DUMMY_VENDOR_ID             0xFFF0
#define DUMMY_DEVICE_ID             0x8748
#define DUMMY_CLASS_ID              0x0880 // ??

#define DUMMY_MAX_MINORS    1
#define DUMMY_VERSION       1

//static struct class *dummy_class;

struct dummy_device {
	unsigned int opened;
	void __iomem *caddr;
	u32 __iomem *iaddr;
	u32 __iomem *oaddr;
	unsigned long status;
	atomic_t counter;
    wait_queue_head_t wait;

	struct mutex open_lock;
	spinlock_t regs_lock;
};


static irqreturn_t dummy_isr(int irq, void *data)
{
	struct dummy_device *dev = data;

printk("Dummy: %s called\n", __func__);
	atomic_inc(&dev->counter);
	wake_up_interruptible(&dev->wait);

	return IRQ_HANDLED;
}

static int __devinit dummy_probe(struct pci_dev *pdev,
	const struct pci_device_id *pci_id)
{
	struct dummy_device *pht;
	int retval;

printk("Dummy: %s called\n", __func__);
	retval = pci_enable_device(pdev);
	if (retval)
		goto err;

	retval = pci_request_regions(pdev, "dummy");
	if (retval)
		goto err_dis;

	retval = -ENOMEM;
	pht = kzalloc(sizeof(*pht), GFP_KERNEL);
	if (pht == NULL) {
		dev_err(&pdev->dev, "unable to allocate device\n");
        printk("Dummy: unable to allocate device\n");
		goto err_reg;
	}

	pht->caddr = pci_iomap(pdev, 0, 0);
	if (pht->caddr == NULL) {
		dev_err(&pdev->dev, "can't remap conf space\n");
		printk("Dummy: can't remap conf space\n");
		goto err_fr;
	}
	pht->iaddr = pci_iomap(pdev, 2, 0);
	if (pht->iaddr == NULL) {
		dev_err(&pdev->dev, "can't remap input space\n");
		printk("Dummy: can't remap input space\n");
		goto err_unmc;
	}
	pht->oaddr = pci_iomap(pdev, 3, 0);
	if (pht->oaddr == NULL) {
		dev_err(&pdev->dev, "can't remap output space\n");
		printk("Dummy: can't remap output space\n");
		goto err_unmi;
	}

	mutex_init(&pht->open_lock);
	spin_lock_init(&pht->regs_lock);
	init_waitqueue_head(&pht->wait);

	retval = request_irq(pdev->irq, dummy_isr,
			IRQF_SHARED | IRQF_DISABLED, "dummy", pht);
	if (retval) {
		dev_err(&pdev->dev, "can't establish ISR\n");
		printk("Dummy: can't establish ISR\n");
		goto err_unmo;
	}

	pci_set_drvdata(pdev, pht);

	return 0;
err_unmo:
	pci_iounmap(pdev, pht->oaddr);
err_unmi:
	pci_iounmap(pdev, pht->iaddr);
err_unmc:
	pci_iounmap(pdev, pht->caddr);
err_fr:
	kfree(pht);
err_reg:
	pci_release_regions(pdev);
err_dis:
	pci_disable_device(pdev);
err:
	return retval;
}

static void __devexit dummy_remove(struct pci_dev *pdev)
{
	struct dummy_device *pht = pci_get_drvdata(pdev);

printk("Dummy: %s called\n", __func__);
	free_irq(pdev->irq, pht);

	pci_iounmap(pdev, pht->oaddr);
	pci_iounmap(pdev, pht->iaddr);
	pci_iounmap(pdev, pht->caddr);

	kfree(pht);

	pci_release_regions(pdev);

	pci_disable_device(pdev);
}

static int dummy_suspend(struct pci_dev *pdev, pm_message_t state)
{
printk("Dummy: %s called\n", __func__);
	synchronize_irq(pdev->irq);

	return 0;
}

static int dummy_resume(struct pci_dev *pdev)
{
printk("Dummy: %s called\n", __func__);
	return 0;
}


static struct pci_device_id dummy_pci_tbl[] __devinitdata = {
	{ .vendor = DUMMY_VENDOR_ID, .device = DUMMY_DEVICE_ID,
	  .subvendor = PCI_ANY_ID, .subdevice = PCI_ANY_ID,
	  .class = (DUMMY_CLASS_ID << 8), .class_mask = 0xffff00 },
	{ 0, }
};
MODULE_DEVICE_TABLE(pci, dummy_pci_tbl);

static struct pci_driver dummy_pci_driver = {
	.name = "dummy",
	.id_table = dummy_pci_tbl,
	.probe = dummy_probe,
	.remove = __devexit_p(dummy_remove),
	.suspend = dummy_suspend,
	.resume = dummy_resume,
};

static int __init dummy_init(void)
{
	int retval;

printk("Dummy: %s called\n", __func__);
/*
	dummy_class = class_create(THIS_MODULE, "dummy");
	if (IS_ERR(dummy_class)) {
		retval = PTR_ERR(dummy_class);
		printk("Dummy: can't register dummy class\n");
		goto err;
	}
*/

	retval = pci_register_driver(&dummy_pci_driver);
	if (retval) {
		printk("Dummy: can't register pci driver\n");
		goto err_class;
	}

	printk("Dummy Linux Driver, init OK\n");

	return 0;
err_class:
//	class_destroy(dummy_class);
//err:
	return retval;
}

static void __exit dummy_exit(void)
{
printk("Dummy: %s called\n", __func__);
	pci_unregister_driver(&dummy_pci_driver);

//	class_destroy(dummy_class);

	printk("Dummy: module successfully removed\n");
}

module_init(dummy_init);
module_exit(dummy_exit);
