/* * Interrupt support for Cirrus Logic Madera codecs * * Copyright 2016 Cirrus Logic * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "irq-madera.h" struct madera_irq_priv { struct device *dev; unsigned int irq_sem; int irq; unsigned int irq_flags; int irq_gpio; struct regmap_irq_chip_data *irq_data; struct irq_domain *domain; struct madera *madera; }; int madera_request_irq(struct madera *madera, int irq, const char *name, irq_handler_t handler, void *data) { struct madera_irq_priv *priv; if (irq < 0) return irq; if (!madera->irq_dev) return -EINVAL; priv = dev_get_drvdata(madera->irq_dev); return request_threaded_irq(regmap_irq_get_virq(priv->irq_data, irq), NULL, handler, IRQF_ONESHOT, name, data); } EXPORT_SYMBOL_GPL(madera_request_irq); void madera_free_irq(struct madera *madera, int irq, void *data) { struct madera_irq_priv *priv; if (irq < 0) return; if (!madera->irq_dev) return; priv = dev_get_drvdata(madera->irq_dev); free_irq(regmap_irq_get_virq(priv->irq_data, irq), data); } EXPORT_SYMBOL_GPL(madera_free_irq); int madera_set_irq_wake(struct madera *madera, int irq, int on) { struct madera_irq_priv *priv; if (irq < 0) return irq; if (!madera->irq_dev) return -EINVAL; priv = dev_get_drvdata(madera->irq_dev); return irq_set_irq_wake(regmap_irq_get_virq(priv->irq_data, irq), on); } EXPORT_SYMBOL_GPL(madera_set_irq_wake); static irqreturn_t madera_irq_thread(int irq, void *data) { struct madera_irq_priv *priv = data; bool poll; int ret; dev_dbg(priv->dev, "irq_thread handler\n"); /* The codec can generate IRQs while it is in low-power mode so * we must do a runtime get before dispatching the IRQ */ ret = pm_runtime_get_sync(priv->madera->dev); if (ret < 0) { dev_err(priv->dev, "Failed to resume device: %d\n", ret); return IRQ_NONE; } do { poll = false; handle_nested_irq(irq_find_mapping(priv->domain, 0)); /* * Poll the IRQ pin status to see if we're really done * if the interrupt controller can't do it for us. */ if (!gpio_is_valid(priv->irq_gpio)) { break; } else if (priv->irq_flags & IRQF_TRIGGER_RISING && gpio_get_value_cansleep(priv->irq_gpio)) { poll = true; } else if (priv->irq_flags & IRQF_TRIGGER_FALLING && !gpio_get_value_cansleep(priv->irq_gpio)) { poll = true; } } while (poll); pm_runtime_mark_last_busy(priv->madera->dev); pm_runtime_put_autosuspend(priv->madera->dev); return IRQ_HANDLED; } static void madera_irq_dummy(struct irq_data *data) { } static int madera_irq_set_wake(struct irq_data *data, unsigned int on) { struct madera_irq_priv *priv = irq_data_get_irq_chip_data(data); return irq_set_irq_wake(priv->irq, on); } static struct irq_chip madera_irq_chip = { .name = "madera", .irq_disable = madera_irq_dummy, .irq_enable = madera_irq_dummy, .irq_ack = madera_irq_dummy, .irq_mask = madera_irq_dummy, .irq_unmask = madera_irq_dummy, .irq_set_wake = madera_irq_set_wake, }; static struct lock_class_key madera_irq_lock_class; static int madera_irq_map(struct irq_domain *h, unsigned int virq, irq_hw_number_t hw) { struct madera_irq_priv *priv = h->host_data; irq_set_chip_data(virq, priv); irq_set_lockdep_class(virq, &madera_irq_lock_class); irq_set_chip_and_handler(virq, &madera_irq_chip, handle_simple_irq); irq_set_nested_thread(virq, true); irq_set_noprobe(virq); return 0; } static const struct irq_domain_ops madera_domain_ops = { .map = madera_irq_map, .xlate = irq_domain_xlate_twocell, }; #ifdef CONFIG_PM_SLEEP static int madera_suspend_noirq(struct device *dev) { struct madera_irq_priv *priv = dev_get_drvdata(dev); dev_dbg(priv->dev, "Late suspend, reenabling IRQ\n"); if (priv->irq_sem) { enable_irq(priv->irq); priv->irq_sem = 0; } return 0; } static int madera_suspend(struct device *dev) { struct madera_irq_priv *priv = dev_get_drvdata(dev); dev_dbg(priv->dev, "Early suspend, disabling IRQ\n"); disable_irq(priv->irq); priv->irq_sem = 1; return 0; } static int madera_resume_noirq(struct device *dev) { struct madera_irq_priv *priv = dev_get_drvdata(dev); dev_dbg(priv->dev, "Early resume, disabling IRQ\n"); disable_irq(priv->irq); priv->irq_sem = 1; return 0; } static int madera_resume(struct device *dev) { struct madera_irq_priv *priv = dev_get_drvdata(dev); dev_dbg(priv->dev, "Late resume, reenabling IRQ\n"); if (priv->irq_sem) { enable_irq(priv->irq); priv->irq_sem = 0; } return 0; } #endif static const struct dev_pm_ops madera_irq_pm_ops = { SET_SYSTEM_SLEEP_PM_OPS(madera_suspend, madera_resume) #ifdef CONFIG_PM_SLEEP .suspend_noirq = madera_suspend_noirq, .resume_noirq = madera_resume_noirq, #endif }; static int madera_irq_of_get(struct madera_irq_priv *priv) { /* all our settings are under the parent DT node */ struct device_node *np = priv->madera->dev->of_node; u32 value; int ret; priv->irq = irq_of_parse_and_map(np, 0); if (priv->irq < 0) return priv->irq; ret = of_property_read_u32(np, "cirrus,irq_flags", &value); if (ret == 0) priv->irq_flags = value; priv->irq_gpio = of_get_named_gpio(np, "cirrus,irq-gpios", 0); return 0; } int madera_irq_probe(struct platform_device *pdev) { struct madera *madera = dev_get_drvdata(pdev->dev.parent); struct madera_irq_priv *priv; const struct regmap_irq_chip *irq = NULL; struct irq_data *irq_data; int flags = IRQF_ONESHOT; int ret; dev_dbg(&pdev->dev, "probe\n"); priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); if (!priv) return -ENOMEM; priv->dev = &pdev->dev; priv->madera = madera; switch (madera->type) { case CS47L35: if (IS_ENABLED(CONFIG_MADERA_IRQ_CS47L35)) irq = &cs47l35_irq; break; case CS47L85: case WM1840: if (IS_ENABLED(CONFIG_MADERA_IRQ_CS47L85)) irq = &cs47l85_irq; break; case CS47L90: case CS47L91: if (IS_ENABLED(CONFIG_MADERA_IRQ_CS47L90)) irq = &cs47l90_irq; break; case CS47L92: case CS47L93: if (IS_ENABLED(CONFIG_MADERA_IRQ_CS47L92)) irq = &cs47l92_irq; break; default: dev_err(madera->dev, "Unsupported Madera device type %d\n", madera->type); return -EINVAL; } if (!irq) { dev_err(madera->dev, "No support for %s\n", madera_name_from_type(madera->type)); return -EINVAL; } if (IS_ENABLED(CONFIG_OF)) { if (!dev_get_platdata(priv->dev)) { ret = madera_irq_of_get(priv); if (ret < 0) return ret; } else { priv->irq = madera->irq; priv->irq_flags = madera->pdata.irqchip.irq_flags; priv->irq_gpio = madera->pdata.irqchip.irq_gpio; /* pdata uses 0 to mean undefined, convert to an * invalid GPIO number */ if (priv->irq_gpio == 0) priv->irq_gpio = -1; } } /* Read the flags from the interrupt controller if not specified */ if (!priv->irq_flags) { irq_data = irq_get_irq_data(priv->irq); if (!irq_data) { dev_err(priv->dev, "Invalid IRQ: %d\n", priv->irq); return -EINVAL; } priv->irq_flags = irqd_get_trigger_type(irq_data); switch (priv->irq_flags) { case IRQF_TRIGGER_LOW: case IRQF_TRIGGER_HIGH: case IRQF_TRIGGER_RISING: case IRQF_TRIGGER_FALLING: break; case IRQ_TYPE_NONE: default: /* Device default */ priv->irq_flags = IRQF_TRIGGER_LOW; break; } } flags |= priv->irq_flags; if (flags & (IRQF_TRIGGER_HIGH | IRQF_TRIGGER_RISING)) { ret = regmap_update_bits(madera->regmap, MADERA_IRQ1_CTRL, MADERA_IRQ_POL_MASK, 0); if (ret) { dev_err(priv->dev, "Couldn't set IRQ polarity: %d\n", ret); return ret; } } if (flags & (IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING)) { /* A GPIO is recommended to emulate edge trigger */ if (gpio_is_valid(priv->irq_gpio)) { if (gpio_to_irq(priv->irq_gpio) != priv->irq) { dev_warn(priv->dev, "IRQ %d is not GPIO %d (%d)\n", priv->irq, priv->irq_gpio, gpio_to_irq(priv->irq_gpio)); priv->irq = gpio_to_irq(priv->irq_gpio); } ret = devm_gpio_request_one(priv->dev, priv->irq_gpio, GPIOF_IN, "madera IRQ"); if (ret) { dev_err(priv->dev, "Failed to request IRQ GPIO %d: %d\n", priv->irq_gpio, ret); return ret; } } dev_dbg(priv->dev, "edge-trigger mode irq_gpio=%d\n", priv->irq_gpio); } else { dev_dbg(priv->dev, "level-trigger mode\n"); } priv->domain = irq_domain_add_linear(NULL, 1, &madera_domain_ops, priv); ret = regmap_add_irq_chip(madera->regmap, irq_create_mapping(priv->domain, 0), IRQF_ONESHOT, 0, irq, &priv->irq_data); if (ret) { dev_err(priv->dev, "add_irq_chip failed: %d\n", ret); return ret; } ret = request_threaded_irq(priv->irq, NULL, madera_irq_thread, flags, "madera", priv); if (ret) { dev_err(priv->dev, "Failed to request threaded irq %d: %d\n", priv->irq, ret); regmap_del_irq_chip(priv->irq, priv->irq_data); return ret; } platform_set_drvdata(pdev, priv); madera->irq_dev = priv->dev; return 0; } int madera_irq_remove(struct platform_device *pdev) { struct madera_irq_priv *priv = platform_get_drvdata(pdev); priv->madera->irq_dev = NULL; regmap_del_irq_chip(priv->irq, priv->irq_data); free_irq(priv->irq, priv); return 0; } static struct platform_driver madera_irq_driver = { .probe = madera_irq_probe, .remove = madera_irq_remove, .driver = { .name = "madera-irq", .owner = THIS_MODULE, .pm = &madera_irq_pm_ops, } }; module_platform_driver(madera_irq_driver); MODULE_DESCRIPTION("Madera IRQ driver"); MODULE_AUTHOR("Richard Fitzgerald "); MODULE_LICENSE("GPL v2");