diff options
author | Minghuan Lian <Minghuan.Lian@nxp.com> | 2017-07-05 02:59:01 -0400 |
---|---|---|
committer | Marc Zyngier <marc.zyngier@arm.com> | 2017-08-31 11:19:34 -0400 |
commit | 4dd5da65a39d9a0405304fdef0804afffece044b (patch) | |
tree | 6ae8318a51d2af9a04b9400b80e2b31e687ba314 /drivers/irqchip | |
parent | cb3421684ee778d60da26232bfea626dca2eb8db (diff) |
irqchip/ls-scfg-msi: Add LS1046a MSI support
LS1046a includes 4 MSIRs, each MSIR is assigned a dedicate GIC
SPI interrupt and provides 32 MSI interrupts. Compared to previous
MSI, LS1046a's IBS(interrupt bit select) shift is changed to 2 and
total MSI interrupt number is changed to 128.
The patch adds structure 'ls_scfg_msir' to describe MSIR setting and
'ibs_shift' to store the different value between the SoCs.
Signed-off-by: Minghuan Lian <Minghuan.Lian@nxp.com>
Signed-off-by: Hou Zhiqiang <Zhiqiang.Hou@nxp.com>
Signed-off-by: Marc Zyngier <marc.zyngier@arm.com>
Diffstat (limited to 'drivers/irqchip')
-rw-r--r-- | drivers/irqchip/irq-ls-scfg-msi.c | 165 |
1 files changed, 130 insertions, 35 deletions
diff --git a/drivers/irqchip/irq-ls-scfg-msi.c b/drivers/irqchip/irq-ls-scfg-msi.c index cef67cc5c0f2..0b1f34ddab00 100644 --- a/drivers/irqchip/irq-ls-scfg-msi.c +++ b/drivers/irqchip/irq-ls-scfg-msi.c | |||
@@ -17,13 +17,24 @@ | |||
17 | #include <linux/irq.h> | 17 | #include <linux/irq.h> |
18 | #include <linux/irqchip/chained_irq.h> | 18 | #include <linux/irqchip/chained_irq.h> |
19 | #include <linux/irqdomain.h> | 19 | #include <linux/irqdomain.h> |
20 | #include <linux/of_irq.h> | ||
20 | #include <linux/of_pci.h> | 21 | #include <linux/of_pci.h> |
21 | #include <linux/of_platform.h> | 22 | #include <linux/of_platform.h> |
22 | #include <linux/spinlock.h> | 23 | #include <linux/spinlock.h> |
23 | 24 | ||
24 | #define MSI_MAX_IRQS 32 | 25 | #define MSI_IRQS_PER_MSIR 32 |
25 | #define MSI_IBS_SHIFT 3 | 26 | #define MSI_MSIR_OFFSET 4 |
26 | #define MSIR 4 | 27 | |
28 | struct ls_scfg_msi_cfg { | ||
29 | u32 ibs_shift; /* Shift of interrupt bit select */ | ||
30 | }; | ||
31 | |||
32 | struct ls_scfg_msir { | ||
33 | struct ls_scfg_msi *msi_data; | ||
34 | unsigned int index; | ||
35 | unsigned int gic_irq; | ||
36 | void __iomem *reg; | ||
37 | }; | ||
27 | 38 | ||
28 | struct ls_scfg_msi { | 39 | struct ls_scfg_msi { |
29 | spinlock_t lock; | 40 | spinlock_t lock; |
@@ -32,8 +43,11 @@ struct ls_scfg_msi { | |||
32 | struct irq_domain *msi_domain; | 43 | struct irq_domain *msi_domain; |
33 | void __iomem *regs; | 44 | void __iomem *regs; |
34 | phys_addr_t msiir_addr; | 45 | phys_addr_t msiir_addr; |
35 | int irq; | 46 | struct ls_scfg_msi_cfg *cfg; |
36 | DECLARE_BITMAP(used, MSI_MAX_IRQS); | 47 | u32 msir_num; |
48 | struct ls_scfg_msir *msir; | ||
49 | u32 irqs_num; | ||
50 | unsigned long *used; | ||
37 | }; | 51 | }; |
38 | 52 | ||
39 | static struct irq_chip ls_scfg_msi_irq_chip = { | 53 | static struct irq_chip ls_scfg_msi_irq_chip = { |
@@ -55,7 +69,7 @@ static void ls_scfg_msi_compose_msg(struct irq_data *data, struct msi_msg *msg) | |||
55 | 69 | ||
56 | msg->address_hi = upper_32_bits(msi_data->msiir_addr); | 70 | msg->address_hi = upper_32_bits(msi_data->msiir_addr); |
57 | msg->address_lo = lower_32_bits(msi_data->msiir_addr); | 71 | msg->address_lo = lower_32_bits(msi_data->msiir_addr); |
58 | msg->data = data->hwirq << MSI_IBS_SHIFT; | 72 | msg->data = data->hwirq; |
59 | } | 73 | } |
60 | 74 | ||
61 | static int ls_scfg_msi_set_affinity(struct irq_data *irq_data, | 75 | static int ls_scfg_msi_set_affinity(struct irq_data *irq_data, |
@@ -81,8 +95,8 @@ static int ls_scfg_msi_domain_irq_alloc(struct irq_domain *domain, | |||
81 | WARN_ON(nr_irqs != 1); | 95 | WARN_ON(nr_irqs != 1); |
82 | 96 | ||
83 | spin_lock(&msi_data->lock); | 97 | spin_lock(&msi_data->lock); |
84 | pos = find_first_zero_bit(msi_data->used, MSI_MAX_IRQS); | 98 | pos = find_first_zero_bit(msi_data->used, msi_data->irqs_num); |
85 | if (pos < MSI_MAX_IRQS) | 99 | if (pos < msi_data->irqs_num) |
86 | __set_bit(pos, msi_data->used); | 100 | __set_bit(pos, msi_data->used); |
87 | else | 101 | else |
88 | err = -ENOSPC; | 102 | err = -ENOSPC; |
@@ -106,7 +120,7 @@ static void ls_scfg_msi_domain_irq_free(struct irq_domain *domain, | |||
106 | int pos; | 120 | int pos; |
107 | 121 | ||
108 | pos = d->hwirq; | 122 | pos = d->hwirq; |
109 | if (pos < 0 || pos >= MSI_MAX_IRQS) { | 123 | if (pos < 0 || pos >= msi_data->irqs_num) { |
110 | pr_err("failed to teardown msi. Invalid hwirq %d\n", pos); | 124 | pr_err("failed to teardown msi. Invalid hwirq %d\n", pos); |
111 | return; | 125 | return; |
112 | } | 126 | } |
@@ -123,15 +137,17 @@ static const struct irq_domain_ops ls_scfg_msi_domain_ops = { | |||
123 | 137 | ||
124 | static void ls_scfg_msi_irq_handler(struct irq_desc *desc) | 138 | static void ls_scfg_msi_irq_handler(struct irq_desc *desc) |
125 | { | 139 | { |
126 | struct ls_scfg_msi *msi_data = irq_desc_get_handler_data(desc); | 140 | struct ls_scfg_msir *msir = irq_desc_get_handler_data(desc); |
141 | struct ls_scfg_msi *msi_data = msir->msi_data; | ||
127 | unsigned long val; | 142 | unsigned long val; |
128 | int pos, virq; | 143 | int pos, virq, hwirq; |
129 | 144 | ||
130 | chained_irq_enter(irq_desc_get_chip(desc), desc); | 145 | chained_irq_enter(irq_desc_get_chip(desc), desc); |
131 | 146 | ||
132 | val = ioread32be(msi_data->regs + MSIR); | 147 | val = ioread32be(msir->reg); |
133 | for_each_set_bit(pos, &val, MSI_MAX_IRQS) { | 148 | for_each_set_bit(pos, &val, MSI_IRQS_PER_MSIR) { |
134 | virq = irq_find_mapping(msi_data->parent, (31 - pos)); | 149 | hwirq = ((31 - pos) << msi_data->cfg->ibs_shift) | msir->index; |
150 | virq = irq_find_mapping(msi_data->parent, hwirq); | ||
135 | if (virq) | 151 | if (virq) |
136 | generic_handle_irq(virq); | 152 | generic_handle_irq(virq); |
137 | } | 153 | } |
@@ -143,7 +159,7 @@ static int ls_scfg_msi_domains_init(struct ls_scfg_msi *msi_data) | |||
143 | { | 159 | { |
144 | /* Initialize MSI domain parent */ | 160 | /* Initialize MSI domain parent */ |
145 | msi_data->parent = irq_domain_add_linear(NULL, | 161 | msi_data->parent = irq_domain_add_linear(NULL, |
146 | MSI_MAX_IRQS, | 162 | msi_data->irqs_num, |
147 | &ls_scfg_msi_domain_ops, | 163 | &ls_scfg_msi_domain_ops, |
148 | msi_data); | 164 | msi_data); |
149 | if (!msi_data->parent) { | 165 | if (!msi_data->parent) { |
@@ -164,16 +180,87 @@ static int ls_scfg_msi_domains_init(struct ls_scfg_msi *msi_data) | |||
164 | return 0; | 180 | return 0; |
165 | } | 181 | } |
166 | 182 | ||
183 | static int ls_scfg_msi_setup_hwirq(struct ls_scfg_msi *msi_data, int index) | ||
184 | { | ||
185 | struct ls_scfg_msir *msir; | ||
186 | int virq, i, hwirq; | ||
187 | |||
188 | virq = platform_get_irq(msi_data->pdev, index); | ||
189 | if (virq <= 0) | ||
190 | return -ENODEV; | ||
191 | |||
192 | msir = &msi_data->msir[index]; | ||
193 | msir->index = index; | ||
194 | msir->msi_data = msi_data; | ||
195 | msir->gic_irq = virq; | ||
196 | msir->reg = msi_data->regs + MSI_MSIR_OFFSET + 4 * index; | ||
197 | |||
198 | irq_set_chained_handler_and_data(msir->gic_irq, | ||
199 | ls_scfg_msi_irq_handler, | ||
200 | msir); | ||
201 | |||
202 | /* Release the hwirqs corresponding to this MSIR */ | ||
203 | for (i = 0; i < MSI_IRQS_PER_MSIR; i++) { | ||
204 | hwirq = i << msi_data->cfg->ibs_shift | msir->index; | ||
205 | bitmap_clear(msi_data->used, hwirq, 1); | ||
206 | } | ||
207 | |||
208 | return 0; | ||
209 | } | ||
210 | |||
211 | static int ls_scfg_msi_teardown_hwirq(struct ls_scfg_msir *msir) | ||
212 | { | ||
213 | struct ls_scfg_msi *msi_data = msir->msi_data; | ||
214 | int i, hwirq; | ||
215 | |||
216 | if (msir->gic_irq > 0) | ||
217 | irq_set_chained_handler_and_data(msir->gic_irq, NULL, NULL); | ||
218 | |||
219 | for (i = 0; i < MSI_IRQS_PER_MSIR; i++) { | ||
220 | hwirq = i << msi_data->cfg->ibs_shift | msir->index; | ||
221 | bitmap_set(msi_data->used, hwirq, 1); | ||
222 | } | ||
223 | |||
224 | return 0; | ||
225 | } | ||
226 | |||
227 | static struct ls_scfg_msi_cfg ls1021_msi_cfg = { | ||
228 | .ibs_shift = 3, | ||
229 | }; | ||
230 | |||
231 | static struct ls_scfg_msi_cfg ls1046_msi_cfg = { | ||
232 | .ibs_shift = 2, | ||
233 | }; | ||
234 | |||
235 | static const struct of_device_id ls_scfg_msi_id[] = { | ||
236 | /* The following two misspelled compatibles are obsolete */ | ||
237 | { .compatible = "fsl,1s1021a-msi", .data = &ls1021_msi_cfg}, | ||
238 | { .compatible = "fsl,1s1043a-msi", .data = &ls1021_msi_cfg}, | ||
239 | |||
240 | { .compatible = "fsl,ls1021a-msi", .data = &ls1021_msi_cfg }, | ||
241 | { .compatible = "fsl,ls1043a-msi", .data = &ls1021_msi_cfg }, | ||
242 | { .compatible = "fsl,ls1046a-msi", .data = &ls1046_msi_cfg }, | ||
243 | {}, | ||
244 | }; | ||
245 | MODULE_DEVICE_TABLE(of, ls_scfg_msi_id); | ||
246 | |||
167 | static int ls_scfg_msi_probe(struct platform_device *pdev) | 247 | static int ls_scfg_msi_probe(struct platform_device *pdev) |
168 | { | 248 | { |
249 | const struct of_device_id *match; | ||
169 | struct ls_scfg_msi *msi_data; | 250 | struct ls_scfg_msi *msi_data; |
170 | struct resource *res; | 251 | struct resource *res; |
171 | int ret; | 252 | int i, ret; |
253 | |||
254 | match = of_match_device(ls_scfg_msi_id, &pdev->dev); | ||
255 | if (!match) | ||
256 | return -ENODEV; | ||
172 | 257 | ||
173 | msi_data = devm_kzalloc(&pdev->dev, sizeof(*msi_data), GFP_KERNEL); | 258 | msi_data = devm_kzalloc(&pdev->dev, sizeof(*msi_data), GFP_KERNEL); |
174 | if (!msi_data) | 259 | if (!msi_data) |
175 | return -ENOMEM; | 260 | return -ENOMEM; |
176 | 261 | ||
262 | msi_data->cfg = (struct ls_scfg_msi_cfg *) match->data; | ||
263 | |||
177 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | 264 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); |
178 | msi_data->regs = devm_ioremap_resource(&pdev->dev, res); | 265 | msi_data->regs = devm_ioremap_resource(&pdev->dev, res); |
179 | if (IS_ERR(msi_data->regs)) { | 266 | if (IS_ERR(msi_data->regs)) { |
@@ -182,23 +269,37 @@ static int ls_scfg_msi_probe(struct platform_device *pdev) | |||
182 | } | 269 | } |
183 | msi_data->msiir_addr = res->start; | 270 | msi_data->msiir_addr = res->start; |
184 | 271 | ||
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; | 272 | msi_data->pdev = pdev; |
192 | spin_lock_init(&msi_data->lock); | 273 | spin_lock_init(&msi_data->lock); |
193 | 274 | ||
275 | msi_data->irqs_num = MSI_IRQS_PER_MSIR * | ||
276 | (1 << msi_data->cfg->ibs_shift); | ||
277 | msi_data->used = devm_kcalloc(&pdev->dev, | ||
278 | BITS_TO_LONGS(msi_data->irqs_num), | ||
279 | sizeof(*msi_data->used), | ||
280 | GFP_KERNEL); | ||
281 | if (!msi_data->used) | ||
282 | return -ENOMEM; | ||
283 | /* | ||
284 | * Reserve all the hwirqs | ||
285 | * The available hwirqs will be released in ls1_msi_setup_hwirq() | ||
286 | */ | ||
287 | bitmap_set(msi_data->used, 0, msi_data->irqs_num); | ||
288 | |||
289 | msi_data->msir_num = of_irq_count(pdev->dev.of_node); | ||
290 | msi_data->msir = devm_kcalloc(&pdev->dev, msi_data->msir_num, | ||
291 | sizeof(*msi_data->msir), | ||
292 | GFP_KERNEL); | ||
293 | if (!msi_data->msir) | ||
294 | return -ENOMEM; | ||
295 | |||
296 | for (i = 0; i < msi_data->msir_num; i++) | ||
297 | ls_scfg_msi_setup_hwirq(msi_data, i); | ||
298 | |||
194 | ret = ls_scfg_msi_domains_init(msi_data); | 299 | ret = ls_scfg_msi_domains_init(msi_data); |
195 | if (ret) | 300 | if (ret) |
196 | return ret; | 301 | return ret; |
197 | 302 | ||
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); | 303 | platform_set_drvdata(pdev, msi_data); |
203 | 304 | ||
204 | return 0; | 305 | return 0; |
@@ -207,8 +308,10 @@ static int ls_scfg_msi_probe(struct platform_device *pdev) | |||
207 | static int ls_scfg_msi_remove(struct platform_device *pdev) | 308 | static int ls_scfg_msi_remove(struct platform_device *pdev) |
208 | { | 309 | { |
209 | struct ls_scfg_msi *msi_data = platform_get_drvdata(pdev); | 310 | struct ls_scfg_msi *msi_data = platform_get_drvdata(pdev); |
311 | int i; | ||
210 | 312 | ||
211 | irq_set_chained_handler_and_data(msi_data->irq, NULL, NULL); | 313 | for (i = 0; i < msi_data->msir_num; i++) |
314 | ls_scfg_msi_teardown_hwirq(&msi_data->msir[i]); | ||
212 | 315 | ||
213 | irq_domain_remove(msi_data->msi_domain); | 316 | irq_domain_remove(msi_data->msi_domain); |
214 | irq_domain_remove(msi_data->parent); | 317 | irq_domain_remove(msi_data->parent); |
@@ -218,14 +321,6 @@ static int ls_scfg_msi_remove(struct platform_device *pdev) | |||
218 | return 0; | 321 | return 0; |
219 | } | 322 | } |
220 | 323 | ||
221 | static const struct of_device_id ls_scfg_msi_id[] = { | ||
222 | { .compatible = "fsl,1s1021a-msi", }, /* a typo */ | ||
223 | { .compatible = "fsl,1s1043a-msi", }, /* a typo */ | ||
224 | { .compatible = "fsl,ls1021a-msi", }, | ||
225 | { .compatible = "fsl,ls1043a-msi", }, | ||
226 | {}, | ||
227 | }; | ||
228 | |||
229 | static struct platform_driver ls_scfg_msi_driver = { | 324 | static struct platform_driver ls_scfg_msi_driver = { |
230 | .driver = { | 325 | .driver = { |
231 | .name = "ls-scfg-msi", | 326 | .name = "ls-scfg-msi", |