1 // SPDX-License-Identifier: GPL-2.0
3 * Altera PCIe MSI support
5 * Author: Ley Foon Tan <lftan@altera.com>
7 * Copyright Altera Corporation (C) 2013-2015. All rights reserved
10 #include <linux/interrupt.h>
11 #include <linux/irqchip/chained_irq.h>
12 #include <linux/irqdomain.h>
13 #include <linux/init.h>
14 #include <linux/module.h>
15 #include <linux/msi.h>
16 #include <linux/of_address.h>
17 #include <linux/of_pci.h>
18 #include <linux/pci.h>
19 #include <linux/platform_device.h>
20 #include <linux/slab.h>
22 #define MSI_STATUS 0x0
24 #define MSI_INTMASK 0x8
26 #define MAX_MSI_VECTORS 32
29 DECLARE_BITMAP(used
, MAX_MSI_VECTORS
);
30 struct mutex lock
; /* protect "used" bitmap */
31 struct platform_device
*pdev
;
32 struct irq_domain
*msi_domain
;
33 struct irq_domain
*inner_domain
;
34 void __iomem
*csr_base
;
35 void __iomem
*vector_base
;
36 phys_addr_t vector_phy
;
41 static inline void msi_writel(struct altera_msi
*msi
, const u32 value
,
44 writel_relaxed(value
, msi
->csr_base
+ reg
);
47 static inline u32
msi_readl(struct altera_msi
*msi
, const u32 reg
)
49 return readl_relaxed(msi
->csr_base
+ reg
);
52 static void altera_msi_isr(struct irq_desc
*desc
)
54 struct irq_chip
*chip
= irq_desc_get_chip(desc
);
55 struct altera_msi
*msi
;
60 chained_irq_enter(chip
, desc
);
61 msi
= irq_desc_get_handler_data(desc
);
63 while ((status
= msi_readl(msi
, MSI_STATUS
)) != 0) {
64 for_each_set_bit(bit
, &status
, msi
->num_of_vectors
) {
65 /* Dummy read from vector to clear the interrupt */
66 readl_relaxed(msi
->vector_base
+ (bit
* sizeof(u32
)));
68 ret
= generic_handle_domain_irq(msi
->inner_domain
, bit
);
70 dev_err_ratelimited(&msi
->pdev
->dev
, "unexpected MSI\n");
74 chained_irq_exit(chip
, desc
);
77 static struct irq_chip altera_msi_irq_chip
= {
78 .name
= "Altera PCIe MSI",
79 .irq_mask
= pci_msi_mask_irq
,
80 .irq_unmask
= pci_msi_unmask_irq
,
83 static struct msi_domain_info altera_msi_domain_info
= {
84 .flags
= MSI_FLAG_USE_DEF_DOM_OPS
| MSI_FLAG_USE_DEF_CHIP_OPS
|
85 MSI_FLAG_NO_AFFINITY
| MSI_FLAG_PCI_MSIX
,
86 .chip
= &altera_msi_irq_chip
,
89 static void altera_compose_msi_msg(struct irq_data
*data
, struct msi_msg
*msg
)
91 struct altera_msi
*msi
= irq_data_get_irq_chip_data(data
);
92 phys_addr_t addr
= msi
->vector_phy
+ (data
->hwirq
* sizeof(u32
));
94 msg
->address_lo
= lower_32_bits(addr
);
95 msg
->address_hi
= upper_32_bits(addr
);
96 msg
->data
= data
->hwirq
;
98 dev_dbg(&msi
->pdev
->dev
, "msi#%d address_hi %#x address_lo %#x\n",
99 (int)data
->hwirq
, msg
->address_hi
, msg
->address_lo
);
102 static struct irq_chip altera_msi_bottom_irq_chip
= {
103 .name
= "Altera MSI",
104 .irq_compose_msi_msg
= altera_compose_msi_msg
,
107 static int altera_irq_domain_alloc(struct irq_domain
*domain
, unsigned int virq
,
108 unsigned int nr_irqs
, void *args
)
110 struct altera_msi
*msi
= domain
->host_data
;
114 WARN_ON(nr_irqs
!= 1);
115 mutex_lock(&msi
->lock
);
117 bit
= find_first_zero_bit(msi
->used
, msi
->num_of_vectors
);
118 if (bit
>= msi
->num_of_vectors
) {
119 mutex_unlock(&msi
->lock
);
123 set_bit(bit
, msi
->used
);
125 mutex_unlock(&msi
->lock
);
127 irq_domain_set_info(domain
, virq
, bit
, &altera_msi_bottom_irq_chip
,
128 domain
->host_data
, handle_simple_irq
,
131 mask
= msi_readl(msi
, MSI_INTMASK
);
133 msi_writel(msi
, mask
, MSI_INTMASK
);
138 static void altera_irq_domain_free(struct irq_domain
*domain
,
139 unsigned int virq
, unsigned int nr_irqs
)
141 struct irq_data
*d
= irq_domain_get_irq_data(domain
, virq
);
142 struct altera_msi
*msi
= irq_data_get_irq_chip_data(d
);
145 mutex_lock(&msi
->lock
);
147 if (!test_bit(d
->hwirq
, msi
->used
)) {
148 dev_err(&msi
->pdev
->dev
, "trying to free unused MSI#%lu\n",
151 __clear_bit(d
->hwirq
, msi
->used
);
152 mask
= msi_readl(msi
, MSI_INTMASK
);
153 mask
&= ~(1 << d
->hwirq
);
154 msi_writel(msi
, mask
, MSI_INTMASK
);
157 mutex_unlock(&msi
->lock
);
160 static const struct irq_domain_ops msi_domain_ops
= {
161 .alloc
= altera_irq_domain_alloc
,
162 .free
= altera_irq_domain_free
,
165 static int altera_allocate_domains(struct altera_msi
*msi
)
167 struct fwnode_handle
*fwnode
= of_node_to_fwnode(msi
->pdev
->dev
.of_node
);
169 msi
->inner_domain
= irq_domain_add_linear(NULL
, msi
->num_of_vectors
,
170 &msi_domain_ops
, msi
);
171 if (!msi
->inner_domain
) {
172 dev_err(&msi
->pdev
->dev
, "failed to create IRQ domain\n");
176 msi
->msi_domain
= pci_msi_create_irq_domain(fwnode
,
177 &altera_msi_domain_info
, msi
->inner_domain
);
178 if (!msi
->msi_domain
) {
179 dev_err(&msi
->pdev
->dev
, "failed to create MSI domain\n");
180 irq_domain_remove(msi
->inner_domain
);
187 static void altera_free_domains(struct altera_msi
*msi
)
189 irq_domain_remove(msi
->msi_domain
);
190 irq_domain_remove(msi
->inner_domain
);
193 static void altera_msi_remove(struct platform_device
*pdev
)
195 struct altera_msi
*msi
= platform_get_drvdata(pdev
);
197 msi_writel(msi
, 0, MSI_INTMASK
);
198 irq_set_chained_handler_and_data(msi
->irq
, NULL
, NULL
);
200 altera_free_domains(msi
);
202 platform_set_drvdata(pdev
, NULL
);
205 static int altera_msi_probe(struct platform_device
*pdev
)
207 struct altera_msi
*msi
;
208 struct device_node
*np
= pdev
->dev
.of_node
;
209 struct resource
*res
;
212 msi
= devm_kzalloc(&pdev
->dev
, sizeof(struct altera_msi
),
217 mutex_init(&msi
->lock
);
220 msi
->csr_base
= devm_platform_ioremap_resource_byname(pdev
, "csr");
221 if (IS_ERR(msi
->csr_base
)) {
222 dev_err(&pdev
->dev
, "failed to map csr memory\n");
223 return PTR_ERR(msi
->csr_base
);
226 res
= platform_get_resource_byname(pdev
, IORESOURCE_MEM
,
228 msi
->vector_base
= devm_ioremap_resource(&pdev
->dev
, res
);
229 if (IS_ERR(msi
->vector_base
))
230 return PTR_ERR(msi
->vector_base
);
232 msi
->vector_phy
= res
->start
;
234 if (of_property_read_u32(np
, "num-vectors", &msi
->num_of_vectors
)) {
235 dev_err(&pdev
->dev
, "failed to parse the number of vectors\n");
239 ret
= altera_allocate_domains(msi
);
243 msi
->irq
= platform_get_irq(pdev
, 0);
249 irq_set_chained_handler_and_data(msi
->irq
, altera_msi_isr
, msi
);
250 platform_set_drvdata(pdev
, msi
);
255 altera_msi_remove(pdev
);
259 static const struct of_device_id altera_msi_of_match
[] = {
260 { .compatible
= "altr,msi-1.0", NULL
},
264 static struct platform_driver altera_msi_driver
= {
266 .name
= "altera-msi",
267 .of_match_table
= altera_msi_of_match
,
269 .probe
= altera_msi_probe
,
270 .remove
= altera_msi_remove
,
273 static int __init
altera_msi_init(void)
275 return platform_driver_register(&altera_msi_driver
);
278 static void __exit
altera_msi_exit(void)
280 platform_driver_unregister(&altera_msi_driver
);
283 subsys_initcall(altera_msi_init
);
284 MODULE_DEVICE_TABLE(of
, altera_msi_of_match
);
285 module_exit(altera_msi_exit
);
286 MODULE_DESCRIPTION("Altera PCIe MSI support driver");
287 MODULE_LICENSE("GPL v2");