/* SPDX-License-Identifier: GPL-2.0+ */
/*
 * Copyright (C) 2019 CERN (www.cern.ch)
 *
 */
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/pci_ids.h>
#include <linux/i2c.h>
#include <linux/i2c-xiic.h>
#include <linux/platform_data/i2c-ocores.h>
#include <linux/spi/spi.h>
#include <linux/mfd/core.h>
#include <linux/firmware.h>

#define DRV_NAME "spec7_pci_fw"
/* I2C Register definations */
#define SPEC7_BASE_REGS_I2C 0x20
#define SPEC7_BASE_REGS_I2C_SIZE 32
#define SPEC7_FPGA_WB_CLK_HZ 62500000
#define SPEC7_FPGA_WB_CLK_KHZ (SPEC7_FPGA_WB_CLK_HZ / 1000)

struct spec7_drv {
	struct device *dev;
	struct pci_dev *pdev;
	void __iomem *bar1;
};

/*Tandem for bitstream over PCIe*/ 
#define TANDEM_WRITE(val32) writel((cpu_to_le32(val32)), drvdata->bar1)
/*I2C Resource defination*/
static struct resource spec7_i2c_resources[] = {
	{
		.flags = IORESOURCE_MEM,
		.start = SPEC7_BASE_REGS_I2C,
		.end   = SPEC7_BASE_REGS_I2C +
			 SPEC7_BASE_REGS_I2C_SIZE - 1,
	}

}; 

static struct ocores_i2c_platform_data spec7_i2c_pdata = {
	.reg_shift = 2,
	.reg_io_width = 4,
	.clock_khz = SPEC7_FPGA_WB_CLK_KHZ,
	.big_endian = 0,
	.num_devices = 0,
	.devices = NULL,
};

static struct pci_device_id pcie_ids[] = {
	{PCI_DEVICE(0x10ee, 0x7022), },
	{PCI_DEVICE(0x10ee, 0x7012), },
	{PCI_DEVICE(0x10dc, 0x18d), },
	{0,},
};

MODULE_DEVICE_TABLE(pci, pcie_ids);

static const struct mfd_cell spec7_mfd_cells[] = {
	{
	.name = "spec7_i2c",
	.num_resources = ARRAY_SIZE(spec7_i2c_resources),
	.resources = spec7_i2c_resources,
	.platform_data = &spec7_i2c_pdata,
	.pdata_size = sizeof(spec7_i2c_pdata),
	.id =1,
	},

};

static int spec7_pci_init(struct pci_dev *pcidev)
{
	struct spec7_drv *drvdata;
	drvdata = devm_kzalloc(&pcidev->dev, sizeof(*drvdata), GFP_KERNEL);
	if(!drvdata)
		return -ENOMEM;

	pci_set_drvdata(pcidev, drvdata);
	drvdata->pdev = pcidev;
	dev_set_drvdata(&pcidev->dev, drvdata);
	return 0;
}

static int spec7_pci_load_firmware(struct spec7_drv *drvdata)
{
	const struct firmware *fw;
	char firmware_name[64] = "spec7_wr_ref_top.bin";
	unsigned nb_words;
	u32 *data;
	int ret = 0;

	dev_info(&drvdata->pdev->dev, "Requesting firmware %s\n",
                firmware_name);
        ret = request_firmware(&fw, firmware_name,
                &drvdata->pdev->dev);
        if (ret) {
                dev_err(&drvdata->pdev->dev,
                        "No firmware loaded\n");
                return ret;
        }
        nb_words = fw->size/4;
        data = (u32 *)fw->data;
        dev_info(&drvdata->pdev->dev,
                                "Firmware %s is %zu bytes long (%u words).\n",
                                firmware_name, fw->size, nb_words);
        /* Write binary file 4 bytes by 4 bytes */
        while (nb_words--) {
		//pr_info("writing data %x", *data);
                TANDEM_WRITE(*data);
                ++data;
        }
        /* Release the firmware object */
        release_firmware(fw);

        return ret;
}

static int spec7_pci_probe(struct pci_dev *pcidev, const struct pci_device_id *id)
{
	int ret,i;
	struct spec7_drv *drvdata;
	unsigned long bar_start;
	unsigned long bar_len ;
	u16 vendor_id,device_id;
	
	ret = pci_enable_device(pcidev);
	if (ret)
		return -ENOMEM;

	pci_read_config_word(pcidev, 0, &vendor_id);
	pci_read_config_word(pcidev, 2, &device_id);
	printk(KERN_ALERT "PCIe driver detected SPEC7 at %x %x", vendor_id, device_id);
	
	ret = spec7_pci_init(pcidev);
	if (ret <0){
		dev_err(&pcidev->dev, "Driver data not initialized\n");
		return ret;
	}

	drvdata = (struct spec7_drv *)pci_get_drvdata(pcidev); 
	
	/*BAR0 Crap*/
	bar_start = pci_resource_start(pcidev, 0);
        bar_len = pci_resource_len(pcidev, 0);
        dev_info(&pcidev->dev,
                "BAR 0  start at address : %lx length : 0x%lx.\n",
                bar_start, bar_len);
        /*Request the memory region*/
        if (!devm_request_mem_region(&pcidev->dev, bar_start + 0x6000,
                                0x8000, "SPEC7_BAR1")) {
                dev_err(&pcidev->dev, "Can't request BAR0 iomem (0x%lx).\n",
                                bar_start);
                ret = -EIO;       
        }
	
        drvdata->bar1 = pcim_iomap(pcidev, 0, bar_len);
        if (!drvdata->bar1) {
                dev_err(&pcidev->dev, "BAR0 pcim_iomap() failed\n");
                ret = -EIO;      
        }
	

	/*Load PCIe FPGA second bitstream*/
        ret = spec7_pci_load_firmware(drvdata);
        if(ret){
                dev_err(&pcidev->dev, "firmware loading failed!\n");       
        }
	else {
		pr_info("firmware loaded at : %lx \n", bar_start);
	}

	/*mfd device*/
	return 0;
}



static void spec7_pci_remove(struct pci_dev *pcidev)
{

	struct spec7_drv *drvdata;
	dev_info(&pcidev->dev, "Removed SPEC7 driver");
	drvdata = (struct spec7_drv *)pci_get_drvdata(pcidev);
	pci_disable_device(pcidev);
}

static struct pci_driver spec7_pci_fw = {
        .name = "spec7_pcie",
        .id_table = pcie_ids,
        .probe = spec7_pci_probe,
        .remove =__exit_p( spec7_pci_remove),
};

module_pci_driver(spec7_pci_fw);

MODULE_DESCRIPTION("SPEC7 PCIe Device Driver");
MODULE_LICENSE("GPL v2");
 

