Skip to content
Snippets Groups Projects
htvic.c 16.15 KiB
// SPDX-License-Identifier: GPL-2.0-or-later
/*
 * Copyright (c) 2016 CERN
 * Author: Federico Vaga <federico.vaga@cern.ch>
 *
 * Driver for the HT-VIC IRQ controller
 */

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/version.h>
#include <linux/irqdomain.h>
#include <linux/irq.h>
#include <linux/interrupt.h>
#include <linux/slab.h>
#include <linux/platform_device.h>
#include <linux/io.h>

#include "htvic.h"

static int htvic_dbg_info(struct seq_file *s, void *offset)
{
	struct htvic_device *htvic = s->private;
	int i;

	seq_printf(s, "%s:\n",dev_name(&htvic->pdev->dev));

	seq_printf(s, "  redirect: %d\n", platform_get_irq(htvic->pdev, 0));
	seq_printf(s, "  irq-mapping:\n");
	for (i = 0; i < VIC_MAX_VECTORS; ++i) {
		seq_printf(s, "    - hardware: %d\n", i);
		seq_printf(s, "      linux: %d\n",
			   irq_find_mapping(htvic->domain, i));
	}

	return 0;
}

static int htvic_dbg_info_open(struct inode *inode, struct file *file)
{
	struct htvic_device *htvic = inode->i_private;

	return single_open(file, htvic_dbg_info, htvic);
}

static const struct file_operations htvic_dbg_info_ops = {
	.owner = THIS_MODULE,
	.open  = htvic_dbg_info_open,
	.read = seq_read,
	.llseek = seq_lseek,
	.release = single_release,
};

static int htvic_dbg_reg(struct seq_file *s, void *offset)
{
	struct htvic_device *htvic = s->private;
	uint32_t val;
	void *addr;
	int i;

#define VIC_REG_N 8
	for (i = 0, addr = htvic->kernel_va; i < VIC_REG_N ; ++i, addr +=4) {
		val = htvic_ioread(htvic, addr);
		seq_printf(s, "%p = 0x%08x\n", addr, val);
	}

	for (i = 0, addr = htvic->kernel_va + VIC_IVT_RAM_BASE;
	     i < VIC_MAX_VECTORS; ++i, addr +=4) {
		val = htvic_ioread(htvic, addr);
		seq_printf(s, "%p = 0x%08x\n", addr, val);
	}

	return 0;
}

static int htvic_dbg_reg_open(struct inode *inode, struct file *file)
{
	struct htvic_device *htvic = inode->i_private;

	return single_open(file, htvic_dbg_reg, htvic);
}

static const struct file_operations htvic_dbg_reg_ops = {
	.owner = THIS_MODULE,
	.open  = htvic_dbg_reg_open,
	.read = seq_read,
	.llseek = seq_lseek,
	.release = single_release,
};

static ssize_t htvic_dbg_swirq_write(struct file *file,
				     const char __user *buf,
				     size_t count, loff_t *ppos)
{
	struct htvic_device *vic = file->private_data;

	htvic_iowrite(vic, 1, vic->kernel_va + VIC_REG_SWIR);

	return count;
}

static int htvic_dbg_swirq_open(struct inode *inode, struct file *file)
{
	struct htvic_device *htvic = inode->i_private;

	file->private_data = htvic;

	return 0;
}

static const struct file_operations htvic_dbg_swirq_ops = {
	.owner = THIS_MODULE,
	.open  = htvic_dbg_swirq_open,
	.write = htvic_dbg_swirq_write,
};

/**
 * It initializes the debugfs interface
 * @htvic: IRQ controler instance
 *
 * Return: 0 on success, otherwise a negative error number
 */
