From patchwork Thu Sep 8 15:22:19 2011 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Tero Kristo X-Patchwork-Id: 1130052 Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by demeter2.kernel.org (8.14.4/8.14.4) with ESMTP id p88FMb3B002601 for ; Thu, 8 Sep 2011 15:22:42 GMT Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1754005Ab1IHPWl (ORCPT ); Thu, 8 Sep 2011 11:22:41 -0400 Received: from bear.ext.ti.com ([192.94.94.41]:40791 "EHLO bear.ext.ti.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1753772Ab1IHPWi convert rfc822-to-8bit (ORCPT ); Thu, 8 Sep 2011 11:22:38 -0400 Received: from dlep34.itg.ti.com ([157.170.170.115]) by bear.ext.ti.com (8.13.7/8.13.7) with ESMTP id p88FMa0v031497 (version=TLSv1/SSLv3 cipher=DHE-RSA-AES256-SHA bits=256 verify=NO); Thu, 8 Sep 2011 10:22:36 -0500 Received: from dlep26.itg.ti.com (smtp-le.itg.ti.com [157.170.170.27]) by dlep34.itg.ti.com (8.13.7/8.13.8) with ESMTP id p88FMaG7009730; Thu, 8 Sep 2011 10:22:36 -0500 (CDT) Received: from dnce72.ent.ti.com (localhost [127.0.0.1]) by dlep26.itg.ti.com (8.13.8/8.13.8) with ESMTP id p88FMZRp022552; Thu, 8 Sep 2011 10:22:35 -0500 (CDT) thread-index: AcxuOyLJ4nq9SkGcRi6o09Q9mArJJw== Content-Class: urn:content-classes:message Importance: normal X-MimeOLE: Produced By Microsoft MimeOLE V6.00.3790.4657 Received: from localhost.localdomain (172.24.88.2) by dnce72.ent.ti.com (137.167.131.87) with Microsoft SMTP Server (TLS) id 8.3.106.1; Thu, 8 Sep 2011 17:22:34 +0200 From: Tero Kristo To: CC: , , Subject: [PATCHv7 4/9] power: omap-prm: added prcm chain interrupt handler Date: Thu, 8 Sep 2011 18:22:19 +0300 Message-ID: <1315495344-23133-5-git-send-email-t-kristo@ti.com> X-Mailer: git-send-email 1.7.4.1 In-Reply-To: <1315495344-23133-1-git-send-email-t-kristo@ti.com> References: <1315495344-23133-1-git-send-email-t-kristo@ti.com> MIME-Version: 1.0 Sender: linux-omap-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-omap@vger.kernel.org X-Greylist: IP, sender and recipient auto-whitelisted, not delayed by milter-greylist-4.2.6 (demeter2.kernel.org [140.211.167.43]); Thu, 08 Sep 2011 15:22:42 +0000 (UTC) The implementation in this patch still requires the irq_setup to be done properly, and also lacks the supported interrupts. These will be added in separate patches. Signed-off-by: Tero Kristo --- drivers/power/omap_prm.c | 214 +++++++++++++++++++++++++++++++++++++++- include/linux/power/omap_prm.h | 19 ++++ 2 files changed, 231 insertions(+), 2 deletions(-) create mode 100644 include/linux/power/omap_prm.h diff --git a/drivers/power/omap_prm.c b/drivers/power/omap_prm.c index dfc0920..745a4bc 100644 --- a/drivers/power/omap_prm.c +++ b/drivers/power/omap_prm.c @@ -15,15 +15,32 @@ #include #include #include +#include +#include #include #include #include #include +#include + #define DRIVER_NAME "omap-prm" +#define OMAP_PRCM_MAX_NR_PENDING_REG 2 + +struct omap_prcm_irq_setup { + u32 ack; + u32 mask; + int irq; + int io_irq; + int base_irq; + int nr_regs; + int nr_irqs; +}; struct omap_prm_device { struct platform_device pdev; + struct omap_prcm_irq_setup irq_setup; + struct irq_chip_generic **irq_chips; }; static struct omap_prm_device prm_dev = { @@ -33,20 +50,213 @@ static struct omap_prm_device prm_dev = { }, }; -static int __init omap_prm_probe(struct platform_device *pdev) +static void prm_pending_events(unsigned long *events) +{ + u32 ena, st; + int i; + + memset(events, 0, prm_dev.irq_setup.nr_regs * 8); + + for (i = 0; i < prm_dev.irq_setup.nr_regs; i++) { + ena = readl(prm_dev.irq_setup.mask); + st = readl(prm_dev.irq_setup.ack); + events[i] = ena & st; + } +} + +/* + * PRCM Interrupt Handler + * + * The PRM_IRQSTATUS_MPU register indicates if there are any pending + * interrupts from the PRCM for the MPU. These bits must be cleared in + * order to clear the PRCM interrupt. The PRCM interrupt handler is + * implemented to simply clear the PRM_IRQSTATUS_MPU in order to clear + * the PRCM interrupt. Please note that bit 0 of the PRM_IRQSTATUS_MPU + * register indicates that a wake-up event is pending for the MPU and + * this bit can only be cleared if the all the wake-up events latched + * in the various PM_WKST_x registers have been cleared. The interrupt + * handler is implemented using a do-while loop so that if a wake-up + * event occurred during the processing of the prcm interrupt handler + * (setting a bit in the corresponding PM_WKST_x register and thus + * preventing us from clearing bit 0 of the PRM_IRQSTATUS_MPU register) + * this would be handled. + */ +static void prcm_irq_handler(unsigned int irq, struct irq_desc *desc) +{ + unsigned long pending[OMAP_PRCM_MAX_NR_PENDING_REG]; + struct irq_chip *chip = irq_desc_get_chip(desc); + unsigned int virtirq; + int nr_irqs = prm_dev.irq_setup.nr_irqs; + + /* + * Loop until all pending irqs are handled, since + * generic_handle_irq() can cause new irqs to come + */ + while (1) { + chip->irq_ack(&desc->irq_data); + + prm_pending_events(pending); + + /* No bit set, then all IRQs are handled */ + if (find_first_bit(pending, nr_irqs) >= nr_irqs) { + chip->irq_unmask(&desc->irq_data); + break; + } + + /* + * Loop on all currently pending irqs so that new irqs + * cannot starve previously pending irqs + */ + for_each_set_bit(virtirq, pending, nr_irqs) + generic_handle_irq(prm_dev.irq_setup.base_irq + + virtirq); + + chip->irq_unmask(&desc->irq_data); + } +} + +/* + * Given a PRCM event name, returns the corresponding IRQ on which the + * handler should be registered. + */ +int omap_prcm_event_to_irq(const char *name) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(omap_prcm_irqs); i++) + if (!strcmp(omap_prcm_irqs[i].name, name)) + return prm_dev.irq_setup.base_irq + + omap_prcm_irqs[i].offset; + + return -ENOENT; +} + +/* + * Reverses memory allocated and other setups done by + * omap_prcm_irq_init(). + */ +void omap_prcm_irq_cleanup(void) { + int i; + + if (prm_dev.irq_chips) { + for (i = 0; i < prm_dev.irq_setup.nr_regs; i++) { + if (prm_dev.irq_chips[i]) + irq_remove_generic_chip(prm_dev.irq_chips[i], + 0xffffffff, 0, 0); + prm_dev.irq_chips[i] = NULL; + } + kfree(prm_dev.irq_chips); + prm_dev.irq_chips = NULL; + } + + irq_set_chained_handler(prm_dev.irq_setup.irq, NULL); + + if (prm_dev.irq_setup.base_irq > 0) + irq_free_descs(prm_dev.irq_setup.base_irq, + prm_dev.irq_setup.nr_irqs); + prm_dev.irq_setup.base_irq = 0; +} + +/* + * Prepare the array of PRCM events corresponding to the current SoC, + * and set-up the chained interrupt handler mechanism. + */ +static int __init omap_prcm_irq_init(void) +{ + int i; + struct irq_chip_generic *gc; + struct irq_chip_type *ct; + int offset; + int max_irq = 64; + + /* XXX: supported irqs should be setup here */ + + irq_set_chained_handler(prm_dev.irq_setup.irq, prcm_irq_handler); + + prm_dev.irq_setup.base_irq = + irq_alloc_descs(-1, 0, prm_dev.irq_setup.nr_irqs, 0); + + if (prm_dev.irq_setup.base_irq < 0) { + pr_err("PRCM: failed to allocate irq descs\n"); + goto err; + } + + prm_dev.irq_chips = kzalloc(sizeof(void *) * prm_dev.irq_setup.nr_regs, + GFP_KERNEL); + + if (!prm_dev.irq_chips) { + pr_err("PRCM: kzalloc failed\n"); + goto err; + } + + for (i = 0; i <= max_irq / 32; i++) { + gc = irq_alloc_generic_chip("PRCM", 1, + prm_dev.irq_setup.base_irq + i * 32, NULL, + handle_level_irq); + + if (!gc) { + pr_err("PRCM: failed to allocate generic chip\n"); + goto err; + } + ct = gc->chip_types; + ct->chip.irq_ack = irq_gc_ack_set_bit; + ct->chip.irq_mask = irq_gc_mask_clr_bit; + ct->chip.irq_unmask = irq_gc_mask_set_bit; + + ct->regs.ack = prm_dev.irq_setup.ack + (i << 2); + ct->regs.mask = prm_dev.irq_setup.mask + (i << 2); + + irq_setup_generic_chip(gc, 0xffffffff, 0, IRQ_NOREQUEST, 0); + prm_dev.irq_chips[i] = gc; + } + return 0; + +err: + omap_prcm_irq_cleanup(); + return -ENOMEM; } -static int __devexit omap_prm_remove(struct platform_device *pdev) +static int omap_prm_prepare(struct device *kdev) { + disable_irq(prm_dev.irq_setup.io_irq); return 0; } +static void omap_prm_complete(struct device *kdev) +{ + enable_irq(prm_dev.irq_setup.io_irq); +} + +static int __devexit omap_prm_remove(struct platform_device *pdev) +{ + return 0; +} + +static int __init omap_prm_probe(struct platform_device *pdev) +{ + /* XXX: prm_dev.irq_setup should be populated here */ + + /* XXX: following calls should be enabled once irq_setup is done */ +#if 0 + omap_prcm_irq_init(); + + prm_dev.irq_setup.io_irq = omap_prcm_event_to_irq("io"); +#endif + return 0; +} + +static const struct dev_pm_ops prm_pm_ops = { + .prepare = omap_prm_prepare, + .complete = omap_prm_complete, +}; + static struct platform_driver prm_driver = { .remove = __exit_p(omap_prm_remove), .driver = { .name = DRIVER_NAME, + .pm = &prm_pm_ops, }, }; diff --git a/include/linux/power/omap_prm.h b/include/linux/power/omap_prm.h new file mode 100644 index 0000000..9b161b5 --- /dev/null +++ b/include/linux/power/omap_prm.h @@ -0,0 +1,19 @@ +/* + * OMAP Power and Reset Management (PRM) driver + * + * Copyright (C) 2011 Texas Instruments, Inc. + * + * Author: Tero Kristo + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#ifndef __LINUX_POWER_OMAP_PRM_H__ +#define __LINUX_POWER_OMAP_PRM_H__ + +int omap_prcm_event_to_irq(const char *name); + +#endif