19536eba0SPaul Cercueil // SPDX-License-Identifier: GPL-2.0 29536eba0SPaul Cercueil /* 39536eba0SPaul Cercueil * JZ47xx SoCs TCU IRQ driver 49536eba0SPaul Cercueil * Copyright (C) 2019 Paul Cercueil <paul@crapouillou.net> 59536eba0SPaul Cercueil */ 69536eba0SPaul Cercueil 79536eba0SPaul Cercueil #include <linux/clk.h> 89536eba0SPaul Cercueil #include <linux/interrupt.h> 99536eba0SPaul Cercueil #include <linux/irqchip.h> 109536eba0SPaul Cercueil #include <linux/irqchip/chained_irq.h> 119536eba0SPaul Cercueil #include <linux/mfd/ingenic-tcu.h> 129536eba0SPaul Cercueil #include <linux/mfd/syscon.h> 139536eba0SPaul Cercueil #include <linux/of_irq.h> 149536eba0SPaul Cercueil #include <linux/regmap.h> 159536eba0SPaul Cercueil 169536eba0SPaul Cercueil struct ingenic_tcu { 179536eba0SPaul Cercueil struct regmap *map; 189536eba0SPaul Cercueil struct clk *clk; 199536eba0SPaul Cercueil struct irq_domain *domain; 209536eba0SPaul Cercueil unsigned int nb_parent_irqs; 219536eba0SPaul Cercueil u32 parent_irqs[3]; 229536eba0SPaul Cercueil }; 239536eba0SPaul Cercueil 249536eba0SPaul Cercueil static void ingenic_tcu_intc_cascade(struct irq_desc *desc) 259536eba0SPaul Cercueil { 269536eba0SPaul Cercueil struct irq_chip *irq_chip = irq_data_get_irq_chip(&desc->irq_data); 279536eba0SPaul Cercueil struct irq_domain *domain = irq_desc_get_handler_data(desc); 289536eba0SPaul Cercueil struct irq_chip_generic *gc = irq_get_domain_generic_chip(domain, 0); 299536eba0SPaul Cercueil struct regmap *map = gc->private; 309536eba0SPaul Cercueil uint32_t irq_reg, irq_mask; 310859bbb0SKees Cook unsigned long bits; 329536eba0SPaul Cercueil unsigned int i; 339536eba0SPaul Cercueil 349536eba0SPaul Cercueil regmap_read(map, TCU_REG_TFR, &irq_reg); 359536eba0SPaul Cercueil regmap_read(map, TCU_REG_TMR, &irq_mask); 369536eba0SPaul Cercueil 379536eba0SPaul Cercueil chained_irq_enter(irq_chip, desc); 389536eba0SPaul Cercueil 399536eba0SPaul Cercueil irq_reg &= ~irq_mask; 400859bbb0SKees Cook bits = irq_reg; 419536eba0SPaul Cercueil 420859bbb0SKees Cook for_each_set_bit(i, &bits, 32) 43046a6ee2SMarc Zyngier generic_handle_domain_irq(domain, i); 449536eba0SPaul Cercueil 459536eba0SPaul Cercueil chained_irq_exit(irq_chip, desc); 469536eba0SPaul Cercueil } 479536eba0SPaul Cercueil 489536eba0SPaul Cercueil static void ingenic_tcu_gc_unmask_enable_reg(struct irq_data *d) 499536eba0SPaul Cercueil { 509536eba0SPaul Cercueil struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d); 519536eba0SPaul Cercueil struct irq_chip_type *ct = irq_data_get_chip_type(d); 529536eba0SPaul Cercueil struct regmap *map = gc->private; 539536eba0SPaul Cercueil u32 mask = d->mask; 549536eba0SPaul Cercueil 559536eba0SPaul Cercueil irq_gc_lock(gc); 569536eba0SPaul Cercueil regmap_write(map, ct->regs.ack, mask); 579536eba0SPaul Cercueil regmap_write(map, ct->regs.enable, mask); 589536eba0SPaul Cercueil *ct->mask_cache |= mask; 599536eba0SPaul Cercueil irq_gc_unlock(gc); 609536eba0SPaul Cercueil } 619536eba0SPaul Cercueil 629536eba0SPaul Cercueil static void ingenic_tcu_gc_mask_disable_reg(struct irq_data *d) 639536eba0SPaul Cercueil { 649536eba0SPaul Cercueil struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d); 659536eba0SPaul Cercueil struct irq_chip_type *ct = irq_data_get_chip_type(d); 669536eba0SPaul Cercueil struct regmap *map = gc->private; 679536eba0SPaul Cercueil u32 mask = d->mask; 689536eba0SPaul Cercueil 699536eba0SPaul Cercueil irq_gc_lock(gc); 709536eba0SPaul Cercueil regmap_write(map, ct->regs.disable, mask); 719536eba0SPaul Cercueil *ct->mask_cache &= ~mask; 729536eba0SPaul Cercueil irq_gc_unlock(gc); 739536eba0SPaul Cercueil } 749536eba0SPaul Cercueil 759536eba0SPaul Cercueil static void ingenic_tcu_gc_mask_disable_reg_and_ack(struct irq_data *d) 769536eba0SPaul Cercueil { 779536eba0SPaul Cercueil struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d); 789536eba0SPaul Cercueil struct irq_chip_type *ct = irq_data_get_chip_type(d); 799536eba0SPaul Cercueil struct regmap *map = gc->private; 809536eba0SPaul Cercueil u32 mask = d->mask; 819536eba0SPaul Cercueil 829536eba0SPaul Cercueil irq_gc_lock(gc); 839536eba0SPaul Cercueil regmap_write(map, ct->regs.ack, mask); 849536eba0SPaul Cercueil regmap_write(map, ct->regs.disable, mask); 859536eba0SPaul Cercueil irq_gc_unlock(gc); 869536eba0SPaul Cercueil } 879536eba0SPaul Cercueil 889536eba0SPaul Cercueil static int __init ingenic_tcu_irq_init(struct device_node *np, 899536eba0SPaul Cercueil struct device_node *parent) 909536eba0SPaul Cercueil { 919536eba0SPaul Cercueil struct irq_chip_generic *gc; 929536eba0SPaul Cercueil struct irq_chip_type *ct; 939536eba0SPaul Cercueil struct ingenic_tcu *tcu; 949536eba0SPaul Cercueil struct regmap *map; 959536eba0SPaul Cercueil unsigned int i; 969536eba0SPaul Cercueil int ret, irqs; 979536eba0SPaul Cercueil 989536eba0SPaul Cercueil map = device_node_to_regmap(np); 999536eba0SPaul Cercueil if (IS_ERR(map)) 1009536eba0SPaul Cercueil return PTR_ERR(map); 1019536eba0SPaul Cercueil 1029536eba0SPaul Cercueil tcu = kzalloc(sizeof(*tcu), GFP_KERNEL); 1039536eba0SPaul Cercueil if (!tcu) 1049536eba0SPaul Cercueil return -ENOMEM; 1059536eba0SPaul Cercueil 1069536eba0SPaul Cercueil tcu->map = map; 1079536eba0SPaul Cercueil 1089536eba0SPaul Cercueil irqs = of_property_count_elems_of_size(np, "interrupts", sizeof(u32)); 1099536eba0SPaul Cercueil if (irqs < 0 || irqs > ARRAY_SIZE(tcu->parent_irqs)) { 1109536eba0SPaul Cercueil pr_crit("%s: Invalid 'interrupts' property\n", __func__); 1119536eba0SPaul Cercueil ret = -EINVAL; 1129536eba0SPaul Cercueil goto err_free_tcu; 1139536eba0SPaul Cercueil } 1149536eba0SPaul Cercueil 1159536eba0SPaul Cercueil tcu->nb_parent_irqs = irqs; 1169536eba0SPaul Cercueil 1179536eba0SPaul Cercueil tcu->domain = irq_domain_add_linear(np, 32, &irq_generic_chip_ops, 1189536eba0SPaul Cercueil NULL); 1199536eba0SPaul Cercueil if (!tcu->domain) { 1209536eba0SPaul Cercueil ret = -ENOMEM; 1219536eba0SPaul Cercueil goto err_free_tcu; 1229536eba0SPaul Cercueil } 1239536eba0SPaul Cercueil 1249536eba0SPaul Cercueil ret = irq_alloc_domain_generic_chips(tcu->domain, 32, 1, "TCU", 1259536eba0SPaul Cercueil handle_level_irq, 0, 1269536eba0SPaul Cercueil IRQ_NOPROBE | IRQ_LEVEL, 0); 1279536eba0SPaul Cercueil if (ret) { 1289536eba0SPaul Cercueil pr_crit("%s: Invalid 'interrupts' property\n", __func__); 1299536eba0SPaul Cercueil goto out_domain_remove; 1309536eba0SPaul Cercueil } 1319536eba0SPaul Cercueil 1329536eba0SPaul Cercueil gc = irq_get_domain_generic_chip(tcu->domain, 0); 1339536eba0SPaul Cercueil ct = gc->chip_types; 1349536eba0SPaul Cercueil 1359536eba0SPaul Cercueil gc->wake_enabled = IRQ_MSK(32); 1369536eba0SPaul Cercueil gc->private = tcu->map; 1379536eba0SPaul Cercueil 1389536eba0SPaul Cercueil ct->regs.disable = TCU_REG_TMSR; 1399536eba0SPaul Cercueil ct->regs.enable = TCU_REG_TMCR; 1409536eba0SPaul Cercueil ct->regs.ack = TCU_REG_TFCR; 1419536eba0SPaul Cercueil ct->chip.irq_unmask = ingenic_tcu_gc_unmask_enable_reg; 1429536eba0SPaul Cercueil ct->chip.irq_mask = ingenic_tcu_gc_mask_disable_reg; 1439536eba0SPaul Cercueil ct->chip.irq_mask_ack = ingenic_tcu_gc_mask_disable_reg_and_ack; 1449536eba0SPaul Cercueil ct->chip.flags = IRQCHIP_MASK_ON_SUSPEND | IRQCHIP_SKIP_SET_WAKE; 1459536eba0SPaul Cercueil 1469536eba0SPaul Cercueil /* Mask all IRQs by default */ 1479536eba0SPaul Cercueil regmap_write(tcu->map, TCU_REG_TMSR, IRQ_MSK(32)); 1489536eba0SPaul Cercueil 1499536eba0SPaul Cercueil /* 1509536eba0SPaul Cercueil * On JZ4740, timer 0 and timer 1 have their own interrupt line; 1519536eba0SPaul Cercueil * timers 2-7 share one interrupt. 1529536eba0SPaul Cercueil * On SoCs >= JZ4770, timer 5 has its own interrupt line; 1539536eba0SPaul Cercueil * timers 0-4 and 6-7 share one single interrupt. 1549536eba0SPaul Cercueil * 1559536eba0SPaul Cercueil * To keep things simple, we just register the same handler to 1569536eba0SPaul Cercueil * all parent interrupts. The handler will properly detect which 1579536eba0SPaul Cercueil * channel fired the interrupt. 1589536eba0SPaul Cercueil */ 1599536eba0SPaul Cercueil for (i = 0; i < irqs; i++) { 1609536eba0SPaul Cercueil tcu->parent_irqs[i] = irq_of_parse_and_map(np, i); 1619536eba0SPaul Cercueil if (!tcu->parent_irqs[i]) { 1629536eba0SPaul Cercueil ret = -EINVAL; 1639536eba0SPaul Cercueil goto out_unmap_irqs; 1649536eba0SPaul Cercueil } 1659536eba0SPaul Cercueil 1669536eba0SPaul Cercueil irq_set_chained_handler_and_data(tcu->parent_irqs[i], 1679536eba0SPaul Cercueil ingenic_tcu_intc_cascade, 1689536eba0SPaul Cercueil tcu->domain); 1699536eba0SPaul Cercueil } 1709536eba0SPaul Cercueil 1719536eba0SPaul Cercueil return 0; 1729536eba0SPaul Cercueil 1739536eba0SPaul Cercueil out_unmap_irqs: 1749536eba0SPaul Cercueil for (; i > 0; i--) 1759536eba0SPaul Cercueil irq_dispose_mapping(tcu->parent_irqs[i - 1]); 1769536eba0SPaul Cercueil out_domain_remove: 1779536eba0SPaul Cercueil irq_domain_remove(tcu->domain); 1789536eba0SPaul Cercueil err_free_tcu: 1799536eba0SPaul Cercueil kfree(tcu); 1809536eba0SPaul Cercueil return ret; 1819536eba0SPaul Cercueil } 1829536eba0SPaul Cercueil IRQCHIP_DECLARE(jz4740_tcu_irq, "ingenic,jz4740-tcu", ingenic_tcu_irq_init); 1839536eba0SPaul Cercueil IRQCHIP_DECLARE(jz4725b_tcu_irq, "ingenic,jz4725b-tcu", ingenic_tcu_irq_init); 1845fbecd23SPaul Cercueil IRQCHIP_DECLARE(jz4760_tcu_irq, "ingenic,jz4760-tcu", ingenic_tcu_irq_init); 1859536eba0SPaul Cercueil IRQCHIP_DECLARE(jz4770_tcu_irq, "ingenic,jz4770-tcu", ingenic_tcu_irq_init); 1867d4cac5bS周琰杰 (Zhou Yanjie) IRQCHIP_DECLARE(x1000_tcu_irq, "ingenic,x1000-tcu", ingenic_tcu_irq_init); 187