static int htvic_debug_init(struct htvic_device *htvic)
{
	htvic->dbg_dir = debugfs_create_dir(dev_name(&htvic->pdev->dev), NULL);
	if (IS_ERR_OR_NULL(htvic->dbg_dir)) {
		dev_err(&htvic->pdev->dev,
			"Cannot create debugfs directory (%ld)\n",
			PTR_ERR(htvic->dbg_dir));
		return PTR_ERR(htvic->dbg_dir);
	}

	htvic->dbg_info = debugfs_create_file(HTVIC_DBG_INFO_NAME, 0444,
					     htvic->dbg_dir, htvic,
					     &htvic_dbg_info_ops);
	if (IS_ERR_OR_NULL(htvic->dbg_info)) {
		dev_err(&htvic->pdev->dev,
			"Cannot create debugfs file \"%s\" (%ld)\n",
			HTVIC_DBG_INFO_NAME, PTR_ERR(htvic->dbg_info));
		return PTR_ERR(htvic->dbg_info);
	}

	htvic->dbg_reg = debugfs_create_file(HTVIC_DBG_REG_NAME, 0444,
					     htvic->dbg_dir, htvic,
					     &htvic_dbg_reg_ops);
	if (IS_ERR_OR_NULL(htvic->dbg_reg)) {
		dev_err(&htvic->pdev->dev,
			"Cannot create debugfs file \"%s\" (%ld)\n",
			HTVIC_DBG_REG_NAME, PTR_ERR(htvic->dbg_reg));
		return PTR_ERR(htvic->dbg_reg);
	}

	htvic->dbg_swirq = debugfs_create_file(HTVIC_DBG_SWIRQ_NAME, 0200,
					       htvic->dbg_dir, htvic,
					       &htvic_dbg_swirq_ops);
	if (IS_ERR_OR_NULL(htvic->dbg_swirq)) {
		dev_err(&htvic->pdev->dev,
			"Cannot create debugfs file \"%s\" (%ld)\n",
			HTVIC_DBG_SWIRQ_NAME, PTR_ERR(htvic->dbg_swirq));
		return PTR_ERR(htvic->dbg_reg);
	}

	return 0;
}

/**
 * It removes the debugfs interface
 * @htvic: IRQ controler instance
 */
static void htvic_debug_exit(struct htvic_device *htvic)
{
	if (htvic->dbg_dir)
		debugfs_remove_recursive(htvic->dbg_dir);
}

/**
 * End of interrupt for the VIC. In case of level interrupt,
 * there is no way to delegate this to the kernel
 */
static void htvic_eoi(struct irq_data *d)
{
	struct htvic_device *vic = irq_data_get_irq_chip_data(d);
	/*
	 * Any write operation acknowledges the pending interrupt.
	 * Then, VIC advances to another pending interrupt(s) or
	 * releases the master interrupt output.
	 */
	htvic_iowrite(vic, 1, vic->kernel_va + VIC_REG_EOIR);
}


static void htvic_mask_disable_reg(struct irq_data *d)
{
	struct htvic_device *htvic = irq_data_get_irq_chip_data(d);

	htvic_iowrite(htvic, 1 << d->hwirq,
		      htvic->kernel_va + VIC_REG_IDR);
}


static void htvic_unmask_enable_reg(struct irq_data *d)
{
	struct htvic_device *htvic = irq_data_get_irq_chip_data(d);

	htvic_iowrite(htvic, 1 << d->hwirq,
		      htvic->kernel_va + VIC_REG_IER);
}

static void htvic_irq_ack(struct irq_data *d)
{
}


/**
 *
 */
static unsigned int htvic_irq_startup(struct irq_data *d)
{
	struct htvic_device *vic = irq_data_get_irq_chip_data(d);
	int ret;

	ret = try_module_get(vic->pdev->dev.driver->owner);
	if (ret == 0) { /* 0 fail, 1 success */
		dev_err(&vic->pdev->dev,
			"Cannot pin the \"%s\" driver. Something really wrong is going on\n",
			vic->pdev->dev.driver->name);
		return 1;
	}

	htvic_unmask_enable_reg(d);
	return 0;
}


/**
 * Executed when a driver does `free_irq()`.
 */
static void htvic_irq_shutdown(struct irq_data *d)
{
	struct htvic_device *vic = irq_data_get_irq_chip_data(d);

	htvic_mask_disable_reg(d);
	module_put(vic->pdev->dev.driver->owner);
}

static int htvic_irq_set_type(struct irq_data *d, unsigned int flow_type)
{
	struct htvic_device *vic = irq_data_get_irq_chip_data(d);

	/* We support only levels */
	if (!(flow_type & IRQ_TYPE_LEVEL_MASK)) {
		dev_err(&vic->pdev->dev,
		       "%s: unsopported type 0x%x\n", __func__, flow_type);
		return -EINVAL;
	}

	return IRQ_SET_MASK_OK;
}

