irq-loongson-pch-pic.c 10.3 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 PIC support
 */

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

#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/irqchip.h>
#include <linux/irqdomain.h>
#include <linux/kernel.h>
#include <linux/platform_device.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
#include <linux/of_platform.h>
18
#include <linux/syscore_ops.h>
19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36

/* Registers */
#define PCH_PIC_MASK		0x20
#define PCH_PIC_HTMSI_EN	0x40
#define PCH_PIC_EDGE		0x60
#define PCH_PIC_CLR		0x80
#define PCH_PIC_AUTO0		0xc0
#define PCH_PIC_AUTO1		0xe0
#define PCH_INT_ROUTE(irq)	(0x100 + irq)
#define PCH_INT_HTVEC(irq)	(0x200 + irq)
#define PCH_PIC_POL		0x3e0

#define PIC_COUNT_PER_REG	32
#define PIC_REG_COUNT		2
#define PIC_COUNT		(PIC_COUNT_PER_REG * PIC_REG_COUNT)
#define PIC_REG_IDX(irq_id)	((irq_id) / PIC_COUNT_PER_REG)
#define PIC_REG_BIT(irq_id)	((irq_id) % PIC_COUNT_PER_REG)

37 38
static int nr_pics;

39 40 41 42 43
struct pch_pic {
	void __iomem		*base;
	struct irq_domain	*pic_domain;
	u32			ht_vec_base;
	raw_spinlock_t		pic_lock;
44 45
	u32			vec_count;
	u32			gsi_base;
46 47 48
	u32			saved_vec_en[PIC_REG_COUNT];
	u32			saved_vec_pol[PIC_REG_COUNT];
	u32			saved_vec_edge[PIC_REG_COUNT];
49 50
};

51 52 53 54
static struct pch_pic *pch_pic_priv[MAX_IO_PICS];

struct fwnode_handle *pch_pic_handle[MAX_IO_PICS];

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
static void pch_pic_bitset(struct pch_pic *priv, int offset, int bit)
{
	u32 reg;
	void __iomem *addr = priv->base + offset + PIC_REG_IDX(bit) * 4;

	raw_spin_lock(&priv->pic_lock);
	reg = readl(addr);
	reg |= BIT(PIC_REG_BIT(bit));
	writel(reg, addr);
	raw_spin_unlock(&priv->pic_lock);
}

static void pch_pic_bitclr(struct pch_pic *priv, int offset, int bit)
{
	u32 reg;
	void __iomem *addr = priv->base + offset + PIC_REG_IDX(bit) * 4;

	raw_spin_lock(&priv->pic_lock);
	reg = readl(addr);
	reg &= ~BIT(PIC_REG_BIT(bit));
	writel(reg, addr);
	raw_spin_unlock(&priv->pic_lock);
}

static void pch_pic_mask_irq(struct irq_data *d)
{
	struct pch_pic *priv = irq_data_get_irq_chip_data(d);

	pch_pic_bitset(priv, PCH_PIC_MASK, d->hwirq);
	irq_chip_mask_parent(d);
}

static void pch_pic_unmask_irq(struct irq_data *d)
{
	struct pch_pic *priv = irq_data_get_irq_chip_data(d);

91 92 93
	writel(BIT(PIC_REG_BIT(d->hwirq)),
			priv->base + PCH_PIC_CLR + PIC_REG_IDX(d->hwirq) * 4);

94 95 96 97 98 99 100 101 102 103 104 105 106
	irq_chip_unmask_parent(d);
	pch_pic_bitclr(priv, PCH_PIC_MASK, d->hwirq);
}

