390 lines
10 KiB
C
390 lines
10 KiB
C
/*
|
|
*
|
|
* Copyright (c) 2012 Loongson Co., Ltd.
|
|
*
|
|
* GSC3280 - Timer Driver <ansonn.wang@gmail.com>
|
|
*
|
|
*/
|
|
#include <linux/io.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/module.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/debugfs.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/time.h>
|
|
#include <asm/clock.h>
|
|
#include <linux/device.h>
|
|
#include <linux/cdev.h>
|
|
#include <linux/miscdevice.h>
|
|
#include <gsc3280/gsc3280_int.h>
|
|
#include <gsc3280/gsc3280_regs.h>
|
|
#include <gsc3280/sysctl.h>
|
|
#include <gsc3280/timer.h>
|
|
|
|
|
|
//#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
|
|
|