/* * 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. */ #include #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; }; struct omap_prm_device { struct platform_device pdev; struct omap_prcm_irq_setup irq_setup; struct irq_chip_generic **irq_chips; int suspended; u32 *saved_ena; }; static struct omap_prm_device prm_dev = { .pdev = { .name = DRIVER_NAME, .id = -1, }, }; struct omap_prcm_irq { const char *name; unsigned int offset; const struct omap_chip_id omap_chip; }; #define OMAP_PRCM_IRQ(_name, _offset, _chip) { \ .name = _name, \ .offset = _offset, \ .omap_chip = OMAP_CHIP_INIT(_chip) \ } static struct omap_prcm_irq omap_prcm_irqs[] = { OMAP_PRCM_IRQ("wkup", 0, CHIP_IS_OMAP3430 | CHIP_GE_OMAP3630ES1_1), OMAP_PRCM_IRQ("io", 9, CHIP_IS_OMAP3430 | CHIP_GE_OMAP3630ES1_1 | CHIP_IS_OMAP4430), }; 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 + i * 4); st = readl(prm_dev.irq_setup.ack + i * 4); 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) { int i; 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_regs * 32; if (prm_dev.suspended) for (i = 0; i < prm_dev.irq_setup.nr_regs; i++) { prm_dev.saved_ena[i] = readl(prm_dev.irq_setup.mask + i * 4); writel(0, prm_dev.irq_setup.mask + i * 4); } /* * Loop until all pending irqs are handled, since * generic_handle_irq() can cause new irqs to come */ while (!prm_dev.suspended) { prm_pending_events(pending); /* No bit set, then all IRQs are handled */ if (find_first_bit(pending, nr_irqs) >= nr_irqs) { 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); } if (chip->irq_ack) chip->irq_ack(&desc->irq_data); if (chip->irq_eoi) chip->irq_eoi(&desc->irq_data); 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_regs * 32); 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; u32 mask[OMAP_PRCM_MAX_NR_PENDING_REG]; int offset; int max_irq = 0; memset(mask, 0, sizeof(mask)); for (i = 0; i < ARRAY_SIZE(omap_prcm_irqs); i++) if (omap_chip_is(omap_prcm_irqs[i].omap_chip)) { offset = omap_prcm_irqs[i].offset; mask[offset >> 5] |= 1 << (offset & 0x1f); if (offset > max_irq) max_irq = offset; } 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_regs * 32, 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); prm_dev.saved_ena = kzalloc(sizeof(u32) * prm_dev.irq_setup.nr_regs, GFP_KERNEL); if (!prm_dev.irq_chips || !prm_dev.saved_ena) { pr_err("PRCM: kzalloc failed\n"); goto err; } for (i = 0; i <= prm_dev.irq_setup.nr_regs; 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, mask[i], 0, IRQ_NOREQUEST, 0); prm_dev.irq_chips[i] = gc; } return 0; err: omap_prcm_irq_cleanup(); return -ENOMEM; } static int omap_prm_prepare(struct device *kdev) { prm_dev.suspended = 1; return 0; } static void omap_prm_complete(struct device *kdev) { int i; prm_dev.suspended = 0; for (i = 0; i < prm_dev.irq_setup.nr_regs; i++) writel(prm_dev.saved_ena[i], prm_dev.irq_setup.mask + i * 4); } static int __devexit omap_prm_remove(struct platform_device *pdev) { return 0; } static int __init omap_prm_probe(struct platform_device *pdev) { prm_dev.irq_setup.ack = 0xb2000000 + 0x4a306010; prm_dev.irq_setup.mask = 0xb2000000 + 0x4a306018; prm_dev.irq_setup.irq = OMAP44XX_IRQ_PRCM; prm_dev.irq_setup.nr_regs = 2; omap_prcm_irq_init(); prm_dev.irq_setup.io_irq = omap_prcm_event_to_irq("io"); 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, }, }; static int __init omap_prm_init(void) { int ret; ret = platform_device_register(&prm_dev.pdev); if (ret) { printk(KERN_ERR "Unable to register PRM device: %d\n", ret); return ret; } ret = platform_driver_probe(&prm_driver, omap_prm_probe); if (ret) printk(KERN_ERR "PRM driver probe failed: %d\n", ret); return ret; } subsys_initcall(omap_prm_init); static void __exit omap_prm_exit(void) { platform_device_unregister(&prm_dev.pdev); platform_driver_unregister(&prm_driver); } module_exit(omap_prm_exit); MODULE_ALIAS("platform:"DRIVER_NAME); MODULE_AUTHOR("Tero Kristo "); MODULE_DESCRIPTION("OMAP PRM driver"); MODULE_LICENSE("GPL");