diff mbox

powerpc/fsl: mpic timer driver

Message ID 1343370058-2983-1-git-send-email-Dongsheng.wang@freescale.com (mailing list archive)
State Superseded, archived
Headers show

Commit Message

Dongsheng Wang July 27, 2012, 6:20 a.m. UTC
From: Wang Dongsheng <Dongsheng.Wang@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@freescale.com>
Signed-off-by: Li Yang <leoli@freescale.com>
---
 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

Comments

Kumar Gala July 27, 2012, 1:13 p.m. UTC | #1
On Jul 27, 2012, at 1:20 AM, <Dongsheng.wang@freescale.com> <Dongsheng.wang@freescale.com> wrote:

> From: Wang Dongsheng <Dongsheng.Wang@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@freescale.com>
> Signed-off-by: Li Yang <leoli@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
Wang Dongsheng-B40534 July 31, 2012, 7:58 a.m. UTC | #2
> -----Original Message-----
> From: Kumar Gala [mailto:galak@kernel.crashing.org]
> Sent: Friday, July 27, 2012 9:14 PM
> To: Wang Dongsheng-B40534
> Cc: benh@kernel.crashing.org; paulus@samba.org; Wood Scott-B07421;
> linuxppc-dev@lists.ozlabs.org
> Subject: Re: [PATCH] powerpc/fsl: mpic timer driver
> 
> 
> On Jul 27, 2012, at 1:20 AM, <Dongsheng.wang@freescale.com>
> <Dongsheng.wang@freescale.com> wrote:
> 
> > From: Wang Dongsheng <Dongsheng.Wang@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@freescale.com>
> > Signed-off-by: Li Yang <leoli@freescale.com>
> 
> How much of this is FSL specific vs openpic?  OpenPIC spec's timer
> support (only a single group).
> 
[Wang Dongsheng] Yes, OpenPIC only a single group timer.
FSL: add more register, features and group.
This patch only to support FSL chip.
"mpic_timer.c" -> "fsl_mpic_timer.c"
I will modify the description of the patch. how about?

 > > +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?
> 
[Wang Dongsheng] ok.

> > _______________________________________________
> > Linuxppc-dev mailing list
> > Linuxppc-dev@lists.ozlabs.org
> > https://lists.ozlabs.org/listinfo/linuxppc-dev
>
Kumar Gala July 31, 2012, 2:31 p.m. UTC | #3
On Jul 31, 2012, at 2:58 AM, Wang Dongsheng-B40534 wrote:

> 
> 
>> -----Original Message-----
>> From: Kumar Gala [mailto:galak@kernel.crashing.org]
>> Sent: Friday, July 27, 2012 9:14 PM
>> To: Wang Dongsheng-B40534
>> Cc: benh@kernel.crashing.org; paulus@samba.org; Wood Scott-B07421;
>> linuxppc-dev@lists.ozlabs.org
>> Subject: Re: [PATCH] powerpc/fsl: mpic timer driver
>> 
>> 
>> On Jul 27, 2012, at 1:20 AM, <Dongsheng.wang@freescale.com>
>> <Dongsheng.wang@freescale.com> wrote:
>> 
>>> From: Wang Dongsheng <Dongsheng.Wang@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@freescale.com>
>>> Signed-off-by: Li Yang <leoli@freescale.com>
>> 
>> How much of this is FSL specific vs openpic?  OpenPIC spec's timer
>> support (only a single group).
>> 
> [Wang Dongsheng] Yes, OpenPIC only a single group timer.
> FSL: add more register, features and group.
> This patch only to support FSL chip.
> "mpic_timer.c" -> "fsl_mpic_timer.c"
> I will modify the description of the patch. how about?

I'd rather we support both, can we not use the MPIC_FSL flag to deal with FSL specific behavior?

