diff options
Diffstat (limited to 'drivers/clocksource')
-rw-r--r-- | drivers/clocksource/Makefile | 2 | ||||
-rw-r--r-- | drivers/clocksource/bcm2835_timer.c | 161 | ||||
-rw-r--r-- | drivers/clocksource/sh_cmt.c | 71 | ||||
-rw-r--r-- | drivers/clocksource/sh_mtu2.c | 41 | ||||
-rw-r--r-- | drivers/clocksource/sh_tmu.c | 112 |
5 files changed, 367 insertions, 20 deletions
diff --git a/drivers/clocksource/Makefile b/drivers/clocksource/Makefile index 65919901a30..603be366f76 100644 --- a/drivers/clocksource/Makefile +++ b/drivers/clocksource/Makefile @@ -13,4 +13,6 @@ obj-$(CONFIG_DW_APB_TIMER) += dw_apb_timer.o obj-$(CONFIG_DW_APB_TIMER_OF) += dw_apb_timer_of.o obj-$(CONFIG_CLKSRC_DBX500_PRCMU) += clksrc-dbx500-prcmu.o obj-$(CONFIG_ARMADA_370_XP_TIMER) += time-armada-370-xp.o +obj-$(CONFIG_ARCH_BCM2835) += bcm2835_timer.o + obj-$(CONFIG_CLKSRC_ARM_GENERIC) += arm_generic.o diff --git a/drivers/clocksource/bcm2835_timer.c b/drivers/clocksource/bcm2835_timer.c new file mode 100644 index 00000000000..bc19f12c20c --- /dev/null +++ b/drivers/clocksource/bcm2835_timer.c @@ -0,0 +1,161 @@ +/* + * Copyright 2012 Simon Arlott + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <linux/bcm2835_timer.h> +#include <linux/bitops.h> +#include <linux/clockchips.h> +#include <linux/clocksource.h> +#include <linux/interrupt.h> +#include <linux/irqreturn.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of_address.h> +#include <linux/of_irq.h> +#include <linux/of_platform.h> +#include <linux/slab.h> +#include <linux/string.h> + +#include <asm/sched_clock.h> +#include <asm/irq.h> + +#define REG_CONTROL 0x00 +#define REG_COUNTER_LO 0x04 +#define REG_COUNTER_HI 0x08 +#define REG_COMPARE(n) (0x0c + (n) * 4) +#define MAX_TIMER 3 +#define DEFAULT_TIMER 3 + +struct bcm2835_timer { + void __iomem *control; + void __iomem *compare; + int match_mask; + struct clock_event_device evt; + struct irqaction act; +}; + +static void __iomem *system_clock __read_mostly; + +static u32 notrace bcm2835_sched_read(void) +{ + return readl_relaxed(system_clock); +} + +static void bcm2835_time_set_mode(enum clock_event_mode mode, + struct clock_event_device *evt_dev) +{ + switch (mode) { + case CLOCK_EVT_MODE_ONESHOT: + case CLOCK_EVT_MODE_UNUSED: + case CLOCK_EVT_MODE_SHUTDOWN: + case CLOCK_EVT_MODE_RESUME: + break; + default: + WARN(1, "%s: unhandled event mode %d\n", __func__, mode); + break; + } +} + +static int bcm2835_time_set_next_event(unsigned long event, + struct clock_event_device *evt_dev) +{ + struct bcm2835_timer *timer = container_of(evt_dev, + struct bcm2835_timer, evt); + writel_relaxed(readl_relaxed(system_clock) + event, + timer->compare); + return 0; +} + +static irqreturn_t bcm2835_time_interrupt(int irq, void *dev_id) +{ + struct bcm2835_timer *timer = dev_id; + void (*event_handler)(struct clock_event_device *); + if (readl_relaxed(timer->control) & timer->match_mask) { + writel_relaxed(timer->match_mask, timer->control); + + event_handler = ACCESS_ONCE(timer->evt.event_handler); + if (event_handler) + event_handler(&timer->evt); + return IRQ_HANDLED; + } else { + return IRQ_NONE; + } +} + +static struct of_device_id bcm2835_time_match[] __initconst = { + { .compatible = "brcm,bcm2835-system-timer" }, + {} +}; + +static void __init bcm2835_time_init(void) +{ + struct device_node *node; + void __iomem *base; + u32 freq; + int irq; + struct bcm2835_timer *timer; + + node = of_find_matching_node(NULL, bcm2835_time_match); + if (!node) + panic("No bcm2835 timer node"); + + base = of_iomap(node, 0); + if (!base) + panic("Can't remap registers"); + + if (of_property_read_u32(node, "clock-frequency", &freq)) + panic("Can't read clock-frequency"); + + system_clock = base + REG_COUNTER_LO; + setup_sched_clock(bcm2835_sched_read, 32, freq); + + clocksource_mmio_init(base + REG_COUNTER_LO, node->name, + freq, 300, 32, clocksource_mmio_readl_up); + + irq = irq_of_parse_and_map(node, DEFAULT_TIMER); + if (irq <= 0) + panic("Can't parse IRQ"); + + timer = kzalloc(sizeof(*timer), GFP_KERNEL); + if (!timer) + panic("Can't allocate timer struct\n"); + + timer->control = base + REG_CONTROL; + timer->compare = base + REG_COMPARE(DEFAULT_TIMER); + timer->match_mask = BIT(DEFAULT_TIMER); + timer->evt.name = node->name; + timer->evt.rating = 300; + timer->evt.features = CLOCK_EVT_FEAT_ONESHOT; + timer->evt.set_mode = bcm2835_time_set_mode; + timer->evt.set_next_event = bcm2835_time_set_next_event; + timer->evt.cpumask = cpumask_of(0); + timer->act.name = node->name; + timer->act.flags = IRQF_TIMER | IRQF_SHARED; + timer->act.dev_id = timer; + timer->act.handler = bcm2835_time_interrupt; + + if (setup_irq(irq, &timer->act)) + panic("Can't set up timer IRQ\n"); + + clockevents_config_and_register(&timer->evt, freq, 0xf, 0xffffffff); + + pr_info("bcm2835: system timer (irq = %d)\n", irq); +} + +struct sys_timer bcm2835_timer = { + .init = bcm2835_time_init, +}; diff --git a/drivers/clocksource/sh_cmt.c b/drivers/clocksource/sh_cmt.c index 98b06baafcc..a5f7829f279 100644 --- a/drivers/clocksource/sh_cmt.c +++ b/drivers/clocksource/sh_cmt.c @@ -33,6 +33,7 @@ #include <linux/slab.h> #include <linux/module.h> #include <linux/pm_domain.h> +#include <linux/pm_runtime.h> struct sh_cmt_priv { void __iomem *mapbase; @@ -52,6 +53,7 @@ struct sh_cmt_priv { struct clock_event_device ced; struct clocksource cs; unsigned long total_cycles; + bool cs_enabled; }; static DEFINE_RAW_SPINLOCK(sh_cmt_lock); @@ -155,6 +157,9 @@ static int sh_cmt_enable(struct sh_cmt_priv *p, unsigned long *rate) { int k, ret; + pm_runtime_get_sync(&p->pdev->dev); + dev_pm_syscore_device(&p->pdev->dev, true); + /* enable clock */ ret = clk_enable(p->clk); if (ret) { @@ -221,6 +226,9 @@ static void sh_cmt_disable(struct sh_cmt_priv *p) /* stop clock */ clk_disable(p->clk); + + dev_pm_syscore_device(&p->pdev->dev, false); + pm_runtime_put(&p->pdev->dev); } /* private flags */ @@ -451,22 +459,42 @@ static int sh_cmt_clocksource_enable(struct clocksource *cs) int ret; struct sh_cmt_priv *p = cs_to_sh_cmt(cs); + WARN_ON(p->cs_enabled); + p->total_cycles = 0; ret = sh_cmt_start(p, FLAG_CLOCKSOURCE); - if (!ret) + if (!ret) { __clocksource_updatefreq_hz(cs, p->rate); + p->cs_enabled = true; + } return ret; } static void sh_cmt_clocksource_disable(struct clocksource *cs) { - sh_cmt_stop(cs_to_sh_cmt(cs), FLAG_CLOCKSOURCE); + struct sh_cmt_priv *p = cs_to_sh_cmt(cs); + + WARN_ON(!p->cs_enabled); + + sh_cmt_stop(p, FLAG_CLOCKSOURCE); + p->cs_enabled = false; +} + +static void sh_cmt_clocksource_suspend(struct clocksource *cs) +{ + struct sh_cmt_priv *p = cs_to_sh_cmt(cs); + + sh_cmt_stop(p, FLAG_CLOCKSOURCE); + pm_genpd_syscore_poweroff(&p->pdev->dev); } static void sh_cmt_clocksource_resume(struct clocksource *cs) { - sh_cmt_start(cs_to_sh_cmt(cs), FLAG_CLOCKSOURCE); + struct sh_cmt_priv *p = cs_to_sh_cmt(cs); + + pm_genpd_syscore_poweron(&p->pdev->dev); + sh_cmt_start(p, FLAG_CLOCKSOURCE); } static int sh_cmt_register_clocksource(struct sh_cmt_priv *p, @@ -479,7 +507,7 @@ static int sh_cmt_register_clocksource(struct sh_cmt_priv *p, cs->read = sh_cmt_clocksource_read; cs->enable = sh_cmt_clocksource_enable; cs->disable = sh_cmt_clocksource_disable; - cs->suspend = sh_cmt_clocksource_disable; + cs->suspend = sh_cmt_clocksource_suspend; cs->resume = sh_cmt_clocksource_resume; cs->mask = CLOCKSOURCE_MASK(sizeof(unsigned long) * 8); cs->flags = CLOCK_SOURCE_IS_CONTINUOUS; @@ -562,6 +590,16 @@ static int sh_cmt_clock_event_next(unsigned long delta, return 0; } +static void sh_cmt_clock_event_suspend(struct clock_event_device *ced) +{ + pm_genpd_syscore_poweroff(&ced_to_sh_cmt(ced)->pdev->dev); +} + +static void sh_cmt_clock_event_resume(struct clock_event_device *ced) +{ + pm_genpd_syscore_poweron(&ced_to_sh_cmt(ced)->pdev->dev); +} + static void sh_cmt_register_clockevent(struct sh_cmt_priv *p, char *name, unsigned long rating) { @@ -576,6 +614,8 @@ static void sh_cmt_register_clockevent(struct sh_cmt_priv *p, ced->cpumask = cpumask_of(0); ced->set_next_event = sh_cmt_clock_event_next; ced->set_mode = sh_cmt_clock_event_mode; + ced->suspend = sh_cmt_clock_event_suspend; + ced->resume = sh_cmt_clock_event_resume; dev_info(&p->pdev->dev, "used for clock events\n"); clockevents_register_device(ced); @@ -670,6 +710,7 @@ static int sh_cmt_setup(struct sh_cmt_priv *p, struct platform_device *pdev) dev_err(&p->pdev->dev, "registration failed\n"); goto err1; } + p->cs_enabled = false; ret = setup_irq(irq, &p->irqaction); if (ret) { @@ -688,14 +729,17 @@ err0: 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 (!is_early_platform_device(pdev)) - pm_genpd_dev_always_on(&pdev->dev, true); + if (!is_early_platform_device(pdev)) { + pm_runtime_set_active(&pdev->dev); + pm_runtime_enable(&pdev->dev); + } if (p) { dev_info(&pdev->dev, "kept as earlytimer\n"); - return 0; + goto out; } p = kmalloc(sizeof(*p), GFP_KERNEL); @@ -708,8 +752,19 @@ static int __devinit sh_cmt_probe(struct platform_device *pdev) if (ret) { kfree(p); platform_set_drvdata(pdev, NULL); + pm_runtime_idle(&pdev->dev); + return ret; } - return ret; + if (is_early_platform_device(pdev)) + return 0; + + out: + if (cfg->clockevent_rating || cfg->clocksource_rating) + pm_runtime_irq_safe(&pdev->dev); + else + pm_runtime_idle(&pdev->dev); + + return 0; } static int __devexit sh_cmt_remove(struct platform_device *pdev) diff --git a/drivers/clocksource/sh_mtu2.c b/drivers/clocksource/sh_mtu2.c index d9b76ca64a6..c5eea858054 100644 --- a/drivers/clocksource/sh_mtu2.c +++ b/drivers/clocksource/sh_mtu2.c @@ -32,6 +32,7 @@ #include <linux/slab.h> #include <linux/module.h> #include <linux/pm_domain.h> +#include <linux/pm_runtime.h> struct sh_mtu2_priv { void __iomem *mapbase; @@ -123,6 +124,9 @@ static int sh_mtu2_enable(struct sh_mtu2_priv *p) { int ret; + pm_runtime_get_sync(&p->pdev->dev); + dev_pm_syscore_device(&p->pdev->dev, true); + /* enable clock */ ret = clk_enable(p->clk); if (ret) { @@ -157,6 +161,9 @@ static void sh_mtu2_disable(struct sh_mtu2_priv *p) /* stop clock */ clk_disable(p->clk); + + dev_pm_syscore_device(&p->pdev->dev, false); + pm_runtime_put(&p->pdev->dev); } static irqreturn_t sh_mtu2_interrupt(int irq, void *dev_id) @@ -208,6 +215,16 @@ static void sh_mtu2_clock_event_mode(enum clock_event_mode mode, } } +static void sh_mtu2_clock_event_suspend(struct clock_event_device *ced) +{ + pm_genpd_syscore_poweroff(&ced_to_sh_mtu2(ced)->pdev->dev); +} + +static void sh_mtu2_clock_event_resume(struct clock_event_device *ced) +{ + pm_genpd_syscore_poweron(&ced_to_sh_mtu2(ced)->pdev->dev); +} + static void sh_mtu2_register_clockevent(struct sh_mtu2_priv *p, char *name, unsigned long rating) { @@ -221,6 +238,8 @@ static void sh_mtu2_register_clockevent(struct sh_mtu2_priv *p, ced->rating = rating; ced->cpumask = cpumask_of(0); ced->set_mode = sh_mtu2_clock_event_mode; + ced->suspend = sh_mtu2_clock_event_suspend; + ced->resume = sh_mtu2_clock_event_resume; dev_info(&p->pdev->dev, "used for clock events\n"); clockevents_register_device(ced); @@ -305,14 +324,17 @@ static int sh_mtu2_setup(struct sh_mtu2_priv *p, struct platform_device *pdev) static int __devinit sh_mtu2_probe(struct platform_device *pdev) { struct sh_mtu2_priv *p = platform_get_drvdata(pdev); + struct sh_timer_config *cfg = pdev->dev.platform_data; int ret; - if (!is_early_platform_device(pdev)) - pm_genpd_dev_always_on(&pdev->dev, true); + if (!is_early_platform_device(pdev)) { + pm_runtime_set_active(&pdev->dev); + pm_runtime_enable(&pdev->dev); + } if (p) { dev_info(&pdev->dev, "kept as earlytimer\n"); - return 0; + goto out; } p = kmalloc(sizeof(*p), GFP_KERNEL); @@ -325,8 +347,19 @@ static int __devinit sh_mtu2_probe(struct platform_device *pdev) if (ret) { kfree(p); platform_set_drvdata(pdev, NULL); + pm_runtime_idle(&pdev->dev); + return ret; } - return ret; + if (is_early_platform_device(pdev)) + return 0; + + out: + if (cfg->clockevent_rating) + pm_runtime_irq_safe(&pdev->dev); + else + pm_runtime_idle(&pdev->dev); + + return 0; } static int __devexit sh_mtu2_remove(struct platform_device *pdev) diff --git a/drivers/clocksource/sh_tmu.c b/drivers/clocksource/sh_tmu.c index c1b51d49d10..0cc4add8827 100644 --- a/drivers/clocksource/sh_tmu.c +++ b/drivers/clocksource/sh_tmu.c @@ -33,6 +33,7 @@ #include <linux/slab.h> #include <linux/module.h> #include <linux/pm_domain.h> +#include <linux/pm_runtime.h> struct sh_tmu_priv { void __iomem *mapbase; @@ -43,6 +44,8 @@ struct sh_tmu_priv { unsigned long periodic; struct clock_event_device ced; struct clocksource cs; + bool cs_enabled; + unsigned int enable_count; }; static DEFINE_RAW_SPINLOCK(sh_tmu_lock); @@ -107,7 +110,7 @@ static void sh_tmu_start_stop_ch(struct sh_tmu_priv *p, int start) raw_spin_unlock_irqrestore(&sh_tmu_lock, flags); } -static int sh_tmu_enable(struct sh_tmu_priv *p) +static int __sh_tmu_enable(struct sh_tmu_priv *p) { int ret; @@ -135,7 +138,18 @@ static int sh_tmu_enable(struct sh_tmu_priv *p) return 0; } -static void sh_tmu_disable(struct sh_tmu_priv *p) +static int sh_tmu_enable(struct sh_tmu_priv *p) +{ + if (p->enable_count++ > 0) + return 0; + + pm_runtime_get_sync(&p->pdev->dev); + dev_pm_syscore_device(&p->pdev->dev, true); + + return __sh_tmu_enable(p); +} + +static void __sh_tmu_disable(struct sh_tmu_priv *p) { /* disable channel */ sh_tmu_start_stop_ch(p, 0); @@ -147,6 +161,20 @@ static void sh_tmu_disable(struct sh_tmu_priv *p) clk_disable(p->clk); } +static void sh_tmu_disable(struct sh_tmu_priv *p) +{ + if (WARN_ON(p->enable_count == 0)) + return; + + if (--p->enable_count > 0) + return; + + __sh_tmu_disable(p); + + dev_pm_syscore_device(&p->pdev->dev, false); + pm_runtime_put(&p->pdev->dev); +} + static void sh_tmu_set_next(struct sh_tmu_priv *p, unsigned long delta, int periodic) { @@ -203,15 +231,53 @@ static int sh_tmu_clocksource_enable(struct clocksource *cs) struct sh_tmu_priv *p = cs_to_sh_tmu(cs); int ret; + if (WARN_ON(p->cs_enabled)) + return 0; + ret = sh_tmu_enable(p); - if (!ret) + if (!ret) { __clocksource_updatefreq_hz(cs, p->rate); + p->cs_enabled = true; + } + return ret; } static void sh_tmu_clocksource_disable(struct clocksource *cs) { - sh_tmu_disable(cs_to_sh_tmu(cs)); + struct sh_tmu_priv *p = cs_to_sh_tmu(cs); + + if (WARN_ON(!p->cs_enabled)) + return; + + sh_tmu_disable(p); + p->cs_enabled = false; +} + +static void sh_tmu_clocksource_suspend(struct clocksource *cs) +{ + struct sh_tmu_priv *p = cs_to_sh_tmu(cs); + + if (!p->cs_enabled) + return; + + if (--p->enable_count == 0) { + __sh_tmu_disable(p); + pm_genpd_syscore_poweroff(&p->pdev->dev); + } +} + +static void sh_tmu_clocksource_resume(struct clocksource *cs) +{ + struct sh_tmu_priv *p = cs_to_sh_tmu(cs); + + if (!p->cs_enabled) + return; + + if (p->enable_count++ == 0) { + pm_genpd_syscore_poweron(&p->pdev->dev); + __sh_tmu_enable(p); + } } static int sh_tmu_register_clocksource(struct sh_tmu_priv *p, @@ -224,6 +290,8 @@ static int sh_tmu_register_clocksource(struct sh_tmu_priv *p, cs->read = sh_tmu_clocksource_read; cs->enable = sh_tmu_clocksource_enable; cs->disable = sh_tmu_clocksource_disable; + cs->suspend = sh_tmu_clocksource_suspend; + cs->resume = sh_tmu_clocksource_resume; cs->mask = CLOCKSOURCE_MASK(32); cs->flags = CLOCK_SOURCE_IS_CONTINUOUS; @@ -301,6 +369,16 @@ static int sh_tmu_clock_event_next(unsigned long delta, return 0; } +static void sh_tmu_clock_event_suspend(struct clock_event_device *ced) +{ + pm_genpd_syscore_poweroff(&ced_to_sh_tmu(ced)->pdev->dev); +} + +static void sh_tmu_clock_event_resume(struct clock_event_device *ced) +{ + pm_genpd_syscore_poweron(&ced_to_sh_tmu(ced)->pdev->dev); +} + static void sh_tmu_register_clockevent(struct sh_tmu_priv *p, char *name, unsigned long rating) { @@ -316,6 +394,8 @@ static void sh_tmu_register_clockevent(struct sh_tmu_priv *p, ced->cpumask = cpumask_of(0); ced->set_next_event = sh_tmu_clock_event_next; ced->set_mode = sh_tmu_clock_event_mode; + ced->suspend = sh_tmu_clock_event_suspend; + ced->resume = sh_tmu_clock_event_resume; dev_info(&p->pdev->dev, "used for clock events\n"); @@ -392,6 +472,8 @@ static int sh_tmu_setup(struct sh_tmu_priv *p, struct platform_device *pdev) ret = PTR_ERR(p->clk); goto err1; } + p->cs_enabled = false; + p->enable_count = 0; return sh_tmu_register(p, (char *)dev_name(&p->pdev->dev), cfg->clockevent_rating, @@ -405,14 +487,17 @@ static int sh_tmu_setup(struct sh_tmu_priv *p, struct platform_device *pdev) static int __devinit sh_tmu_probe(struct platform_device *pdev) { struct sh_tmu_priv *p = platform_get_drvdata(pdev); + struct sh_timer_config *cfg = pdev->dev.platform_data; int ret; - if (!is_early_platform_device(pdev)) - pm_genpd_dev_always_on(&pdev->dev, true); + if (!is_early_platform_device(pdev)) { + pm_runtime_set_active(&pdev->dev); + pm_runtime_enable(&pdev->dev); + } if (p) { dev_info(&pdev->dev, "kept as earlytimer\n"); - return 0; + goto out; } p = kmalloc(sizeof(*p), GFP_KERNEL); @@ -425,8 +510,19 @@ static int __devinit sh_tmu_probe(struct platform_device *pdev) if (ret) { kfree(p); platform_set_drvdata(pdev, NULL); + pm_runtime_idle(&pdev->dev); + return ret; } - return ret; + if (is_early_platform_device(pdev)) + return 0; + + out: + if (cfg->clockevent_rating || cfg->clocksource_rating) + pm_runtime_irq_safe(&pdev->dev); + else + pm_runtime_idle(&pdev->dev); + + return 0; } static int __devexit sh_tmu_remove(struct platform_device *pdev) |