On Jul 27, 2012, at 1:20 AM, <dongsheng.w...@freescale.com> <dongsheng.w...@freescale.com> wrote:
> From: Wang Dongsheng <dongsheng.w...@freescale.com> > > Global timers A and B internal to the PIC. The two independent groups > of global timer, group A and group B, are identical in their functionality. > The hardware timer generates an interrupt on every timer cycle. > e.g > Power management can use the hardware timer to wake up the machine. > > Signed-off-by: Wang Dongsheng <dongsheng.w...@freescale.com> > Signed-off-by: Li Yang <le...@freescale.com> How much of this is FSL specific vs openpic? OpenPIC spec's timer support (only a single group). > --- > arch/powerpc/include/asm/mpic_timer.h | 15 + > arch/powerpc/platforms/Kconfig | 5 + > arch/powerpc/sysdev/Makefile | 1 + > arch/powerpc/sysdev/mpic_timer.c | 459 +++++++++++++++++++++++++++++++++ > 4 files changed, 480 insertions(+), 0 deletions(-) > create mode 100644 arch/powerpc/include/asm/mpic_timer.h > create mode 100644 arch/powerpc/sysdev/mpic_timer.c > > diff --git a/arch/powerpc/include/asm/mpic_timer.h > b/arch/powerpc/include/asm/mpic_timer.h > new file mode 100644 > index 0000000..01d58a2 > --- /dev/null > +++ b/arch/powerpc/include/asm/mpic_timer.h > @@ -0,0 +1,15 @@ > +#ifndef __MPIC_TIMER__ > +#define __MPIC_TIMER__ > + > +#include <linux/interrupt.h> > +#include <linux/time.h> > + > +struct mpic_timer *mpic_request_timer(irq_handler_t fn, void *dev, > + const struct timeval *time); > + > +void mpic_start_timer(struct mpic_timer *handle); > + > +void mpic_stop_timer(struct mpic_timer *handle); > + > +void mpic_free_timer(struct mpic_timer *handle); > +#endif > diff --git a/arch/powerpc/platforms/Kconfig b/arch/powerpc/platforms/Kconfig > index f21af8d..3466690 100644 > --- a/arch/powerpc/platforms/Kconfig > +++ b/arch/powerpc/platforms/Kconfig > @@ -87,6 +87,11 @@ config MPIC > bool > default n > > +config MPIC_TIMER > + bool "MPIC Global Timer" > + depends on MPIC && FSL_SOC > + default n > + > config PPC_EPAPR_HV_PIC > bool > default n > diff --git a/arch/powerpc/sysdev/Makefile b/arch/powerpc/sysdev/Makefile > index b0aff6c..3002f28 100644 > --- a/arch/powerpc/sysdev/Makefile > +++ b/arch/powerpc/sysdev/Makefile > @@ -4,6 +4,7 @@ ccflags-$(CONFIG_PPC64) := -mno-minimal-toc > > mpic-msi-obj-$(CONFIG_PCI_MSI) += mpic_msi.o mpic_u3msi.o > mpic_pasemi_msi.o > obj-$(CONFIG_MPIC) += mpic.o $(mpic-msi-obj-y) > +obj-$(CONFIG_MPIC_TIMER) += mpic_timer.o > obj-$(CONFIG_PPC_EPAPR_HV_PIC) += ehv_pic.o > fsl-msi-obj-$(CONFIG_PCI_MSI) += fsl_msi.o > obj-$(CONFIG_PPC_MSI_BITMAP) += msi_bitmap.o > diff --git a/arch/powerpc/sysdev/mpic_timer.c > b/arch/powerpc/sysdev/mpic_timer.c > new file mode 100644 > index 0000000..ef0db4d > --- /dev/null > +++ b/arch/powerpc/sysdev/mpic_timer.c > @@ -0,0 +1,459 @@ > +/* > + * Copyright (c) 2012 Freescale Semiconductor, Inc. All rights reserved. > + * > + * 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 <linux/kernel.h> > +#include <linux/init.h> > +#include <linux/errno.h> > +#include <asm/io.h> > +#include <linux/mm.h> > +#include <linux/interrupt.h> > +#include <linux/slab.h> > +#include <linux/of.h> > +#include <linux/of_device.h> > + > +#include <sysdev/fsl_soc.h> > +#include <asm/mpic_timer.h> > + > + > +#define MPIC_TIMER_TCR_ROVR_OFFSET 24 > +#define MPIC_TIMER_TCR_CLKDIV_64 0x00000300 > + > +#define MPIC_TIMER_STOP 0x80000000 > +#define MPIC_ALL_TIMER 4 > + > +#define MAX_TIME (~0U>>1) > +#define MAX_TIME_CASCADE (~0U) > + > +#define TIMER_OFFSET(num) (1 << (MPIC_ALL_TIMER - 1 - num)) > +#define ONE_SECOND 1000000 > + > +struct timer_regs { > + u32 gtccr; > + u32 res0[3]; > + u32 gtbcr; > + u32 res1[3]; > + u32 gtvpr; > + u32 res2[3]; > + u32 gtdr; > + u32 res3[3]; > +}; > + > +struct mpic_timer { > + void *dev; > + struct cascade_priv *cascade_handle; > + unsigned int num; > + int irq; > +}; > + > +struct cascade_priv { > + u32 tcr_value; /* TCR register: CASC & ROVR value */ > + unsigned int cascade_map; /* cascade map */ > + unsigned int timer_num; /* cascade control timer */ > +}; > + > +struct group_priv { > + struct timer_regs __iomem *regs; > + struct mpic_timer timer[MPIC_ALL_TIMER]; > + struct list_head node; > + unsigned int idle; > + spinlock_t lock; > + void __iomem *group_tcr; > +}; > + > +static struct cascade_priv cascade_timer[] = { > + /* cascade timer 0 and 1 */ > + {0x1, 0xc, 0x1}, > + /* cascade timer 1 and 2 */ > + {0x2, 0x6, 0x2}, > + /* cascade timer 2 and 3 */ > + {0x4, 0x3, 0x3} > +}; > + > +static u32 ccbfreq; > +static u64 max_value; /* prevent u64 overflow */ > +static LIST_HEAD(group_list); > + > +/* the time set by the user is converted to "ticks" */ > +static int transform_time(const struct timeval *time, int clkdiv, u64 *ticks) > +{ > + u64 tmp = 0; > + u64 tmp_sec = 0; > + u64 tmp_ms = 0; > + u64 tmp_us = 0; > + u32 div = 0; > + > + if ((time->tv_sec + time->tv_usec) == 0 || > + time->tv_sec < 0 || time->tv_usec < 0) > + return -EINVAL; > + > + if (time->tv_usec > ONE_SECOND) > + return -EINVAL; > + > + if (time->tv_sec > max_value || > + (time->tv_sec == max_value && time->tv_usec > 0)) > + return -EINVAL; > + > + div = (1 << (clkdiv >> 8)) * 8; > + > + tmp_sec = div_u64((u64)time->tv_sec * (u64)ccbfreq, div); > + tmp += tmp_sec; > + > + tmp_ms = time->tv_usec / 1000; > + tmp_ms = div_u64((u64)tmp_ms * (u64)ccbfreq, div * 1000); > + tmp += tmp_ms; > + > + tmp_us = time->tv_usec % 1000; > + tmp_us = div_u64((u64)tmp_us * (u64)ccbfreq, div * 1000000); > + tmp += tmp_us; > + > + *ticks = tmp; > + > + return 0; > +} > + > +/* detect whether there is a cascade timer available */ > +struct mpic_timer *detect_idle_cascade_timer(void) should this be static? > +{ > + struct group_priv *priv; > + struct cascade_priv *casc_priv; > + unsigned int tmp; > + unsigned int array_size = ARRAY_SIZE(cascade_timer); > + unsigned int num; > + unsigned int i; > + > + list_for_each_entry(priv, &group_list, node) { > + casc_priv = cascade_timer; > + > + for (i = 0; i < array_size; i++) { > + unsigned long flags; > + > + spin_lock_irqsave(&priv->lock, flags); > + tmp = casc_priv->cascade_map & priv->idle; > + if (tmp == casc_priv->cascade_map) { > + num = casc_priv->timer_num; > + priv->timer[num].cascade_handle = casc_priv; > + > + /* set timer busy */ > + priv->idle &= ~casc_priv->cascade_map; > + spin_unlock_irqrestore(&priv->lock, flags); > + return &priv->timer[num]; > + } > + spin_unlock_irqrestore(&priv->lock, flags); > + casc_priv++; > + } > + } > + > + return NULL; > +} > + > +static int set_cascade_timer(struct group_priv *priv, u64 ticks, > + unsigned int num) > +{ > + struct cascade_priv *casc_priv; > + u32 tmp; > + u32 tmp_ticks; > + u32 rem_ticks; > + > + /* set group tcr reg for cascade */ > + casc_priv = priv->timer[num].cascade_handle; > + if (!casc_priv) > + return -EINVAL; > + > + tmp = casc_priv->tcr_value | > + (casc_priv->tcr_value << MPIC_TIMER_TCR_ROVR_OFFSET); > + setbits32(priv->group_tcr, tmp); > + > + tmp_ticks = div_u64_rem(ticks, MAX_TIME_CASCADE, &rem_ticks); > + > + out_be32(&priv->regs[num].gtccr, 0); > + out_be32(&priv->regs[num].gtbcr, tmp_ticks | MPIC_TIMER_STOP); > + > + out_be32(&priv->regs[num - 1].gtccr, 0); > + out_be32(&priv->regs[num - 1].gtbcr, rem_ticks); > + > + return 0; > +} > + > +struct mpic_timer *get_cascade_timer(u64 ticks) > +{ should this be static? > + struct group_priv *priv = NULL; > + struct mpic_timer *allocated_timer = NULL; > + > + /* Two cascade timers: Support the maximum time */ > + const u64 max_ticks = (u64)MAX_TIME * (u64)MAX_TIME_CASCADE; > + int ret; > + > + if (ticks > max_ticks) > + return NULL; > + > + /* detect idle timer */ > + allocated_timer = detect_idle_cascade_timer(); > + if (!allocated_timer) > + return NULL; > + > + priv = container_of(allocated_timer, struct group_priv, > + timer[allocated_timer->num]); > + > + /* set ticks to timer */ > + ret = set_cascade_timer(priv, ticks, allocated_timer->num); > + if (ret < 0) > + return NULL; > + > + return allocated_timer; > +} > + > +struct mpic_timer *get_timer(u64 ticks) > +{ should this be static? > + struct group_priv *priv; > + unsigned int num; > + unsigned int i; > + > + list_for_each_entry(priv, &group_list, node) { > + for (i = 0; i < MPIC_ALL_TIMER; i++) { > + unsigned long flags; > + > + /* one timer: Reverse allocation */ > + num = MPIC_ALL_TIMER - 1 - i; > + > + spin_lock_irqsave(&priv->lock, flags); > + if (priv->idle & (1 << i)) { > + /* set timer busy */ > + priv->idle &= ~(1 << i); > + /* set ticks & stop timer */ > + out_be32(&priv->regs[num].gtbcr, > + ticks | MPIC_TIMER_STOP); > + out_be32(&priv->regs[num].gtccr, 0); > + > + spin_unlock_irqrestore(&priv->lock, flags); > + priv->timer[num].cascade_handle = NULL; > + > + return &priv->timer[num]; > + } > + spin_unlock_irqrestore(&priv->lock, flags); > + } > + } > + > + return NULL; > +} > + > +/** > + * mpic_request_timer - get a hardware timer > + * @fn: interrupt handler function > + * @dev: callback function of the data > + * @time: time for timer > + * > + * This executes the "request_irq", returning NULL > + * else "handle" on success. > + */ > +struct mpic_timer *mpic_request_timer(irq_handler_t fn, void *dev, > + const struct timeval *time) > +{ > + struct mpic_timer *allocated_timer = NULL; > + u64 ticks = 0; > + int ret = 0; > + > + if (list_empty(&group_list)) > + return NULL; > + > + ret = transform_time(time, MPIC_TIMER_TCR_CLKDIV_64, &ticks); > + if (ret < 0) > + return NULL; > + > + if (ticks > MAX_TIME) > + allocated_timer = get_cascade_timer(ticks); > + else > + allocated_timer = get_timer(ticks); > + > + if (!allocated_timer) > + return NULL; > + > + ret = request_irq(allocated_timer->irq, fn, IRQF_TRIGGER_LOW, > + "mpic-global-timer", dev); > + if (ret) > + return NULL; > + > + allocated_timer->dev = dev; > + > + return allocated_timer; > +} > +EXPORT_SYMBOL(mpic_request_timer); > + > +/** > + * mpic_start_timer - start hardware timer > + * @handle: the timer to be started. > + * > + * It will do ->fn(->dev) callback from the hardware interrupt at > + * the ->timeval point in the future. > + */ > +void mpic_start_timer(struct mpic_timer *handle) > +{ > + struct group_priv *priv = container_of(handle, struct group_priv, > + timer[handle->num]); > + > + clrbits32(&priv->regs[handle->num].gtbcr, MPIC_TIMER_STOP); > +} > +EXPORT_SYMBOL(mpic_start_timer); > + > +/** > + * mpic_stop_timer - stop hardware timer > + * @handle: the timer to be stoped > + * > + * The timer periodically generates an interrupt. Unless user stops the > timer. > + */ > +void mpic_stop_timer(struct mpic_timer *handle) > +{ > + struct group_priv *priv = container_of(handle, struct group_priv, > + timer[handle->num]); > + > + setbits32(&priv->regs[handle->num].gtbcr, MPIC_TIMER_STOP); > +} > +EXPORT_SYMBOL(mpic_stop_timer); > + > +/** > + * mpic_free_timer - free hardware timer > + * @handle: the timer to be removed. > + * > + * Free the timer. > + * > + * Note: can not be used in interrupt context. > + */ > +void mpic_free_timer(struct mpic_timer *handle) > +{ > + struct group_priv *priv = container_of(handle, struct group_priv, > + timer[handle->num]); > + > + struct cascade_priv *casc_priv = NULL; > + unsigned long flags; > + > + mpic_stop_timer(handle); > + > + casc_priv = priv->timer[handle->num].cascade_handle; > + > + free_irq(priv->timer[handle->num].irq, priv->timer[handle->num].dev); > + > + spin_lock_irqsave(&priv->lock, flags); > + if (casc_priv) { > + u32 tmp; > + tmp = casc_priv->tcr_value | (casc_priv->tcr_value << > + MPIC_TIMER_TCR_ROVR_OFFSET); > + clrbits32(priv->group_tcr, tmp); > + priv->idle |= casc_priv->cascade_map; > + priv->timer[handle->num].cascade_handle = NULL; > + } else { > + priv->idle |= 1 << (MPIC_ALL_TIMER - 1 - handle->num); > + } > + spin_unlock_irqrestore(&priv->lock, flags); > +} > +EXPORT_SYMBOL(mpic_free_timer); > + > +static void group_init(struct device_node *np) > +{ > + struct group_priv *priv = NULL; > + const u32 all_timer[] = { 0, MPIC_ALL_TIMER }; > + const u32 *p; > + u32 offset; > + u32 count; > + > + unsigned int i = 0; > + unsigned int j = 0; > + unsigned int irq_index = 0; > + int irq = 0; > + int len = 0; > + > + priv = kzalloc(sizeof(struct group_priv), GFP_KERNEL); > + if (!priv) { > + pr_err("%s: cannot allocate memory for group.\n", > + np->full_name); > + return; > + } > + > + priv->regs = of_iomap(np, 0); > + if (!priv->regs) { > + pr_err("%s: cannot ioremap register address.\n", > + np->full_name); > + goto out; > + } > + > + priv->group_tcr = of_iomap(np, 1); > + if (!priv->group_tcr) { > + pr_err("%s: cannot ioremap tcr address.\n", np->full_name); > + goto out; > + } > + > + /* Get irq numbers form dts */ > + p = of_get_property(np, "fsl,available-ranges", &len); > + if (p && len % (2 * sizeof(u32)) != 0) { > + pr_err("%s: malformed fsl,available-ranges property.\n", > + np->full_name); > + goto out; > + } > + > + if (!p) { > + p = all_timer; > + len = sizeof(all_timer); > + } > + > + len /= 2 * sizeof(u32); > + > + for (i = 0; i < len; i++) { > + offset = p[i * 2]; > + count = p[i * 2 + 1]; > + for (j = 0; j < count; j++) { > + irq = irq_of_parse_and_map(np, irq_index); > + if (!irq) > + break; > + > + /* Set timer idle */ > + priv->idle |= TIMER_OFFSET((offset + j)); > + priv->timer[offset + j].irq = irq; > + priv->timer[offset + j].num = offset + j; > + irq_index++; > + } > + } > + > + /* Init lock */ > + spin_lock_init(&priv->lock); > + > + /* Init timer hardware */ > + setbits32(priv->group_tcr, MPIC_TIMER_TCR_CLKDIV_64); > + > + list_add_tail(&priv->node, &group_list); > + > + return; > +out: > + if (priv->group_tcr) > + iounmap(priv->group_tcr); > + > + if (priv->regs) > + iounmap(priv->regs); > + > + kfree(priv); > +} > + > +static int __init mpic_timer_init(void) > +{ > + struct device_node *np = NULL; > + > + ccbfreq = fsl_get_sys_freq(); > + if (ccbfreq == 0) { > + pr_err("mpic_timer: No bus frequency " > + "in device tree.\n"); > + return -ENODEV; > + } > + > + max_value = div_u64(ULLONG_MAX, ccbfreq); > + > + for_each_compatible_node(np, NULL, "fsl,mpic-global-timer") > + group_init(np); > + > + if (list_empty(&group_list)) > + return -ENODEV; > + > + return 0; > +} > +arch_initcall(mpic_timer_init); > -- > 1.7.5.1 > > > _______________________________________________ > Linuxppc-dev mailing list > Linuxppc-dev@lists.ozlabs.org > https://lists.ozlabs.org/listinfo/linuxppc-dev _______________________________________________ Linuxppc-dev mailing list Linuxppc-dev@lists.ozlabs.org https://lists.ozlabs.org/listinfo/linuxppc-dev