Back to home page

OSCL-LXR

 
 

    


0001 // SPDX-License-Identifier: GPL-2.0
0002 
0003 #define pr_fmt(fmt) "irq-ls-extirq: " fmt
0004 
0005 #include <linux/irq.h>
0006 #include <linux/irqchip.h>
0007 #include <linux/irqdomain.h>
0008 #include <linux/of.h>
0009 #include <linux/mfd/syscon.h>
0010 #include <linux/regmap.h>
0011 #include <linux/slab.h>
0012 
0013 #include <dt-bindings/interrupt-controller/arm-gic.h>
0014 
0015 #define MAXIRQ 12
0016 #define LS1021A_SCFGREVCR 0x200
0017 
0018 struct ls_extirq_data {
0019     struct regmap       *syscon;
0020     u32         intpcr;
0021     bool            is_ls1021a_or_ls1043a;
0022     u32         nirq;
0023     struct irq_fwspec   map[MAXIRQ];
0024 };
0025 
0026 static int
0027 ls_extirq_set_type(struct irq_data *data, unsigned int type)
0028 {
0029     struct ls_extirq_data *priv = data->chip_data;
0030     irq_hw_number_t hwirq = data->hwirq;
0031     u32 value, mask;
0032 
0033     if (priv->is_ls1021a_or_ls1043a)
0034         mask = 1U << (31 - hwirq);
0035     else
0036         mask = 1U << hwirq;
0037 
0038     switch (type) {
0039     case IRQ_TYPE_LEVEL_LOW:
0040         type = IRQ_TYPE_LEVEL_HIGH;
0041         value = mask;
0042         break;
0043     case IRQ_TYPE_EDGE_FALLING:
0044         type = IRQ_TYPE_EDGE_RISING;
0045         value = mask;
0046         break;
0047     case IRQ_TYPE_LEVEL_HIGH:
0048     case IRQ_TYPE_EDGE_RISING:
0049         value = 0;
0050         break;
0051     default:
0052         return -EINVAL;
0053     }
0054     regmap_update_bits(priv->syscon, priv->intpcr, mask, value);
0055 
0056     return irq_chip_set_type_parent(data, type);
0057 }
0058 
0059 static struct irq_chip ls_extirq_chip = {
0060     .name           = "ls-extirq",
0061     .irq_mask       = irq_chip_mask_parent,
0062     .irq_unmask     = irq_chip_unmask_parent,
0063     .irq_eoi        = irq_chip_eoi_parent,
0064     .irq_set_type       = ls_extirq_set_type,
0065     .irq_retrigger      = irq_chip_retrigger_hierarchy,
0066     .irq_set_affinity   = irq_chip_set_affinity_parent,
0067     .flags                  = IRQCHIP_SET_TYPE_MASKED | IRQCHIP_SKIP_SET_WAKE,
0068 };
0069 
0070 static int
0071 ls_extirq_domain_alloc(struct irq_domain *domain, unsigned int virq,
0072                unsigned int nr_irqs, void *arg)
0073 {
0074     struct ls_extirq_data *priv = domain->host_data;
0075     struct irq_fwspec *fwspec = arg;
0076     irq_hw_number_t hwirq;
0077 
0078     if (fwspec->param_count != 2)
0079         return -EINVAL;
0080 
0081     hwirq = fwspec->param[0];
0082     if (hwirq >= priv->nirq)
0083         return -EINVAL;
0084 
0085     irq_domain_set_hwirq_and_chip(domain, virq, hwirq, &ls_extirq_chip,
0086                       priv);
0087 
0088     return irq_domain_alloc_irqs_parent(domain, virq, 1, &priv->map[hwirq]);
0089 }
0090 
0091 static const struct irq_domain_ops extirq_domain_ops = {
0092     .xlate      = irq_domain_xlate_twocell,
0093     .alloc      = ls_extirq_domain_alloc,
0094     .free       = irq_domain_free_irqs_common,
0095 };
0096 
0097 static int
0098 ls_extirq_parse_map(struct ls_extirq_data *priv, struct device_node *node)
0099 {
0100     const __be32 *map;
0101     u32 mapsize;
0102     int ret;
0103 
0104     map = of_get_property(node, "interrupt-map", &mapsize);
0105     if (!map)
0106         return -ENOENT;
0107     if (mapsize % sizeof(*map))
0108         return -EINVAL;
0109     mapsize /= sizeof(*map);
0110 
0111     while (mapsize) {
0112         struct device_node *ipar;
0113         u32 hwirq, intsize, j;
0114 
0115         if (mapsize < 3)
0116             return -EINVAL;
0117         hwirq = be32_to_cpup(map);
0118         if (hwirq >= MAXIRQ)
0119             return -EINVAL;
0120         priv->nirq = max(priv->nirq, hwirq + 1);
0121 
0122         ipar = of_find_node_by_phandle(be32_to_cpup(map + 2));
0123         map += 3;
0124         mapsize -= 3;
0125         if (!ipar)
0126             return -EINVAL;
0127         priv->map[hwirq].fwnode = &ipar->fwnode;
0128         ret = of_property_read_u32(ipar, "#interrupt-cells", &intsize);
0129         if (ret)
0130             return ret;
0131 
0132         if (intsize > mapsize)
0133             return -EINVAL;
0134 
0135         priv->map[hwirq].param_count = intsize;
0136         for (j = 0; j < intsize; ++j)
0137             priv->map[hwirq].param[j] = be32_to_cpup(map++);
0138         mapsize -= intsize;
0139     }
0140     return 0;
0141 }
0142 
0143 static int __init
0144 ls_extirq_of_init(struct device_node *node, struct device_node *parent)
0145 {
0146 
0147     struct irq_domain *domain, *parent_domain;
0148     struct ls_extirq_data *priv;
0149     int ret;
0150 
0151     parent_domain = irq_find_host(parent);
0152     if (!parent_domain) {
0153         pr_err("Cannot find parent domain\n");
0154         return -ENODEV;
0155     }
0156 
0157     priv = kzalloc(sizeof(*priv), GFP_KERNEL);
0158     if (!priv)
0159         return -ENOMEM;
0160 
0161     priv->syscon = syscon_node_to_regmap(node->parent);
0162     if (IS_ERR(priv->syscon)) {
0163         ret = PTR_ERR(priv->syscon);
0164         pr_err("Failed to lookup parent regmap\n");
0165         goto out;
0166     }
0167     ret = of_property_read_u32(node, "reg", &priv->intpcr);
0168     if (ret) {
0169         pr_err("Missing INTPCR offset value\n");
0170         goto out;
0171     }
0172 
0173     ret = ls_extirq_parse_map(priv, node);
0174     if (ret)
0175         goto out;
0176 
0177     priv->is_ls1021a_or_ls1043a = of_device_is_compatible(node, "fsl,ls1021a-extirq") ||
0178                       of_device_is_compatible(node, "fsl,ls1043a-extirq");
0179 
0180     domain = irq_domain_add_hierarchy(parent_domain, 0, priv->nirq, node,
0181                       &extirq_domain_ops, priv);
0182     if (!domain)
0183         ret = -ENOMEM;
0184 
0185 out:
0186     if (ret)
0187         kfree(priv);
0188     return ret;
0189 }
0190 
0191 IRQCHIP_DECLARE(ls1021a_extirq, "fsl,ls1021a-extirq", ls_extirq_of_init);
0192 IRQCHIP_DECLARE(ls1043a_extirq, "fsl,ls1043a-extirq", ls_extirq_of_init);
0193 IRQCHIP_DECLARE(ls1088a_extirq, "fsl,ls1088a-extirq", ls_extirq_of_init);