static struct irq_chip htvic_chip = {
	.name = "HT-VIC",
	.irq_startup = htvic_irq_startup,
	.irq_shutdown = htvic_irq_shutdown,
	.irq_ack  = htvic_irq_ack,
	.irq_eoi = htvic_eoi,
	.irq_mask_ack = htvic_mask_disable_reg,
	.irq_mask = htvic_mask_disable_reg,
	.irq_unmask = htvic_unmask_enable_reg,
	.irq_set_type = htvic_irq_set_type,
};

#if LINUX_VERSION_CODE >= KERNEL_VERSION(4,7,0)
static int htvic_irq_domain_select(struct irq_domain *d, struct irq_fwspec *fwspec,
		      enum irq_domain_bus_token bus_token)
{
	struct htvic_device *htvic = d->host_data;
	/*
	 * FIXME this should point to htvic->pdev->dev.parent. Today it is not
	 * a problem for CERN-like installations, so we leave it like this
	 * so that the WR-stating kit works.
	 */
	struct device *dev = &htvic->pdev->dev;
	struct device *req_dev;

	if(fwspec->param_count != 2)
		return 0;

	req_dev = (struct device *) ((((unsigned long) fwspec->param[0]) << 32) |
		(((unsigned long) fwspec->param[1]) & 0xFFFFFFFF));

	return (dev == req_dev);
}
#endif

/**
 * Given the hardware IRQ and the Linux IRQ number (virtirq), configure the
 * Linux IRQ number in order to handle properly the incoming interrupts
 * on the hardware IRQ line.
 */
static int htvic_irq_domain_map(struct irq_domain *h,
				unsigned int virtirq,
				irq_hw_number_t hwirq)
{
	struct htvic_device *htvic = h->host_data;

	irq_set_chip_data(virtirq, htvic);
	irq_set_chip(virtirq, &htvic_chip);
	irq_set_handler(virtirq, handle_level_irq); /* not really used now */
	/* all handlers are directly nested */
	irq_set_nested_thread(virtirq, 1);

	/*
	 * It MUST be no-thread because the VIC EOI must occur AFTER
	 * the device handler ack its signal. Any way the interrupt from
	 * the carrier is already threaded (most likely, if not we will
	 * see problems)
	 */

	return 0;
}


static struct irq_domain_ops htvic_irq_domain_ops = {
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4,7,0)
	.select = htvic_irq_domain_select,
#endif
	.map = htvic_irq_domain_map,
};


/**
 * Mapping of HTVIC irqs to Linux irqs using linear IRQ domain
 */
static int htvic_irq_mapping(struct htvic_device *htvic)
{
	int i, irq;

	htvic->domain = irq_domain_add_linear((void *)&htvic->pdev->dev,
					      VIC_MAX_VECTORS,
					      &htvic_irq_domain_ops, htvic);
	if (!htvic->domain)
		return -ENOMEM;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,11,0)
	htvic->domain->name = kasprintf(GFP_KERNEL, "%s",
					dev_name(&htvic->pdev->dev));
#endif

	/* Create the mapping between HW irq and virtual IRQ number */
	for (i = 0; i < VIC_MAX_VECTORS; ++i) {
		htvic->hwid[i] = htvic_ioread(htvic, htvic->kernel_va +
					      VIC_IVT_RAM_BASE + 4 * i);
		htvic_iowrite(htvic, i,
			      htvic->kernel_va + VIC_IVT_RAM_BASE + 4 * i);

		irq = irq_create_mapping(htvic->domain, i);
		if (irq <= 0)
			goto out;
	}


	return 0;

out:
	irq_domain_remove(htvic->domain);
	return -EPERM;
}

/**
 * Check if the platform is providing all the necessary information
 * for the HTVIC to work properly.
 *
 * The HTVIC needs the following informations:
 * - a Linux IRQ number where it should attach itself
 * - a virtual address where to find the component
 */
static int htvic_validation(struct platform_device *pdev)
{
	struct resource *r;

	r = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
	if (!r) {
		dev_err(&pdev->dev, "Carrier IRQ number is missing\n");
		return -EINVAL;
	}

	if (!(r->flags & (IORESOURCE_IRQ_HIGHEDGE |
			  IORESOURCE_IRQ_LOWEDGE |
			  IORESOURCE_IRQ_HIGHLEVEL |
			  IORESOURCE_IRQ_LOWLEVEL))) {
		dev_err(&pdev->dev,
			"Edge/Level High/Low information missing\n");
		return -EINVAL;
	}

	r = platform_get_resource(pdev, IORESOURCE_MEM, HTVIC_MEM_BASE);
	if (!r) {
		dev_err(&pdev->dev, "VIC base address is missing\n");
		return -EINVAL;
	}

	return 0;
}


