// SPDX-License-Identifier: GPL-2.0-or-later
/*
 * Copyright (C) 2019 CERN (www.cern.ch)
 * Author: Federico Vaga <federico.vaga@cern.ch>
 */

#include <linux/module.h>
#include<linux/moduleparam.h>
#include <linux/platform_device.h>
#include <linux/mfd/core.h>
#include <linux/mod_devicetable.h>

static char *drivers = "";
module_param(drivers, charp, 0444);
MODULE_PARM_DESC(drivers,
		 "Extra drivers to load: fd (fmc-fine-delay), tdc (fmc-tdc), fdtdc (fmc-fine-delay and fmc-tdc)\n");

enum wrtd_stf_dev_offsets {
	WRTD_STF_FDT_MEM_START = 0x00004000,
	WRTD_STF_FDT_MEM_END = 0x000041FF,
	WRTD_STF_TDC_MEM_START = 0x0000C000,
	WRTD_STF_TDC_MEM_END = 0x00001BFFF,
	WRTD_STF_TRTL_MEM_START = 0x0001C000,
	WRTD_STF_TRTL_MEM_END = 0x0003C000,
};

/* MFD devices */

static struct resource wrtd_stf_fdt_res[] = {
	{
		.name = "fmc-fdelay-tdc-mem",
		.flags = IORESOURCE_MEM,
		.start = WRTD_STF_FDT_MEM_START,
		.end = WRTD_STF_FDT_MEM_END,
	}, {
		.name = "fmc-fdelay-tdc-irq",
		.flags = IORESOURCE_IRQ | IORESOURCE_IRQ_HIGHLEVEL,
		.start = 1,
		.end = 1,
	},
};

static struct resource wrtd_stf_tdc_res[] = {
	{
		.name = "fmc-tdc-mem",
		.flags = IORESOURCE_MEM,
		.start = WRTD_STF_TDC_MEM_START,
		.end = WRTD_STF_TDC_MEM_END,
	}, {
		.name = "fmc-tdc-irq",
		.flags = IORESOURCE_IRQ | IORESOURCE_IRQ_HIGHLEVEL,
		.start = 0,
		.end = 0,
	},
};

static struct resource wrtd_stf_trtl_res[] = {
	{
		.name = "mock-turtle-mem",
		.flags = IORESOURCE_MEM,
		.start = WRTD_STF_TRTL_MEM_START,
		.end = WRTD_STF_TRTL_MEM_END,
	}, {
		.name = "mock-turtle-irq_in",
		.flags = IORESOURCE_IRQ | IORESOURCE_IRQ_HIGHLEVEL,
		.start = 2,
	}, {
		.name = "mock-turtle-irq_out",
		.flags = IORESOURCE_IRQ | IORESOURCE_IRQ_HIGHLEVEL,
		.start = 3,
	}, {
		.name = "mock-turtle-irq_con",
		.flags = IORESOURCE_IRQ | IORESOURCE_IRQ_HIGHLEVEL,
		.start = 4,
	}, {
		.name = "mock-turtle-irq_not",
		.flags = IORESOURCE_IRQ | IORESOURCE_IRQ_HIGHLEVEL,
		.start = 5,
	},
};

#define MFD_CELL_TRTL { \
		.name = "mock-turtle", \
		.platform_data = NULL, \
		.pdata_size = 0, \
		.num_resources = ARRAY_SIZE(wrtd_stf_trtl_res), \
		.resources = wrtd_stf_trtl_res, \
	}
#define MFD_CELL_FDT { \
		.name = "fmc-fdelay-tdc", \
		.platform_data = NULL, \
		.pdata_size = 0, \
		.num_resources = ARRAY_SIZE(wrtd_stf_fdt_res), \
		.resources = wrtd_stf_fdt_res, \
	}
