[PATCH] powerpc/fsl: mpic timer driver
Kumar Gala
galak at kernel.crashing.org
Fri Jul 27 23:13:52 EST 2012
On Jul 27, 2012, at 1:20 AM, <Dongsheng.wang at freescale.com> <Dongsheng.wang at freescale.com> wrote:
> From: Wang Dongsheng <Dongsheng.Wang at 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.Wang at freescale.com>
> Signed-off-by: Li Yang <leoli at 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 at lists.ozlabs.org
> https://lists.ozlabs.org/listinfo/linuxppc-dev
More information about the Linuxppc-dev
mailing list