/**
 * It acks any pending interrupt in order to avoid to bring the HTVIC
 * to a stable status (possibly)
 */
static inline void htvic_ack_pending(struct htvic_device *htvic)
{
	while (htvic_ioread(htvic, htvic->kernel_va + VIC_REG_RISR))
		htvic_iowrite(htvic, 1, htvic->kernel_va + VIC_REG_EOIR);
}


/**
 * This is the place to re-route interrupts to the proper handler
 */
static irqreturn_t htvic_handler(int irq, void *arg)
{
	struct htvic_device *htvic = arg;
	u32 risr;

	risr = htvic_ioread(htvic, htvic->kernel_va + VIC_REG_RISR);
	if (!risr) /* Nothing to do - not for us */
		return IRQ_NONE;

	do {
		unsigned int cascade_irq;
		uint32_t vect;

		vect = htvic_ioread(htvic, htvic->kernel_va + VIC_REG_VAR) & 0xFF;
		if (WARN(vect >= VIC_MAX_VECTORS,
			 "Invalid vector number %d\n", vect))
			return IRQ_HANDLED;

		cascade_irq = irq_find_mapping(htvic->domain, vect);
		dev_dbg(&htvic->pdev->dev, "Raw: 0x%x Vect: 0x%x, IRQ: %d\n",
			risr, vect, cascade_irq);
		risr &= ~(1 << vect);
		/*
		 * Ok, now we execute the handler for the given IRQ. Please
		 * note that this is not the action requested by the device driver
		 * but it is the handler defined during the IRQ mapping
		 */
		handle_nested_irq(cascade_irq);

		/**
		 * ATTENTION here the ack is actually an EOI.The kernel
		 * does not export the handle_edge_eoi_irq() handler which
		 * is the one we need here. The kernel offers us the
		 * handle_edge_irq() which use only the ack() function.
		 * So what actually we need to to is to call the ack
		 * function but not the eoi function.
		 */
		htvic_eoi(irq_get_irq_data(cascade_irq));
		/*
		 * Read the RISR register again (it could be any other
		 * register) to introduce a delay equivalent to the time
		 * necessary for the VIC to propagate the IRQ status line
		 * to the processor.
		 */
		htvic_ioread(htvic, htvic->kernel_va + VIC_REG_RISR);
	} while(risr);

	return IRQ_HANDLED;
}


/**
 * Create a new instance for this driver.
 */
