linux/drivers/irqchip/irq-ls-scfg-msi.c
<<
>>
Prefs
   1/*
   2 * Freescale SCFG MSI(-X) support
   3 *
   4 * Copyright (C) 2016 Freescale Semiconductor.
   5 *
   6 * Author: Minghuan Lian <Minghuan.Lian@nxp.com>
   7 *
   8 * This program is free software; you can redistribute it and/or modify
   9 * it under the terms of the GNU General Public License version 2 as
  10 * published by the Free Software Foundation.
  11 */
  12
  13#include <linux/kernel.h>
  14#include <linux/module.h>
  15#include <linux/msi.h>
  16#include <linux/interrupt.h>
  17#include <linux/irq.h>
  18#include <linux/irqchip/chained_irq.h>
  19#include <linux/irqdomain.h>
  20#include <linux/of_pci.h>
  21#include <linux/of_platform.h>
  22#include <linux/spinlock.h>
  23
  24#define MSI_MAX_IRQS    32
  25#define MSI_IBS_SHIFT   3
  26#define MSIR            4
  27
  28struct ls_scfg_msi {
  29        spinlock_t              lock;
  30        struct platform_device  *pdev;
  31        struct irq_domain       *parent;
  32        struct irq_domain       *msi_domain;
  33        void __iomem            *regs;
  34        phys_addr_t             msiir_addr;
  35        int                     irq;
  36        DECLARE_BITMAP(used, MSI_MAX_IRQS);
  37};
  38
  39static struct irq_chip ls_scfg_msi_irq_chip = {
  40        .name = "MSI",
  41        .irq_mask       = pci_msi_mask_irq,
  42        .irq_unmask     = pci_msi_unmask_irq,
  43};
  44
  45static struct msi_domain_info ls_scfg_msi_domain_info = {
  46        .flags  = (MSI_FLAG_USE_DEF_DOM_OPS |
  47                   MSI_FLAG_USE_DEF_CHIP_OPS |
  48                   MSI_FLAG_PCI_MSIX),
  49        .chip   = &ls_scfg_msi_irq_chip,
  50};
  51
  52static void ls_scfg_msi_compose_msg(struct irq_data *data, struct msi_msg *msg)
  53{
  54        struct ls_scfg_msi *msi_data = irq_data_get_irq_chip_data(data);
  55
  56        msg->address_hi = upper_32_bits(msi_data->msiir_addr);
  57        msg->address_lo = lower_32_bits(msi_data->msiir_addr);
  58        msg->data = data->hwirq << MSI_IBS_SHIFT;
  59}
  60
  61static int ls_scfg_msi_set_affinity(struct irq_data *irq_data,
  62                                    const struct cpumask *mask, bool force)
  63{
  64        return -EINVAL;
  65}
  66
  67static struct irq_chip ls_scfg_msi_parent_chip = {
  68        .name                   = "SCFG",
  69        .irq_compose_msi_msg    = ls_scfg_msi_compose_msg,
  70        .irq_set_affinity       = ls_scfg_msi_set_affinity,
  71};
  72
  73static int ls_scfg_msi_domain_irq_alloc(struct irq_domain *domain,
  74                                        unsigned int virq,
  75                                        unsigned int nr_irqs,
  76                                        void *args)
  77{
  78        struct ls_scfg_msi *msi_data = domain->host_data;
  79        int pos, err = 0;
  80
  81        WARN_ON(nr_irqs != 1);
  82
  83        spin_lock(&msi_data->lock);
  84        pos = find_first_zero_bit(msi_data->used, MSI_MAX_IRQS);
  85        if (pos < MSI_MAX_IRQS)
  86                __set_bit(pos, msi_data->used);
  87        else
  88                err = -ENOSPC;
  89        spin_unlock(&msi_data->lock);
  90
  91        if (err)
  92                return err;
  93
  94        irq_domain_set_info(domain, virq, pos,
  95                            &ls_scfg_msi_parent_chip, msi_data,
  96                            handle_simple_irq, NULL, NULL);
  97
  98        return 0;
  99}
 100
 101static void ls_scfg_msi_domain_irq_free(struct irq_domain *domain,
 102                                   unsigned int virq, unsigned int nr_irqs)
 103{
 104        struct irq_data *d = irq_domain_get_irq_data(domain, virq);
 105        struct ls_scfg_msi *msi_data = irq_data_get_irq_chip_data(d);
 106        int pos;
 107
 108        pos = d->hwirq;
 109        if (pos < 0 || pos >= MSI_MAX_IRQS) {
 110                pr_err("failed to teardown msi. Invalid hwirq %d\n", pos);
 111                return;
 112        }
 113
 114        spin_lock(&msi_data->lock);
 115        __clear_bit(pos, msi_data->used);
 116        spin_unlock(&msi_data->lock);
 117}
 118
 119static const struct irq_domain_ops ls_scfg_msi_domain_ops = {
 120        .alloc  = ls_scfg_msi_domain_irq_alloc,
 121        .free   = ls_scfg_msi_domain_irq_free,
 122};
 123
 124static void ls_scfg_msi_irq_handler(struct irq_desc *desc)
 125{
 126        struct ls_scfg_msi *msi_data = irq_desc_get_handler_data(desc);
 127        unsigned long val;
 128        int pos, virq;
 129
 130        chained_irq_enter(irq_desc_get_chip(desc), desc);
 131
 132        val = ioread32be(msi_data->regs + MSIR);
 133        for_each_set_bit(pos, &val, MSI_MAX_IRQS) {
 134                virq = irq_find_mapping(msi_data->parent, (31 - pos));
 135                if (virq)
 136                        generic_handle_irq(virq);
 137        }
 138
 139        chained_irq_exit(irq_desc_get_chip(desc), desc);
 140}
 141
 142static int ls_scfg_msi_domains_init(struct ls_scfg_msi *msi_data)
 143{
 144        /* Initialize MSI domain parent */
 145        msi_data->parent = irq_domain_add_linear(NULL,
 146                                                 MSI_MAX_IRQS,
 147                                                 &ls_scfg_msi_domain_ops,
 148                                                 msi_data);
 149        if (!msi_data->parent) {
 150                dev_err(&msi_data->pdev->dev, "failed to create IRQ domain\n");
 151                return -ENOMEM;
 152        }
 153
 154        msi_data->msi_domain = pci_msi_create_irq_domain(
 155                                of_node_to_fwnode(msi_data->pdev->dev.of_node),
 156                                &ls_scfg_msi_domain_info,
 157                                msi_data->parent);
 158        if (!msi_data->msi_domain) {
 159                dev_err(&msi_data->pdev->dev, "failed to create MSI domain\n");
 160                irq_domain_remove(msi_data->parent);
 161                return -ENOMEM;
 162        }
 163
 164        return 0;
 165}
 166
 167static int ls_scfg_msi_probe(struct platform_device *pdev)
 168{
 169        struct ls_scfg_msi *msi_data;
 170        struct resource *res;
 171        int ret;
 172
 173        msi_data = devm_kzalloc(&pdev->dev, sizeof(*msi_data), GFP_KERNEL);
 174        if (!msi_data)
 175                return -ENOMEM;
 176
 177        res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
 178        msi_data->regs = devm_ioremap_resource(&pdev->dev, res);
 179        if (IS_ERR(msi_data->regs)) {
 180                dev_err(&pdev->dev, "failed to initialize 'regs'\n");
 181                return PTR_ERR(msi_data->regs);
 182        }
 183        msi_data->msiir_addr = res->start;
 184
 185        msi_data->irq = platform_get_irq(pdev, 0);
 186        if (msi_data->irq <= 0) {
 187                dev_err(&pdev->dev, "failed to get MSI irq\n");
 188                return -ENODEV;
 189        }
 190
 191        msi_data->pdev = pdev;
 192        spin_lock_init(&msi_data->lock);
 193
 194        ret = ls_scfg_msi_domains_init(msi_data);
 195        if (ret)
 196                return ret;
 197
 198        irq_set_chained_handler_and_data(msi_data->irq,
 199                                         ls_scfg_msi_irq_handler,
 200                                         msi_data);
 201
 202        platform_set_drvdata(pdev, msi_data);
 203
 204        return 0;
 205}
 206
 207static int ls_scfg_msi_remove(struct platform_device *pdev)
 208{
 209        struct ls_scfg_msi *msi_data = platform_get_drvdata(pdev);
 210
 211        irq_set_chained_handler_and_data(msi_data->irq, NULL, NULL);
 212
 213        irq_domain_remove(msi_data->msi_domain);
 214        irq_domain_remove(msi_data->parent);
 215
 216        platform_set_drvdata(pdev, NULL);
 217
 218        return 0;
 219}
 220
 221static const struct of_device_id ls_scfg_msi_id[] = {
 222        { .compatible = "fsl,1s1021a-msi", },
 223        { .compatible = "fsl,1s1043a-msi", },
 224        {},
 225};
 226
 227static struct platform_driver ls_scfg_msi_driver = {
 228        .driver = {
 229                .name = "ls-scfg-msi",
 230                .of_match_table = ls_scfg_msi_id,
 231        },
 232        .probe = ls_scfg_msi_probe,
 233        .remove = ls_scfg_msi_remove,
 234};
 235
 236module_platform_driver(ls_scfg_msi_driver);
 237
 238MODULE_AUTHOR("Minghuan Lian <Minghuan.Lian@nxp.com>");
 239MODULE_DESCRIPTION("Freescale Layerscape SCFG MSI controller driver");
 240MODULE_LICENSE("GPL v2");
 241