Back to home page

OSCL-LXR

 
 

    


0001 // SPDX-License-Identifier: GPL-2.0-only
0002 /*
0003  * Driver for UniPhier AIDET (ARM Interrupt Detector)
0004  *
0005  * Copyright (C) 2017 Socionext Inc.
0006  *   Author: Masahiro Yamada <yamada.masahiro@socionext.com>
0007  */
0008 
0009 #include <linux/bitops.h>
0010 #include <linux/init.h>
0011 #include <linux/irq.h>
0012 #include <linux/irqdomain.h>
0013 #include <linux/kernel.h>
0014 #include <linux/of.h>
0015 #include <linux/of_device.h>
0016 #include <linux/of_irq.h>
0017 #include <linux/platform_device.h>
0018 #include <linux/spinlock.h>
0019 
0020 #define UNIPHIER_AIDET_NR_IRQS      256
0021 
0022 #define UNIPHIER_AIDET_DETCONF      0x04    /* inverter register base */
0023 
0024 struct uniphier_aidet_priv {
0025     struct irq_domain *domain;
0026     void __iomem *reg_base;
0027     spinlock_t lock;
0028     u32 saved_vals[UNIPHIER_AIDET_NR_IRQS / 32];
0029 };
0030 
0031 static void uniphier_aidet_reg_update(struct uniphier_aidet_priv *priv,
0032                       unsigned int reg, u32 mask, u32 val)
0033 {
0034     unsigned long flags;
0035     u32 tmp;
0036 
0037     spin_lock_irqsave(&priv->lock, flags);
0038     tmp = readl_relaxed(priv->reg_base + reg);
0039     tmp &= ~mask;
0040     tmp |= mask & val;
0041     writel_relaxed(tmp, priv->reg_base + reg);
0042     spin_unlock_irqrestore(&priv->lock, flags);
0043 }
0044 
0045 static void uniphier_aidet_detconf_update(struct uniphier_aidet_priv *priv,
0046                       unsigned long index, unsigned int val)
0047 {
0048     unsigned int reg;
0049     u32 mask;
0050 
0051     reg = UNIPHIER_AIDET_DETCONF + index / 32 * 4;
0052     mask = BIT(index % 32);
0053 
0054     uniphier_aidet_reg_update(priv, reg, mask, val ? mask : 0);
0055 }
0056 
0057 static int uniphier_aidet_irq_set_type(struct irq_data *data, unsigned int type)
0058 {
0059     struct uniphier_aidet_priv *priv = data->chip_data;
0060     unsigned int val;
0061 
0062     /* enable inverter for active low triggers */
0063     switch (type) {
0064     case IRQ_TYPE_EDGE_RISING:
0065     case IRQ_TYPE_LEVEL_HIGH:
0066         val = 0;
0067         break;
0068     case IRQ_TYPE_EDGE_FALLING:
0069         val = 1;
0070         type = IRQ_TYPE_EDGE_RISING;
0071         break;
0072     case IRQ_TYPE_LEVEL_LOW:
0073         val = 1;
0074         type = IRQ_TYPE_LEVEL_HIGH;
0075         break;
0076     default:
0077         return -EINVAL;
0078     }
0079 
0080     uniphier_aidet_detconf_update(priv, data->hwirq, val);
0081 
0082     return irq_chip_set_type_parent(data, type);
0083 }
0084 
0085 static struct irq_chip uniphier_aidet_irq_chip = {
0086     .name = "AIDET",
0087     .irq_mask = irq_chip_mask_parent,
0088     .irq_unmask = irq_chip_unmask_parent,
0089     .irq_eoi = irq_chip_eoi_parent,
0090     .irq_set_affinity = irq_chip_set_affinity_parent,
0091     .irq_set_type = uniphier_aidet_irq_set_type,
0092 };
0093 
0094 static int uniphier_aidet_domain_translate(struct irq_domain *domain,
0095                        struct irq_fwspec *fwspec,
0096                        unsigned long *out_hwirq,
0097                        unsigned int *out_type)
0098 {
0099     if (WARN_ON(fwspec->param_count < 2))
0100         return -EINVAL;
0101 
0102     *out_hwirq = fwspec->param[0];
0103     *out_type = fwspec->param[1] & IRQ_TYPE_SENSE_MASK;
0104 
0105     return 0;
0106 }
0107 
0108 static int uniphier_aidet_domain_alloc(struct irq_domain *domain,
0109                        unsigned int virq, unsigned int nr_irqs,
0110                        void *arg)
0111 {
0112     struct irq_fwspec parent_fwspec;
0113     irq_hw_number_t hwirq;
0114     unsigned int type;
0115     int ret;
0116 
0117     if (nr_irqs != 1)
0118         return -EINVAL;
0119 
0120     ret = uniphier_aidet_domain_translate(domain, arg, &hwirq, &type);
0121     if (ret)
0122         return ret;
0123 
0124     switch (type) {
0125     case IRQ_TYPE_EDGE_RISING:
0126     case IRQ_TYPE_LEVEL_HIGH:
0127         break;
0128     case IRQ_TYPE_EDGE_FALLING:
0129         type = IRQ_TYPE_EDGE_RISING;
0130         break;
0131     case IRQ_TYPE_LEVEL_LOW:
0132         type = IRQ_TYPE_LEVEL_HIGH;
0133         break;
0134     default:
0135         return -EINVAL;
0136     }
0137 
0138     if (hwirq >= UNIPHIER_AIDET_NR_IRQS)
0139         return -ENXIO;
0140 
0141     ret = irq_domain_set_hwirq_and_chip(domain, virq, hwirq,
0142                         &uniphier_aidet_irq_chip,
0143                         domain->host_data);
0144     if (ret)
0145         return ret;
0146 
0147     /* parent is GIC */
0148     parent_fwspec.fwnode = domain->parent->fwnode;
0149     parent_fwspec.param_count = 3;
0150     parent_fwspec.param[0] = 0;     /* SPI */
0151     parent_fwspec.param[1] = hwirq;
0152     parent_fwspec.param[2] = type;
0153 
0154     return irq_domain_alloc_irqs_parent(domain, virq, 1, &parent_fwspec);
0155 }
0156 
0157 static const struct irq_domain_ops uniphier_aidet_domain_ops = {
0158     .alloc = uniphier_aidet_domain_alloc,
0159     .free = irq_domain_free_irqs_common,
0160     .translate = uniphier_aidet_domain_translate,
0161 };
0162 
0163 static int uniphier_aidet_probe(struct platform_device *pdev)
0164 {
0165     struct device *dev = &pdev->dev;
0166     struct device_node *parent_np;
0167     struct irq_domain *parent_domain;
0168     struct uniphier_aidet_priv *priv;
0169 
0170     parent_np = of_irq_find_parent(dev->of_node);
0171     if (!parent_np)
0172         return -ENXIO;
0173 
0174     parent_domain = irq_find_host(parent_np);
0175     of_node_put(parent_np);
0176     if (!parent_domain)
0177         return -EPROBE_DEFER;
0178 
0179     priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
0180     if (!priv)
0181         return -ENOMEM;
0182 
0183     priv->reg_base = devm_platform_ioremap_resource(pdev, 0);
0184     if (IS_ERR(priv->reg_base))
0185         return PTR_ERR(priv->reg_base);
0186 
0187     spin_lock_init(&priv->lock);
0188 
0189     priv->domain = irq_domain_create_hierarchy(
0190                     parent_domain, 0,
0191                     UNIPHIER_AIDET_NR_IRQS,
0192                     of_node_to_fwnode(dev->of_node),
0193                     &uniphier_aidet_domain_ops, priv);
0194     if (!priv->domain)
0195         return -ENOMEM;
0196 
0197     platform_set_drvdata(pdev, priv);
0198 
0199     return 0;
0200 }
0201 
0202 static int __maybe_unused uniphier_aidet_suspend(struct device *dev)
0203 {
0204     struct uniphier_aidet_priv *priv = dev_get_drvdata(dev);
0205     int i;
0206 
0207     for (i = 0; i < ARRAY_SIZE(priv->saved_vals); i++)
0208         priv->saved_vals[i] = readl_relaxed(
0209             priv->reg_base + UNIPHIER_AIDET_DETCONF + i * 4);
0210 
0211     return 0;
0212 }
0213 
0214 static int __maybe_unused uniphier_aidet_resume(struct device *dev)
0215 {
0216     struct uniphier_aidet_priv *priv = dev_get_drvdata(dev);
0217     int i;
0218 
0219     for (i = 0; i < ARRAY_SIZE(priv->saved_vals); i++)
0220         writel_relaxed(priv->saved_vals[i],
0221                    priv->reg_base + UNIPHIER_AIDET_DETCONF + i * 4);
0222 
0223     return 0;
0224 }
0225 
0226 static const struct dev_pm_ops uniphier_aidet_pm_ops = {
0227     SET_NOIRQ_SYSTEM_SLEEP_PM_OPS(uniphier_aidet_suspend,
0228                       uniphier_aidet_resume)
0229 };
0230 
0231 static const struct of_device_id uniphier_aidet_match[] = {
0232     { .compatible = "socionext,uniphier-ld4-aidet" },
0233     { .compatible = "socionext,uniphier-pro4-aidet" },
0234     { .compatible = "socionext,uniphier-sld8-aidet" },
0235     { .compatible = "socionext,uniphier-pro5-aidet" },
0236     { .compatible = "socionext,uniphier-pxs2-aidet" },
0237     { .compatible = "socionext,uniphier-ld11-aidet" },
0238     { .compatible = "socionext,uniphier-ld20-aidet" },
0239     { .compatible = "socionext,uniphier-pxs3-aidet" },
0240     { .compatible = "socionext,uniphier-nx1-aidet" },
0241     { /* sentinel */ }
0242 };
0243 
0244 static struct platform_driver uniphier_aidet_driver = {
0245     .probe = uniphier_aidet_probe,
0246     .driver = {
0247         .name = "uniphier-aidet",
0248         .of_match_table = uniphier_aidet_match,
0249         .pm = &uniphier_aidet_pm_ops,
0250     },
0251 };
0252 builtin_platform_driver(uniphier_aidet_driver);