Hi
a few more comments here:
On Fri, 16 Sep 2011, Tero Kristo wrote:
> diff --git a/drivers/power/omap_prm.c b/drivers/power/omap_prm.c
> index dfc0920..880748a 100644
> --- a/drivers/power/omap_prm.c
> +++ b/drivers/power/omap_prm.c
> #define DRIVER_NAME "omap-prm"
> +#define OMAP_PRCM_MAX_NR_PENDING_REG 2
> +
> +struct omap_prcm_irq_setup {
> + u32 ack;
> + u32 mask;
> + int nr_regs;
> +};
>
> struct omap_prm_device {
> - struct platform_device pdev;
> + struct platform_device pdev;
> + const struct omap_prcm_irq_setup *irq_setup;
> + struct irq_chip_generic **irq_chips;
> + int suspended;
> + u32 *saved_ena;
> + u32 *priority_mask;
> + int base_irq;
> + int irq;
> + void __iomem *base;
> + int revision;
> +};
> +
> +#define OMAP3_PRM_REVISION 0x10
> +#define OMAP4_PRM_REVISION 0x40000100
> +
> +#define PRM_OMAP3 0x1
> +#define PRM_OMAP4 0x2
These should no longer be needed, as far as I can see.
> +#define OMAP3_PRM_IRQSTATUS_OFFSET 0x818
> +#define OMAP3_PRM_IRQENABLE_OFFSET 0x81c
> +#define OMAP4_PRM_IRQSTATUS_OFFSET 0x10
> +#define OMAP4_PRM_IRQENABLE_OFFSET 0x18
It probably would be best to put these in header files, and also just to
cut and paste the needed macros from the arch/arm/mach-omap2/prm*.h files
- the 44xx files are autogenerated and the 24xx/34xx files were partially
autogenerated.
> +
> +static const struct omap_prcm_irq_setup omap3_prcm_irq_setup = {
> + .ack = OMAP3_PRM_IRQSTATUS_OFFSET,
> + .mask = OMAP3_PRM_IRQENABLE_OFFSET,
> + .nr_regs = 1,
> +};
> +
> +static const struct omap_prcm_irq_setup omap4_prcm_irq_setup = {
> + .ack = OMAP4_PRM_IRQSTATUS_OFFSET,
> + .mask = OMAP4_PRM_IRQENABLE_OFFSET,
> + .nr_regs = 2,
> };
>
> static struct omap_prm_device prm_dev = {
> @@ -33,20 +77,321 @@ static struct omap_prm_device prm_dev = {
> },
> };
>
> -static int __init omap_prm_probe(struct platform_device *pdev)
> +struct omap_prcm_irq {
> + const char *name;
> + unsigned int offset;
> + bool priority;
> + u32 supported_rev;
> +};
> +
> +#define OMAP_PRCM_IRQ(_name, _offset, _high_priority, _rev) { \
> + .name = _name, \
> + .offset = _offset, \
> + .priority = _high_priority, \
> + .supported_rev = _rev \
> + }
> +
> +static struct omap_prcm_irq omap_prcm_irqs[] = {
> + OMAP_PRCM_IRQ("wkup", 0, 0, PRM_OMAP3),
> + OMAP_PRCM_IRQ("io", 9, 1, PRM_OMAP3 | PRM_OMAP4),
> +};
> +
> +static inline u32 prm_read_reg(int offset)
> +{
> + return __raw_readl(prm_dev.base + offset);
> +}
> +
> +static inline void prm_write_reg(u32 value, int offset)
> +{
> + __raw_writel(value, prm_dev.base + offset);
> +}
What to do with these functions will depend on how you split the files up.
Based on a naïve look, I'd suggest putting copies of prcm_irq_handler()
into each of the omap*_prm.c files, calling local static functions for
read_pending_events() and save_and_disable_irqenable_bits(), and then
calling into common code in omap_prm_common.c for the rest of the
function. Looks like omap_prm_complete() would also need
PRM-variant-specific copies. I guess most of the rest could be common
code?
> +
> +static void prm_pending_events(unsigned long *events)
> +{
> + u32 ena, st;
> + int i;
> +
> + memset(events, 0, prm_dev.irq_setup->nr_regs * 4);
> +
> + for (i = 0; i < prm_dev.irq_setup->nr_regs; i++) {
> + ena = prm_read_reg(prm_dev.irq_setup->mask + i * 4);
> + st = prm_read_reg(prm_dev.irq_setup->ack + i * 4);
> + events[i] = ena & st;
> + }
> +}
> +
> +static void prm_events_filter_priority(unsigned long *events,
> + unsigned long *priority_events)
> +{
> + int i;
> +
> + for (i = 0; i < prm_dev.irq_setup->nr_regs; i++) {
> + priority_events[i] = events[i] & prm_dev.priority_mask[i];
> + events[i] ^= priority_events[i];
> + }
> +}
> +
> +/*
> + * 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];
> + unsigned long priority_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] =
> + prm_read_reg(prm_dev.irq_setup->mask + i * 4);
> + prm_write_reg(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;
> +
> + prm_events_filter_priority(pending, priority_pending);
> +
> + /*
> + * Loop on all currently pending irqs so that new irqs
> + * cannot starve previously pending irqs
> + */
> +
> + /* Serve priority events first */
> + for_each_set_bit(virtirq, priority_pending, nr_irqs)
> + generic_handle_irq(prm_dev.base_irq + virtirq);
> +
> + /* Serve normal events next */
> + for_each_set_bit(virtirq, pending, nr_irqs)
> + generic_handle_irq(prm_dev.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.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;
> + }
> +
> + kfree(prm_dev.saved_ena);
> + prm_dev.saved_ena = NULL;
> +
> + kfree(prm_dev.priority_mask);
> + prm_dev.priority_mask = NULL;
> +
> + irq_set_chained_handler(prm_dev.irq, NULL);
> +
> + if (prm_dev.base_irq > 0)
> + irq_free_descs(prm_dev.base_irq,
> + prm_dev.irq_setup->nr_regs * 32);
> + prm_dev.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;
> +
> + 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);
> +
> + prm_dev.priority_mask = kzalloc(sizeof(u32) *
> + prm_dev.irq_setup->nr_regs, GFP_KERNEL);
> +
> + if (!prm_dev.irq_chips || !prm_dev.saved_ena ||
> + !prm_dev.priority_mask) {
> + pr_err("PRCM: kzalloc failed\n");
> + goto err;
> + }
> +
> + memset(mask, 0, sizeof(mask));
> + for (i = 0; i < ARRAY_SIZE(omap_prcm_irqs); i++)
> + if (prm_dev.revision & omap_prcm_irqs[i].supported_rev) {
> + offset = omap_prcm_irqs[i].offset;
> + mask[offset >> 5] |= 1 << (offset & 0x1f);
> + if (offset > max_irq)
> + max_irq = offset;
> + if (omap_prcm_irqs[i].priority)
> + prm_dev.priority_mask[offset >> 5] |=
> + 1 << (offset & 0x1f);
> + }
> +
> + irq_set_chained_handler(prm_dev.irq, prcm_irq_handler);
> +
> + prm_dev.base_irq =
> + irq_alloc_descs(-1, 0, prm_dev.irq_setup->nr_regs * 32, 0);
> +
> + if (prm_dev.base_irq < 0) {
> + pr_err("PRCM: failed to allocate irq descs\n");
> + goto err;
> + }
> +
> + for (i = 0; i <= prm_dev.irq_setup->nr_regs; i++) {
> + gc = irq_alloc_generic_chip("PRCM", 1,
> + prm_dev.base_irq + i * 32, prm_dev.base,
> + 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++)
> + prm_write_reg(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)
> +{
> + struct omap_hwmod *oh;
> + int rev;
> +
> + oh = omap_hwmod_lookup("prm");
> +
> + if (!oh) {
> + pr_err("prm hwmod not found\n");
> + return -ENODEV;
> + }
> +
> + prm_dev.base = omap_hwmod_get_mpu_rt_va(oh);
> +
> + rev = prm_read_reg(oh->class->sysc->rev_offs);
> +
> + switch (rev) {
> + case OMAP3_PRM_REVISION:
> + prm_dev.irq_setup = &omap3_prcm_irq_setup;
> + prm_dev.revision = PRM_OMAP3;
> + break;
> + case OMAP4_PRM_REVISION:
> + prm_dev.irq_setup = &omap4_prcm_irq_setup;
> + prm_dev.revision = PRM_OMAP4;
> + break;
> + default:
> + pr_err("unknown PRM revision: %08x\n", rev);
> + return -ENODEV;
> + }
> +
> + prm_dev.irq = oh->mpu_irqs[0].irq;
> +
> + omap_prcm_irq_init();
> +
> + 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 <[email protected]>
> + *
> + * 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
> --
> 1.7.4.1
>
>
> Texas Instruments Oy, Tekniikantie 12, 02150 Espoo. Y-tunnus: 0115040-6.
> Kotipaikka: Helsinki
>
>
> --
> To unsubscribe from this list: send the line "unsubscribe linux-omap" in
> the body of a message to [email protected]
> More majordomo info at http://vger.kernel.org/majordomo-info.html
>
- Paul