Contributors: 1
Author Tokens Token Proportion Commits Commit Proportion
Martin Tůma 3420 100.00% 2 100.00%
Total 3420 2


// SPDX-License-Identifier: GPL-2.0-only
/*
 * This is the driver for the MGB4 video grabber card by Digiteq Automotive.
 *
 * Copyright (C) 2021-2023 Digiteq Automotive
 *     author: Martin Tuma <martin.tuma@digiteqautomotive.com>
 *
 * This is the main driver module. The DMA, I2C and SPI sub-drivers are
 * initialized here and the input/output v4l2 devices are created.
 *
 * The mgb4 card uses different expansion modules for different video sources
 * (GMSL and FPDL3 for now) so in probe() we detect the module type based on
 * what we see on the I2C bus and check if it matches the FPGA bitstream (there
 * are different bitstreams for different expansion modules). When no expansion
 * module is present, we still let the driver initialize to allow flashing of
 * the FPGA firmware using the SPI FLASH device. No v4l2 video devices are
 * created in this case.
 */

#include <linux/types.h>
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/platform_device.h>
#include <linux/clk.h>
#include <linux/clk-provider.h>
#include <linux/clkdev.h>
#include <linux/i2c.h>
#include <linux/delay.h>
#include <linux/dma/amd_xdma.h>
#include <linux/platform_data/amd_xdma.h>
#include <linux/spi/xilinx_spi.h>
#include <linux/mtd/mtd.h>
#include <linux/hwmon.h>
#include <linux/debugfs.h>
#include "mgb4_dma.h"
#include "mgb4_i2c.h"
#include "mgb4_sysfs.h"
#include "mgb4_vout.h"
#include "mgb4_vin.h"
#include "mgb4_trigger.h"
#include "mgb4_core.h"

#define MGB4_USER_IRQS 16

#define DIGITEQ_VID 0x1ed8
#define T100_DID    0x0101
#define T200_DID    0x0201

ATTRIBUTE_GROUPS(mgb4_pci);

static int flashid;

static struct xdma_chan_info h2c_chan_info = {
	.dir = DMA_MEM_TO_DEV,
};

static struct xdma_chan_info c2h_chan_info = {
	.dir = DMA_DEV_TO_MEM,
};

static struct xspi_platform_data spi_platform_data = {
	.num_chipselect = 1,
	.bits_per_word = 8
};

static const struct i2c_board_info extender_info = {
	I2C_BOARD_INFO("extender", 0x21)
};

#if IS_REACHABLE(CONFIG_HWMON)
static umode_t temp_is_visible(const void *data, enum hwmon_sensor_types type,
			       u32 attr, int channel)
{
	if (type == hwmon_temp &&
	    (attr == hwmon_temp_input || attr == hwmon_temp_label))
		return 0444;
	else
		return 0;
}

static int temp_read(struct device *dev, enum hwmon_sensor_types type, u32 attr,
		     int channel, long *val)
{
	struct mgb4_dev *mgbdev = dev_get_drvdata(dev);
	u32 val10, raw;

	if (type != hwmon_temp || attr != hwmon_temp_input)
		return -EOPNOTSUPP;

	raw = mgb4_read_reg(&mgbdev->video, 0xD0);
	/* register value -> Celsius degrees formula given by Xilinx */
	val10 = ((((raw >> 20) & 0xFFF) * 503975) - 1118822400) / 409600;
	*val = val10 * 100;

	return 0;
}

static int temp_read_string(struct device *dev, enum hwmon_sensor_types type,
			    u32 attr, int channel, const char **str)
{
	if (type != hwmon_temp || attr != hwmon_temp_label)
		return -EOPNOTSUPP;

	*str = "FPGA Temperature";

	return 0;
}

static const struct hwmon_ops temp_ops = {
	.is_visible = temp_is_visible,
	.read = temp_read,
	.read_string = temp_read_string
};

static const struct hwmon_channel_info *temp_channel_info[] = {
	HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT | HWMON_T_LABEL),
	NULL
};

static const struct hwmon_chip_info temp_chip_info = {
	.ops = &temp_ops,
	.info = temp_channel_info,
};
#endif

