2 * MSI framework for platform devices
4 * Copyright (C) 2015 ARM Limited, All Rights Reserved.
5 * Author: Marc Zyngier <marc.zyngier@arm.com>
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License version 2 as
9 * published by the Free Software Foundation.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
20 #include <linux/device.h>
21 #include <linux/idr.h>
22 #include <linux/irq.h>
23 #include <linux/irqdomain.h>
24 #include <linux/msi.h>
25 #include <linux/slab.h>
27 #define DEV_ID_SHIFT 24
30 * Internal data structure containing a (made up, but unique) devid
31 * and the callback to write the MSI message.
33 struct platform_msi_priv_data
{
34 irq_write_msi_msg_t write_msg
;
38 /* The devid allocator */
39 static DEFINE_IDA(platform_msi_devid_ida
);
41 #ifdef GENERIC_MSI_DOMAIN_OPS
43 * Convert an msi_desc to a globaly unique identifier (per-device
44 * devid + msi_desc position in the msi_list).
46 static irq_hw_number_t
platform_msi_calc_hwirq(struct msi_desc
*desc
)
50 devid
= desc
->platform
.msi_priv_data
->devid
;
52 return (devid
<< (32 - DEV_ID_SHIFT
)) | desc
->platform
.msi_index
;
55 static void platform_msi_set_desc(msi_alloc_info_t
*arg
, struct msi_desc
*desc
)
58 arg
->hwirq
= platform_msi_calc_hwirq(desc
);
61 static int platform_msi_init(struct irq_domain
*domain
,
62 struct msi_domain_info
*info
,
63 unsigned int virq
, irq_hw_number_t hwirq
,
64 msi_alloc_info_t
*arg
)
66 return irq_domain_set_hwirq_and_chip(domain
, virq
, hwirq
,
67 info
->chip
, info
->chip_data
);
70 #define platform_msi_set_desc NULL
71 #define platform_msi_init NULL
74 static void platform_msi_update_dom_ops(struct msi_domain_info
*info
)
76 struct msi_domain_ops
*ops
= info
->ops
;
80 if (ops
->msi_init
== NULL
)
81 ops
->msi_init
= platform_msi_init
;
82 if (ops
->set_desc
== NULL
)
83 ops
->set_desc
= platform_msi_set_desc
;
86 static void platform_msi_write_msg(struct irq_data
*data
, struct msi_msg
*msg
)
88 struct msi_desc
*desc
= irq_data_get_msi_desc(data
);
89 struct platform_msi_priv_data
*priv_data
;
91 priv_data
= desc
->platform
.msi_priv_data
;
93 priv_data
->write_msg(desc
, msg
);
96 static void platform_msi_update_chip_ops(struct msi_domain_info
*info
)
98 struct irq_chip
*chip
= info
->chip
;
102 chip
->irq_mask
= irq_chip_mask_parent
;
103 if (!chip
->irq_unmask
)
104 chip
->irq_unmask
= irq_chip_unmask_parent
;
106 chip
->irq_eoi
= irq_chip_eoi_parent
;
107 if (!chip
->irq_set_affinity
)
108 chip
->irq_set_affinity
= msi_domain_set_affinity
;
109 if (!chip
->irq_write_msi_msg
)
110 chip
->irq_write_msi_msg
= platform_msi_write_msg
;
113 static void platform_msi_free_descs(struct device
*dev
)
115 struct msi_desc
*desc
, *tmp
;
117 list_for_each_entry_safe(desc
, tmp
, dev_to_msi_list(dev
), list
) {
118 list_del(&desc
->list
);
119 free_msi_entry(desc
);
123 static int platform_msi_alloc_descs(struct device
*dev
, int nvec
,
124 struct platform_msi_priv_data
*data
)
129 for (i
= 0; i
< nvec
; i
++) {
130 struct msi_desc
*desc
;
132 desc
= alloc_msi_entry(dev
);
136 desc
->platform
.msi_priv_data
= data
;
137 desc
->platform
.msi_index
= i
;
140 list_add_tail(&desc
->list
, dev_to_msi_list(dev
));
144 /* Clean up the mess */
145 platform_msi_free_descs(dev
);
154 * platform_msi_create_irq_domain - Create a platform MSI interrupt domain
155 * @fwnode: Optional fwnode of the interrupt controller
156 * @info: MSI domain info
157 * @parent: Parent irq domain
159 * Updates the domain and chip ops and creates a platform MSI
163 * A domain pointer or NULL in case of failure.
165 struct irq_domain
*platform_msi_create_irq_domain(struct fwnode_handle
*fwnode
,
166 struct msi_domain_info
*info
,
167 struct irq_domain
*parent
)
169 struct irq_domain
*domain
;
171 if (info
->flags
& MSI_FLAG_USE_DEF_DOM_OPS
)
172 platform_msi_update_dom_ops(info
);
173 if (info
->flags
& MSI_FLAG_USE_DEF_CHIP_OPS
)
174 platform_msi_update_chip_ops(info
);
176 domain
= msi_create_irq_domain(fwnode
, info
, parent
);
178 domain
->bus_token
= DOMAIN_BUS_PLATFORM_MSI
;
184 * platform_msi_domain_alloc_irqs - Allocate MSI interrupts for @dev
185 * @dev: The device for which to allocate interrupts
186 * @nvec: The number of interrupts to allocate
187 * @write_msi_msg: Callback to write an interrupt message for @dev
190 * Zero for success, or an error code in case of failure
192 int platform_msi_domain_alloc_irqs(struct device
*dev
, unsigned int nvec
,
193 irq_write_msi_msg_t write_msi_msg
)
195 struct platform_msi_priv_data
*priv_data
;
199 * Limit the number of interrupts to 256 per device. Should we
200 * need to bump this up, DEV_ID_SHIFT should be adjusted
201 * accordingly (which would impact the max number of MSI
204 if (!dev
->msi_domain
|| !write_msi_msg
|| !nvec
||
205 nvec
> (1 << (32 - DEV_ID_SHIFT
)))
208 if (dev
->msi_domain
->bus_token
!= DOMAIN_BUS_PLATFORM_MSI
) {
209 dev_err(dev
, "Incompatible msi_domain, giving up\n");
213 /* Already had a helping of MSI? Greed... */
214 if (!list_empty(dev_to_msi_list(dev
)))
217 priv_data
= kzalloc(sizeof(*priv_data
), GFP_KERNEL
);
221 priv_data
->devid
= ida_simple_get(&platform_msi_devid_ida
,
222 0, 1 << DEV_ID_SHIFT
, GFP_KERNEL
);
223 if (priv_data
->devid
< 0) {
224 err
= priv_data
->devid
;
228 priv_data
->write_msg
= write_msi_msg
;
230 err
= platform_msi_alloc_descs(dev
, nvec
, priv_data
);
234 err
= msi_domain_alloc_irqs(dev
->msi_domain
, dev
, nvec
);
241 platform_msi_free_descs(dev
);
243 ida_simple_remove(&platform_msi_devid_ida
, priv_data
->devid
);
251 * platform_msi_domain_free_irqs - Free MSI interrupts for @dev
252 * @dev: The device for which to free interrupts
254 void platform_msi_domain_free_irqs(struct device
*dev
)
256 struct msi_desc
*desc
;
258 desc
= first_msi_entry(dev
);
260 struct platform_msi_priv_data
*data
;
262 data
= desc
->platform
.msi_priv_data
;
264 ida_simple_remove(&platform_msi_devid_ida
, data
->devid
);
268 msi_domain_free_irqs(dev
->msi_domain
, dev
);
269 platform_msi_free_descs(dev
);