linux/drivers/irqchip/irq-st.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0-only
   2/*
   3 *  Copyright (C) 2014 STMicroelectronics – All Rights Reserved
   4 *
   5 *  Author: Lee Jones <lee.jones@linaro.org>
   6 *
   7 *  This is a re-write of Christophe Kerello's PMU driver.
   8 */
   9
  10#include <dt-bindings/interrupt-controller/irq-st.h>
  11#include <linux/err.h>
  12#include <linux/mfd/syscon.h>
  13#include <linux/of_device.h>
  14#include <linux/platform_device.h>
  15#include <linux/regmap.h>
  16#include <linux/slab.h>
  17
  18#define STIH415_SYSCFG_642              0x0a8
  19#define STIH416_SYSCFG_7543             0x87c
  20#define STIH407_SYSCFG_5102             0x198
  21#define STID127_SYSCFG_734              0x088
  22
  23#define ST_A9_IRQ_MASK                  0x001FFFFF
  24#define ST_A9_IRQ_MAX_CHANS             2
  25
  26#define ST_A9_IRQ_EN_CTI_0              BIT(0)
  27#define ST_A9_IRQ_EN_CTI_1              BIT(1)
  28#define ST_A9_IRQ_EN_PMU_0              BIT(2)
  29#define ST_A9_IRQ_EN_PMU_1              BIT(3)
  30#define ST_A9_IRQ_EN_PL310_L2           BIT(4)
  31#define ST_A9_IRQ_EN_EXT_0              BIT(5)
  32#define ST_A9_IRQ_EN_EXT_1              BIT(6)
  33#define ST_A9_IRQ_EN_EXT_2              BIT(7)
  34
  35#define ST_A9_FIQ_N_SEL(dev, chan)      (dev << (8  + (chan * 3)))
  36#define ST_A9_IRQ_N_SEL(dev, chan)      (dev << (14 + (chan * 3)))
  37#define ST_A9_EXTIRQ_INV_SEL(dev)       (dev << 20)
  38
  39struct st_irq_syscfg {
  40        struct regmap *regmap;
  41        unsigned int syscfg;
  42        unsigned int config;
  43        bool ext_inverted;
  44};
  45
  46static const struct of_device_id st_irq_syscfg_match[] = {
  47        {
  48                .compatible = "st,stih415-irq-syscfg",
  49                .data = (void *)STIH415_SYSCFG_642,
  50        },
  51        {
  52                .compatible = "st,stih416-irq-syscfg",
  53                .data = (void *)STIH416_SYSCFG_7543,
  54        },
  55        {
  56                .compatible = "st,stih407-irq-syscfg",
  57                .data = (void *)STIH407_SYSCFG_5102,
  58        },
  59        {
  60                .compatible = "st,stid127-irq-syscfg",
  61                .data = (void *)STID127_SYSCFG_734,
  62        },
  63        {}
  64};
  65
  66static int st_irq_xlate(struct platform_device *pdev,
  67                        int device, int channel, bool irq)
  68{
  69        struct st_irq_syscfg *ddata = dev_get_drvdata(&pdev->dev);
  70
  71        /* Set the device enable bit. */
  72        switch (device) {
  73        case ST_IRQ_SYSCFG_EXT_0:
  74                ddata->config |= ST_A9_IRQ_EN_EXT_0;
  75                break;
  76        case ST_IRQ_SYSCFG_EXT_1:
  77                ddata->config |= ST_A9_IRQ_EN_EXT_1;
  78                break;
  79        case ST_IRQ_SYSCFG_EXT_2:
  80                ddata->config |= ST_A9_IRQ_EN_EXT_2;
  81                break;
  82        case ST_IRQ_SYSCFG_CTI_0:
  83                ddata->config |= ST_A9_IRQ_EN_CTI_0;
  84                break;
  85        case ST_IRQ_SYSCFG_CTI_1:
  86                ddata->config |= ST_A9_IRQ_EN_CTI_1;
  87                break;
  88        case ST_IRQ_SYSCFG_PMU_0:
  89                ddata->config |= ST_A9_IRQ_EN_PMU_0;
  90                break;
  91        case ST_IRQ_SYSCFG_PMU_1:
  92                ddata->config |= ST_A9_IRQ_EN_PMU_1;
  93                break;
  94        case ST_IRQ_SYSCFG_pl310_L2:
  95                ddata->config |= ST_A9_IRQ_EN_PL310_L2;
  96                break;
  97        case ST_IRQ_SYSCFG_DISABLED:
  98                return 0;
  99        default:
 100                dev_err(&pdev->dev, "Unrecognised device %d\n", device);
 101                return -EINVAL;
 102        }
 103
 104        /* Select IRQ/FIQ channel for device. */
 105        ddata->config |= irq ?
 106                ST_A9_IRQ_N_SEL(device, channel) :
 107                ST_A9_FIQ_N_SEL(device, channel);
 108
 109        return 0;
 110}
 111
 112static int st_irq_syscfg_enable(struct platform_device *pdev)
 113{
 114        struct device_node *np = pdev->dev.of_node;
 115        struct st_irq_syscfg *ddata = dev_get_drvdata(&pdev->dev);
 116        int channels, ret, i;
 117        u32 device, invert;
 118
 119        channels = of_property_count_u32_elems(np, "st,irq-device");
 120        if (channels != ST_A9_IRQ_MAX_CHANS) {
 121                dev_err(&pdev->dev, "st,enable-irq-device must have 2 elems\n");
 122                return -EINVAL;
 123        }
 124
 125        channels = of_property_count_u32_elems(np, "st,fiq-device");
 126        if (channels != ST_A9_IRQ_MAX_CHANS) {
 127                dev_err(&pdev->dev, "st,enable-fiq-device must have 2 elems\n");
 128                return -EINVAL;
 129        }
 130
 131        for (i = 0; i < ST_A9_IRQ_MAX_CHANS; i++) {
 132                of_property_read_u32_index(np, "st,irq-device", i, &device);
 133
 134                ret = st_irq_xlate(pdev, device, i, true);
 135                if (ret)
 136                        return ret;
 137
 138                of_property_read_u32_index(np, "st,fiq-device", i, &device);
 139
 140                ret = st_irq_xlate(pdev, device, i, false);
 141                if (ret)
 142                        return ret;
 143        }
 144
 145        /* External IRQs may be inverted. */
 146        of_property_read_u32(np, "st,invert-ext", &invert);
 147        ddata->config |= ST_A9_EXTIRQ_INV_SEL(invert);
 148
 149        return regmap_update_bits(ddata->regmap, ddata->syscfg,
 150                                  ST_A9_IRQ_MASK, ddata->config);
 151}
 152
 153static int st_irq_syscfg_probe(struct platform_device *pdev)
 154{
 155        struct device_node *np = pdev->dev.of_node;
 156        const struct of_device_id *match;
 157        struct st_irq_syscfg *ddata;
 158
 159        ddata = devm_kzalloc(&pdev->dev, sizeof(*ddata), GFP_KERNEL);
 160        if (!ddata)
 161                return -ENOMEM;
 162
 163        match = of_match_device(st_irq_syscfg_match, &pdev->dev);
 164        if (!match)
 165                return -ENODEV;
 166
 167        ddata->syscfg = (unsigned int)match->data;
 168
 169        ddata->regmap = syscon_regmap_lookup_by_phandle(np, "st,syscfg");
 170        if (IS_ERR(ddata->regmap)) {
 171                dev_err(&pdev->dev, "syscfg phandle missing\n");
 172                return PTR_ERR(ddata->regmap);
 173        }
 174
 175        dev_set_drvdata(&pdev->dev, ddata);
 176
 177        return st_irq_syscfg_enable(pdev);
 178}
 179
 180static int __maybe_unused st_irq_syscfg_resume(struct device *dev)
 181{
 182        struct st_irq_syscfg *ddata = dev_get_drvdata(dev);
 183
 184        return regmap_update_bits(ddata->regmap, ddata->syscfg,
 185                                  ST_A9_IRQ_MASK, ddata->config);
 186}
 187
 188static SIMPLE_DEV_PM_OPS(st_irq_syscfg_pm_ops, NULL, st_irq_syscfg_resume);
 189
 190static struct platform_driver st_irq_syscfg_driver = {
 191        .driver = {
 192                .name = "st_irq_syscfg",
 193                .pm = &st_irq_syscfg_pm_ops,
 194                .of_match_table = st_irq_syscfg_match,
 195        },
 196        .probe = st_irq_syscfg_probe,
 197};
 198
 199static int __init st_irq_syscfg_init(void)
 200{
 201        return platform_driver_register(&st_irq_syscfg_driver);
 202}
 203core_initcall(st_irq_syscfg_init);
 204