static int match_i2c_adap(struct device *dev, void *data)
{
	return i2c_verify_adapter(dev) ? 1 : 0;
}

static struct i2c_adapter *get_i2c_adap(struct platform_device *pdev)
{
	struct device *dev;

	mutex_lock(&pdev->dev.mutex);
	dev = device_find_child(&pdev->dev, NULL, match_i2c_adap);
	mutex_unlock(&pdev->dev.mutex);

	return dev ? to_i2c_adapter(dev) : NULL;
}

static int match_spi_adap(struct device *dev, void *data)
{
	return to_spi_device(dev) ? 1 : 0;
}

static struct spi_master *get_spi_adap(struct platform_device *pdev)
{
	struct device *dev;

	mutex_lock(&pdev->dev.mutex);
	dev = device_find_child(&pdev->dev, NULL, match_spi_adap);
	mutex_unlock(&pdev->dev.mutex);

	return dev ? container_of(dev, struct spi_master, dev) : NULL;
}

static int init_spi(struct mgb4_dev *mgbdev, u32 devid)
{
	struct resource spi_resources[] = {
		{
			.start	= 0x400,
			.end	= 0x47f,
			.flags	= IORESOURCE_MEM,
			.name	= "io-memory",
		},
		{
			.start	= 14,
			.end	= 14,
			.flags	= IORESOURCE_IRQ,
			.name	= "irq",
		},
	};
	struct spi_board_info spi_info = {
		.max_speed_hz = 10000000,
		.modalias = "m25p80",
		.chip_select = 0,
		.mode = SPI_MODE_3,
	};
	struct pci_dev *pdev = mgbdev->pdev;
	struct device *dev = &pdev->dev;
	struct spi_master *master;
	struct spi_device *spi_dev;
	u32 irq;
	int rv, id;
	resource_size_t mapbase = pci_resource_start(pdev, MGB4_MGB4_BAR_ID);

	request_module("platform:xilinx_spi");

	irq = xdma_get_user_irq(mgbdev->xdev, 14);
	xdma_enable_user_irq(mgbdev->xdev, irq);

	spi_resources[0].parent = &pdev->resource[MGB4_MGB4_BAR_ID];
	spi_resources[0].start += mapbase;
	spi_resources[0].end += mapbase;
	spi_resources[1].start = irq;
	spi_resources[1].end = irq;

	id = pci_dev_id(pdev);
	mgbdev->spi_pdev = platform_device_register_resndata(dev, "xilinx_spi",
							     id, spi_resources,
							     ARRAY_SIZE(spi_resources),
							     &spi_platform_data,
							     sizeof(spi_platform_data));
	if (IS_ERR(mgbdev->spi_pdev)) {
		dev_err(dev, "failed to register SPI device\n");
		return PTR_ERR(mgbdev->spi_pdev);
	}

	master = get_spi_adap(mgbdev->spi_pdev);
	if (!master) {
		dev_err(dev, "failed to get SPI adapter\n");
		rv = -EINVAL;
		goto err_pdev;
	}

	snprintf(mgbdev->fw_part_name, sizeof(mgbdev->fw_part_name),
		 "mgb4-fw.%d", flashid);
	mgbdev->partitions[0].name = mgbdev->fw_part_name;
	if (devid == T200_DID) {
		mgbdev->partitions[0].size = 0x950000;
		mgbdev->partitions[0].offset = 0x1000000;
	} else {
		mgbdev->partitions[0].size = 0x400000;
		mgbdev->partitions[0].offset = 0x400000;
	}
	mgbdev->partitions[0].mask_flags = 0;

	snprintf(mgbdev->data_part_name, sizeof(mgbdev->data_part_name),
		 "mgb4-data.%d", flashid);
	mgbdev->partitions[1].name = mgbdev->data_part_name;
	mgbdev->partitions[1].size = 0x10000;
	mgbdev->partitions[1].offset = 0xFF0000;
	mgbdev->partitions[1].mask_flags = MTD_CAP_NORFLASH;

	snprintf(mgbdev->flash_name, sizeof(mgbdev->flash_name),
		 "mgb4-flash.%d", flashid);
	mgbdev->flash_data.name = mgbdev->flash_name;
	mgbdev->flash_data.parts = mgbdev->partitions;
	mgbdev->flash_data.nr_parts = ARRAY_SIZE(mgbdev->partitions);
	mgbdev->flash_data.type = "spi-nor";

	spi_info.platform_data = &mgbdev->flash_data;

	spi_dev = spi_new_device(master, &spi_info);
	put_device(&master->dev);
	if (!spi_dev) {
		dev_err(dev, "failed to create MTD device\n");
		rv = -EINVAL;
		goto err_pdev;
	}

	return 0;

err_pdev:
	platform_device_unregister(mgbdev->spi_pdev);

	return rv;
}

