2011-03-31 15:12:59

by Jamie Iles

[permalink] [raw]
Subject: [PATCH] clocksource: clocksource/clockevent driver for Synopsys dw_apb_timer

This patch adds a driver for the Synopsys DesignWare APB timer block
found in some ARM systems. This uses the timers with an IRQ as a
clockevents device and a timer without an IRQ as a clocksource
device.

Cc: John Stultz <[email protected]>
Cc: Thomas Gleixner <[email protected]>
Signed-off-by: Jamie Iles <[email protected]>
---
arch/arm/Kconfig | 3 +
drivers/clocksource/Makefile | 1 +
drivers/clocksource/dw_apb.c | 298 ++++++++++++++++++++++++++++++++++++++++++
3 files changed, 302 insertions(+), 0 deletions(-)
create mode 100644 drivers/clocksource/dw_apb.c

diff --git a/arch/arm/Kconfig b/arch/arm/Kconfig
index 7c0effb..8988034 100644
--- a/arch/arm/Kconfig
+++ b/arch/arm/Kconfig
@@ -1316,6 +1316,9 @@ menu "Kernel Features"

source "kernel/time/Kconfig"

+config DWAPB_TIMER
+ bool
+
config SMP
bool "Symmetric Multi-Processing (EXPERIMENTAL)"
depends on EXPERIMENTAL
diff --git a/drivers/clocksource/Makefile b/drivers/clocksource/Makefile
index be61ece..5961052 100644
--- a/drivers/clocksource/Makefile
+++ b/drivers/clocksource/Makefile
@@ -6,3 +6,4 @@ obj-$(CONFIG_CS5535_CLOCK_EVENT_SRC) += cs5535-clockevt.o
obj-$(CONFIG_SH_TIMER_CMT) += sh_cmt.o
obj-$(CONFIG_SH_TIMER_MTU2) += sh_mtu2.o
obj-$(CONFIG_SH_TIMER_TMU) += sh_tmu.o
+obj-$(CONFIG_DWAPB_TIMER) += dw_apb.o
diff --git a/drivers/clocksource/dw_apb.c b/drivers/clocksource/dw_apb.c
new file mode 100644
index 0000000..776eb34
--- /dev/null
+++ b/drivers/clocksource/dw_apb.c
@@ -0,0 +1,298 @@
+/*
+ * Copyright (c) 2011 Picochip Ltd., Jamie Iles
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * Support for the Synopsys DesignWare APB Timers.
+ */
+#include <linux/clk.h>
+#include <linux/err.h>
+#include <linux/clockchips.h>
+#include <linux/clocksource.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/irq.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+
+#define DW_APB_TIMER_LOAD_COUNT_REG_OFFSET 0x00
+#define DW_APB_TIMER_CURRENT_VAL_REG_OFFSET 0x04
+#define DW_APB_TIMER_CONTROL_REG_OFFSET 0x08
+#define DW_APB_TIMER_EOI_REG_OFFSET 0x0c
+#define DW_APB_TIMER_ENABLE 0x00000001
+#define DW_APB_TIMER_MODE 0x00000002
+#define DW_APB_TIMER_INTERRUPT_MASK 0x00000004
+#define DW_APB_TIMER_MIN_RANGE 4
+
+struct dw_apb_event_device {
+ struct clock_event_device dev;
+ void __iomem *base;
+ struct clk *clk;
+ struct irqaction irqaction;
+};
+
+struct dw_apb_source_device {
+ struct clocksource dev;
+ void __iomem *base;
+ struct clk *clk;
+};
+
+static inline struct dw_apb_source_device *
+to_dw_apb_source_device(struct clocksource *dev)
+{
+ return container_of(dev, struct dw_apb_source_device, dev);
+}
+
+static inline struct dw_apb_event_device *
+to_dw_apb_event_device(struct clock_event_device *dev)
+{
+ return container_of(dev, struct dw_apb_event_device, dev);
+}
+
+static void dw_apb_timer_set_mode(enum clock_event_mode mode,
+ struct clock_event_device *clk)
+{
+ struct dw_apb_event_device *dwclk = to_dw_apb_event_device(clk);
+ unsigned long load_count = DIV_ROUND_UP(clk_get_rate(dwclk->clk), HZ);
+
+ switch (mode) {
+ case CLOCK_EVT_MODE_PERIODIC:
+ /*
+ * By default, use the kernel tick rate. The reload value can
+ * be changed with the timer_set_next_event() function.
+ */
+ writel(load_count,
+ dwclk->base + DW_APB_TIMER_LOAD_COUNT_REG_OFFSET);
+ writel(DW_APB_TIMER_ENABLE | DW_APB_TIMER_MODE,
+ dwclk->base + DW_APB_TIMER_CONTROL_REG_OFFSET);
+ break;
+
+ case CLOCK_EVT_MODE_ONESHOT:
+ case CLOCK_EVT_MODE_UNUSED:
+ case CLOCK_EVT_MODE_SHUTDOWN:
+ default:
+ writel(0, dwclk->base + DW_APB_TIMER_CONTROL_REG_OFFSET);
+ break;
+ }
+}
+
+static int dw_apb_timer_set_next_event(unsigned long evt,
+ struct clock_event_device *clk)
+{
+ struct dw_apb_event_device *dwclk = to_dw_apb_event_device(clk);
+
+ writel(0, dwclk->base + DW_APB_TIMER_CONTROL_REG_OFFSET);
+ writel(evt, dwclk->base + DW_APB_TIMER_LOAD_COUNT_REG_OFFSET);
+ writel(DW_APB_TIMER_ENABLE | DW_APB_TIMER_MODE,
+ dwclk->base + DW_APB_TIMER_CONTROL_REG_OFFSET);
+
+ return 0;
+}
+
+static irqreturn_t dw_apb_event_irq(int irq, void *dev_id)
+{
+ struct dw_apb_event_device *dwclk = to_dw_apb_event_device(dev_id);
+
+ readl(dwclk->base + DW_APB_TIMER_EOI_REG_OFFSET);
+ dwclk->dev.event_handler(&dwclk->dev);
+
+ return IRQ_HANDLED;
+}
+
+static int dw_apb_event_probe(struct platform_device *pdev, int irq)
+{
+ int err;
+ struct resource *mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ struct dw_apb_event_device *dwclk = kzalloc(sizeof(*dwclk), GFP_KERNEL);
+
+ if (!dwclk)
+ return -ENOMEM;
+
+ if (!request_mem_region(mem->start, resource_size(mem),
+ "dw_apb_timer")) {
+ err = -ENOMEM;
+ goto out_free;
+ }
+
+ dwclk->base = ioremap(mem->start, resource_size(mem));
+ if (!dwclk->base) {
+ dev_err(&pdev->dev, "failed to remap i/o memory\n");
+ err = -ENOMEM;
+ goto out_release_mem;
+ }
+
+ dwclk->clk = clk_get(&pdev->dev, NULL);
+ if (IS_ERR(dwclk->clk)) {
+ dev_err(&pdev->dev, "no clk\n");
+ err = PTR_ERR(dwclk->clk);
+ goto out_unmap;
+ }
+
+ err = clk_enable(dwclk->clk);
+ if (err) {
+ dev_err(&pdev->dev, "failed to enable clk\n");
+ goto out_put_clk;
+ }
+
+ if (irq < 0) {
+ dev_err(&pdev->dev, "no IRQ for timer\n");
+ err = irq;
+ goto out_disable_clk;
+ }
+ clockevents_calc_mult_shift(&dwclk->dev, CLOCK_TICK_RATE,
+ DW_APB_TIMER_MIN_RANGE);
+ dwclk->dev.max_delta_ns = clockevent_delta2ns(0xfffffffe,
+ &dwclk->dev);
+ dwclk->dev.min_delta_ns = 50000;
+ dwclk->dev.cpumask = cpumask_of(0);
+ dwclk->dev.features = CLOCK_EVT_FEAT_PERIODIC |
+ CLOCK_EVT_FEAT_ONESHOT,
+ dwclk->dev.set_next_event = dw_apb_timer_set_next_event,
+ dwclk->dev.set_mode = dw_apb_timer_set_mode,
+ dwclk->dev.name = "dw_apb_timer";
+
+ /* Start with the timer disabled and the interrupt enabled. */
+ writel(0, dwclk->base + DW_APB_TIMER_CONTROL_REG_OFFSET);
+
+ dwclk->irqaction.name = dev_name(&pdev->dev);
+ dwclk->irqaction.handler = dw_apb_event_irq;
+ dwclk->irqaction.dev_id = dwclk;
+ dwclk->irqaction.irq = irq;
+ dwclk->irqaction.flags = IRQF_TIMER | IRQF_IRQPOLL |
+ IRQF_NOBALANCING;
+ err = setup_irq(dwclk->irqaction.irq, &dwclk->irqaction);
+ if (err) {
+ dev_err(&pdev->dev, "failed to request timer irq\n");
+ goto out_disable_clk;
+ }
+
+ clockevents_register_device(&dwclk->dev);
+
+ return 0;
+
+out_disable_clk:
+ clk_disable(dwclk->clk);
+out_put_clk:
+ clk_put(dwclk->clk);
+out_unmap:
+ iounmap(dwclk->base);
+out_release_mem:
+ release_mem_region(mem->start, resource_size(mem));
+out_free:
+ kfree(dwclk);
+
+ return err;
+}
+
+static cycle_t dw_apb_source_read(struct clocksource *cs)
+{
+ struct dw_apb_source_device *dwclk = to_dw_apb_source_device(cs);
+
+ return ~readl(dwclk->base + DW_APB_TIMER_CURRENT_VAL_REG_OFFSET);
+}
+
+static int dw_apb_source_probe(struct platform_device *pdev)
+{
+ struct resource *mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ int err;
+ struct dw_apb_source_device *dwclk = kzalloc(sizeof(*dwclk),
+ GFP_KERNEL);
+
+ if (!dwclk)
+ return -ENOMEM;
+
+ if (!request_mem_region(mem->start, resource_size(mem),
+ "dw_apb_timer")) {
+ err = -ENOMEM;
+ goto out_free;
+ }
+
+ dwclk->base = ioremap(mem->start, resource_size(mem));
+ if (!dwclk->base) {
+ dev_err(&pdev->dev, "failed to remap i/o memory\n");
+ err = -ENOMEM;
+ goto out_release_mem;
+ }
+
+ dwclk->clk = clk_get(&pdev->dev, NULL);
+ if (IS_ERR(dwclk->clk)) {
+ dev_err(&pdev->dev, "no clk\n");
+ err = PTR_ERR(dwclk->clk);
+ goto out_unmap;
+ }
+
+ err = clk_enable(dwclk->clk);
+ if (err) {
+ dev_err(&pdev->dev, "failed to enable clk\n");
+ goto out_put_clk;
+ }
+
+ dwclk->dev.read = dw_apb_source_read;
+ dwclk->dev.name = "dw_apb_timer";
+ dwclk->dev.rating = 300;
+ dwclk->dev.mask = CLOCKSOURCE_MASK(32);
+ dwclk->dev.flags = CLOCK_SOURCE_IS_CONTINUOUS;
+ writel(~0, dwclk->base + DW_APB_TIMER_LOAD_COUNT_REG_OFFSET);
+ writel(DW_APB_TIMER_ENABLE,
+ dwclk->base + DW_APB_TIMER_CONTROL_REG_OFFSET);
+ clocksource_register_hz(&dwclk->dev, clk_get_rate(dwclk->clk));
+
+ return 0;
+
+out_put_clk:
+ clk_put(dwclk->clk);
+out_unmap:
+ iounmap(dwclk->base);
+out_release_mem:
+ release_mem_region(mem->start, resource_size(mem));
+out_free:
+ kfree(dwclk);
+
+ return err;
+}
+
+static int __devinit dw_apb_timer_probe(struct platform_device *pdev)
+{
+ int irq = platform_get_irq(pdev, 0);
+
+ /*
+ * If the timer has an interrupt defined then we use it as a
+ * clockevents device otherwise we use it as a clocksource device.
+ */
+ return irq >= 0 ? dw_apb_event_probe(pdev, irq) :
+ dw_apb_source_probe(pdev);
+}
+
+static int __devexit dw_apb_timer_remove(struct platform_device *pdev)
+{
+ return -EBUSY;
+}
+
+static struct platform_driver dw_apb_timer_driver = {
+ .probe = dw_apb_timer_probe,
+ .remove = __devexit_p(dw_apb_timer_remove),
+ .driver = {
+ .owner = THIS_MODULE,
+ .name = "dw_apb_timer",
+ },
+};
+early_platform_init("earlytimer", &dw_apb_timer_driver);
+
+
+static int __init dw_apb_timers_init(void)
+{
+ return platform_driver_register(&dw_apb_timer_driver);
+}
+module_init(dw_apb_timers_init);
+
+static void __exit dw_apb_timers_exit(void)
+{
+ platform_driver_unregister(&dw_apb_timer_driver);
+}
+module_exit(dw_apb_timers_exit);
+
+MODULE_AUTHOR("Jamie Iles");
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Timer driver for Synopsys DesignWare APB timers");
--
1.7.4


