irq-loongson-pch-msi.c 6.9 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
// SPDX-License-Identifier: GPL-2.0
/*
 *  Copyright (C) 2020, Jiaxun Yang <jiaxun.yang@flygoat.com>
 *  Loongson PCH MSI support
 */

#define pr_fmt(fmt) "pch-msi: " fmt

#include <linux/irqchip.h>
#include <linux/msi.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
#include <linux/of_pci.h>
#include <linux/pci.h>
#include <linux/slab.h>

18 19
static int nr_pics;

20 21 22 23 24 25 26 27
struct pch_msi_data {
	struct mutex	msi_map_lock;
	phys_addr_t	doorbell;
	u32		irq_first;	/* The vector number that MSIs starts */
	u32		num_irqs;	/* The number of vectors for MSIs */
	unsigned long	*msi_map;
};

28 29
static struct fwnode_handle *pch_msi_handle[MAX_IO_PICS];

30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111
static void pch_msi_mask_msi_irq(struct irq_data *d)
{
	pci_msi_mask_irq(d);
	irq_chip_mask_parent(d);
}

static void pch_msi_unmask_msi_irq(struct irq_data *d)
{
	irq_chip_unmask_parent(d);
	pci_msi_unmask_irq(d);
}

static struct irq_chip pch_msi_irq_chip = {
	.name			= "PCH PCI MSI",
	.irq_mask		= pch_msi_mask_msi_irq,
	.irq_unmask		= pch_msi_unmask_msi_irq,
	.irq_ack		= irq_chip_ack_parent,
	.irq_set_affinity	= irq_chip_set_affinity_parent,
};

static int pch_msi_allocate_hwirq(struct pch_msi_data *priv, int num_req)
{
	int first;

	mutex_lock(&priv->msi_map_lock);

	first = bitmap_find_free_region(priv->msi_map, priv->num_irqs,
					get_count_order(num_req));
	if (first < 0) {
		mutex_unlock(&priv->msi_map_lock);
		return -ENOSPC;
	}

	mutex_unlock(&priv->msi_map_lock);

	return priv->irq_first + first;
}

static void pch_msi_free_hwirq(struct pch_msi_data *priv,
				int hwirq, int num_req)
{
	int first = hwirq - priv->irq_first;

	mutex_lock(&priv->msi_map_lock);
	bitmap_release_region(priv->msi_map, first, get_count_order(num_req));
	mutex_unlock(&priv->msi_map_lock);
}

static void pch_msi_compose_msi_msg(struct irq_data *data,
					struct msi_msg *msg)
{
	struct pch_msi_data *priv = irq_data_get_irq_chip_data(data);

	msg->address_hi = upper_32_bits(priv->doorbell);
	msg->address_lo = lower_32_bits(priv->doorbell);
	msg->data = data->hwirq;
}

static struct msi_domain_info pch_msi_domain_info = {
	.flags	= MSI_FLAG_USE_DEF_DOM_OPS | MSI_FLAG_USE_DEF_CHIP_OPS |
		  MSI_FLAG_MULTI_PCI_MSI | MSI_FLAG_PCI_MSIX,
	.chip	= &pch_msi_irq_chip,
};

static struct irq_chip middle_irq_chip = {
	.name			= "PCH MSI",
	.irq_mask		= irq_chip_mask_parent,
	.irq_unmask		= irq_chip_unmask_parent,
	.irq_ack		= irq_chip_ack_parent,
	.irq_set_affinity	= irq_chip_set_affinity_parent,
	.irq_compose_msi_msg	= pch_msi_compose_msi_msg,
};

static int pch_msi_parent_domain_alloc(struct irq_domain *domain,
					unsigned int virq, int hwirq)
{
	struct irq_fwspec fwspec;

	fwspec.fwnode = domain->parent->fwnode;
	fwspec.param_count = 1;
	fwspec.param[0] = hwirq;

112
	return irq_domain_alloc_irqs_parent(domain, virq, 1, &fwspec);
113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160
}

static int pch_msi_middle_domain_alloc(struct irq_domain *domain,
					   unsigned int virq,
					   unsigned int nr_irqs, void *args)
{
	struct pch_msi_data *priv = domain->host_data;
	int hwirq, err, i;

	hwirq = pch_msi_allocate_hwirq(priv, nr_irqs);
	if (hwirq < 0)
		return hwirq;

	for (i = 0; i < nr_irqs; i++) {
		err = pch_msi_parent_domain_alloc(domain, virq + i, hwirq + i);
		if (err)
			goto err_hwirq;

		irq_domain_set_hwirq_and_chip(domain, virq + i, hwirq + i,
					      &middle_irq_chip, priv);
	}

	return 0;

err_hwirq:
	pch_msi_free_hwirq(priv, hwirq, nr_irqs);
	irq_domain_free_irqs_parent(domain, virq, i - 1);

	return err;
}

