18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * Renesas RZ/A1 IRQC Driver
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci * Copyright (C) 2019 Glider bvba
68c2ecf20Sopenharmony_ci */
78c2ecf20Sopenharmony_ci
88c2ecf20Sopenharmony_ci#include <linux/err.h>
98c2ecf20Sopenharmony_ci#include <linux/init.h>
108c2ecf20Sopenharmony_ci#include <linux/interrupt.h>
118c2ecf20Sopenharmony_ci#include <linux/io.h>
128c2ecf20Sopenharmony_ci#include <linux/irqdomain.h>
138c2ecf20Sopenharmony_ci#include <linux/irq.h>
148c2ecf20Sopenharmony_ci#include <linux/module.h>
158c2ecf20Sopenharmony_ci#include <linux/of_irq.h>
168c2ecf20Sopenharmony_ci#include <linux/platform_device.h>
178c2ecf20Sopenharmony_ci#include <linux/slab.h>
188c2ecf20Sopenharmony_ci
198c2ecf20Sopenharmony_ci#include <dt-bindings/interrupt-controller/arm-gic.h>
208c2ecf20Sopenharmony_ci
218c2ecf20Sopenharmony_ci#define IRQC_NUM_IRQ		8
228c2ecf20Sopenharmony_ci
238c2ecf20Sopenharmony_ci#define ICR0			0	/* Interrupt Control Register 0 */
248c2ecf20Sopenharmony_ci
258c2ecf20Sopenharmony_ci#define ICR0_NMIL		BIT(15)	/* NMI Input Level (0=low, 1=high) */
268c2ecf20Sopenharmony_ci#define ICR0_NMIE		BIT(8)	/* Edge Select (0=falling, 1=rising) */
278c2ecf20Sopenharmony_ci#define ICR0_NMIF		BIT(1)	/* NMI Interrupt Request */
288c2ecf20Sopenharmony_ci
298c2ecf20Sopenharmony_ci#define ICR1			2	/* Interrupt Control Register 1 */
308c2ecf20Sopenharmony_ci
318c2ecf20Sopenharmony_ci#define ICR1_IRQS(n, sense)	((sense) << ((n) * 2))	/* IRQ Sense Select */
328c2ecf20Sopenharmony_ci#define ICR1_IRQS_LEVEL_LOW	0
338c2ecf20Sopenharmony_ci#define ICR1_IRQS_EDGE_FALLING	1
348c2ecf20Sopenharmony_ci#define ICR1_IRQS_EDGE_RISING	2
358c2ecf20Sopenharmony_ci#define ICR1_IRQS_EDGE_BOTH	3
368c2ecf20Sopenharmony_ci#define ICR1_IRQS_MASK(n)	ICR1_IRQS((n), 3)
378c2ecf20Sopenharmony_ci
388c2ecf20Sopenharmony_ci#define IRQRR			4	/* IRQ Interrupt Request Register */
398c2ecf20Sopenharmony_ci
408c2ecf20Sopenharmony_ci
418c2ecf20Sopenharmony_cistruct rza1_irqc_priv {
428c2ecf20Sopenharmony_ci	struct device *dev;
438c2ecf20Sopenharmony_ci	void __iomem *base;
448c2ecf20Sopenharmony_ci	struct irq_chip chip;
458c2ecf20Sopenharmony_ci	struct irq_domain *irq_domain;
468c2ecf20Sopenharmony_ci	struct of_phandle_args map[IRQC_NUM_IRQ];
478c2ecf20Sopenharmony_ci};
488c2ecf20Sopenharmony_ci
498c2ecf20Sopenharmony_cistatic struct rza1_irqc_priv *irq_data_to_priv(struct irq_data *data)
508c2ecf20Sopenharmony_ci{
518c2ecf20Sopenharmony_ci	return data->domain->host_data;
528c2ecf20Sopenharmony_ci}
538c2ecf20Sopenharmony_ci
548c2ecf20Sopenharmony_cistatic void rza1_irqc_eoi(struct irq_data *d)
558c2ecf20Sopenharmony_ci{
568c2ecf20Sopenharmony_ci	struct rza1_irqc_priv *priv = irq_data_to_priv(d);
578c2ecf20Sopenharmony_ci	u16 bit = BIT(irqd_to_hwirq(d));
588c2ecf20Sopenharmony_ci	u16 tmp;
598c2ecf20Sopenharmony_ci
608c2ecf20Sopenharmony_ci	tmp = readw_relaxed(priv->base + IRQRR);
618c2ecf20Sopenharmony_ci	if (tmp & bit)
628c2ecf20Sopenharmony_ci		writew_relaxed(GENMASK(IRQC_NUM_IRQ - 1, 0) & ~bit,
638c2ecf20Sopenharmony_ci			       priv->base + IRQRR);
648c2ecf20Sopenharmony_ci
658c2ecf20Sopenharmony_ci	irq_chip_eoi_parent(d);
668c2ecf20Sopenharmony_ci}
678c2ecf20Sopenharmony_ci
688c2ecf20Sopenharmony_cistatic int rza1_irqc_set_type(struct irq_data *d, unsigned int type)
698c2ecf20Sopenharmony_ci{
708c2ecf20Sopenharmony_ci	struct rza1_irqc_priv *priv = irq_data_to_priv(d);
718c2ecf20Sopenharmony_ci	unsigned int hw_irq = irqd_to_hwirq(d);
728c2ecf20Sopenharmony_ci	u16 sense, tmp;
738c2ecf20Sopenharmony_ci
748c2ecf20Sopenharmony_ci	switch (type & IRQ_TYPE_SENSE_MASK) {
758c2ecf20Sopenharmony_ci	case IRQ_TYPE_LEVEL_LOW:
768c2ecf20Sopenharmony_ci		sense = ICR1_IRQS_LEVEL_LOW;
778c2ecf20Sopenharmony_ci		break;
788c2ecf20Sopenharmony_ci
798c2ecf20Sopenharmony_ci	case IRQ_TYPE_EDGE_FALLING:
808c2ecf20Sopenharmony_ci		sense = ICR1_IRQS_EDGE_FALLING;
818c2ecf20Sopenharmony_ci		break;
828c2ecf20Sopenharmony_ci
838c2ecf20Sopenharmony_ci	case IRQ_TYPE_EDGE_RISING:
848c2ecf20Sopenharmony_ci		sense = ICR1_IRQS_EDGE_RISING;
858c2ecf20Sopenharmony_ci		break;
868c2ecf20Sopenharmony_ci
878c2ecf20Sopenharmony_ci	case IRQ_TYPE_EDGE_BOTH:
888c2ecf20Sopenharmony_ci		sense = ICR1_IRQS_EDGE_BOTH;
898c2ecf20Sopenharmony_ci		break;
908c2ecf20Sopenharmony_ci
918c2ecf20Sopenharmony_ci	default:
928c2ecf20Sopenharmony_ci		return -EINVAL;
938c2ecf20Sopenharmony_ci	}
948c2ecf20Sopenharmony_ci
958c2ecf20Sopenharmony_ci	tmp = readw_relaxed(priv->base + ICR1);
968c2ecf20Sopenharmony_ci	tmp &= ~ICR1_IRQS_MASK(hw_irq);
978c2ecf20Sopenharmony_ci	tmp |= ICR1_IRQS(hw_irq, sense);
988c2ecf20Sopenharmony_ci	writew_relaxed(tmp, priv->base + ICR1);
998c2ecf20Sopenharmony_ci	return 0;
1008c2ecf20Sopenharmony_ci}
1018c2ecf20Sopenharmony_ci
1028c2ecf20Sopenharmony_cistatic int rza1_irqc_alloc(struct irq_domain *domain, unsigned int virq,
1038c2ecf20Sopenharmony_ci			   unsigned int nr_irqs, void *arg)
1048c2ecf20Sopenharmony_ci{
1058c2ecf20Sopenharmony_ci	struct rza1_irqc_priv *priv = domain->host_data;
1068c2ecf20Sopenharmony_ci	struct irq_fwspec *fwspec = arg;
1078c2ecf20Sopenharmony_ci	unsigned int hwirq = fwspec->param[0];
1088c2ecf20Sopenharmony_ci	struct irq_fwspec spec;
1098c2ecf20Sopenharmony_ci	unsigned int i;
1108c2ecf20Sopenharmony_ci	int ret;
1118c2ecf20Sopenharmony_ci
1128c2ecf20Sopenharmony_ci	ret = irq_domain_set_hwirq_and_chip(domain, virq, hwirq, &priv->chip,
1138c2ecf20Sopenharmony_ci					    priv);
1148c2ecf20Sopenharmony_ci	if (ret)
1158c2ecf20Sopenharmony_ci		return ret;
1168c2ecf20Sopenharmony_ci
1178c2ecf20Sopenharmony_ci	spec.fwnode = &priv->dev->of_node->fwnode;
1188c2ecf20Sopenharmony_ci	spec.param_count = priv->map[hwirq].args_count;
1198c2ecf20Sopenharmony_ci	for (i = 0; i < spec.param_count; i++)
1208c2ecf20Sopenharmony_ci		spec.param[i] = priv->map[hwirq].args[i];
1218c2ecf20Sopenharmony_ci
1228c2ecf20Sopenharmony_ci	return irq_domain_alloc_irqs_parent(domain, virq, nr_irqs, &spec);
1238c2ecf20Sopenharmony_ci}
1248c2ecf20Sopenharmony_ci
1258c2ecf20Sopenharmony_cistatic int rza1_irqc_translate(struct irq_domain *domain,
1268c2ecf20Sopenharmony_ci			       struct irq_fwspec *fwspec, unsigned long *hwirq,
1278c2ecf20Sopenharmony_ci			       unsigned int *type)
1288c2ecf20Sopenharmony_ci{
1298c2ecf20Sopenharmony_ci	if (fwspec->param_count != 2 || fwspec->param[0] >= IRQC_NUM_IRQ)
1308c2ecf20Sopenharmony_ci		return -EINVAL;
1318c2ecf20Sopenharmony_ci
1328c2ecf20Sopenharmony_ci	*hwirq = fwspec->param[0];
1338c2ecf20Sopenharmony_ci	*type = fwspec->param[1];
1348c2ecf20Sopenharmony_ci	return 0;
1358c2ecf20Sopenharmony_ci}
1368c2ecf20Sopenharmony_ci
1378c2ecf20Sopenharmony_cistatic const struct irq_domain_ops rza1_irqc_domain_ops = {
1388c2ecf20Sopenharmony_ci	.alloc = rza1_irqc_alloc,
1398c2ecf20Sopenharmony_ci	.translate = rza1_irqc_translate,
1408c2ecf20Sopenharmony_ci};
1418c2ecf20Sopenharmony_ci
1428c2ecf20Sopenharmony_cistatic int rza1_irqc_parse_map(struct rza1_irqc_priv *priv,
1438c2ecf20Sopenharmony_ci			       struct device_node *gic_node)
1448c2ecf20Sopenharmony_ci{
1458c2ecf20Sopenharmony_ci	unsigned int imaplen, i, j, ret;
1468c2ecf20Sopenharmony_ci	struct device *dev = priv->dev;
1478c2ecf20Sopenharmony_ci	struct device_node *ipar;
1488c2ecf20Sopenharmony_ci	const __be32 *imap;
1498c2ecf20Sopenharmony_ci	u32 intsize;
1508c2ecf20Sopenharmony_ci
1518c2ecf20Sopenharmony_ci	imap = of_get_property(dev->of_node, "interrupt-map", &imaplen);
1528c2ecf20Sopenharmony_ci	if (!imap)
1538c2ecf20Sopenharmony_ci		return -EINVAL;
1548c2ecf20Sopenharmony_ci
1558c2ecf20Sopenharmony_ci	for (i = 0; i < IRQC_NUM_IRQ; i++) {
1568c2ecf20Sopenharmony_ci		if (imaplen < 3)
1578c2ecf20Sopenharmony_ci			return -EINVAL;
1588c2ecf20Sopenharmony_ci
1598c2ecf20Sopenharmony_ci		/* Check interrupt number, ignore sense */
1608c2ecf20Sopenharmony_ci		if (be32_to_cpup(imap) != i)
1618c2ecf20Sopenharmony_ci			return -EINVAL;
1628c2ecf20Sopenharmony_ci
1638c2ecf20Sopenharmony_ci		ipar = of_find_node_by_phandle(be32_to_cpup(imap + 2));
1648c2ecf20Sopenharmony_ci		if (ipar != gic_node) {
1658c2ecf20Sopenharmony_ci			of_node_put(ipar);
1668c2ecf20Sopenharmony_ci			return -EINVAL;
1678c2ecf20Sopenharmony_ci		}
1688c2ecf20Sopenharmony_ci
1698c2ecf20Sopenharmony_ci		imap += 3;
1708c2ecf20Sopenharmony_ci		imaplen -= 3;
1718c2ecf20Sopenharmony_ci
1728c2ecf20Sopenharmony_ci		ret = of_property_read_u32(ipar, "#interrupt-cells", &intsize);
1738c2ecf20Sopenharmony_ci		of_node_put(ipar);
1748c2ecf20Sopenharmony_ci		if (ret)
1758c2ecf20Sopenharmony_ci			return ret;
1768c2ecf20Sopenharmony_ci
1778c2ecf20Sopenharmony_ci		if (imaplen < intsize)
1788c2ecf20Sopenharmony_ci			return -EINVAL;
1798c2ecf20Sopenharmony_ci
1808c2ecf20Sopenharmony_ci		priv->map[i].args_count = intsize;
1818c2ecf20Sopenharmony_ci		for (j = 0; j < intsize; j++)
1828c2ecf20Sopenharmony_ci			priv->map[i].args[j] = be32_to_cpup(imap++);
1838c2ecf20Sopenharmony_ci
1848c2ecf20Sopenharmony_ci		imaplen -= intsize;
1858c2ecf20Sopenharmony_ci	}
1868c2ecf20Sopenharmony_ci
1878c2ecf20Sopenharmony_ci	return 0;
1888c2ecf20Sopenharmony_ci}
1898c2ecf20Sopenharmony_ci
1908c2ecf20Sopenharmony_cistatic int rza1_irqc_probe(struct platform_device *pdev)
1918c2ecf20Sopenharmony_ci{
1928c2ecf20Sopenharmony_ci	struct device *dev = &pdev->dev;
1938c2ecf20Sopenharmony_ci	struct device_node *np = dev->of_node;
1948c2ecf20Sopenharmony_ci	struct irq_domain *parent = NULL;
1958c2ecf20Sopenharmony_ci	struct device_node *gic_node;
1968c2ecf20Sopenharmony_ci	struct rza1_irqc_priv *priv;
1978c2ecf20Sopenharmony_ci	int ret;
1988c2ecf20Sopenharmony_ci
1998c2ecf20Sopenharmony_ci	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
2008c2ecf20Sopenharmony_ci	if (!priv)
2018c2ecf20Sopenharmony_ci		return -ENOMEM;
2028c2ecf20Sopenharmony_ci
2038c2ecf20Sopenharmony_ci	platform_set_drvdata(pdev, priv);
2048c2ecf20Sopenharmony_ci	priv->dev = dev;
2058c2ecf20Sopenharmony_ci
2068c2ecf20Sopenharmony_ci	priv->base = devm_platform_ioremap_resource(pdev, 0);
2078c2ecf20Sopenharmony_ci	if (IS_ERR(priv->base))
2088c2ecf20Sopenharmony_ci		return PTR_ERR(priv->base);
2098c2ecf20Sopenharmony_ci
2108c2ecf20Sopenharmony_ci	gic_node = of_irq_find_parent(np);
2118c2ecf20Sopenharmony_ci	if (gic_node)
2128c2ecf20Sopenharmony_ci		parent = irq_find_host(gic_node);
2138c2ecf20Sopenharmony_ci
2148c2ecf20Sopenharmony_ci	if (!parent) {
2158c2ecf20Sopenharmony_ci		dev_err(dev, "cannot find parent domain\n");
2168c2ecf20Sopenharmony_ci		ret = -ENODEV;
2178c2ecf20Sopenharmony_ci		goto out_put_node;
2188c2ecf20Sopenharmony_ci	}
2198c2ecf20Sopenharmony_ci
2208c2ecf20Sopenharmony_ci	ret = rza1_irqc_parse_map(priv, gic_node);
2218c2ecf20Sopenharmony_ci	if (ret) {
2228c2ecf20Sopenharmony_ci		dev_err(dev, "cannot parse %s: %d\n", "interrupt-map", ret);
2238c2ecf20Sopenharmony_ci		goto out_put_node;
2248c2ecf20Sopenharmony_ci	}
2258c2ecf20Sopenharmony_ci
2268c2ecf20Sopenharmony_ci	priv->chip.name = "rza1-irqc",
2278c2ecf20Sopenharmony_ci	priv->chip.irq_mask = irq_chip_mask_parent,
2288c2ecf20Sopenharmony_ci	priv->chip.irq_unmask = irq_chip_unmask_parent,
2298c2ecf20Sopenharmony_ci	priv->chip.irq_eoi = rza1_irqc_eoi,
2308c2ecf20Sopenharmony_ci	priv->chip.irq_retrigger = irq_chip_retrigger_hierarchy,
2318c2ecf20Sopenharmony_ci	priv->chip.irq_set_type = rza1_irqc_set_type,
2328c2ecf20Sopenharmony_ci	priv->chip.flags = IRQCHIP_MASK_ON_SUSPEND | IRQCHIP_SKIP_SET_WAKE;
2338c2ecf20Sopenharmony_ci
2348c2ecf20Sopenharmony_ci	priv->irq_domain = irq_domain_add_hierarchy(parent, 0, IRQC_NUM_IRQ,
2358c2ecf20Sopenharmony_ci						    np, &rza1_irqc_domain_ops,
2368c2ecf20Sopenharmony_ci						    priv);
2378c2ecf20Sopenharmony_ci	if (!priv->irq_domain) {
2388c2ecf20Sopenharmony_ci		dev_err(dev, "cannot initialize irq domain\n");
2398c2ecf20Sopenharmony_ci		ret = -ENOMEM;
2408c2ecf20Sopenharmony_ci	}
2418c2ecf20Sopenharmony_ci
2428c2ecf20Sopenharmony_ciout_put_node:
2438c2ecf20Sopenharmony_ci	of_node_put(gic_node);
2448c2ecf20Sopenharmony_ci	return ret;
2458c2ecf20Sopenharmony_ci}
2468c2ecf20Sopenharmony_ci
2478c2ecf20Sopenharmony_cistatic int rza1_irqc_remove(struct platform_device *pdev)
2488c2ecf20Sopenharmony_ci{
2498c2ecf20Sopenharmony_ci	struct rza1_irqc_priv *priv = platform_get_drvdata(pdev);
2508c2ecf20Sopenharmony_ci
2518c2ecf20Sopenharmony_ci	irq_domain_remove(priv->irq_domain);
2528c2ecf20Sopenharmony_ci	return 0;
2538c2ecf20Sopenharmony_ci}
2548c2ecf20Sopenharmony_ci
2558c2ecf20Sopenharmony_cistatic const struct of_device_id rza1_irqc_dt_ids[] = {
2568c2ecf20Sopenharmony_ci	{ .compatible = "renesas,rza1-irqc" },
2578c2ecf20Sopenharmony_ci	{},
2588c2ecf20Sopenharmony_ci};
2598c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(of, rza1_irqc_dt_ids);
2608c2ecf20Sopenharmony_ci
2618c2ecf20Sopenharmony_cistatic struct platform_driver rza1_irqc_device_driver = {
2628c2ecf20Sopenharmony_ci	.probe		= rza1_irqc_probe,
2638c2ecf20Sopenharmony_ci	.remove		= rza1_irqc_remove,
2648c2ecf20Sopenharmony_ci	.driver		= {
2658c2ecf20Sopenharmony_ci		.name	= "renesas_rza1_irqc",
2668c2ecf20Sopenharmony_ci		.of_match_table	= rza1_irqc_dt_ids,
2678c2ecf20Sopenharmony_ci	}
2688c2ecf20Sopenharmony_ci};
2698c2ecf20Sopenharmony_ci
2708c2ecf20Sopenharmony_cistatic int __init rza1_irqc_init(void)
2718c2ecf20Sopenharmony_ci{
2728c2ecf20Sopenharmony_ci	return platform_driver_register(&rza1_irqc_device_driver);
2738c2ecf20Sopenharmony_ci}
2748c2ecf20Sopenharmony_cipostcore_initcall(rza1_irqc_init);
2758c2ecf20Sopenharmony_ci
2768c2ecf20Sopenharmony_cistatic void __exit rza1_irqc_exit(void)
2778c2ecf20Sopenharmony_ci{
2788c2ecf20Sopenharmony_ci	platform_driver_unregister(&rza1_irqc_device_driver);
2798c2ecf20Sopenharmony_ci}
2808c2ecf20Sopenharmony_cimodule_exit(rza1_irqc_exit);
2818c2ecf20Sopenharmony_ci
2828c2ecf20Sopenharmony_ciMODULE_AUTHOR("Geert Uytterhoeven <geert+renesas@glider.be>");
2838c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("Renesas RZ/A1 IRQC Driver");
2848c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL v2");
285