2011-03-31 15:33:18

by Thomas Gleixner

[permalink] [raw]
Subject: Re: [PATCH] clocksource: clocksource/clockevent driver for Synopsys dw_apb_timer

On Thu, 31 Mar 2011, Jamie Iles wrote:

> This patch adds a driver for the Synopsys DesignWare APB timer block
> found in some ARM systems. This uses the timers with an IRQ as a
> clockevents device and a timer without an IRQ as a clocksource
> device.

Interesting. That's probably the same thing as:

arch/x86/kernel/apb_timer.c

So if we merge that thing, then we should make sure, that we can
replace the x86 one with it.

Thanks,

tglx

2011-03-31 16:50:54

by Jacob Pan

[permalink] [raw]
Subject: Re: [PATCH] clocksource: clocksource/clockevent driver for Synopsys dw_apb_timer

On Thu, 31 Mar 2011 17:33:12 +0200 (CEST)
Thomas Gleixner <[email protected]> wrote:

> On Thu, 31 Mar 2011, Jamie Iles wrote:
>
> > This patch adds a driver for the Synopsys DesignWare APB timer block
> > found in some ARM systems. This uses the timers with an IRQ as a
> > clockevents device and a timer without an IRQ as a clocksource
> > device.
>
> Interesting. That's probably the same thing as:
>
> arch/x86/kernel/apb_timer.c
>
> So if we merge that thing, then we should make sure, that we can
> replace the x86 one with it.
>
It seems we have room to consolidate, here is my 2c:
1. need to support multiple timer channels
2. support percpu clockevent, need to deal with cpu online/offline
3. early boot needs. I don't know if abp timer is needed for booting on
ARM platforms. But for Moorestown, we need timer before platform bus
running. So I guess we cannot enumerate the timer as platform device.

