/*******************************************************************************

    AudioScience Linux HPI driver

    Top level initialization and ioctl for standalone HPI driver.

    Copyright (C) 1997-2017  AudioScience Inc. <support@audioscience.com>

    This program is free software; you can redistribute it and/or modify
    it under the terms of version 2 of the GNU General Public License as
    published by the Free Software Foundation;

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

*******************************************************************************/
#define SOURCEFILE_NAME "hpimod.c"

#include "hpi_internal.h"
#include "hpi_version.h"
#include "hpimsginit.h"
#include "hpidebug.h"
#include "hpimsgx.h"
#include "hpicmn.h"
#include "hpioctl.h"

#include <linux/fs.h>
#include <linux/miscdevice.h>
#include <linux/slab.h>
#include <linux/moduleparam.h>
#include <linux/module.h>
#include <asm/uaccess.h>
#include <linux/stringify.h>

MODULE_LICENSE("GPL");
MODULE_AUTHOR("AudioScience Inc. <support@audioscience.com>");
MODULE_DESCRIPTION("AudioScience HPI " HPI_VER_STRING);

static int hpi_release(struct inode *inode, struct file *file)
{
	return asihpi_hpi_release(file);
}

static long hpi_hpi_ioctl(struct file *file,
	  unsigned int cmd, unsigned long arg)
{
	return asihpi_hpi_ioctl(file, cmd, arg);
}
static struct file_operations hpi_fops = {
	.owner = THIS_MODULE,
	.unlocked_ioctl = hpi_hpi_ioctl,
	.open = nonseekable_open,
	.release = hpi_release
};

static struct pci_device_id  asihpi_pci_tbl[] = {
#include <hpipcida.h>
};
MODULE_DEVICE_TABLE(pci, asihpi_pci_tbl);

static int adapter_count;

static int adapter_probe(struct pci_dev *pci_dev,
				       const struct pci_device_id *pci_id)
{
	int err;

	err = asihpi_adapter_probe(pci_dev, pci_id);
	if (err < 0)
		return err;
	adapter_count++;
	return 0;
}

static void adapter_remove(struct pci_dev *pci_dev)
{
	asihpi_adapter_remove(pci_dev);
}

static struct pci_driver asihpi_pci_driver = {
	.name     = "asihpi",
	.id_table = asihpi_pci_tbl,
	.probe    = adapter_probe,
	.remove   = adapter_remove,
};

static struct miscdevice asihpi_misc = {
        .minor  = MISC_DYNAMIC_MINOR,
        .name   = "asihpi",
        .fops   = &hpi_fops,
};

static int misc_registered = -1;
static int pci_driver_registered = -1;

static void hpimod_cleanup(void)
{

	struct hpi_message hm;
	struct hpi_response hr;

	HPI_DEBUG_LOG(DEBUG, "cleanup_module\n");

	if (misc_registered >= 0)
		misc_deregister(&asihpi_misc);

	if (pci_driver_registered >= 0)
		pci_unregister_driver(&asihpi_pci_driver);

	HPI_InitMessageResponse(&hm, &hr, HPI_OBJ_SUBSYSTEM,
				HPI_SUBSYS_DRIVER_UNLOAD);
	HPI_MessageEx(&hm, &hr, HOWNER_KERNEL);
}

static void __exit hpimod_exit(void)
{
	hpimod_cleanup();
}

static int __init hpimod_init(void)
{
	asihpi_init();

	pci_driver_registered = pci_register_driver(&asihpi_pci_driver);
	if (pci_driver_registered < 0) {
		HPI_DEBUG_LOG(ERROR,
			"HPI: pci_register_driver returned %d\n", pci_driver_registered);
		hpimod_cleanup();
		return pci_driver_registered;
	}

	/* note need to remove this if we want driver to stay
	 * loaded with no devices and devices can be hotplugged later
	 */
	if (!adapter_count) {
		HPI_DEBUG_LOG(INFO, "No adapters found\n");
		hpimod_cleanup();
		return -ENODEV;
	}

	misc_registered = misc_register(&asihpi_misc);

	if (misc_registered < 0) {
		printk(KERN_ERR
			"HPI: failed with error %d for misc minor number %d\n",
			-misc_registered, MISC_DYNAMIC_MINOR);
		hpimod_cleanup();
		return -EIO;
	}

	return 0;
}

module_init(hpimod_init)
module_exit(hpimod_exit)