static void free_spi(struct mgb4_dev *mgbdev)
{
	platform_device_unregister(mgbdev->spi_pdev);
}

static int init_i2c(struct mgb4_dev *mgbdev)
{
	struct resource i2c_resources[] = {
		{
			.start	= 0x200,
			.end	= 0x3ff,
			.flags	= IORESOURCE_MEM,
			.name	= "io-memory",
		},
		{
			.start	= 15,
			.end	= 15,
			.flags	= IORESOURCE_IRQ,
			.name	= "irq",
		},
	};
	struct pci_dev *pdev = mgbdev->pdev;
	struct device *dev = &pdev->dev;
	char clk_name[16];
	u32 irq;
	int rv, id;
	resource_size_t mapbase = pci_resource_start(pdev, MGB4_MGB4_BAR_ID);

	request_module("platform:xiic-i2c");

	irq = xdma_get_user_irq(mgbdev->xdev, 15);
	xdma_enable_user_irq(mgbdev->xdev, irq);

	i2c_resources[0].parent = &pdev->resource[MGB4_MGB4_BAR_ID];
	i2c_resources[0].start += mapbase;
	i2c_resources[0].end += mapbase;
	i2c_resources[1].start = irq;
	i2c_resources[1].end = irq;

	id = pci_dev_id(pdev);

	/* create dummy clock required by the xiic-i2c adapter */
	snprintf(clk_name, sizeof(clk_name), "xiic-i2c.%d", id);
	mgbdev->i2c_clk = clk_hw_register_fixed_rate(NULL, clk_name, NULL,
						     0, 125000000);
	if (IS_ERR(mgbdev->i2c_clk)) {
		dev_err(dev, "failed to register I2C clock\n");
		return PTR_ERR(mgbdev->i2c_clk);
	}
	mgbdev->i2c_cl = clkdev_hw_create(mgbdev->i2c_clk, NULL, "xiic-i2c.%d",
					  id);
	if (!mgbdev->i2c_cl) {
		dev_err(dev, "failed to register I2C clockdev\n");
		rv = -ENOMEM;
		goto err_clk;
	}

	mgbdev->i2c_pdev = platform_device_register_resndata(dev, "xiic-i2c",
							     id, i2c_resources,
							     ARRAY_SIZE(i2c_resources),
							     NULL, 0);
	if (IS_ERR(mgbdev->i2c_pdev)) {
		dev_err(dev, "failed to register I2C device\n");
		rv = PTR_ERR(mgbdev->i2c_pdev);
		goto err_clkdev;
	}

	mgbdev->i2c_adap = get_i2c_adap(mgbdev->i2c_pdev);
	if (!mgbdev->i2c_adap) {
		dev_err(dev, "failed to get I2C adapter\n");
		rv = -EINVAL;
		goto err_pdev;
	}

	mutex_init(&mgbdev->i2c_lock);

	return 0;

err_pdev:
	platform_device_unregister(mgbdev->i2c_pdev);
err_clkdev:
	clkdev_drop(mgbdev->i2c_cl);
err_clk:
	clk_hw_unregister(mgbdev->i2c_clk);

	return rv;
}

static void free_i2c(struct mgb4_dev *mgbdev)
{
	put_device(&mgbdev->i2c_adap->dev);
	platform_device_unregister(mgbdev->i2c_pdev);
	clkdev_drop(mgbdev->i2c_cl);
	clk_hw_unregister(mgbdev->i2c_clk);
}

