/* 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/platform_data/i2c-ocores.h"
#include <linux/mfd/core.h>
#include <linux/firmware.h>

#include "spec7-core.h"
#define DRV_NAME "spec7-core"

/*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,
	}

}; 

struct i2c_board_info ocores_i2c_board_info[] = {
      {
              I2C_BOARD_INFO("24c1024", 0x50),
              
      }
};

static struct ocores_i2c_platform_data spec7_i2c_pdata = {
	.reg_shift = 2, /* 32bit aligned */
	.reg_io_width = 4,
	.clock_khz = SPEC7_FPGA_WB_CLK_KHZ,
	.big_endian = 0,
	.num_devices = 1,
	.devices    = ocores_i2c_board_info,
};

struct platform_device spec7_i2c = {
        .name ="ocores-i2c",
	.resource = spec7_i2c_resources,
	.dev = {
			.platform_data = &spec7_i2c_pdata,
	       },
};


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_i2c_init(struct pci_dev *pcidev)
{	
	struct platform_device *pdev =&spec7_i2c;
	struct resource res[ARRAY_SIZE(spec7_i2c_resources)];
	const unsigned int res_n = ARRAY_SIZE(spec7_i2c_resources);
	struct ocores_i2c_platform_data *pdata =  &spec7_i2c_pdata; 
	unsigned int addr=0, remap;	
	pdev = kzalloc(sizeof(*pdev), GFP_KERNEL);
	if(!pdev)
		return -ENOMEM;

	memcpy(&res, spec7_i2c_resources, sizeof(spec7_i2c_resources));
	res[0].start += addr;
	res[0].end += addr;
	remap = res[0].end  -  res[0].start + 1;
	spec7_i2c.resource = res;
	pdev = &spec7_i2c;
	
	pdev->dev.parent = &pcidev->dev;
	pdev->dev.platform_data = pdata;
	
	pdev = platform_device_register_resndata(pdev->dev.parent,
                                                 "ocores-i2c",
                                                 PLATFORM_DEVID_AUTO,
                                                 res, res_n,
                                                 &spec7_i2c_pdata, sizeof(spec7_i2c_pdata));
	
	return 0;
}


static int spec7_pci_probe(struct pci_dev *pcidev, const struct pci_device_id *id)
{
	int ret;
	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*/
	bar_start = pci_resource_start(pcidev, SPEC7_WR_BAR);
        bar_len = pci_resource_len(pcidev, SPEC7_WR_BAR);
        dev_info(&pcidev->dev,
                "SPEC7_WR_BAR 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,
                                0x3000, "SPEC7_WR_BAR")) {
                dev_err(&pcidev->dev, "Can't request BAR0 iomem (0x%lx).\n",
                                bar_start);
                ret = -EIO;       
        }
	
        drvdata->bar = pcim_iomap(pcidev, SPEC7_WR_BAR, bar_len);
        if (!drvdata->bar) {
                dev_err(&pcidev->dev, "BAR0 pcim_iomap() failed\n");
                ret = -EIO;      
        }
	
	/*I2C device*/
	
	ret = spec7_i2c_init(pcidev);
	if (ret <0){
		dev_err(&pcidev->dev, "I2C not initialized\n");
		return ret;
	}



	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_core = {
        .name = "spec7_pci_core",
        .id_table = pcie_ids,
        .probe = spec7_pci_probe,
        .remove =__exit_p( spec7_pci_remove),
};

module_pci_driver(spec7_core);
MODULE_DEVICE_TABLE(pci, pcie_ids);
MODULE_DESCRIPTION("SPEC7 PCIe Device Driver");
MODULE_LICENSE("GPL v2");
MODULE_SOFTDEP("pre: at24 i2c-core xdma"); 