- k
Scott Wood July 31, 2012, 4:26 p.m. UTC | #4
On 07/31/2012 09:31 AM, Kumar Gala wrote:
> 
> On Jul 31, 2012, at 2:58 AM, Wang Dongsheng-B40534 wrote:
> 
>>
>>
>>> -----Original Message-----
>>> From: Kumar Gala [mailto:galak@kernel.crashing.org]
>>> Sent: Friday, July 27, 2012 9:14 PM
>>> To: Wang Dongsheng-B40534
>>> Cc: benh@kernel.crashing.org; paulus@samba.org; Wood Scott-B07421;
>>> linuxppc-dev@lists.ozlabs.org
>>> Subject: Re: [PATCH] powerpc/fsl: mpic timer driver
>>>
>>>
>>> On Jul 27, 2012, at 1:20 AM, <Dongsheng.wang@freescale.com>
>>> <Dongsheng.wang@freescale.com> wrote:
>>>
>>>> From: Wang Dongsheng <Dongsheng.Wang@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@freescale.com>
>>>> Signed-off-by: Li Yang <leoli@freescale.com>
>>>
>>> How much of this is FSL specific vs openpic?  OpenPIC spec's timer
>>> support (only a single group).
>>>
>> [Wang Dongsheng] Yes, OpenPIC only a single group timer.
>> FSL: add more register, features and group.
>> This patch only to support FSL chip.
>> "mpic_timer.c" -> "fsl_mpic_timer.c"
>> I will modify the description of the patch. how about?
> 
> I'd rather we support both, can we not use the MPIC_FSL flag to deal with FSL specific behavior?

The device this driver binds against is "fsl,mpic-global-timer".  We
don't have a binding for ordinary OpenPIC timers, and inferring them
from the basic OpenPIC node will cause AMP headaches.

Let someone who cares about ordinary OpenPIC drivers add support. :-)

-Scott
Wang Dongsheng-B40534 Aug. 1, 2012, 8:19 a.m. UTC | #5
> -----Original Message-----
> From: Kumar Gala [mailto:galak@kernel.crashing.org]
> Sent: Tuesday, July 31, 2012 10:31 PM
> To: Wang Dongsheng-B40534
> Cc: benh@kernel.crashing.org; paulus@samba.org; Wood Scott-B07421;
> linuxppc-dev@lists.ozlabs.org
> Subject: Re: [PATCH] powerpc/fsl: mpic timer driver
> 
> 
> On Jul 31, 2012, at 2:58 AM, Wang Dongsheng-B40534 wrote:
> 
> >
> >
> >> -----Original Message-----
> >> From: Kumar Gala [mailto:galak@kernel.crashing.org]
> >> Sent: Friday, July 27, 2012 9:14 PM
> >> To: Wang Dongsheng-B40534
> >> Cc: benh@kernel.crashing.org; paulus@samba.org; Wood Scott-B07421;
> >> linuxppc-dev@lists.ozlabs.org
> >> Subject: Re: [PATCH] powerpc/fsl: mpic timer driver
> >>
> >>
> >> On Jul 27, 2012, at 1:20 AM, <Dongsheng.wang@freescale.com>
> >> <Dongsheng.wang@freescale.com> wrote:
> >>
> >>> From: Wang Dongsheng <Dongsheng.Wang@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@freescale.com>
> >>> Signed-off-by: Li Yang <leoli@freescale.com>
> >>
> >> How much of this is FSL specific vs openpic?  OpenPIC spec's timer
> >> support (only a single group).
> >>
> > [Wang Dongsheng] Yes, OpenPIC only a single group timer.
> > FSL: add more register, features and group.
> > This patch only to support FSL chip.
> > "mpic_timer.c" -> "fsl_mpic_timer.c"
> > I will modify the description of the patch. how about?
> 
> I'd rather we support both, can we not use the MPIC_FSL flag to deal with
> FSL specific behavior?
> 
[Wang Dongsheng] This patch, most of them are done processing cascade mode.
Cascade is a FSL chip function. I think this patch is very targeted.
I think that the patch only supports FSL chip.

> - k
>
diff mbox

Patch

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)
+{
+	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)
+{
+	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)
+{
+	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);