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 struct irq_data
*data
;
68 irq_domain_set_hwirq_and_chip(domain
, virq
, hwirq
,
69 info
->chip
, info
->chip_data
);
72 * Save the MSI descriptor in handler_data so that the
73 * irq_write_msi_msg callback can retrieve it (and the
76 data
= irq_domain_get_irq_data(domain
, virq
);
77 data
->handler_data
= arg
->desc
;
82 #define platform_msi_set_desc NULL
83 #define platform_msi_init NULL
86 static void platform_msi_update_dom_ops(struct msi_domain_info
*info
)
88 struct msi_domain_ops
*ops
= info
->ops
;
92 if (ops
->msi_init
== NULL
)
93 ops
->msi_init
= platform_msi_init
;
94 if (ops
->set_desc
== NULL
)
95 ops
->set_desc
= platform_msi_set_desc
;
98 static void platform_msi_write_msg(struct irq_data
*data
, struct msi_msg
*msg
)
100 struct msi_desc
*desc
= irq_data_get_irq_handler_data(data
);
101 struct platform_msi_priv_data
*priv_data
;
103 priv_data
= desc
->platform
.msi_priv_data
;
105 priv_data
->write_msg(desc
, msg
);
108 static void platform_msi_update_chip_ops(struct msi_domain_info
*info
)
110 struct irq_chip
*chip
= info
->chip
;
114 chip
->irq_mask
= irq_chip_mask_parent
;
115 if (!chip
->irq_unmask
)
116 chip
->irq_unmask
= irq_chip_unmask_parent
;
118 chip
->irq_eoi
= irq_chip_eoi_parent
;
119 if (!chip
->irq_set_affinity
)
120 chip
->irq_set_affinity
= msi_domain_set_affinity
;
121 if (!chip
->irq_write_msi_msg
)
122 chip
->irq_write_msi_msg
= platform_msi_write_msg
;
125 static void platform_msi_free_descs(struct device
*dev
)
127 struct msi_desc
*desc
, *tmp
;
129 list_for_each_entry_safe(desc
, tmp
, dev_to_msi_list(dev
), list
) {
130 list_del(&desc
->list
);
131 free_msi_entry(desc
);
135 static int platform_msi_alloc_descs(struct device
*dev
, int nvec
,
136 struct platform_msi_priv_data
*data
)
141 for (i
= 0; i
< nvec
; i
++) {
142 struct msi_desc
*desc
;
144 desc
= alloc_msi_entry(dev
);
148 desc
->platform
.msi_priv_data
= data
;
149 desc
->platform
.msi_index
= i
;
152 list_add_tail(&desc
->list
, dev_to_msi_list(dev
));
156 /* Clean up the mess */
157 platform_msi_free_descs(dev
);
166 * platform_msi_create_irq_domain - Create a platform MSI interrupt domain
167 * @np: Optional device-tree node of the interrupt controller
168 * @info: MSI domain info
169 * @parent: Parent irq domain
171 * Updates the domain and chip ops and creates a platform MSI
175 * A domain pointer or NULL in case of failure.
177 struct irq_domain
*platform_msi_create_irq_domain(struct device_node
*np
,
178 struct msi_domain_info
*info
,
179 struct irq_domain
*parent
)
181 struct irq_domain
*domain
;
183 if (info
->flags
& MSI_FLAG_USE_DEF_DOM_OPS
)
184 platform_msi_update_dom_ops(info
);
185 if (info
->flags
& MSI_FLAG_USE_DEF_CHIP_OPS
)
186 platform_msi_update_chip_ops(info
);
188 domain
= msi_create_irq_domain(np
, info
, parent
);
190 domain
->bus_token
= DOMAIN_BUS_PLATFORM_MSI
;
196 * platform_msi_domain_alloc_irqs - Allocate MSI interrupts for @dev
197 * @dev: The device for which to allocate interrupts
198 * @nvec: The number of interrupts to allocate
199 * @write_msi_msg: Callback to write an interrupt message for @dev
202 * Zero for success, or an error code in case of failure
204 int platform_msi_domain_alloc_irqs(struct device
*dev
, unsigned int nvec
,
205 irq_write_msi_msg_t write_msi_msg
)
207 struct platform_msi_priv_data
*priv_data
;
211 * Limit the number of interrupts to 256 per device. Should we
212 * need to bump this up, DEV_ID_SHIFT should be adjusted
213 * accordingly (which would impact the max number of MSI
216 if (!dev
->msi_domain
|| !write_msi_msg
|| !nvec
||
217 nvec
> (1 << (32 - DEV_ID_SHIFT
)))
220 if (dev
->msi_domain
->bus_token
!= DOMAIN_BUS_PLATFORM_MSI
) {
221 dev_err(dev
, "Incompatible msi_domain, giving up\n");
225 /* Already had a helping of MSI? Greed... */
226 if (!list_empty(dev_to_msi_list(dev
)))
229 priv_data
= kzalloc(sizeof(*priv_data
), GFP_KERNEL
);
233 priv_data
->devid
= ida_simple_get(&platform_msi_devid_ida
,
234 0, 1 << DEV_ID_SHIFT
, GFP_KERNEL
);
235 if (priv_data
->devid
< 0) {
236 err
= priv_data
->devid
;
240 priv_data
->write_msg
= write_msi_msg
;
242 err
= platform_msi_alloc_descs(dev
, nvec
, priv_data
);
246 err
= msi_domain_alloc_irqs(dev
->msi_domain
, dev
, nvec
);
253 platform_msi_free_descs(dev
);
255 ida_simple_remove(&platform_msi_devid_ida
, priv_data
->devid
);
263 * platform_msi_domain_free_irqs - Free MSI interrupts for @dev
264 * @dev: The device for which to free interrupts
266 void platform_msi_domain_free_irqs(struct device
*dev
)
268 struct msi_desc
*desc
;
270 desc
= first_msi_entry(dev
);
272 struct platform_msi_priv_data
*data
;
274 data
= desc
->platform
.msi_priv_data
;
276 ida_simple_remove(&platform_msi_devid_ida
, data
->devid
);
280 msi_domain_free_irqs(dev
->msi_domain
, dev
);
281 platform_msi_free_descs(dev
);