summaryrefslogtreecommitdiff
path: root/drivers/clocksource/sh_cmt.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/clocksource/sh_cmt.c')
-rw-r--r--drivers/clocksource/sh_cmt.c116
1 files changed, 97 insertions, 19 deletions
diff --git a/drivers/clocksource/sh_cmt.c b/drivers/clocksource/sh_cmt.c
index 1c92c39..cf56a2a 100644
--- a/drivers/clocksource/sh_cmt.c
+++ b/drivers/clocksource/sh_cmt.c
@@ -18,7 +18,6 @@
*/
#include <linux/init.h>
-#include <linux/bootmem.h>
#include <linux/platform_device.h>
#include <linux/spinlock.h>
#include <linux/interrupt.h>
@@ -29,7 +28,7 @@
#include <linux/err.h>
#include <linux/clocksource.h>
#include <linux/clockchips.h>
-#include <linux/sh_cmt.h>
+#include <linux/sh_timer.h>
struct sh_cmt_priv {
void __iomem *mapbase;
@@ -47,6 +46,7 @@ struct sh_cmt_priv {
unsigned long rate;
spinlock_t lock;
struct clock_event_device ced;
+ struct clocksource cs;
unsigned long total_cycles;
};
@@ -59,7 +59,7 @@ static DEFINE_SPINLOCK(sh_cmt_lock);
static inline unsigned long sh_cmt_read(struct sh_cmt_priv *p, int reg_nr)
{
- struct sh_cmt_config *cfg = p->pdev->dev.platform_data;
+ struct sh_timer_config *cfg = p->pdev->dev.platform_data;
void __iomem *base = p->mapbase;
unsigned long offs;
@@ -83,7 +83,7 @@ static inline unsigned long sh_cmt_read(struct sh_cmt_priv *p, int reg_nr)
static inline void sh_cmt_write(struct sh_cmt_priv *p, int reg_nr,
unsigned long value)
{
- struct sh_cmt_config *cfg = p->pdev->dev.platform_data;
+ struct sh_timer_config *cfg = p->pdev->dev.platform_data;
void __iomem *base = p->mapbase;
unsigned long offs;
@@ -110,23 +110,28 @@ static unsigned long sh_cmt_get_counter(struct sh_cmt_priv *p,
int *has_wrapped)
{
unsigned long v1, v2, v3;
+ int o1, o2;
+
+ o1 = sh_cmt_read(p, CMCSR) & p->overflow_bit;
/* Make sure the timer value is stable. Stolen from acpi_pm.c */
do {
+ o2 = o1;
v1 = sh_cmt_read(p, CMCNT);
v2 = sh_cmt_read(p, CMCNT);
v3 = sh_cmt_read(p, CMCNT);
- } while (unlikely((v1 > v2 && v1 < v3) || (v2 > v3 && v2 < v1)
- || (v3 > v1 && v3 < v2)));
+ o1 = sh_cmt_read(p, CMCSR) & p->overflow_bit;
+ } while (unlikely((o1 != o2) || (v1 > v2 && v1 < v3)
+ || (v2 > v3 && v2 < v1) || (v3 > v1 && v3 < v2)));
- *has_wrapped = sh_cmt_read(p, CMCSR) & p->overflow_bit;
+ *has_wrapped = o1;
return v2;
}
static void sh_cmt_start_stop_ch(struct sh_cmt_priv *p, int start)
{
- struct sh_cmt_config *cfg = p->pdev->dev.platform_data;
+ struct sh_timer_config *cfg = p->pdev->dev.platform_data;
unsigned long flags, value;
/* start stop register shared by multiple timer channels */
@@ -144,7 +149,7 @@ static void sh_cmt_start_stop_ch(struct sh_cmt_priv *p, int start)
static int sh_cmt_enable(struct sh_cmt_priv *p, unsigned long *rate)
{
- struct sh_cmt_config *cfg = p->pdev->dev.platform_data;
+ struct sh_timer_config *cfg = p->pdev->dev.platform_data;
int ret;
/* enable clock */
@@ -153,16 +158,18 @@ static int sh_cmt_enable(struct sh_cmt_priv *p, unsigned long *rate)
pr_err("sh_cmt: cannot enable clock \"%s\"\n", cfg->clk);
return ret;
}
- *rate = clk_get_rate(p->clk) / 8;
/* make sure channel is disabled */
sh_cmt_start_stop_ch(p, 0);
/* configure channel, periodic mode and maximum timeout */
- if (p->width == 16)
- sh_cmt_write(p, CMCSR, 0);
- else
+ if (p->width == 16) {
+ *rate = clk_get_rate(p->clk) / 512;
+ sh_cmt_write(p, CMCSR, 0x43);
+ } else {
+ *rate = clk_get_rate(p->clk) / 8;
sh_cmt_write(p, CMCSR, 0x01a4);
+ }
sh_cmt_write(p, CMCOR, 0xffffffff);
sh_cmt_write(p, CMCNT, 0);
@@ -376,6 +383,68 @@ static void sh_cmt_stop(struct sh_cmt_priv *p, unsigned long flag)
spin_unlock_irqrestore(&p->lock, flags);
}
+static struct sh_cmt_priv *cs_to_sh_cmt(struct clocksource *cs)
+{
+ return container_of(cs, struct sh_cmt_priv, cs);
+}
+
+static cycle_t sh_cmt_clocksource_read(struct clocksource *cs)
+{
+ struct sh_cmt_priv *p = cs_to_sh_cmt(cs);
+ unsigned long flags, raw;
+ unsigned long value;
+ int has_wrapped;
+
+ spin_lock_irqsave(&p->lock, flags);
+ value = p->total_cycles;
+ raw = sh_cmt_get_counter(p, &has_wrapped);
+
+ if (unlikely(has_wrapped))
+ raw += p->match_value;
+ spin_unlock_irqrestore(&p->lock, flags);
+
+ return value + raw;
+}
+
+static int sh_cmt_clocksource_enable(struct clocksource *cs)
+{
+ struct sh_cmt_priv *p = cs_to_sh_cmt(cs);
+ int ret;
+
+ p->total_cycles = 0;
+
+ ret = sh_cmt_start(p, FLAG_CLOCKSOURCE);
+ if (ret)
+ return ret;
+
+ /* TODO: calculate good shift from rate and counter bit width */
+ cs->shift = 0;
+ cs->mult = clocksource_hz2mult(p->rate, cs->shift);
+ return 0;
+}
+
+static void sh_cmt_clocksource_disable(struct clocksource *cs)
+{
+ sh_cmt_stop(cs_to_sh_cmt(cs), FLAG_CLOCKSOURCE);
+}
+
+static int sh_cmt_register_clocksource(struct sh_cmt_priv *p,
+ char *name, unsigned long rating)
+{
+ struct clocksource *cs = &p->cs;
+
+ cs->name = name;
+ cs->rating = rating;
+ cs->read = sh_cmt_clocksource_read;
+ cs->enable = sh_cmt_clocksource_enable;
+ cs->disable = sh_cmt_clocksource_disable;
+ cs->mask = CLOCKSOURCE_MASK(sizeof(unsigned long) * 8);
+ cs->flags = CLOCK_SOURCE_IS_CONTINUOUS;
+ pr_info("sh_cmt: %s used as clock source\n", cs->name);
+ clocksource_register(cs);
+ return 0;
+}
+
static struct sh_cmt_priv *ced_to_sh_cmt(struct clock_event_device *ced)
{
return container_of(ced, struct sh_cmt_priv, ced);
@@ -468,9 +537,9 @@ static void sh_cmt_register_clockevent(struct sh_cmt_priv *p,
clockevents_register_device(ced);
}
-int sh_cmt_register(struct sh_cmt_priv *p, char *name,
- unsigned long clockevent_rating,
- unsigned long clocksource_rating)
+static int sh_cmt_register(struct sh_cmt_priv *p, char *name,
+ unsigned long clockevent_rating,
+ unsigned long clocksource_rating)
{
if (p->width == (sizeof(p->max_match_value) * 8))
p->max_match_value = ~0;
@@ -483,12 +552,15 @@ int sh_cmt_register(struct sh_cmt_priv *p, char *name,
if (clockevent_rating)
sh_cmt_register_clockevent(p, name, clockevent_rating);
+ if (clocksource_rating)
+ sh_cmt_register_clocksource(p, name, clocksource_rating);
+
return 0;
}
static int sh_cmt_setup(struct sh_cmt_priv *p, struct platform_device *pdev)
{
- struct sh_cmt_config *cfg = pdev->dev.platform_data;
+ struct sh_timer_config *cfg = pdev->dev.platform_data;
struct resource *res;
int irq, ret;
ret = -ENXIO;
@@ -545,7 +617,7 @@ static int sh_cmt_setup(struct sh_cmt_priv *p, struct platform_device *pdev)
if (resource_size(res) == 6) {
p->width = 16;
p->overflow_bit = 0x80;
- p->clear_bits = ~0xc0;
+ p->clear_bits = ~0x80;
} else {
p->width = 32;
p->overflow_bit = 0x8000;
@@ -566,8 +638,14 @@ static int sh_cmt_setup(struct sh_cmt_priv *p, struct platform_device *pdev)
static int __devinit sh_cmt_probe(struct platform_device *pdev)
{
struct sh_cmt_priv *p = platform_get_drvdata(pdev);
+ struct sh_timer_config *cfg = pdev->dev.platform_data;
int ret;
+ if (p) {
+ pr_info("sh_cmt: %s kept as earlytimer\n", cfg->name);
+ return 0;
+ }
+
p = kmalloc(sizeof(*p), GFP_KERNEL);
if (p == NULL) {
dev_err(&pdev->dev, "failed to allocate driver data\n");
@@ -577,7 +655,6 @@ static int __devinit sh_cmt_probe(struct platform_device *pdev)
ret = sh_cmt_setup(p, pdev);
if (ret) {
kfree(p);
-
platform_set_drvdata(pdev, NULL);
}
return ret;
@@ -606,6 +683,7 @@ static void __exit sh_cmt_exit(void)
platform_driver_unregister(&sh_cmt_device_driver);
}
+early_platform_init("earlytimer", &sh_cmt_device_driver);
module_init(sh_cmt_init);
module_exit(sh_cmt_exit);