From: Yoshinori Sato <ysato@users.sourceforge.jp>
To: linux-kernel@vger.kernel.org, linux-arch@vger.kernel.org
Cc: Yoshinori Sato <ysato@users.sourceforge.jp>
Subject: [PATCH v10 17/19] h8300: clocksource
Date: Mon, 4 May 2015 19:42:00 +0900 [thread overview]
Message-ID: <1430736122-20929-19-git-send-email-ysato@users.sourceforge.jp> (raw)
In-Reply-To: <1430736122-20929-2-git-send-email-ysato@users.sourceforge.jp>
H8/300 internal timer driver.
h8300_timer8 - 8bit Timer driver.
h8300_timer16 - 16bit Timer driver (only H8/3069).
h8300_tpu - Timer Pulse Unit driver (only H8S2678).
Signed-off-by: Yoshinori Sato <ysato@users.sourceforge.jp>
---
.../bindings/timer/renesas,16bit-timer.txt | 25 ++
.../bindings/timer/renesas,8bit-timer.txt | 31 ++
.../devicetree/bindings/timer/renesas,tpu.txt | 21 ++
drivers/clocksource/Kconfig | 6 +
drivers/clocksource/Makefile | 3 +
drivers/clocksource/h8300_timer16.c | 335 +++++++++++++++++
drivers/clocksource/h8300_timer8.c | 405 +++++++++++++++++++++
drivers/clocksource/h8300_tpu.c | 207 +++++++++++
include/dt-bindings/clock/renesas,8bit-timer.h | 11 +
9 files changed, 1044 insertions(+)
create mode 100644 Documentation/devicetree/bindings/timer/renesas,16bit-timer.txt
create mode 100644 Documentation/devicetree/bindings/timer/renesas,8bit-timer.txt
create mode 100644 Documentation/devicetree/bindings/timer/renesas,tpu.txt
create mode 100644 drivers/clocksource/h8300_timer16.c
create mode 100644 drivers/clocksource/h8300_timer8.c
create mode 100644 drivers/clocksource/h8300_tpu.c
create mode 100644 include/dt-bindings/clock/renesas,8bit-timer.h
diff --git a/Documentation/devicetree/bindings/timer/renesas,16bit-timer.txt b/Documentation/devicetree/bindings/timer/renesas,16bit-timer.txt
new file mode 100644
index 0000000..e879244
--- /dev/null
+++ b/Documentation/devicetree/bindings/timer/renesas,16bit-timer.txt
@@ -0,0 +1,25 @@
+* Renesas H8/300 16bit timer
+
+The 16bit timer is a 16bit timer/counter with configurable clock inputs and
+programmable compare match.
+
+Required Properties:
+
+ - compatible: must contain "renesas,16bit-timer"
+ - reg: base address and length of the registers block for the timer module.
+ - interrupts: interrupt-specifier for the timer, IMIA
+ - clocks: a list of phandle, one for each entry in clock-names.
+ - clock-names: must contain "peripheral_clk" for the functional clock.
+ - renesas,channel: timer channel number.
+
+Example:
+
+ timer16: timer@ffff68 {
+ compatible = "reneas,16bit-timer";
+ reg = <0xffff68 8>, <0xffff60 8>;
+ interrupts = <24>;
+ renesas,channel = <0>;
+ clocks = <&pclk>;
+ clock-names = "peripheral_clk";
+ };
+
diff --git a/Documentation/devicetree/bindings/timer/renesas,8bit-timer.txt b/Documentation/devicetree/bindings/timer/renesas,8bit-timer.txt
new file mode 100644
index 0000000..50a42a9
--- /dev/null
+++ b/Documentation/devicetree/bindings/timer/renesas,8bit-timer.txt
@@ -0,0 +1,31 @@
+* Renesas H8/300 8bit timer
+
+The 8bit timer is a 8bit timer/counter with configurable clock inputs and
+programmable compare match.
+
+This implement only supported cascade mode.
+
+Required Properties:
+
+ - compatible: must contain "renesas,8bit-timer"
+ - reg: base address and length of the registers block for the timer module.
+ - interrupts: interrupt-specifier for the timer, CMIA and TOVI
+ - clocks: a list of phandle, one for each entry in clock-names.
+ - clock-names: must contain "fck" for the functional clock.
+
+ - renesas,mode: clocksource or clockevents
+ - renesas,div: prescaler divide rate
+
+
+Example:
+
+ timer8_0: timer@ffff80 {
+ compatible = "renesas,8bit-timer";
+ reg = <0xffff80 10>;
+ interrupts = <36 39>;
+ clocks = <&pclk>;
+ clock-names = "peripheral_clk";
+ renesas,mode = "clocksource";
+ renesas,div = <0>;
+ };
+
diff --git a/Documentation/devicetree/bindings/timer/renesas,tpu.txt b/Documentation/devicetree/bindings/timer/renesas,tpu.txt
new file mode 100644
index 0000000..f8b2589
--- /dev/null
+++ b/Documentation/devicetree/bindings/timer/renesas,tpu.txt
@@ -0,0 +1,21 @@
+* Renesas H8/300 Timer Pluse Unit
+
+The TPU is a 16bit timer/counter with configurable clock inputs and
+programmable compare match.
+This implementation support only cascade mode.
+
+Required Properties:
+
+ - compatible: must contain "renesas,tpu"
+ - reg: base address and length of the registers block in 2 channel.
+ - clocks: a list of phandle, one for each entry in clock-names.
+ - clock-names: must contain "peripheral_clk" for the functional clock.
+
+
+Example:
+ tpu: tpu@ffffe0 {
+ compatible = "renesas,tpu";
+ reg = <0xffffe0 16>, <0xfffff0 12>;
+ clocks = <&pclk>;
+ clock-names = "peripheral_clk";
+ };
diff --git a/drivers/clocksource/Kconfig b/drivers/clocksource/Kconfig
index 51d7865f..96e2513 100644
--- a/drivers/clocksource/Kconfig
+++ b/drivers/clocksource/Kconfig
@@ -258,4 +258,10 @@ config CLKSRC_PXA
help
This enables OST0 support available on PXA and SA-11x0
platforms.
+config H8300_TMR16
+ bool
+
+config H8300_TPU
+ bool
+
endmenu
diff --git a/drivers/clocksource/Makefile b/drivers/clocksource/Makefile
index 5b85f6a..8288c4e 100644
--- a/drivers/clocksource/Makefile
+++ b/drivers/clocksource/Makefile
@@ -52,3 +52,6 @@ obj-$(CONFIG_ARCH_INTEGRATOR_AP) += timer-integrator-ap.o
obj-$(CONFIG_CLKSRC_VERSATILE) += versatile.o
obj-$(CONFIG_CLKSRC_MIPS_GIC) += mips-gic-timer.o
obj-$(CONFIG_ASM9260_TIMER) += asm9260_timer.o
+obj-$(CONFIG_H8300) += h8300_timer8.o
+obj-$(CONFIG_H8300_TMR16) += h8300_timer16.o
+obj-$(CONFIG_H8300_TPU) += h8300_tpu.o
diff --git a/drivers/clocksource/h8300_timer16.c b/drivers/clocksource/h8300_timer16.c
new file mode 100644
index 0000000..f81f29d
--- /dev/null
+++ b/drivers/clocksource/h8300_timer16.c
@@ -0,0 +1,335 @@
+/*
+ * H8/300 16bit Timer driver
+ *
+ * Copyright 2015 Yoshinori Sato <ysato@users.sourcefoge.jp>
+ */
+
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/param.h>
+#include <linux/string.h>
+#include <linux/slab.h>
+#include <linux/interrupt.h>
+#include <linux/init.h>
+#include <linux/platform_device.h>
+#include <linux/clockchips.h>
+#include <linux/module.h>
+#include <linux/clk.h>
+#include <linux/io.h>
+#include <linux/of.h>
+
+#include <asm/segment.h>
+#include <asm/irq.h>
+
+#define TSTR 0
+#define TSNC 1
+#define TMDR 2
+#define TOLR 3
+#define TISRA 4
+#define TISRB 5
+#define TISRC 6
+
+#define TCR 0
+#define TIOR 1
+#define TCNT 2
+#define GRA 4
+#define GRB 6
+
+#define FLAG_REPROGRAM (1 << 0)
+#define FLAG_SKIPEVENT (1 << 1)
+#define FLAG_IRQCONTEXT (1 << 2)
+#define FLAG_STARTED (1 << 3)
+
+#define ONESHOT 0
+#define PERIODIC 1
+
+#define RELATIVE 0
+#define ABSOLUTE 1
+
+struct timer16_priv {
+ struct platform_device *pdev;
+ struct clock_event_device ced;
+ struct irqaction irqaction;
+ unsigned long mapbase;
+ unsigned long mapcommon;
+ unsigned long flags;
+ unsigned int rate;
+ unsigned short gra;
+ unsigned char enb;
+ unsigned char imfa;
+ unsigned char imiea;
+ unsigned char ovf;
+ raw_spinlock_t lock;
+ struct clk *clk;
+};
+
+static unsigned long timer16_get_counter(struct timer16_priv *p)
+{
+ unsigned long v1, v2, v3;
+ int o1, o2;
+
+ o1 = ctrl_inb(p->mapcommon + TISRC) & p->ovf;
+
+ /* Make sure the timer value is stable. Stolen from acpi_pm.c */
+ do {
+ o2 = o1;
+ v1 = ctrl_inw(p->mapbase + TCNT);
+ v2 = ctrl_inw(p->mapbase + TCNT);
+ v3 = ctrl_inw(p->mapbase + TCNT);
+ o1 = ctrl_inb(p->mapcommon + TISRC) & p->ovf;
+ } while (unlikely((o1 != o2) || (v1 > v2 && v1 < v3)
+ || (v2 > v3 && v2 < v1) || (v3 > v1 && v3 < v2)));
+
+ v2 |= 0x10000;
+ return v2;
+}
+
+
+static irqreturn_t timer16_interrupt(int irq, void *dev_id)
+{
+ struct timer16_priv *p = (struct timer16_priv *)dev_id;
+
+ ctrl_outb(ctrl_inb(p->mapcommon + TISRA) & ~p->imfa,
+ p->mapcommon + TISRA);
+
+ p->flags |= FLAG_IRQCONTEXT;
+ ctrl_outw(p->gra, p->mapbase + GRA);
+ if (!(p->flags & FLAG_SKIPEVENT)) {
+ if (p->ced.mode == CLOCK_EVT_MODE_ONESHOT) {
+ ctrl_outb(ctrl_inb(p->mapcommon + TSTR) & ~p->enb,
+ p->mapcommon + TISRA);
+ }
+ p->ced.event_handler(&p->ced);
+ }
+
+ p->flags &= ~(FLAG_SKIPEVENT | FLAG_IRQCONTEXT);
+
+ return IRQ_HANDLED;
+}
+
+static void timer16_set_next(struct timer16_priv *p, unsigned long delta)
+{
+ unsigned long flags;
+ unsigned long now;
+
+ raw_spin_lock_irqsave(&p->lock, flags);
+ if (delta >= 0x10000)
+ dev_warn(&p->pdev->dev, "delta out of range\n");
+ now = timer16_get_counter(p);
+ p->gra = delta;
+ ctrl_outb(ctrl_inb(p->mapcommon + TISRA) | p->imiea,
+ p->mapcommon + TISRA);
+ if (delta > now)
+ ctrl_outw(delta, p->mapbase + GRA);
+ else
+ ctrl_outw(now + 1, p->mapbase + GRA);
+
+ raw_spin_unlock_irqrestore(&p->lock, flags);
+}
+
+static int timer16_enable(struct timer16_priv *p)
+{
+ p->rate = clk_get_rate(p->clk) / 8;
+ ctrl_outw(0xffff, p->mapbase + GRA);
+ ctrl_outw(0x0000, p->mapbase + TCNT);
+ ctrl_outb(0xa3, p->mapbase + TCR);
+ ctrl_outb(ctrl_inb(p->mapcommon + TSTR) | p->enb,
+ p->mapcommon + TSTR);
+
+ return 0;
+}
+
+static int timer16_start(struct timer16_priv *p)
+{
+ int ret = 0;
+ unsigned long flags;
+
+ raw_spin_lock_irqsave(&p->lock, flags);
+
+ if (!(p->flags & FLAG_STARTED))
+ ret = timer16_enable(p);
+
+ if (ret)
+ goto out;
+ p->flags |= FLAG_STARTED;
+
+ out:
+ raw_spin_unlock_irqrestore(&p->lock, flags);
+
+ return ret;
+}
+
+static void timer16_stop(struct timer16_priv *p)
+{
+ unsigned long flags;
+
+ raw_spin_lock_irqsave(&p->lock, flags);
+
+ ctrl_outb(ctrl_inb(p->mapcommon + TSTR) & ~p->enb,
+ p->mapcommon + TSTR);
+
+ raw_spin_unlock_irqrestore(&p->lock, flags);
+}
+
+static inline struct timer16_priv *ced_to_priv(struct clock_event_device *ced)
+{
+ return container_of(ced, struct timer16_priv, ced);
+}
+
+static void timer16_clock_event_start(struct timer16_priv *p, int periodic)
+{
+ struct clock_event_device *ced = &p->ced;
+
+ timer16_start(p);
+
+ ced->shift = 32;
+ ced->mult = div_sc(p->rate, NSEC_PER_SEC, ced->shift);
+ ced->max_delta_ns = clockevent_delta2ns(0xffff, ced);
+ ced->min_delta_ns = clockevent_delta2ns(0x0001, ced);
+
+ timer16_set_next(p, periodic?(p->rate + HZ/2) / HZ:0x10000);
+}
+
+static void timer16_clock_event_mode(enum clock_event_mode mode,
+ struct clock_event_device *ced)
+{
+ struct timer16_priv *p = ced_to_priv(ced);
+
+ switch (mode) {
+ case CLOCK_EVT_MODE_PERIODIC:
+ dev_info(&p->pdev->dev, "used for periodic clock events\n");
+ timer16_stop(p);
+ timer16_clock_event_start(p, PERIODIC);
+ break;
+ case CLOCK_EVT_MODE_ONESHOT:
+ dev_info(&p->pdev->dev, "used for oneshot clock events\n");
+ timer16_stop(p);
+ timer16_clock_event_start(p, ONESHOT);
+ break;
+ case CLOCK_EVT_MODE_SHUTDOWN:
+ case CLOCK_EVT_MODE_UNUSED:
+ timer16_stop(p);
+ break;
+ default:
+ break;
+ }
+}
+
+static int timer16_clock_event_next(unsigned long delta,
+ struct clock_event_device *ced)
+{
+ struct timer16_priv *p = ced_to_priv(ced);
+
+ BUG_ON(ced->mode != CLOCK_EVT_MODE_ONESHOT);
+ timer16_set_next(p, delta - 1);
+
+ return 0;
+}
+
+#define REG_CH 0
+#define REG_COMM 1
+
+static int timer16_setup(struct timer16_priv *p, struct platform_device *pdev)
+{
+ struct resource *res[2];
+ int ret, irq;
+ unsigned int ch;
+
+ memset(p, 0, sizeof(*p));
+ p->pdev = pdev;
+
+ res[REG_CH] = platform_get_resource(p->pdev,
+ IORESOURCE_MEM, REG_CH);
+ res[REG_COMM] = platform_get_resource(p->pdev,
+ IORESOURCE_MEM, REG_COMM);
+ if (!res[REG_CH] || !res[REG_COMM]) {
+ dev_err(&p->pdev->dev, "failed to get I/O memory\n");
+ return -ENXIO;
+ }
+ irq = platform_get_irq(p->pdev, 0);
+ if (irq < 0) {
+ dev_err(&p->pdev->dev, "failed to get irq\n");
+ return irq;
+ }
+
+ p->clk = clk_get(&p->pdev->dev, "peripheral_clk");
+ if (IS_ERR(p->clk)) {
+ dev_err(&p->pdev->dev, "can't get clk\n");
+ return PTR_ERR(p->clk);
+ }
+ of_property_read_u32(p->pdev->dev.of_node, "renesas,channel", &ch);
+
+ p->pdev = pdev;
+ p->mapbase = res[REG_CH]->start;
+ p->mapcommon = res[REG_COMM]->start;
+ p->enb = 1 << ch;
+ p->imfa = 1 << ch;
+ p->imiea = 1 << (4 + ch);
+ p->ced.name = pdev->name;
+ p->ced.features = CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT;
+ p->ced.rating = 200;
+ p->ced.cpumask = cpumask_of(0);
+ p->ced.set_next_event = timer16_clock_event_next;
+ p->ced.set_mode = timer16_clock_event_mode;
+
+ ret = request_irq(irq, timer16_interrupt,
+ IRQF_TIMER, pdev->name, p);
+ if (ret < 0) {
+ dev_err(&p->pdev->dev, "failed to request irq %d\n", irq);
+ return ret;
+ }
+
+ clockevents_register_device(&p->ced);
+
+ return 0;
+}
+
+static int timer16_probe(struct platform_device *pdev)
+{
+ struct timer16_priv *p = platform_get_drvdata(pdev);
+
+ if (p) {
+ dev_info(&pdev->dev, "kept as earlytimer\n");
+ return 0;
+ }
+
+ p = devm_kzalloc(&pdev->dev, sizeof(*p), GFP_KERNEL);
+ if (!p)
+ return -ENOMEM;
+
+ return timer16_setup(p, pdev);
+}
+
+static int timer16_remove(struct platform_device *pdev)
+{
+ return -EBUSY;
+}
+
+static const struct of_device_id timer16_of_table[] = {
+ { .compatible = "renesas,16bit-timer" },
+ { }
+};
+static struct platform_driver timer16_driver = {
+ .probe = timer16_probe,
+ .remove = timer16_remove,
+ .driver = {
+ .name = "h8300h-16timer",
+ .of_match_table = of_match_ptr(timer16_of_table),
+ }
+};
+
+static int __init timer16_init(void)
+{
+ return platform_driver_register(&timer16_driver);
+}
+
+static void __exit timer16_exit(void)
+{
+ platform_driver_unregister(&timer16_driver);
+}
+
+subsys_initcall(timer16_init);
+module_exit(timer16_exit);
+MODULE_AUTHOR("Yoshinori Sato");
+MODULE_DESCRIPTION("H8/300H 16bit Timer Driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/clocksource/h8300_timer8.c b/drivers/clocksource/h8300_timer8.c
new file mode 100644
index 0000000..4859033
--- /dev/null
+++ b/drivers/clocksource/h8300_timer8.c
@@ -0,0 +1,405 @@
+/*
+ * linux/arch/h8300/kernel/cpu/timer/timer8.c
+ *
+ * Yoshinori Sato <ysato@users.sourcefoge.jp>
+ *
+ * 8bit Timer driver
+ *
+ */
+
+#include <linux/errno.h>
+#include <linux/sched.h>
+#include <linux/kernel.h>
+#include <linux/interrupt.h>
+#include <linux/init.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/clocksource.h>
+#include <linux/clockchips.h>
+#include <linux/module.h>
+#include <linux/clk.h>
+#include <linux/io.h>
+#include <linux/of.h>
+
+#include <asm/irq.h>
+
+#include <dt-bindings/clock/renesas,8bit-timer.h>
+
+#define _8TCR 0
+#define _8TCSR 2
+#define TCORA 4
+#define TCORB 6
+#define _8TCNT 8
+
+#define FLAG_REPROGRAM (1 << 0)
+#define FLAG_SKIPEVENT (1 << 1)
+#define FLAG_IRQCONTEXT (1 << 2)
+#define FLAG_STARTED (1 << 3)
+
+#define ONESHOT 0
+#define PERIODIC 1
+
+#define RELATIVE 0
+#define ABSOLUTE 1
+
+struct timer8_priv {
+ struct platform_device *pdev;
+ int mode;
+ union {
+ struct clocksource cs;
+ struct clock_event_device ced;
+ } clk;
+ struct irqaction irqaction;
+ unsigned long mapbase;
+ raw_spinlock_t lock;
+ unsigned long total_cycles;
+ unsigned int cs_enabled;
+ unsigned long flags;
+ unsigned int rate;
+ unsigned short tcora;
+ unsigned short div;
+ struct clk *pclk;
+};
+
+static const int div_rate[] = {8, 64, 8192};
+
+static unsigned long timer8_get_counter(struct timer8_priv *p)
+{
+ unsigned long v1, v2, v3;
+ int o1, o2;
+
+ o1 = ctrl_inb(p->mapbase + _8TCSR) & 0x20;
+
+ /* Make sure the timer value is stable. Stolen from acpi_pm.c */
+ do {
+ o2 = o1;
+ v1 = ctrl_inw(p->mapbase + _8TCNT);
+ v2 = ctrl_inw(p->mapbase + _8TCNT);
+ v3 = ctrl_inw(p->mapbase + _8TCNT);
+ o1 = ctrl_inb(p->mapbase + _8TCSR) & 0x20;
+ } while (unlikely((o1 != o2) || (v1 > v2 && v1 < v3)
+ || (v2 > v3 && v2 < v1) || (v3 > v1 && v3 < v2)));
+
+ v2 |= o1 << 10;
+ return v2;
+}
+
+static irqreturn_t timer8_interrupt(int irq, void *dev_id)
+{
+ struct timer8_priv *p = dev_id;
+
+ switch (p->mode) {
+ case CLOCKSOURCE:
+ ctrl_outb(ctrl_inb(p->mapbase + _8TCSR) & ~0x20,
+ p->mapbase + _8TCSR);
+ p->total_cycles += 0x10000;
+ break;
+ case CLOCKEVENTDEVICE:
+ ctrl_outb(ctrl_inb(p->mapbase + _8TCSR) & ~0x40,
+ p->mapbase + _8TCSR);
+ p->flags |= FLAG_IRQCONTEXT;
+ ctrl_outw(p->tcora, p->mapbase + TCORA);
+ if (!(p->flags & FLAG_SKIPEVENT)) {
+ if (p->clk.ced.mode == CLOCK_EVT_MODE_ONESHOT)
+ ctrl_outw(0x0000, p->mapbase + _8TCR);
+ p->clk.ced.event_handler(&p->clk.ced);
+ }
+ p->flags &= ~(FLAG_SKIPEVENT | FLAG_IRQCONTEXT);
+ break;
+ }
+
+ return IRQ_HANDLED;
+}
+
+static inline struct timer8_priv *cs_to_priv(struct clocksource *cs)
+{
+ return container_of(cs, struct timer8_priv, clk.cs);
+}
+
+static cycle_t timer8_clocksource_read(struct clocksource *cs)
+{
+ struct timer8_priv *p = cs_to_priv(cs);
+ unsigned long flags, raw;
+ unsigned long value;
+
+ raw_spin_lock_irqsave(&p->lock, flags);
+ value = p->total_cycles;
+ raw = timer8_get_counter(p);
+ raw_spin_unlock_irqrestore(&p->lock, flags);
+
+ return value + raw;
+}
+
+static int timer8_clocksource_enable(struct clocksource *cs)
+{
+ struct timer8_priv *p = cs_to_priv(cs);
+
+ WARN_ON(p->cs_enabled);
+
+ p->total_cycles = 0;
+ ctrl_outw(0, p->mapbase + _8TCNT);
+ ctrl_outw(0x2400 | p->div, p->mapbase + _8TCR);
+
+ p->cs_enabled = true;
+ return 0;
+}
+
+static void timer8_clocksource_disable(struct clocksource *cs)
+{
+ struct timer8_priv *p = cs_to_priv(cs);
+
+ WARN_ON(!p->cs_enabled);
+
+ ctrl_outb(0, p->mapbase + _8TCR);
+ p->cs_enabled = false;
+}
+
+static void timer8_set_next(struct timer8_priv *p, unsigned long delta)
+{
+ unsigned long flags;
+ unsigned long now;
+
+ raw_spin_lock_irqsave(&p->lock, flags);
+ if (delta >= 0x10000)
+ dev_warn(&p->pdev->dev, "delta out of range\n");
+ now = timer8_get_counter(p);
+ p->tcora = delta;
+ ctrl_outb(ctrl_inb(p->mapbase + _8TCR) | 0x40, p->mapbase + _8TCR);
+ if (delta > now)
+ ctrl_outw(delta, p->mapbase + TCORA);
+ else
+ ctrl_outw(now + 1, p->mapbase + TCORA);
+
+ raw_spin_unlock_irqrestore(&p->lock, flags);
+}
+
+static int timer8_enable(struct timer8_priv *p)
+{
+ p->rate = clk_get_rate(p->pclk) / div_rate[p->div];
+ ctrl_outw(0xffff, p->mapbase + TCORA);
+ ctrl_outw(0x0000, p->mapbase + _8TCNT);
+ ctrl_outw(0x0c01, p->mapbase + _8TCR);
+
+ return 0;
+}
+
+static int timer8_start(struct timer8_priv *p)
+{
+ int ret = 0;
+ unsigned long flags;
+
+ raw_spin_lock_irqsave(&p->lock, flags);
+
+ if (!(p->flags & FLAG_STARTED))
+ ret = timer8_enable(p);
+
+ if (ret)
+ goto out;
+ p->flags |= FLAG_STARTED;
+
+ out:
+ raw_spin_unlock_irqrestore(&p->lock, flags);
+
+ return ret;
+}
+
+static void timer8_stop(struct timer8_priv *p)
+{
+ unsigned long flags;
+
+ raw_spin_lock_irqsave(&p->lock, flags);
+
+ ctrl_outw(0x0000, p->mapbase + _8TCR);
+
+ raw_spin_unlock_irqrestore(&p->lock, flags);
+}
+
+static inline struct timer8_priv *ced_to_priv(struct clock_event_device *ced)
+{
+ return container_of(ced, struct timer8_priv, clk.ced);
+}
+
+static void timer8_clock_event_start(struct timer8_priv *p, int periodic)
+{
+ struct clock_event_device *ced = &p->clk.ced;
+
+ timer8_start(p);
+
+ ced->shift = 32;
+ ced->mult = div_sc(p->rate, NSEC_PER_SEC, ced->shift);
+ ced->max_delta_ns = clockevent_delta2ns(0xffff, ced);
+ ced->min_delta_ns = clockevent_delta2ns(0x0001, ced);
+
+ timer8_set_next(p, periodic?(p->rate + HZ/2) / HZ:0x10000);
+}
+
+static void timer8_clock_event_mode(enum clock_event_mode mode,
+ struct clock_event_device *ced)
+{
+ struct timer8_priv *p = ced_to_priv(ced);
+
+ switch (mode) {
+ case CLOCK_EVT_MODE_PERIODIC:
+ dev_info(&p->pdev->dev, "used for periodic clock events\n");
+ timer8_stop(p);
+ timer8_clock_event_start(p, PERIODIC);
+ break;
+ case CLOCK_EVT_MODE_ONESHOT:
+ dev_info(&p->pdev->dev, "used for oneshot clock events\n");
+ timer8_stop(p);
+ timer8_clock_event_start(p, ONESHOT);
+ break;
+ case CLOCK_EVT_MODE_SHUTDOWN:
+ case CLOCK_EVT_MODE_UNUSED:
+ timer8_stop(p);
+ break;
+ default:
+ break;
+ }
+}
+
+static int timer8_clock_event_next(unsigned long delta,
+ struct clock_event_device *ced)
+{
+ struct timer8_priv *p = ced_to_priv(ced);
+
+ BUG_ON(ced->mode != CLOCK_EVT_MODE_ONESHOT);
+ timer8_set_next(p, delta - 1);
+
+ return 0;
+}
+
+#define CMI 0
+#define OVI 1
+static int timer8_setup(struct timer8_priv *p,
+ struct platform_device *pdev)
+{
+ struct resource *res;
+ int irq[2];
+ int ret;
+
+ memset(p, 0, sizeof(*p));
+ p->pdev = pdev;
+
+ res = platform_get_resource(p->pdev, IORESOURCE_MEM, 0);
+ if (!res) {
+ dev_err(&p->pdev->dev, "failed to get I/O memory\n");
+ return -ENXIO;
+ }
+
+ irq[CMI] = platform_get_irq(p->pdev, CMI);
+ irq[OVI] = platform_get_irq(p->pdev, OVI);
+ if (irq[CMI] < 0 || irq[OVI] < 0) {
+ dev_err(&p->pdev->dev, "failed to get irq\n");
+ return -ENXIO;
+ }
+
+ p->mapbase = res->start;
+
+ p->irqaction.name = dev_name(&p->pdev->dev);
+ p->irqaction.handler = timer8_interrupt;
+ p->irqaction.dev_id = p;
+ p->irqaction.flags = IRQF_TIMER;
+
+ of_property_read_s32(p->pdev->dev.of_node, "renesas,mode", &p->mode);
+ of_property_read_u16(p->pdev->dev.of_node, "renesas,div", &p->div);
+
+ p->pclk = clk_get(&p->pdev->dev, "peripheral_clk");
+ if (IS_ERR(p->pclk)) {
+ dev_err(&p->pdev->dev, "can't get clk\n");
+ return PTR_ERR(p->pclk);
+ }
+
+ switch (p->mode) {
+ case CLOCKSOURCE:
+ p->clk.cs.name = pdev->name;
+ p->clk.cs.rating = 200;
+ p->clk.cs.read = timer8_clocksource_read;
+ p->clk.cs.enable = timer8_clocksource_enable;
+ p->clk.cs.disable = timer8_clocksource_disable;
+ p->clk.cs.mask = CLOCKSOURCE_MASK(sizeof(unsigned long) * 8);
+ p->clk.cs.flags = CLOCK_SOURCE_IS_CONTINUOUS;
+
+ ret = setup_irq(irq[OVI], &p->irqaction);
+ if (ret < 0) {
+ dev_err(&p->pdev->dev,
+ "failed to request irq %d\n", irq[OVI]);
+ return ret;
+ }
+ clocksource_register_hz(&p->clk.cs,
+ clk_get_rate(p->pclk) / div_rate[p->div]);
+ break;
+ case CLOCKEVENTDEVICE:
+ p->clk.ced.name = pdev->name;
+ p->clk.ced.features = CLOCK_EVT_FEAT_PERIODIC |
+ CLOCK_EVT_FEAT_ONESHOT;
+ p->clk.ced.rating = 200;
+ p->clk.ced.cpumask = cpumask_of(0);
+ p->clk.ced.set_next_event = timer8_clock_event_next;
+ p->clk.ced.set_mode = timer8_clock_event_mode;
+
+ ret = setup_irq(irq[CMI], &p->irqaction);
+ if (ret < 0) {
+ dev_err(&p->pdev->dev,
+ "failed to request irq %d\n", irq[CMI]);
+ return ret;
+ }
+ clockevents_register_device(&p->clk.ced);
+ break;
+ }
+ platform_set_drvdata(pdev, p);
+
+ return 0;
+}
+
+static int timer8_probe(struct platform_device *pdev)
+{
+ struct timer8_priv *p = platform_get_drvdata(pdev);
+
+ if (p) {
+ dev_info(&pdev->dev, "kept as earlytimer\n");
+ return 0;
+ }
+
+ p = devm_kzalloc(&pdev->dev, sizeof(*p), GFP_KERNEL);
+ if (!p)
+ return -ENOMEM;
+
+ return timer8_setup(p, pdev);
+}
+
+static int timer8_remove(struct platform_device *pdev)
+{
+ return -EBUSY;
+}
+
+static const struct of_device_id timer8_of_table[] __maybe_unused = {
+ { .compatible = "renesas,8bit-timer" },
+ { }
+};
+
+MODULE_DEVICE_TABLE(of, sh_cmt_of_table);
+static struct platform_driver timer8_driver = {
+ .probe = timer8_probe,
+ .remove = timer8_remove,
+ .driver = {
+ .name = "h8300-8timer",
+ .of_match_table = of_match_ptr(timer8_of_table),
+ }
+};
+
+static int __init timer8_init(void)
+{
+ return platform_driver_register(&timer8_driver);
+}
+
+static void __exit timer8_exit(void)
+{
+ platform_driver_unregister(&timer8_driver);
+}
+
+subsys_initcall(timer8_init);
+module_exit(timer8_exit);
+MODULE_AUTHOR("Yoshinori Sato");
+MODULE_DESCRIPTION("H8/300 8bit Timer Driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/clocksource/h8300_tpu.c b/drivers/clocksource/h8300_tpu.c
new file mode 100644
index 0000000..a32a4fd
--- /dev/null
+++ b/drivers/clocksource/h8300_tpu.c
@@ -0,0 +1,207 @@
+/*
+ * H8/300 TPU Driver
+ *
+ * Copyright 2015 Yoshinori Sato <ysato@users.sourcefoge.jp>
+ *
+ */
+
+#include <linux/errno.h>
+#include <linux/sched.h>
+#include <linux/kernel.h>
+#include <linux/interrupt.h>
+#include <linux/init.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/clocksource.h>
+#include <linux/module.h>
+#include <linux/clk.h>
+#include <linux/io.h>
+#include <linux/of.h>
+
+#include <asm/irq.h>
+
+#define TCR 0
+#define TMDR 1
+#define TIOR 2
+#define TER 4
+#define TSR 5
+#define TCNT 6
+#define TGRA 8
+#define TGRB 10
+#define TGRC 12
+#define TGRD 14
+
+struct tpu_priv {
+ struct platform_device *pdev;
+ struct clocksource cs;
+ struct clk *clk;
+ unsigned long mapbase1;
+ unsigned long mapbase2;
+ raw_spinlock_t lock;
+ unsigned int cs_enabled;
+};
+
+static inline unsigned long read_tcnt32(struct tpu_priv *p)
+{
+ unsigned long tcnt;
+
+ tcnt = ctrl_inw(p->mapbase1 + TCNT) << 16;
+ tcnt |= ctrl_inw(p->mapbase2 + TCNT);
+ return tcnt;
+}
+
+static int tpu_get_counter(struct tpu_priv *p, unsigned long long *val)
+{
+ unsigned long v1, v2, v3;
+ int o1, o2;
+
+ o1 = ctrl_inb(p->mapbase1 + TSR) & 0x10;
+
+ /* Make sure the timer value is stable. Stolen from acpi_pm.c */
+ do {
+ o2 = o1;
+ v1 = read_tcnt32(p);
+ v2 = read_tcnt32(p);
+ v3 = read_tcnt32(p);
+ o1 = ctrl_inb(p->mapbase1 + TSR) & 0x10;
+ } while (unlikely((o1 != o2) || (v1 > v2 && v1 < v3)
+ || (v2 > v3 && v2 < v1) || (v3 > v1 && v3 < v2)));
+
+ *val = v2;
+ return o1;
+}
+
+static inline struct tpu_priv *cs_to_priv(struct clocksource *cs)
+{
+ return container_of(cs, struct tpu_priv, cs);
+}
+
+static cycle_t tpu_clocksource_read(struct clocksource *cs)
+{
+ struct tpu_priv *p = cs_to_priv(cs);
+ unsigned long flags;
+ unsigned long long value;
+
+ raw_spin_lock_irqsave(&p->lock, flags);
+ if (tpu_get_counter(p, &value))
+ value += 0x100000000;
+ raw_spin_unlock_irqrestore(&p->lock, flags);
+
+ return value;
+}
+
+static int tpu_clocksource_enable(struct clocksource *cs)
+{
+ struct tpu_priv *p = cs_to_priv(cs);
+
+ WARN_ON(p->cs_enabled);
+
+ ctrl_outw(0, p->mapbase1 + TCNT);
+ ctrl_outw(0, p->mapbase2 + TCNT);
+ ctrl_outb(0x0f, p->mapbase1 + TCR);
+ ctrl_outb(0x03, p->mapbase2 + TCR);
+
+ p->cs_enabled = true;
+ return 0;
+}
+
+static void tpu_clocksource_disable(struct clocksource *cs)
+{
+ struct tpu_priv *p = cs_to_priv(cs);
+
+ WARN_ON(!p->cs_enabled);
+
+ ctrl_outb(0, p->mapbase1 + TCR);
+ ctrl_outb(0, p->mapbase2 + TCR);
+ p->cs_enabled = false;
+}
+
+#define CH_L 0
+#define CH_H 1
+
+static int __init tpu_setup(struct tpu_priv *p, struct platform_device *pdev)
+{
+ struct resource *res[2];
+
+ memset(p, 0, sizeof(*p));
+ p->pdev = pdev;
+
+ res[CH_L] = platform_get_resource(p->pdev, IORESOURCE_MEM, CH_L);
+ res[CH_H] = platform_get_resource(p->pdev, IORESOURCE_MEM, CH_H);
+ if (!res[CH_L] || !res[CH_H]) {
+ dev_err(&p->pdev->dev, "failed to get I/O memory\n");
+ return -ENXIO;
+ }
+
+ p->clk = clk_get(&p->pdev->dev, "peripheral_clk");
+ if (IS_ERR(p->clk)) {
+ dev_err(&p->pdev->dev, "can't get clk\n");
+ return PTR_ERR(p->clk);
+ }
+
+ p->mapbase1 = res[CH_L]->start;
+ p->mapbase2 = res[CH_H]->start;
+
+ p->cs.name = pdev->name;
+ p->cs.rating = 200;
+ p->cs.read = tpu_clocksource_read;
+ p->cs.enable = tpu_clocksource_enable;
+ p->cs.disable = tpu_clocksource_disable;
+ p->cs.mask = CLOCKSOURCE_MASK(sizeof(unsigned long) * 8);
+ p->cs.flags = CLOCK_SOURCE_IS_CONTINUOUS;
+ clocksource_register_hz(&p->cs, clk_get_rate(p->clk) / 64);
+ platform_set_drvdata(pdev, p);
+
+ return 0;
+}
+
+static int tpu_probe(struct platform_device *pdev)
+{
+ struct tpu_priv *p = platform_get_drvdata(pdev);
+
+ if (p) {
+ dev_info(&pdev->dev, "kept as earlytimer\n");
+ return 0;
+ }
+
+ p = devm_kzalloc(&pdev->dev, sizeof(*p), GFP_KERNEL);
+ if (!p)
+ return -ENOMEM;
+
+ return tpu_setup(p, pdev);
+}
+
+static int tpu_remove(struct platform_device *pdev)
+{
+ return -EBUSY;
+}
+
+static const struct of_device_id tpu_of_table[] = {
+ { .compatible = "renesas,tpu" },
+ { }
+};
+
+static struct platform_driver tpu_driver = {
+ .probe = tpu_probe,
+ .remove = tpu_remove,
+ .driver = {
+ .name = "h8s-tpu",
+ .of_match_table = of_match_ptr(tpu_of_table),
+ }
+};
+
+static int __init tpu_init(void)
+{
+ return platform_driver_register(&tpu_driver);
+}
+
+static void __exit tpu_exit(void)
+{
+ platform_driver_unregister(&tpu_driver);
+}
+
+subsys_initcall(tpu_init);
+module_exit(tpu_exit);
+MODULE_AUTHOR("Yoshinori Sato");
+MODULE_DESCRIPTION("H8S Timer Pulse Unit Driver");
+MODULE_LICENSE("GPL v2");
diff --git a/include/dt-bindings/clock/renesas,8bit-timer.h b/include/dt-bindings/clock/renesas,8bit-timer.h
new file mode 100644
index 0000000..2aefaa8
--- /dev/null
+++ b/include/dt-bindings/clock/renesas,8bit-timer.h
@@ -0,0 +1,11 @@
+#ifndef __DT_BINDINGS_CLOCK_RENESAS_8BIT_TIMER_H__
+#define __DT_BINDINGS_CLOCK_RENESAS_8BIT_TIMER_H__
+
+#define CLOCKSOURCE 0
+#define CLOCKEVENTDEVICE 1
+
+#define DIV_8 0
+#define DIV_64 1
+#define DIV_8192 2
+
+#endif
--
2.1.4
next prev parent reply other threads:[~2015-05-04 10:42 UTC|newest]
Thread overview: 32+ messages / expand[flat|nested] mbox.gz Atom feed top
2015-05-04 10:41 [PATCH v10 00/19] Re-introduce h8300 architecture Yoshinori Sato
2015-05-04 10:41 ` [PATCH v10 01/19] MAINTENRS: Add h8300 Yoshinori Sato
2015-05-04 10:41 ` [PATCH v10 02/19] Add H8/300 ELF Machine Yoshinori Sato
2015-05-04 10:41 ` [PATCH v10 03/19] mksysmap: Avoid h8300's local symbol Yoshinori Sato
2015-05-04 10:41 ` [PATCH v10 04/19] sh-sci: Add H8/300 SCI Yoshinori Sato
2015-05-04 10:41 ` Yoshinori Sato
2015-05-04 13:58 ` Geert Uytterhoeven
2015-05-04 13:58 ` Geert Uytterhoeven
2015-05-04 10:41 ` [PATCH v10 05/19] Add common asm-offsets.h Yoshinori Sato
2015-05-04 14:46 ` Arnd Bergmann
2015-05-07 4:24 ` Yoshinori Sato
2015-05-04 10:41 ` [PATCH v10 06/19] h8300: Assembly headers Yoshinori Sato
2015-05-04 10:41 ` [PATCH v10 07/19] h8300: UAPI assembly headers Yoshinori Sato
2015-05-04 10:41 ` [PATCH v10 08/19] h8300: Exception and Interrupt handling Yoshinori Sato
2015-05-04 10:41 ` [PATCH v10 09/19] h8300: kernel booting Yoshinori Sato
2015-05-04 10:41 ` [PATCH v10 10/19] h8300: process and signals Yoshinori Sato
2015-05-04 10:41 ` [PATCH v10 11/19] h8300: CPU depend helpers Yoshinori Sato
2015-05-04 10:41 ` [PATCH v10 12/19] h8300: miscellaneous functions Yoshinori Sato
2015-05-04 10:41 ` [PATCH v10 13/19] h8300: Memory management Yoshinori Sato
2015-05-04 10:41 ` [PATCH v10 14/19] h8300: Library functions Yoshinori Sato
2015-05-04 10:41 ` [PATCH v10 15/19] h8300: Build scripts Yoshinori Sato
2015-05-04 10:41 ` [PATCH v10 16/19] h8300: clock driver Yoshinori Sato
2015-05-04 10:42 ` Yoshinori Sato [this message]
2015-05-04 10:42 ` [PATCH v10 18/19] h8300: configs Yoshinori Sato
2015-05-04 10:42 ` [PATCH v10 19/19] h8300: devicetree source Yoshinori Sato
[not found] ` <1430736122-20929-21-git-send-email-ysato-Rn4VEauK+AKRv+LV9MX5uooqe+aC9MnS@public.gmane.org>
2015-05-04 12:40 ` Geert Uytterhoeven
2015-05-04 12:40 ` Geert Uytterhoeven
2015-05-04 15:09 ` Arnd Bergmann
2015-05-07 4:47 ` Yoshinori Sato
2015-05-04 15:28 ` [PATCH v10 00/19] Re-introduce h8300 architecture Arnd Bergmann
2015-05-05 2:27 ` Guenter Roeck
2015-05-07 4:22 ` Yoshinori Sato
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=1430736122-20929-19-git-send-email-ysato@users.sourceforge.jp \
--to=ysato@users.sourceforge.jp \
--cc=linux-arch@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.