--
Thanks

Jacob

2011-03-31 17:00:32

by Thomas Gleixner

[permalink] [raw]
Subject: Re: [PATCH] clocksource: clocksource/clockevent driver for Synopsys dw_apb_timer

On Thu, 31 Mar 2011, jacob pan wrote:

> On Thu, 31 Mar 2011 17:33:12 +0200 (CEST)
> Thomas Gleixner <[email protected]> wrote:
>
> > On Thu, 31 Mar 2011, Jamie Iles wrote:
> >
> > > This patch adds a driver for the Synopsys DesignWare APB timer block
> > > found in some ARM systems. This uses the timers with an IRQ as a
> > > clockevents device and a timer without an IRQ as a clocksource
> > > device.
> >
> > Interesting. That's probably the same thing as:
> >
> > arch/x86/kernel/apb_timer.c
> >
> > So if we merge that thing, then we should make sure, that we can
> > replace the x86 one with it.
> >
> It seems we have room to consolidate, here is my 2c:
> 1. need to support multiple timer channels
> 2. support percpu clockevent, need to deal with cpu online/offline
> 3. early boot needs. I don't know if abp timer is needed for booting on
> ARM platforms. But for Moorestown, we need timer before platform bus
> running. So I guess we cannot enumerate the timer as platform device.

