/* * Copyright (c) 2008-2010 Freescale Semiconductor, Inc. All rights reserved. * Dave Liu * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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); // 计算时间差,单位为 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 // 当前标准时间差 need = g_TimerCnt * 1000; // 校正时间 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 // 当前标准时间差 need = g_TimerCnt * 100; // 校正时间 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 // 更新定时器 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");