static int pch_pic_set_type(struct irq_data *d, unsigned int type)
{
	struct pch_pic *priv = irq_data_get_irq_chip_data(d);
	int ret = 0;

	switch (type) {
	case IRQ_TYPE_EDGE_RISING:
		pch_pic_bitset(priv, PCH_PIC_EDGE, d->hwirq);
		pch_pic_bitclr(priv, PCH_PIC_POL, d->hwirq);
107
		irq_set_handler_locked(d, handle_edge_irq);
108 109 110 111
		break;
	case IRQ_TYPE_EDGE_FALLING:
		pch_pic_bitset(priv, PCH_PIC_EDGE, d->hwirq);
		pch_pic_bitset(priv, PCH_PIC_POL, d->hwirq);
112
		irq_set_handler_locked(d, handle_edge_irq);
113 114 115 116
		break;
	case IRQ_TYPE_LEVEL_HIGH:
		pch_pic_bitclr(priv, PCH_PIC_EDGE, d->hwirq);
		pch_pic_bitclr(priv, PCH_PIC_POL, d->hwirq);
117
		irq_set_handler_locked(d, handle_level_irq);
118 119 120 121
		break;
	case IRQ_TYPE_LEVEL_LOW:
		pch_pic_bitclr(priv, PCH_PIC_EDGE, d->hwirq);
		pch_pic_bitset(priv, PCH_PIC_POL, d->hwirq);
122
		irq_set_handler_locked(d, handle_level_irq);
123 124 125 126 127 128 129 130 131
		break;
	default:
		ret = -EINVAL;
		break;
	}

	return ret;
}

132 133 134 135 136 137 138 139 140 141 142 143 144
static void pch_pic_ack_irq(struct irq_data *d)
{
	unsigned int reg;
	struct pch_pic *priv = irq_data_get_irq_chip_data(d);

	reg = readl(priv->base + PCH_PIC_EDGE + PIC_REG_IDX(d->hwirq) * 4);
	if (reg & BIT(PIC_REG_BIT(d->hwirq))) {
		writel(BIT(PIC_REG_BIT(d->hwirq)),
			priv->base + PCH_PIC_CLR + PIC_REG_IDX(d->hwirq) * 4);
	}
	irq_chip_ack_parent(d);
}

145 146 147 148
static struct irq_chip pch_pic_irq_chip = {
	.name			= "PCH PIC",
	.irq_mask		= pch_pic_mask_irq,
	.irq_unmask		= pch_pic_unmask_irq,
149
	.irq_ack		= pch_pic_ack_irq,
150 151
	.irq_set_affinity	= irq_chip_set_affinity_parent,
	.irq_set_type		= pch_pic_set_type,
152
	.flags			= IRQCHIP_SKIP_SET_WAKE,
153 154
};

155 156 157 158 159 160 161 162 163
static int pch_pic_domain_translate(struct irq_domain *d,
					struct irq_fwspec *fwspec,
					unsigned long *hwirq,
					unsigned int *type)
{
	struct pch_pic *priv = d->host_data;
	struct device_node *of_node = to_of_node(fwspec->fwnode);

	if (of_node) {
164 165 166
		if (fwspec->param_count < 2)
			return -EINVAL;

167 168 169
		*hwirq = fwspec->param[0] + priv->ht_vec_base;
		*type = fwspec->param[1] & IRQ_TYPE_SENSE_MASK;
	} else {
170 171 172
		if (fwspec->param_count < 1)
			return -EINVAL;

173
		*hwirq = fwspec->param[0] - priv->gsi_base;
174 175 176 177
		if (fwspec->param_count > 1)
			*type = fwspec->param[1] & IRQ_TYPE_SENSE_MASK;
		else
			*type = IRQ_TYPE_NONE;
178 179 180 181 182
	}

	return 0;
}

183 184 185 186 187 188
static int pch_pic_alloc(struct irq_domain *domain, unsigned int virq,
			      unsigned int nr_irqs, void *arg)
{
	int err;
	unsigned int type;
	unsigned long hwirq;
189 190
	struct irq_fwspec *fwspec = arg;
	struct irq_fwspec parent_fwspec;
191 192
	struct pch_pic *priv = domain->host_data;

193
	err = pch_pic_domain_translate(domain, fwspec, &hwirq, &type);
194 195
	if (err)
		return err;
196

197 198
	parent_fwspec.fwnode = domain->parent->fwnode;
	parent_fwspec.param_count = 1;
199
	parent_fwspec.param[0] = hwirq;
200

201
	err = irq_domain_alloc_irqs_parent(domain, virq, 1, &parent_fwspec);
202 203 204 205 206
	if (err)
		return err;

	irq_domain_set_info(domain, virq, hwirq,
			    &pch_pic_irq_chip, priv,
207
			    handle_level_irq, NULL, NULL);
208 209 210 211 212 213
	irq_set_probe(virq);

	return 0;
}

