469 lines
10 KiB
C
469 lines
10 KiB
C
|
/*
|
|||
|
* Copyright (c) 2008-2010 Freescale Semiconductor, Inc. All rights reserved.
|
|||
|
* Dave Liu <daveliu@freescale.com>
|
|||
|
* copy from the 83xx GTM driver and modify for MPIC global timer,
|
|||
|
* implement the global timer 0 function.
|
|||
|
*
|
|||
|
* 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/sched.h>
|
|||
|
#include <linux/errno.h>
|
|||
|
#include <linux/module.h>
|
|||
|
#include <linux/mm.h>
|
|||
|
#include <linux/delay.h>
|
|||
|
#include <linux/fs.h>
|
|||
|
#include <linux/string.h>
|
|||
|
#include <linux/interrupt.h>
|
|||
|
#include <linux/sysfs.h>
|
|||
|
#include <linux/of_platform.h>
|
|||
|
|
|||
|
#include <linux/io.h>
|
|||
|
#include <linux/irq.h>
|
|||
|
|
|||
|
#include <sysdev/fsl_soc.h>
|
|||
|
#include "log.h"
|
|||
|
#include "mpic_timer.h"
|
|||
|
|
|||
|
#define MPIC_TIMER_TCR_OFFSET 0x200
|
|||
|
#define MPIC_TIMER_TCR_CLKDIV_64 0x00000300
|
|||
|
#define MPIC_TIMER_STOP 0x80000000
|
|||
|
|
|||
|
struct mpic_tm_regs
|
|||
|
{
|
|||
|
u32 gtccr;
|
|||
|
u32 res0[3];
|
|||
|
u32 gtbcr;
|
|||
|
u32 res1[3];
|
|||
|
u32 gtvpr;
|
|||
|
u32 res2[3];
|
|||
|
u32 gtdr;
|
|||
|
u32 res3[3];
|
|||
|
};
|
|||
|
|
|||
|
struct mpic_tm_priv
|
|||
|
{
|
|||
|
struct mpic_tm_regs __iomem* regs;
|
|||
|
int irq;
|
|||
|
int ticks_per_sec;
|
|||
|
int ticks_per_msec;
|
|||
|
spinlock_t lock;
|
|||
|
};
|
|||
|
|
|||
|
struct mpic_type
|
|||
|
{
|
|||
|
int has_tcr;
|
|||
|
};
|
|||
|
|
|||
|
struct irq_hook
|
|||
|
{
|
|||
|
PMIPC_TIMER_RUNTIME hook;
|
|||
|
void* data;
|
|||
|
};
|
|||
|
|
|||
|
static unsigned int g_CntPerMs = 0;
|
|||
|
static unsigned int g_CurrentVal = 0;
|
|||
|
|
|||
|
static struct irq_hook irq_hooks[32];
|
|||
|
|
|||
|
static volatile uint32_t poll_hook_bitmap = 0xffffffff;
|
|||
|
|
|||
|
|
|||
|
int register_mpic_timer_irq(PMIPC_TIMER_RUNTIME phooks, void* data)
|
|||
|
{
|
|||
|
int ret = -ENOSPC;
|
|||
|
int idx = 0;
|
|||
|
|
|||
|
if((idx = fls(poll_hook_bitmap) - 1) >= 0)
|
|||
|
{
|
|||
|
poll_hook_bitmap &= ~(1 << idx);
|
|||
|
irq_hooks[idx].hook = phooks;
|
|||
|
irq_hooks[idx].data = data;
|
|||
|
ret = idx;
|
|||
|
printk("irq_poll_register %x\n", ret);
|
|||
|
}
|
|||
|
|
|||
|
return ret;
|
|||
|
}
|
|||
|
|
|||
|
EXPORT_SYMBOL(register_mpic_timer_irq);
|
|||
|
|
|||
|
int unregister_mpic_timer_irq(int idx)
|
|||
|
{
|
|||
|
int ret = -EINVAL;
|
|||
|
printk("irq_poll_unregister %x\n", idx);
|
|||
|
|
|||
|
if(!(poll_hook_bitmap & (1 << idx)))
|
|||
|
{
|
|||
|
poll_hook_bitmap |= (1 << idx);
|
|||
|
}
|
|||
|
|
|||
|
return ret;
|
|||
|
}
|
|||
|
EXPORT_SYMBOL(unregister_mpic_timer_irq);
|
|||
|
|
|||
|
static unsigned int g_TimerCnt = 0;
|
|||
|
static struct timeval g_Clock;
|
|||
|
|
|||
|
static irqreturn_t mpic_tm_isr(int irq, void* dev_id)
|
|||
|
{
|
|||
|
struct mpic_tm_priv* priv = dev_id;
|
|||
|
uint32_t bits;
|
|||
|
int i;
|
|||
|
unsigned int newTimerCnt;
|
|||
|
struct timeval ts;
|
|||
|
unsigned int rlt, need;
|
|||
|
|
|||
|
/*
|
|||
|
unsigned long flags;
|
|||
|
unsigned long temp;
|
|||
|
spin_lock_irqsave(&priv->lock, flags);
|
|||
|
temp = in_be32(&priv->regs->gtbcr);
|
|||
|
temp |= MPIC_TIMER_STOP; // counting inhibited
|
|||
|
out_be32(&priv->regs->gtbcr, temp);
|
|||
|
out_be32(&priv->regs->gtbcr, priv->ticks_per_msec);
|
|||
|
spin_unlock_irqrestore(&priv->lock, flags);
|
|||
|
//spin_lock_irqsave(&priv->lock, flags);
|
|||
|
//out_be32(&priv->regs->gtbcr, newTimerCnt | MPIC_TIMER_STOP);
|
|||
|
//spin_unlock_irqrestore(&priv->lock, flags);
|
|||
|
*/
|
|||
|
|
|||
|
bits = ~poll_hook_bitmap;
|
|||
|
|
|||
|
while((i = fls(bits) - 1) >= 0)
|
|||
|
{
|
|||
|
bits &= ~(1 << i);
|
|||
|
irq_hooks[i].hook(irq_hooks[i].data);
|
|||
|
}
|
|||
|
|
|||
|
do_gettimeofday(&ts);
|
|||
|
|
|||
|
// <20><><EFBFBD><EFBFBD>ʱ<EFBFBD><CAB1><EFBFBD><EFBFBD><EEA3AC>λΪ US
|
|||
|
if(ts.tv_usec < g_Clock.tv_usec)
|
|||
|
{
|
|||
|
rlt = (ts.tv_sec - g_Clock.tv_sec - 1) * 1000000 +
|
|||
|
((1000000 + ts.tv_usec) - g_Clock.tv_usec);
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
rlt = (ts.tv_sec - g_Clock.tv_sec) * 1000000 +
|
|||
|
(ts.tv_usec - g_Clock.tv_usec);
|
|||
|
}
|
|||
|
|
|||
|
#if 1
|
|||
|
|
|||
|
// <20><>ǰ<EFBFBD><C7B0>ʱ<D7BC><CAB1><EFBFBD><EFBFBD>
|
|||
|
need = g_TimerCnt * 1000;
|
|||
|
|
|||
|
// У<><D0A3>ʱ<EFBFBD><CAB1>
|
|||
|
if(need > rlt)
|
|||
|
{
|
|||
|
newTimerCnt = g_CntPerMs + (need - rlt) * (g_CntPerMs / 1000);
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
if((rlt - need) > 1000)
|
|||
|
{
|
|||
|
newTimerCnt = g_CntPerMs / 2;
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
newTimerCnt = g_CntPerMs - (rlt - need) * (g_CntPerMs / 1000);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
g_CurrentVal = newTimerCnt;
|
|||
|
|
|||
|
#else
|
|||
|
|
|||
|
// <20><>ǰ<EFBFBD><C7B0>ʱ<D7BC><CAB1><EFBFBD><EFBFBD>
|
|||
|
need = g_TimerCnt * 100;
|
|||
|
|
|||
|
// У<><D0A3>ʱ<EFBFBD><CAB1>
|
|||
|
if(need > rlt)
|
|||
|
{
|
|||
|
newTimerCnt = g_CntPerMs + (need - rlt) * (g_CntPerMs / 100);
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
if((rlt - need) > 100)
|
|||
|
{
|
|||
|
newTimerCnt = g_CntPerMs / 2;
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
newTimerCnt = g_CntPerMs - (rlt - need) * (g_CntPerMs / 100);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
g_CurrentVal = newTimerCnt;
|
|||
|
|
|||
|
#endif
|
|||
|
|
|||
|
// <20><><EFBFBD>¶<EFBFBD>ʱ<EFBFBD><CAB1>
|
|||
|
out_be32(&priv->regs->gtbcr, newTimerCnt | MPIC_TIMER_STOP);
|
|||
|
out_be32(&priv->regs->gtbcr, newTimerCnt & 0x7FFFFFFF);
|
|||
|
|
|||
|
g_TimerCnt++;
|
|||
|
/*
|
|||
|
if(g_TimerCnt % 1000 == 0)
|
|||
|
{
|
|||
|
|
|||
|
//printk("[T]%d.%d - %d.%d\n", ts.tv_sec, ts.tv_usec, g_Clock.tv_sec, g_Clock.tv_usec);
|
|||
|
//printk("[T]need = %d, rlt = %d, newTimerCnt = %d\n", need, rlt, newTimerCnt);
|
|||
|
printk("[%d]Time:%d.%d\n", g_TimerCnt, ts.tv_sec, ts.tv_usec / 1000);
|
|||
|
}
|
|||
|
*/
|
|||
|
return IRQ_HANDLED;
|
|||
|
}
|
|||
|
|
|||
|
static ssize_t mpic_tm_timeout_store(struct device* dev,
|
|||
|
struct device_attribute* attr,
|
|||
|
const char* buf, size_t count)
|
|||
|
{
|
|||
|
struct mpic_tm_priv* priv = dev_get_drvdata(dev);
|
|||
|
unsigned long interval = simple_strtoul(buf, NULL, 0);
|
|||
|
unsigned long temp;
|
|||
|
|
|||
|
if(interval > 0x7fffffff)
|
|||
|
{
|
|||
|
dev_dbg(dev, "mpic_tm: interval %lu (in s) too long\n", interval);
|
|||
|
return -EINVAL;
|
|||
|
}
|
|||
|
|
|||
|
temp = interval;
|
|||
|
interval *= priv->ticks_per_sec;
|
|||
|
|
|||
|
if(interval > 0x7fffffff || (interval / priv->ticks_per_sec) != temp)
|
|||
|
{
|
|||
|
dev_dbg(dev, "mpic_tm: interval %lu (in ticks) too long\n",
|
|||
|
interval);
|
|||
|
return -EINVAL;
|
|||
|
}
|
|||
|
|
|||
|
spin_lock_irq(&priv->lock);
|
|||
|
|
|||
|
/* stop timer 0 */
|
|||
|
temp = in_be32(&priv->regs->gtbcr);
|
|||
|
temp |= MPIC_TIMER_STOP; /* counting inhibited */
|
|||
|
out_be32(&priv->regs->gtbcr, temp);
|
|||
|
|
|||
|
if(interval != 0)
|
|||
|
{
|
|||
|
/* start timer */
|
|||
|
out_be32(&priv->regs->gtbcr, interval | MPIC_TIMER_STOP);
|
|||
|
out_be32(&priv->regs->gtbcr, interval);
|
|||
|
}
|
|||
|
|
|||
|
spin_unlock_irq(&priv->lock);
|
|||
|
return count;
|
|||
|
}
|
|||
|
|
|||
|
struct mpic_tm_priv* g_tm_priv = NULL;
|
|||
|
|
|||
|
int mpic_tm_get_current(void)
|
|||
|
{
|
|||
|
return in_be32(&g_tm_priv->regs->gtccr);
|
|||
|
}
|
|||
|
EXPORT_SYMBOL(mpic_tm_get_current);
|
|||
|
|
|||
|
|
|||
|
|
|||
|
static ssize_t mpic_tm_timeout_show(struct device* dev,
|
|||
|
struct device_attribute* attr,
|
|||
|
char* buf)
|
|||
|
{
|
|||
|
struct mpic_tm_priv* priv = dev_get_drvdata(dev);
|
|||
|
int timeout = 0;
|
|||
|
|
|||
|
spin_lock_irq(&priv->lock);
|
|||
|
|
|||
|
if(!(in_be32(&priv->regs->gtbcr) & MPIC_TIMER_STOP))
|
|||
|
{
|
|||
|
timeout = in_be32(&priv->regs->gtccr);
|
|||
|
timeout += priv->ticks_per_sec - 1;
|
|||
|
timeout /= priv->ticks_per_sec;
|
|||
|
}
|
|||
|
|
|||
|
spin_unlock_irq(&priv->lock);
|
|||
|
return sprintf(buf, "TimeOut:0x%08X - TimerCnt:0x%08X\n", timeout,
|
|||
|
g_CurrentVal);
|
|||
|
}
|
|||
|
|
|||
|
static DEVICE_ATTR(timeout, 0660, mpic_tm_timeout_show, mpic_tm_timeout_store);
|
|||
|
|
|||
|
static int mpic_tm_probe(struct of_device* dev,
|
|||
|
const struct of_device_id* match)
|
|||
|
{
|
|||
|
struct device_node* np = dev->dev.of_node;
|
|||
|
struct resource res;
|
|||
|
struct mpic_tm_priv* priv;
|
|||
|
struct mpic_type* type = match->data;
|
|||
|
int has_tcr = type->has_tcr;
|
|||
|
u32 busfreq = fsl_get_sys_freq();
|
|||
|
int ret = 0;
|
|||
|
|
|||
|
if(busfreq == 0)
|
|||
|
{
|
|||
|
dev_err(&dev->dev, "mpic_tm: No bus frequency in device tree.\n");
|
|||
|
return -ENODEV;
|
|||
|
}
|
|||
|
|
|||
|
priv = kmalloc(sizeof(struct mpic_tm_priv), GFP_KERNEL);
|
|||
|
|
|||
|
if(!priv)
|
|||
|
{
|
|||
|
return -ENOMEM;
|
|||
|
}
|
|||
|
|
|||
|
spin_lock_init(&priv->lock);
|
|||
|
dev_set_drvdata(&dev->dev, priv);
|
|||
|
|
|||
|
ret = of_address_to_resource(np, 0, &res);
|
|||
|
|
|||
|
if(ret)
|
|||
|
{
|
|||
|
goto out;
|
|||
|
}
|
|||
|
|
|||
|
priv->irq = irq_of_parse_and_map(np, 0);
|
|||
|
|
|||
|
if(priv->irq == NO_IRQ)
|
|||
|
{
|
|||
|
dev_err(&dev->dev, "MPIC global timer0 exists in device tree "
|
|||
|
"without an IRQ.\n");
|
|||
|
ret = -ENODEV;
|
|||
|
goto out;
|
|||
|
}
|
|||
|
|
|||
|
ret = request_irq(priv->irq, mpic_tm_isr, 0, "mpic timer 0", priv);
|
|||
|
|
|||
|
if(ret)
|
|||
|
{
|
|||
|
goto out;
|
|||
|
}
|
|||
|
|
|||
|
priv->regs = ioremap(res.start, res.end - res.start + 1);
|
|||
|
|
|||
|
if(!priv->regs)
|
|||
|
{
|
|||
|
ret = -ENOMEM;
|
|||
|
goto out;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* MPIC implementation from Freescale has the TCR register,
|
|||
|
* the MPIC_TIMER_TCR_OFFSET is 0x200 from global timer base
|
|||
|
* the default clock source to the MPIC timer 0 is CCB freq / 8.
|
|||
|
* to extend the timer period, we divide the timer clock source
|
|||
|
* as CCB freq / 64, so the max timer period is 336 seconds
|
|||
|
* when the CCB frequence is 400MHz.
|
|||
|
*/
|
|||
|
if(!has_tcr)
|
|||
|
{
|
|||
|
priv->ticks_per_sec = busfreq / 8;
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
u32 __iomem* tcr;
|
|||
|
tcr = (u32 __iomem*)((u32)priv->regs + MPIC_TIMER_TCR_OFFSET);
|
|||
|
out_be32(tcr, in_be32(tcr) | MPIC_TIMER_TCR_CLKDIV_64);
|
|||
|
priv->ticks_per_sec = busfreq / 64;
|
|||
|
}
|
|||
|
|
|||
|
g_CntPerMs = priv->ticks_per_sec / 1000;
|
|||
|
|
|||
|
priv->ticks_per_msec = g_CntPerMs;
|
|||
|
|
|||
|
LOG_EX(LOG_Info, "freq = 0x%08X, msec = 0x%08X\n",
|
|||
|
busfreq, priv->ticks_per_msec);
|
|||
|
|
|||
|
ret = device_create_file(&dev->dev, &dev_attr_timeout);
|
|||
|
|
|||
|
if(ret)
|
|||
|
{
|
|||
|
goto out;
|
|||
|
}
|
|||
|
|
|||
|
/* start timer now */
|
|||
|
out_be32(&priv->regs->gtbcr, priv->ticks_per_msec | MPIC_TIMER_STOP);
|
|||
|
out_be32(&priv->regs->gtbcr, priv->ticks_per_msec);
|
|||
|
|
|||
|
do_gettimeofday(&g_Clock);
|
|||
|
|
|||
|
g_tm_priv = priv;
|
|||
|
|
|||
|
return 0;
|
|||
|
|
|||
|
out:
|
|||
|
|
|||
|
kfree(priv);
|
|||
|
return ret;
|
|||
|
}
|
|||
|
|
|||
|
static int mpic_tm_remove(struct of_device* dev)
|
|||
|
{
|
|||
|
struct mpic_tm_priv* priv = dev_get_drvdata(&dev->dev);
|
|||
|
|
|||
|
device_remove_file(&dev->dev, &dev_attr_timeout);
|
|||
|
free_irq(priv->irq, priv);
|
|||
|
iounmap(priv->regs);
|
|||
|
|
|||
|
dev_set_drvdata(&dev->dev, NULL);
|
|||
|
kfree(priv);
|
|||
|
return 0;
|
|||
|
}
|
|||
|
|
|||
|
static struct mpic_type mpic_types[] =
|
|||
|
{
|
|||
|
{
|
|||
|
.has_tcr = 0,
|
|||
|
},
|
|||
|
{
|
|||
|
.has_tcr = 1,
|
|||
|
}
|
|||
|
};
|
|||
|
|
|||
|
static struct of_device_id mpic_tm_match[] =
|
|||
|
{
|
|||
|
{
|
|||
|
.compatible = "fsl,mpic-global-timer",
|
|||
|
.data = &mpic_types[1],
|
|||
|
},
|
|||
|
{},
|
|||
|
};
|
|||
|
|
|||
|
static struct of_platform_driver mpic_tm_driver =
|
|||
|
{
|
|||
|
.driver = {
|
|||
|
.name = "mpic-global-timer",
|
|||
|
.owner = THIS_MODULE,
|
|||
|
.of_match_table = mpic_tm_match,
|
|||
|
},
|
|||
|
.probe = mpic_tm_probe,
|
|||
|
.remove = mpic_tm_remove,
|
|||
|
};
|
|||
|
|
|||
|
static int __init mpic_tm_init(void)
|
|||
|
{
|
|||
|
printk("Gianfar Build:%s(%s)\n", __DATE__, __TIME__);
|
|||
|
return of_register_platform_driver(&mpic_tm_driver);
|
|||
|
}
|
|||
|
|
|||
|
static void __exit mpic_tm_exit(void)
|
|||
|
{
|
|||
|
of_unregister_platform_driver(&mpic_tm_driver);
|
|||
|
}
|
|||
|
|
|||
|
module_init(mpic_tm_init);
|
|||
|
module_exit(mpic_tm_exit);
|
|||
|
|
|||
|
MODULE_AUTHOR("Freescale Semiconductor, Inc");
|
|||
|
MODULE_DESCRIPTION("MPIC Timer Driver");
|
|||
|
MODULE_LICENSE("GPL");
|
|||
|
|