static int get_serial_number(struct mgb4_dev *mgbdev)
{
	struct device *dev = &mgbdev->pdev->dev;
	struct mtd_info *mtd;
	size_t rs;
	int rv;

	mgbdev->serial_number = 0;

	mtd = get_mtd_device_nm(mgbdev->data_part_name);
	if (IS_ERR(mtd)) {
		dev_warn(dev, "failed to get data MTD device\n");
		return -ENOENT;
	}
	rv = mtd_read(mtd, 0, sizeof(mgbdev->serial_number), &rs,
		      (u_char *)&mgbdev->serial_number);
	put_mtd_device(mtd);
	if (rv < 0 || rs != sizeof(mgbdev->serial_number)) {
		dev_warn(dev, "error reading MTD device\n");
		return -EIO;
	}

	return 0;
}

static int get_module_version(struct mgb4_dev *mgbdev)
{
	struct device *dev = &mgbdev->pdev->dev;
	struct mgb4_i2c_client extender;
	s32 version;
	u32 fw_version;
	int rv;

	rv = mgb4_i2c_init(&extender, mgbdev->i2c_adap, &extender_info, 8);
	if (rv < 0) {
		dev_err(dev, "failed to create extender I2C device\n");
		return rv;
	}
	version = mgb4_i2c_read_byte(&extender, 0x00);
	mgb4_i2c_free(&extender);
	if (version < 0) {
		dev_err(dev, "error reading module version\n");
		return -EIO;
	}

	mgbdev->module_version = ~((u32)version) & 0xff;
	if (!(MGB4_IS_FPDL3(mgbdev) || MGB4_IS_GMSL(mgbdev))) {
		dev_err(dev, "unknown module type\n");
		return -EINVAL;
	}
	fw_version = mgb4_read_reg(&mgbdev->video, 0xC4);
	if (fw_version >> 24 != mgbdev->module_version >> 4) {
		dev_err(dev, "module/firmware type mismatch\n");
		return -EINVAL;
	}

	dev_info(dev, "%s module detected\n",
		 MGB4_IS_FPDL3(mgbdev) ? "FPDL3" : "GMSL");

	return 0;
}

static int map_regs(struct pci_dev *pdev, struct resource *res,
		    struct mgb4_regs *regs)
{
	int rv;
	resource_size_t mapbase = pci_resource_start(pdev, MGB4_MGB4_BAR_ID);

	res->start += mapbase;
	res->end += mapbase;

	rv = mgb4_regs_map(res, regs);
	if (rv < 0) {
		dev_err(&pdev->dev, "failed to map %s registers\n", res->name);
		return rv;
	}

	return 0;
}

static int init_xdma(struct mgb4_dev *mgbdev)
{
	struct xdma_platdata data;
	struct resource res[2] = { 0 };
	struct dma_slave_map *map;
	struct pci_dev *pdev = mgbdev->pdev;
	struct device *dev = &pdev->dev;
	int i;

	res[0].start = pci_resource_start(pdev, MGB4_XDMA_BAR_ID);
	res[0].end = pci_resource_end(pdev, MGB4_XDMA_BAR_ID);
	res[0].flags = IORESOURCE_MEM;
	res[0].parent = &pdev->resource[MGB4_XDMA_BAR_ID];
	res[1].start = pci_irq_vector(pdev, 0);
	res[1].end = res[1].start + MGB4_VIN_DEVICES + MGB4_VOUT_DEVICES
		     + MGB4_USER_IRQS - 1;
	res[1].flags = IORESOURCE_IRQ;

	data.max_dma_channels = MGB4_VIN_DEVICES + MGB4_VOUT_DEVICES;
	data.device_map = mgbdev->slave_map;
	data.device_map_cnt = MGB4_VIN_DEVICES + MGB4_VOUT_DEVICES;

	for (i = 0; i < MGB4_VIN_DEVICES; i++) {
		sprintf(mgbdev->channel_names[i], "c2h%d", i);
		map = &data.device_map[i];
		map->slave = mgbdev->channel_names[i];
		map->devname = dev_name(dev);
		map->param = XDMA_FILTER_PARAM(&c2h_chan_info);
	}
	for (i = 0; i < MGB4_VOUT_DEVICES; i++) {
		sprintf(mgbdev->channel_names[i + MGB4_VIN_DEVICES], "h2c%d", i);
		map = &data.device_map[i + MGB4_VIN_DEVICES];
		map->slave = mgbdev->channel_names[i + MGB4_VIN_DEVICES];
		map->devname = dev_name(dev);
		map->param = XDMA_FILTER_PARAM(&h2c_chan_info);
	}

	mgbdev->xdev = platform_device_register_resndata(dev, "xdma",
							 PLATFORM_DEVID_AUTO, res,
							 2, &data, sizeof(data));
	if (IS_ERR(mgbdev->xdev)) {
		dev_err(dev, "failed to register XDMA device\n");
		return PTR_ERR(mgbdev->xdev);
	}

	return 0;
}