#define MFD_CELL_TDC { \
		.name = "fmc-tdc", \
		.platform_data = NULL, \
		.pdata_size = 0, \
		.num_resources = ARRAY_SIZE(wrtd_stf_tdc_res), \
		.resources = wrtd_stf_tdc_res, \
	}

static const struct mfd_cell __wrtd_stf_mfd_devs_base[] = {
	MFD_CELL_TRTL,
};
static const struct mfd_cell __wrtd_stf_mfd_devs_fdt[] = {
	MFD_CELL_TRTL,
	MFD_CELL_FDT,
};
static const struct mfd_cell __wrtd_stf_mfd_devs_tdc[] = {
	MFD_CELL_TRTL,
	MFD_CELL_TDC,
};
static const struct mfd_cell __wrtd_stf_mfd_devs_fdtdc[] = {
	MFD_CELL_TRTL,
	MFD_CELL_FDT,
	MFD_CELL_TDC,
};

static const struct mfd_cell *wrtd_stf_mfd_cells(const char *extra)
{
	if (strncmp("fdtdc", extra, 5) == 0)
		return __wrtd_stf_mfd_devs_fdtdc;
	else if (strncmp("tdc", extra, 3) == 0)
		return __wrtd_stf_mfd_devs_tdc;
	else if (strncmp("fd", extra, 2) == 0)
		return __wrtd_stf_mfd_devs_fdt;
	else
		return __wrtd_stf_mfd_devs_base;
}
static unsigned int wrtd_stf_mfd_count(const char *extra)
{
	int count = 1;

	if (strncmp("fd", extra, 2) == 0)
		count++;
	if (strncmp("tdc", extra, 3) == 0 ||
	    strncmp("fdtdc", extra, 5) == 0)
		count++;
	return count;
}

static int wrtd_stf_probe(struct platform_device *pdev)
{
	struct resource *rmem;
	int irq;

	rmem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	if (!rmem) {
		dev_err(&pdev->dev, "Missing memory resource\n");
		return -EINVAL;
	}

	irq = platform_get_irq(pdev, 0);
	if (irq < 0) {
		dev_err(&pdev->dev, "Missing IRQ number\n");
		return -EINVAL;
	}

	/*
	 * We know that this design uses the HTVIC IRQ controller.
	 * This IRQ controller has a linear mapping, so it is enough
	 * to give the first one as input
	 */

	return mfd_add_devices(&pdev->dev, PLATFORM_DEVID_AUTO,
			       wrtd_stf_mfd_cells(drivers),
			       wrtd_stf_mfd_count(drivers),
			       rmem, irq, NULL);
}

static int wrtd_stf_remove(struct platform_device *pdev)
{
	mfd_remove_devices(&pdev->dev);

	return 0;
}

/**
 * List of supported platform
 */
enum wrtd_stf_version {
	WRTD_STF_VER = 0,
};

static const struct platform_device_id wrtd_stf_id_table[] = {
	{
		.name = "wrtd-stf",
		.driver_data = WRTD_STF_VER,
	}, {
		.name = "id:000010DC57544E02",
		.driver_data = WRTD_STF_VER,
	}, {
		.name = "id:000010dc57544e02",
		.driver_data = WRTD_STF_VER,
	},
	{},
};

static struct platform_driver wrtd_stf_driver = {
	.driver = {
		.name = "wrtd-stf",
		.owner = THIS_MODULE,
	},
	.id_table = wrtd_stf_id_table,
	.probe = wrtd_stf_probe,
	.remove = wrtd_stf_remove,
};
module_platform_driver(wrtd_stf_driver);

MODULE_AUTHOR("Federico Vaga <federico.vaga@cern.ch>");
MODULE_LICENSE("GPL");
MODULE_VERSION(DRV_VERSION);
MODULE_DESCRIPTION("Driver for the WRTD SVEC TDC Fine-Delay");
MODULE_DEVICE_TABLE(platform, wrtd_stf_id_table);

MODULE_SOFTDEP("pre: svec_fmc_carrier mockturtle");