That's a solvable problem. I just don't want to see two incarnations
of the same thing in the kernel. Can you folks hash that out together
what it needs to work everywhere please ?

Thanks,

tglx

2011-03-31 17:42:55

by Jamie Iles

[permalink] [raw]
Subject: Re: [PATCH] clocksource: clocksource/clockevent driver for Synopsys dw_apb_timer

On Thu, Mar 31, 2011 at 09:50:51AM -0700, jacob pan wrote:
> On Thu, 31 Mar 2011 17:33:12 +0200 (CEST)
> Thomas Gleixner <[email protected]> wrote:
>
> > On Thu, 31 Mar 2011, Jamie Iles wrote:
> >
> > > This patch adds a driver for the Synopsys DesignWare APB timer block
> > > found in some ARM systems. This uses the timers with an IRQ as a
> > > clockevents device and a timer without an IRQ as a clocksource
> > > device.
> >
> > Interesting. That's probably the same thing as:
> >
> > arch/x86/kernel/apb_timer.c
> >
> > So if we merge that thing, then we should make sure, that we can
> > replace the x86 one with it.
> >
> It seems we have room to consolidate, here is my 2c:
> 1. need to support multiple timer channels
> 2. support percpu clockevent, need to deal with cpu online/offline
> 3. early boot needs. I don't know if abp timer is needed for booting on
> ARM platforms. But for Moorestown, we need timer before platform bus
> running. So I guess we cannot enumerate the timer as platform device.