static void free_xdma(struct mgb4_dev *mgbdev)
{
	platform_device_unregister(mgbdev->xdev);
}

static int mgb4_probe(struct pci_dev *pdev, const struct pci_device_id *id)
{
	int i, rv;
	struct mgb4_dev *mgbdev;
	struct resource video = {
		.start	= 0x0,
		.end	= 0x100,
		.flags	= IORESOURCE_MEM,
		.name	= "mgb4-video",
	};
	struct resource cmt = {
		.start	= 0x1000,
		.end	= 0x1800,
		.flags	= IORESOURCE_MEM,
		.name	= "mgb4-cmt",
	};
	int irqs = pci_msix_vec_count(pdev);

	mgbdev = kzalloc(sizeof(*mgbdev), GFP_KERNEL);
	if (!mgbdev)
		return -ENOMEM;

	mgbdev->pdev = pdev;
	pci_set_drvdata(pdev, mgbdev);

	/* PCIe related stuff */
	rv = pci_enable_device(pdev);
	if (rv) {
		dev_err(&pdev->dev, "error enabling PCI device\n");
		goto err_mgbdev;
	}

	rv = pcie_capability_set_word(pdev, PCI_EXP_DEVCTL, PCI_EXP_DEVCTL_RELAX_EN);
	if (rv)
		dev_warn(&pdev->dev, "error enabling PCIe relaxed ordering\n");
	rv = pcie_capability_set_word(pdev, PCI_EXP_DEVCTL, PCI_EXP_DEVCTL_EXT_TAG);
	if (rv)
		dev_warn(&pdev->dev, "error enabling PCIe extended tag field\n");
	rv = pcie_set_readrq(pdev, 512);
	if (rv)
		dev_warn(&pdev->dev, "error setting PCIe max. memory read size\n");
	pci_set_master(pdev);

	rv = pci_alloc_irq_vectors(pdev, irqs, irqs, PCI_IRQ_MSIX);
	if (rv < 0) {
		dev_err(&pdev->dev, "error allocating MSI-X IRQs\n");
		goto err_enable_pci;
	}

	rv = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(64));
	if (rv) {
		dev_err(&pdev->dev, "error setting DMA mask\n");
		goto err_enable_pci;
	}

	/* DMA + IRQ engine */
	rv = init_xdma(mgbdev);
	if (rv)
		goto err_alloc_irq;
	rv = mgb4_dma_channel_init(mgbdev);
	if (rv)
		goto err_dma_chan;

	/* mgb4 video registers */
	rv = map_regs(pdev, &video, &mgbdev->video);
	if (rv < 0)
		goto err_dma_chan;
	/* mgb4 cmt registers */
	rv = map_regs(pdev, &cmt, &mgbdev->cmt);
	if (rv < 0)
		goto err_video_regs;

	/* SPI FLASH */
	rv = init_spi(mgbdev, id->device);
	if (rv < 0)
		goto err_cmt_regs;

	/* I2C controller */
	rv = init_i2c(mgbdev);
	if (rv < 0)
		goto err_spi;

	/* PCI card related sysfs attributes */
	rv = device_add_groups(&pdev->dev, mgb4_pci_groups);
	if (rv < 0)
		goto err_i2c;