static const struct irq_domain_ops pch_pic_domain_ops = {
214
	.translate	= pch_pic_domain_translate,
215 216 217 218 219 220 221 222 223
	.alloc		= pch_pic_alloc,
	.free		= irq_domain_free_irqs_parent,
};

static void pch_pic_reset(struct pch_pic *priv)
{
	int i;

	for (i = 0; i < PIC_COUNT; i++) {
224
		/* Write vector ID */
225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241
		writeb(priv->ht_vec_base + i, priv->base + PCH_INT_HTVEC(i));
		/* Hardcode route to HT0 Lo */
		writeb(1, priv->base + PCH_INT_ROUTE(i));
	}

	for (i = 0; i < PIC_REG_COUNT; i++) {
		/* Clear IRQ cause registers, mask all interrupts */
		writel_relaxed(0xFFFFFFFF, priv->base + PCH_PIC_MASK + 4 * i);
		writel_relaxed(0xFFFFFFFF, priv->base + PCH_PIC_CLR + 4 * i);
		/* Clear auto bounce, we don't need that */
		writel_relaxed(0, priv->base + PCH_PIC_AUTO0 + 4 * i);
		writel_relaxed(0, priv->base + PCH_PIC_AUTO1 + 4 * i);
		/* Enable HTMSI transformer */
		writel_relaxed(0xFFFFFFFF, priv->base + PCH_PIC_HTMSI_EN + 4 * i);
	}
}

242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281
static int pch_pic_suspend(void)
{
	int i, j;

	for (i = 0; i < nr_pics; i++) {
		for (j = 0; j < PIC_REG_COUNT; j++) {
			pch_pic_priv[i]->saved_vec_pol[j] =
				readl(pch_pic_priv[i]->base + PCH_PIC_POL + 4 * j);
			pch_pic_priv[i]->saved_vec_edge[j] =
				readl(pch_pic_priv[i]->base + PCH_PIC_EDGE + 4 * j);
			pch_pic_priv[i]->saved_vec_en[j] =
				readl(pch_pic_priv[i]->base + PCH_PIC_MASK + 4 * j);
		}
	}

	return 0;
}

static void pch_pic_resume(void)
{
	int i, j;

	for (i = 0; i < nr_pics; i++) {
		pch_pic_reset(pch_pic_priv[i]);
		for (j = 0; j < PIC_REG_COUNT; j++) {
			writel(pch_pic_priv[i]->saved_vec_pol[j],
					pch_pic_priv[i]->base + PCH_PIC_POL + 4 * j);
			writel(pch_pic_priv[i]->saved_vec_edge[j],
					pch_pic_priv[i]->base + PCH_PIC_EDGE + 4 * j);
			writel(pch_pic_priv[i]->saved_vec_en[j],
					pch_pic_priv[i]->base + PCH_PIC_MASK + 4 * j);
		}
	}
}

static struct syscore_ops pch_pic_syscore_ops = {
	.suspend =  pch_pic_suspend,
	.resume =  pch_pic_resume,
};

282 283 284
static int pch_pic_init(phys_addr_t addr, unsigned long size, int vec_base,
			struct irq_domain *parent_domain, struct fwnode_handle *domain_handle,
			u32 gsi_base)