static void pch_msi_middle_domain_free(struct irq_domain *domain,
					   unsigned int virq,
					   unsigned int nr_irqs)
{
	struct irq_data *d = irq_domain_get_irq_data(domain, virq);
	struct pch_msi_data *priv = irq_data_get_irq_chip_data(d);

	irq_domain_free_irqs_parent(domain, virq, nr_irqs);
	pch_msi_free_hwirq(priv, d->hwirq, nr_irqs);
}

static const struct irq_domain_ops pch_msi_middle_domain_ops = {
	.alloc	= pch_msi_middle_domain_alloc,
	.free	= pch_msi_middle_domain_free,
};

static int pch_msi_init_domains(struct pch_msi_data *priv,
161 162
				struct irq_domain *parent,
				struct fwnode_handle *domain_handle)
163 164 165
{
	struct irq_domain *middle_domain, *msi_domain;

166
	middle_domain = irq_domain_create_linear(domain_handle,
167 168 169 170 171 172 173 174 175 176 177
						priv->num_irqs,
						&pch_msi_middle_domain_ops,
						priv);
	if (!middle_domain) {
		pr_err("Failed to create the MSI middle domain\n");
		return -ENOMEM;
	}

	middle_domain->parent = parent;
	irq_domain_update_bus_token(middle_domain, DOMAIN_BUS_NEXUS);

178
	msi_domain = pci_msi_create_irq_domain(domain_handle,
179 180 181 182 183 184 185 186 187 188 189
					       &pch_msi_domain_info,
					       middle_domain);
	if (!msi_domain) {
		pr_err("Failed to create PCI MSI domain\n");
		irq_domain_remove(middle_domain);
		return -ENOMEM;
	}

	return 0;
}

190 191
static int pch_msi_init(phys_addr_t msg_address, int irq_base, int irq_count,
			struct irq_domain *parent_domain, struct fwnode_handle *domain_handle)
192 193
{
	int ret;
194
	struct pch_msi_data *priv;
195 196 197 198 199 200 201

	priv = kzalloc(sizeof(*priv), GFP_KERNEL);
	if (!priv)
		return -ENOMEM;

	mutex_init(&priv->msi_map_lock);

202 203 204
	priv->doorbell = msg_address;
	priv->irq_first = irq_base;
	priv->num_irqs = irq_count;
205

206
	priv->msi_map = bitmap_zalloc(priv->num_irqs, GFP_KERNEL);
207
	if (!priv->msi_map)
208 209 210 211 212
		goto err_priv;

	pr_debug("Registering %d MSIs, starting at %d\n",
		 priv->num_irqs, priv->irq_first);

213
	ret = pch_msi_init_domains(priv, parent_domain, domain_handle);
214 215 216
	if (ret)
		goto err_map;

217
	pch_msi_handle[nr_pics++] = domain_handle;
218 219 220
	return 0;

err_map:
221
	bitmap_free(priv->msi_map);
222 223
err_priv:
	kfree(priv);
224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261

	return -EINVAL;
}

#ifdef CONFIG_OF
static int pch_msi_of_init(struct device_node *node, struct device_node *parent)
{
	int err;
	int irq_base, irq_count;
	struct resource res;
	struct irq_domain *parent_domain;

	parent_domain = irq_find_host(parent);
	if (!parent_domain) {
		pr_err("Failed to find the parent domain\n");
		return -ENXIO;
	}

	if (of_address_to_resource(node, 0, &res)) {
		pr_err("Failed to allocate resource\n");
		return -EINVAL;
	}

	if (of_property_read_u32(node, "loongson,msi-base-vec", &irq_base)) {
		pr_err("Unable to parse MSI vec base\n");
		return -EINVAL;
	}

	if (of_property_read_u32(node, "loongson,msi-num-vecs", &irq_count)) {
		pr_err("Unable to parse MSI vec number\n");
		return -EINVAL;
	}

	err = pch_msi_init(res.start, irq_base, irq_count, parent_domain, of_node_to_fwnode(node));
	if (err < 0)
		return err;

	return 0;
262 263
}

264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284
IRQCHIP_DECLARE(pch_msi, "loongson,pch-msi-1.0", pch_msi_of_init);
#endif

#ifdef CONFIG_ACPI
struct fwnode_handle *get_pch_msi_handle(int pci_segment)
{
	int i;

	for (i = 0; i < MAX_IO_PICS; i++) {
		if (msi_group[i].pci_segment == pci_segment)
			return pch_msi_handle[i];
	}
	return NULL;
}

int __init pch_msi_acpi_init(struct irq_domain *parent,
					struct acpi_madt_msi_pic *acpi_pchmsi)
{
	int ret;
	struct fwnode_handle *domain_handle;

285
	domain_handle = irq_domain_alloc_fwnode(&acpi_pchmsi->msg_address);
286 287 288 289 290 291 292 293
	ret = pch_msi_init(acpi_pchmsi->msg_address, acpi_pchmsi->start,
				acpi_pchmsi->count, parent, domain_handle);
	if (ret < 0)
		irq_domain_free_fwnode(domain_handle);

	return ret;
}
#endif