static int htvic_probe(struct platform_device *pdev)
{
	struct htvic_device *htvic;
	const struct resource *r;
	unsigned long irq_flags = 0;
	uint32_t ctl;
	int ret;

	ret = htvic_validation(pdev);
	if (ret)
		return ret;

	htvic = kzalloc(sizeof(struct htvic_device), GFP_KERNEL);
	if (!htvic)
		return -ENOMEM;
	dev_set_drvdata(&pdev->dev, htvic);
	htvic->pdev = pdev;
	/*
	 * TODO theoretically speaking all the confguration should come
	 * from a platform_data structure. Since we do not have it yet,
	 * we proceed this way
	 */
	switch(pdev->id_entry->driver_data) {
	case HTVIC_VER_SPEC:
	case HTVIC_VER_WRSWI:
		htvic->memop.read = __htvic_ioread32;
		htvic->memop.write = __htvic_iowrite32;
		break;
	case HTVIC_VER_SVEC:
		htvic->memop.read = __htvic_ioread32be;
		htvic->memop.write = __htvic_iowrite32be;
		break;
	default:
		dev_err(&pdev->dev, "Can't identify memory operations\n");
		ret = -EINVAL;
		goto out_memop;
	}

	r = platform_get_resource(pdev, IORESOURCE_MEM, HTVIC_MEM_BASE);
	htvic->kernel_va = ioremap(r->start, resource_size(r));

	/* Disable the VIC during the configuration */
	htvic_iowrite(htvic, 0, htvic->kernel_va + VIC_REG_CTL);
	/* Disable also all interrupt lines */
	htvic_iowrite(htvic, ~0, htvic->kernel_va + VIC_REG_IDR);
	/* Ack any pending interrupt */
	htvic_ack_pending(htvic);

	ret = htvic_irq_mapping(htvic);
	if (ret)
		goto out_map;

	/* VIC configuration */
	ctl = 0;
	ctl |= VIC_CTL_ENABLE;
	irq_flags |= IRQF_SHARED;
	switch (pdev->id_entry->driver_data) {
	case HTVIC_VER_SPEC:	
		ctl |= VIC_CTL_POL;
		ctl |= VIC_CTL_EMU_EDGE;
		ctl |= VIC_CTL_EMU_LEN_W(250);
		irq_flags |= IRQF_TRIGGER_HIGH;
		break;
	case HTVIC_VER_SVEC:
		ctl |= VIC_CTL_POL;
		irq_flags |= IRQF_TRIGGER_HIGH;
		/* TODO what if we want and edge using the edge emulator? */
		break;
	case HTVIC_VER_WRSWI:
		break;
	default:
		goto out_ctl;
	}

	/*
	 * It depends on the platform and on the IRQ on which we are connecting
	 * but most likely our interrupt handler will be a thread
	 */
	htvic->irq = platform_get_irq(htvic->pdev, 0);
	ret = request_any_context_irq(htvic->irq,
				      htvic_handler, irq_flags,
				      dev_name(&pdev->dev),
				      htvic);
	if (ret < 0) {
		dev_err(&pdev->dev, "Can't request IRQ %d (%d)\n",
			platform_get_irq(htvic->pdev, 0), ret);
		goto out_req;
	}

	htvic_debug_init(htvic);
	htvic_iowrite(htvic, ctl, htvic->kernel_va + VIC_REG_CTL);

	return 0;

out_req:
out_ctl:
out_map:
out_memop:
	dev_set_drvdata(&pdev->dev, NULL);
	kfree(htvic);
	return ret;
}

/**
 * Unload the htvic driver from the platform
 */
static int htvic_remove(struct platform_device *pdev)
{
	struct htvic_device *htvic = dev_get_drvdata(&pdev->dev);
	/* struct irq_desc *desc = irq_to_desc(platform_get_irq(htvic->pdev, 0)); */
	int i;

	if (!htvic)
		return 0;

	htvic_debug_exit(htvic);

	/*
	 * Disable all interrupts to prevent spurious interrupt
	 * Disable also the HTVIC component for the very same reason,
	 * but this way on next instance even if we enable the VIC
	 * no interrupt will come unless configured.
	 */
	htvic_iowrite(htvic, ~0, htvic->kernel_va + VIC_REG_IDR);
	htvic_iowrite(htvic, 0, htvic->kernel_va + VIC_REG_CTL);

	/*
	 * Restore HTVIC vector table with it's original content
	 * Release Linux IRQ number
	 */
	for (i = 0; i < VIC_MAX_VECTORS; i++) {
		htvic_iowrite(htvic, htvic->hwid[i], htvic->kernel_va + VIC_IVT_RAM_BASE + 4 * i);
		irq_dispose_mapping(irq_find_mapping(htvic->domain, i));
	}


	free_irq(htvic->irq, htvic);

	/*
	 * Clear the memory and restore flags when needed
	 */
	irq_domain_remove(htvic->domain);
	kfree(htvic);
	dev_set_drvdata(&pdev->dev, NULL);

	return 0;
}


/**
 * List of supported platform
 */
static const struct platform_device_id htvic_id_table[] = {
	{	/* SPEC compatible */
		.name = "htvic-spec",
		.driver_data = HTVIC_VER_SPEC,
	}, {	/* SVEC compatible */
		.name = "htvic-svec",
		.driver_data = HTVIC_VER_SVEC,
	}, {
		.name = "htvic-wr-swi",
		.driver_data = HTVIC_VER_WRSWI,
	},
	{},
};


static struct platform_driver htvic_driver = {
	.driver = {
		.name = "htvic",
		.owner = THIS_MODULE,
	},
	.id_table = htvic_id_table,
	.probe = htvic_probe,
	.remove = htvic_remove,
};
module_platform_driver(htvic_driver);

MODULE_AUTHOR("Federico Vaga <federico.vaga@cern.ch>");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("CERN BECOHT VHDL Vector Interrupt Controller - HTVIC");
MODULE_DEVICE_TABLE(platform, htvic_id_table);

ADDITIONAL_VERSIONS;