[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