ARM does need it quite early to calibrate the delay loop but I've been
registering it early by creating an early_platform_device. The other
difference is that x86 doesn't support the clk API.

How about factoring out the clockevent and clocksource operations
(.set_next_event, .set_mode, .read etc) and the bits that actually drive
the hardware into an apb_timer.c then have stuff that does platform
device registration or langwell specific stuff in a higher layer?

It looks like arch/x86/kernel/apb_timer.c would still need some lower
level access to the timers for doing things like the calibration in
apbt_quick_calibrate() before registering the clocksource.

Jamie

2011-03-31 19:19:16

by Jacob Pan

[permalink] [raw]
Subject: Re: [PATCH] clocksource: clocksource/clockevent driver for Synopsys dw_apb_timer

On Thu, 31 Mar 2011 18:42:47 +0100
Jamie Iles <[email protected]> wrote:

> On Thu, Mar 31, 2011 at 09:50:51AM -0700, jacob pan wrote:
> > On Thu, 31 Mar 2011 17:33:12 +0200 (CEST)
> > Thomas Gleixner <[email protected]> wrote:
> >
> > > On Thu, 31 Mar 2011, Jamie Iles wrote:
> > >
> > > > This patch adds a driver for the Synopsys DesignWare APB timer
> > > > block found in some ARM systems. This uses the timers with an
> > > > IRQ as a clockevents device and a timer without an IRQ as a
> > > > clocksource device.
> > >
> > > Interesting. That's probably the same thing as:
> > >
> > > arch/x86/kernel/apb_timer.c
> > >
> > > So if we merge that thing, then we should make sure, that we can
> > > replace the x86 one with it.
> > >
> > It seems we have room to consolidate, here is my 2c:
> > 1. need to support multiple timer channels
> > 2. support percpu clockevent, need to deal with cpu online/offline
> > 3. early boot needs. I don't know if abp timer is needed for
> > booting on ARM platforms. But for Moorestown, we need timer before
> > platform bus running. So I guess we cannot enumerate the timer as
> > platform device.
>
> ARM does need it quite early to calibrate the delay loop but I've
> been registering it early by creating an early_platform_device. The
> other difference is that x86 doesn't support the clk API.
>
> How about factoring out the clockevent and clocksource operations
> (.set_next_event, .set_mode, .read etc) and the bits that actually
> drive the hardware into an apb_timer.c then have stuff that does
> platform device registration or langwell specific stuff in a higher
> layer?
>
I agree to extract out hardware access bits. how about also include a
common allocation/reservation mechanism for apbt timer channels. i
guess we have to stay with device specific bits. e.g.
struct apbt_dev {
int irq;
__iomem *base;
int channel_id;
unsigned int flags; /* for possible platform quirks */
};
The allocation should also allow hints or specific request a specific
timer. Our platform has hardcoded usage of timer channels.

Can you be more specific about "higher layer"?, my guess is that you
meant platform code or drivers that uses apb timer. So arch specific
timer code can alloc apb timer and combine with things that is
appropriate for their platform. e.g. clk api.


> It looks like arch/x86/kernel/apb_timer.c would still need some lower
> level access to the timers for doing things like the calibration in
> apbt_quick_calibrate() before registering the clocksource.
yeah, we still need some early access. perhaps by-pass the clocksource
as an exception. I don't have a better idea at the moment.