linux/drivers/irqchip/irq-loongson-pch-msi.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0
   2/*
   3 *  Copyright (C) 2020, Jiaxun Yang <jiaxun.yang@flygoat.com>
   4 *  Loongson PCH MSI support
   5 */
   6
   7#define pr_fmt(fmt) "pch-msi: " fmt
   8
   9#include <linux/irqchip.h>
  10#include <linux/msi.h>
  11#include <linux/of.h>
  12#include <linux/of_address.h>
  13#include <linux/of_irq.h>
  14#include <linux/of_pci.h>
  15#include <linux/pci.h>
  16#include <linux/slab.h>
  17
  18struct pch_msi_data {
  19        struct mutex    msi_map_lock;
  20        phys_addr_t     doorbell;
  21        u32             irq_first;      /* The vector number that MSIs starts */
  22        u32             num_irqs;       /* The number of vectors for MSIs */
  23        unsigned long   *msi_map;
  24};
  25
  26static void pch_msi_mask_msi_irq(struct irq_data *d)
  27{
  28        pci_msi_mask_irq(d);
  29        irq_chip_mask_parent(d);
  30}
  31
  32static void pch_msi_unmask_msi_irq(struct irq_data *d)
  33{
  34        irq_chip_unmask_parent(d);
  35        pci_msi_unmask_irq(d);
  36}
  37
  38static struct irq_chip pch_msi_irq_chip = {
  39        .name                   = "PCH PCI MSI",
  40        .irq_mask               = pch_msi_mask_msi_irq,
  41        .irq_unmask             = pch_msi_unmask_msi_irq,
  42        .irq_ack                = irq_chip_ack_parent,
  43        .irq_set_affinity       = irq_chip_set_affinity_parent,
  44};
  45
  46static int pch_msi_allocate_hwirq(struct pch_msi_data *priv, int num_req)
  47{
  48        int first;
  49
  50        mutex_lock(&priv->msi_map_lock);
  51
  52        first = bitmap_find_free_region(priv->msi_map, priv->num_irqs,
  53                                        get_count_order(num_req));
  54        if (first < 0) {
  55                mutex_unlock(&priv->msi_map_lock);
  56                return -ENOSPC;
  57        }
  58
  59        mutex_unlock(&priv->msi_map_lock);
  60
  61        return priv->irq_first + first;
  62}
  63
  64static void pch_msi_free_hwirq(struct pch_msi_data *priv,
  65                                int hwirq, int num_req)
  66{
  67        int first = hwirq - priv->irq_first;
  68
  69        mutex_lock(&priv->msi_map_lock);
  70        bitmap_release_region(priv->msi_map, first, get_count_order(num_req));
  71        mutex_unlock(&priv->msi_map_lock);
  72}
  73
  74static void pch_msi_compose_msi_msg(struct irq_data *data,
  75                                        struct msi_msg *msg)
  76{
  77        struct pch_msi_data *priv = irq_data_get_irq_chip_data(data);
  78
  79        msg->address_hi = upper_32_bits(priv->doorbell);
  80        msg->address_lo = lower_32_bits(priv->doorbell);
  81        msg->data = data->hwirq;
  82}
  83
  84static struct msi_domain_info pch_msi_domain_info = {
  85        .flags  = MSI_FLAG_USE_DEF_DOM_OPS | MSI_FLAG_USE_DEF_CHIP_OPS |
  86                  MSI_FLAG_MULTI_PCI_MSI | MSI_FLAG_PCI_MSIX,
  87        .chip   = &pch_msi_irq_chip,
  88};
  89
  90static struct irq_chip middle_irq_chip = {
  91        .name                   = "PCH MSI",
  92        .irq_mask               = irq_chip_mask_parent,
  93        .irq_unmask             = irq_chip_unmask_parent,
  94        .irq_ack                = irq_chip_ack_parent,
  95        .irq_set_affinity       = irq_chip_set_affinity_parent,
  96        .irq_compose_msi_msg    = pch_msi_compose_msi_msg,
  97};
  98
  99static int pch_msi_parent_domain_alloc(struct irq_domain *domain,
 100                                        unsigned int virq, int hwirq)
 101{
 102        struct irq_fwspec fwspec;
 103
 104        fwspec.fwnode = domain->parent->fwnode;
 105        fwspec.param_count = 1;
 106        fwspec.param[0] = hwirq;
 107
 108        return irq_domain_alloc_irqs_parent(domain, virq, 1, &fwspec);
 109}
 110
 111static int pch_msi_middle_domain_alloc(struct irq_domain *domain,
 112                                           unsigned int virq,
 113                                           unsigned int nr_irqs, void *args)
 114{
 115        struct pch_msi_data *priv = domain->host_data;
 116        int hwirq, err, i;
 117
 118        hwirq = pch_msi_allocate_hwirq(priv, nr_irqs);
 119        if (hwirq < 0)
 120                return hwirq;
 121
 122        for (i = 0; i < nr_irqs; i++) {
 123                err = pch_msi_parent_domain_alloc(domain, virq + i, hwirq + i);
 124                if (err)
 125                        goto err_hwirq;
 126
 127                irq_domain_set_hwirq_and_chip(domain, virq + i, hwirq + i,
 128                                              &middle_irq_chip, priv);
 129        }
 130
 131        return 0;
 132
 133err_hwirq:
 134        pch_msi_free_hwirq(priv, hwirq, nr_irqs);
 135        irq_domain_free_irqs_parent(domain, virq, i - 1);
 136
 137        return err;
 138}
 139
 140static void pch_msi_middle_domain_free(struct irq_domain *domain,
 141                                           unsigned int virq,
 142                                           unsigned int nr_irqs)
 143{
 144        struct irq_data *d = irq_domain_get_irq_data(domain, virq);
 145        struct pch_msi_data *priv = irq_data_get_irq_chip_data(d);
 146
 147        irq_domain_free_irqs_parent(domain, virq, nr_irqs);
 148        pch_msi_free_hwirq(priv, d->hwirq, nr_irqs);
 149}
 150
 151static const struct irq_domain_ops pch_msi_middle_domain_ops = {
 152        .alloc  = pch_msi_middle_domain_alloc,
 153        .free   = pch_msi_middle_domain_free,
 154};
 155
 156static int pch_msi_init_domains(struct pch_msi_data *priv,
 157                                struct device_node *node,
 158                                struct irq_domain *parent)
 159{
 160        struct irq_domain *middle_domain, *msi_domain;
 161
 162        middle_domain = irq_domain_create_linear(of_node_to_fwnode(node),
 163                                                priv->num_irqs,
 164                                                &pch_msi_middle_domain_ops,
 165                                                priv);
 166        if (!middle_domain) {
 167                pr_err("Failed to create the MSI middle domain\n");
 168                return -ENOMEM;
 169        }
 170
 171        middle_domain->parent = parent;
 172        irq_domain_update_bus_token(middle_domain, DOMAIN_BUS_NEXUS);
 173
 174        msi_domain = pci_msi_create_irq_domain(of_node_to_fwnode(node),
 175                                               &pch_msi_domain_info,
 176                                               middle_domain);
 177        if (!msi_domain) {
 178                pr_err("Failed to create PCI MSI domain\n");
 179                irq_domain_remove(middle_domain);
 180                return -ENOMEM;
 181        }
 182
 183        return 0;
 184}
 185
 186static int pch_msi_init(struct device_node *node,
 187                            struct device_node *parent)
 188{
 189        struct pch_msi_data *priv;
 190        struct irq_domain *parent_domain;
 191        struct resource res;
 192        int ret;
 193
 194        parent_domain = irq_find_host(parent);
 195        if (!parent_domain) {
 196                pr_err("Failed to find the parent domain\n");
 197                return -ENXIO;
 198        }
 199
 200        priv = kzalloc(sizeof(*priv), GFP_KERNEL);
 201        if (!priv)
 202                return -ENOMEM;
 203
 204        mutex_init(&priv->msi_map_lock);
 205
 206        ret = of_address_to_resource(node, 0, &res);
 207        if (ret) {
 208                pr_err("Failed to allocate resource\n");
 209                goto err_priv;
 210        }
 211
 212        priv->doorbell = res.start;
 213
 214        if (of_property_read_u32(node, "loongson,msi-base-vec",
 215                                &priv->irq_first)) {
 216                pr_err("Unable to parse MSI vec base\n");
 217                ret = -EINVAL;
 218                goto err_priv;
 219        }
 220
 221        if (of_property_read_u32(node, "loongson,msi-num-vecs",
 222                                &priv->num_irqs)) {
 223                pr_err("Unable to parse MSI vec number\n");
 224                ret = -EINVAL;
 225                goto err_priv;
 226        }
 227
 228        priv->msi_map = bitmap_alloc(priv->num_irqs, GFP_KERNEL);
 229        if (!priv->msi_map) {
 230                ret = -ENOMEM;
 231                goto err_priv;
 232        }
 233
 234        pr_debug("Registering %d MSIs, starting at %d\n",
 235                 priv->num_irqs, priv->irq_first);
 236
 237        ret = pch_msi_init_domains(priv, node, parent_domain);
 238        if (ret)
 239                goto err_map;
 240
 241        return 0;
 242
 243err_map:
 244        kfree(priv->msi_map);
 245err_priv:
 246        kfree(priv);
 247        return ret;
 248}
 249
 250IRQCHIP_DECLARE(pch_msi, "loongson,pch-msi-1.0", pch_msi_init);
 251