#if IS_REACHABLE(CONFIG_HWMON)
	/* HWmon (card temperature) */
	mgbdev->hwmon_dev = hwmon_device_register_with_info(&pdev->dev, "mgb4",
							    mgbdev,
							    &temp_chip_info,
							    NULL);
#endif

#ifdef CONFIG_DEBUG_FS
	mgbdev->debugfs = debugfs_create_dir(dev_name(&pdev->dev), NULL);
#endif

	/* Get card serial number. On systems without MTD flash support we may
	 * get an error thus ignore the return value. An invalid serial number
	 * should not break anything...
	 */
	if (get_serial_number(mgbdev) < 0)
		dev_warn(&pdev->dev, "error reading card serial number\n");

	/* Get module type. If no valid module is found, skip the video device
	 * creation part but do not exit with error to allow flashing the card.
	 */
	rv = get_module_version(mgbdev);
	if (rv < 0)
		goto exit;

	/* Video input v4l2 devices */
	for (i = 0; i < MGB4_VIN_DEVICES; i++)
		mgbdev->vin[i] = mgb4_vin_create(mgbdev, i);

	/* Video output v4l2 devices */
	for (i = 0; i < MGB4_VOUT_DEVICES; i++)
		mgbdev->vout[i] = mgb4_vout_create(mgbdev, i);

	/* Triggers */
	mgbdev->indio_dev = mgb4_trigger_create(mgbdev);

exit:
	flashid++;

	return 0;

err_i2c:
	free_i2c(mgbdev);
err_spi:
	free_spi(mgbdev);
err_cmt_regs:
	mgb4_regs_free(&mgbdev->cmt);
err_video_regs:
	mgb4_regs_free(&mgbdev->video);
err_dma_chan:
	mgb4_dma_channel_free(mgbdev);
	free_xdma(mgbdev);
err_alloc_irq:
	pci_disable_msix(pdev);
err_enable_pci:
	pci_disable_device(pdev);
err_mgbdev:
	kfree(mgbdev);

	return rv;
}

static void mgb4_remove(struct pci_dev *pdev)
{
	struct mgb4_dev *mgbdev = pci_get_drvdata(pdev);
	int i;

#ifdef CONFIG_DEBUG_FS
	debugfs_remove_recursive(mgbdev->debugfs);
#endif
#if IS_REACHABLE(CONFIG_HWMON)
	hwmon_device_unregister(mgbdev->hwmon_dev);
#endif

	if (mgbdev->indio_dev)
		mgb4_trigger_free(mgbdev->indio_dev);

	for (i = 0; i < MGB4_VOUT_DEVICES; i++)
		if (mgbdev->vout[i])
			mgb4_vout_free(mgbdev->vout[i]);
	for (i = 0; i < MGB4_VIN_DEVICES; i++)
		if (mgbdev->vin[i])
			mgb4_vin_free(mgbdev->vin[i]);

	device_remove_groups(&mgbdev->pdev->dev, mgb4_pci_groups);
	free_spi(mgbdev);
	free_i2c(mgbdev);
	mgb4_regs_free(&mgbdev->video);
	mgb4_regs_free(&mgbdev->cmt);

	mgb4_dma_channel_free(mgbdev);
	free_xdma(mgbdev);

	pci_disable_msix(mgbdev->pdev);
	pci_disable_device(mgbdev->pdev);

	kfree(mgbdev);
}

static const struct pci_device_id mgb4_pci_ids[] = {
	{ PCI_DEVICE(DIGITEQ_VID, T100_DID), },
	{ PCI_DEVICE(DIGITEQ_VID, T200_DID), },
	{ 0, }
};
MODULE_DEVICE_TABLE(pci, mgb4_pci_ids);

static struct pci_driver mgb4_pci_driver = {
	.name = KBUILD_MODNAME,
	.id_table = mgb4_pci_ids,
	.probe = mgb4_probe,
	.remove = mgb4_remove,
};

module_pci_driver(mgb4_pci_driver);

MODULE_AUTHOR("Digiteq Automotive s.r.o.");
MODULE_DESCRIPTION("Digiteq Automotive MGB4 Driver");
MODULE_LICENSE("GPL");
MODULE_SOFTDEP("pre: platform:xiic-i2c platform:xilinx_spi spi-nor");