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 18 struct 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 26 static 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 32 static 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 38 static 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 46 static 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 64 static 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 74 static 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 84 static 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 90 static 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 99 static 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 111 static 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 133 err_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 140 static 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 151 static 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 156 static 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 186 static 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 243 err_map: 244 kfree(priv->msi_map); 245 err_priv: 246 kfree(priv); 247 return ret; 248 } 249 250 IRQCHIP_DECLARE(pch_msi, "loongson,pch-msi-1.0", pch_msi_init); 251