/* * * Copyright (c) 2012 Loongson Co., Ltd. * * GSC3280 - Timer Driver * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include //#define CONFIG_GSC3280_TIMER_DEBUG #ifdef CONFIG_GSC3280_TIMER_DEBUG #define DBG(msg...) do { \ printk(KERN_INFO msg); \ } while (0) #else #define DBG(msg...) do { } while(0) #endif #define TIMER_LC 0x00 #define TIMER_CV 0x04 #define TIMER_CR 0x08 #define TIMER_EOI 0x0C #define TIMER_IS 0x10 typedef union { /** raw register data */ u32 w32; /** register bits */ struct { //0:disable, 1:enable unsigned enable:1; //0:one, 1:loop unsigned mode:1; //0:irq enable, 1:irq mask unsigned irq_mask:1; unsigned reserved3_31:29; } b; } timer_tcr_u; enum { TIMER0, TIMER1, TIMER2, TIMER3, GSC3280_H_TIMER_NR }; struct gsc3280_timer { bool in_use; void __iomem *base; unsigned long rate; struct gsc3280_hard_timer *h_timer; char *name; }; static struct gsc3280_timer gsc3280_timer_priv[GSC3280_H_TIMER_NR] = { {false, (__iomem void *)GSC3280_TIMER_BASEADDR + 0x00, 0, NULL, "Timer0"}, {false, (__iomem void *)GSC3280_TIMER_BASEADDR + 0x14, 0, NULL, "Timer1"}, {false, (__iomem void *)GSC3280_TIMER_BASEADDR + 0x28, 0, NULL, "Timer2"}, {false, (__iomem void *)GSC3280_TIMER_BASEADDR + 0x3C, 0, NULL, "Timer3"} }; static irqreturn_t gsc3280_timer_interrupt(int irq, void *_ptr) { irqreturn_t ret = IRQ_NONE; struct gsc3280_timer *timer = (struct gsc3280_timer *)_ptr; if (__raw_readl(timer->base + TIMER_IS) & 0x01) { //is real interrupt timer->h_timer->function(timer->h_timer->data); ret = IRQ_HANDLED; } //clr interrupt bit __raw_readl(timer->base + TIMER_EOI); return ret; } static void start_hard_timer(struct gsc3280_timer *timer) { unsigned long tlc = 0; timer_tcr_u tcr = {.w32 = 0}; if (timer->h_timer->value_type == 0) tlc = (timer->rate * timer->h_timer->expires) /1000; else tlc = timer->rate / timer->h_timer->bps - 1; tcr.w32 = __raw_readl(timer->base + TIMER_CR); if (timer->h_timer->type == LOOP) tcr.b.mode = 1; else tcr.b.mode = 0; __raw_writel(tcr.w32, timer->base + TIMER_CR); __raw_writel(tlc, timer->base + TIMER_LC); //Enable Timer Interupt tcr.w32 = __raw_readl(timer->base + TIMER_CR); tcr.b.irq_mask = 0; //not mask timer irq tcr.b.enable = 1; //enable timer __raw_writel(tcr.w32, timer->base + TIMER_CR); __raw_readl(timer->base + TIMER_EOI); timer->in_use = true; } static void stop_hard_timer(struct gsc3280_timer *timer) { timer_tcr_u tcr = {.w32 = 0}; tcr.w32 = __raw_readl(timer->base + TIMER_CR); tcr.b.irq_mask = 1; //mask timer irq tcr.b.enable = 0; //disable timer __raw_writel(tcr.w32, timer->base + TIMER_CR); timer->in_use = false; } int gsc3280_timer_start(struct gsc3280_hard_timer *h_timer) { int i =0; if ((h_timer->data == 0) || (h_timer->function == NULL)) { printk(KERN_ERR "GSC3280 Request Timer, But The Param Wrong!"); return -EINVAL; } for (i = 0; i < GSC3280_H_TIMER_NR; i++) { if (gsc3280_timer_priv[i].h_timer == h_timer) { if (!gsc3280_timer_priv[i].in_use) start_hard_timer(&gsc3280_timer_priv[i]); return 0; } } DBG("gsc3280_timer_start error!!!!\n"); return -EBUSY; } EXPORT_SYMBOL(gsc3280_timer_start); void gsc3280_timer_stop(struct gsc3280_hard_timer *h_timer) { int i= 0; for (i = 0; i < GSC3280_H_TIMER_NR; i++) { if (gsc3280_timer_priv[i].h_timer == h_timer) { if (gsc3280_timer_priv[i].in_use) { stop_hard_timer(&gsc3280_timer_priv[i]); break; } } } } EXPORT_SYMBOL(gsc3280_timer_stop); int gsc3280_mod_timer(struct gsc3280_hard_timer *h_timer) { int i = 0; for (i = 0; i < GSC3280_H_TIMER_NR; i++) { if (gsc3280_timer_priv[i].h_timer == h_timer) { if (gsc3280_timer_priv[i].in_use) { stop_hard_timer(&gsc3280_timer_priv[i]); start_hard_timer(&gsc3280_timer_priv[i]); } } } return 0; } EXPORT_SYMBOL(gsc3280_mod_timer); int gsc3280_request_hard_timer(struct gsc3280_hard_timer *h_timer) { int i = 0, ret = 0; for (i = 0; i < GSC3280_H_TIMER_NR; i++) { if (gsc3280_timer_priv[i].h_timer == NULL) { gsc3280_timer_priv[i].h_timer = h_timer; ret = request_irq(EXT_GSC3280_TIMER_IRQ, &gsc3280_timer_interrupt, IRQF_SHARED, gsc3280_timer_priv[i].name, &gsc3280_timer_priv[i]); if (ret) { printk(KERN_ERR "Start hard Timer request irq failed!\n"); return ret; } DBG("GSC3280 Request Timer Success."); return 0; } } DBG("GSC3280 Request Timer error!"); return -EBUSY; } EXPORT_SYMBOL(gsc3280_request_hard_timer); void gsc3280_free_hard_timer(struct gsc3280_hard_timer *h_timer) { int i = 0; for (i = 0; i < GSC3280_H_TIMER_NR; i++) { if (gsc3280_timer_priv[i].h_timer == h_timer) { if (gsc3280_timer_priv[i].in_use) gsc3280_timer_stop(h_timer); free_irq(EXT_GSC3280_TIMER_IRQ, &gsc3280_timer_priv[i]); gsc3280_timer_priv[i].h_timer = NULL; } } } EXPORT_SYMBOL(gsc3280_free_hard_timer); static int __init gsc3280_timer_init(void) { int i = 0; struct clk *clk = NULL; timer_tcr_u tcr = {.w32 = 0}; DBG("############"); DBG("gsc3280 timer init start\n"); //enable timer modules sysctl_mod_enable(SYSCTL_MOD_TIMER0); sysctl_mod_enable(SYSCTL_MOD_TIMER1); sysctl_mod_enable(SYSCTL_MOD_TIMER2); sysctl_mod_enable(SYSCTL_MOD_TIMER3); //get 4 timer rate, use this cal LC clk = clk_get(NULL, "timer0"); gsc3280_timer_priv[TIMER0].rate = clk_get_rate(clk); clk = clk_get(NULL, "timer1"); gsc3280_timer_priv[TIMER1].rate = clk_get_rate(clk); clk = clk_get(NULL, "timer2"); gsc3280_timer_priv[TIMER2].rate = clk_get_rate(clk); clk = clk_get(NULL, "timer3"); gsc3280_timer_priv[TIMER3].rate = clk_get_rate(clk); for (i = 0; i < GSC3280_H_TIMER_NR; i++) { if (gsc3280_timer_priv[i].rate == 0) { DBG("GSC3280 TIMER%d Get Rate Failed!", i); return -EAGAIN; } DBG("timer[%d] = %ld\n", i, gsc3280_timer_priv[i].rate); tcr.w32 = __raw_readl(gsc3280_timer_priv[i].base + TIMER_CR); tcr.b.enable = 0; //disable timer tcr.b.irq_mask = 1; //mask timer irq __raw_writel(tcr.w32, gsc3280_timer_priv[i].base + TIMER_CR); } printk(KERN_INFO "GSC3280 TIMER0~3 Enable Success!"); DBG("############"); return 0; } core_initcall(gsc3280_timer_init); #ifdef CONFIG_DEBUG_FS struct gsc3280_timer_debug { int timer_idx; unsigned int int_nr; struct timeval start; struct timeval end; struct gsc3280_hard_timer h_timer; }; unsigned int timer_lc[GSC3280_H_TIMER_NR] = { 1000, 1000, 1000, 1000 }; static struct gsc3280_timer_debug timer_debug[GSC3280_H_TIMER_NR]; static void timer_handle(unsigned long arg) { struct timeval cur_time; struct gsc3280_timer_debug *ptr = (struct gsc3280_timer_debug *)arg; ptr->int_nr++; do_gettimeofday(&cur_time); if ((cur_time.tv_sec - ptr->end.tv_sec) >= 1) { printk(KERN_ERR "Timer%d:start[%ld.%ld] --- current[%ld.%ld] interrupt_nr[%u]", ptr->timer_idx, ptr->start.tv_sec, ptr->start.tv_usec, cur_time.tv_sec, cur_time.tv_usec, ptr->int_nr); do_gettimeofday(&ptr->end); } return; } static int gsc3280_timer_debug_open(struct inode *inode, struct file *file) { file->private_data = inode->i_private; return 0; } static int gsc3280_timer_debug_write(struct file *file, const char __user *buf, size_t len, loff_t *p_pos) { int timer_idx = TIMER0; char buffer[64] = { 0 }; if (len > 32) { printk(KERN_ERR "input len in not correct! only can start stop"); return -1; } if (!strcmp("timer0", (char *)file->private_data)) timer_idx = TIMER0; else if (!strcmp("timer1", (char *)file->private_data)) timer_idx = TIMER1; else if (!strcmp("timer2", (char *)file->private_data)) timer_idx = TIMER2; else if (!strcmp("timer3", (char *)file->private_data)) timer_idx = TIMER3; else return -1; copy_from_user(buffer, buf, len); if (!strncmp(buffer, "start", 5)) { printk(KERN_ERR "\nstart timer%d", timer_idx); timer_debug[timer_idx].timer_idx = timer_idx; timer_debug[timer_idx].int_nr = 0; do_gettimeofday(&timer_debug[timer_idx].start); do_gettimeofday(&timer_debug[timer_idx].end); timer_debug[timer_idx].h_timer.type = LOOP; timer_debug[timer_idx].h_timer.expires = timer_lc[timer_idx]; timer_debug[timer_idx].h_timer.data = (unsigned long)&timer_debug[timer_idx]; timer_debug[timer_idx].h_timer.function = timer_handle; gsc3280_free_hard_timer(&timer_debug[timer_idx].h_timer); gsc3280_request_hard_timer(&timer_debug[timer_idx].h_timer); gsc3280_timer_start(&timer_debug[timer_idx].h_timer); } else if (!strncmp(buffer, "stop", 4)) { printk(KERN_ERR "\nstop timer%d", timer_idx); gsc3280_free_hard_timer(&timer_debug[timer_idx].h_timer); } else { printk(KERN_ERR "can only write start&stop cmd=%s", buffer); return -1; } return len; } static int gsc3280_timer_debug_read(struct file *file, char __user *buf, size_t len, loff_t *p_pos) { printk(KERN_ERR "debug file read %s", (char *)file->private_data); return 0; } static const struct file_operations gsc3280_timer_debug_opt = { .open = gsc3280_timer_debug_open, .write = gsc3280_timer_debug_write, .read = gsc3280_timer_debug_read, }; /*---------------------------------------------------------------------------*/ static int __init gsc3280_timer_debug_init(void) { struct dentry *timer_debug = debugfs_create_dir("timer", NULL); (void)debugfs_create_file("timer0", 0644, timer_debug, "timer0", &gsc3280_timer_debug_opt); (void)debugfs_create_file("timer1", 0644, timer_debug, "timer1", &gsc3280_timer_debug_opt); (void)debugfs_create_file("timer2", 0644, timer_debug, "timer2", &gsc3280_timer_debug_opt); (void)debugfs_create_file("timer3", 0644, timer_debug, "timer3", &gsc3280_timer_debug_opt); (void)debugfs_create_u32("timer0_lc", 0644, timer_debug, &timer_lc[0]); (void)debugfs_create_u32("timer1_lc", 0644, timer_debug, &timer_lc[1]); (void)debugfs_create_u32("timer2_lc", 0644, timer_debug, &timer_lc[2]); (void)debugfs_create_u32("timer3_lc", 0644, timer_debug, &timer_lc[3]); return 0; } subsys_initcall(gsc3280_timer_debug_init); #endif