Commit | Line | Data |
---|---|---|
203e2de3 | 1 | // SPDX-License-Identifier: GPL-2.0 |
3a288fd5 GR |
2 | /* |
3 | * Freescale Management Complex (MC) bus driver MSI support | |
4 | * | |
6466dac7 | 5 | * Copyright (C) 2015-2016 Freescale Semiconductor, Inc. |
3a288fd5 GR |
6 | * Author: German Rivera <German.Rivera@freescale.com> |
7 | * | |
3a288fd5 GR |
8 | */ |
9 | ||
6305166c MP |
10 | #include <linux/acpi.h> |
11 | #include <linux/acpi_iort.h> | |
3a288fd5 GR |
12 | #include <linux/of_device.h> |
13 | #include <linux/of_address.h> | |
3a288fd5 GR |
14 | #include <linux/irq.h> |
15 | #include <linux/msi.h> | |
16 | #include <linux/of.h> | |
17 | #include <linux/of_irq.h> | |
6bd067c4 | 18 | #include <linux/fsl/mc.h> |
3a288fd5 GR |
19 | |
20 | static struct irq_chip its_msi_irq_chip = { | |
d64c28a3 | 21 | .name = "ITS-fMSI", |
3a288fd5 GR |
22 | .irq_mask = irq_chip_mask_parent, |
23 | .irq_unmask = irq_chip_unmask_parent, | |
24 | .irq_eoi = irq_chip_eoi_parent, | |
25 | .irq_set_affinity = msi_domain_set_affinity | |
26 | }; | |
27 | ||
998fb7ba DC |
28 | static u32 fsl_mc_msi_domain_get_msi_id(struct irq_domain *domain, |
29 | struct fsl_mc_device *mc_dev) | |
30 | { | |
31 | struct device_node *of_node; | |
32 | u32 out_id; | |
33 | ||
34 | of_node = irq_domain_get_of_node(domain); | |
6305166c MP |
35 | out_id = of_node ? of_msi_map_id(&mc_dev->dev, of_node, mc_dev->icid) : |
36 | iort_msi_map_id(&mc_dev->dev, mc_dev->icid); | |
998fb7ba DC |
37 | |
38 | return out_id; | |
39 | } | |
40 | ||
3a288fd5 GR |
41 | static int its_fsl_mc_msi_prepare(struct irq_domain *msi_domain, |
42 | struct device *dev, | |
43 | int nvec, msi_alloc_info_t *info) | |
44 | { | |
45 | struct fsl_mc_device *mc_bus_dev; | |
46 | struct msi_domain_info *msi_info; | |
47 | ||
a385dd7b | 48 | if (!dev_is_fsl_mc(dev)) |
3a288fd5 GR |
49 | return -EINVAL; |
50 | ||
51 | mc_bus_dev = to_fsl_mc_device(dev); | |
a385dd7b | 52 | if (!(mc_bus_dev->flags & FSL_MC_IS_DPRC)) |
3a288fd5 GR |
53 | return -EINVAL; |
54 | ||
55 | /* | |
56 | * Set the device Id to be passed to the GIC-ITS: | |
57 | * | |
58 | * NOTE: This device id corresponds to the IOMMU stream ID | |
59 | * associated with the DPRC object (ICID). | |
60 | */ | |
998fb7ba DC |
61 | info->scratchpad[0].ul = fsl_mc_msi_domain_get_msi_id(msi_domain, |
62 | mc_bus_dev); | |
3a288fd5 | 63 | msi_info = msi_get_domain_info(msi_domain->parent); |
147c8f37 MZ |
64 | |
65 | /* Allocate at least 32 MSIs, and always as a power of 2 */ | |
66 | nvec = max_t(int, 32, roundup_pow_of_two(nvec)); | |
3a288fd5 GR |
67 | return msi_info->ops->msi_prepare(msi_domain->parent, dev, nvec, info); |
68 | } | |
69 | ||
76b94eb1 | 70 | static struct msi_domain_ops its_fsl_mc_msi_ops __ro_after_init = { |
3a288fd5 GR |
71 | .msi_prepare = its_fsl_mc_msi_prepare, |
72 | }; | |
73 | ||
74 | static struct msi_domain_info its_fsl_mc_msi_domain_info = { | |
75 | .flags = (MSI_FLAG_USE_DEF_DOM_OPS | MSI_FLAG_USE_DEF_CHIP_OPS), | |
76 | .ops = &its_fsl_mc_msi_ops, | |
77 | .chip = &its_msi_irq_chip, | |
78 | }; | |
79 | ||
80 | static const struct of_device_id its_device_id[] = { | |
81 | { .compatible = "arm,gic-v3-its", }, | |
82 | {}, | |
83 | }; | |
84 | ||
6305166c MP |
85 | static void __init its_fsl_mc_msi_init_one(struct fwnode_handle *handle, |
86 | const char *name) | |
3a288fd5 | 87 | { |
3a288fd5 GR |
88 | struct irq_domain *parent; |
89 | struct irq_domain *mc_msi_domain; | |
90 | ||
6305166c MP |
91 | parent = irq_find_matching_fwnode(handle, DOMAIN_BUS_NEXUS); |
92 | if (!parent || !msi_get_domain_info(parent)) { | |
93 | pr_err("%s: unable to locate ITS domain\n", name); | |
94 | return; | |
95 | } | |
96 | ||
97 | mc_msi_domain = fsl_mc_msi_create_irq_domain(handle, | |
98 | &its_fsl_mc_msi_domain_info, | |
99 | parent); | |
100 | if (!mc_msi_domain) { | |
101 | pr_err("%s: unable to create fsl-mc domain\n", name); | |
102 | return; | |
103 | } | |
104 | ||
105 | pr_info("fsl-mc MSI: %s domain created\n", name); | |
106 | } | |
107 | ||
108 | #ifdef CONFIG_ACPI | |
109 | static int __init | |
110 | its_fsl_mc_msi_parse_madt(union acpi_subtable_headers *header, | |
111 | const unsigned long end) | |
112 | { | |
113 | struct acpi_madt_generic_translator *its_entry; | |
114 | struct fwnode_handle *dom_handle; | |
115 | const char *node_name; | |
116 | int err = 0; | |
117 | ||
118 | its_entry = (struct acpi_madt_generic_translator *)header; | |
119 | node_name = kasprintf(GFP_KERNEL, "ITS@0x%lx", | |
120 | (long)its_entry->base_address); | |
121 | ||
122 | dom_handle = iort_find_domain_token(its_entry->translation_id); | |
123 | if (!dom_handle) { | |
124 | pr_err("%s: Unable to locate ITS domain handle\n", node_name); | |
125 | err = -ENXIO; | |
126 | goto out; | |
127 | } | |
128 | ||
129 | its_fsl_mc_msi_init_one(dom_handle, node_name); | |
130 | ||
131 | out: | |
132 | kfree(node_name); | |
133 | return err; | |
134 | } | |
135 | ||
136 | ||
137 | static void __init its_fsl_mc_acpi_msi_init(void) | |
138 | { | |
139 | acpi_table_parse_madt(ACPI_MADT_TYPE_GENERIC_TRANSLATOR, | |
140 | its_fsl_mc_msi_parse_madt, 0); | |
141 | } | |
142 | #else | |
143 | static inline void its_fsl_mc_acpi_msi_init(void) { } | |
144 | #endif | |
145 | ||
146 | static void __init its_fsl_mc_of_msi_init(void) | |
147 | { | |
148 | struct device_node *np; | |
149 | ||
3a288fd5 GR |
150 | for (np = of_find_matching_node(NULL, its_device_id); np; |
151 | np = of_find_matching_node(np, its_device_id)) { | |
95a25625 SB |
152 | if (!of_device_is_available(np)) |
153 | continue; | |
3a288fd5 GR |
154 | if (!of_property_read_bool(np, "msi-controller")) |
155 | continue; | |
156 | ||
6305166c MP |
157 | its_fsl_mc_msi_init_one(of_node_to_fwnode(np), |
158 | np->full_name); | |
3a288fd5 | 159 | } |
6305166c MP |
160 | } |
161 | ||
162 | static int __init its_fsl_mc_msi_init(void) | |
163 | { | |
164 | its_fsl_mc_of_msi_init(); | |
165 | its_fsl_mc_acpi_msi_init(); | |
3a288fd5 GR |
166 | |
167 | return 0; | |
168 | } | |
1d11d556 IR |
169 | |
170 | early_initcall(its_fsl_mc_msi_init); |