285 286 287 288 289 290 291 292
{
	struct pch_pic *priv;

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

	raw_spin_lock_init(&priv->pic_lock);
293 294
	priv->base = ioremap(addr, size);
	if (!priv->base)
295 296
		goto free_priv;

297 298 299
	priv->ht_vec_base = vec_base;
	priv->vec_count = ((readq(priv->base) >> 48) & 0xff) + 1;
	priv->gsi_base = gsi_base;
300 301

	priv->pic_domain = irq_domain_create_hierarchy(parent_domain, 0,
302 303 304
						priv->vec_count, domain_handle,
						&pch_pic_domain_ops, priv);

305 306 307 308 309 310
	if (!priv->pic_domain) {
		pr_err("Failed to create IRQ domain\n");
		goto iounmap_base;
	}

	pch_pic_reset(priv);
311 312
	pch_pic_handle[nr_pics] = domain_handle;
	pch_pic_priv[nr_pics++] = priv;
313

314 315
	if (nr_pics == 1)
		register_syscore_ops(&pch_pic_syscore_ops);
316

317 318 319 320 321 322 323
	return 0;

iounmap_base:
	iounmap(priv->base);
free_priv:
	kfree(priv);

324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355
	return -EINVAL;
}

#ifdef CONFIG_OF

static int pch_pic_of_init(struct device_node *node,
				struct device_node *parent)
{
	int err, vec_base;
	struct resource res;
	struct irq_domain *parent_domain;

	if (of_address_to_resource(node, 0, &res))
		return -EINVAL;

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

	if (of_property_read_u32(node, "loongson,pic-base-vec", &vec_base)) {
		pr_err("Failed to determine pic-base-vec\n");
		return -EINVAL;
	}

	err = pch_pic_init(res.start, resource_size(&res), vec_base,
				parent_domain, of_node_to_fwnode(node), 0);
	if (err < 0)
		return err;

	return 0;
356 357 358
}

IRQCHIP_DECLARE(pch_pic, "loongson,pch-pic-1.0", pch_pic_of_init);
359 360 361 362

#endif

#ifdef CONFIG_ACPI
363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381
int find_pch_pic(u32 gsi)
{
	int i;

	/* Find the PCH_PIC that manages this GSI. */
	for (i = 0; i < MAX_IO_PICS; i++) {
		struct pch_pic *priv = pch_pic_priv[i];

		if (!priv)
			return -1;

		if (gsi >= priv->gsi_base && gsi < (priv->gsi_base + priv->vec_count))
			return i;
	}

	pr_err("ERROR: Unable to locate PCH_PIC for GSI %d\n", gsi);
	return -1;
}

382 383
static int __init pch_lpc_parse_madt(union acpi_subtable_headers *header,
					const unsigned long end)
384 385 386 387 388 389 390 391
{
	struct acpi_madt_lpc_pic *pchlpc_entry = (struct acpi_madt_lpc_pic *)header;

	return pch_lpc_acpi_init(pch_pic_priv[0]->pic_domain, pchlpc_entry);
}

static int __init acpi_cascade_irqdomain_init(void)
{
392 393 394 395 396 397
	int r;

	r = acpi_table_parse_madt(ACPI_MADT_TYPE_LPC_PIC, pch_lpc_parse_madt, 0);
	if (r < 0)
		return r;

398 399 400 401 402 403 404 405 406
	return 0;
}

int __init pch_pic_acpi_init(struct irq_domain *parent,
					struct acpi_madt_bio_pic *acpi_pchpic)
{
	int ret, vec_base;
	struct fwnode_handle *domain_handle;

407 408 409
	if (find_pch_pic(acpi_pchpic->gsi_base) >= 0)
		return 0;

410 411
	vec_base = acpi_pchpic->gsi_base - GSI_MIN_PCH_IRQ;

412
	domain_handle = irq_domain_alloc_fwnode(&acpi_pchpic->address);
413 414 415 416 417 418 419 420 421 422 423 424 425 426
	if (!domain_handle) {
		pr_err("Unable to allocate domain handle\n");
		return -ENOMEM;
	}

	ret = pch_pic_init(acpi_pchpic->address, acpi_pchpic->size,
				vec_base, parent, domain_handle, acpi_pchpic->gsi_base);

	if (ret < 0) {
		irq_domain_free_fwnode(domain_handle);
		return ret;
	}

	if (acpi_pchpic->id == 0)
427
		ret = acpi_cascade_irqdomain_init();
428 429 430 431

	return ret;
}
#endif