162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * MSI framework for platform devices
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Copyright (C) 2015 ARM Limited, All Rights Reserved.
662306a36Sopenharmony_ci * Author: Marc Zyngier <marc.zyngier@arm.com>
762306a36Sopenharmony_ci */
862306a36Sopenharmony_ci
962306a36Sopenharmony_ci#include <linux/device.h>
1062306a36Sopenharmony_ci#include <linux/idr.h>
1162306a36Sopenharmony_ci#include <linux/irq.h>
1262306a36Sopenharmony_ci#include <linux/irqdomain.h>
1362306a36Sopenharmony_ci#include <linux/msi.h>
1462306a36Sopenharmony_ci#include <linux/slab.h>
1562306a36Sopenharmony_ci
1662306a36Sopenharmony_ci#define DEV_ID_SHIFT	21
1762306a36Sopenharmony_ci#define MAX_DEV_MSIS	(1 << (32 - DEV_ID_SHIFT))
1862306a36Sopenharmony_ci
1962306a36Sopenharmony_ci/*
2062306a36Sopenharmony_ci * Internal data structure containing a (made up, but unique) devid
2162306a36Sopenharmony_ci * and the callback to write the MSI message.
2262306a36Sopenharmony_ci */
2362306a36Sopenharmony_cistruct platform_msi_priv_data {
2462306a36Sopenharmony_ci	struct device			*dev;
2562306a36Sopenharmony_ci	void				*host_data;
2662306a36Sopenharmony_ci	msi_alloc_info_t		arg;
2762306a36Sopenharmony_ci	irq_write_msi_msg_t		write_msg;
2862306a36Sopenharmony_ci	int				devid;
2962306a36Sopenharmony_ci};
3062306a36Sopenharmony_ci
3162306a36Sopenharmony_ci/* The devid allocator */
3262306a36Sopenharmony_cistatic DEFINE_IDA(platform_msi_devid_ida);
3362306a36Sopenharmony_ci
3462306a36Sopenharmony_ci#ifdef GENERIC_MSI_DOMAIN_OPS
3562306a36Sopenharmony_ci/*
3662306a36Sopenharmony_ci * Convert an msi_desc to a globaly unique identifier (per-device
3762306a36Sopenharmony_ci * devid + msi_desc position in the msi_list).
3862306a36Sopenharmony_ci */
3962306a36Sopenharmony_cistatic irq_hw_number_t platform_msi_calc_hwirq(struct msi_desc *desc)
4062306a36Sopenharmony_ci{
4162306a36Sopenharmony_ci	u32 devid = desc->dev->msi.data->platform_data->devid;
4262306a36Sopenharmony_ci
4362306a36Sopenharmony_ci	return (devid << (32 - DEV_ID_SHIFT)) | desc->msi_index;
4462306a36Sopenharmony_ci}
4562306a36Sopenharmony_ci
4662306a36Sopenharmony_cistatic void platform_msi_set_desc(msi_alloc_info_t *arg, struct msi_desc *desc)
4762306a36Sopenharmony_ci{
4862306a36Sopenharmony_ci	arg->desc = desc;
4962306a36Sopenharmony_ci	arg->hwirq = platform_msi_calc_hwirq(desc);
5062306a36Sopenharmony_ci}
5162306a36Sopenharmony_ci
5262306a36Sopenharmony_cistatic int platform_msi_init(struct irq_domain *domain,
5362306a36Sopenharmony_ci			     struct msi_domain_info *info,
5462306a36Sopenharmony_ci			     unsigned int virq, irq_hw_number_t hwirq,
5562306a36Sopenharmony_ci			     msi_alloc_info_t *arg)
5662306a36Sopenharmony_ci{
5762306a36Sopenharmony_ci	return irq_domain_set_hwirq_and_chip(domain, virq, hwirq,
5862306a36Sopenharmony_ci					     info->chip, info->chip_data);
5962306a36Sopenharmony_ci}
6062306a36Sopenharmony_ci
6162306a36Sopenharmony_cistatic void platform_msi_set_proxy_dev(msi_alloc_info_t *arg)
6262306a36Sopenharmony_ci{
6362306a36Sopenharmony_ci	arg->flags |= MSI_ALLOC_FLAGS_PROXY_DEVICE;
6462306a36Sopenharmony_ci}
6562306a36Sopenharmony_ci#else
6662306a36Sopenharmony_ci#define platform_msi_set_desc		NULL
6762306a36Sopenharmony_ci#define platform_msi_init		NULL
6862306a36Sopenharmony_ci#define platform_msi_set_proxy_dev(x)	do {} while(0)
6962306a36Sopenharmony_ci#endif
7062306a36Sopenharmony_ci
7162306a36Sopenharmony_cistatic void platform_msi_update_dom_ops(struct msi_domain_info *info)
7262306a36Sopenharmony_ci{
7362306a36Sopenharmony_ci	struct msi_domain_ops *ops = info->ops;
7462306a36Sopenharmony_ci
7562306a36Sopenharmony_ci	BUG_ON(!ops);
7662306a36Sopenharmony_ci
7762306a36Sopenharmony_ci	if (ops->msi_init == NULL)
7862306a36Sopenharmony_ci		ops->msi_init = platform_msi_init;
7962306a36Sopenharmony_ci	if (ops->set_desc == NULL)
8062306a36Sopenharmony_ci		ops->set_desc = platform_msi_set_desc;
8162306a36Sopenharmony_ci}
8262306a36Sopenharmony_ci
8362306a36Sopenharmony_cistatic void platform_msi_write_msg(struct irq_data *data, struct msi_msg *msg)
8462306a36Sopenharmony_ci{
8562306a36Sopenharmony_ci	struct msi_desc *desc = irq_data_get_msi_desc(data);
8662306a36Sopenharmony_ci
8762306a36Sopenharmony_ci	desc->dev->msi.data->platform_data->write_msg(desc, msg);
8862306a36Sopenharmony_ci}
8962306a36Sopenharmony_ci
9062306a36Sopenharmony_cistatic void platform_msi_update_chip_ops(struct msi_domain_info *info)
9162306a36Sopenharmony_ci{
9262306a36Sopenharmony_ci	struct irq_chip *chip = info->chip;
9362306a36Sopenharmony_ci
9462306a36Sopenharmony_ci	BUG_ON(!chip);
9562306a36Sopenharmony_ci	if (!chip->irq_mask)
9662306a36Sopenharmony_ci		chip->irq_mask = irq_chip_mask_parent;
9762306a36Sopenharmony_ci	if (!chip->irq_unmask)
9862306a36Sopenharmony_ci		chip->irq_unmask = irq_chip_unmask_parent;
9962306a36Sopenharmony_ci	if (!chip->irq_eoi)
10062306a36Sopenharmony_ci		chip->irq_eoi = irq_chip_eoi_parent;
10162306a36Sopenharmony_ci	if (!chip->irq_set_affinity)
10262306a36Sopenharmony_ci		chip->irq_set_affinity = msi_domain_set_affinity;
10362306a36Sopenharmony_ci	if (!chip->irq_write_msi_msg)
10462306a36Sopenharmony_ci		chip->irq_write_msi_msg = platform_msi_write_msg;
10562306a36Sopenharmony_ci	if (WARN_ON((info->flags & MSI_FLAG_LEVEL_CAPABLE) &&
10662306a36Sopenharmony_ci		    !(chip->flags & IRQCHIP_SUPPORTS_LEVEL_MSI)))
10762306a36Sopenharmony_ci		info->flags &= ~MSI_FLAG_LEVEL_CAPABLE;
10862306a36Sopenharmony_ci}
10962306a36Sopenharmony_ci
11062306a36Sopenharmony_ci/**
11162306a36Sopenharmony_ci * platform_msi_create_irq_domain - Create a platform MSI interrupt domain
11262306a36Sopenharmony_ci * @fwnode:		Optional fwnode of the interrupt controller
11362306a36Sopenharmony_ci * @info:	MSI domain info
11462306a36Sopenharmony_ci * @parent:	Parent irq domain
11562306a36Sopenharmony_ci *
11662306a36Sopenharmony_ci * Updates the domain and chip ops and creates a platform MSI
11762306a36Sopenharmony_ci * interrupt domain.
11862306a36Sopenharmony_ci *
11962306a36Sopenharmony_ci * Returns:
12062306a36Sopenharmony_ci * A domain pointer or NULL in case of failure.
12162306a36Sopenharmony_ci */
12262306a36Sopenharmony_cistruct irq_domain *platform_msi_create_irq_domain(struct fwnode_handle *fwnode,
12362306a36Sopenharmony_ci						  struct msi_domain_info *info,
12462306a36Sopenharmony_ci						  struct irq_domain *parent)
12562306a36Sopenharmony_ci{
12662306a36Sopenharmony_ci	struct irq_domain *domain;
12762306a36Sopenharmony_ci
12862306a36Sopenharmony_ci	if (info->flags & MSI_FLAG_USE_DEF_DOM_OPS)
12962306a36Sopenharmony_ci		platform_msi_update_dom_ops(info);
13062306a36Sopenharmony_ci	if (info->flags & MSI_FLAG_USE_DEF_CHIP_OPS)
13162306a36Sopenharmony_ci		platform_msi_update_chip_ops(info);
13262306a36Sopenharmony_ci	info->flags |= MSI_FLAG_DEV_SYSFS | MSI_FLAG_ALLOC_SIMPLE_MSI_DESCS |
13362306a36Sopenharmony_ci		       MSI_FLAG_FREE_MSI_DESCS;
13462306a36Sopenharmony_ci
13562306a36Sopenharmony_ci	domain = msi_create_irq_domain(fwnode, info, parent);
13662306a36Sopenharmony_ci	if (domain)
13762306a36Sopenharmony_ci		irq_domain_update_bus_token(domain, DOMAIN_BUS_PLATFORM_MSI);
13862306a36Sopenharmony_ci
13962306a36Sopenharmony_ci	return domain;
14062306a36Sopenharmony_ci}
14162306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(platform_msi_create_irq_domain);
14262306a36Sopenharmony_ci
14362306a36Sopenharmony_cistatic int platform_msi_alloc_priv_data(struct device *dev, unsigned int nvec,
14462306a36Sopenharmony_ci					irq_write_msi_msg_t write_msi_msg)
14562306a36Sopenharmony_ci{
14662306a36Sopenharmony_ci	struct platform_msi_priv_data *datap;
14762306a36Sopenharmony_ci	int err;
14862306a36Sopenharmony_ci
14962306a36Sopenharmony_ci	/*
15062306a36Sopenharmony_ci	 * Limit the number of interrupts to 2048 per device. Should we
15162306a36Sopenharmony_ci	 * need to bump this up, DEV_ID_SHIFT should be adjusted
15262306a36Sopenharmony_ci	 * accordingly (which would impact the max number of MSI
15362306a36Sopenharmony_ci	 * capable devices).
15462306a36Sopenharmony_ci	 */
15562306a36Sopenharmony_ci	if (!dev->msi.domain || !write_msi_msg || !nvec || nvec > MAX_DEV_MSIS)
15662306a36Sopenharmony_ci		return -EINVAL;
15762306a36Sopenharmony_ci
15862306a36Sopenharmony_ci	if (dev->msi.domain->bus_token != DOMAIN_BUS_PLATFORM_MSI) {
15962306a36Sopenharmony_ci		dev_err(dev, "Incompatible msi_domain, giving up\n");
16062306a36Sopenharmony_ci		return -EINVAL;
16162306a36Sopenharmony_ci	}
16262306a36Sopenharmony_ci
16362306a36Sopenharmony_ci	err = msi_setup_device_data(dev);
16462306a36Sopenharmony_ci	if (err)
16562306a36Sopenharmony_ci		return err;
16662306a36Sopenharmony_ci
16762306a36Sopenharmony_ci	/* Already initialized? */
16862306a36Sopenharmony_ci	if (dev->msi.data->platform_data)
16962306a36Sopenharmony_ci		return -EBUSY;
17062306a36Sopenharmony_ci
17162306a36Sopenharmony_ci	datap = kzalloc(sizeof(*datap), GFP_KERNEL);
17262306a36Sopenharmony_ci	if (!datap)
17362306a36Sopenharmony_ci		return -ENOMEM;
17462306a36Sopenharmony_ci
17562306a36Sopenharmony_ci	datap->devid = ida_simple_get(&platform_msi_devid_ida,
17662306a36Sopenharmony_ci				      0, 1 << DEV_ID_SHIFT, GFP_KERNEL);
17762306a36Sopenharmony_ci	if (datap->devid < 0) {
17862306a36Sopenharmony_ci		err = datap->devid;
17962306a36Sopenharmony_ci		kfree(datap);
18062306a36Sopenharmony_ci		return err;
18162306a36Sopenharmony_ci	}
18262306a36Sopenharmony_ci
18362306a36Sopenharmony_ci	datap->write_msg = write_msi_msg;
18462306a36Sopenharmony_ci	datap->dev = dev;
18562306a36Sopenharmony_ci	dev->msi.data->platform_data = datap;
18662306a36Sopenharmony_ci	return 0;
18762306a36Sopenharmony_ci}
18862306a36Sopenharmony_ci
18962306a36Sopenharmony_cistatic void platform_msi_free_priv_data(struct device *dev)
19062306a36Sopenharmony_ci{
19162306a36Sopenharmony_ci	struct platform_msi_priv_data *data = dev->msi.data->platform_data;
19262306a36Sopenharmony_ci
19362306a36Sopenharmony_ci	dev->msi.data->platform_data = NULL;
19462306a36Sopenharmony_ci	ida_simple_remove(&platform_msi_devid_ida, data->devid);
19562306a36Sopenharmony_ci	kfree(data);
19662306a36Sopenharmony_ci}
19762306a36Sopenharmony_ci
19862306a36Sopenharmony_ci/**
19962306a36Sopenharmony_ci * platform_msi_domain_alloc_irqs - Allocate MSI interrupts for @dev
20062306a36Sopenharmony_ci * @dev:		The device for which to allocate interrupts
20162306a36Sopenharmony_ci * @nvec:		The number of interrupts to allocate
20262306a36Sopenharmony_ci * @write_msi_msg:	Callback to write an interrupt message for @dev
20362306a36Sopenharmony_ci *
20462306a36Sopenharmony_ci * Returns:
20562306a36Sopenharmony_ci * Zero for success, or an error code in case of failure
20662306a36Sopenharmony_ci */
20762306a36Sopenharmony_ciint platform_msi_domain_alloc_irqs(struct device *dev, unsigned int nvec,
20862306a36Sopenharmony_ci				   irq_write_msi_msg_t write_msi_msg)
20962306a36Sopenharmony_ci{
21062306a36Sopenharmony_ci	int err;
21162306a36Sopenharmony_ci
21262306a36Sopenharmony_ci	err = platform_msi_alloc_priv_data(dev, nvec, write_msi_msg);
21362306a36Sopenharmony_ci	if (err)
21462306a36Sopenharmony_ci		return err;
21562306a36Sopenharmony_ci
21662306a36Sopenharmony_ci	err = msi_domain_alloc_irqs_range(dev, MSI_DEFAULT_DOMAIN, 0, nvec - 1);
21762306a36Sopenharmony_ci	if (err)
21862306a36Sopenharmony_ci		platform_msi_free_priv_data(dev);
21962306a36Sopenharmony_ci
22062306a36Sopenharmony_ci	return err;
22162306a36Sopenharmony_ci}
22262306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(platform_msi_domain_alloc_irqs);
22362306a36Sopenharmony_ci
22462306a36Sopenharmony_ci/**
22562306a36Sopenharmony_ci * platform_msi_domain_free_irqs - Free MSI interrupts for @dev
22662306a36Sopenharmony_ci * @dev:	The device for which to free interrupts
22762306a36Sopenharmony_ci */
22862306a36Sopenharmony_civoid platform_msi_domain_free_irqs(struct device *dev)
22962306a36Sopenharmony_ci{
23062306a36Sopenharmony_ci	msi_domain_free_irqs_all(dev, MSI_DEFAULT_DOMAIN);
23162306a36Sopenharmony_ci	platform_msi_free_priv_data(dev);
23262306a36Sopenharmony_ci}
23362306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(platform_msi_domain_free_irqs);
23462306a36Sopenharmony_ci
23562306a36Sopenharmony_ci/**
23662306a36Sopenharmony_ci * platform_msi_get_host_data - Query the private data associated with
23762306a36Sopenharmony_ci *                              a platform-msi domain
23862306a36Sopenharmony_ci * @domain:	The platform-msi domain
23962306a36Sopenharmony_ci *
24062306a36Sopenharmony_ci * Return: The private data provided when calling
24162306a36Sopenharmony_ci * platform_msi_create_device_domain().
24262306a36Sopenharmony_ci */
24362306a36Sopenharmony_civoid *platform_msi_get_host_data(struct irq_domain *domain)
24462306a36Sopenharmony_ci{
24562306a36Sopenharmony_ci	struct platform_msi_priv_data *data = domain->host_data;
24662306a36Sopenharmony_ci
24762306a36Sopenharmony_ci	return data->host_data;
24862306a36Sopenharmony_ci}
24962306a36Sopenharmony_ci
25062306a36Sopenharmony_cistatic struct lock_class_key platform_device_msi_lock_class;
25162306a36Sopenharmony_ci
25262306a36Sopenharmony_ci/**
25362306a36Sopenharmony_ci * __platform_msi_create_device_domain - Create a platform-msi device domain
25462306a36Sopenharmony_ci *
25562306a36Sopenharmony_ci * @dev:		The device generating the MSIs
25662306a36Sopenharmony_ci * @nvec:		The number of MSIs that need to be allocated
25762306a36Sopenharmony_ci * @is_tree:		flag to indicate tree hierarchy
25862306a36Sopenharmony_ci * @write_msi_msg:	Callback to write an interrupt message for @dev
25962306a36Sopenharmony_ci * @ops:		The hierarchy domain operations to use
26062306a36Sopenharmony_ci * @host_data:		Private data associated to this domain
26162306a36Sopenharmony_ci *
26262306a36Sopenharmony_ci * Return: An irqdomain for @nvec interrupts on success, NULL in case of error.
26362306a36Sopenharmony_ci *
26462306a36Sopenharmony_ci * This is for interrupt domains which stack on a platform-msi domain
26562306a36Sopenharmony_ci * created by platform_msi_create_irq_domain(). @dev->msi.domain points to
26662306a36Sopenharmony_ci * that platform-msi domain which is the parent for the new domain.
26762306a36Sopenharmony_ci */
26862306a36Sopenharmony_cistruct irq_domain *
26962306a36Sopenharmony_ci__platform_msi_create_device_domain(struct device *dev,
27062306a36Sopenharmony_ci				    unsigned int nvec,
27162306a36Sopenharmony_ci				    bool is_tree,
27262306a36Sopenharmony_ci				    irq_write_msi_msg_t write_msi_msg,
27362306a36Sopenharmony_ci				    const struct irq_domain_ops *ops,
27462306a36Sopenharmony_ci				    void *host_data)
27562306a36Sopenharmony_ci{
27662306a36Sopenharmony_ci	struct platform_msi_priv_data *data;
27762306a36Sopenharmony_ci	struct irq_domain *domain;
27862306a36Sopenharmony_ci	int err;
27962306a36Sopenharmony_ci
28062306a36Sopenharmony_ci	err = platform_msi_alloc_priv_data(dev, nvec, write_msi_msg);
28162306a36Sopenharmony_ci	if (err)
28262306a36Sopenharmony_ci		return NULL;
28362306a36Sopenharmony_ci
28462306a36Sopenharmony_ci	/*
28562306a36Sopenharmony_ci	 * Use a separate lock class for the MSI descriptor mutex on
28662306a36Sopenharmony_ci	 * platform MSI device domains because the descriptor mutex nests
28762306a36Sopenharmony_ci	 * into the domain mutex. See alloc/free below.
28862306a36Sopenharmony_ci	 */
28962306a36Sopenharmony_ci	lockdep_set_class(&dev->msi.data->mutex, &platform_device_msi_lock_class);
29062306a36Sopenharmony_ci
29162306a36Sopenharmony_ci	data = dev->msi.data->platform_data;
29262306a36Sopenharmony_ci	data->host_data = host_data;
29362306a36Sopenharmony_ci	domain = irq_domain_create_hierarchy(dev->msi.domain, 0,
29462306a36Sopenharmony_ci					     is_tree ? 0 : nvec,
29562306a36Sopenharmony_ci					     dev->fwnode, ops, data);
29662306a36Sopenharmony_ci	if (!domain)
29762306a36Sopenharmony_ci		goto free_priv;
29862306a36Sopenharmony_ci
29962306a36Sopenharmony_ci	platform_msi_set_proxy_dev(&data->arg);
30062306a36Sopenharmony_ci	err = msi_domain_prepare_irqs(domain->parent, dev, nvec, &data->arg);
30162306a36Sopenharmony_ci	if (err)
30262306a36Sopenharmony_ci		goto free_domain;
30362306a36Sopenharmony_ci
30462306a36Sopenharmony_ci	return domain;
30562306a36Sopenharmony_ci
30662306a36Sopenharmony_cifree_domain:
30762306a36Sopenharmony_ci	irq_domain_remove(domain);
30862306a36Sopenharmony_cifree_priv:
30962306a36Sopenharmony_ci	platform_msi_free_priv_data(dev);
31062306a36Sopenharmony_ci	return NULL;
31162306a36Sopenharmony_ci}
31262306a36Sopenharmony_ci
31362306a36Sopenharmony_ci/**
31462306a36Sopenharmony_ci * platform_msi_device_domain_free - Free interrupts associated with a platform-msi
31562306a36Sopenharmony_ci *				     device domain
31662306a36Sopenharmony_ci *
31762306a36Sopenharmony_ci * @domain:	The platform-msi device domain
31862306a36Sopenharmony_ci * @virq:	The base irq from which to perform the free operation
31962306a36Sopenharmony_ci * @nr_irqs:	How many interrupts to free from @virq
32062306a36Sopenharmony_ci */
32162306a36Sopenharmony_civoid platform_msi_device_domain_free(struct irq_domain *domain, unsigned int virq,
32262306a36Sopenharmony_ci				     unsigned int nr_irqs)
32362306a36Sopenharmony_ci{
32462306a36Sopenharmony_ci	struct platform_msi_priv_data *data = domain->host_data;
32562306a36Sopenharmony_ci
32662306a36Sopenharmony_ci	msi_lock_descs(data->dev);
32762306a36Sopenharmony_ci	msi_domain_depopulate_descs(data->dev, virq, nr_irqs);
32862306a36Sopenharmony_ci	irq_domain_free_irqs_common(domain, virq, nr_irqs);
32962306a36Sopenharmony_ci	msi_free_msi_descs_range(data->dev, virq, virq + nr_irqs - 1);
33062306a36Sopenharmony_ci	msi_unlock_descs(data->dev);
33162306a36Sopenharmony_ci}
33262306a36Sopenharmony_ci
33362306a36Sopenharmony_ci/**
33462306a36Sopenharmony_ci * platform_msi_device_domain_alloc - Allocate interrupts associated with
33562306a36Sopenharmony_ci *				      a platform-msi device domain
33662306a36Sopenharmony_ci *
33762306a36Sopenharmony_ci * @domain:	The platform-msi device domain
33862306a36Sopenharmony_ci * @virq:	The base irq from which to perform the allocate operation
33962306a36Sopenharmony_ci * @nr_irqs:	How many interrupts to allocate from @virq
34062306a36Sopenharmony_ci *
34162306a36Sopenharmony_ci * Return 0 on success, or an error code on failure. Must be called
34262306a36Sopenharmony_ci * with irq_domain_mutex held (which can only be done as part of a
34362306a36Sopenharmony_ci * top-level interrupt allocation).
34462306a36Sopenharmony_ci */
34562306a36Sopenharmony_ciint platform_msi_device_domain_alloc(struct irq_domain *domain, unsigned int virq,
34662306a36Sopenharmony_ci				     unsigned int nr_irqs)
34762306a36Sopenharmony_ci{
34862306a36Sopenharmony_ci	struct platform_msi_priv_data *data = domain->host_data;
34962306a36Sopenharmony_ci	struct device *dev = data->dev;
35062306a36Sopenharmony_ci
35162306a36Sopenharmony_ci	return msi_domain_populate_irqs(domain->parent, dev, virq, nr_irqs, &data->arg);
35262306a36Sopenharmony_ci}
353