misc: add hres_counter driver
diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
index 6d030b3..b9842d3 100644
--- a/drivers/misc/Kconfig
+++ b/drivers/misc/Kconfig
@@ -739,6 +739,11 @@
 	help
 	  RichTek RT5501 AMP Delay Workaround for pop noise of headset enable.
 
+config HRES_COUNTER
+	tristate "High resolution counter"
+	---help---
+	  Select Y if you want to enable High resolution counter.
+
 source "drivers/misc/c2port/Kconfig"
 source "drivers/misc/eeprom/Kconfig"
 source "drivers/misc/cb710/Kconfig"
diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
index 9468462..9beda2d 100644
--- a/drivers/misc/Makefile
+++ b/drivers/misc/Makefile
@@ -83,3 +83,4 @@
 obj-$(CONFIG_AMP_TFA9887L)	+= tfa9887l.o
 obj-$(CONFIG_AMP_RT5501)	+= rt5501.o
 obj-$(CONFIG_A6)		+= a6/
+obj-$(CONFIG_HRES_COUNTER)	+= hres_counter.o
diff --git a/drivers/misc/hres_counter.c b/drivers/misc/hres_counter.c
new file mode 100644
index 0000000..7a3596c
--- /dev/null
+++ b/drivers/misc/hres_counter.c
@@ -0,0 +1,576 @@
+ /* 
+ * linux/drivers/misc/hres_counter.c
+ * 
+ * Copyright (C) 2008 Palm, Inc. 
+ * 
+ * This program is free software; you can redistribute it and/or 
+ * modify it under the terms of the GNU General Public License. 
+ */ 
+ 
+#include <linux/module.h> 
+#include <linux/init.h> 
+#include <linux/types.h> 
+#include <linux/kernel.h> 
+#include <linux/errno.h> 
+#include <linux/clk.h>
+#include <linux/slab.h>
+#include <linux/tty.h>
+#include <linux/hres_counter.h>
+#include <linux/sysrq.h>
+#include <linux/platform_device.h>
+
+#include <asm/io.h>
+
+#define NUM_CHANNELS    32
+#define NUM_EVENTS     512
+
+struct count_ch {
+	u32 count;
+	u32 total;
+	u32 tstamp;
+	u32 active;
+};
+
+struct hres_event {
+	u32   tstamp;
+	char* type;
+	u32   arg1;
+	u32   arg2;
+};
+
+struct dev_ctxt {
+	void *timer;
+	int suspended;
+	struct platform_device *pdev;
+	struct count_ch ch[NUM_CHANNELS];
+
+	int (*init_hres_timer)(void **);
+	int (*release_hres_timer)(void *);
+	int (*suspend_hres_timer)(void *);
+	int (*resume_hres_timer)(void *);
+
+	u32 (*read_hres_timer)(void *);
+	u32 (*convert_hres_timer)(u32);
+};
+
+static struct dev_ctxt *gdev = NULL;
+
+static int    evlog_on  = 1;
+static int    evlog_cnt = 0;
+static int    evlog_num = 0;
+static struct hres_event evlog[NUM_EVENTS];
+
+
+/*
+ *
+ */
+static inline u32
+hres_read_tick ( void ) {
+	if( unlikely(gdev == NULL))
+		return 0;
+
+	return gdev->read_hres_timer( gdev->timer );
+}
+
+/*
+ *   Current value of high res counter
+ */
+u32 
+hres_get_counter ( void )
+{
+	return hres_read_tick();
+}
+EXPORT_SYMBOL(hres_get_counter);
+
+/*
+ *   Return the delta in useconds from start to end
+ */
+u32
+hres_get_delta_usec ( u32 start, u32 end )
+{
+	return gdev->convert_hres_timer(end - start);
+}
+
+/*
+ *   Reset channel
+ */
+void 
+hres_ch_reset ( uint ch ) 
+{
+	if( unlikely(gdev == NULL))
+		return;
+
+	if( unlikely(ch >= NUM_CHANNELS))
+		return;
+	
+	memset ( &gdev->ch[ch], 0, sizeof(struct count_ch));
+}
+EXPORT_SYMBOL(hres_ch_reset);
+
+/*
+ *   Increment Event count
+ */
+void 
+hres_event_cnt ( uint ch )
+{
+	unsigned long flags;
+	
+	if( unlikely(gdev == NULL))
+		return;
+		
+	if( unlikely(ch >= NUM_CHANNELS))
+		return;
+		
+	local_irq_save(flags);
+	gdev->ch[ch].count++;
+	local_irq_restore(flags);
+}
+EXPORT_SYMBOL(hres_event_cnt);
+
+/*
+ *   Mark Event Start
+ */
+void 
+hres_event_start ( uint ch )
+{
+	unsigned long flags;
+	
+	if( unlikely(gdev == NULL))
+		return;
+		
+	if( unlikely(ch >= NUM_CHANNELS))
+		return;
+		
+	local_irq_save(flags);
+	gdev->ch[ch].tstamp = hres_read_tick();
+	gdev->ch[ch].active = 1;
+	local_irq_restore(flags);
+}
+EXPORT_SYMBOL(hres_event_start);
+
+/*
+ *   Mark Event End and count
+ */
+u32 
+hres_event_end ( uint ch )
+{
+	u32 ts = 0;
+	unsigned long flags;
+
+	if( unlikely(gdev == NULL))
+		return 0;
+
+	if( unlikely(ch >= NUM_CHANNELS)) 
+		return 0;
+
+	if(!gdev->ch[ch].active)
+		return 0;
+		
+	local_irq_save(flags);
+	ts = gdev->read_hres_timer( gdev->timer ) - gdev->ch[ch].tstamp;
+	gdev->ch[ch].total += ts;
+	gdev->ch[ch].count++;
+	gdev->ch[ch].active = 0;
+	local_irq_restore(flags);
+	return ts;
+	
+}
+EXPORT_SYMBOL(hres_event_end);
+
+
+/*
+ *   Mark Event End and count
+ */
+void
+hres_event ( char *type, u32 arg1, u32 arg2 )
+{
+	unsigned long flags;
+	struct hres_event *evt;
+
+	if( unlikely(gdev == NULL))
+		return;
+
+	if( !evlog_on )
+		return;
+
+	local_irq_save(flags);
+	evt = evlog + evlog_cnt;
+	evlog_cnt++;
+	if( evlog_cnt > evlog_num )
+		evlog_num = evlog_cnt;
+	if( evlog_cnt == NUM_EVENTS )
+		evlog_cnt = 0;
+	evt->tstamp = hres_read_tick();
+	evt->type   = type;
+	evt->arg1   = arg1;
+	evt->arg2   = arg2;
+	local_irq_restore(flags);
+	return;
+	
+}
+EXPORT_SYMBOL(hres_event);
+
+
+/*
+ *
+ */
+void
+hres_evlog_reset(void)
+{
+	unsigned long flags;
+	
+	local_irq_save(flags);
+	evlog_cnt = 0;
+	evlog_num = 0;
+	local_irq_restore(flags);
+}
+EXPORT_SYMBOL(hres_evlog_reset);
+
+
+/*
+ *  Enable logging
+ */
+int 
+hres_evlog_enable(void)
+{
+	int rc = evlog_on;
+	evlog_on = 1;
+	return rc;
+}
+EXPORT_SYMBOL(hres_evlog_enable);
+
+/*
+ *  Disable logging
+ */
+int 
+hres_evlog_disable(void)
+{
+	int rc = evlog_on;
+	evlog_on = 0;
+	return rc;
+}
+EXPORT_SYMBOL(hres_evlog_disable);
+
+/*
+ *  Print log
+ */
+void
+hres_evlog_print ( void )
+{
+	int i;
+	u32 last_tstamp = 0;
+	struct hres_event *evt = evlog;
+
+	printk ("evlog: beg\n");
+	for (i = 0; i < evlog_num; i++, evt++ ) {
+		printk ( "%010d (d%10d): %12d (0x%08x)  %12d (0x%08x)  %s\n",
+		   gdev->convert_hres_timer(evt->tstamp),
+		   gdev->convert_hres_timer(evt->tstamp - last_tstamp),
+		   evt->arg1, evt->arg1,
+		   evt->arg2, evt->arg2, 
+		   evt->type );
+		last_tstamp = evt->tstamp;
+	}
+	printk ("evlog: end\n");
+}
+EXPORT_SYMBOL(hres_evlog_print);
+
+
+/*
+ *   Sysfs
+ */
+static ssize_t 
+counter_show ( struct device *dev, 
+               struct device_attribute *attr, 
+               char *buf )
+{
+	return snprintf( buf, PAGE_SIZE, "%d\n", hres_read_tick());
+}
+
+static DEVICE_ATTR( counter, S_IRUGO | S_IWUSR, counter_show, NULL );
+
+
+
+static ssize_t 
+evlog_show ( struct device *dev, 
+             struct device_attribute *attr, 
+             char *buf )
+{
+	hres_evlog_print ();
+	return 0;
+}
+
+static ssize_t 
+evlog_store( struct device *dev,
+             struct device_attribute *attr,
+             const char *buf, size_t count )
+{
+	hres_evlog_reset();
+	return count;
+}
+
+
+static DEVICE_ATTR( evlog, S_IRUGO | S_IWUSR, evlog_show, evlog_store );
+
+
+
+static ssize_t 
+channels_show ( struct device *dev, 
+                struct device_attribute *attr, 
+                char *buf )
+{
+	int ch; 
+	ssize_t len = 0;
+
+	for ( ch = 0; ch < NUM_CHANNELS; ch++ ) {
+		len += snprintf( buf + len, PAGE_SIZE - len, 
+		"%02d:  %010u   %4d\n", ch,
+		gdev->convert_hres_timer(gdev->ch[ch].total),
+		gdev->ch[ch].count
+		);
+	}
+	return len;
+}
+
+static ssize_t 
+channels_store( struct device *dev,
+                struct device_attribute *attr,
+                const char *buf, size_t count )
+{
+	int ch;
+
+	ch = simple_strtol ( buf, NULL, 10 );
+	if( ch == -1 ) {
+		for ( ch = 0; ch < NUM_CHANNELS; ch++ )
+			hres_ch_reset ( ch );
+	}
+	else {
+		hres_ch_reset ((uint) ch );
+	}
+	return count;
+}
+
+static DEVICE_ATTR( channels, S_IRUGO | S_IWUSR, channels_show, channels_store);
+
+
+static void 
+hres_sysrq_show_evlog(int key)
+{
+	hres_evlog_print();
+}
+
+
+/* 
+ *
+ */
+static int
+hres_panic(struct notifier_block *this, unsigned long event, void *ptr)
+{
+	hres_evlog_print();
+	
+	return NOTIFY_DONE;
+}
+
+static struct notifier_block panic_block = {
+	.notifier_call = hres_panic,
+};
+
+/*
+ *  Sys req related 
+ */
+static struct sysrq_key_op sysrq_show_log_op = {
+	.handler	= hres_sysrq_show_evlog,
+	.help_msg	= "show-evLog",
+	.action_msg	= "Show HiRes EvLog",
+};
+
+static void 
+hres_sysrq_reset_evlog(int key)
+{
+	hres_evlog_reset();
+}
+
+
+static struct sysrq_key_op sysrq_reset_log_op = {
+	.handler	= hres_sysrq_reset_evlog,
+	.help_msg	= "reset-evlog(c)",
+	.action_msg	= "Reset Hires EvLog",
+};
+
+/*
+ *
+ */
+static int __devexit
+hres_counter_remove ( struct platform_device *pdev )
+{
+	struct dev_ctxt *ctxt = gdev;
+
+	gdev = NULL;
+
+	device_remove_file ( &pdev->dev, &dev_attr_counter  );
+	device_remove_file ( &pdev->dev, &dev_attr_channels );
+	device_remove_file ( &pdev->dev, &dev_attr_evlog    );
+		
+	gdev->release_hres_timer  ( ctxt->timer );
+
+	platform_set_drvdata( pdev, NULL );
+
+	kfree ( ctxt );
+
+	return 0;	 
+}
+
+
+
+/*
+ *
+ */
+static int __devinit 
+hres_counter_probe ( struct platform_device  *pdev )
+{
+	int rc;
+	struct dev_ctxt *ctxt;
+	struct hres_counter_platform_data *pdata = pdev->dev.platform_data;
+
+	ctxt = kzalloc ( sizeof(struct dev_ctxt), GFP_KERNEL );
+	if( ctxt == NULL ) 
+		goto ret_nodev;
+
+	ctxt->pdev = pdev;
+	ctxt->init_hres_timer = pdata->init_hres_timer;
+	ctxt->release_hres_timer = pdata->release_hres_timer;
+	ctxt->read_hres_timer = pdata->read_hres_timer;
+	ctxt->convert_hres_timer = pdata->convert_hres_timer;
+
+	ctxt->suspend_hres_timer = pdata->suspend_hres_timer;
+	ctxt->resume_hres_timer = pdata->resume_hres_timer;
+			
+	rc = ctxt->init_hres_timer(&ctxt->timer);
+	if( rc )
+		goto free_ctxt;
+	
+	platform_set_drvdata ( pdev, ctxt );
+
+	rc = device_create_file ( &pdev->dev, &dev_attr_counter );
+	if( rc ) 
+		goto free_timer;
+
+	rc = device_create_file ( &pdev->dev, &dev_attr_channels );
+	if( rc ) 
+		goto free_sysfs_counter;
+
+	rc = device_create_file ( &pdev->dev, &dev_attr_evlog );
+	if( rc ) 
+		goto free_sysfs_channels;
+
+	gdev = ctxt;
+
+	printk( KERN_INFO "Initialize %s device\n", 
+	        pdev->name);
+#if 0
+#ifndef CONFIG_HRES_COUNTER_TIMER32K
+	printk( KERN_INFO "Initialize %s device (%dMHz)\n", 
+	        pdev->name, clk_rate );
+#else
+	printk( KERN_INFO "Initialize %s device (32KHz)\n", pdev->name);
+#endif
+#endif
+
+	// register sys request key
+	register_sysrq_key( 'l', &sysrq_show_log_op );
+	register_sysrq_key( 'c', &sysrq_reset_log_op );
+
+	// register panic callback
+	atomic_notifier_chain_register(&panic_notifier_list, &panic_block);
+
+	return 0;
+	
+free_sysfs_channels:
+	device_remove_file ( &pdev->dev, &dev_attr_channels );
+free_sysfs_counter:
+	device_remove_file ( &pdev->dev, &dev_attr_counter );
+free_timer:
+	ctxt->release_hres_timer ( ctxt->timer );
+free_ctxt:
+	kfree ( ctxt );
+ret_nodev:
+	return -ENODEV;
+}
+
+
+#ifdef CONFIG_HRES_COUNTER_TIMER32K
+#undef CONFIG_PM
+#endif
+
+#ifdef CONFIG_PM
+static int 
+hres_counter_suspend( struct platform_device *dev, pm_message_t state )
+{
+	struct dev_ctxt *ctxt = gdev;
+
+	if( ctxt->suspended )
+		return 0;
+
+	ctxt->resume_hres_timer(gdev->timer);
+
+	ctxt->suspended = 1;
+
+	return 0;
+}
+
+static int 
+hres_counter_resume ( struct platform_device *dev )
+{
+	struct dev_ctxt *ctxt = gdev;
+
+	if(!ctxt->suspended )
+		return 0;
+
+	ctxt->suspend_hres_timer(gdev->timer);
+
+	ctxt->suspended = 0;
+
+	return 0;
+}
+#else
+#define hres_counter_suspend	NULL
+#define hres_counter_resume	NULL
+#endif	/* CONFIG_PM */
+
+
+/*
+ *
+ */
+
+static struct platform_driver hres_counter_driver = {
+	.driver   = {
+		.name = "hres_counter",
+	},
+	.probe	  = hres_counter_probe,
+	.remove   = __devexit_p(hres_counter_remove),
+	.suspend  = hres_counter_suspend,
+	.resume   = hres_counter_resume,
+};
+
+static int __init  
+hres_counter_init(void) 
+{ 
+	platform_driver_register ( &hres_counter_driver );
+	return 0;
+} 
+
+ 
+static void __exit 
+hres_counter_exit(void) 
+{ 
+	platform_driver_unregister ( &hres_counter_driver );
+	return;
+} 
+ 
+module_init(hres_counter_init); 
+module_exit(hres_counter_exit); 
+ 
+MODULE_DESCRIPTION("OMAP High resolution counter driver" );
+MODULE_LICENSE("GPL");  
+
+
diff --git a/include/linux/hres_counter.h b/include/linux/hres_counter.h
new file mode 100644
index 0000000..9672f1c
--- /dev/null
+++ b/include/linux/hres_counter.h
@@ -0,0 +1,64 @@
+#ifndef __HRES_COUNTER_INCLUDED__
+#define __HRES_COUNTER_INCLUDED__
+
+#include <linux/types.h>
+
+struct hres_counter_platform_data {
+	/* Initialize/obtain the timer resource */
+	int (*init_hres_timer)(void **);
+
+	/* Release the timer resource*/
+	int (*release_hres_timer)(void *);
+
+	/* PM functions */
+	int (*suspend_hres_timer)(void *);
+	int (*resume_hres_timer)(void *);
+
+	/* Read native timer count value */
+	u32 (*read_hres_timer)(void *);
+
+	/* Convert native timer value to desired human */
+	/* readable format (usec or msec, etc) */
+	u32 (*convert_hres_timer)(u32);
+};
+
+#define LOG_MMC_TIMEOUT_TIMING_MEASUREMENTS 1
+#if !defined(CONFIG_HRES_COUNTER) && LOG_MMC_TIMEOUT_TIMING_MEASUREMENTS
+#error "MMC timeout measurements can only be done with hires counters"
+#endif 
+
+#ifdef CONFIG_HRES_COUNTER
+
+extern u32   hres_get_counter ( void );
+extern u32   hres_get_delta_usec ( u32 start, u32 end );
+extern void  hres_ch_reset    ( uint ch ); 
+extern void  hres_event_cnt   ( uint ch );
+extern void  hres_event_start ( uint ch );
+extern u32   hres_event_end   ( uint ch );
+extern void  hres_event       ( char *type, u32 arg1, u32 arg2 );
+extern int   hres_evlog_enable  ( void );
+extern int   hres_evlog_disable ( void );
+extern void  hres_evlog_print   ( void );
+extern void  hres_evlog_reset   ( void );
+
+#else
+
+#define hres_get_counter(args...)
+#define hres_get_delta_usec(args...)
+#define hres_ch_reset(args...)
+#define hres_event_cnt(args...)
+#define hres_event_start(args...)
+#define hres_event_end(args...)
+#define hres_event(args...)
+#define hres_evlog_enable(args...)
+#define hres_evlog_disable(args...)
+#define hres_evlog_print(args...)
+#define hres_evlog_reset(args...)
+
+#endif
+
+
+#endif // __HRES_COUNTER_INCLUDED__
+
+
+