Blame view
drivers/irqchip/irq-mvebu-icu.c
11.3 KB
e0de91a97 irqchip/irq-mvebu... |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
/* * Copyright (C) 2017 Marvell * * Hanna Hawa <hannah@marvell.com> * Thomas Petazzoni <thomas.petazzoni@free-electrons.com> * * This file is licensed under the terms of the GNU General Public * License version 2. This program is licensed "as is" without any * warranty of any kind, whether express or implied. */ #include <linux/interrupt.h> #include <linux/irq.h> #include <linux/irqchip.h> #include <linux/irqdomain.h> |
4f4c867c9 irqchip/irq-mvebu... |
16 |
#include <linux/jump_label.h> |
e0de91a97 irqchip/irq-mvebu... |
17 18 19 20 21 22 23 |
#include <linux/kernel.h> #include <linux/msi.h> #include <linux/of_irq.h> #include <linux/of_platform.h> #include <linux/platform_device.h> #include <dt-bindings/interrupt-controller/mvebu-icu.h> |
e0de91a97 irqchip/irq-mvebu... |
24 25 26 27 28 |
/* ICU registers */ #define ICU_SETSPI_NSR_AL 0x10 #define ICU_SETSPI_NSR_AH 0x14 #define ICU_CLRSPI_NSR_AL 0x18 #define ICU_CLRSPI_NSR_AH 0x1c |
175c98aa2 irqchip/irq-mvebu... |
29 30 31 32 |
#define ICU_SET_SEI_AL 0x50 #define ICU_SET_SEI_AH 0x54 #define ICU_CLR_SEI_AL 0x58 #define ICU_CLR_SEI_AH 0x5C |
e0de91a97 irqchip/irq-mvebu... |
33 34 35 36 37 38 39 40 41 |
#define ICU_INT_CFG(x) (0x100 + 4 * (x)) #define ICU_INT_ENABLE BIT(24) #define ICU_IS_EDGE BIT(28) #define ICU_GROUP_SHIFT 29 /* ICU definitions */ #define ICU_MAX_IRQS 207 #define ICU_SATA0_ICU_ID 109 #define ICU_SATA1_ICU_ID 107 |
175c98aa2 irqchip/irq-mvebu... |
42 43 44 45 46 47 48 |
struct mvebu_icu_subset_data { unsigned int icu_group; unsigned int offset_set_ah; unsigned int offset_set_al; unsigned int offset_clr_ah; unsigned int offset_clr_al; }; |
e0de91a97 irqchip/irq-mvebu... |
49 |
struct mvebu_icu { |
e0de91a97 irqchip/irq-mvebu... |
50 |
void __iomem *base; |
e0de91a97 irqchip/irq-mvebu... |
51 |
struct device *dev; |
175c98aa2 irqchip/irq-mvebu... |
52 53 54 55 |
}; struct mvebu_icu_msi_data { struct mvebu_icu *icu; |
25eaaabb5 irqchip/mvebu-gic... |
56 |
atomic_t initialized; |
175c98aa2 irqchip/irq-mvebu... |
57 |
const struct mvebu_icu_subset_data *subset_data; |
e0de91a97 irqchip/irq-mvebu... |
58 59 60 61 62 63 64 |
}; struct mvebu_icu_irq_data { struct mvebu_icu *icu; unsigned int icu_group; unsigned int type; }; |
9fed9ccb1 irqchip/irq-mvebu... |
65 |
static DEFINE_STATIC_KEY_FALSE(legacy_bindings); |
4f4c867c9 irqchip/irq-mvebu... |
66 |
|
175c98aa2 irqchip/irq-mvebu... |
67 68 69 |
static void mvebu_icu_init(struct mvebu_icu *icu, struct mvebu_icu_msi_data *msi_data, struct msi_msg *msg) |
25eaaabb5 irqchip/mvebu-gic... |
70 |
{ |
175c98aa2 irqchip/irq-mvebu... |
71 72 73 |
const struct mvebu_icu_subset_data *subset = msi_data->subset_data; if (atomic_cmpxchg(&msi_data->initialized, false, true)) |
25eaaabb5 irqchip/mvebu-gic... |
74 |
return; |
175c98aa2 irqchip/irq-mvebu... |
75 76 77 78 79 80 81 82 83 84 |
/* Set 'SET' ICU SPI message address in AP */ writel_relaxed(msg[0].address_hi, icu->base + subset->offset_set_ah); writel_relaxed(msg[0].address_lo, icu->base + subset->offset_set_al); if (subset->icu_group != ICU_GRP_NSR) return; /* Set 'CLEAR' ICU SPI message address in AP (level-MSI only) */ writel_relaxed(msg[1].address_hi, icu->base + subset->offset_clr_ah); writel_relaxed(msg[1].address_lo, icu->base + subset->offset_clr_al); |
25eaaabb5 irqchip/mvebu-gic... |
85 |
} |
e0de91a97 irqchip/irq-mvebu... |
86 87 88 |
static void mvebu_icu_write_msg(struct msi_desc *desc, struct msi_msg *msg) { struct irq_data *d = irq_get_irq_data(desc->irq); |
175c98aa2 irqchip/irq-mvebu... |
89 |
struct mvebu_icu_msi_data *msi_data = platform_msi_get_host_data(d->domain); |
e0de91a97 irqchip/irq-mvebu... |
90 91 92 93 94 |
struct mvebu_icu_irq_data *icu_irqd = d->chip_data; struct mvebu_icu *icu = icu_irqd->icu; unsigned int icu_int; if (msg->address_lo || msg->address_hi) { |
175c98aa2 irqchip/irq-mvebu... |
95 96 |
/* One off initialization per domain */ mvebu_icu_init(icu, msi_data, msg); |
e0de91a97 irqchip/irq-mvebu... |
97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 |
/* Configure the ICU with irq number & type */ icu_int = msg->data | ICU_INT_ENABLE; if (icu_irqd->type & IRQ_TYPE_EDGE_RISING) icu_int |= ICU_IS_EDGE; icu_int |= icu_irqd->icu_group << ICU_GROUP_SHIFT; } else { /* De-configure the ICU */ icu_int = 0; } writel_relaxed(icu_int, icu->base + ICU_INT_CFG(d->hwirq)); /* * The SATA unit has 2 ports, and a dedicated ICU entry per * port. The ahci sata driver supports only one irq interrupt * per SATA unit. To solve this conflict, we configure the 2 * SATA wired interrupts in the south bridge into 1 GIC * interrupt in the north bridge. Even if only a single port * is enabled, if sata node is enabled, both interrupts are * configured (regardless of which port is actually in use). */ if (d->hwirq == ICU_SATA0_ICU_ID || d->hwirq == ICU_SATA1_ICU_ID) { writel_relaxed(icu_int, icu->base + ICU_INT_CFG(ICU_SATA0_ICU_ID)); writel_relaxed(icu_int, icu->base + ICU_INT_CFG(ICU_SATA1_ICU_ID)); } } |
175c98aa2 irqchip/irq-mvebu... |
125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 |
static struct irq_chip mvebu_icu_nsr_chip = { .name = "ICU-NSR", .irq_mask = irq_chip_mask_parent, .irq_unmask = irq_chip_unmask_parent, .irq_eoi = irq_chip_eoi_parent, .irq_set_type = irq_chip_set_type_parent, .irq_set_affinity = irq_chip_set_affinity_parent, }; static struct irq_chip mvebu_icu_sei_chip = { .name = "ICU-SEI", .irq_ack = irq_chip_ack_parent, .irq_mask = irq_chip_mask_parent, .irq_unmask = irq_chip_unmask_parent, .irq_set_type = irq_chip_set_type_parent, .irq_set_affinity = irq_chip_set_affinity_parent, }; |
e0de91a97 irqchip/irq-mvebu... |
142 143 144 145 |
static int mvebu_icu_irq_domain_translate(struct irq_domain *d, struct irq_fwspec *fwspec, unsigned long *hwirq, unsigned int *type) { |
175c98aa2 irqchip/irq-mvebu... |
146 |
struct mvebu_icu_msi_data *msi_data = platform_msi_get_host_data(d); |
2b4dab69d irqchip/irq-mvebu... |
147 |
struct mvebu_icu *icu = platform_msi_get_host_data(d); |
4f4c867c9 irqchip/irq-mvebu... |
148 |
unsigned int param_count = static_branch_unlikely(&legacy_bindings) ? 3 : 2; |
e0de91a97 irqchip/irq-mvebu... |
149 150 |
/* Check the count of the parameters in dt */ |
4f4c867c9 irqchip/irq-mvebu... |
151 |
if (WARN_ON(fwspec->param_count != param_count)) { |
e0de91a97 irqchip/irq-mvebu... |
152 153 154 155 156 |
dev_err(icu->dev, "wrong ICU parameter count %d ", fwspec->param_count); return -EINVAL; } |
4f4c867c9 irqchip/irq-mvebu... |
157 158 159 160 161 162 163 164 165 166 167 168 |
if (static_branch_unlikely(&legacy_bindings)) { *hwirq = fwspec->param[1]; *type = fwspec->param[2] & IRQ_TYPE_SENSE_MASK; if (fwspec->param[0] != ICU_GRP_NSR) { dev_err(icu->dev, "wrong ICU group type %x ", fwspec->param[0]); return -EINVAL; } } else { *hwirq = fwspec->param[0]; *type = fwspec->param[1] & IRQ_TYPE_SENSE_MASK; |
175c98aa2 irqchip/irq-mvebu... |
169 170 171 172 173 174 175 176 177 |
/* * The ICU receives level interrupts. While the NSR are also * level interrupts, SEI are edge interrupts. Force the type * here in this case. Please note that this makes the interrupt * handling unreliable. */ if (msi_data->subset_data->icu_group == ICU_GRP_SEI) *type = IRQ_TYPE_EDGE_RISING; |
e0de91a97 irqchip/irq-mvebu... |
178 |
} |
e0de91a97 irqchip/irq-mvebu... |
179 180 181 182 183 |
if (*hwirq >= ICU_MAX_IRQS) { dev_err(icu->dev, "invalid interrupt number %ld ", *hwirq); return -EINVAL; } |
e0de91a97 irqchip/irq-mvebu... |
184 185 186 187 188 189 190 191 192 193 |
return 0; } static int mvebu_icu_irq_domain_alloc(struct irq_domain *domain, unsigned int virq, unsigned int nr_irqs, void *args) { int err; unsigned long hwirq; struct irq_fwspec *fwspec = args; |
175c98aa2 irqchip/irq-mvebu... |
194 195 |
struct mvebu_icu_msi_data *msi_data = platform_msi_get_host_data(domain); struct mvebu_icu *icu = msi_data->icu; |
e0de91a97 irqchip/irq-mvebu... |
196 |
struct mvebu_icu_irq_data *icu_irqd; |
175c98aa2 irqchip/irq-mvebu... |
197 |
struct irq_chip *chip = &mvebu_icu_nsr_chip; |
e0de91a97 irqchip/irq-mvebu... |
198 199 200 201 202 203 204 205 206 207 208 209 |
icu_irqd = kmalloc(sizeof(*icu_irqd), GFP_KERNEL); if (!icu_irqd) return -ENOMEM; err = mvebu_icu_irq_domain_translate(domain, fwspec, &hwirq, &icu_irqd->type); if (err) { dev_err(icu->dev, "failed to translate ICU parameters "); goto free_irqd; } |
4f4c867c9 irqchip/irq-mvebu... |
210 211 212 |
if (static_branch_unlikely(&legacy_bindings)) icu_irqd->icu_group = fwspec->param[0]; else |
175c98aa2 irqchip/irq-mvebu... |
213 |
icu_irqd->icu_group = msi_data->subset_data->icu_group; |
e0de91a97 irqchip/irq-mvebu... |
214 215 216 217 218 219 220 221 222 223 224 225 226 |
icu_irqd->icu = icu; err = platform_msi_domain_alloc(domain, virq, nr_irqs); if (err) { dev_err(icu->dev, "failed to allocate ICU interrupt in parent domain "); goto free_irqd; } /* Make sure there is no interrupt left pending by the firmware */ err = irq_set_irqchip_state(virq, IRQCHIP_STATE_PENDING, false); if (err) goto free_msi; |
175c98aa2 irqchip/irq-mvebu... |
227 228 |
if (icu_irqd->icu_group == ICU_GRP_SEI) chip = &mvebu_icu_sei_chip; |
e0de91a97 irqchip/irq-mvebu... |
229 |
err = irq_domain_set_hwirq_and_chip(domain, virq, hwirq, |
175c98aa2 irqchip/irq-mvebu... |
230 |
chip, icu_irqd); |
e0de91a97 irqchip/irq-mvebu... |
231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 |
if (err) { dev_err(icu->dev, "failed to set the data to IRQ domain "); goto free_msi; } return 0; free_msi: platform_msi_domain_free(domain, virq, nr_irqs); free_irqd: kfree(icu_irqd); return err; } static void mvebu_icu_irq_domain_free(struct irq_domain *domain, unsigned int virq, unsigned int nr_irqs) { struct irq_data *d = irq_get_irq_data(virq); struct mvebu_icu_irq_data *icu_irqd = d->chip_data; kfree(icu_irqd); platform_msi_domain_free(domain, virq, nr_irqs); } static const struct irq_domain_ops mvebu_icu_domain_ops = { .translate = mvebu_icu_irq_domain_translate, .alloc = mvebu_icu_irq_domain_alloc, .free = mvebu_icu_irq_domain_free, }; |
175c98aa2 irqchip/irq-mvebu... |
263 264 265 266 267 268 269 270 271 272 273 274 275 |
static const struct mvebu_icu_subset_data mvebu_icu_nsr_subset_data = { .icu_group = ICU_GRP_NSR, .offset_set_ah = ICU_SETSPI_NSR_AH, .offset_set_al = ICU_SETSPI_NSR_AL, .offset_clr_ah = ICU_CLRSPI_NSR_AH, .offset_clr_al = ICU_CLRSPI_NSR_AL, }; static const struct mvebu_icu_subset_data mvebu_icu_sei_subset_data = { .icu_group = ICU_GRP_SEI, .offset_set_ah = ICU_SET_SEI_AH, .offset_set_al = ICU_SET_SEI_AL, }; |
4f4c867c9 irqchip/irq-mvebu... |
276 277 278 |
static const struct of_device_id mvebu_icu_subset_of_match[] = { { .compatible = "marvell,cp110-icu-nsr", |
175c98aa2 irqchip/irq-mvebu... |
279 280 281 282 283 |
.data = &mvebu_icu_nsr_subset_data, }, { .compatible = "marvell,cp110-icu-sei", .data = &mvebu_icu_sei_subset_data, |
4f4c867c9 irqchip/irq-mvebu... |
284 285 286 |
}, {}, }; |
00885a77c irqchip/irq-mvebu... |
287 288 |
static int mvebu_icu_subset_probe(struct platform_device *pdev) { |
175c98aa2 irqchip/irq-mvebu... |
289 |
struct mvebu_icu_msi_data *msi_data; |
00885a77c irqchip/irq-mvebu... |
290 291 292 |
struct device_node *msi_parent_dn; struct device *dev = &pdev->dev; struct irq_domain *irq_domain; |
00885a77c irqchip/irq-mvebu... |
293 |
|
175c98aa2 irqchip/irq-mvebu... |
294 295 296 297 298 299 300 301 302 303 304 |
msi_data = devm_kzalloc(dev, sizeof(*msi_data), GFP_KERNEL); if (!msi_data) return -ENOMEM; if (static_branch_unlikely(&legacy_bindings)) { msi_data->icu = dev_get_drvdata(dev); msi_data->subset_data = &mvebu_icu_nsr_subset_data; } else { msi_data->icu = dev_get_drvdata(dev->parent); msi_data->subset_data = of_device_get_match_data(dev); } |
00885a77c irqchip/irq-mvebu... |
305 306 307 308 309 310 311 312 313 314 315 316 317 |
dev->msi_domain = of_msi_get_domain(dev, dev->of_node, DOMAIN_BUS_PLATFORM_MSI); if (!dev->msi_domain) return -EPROBE_DEFER; msi_parent_dn = irq_domain_get_of_node(dev->msi_domain); if (!msi_parent_dn) return -ENODEV; irq_domain = platform_msi_create_device_tree_domain(dev, ICU_MAX_IRQS, mvebu_icu_write_msg, &mvebu_icu_domain_ops, |
175c98aa2 irqchip/irq-mvebu... |
318 |
msi_data); |
00885a77c irqchip/irq-mvebu... |
319 320 321 322 323 324 325 326 |
if (!irq_domain) { dev_err(dev, "Failed to create ICU MSI domain "); return -ENOMEM; } return 0; } |
4f4c867c9 irqchip/irq-mvebu... |
327 328 329 330 331 332 333 334 |
static struct platform_driver mvebu_icu_subset_driver = { .probe = mvebu_icu_subset_probe, .driver = { .name = "mvebu-icu-subset", .of_match_table = mvebu_icu_subset_of_match, }, }; builtin_platform_driver(mvebu_icu_subset_driver); |
e0de91a97 irqchip/irq-mvebu... |
335 336 337 |
static int mvebu_icu_probe(struct platform_device *pdev) { struct mvebu_icu *icu; |
e0de91a97 irqchip/irq-mvebu... |
338 |
struct resource *res; |
25eaaabb5 irqchip/mvebu-gic... |
339 |
int i; |
e0de91a97 irqchip/irq-mvebu... |
340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 |
icu = devm_kzalloc(&pdev->dev, sizeof(struct mvebu_icu), GFP_KERNEL); if (!icu) return -ENOMEM; icu->dev = &pdev->dev; res = platform_get_resource(pdev, IORESOURCE_MEM, 0); icu->base = devm_ioremap_resource(&pdev->dev, res); if (IS_ERR(icu->base)) { dev_err(&pdev->dev, "Failed to map icu base address. "); return PTR_ERR(icu->base); } |
4f4c867c9 irqchip/irq-mvebu... |
355 356 357 358 359 360 361 362 363 |
/* * Legacy bindings: ICU is one node with one MSI parent: force manually * the probe of the NSR interrupts side. * New bindings: ICU node has children, one per interrupt controller * having its own MSI parent: call platform_populate(). * All ICU instances should use the same bindings. */ if (!of_get_child_count(pdev->dev.of_node)) static_branch_enable(&legacy_bindings); |
e0de91a97 irqchip/irq-mvebu... |
364 |
/* |
175c98aa2 irqchip/irq-mvebu... |
365 |
* Clean all ICU interrupts of type NSR and SEI, required to |
e0de91a97 irqchip/irq-mvebu... |
366 367 368 |
* avoid unpredictable SPI assignments done by firmware. */ for (i = 0 ; i < ICU_MAX_IRQS ; i++) { |
9770c6677 irqchip/irq-mvebu... |
369 370 371 372 |
u32 icu_int, icu_grp; icu_int = readl_relaxed(icu->base + ICU_INT_CFG(i)); icu_grp = icu_int >> ICU_GROUP_SHIFT; |
4f4c867c9 irqchip/irq-mvebu... |
373 374 375 |
if (icu_grp == ICU_GRP_NSR || (icu_grp == ICU_GRP_SEI && !static_branch_unlikely(&legacy_bindings))) |
e0de91a97 irqchip/irq-mvebu... |
376 377 |
writel_relaxed(0x0, icu->base + ICU_INT_CFG(i)); } |
00885a77c irqchip/irq-mvebu... |
378 |
platform_set_drvdata(pdev, icu); |
e0de91a97 irqchip/irq-mvebu... |
379 |
|
4f4c867c9 irqchip/irq-mvebu... |
380 381 382 383 |
if (static_branch_unlikely(&legacy_bindings)) return mvebu_icu_subset_probe(pdev); else return devm_of_platform_populate(&pdev->dev); |
e0de91a97 irqchip/irq-mvebu... |
384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 |
} static const struct of_device_id mvebu_icu_of_match[] = { { .compatible = "marvell,cp110-icu", }, {}, }; static struct platform_driver mvebu_icu_driver = { .probe = mvebu_icu_probe, .driver = { .name = "mvebu-icu", .of_match_table = mvebu_icu_of_match, }, }; builtin_platform_driver(mvebu_icu_driver); |