18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * Interrupt support for Cirrus Logic Madera codecs 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Copyright (C) 2015-2018 Cirrus Logic, Inc. and 68c2ecf20Sopenharmony_ci * Cirrus Logic International Semiconductor Ltd. 78c2ecf20Sopenharmony_ci */ 88c2ecf20Sopenharmony_ci 98c2ecf20Sopenharmony_ci#include <linux/module.h> 108c2ecf20Sopenharmony_ci#include <linux/interrupt.h> 118c2ecf20Sopenharmony_ci#include <linux/irq.h> 128c2ecf20Sopenharmony_ci#include <linux/irqdomain.h> 138c2ecf20Sopenharmony_ci#include <linux/pm_runtime.h> 148c2ecf20Sopenharmony_ci#include <linux/regmap.h> 158c2ecf20Sopenharmony_ci#include <linux/slab.h> 168c2ecf20Sopenharmony_ci#include <linux/of.h> 178c2ecf20Sopenharmony_ci#include <linux/of_device.h> 188c2ecf20Sopenharmony_ci#include <linux/of_irq.h> 198c2ecf20Sopenharmony_ci#include <linux/irqchip/irq-madera.h> 208c2ecf20Sopenharmony_ci#include <linux/mfd/madera/core.h> 218c2ecf20Sopenharmony_ci#include <linux/mfd/madera/pdata.h> 228c2ecf20Sopenharmony_ci#include <linux/mfd/madera/registers.h> 238c2ecf20Sopenharmony_ci 248c2ecf20Sopenharmony_ci#define MADERA_IRQ(_irq, _reg) \ 258c2ecf20Sopenharmony_ci [MADERA_IRQ_ ## _irq] = { \ 268c2ecf20Sopenharmony_ci .reg_offset = (_reg) - MADERA_IRQ1_STATUS_2, \ 278c2ecf20Sopenharmony_ci .mask = MADERA_ ## _irq ## _EINT1 \ 288c2ecf20Sopenharmony_ci } 298c2ecf20Sopenharmony_ci 308c2ecf20Sopenharmony_ci/* Mappings are the same for all Madera codecs */ 318c2ecf20Sopenharmony_cistatic const struct regmap_irq madera_irqs[MADERA_NUM_IRQ] = { 328c2ecf20Sopenharmony_ci MADERA_IRQ(FLL1_LOCK, MADERA_IRQ1_STATUS_2), 338c2ecf20Sopenharmony_ci MADERA_IRQ(FLL2_LOCK, MADERA_IRQ1_STATUS_2), 348c2ecf20Sopenharmony_ci MADERA_IRQ(FLL3_LOCK, MADERA_IRQ1_STATUS_2), 358c2ecf20Sopenharmony_ci MADERA_IRQ(FLLAO_LOCK, MADERA_IRQ1_STATUS_2), 368c2ecf20Sopenharmony_ci 378c2ecf20Sopenharmony_ci MADERA_IRQ(MICDET1, MADERA_IRQ1_STATUS_6), 388c2ecf20Sopenharmony_ci MADERA_IRQ(MICDET2, MADERA_IRQ1_STATUS_6), 398c2ecf20Sopenharmony_ci MADERA_IRQ(HPDET, MADERA_IRQ1_STATUS_6), 408c2ecf20Sopenharmony_ci 418c2ecf20Sopenharmony_ci MADERA_IRQ(MICD_CLAMP_RISE, MADERA_IRQ1_STATUS_7), 428c2ecf20Sopenharmony_ci MADERA_IRQ(MICD_CLAMP_FALL, MADERA_IRQ1_STATUS_7), 438c2ecf20Sopenharmony_ci MADERA_IRQ(JD1_RISE, MADERA_IRQ1_STATUS_7), 448c2ecf20Sopenharmony_ci MADERA_IRQ(JD1_FALL, MADERA_IRQ1_STATUS_7), 458c2ecf20Sopenharmony_ci 468c2ecf20Sopenharmony_ci MADERA_IRQ(ASRC2_IN1_LOCK, MADERA_IRQ1_STATUS_9), 478c2ecf20Sopenharmony_ci MADERA_IRQ(ASRC2_IN2_LOCK, MADERA_IRQ1_STATUS_9), 488c2ecf20Sopenharmony_ci MADERA_IRQ(ASRC1_IN1_LOCK, MADERA_IRQ1_STATUS_9), 498c2ecf20Sopenharmony_ci MADERA_IRQ(ASRC1_IN2_LOCK, MADERA_IRQ1_STATUS_9), 508c2ecf20Sopenharmony_ci MADERA_IRQ(DRC2_SIG_DET, MADERA_IRQ1_STATUS_9), 518c2ecf20Sopenharmony_ci MADERA_IRQ(DRC1_SIG_DET, MADERA_IRQ1_STATUS_9), 528c2ecf20Sopenharmony_ci 538c2ecf20Sopenharmony_ci MADERA_IRQ(DSP_IRQ1, MADERA_IRQ1_STATUS_11), 548c2ecf20Sopenharmony_ci MADERA_IRQ(DSP_IRQ2, MADERA_IRQ1_STATUS_11), 558c2ecf20Sopenharmony_ci MADERA_IRQ(DSP_IRQ3, MADERA_IRQ1_STATUS_11), 568c2ecf20Sopenharmony_ci MADERA_IRQ(DSP_IRQ4, MADERA_IRQ1_STATUS_11), 578c2ecf20Sopenharmony_ci MADERA_IRQ(DSP_IRQ5, MADERA_IRQ1_STATUS_11), 588c2ecf20Sopenharmony_ci MADERA_IRQ(DSP_IRQ6, MADERA_IRQ1_STATUS_11), 598c2ecf20Sopenharmony_ci MADERA_IRQ(DSP_IRQ7, MADERA_IRQ1_STATUS_11), 608c2ecf20Sopenharmony_ci MADERA_IRQ(DSP_IRQ8, MADERA_IRQ1_STATUS_11), 618c2ecf20Sopenharmony_ci MADERA_IRQ(DSP_IRQ9, MADERA_IRQ1_STATUS_11), 628c2ecf20Sopenharmony_ci MADERA_IRQ(DSP_IRQ10, MADERA_IRQ1_STATUS_11), 638c2ecf20Sopenharmony_ci MADERA_IRQ(DSP_IRQ11, MADERA_IRQ1_STATUS_11), 648c2ecf20Sopenharmony_ci MADERA_IRQ(DSP_IRQ12, MADERA_IRQ1_STATUS_11), 658c2ecf20Sopenharmony_ci MADERA_IRQ(DSP_IRQ13, MADERA_IRQ1_STATUS_11), 668c2ecf20Sopenharmony_ci MADERA_IRQ(DSP_IRQ14, MADERA_IRQ1_STATUS_11), 678c2ecf20Sopenharmony_ci MADERA_IRQ(DSP_IRQ15, MADERA_IRQ1_STATUS_11), 688c2ecf20Sopenharmony_ci MADERA_IRQ(DSP_IRQ16, MADERA_IRQ1_STATUS_11), 698c2ecf20Sopenharmony_ci 708c2ecf20Sopenharmony_ci MADERA_IRQ(HP3R_SC, MADERA_IRQ1_STATUS_12), 718c2ecf20Sopenharmony_ci MADERA_IRQ(HP3L_SC, MADERA_IRQ1_STATUS_12), 728c2ecf20Sopenharmony_ci MADERA_IRQ(HP2R_SC, MADERA_IRQ1_STATUS_12), 738c2ecf20Sopenharmony_ci MADERA_IRQ(HP2L_SC, MADERA_IRQ1_STATUS_12), 748c2ecf20Sopenharmony_ci MADERA_IRQ(HP1R_SC, MADERA_IRQ1_STATUS_12), 758c2ecf20Sopenharmony_ci MADERA_IRQ(HP1L_SC, MADERA_IRQ1_STATUS_12), 768c2ecf20Sopenharmony_ci 778c2ecf20Sopenharmony_ci MADERA_IRQ(SPK_OVERHEAT_WARN, MADERA_IRQ1_STATUS_15), 788c2ecf20Sopenharmony_ci MADERA_IRQ(SPK_OVERHEAT, MADERA_IRQ1_STATUS_15), 798c2ecf20Sopenharmony_ci 808c2ecf20Sopenharmony_ci MADERA_IRQ(DSP1_BUS_ERR, MADERA_IRQ1_STATUS_33), 818c2ecf20Sopenharmony_ci MADERA_IRQ(DSP2_BUS_ERR, MADERA_IRQ1_STATUS_33), 828c2ecf20Sopenharmony_ci MADERA_IRQ(DSP3_BUS_ERR, MADERA_IRQ1_STATUS_33), 838c2ecf20Sopenharmony_ci MADERA_IRQ(DSP4_BUS_ERR, MADERA_IRQ1_STATUS_33), 848c2ecf20Sopenharmony_ci MADERA_IRQ(DSP5_BUS_ERR, MADERA_IRQ1_STATUS_33), 858c2ecf20Sopenharmony_ci MADERA_IRQ(DSP6_BUS_ERR, MADERA_IRQ1_STATUS_33), 868c2ecf20Sopenharmony_ci MADERA_IRQ(DSP7_BUS_ERR, MADERA_IRQ1_STATUS_33), 878c2ecf20Sopenharmony_ci}; 888c2ecf20Sopenharmony_ci 898c2ecf20Sopenharmony_cistatic const struct regmap_irq_chip madera_irq_chip = { 908c2ecf20Sopenharmony_ci .name = "madera IRQ", 918c2ecf20Sopenharmony_ci .status_base = MADERA_IRQ1_STATUS_2, 928c2ecf20Sopenharmony_ci .mask_base = MADERA_IRQ1_MASK_2, 938c2ecf20Sopenharmony_ci .ack_base = MADERA_IRQ1_STATUS_2, 948c2ecf20Sopenharmony_ci .runtime_pm = true, 958c2ecf20Sopenharmony_ci .num_regs = 32, 968c2ecf20Sopenharmony_ci .irqs = madera_irqs, 978c2ecf20Sopenharmony_ci .num_irqs = ARRAY_SIZE(madera_irqs), 988c2ecf20Sopenharmony_ci}; 998c2ecf20Sopenharmony_ci 1008c2ecf20Sopenharmony_ci#ifdef CONFIG_PM_SLEEP 1018c2ecf20Sopenharmony_cistatic int madera_suspend(struct device *dev) 1028c2ecf20Sopenharmony_ci{ 1038c2ecf20Sopenharmony_ci struct madera *madera = dev_get_drvdata(dev->parent); 1048c2ecf20Sopenharmony_ci 1058c2ecf20Sopenharmony_ci dev_dbg(madera->irq_dev, "Suspend, disabling IRQ\n"); 1068c2ecf20Sopenharmony_ci 1078c2ecf20Sopenharmony_ci /* 1088c2ecf20Sopenharmony_ci * A runtime resume would be needed to access the chip interrupt 1098c2ecf20Sopenharmony_ci * controller but runtime pm doesn't function during suspend. 1108c2ecf20Sopenharmony_ci * Temporarily disable interrupts until we reach suspend_noirq state. 1118c2ecf20Sopenharmony_ci */ 1128c2ecf20Sopenharmony_ci disable_irq(madera->irq); 1138c2ecf20Sopenharmony_ci 1148c2ecf20Sopenharmony_ci return 0; 1158c2ecf20Sopenharmony_ci} 1168c2ecf20Sopenharmony_ci 1178c2ecf20Sopenharmony_cistatic int madera_suspend_noirq(struct device *dev) 1188c2ecf20Sopenharmony_ci{ 1198c2ecf20Sopenharmony_ci struct madera *madera = dev_get_drvdata(dev->parent); 1208c2ecf20Sopenharmony_ci 1218c2ecf20Sopenharmony_ci dev_dbg(madera->irq_dev, "No IRQ suspend, reenabling IRQ\n"); 1228c2ecf20Sopenharmony_ci 1238c2ecf20Sopenharmony_ci /* Re-enable interrupts to service wakeup interrupts from the chip */ 1248c2ecf20Sopenharmony_ci enable_irq(madera->irq); 1258c2ecf20Sopenharmony_ci 1268c2ecf20Sopenharmony_ci return 0; 1278c2ecf20Sopenharmony_ci} 1288c2ecf20Sopenharmony_ci 1298c2ecf20Sopenharmony_cistatic int madera_resume_noirq(struct device *dev) 1308c2ecf20Sopenharmony_ci{ 1318c2ecf20Sopenharmony_ci struct madera *madera = dev_get_drvdata(dev->parent); 1328c2ecf20Sopenharmony_ci 1338c2ecf20Sopenharmony_ci dev_dbg(madera->irq_dev, "No IRQ resume, disabling IRQ\n"); 1348c2ecf20Sopenharmony_ci 1358c2ecf20Sopenharmony_ci /* 1368c2ecf20Sopenharmony_ci * We can't handle interrupts until runtime pm is available again. 1378c2ecf20Sopenharmony_ci * Disable them temporarily. 1388c2ecf20Sopenharmony_ci */ 1398c2ecf20Sopenharmony_ci disable_irq(madera->irq); 1408c2ecf20Sopenharmony_ci 1418c2ecf20Sopenharmony_ci return 0; 1428c2ecf20Sopenharmony_ci} 1438c2ecf20Sopenharmony_ci 1448c2ecf20Sopenharmony_cistatic int madera_resume(struct device *dev) 1458c2ecf20Sopenharmony_ci{ 1468c2ecf20Sopenharmony_ci struct madera *madera = dev_get_drvdata(dev->parent); 1478c2ecf20Sopenharmony_ci 1488c2ecf20Sopenharmony_ci dev_dbg(madera->irq_dev, "Resume, reenabling IRQ\n"); 1498c2ecf20Sopenharmony_ci 1508c2ecf20Sopenharmony_ci /* Interrupts can now be handled */ 1518c2ecf20Sopenharmony_ci enable_irq(madera->irq); 1528c2ecf20Sopenharmony_ci 1538c2ecf20Sopenharmony_ci return 0; 1548c2ecf20Sopenharmony_ci} 1558c2ecf20Sopenharmony_ci#endif 1568c2ecf20Sopenharmony_ci 1578c2ecf20Sopenharmony_cistatic const struct dev_pm_ops madera_irq_pm_ops = { 1588c2ecf20Sopenharmony_ci SET_SYSTEM_SLEEP_PM_OPS(madera_suspend, madera_resume) 1598c2ecf20Sopenharmony_ci SET_NOIRQ_SYSTEM_SLEEP_PM_OPS(madera_suspend_noirq, 1608c2ecf20Sopenharmony_ci madera_resume_noirq) 1618c2ecf20Sopenharmony_ci}; 1628c2ecf20Sopenharmony_ci 1638c2ecf20Sopenharmony_cistatic int madera_irq_probe(struct platform_device *pdev) 1648c2ecf20Sopenharmony_ci{ 1658c2ecf20Sopenharmony_ci struct madera *madera = dev_get_drvdata(pdev->dev.parent); 1668c2ecf20Sopenharmony_ci struct irq_data *irq_data; 1678c2ecf20Sopenharmony_ci unsigned int irq_flags = 0; 1688c2ecf20Sopenharmony_ci int ret; 1698c2ecf20Sopenharmony_ci 1708c2ecf20Sopenharmony_ci dev_dbg(&pdev->dev, "probe\n"); 1718c2ecf20Sopenharmony_ci 1728c2ecf20Sopenharmony_ci /* 1738c2ecf20Sopenharmony_ci * Read the flags from the interrupt controller if not specified 1748c2ecf20Sopenharmony_ci * by pdata 1758c2ecf20Sopenharmony_ci */ 1768c2ecf20Sopenharmony_ci irq_flags = madera->pdata.irq_flags; 1778c2ecf20Sopenharmony_ci if (!irq_flags) { 1788c2ecf20Sopenharmony_ci irq_data = irq_get_irq_data(madera->irq); 1798c2ecf20Sopenharmony_ci if (!irq_data) { 1808c2ecf20Sopenharmony_ci dev_err(&pdev->dev, "Invalid IRQ: %d\n", madera->irq); 1818c2ecf20Sopenharmony_ci return -EINVAL; 1828c2ecf20Sopenharmony_ci } 1838c2ecf20Sopenharmony_ci 1848c2ecf20Sopenharmony_ci irq_flags = irqd_get_trigger_type(irq_data); 1858c2ecf20Sopenharmony_ci 1868c2ecf20Sopenharmony_ci /* Codec defaults to trigger low, use this if no flags given */ 1878c2ecf20Sopenharmony_ci if (irq_flags == IRQ_TYPE_NONE) 1888c2ecf20Sopenharmony_ci irq_flags = IRQF_TRIGGER_LOW; 1898c2ecf20Sopenharmony_ci } 1908c2ecf20Sopenharmony_ci 1918c2ecf20Sopenharmony_ci if (irq_flags & (IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING)) { 1928c2ecf20Sopenharmony_ci dev_err(&pdev->dev, "Host interrupt not level-triggered\n"); 1938c2ecf20Sopenharmony_ci return -EINVAL; 1948c2ecf20Sopenharmony_ci } 1958c2ecf20Sopenharmony_ci 1968c2ecf20Sopenharmony_ci /* 1978c2ecf20Sopenharmony_ci * The silicon always starts at active-low, check if we need to 1988c2ecf20Sopenharmony_ci * switch to active-high. 1998c2ecf20Sopenharmony_ci */ 2008c2ecf20Sopenharmony_ci if (irq_flags & IRQF_TRIGGER_HIGH) { 2018c2ecf20Sopenharmony_ci ret = regmap_update_bits(madera->regmap, MADERA_IRQ1_CTRL, 2028c2ecf20Sopenharmony_ci MADERA_IRQ_POL_MASK, 0); 2038c2ecf20Sopenharmony_ci if (ret) { 2048c2ecf20Sopenharmony_ci dev_err(&pdev->dev, 2058c2ecf20Sopenharmony_ci "Failed to set IRQ polarity: %d\n", ret); 2068c2ecf20Sopenharmony_ci return ret; 2078c2ecf20Sopenharmony_ci } 2088c2ecf20Sopenharmony_ci } 2098c2ecf20Sopenharmony_ci 2108c2ecf20Sopenharmony_ci /* 2118c2ecf20Sopenharmony_ci * NOTE: regmap registers this against the OF node of the parent of 2128c2ecf20Sopenharmony_ci * the regmap - that is, against the mfd driver 2138c2ecf20Sopenharmony_ci */ 2148c2ecf20Sopenharmony_ci ret = regmap_add_irq_chip(madera->regmap, madera->irq, IRQF_ONESHOT, 0, 2158c2ecf20Sopenharmony_ci &madera_irq_chip, &madera->irq_data); 2168c2ecf20Sopenharmony_ci if (ret) { 2178c2ecf20Sopenharmony_ci dev_err(&pdev->dev, "add_irq_chip failed: %d\n", ret); 2188c2ecf20Sopenharmony_ci return ret; 2198c2ecf20Sopenharmony_ci } 2208c2ecf20Sopenharmony_ci 2218c2ecf20Sopenharmony_ci /* Save dev in parent MFD struct so it is accessible to siblings */ 2228c2ecf20Sopenharmony_ci madera->irq_dev = &pdev->dev; 2238c2ecf20Sopenharmony_ci 2248c2ecf20Sopenharmony_ci return 0; 2258c2ecf20Sopenharmony_ci} 2268c2ecf20Sopenharmony_ci 2278c2ecf20Sopenharmony_cistatic int madera_irq_remove(struct platform_device *pdev) 2288c2ecf20Sopenharmony_ci{ 2298c2ecf20Sopenharmony_ci struct madera *madera = dev_get_drvdata(pdev->dev.parent); 2308c2ecf20Sopenharmony_ci 2318c2ecf20Sopenharmony_ci /* 2328c2ecf20Sopenharmony_ci * The IRQ is disabled by the parent MFD driver before 2338c2ecf20Sopenharmony_ci * it starts cleaning up all child drivers 2348c2ecf20Sopenharmony_ci */ 2358c2ecf20Sopenharmony_ci madera->irq_dev = NULL; 2368c2ecf20Sopenharmony_ci regmap_del_irq_chip(madera->irq, madera->irq_data); 2378c2ecf20Sopenharmony_ci 2388c2ecf20Sopenharmony_ci return 0; 2398c2ecf20Sopenharmony_ci} 2408c2ecf20Sopenharmony_ci 2418c2ecf20Sopenharmony_cistatic struct platform_driver madera_irq_driver = { 2428c2ecf20Sopenharmony_ci .probe = &madera_irq_probe, 2438c2ecf20Sopenharmony_ci .remove = &madera_irq_remove, 2448c2ecf20Sopenharmony_ci .driver = { 2458c2ecf20Sopenharmony_ci .name = "madera-irq", 2468c2ecf20Sopenharmony_ci .pm = &madera_irq_pm_ops, 2478c2ecf20Sopenharmony_ci } 2488c2ecf20Sopenharmony_ci}; 2498c2ecf20Sopenharmony_cimodule_platform_driver(madera_irq_driver); 2508c2ecf20Sopenharmony_ci 2518c2ecf20Sopenharmony_ciMODULE_SOFTDEP("pre: madera"); 2528c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("Madera IRQ driver"); 2538c2ecf20Sopenharmony_ciMODULE_AUTHOR("Richard Fitzgerald <rf@opensource.cirrus.com>"); 2548c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL v2"); 255