diff options
| author | Meizu OpenSource <patchwork@meizu.com> | 2016-08-15 10:19:42 +0800 |
|---|---|---|
| committer | Meizu OpenSource <patchwork@meizu.com> | 2016-08-15 10:19:42 +0800 |
| commit | d2e1446d81725c351dc73a03b397ce043fb18452 (patch) | |
| tree | 4dbc616b7f92aea39cd697a9084205ddb805e344 /drivers/clocksource | |
first commit
Diffstat (limited to 'drivers/clocksource')
32 files changed, 8586 insertions, 0 deletions
diff --git a/drivers/clocksource/Kconfig b/drivers/clocksource/Kconfig new file mode 100644 index 000000000..f151c6cf2 --- /dev/null +++ b/drivers/clocksource/Kconfig @@ -0,0 +1,87 @@ +config CLKSRC_OF + bool + +config CLKSRC_I8253 + bool + +config CLKEVT_I8253 + bool + +config I8253_LOCK + bool + +config CLKBLD_I8253 + def_bool y if CLKSRC_I8253 || CLKEVT_I8253 || I8253_LOCK + +config CLKSRC_MMIO + bool + +config DW_APB_TIMER + bool + +config DW_APB_TIMER_OF + bool + +config ARMADA_370_XP_TIMER + bool + +config SUN4I_TIMER + bool + +config VT8500_TIMER + bool + +config CADENCE_TTC_TIMER + bool + +config CLKSRC_NOMADIK_MTU + bool + depends on (ARCH_NOMADIK || ARCH_U8500) + select CLKSRC_MMIO + help + Support for Multi Timer Unit. MTU provides access + to multiple interrupt generating programmable + 32-bit free running decrementing counters. + +config CLKSRC_NOMADIK_MTU_SCHED_CLOCK + bool + depends on CLKSRC_NOMADIK_MTU + help + Use the Multi Timer Unit as the sched_clock. + +config CLKSRC_DBX500_PRCMU + bool "Clocksource PRCMU Timer" + depends on UX500_SOC_DB8500 + default y + help + Use the always on PRCMU Timer as clocksource + +config CLKSRC_DBX500_PRCMU_SCHED_CLOCK + bool "Clocksource PRCMU Timer sched_clock" + depends on (CLKSRC_DBX500_PRCMU && !CLKSRC_NOMADIK_MTU_SCHED_CLOCK) + default y + help + Use the always on PRCMU Timer as sched_clock + +config ARM_ARCH_TIMER + bool + select CLKSRC_OF if OF + +config CLKSRC_METAG_GENERIC + def_bool y if METAG + help + This option enables support for the Meta per-thread timers. + +config CLKSRC_EXYNOS_MCT + def_bool y if ARCH_EXYNOS + help + Support for Multi Core Timer controller on Exynos SoCs. + +config CLKSRC_SAMSUNG_PWM + bool + select CLKSRC_MMIO + help + This is a new clocksource driver for the PWM timer found in + Samsung S3C, S5P and Exynos SoCs, replacing an earlier driver + for all devicetree enabled platforms. This driver will be + needed only on systems that do not have the Exynos MCT available. diff --git a/drivers/clocksource/Makefile b/drivers/clocksource/Makefile new file mode 100644 index 000000000..ef4720ffe --- /dev/null +++ b/drivers/clocksource/Makefile @@ -0,0 +1,31 @@ +obj-$(CONFIG_CLKSRC_OF) += clksrc-of.o +obj-$(CONFIG_ATMEL_TCB_CLKSRC) += tcb_clksrc.o +obj-$(CONFIG_X86_CYCLONE_TIMER) += cyclone.o +obj-$(CONFIG_X86_PM_TIMER) += acpi_pm.o +obj-$(CONFIG_SCx200HR_TIMER) += scx200_hrt.o +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_EM_TIMER_STI) += em_sti.o +obj-$(CONFIG_CLKBLD_I8253) += i8253.o +obj-$(CONFIG_CLKSRC_MMIO) += mmio.o +obj-$(CONFIG_DW_APB_TIMER) += dw_apb_timer.o +obj-$(CONFIG_DW_APB_TIMER_OF) += dw_apb_timer_of.o +obj-$(CONFIG_CLKSRC_NOMADIK_MTU) += nomadik-mtu.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_ARCH_MARCO) += timer-marco.o +obj-$(CONFIG_ARCH_MXS) += mxs_timer.o +obj-$(CONFIG_ARCH_PRIMA2) += timer-prima2.o +obj-$(CONFIG_SUN4I_TIMER) += sun4i_timer.o +obj-$(CONFIG_ARCH_TEGRA) += tegra20_timer.o +obj-$(CONFIG_VT8500_TIMER) += vt8500_timer.o +obj-$(CONFIG_ARCH_BCM) += bcm_kona_timer.o +obj-$(CONFIG_CADENCE_TTC_TIMER) += cadence_ttc_timer.o +obj-$(CONFIG_CLKSRC_EXYNOS_MCT) += exynos_mct.o +obj-$(CONFIG_CLKSRC_SAMSUNG_PWM) += samsung_pwm_timer.o + +#obj-$(CONFIG_ARM_ARCH_TIMER) += arm_arch_timer.o +obj-$(CONFIG_CLKSRC_METAG_GENERIC) += metag_generic.o diff --git a/drivers/clocksource/acpi_pm.c b/drivers/clocksource/acpi_pm.c new file mode 100644 index 000000000..6efe4d1ab --- /dev/null +++ b/drivers/clocksource/acpi_pm.c @@ -0,0 +1,249 @@ +/* + * linux/drivers/clocksource/acpi_pm.c + * + * This file contains the ACPI PM based clocksource. + * + * This code was largely moved from the i386 timer_pm.c file + * which was (C) Dominik Brodowski <linux@brodo.de> 2003 + * and contained the following comments: + * + * Driver to use the Power Management Timer (PMTMR) available in some + * southbridges as primary timing source for the Linux kernel. + * + * Based on parts of linux/drivers/acpi/hardware/hwtimer.c, timer_pit.c, + * timer_hpet.c, and on Arjan van de Ven's implementation for 2.4. + * + * This file is licensed under the GPL v2. + */ + +#include <linux/acpi_pmtmr.h> +#include <linux/clocksource.h> +#include <linux/timex.h> +#include <linux/errno.h> +#include <linux/init.h> +#include <linux/pci.h> +#include <linux/delay.h> +#include <asm/io.h> + +/* + * The I/O port the PMTMR resides at. + * The location is detected during setup_arch(), + * in arch/i386/kernel/acpi/boot.c + */ +u32 pmtmr_ioport __read_mostly; + +static inline u32 read_pmtmr(void) +{ + /* mask the output to 24 bits */ + return inl(pmtmr_ioport) & ACPI_PM_MASK; +} + +u32 acpi_pm_read_verified(void) +{ + u32 v1 = 0, v2 = 0, v3 = 0; + + /* + * It has been reported that because of various broken + * chipsets (ICH4, PIIX4 and PIIX4E) where the ACPI PM clock + * source is not latched, you must read it multiple + * times to ensure a safe value is read: + */ + do { + v1 = read_pmtmr(); + v2 = read_pmtmr(); + v3 = read_pmtmr(); + } while (unlikely((v1 > v2 && v1 < v3) || (v2 > v3 && v2 < v1) + || (v3 > v1 && v3 < v2))); + + return v2; +} + +static cycle_t acpi_pm_read(struct clocksource *cs) +{ + return (cycle_t)read_pmtmr(); +} + +static struct clocksource clocksource_acpi_pm = { + .name = "acpi_pm", + .rating = 200, + .read = acpi_pm_read, + .mask = (cycle_t)ACPI_PM_MASK, + .flags = CLOCK_SOURCE_IS_CONTINUOUS, +}; + + +#ifdef CONFIG_PCI +static int acpi_pm_good; +static int __init acpi_pm_good_setup(char *__str) +{ + acpi_pm_good = 1; + return 1; +} +__setup("acpi_pm_good", acpi_pm_good_setup); + +static cycle_t acpi_pm_read_slow(struct clocksource *cs) +{ + return (cycle_t)acpi_pm_read_verified(); +} + +static inline void acpi_pm_need_workaround(void) +{ + clocksource_acpi_pm.read = acpi_pm_read_slow; + clocksource_acpi_pm.rating = 120; +} + +/* + * PIIX4 Errata: + * + * The power management timer may return improper results when read. + * Although the timer value settles properly after incrementing, + * while incrementing there is a 3 ns window every 69.8 ns where the + * timer value is indeterminate (a 4.2% chance that the data will be + * incorrect when read). As a result, the ACPI free running count up + * timer specification is violated due to erroneous reads. + */ +static void acpi_pm_check_blacklist(struct pci_dev *dev) +{ + if (acpi_pm_good) + return; + + /* the bug has been fixed in PIIX4M */ + if (dev->revision < 3) { + printk(KERN_WARNING "* Found PM-Timer Bug on the chipset." + " Due to workarounds for a bug,\n" + "* this clock source is slow. Consider trying" + " other clock sources\n"); + + acpi_pm_need_workaround(); + } +} +DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82371AB_3, + acpi_pm_check_blacklist); + +static void acpi_pm_check_graylist(struct pci_dev *dev) +{ + if (acpi_pm_good) + return; + + printk(KERN_WARNING "* The chipset may have PM-Timer Bug. Due to" + " workarounds for a bug,\n" + "* this clock source is slow. If you are sure your timer" + " does not have\n" + "* this bug, please use \"acpi_pm_good\" to disable the" + " workaround\n"); + + acpi_pm_need_workaround(); +} +DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801DB_0, + acpi_pm_check_graylist); +DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_SERVERWORKS, PCI_DEVICE_ID_SERVERWORKS_LE, + acpi_pm_check_graylist); +#endif + +#ifndef CONFIG_X86_64 +#include <asm/mach_timer.h> +#define PMTMR_EXPECTED_RATE \ + ((CALIBRATE_LATCH * (PMTMR_TICKS_PER_SEC >> 10)) / (PIT_TICK_RATE>>10)) +/* + * Some boards have the PMTMR running way too fast. We check + * the PMTMR rate against PIT channel 2 to catch these cases. + */ +static int verify_pmtmr_rate(void) +{ + cycle_t value1, value2; + unsigned long count, delta; + + mach_prepare_counter(); + value1 = clocksource_acpi_pm.read(&clocksource_acpi_pm); + mach_countup(&count); + value2 = clocksource_acpi_pm.read(&clocksource_acpi_pm); + delta = (value2 - value1) & ACPI_PM_MASK; + + /* Check that the PMTMR delta is within 5% of what we expect */ + if (delta < (PMTMR_EXPECTED_RATE * 19) / 20 || + delta > (PMTMR_EXPECTED_RATE * 21) / 20) { + printk(KERN_INFO "PM-Timer running at invalid rate: %lu%% " + "of normal - aborting.\n", + 100UL * delta / PMTMR_EXPECTED_RATE); + return -1; + } + + return 0; +} +#else +#define verify_pmtmr_rate() (0) +#endif + +/* Number of monotonicity checks to perform during initialization */ +#define ACPI_PM_MONOTONICITY_CHECKS 10 +/* Number of reads we try to get two different values */ +#define ACPI_PM_READ_CHECKS 10000 + +static int __init init_acpi_pm_clocksource(void) +{ + cycle_t value1, value2; + unsigned int i, j = 0; + + if (!pmtmr_ioport) + return -ENODEV; + + /* "verify" this timing source: */ + for (j = 0; j < ACPI_PM_MONOTONICITY_CHECKS; j++) { + udelay(100 * j); + value1 = clocksource_acpi_pm.read(&clocksource_acpi_pm); + for (i = 0; i < ACPI_PM_READ_CHECKS; i++) { + value2 = clocksource_acpi_pm.read(&clocksource_acpi_pm); + if (value2 == value1) + continue; + if (value2 > value1) + break; + if ((value2 < value1) && ((value2) < 0xFFF)) + break; + printk(KERN_INFO "PM-Timer had inconsistent results:" + " 0x%#llx, 0x%#llx - aborting.\n", + value1, value2); + pmtmr_ioport = 0; + return -EINVAL; + } + if (i == ACPI_PM_READ_CHECKS) { + printk(KERN_INFO "PM-Timer failed consistency check " + " (0x%#llx) - aborting.\n", value1); + pmtmr_ioport = 0; + return -ENODEV; + } + } + + if (verify_pmtmr_rate() != 0){ + pmtmr_ioport = 0; + return -ENODEV; + } + + return clocksource_register_hz(&clocksource_acpi_pm, + PMTMR_TICKS_PER_SEC); +} + +/* We use fs_initcall because we want the PCI fixups to have run + * but we still need to load before device_initcall + */ +fs_initcall(init_acpi_pm_clocksource); + +/* + * Allow an override of the IOPort. Stupid BIOSes do not tell us about + * the PMTimer, but we might know where it is. + */ +static int __init parse_pmtmr(char *arg) +{ + unsigned int base; + int ret; + + ret = kstrtouint(arg, 16, &base); + if (ret) + return ret; + + pr_info("PMTMR IOPort override: 0x%04x -> 0x%04x\n", pmtmr_ioport, + base); + pmtmr_ioport = base; + + return 1; +} +__setup("pmtmr=", parse_pmtmr); diff --git a/drivers/clocksource/arm_arch_timer.c b/drivers/clocksource/arm_arch_timer.c new file mode 100644 index 000000000..053d846ab --- /dev/null +++ b/drivers/clocksource/arm_arch_timer.c @@ -0,0 +1,375 @@ +/* + * linux/drivers/clocksource/arm_arch_timer.c + * + * Copyright (C) 2011 ARM Ltd. + * All Rights Reserved + * + * 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. + */ +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/device.h> +#include <linux/smp.h> +#include <linux/cpu.h> +#include <linux/clockchips.h> +#include <linux/interrupt.h> +#include <linux/of_irq.h> +#include <linux/io.h> + +#include <asm/arch_timer.h> +#include <asm/virt.h> + +#include <clocksource/arm_arch_timer.h> + +static u32 arch_timer_rate; + +enum ppi_nr { + PHYS_SECURE_PPI, + PHYS_NONSECURE_PPI, + VIRT_PPI, + HYP_PPI, + MAX_TIMER_PPI +}; + +static int arch_timer_ppi[MAX_TIMER_PPI]; + +static struct clock_event_device __percpu *arch_timer_evt; + +static bool arch_timer_use_virtual = true; + +/* + * Architected system timer support. + */ + +static inline irqreturn_t timer_handler(const int access, + struct clock_event_device *evt) +{ + unsigned long ctrl; + ctrl = arch_timer_reg_read(access, ARCH_TIMER_REG_CTRL); + if (ctrl & ARCH_TIMER_CTRL_IT_STAT) { + ctrl |= ARCH_TIMER_CTRL_IT_MASK; + arch_timer_reg_write(access, ARCH_TIMER_REG_CTRL, ctrl); + evt->event_handler(evt); + return IRQ_HANDLED; + } + + return IRQ_NONE; +} + +static irqreturn_t arch_timer_handler_virt(int irq, void *dev_id) +{ + struct clock_event_device *evt = dev_id; + + return timer_handler(ARCH_TIMER_VIRT_ACCESS, evt); +} + +static irqreturn_t arch_timer_handler_phys(int irq, void *dev_id) +{ + struct clock_event_device *evt = dev_id; + + return timer_handler(ARCH_TIMER_PHYS_ACCESS, evt); +} + +static inline void timer_set_mode(const int access, int mode) +{ + unsigned long ctrl; + switch (mode) { + case CLOCK_EVT_MODE_UNUSED: + case CLOCK_EVT_MODE_SHUTDOWN: + ctrl = arch_timer_reg_read(access, ARCH_TIMER_REG_CTRL); + ctrl &= ~ARCH_TIMER_CTRL_ENABLE; + arch_timer_reg_write(access, ARCH_TIMER_REG_CTRL, ctrl); + break; + default: + break; + } +} + +static void arch_timer_set_mode_virt(enum clock_event_mode mode, + struct clock_event_device *clk) +{ + timer_set_mode(ARCH_TIMER_VIRT_ACCESS, mode); +} + +static void arch_timer_set_mode_phys(enum clock_event_mode mode, + struct clock_event_device *clk) +{ + timer_set_mode(ARCH_TIMER_PHYS_ACCESS, mode); +} + +static inline void set_next_event(const int access, unsigned long evt) +{ + unsigned long ctrl; + ctrl = arch_timer_reg_read(access, ARCH_TIMER_REG_CTRL); + ctrl |= ARCH_TIMER_CTRL_ENABLE; + ctrl &= ~ARCH_TIMER_CTRL_IT_MASK; + arch_timer_reg_write(access, ARCH_TIMER_REG_TVAL, evt); + arch_timer_reg_write(access, ARCH_TIMER_REG_CTRL, ctrl); +} + +static int arch_timer_set_next_event_virt(unsigned long evt, + struct clock_event_device *unused) +{ + set_next_event(ARCH_TIMER_VIRT_ACCESS, evt); + return 0; +} + +static int arch_timer_set_next_event_phys(unsigned long evt, + struct clock_event_device *unused) +{ + set_next_event(ARCH_TIMER_PHYS_ACCESS, evt); + return 0; +} + +static int __cpuinit arch_timer_setup(struct clock_event_device *clk) +{ + clk->features = CLOCK_EVT_FEAT_ONESHOT | CLOCK_EVT_FEAT_C3STOP; + clk->name = "arch_sys_timer"; + clk->rating = 450; + if (arch_timer_use_virtual) { + clk->irq = arch_timer_ppi[VIRT_PPI]; + clk->set_mode = arch_timer_set_mode_virt; + clk->set_next_event = arch_timer_set_next_event_virt; + } else { + clk->irq = arch_timer_ppi[PHYS_SECURE_PPI]; + clk->set_mode = arch_timer_set_mode_phys; + clk->set_next_event = arch_timer_set_next_event_phys; + } + + clk->cpumask = cpumask_of(smp_processor_id()); + + clk->set_mode(CLOCK_EVT_MODE_SHUTDOWN, NULL); + + clockevents_config_and_register(clk, arch_timer_rate, + 0xf, 0x7fffffff); + + if (arch_timer_use_virtual) + enable_percpu_irq(arch_timer_ppi[VIRT_PPI], 0); + else { + enable_percpu_irq(arch_timer_ppi[PHYS_SECURE_PPI], 0); + if (arch_timer_ppi[PHYS_NONSECURE_PPI]) + enable_percpu_irq(arch_timer_ppi[PHYS_NONSECURE_PPI], 0); + } + + arch_counter_set_user_access(); + + return 0; +} + +static int arch_timer_available(void) +{ + u32 freq; + + if (arch_timer_rate == 0) { + freq = arch_timer_get_cntfrq(); + + /* Check the timer frequency. */ + if (freq == 0) { + pr_warn("Architected timer frequency not available\n"); + return -EINVAL; + } + + arch_timer_rate = freq; + } + + pr_info_once("Architected local timer running at %lu.%02luMHz (%s).\n", + (unsigned long)arch_timer_rate / 1000000, + (unsigned long)(arch_timer_rate / 10000) % 100, + arch_timer_use_virtual ? "virt" : "phys"); + return 0; +} + +u32 arch_timer_get_rate(void) +{ + return arch_timer_rate; +} + +u64 arch_timer_read_counter(void) +{ + return arch_counter_get_cntvct(); +} + +static cycle_t arch_counter_read(struct clocksource *cs) +{ + return arch_counter_get_cntvct(); +} + +static cycle_t arch_counter_read_cc(const struct cyclecounter *cc) +{ + return arch_counter_get_cntvct(); +} + +static struct clocksource clocksource_counter = { + .name = "arch_sys_counter", + .rating = 400, + .read = arch_counter_read, + .mask = CLOCKSOURCE_MASK(56), + .flags = CLOCK_SOURCE_IS_CONTINUOUS, +}; + +static struct cyclecounter cyclecounter = { + .read = arch_counter_read_cc, + .mask = CLOCKSOURCE_MASK(56), +}; + +static struct timecounter timecounter; + +struct timecounter *arch_timer_get_timecounter(void) +{ + return &timecounter; +} + +static void __cpuinit arch_timer_stop(struct clock_event_device *clk) +{ + pr_debug("arch_timer_teardown disable IRQ%d cpu #%d\n", + clk->irq, smp_processor_id()); + + if (arch_timer_use_virtual) + disable_percpu_irq(arch_timer_ppi[VIRT_PPI]); + else { + disable_percpu_irq(arch_timer_ppi[PHYS_SECURE_PPI]); + if (arch_timer_ppi[PHYS_NONSECURE_PPI]) + disable_percpu_irq(arch_timer_ppi[PHYS_NONSECURE_PPI]); + } + + clk->set_mode(CLOCK_EVT_MODE_UNUSED, clk); +} + +static int __cpuinit arch_timer_cpu_notify(struct notifier_block *self, + unsigned long action, void *hcpu) +{ + /* + * Grab cpu pointer in each case to avoid spurious + * preemptible warnings + */ + switch (action & ~CPU_TASKS_FROZEN) { + case CPU_STARTING: + arch_timer_setup(this_cpu_ptr(arch_timer_evt)); + break; + case CPU_DYING: + arch_timer_stop(this_cpu_ptr(arch_timer_evt)); + break; + } + + return NOTIFY_OK; +} + +static struct notifier_block arch_timer_cpu_nb __cpuinitdata = { + .notifier_call = arch_timer_cpu_notify, +}; + +static int __init arch_timer_register(void) +{ + int err; + int ppi; + + err = arch_timer_available(); + if (err) + goto out; + + arch_timer_evt = alloc_percpu(struct clock_event_device); + if (!arch_timer_evt) { + err = -ENOMEM; + goto out; + } + + clocksource_register_hz(&clocksource_counter, arch_timer_rate); + cyclecounter.mult = clocksource_counter.mult; + cyclecounter.shift = clocksource_counter.shift; + timecounter_init(&timecounter, &cyclecounter, + arch_counter_get_cntvct()); + + if (arch_timer_use_virtual) { + ppi = arch_timer_ppi[VIRT_PPI]; + err = request_percpu_irq(ppi, arch_timer_handler_virt, + "arch_timer", arch_timer_evt); + } else { + ppi = arch_timer_ppi[PHYS_SECURE_PPI]; + err = request_percpu_irq(ppi, arch_timer_handler_phys, + "arch_timer", arch_timer_evt); + if (!err && arch_timer_ppi[PHYS_NONSECURE_PPI]) { + ppi = arch_timer_ppi[PHYS_NONSECURE_PPI]; + err = request_percpu_irq(ppi, arch_timer_handler_phys, + "arch_timer", arch_timer_evt); + if (err) + free_percpu_irq(arch_timer_ppi[PHYS_SECURE_PPI], + arch_timer_evt); + } + } + + if (err) { + pr_err("arch_timer: can't register interrupt %d (%d)\n", + ppi, err); + goto out_free; + } + + err = register_cpu_notifier(&arch_timer_cpu_nb); + if (err) + goto out_free_irq; + + /* Immediately configure the timer on the boot CPU */ + arch_timer_setup(this_cpu_ptr(arch_timer_evt)); + + return 0; + +out_free_irq: + if (arch_timer_use_virtual) + free_percpu_irq(arch_timer_ppi[VIRT_PPI], arch_timer_evt); + else { + free_percpu_irq(arch_timer_ppi[PHYS_SECURE_PPI], + arch_timer_evt); + if (arch_timer_ppi[PHYS_NONSECURE_PPI]) + free_percpu_irq(arch_timer_ppi[PHYS_NONSECURE_PPI], + arch_timer_evt); + } + +out_free: + free_percpu(arch_timer_evt); +out: + return err; +} + +static void __init arch_timer_init(struct device_node *np) +{ + u32 freq; + int i; + + if (arch_timer_get_rate()) { + pr_warn("arch_timer: multiple nodes in dt, skipping\n"); + return; + } + + /* Try to determine the frequency from the device tree or CNTFRQ */ + if (!of_property_read_u32(np, "clock-frequency", &freq)) + arch_timer_rate = freq; + + for (i = PHYS_SECURE_PPI; i < MAX_TIMER_PPI; i++) + arch_timer_ppi[i] = irq_of_parse_and_map(np, i); + + of_node_put(np); + + /* + * If HYP mode is available, we know that the physical timer + * has been configured to be accessible from PL1. Use it, so + * that a guest can use the virtual timer instead. + * + * If no interrupt provided for virtual timer, we'll have to + * stick to the physical timer. It'd better be accessible... + */ + if (is_hyp_mode_available() || !arch_timer_ppi[VIRT_PPI]) { + arch_timer_use_virtual = false; + + if (!arch_timer_ppi[PHYS_SECURE_PPI] || + !arch_timer_ppi[PHYS_NONSECURE_PPI]) { + pr_warn("arch_timer: No interrupt available, giving up\n"); + return; + } + } + + arch_timer_register(); + arch_timer_arch_init(); +} +CLOCKSOURCE_OF_DECLARE(armv7_arch_timer, "arm,armv7-timer", arch_timer_init); +CLOCKSOURCE_OF_DECLARE(armv8_arch_timer, "arm,armv8-timer", arch_timer_init); diff --git a/drivers/clocksource/bcm2835_timer.c b/drivers/clocksource/bcm2835_timer.c new file mode 100644 index 000000000..766611d29 --- /dev/null +++ b/drivers/clocksource/bcm2835_timer.c @@ -0,0 +1,148 @@ +/* + * 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/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 void __init bcm2835_timer_init(struct device_node *node) +{ + void __iomem *base; + u32 freq; + int irq; + struct bcm2835_timer *timer; + + 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); +} +CLOCKSOURCE_OF_DECLARE(bcm2835, "brcm,bcm2835-system-timer", + bcm2835_timer_init); diff --git a/drivers/clocksource/bcm_kona_timer.c b/drivers/clocksource/bcm_kona_timer.c new file mode 100644 index 000000000..350f49356 --- /dev/null +++ b/drivers/clocksource/bcm_kona_timer.c @@ -0,0 +1,211 @@ +/* + * Copyright (C) 2012 Broadcom Corporation + * + * 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 version 2. + * + * This program is distributed "as is" WITHOUT ANY WARRANTY of any + * kind, whether express or implied; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/init.h> +#include <linux/irq.h> +#include <linux/interrupt.h> +#include <linux/jiffies.h> +#include <linux/clockchips.h> +#include <linux/types.h> + +#include <linux/io.h> +#include <asm/mach/time.h> + +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/of_irq.h> + + +#define KONA_GPTIMER_STCS_OFFSET 0x00000000 +#define KONA_GPTIMER_STCLO_OFFSET 0x00000004 +#define KONA_GPTIMER_STCHI_OFFSET 0x00000008 +#define KONA_GPTIMER_STCM0_OFFSET 0x0000000C + +#define KONA_GPTIMER_STCS_TIMER_MATCH_SHIFT 0 +#define KONA_GPTIMER_STCS_COMPARE_ENABLE_SHIFT 4 + +struct kona_bcm_timers { + int tmr_irq; + void __iomem *tmr_regs; +}; + +static struct kona_bcm_timers timers; + +static u32 arch_timer_rate; + +/* + * We use the peripheral timers for system tick, the cpu global timer for + * profile tick + */ +static void kona_timer_disable_and_clear(void __iomem *base) +{ + uint32_t reg; + + /* + * clear and disable interrupts + * We are using compare/match register 0 for our system interrupts + */ + reg = readl(base + KONA_GPTIMER_STCS_OFFSET); + + /* Clear compare (0) interrupt */ + reg |= 1 << KONA_GPTIMER_STCS_TIMER_MATCH_SHIFT; + /* disable compare */ + reg &= ~(1 << KONA_GPTIMER_STCS_COMPARE_ENABLE_SHIFT); + + writel(reg, base + KONA_GPTIMER_STCS_OFFSET); + +} + +static void +kona_timer_get_counter(void *timer_base, uint32_t *msw, uint32_t *lsw) +{ + void __iomem *base = IOMEM(timer_base); + int loop_limit = 4; + + /* + * Read 64-bit free running counter + * 1. Read hi-word + * 2. Read low-word + * 3. Read hi-word again + * 4.1 + * if new hi-word is not equal to previously read hi-word, then + * start from #1 + * 4.2 + * if new hi-word is equal to previously read hi-word then stop. + */ + + while (--loop_limit) { + *msw = readl(base + KONA_GPTIMER_STCHI_OFFSET); + *lsw = readl(base + KONA_GPTIMER_STCLO_OFFSET); + if (*msw == readl(base + KONA_GPTIMER_STCHI_OFFSET)) + break; + } + if (!loop_limit) { + pr_err("bcm_kona_timer: getting counter failed.\n"); + pr_err(" Timer will be impacted\n"); + } + + return; +} + +static const struct of_device_id bcm_timer_ids[] __initconst = { + {.compatible = "bcm,kona-timer"}, + {}, +}; + +static void __init kona_timers_init(void) +{ + struct device_node *node; + u32 freq; + + node = of_find_matching_node(NULL, bcm_timer_ids); + + if (!node) + panic("No timer"); + + if (!of_property_read_u32(node, "clock-frequency", &freq)) + arch_timer_rate = freq; + else + panic("clock-frequency not set in the .dts file"); + + /* Setup IRQ numbers */ + timers.tmr_irq = irq_of_parse_and_map(node, 0); + + /* Setup IO addresses */ + timers.tmr_regs = of_iomap(node, 0); + + kona_timer_disable_and_clear(timers.tmr_regs); +} + +static int kona_timer_set_next_event(unsigned long clc, + struct clock_event_device *unused) +{ + /* + * timer (0) is disabled by the timer interrupt already + * so, here we reload the next event value and re-enable + * the timer. + * + * This way, we are potentially losing the time between + * timer-interrupt->set_next_event. CPU local timers, when + * they come in should get rid of skew. + */ + + uint32_t lsw, msw; + uint32_t reg; + + kona_timer_get_counter(timers.tmr_regs, &msw, &lsw); + + /* Load the "next" event tick value */ + writel(lsw + clc, timers.tmr_regs + KONA_GPTIMER_STCM0_OFFSET); + + /* Enable compare */ + reg = readl(timers.tmr_regs + KONA_GPTIMER_STCS_OFFSET); + reg |= (1 << KONA_GPTIMER_STCS_COMPARE_ENABLE_SHIFT); + writel(reg, timers.tmr_regs + KONA_GPTIMER_STCS_OFFSET); + + return 0; +} + +static void kona_timer_set_mode(enum clock_event_mode mode, + struct clock_event_device *unused) +{ + switch (mode) { + case CLOCK_EVT_MODE_ONESHOT: + /* by default mode is one shot don't do any thing */ + break; + case CLOCK_EVT_MODE_UNUSED: + case CLOCK_EVT_MODE_SHUTDOWN: + default: + kona_timer_disable_and_clear(timers.tmr_regs); + } +} + +static struct clock_event_device kona_clockevent_timer = { + .name = "timer 1", + .features = CLOCK_EVT_FEAT_ONESHOT, + .set_next_event = kona_timer_set_next_event, + .set_mode = kona_timer_set_mode +}; + +static void __init kona_timer_clockevents_init(void) +{ + kona_clockevent_timer.cpumask = cpumask_of(0); + clockevents_config_and_register(&kona_clockevent_timer, + arch_timer_rate, 6, 0xffffffff); +} + +static irqreturn_t kona_timer_interrupt(int irq, void *dev_id) +{ + struct clock_event_device *evt = &kona_clockevent_timer; + + kona_timer_disable_and_clear(timers.tmr_regs); + evt->event_handler(evt); + return IRQ_HANDLED; +} + +static struct irqaction kona_timer_irq = { + .name = "Kona Timer Tick", + .flags = IRQF_TIMER, + .handler = kona_timer_interrupt, +}; + +static void __init kona_timer_init(void) +{ + kona_timers_init(); + kona_timer_clockevents_init(); + setup_irq(timers.tmr_irq, &kona_timer_irq); + kona_timer_set_next_event((arch_timer_rate / HZ), NULL); +} + +CLOCKSOURCE_OF_DECLARE(bcm_kona, "bcm,kona-timer", + kona_timer_init); diff --git a/drivers/clocksource/cadence_ttc_timer.c b/drivers/clocksource/cadence_ttc_timer.c new file mode 100644 index 000000000..685bc60e2 --- /dev/null +++ b/drivers/clocksource/cadence_ttc_timer.c @@ -0,0 +1,436 @@ +/* + * This file contains driver for the Cadence Triple Timer Counter Rev 06 + * + * Copyright (C) 2011-2013 Xilinx + * + * based on arch/mips/kernel/time.c timer driver + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + */ + +#include <linux/clk.h> +#include <linux/interrupt.h> +#include <linux/clockchips.h> +#include <linux/of_address.h> +#include <linux/of_irq.h> +#include <linux/slab.h> +#include <linux/clk-provider.h> + +/* + * This driver configures the 2 16-bit count-up timers as follows: + * + * T1: Timer 1, clocksource for generic timekeeping + * T2: Timer 2, clockevent source for hrtimers + * T3: Timer 3, <unused> + * + * The input frequency to the timer module for emulation is 2.5MHz which is + * common to all the timer channels (T1, T2, and T3). With a pre-scaler of 32, + * the timers are clocked at 78.125KHz (12.8 us resolution). + + * The input frequency to the timer module in silicon is configurable and + * obtained from device tree. The pre-scaler of 32 is used. + */ + +/* + * Timer Register Offset Definitions of Timer 1, Increment base address by 4 + * and use same offsets for Timer 2 + */ +#define TTC_CLK_CNTRL_OFFSET 0x00 /* Clock Control Reg, RW */ +#define TTC_CNT_CNTRL_OFFSET 0x0C /* Counter Control Reg, RW */ +#define TTC_COUNT_VAL_OFFSET 0x18 /* Counter Value Reg, RO */ +#define TTC_INTR_VAL_OFFSET 0x24 /* Interval Count Reg, RW */ +#define TTC_ISR_OFFSET 0x54 /* Interrupt Status Reg, RO */ +#define TTC_IER_OFFSET 0x60 /* Interrupt Enable Reg, RW */ + +#define TTC_CNT_CNTRL_DISABLE_MASK 0x1 + +/* + * Setup the timers to use pre-scaling, using a fixed value for now that will + * work across most input frequency, but it may need to be more dynamic + */ +#define PRESCALE_EXPONENT 11 /* 2 ^ PRESCALE_EXPONENT = PRESCALE */ +#define PRESCALE 2048 /* The exponent must match this */ +#define CLK_CNTRL_PRESCALE ((PRESCALE_EXPONENT - 1) << 1) +#define CLK_CNTRL_PRESCALE_EN 1 +#define CNT_CNTRL_RESET (1 << 4) + +/** + * struct ttc_timer - This definition defines local timer structure + * + * @base_addr: Base address of timer + * @clk: Associated clock source + * @clk_rate_change_nb Notifier block for clock rate changes + */ +struct ttc_timer { + void __iomem *base_addr; + struct clk *clk; + struct notifier_block clk_rate_change_nb; +}; + +#define to_ttc_timer(x) \ + container_of(x, struct ttc_timer, clk_rate_change_nb) + +struct ttc_timer_clocksource { + struct ttc_timer ttc; + struct clocksource cs; +}; + +#define to_ttc_timer_clksrc(x) \ + container_of(x, struct ttc_timer_clocksource, cs) + +struct ttc_timer_clockevent { + struct ttc_timer ttc; + struct clock_event_device ce; +}; + +#define to_ttc_timer_clkevent(x) \ + container_of(x, struct ttc_timer_clockevent, ce) + +/** + * ttc_set_interval - Set the timer interval value + * + * @timer: Pointer to the timer instance + * @cycles: Timer interval ticks + **/ +static void ttc_set_interval(struct ttc_timer *timer, + unsigned long cycles) +{ + u32 ctrl_reg; + + /* Disable the counter, set the counter value and re-enable counter */ + ctrl_reg = __raw_readl(timer->base_addr + TTC_CNT_CNTRL_OFFSET); + ctrl_reg |= TTC_CNT_CNTRL_DISABLE_MASK; + __raw_writel(ctrl_reg, timer->base_addr + TTC_CNT_CNTRL_OFFSET); + + __raw_writel(cycles, timer->base_addr + TTC_INTR_VAL_OFFSET); + + /* + * Reset the counter (0x10) so that it starts from 0, one-shot + * mode makes this needed for timing to be right. + */ + ctrl_reg |= CNT_CNTRL_RESET; + ctrl_reg &= ~TTC_CNT_CNTRL_DISABLE_MASK; + __raw_writel(ctrl_reg, timer->base_addr + TTC_CNT_CNTRL_OFFSET); +} + +/** + * ttc_clock_event_interrupt - Clock event timer interrupt handler + * + * @irq: IRQ number of the Timer + * @dev_id: void pointer to the ttc_timer instance + * + * returns: Always IRQ_HANDLED - success + **/ +static irqreturn_t ttc_clock_event_interrupt(int irq, void *dev_id) +{ + struct ttc_timer_clockevent *ttce = dev_id; + struct ttc_timer *timer = &ttce->ttc; + + /* Acknowledge the interrupt and call event handler */ + __raw_readl(timer->base_addr + TTC_ISR_OFFSET); + + ttce->ce.event_handler(&ttce->ce); + + return IRQ_HANDLED; +} + +/** + * __ttc_clocksource_read - Reads the timer counter register + * + * returns: Current timer counter register value + **/ +static cycle_t __ttc_clocksource_read(struct clocksource *cs) +{ + struct ttc_timer *timer = &to_ttc_timer_clksrc(cs)->ttc; + + return (cycle_t)__raw_readl(timer->base_addr + + TTC_COUNT_VAL_OFFSET); +} + +/** + * ttc_set_next_event - Sets the time interval for next event + * + * @cycles: Timer interval ticks + * @evt: Address of clock event instance + * + * returns: Always 0 - success + **/ +static int ttc_set_next_event(unsigned long cycles, + struct clock_event_device *evt) +{ + struct ttc_timer_clockevent *ttce = to_ttc_timer_clkevent(evt); + struct ttc_timer *timer = &ttce->ttc; + + ttc_set_interval(timer, cycles); + return 0; +} + +/** + * ttc_set_mode - Sets the mode of timer + * + * @mode: Mode to be set + * @evt: Address of clock event instance + **/ +static void ttc_set_mode(enum clock_event_mode mode, + struct clock_event_device *evt) +{ + struct ttc_timer_clockevent *ttce = to_ttc_timer_clkevent(evt); + struct ttc_timer *timer = &ttce->ttc; + u32 ctrl_reg; + + switch (mode) { + case CLOCK_EVT_MODE_PERIODIC: + ttc_set_interval(timer, + DIV_ROUND_CLOSEST(clk_get_rate(ttce->ttc.clk), + PRESCALE * HZ)); + break; + case CLOCK_EVT_MODE_ONESHOT: + case CLOCK_EVT_MODE_UNUSED: + case CLOCK_EVT_MODE_SHUTDOWN: + ctrl_reg = __raw_readl(timer->base_addr + + TTC_CNT_CNTRL_OFFSET); + ctrl_reg |= TTC_CNT_CNTRL_DISABLE_MASK; + __raw_writel(ctrl_reg, + timer->base_addr + TTC_CNT_CNTRL_OFFSET); + break; + case CLOCK_EVT_MODE_RESUME: + ctrl_reg = __raw_readl(timer->base_addr + + TTC_CNT_CNTRL_OFFSET); + ctrl_reg &= ~TTC_CNT_CNTRL_DISABLE_MASK; + __raw_writel(ctrl_reg, + timer->base_addr + TTC_CNT_CNTRL_OFFSET); + break; + } +} + +static int ttc_rate_change_clocksource_cb(struct notifier_block *nb, + unsigned long event, void *data) +{ + struct clk_notifier_data *ndata = data; + struct ttc_timer *ttc = to_ttc_timer(nb); + struct ttc_timer_clocksource *ttccs = container_of(ttc, + struct ttc_timer_clocksource, ttc); + + switch (event) { + case POST_RATE_CHANGE: + /* + * Do whatever is necessary to maintain a proper time base + * + * I cannot find a way to adjust the currently used clocksource + * to the new frequency. __clocksource_updatefreq_hz() sounds + * good, but does not work. Not sure what's that missing. + * + * This approach works, but triggers two clocksource switches. + * The first after unregister to clocksource jiffies. And + * another one after the register to the newly registered timer. + * + * Alternatively we could 'waste' another HW timer to ping pong + * between clock sources. That would also use one register and + * one unregister call, but only trigger one clocksource switch + * for the cost of another HW timer used by the OS. + */ + clocksource_unregister(&ttccs->cs); + clocksource_register_hz(&ttccs->cs, + ndata->new_rate / PRESCALE); + /* fall through */ + case PRE_RATE_CHANGE: + case ABORT_RATE_CHANGE: + default: + return NOTIFY_DONE; + } +} + +static void __init ttc_setup_clocksource(struct clk *clk, void __iomem *base) +{ + struct ttc_timer_clocksource *ttccs; + int err; + + ttccs = kzalloc(sizeof(*ttccs), GFP_KERNEL); + if (WARN_ON(!ttccs)) + return; + + ttccs->ttc.clk = clk; + + err = clk_prepare_enable(ttccs->ttc.clk); + if (WARN_ON(err)) { + kfree(ttccs); + return; + } + + ttccs->ttc.clk_rate_change_nb.notifier_call = + ttc_rate_change_clocksource_cb; + ttccs->ttc.clk_rate_change_nb.next = NULL; + if (clk_notifier_register(ttccs->ttc.clk, + &ttccs->ttc.clk_rate_change_nb)) + pr_warn("Unable to register clock notifier.\n"); + + ttccs->ttc.base_addr = base; + ttccs->cs.name = "ttc_clocksource"; + ttccs->cs.rating = 200; + ttccs->cs.read = __ttc_clocksource_read; + ttccs->cs.mask = CLOCKSOURCE_MASK(16); + ttccs->cs.flags = CLOCK_SOURCE_IS_CONTINUOUS; + + /* + * Setup the clock source counter to be an incrementing counter + * with no interrupt and it rolls over at 0xFFFF. Pre-scale + * it by 32 also. Let it start running now. + */ + __raw_writel(0x0, ttccs->ttc.base_addr + TTC_IER_OFFSET); + __raw_writel(CLK_CNTRL_PRESCALE | CLK_CNTRL_PRESCALE_EN, + ttccs->ttc.base_addr + TTC_CLK_CNTRL_OFFSET); + __raw_writel(CNT_CNTRL_RESET, + ttccs->ttc.base_addr + TTC_CNT_CNTRL_OFFSET); + + err = clocksource_register_hz(&ttccs->cs, + clk_get_rate(ttccs->ttc.clk) / PRESCALE); + if (WARN_ON(err)) { + kfree(ttccs); + return; + } +} + +static int ttc_rate_change_clockevent_cb(struct notifier_block *nb, + unsigned long event, void *data) +{ + struct clk_notifier_data *ndata = data; + struct ttc_timer *ttc = to_ttc_timer(nb); + struct ttc_timer_clockevent *ttcce = container_of(ttc, + struct ttc_timer_clockevent, ttc); + + switch (event) { + case POST_RATE_CHANGE: + { + unsigned long flags; + + /* + * clockevents_update_freq should be called with IRQ disabled on + * the CPU the timer provides events for. The timer we use is + * common to both CPUs, not sure if we need to run on both + * cores. + */ + local_irq_save(flags); + clockevents_update_freq(&ttcce->ce, + ndata->new_rate / PRESCALE); + local_irq_restore(flags); + + /* fall through */ + } + case PRE_RATE_CHANGE: + case ABORT_RATE_CHANGE: + default: + return NOTIFY_DONE; + } +} + +static void __init ttc_setup_clockevent(struct clk *clk, + void __iomem *base, u32 irq) +{ + struct ttc_timer_clockevent *ttcce; + int err; + + ttcce = kzalloc(sizeof(*ttcce), GFP_KERNEL); + if (WARN_ON(!ttcce)) + return; + + ttcce->ttc.clk = clk; + + err = clk_prepare_enable(ttcce->ttc.clk); + if (WARN_ON(err)) { + kfree(ttcce); + return; + } + + ttcce->ttc.clk_rate_change_nb.notifier_call = + ttc_rate_change_clockevent_cb; + ttcce->ttc.clk_rate_change_nb.next = NULL; + if (clk_notifier_register(ttcce->ttc.clk, + &ttcce->ttc.clk_rate_change_nb)) + pr_warn("Unable to register clock notifier.\n"); + + ttcce->ttc.base_addr = base; + ttcce->ce.name = "ttc_clockevent"; + ttcce->ce.features = CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT; + ttcce->ce.set_next_event = ttc_set_next_event; + ttcce->ce.set_mode = ttc_set_mode; + ttcce->ce.rating = 200; + ttcce->ce.irq = irq; + ttcce->ce.cpumask = cpu_possible_mask; + + /* + * Setup the clock event timer to be an interval timer which + * is prescaled by 32 using the interval interrupt. Leave it + * disabled for now. + */ + __raw_writel(0x23, ttcce->ttc.base_addr + TTC_CNT_CNTRL_OFFSET); + __raw_writel(CLK_CNTRL_PRESCALE | CLK_CNTRL_PRESCALE_EN, + ttcce->ttc.base_addr + TTC_CLK_CNTRL_OFFSET); + __raw_writel(0x1, ttcce->ttc.base_addr + TTC_IER_OFFSET); + + err = request_irq(irq, ttc_clock_event_interrupt, + IRQF_DISABLED | IRQF_TIMER, + ttcce->ce.name, ttcce); + if (WARN_ON(err)) { + kfree(ttcce); + return; + } + + clockevents_config_and_register(&ttcce->ce, + clk_get_rate(ttcce->ttc.clk) / PRESCALE, 1, 0xfffe); +} + +/** + * ttc_timer_init - Initialize the timer + * + * Initializes the timer hardware and register the clock source and clock event + * timers with Linux kernal timer framework + */ +static void __init ttc_timer_init(struct device_node *timer) +{ + unsigned int irq; + void __iomem *timer_baseaddr; + struct clk *clk; + static int initialized; + + if (initialized) + return; + + initialized = 1; + + /* + * Get the 1st Triple Timer Counter (TTC) block from the device tree + * and use it. Note that the event timer uses the interrupt and it's the + * 2nd TTC hence the irq_of_parse_and_map(,1) + */ + timer_baseaddr = of_iomap(timer, 0); + if (!timer_baseaddr) { + pr_err("ERROR: invalid timer base address\n"); + BUG(); + } + + irq = irq_of_parse_and_map(timer, 1); + if (irq <= 0) { + pr_err("ERROR: invalid interrupt number\n"); + BUG(); + } + + clk = of_clk_get_by_name(timer, "cpu_1x"); + if (IS_ERR(clk)) { + pr_err("ERROR: timer input clock not found\n"); + BUG(); + } + + ttc_setup_clocksource(clk, timer_baseaddr); + ttc_setup_clockevent(clk, timer_baseaddr + 4, irq); + + pr_info("%s #0 at %p, irq=%d\n", timer->name, timer_baseaddr, irq); +} + +CLOCKSOURCE_OF_DECLARE(ttc, "cdns,ttc", ttc_timer_init); diff --git a/drivers/clocksource/clksrc-dbx500-prcmu.c b/drivers/clocksource/clksrc-dbx500-prcmu.c new file mode 100644 index 000000000..54f3d119d --- /dev/null +++ b/drivers/clocksource/clksrc-dbx500-prcmu.c @@ -0,0 +1,90 @@ +/* + * Copyright (C) ST-Ericsson SA 2011 + * + * License Terms: GNU General Public License v2 + * Author: Mattias Wallin <mattias.wallin@stericsson.com> for ST-Ericsson + * Author: Sundar Iyer for ST-Ericsson + * sched_clock implementation is based on: + * plat-nomadik/timer.c Linus Walleij <linus.walleij@stericsson.com> + * + * DBx500-PRCMU Timer + * The PRCMU has 5 timers which are available in a always-on + * power domain. We use the Timer 4 for our always-on clock + * source on DB8500 and Timer 3 on DB5500. + */ +#include <linux/clockchips.h> +#include <linux/clksrc-dbx500-prcmu.h> + +#include <asm/sched_clock.h> + +#define RATE_32K 32768 + +#define TIMER_MODE_CONTINOUS 0x1 +#define TIMER_DOWNCOUNT_VAL 0xffffffff + +#define PRCMU_TIMER_REF 0 +#define PRCMU_TIMER_DOWNCOUNT 0x4 +#define PRCMU_TIMER_MODE 0x8 + +#define SCHED_CLOCK_MIN_WRAP 131072 /* 2^32 / 32768 */ + +static void __iomem *clksrc_dbx500_timer_base; + +static cycle_t clksrc_dbx500_prcmu_read(struct clocksource *cs) +{ + u32 count, count2; + + do { + count = readl(clksrc_dbx500_timer_base + + PRCMU_TIMER_DOWNCOUNT); + count2 = readl(clksrc_dbx500_timer_base + + PRCMU_TIMER_DOWNCOUNT); + } while (count2 != count); + + /* Negate because the timer is a decrementing counter */ + return ~count; +} + +static struct clocksource clocksource_dbx500_prcmu = { + .name = "dbx500-prcmu-timer", + .rating = 300, + .read = clksrc_dbx500_prcmu_read, + .mask = CLOCKSOURCE_MASK(32), + .flags = CLOCK_SOURCE_IS_CONTINUOUS, +}; + +#ifdef CONFIG_CLKSRC_DBX500_PRCMU_SCHED_CLOCK + +static u32 notrace dbx500_prcmu_sched_clock_read(void) +{ + if (unlikely(!clksrc_dbx500_timer_base)) + return 0; + + return clksrc_dbx500_prcmu_read(&clocksource_dbx500_prcmu); +} + +#endif + +void __init clksrc_dbx500_prcmu_init(void __iomem *base) +{ + clksrc_dbx500_timer_base = base; + + /* + * The A9 sub system expects the timer to be configured as + * a continous looping timer. + * The PRCMU should configure it but if it for some reason + * don't we do it here. + */ + if (readl(clksrc_dbx500_timer_base + PRCMU_TIMER_MODE) != + TIMER_MODE_CONTINOUS) { + writel(TIMER_MODE_CONTINOUS, + clksrc_dbx500_timer_base + PRCMU_TIMER_MODE); + writel(TIMER_DOWNCOUNT_VAL, + clksrc_dbx500_timer_base + PRCMU_TIMER_REF); + } +#ifdef CONFIG_CLKSRC_DBX500_PRCMU_SCHED_CLOCK + setup_sched_clock(dbx500_prcmu_sched_clock_read, + 32, RATE_32K); +#endif + clocksource_register_hz(&clocksource_dbx500_prcmu, RATE_32K); +} diff --git a/drivers/clocksource/clksrc-of.c b/drivers/clocksource/clksrc-of.c new file mode 100644 index 000000000..37f5325be --- /dev/null +++ b/drivers/clocksource/clksrc-of.c @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2012, NVIDIA CORPORATION. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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, see <http://www.gnu.org/licenses/>. + */ + +#include <linux/init.h> +#include <linux/of.h> +#include <linux/clocksource.h> + +extern struct of_device_id __clksrc_of_table[]; + +static const struct of_device_id __clksrc_of_table_sentinel + __used __section(__clksrc_of_table_end); + +void __init clocksource_of_init(void) +{ + struct device_node *np; + const struct of_device_id *match; + clocksource_of_init_fn init_func; + + for_each_matching_node_and_match(np, __clksrc_of_table, &match) { + init_func = match->data; + init_func(np); + } +} diff --git a/drivers/clocksource/cs5535-clockevt.c b/drivers/clocksource/cs5535-clockevt.c new file mode 100644 index 000000000..ea210482d --- /dev/null +++ b/drivers/clocksource/cs5535-clockevt.c @@ -0,0 +1,191 @@ +/* + * Clock event driver for the CS5535/CS5536 + * + * Copyright (C) 2006, Advanced Micro Devices, Inc. + * Copyright (C) 2007 Andres Salomon <dilinger@debian.org> + * Copyright (C) 2009 Andres Salomon <dilinger@collabora.co.uk> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2 of the GNU General Public License + * as published by the Free Software Foundation. + * + * The MFGPTs are documented in AMD Geode CS5536 Companion Device Data Book. + */ + +#include <linux/kernel.h> +#include <linux/irq.h> +#include <linux/interrupt.h> +#include <linux/module.h> +#include <linux/cs5535.h> +#include <linux/clockchips.h> + +#define DRV_NAME "cs5535-clockevt" + +static int timer_irq; +module_param_named(irq, timer_irq, int, 0644); +MODULE_PARM_DESC(irq, "Which IRQ to use for the clock source MFGPT ticks."); + +/* + * We are using the 32.768kHz input clock - it's the only one that has the + * ranges we find desirable. The following table lists the suitable + * divisors and the associated Hz, minimum interval and the maximum interval: + * + * Divisor Hz Min Delta (s) Max Delta (s) + * 1 32768 .00048828125 2.000 + * 2 16384 .0009765625 4.000 + * 4 8192 .001953125 8.000 + * 8 4096 .00390625 16.000 + * 16 2048 .0078125 32.000 + * 32 1024 .015625 64.000 + * 64 512 .03125 128.000 + * 128 256 .0625 256.000 + * 256 128 .125 512.000 + */ + +static unsigned int cs5535_tick_mode = CLOCK_EVT_MODE_SHUTDOWN; +static struct cs5535_mfgpt_timer *cs5535_event_clock; + +/* Selected from the table above */ + +#define MFGPT_DIVISOR 16 +#define MFGPT_SCALE 4 /* divisor = 2^(scale) */ +#define MFGPT_HZ (32768 / MFGPT_DIVISOR) +#define MFGPT_PERIODIC (MFGPT_HZ / HZ) + +/* + * The MFGPT timers on the CS5536 provide us with suitable timers to use + * as clock event sources - not as good as a HPET or APIC, but certainly + * better than the PIT. This isn't a general purpose MFGPT driver, but + * a simplified one designed specifically to act as a clock event source. + * For full details about the MFGPT, please consult the CS5536 data sheet. + */ + +static void disable_timer(struct cs5535_mfgpt_timer *timer) +{ + /* avoid races by clearing CMP1 and CMP2 unconditionally */ + cs5535_mfgpt_write(timer, MFGPT_REG_SETUP, + (uint16_t) ~MFGPT_SETUP_CNTEN | MFGPT_SETUP_CMP1 | + MFGPT_SETUP_CMP2); +} + +static void start_timer(struct cs5535_mfgpt_timer *timer, uint16_t delta) +{ + cs5535_mfgpt_write(timer, MFGPT_REG_CMP2, delta); + cs5535_mfgpt_write(timer, MFGPT_REG_COUNTER, 0); + + cs5535_mfgpt_write(timer, MFGPT_REG_SETUP, + MFGPT_SETUP_CNTEN | MFGPT_SETUP_CMP2); +} + +static void mfgpt_set_mode(enum clock_event_mode mode, + struct clock_event_device *evt) +{ + disable_timer(cs5535_event_clock); + + if (mode == CLOCK_EVT_MODE_PERIODIC) + start_timer(cs5535_event_clock, MFGPT_PERIODIC); + + cs5535_tick_mode = mode; +} + +static int mfgpt_next_event(unsigned long delta, struct clock_event_device *evt) +{ + start_timer(cs5535_event_clock, delta); + return 0; +} + +static struct clock_event_device cs5535_clockevent = { + .name = DRV_NAME, + .features = CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT, + .set_mode = mfgpt_set_mode, + .set_next_event = mfgpt_next_event, + .rating = 250, +}; + +static irqreturn_t mfgpt_tick(int irq, void *dev_id) +{ + uint16_t val = cs5535_mfgpt_read(cs5535_event_clock, MFGPT_REG_SETUP); + + /* See if the interrupt was for us */ + if (!(val & (MFGPT_SETUP_SETUP | MFGPT_SETUP_CMP2 | MFGPT_SETUP_CMP1))) + return IRQ_NONE; + + /* Turn off the clock (and clear the event) */ + disable_timer(cs5535_event_clock); + + if (cs5535_tick_mode == CLOCK_EVT_MODE_SHUTDOWN) + return IRQ_HANDLED; + + /* Clear the counter */ + cs5535_mfgpt_write(cs5535_event_clock, MFGPT_REG_COUNTER, 0); + + /* Restart the clock in periodic mode */ + + if (cs5535_tick_mode == CLOCK_EVT_MODE_PERIODIC) + cs5535_mfgpt_write(cs5535_event_clock, MFGPT_REG_SETUP, + MFGPT_SETUP_CNTEN | MFGPT_SETUP_CMP2); + + cs5535_clockevent.event_handler(&cs5535_clockevent); + return IRQ_HANDLED; +} + +static struct irqaction mfgptirq = { + .handler = mfgpt_tick, + .flags = IRQF_DISABLED | IRQF_NOBALANCING | IRQF_TIMER | IRQF_SHARED, + .name = DRV_NAME, +}; + +static int __init cs5535_mfgpt_init(void) +{ + struct cs5535_mfgpt_timer *timer; + int ret; + uint16_t val; + + timer = cs5535_mfgpt_alloc_timer(MFGPT_TIMER_ANY, MFGPT_DOMAIN_WORKING); + if (!timer) { + printk(KERN_ERR DRV_NAME ": Could not allocate MFGPT timer\n"); + return -ENODEV; + } + cs5535_event_clock = timer; + + /* Set up the IRQ on the MFGPT side */ + if (cs5535_mfgpt_setup_irq(timer, MFGPT_CMP2, &timer_irq)) { + printk(KERN_ERR DRV_NAME ": Could not set up IRQ %d\n", + timer_irq); + goto err_timer; + } + + /* And register it with the kernel */ + ret = setup_irq(timer_irq, &mfgptirq); + if (ret) { + printk(KERN_ERR DRV_NAME ": Unable to set up the interrupt.\n"); + goto err_irq; + } + + /* Set the clock scale and enable the event mode for CMP2 */ + val = MFGPT_SCALE | (3 << 8); + + cs5535_mfgpt_write(cs5535_event_clock, MFGPT_REG_SETUP, val); + + /* Set up the clock event */ + printk(KERN_INFO DRV_NAME + ": Registering MFGPT timer as a clock event, using IRQ %d\n", + timer_irq); + clockevents_config_and_register(&cs5535_clockevent, MFGPT_HZ, + 0xF, 0xFFFE); + + return 0; + +err_irq: + cs5535_mfgpt_release_irq(cs5535_event_clock, MFGPT_CMP2, &timer_irq); +err_timer: + cs5535_mfgpt_free_timer(cs5535_event_clock); + printk(KERN_ERR DRV_NAME ": Unable to set up the MFGPT clock source\n"); + return -EIO; +} + +module_init(cs5535_mfgpt_init); + +MODULE_AUTHOR("Andres Salomon <dilinger@queued.net>"); +MODULE_DESCRIPTION("CS5535/CS5536 MFGPT clock event driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/clocksource/cyclone.c b/drivers/clocksource/cyclone.c new file mode 100644 index 000000000..9e0998f22 --- /dev/null +++ b/drivers/clocksource/cyclone.c @@ -0,0 +1,113 @@ +#include <linux/clocksource.h> +#include <linux/string.h> +#include <linux/errno.h> +#include <linux/timex.h> +#include <linux/init.h> + +#include <asm/pgtable.h> +#include <asm/io.h> + +#include <asm/mach_timer.h> + +#define CYCLONE_CBAR_ADDR 0xFEB00CD0 /* base address ptr */ +#define CYCLONE_PMCC_OFFSET 0x51A0 /* offset to control register */ +#define CYCLONE_MPCS_OFFSET 0x51A8 /* offset to select register */ +#define CYCLONE_MPMC_OFFSET 0x51D0 /* offset to count register */ +#define CYCLONE_TIMER_FREQ 99780000 /* 100Mhz, but not really */ +#define CYCLONE_TIMER_MASK CLOCKSOURCE_MASK(32) /* 32 bit mask */ + +int use_cyclone = 0; +static void __iomem *cyclone_ptr; + +static cycle_t read_cyclone(struct clocksource *cs) +{ + return (cycle_t)readl(cyclone_ptr); +} + +static struct clocksource clocksource_cyclone = { + .name = "cyclone", + .rating = 250, + .read = read_cyclone, + .mask = CYCLONE_TIMER_MASK, + .flags = CLOCK_SOURCE_IS_CONTINUOUS, +}; + +static int __init init_cyclone_clocksource(void) +{ + unsigned long base; /* saved value from CBAR */ + unsigned long offset; + u32 __iomem* volatile cyclone_timer; /* Cyclone MPMC0 register */ + u32 __iomem* reg; + int i; + + /* make sure we're on a summit box: */ + if (!use_cyclone) + return -ENODEV; + + printk(KERN_INFO "Summit chipset: Starting Cyclone Counter.\n"); + + /* find base address: */ + offset = CYCLONE_CBAR_ADDR; + reg = ioremap_nocache(offset, sizeof(reg)); + if (!reg) { + printk(KERN_ERR "Summit chipset: Could not find valid CBAR register.\n"); + return -ENODEV; + } + /* even on 64bit systems, this is only 32bits: */ + base = readl(reg); + iounmap(reg); + if (!base) { + printk(KERN_ERR "Summit chipset: Could not find valid CBAR value.\n"); + return -ENODEV; + } + + /* setup PMCC: */ + offset = base + CYCLONE_PMCC_OFFSET; + reg = ioremap_nocache(offset, sizeof(reg)); + if (!reg) { + printk(KERN_ERR "Summit chipset: Could not find valid PMCC register.\n"); + return -ENODEV; + } + writel(0x00000001,reg); + iounmap(reg); + + /* setup MPCS: */ + offset = base + CYCLONE_MPCS_OFFSET; + reg = ioremap_nocache(offset, sizeof(reg)); + if (!reg) { + printk(KERN_ERR "Summit chipset: Could not find valid MPCS register.\n"); + return -ENODEV; + } + writel(0x00000001,reg); + iounmap(reg); + + /* map in cyclone_timer: */ + offset = base + CYCLONE_MPMC_OFFSET; + cyclone_timer = ioremap_nocache(offset, sizeof(u64)); + if (!cyclone_timer) { + printk(KERN_ERR "Summit chipset: Could not find valid MPMC register.\n"); + return -ENODEV; + } + + /* quick test to make sure its ticking: */ + for (i = 0; i < 3; i++){ + u32 old = readl(cyclone_timer); + int stall = 100; + + while (stall--) + barrier(); + + if (readl(cyclone_timer) == old) { + printk(KERN_ERR "Summit chipset: Counter not counting! DISABLED\n"); + iounmap(cyclone_timer); + cyclone_timer = NULL; + return -ENODEV; + } + } + cyclone_ptr = cyclone_timer; + + return clocksource_register_hz(&clocksource_cyclone, + CYCLONE_TIMER_FREQ); +} + +arch_initcall(init_cyclone_clocksource); diff --git a/drivers/clocksource/dw_apb_timer.c b/drivers/clocksource/dw_apb_timer.c new file mode 100644 index 000000000..8c2a35f26 --- /dev/null +++ b/drivers/clocksource/dw_apb_timer.c @@ -0,0 +1,401 @@ +/* + * (C) Copyright 2009 Intel Corporation + * Author: Jacob Pan (jacob.jun.pan@intel.com) + * + * Shared with ARM platforms, Jamie Iles, Picochip 2011 + * + * 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/dw_apb_timer.h> +#include <linux/delay.h> +#include <linux/kernel.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/io.h> +#include <linux/slab.h> + +#define APBT_MIN_PERIOD 4 +#define APBT_MIN_DELTA_USEC 200 + +#define APBTMR_N_LOAD_COUNT 0x00 +#define APBTMR_N_CURRENT_VALUE 0x04 +#define APBTMR_N_CONTROL 0x08 +#define APBTMR_N_EOI 0x0c +#define APBTMR_N_INT_STATUS 0x10 + +#define APBTMRS_INT_STATUS 0xa0 +#define APBTMRS_EOI 0xa4 +#define APBTMRS_RAW_INT_STATUS 0xa8 +#define APBTMRS_COMP_VERSION 0xac + +#define APBTMR_CONTROL_ENABLE (1 << 0) +/* 1: periodic, 0:free running. */ +#define APBTMR_CONTROL_MODE_PERIODIC (1 << 1) +#define APBTMR_CONTROL_INT (1 << 2) + +static inline struct dw_apb_clock_event_device * +ced_to_dw_apb_ced(struct clock_event_device *evt) +{ + return container_of(evt, struct dw_apb_clock_event_device, ced); +} + +static inline struct dw_apb_clocksource * +clocksource_to_dw_apb_clocksource(struct clocksource *cs) +{ + return container_of(cs, struct dw_apb_clocksource, cs); +} + +static unsigned long apbt_readl(struct dw_apb_timer *timer, unsigned long offs) +{ + return readl(timer->base + offs); +} + +static void apbt_writel(struct dw_apb_timer *timer, unsigned long val, + unsigned long offs) +{ + writel(val, timer->base + offs); +} + +static void apbt_disable_int(struct dw_apb_timer *timer) +{ + unsigned long ctrl = apbt_readl(timer, APBTMR_N_CONTROL); + + ctrl |= APBTMR_CONTROL_INT; + apbt_writel(timer, ctrl, APBTMR_N_CONTROL); +} + +/** + * dw_apb_clockevent_pause() - stop the clock_event_device from running + * + * @dw_ced: The APB clock to stop generating events. + */ +void dw_apb_clockevent_pause(struct dw_apb_clock_event_device *dw_ced) +{ + disable_irq(dw_ced->timer.irq); + apbt_disable_int(&dw_ced->timer); +} + +static void apbt_eoi(struct dw_apb_timer *timer) +{ + apbt_readl(timer, APBTMR_N_EOI); +} + +static irqreturn_t dw_apb_clockevent_irq(int irq, void *data) +{ + struct clock_event_device *evt = data; + struct dw_apb_clock_event_device *dw_ced = ced_to_dw_apb_ced(evt); + + if (!evt->event_handler) { + pr_info("Spurious APBT timer interrupt %d", irq); + return IRQ_NONE; + } + + if (dw_ced->eoi) + dw_ced->eoi(&dw_ced->timer); + + evt->event_handler(evt); + return IRQ_HANDLED; +} + +static void apbt_enable_int(struct dw_apb_timer *timer) +{ + unsigned long ctrl = apbt_readl(timer, APBTMR_N_CONTROL); + /* clear pending intr */ + apbt_readl(timer, APBTMR_N_EOI); + ctrl &= ~APBTMR_CONTROL_INT; + apbt_writel(timer, ctrl, APBTMR_N_CONTROL); +} + +static void apbt_set_mode(enum clock_event_mode mode, + struct clock_event_device *evt) +{ + unsigned long ctrl; + unsigned long period; + struct dw_apb_clock_event_device *dw_ced = ced_to_dw_apb_ced(evt); + + pr_debug("%s CPU %d mode=%d\n", __func__, first_cpu(*evt->cpumask), + mode); + + switch (mode) { + case CLOCK_EVT_MODE_PERIODIC: + period = DIV_ROUND_UP(dw_ced->timer.freq, HZ); + ctrl = apbt_readl(&dw_ced->timer, APBTMR_N_CONTROL); + ctrl |= APBTMR_CONTROL_MODE_PERIODIC; + apbt_writel(&dw_ced->timer, ctrl, APBTMR_N_CONTROL); + /* + * DW APB p. 46, have to disable timer before load counter, + * may cause sync problem. + */ + ctrl &= ~APBTMR_CONTROL_ENABLE; + apbt_writel(&dw_ced->timer, ctrl, APBTMR_N_CONTROL); + udelay(1); + pr_debug("Setting clock period %lu for HZ %d\n", period, HZ); + apbt_writel(&dw_ced->timer, period, APBTMR_N_LOAD_COUNT); + ctrl |= APBTMR_CONTROL_ENABLE; + apbt_writel(&dw_ced->timer, ctrl, APBTMR_N_CONTROL); + break; + + case CLOCK_EVT_MODE_ONESHOT: + ctrl = apbt_readl(&dw_ced->timer, APBTMR_N_CONTROL); + /* + * set free running mode, this mode will let timer reload max + * timeout which will give time (3min on 25MHz clock) to rearm + * the next event, therefore emulate the one-shot mode. + */ + ctrl &= ~APBTMR_CONTROL_ENABLE; + ctrl &= ~APBTMR_CONTROL_MODE_PERIODIC; + + apbt_writel(&dw_ced->timer, ctrl, APBTMR_N_CONTROL); + /* write again to set free running mode */ + apbt_writel(&dw_ced->timer, ctrl, APBTMR_N_CONTROL); + + /* + * DW APB p. 46, load counter with all 1s before starting free + * running mode. + */ + apbt_writel(&dw_ced->timer, ~0, APBTMR_N_LOAD_COUNT); + ctrl &= ~APBTMR_CONTROL_INT; + ctrl |= APBTMR_CONTROL_ENABLE; + apbt_writel(&dw_ced->timer, ctrl, APBTMR_N_CONTROL); + break; + + case CLOCK_EVT_MODE_UNUSED: + case CLOCK_EVT_MODE_SHUTDOWN: + ctrl = apbt_readl(&dw_ced->timer, APBTMR_N_CONTROL); + ctrl &= ~APBTMR_CONTROL_ENABLE; + apbt_writel(&dw_ced->timer, ctrl, APBTMR_N_CONTROL); + break; + + case CLOCK_EVT_MODE_RESUME: + apbt_enable_int(&dw_ced->timer); + break; + } +} + +static int apbt_next_event(unsigned long delta, + struct clock_event_device *evt) +{ + unsigned long ctrl; + struct dw_apb_clock_event_device *dw_ced = ced_to_dw_apb_ced(evt); + + /* Disable timer */ + ctrl = apbt_readl(&dw_ced->timer, APBTMR_N_CONTROL); + ctrl &= ~APBTMR_CONTROL_ENABLE; + apbt_writel(&dw_ced->timer, ctrl, APBTMR_N_CONTROL); + /* write new count */ + apbt_writel(&dw_ced->timer, delta, APBTMR_N_LOAD_COUNT); + ctrl |= APBTMR_CONTROL_ENABLE; + apbt_writel(&dw_ced->timer, ctrl, APBTMR_N_CONTROL); + + return 0; +} + +/** + * dw_apb_clockevent_init() - use an APB timer as a clock_event_device + * + * @cpu: The CPU the events will be targeted at. + * @name: The name used for the timer and the IRQ for it. + * @rating: The rating to give the timer. + * @base: I/O base for the timer registers. + * @irq: The interrupt number to use for the timer. + * @freq: The frequency that the timer counts at. + * + * This creates a clock_event_device for using with the generic clock layer + * but does not start and register it. This should be done with + * dw_apb_clockevent_register() as the next step. If this is the first time + * it has been called for a timer then the IRQ will be requested, if not it + * just be enabled to allow CPU hotplug to avoid repeatedly requesting and + * releasing the IRQ. + */ +struct dw_apb_clock_event_device * +dw_apb_clockevent_init(int cpu, const char *name, unsigned rating, + void __iomem *base, int irq, unsigned long freq) +{ + struct dw_apb_clock_event_device *dw_ced = + kzalloc(sizeof(*dw_ced), GFP_KERNEL); + int err; + + if (!dw_ced) + return NULL; + + dw_ced->timer.base = base; + dw_ced->timer.irq = irq; + dw_ced->timer.freq = freq; + + clockevents_calc_mult_shift(&dw_ced->ced, freq, APBT_MIN_PERIOD); + dw_ced->ced.max_delta_ns = clockevent_delta2ns(0x7fffffff, + &dw_ced->ced); + dw_ced->ced.min_delta_ns = clockevent_delta2ns(5000, &dw_ced->ced); + dw_ced->ced.cpumask = cpumask_of(cpu); + dw_ced->ced.features = CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT; + dw_ced->ced.set_mode = apbt_set_mode; + dw_ced->ced.set_next_event = apbt_next_event; + dw_ced->ced.irq = dw_ced->timer.irq; + dw_ced->ced.rating = rating; + dw_ced->ced.name = name; + + dw_ced->irqaction.name = dw_ced->ced.name; + dw_ced->irqaction.handler = dw_apb_clockevent_irq; + dw_ced->irqaction.dev_id = &dw_ced->ced; + dw_ced->irqaction.irq = irq; + dw_ced->irqaction.flags = IRQF_TIMER | IRQF_IRQPOLL | + IRQF_NOBALANCING | + IRQF_DISABLED; + + dw_ced->eoi = apbt_eoi; + err = setup_irq(irq, &dw_ced->irqaction); + if (err) { + pr_err("failed to request timer irq\n"); + kfree(dw_ced); + dw_ced = NULL; + } + + return dw_ced; +} + +/** + * dw_apb_clockevent_resume() - resume a clock that has been paused. + * + * @dw_ced: The APB clock to resume. + */ +void dw_apb_clockevent_resume(struct dw_apb_clock_event_device *dw_ced) +{ + enable_irq(dw_ced->timer.irq); +} + +/** + * dw_apb_clockevent_stop() - stop the clock_event_device and release the IRQ. + * + * @dw_ced: The APB clock to stop generating the events. + */ +void dw_apb_clockevent_stop(struct dw_apb_clock_event_device *dw_ced) +{ + free_irq(dw_ced->timer.irq, &dw_ced->ced); +} + +/** + * dw_apb_clockevent_register() - register the clock with the generic layer + * + * @dw_ced: The APB clock to register as a clock_event_device. + */ +void dw_apb_clockevent_register(struct dw_apb_clock_event_device *dw_ced) +{ + apbt_writel(&dw_ced->timer, 0, APBTMR_N_CONTROL); + clockevents_register_device(&dw_ced->ced); + apbt_enable_int(&dw_ced->timer); +} + +/** + * dw_apb_clocksource_start() - start the clocksource counting. + * + * @dw_cs: The clocksource to start. + * + * This is used to start the clocksource before registration and can be used + * to enable calibration of timers. + */ +void dw_apb_clocksource_start(struct dw_apb_clocksource *dw_cs) +{ + /* + * start count down from 0xffff_ffff. this is done by toggling the + * enable bit then load initial load count to ~0. + */ + unsigned long ctrl = apbt_readl(&dw_cs->timer, APBTMR_N_CONTROL); + + ctrl &= ~APBTMR_CONTROL_ENABLE; + apbt_writel(&dw_cs->timer, ctrl, APBTMR_N_CONTROL); + apbt_writel(&dw_cs->timer, ~0, APBTMR_N_LOAD_COUNT); + /* enable, mask interrupt */ + ctrl &= ~APBTMR_CONTROL_MODE_PERIODIC; + ctrl |= (APBTMR_CONTROL_ENABLE | APBTMR_CONTROL_INT); + apbt_writel(&dw_cs->timer, ctrl, APBTMR_N_CONTROL); + /* read it once to get cached counter value initialized */ + dw_apb_clocksource_read(dw_cs); +} + +static cycle_t __apbt_read_clocksource(struct clocksource *cs) +{ + unsigned long current_count; + struct dw_apb_clocksource *dw_cs = + clocksource_to_dw_apb_clocksource(cs); + + current_count = apbt_readl(&dw_cs->timer, APBTMR_N_CURRENT_VALUE); + + return (cycle_t)~current_count; +} + +static void apbt_restart_clocksource(struct clocksource *cs) +{ + struct dw_apb_clocksource *dw_cs = + clocksource_to_dw_apb_clocksource(cs); + + dw_apb_clocksource_start(dw_cs); +} + +/** + * dw_apb_clocksource_init() - use an APB timer as a clocksource. + * + * @rating: The rating to give the clocksource. + * @name: The name for the clocksource. + * @base: The I/O base for the timer registers. + * @freq: The frequency that the timer counts at. + * + * This creates a clocksource using an APB timer but does not yet register it + * with the clocksource system. This should be done with + * dw_apb_clocksource_register() as the next step. + */ +struct dw_apb_clocksource * +dw_apb_clocksource_init(unsigned rating, const char *name, void __iomem *base, + unsigned long freq) +{ + struct dw_apb_clocksource *dw_cs = kzalloc(sizeof(*dw_cs), GFP_KERNEL); + + if (!dw_cs) + return NULL; + + dw_cs->timer.base = base; + dw_cs->timer.freq = freq; + dw_cs->cs.name = name; + dw_cs->cs.rating = rating; + dw_cs->cs.read = __apbt_read_clocksource; + dw_cs->cs.mask = CLOCKSOURCE_MASK(32); + dw_cs->cs.flags = CLOCK_SOURCE_IS_CONTINUOUS; + dw_cs->cs.resume = apbt_restart_clocksource; + + return dw_cs; +} + +/** + * dw_apb_clocksource_register() - register the APB clocksource. + * + * @dw_cs: The clocksource to register. + */ +void dw_apb_clocksource_register(struct dw_apb_clocksource *dw_cs) +{ + clocksource_register_hz(&dw_cs->cs, dw_cs->timer.freq); +} + +/** + * dw_apb_clocksource_read() - read the current value of a clocksource. + * + * @dw_cs: The clocksource to read. + */ +cycle_t dw_apb_clocksource_read(struct dw_apb_clocksource *dw_cs) +{ + return (cycle_t)~apbt_readl(&dw_cs->timer, APBTMR_N_CURRENT_VALUE); +} + +/** + * dw_apb_clocksource_unregister() - unregister and free a clocksource. + * + * @dw_cs: The clocksource to unregister/free. + */ +void dw_apb_clocksource_unregister(struct dw_apb_clocksource *dw_cs) +{ + clocksource_unregister(&dw_cs->cs); + + kfree(dw_cs); +} diff --git a/drivers/clocksource/dw_apb_timer_of.c b/drivers/clocksource/dw_apb_timer_of.c new file mode 100644 index 000000000..f22417cb0 --- /dev/null +++ b/drivers/clocksource/dw_apb_timer_of.c @@ -0,0 +1,127 @@ +/* + * Copyright (C) 2012 Altera Corporation + * Copyright (c) 2011 Picochip Ltd., Jamie Iles + * + * Modified from mach-picoxcell/time.c + * + * 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. + * + * 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, see <http://www.gnu.org/licenses/>. + */ +#include <linux/dw_apb_timer.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/of_irq.h> + +#include <asm/mach/time.h> +#include <asm/sched_clock.h> + +static void timer_get_base_and_rate(struct device_node *np, + void __iomem **base, u32 *rate) +{ + *base = of_iomap(np, 0); + + if (!*base) + panic("Unable to map regs for %s", np->name); + + if (of_property_read_u32(np, "clock-freq", rate) && + of_property_read_u32(np, "clock-frequency", rate)) + panic("No clock-frequency property for %s", np->name); +} + +static void add_clockevent(struct device_node *event_timer) +{ + void __iomem *iobase; + struct dw_apb_clock_event_device *ced; + u32 irq, rate; + + irq = irq_of_parse_and_map(event_timer, 0); + if (irq == 0) + panic("No IRQ for clock event timer"); + + timer_get_base_and_rate(event_timer, &iobase, &rate); + + ced = dw_apb_clockevent_init(0, event_timer->name, 300, iobase, irq, + rate); + if (!ced) + panic("Unable to initialise clockevent device"); + + dw_apb_clockevent_register(ced); +} + +static void add_clocksource(struct device_node *source_timer) +{ + void __iomem *iobase; + struct dw_apb_clocksource *cs; + u32 rate; + + timer_get_base_and_rate(source_timer, &iobase, &rate); + + cs = dw_apb_clocksource_init(300, source_timer->name, iobase, rate); + if (!cs) + panic("Unable to initialise clocksource device"); + + dw_apb_clocksource_start(cs); + dw_apb_clocksource_register(cs); +} + +static void __iomem *sched_io_base; + +static u32 read_sched_clock(void) +{ + return ~__raw_readl(sched_io_base); +} + +static const struct of_device_id sptimer_ids[] __initconst = { + { .compatible = "picochip,pc3x2-rtc" }, + { .compatible = "snps,dw-apb-timer-sp" }, + { /* Sentinel */ }, +}; + +static void init_sched_clock(void) +{ + struct device_node *sched_timer; + u32 rate; + + sched_timer = of_find_matching_node(NULL, sptimer_ids); + if (!sched_timer) + panic("No RTC for sched clock to use"); + + timer_get_base_and_rate(sched_timer, &sched_io_base, &rate); + of_node_put(sched_timer); + + setup_sched_clock(read_sched_clock, 32, rate); +} + +static const struct of_device_id osctimer_ids[] __initconst = { + { .compatible = "picochip,pc3x2-timer" }, + { .compatible = "snps,dw-apb-timer-osc" }, + {}, +}; + +void __init dw_apb_timer_init(void) +{ + struct device_node *event_timer, *source_timer; + + event_timer = of_find_matching_node(NULL, osctimer_ids); + if (!event_timer) + panic("No timer for clockevent"); + add_clockevent(event_timer); + + source_timer = of_find_matching_node(event_timer, osctimer_ids); + if (!source_timer) + panic("No timer for clocksource"); + add_clocksource(source_timer); + + of_node_put(source_timer); + + init_sched_clock(); +} diff --git a/drivers/clocksource/em_sti.c b/drivers/clocksource/em_sti.c new file mode 100644 index 000000000..314184932 --- /dev/null +++ b/drivers/clocksource/em_sti.c @@ -0,0 +1,417 @@ +/* + * Emma Mobile Timer Support - STI + * + * Copyright (C) 2012 Magnus Damm + * + * 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 + * + * 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/init.h> +#include <linux/platform_device.h> +#include <linux/spinlock.h> +#include <linux/interrupt.h> +#include <linux/ioport.h> +#include <linux/io.h> +#include <linux/clk.h> +#include <linux/irq.h> +#include <linux/err.h> +#include <linux/delay.h> +#include <linux/clocksource.h> +#include <linux/clockchips.h> +#include <linux/slab.h> +#include <linux/module.h> + +enum { USER_CLOCKSOURCE, USER_CLOCKEVENT, USER_NR }; + +struct em_sti_priv { + void __iomem *base; + struct clk *clk; + struct platform_device *pdev; + unsigned int active[USER_NR]; + unsigned long rate; + raw_spinlock_t lock; + struct clock_event_device ced; + struct clocksource cs; +}; + +#define STI_CONTROL 0x00 +#define STI_COMPA_H 0x10 +#define STI_COMPA_L 0x14 +#define STI_COMPB_H 0x18 +#define STI_COMPB_L 0x1c +#define STI_COUNT_H 0x20 +#define STI_COUNT_L 0x24 +#define STI_COUNT_RAW_H 0x28 +#define STI_COUNT_RAW_L 0x2c +#define STI_SET_H 0x30 +#define STI_SET_L 0x34 +#define STI_INTSTATUS 0x40 +#define STI_INTRAWSTATUS 0x44 +#define STI_INTENSET 0x48 +#define STI_INTENCLR 0x4c +#define STI_INTFFCLR 0x50 + +static inline unsigned long em_sti_read(struct em_sti_priv *p, int offs) +{ + return ioread32(p->base + offs); +} + +static inline void em_sti_write(struct em_sti_priv *p, int offs, + unsigned long value) +{ + iowrite32(value, p->base + offs); +} + +static int em_sti_enable(struct em_sti_priv *p) +{ + int ret; + + /* enable clock */ + ret = clk_enable(p->clk); + if (ret) { + dev_err(&p->pdev->dev, "cannot enable clock\n"); + return ret; + } + + /* configure channel, periodic mode and maximum timeout */ + p->rate = clk_get_rate(p->clk); + + /* reset the counter */ + em_sti_write(p, STI_SET_H, 0x40000000); + em_sti_write(p, STI_SET_L, 0x00000000); + + /* mask and clear pending interrupts */ + em_sti_write(p, STI_INTENCLR, 3); + em_sti_write(p, STI_INTFFCLR, 3); + + /* enable updates of counter registers */ + em_sti_write(p, STI_CONTROL, 1); + + return 0; +} + +static void em_sti_disable(struct em_sti_priv *p) +{ + /* mask interrupts */ + em_sti_write(p, STI_INTENCLR, 3); + + /* stop clock */ + clk_disable(p->clk); +} + +static cycle_t em_sti_count(struct em_sti_priv *p) +{ + cycle_t ticks; + unsigned long flags; + + /* the STI hardware buffers the 48-bit count, but to + * break it out into two 32-bit access the registers + * must be accessed in a certain order. + * Always read STI_COUNT_H before STI_COUNT_L. + */ + raw_spin_lock_irqsave(&p->lock, flags); + ticks = (cycle_t)(em_sti_read(p, STI_COUNT_H) & 0xffff) << 32; + ticks |= em_sti_read(p, STI_COUNT_L); + raw_spin_unlock_irqrestore(&p->lock, flags); + + return ticks; +} + +static cycle_t em_sti_set_next(struct em_sti_priv *p, cycle_t next) +{ + unsigned long flags; + + raw_spin_lock_irqsave(&p->lock, flags); + + /* mask compare A interrupt */ + em_sti_write(p, STI_INTENCLR, 1); + + /* update compare A value */ + em_sti_write(p, STI_COMPA_H, next >> 32); + em_sti_write(p, STI_COMPA_L, next & 0xffffffff); + + /* clear compare A interrupt source */ + em_sti_write(p, STI_INTFFCLR, 1); + + /* unmask compare A interrupt */ + em_sti_write(p, STI_INTENSET, 1); + + raw_spin_unlock_irqrestore(&p->lock, flags); + + return next; +} + +static irqreturn_t em_sti_interrupt(int irq, void *dev_id) +{ + struct em_sti_priv *p = dev_id; + + p->ced.event_handler(&p->ced); + return IRQ_HANDLED; +} + +static int em_sti_start(struct em_sti_priv *p, unsigned int user) +{ + unsigned long flags; + int used_before; + int ret = 0; + + raw_spin_lock_irqsave(&p->lock, flags); + used_before = p->active[USER_CLOCKSOURCE] | p->active[USER_CLOCKEVENT]; + if (!used_before) + ret = em_sti_enable(p); + + if (!ret) + p->active[user] = 1; + raw_spin_unlock_irqrestore(&p->lock, flags); + + return ret; +} + +static void em_sti_stop(struct em_sti_priv *p, unsigned int user) +{ + unsigned long flags; + int used_before, used_after; + + raw_spin_lock_irqsave(&p->lock, flags); + used_before = p->active[USER_CLOCKSOURCE] | p->active[USER_CLOCKEVENT]; + p->active[user] = 0; + used_after = p->active[USER_CLOCKSOURCE] | p->active[USER_CLOCKEVENT]; + + if (used_before && !used_after) + em_sti_disable(p); + raw_spin_unlock_irqrestore(&p->lock, flags); +} + +static struct em_sti_priv *cs_to_em_sti(struct clocksource *cs) +{ + return container_of(cs, struct em_sti_priv, cs); +} + +static cycle_t em_sti_clocksource_read(struct clocksource *cs) +{ + return em_sti_count(cs_to_em_sti(cs)); +} + +static int em_sti_clocksource_enable(struct clocksource *cs) +{ + int ret; + struct em_sti_priv *p = cs_to_em_sti(cs); + + ret = em_sti_start(p, USER_CLOCKSOURCE); + if (!ret) + __clocksource_updatefreq_hz(cs, p->rate); + return ret; +} + +static void em_sti_clocksource_disable(struct clocksource *cs) +{ + em_sti_stop(cs_to_em_sti(cs), USER_CLOCKSOURCE); +} + +static void em_sti_clocksource_resume(struct clocksource *cs) +{ + em_sti_clocksource_enable(cs); +} + +static int em_sti_register_clocksource(struct em_sti_priv *p) +{ + struct clocksource *cs = &p->cs; + + memset(cs, 0, sizeof(*cs)); + cs->name = dev_name(&p->pdev->dev); + cs->rating = 200; + cs->read = em_sti_clocksource_read; + cs->enable = em_sti_clocksource_enable; + cs->disable = em_sti_clocksource_disable; + cs->suspend = em_sti_clocksource_disable; + cs->resume = em_sti_clocksource_resume; + cs->mask = CLOCKSOURCE_MASK(48); + cs->flags = CLOCK_SOURCE_IS_CONTINUOUS; + + dev_info(&p->pdev->dev, "used as clock source\n"); + + /* Register with dummy 1 Hz value, gets updated in ->enable() */ + clocksource_register_hz(cs, 1); + return 0; +} + +static struct em_sti_priv *ced_to_em_sti(struct clock_event_device *ced) +{ + return container_of(ced, struct em_sti_priv, ced); +} + +static void em_sti_clock_event_mode(enum clock_event_mode mode, + struct clock_event_device *ced) +{ + struct em_sti_priv *p = ced_to_em_sti(ced); + + /* deal with old setting first */ + switch (ced->mode) { + case CLOCK_EVT_MODE_ONESHOT: + em_sti_stop(p, USER_CLOCKEVENT); + break; + default: + break; + } + + switch (mode) { + case CLOCK_EVT_MODE_ONESHOT: + dev_info(&p->pdev->dev, "used for oneshot clock events\n"); + em_sti_start(p, USER_CLOCKEVENT); + clockevents_config(&p->ced, p->rate); + break; + case CLOCK_EVT_MODE_SHUTDOWN: + case CLOCK_EVT_MODE_UNUSED: + em_sti_stop(p, USER_CLOCKEVENT); + break; + default: + break; + } +} + +static int em_sti_clock_event_next(unsigned long delta, + struct clock_event_device *ced) +{ + struct em_sti_priv *p = ced_to_em_sti(ced); + cycle_t next; + int safe; + + next = em_sti_set_next(p, em_sti_count(p) + delta); + safe = em_sti_count(p) < (next - 1); + + return !safe; +} + +static void em_sti_register_clockevent(struct em_sti_priv *p) +{ + struct clock_event_device *ced = &p->ced; + + memset(ced, 0, sizeof(*ced)); + ced->name = dev_name(&p->pdev->dev); + ced->features = CLOCK_EVT_FEAT_ONESHOT; + ced->rating = 200; + ced->cpumask = cpu_possible_mask; + ced->set_next_event = em_sti_clock_event_next; + ced->set_mode = em_sti_clock_event_mode; + + dev_info(&p->pdev->dev, "used for clock events\n"); + + /* Register with dummy 1 Hz value, gets updated in ->set_mode() */ + clockevents_config_and_register(ced, 1, 2, 0xffffffff); +} + +static int em_sti_probe(struct platform_device *pdev) +{ + struct em_sti_priv *p; + struct resource *res; + int irq, ret; + + p = kzalloc(sizeof(*p), GFP_KERNEL); + if (p == NULL) { + dev_err(&pdev->dev, "failed to allocate driver data\n"); + ret = -ENOMEM; + goto err0; + } + + p->pdev = pdev; + platform_set_drvdata(pdev, p); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(&pdev->dev, "failed to get I/O memory\n"); + ret = -EINVAL; + goto err0; + } + + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + dev_err(&pdev->dev, "failed to get irq\n"); + ret = -EINVAL; + goto err0; + } + + /* map memory, let base point to the STI instance */ + p->base = ioremap_nocache(res->start, resource_size(res)); + if (p->base == NULL) { + dev_err(&pdev->dev, "failed to remap I/O memory\n"); + ret = -ENXIO; + goto err0; + } + + /* get hold of clock */ + p->clk = clk_get(&pdev->dev, "sclk"); + if (IS_ERR(p->clk)) { + dev_err(&pdev->dev, "cannot get clock\n"); + ret = PTR_ERR(p->clk); + goto err1; + } + + if (request_irq(irq, em_sti_interrupt, + IRQF_TIMER | IRQF_IRQPOLL | IRQF_NOBALANCING, + dev_name(&pdev->dev), p)) { + dev_err(&pdev->dev, "failed to request low IRQ\n"); + ret = -ENOENT; + goto err2; + } + + raw_spin_lock_init(&p->lock); + em_sti_register_clockevent(p); + em_sti_register_clocksource(p); + return 0; + +err2: + clk_put(p->clk); +err1: + iounmap(p->base); +err0: + kfree(p); + return ret; +} + +static int em_sti_remove(struct platform_device *pdev) +{ + return -EBUSY; /* cannot unregister clockevent and clocksource */ +} + +static const struct of_device_id em_sti_dt_ids[] = { + { .compatible = "renesas,em-sti", }, + {}, +}; +MODULE_DEVICE_TABLE(of, em_sti_dt_ids); + +static struct platform_driver em_sti_device_driver = { + .probe = em_sti_probe, + .remove = em_sti_remove, + .driver = { + .name = "em_sti", + .of_match_table = em_sti_dt_ids, + } +}; + +static int __init em_sti_init(void) +{ + return platform_driver_register(&em_sti_device_driver); +} + +static void __exit em_sti_exit(void) +{ + platform_driver_unregister(&em_sti_device_driver); +} + +subsys_initcall(em_sti_init); +module_exit(em_sti_exit); + +MODULE_AUTHOR("Magnus Damm"); +MODULE_DESCRIPTION("Renesas Emma Mobile STI Timer Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/clocksource/exynos_mct.c b/drivers/clocksource/exynos_mct.c new file mode 100644 index 000000000..b79601859 --- /dev/null +++ b/drivers/clocksource/exynos_mct.c @@ -0,0 +1,558 @@ +/* linux/arch/arm/mach-exynos4/mct.c + * + * Copyright (c) 2011 Samsung Electronics Co., Ltd. + * http://www.samsung.com + * + * EXYNOS4 MCT(Multi-Core Timer) support + * + * 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. +*/ + +#include <linux/sched.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/err.h> +#include <linux/clk.h> +#include <linux/clockchips.h> +#include <linux/platform_device.h> +#include <linux/delay.h> +#include <linux/percpu.h> +#include <linux/of.h> +#include <linux/of_irq.h> +#include <linux/of_address.h> +#include <linux/clocksource.h> + +#include <asm/localtimer.h> +#include <asm/mach/time.h> + +#define EXYNOS4_MCTREG(x) (x) +#define EXYNOS4_MCT_G_CNT_L EXYNOS4_MCTREG(0x100) +#define EXYNOS4_MCT_G_CNT_U EXYNOS4_MCTREG(0x104) +#define EXYNOS4_MCT_G_CNT_WSTAT EXYNOS4_MCTREG(0x110) +#define EXYNOS4_MCT_G_COMP0_L EXYNOS4_MCTREG(0x200) +#define EXYNOS4_MCT_G_COMP0_U EXYNOS4_MCTREG(0x204) +#define EXYNOS4_MCT_G_COMP0_ADD_INCR EXYNOS4_MCTREG(0x208) +#define EXYNOS4_MCT_G_TCON EXYNOS4_MCTREG(0x240) +#define EXYNOS4_MCT_G_INT_CSTAT EXYNOS4_MCTREG(0x244) +#define EXYNOS4_MCT_G_INT_ENB EXYNOS4_MCTREG(0x248) +#define EXYNOS4_MCT_G_WSTAT EXYNOS4_MCTREG(0x24C) +#define _EXYNOS4_MCT_L_BASE EXYNOS4_MCTREG(0x300) +#define EXYNOS4_MCT_L_BASE(x) (_EXYNOS4_MCT_L_BASE + (0x100 * x)) +#define EXYNOS4_MCT_L_MASK (0xffffff00) + +#define MCT_L_TCNTB_OFFSET (0x00) +#define MCT_L_ICNTB_OFFSET (0x08) +#define MCT_L_TCON_OFFSET (0x20) +#define MCT_L_INT_CSTAT_OFFSET (0x30) +#define MCT_L_INT_ENB_OFFSET (0x34) +#define MCT_L_WSTAT_OFFSET (0x40) +#define MCT_G_TCON_START (1 << 8) +#define MCT_G_TCON_COMP0_AUTO_INC (1 << 1) +#define MCT_G_TCON_COMP0_ENABLE (1 << 0) +#define MCT_L_TCON_INTERVAL_MODE (1 << 2) +#define MCT_L_TCON_INT_START (1 << 1) +#define MCT_L_TCON_TIMER_START (1 << 0) + +#define TICK_BASE_CNT 1 + +enum { + MCT_INT_SPI, + MCT_INT_PPI +}; + +enum { + MCT_G0_IRQ, + MCT_G1_IRQ, + MCT_G2_IRQ, + MCT_G3_IRQ, + MCT_L0_IRQ, + MCT_L1_IRQ, + MCT_L2_IRQ, + MCT_L3_IRQ, + MCT_NR_IRQS, +}; + +static void __iomem *reg_base; +static unsigned long clk_rate; +static unsigned int mct_int_type; +static int mct_irqs[MCT_NR_IRQS]; + +struct mct_clock_event_device { + struct clock_event_device *evt; + unsigned long base; + char name[10]; +}; + +static void exynos4_mct_write(unsigned int value, unsigned long offset) +{ + unsigned long stat_addr; + u32 mask; + u32 i; + + __raw_writel(value, reg_base + offset); + + if (likely(offset >= EXYNOS4_MCT_L_BASE(0))) { + stat_addr = (offset & ~EXYNOS4_MCT_L_MASK) + MCT_L_WSTAT_OFFSET; + switch (offset & EXYNOS4_MCT_L_MASK) { + case MCT_L_TCON_OFFSET: + mask = 1 << 3; /* L_TCON write status */ + break; + case MCT_L_ICNTB_OFFSET: + mask = 1 << 1; /* L_ICNTB write status */ + break; + case MCT_L_TCNTB_OFFSET: + mask = 1 << 0; /* L_TCNTB write status */ + break; + default: + return; + } + } else { + switch (offset) { + case EXYNOS4_MCT_G_TCON: + stat_addr = EXYNOS4_MCT_G_WSTAT; + mask = 1 << 16; /* G_TCON write status */ + break; + case EXYNOS4_MCT_G_COMP0_L: + stat_addr = EXYNOS4_MCT_G_WSTAT; + mask = 1 << 0; /* G_COMP0_L write status */ + break; + case EXYNOS4_MCT_G_COMP0_U: + stat_addr = EXYNOS4_MCT_G_WSTAT; + mask = 1 << 1; /* G_COMP0_U write status */ + break; + case EXYNOS4_MCT_G_COMP0_ADD_INCR: + stat_addr = EXYNOS4_MCT_G_WSTAT; + mask = 1 << 2; /* G_COMP0_ADD_INCR w status */ + break; + case EXYNOS4_MCT_G_CNT_L: + stat_addr = EXYNOS4_MCT_G_CNT_WSTAT; + mask = 1 << 0; /* G_CNT_L write status */ + break; + case EXYNOS4_MCT_G_CNT_U: + stat_addr = EXYNOS4_MCT_G_CNT_WSTAT; + mask = 1 << 1; /* G_CNT_U write status */ + break; + default: + return; + } + } + + /* Wait maximum 1 ms until written values are applied */ + for (i = 0; i < loops_per_jiffy / 1000 * HZ; i++) + if (__raw_readl(reg_base + stat_addr) & mask) { + __raw_writel(mask, reg_base + stat_addr); + return; + } + + panic("MCT hangs after writing %d (offset:0x%lx)\n", value, offset); +} + +/* Clocksource handling */ +static void exynos4_mct_frc_start(u32 hi, u32 lo) +{ + u32 reg; + + exynos4_mct_write(lo, EXYNOS4_MCT_G_CNT_L); + exynos4_mct_write(hi, EXYNOS4_MCT_G_CNT_U); + + reg = __raw_readl(reg_base + EXYNOS4_MCT_G_TCON); + reg |= MCT_G_TCON_START; + exynos4_mct_write(reg, EXYNOS4_MCT_G_TCON); +} + +static cycle_t exynos4_frc_read(struct clocksource *cs) +{ + unsigned int lo, hi; + u32 hi2 = __raw_readl(reg_base + EXYNOS4_MCT_G_CNT_U); + + do { + hi = hi2; + lo = __raw_readl(reg_base + EXYNOS4_MCT_G_CNT_L); + hi2 = __raw_readl(reg_base + EXYNOS4_MCT_G_CNT_U); + } while (hi != hi2); + + return ((cycle_t)hi << 32) | lo; +} + +static void exynos4_frc_resume(struct clocksource *cs) +{ + exynos4_mct_frc_start(0, 0); +} + +struct clocksource mct_frc = { + .name = "mct-frc", + .rating = 400, + .read = exynos4_frc_read, + .mask = CLOCKSOURCE_MASK(64), + .flags = CLOCK_SOURCE_IS_CONTINUOUS, + .resume = exynos4_frc_resume, +}; + +static void __init exynos4_clocksource_init(void) +{ + exynos4_mct_frc_start(0, 0); + + if (clocksource_register_hz(&mct_frc, clk_rate)) + panic("%s: can't register clocksource\n", mct_frc.name); +} + +static void exynos4_mct_comp0_stop(void) +{ + unsigned int tcon; + + tcon = __raw_readl(reg_base + EXYNOS4_MCT_G_TCON); + tcon &= ~(MCT_G_TCON_COMP0_ENABLE | MCT_G_TCON_COMP0_AUTO_INC); + + exynos4_mct_write(tcon, EXYNOS4_MCT_G_TCON); + exynos4_mct_write(0, EXYNOS4_MCT_G_INT_ENB); +} + +static void exynos4_mct_comp0_start(enum clock_event_mode mode, + unsigned long cycles) +{ + unsigned int tcon; + cycle_t comp_cycle; + + tcon = __raw_readl(reg_base + EXYNOS4_MCT_G_TCON); + + if (mode == CLOCK_EVT_MODE_PERIODIC) { + tcon |= MCT_G_TCON_COMP0_AUTO_INC; + exynos4_mct_write(cycles, EXYNOS4_MCT_G_COMP0_ADD_INCR); + } + + comp_cycle = exynos4_frc_read(&mct_frc) + cycles; + exynos4_mct_write((u32)comp_cycle, EXYNOS4_MCT_G_COMP0_L); + exynos4_mct_write((u32)(comp_cycle >> 32), EXYNOS4_MCT_G_COMP0_U); + + exynos4_mct_write(0x1, EXYNOS4_MCT_G_INT_ENB); + + tcon |= MCT_G_TCON_COMP0_ENABLE; + exynos4_mct_write(tcon , EXYNOS4_MCT_G_TCON); +} + +static int exynos4_comp_set_next_event(unsigned long cycles, + struct clock_event_device *evt) +{ + exynos4_mct_comp0_start(evt->mode, cycles); + + return 0; +} + +static void exynos4_comp_set_mode(enum clock_event_mode mode, + struct clock_event_device *evt) +{ + unsigned long cycles_per_jiffy; + exynos4_mct_comp0_stop(); + + switch (mode) { + case CLOCK_EVT_MODE_PERIODIC: + cycles_per_jiffy = + (((unsigned long long) NSEC_PER_SEC / HZ * evt->mult) >> evt->shift); + exynos4_mct_comp0_start(mode, cycles_per_jiffy); + break; + + case CLOCK_EVT_MODE_ONESHOT: + case CLOCK_EVT_MODE_UNUSED: + case CLOCK_EVT_MODE_SHUTDOWN: + case CLOCK_EVT_MODE_RESUME: + break; + } +} + +static struct clock_event_device mct_comp_device = { + .name = "mct-comp", + .features = CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT, + .rating = 250, + .set_next_event = exynos4_comp_set_next_event, + .set_mode = exynos4_comp_set_mode, +}; + +static irqreturn_t exynos4_mct_comp_isr(int irq, void *dev_id) +{ + struct clock_event_device *evt = dev_id; + + exynos4_mct_write(0x1, EXYNOS4_MCT_G_INT_CSTAT); + + evt->event_handler(evt); + + return IRQ_HANDLED; +} + +static struct irqaction mct_comp_event_irq = { + .name = "mct_comp_irq", + .flags = IRQF_TIMER | IRQF_IRQPOLL, + .handler = exynos4_mct_comp_isr, + .dev_id = &mct_comp_device, +}; + +static void exynos4_clockevent_init(void) +{ + mct_comp_device.cpumask = cpumask_of(0); + clockevents_config_and_register(&mct_comp_device, clk_rate, + 0xf, 0xffffffff); + setup_irq(mct_irqs[MCT_G0_IRQ], &mct_comp_event_irq); +} + +#ifdef CONFIG_LOCAL_TIMERS + +static DEFINE_PER_CPU(struct mct_clock_event_device, percpu_mct_tick); + +/* Clock event handling */ +static void exynos4_mct_tick_stop(struct mct_clock_event_device *mevt) +{ + unsigned long tmp; + unsigned long mask = MCT_L_TCON_INT_START | MCT_L_TCON_TIMER_START; + unsigned long offset = mevt->base + MCT_L_TCON_OFFSET; + + tmp = __raw_readl(reg_base + offset); + if (tmp & mask) { + tmp &= ~mask; + exynos4_mct_write(tmp, offset); + } +} + +static void exynos4_mct_tick_start(unsigned long cycles, + struct mct_clock_event_device *mevt) +{ + unsigned long tmp; + + exynos4_mct_tick_stop(mevt); + + tmp = (1 << 31) | cycles; /* MCT_L_UPDATE_ICNTB */ + + /* update interrupt count buffer */ + exynos4_mct_write(tmp, mevt->base + MCT_L_ICNTB_OFFSET); + + /* enable MCT tick interrupt */ + exynos4_mct_write(0x1, mevt->base + MCT_L_INT_ENB_OFFSET); + + tmp = __raw_readl(reg_base + mevt->base + MCT_L_TCON_OFFSET); + tmp |= MCT_L_TCON_INT_START | MCT_L_TCON_TIMER_START | + MCT_L_TCON_INTERVAL_MODE; + exynos4_mct_write(tmp, mevt->base + MCT_L_TCON_OFFSET); +} + +static int exynos4_tick_set_next_event(unsigned long cycles, + struct clock_event_device *evt) +{ + struct mct_clock_event_device *mevt = this_cpu_ptr(&percpu_mct_tick); + + exynos4_mct_tick_start(cycles, mevt); + + return 0; +} + +static inline void exynos4_tick_set_mode(enum clock_event_mode mode, + struct clock_event_device *evt) +{ + struct mct_clock_event_device *mevt = this_cpu_ptr(&percpu_mct_tick); + unsigned long cycles_per_jiffy; + + exynos4_mct_tick_stop(mevt); + + switch (mode) { + case CLOCK_EVT_MODE_PERIODIC: + cycles_per_jiffy = + (((unsigned long long) NSEC_PER_SEC / HZ * evt->mult) >> evt->shift); + exynos4_mct_tick_start(cycles_per_jiffy, mevt); + break; + + case CLOCK_EVT_MODE_ONESHOT: + case CLOCK_EVT_MODE_UNUSED: + case CLOCK_EVT_MODE_SHUTDOWN: + case CLOCK_EVT_MODE_RESUME: + break; + } +} + +static int exynos4_mct_tick_clear(struct mct_clock_event_device *mevt) +{ + struct clock_event_device *evt = mevt->evt; + + /* + * This is for supporting oneshot mode. + * Mct would generate interrupt periodically + * without explicit stopping. + */ + if (evt->mode != CLOCK_EVT_MODE_PERIODIC) + exynos4_mct_tick_stop(mevt); + + /* Clear the MCT tick interrupt */ + if (__raw_readl(reg_base + mevt->base + MCT_L_INT_CSTAT_OFFSET) & 1) { + exynos4_mct_write(0x1, mevt->base + MCT_L_INT_CSTAT_OFFSET); + return 1; + } else { + return 0; + } +} + +static irqreturn_t exynos4_mct_tick_isr(int irq, void *dev_id) +{ + struct mct_clock_event_device *mevt = dev_id; + struct clock_event_device *evt = mevt->evt; + + exynos4_mct_tick_clear(mevt); + + evt->event_handler(evt); + + return IRQ_HANDLED; +} + +static struct irqaction mct_tick0_event_irq = { + .name = "mct_tick0_irq", + .flags = IRQF_TIMER | IRQF_NOBALANCING, + .handler = exynos4_mct_tick_isr, +}; + +static struct irqaction mct_tick1_event_irq = { + .name = "mct_tick1_irq", + .flags = IRQF_TIMER | IRQF_NOBALANCING, + .handler = exynos4_mct_tick_isr, +}; + +static int __cpuinit exynos4_local_timer_setup(struct clock_event_device *evt) +{ + struct mct_clock_event_device *mevt; + unsigned int cpu = smp_processor_id(); + + mevt = this_cpu_ptr(&percpu_mct_tick); + mevt->evt = evt; + + mevt->base = EXYNOS4_MCT_L_BASE(cpu); + sprintf(mevt->name, "mct_tick%d", cpu); + + evt->name = mevt->name; + evt->cpumask = cpumask_of(cpu); + evt->set_next_event = exynos4_tick_set_next_event; + evt->set_mode = exynos4_tick_set_mode; + evt->features = CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT; + evt->rating = 450; + + exynos4_mct_write(TICK_BASE_CNT, mevt->base + MCT_L_TCNTB_OFFSET); + + if (mct_int_type == MCT_INT_SPI) { + if (cpu == 0) { + mct_tick0_event_irq.dev_id = mevt; + evt->irq = mct_irqs[MCT_L0_IRQ]; + setup_irq(evt->irq, &mct_tick0_event_irq); + } else { + mct_tick1_event_irq.dev_id = mevt; + evt->irq = mct_irqs[MCT_L1_IRQ]; + setup_irq(evt->irq, &mct_tick1_event_irq); + irq_set_affinity(evt->irq, cpumask_of(1)); + } + } else { + enable_percpu_irq(mct_irqs[MCT_L0_IRQ], 0); + } + clockevents_config_and_register(evt, clk_rate / (TICK_BASE_CNT + 1), + 0xf, 0x7fffffff); + + return 0; +} + +static void exynos4_local_timer_stop(struct clock_event_device *evt) +{ + unsigned int cpu = smp_processor_id(); + evt->set_mode(CLOCK_EVT_MODE_UNUSED, evt); + if (mct_int_type == MCT_INT_SPI) + if (cpu == 0) + remove_irq(evt->irq, &mct_tick0_event_irq); + else + remove_irq(evt->irq, &mct_tick1_event_irq); + else + disable_percpu_irq(mct_irqs[MCT_L0_IRQ]); +} + +static struct local_timer_ops exynos4_mct_tick_ops __cpuinitdata = { + .setup = exynos4_local_timer_setup, + .stop = exynos4_local_timer_stop, +}; +#endif /* CONFIG_LOCAL_TIMERS */ + +static void __init exynos4_timer_resources(struct device_node *np, void __iomem *base) +{ + struct clk *mct_clk, *tick_clk; + + tick_clk = np ? of_clk_get_by_name(np, "fin_pll") : + clk_get(NULL, "fin_pll"); + if (IS_ERR(tick_clk)) + panic("%s: unable to determine tick clock rate\n", __func__); + clk_rate = clk_get_rate(tick_clk); + + mct_clk = np ? of_clk_get_by_name(np, "mct") : clk_get(NULL, "mct"); + if (IS_ERR(mct_clk)) + panic("%s: unable to retrieve mct clock instance\n", __func__); + clk_prepare_enable(mct_clk); + + reg_base = base; + if (!reg_base) + panic("%s: unable to ioremap mct address space\n", __func__); + +#ifdef CONFIG_LOCAL_TIMERS + if (mct_int_type == MCT_INT_PPI) { + int err; + + err = request_percpu_irq(mct_irqs[MCT_L0_IRQ], + exynos4_mct_tick_isr, "MCT", + &percpu_mct_tick); + WARN(err, "MCT: can't request IRQ %d (%d)\n", + mct_irqs[MCT_L0_IRQ], err); + } + + local_timer_register(&exynos4_mct_tick_ops); +#endif /* CONFIG_LOCAL_TIMERS */ +} + +void __init mct_init(void __iomem *base, int irq_g0, int irq_l0, int irq_l1) +{ + mct_irqs[MCT_G0_IRQ] = irq_g0; + mct_irqs[MCT_L0_IRQ] = irq_l0; + mct_irqs[MCT_L1_IRQ] = irq_l1; + mct_int_type = MCT_INT_SPI; + + exynos4_timer_resources(NULL, base); + exynos4_clocksource_init(); + exynos4_clockevent_init(); +} + +static void __init mct_init_dt(struct device_node *np, unsigned int int_type) +{ + u32 nr_irqs, i; + + mct_int_type = int_type; + + /* This driver uses only one global timer interrupt */ + mct_irqs[MCT_G0_IRQ] = irq_of_parse_and_map(np, MCT_G0_IRQ); + + /* + * Find out the number of local irqs specified. The local + * timer irqs are specified after the four global timer + * irqs are specified. + */ +#ifdef CONFIG_OF + nr_irqs = of_irq_count(np); +#else + nr_irqs = 0; +#endif + for (i = MCT_L0_IRQ; i < nr_irqs; i++) + mct_irqs[i] = irq_of_parse_and_map(np, i); + + exynos4_timer_resources(np, of_iomap(np, 0)); + exynos4_clocksource_init(); + exynos4_clockevent_init(); +} + + +static void __init mct_init_spi(struct device_node *np) +{ + return mct_init_dt(np, MCT_INT_SPI); +} + +static void __init mct_init_ppi(struct device_node *np) +{ + return mct_init_dt(np, MCT_INT_PPI); +} +CLOCKSOURCE_OF_DECLARE(exynos4210, "samsung,exynos4210-mct", mct_init_spi); +CLOCKSOURCE_OF_DECLARE(exynos4412, "samsung,exynos4412-mct", mct_init_ppi); diff --git a/drivers/clocksource/i8253.c b/drivers/clocksource/i8253.c new file mode 100644 index 000000000..14ee3efcc --- /dev/null +++ b/drivers/clocksource/i8253.c @@ -0,0 +1,186 @@ +/* + * i8253 PIT clocksource + */ +#include <linux/clockchips.h> +#include <linux/init.h> +#include <linux/io.h> +#include <linux/spinlock.h> +#include <linux/timex.h> +#include <linux/module.h> +#include <linux/i8253.h> +#include <linux/smp.h> + +/* + * Protects access to I/O ports + * + * 0040-0043 : timer0, i8253 / i8254 + * 0061-0061 : NMI Control Register which contains two speaker control bits. + */ +DEFINE_RAW_SPINLOCK(i8253_lock); +EXPORT_SYMBOL(i8253_lock); + +#ifdef CONFIG_CLKSRC_I8253 +/* + * Since the PIT overflows every tick, its not very useful + * to just read by itself. So use jiffies to emulate a free + * running counter: + */ +static cycle_t i8253_read(struct clocksource *cs) +{ + static int old_count; + static u32 old_jifs; + unsigned long flags; + int count; + u32 jifs; + + raw_spin_lock_irqsave(&i8253_lock, flags); + /* + * Although our caller may have the read side of jiffies_lock, + * this is now a seqlock, and we are cheating in this routine + * by having side effects on state that we cannot undo if + * there is a collision on the seqlock and our caller has to + * retry. (Namely, old_jifs and old_count.) So we must treat + * jiffies as volatile despite the lock. We read jiffies + * before latching the timer count to guarantee that although + * the jiffies value might be older than the count (that is, + * the counter may underflow between the last point where + * jiffies was incremented and the point where we latch the + * count), it cannot be newer. + */ + jifs = jiffies; + outb_p(0x00, PIT_MODE); /* latch the count ASAP */ + count = inb_p(PIT_CH0); /* read the latched count */ + count |= inb_p(PIT_CH0) << 8; + + /* VIA686a test code... reset the latch if count > max + 1 */ + if (count > PIT_LATCH) { + outb_p(0x34, PIT_MODE); + outb_p(PIT_LATCH & 0xff, PIT_CH0); + outb_p(PIT_LATCH >> 8, PIT_CH0); + count = PIT_LATCH - 1; + } + + /* + * It's possible for count to appear to go the wrong way for a + * couple of reasons: + * + * 1. The timer counter underflows, but we haven't handled the + * resulting interrupt and incremented jiffies yet. + * 2. Hardware problem with the timer, not giving us continuous time, + * the counter does small "jumps" upwards on some Pentium systems, + * (see c't 95/10 page 335 for Neptun bug.) + * + * Previous attempts to handle these cases intelligently were + * buggy, so we just do the simple thing now. + */ + if (count > old_count && jifs == old_jifs) + count = old_count; + + old_count = count; + old_jifs = jifs; + + raw_spin_unlock_irqrestore(&i8253_lock, flags); + + count = (PIT_LATCH - 1) - count; + + return (cycle_t)(jifs * PIT_LATCH) + count; +} + +static struct clocksource i8253_cs = { + .name = "pit", + .rating = 110, + .read = i8253_read, + .mask = CLOCKSOURCE_MASK(32), +}; + +int __init clocksource_i8253_init(void) +{ + return clocksource_register_hz(&i8253_cs, PIT_TICK_RATE); +} +#endif + +#ifdef CONFIG_CLKEVT_I8253 +/* + * Initialize the PIT timer. + * + * This is also called after resume to bring the PIT into operation again. + */ +static void init_pit_timer(enum clock_event_mode mode, + struct clock_event_device *evt) +{ + raw_spin_lock(&i8253_lock); + + switch (mode) { + case CLOCK_EVT_MODE_PERIODIC: + /* binary, mode 2, LSB/MSB, ch 0 */ + outb_p(0x34, PIT_MODE); + outb_p(PIT_LATCH & 0xff , PIT_CH0); /* LSB */ + outb_p(PIT_LATCH >> 8 , PIT_CH0); /* MSB */ + break; + + case CLOCK_EVT_MODE_SHUTDOWN: + case CLOCK_EVT_MODE_UNUSED: + if (evt->mode == CLOCK_EVT_MODE_PERIODIC || + evt->mode == CLOCK_EVT_MODE_ONESHOT) { + outb_p(0x30, PIT_MODE); + outb_p(0, PIT_CH0); + outb_p(0, PIT_CH0); + } + break; + + case CLOCK_EVT_MODE_ONESHOT: + /* One shot setup */ + outb_p(0x38, PIT_MODE); + break; + + case CLOCK_EVT_MODE_RESUME: + /* Nothing to do here */ + break; + } + raw_spin_unlock(&i8253_lock); +} + +/* + * Program the next event in oneshot mode + * + * Delta is given in PIT ticks + */ +static int pit_next_event(unsigned long delta, struct clock_event_device *evt) +{ + raw_spin_lock(&i8253_lock); + outb_p(delta & 0xff , PIT_CH0); /* LSB */ + outb_p(delta >> 8 , PIT_CH0); /* MSB */ + raw_spin_unlock(&i8253_lock); + + return 0; +} + +/* + * On UP the PIT can serve all of the possible timer functions. On SMP systems + * it can be solely used for the global tick. + */ +struct clock_event_device i8253_clockevent = { + .name = "pit", + .features = CLOCK_EVT_FEAT_PERIODIC, + .set_mode = init_pit_timer, + .set_next_event = pit_next_event, +}; + +/* + * Initialize the conversion factor and the min/max deltas of the clock event + * structure and register the clock event source with the framework. + */ +void __init clockevent_i8253_init(bool oneshot) +{ + if (oneshot) + i8253_clockevent.features |= CLOCK_EVT_FEAT_ONESHOT; + /* + * Start pit with the boot cpu mask. x86 might make it global + * when it is used as broadcast device later. + */ + i8253_clockevent.cpumask = cpumask_of(smp_processor_id()); + + clockevents_config_and_register(&i8253_clockevent, PIT_TICK_RATE, + 0xF, 0x7FFF); +} +#endif diff --git a/drivers/clocksource/metag_generic.c b/drivers/clocksource/metag_generic.c new file mode 100644 index 000000000..ade7513a1 --- /dev/null +++ b/drivers/clocksource/metag_generic.c @@ -0,0 +1,198 @@ +/* + * Copyright (C) 2005-2013 Imagination Technologies Ltd. + * + * 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. + * + * 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, see <http://www.gnu.org/licenses/>. + * + * + * Support for Meta per-thread timers. + * + * Meta hardware threads have 2 timers. The background timer (TXTIMER) is used + * as a free-running time base (hz clocksource), and the interrupt timer + * (TXTIMERI) is used for the timer interrupt (clock event). Both counters + * traditionally count at approximately 1MHz. + */ + +#include <clocksource/metag_generic.h> +#include <linux/cpu.h> +#include <linux/errno.h> +#include <linux/sched.h> +#include <linux/kernel.h> +#include <linux/param.h> +#include <linux/time.h> +#include <linux/init.h> +#include <linux/proc_fs.h> +#include <linux/clocksource.h> +#include <linux/clockchips.h> +#include <linux/interrupt.h> + +#include <asm/clock.h> +#include <asm/hwthread.h> +#include <asm/core_reg.h> +#include <asm/metag_mem.h> +#include <asm/tbx.h> + +#define HARDWARE_FREQ 1000000 /* 1MHz */ +#define HARDWARE_DIV 1 /* divide by 1 = 1MHz clock */ +#define HARDWARE_TO_NS_SHIFT 10 /* convert ticks to ns */ + +static unsigned int hwtimer_freq = HARDWARE_FREQ; +static DEFINE_PER_CPU(struct clock_event_device, local_clockevent); +static DEFINE_PER_CPU(char [11], local_clockevent_name); + +static int metag_timer_set_next_event(unsigned long delta, + struct clock_event_device *dev) +{ + __core_reg_set(TXTIMERI, -delta); + return 0; +} + +static void metag_timer_set_mode(enum clock_event_mode mode, + struct clock_event_device *evt) +{ + switch (mode) { + case CLOCK_EVT_MODE_ONESHOT: + case CLOCK_EVT_MODE_RESUME: + break; + + case CLOCK_EVT_MODE_SHUTDOWN: + /* We should disable the IRQ here */ + break; + + case CLOCK_EVT_MODE_PERIODIC: + case CLOCK_EVT_MODE_UNUSED: + WARN_ON(1); + break; + }; +} + +static cycle_t metag_clocksource_read(struct clocksource *cs) +{ + return __core_reg_get(TXTIMER); +} + +static struct clocksource clocksource_metag = { + .name = "META", + .rating = 200, + .mask = CLOCKSOURCE_MASK(32), + .read = metag_clocksource_read, + .flags = CLOCK_SOURCE_IS_CONTINUOUS, +}; + +static irqreturn_t metag_timer_interrupt(int irq, void *dummy) +{ + struct clock_event_device *evt = &__get_cpu_var(local_clockevent); + + evt->event_handler(evt); + + return IRQ_HANDLED; +} + +static struct irqaction metag_timer_irq = { + .name = "META core timer", + .handler = metag_timer_interrupt, + .flags = IRQF_TIMER | IRQF_IRQPOLL | IRQF_PERCPU, +}; + +unsigned long long sched_clock(void) +{ + unsigned long long ticks = __core_reg_get(TXTIMER); + return ticks << HARDWARE_TO_NS_SHIFT; +} + +static void __cpuinit arch_timer_setup(unsigned int cpu) +{ + unsigned int txdivtime; + struct clock_event_device *clk = &per_cpu(local_clockevent, cpu); + char *name = per_cpu(local_clockevent_name, cpu); + + txdivtime = __core_reg_get(TXDIVTIME); + + txdivtime &= ~TXDIVTIME_DIV_BITS; + txdivtime |= (HARDWARE_DIV & TXDIVTIME_DIV_BITS); + + __core_reg_set(TXDIVTIME, txdivtime); + + sprintf(name, "META %d", cpu); + clk->name = name; + clk->features = CLOCK_EVT_FEAT_ONESHOT, + + clk->rating = 200, + clk->shift = 12, + clk->irq = tbisig_map(TBID_SIGNUM_TRT), + clk->set_mode = metag_timer_set_mode, + clk->set_next_event = metag_timer_set_next_event, + + clk->mult = div_sc(hwtimer_freq, NSEC_PER_SEC, clk->shift); + clk->max_delta_ns = clockevent_delta2ns(0x7fffffff, clk); + clk->min_delta_ns = clockevent_delta2ns(0xf, clk); + clk->cpumask = cpumask_of(cpu); + + clockevents_register_device(clk); + + /* + * For all non-boot CPUs we need to synchronize our free + * running clock (TXTIMER) with the boot CPU's clock. + * + * While this won't be accurate, it should be close enough. + */ + if (cpu) { + unsigned int thread0 = cpu_2_hwthread_id[0]; + unsigned long val; + + val = core_reg_read(TXUCT_ID, TXTIMER_REGNUM, thread0); + __core_reg_set(TXTIMER, val); + } +} + +static int __cpuinit arch_timer_cpu_notify(struct notifier_block *self, + unsigned long action, void *hcpu) +{ + int cpu = (long)hcpu; + + switch (action) { + case CPU_STARTING: + case CPU_STARTING_FROZEN: + arch_timer_setup(cpu); + break; + } + + return NOTIFY_OK; +} + +static struct notifier_block __cpuinitdata arch_timer_cpu_nb = { + .notifier_call = arch_timer_cpu_notify, +}; + +int __init metag_generic_timer_init(void) +{ + /* + * On Meta 2 SoCs, the actual frequency of the timer is based on the + * Meta core clock speed divided by an integer, so it is only + * approximately 1MHz. Calculating the real frequency here drastically + * reduces clock skew on these SoCs. + */ +#ifdef CONFIG_METAG_META21 + hwtimer_freq = get_coreclock() / (metag_in32(EXPAND_TIMER_DIV) + 1); +#endif + clocksource_register_hz(&clocksource_metag, hwtimer_freq); + + setup_irq(tbisig_map(TBID_SIGNUM_TRT), &metag_timer_irq); + + /* Configure timer on boot CPU */ + arch_timer_setup(smp_processor_id()); + + /* Hook cpu boot to configure other CPU's timers */ + register_cpu_notifier(&arch_timer_cpu_nb); + + return 0; +} diff --git a/drivers/clocksource/mmio.c b/drivers/clocksource/mmio.c new file mode 100644 index 000000000..c0e25125a --- /dev/null +++ b/drivers/clocksource/mmio.c @@ -0,0 +1,73 @@ +/* + * Generic MMIO clocksource support + * + * 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. + */ +#include <linux/clocksource.h> +#include <linux/errno.h> +#include <linux/init.h> +#include <linux/slab.h> + +struct clocksource_mmio { + void __iomem *reg; + struct clocksource clksrc; +}; + +static inline struct clocksource_mmio *to_mmio_clksrc(struct clocksource *c) +{ + return container_of(c, struct clocksource_mmio, clksrc); +} + +cycle_t clocksource_mmio_readl_up(struct clocksource *c) +{ + return readl_relaxed(to_mmio_clksrc(c)->reg); +} + +cycle_t clocksource_mmio_readl_down(struct clocksource *c) +{ + return ~readl_relaxed(to_mmio_clksrc(c)->reg); +} + +cycle_t clocksource_mmio_readw_up(struct clocksource *c) +{ + return readw_relaxed(to_mmio_clksrc(c)->reg); +} + +cycle_t clocksource_mmio_readw_down(struct clocksource *c) +{ + return ~(unsigned)readw_relaxed(to_mmio_clksrc(c)->reg); +} + +/** + * clocksource_mmio_init - Initialize a simple mmio based clocksource + * @base: Virtual address of the clock readout register + * @name: Name of the clocksource + * @hz: Frequency of the clocksource in Hz + * @rating: Rating of the clocksource + * @bits: Number of valid bits + * @read: One of clocksource_mmio_read*() above + */ +int __init clocksource_mmio_init(void __iomem *base, const char *name, + unsigned long hz, int rating, unsigned bits, + cycle_t (*read)(struct clocksource *)) +{ + struct clocksource_mmio *cs; + + if (bits > 32 || bits < 16) + return -EINVAL; + + cs = kzalloc(sizeof(struct clocksource_mmio), GFP_KERNEL); + if (!cs) + return -ENOMEM; + + cs->reg = base; + cs->clksrc.name = name; + cs->clksrc.rating = rating; + cs->clksrc.read = read; + cs->clksrc.mask = CLOCKSOURCE_MASK(bits); + cs->clksrc.flags = CLOCK_SOURCE_IS_CONTINUOUS; + + return clocksource_register_hz(&cs->clksrc, hz); +} diff --git a/drivers/clocksource/mxs_timer.c b/drivers/clocksource/mxs_timer.c new file mode 100644 index 000000000..02af4204a --- /dev/null +++ b/drivers/clocksource/mxs_timer.c @@ -0,0 +1,304 @@ +/* + * Copyright (C) 2000-2001 Deep Blue Solutions + * Copyright (C) 2002 Shane Nay (shane@minirl.com) + * Copyright (C) 2006-2007 Pavel Pisa (ppisa@pikron.com) + * Copyright (C) 2008 Juergen Beisert (kernel@pengutronix.de) + * Copyright (C) 2010 Freescale Semiconductor, Inc. All Rights Reserved. + * + * 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., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301, USA. + */ + +#include <linux/err.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/clockchips.h> +#include <linux/clk.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/of_irq.h> +#include <linux/stmp_device.h> + +#include <asm/mach/time.h> +#include <asm/sched_clock.h> + +/* + * There are 2 versions of the timrot on Freescale MXS-based SoCs. + * The v1 on MX23 only gets 16 bits counter, while v2 on MX28 + * extends the counter to 32 bits. + * + * The implementation uses two timers, one for clock_event and + * another for clocksource. MX28 uses timrot 0 and 1, while MX23 + * uses 0 and 2. + */ + +#define MX23_TIMROT_VERSION_OFFSET 0x0a0 +#define MX28_TIMROT_VERSION_OFFSET 0x120 +#define BP_TIMROT_MAJOR_VERSION 24 +#define BV_TIMROT_VERSION_1 0x01 +#define BV_TIMROT_VERSION_2 0x02 +#define timrot_is_v1() (timrot_major_version == BV_TIMROT_VERSION_1) + +/* + * There are 4 registers for each timrotv2 instance, and 2 registers + * for each timrotv1. So address step 0x40 in macros below strides + * one instance of timrotv2 while two instances of timrotv1. + * + * As the result, HW_TIMROT_XXXn(1) defines the address of timrot1 + * on MX28 while timrot2 on MX23. + */ +/* common between v1 and v2 */ +#define HW_TIMROT_ROTCTRL 0x00 +#define HW_TIMROT_TIMCTRLn(n) (0x20 + (n) * 0x40) +/* v1 only */ +#define HW_TIMROT_TIMCOUNTn(n) (0x30 + (n) * 0x40) +/* v2 only */ +#define HW_TIMROT_RUNNING_COUNTn(n) (0x30 + (n) * 0x40) +#define HW_TIMROT_FIXED_COUNTn(n) (0x40 + (n) * 0x40) + +#define BM_TIMROT_TIMCTRLn_RELOAD (1 << 6) +#define BM_TIMROT_TIMCTRLn_UPDATE (1 << 7) +#define BM_TIMROT_TIMCTRLn_IRQ_EN (1 << 14) +#define BM_TIMROT_TIMCTRLn_IRQ (1 << 15) +#define BP_TIMROT_TIMCTRLn_SELECT 0 +#define BV_TIMROTv1_TIMCTRLn_SELECT__32KHZ_XTAL 0x8 +#define BV_TIMROTv2_TIMCTRLn_SELECT__32KHZ_XTAL 0xb +#define BV_TIMROTv2_TIMCTRLn_SELECT__TICK_ALWAYS 0xf + +static struct clock_event_device mxs_clockevent_device; +static enum clock_event_mode mxs_clockevent_mode = CLOCK_EVT_MODE_UNUSED; + +static void __iomem *mxs_timrot_base; +static u32 timrot_major_version; + +static inline void timrot_irq_disable(void) +{ + __raw_writel(BM_TIMROT_TIMCTRLn_IRQ_EN, mxs_timrot_base + + HW_TIMROT_TIMCTRLn(0) + STMP_OFFSET_REG_CLR); +} + +static inline void timrot_irq_enable(void) +{ + __raw_writel(BM_TIMROT_TIMCTRLn_IRQ_EN, mxs_timrot_base + + HW_TIMROT_TIMCTRLn(0) + STMP_OFFSET_REG_SET); +} + +static void timrot_irq_acknowledge(void) +{ + __raw_writel(BM_TIMROT_TIMCTRLn_IRQ, mxs_timrot_base + + HW_TIMROT_TIMCTRLn(0) + STMP_OFFSET_REG_CLR); +} + +static cycle_t timrotv1_get_cycles(struct clocksource *cs) +{ + return ~((__raw_readl(mxs_timrot_base + HW_TIMROT_TIMCOUNTn(1)) + & 0xffff0000) >> 16); +} + +static int timrotv1_set_next_event(unsigned long evt, + struct clock_event_device *dev) +{ + /* timrot decrements the count */ + __raw_writel(evt, mxs_timrot_base + HW_TIMROT_TIMCOUNTn(0)); + + return 0; +} + +static int timrotv2_set_next_event(unsigned long evt, + struct clock_event_device *dev) +{ + /* timrot decrements the count */ + __raw_writel(evt, mxs_timrot_base + HW_TIMROT_FIXED_COUNTn(0)); + + return 0; +} + +static irqreturn_t mxs_timer_interrupt(int irq, void *dev_id) +{ + struct clock_event_device *evt = dev_id; + + timrot_irq_acknowledge(); + evt->event_handler(evt); + + return IRQ_HANDLED; +} + +static struct irqaction mxs_timer_irq = { + .name = "MXS Timer Tick", + .dev_id = &mxs_clockevent_device, + .flags = IRQF_TIMER | IRQF_IRQPOLL, + .handler = mxs_timer_interrupt, +}; + +#ifdef DEBUG +static const char *clock_event_mode_label[] const = { + [CLOCK_EVT_MODE_PERIODIC] = "CLOCK_EVT_MODE_PERIODIC", + [CLOCK_EVT_MODE_ONESHOT] = "CLOCK_EVT_MODE_ONESHOT", + [CLOCK_EVT_MODE_SHUTDOWN] = "CLOCK_EVT_MODE_SHUTDOWN", + [CLOCK_EVT_MODE_UNUSED] = "CLOCK_EVT_MODE_UNUSED" +}; +#endif /* DEBUG */ + +static void mxs_set_mode(enum clock_event_mode mode, + struct clock_event_device *evt) +{ + /* Disable interrupt in timer module */ + timrot_irq_disable(); + + if (mode != mxs_clockevent_mode) { + /* Set event time into the furthest future */ + if (timrot_is_v1()) + __raw_writel(0xffff, + mxs_timrot_base + HW_TIMROT_TIMCOUNTn(1)); + else + __raw_writel(0xffffffff, + mxs_timrot_base + HW_TIMROT_FIXED_COUNTn(1)); + + /* Clear pending interrupt */ + timrot_irq_acknowledge(); + } + +#ifdef DEBUG + pr_info("%s: changing mode from %s to %s\n", __func__, + clock_event_mode_label[mxs_clockevent_mode], + clock_event_mode_label[mode]); +#endif /* DEBUG */ + + /* Remember timer mode */ + mxs_clockevent_mode = mode; + + switch (mode) { + case CLOCK_EVT_MODE_PERIODIC: + pr_err("%s: Periodic mode is not implemented\n", __func__); + break; + case CLOCK_EVT_MODE_ONESHOT: + timrot_irq_enable(); + break; + case CLOCK_EVT_MODE_SHUTDOWN: + case CLOCK_EVT_MODE_UNUSED: + case CLOCK_EVT_MODE_RESUME: + /* Left event sources disabled, no more interrupts appear */ + break; + } +} + +static struct clock_event_device mxs_clockevent_device = { + .name = "mxs_timrot", + .features = CLOCK_EVT_FEAT_ONESHOT, + .set_mode = mxs_set_mode, + .set_next_event = timrotv2_set_next_event, + .rating = 200, +}; + +static int __init mxs_clockevent_init(struct clk *timer_clk) +{ + if (timrot_is_v1()) + mxs_clockevent_device.set_next_event = timrotv1_set_next_event; + mxs_clockevent_device.cpumask = cpumask_of(0); + clockevents_config_and_register(&mxs_clockevent_device, + clk_get_rate(timer_clk), + timrot_is_v1() ? 0xf : 0x2, + timrot_is_v1() ? 0xfffe : 0xfffffffe); + + return 0; +} + +static struct clocksource clocksource_mxs = { + .name = "mxs_timer", + .rating = 200, + .read = timrotv1_get_cycles, + .mask = CLOCKSOURCE_MASK(16), + .flags = CLOCK_SOURCE_IS_CONTINUOUS, +}; + +static u32 notrace mxs_read_sched_clock_v2(void) +{ + return ~readl_relaxed(mxs_timrot_base + HW_TIMROT_RUNNING_COUNTn(1)); +} + +static int __init mxs_clocksource_init(struct clk *timer_clk) +{ + unsigned int c = clk_get_rate(timer_clk); + + if (timrot_is_v1()) + clocksource_register_hz(&clocksource_mxs, c); + else { + clocksource_mmio_init(mxs_timrot_base + HW_TIMROT_RUNNING_COUNTn(1), + "mxs_timer", c, 200, 32, clocksource_mmio_readl_down); + setup_sched_clock(mxs_read_sched_clock_v2, 32, c); + } + + return 0; +} + +static void __init mxs_timer_init(struct device_node *np) +{ + struct clk *timer_clk; + int irq; + + mxs_timrot_base = of_iomap(np, 0); + WARN_ON(!mxs_timrot_base); + + timer_clk = of_clk_get(np, 0); + if (IS_ERR(timer_clk)) { + pr_err("%s: failed to get clk\n", __func__); + return; + } + + clk_prepare_enable(timer_clk); + + /* + * Initialize timers to a known state + */ + stmp_reset_block(mxs_timrot_base + HW_TIMROT_ROTCTRL); + + /* get timrot version */ + timrot_major_version = __raw_readl(mxs_timrot_base + + (of_device_is_compatible(np, "fsl,imx23-timrot") ? + MX23_TIMROT_VERSION_OFFSET : + MX28_TIMROT_VERSION_OFFSET)); + timrot_major_version >>= BP_TIMROT_MAJOR_VERSION; + + /* one for clock_event */ + __raw_writel((timrot_is_v1() ? + BV_TIMROTv1_TIMCTRLn_SELECT__32KHZ_XTAL : + BV_TIMROTv2_TIMCTRLn_SELECT__TICK_ALWAYS) | + BM_TIMROT_TIMCTRLn_UPDATE | + BM_TIMROT_TIMCTRLn_IRQ_EN, + mxs_timrot_base + HW_TIMROT_TIMCTRLn(0)); + + /* another for clocksource */ + __raw_writel((timrot_is_v1() ? + BV_TIMROTv1_TIMCTRLn_SELECT__32KHZ_XTAL : + BV_TIMROTv2_TIMCTRLn_SELECT__TICK_ALWAYS) | + BM_TIMROT_TIMCTRLn_RELOAD, + mxs_timrot_base + HW_TIMROT_TIMCTRLn(1)); + + /* set clocksource timer fixed count to the maximum */ + if (timrot_is_v1()) + __raw_writel(0xffff, + mxs_timrot_base + HW_TIMROT_TIMCOUNTn(1)); + else + __raw_writel(0xffffffff, + mxs_timrot_base + HW_TIMROT_FIXED_COUNTn(1)); + + /* init and register the timer to the framework */ + mxs_clocksource_init(timer_clk); + mxs_clockevent_init(timer_clk); + + /* Make irqs happen */ + irq = irq_of_parse_and_map(np, 0); + setup_irq(irq, &mxs_timer_irq); +} +CLOCKSOURCE_OF_DECLARE(mxs, "fsl,timrot", mxs_timer_init); diff --git a/drivers/clocksource/nomadik-mtu.c b/drivers/clocksource/nomadik-mtu.c new file mode 100644 index 000000000..e405531e1 --- /dev/null +++ b/drivers/clocksource/nomadik-mtu.c @@ -0,0 +1,249 @@ +/* + * Copyright (C) 2008 STMicroelectronics + * Copyright (C) 2010 Alessandro Rubini + * Copyright (C) 2010 Linus Walleij for ST-Ericsson + * + * 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. + */ +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/io.h> +#include <linux/clockchips.h> +#include <linux/clocksource.h> +#include <linux/clk.h> +#include <linux/jiffies.h> +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/platform_data/clocksource-nomadik-mtu.h> +#include <asm/mach/time.h> +#include <asm/sched_clock.h> + +/* + * The MTU device hosts four different counters, with 4 set of + * registers. These are register names. + */ + +#define MTU_IMSC 0x00 /* Interrupt mask set/clear */ +#define MTU_RIS 0x04 /* Raw interrupt status */ +#define MTU_MIS 0x08 /* Masked interrupt status */ +#define MTU_ICR 0x0C /* Interrupt clear register */ + +/* per-timer registers take 0..3 as argument */ +#define MTU_LR(x) (0x10 + 0x10 * (x) + 0x00) /* Load value */ +#define MTU_VAL(x) (0x10 + 0x10 * (x) + 0x04) /* Current value */ +#define MTU_CR(x) (0x10 + 0x10 * (x) + 0x08) /* Control reg */ +#define MTU_BGLR(x) (0x10 + 0x10 * (x) + 0x0c) /* At next overflow */ + +/* bits for the control register */ +#define MTU_CRn_ENA 0x80 +#define MTU_CRn_PERIODIC 0x40 /* if 0 = free-running */ +#define MTU_CRn_PRESCALE_MASK 0x0c +#define MTU_CRn_PRESCALE_1 0x00 +#define MTU_CRn_PRESCALE_16 0x04 +#define MTU_CRn_PRESCALE_256 0x08 +#define MTU_CRn_32BITS 0x02 +#define MTU_CRn_ONESHOT 0x01 /* if 0 = wraps reloading from BGLR*/ + +/* Other registers are usual amba/primecell registers, currently not used */ +#define MTU_ITCR 0xff0 +#define MTU_ITOP 0xff4 + +#define MTU_PERIPH_ID0 0xfe0 +#define MTU_PERIPH_ID1 0xfe4 +#define MTU_PERIPH_ID2 0xfe8 +#define MTU_PERIPH_ID3 0xfeC + +#define MTU_PCELL0 0xff0 +#define MTU_PCELL1 0xff4 +#define MTU_PCELL2 0xff8 +#define MTU_PCELL3 0xffC + +static void __iomem *mtu_base; +static bool clkevt_periodic; +static u32 clk_prescale; +static u32 nmdk_cycle; /* write-once */ +static struct delay_timer mtu_delay_timer; + +#ifdef CONFIG_CLKSRC_NOMADIK_MTU_SCHED_CLOCK +/* + * Override the global weak sched_clock symbol with this + * local implementation which uses the clocksource to get some + * better resolution when scheduling the kernel. + */ +static u32 notrace nomadik_read_sched_clock(void) +{ + if (unlikely(!mtu_base)) + return 0; + + return -readl(mtu_base + MTU_VAL(0)); +} +#endif + +static unsigned long nmdk_timer_read_current_timer(void) +{ + return ~readl_relaxed(mtu_base + MTU_VAL(0)); +} + +/* Clockevent device: use one-shot mode */ +static int nmdk_clkevt_next(unsigned long evt, struct clock_event_device *ev) +{ + writel(1 << 1, mtu_base + MTU_IMSC); + writel(evt, mtu_base + MTU_LR(1)); + /* Load highest value, enable device, enable interrupts */ + writel(MTU_CRn_ONESHOT | clk_prescale | + MTU_CRn_32BITS | MTU_CRn_ENA, + mtu_base + MTU_CR(1)); + + return 0; +} + +void nmdk_clkevt_reset(void) +{ + if (clkevt_periodic) { + /* Timer: configure load and background-load, and fire it up */ + writel(nmdk_cycle, mtu_base + MTU_LR(1)); + writel(nmdk_cycle, mtu_base + MTU_BGLR(1)); + + writel(MTU_CRn_PERIODIC | clk_prescale | + MTU_CRn_32BITS | MTU_CRn_ENA, + mtu_base + MTU_CR(1)); + writel(1 << 1, mtu_base + MTU_IMSC); + } else { + /* Generate an interrupt to start the clockevent again */ + (void) nmdk_clkevt_next(nmdk_cycle, NULL); + } +} + +static void nmdk_clkevt_mode(enum clock_event_mode mode, + struct clock_event_device *dev) +{ + switch (mode) { + case CLOCK_EVT_MODE_PERIODIC: + clkevt_periodic = true; + nmdk_clkevt_reset(); + break; + case CLOCK_EVT_MODE_ONESHOT: + clkevt_periodic = false; + break; + case CLOCK_EVT_MODE_SHUTDOWN: + case CLOCK_EVT_MODE_UNUSED: + writel(0, mtu_base + MTU_IMSC); + /* disable timer */ + writel(0, mtu_base + MTU_CR(1)); + /* load some high default value */ + writel(0xffffffff, mtu_base + MTU_LR(1)); + break; + case CLOCK_EVT_MODE_RESUME: + break; + } +} + +void nmdk_clksrc_reset(void) +{ + /* Disable */ + writel(0, mtu_base + MTU_CR(0)); + + /* ClockSource: configure load and background-load, and fire it up */ + writel(nmdk_cycle, mtu_base + MTU_LR(0)); + writel(nmdk_cycle, mtu_base + MTU_BGLR(0)); + + writel(clk_prescale | MTU_CRn_32BITS | MTU_CRn_ENA, + mtu_base + MTU_CR(0)); +} + +static void nmdk_clkevt_resume(struct clock_event_device *cedev) +{ + nmdk_clkevt_reset(); + nmdk_clksrc_reset(); +} + +static struct clock_event_device nmdk_clkevt = { + .name = "mtu_1", + .features = CLOCK_EVT_FEAT_ONESHOT | CLOCK_EVT_FEAT_PERIODIC, + .rating = 200, + .set_mode = nmdk_clkevt_mode, + .set_next_event = nmdk_clkevt_next, + .resume = nmdk_clkevt_resume, +}; + +/* + * IRQ Handler for timer 1 of the MTU block. + */ +static irqreturn_t nmdk_timer_interrupt(int irq, void *dev_id) +{ + struct clock_event_device *evdev = dev_id; + + writel(1 << 1, mtu_base + MTU_ICR); /* Interrupt clear reg */ + evdev->event_handler(evdev); + return IRQ_HANDLED; +} + +static struct irqaction nmdk_timer_irq = { + .name = "Nomadik Timer Tick", + .flags = IRQF_DISABLED | IRQF_TIMER, + .handler = nmdk_timer_interrupt, + .dev_id = &nmdk_clkevt, +}; + +void __init nmdk_timer_init(void __iomem *base, int irq) +{ + unsigned long rate; + struct clk *clk0, *pclk0; + + mtu_base = base; + + pclk0 = clk_get_sys("mtu0", "apb_pclk"); + BUG_ON(IS_ERR(pclk0)); + BUG_ON(clk_prepare(pclk0) < 0); + BUG_ON(clk_enable(pclk0) < 0); + + clk0 = clk_get_sys("mtu0", NULL); + BUG_ON(IS_ERR(clk0)); + BUG_ON(clk_prepare(clk0) < 0); + BUG_ON(clk_enable(clk0) < 0); + + /* + * Tick rate is 2.4MHz for Nomadik and 2.4Mhz, 100MHz or 133 MHz + * for ux500. + * Use a divide-by-16 counter if the tick rate is more than 32MHz. + * At 32 MHz, the timer (with 32 bit counter) can be programmed + * to wake-up at a max 127s a head in time. Dividing a 2.4 MHz timer + * with 16 gives too low timer resolution. + */ + rate = clk_get_rate(clk0); + if (rate > 32000000) { + rate /= 16; + clk_prescale = MTU_CRn_PRESCALE_16; + } else { + clk_prescale = MTU_CRn_PRESCALE_1; + } + + /* Cycles for periodic mode */ + nmdk_cycle = DIV_ROUND_CLOSEST(rate, HZ); + + + /* Timer 0 is the free running clocksource */ + nmdk_clksrc_reset(); + + if (clocksource_mmio_init(mtu_base + MTU_VAL(0), "mtu_0", + rate, 200, 32, clocksource_mmio_readl_down)) + pr_err("timer: failed to initialize clock source %s\n", + "mtu_0"); + +#ifdef CONFIG_CLKSRC_NOMADIK_MTU_SCHED_CLOCK + setup_sched_clock(nomadik_read_sched_clock, 32, rate); +#endif + + /* Timer 1 is used for events, register irq and clockevents */ + setup_irq(irq, &nmdk_timer_irq); + nmdk_clkevt.cpumask = cpumask_of(0); + nmdk_clkevt.irq = irq; + clockevents_config_and_register(&nmdk_clkevt, rate, 2, 0xffffffffU); + + mtu_delay_timer.read_current_timer = &nmdk_timer_read_current_timer; + mtu_delay_timer.freq = rate; + register_current_timer_delay(&mtu_delay_timer); +} diff --git a/drivers/clocksource/samsung_pwm_timer.c b/drivers/clocksource/samsung_pwm_timer.c new file mode 100644 index 000000000..0234c8d2c --- /dev/null +++ b/drivers/clocksource/samsung_pwm_timer.c @@ -0,0 +1,494 @@ +/* + * Copyright (c) 2011 Samsung Electronics Co., Ltd. + * http://www.samsung.com/ + * + * samsung - Common hr-timer support (s3c and s5p) + * + * 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. +*/ + +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/err.h> +#include <linux/clk.h> +#include <linux/clockchips.h> +#include <linux/list.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/of_irq.h> +#include <linux/platform_device.h> +#include <linux/slab.h> + +#include <clocksource/samsung_pwm.h> + +#include <asm/sched_clock.h> + +/* + * Clocksource driver + */ + +#define REG_TCFG0 0x00 +#define REG_TCFG1 0x04 +#define REG_TCON 0x08 +#define REG_TINT_CSTAT 0x44 + +#define REG_TCNTB(chan) (0x0c + 12 * (chan)) +#define REG_TCMPB(chan) (0x10 + 12 * (chan)) + +#define TCFG0_PRESCALER_MASK 0xff +#define TCFG0_PRESCALER1_SHIFT 8 + +#define TCFG1_SHIFT(x) ((x) * 4) +#define TCFG1_MUX_MASK 0xf + +#define TCON_START(chan) (1 << (4 * (chan) + 0)) +#define TCON_MANUALUPDATE(chan) (1 << (4 * (chan) + 1)) +#define TCON_INVERT(chan) (1 << (4 * (chan) + 2)) +#define TCON_AUTORELOAD(chan) (1 << (4 * (chan) + 3)) + +DEFINE_SPINLOCK(samsung_pwm_lock); +EXPORT_SYMBOL(samsung_pwm_lock); + +struct samsung_pwm_clocksource { + void __iomem *base; + unsigned int irq[SAMSUNG_PWM_NUM]; + struct samsung_pwm_variant variant; + + struct clk *timerclk; + + unsigned int event_id; + unsigned int source_id; + unsigned int tcnt_max; + unsigned int tscaler_div; + unsigned int tdiv; + + unsigned long clock_count_per_tick; +}; + +static struct samsung_pwm_clocksource pwm; + +static void samsung_timer_set_prescale(unsigned int channel, u16 prescale) +{ + unsigned long flags; + u8 shift = 0; + u32 reg; + + if (channel >= 2) + shift = TCFG0_PRESCALER1_SHIFT; + + spin_lock_irqsave(&samsung_pwm_lock, flags); + + reg = readl(pwm.base + REG_TCFG0); + reg &= ~(TCFG0_PRESCALER_MASK << shift); + reg |= (prescale - 1) << shift; + writel(reg, pwm.base + REG_TCFG0); + + spin_unlock_irqrestore(&samsung_pwm_lock, flags); +} + +static void samsung_timer_set_divisor(unsigned int channel, u8 divisor) +{ + u8 shift = TCFG1_SHIFT(channel); + unsigned long flags; + u32 reg; + u8 bits; + + bits = (fls(divisor) - 1) - pwm.variant.div_base; + + spin_lock_irqsave(&samsung_pwm_lock, flags); + + reg = readl(pwm.base + REG_TCFG1); + reg &= ~(TCFG1_MUX_MASK << shift); + reg |= bits << shift; + writel(reg, pwm.base + REG_TCFG1); + + spin_unlock_irqrestore(&samsung_pwm_lock, flags); +} + +static void samsung_time_stop(unsigned int channel) +{ + unsigned long tcon; + unsigned long flags; + + if (channel > 0) + ++channel; + + spin_lock_irqsave(&samsung_pwm_lock, flags); + + tcon = __raw_readl(pwm.base + REG_TCON); + tcon &= ~TCON_START(channel); + __raw_writel(tcon, pwm.base + REG_TCON); + + spin_unlock_irqrestore(&samsung_pwm_lock, flags); +} + +static void samsung_time_setup(unsigned int channel, unsigned long tcnt) +{ + unsigned long tcon; + unsigned long flags; + unsigned int tcon_chan = channel; + + if (tcon_chan > 0) + ++tcon_chan; + + spin_lock_irqsave(&samsung_pwm_lock, flags); + + tcon = __raw_readl(pwm.base + REG_TCON); + + tcon &= ~(TCON_START(tcon_chan) | TCON_AUTORELOAD(tcon_chan)); + tcon |= TCON_MANUALUPDATE(tcon_chan); + + __raw_writel(tcnt, pwm.base + REG_TCNTB(channel)); + __raw_writel(tcnt, pwm.base + REG_TCMPB(channel)); + __raw_writel(tcon, pwm.base + REG_TCON); + + spin_unlock_irqrestore(&samsung_pwm_lock, flags); +} + +static void samsung_time_start(unsigned int channel, bool periodic) +{ + unsigned long tcon; + unsigned long flags; + + if (channel > 0) + ++channel; + + spin_lock_irqsave(&samsung_pwm_lock, flags); + + tcon = __raw_readl(pwm.base + REG_TCON); + + tcon &= ~TCON_MANUALUPDATE(channel); + tcon |= TCON_START(channel); + + if (periodic) + tcon |= TCON_AUTORELOAD(channel); + else + tcon &= ~TCON_AUTORELOAD(channel); + + __raw_writel(tcon, pwm.base + REG_TCON); + + spin_unlock_irqrestore(&samsung_pwm_lock, flags); +} + +static int samsung_set_next_event(unsigned long cycles, + struct clock_event_device *evt) +{ + /* + * This check is needed to account for internal rounding + * errors inside clockevents core, which might result in + * passing cycles = 0, which in turn would not generate any + * timer interrupt and hang the system. + * + * Another solution would be to set up the clockevent device + * with min_delta = 2, but this would unnecessarily increase + * the minimum sleep period. + */ + if (!cycles) + cycles = 1; + + samsung_time_setup(pwm.event_id, cycles); + samsung_time_start(pwm.event_id, false); + + return 0; +} + +static void samsung_timer_resume(void) +{ + /* event timer restart */ + samsung_time_setup(pwm.event_id, pwm.clock_count_per_tick - 1); + samsung_time_start(pwm.event_id, true); + + /* source timer restart */ + samsung_time_setup(pwm.source_id, pwm.tcnt_max); + samsung_time_start(pwm.source_id, true); +} + +static void samsung_set_mode(enum clock_event_mode mode, + struct clock_event_device *evt) +{ + samsung_time_stop(pwm.event_id); + + switch (mode) { + case CLOCK_EVT_MODE_PERIODIC: + samsung_time_setup(pwm.event_id, pwm.clock_count_per_tick - 1); + samsung_time_start(pwm.event_id, true); + break; + + case CLOCK_EVT_MODE_ONESHOT: + break; + + case CLOCK_EVT_MODE_UNUSED: + case CLOCK_EVT_MODE_SHUTDOWN: + break; + + case CLOCK_EVT_MODE_RESUME: + samsung_timer_resume(); + break; + } +} + +static struct clock_event_device time_event_device = { + .name = "samsung_event_timer", + .features = CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT, + .rating = 200, + .set_next_event = samsung_set_next_event, + .set_mode = samsung_set_mode, +}; + +static irqreturn_t samsung_clock_event_isr(int irq, void *dev_id) +{ + struct clock_event_device *evt = dev_id; + + if (pwm.variant.has_tint_cstat) { + u32 mask = (1 << pwm.event_id); + writel(mask | (mask << 5), pwm.base + REG_TINT_CSTAT); + } + + evt->event_handler(evt); + + return IRQ_HANDLED; +} + +static struct irqaction samsung_clock_event_irq = { + .name = "samsung_time_irq", + .flags = IRQF_DISABLED | IRQF_TIMER | IRQF_IRQPOLL, + .handler = samsung_clock_event_isr, + .dev_id = &time_event_device, +}; + +static void __init samsung_clockevent_init(void) +{ + unsigned long pclk; + unsigned long clock_rate; + unsigned int irq_number; + + pclk = clk_get_rate(pwm.timerclk); + + samsung_timer_set_prescale(pwm.event_id, pwm.tscaler_div); + samsung_timer_set_divisor(pwm.event_id, pwm.tdiv); + + clock_rate = pclk / (pwm.tscaler_div * pwm.tdiv); + pwm.clock_count_per_tick = clock_rate / HZ; + + time_event_device.cpumask = cpumask_of(0); + clockevents_config_and_register(&time_event_device, + clock_rate, 1, pwm.tcnt_max); + + irq_number = pwm.irq[pwm.event_id]; + setup_irq(irq_number, &samsung_clock_event_irq); + + if (pwm.variant.has_tint_cstat) { + u32 mask = (1 << pwm.event_id); + writel(mask | (mask << 5), pwm.base + REG_TINT_CSTAT); + } +} + +static void __iomem *samsung_timer_reg(void) +{ + switch (pwm.source_id) { + case 0: + case 1: + case 2: + case 3: + return pwm.base + pwm.source_id * 0x0c + 0x14; + + case 4: + return pwm.base + 0x40; + + default: + BUG(); + } +} + +/* + * Override the global weak sched_clock symbol with this + * local implementation which uses the clocksource to get some + * better resolution when scheduling the kernel. We accept that + * this wraps around for now, since it is just a relative time + * stamp. (Inspired by U300 implementation.) + */ +static u32 notrace samsung_read_sched_clock(void) +{ + void __iomem *reg = samsung_timer_reg(); + + if (!reg) + return 0; + + return ~__raw_readl(reg); +} + +static void __init samsung_clocksource_init(void) +{ + void __iomem *reg = samsung_timer_reg(); + unsigned long pclk; + unsigned long clock_rate; + int ret; + + pclk = clk_get_rate(pwm.timerclk); + + samsung_timer_set_prescale(pwm.source_id, pwm.tscaler_div); + samsung_timer_set_divisor(pwm.source_id, pwm.tdiv); + + clock_rate = pclk / (pwm.tscaler_div * pwm.tdiv); + + samsung_time_setup(pwm.source_id, pwm.tcnt_max); + samsung_time_start(pwm.source_id, true); + + setup_sched_clock(samsung_read_sched_clock, + pwm.variant.bits, clock_rate); + + ret = clocksource_mmio_init(reg, "samsung_clocksource_timer", + clock_rate, 250, pwm.variant.bits, + clocksource_mmio_readl_down); + if (ret) + panic("samsung_clocksource_timer: can't register clocksource\n"); +} + +static void __init samsung_timer_resources(void) +{ + pwm.timerclk = clk_get(NULL, "timers"); + if (IS_ERR(pwm.timerclk)) + panic("failed to get timers clock for timer"); + + clk_prepare_enable(pwm.timerclk); + + pwm.tcnt_max = (1UL << pwm.variant.bits) - 1; + if (pwm.variant.bits == 16) { + pwm.tscaler_div = 25; + pwm.tdiv = 2; + } else { + pwm.tscaler_div = 2; + pwm.tdiv = 1; + } +} + +/* + * PWM master driver + */ +static void __init _samsung_pwm_clocksource_init(void) +{ + u8 mask; + int channel; + + mask = ~pwm.variant.output_mask & ((1 << SAMSUNG_PWM_NUM) - 1); + channel = fls(mask) - 1; + if (channel < 0) + panic("failed to find PWM channel for clocksource"); + pwm.source_id = channel; + + mask &= ~(1 << channel); + channel = fls(mask) - 1; + if (channel < 0) + panic("failed to find PWM channel for clock event"); + pwm.event_id = channel; + + samsung_timer_resources(); + samsung_clockevent_init(); + samsung_clocksource_init(); +} + +void __init samsung_pwm_clocksource_init(void __iomem *base, + unsigned int *irqs, struct samsung_pwm_variant *variant) +{ + pwm.base = base; + memcpy(&pwm.variant, variant, sizeof(pwm.variant)); + memcpy(pwm.irq, irqs, SAMSUNG_PWM_NUM * sizeof(*irqs)); + + _samsung_pwm_clocksource_init(); +} + +#ifdef CONFIG_CLKSRC_OF +static void __init samsung_pwm_alloc(struct device_node *np, + const struct samsung_pwm_variant *variant) +{ + struct resource res; + struct property *prop; + const __be32 *cur; + u32 val; + int i; + + memcpy(&pwm.variant, variant, sizeof(pwm.variant)); + for (i = 0; i < SAMSUNG_PWM_NUM; ++i) + pwm.irq[i] = irq_of_parse_and_map(np, i); + + of_property_for_each_u32(np, "samsung,pwm-outputs", prop, cur, val) { + if (val >= SAMSUNG_PWM_NUM) { + pr_warning("%s: invalid channel index in samsung,pwm-outputs property\n", + __func__); + continue; + } + pwm.variant.output_mask |= 1 << val; + } + + of_address_to_resource(np, 0, &res); + if (!request_mem_region(res.start, + resource_size(&res), "samsung-pwm")) { + pr_err("%s: failed to request IO mem region\n", __func__); + return; + } + + pwm.base = ioremap(res.start, resource_size(&res)); + if (!pwm.base) { + pr_err("%s: failed to map PWM registers\n", __func__); + release_mem_region(res.start, resource_size(&res)); + return; + } + + _samsung_pwm_clocksource_init(); +} + +static const struct samsung_pwm_variant s3c24xx_variant = { + .bits = 16, + .div_base = 1, + .has_tint_cstat = false, + .tclk_mask = (1 << 4), +}; + +static void __init s3c2410_pwm_clocksource_init(struct device_node *np) +{ + samsung_pwm_alloc(np, &s3c24xx_variant); +} +CLOCKSOURCE_OF_DECLARE(s3c2410_pwm, "samsung,s3c2410-pwm", s3c2410_pwm_clocksource_init); + +static const struct samsung_pwm_variant s3c64xx_variant = { + .bits = 32, + .div_base = 0, + .has_tint_cstat = true, + .tclk_mask = (1 << 7) | (1 << 6) | (1 << 5), +}; + +static void __init s3c64xx_pwm_clocksource_init(struct device_node *np) +{ + samsung_pwm_alloc(np, &s3c64xx_variant); +} +CLOCKSOURCE_OF_DECLARE(s3c6400_pwm, "samsung,s3c6400-pwm", s3c64xx_pwm_clocksource_init); + +static const struct samsung_pwm_variant s5p64x0_variant = { + .bits = 32, + .div_base = 0, + .has_tint_cstat = true, + .tclk_mask = 0, +}; + +static void __init s5p64x0_pwm_clocksource_init(struct device_node *np) +{ + samsung_pwm_alloc(np, &s5p64x0_variant); +} +CLOCKSOURCE_OF_DECLARE(s5p6440_pwm, "samsung,s5p6440-pwm", s5p64x0_pwm_clocksource_init); + +static const struct samsung_pwm_variant s5p_variant = { + .bits = 32, + .div_base = 0, + .has_tint_cstat = true, + .tclk_mask = (1 << 5), +}; + +static void __init s5p_pwm_clocksource_init(struct device_node *np) +{ + samsung_pwm_alloc(np, &s5p_variant); +} +CLOCKSOURCE_OF_DECLARE(s5pc100_pwm, "samsung,s5pc100-pwm", s5p_pwm_clocksource_init); +#endif diff --git a/drivers/clocksource/scx200_hrt.c b/drivers/clocksource/scx200_hrt.c new file mode 100644 index 000000000..64f9e8294 --- /dev/null +++ b/drivers/clocksource/scx200_hrt.c @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2006 Jim Cromie + * + * This is a clocksource driver for the Geode SCx200's 1 or 27 MHz + * high-resolution timer. The Geode SC-1100 (at least) has a buggy + * time stamp counter (TSC), which loses time unless 'idle=poll' is + * given as a boot-arg. In its absence, the Generic Timekeeping code + * will detect and de-rate the bad TSC, allowing this timer to take + * over timekeeping duties. + * + * Based on work by John Stultz, and Ted Phelps (in a 2.6.12-rc6 patch) + * + * 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. + */ + +#include <linux/clocksource.h> +#include <linux/init.h> +#include <linux/module.h> +#include <linux/ioport.h> +#include <linux/scx200.h> + +#define NAME "scx200_hrt" + +static int mhz27; +module_param(mhz27, int, 0); /* load time only */ +MODULE_PARM_DESC(mhz27, "count at 27.0 MHz (default is 1.0 MHz)"); + +static int ppm; +module_param(ppm, int, 0); /* load time only */ +MODULE_PARM_DESC(ppm, "+-adjust to actual XO freq (ppm)"); + +/* HiRes Timer configuration register address */ +#define SCx200_TMCNFG_OFFSET (SCx200_TIMER_OFFSET + 5) + +/* and config settings */ +#define HR_TMEN (1 << 0) /* timer interrupt enable */ +#define HR_TMCLKSEL (1 << 1) /* 1|0 counts at 27|1 MHz */ +#define HR_TM27MPD (1 << 2) /* 1 turns off input clock (power-down) */ + +/* The base timer frequency, * 27 if selected */ +#define HRT_FREQ 1000000 + +static cycle_t read_hrt(struct clocksource *cs) +{ + /* Read the timer value */ + return (cycle_t) inl(scx200_cb_base + SCx200_TIMER_OFFSET); +} + +static struct clocksource cs_hrt = { + .name = "scx200_hrt", + .rating = 250, + .read = read_hrt, + .mask = CLOCKSOURCE_MASK(32), + .flags = CLOCK_SOURCE_IS_CONTINUOUS, + /* mult, shift are set based on mhz27 flag */ +}; + +static int __init init_hrt_clocksource(void) +{ + u32 freq; + /* Make sure scx200 has initialized the configuration block */ + if (!scx200_cb_present()) + return -ENODEV; + + /* Reserve the timer's ISA io-region for ourselves */ + if (!request_region(scx200_cb_base + SCx200_TIMER_OFFSET, + SCx200_TIMER_SIZE, + "NatSemi SCx200 High-Resolution Timer")) { + pr_warn("unable to lock timer region\n"); + return -ENODEV; + } + + /* write timer config */ + outb(HR_TMEN | (mhz27 ? HR_TMCLKSEL : 0), + scx200_cb_base + SCx200_TMCNFG_OFFSET); + + freq = (HRT_FREQ + ppm); + if (mhz27) + freq *= 27; + + pr_info("enabling scx200 high-res timer (%s MHz +%d ppm)\n", mhz27 ? "27":"1", ppm); + + return clocksource_register_hz(&cs_hrt, freq); +} + +module_init(init_hrt_clocksource); + +MODULE_AUTHOR("Jim Cromie <jim.cromie@gmail.com>"); +MODULE_DESCRIPTION("clocksource on SCx200 HiRes Timer"); +MODULE_LICENSE("GPL"); diff --git a/drivers/clocksource/sh_cmt.c b/drivers/clocksource/sh_cmt.c new file mode 100644 index 000000000..08d0c418c --- /dev/null +++ b/drivers/clocksource/sh_cmt.c @@ -0,0 +1,846 @@ +/* + * SuperH Timer Support - CMT + * + * Copyright (C) 2008 Magnus Damm + * + * 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 + * + * 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/init.h> +#include <linux/platform_device.h> +#include <linux/spinlock.h> +#include <linux/interrupt.h> +#include <linux/ioport.h> +#include <linux/io.h> +#include <linux/clk.h> +#include <linux/irq.h> +#include <linux/err.h> +#include <linux/delay.h> +#include <linux/clocksource.h> +#include <linux/clockchips.h> +#include <linux/sh_timer.h> +#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; + struct clk *clk; + unsigned long width; /* 16 or 32 bit version of hardware block */ + unsigned long overflow_bit; + unsigned long clear_bits; + struct irqaction irqaction; + struct platform_device *pdev; + + unsigned long flags; + unsigned long match_value; + unsigned long next_match_value; + unsigned long max_match_value; + unsigned long rate; + raw_spinlock_t lock; + struct clock_event_device ced; + struct clocksource cs; + unsigned long total_cycles; + bool cs_enabled; + + /* callbacks for CMSTR and CMCSR access */ + unsigned long (*read_control)(void __iomem *base, unsigned long offs); + void (*write_control)(void __iomem *base, unsigned long offs, + unsigned long value); + + /* callbacks for CMCNT and CMCOR access */ + unsigned long (*read_count)(void __iomem *base, unsigned long offs); + void (*write_count)(void __iomem *base, unsigned long offs, + unsigned long value); +}; + +/* Examples of supported CMT timer register layouts and I/O access widths: + * + * "16-bit counter and 16-bit control" as found on sh7263: + * CMSTR 0xfffec000 16-bit + * CMCSR 0xfffec002 16-bit + * CMCNT 0xfffec004 16-bit + * CMCOR 0xfffec006 16-bit + * + * "32-bit counter and 16-bit control" as found on sh7372, sh73a0, r8a7740: + * CMSTR 0xffca0000 16-bit + * CMCSR 0xffca0060 16-bit + * CMCNT 0xffca0064 32-bit + * CMCOR 0xffca0068 32-bit + */ + +static unsigned long sh_cmt_read16(void __iomem *base, unsigned long offs) +{ + return ioread16(base + (offs << 1)); +} + +static unsigned long sh_cmt_read32(void __iomem *base, unsigned long offs) +{ + return ioread32(base + (offs << 2)); +} + +static void sh_cmt_write16(void __iomem *base, unsigned long offs, + unsigned long value) +{ + iowrite16(value, base + (offs << 1)); +} + +static void sh_cmt_write32(void __iomem *base, unsigned long offs, + unsigned long value) +{ + iowrite32(value, base + (offs << 2)); +} + +#define CMCSR 0 /* channel register */ +#define CMCNT 1 /* channel register */ +#define CMCOR 2 /* channel register */ + +static inline unsigned long sh_cmt_read_cmstr(struct sh_cmt_priv *p) +{ + struct sh_timer_config *cfg = p->pdev->dev.platform_data; + + return p->read_control(p->mapbase - cfg->channel_offset, 0); +} + +static inline unsigned long sh_cmt_read_cmcsr(struct sh_cmt_priv *p) +{ + return p->read_control(p->mapbase, CMCSR); +} + +static inline unsigned long sh_cmt_read_cmcnt(struct sh_cmt_priv *p) +{ + return p->read_count(p->mapbase, CMCNT); +} + +static inline void sh_cmt_write_cmstr(struct sh_cmt_priv *p, + unsigned long value) +{ + struct sh_timer_config *cfg = p->pdev->dev.platform_data; + + p->write_control(p->mapbase - cfg->channel_offset, 0, value); +} + +static inline void sh_cmt_write_cmcsr(struct sh_cmt_priv *p, + unsigned long value) +{ + p->write_control(p->mapbase, CMCSR, value); +} + +static inline void sh_cmt_write_cmcnt(struct sh_cmt_priv *p, + unsigned long value) +{ + p->write_count(p->mapbase, CMCNT, value); +} + +static inline void sh_cmt_write_cmcor(struct sh_cmt_priv *p, + unsigned long value) +{ + p->write_count(p->mapbase, CMCOR, value); +} + +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_cmcsr(p) & p->overflow_bit; + + /* Make sure the timer value is stable. Stolen from acpi_pm.c */ + do { + o2 = o1; + v1 = sh_cmt_read_cmcnt(p); + v2 = sh_cmt_read_cmcnt(p); + v3 = sh_cmt_read_cmcnt(p); + o1 = sh_cmt_read_cmcsr(p) & p->overflow_bit; + } while (unlikely((o1 != o2) || (v1 > v2 && v1 < v3) + || (v2 > v3 && v2 < v1) || (v3 > v1 && v3 < v2))); + + *has_wrapped = o1; + return v2; +} + +static DEFINE_RAW_SPINLOCK(sh_cmt_lock); + +static void sh_cmt_start_stop_ch(struct sh_cmt_priv *p, int start) +{ + struct sh_timer_config *cfg = p->pdev->dev.platform_data; + unsigned long flags, value; + + /* start stop register shared by multiple timer channels */ + raw_spin_lock_irqsave(&sh_cmt_lock, flags); + value = sh_cmt_read_cmstr(p); + + if (start) + value |= 1 << cfg->timer_bit; + else + value &= ~(1 << cfg->timer_bit); + + sh_cmt_write_cmstr(p, value); + raw_spin_unlock_irqrestore(&sh_cmt_lock, flags); +} + +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) { + dev_err(&p->pdev->dev, "cannot enable clock\n"); + goto err0; + } + + /* make sure channel is disabled */ + sh_cmt_start_stop_ch(p, 0); + + /* configure channel, periodic mode and maximum timeout */ + if (p->width == 16) { + *rate = clk_get_rate(p->clk) / 512; + sh_cmt_write_cmcsr(p, 0x43); + } else { + *rate = clk_get_rate(p->clk) / 8; + sh_cmt_write_cmcsr(p, 0x01a4); + } + + sh_cmt_write_cmcor(p, 0xffffffff); + sh_cmt_write_cmcnt(p, 0); + + /* + * According to the sh73a0 user's manual, as CMCNT can be operated + * only by the RCLK (Pseudo 32 KHz), there's one restriction on + * modifying CMCNT register; two RCLK cycles are necessary before + * this register is either read or any modification of the value + * it holds is reflected in the LSI's actual operation. + * + * While at it, we're supposed to clear out the CMCNT as of this + * moment, so make sure it's processed properly here. This will + * take RCLKx2 at maximum. + */ + for (k = 0; k < 100; k++) { + if (!sh_cmt_read_cmcnt(p)) + break; + udelay(1); + } + + if (sh_cmt_read_cmcnt(p)) { + dev_err(&p->pdev->dev, "cannot clear CMCNT\n"); + ret = -ETIMEDOUT; + goto err1; + } + + /* enable channel */ + sh_cmt_start_stop_ch(p, 1); + return 0; + err1: + /* stop clock */ + clk_disable(p->clk); + + err0: + return ret; +} + +static void sh_cmt_disable(struct sh_cmt_priv *p) +{ + /* disable channel */ + sh_cmt_start_stop_ch(p, 0); + + /* disable interrupts in CMT block */ + sh_cmt_write_cmcsr(p, 0); + + /* stop clock */ + clk_disable(p->clk); + + dev_pm_syscore_device(&p->pdev->dev, false); + pm_runtime_put(&p->pdev->dev); +} + +/* private flags */ +#define FLAG_CLOCKEVENT (1 << 0) +#define FLAG_CLOCKSOURCE (1 << 1) +#define FLAG_REPROGRAM (1 << 2) +#define FLAG_SKIPEVENT (1 << 3) +#define FLAG_IRQCONTEXT (1 << 4) + +static void sh_cmt_clock_event_program_verify(struct sh_cmt_priv *p, + int absolute) +{ + unsigned long new_match; + unsigned long value = p->next_match_value; + unsigned long delay = 0; + unsigned long now = 0; + int has_wrapped; + + now = sh_cmt_get_counter(p, &has_wrapped); + p->flags |= FLAG_REPROGRAM; /* force reprogram */ + + if (has_wrapped) { + /* we're competing with the interrupt handler. + * -> let the interrupt handler reprogram the timer. + * -> interrupt number two handles the event. + */ + p->flags |= FLAG_SKIPEVENT; + return; + } + + if (absolute) + now = 0; + + do { + /* reprogram the timer hardware, + * but don't save the new match value yet. + */ + new_match = now + value + delay; + if (new_match > p->max_match_value) + new_match = p->max_match_value; + + sh_cmt_write_cmcor(p, new_match); + + now = sh_cmt_get_counter(p, &has_wrapped); + if (has_wrapped && (new_match > p->match_value)) { + /* we are changing to a greater match value, + * so this wrap must be caused by the counter + * matching the old value. + * -> first interrupt reprograms the timer. + * -> interrupt number two handles the event. + */ + p->flags |= FLAG_SKIPEVENT; + break; + } + + if (has_wrapped) { + /* we are changing to a smaller match value, + * so the wrap must be caused by the counter + * matching the new value. + * -> save programmed match value. + * -> let isr handle the event. + */ + p->match_value = new_match; + break; + } + + /* be safe: verify hardware settings */ + if (now < new_match) { + /* timer value is below match value, all good. + * this makes sure we won't miss any match events. + * -> save programmed match value. + * -> let isr handle the event. + */ + p->match_value = new_match; + break; + } + + /* the counter has reached a value greater + * than our new match value. and since the + * has_wrapped flag isn't set we must have + * programmed a too close event. + * -> increase delay and retry. + */ + if (delay) + delay <<= 1; + else + delay = 1; + + if (!delay) + dev_warn(&p->pdev->dev, "too long delay\n"); + + } while (delay); +} + +static void __sh_cmt_set_next(struct sh_cmt_priv *p, unsigned long delta) +{ + if (delta > p->max_match_value) + dev_warn(&p->pdev->dev, "delta out of range\n"); + + p->next_match_value = delta; + sh_cmt_clock_event_program_verify(p, 0); +} + +static void sh_cmt_set_next(struct sh_cmt_priv *p, unsigned long delta) +{ + unsigned long flags; + + raw_spin_lock_irqsave(&p->lock, flags); + __sh_cmt_set_next(p, delta); + raw_spin_unlock_irqrestore(&p->lock, flags); +} + +static irqreturn_t sh_cmt_interrupt(int irq, void *dev_id) +{ + struct sh_cmt_priv *p = dev_id; + + /* clear flags */ + sh_cmt_write_cmcsr(p, sh_cmt_read_cmcsr(p) & p->clear_bits); + + /* update clock source counter to begin with if enabled + * the wrap flag should be cleared by the timer specific + * isr before we end up here. + */ + if (p->flags & FLAG_CLOCKSOURCE) + p->total_cycles += p->match_value + 1; + + if (!(p->flags & FLAG_REPROGRAM)) + p->next_match_value = p->max_match_value; + + p->flags |= FLAG_IRQCONTEXT; + + if (p->flags & FLAG_CLOCKEVENT) { + if (!(p->flags & FLAG_SKIPEVENT)) { + if (p->ced.mode == CLOCK_EVT_MODE_ONESHOT) { + p->next_match_value = p->max_match_value; + p->flags |= FLAG_REPROGRAM; + } + + p->ced.event_handler(&p->ced); + } + } + + p->flags &= ~FLAG_SKIPEVENT; + + if (p->flags & FLAG_REPROGRAM) { + p->flags &= ~FLAG_REPROGRAM; + sh_cmt_clock_event_program_verify(p, 1); + + if (p->flags & FLAG_CLOCKEVENT) + if ((p->ced.mode == CLOCK_EVT_MODE_SHUTDOWN) + || (p->match_value == p->next_match_value)) + p->flags &= ~FLAG_REPROGRAM; + } + + p->flags &= ~FLAG_IRQCONTEXT; + + return IRQ_HANDLED; +} + +static int sh_cmt_start(struct sh_cmt_priv *p, unsigned long flag) +{ + int ret = 0; + unsigned long flags; + + raw_spin_lock_irqsave(&p->lock, flags); + + if (!(p->flags & (FLAG_CLOCKEVENT | FLAG_CLOCKSOURCE))) + ret = sh_cmt_enable(p, &p->rate); + + if (ret) + goto out; + p->flags |= flag; + + /* setup timeout if no clockevent */ + if ((flag == FLAG_CLOCKSOURCE) && (!(p->flags & FLAG_CLOCKEVENT))) + __sh_cmt_set_next(p, p->max_match_value); + out: + raw_spin_unlock_irqrestore(&p->lock, flags); + + return ret; +} + +static void sh_cmt_stop(struct sh_cmt_priv *p, unsigned long flag) +{ + unsigned long flags; + unsigned long f; + + raw_spin_lock_irqsave(&p->lock, flags); + + f = p->flags & (FLAG_CLOCKEVENT | FLAG_CLOCKSOURCE); + p->flags &= ~flag; + + if (f && !(p->flags & (FLAG_CLOCKEVENT | FLAG_CLOCKSOURCE))) + sh_cmt_disable(p); + + /* adjust the timeout to maximum if only clocksource left */ + if ((flag == FLAG_CLOCKEVENT) && (p->flags & FLAG_CLOCKSOURCE)) + __sh_cmt_set_next(p, p->max_match_value); + + raw_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; + + raw_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 + 1; + raw_spin_unlock_irqrestore(&p->lock, flags); + + return value + raw; +} + +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) { + __clocksource_updatefreq_hz(cs, p->rate); + p->cs_enabled = true; + } + return ret; +} + +static void sh_cmt_clocksource_disable(struct clocksource *cs) +{ + 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) +{ + 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, + 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->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; + + dev_info(&p->pdev->dev, "used as clock source\n"); + + /* Register with dummy 1 Hz value, gets updated in ->enable() */ + clocksource_register_hz(cs, 1); + 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); +} + +static void sh_cmt_clock_event_start(struct sh_cmt_priv *p, int periodic) +{ + struct clock_event_device *ced = &p->ced; + + sh_cmt_start(p, FLAG_CLOCKEVENT); + + /* TODO: calculate good shift from rate and counter bit width */ + + ced->shift = 32; + ced->mult = div_sc(p->rate, NSEC_PER_SEC, ced->shift); + ced->max_delta_ns = clockevent_delta2ns(p->max_match_value, ced); + ced->min_delta_ns = clockevent_delta2ns(0x1f, ced); + + if (periodic) + sh_cmt_set_next(p, ((p->rate + HZ/2) / HZ) - 1); + else + sh_cmt_set_next(p, p->max_match_value); +} + +static void sh_cmt_clock_event_mode(enum clock_event_mode mode, + struct clock_event_device *ced) +{ + struct sh_cmt_priv *p = ced_to_sh_cmt(ced); + + /* deal with old setting first */ + switch (ced->mode) { + case CLOCK_EVT_MODE_PERIODIC: + case CLOCK_EVT_MODE_ONESHOT: + sh_cmt_stop(p, FLAG_CLOCKEVENT); + break; + default: + break; + } + + switch (mode) { + case CLOCK_EVT_MODE_PERIODIC: + dev_info(&p->pdev->dev, "used for periodic clock events\n"); + sh_cmt_clock_event_start(p, 1); + break; + case CLOCK_EVT_MODE_ONESHOT: + dev_info(&p->pdev->dev, "used for oneshot clock events\n"); + sh_cmt_clock_event_start(p, 0); + break; + case CLOCK_EVT_MODE_SHUTDOWN: + case CLOCK_EVT_MODE_UNUSED: + sh_cmt_stop(p, FLAG_CLOCKEVENT); + break; + default: + break; + } +} + +static int sh_cmt_clock_event_next(unsigned long delta, + struct clock_event_device *ced) +{ + struct sh_cmt_priv *p = ced_to_sh_cmt(ced); + + BUG_ON(ced->mode != CLOCK_EVT_MODE_ONESHOT); + if (likely(p->flags & FLAG_IRQCONTEXT)) + p->next_match_value = delta - 1; + else + sh_cmt_set_next(p, delta - 1); + + 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) +{ + struct clock_event_device *ced = &p->ced; + + memset(ced, 0, sizeof(*ced)); + + ced->name = name; + ced->features = CLOCK_EVT_FEAT_PERIODIC; + ced->features |= CLOCK_EVT_FEAT_ONESHOT; + ced->rating = rating; + 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); +} + +static int sh_cmt_register(struct sh_cmt_priv *p, char *name, + unsigned long clockevent_rating, + unsigned long clocksource_rating) +{ + 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_timer_config *cfg = pdev->dev.platform_data; + struct resource *res; + int irq, ret; + ret = -ENXIO; + + memset(p, 0, sizeof(*p)); + p->pdev = pdev; + + if (!cfg) { + dev_err(&p->pdev->dev, "missing platform data\n"); + goto err0; + } + + res = platform_get_resource(p->pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(&p->pdev->dev, "failed to get I/O memory\n"); + goto err0; + } + + irq = platform_get_irq(p->pdev, 0); + if (irq < 0) { + dev_err(&p->pdev->dev, "failed to get irq\n"); + goto err0; + } + + /* map memory, let mapbase point to our channel */ + p->mapbase = ioremap_nocache(res->start, resource_size(res)); + if (p->mapbase == NULL) { + dev_err(&p->pdev->dev, "failed to remap I/O memory\n"); + goto err0; + } + + /* request irq using setup_irq() (too early for request_irq()) */ + p->irqaction.name = dev_name(&p->pdev->dev); + p->irqaction.handler = sh_cmt_interrupt; + p->irqaction.dev_id = p; + p->irqaction.flags = IRQF_DISABLED | IRQF_TIMER | \ + IRQF_IRQPOLL | IRQF_NOBALANCING; + + /* get hold of clock */ + p->clk = clk_get(&p->pdev->dev, "cmt_fck"); + if (IS_ERR(p->clk)) { + dev_err(&p->pdev->dev, "cannot get clock\n"); + ret = PTR_ERR(p->clk); + goto err1; + } + + p->read_control = sh_cmt_read16; + p->write_control = sh_cmt_write16; + + if (resource_size(res) == 6) { + p->width = 16; + p->read_count = sh_cmt_read16; + p->write_count = sh_cmt_write16; + p->overflow_bit = 0x80; + p->clear_bits = ~0x80; + } else { + p->width = 32; + p->read_count = sh_cmt_read32; + p->write_count = sh_cmt_write32; + p->overflow_bit = 0x8000; + p->clear_bits = ~0xc000; + } + + if (p->width == (sizeof(p->max_match_value) * 8)) + p->max_match_value = ~0; + else + p->max_match_value = (1 << p->width) - 1; + + p->match_value = p->max_match_value; + raw_spin_lock_init(&p->lock); + + ret = sh_cmt_register(p, (char *)dev_name(&p->pdev->dev), + cfg->clockevent_rating, + cfg->clocksource_rating); + if (ret) { + dev_err(&p->pdev->dev, "registration failed\n"); + goto err2; + } + p->cs_enabled = false; + + ret = setup_irq(irq, &p->irqaction); + if (ret) { + dev_err(&p->pdev->dev, "failed to request irq %d\n", irq); + goto err2; + } + + platform_set_drvdata(pdev, p); + + return 0; +err2: + clk_put(p->clk); + +err1: + iounmap(p->mapbase); +err0: + return ret; +} + +static int 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_runtime_set_active(&pdev->dev); + pm_runtime_enable(&pdev->dev); + } + + if (p) { + dev_info(&pdev->dev, "kept as earlytimer\n"); + goto out; + } + + p = kmalloc(sizeof(*p), GFP_KERNEL); + if (p == NULL) { + dev_err(&pdev->dev, "failed to allocate driver data\n"); + return -ENOMEM; + } + + ret = sh_cmt_setup(p, pdev); + if (ret) { + kfree(p); + pm_runtime_idle(&pdev->dev); + 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 sh_cmt_remove(struct platform_device *pdev) +{ + return -EBUSY; /* cannot unregister clockevent and clocksource */ +} + +static struct platform_driver sh_cmt_device_driver = { + .probe = sh_cmt_probe, + .remove = sh_cmt_remove, + .driver = { + .name = "sh_cmt", + } +}; + +static int __init sh_cmt_init(void) +{ + return platform_driver_register(&sh_cmt_device_driver); +} + +static void __exit sh_cmt_exit(void) +{ + platform_driver_unregister(&sh_cmt_device_driver); +} + +early_platform_init("earlytimer", &sh_cmt_device_driver); +subsys_initcall(sh_cmt_init); +module_exit(sh_cmt_exit); + +MODULE_AUTHOR("Magnus Damm"); +MODULE_DESCRIPTION("SuperH CMT Timer Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/clocksource/sh_mtu2.c b/drivers/clocksource/sh_mtu2.c new file mode 100644 index 000000000..4aac9ee0d --- /dev/null +++ b/drivers/clocksource/sh_mtu2.c @@ -0,0 +1,394 @@ +/* + * SuperH Timer Support - MTU2 + * + * Copyright (C) 2009 Magnus Damm + * + * 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 + * + * 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/init.h> +#include <linux/platform_device.h> +#include <linux/spinlock.h> +#include <linux/interrupt.h> +#include <linux/ioport.h> +#include <linux/delay.h> +#include <linux/io.h> +#include <linux/clk.h> +#include <linux/irq.h> +#include <linux/err.h> +#include <linux/clockchips.h> +#include <linux/sh_timer.h> +#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; + struct clk *clk; + struct irqaction irqaction; + struct platform_device *pdev; + unsigned long rate; + unsigned long periodic; + struct clock_event_device ced; +}; + +static DEFINE_RAW_SPINLOCK(sh_mtu2_lock); + +#define TSTR -1 /* shared register */ +#define TCR 0 /* channel register */ +#define TMDR 1 /* channel register */ +#define TIOR 2 /* channel register */ +#define TIER 3 /* channel register */ +#define TSR 4 /* channel register */ +#define TCNT 5 /* channel register */ +#define TGR 6 /* channel register */ + +static unsigned long mtu2_reg_offs[] = { + [TCR] = 0, + [TMDR] = 1, + [TIOR] = 2, + [TIER] = 4, + [TSR] = 5, + [TCNT] = 6, + [TGR] = 8, +}; + +static inline unsigned long sh_mtu2_read(struct sh_mtu2_priv *p, int reg_nr) +{ + struct sh_timer_config *cfg = p->pdev->dev.platform_data; + void __iomem *base = p->mapbase; + unsigned long offs; + + if (reg_nr == TSTR) + return ioread8(base + cfg->channel_offset); + + offs = mtu2_reg_offs[reg_nr]; + + if ((reg_nr == TCNT) || (reg_nr == TGR)) + return ioread16(base + offs); + else + return ioread8(base + offs); +} + +static inline void sh_mtu2_write(struct sh_mtu2_priv *p, int reg_nr, + unsigned long value) +{ + struct sh_timer_config *cfg = p->pdev->dev.platform_data; + void __iomem *base = p->mapbase; + unsigned long offs; + + if (reg_nr == TSTR) { + iowrite8(value, base + cfg->channel_offset); + return; + } + + offs = mtu2_reg_offs[reg_nr]; + + if ((reg_nr == TCNT) || (reg_nr == TGR)) + iowrite16(value, base + offs); + else + iowrite8(value, base + offs); +} + +static void sh_mtu2_start_stop_ch(struct sh_mtu2_priv *p, int start) +{ + struct sh_timer_config *cfg = p->pdev->dev.platform_data; + unsigned long flags, value; + + /* start stop register shared by multiple timer channels */ + raw_spin_lock_irqsave(&sh_mtu2_lock, flags); + value = sh_mtu2_read(p, TSTR); + + if (start) + value |= 1 << cfg->timer_bit; + else + value &= ~(1 << cfg->timer_bit); + + sh_mtu2_write(p, TSTR, value); + raw_spin_unlock_irqrestore(&sh_mtu2_lock, flags); +} + +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) { + dev_err(&p->pdev->dev, "cannot enable clock\n"); + return ret; + } + + /* make sure channel is disabled */ + sh_mtu2_start_stop_ch(p, 0); + + p->rate = clk_get_rate(p->clk) / 64; + p->periodic = (p->rate + HZ/2) / HZ; + + /* "Periodic Counter Operation" */ + sh_mtu2_write(p, TCR, 0x23); /* TGRA clear, divide clock by 64 */ + sh_mtu2_write(p, TIOR, 0); + sh_mtu2_write(p, TGR, p->periodic); + sh_mtu2_write(p, TCNT, 0); + sh_mtu2_write(p, TMDR, 0); + sh_mtu2_write(p, TIER, 0x01); + + /* enable channel */ + sh_mtu2_start_stop_ch(p, 1); + + return 0; +} + +static void sh_mtu2_disable(struct sh_mtu2_priv *p) +{ + /* disable channel */ + sh_mtu2_start_stop_ch(p, 0); + + /* 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) +{ + struct sh_mtu2_priv *p = dev_id; + + /* acknowledge interrupt */ + sh_mtu2_read(p, TSR); + sh_mtu2_write(p, TSR, 0xfe); + + /* notify clockevent layer */ + p->ced.event_handler(&p->ced); + return IRQ_HANDLED; +} + +static struct sh_mtu2_priv *ced_to_sh_mtu2(struct clock_event_device *ced) +{ + return container_of(ced, struct sh_mtu2_priv, ced); +} + +static void sh_mtu2_clock_event_mode(enum clock_event_mode mode, + struct clock_event_device *ced) +{ + struct sh_mtu2_priv *p = ced_to_sh_mtu2(ced); + int disabled = 0; + + /* deal with old setting first */ + switch (ced->mode) { + case CLOCK_EVT_MODE_PERIODIC: + sh_mtu2_disable(p); + disabled = 1; + break; + default: + break; + } + + switch (mode) { + case CLOCK_EVT_MODE_PERIODIC: + dev_info(&p->pdev->dev, "used for periodic clock events\n"); + sh_mtu2_enable(p); + break; + case CLOCK_EVT_MODE_UNUSED: + if (!disabled) + sh_mtu2_disable(p); + break; + case CLOCK_EVT_MODE_SHUTDOWN: + default: + break; + } +} + +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) +{ + struct clock_event_device *ced = &p->ced; + int ret; + + memset(ced, 0, sizeof(*ced)); + + ced->name = name; + ced->features = CLOCK_EVT_FEAT_PERIODIC; + 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); + + ret = setup_irq(p->irqaction.irq, &p->irqaction); + if (ret) { + dev_err(&p->pdev->dev, "failed to request irq %d\n", + p->irqaction.irq); + return; + } +} + +static int sh_mtu2_register(struct sh_mtu2_priv *p, char *name, + unsigned long clockevent_rating) +{ + if (clockevent_rating) + sh_mtu2_register_clockevent(p, name, clockevent_rating); + + return 0; +} + +static int sh_mtu2_setup(struct sh_mtu2_priv *p, struct platform_device *pdev) +{ + struct sh_timer_config *cfg = pdev->dev.platform_data; + struct resource *res; + int irq, ret; + ret = -ENXIO; + + memset(p, 0, sizeof(*p)); + p->pdev = pdev; + + if (!cfg) { + dev_err(&p->pdev->dev, "missing platform data\n"); + goto err0; + } + + platform_set_drvdata(pdev, p); + + res = platform_get_resource(p->pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(&p->pdev->dev, "failed to get I/O memory\n"); + goto err0; + } + + irq = platform_get_irq(p->pdev, 0); + if (irq < 0) { + dev_err(&p->pdev->dev, "failed to get irq\n"); + goto err0; + } + + /* map memory, let mapbase point to our channel */ + p->mapbase = ioremap_nocache(res->start, resource_size(res)); + if (p->mapbase == NULL) { + dev_err(&p->pdev->dev, "failed to remap I/O memory\n"); + goto err0; + } + + /* setup data for setup_irq() (too early for request_irq()) */ + p->irqaction.name = dev_name(&p->pdev->dev); + p->irqaction.handler = sh_mtu2_interrupt; + p->irqaction.dev_id = p; + p->irqaction.irq = irq; + p->irqaction.flags = IRQF_DISABLED | IRQF_TIMER | \ + IRQF_IRQPOLL | IRQF_NOBALANCING; + + /* get hold of clock */ + p->clk = clk_get(&p->pdev->dev, "mtu2_fck"); + if (IS_ERR(p->clk)) { + dev_err(&p->pdev->dev, "cannot get clock\n"); + ret = PTR_ERR(p->clk); + goto err1; + } + + return sh_mtu2_register(p, (char *)dev_name(&p->pdev->dev), + cfg->clockevent_rating); + err1: + iounmap(p->mapbase); + err0: + return ret; +} + +static int 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_runtime_set_active(&pdev->dev); + pm_runtime_enable(&pdev->dev); + } + + if (p) { + dev_info(&pdev->dev, "kept as earlytimer\n"); + goto out; + } + + p = kmalloc(sizeof(*p), GFP_KERNEL); + if (p == NULL) { + dev_err(&pdev->dev, "failed to allocate driver data\n"); + return -ENOMEM; + } + + ret = sh_mtu2_setup(p, pdev); + if (ret) { + kfree(p); + platform_set_drvdata(pdev, NULL); + pm_runtime_idle(&pdev->dev); + 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 sh_mtu2_remove(struct platform_device *pdev) +{ + return -EBUSY; /* cannot unregister clockevent */ +} + +static struct platform_driver sh_mtu2_device_driver = { + .probe = sh_mtu2_probe, + .remove = sh_mtu2_remove, + .driver = { + .name = "sh_mtu2", + } +}; + +static int __init sh_mtu2_init(void) +{ + return platform_driver_register(&sh_mtu2_device_driver); +} + +static void __exit sh_mtu2_exit(void) +{ + platform_driver_unregister(&sh_mtu2_device_driver); +} + +early_platform_init("earlytimer", &sh_mtu2_device_driver); +subsys_initcall(sh_mtu2_init); +module_exit(sh_mtu2_exit); + +MODULE_AUTHOR("Magnus Damm"); +MODULE_DESCRIPTION("SuperH MTU2 Timer Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/clocksource/sh_tmu.c b/drivers/clocksource/sh_tmu.c new file mode 100644 index 000000000..78b8dae49 --- /dev/null +++ b/drivers/clocksource/sh_tmu.c @@ -0,0 +1,557 @@ +/* + * SuperH Timer Support - TMU + * + * Copyright (C) 2009 Magnus Damm + * + * 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 + * + * 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/init.h> +#include <linux/platform_device.h> +#include <linux/spinlock.h> +#include <linux/interrupt.h> +#include <linux/ioport.h> +#include <linux/delay.h> +#include <linux/io.h> +#include <linux/clk.h> +#include <linux/irq.h> +#include <linux/err.h> +#include <linux/clocksource.h> +#include <linux/clockchips.h> +#include <linux/sh_timer.h> +#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; + struct clk *clk; + struct irqaction irqaction; + struct platform_device *pdev; + unsigned long rate; + 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); + +#define TSTR -1 /* shared register */ +#define TCOR 0 /* channel register */ +#define TCNT 1 /* channel register */ +#define TCR 2 /* channel register */ + +static inline unsigned long sh_tmu_read(struct sh_tmu_priv *p, int reg_nr) +{ + struct sh_timer_config *cfg = p->pdev->dev.platform_data; + void __iomem *base = p->mapbase; + unsigned long offs; + + if (reg_nr == TSTR) + return ioread8(base - cfg->channel_offset); + + offs = reg_nr << 2; + + if (reg_nr == TCR) + return ioread16(base + offs); + else + return ioread32(base + offs); +} + +static inline void sh_tmu_write(struct sh_tmu_priv *p, int reg_nr, + unsigned long value) +{ + struct sh_timer_config *cfg = p->pdev->dev.platform_data; + void __iomem *base = p->mapbase; + unsigned long offs; + + if (reg_nr == TSTR) { + iowrite8(value, base - cfg->channel_offset); + return; + } + + offs = reg_nr << 2; + + if (reg_nr == TCR) + iowrite16(value, base + offs); + else + iowrite32(value, base + offs); +} + +static void sh_tmu_start_stop_ch(struct sh_tmu_priv *p, int start) +{ + struct sh_timer_config *cfg = p->pdev->dev.platform_data; + unsigned long flags, value; + + /* start stop register shared by multiple timer channels */ + raw_spin_lock_irqsave(&sh_tmu_lock, flags); + value = sh_tmu_read(p, TSTR); + + if (start) + value |= 1 << cfg->timer_bit; + else + value &= ~(1 << cfg->timer_bit); + + sh_tmu_write(p, TSTR, value); + raw_spin_unlock_irqrestore(&sh_tmu_lock, flags); +} + +static int __sh_tmu_enable(struct sh_tmu_priv *p) +{ + int ret; + + /* enable clock */ + ret = clk_enable(p->clk); + if (ret) { + dev_err(&p->pdev->dev, "cannot enable clock\n"); + return ret; + } + + /* make sure channel is disabled */ + sh_tmu_start_stop_ch(p, 0); + + /* maximum timeout */ + sh_tmu_write(p, TCOR, 0xffffffff); + sh_tmu_write(p, TCNT, 0xffffffff); + + /* configure channel to parent clock / 4, irq off */ + p->rate = clk_get_rate(p->clk) / 4; + sh_tmu_write(p, TCR, 0x0000); + + /* enable channel */ + sh_tmu_start_stop_ch(p, 1); + + return 0; +} + +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); + + /* disable interrupts in TMU block */ + sh_tmu_write(p, TCR, 0x0000); + + /* stop clock */ + 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) +{ + /* stop timer */ + sh_tmu_start_stop_ch(p, 0); + + /* acknowledge interrupt */ + sh_tmu_read(p, TCR); + + /* enable interrupt */ + sh_tmu_write(p, TCR, 0x0020); + + /* reload delta value in case of periodic timer */ + if (periodic) + sh_tmu_write(p, TCOR, delta); + else + sh_tmu_write(p, TCOR, 0xffffffff); + + sh_tmu_write(p, TCNT, delta); + + /* start timer */ + sh_tmu_start_stop_ch(p, 1); +} + +static irqreturn_t sh_tmu_interrupt(int irq, void *dev_id) +{ + struct sh_tmu_priv *p = dev_id; + + /* disable or acknowledge interrupt */ + if (p->ced.mode == CLOCK_EVT_MODE_ONESHOT) + sh_tmu_write(p, TCR, 0x0000); + else + sh_tmu_write(p, TCR, 0x0020); + + /* notify clockevent layer */ + p->ced.event_handler(&p->ced); + return IRQ_HANDLED; +} + +static struct sh_tmu_priv *cs_to_sh_tmu(struct clocksource *cs) +{ + return container_of(cs, struct sh_tmu_priv, cs); +} + +static cycle_t sh_tmu_clocksource_read(struct clocksource *cs) +{ + struct sh_tmu_priv *p = cs_to_sh_tmu(cs); + + return sh_tmu_read(p, TCNT) ^ 0xffffffff; +} + +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) { + __clocksource_updatefreq_hz(cs, p->rate); + p->cs_enabled = true; + } + + return ret; +} + +static void sh_tmu_clocksource_disable(struct clocksource *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, + char *name, unsigned long rating) +{ + struct clocksource *cs = &p->cs; + + cs->name = name; + cs->rating = rating; + 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; + + dev_info(&p->pdev->dev, "used as clock source\n"); + + /* Register with dummy 1 Hz value, gets updated in ->enable() */ + clocksource_register_hz(cs, 1); + return 0; +} + +static struct sh_tmu_priv *ced_to_sh_tmu(struct clock_event_device *ced) +{ + return container_of(ced, struct sh_tmu_priv, ced); +} + +static void sh_tmu_clock_event_start(struct sh_tmu_priv *p, int periodic) +{ + struct clock_event_device *ced = &p->ced; + + sh_tmu_enable(p); + + clockevents_config(ced, p->rate); + + if (periodic) { + p->periodic = (p->rate + HZ/2) / HZ; + sh_tmu_set_next(p, p->periodic, 1); + } +} + +static void sh_tmu_clock_event_mode(enum clock_event_mode mode, + struct clock_event_device *ced) +{ + struct sh_tmu_priv *p = ced_to_sh_tmu(ced); + int disabled = 0; + + /* deal with old setting first */ + switch (ced->mode) { + case CLOCK_EVT_MODE_PERIODIC: + case CLOCK_EVT_MODE_ONESHOT: + sh_tmu_disable(p); + disabled = 1; + break; + default: + break; + } + + switch (mode) { + case CLOCK_EVT_MODE_PERIODIC: + dev_info(&p->pdev->dev, "used for periodic clock events\n"); + sh_tmu_clock_event_start(p, 1); + break; + case CLOCK_EVT_MODE_ONESHOT: + dev_info(&p->pdev->dev, "used for oneshot clock events\n"); + sh_tmu_clock_event_start(p, 0); + break; + case CLOCK_EVT_MODE_UNUSED: + if (!disabled) + sh_tmu_disable(p); + break; + case CLOCK_EVT_MODE_SHUTDOWN: + default: + break; + } +} + +static int sh_tmu_clock_event_next(unsigned long delta, + struct clock_event_device *ced) +{ + struct sh_tmu_priv *p = ced_to_sh_tmu(ced); + + BUG_ON(ced->mode != CLOCK_EVT_MODE_ONESHOT); + + /* program new delta value */ + sh_tmu_set_next(p, delta, 0); + 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) +{ + struct clock_event_device *ced = &p->ced; + int ret; + + memset(ced, 0, sizeof(*ced)); + + ced->name = name; + ced->features = CLOCK_EVT_FEAT_PERIODIC; + ced->features |= CLOCK_EVT_FEAT_ONESHOT; + ced->rating = rating; + 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"); + + clockevents_config_and_register(ced, 1, 0x300, 0xffffffff); + + ret = setup_irq(p->irqaction.irq, &p->irqaction); + if (ret) { + dev_err(&p->pdev->dev, "failed to request irq %d\n", + p->irqaction.irq); + return; + } +} + +static int sh_tmu_register(struct sh_tmu_priv *p, char *name, + unsigned long clockevent_rating, + unsigned long clocksource_rating) +{ + if (clockevent_rating) + sh_tmu_register_clockevent(p, name, clockevent_rating); + else if (clocksource_rating) + sh_tmu_register_clocksource(p, name, clocksource_rating); + + return 0; +} + +static int sh_tmu_setup(struct sh_tmu_priv *p, struct platform_device *pdev) +{ + struct sh_timer_config *cfg = pdev->dev.platform_data; + struct resource *res; + int irq, ret; + ret = -ENXIO; + + memset(p, 0, sizeof(*p)); + p->pdev = pdev; + + if (!cfg) { + dev_err(&p->pdev->dev, "missing platform data\n"); + goto err0; + } + + platform_set_drvdata(pdev, p); + + res = platform_get_resource(p->pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(&p->pdev->dev, "failed to get I/O memory\n"); + goto err0; + } + + irq = platform_get_irq(p->pdev, 0); + if (irq < 0) { + dev_err(&p->pdev->dev, "failed to get irq\n"); + goto err0; + } + + /* map memory, let mapbase point to our channel */ + p->mapbase = ioremap_nocache(res->start, resource_size(res)); + if (p->mapbase == NULL) { + dev_err(&p->pdev->dev, "failed to remap I/O memory\n"); + goto err0; + } + + /* setup data for setup_irq() (too early for request_irq()) */ + p->irqaction.name = dev_name(&p->pdev->dev); + p->irqaction.handler = sh_tmu_interrupt; + p->irqaction.dev_id = p; + p->irqaction.irq = irq; + p->irqaction.flags = IRQF_DISABLED | IRQF_TIMER | \ + IRQF_IRQPOLL | IRQF_NOBALANCING; + + /* get hold of clock */ + p->clk = clk_get(&p->pdev->dev, "tmu_fck"); + if (IS_ERR(p->clk)) { + dev_err(&p->pdev->dev, "cannot get clock\n"); + 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, + cfg->clocksource_rating); + err1: + iounmap(p->mapbase); + err0: + return ret; +} + +static int 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_runtime_set_active(&pdev->dev); + pm_runtime_enable(&pdev->dev); + } + + if (p) { + dev_info(&pdev->dev, "kept as earlytimer\n"); + goto out; + } + + p = kmalloc(sizeof(*p), GFP_KERNEL); + if (p == NULL) { + dev_err(&pdev->dev, "failed to allocate driver data\n"); + return -ENOMEM; + } + + ret = sh_tmu_setup(p, pdev); + if (ret) { + kfree(p); + platform_set_drvdata(pdev, NULL); + pm_runtime_idle(&pdev->dev); + 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 sh_tmu_remove(struct platform_device *pdev) +{ + return -EBUSY; /* cannot unregister clockevent and clocksource */ +} + +static struct platform_driver sh_tmu_device_driver = { + .probe = sh_tmu_probe, + .remove = sh_tmu_remove, + .driver = { + .name = "sh_tmu", + } +}; + +static int __init sh_tmu_init(void) +{ + return platform_driver_register(&sh_tmu_device_driver); +} + +static void __exit sh_tmu_exit(void) +{ + platform_driver_unregister(&sh_tmu_device_driver); +} + +early_platform_init("earlytimer", &sh_tmu_device_driver); +subsys_initcall(sh_tmu_init); +module_exit(sh_tmu_exit); + +MODULE_AUTHOR("Magnus Damm"); +MODULE_DESCRIPTION("SuperH TMU Timer Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/clocksource/sun4i_timer.c b/drivers/clocksource/sun4i_timer.c new file mode 100644 index 000000000..d4674e78e --- /dev/null +++ b/drivers/clocksource/sun4i_timer.c @@ -0,0 +1,148 @@ +/* + * Allwinner A1X SoCs timer handling. + * + * Copyright (C) 2012 Maxime Ripard + * + * Maxime Ripard <maxime.ripard@free-electrons.com> + * + * Based on code from + * Allwinner Technology Co., Ltd. <www.allwinnertech.com> + * Benn Huang <benn@allwinnertech.com> + * + * This file is licensed under the terms of the GNU General Public + * License version 2. This program is licensed "as is" without any + * warranty of any kind, whether express or implied. + */ + +#include <linux/clk.h> +#include <linux/clockchips.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/irqreturn.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/of_irq.h> + +#define TIMER_IRQ_EN_REG 0x00 +#define TIMER_IRQ_EN(val) (1 << val) +#define TIMER_IRQ_ST_REG 0x04 +#define TIMER_CTL_REG(val) (0x10 * val + 0x10) +#define TIMER_CTL_ENABLE (1 << 0) +#define TIMER_CTL_AUTORELOAD (1 << 1) +#define TIMER_CTL_ONESHOT (1 << 7) +#define TIMER_INTVAL_REG(val) (0x10 * val + 0x14) +#define TIMER_CNTVAL_REG(val) (0x10 * val + 0x18) + +#define TIMER_SCAL 16 + +static void __iomem *timer_base; + +static void sun4i_clkevt_mode(enum clock_event_mode mode, + struct clock_event_device *clk) +{ + u32 u = readl(timer_base + TIMER_CTL_REG(0)); + + switch (mode) { + case CLOCK_EVT_MODE_PERIODIC: + u &= ~(TIMER_CTL_ONESHOT); + writel(u | TIMER_CTL_ENABLE, timer_base + TIMER_CTL_REG(0)); + break; + + case CLOCK_EVT_MODE_ONESHOT: + writel(u | TIMER_CTL_ONESHOT, timer_base + TIMER_CTL_REG(0)); + break; + case CLOCK_EVT_MODE_UNUSED: + case CLOCK_EVT_MODE_SHUTDOWN: + default: + writel(u & ~(TIMER_CTL_ENABLE), timer_base + TIMER_CTL_REG(0)); + break; + } +} + +static int sun4i_clkevt_next_event(unsigned long evt, + struct clock_event_device *unused) +{ + u32 u = readl(timer_base + TIMER_CTL_REG(0)); + writel(evt, timer_base + TIMER_CNTVAL_REG(0)); + writel(u | TIMER_CTL_ENABLE | TIMER_CTL_AUTORELOAD, + timer_base + TIMER_CTL_REG(0)); + + return 0; +} + +static struct clock_event_device sun4i_clockevent = { + .name = "sun4i_tick", + .rating = 300, + .features = CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT, + .set_mode = sun4i_clkevt_mode, + .set_next_event = sun4i_clkevt_next_event, +}; + + +static irqreturn_t sun4i_timer_interrupt(int irq, void *dev_id) +{ + struct clock_event_device *evt = (struct clock_event_device *)dev_id; + + writel(0x1, timer_base + TIMER_IRQ_ST_REG); + evt->event_handler(evt); + + return IRQ_HANDLED; +} + +static struct irqaction sun4i_timer_irq = { + .name = "sun4i_timer0", + .flags = IRQF_DISABLED | IRQF_TIMER | IRQF_IRQPOLL, + .handler = sun4i_timer_interrupt, + .dev_id = &sun4i_clockevent, +}; + +static void __init sun4i_timer_init(struct device_node *node) +{ + unsigned long rate = 0; + struct clk *clk; + int ret, irq; + u32 val; + + timer_base = of_iomap(node, 0); + if (!timer_base) + panic("Can't map registers"); + + irq = irq_of_parse_and_map(node, 0); + if (irq <= 0) + panic("Can't parse IRQ"); + + clk = of_clk_get(node, 0); + if (IS_ERR(clk)) + panic("Can't get timer clock"); + + rate = clk_get_rate(clk); + + writel(rate / (TIMER_SCAL * HZ), + timer_base + TIMER_INTVAL_REG(0)); + + /* set clock source to HOSC, 16 pre-division */ + val = readl(timer_base + TIMER_CTL_REG(0)); + val &= ~(0x07 << 4); + val &= ~(0x03 << 2); + val |= (4 << 4) | (1 << 2); + writel(val, timer_base + TIMER_CTL_REG(0)); + + /* set mode to auto reload */ + val = readl(timer_base + TIMER_CTL_REG(0)); + writel(val | TIMER_CTL_AUTORELOAD, timer_base + TIMER_CTL_REG(0)); + + ret = setup_irq(irq, &sun4i_timer_irq); + if (ret) + pr_warn("failed to setup irq %d\n", irq); + + /* Enable timer0 interrupt */ + val = readl(timer_base + TIMER_IRQ_EN_REG); + writel(val | TIMER_IRQ_EN(0), timer_base + TIMER_IRQ_EN_REG); + + sun4i_clockevent.cpumask = cpumask_of(0); + + clockevents_config_and_register(&sun4i_clockevent, rate / TIMER_SCAL, + 0x1, 0xff); +} +CLOCKSOURCE_OF_DECLARE(sun4i, "allwinner,sun4i-timer", + sun4i_timer_init); diff --git a/drivers/clocksource/tcb_clksrc.c b/drivers/clocksource/tcb_clksrc.c new file mode 100644 index 000000000..8a6187225 --- /dev/null +++ b/drivers/clocksource/tcb_clksrc.c @@ -0,0 +1,329 @@ +#include <linux/init.h> +#include <linux/clocksource.h> +#include <linux/clockchips.h> +#include <linux/interrupt.h> +#include <linux/irq.h> + +#include <linux/clk.h> +#include <linux/err.h> +#include <linux/ioport.h> +#include <linux/io.h> +#include <linux/platform_device.h> +#include <linux/atmel_tc.h> + + +/* + * We're configured to use a specific TC block, one that's not hooked + * up to external hardware, to provide a time solution: + * + * - Two channels combine to create a free-running 32 bit counter + * with a base rate of 5+ MHz, packaged as a clocksource (with + * resolution better than 200 nsec). + * - Some chips support 32 bit counter. A single channel is used for + * this 32 bit free-running counter. the second channel is not used. + * + * - The third channel may be used to provide a 16-bit clockevent + * source, used in either periodic or oneshot mode. This runs + * at 32 KiHZ, and can handle delays of up to two seconds. + * + * A boot clocksource and clockevent source are also currently needed, + * unless the relevant platforms (ARM/AT91, AVR32/AT32) are changed so + * this code can be used when init_timers() is called, well before most + * devices are set up. (Some low end AT91 parts, which can run uClinux, + * have only the timers in one TC block... they currently don't support + * the tclib code, because of that initialization issue.) + * + * REVISIT behavior during system suspend states... we should disable + * all clocks and save the power. Easily done for clockevent devices, + * but clocksources won't necessarily get the needed notifications. + * For deeper system sleep states, this will be mandatory... + */ + +static void __iomem *tcaddr; + +static cycle_t tc_get_cycles(struct clocksource *cs) +{ + unsigned long flags; + u32 lower, upper; + + raw_local_irq_save(flags); + do { + upper = __raw_readl(tcaddr + ATMEL_TC_REG(1, CV)); + lower = __raw_readl(tcaddr + ATMEL_TC_REG(0, CV)); + } while (upper != __raw_readl(tcaddr + ATMEL_TC_REG(1, CV))); + + raw_local_irq_restore(flags); + return (upper << 16) | lower; +} + +static cycle_t tc_get_cycles32(struct clocksource *cs) +{ + return __raw_readl(tcaddr + ATMEL_TC_REG(0, CV)); +} + +static struct clocksource clksrc = { + .name = "tcb_clksrc", + .rating = 200, + .read = tc_get_cycles, + .mask = CLOCKSOURCE_MASK(32), + .flags = CLOCK_SOURCE_IS_CONTINUOUS, +}; + +#ifdef CONFIG_GENERIC_CLOCKEVENTS + +struct tc_clkevt_device { + struct clock_event_device clkevt; + struct clk *clk; + void __iomem *regs; +}; + +static struct tc_clkevt_device *to_tc_clkevt(struct clock_event_device *clkevt) +{ + return container_of(clkevt, struct tc_clkevt_device, clkevt); +} + +/* For now, we always use the 32K clock ... this optimizes for NO_HZ, + * because using one of the divided clocks would usually mean the + * tick rate can never be less than several dozen Hz (vs 0.5 Hz). + * + * A divided clock could be good for high resolution timers, since + * 30.5 usec resolution can seem "low". + */ +static u32 timer_clock; + +static void tc_mode(enum clock_event_mode m, struct clock_event_device *d) +{ + struct tc_clkevt_device *tcd = to_tc_clkevt(d); + void __iomem *regs = tcd->regs; + + if (tcd->clkevt.mode == CLOCK_EVT_MODE_PERIODIC + || tcd->clkevt.mode == CLOCK_EVT_MODE_ONESHOT) { + __raw_writel(0xff, regs + ATMEL_TC_REG(2, IDR)); + __raw_writel(ATMEL_TC_CLKDIS, regs + ATMEL_TC_REG(2, CCR)); + clk_disable(tcd->clk); + } + + switch (m) { + + /* By not making the gentime core emulate periodic mode on top + * of oneshot, we get lower overhead and improved accuracy. + */ + case CLOCK_EVT_MODE_PERIODIC: + clk_enable(tcd->clk); + + /* slow clock, count up to RC, then irq and restart */ + __raw_writel(timer_clock + | ATMEL_TC_WAVE | ATMEL_TC_WAVESEL_UP_AUTO, + regs + ATMEL_TC_REG(2, CMR)); + __raw_writel((32768 + HZ/2) / HZ, tcaddr + ATMEL_TC_REG(2, RC)); + + /* Enable clock and interrupts on RC compare */ + __raw_writel(ATMEL_TC_CPCS, regs + ATMEL_TC_REG(2, IER)); + + /* go go gadget! */ + __raw_writel(ATMEL_TC_CLKEN | ATMEL_TC_SWTRG, + regs + ATMEL_TC_REG(2, CCR)); + break; + + case CLOCK_EVT_MODE_ONESHOT: + clk_enable(tcd->clk); + + /* slow clock, count up to RC, then irq and stop */ + __raw_writel(timer_clock | ATMEL_TC_CPCSTOP + | ATMEL_TC_WAVE | ATMEL_TC_WAVESEL_UP_AUTO, + regs + ATMEL_TC_REG(2, CMR)); + __raw_writel(ATMEL_TC_CPCS, regs + ATMEL_TC_REG(2, IER)); + + /* set_next_event() configures and starts the timer */ + break; + + default: + break; + } +} + +static int tc_next_event(unsigned long delta, struct clock_event_device *d) +{ + __raw_writel(delta, tcaddr + ATMEL_TC_REG(2, RC)); + + /* go go gadget! */ + __raw_writel(ATMEL_TC_CLKEN | ATMEL_TC_SWTRG, + tcaddr + ATMEL_TC_REG(2, CCR)); + return 0; +} + +static struct tc_clkevt_device clkevt = { + .clkevt = { + .name = "tc_clkevt", + .features = CLOCK_EVT_FEAT_PERIODIC + | CLOCK_EVT_FEAT_ONESHOT, + /* Should be lower than at91rm9200's system timer */ + .rating = 125, + .set_next_event = tc_next_event, + .set_mode = tc_mode, + }, +}; + +static irqreturn_t ch2_irq(int irq, void *handle) +{ + struct tc_clkevt_device *dev = handle; + unsigned int sr; + + sr = __raw_readl(dev->regs + ATMEL_TC_REG(2, SR)); + if (sr & ATMEL_TC_CPCS) { + dev->clkevt.event_handler(&dev->clkevt); + return IRQ_HANDLED; + } + + return IRQ_NONE; +} + +static struct irqaction tc_irqaction = { + .name = "tc_clkevt", + .flags = IRQF_TIMER | IRQF_DISABLED, + .handler = ch2_irq, +}; + +static void __init setup_clkevents(struct atmel_tc *tc, int clk32k_divisor_idx) +{ + struct clk *t2_clk = tc->clk[2]; + int irq = tc->irq[2]; + + clkevt.regs = tc->regs; + clkevt.clk = t2_clk; + tc_irqaction.dev_id = &clkevt; + + timer_clock = clk32k_divisor_idx; + + clkevt.clkevt.cpumask = cpumask_of(0); + + clockevents_config_and_register(&clkevt.clkevt, 32768, 1, 0xffff); + + setup_irq(irq, &tc_irqaction); +} + +#else /* !CONFIG_GENERIC_CLOCKEVENTS */ + +static void __init setup_clkevents(struct atmel_tc *tc, int clk32k_divisor_idx) +{ + /* NOTHING */ +} + +#endif + +static void __init tcb_setup_dual_chan(struct atmel_tc *tc, int mck_divisor_idx) +{ + /* channel 0: waveform mode, input mclk/8, clock TIOA0 on overflow */ + __raw_writel(mck_divisor_idx /* likely divide-by-8 */ + | ATMEL_TC_WAVE + | ATMEL_TC_WAVESEL_UP /* free-run */ + | ATMEL_TC_ACPA_SET /* TIOA0 rises at 0 */ + | ATMEL_TC_ACPC_CLEAR, /* (duty cycle 50%) */ + tcaddr + ATMEL_TC_REG(0, CMR)); + __raw_writel(0x0000, tcaddr + ATMEL_TC_REG(0, RA)); + __raw_writel(0x8000, tcaddr + ATMEL_TC_REG(0, RC)); + __raw_writel(0xff, tcaddr + ATMEL_TC_REG(0, IDR)); /* no irqs */ + __raw_writel(ATMEL_TC_CLKEN, tcaddr + ATMEL_TC_REG(0, CCR)); + + /* channel 1: waveform mode, input TIOA0 */ + __raw_writel(ATMEL_TC_XC1 /* input: TIOA0 */ + | ATMEL_TC_WAVE + | ATMEL_TC_WAVESEL_UP, /* free-run */ + tcaddr + ATMEL_TC_REG(1, CMR)); + __raw_writel(0xff, tcaddr + ATMEL_TC_REG(1, IDR)); /* no irqs */ + __raw_writel(ATMEL_TC_CLKEN, tcaddr + ATMEL_TC_REG(1, CCR)); + + /* chain channel 0 to channel 1*/ + __raw_writel(ATMEL_TC_TC1XC1S_TIOA0, tcaddr + ATMEL_TC_BMR); + /* then reset all the timers */ + __raw_writel(ATMEL_TC_SYNC, tcaddr + ATMEL_TC_BCR); +} + +static void __init tcb_setup_single_chan(struct atmel_tc *tc, int mck_divisor_idx) +{ + /* channel 0: waveform mode, input mclk/8 */ + __raw_writel(mck_divisor_idx /* likely divide-by-8 */ + | ATMEL_TC_WAVE + | ATMEL_TC_WAVESEL_UP, /* free-run */ + tcaddr + ATMEL_TC_REG(0, CMR)); + __raw_writel(0xff, tcaddr + ATMEL_TC_REG(0, IDR)); /* no irqs */ + __raw_writel(ATMEL_TC_CLKEN, tcaddr + ATMEL_TC_REG(0, CCR)); + + /* then reset all the timers */ + __raw_writel(ATMEL_TC_SYNC, tcaddr + ATMEL_TC_BCR); +} + +static int __init tcb_clksrc_init(void) +{ + static char bootinfo[] __initdata + = KERN_DEBUG "%s: tc%d at %d.%03d MHz\n"; + + struct platform_device *pdev; + struct atmel_tc *tc; + struct clk *t0_clk; + u32 rate, divided_rate = 0; + int best_divisor_idx = -1; + int clk32k_divisor_idx = -1; + int i; + + tc = atmel_tc_alloc(CONFIG_ATMEL_TCB_CLKSRC_BLOCK, clksrc.name); + if (!tc) { + pr_debug("can't alloc TC for clocksource\n"); + return -ENODEV; + } + tcaddr = tc->regs; + pdev = tc->pdev; + + t0_clk = tc->clk[0]; + clk_enable(t0_clk); + + /* How fast will we be counting? Pick something over 5 MHz. */ + rate = (u32) clk_get_rate(t0_clk); + for (i = 0; i < 5; i++) { + unsigned divisor = atmel_tc_divisors[i]; + unsigned tmp; + + /* remember 32 KiHz clock for later */ + if (!divisor) { + clk32k_divisor_idx = i; + continue; + } + + tmp = rate / divisor; + pr_debug("TC: %u / %-3u [%d] --> %u\n", rate, divisor, i, tmp); + if (best_divisor_idx > 0) { + if (tmp < 5 * 1000 * 1000) + continue; + } + divided_rate = tmp; + best_divisor_idx = i; + } + + + printk(bootinfo, clksrc.name, CONFIG_ATMEL_TCB_CLKSRC_BLOCK, + divided_rate / 1000000, + ((divided_rate + 500000) % 1000000) / 1000); + + if (tc->tcb_config && tc->tcb_config->counter_width == 32) { + /* use apropriate function to read 32 bit counter */ + clksrc.read = tc_get_cycles32; + /* setup ony channel 0 */ + tcb_setup_single_chan(tc, best_divisor_idx); + } else { + /* tclib will give us three clocks no matter what the + * underlying platform supports. + */ + clk_enable(tc->clk[1]); + /* setup both channel 0 & 1 */ + tcb_setup_dual_chan(tc, best_divisor_idx); + } + + /* and away we go! */ + clocksource_register_hz(&clksrc, divided_rate); + + /* channel 2: periodic and oneshot timer support */ + setup_clkevents(tc, clk32k_divisor_idx); + + return 0; +} +arch_initcall(tcb_clksrc_init); diff --git a/drivers/clocksource/tegra20_timer.c b/drivers/clocksource/tegra20_timer.c new file mode 100644 index 000000000..ae877b021 --- /dev/null +++ b/drivers/clocksource/tegra20_timer.c @@ -0,0 +1,262 @@ +/* + * Copyright (C) 2010 Google, Inc. + * + * Author: + * Colin Cross <ccross@google.com> + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + * + */ + +#include <linux/init.h> +#include <linux/err.h> +#include <linux/time.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/clockchips.h> +#include <linux/clocksource.h> +#include <linux/clk.h> +#include <linux/io.h> +#include <linux/of_address.h> +#include <linux/of_irq.h> + +#include <asm/mach/time.h> +#include <asm/smp_twd.h> +#include <asm/sched_clock.h> + +#define RTC_SECONDS 0x08 +#define RTC_SHADOW_SECONDS 0x0c +#define RTC_MILLISECONDS 0x10 + +#define TIMERUS_CNTR_1US 0x10 +#define TIMERUS_USEC_CFG 0x14 +#define TIMERUS_CNTR_FREEZE 0x4c + +#define TIMER1_BASE 0x0 +#define TIMER2_BASE 0x8 +#define TIMER3_BASE 0x50 +#define TIMER4_BASE 0x58 + +#define TIMER_PTV 0x0 +#define TIMER_PCR 0x4 + +static void __iomem *timer_reg_base; +static void __iomem *rtc_base; + +static struct timespec persistent_ts; +static u64 persistent_ms, last_persistent_ms; + +#define timer_writel(value, reg) \ + __raw_writel(value, timer_reg_base + (reg)) +#define timer_readl(reg) \ + __raw_readl(timer_reg_base + (reg)) + +static int tegra_timer_set_next_event(unsigned long cycles, + struct clock_event_device *evt) +{ + u32 reg; + + reg = 0x80000000 | ((cycles > 1) ? (cycles-1) : 0); + timer_writel(reg, TIMER3_BASE + TIMER_PTV); + + return 0; +} + +static void tegra_timer_set_mode(enum clock_event_mode mode, + struct clock_event_device *evt) +{ + u32 reg; + + timer_writel(0, TIMER3_BASE + TIMER_PTV); + + switch (mode) { + case CLOCK_EVT_MODE_PERIODIC: + reg = 0xC0000000 | ((1000000/HZ)-1); + timer_writel(reg, TIMER3_BASE + TIMER_PTV); + break; + case CLOCK_EVT_MODE_ONESHOT: + break; + case CLOCK_EVT_MODE_UNUSED: + case CLOCK_EVT_MODE_SHUTDOWN: + case CLOCK_EVT_MODE_RESUME: + break; + } +} + +static struct clock_event_device tegra_clockevent = { + .name = "timer0", + .rating = 300, + .features = CLOCK_EVT_FEAT_ONESHOT | CLOCK_EVT_FEAT_PERIODIC, + .set_next_event = tegra_timer_set_next_event, + .set_mode = tegra_timer_set_mode, +}; + +static u32 notrace tegra_read_sched_clock(void) +{ + return timer_readl(TIMERUS_CNTR_1US); +} + +/* + * tegra_rtc_read - Reads the Tegra RTC registers + * Care must be taken that this funciton is not called while the + * tegra_rtc driver could be executing to avoid race conditions + * on the RTC shadow register + */ +static u64 tegra_rtc_read_ms(void) +{ + u32 ms = readl(rtc_base + RTC_MILLISECONDS); + u32 s = readl(rtc_base + RTC_SHADOW_SECONDS); + return (u64)s * MSEC_PER_SEC + ms; +} + +/* + * tegra_read_persistent_clock - Return time from a persistent clock. + * + * Reads the time from a source which isn't disabled during PM, the + * 32k sync timer. Convert the cycles elapsed since last read into + * nsecs and adds to a monotonically increasing timespec. + * Care must be taken that this funciton is not called while the + * tegra_rtc driver could be executing to avoid race conditions + * on the RTC shadow register + */ +static void tegra_read_persistent_clock(struct timespec *ts) +{ + u64 delta; + struct timespec *tsp = &persistent_ts; + + last_persistent_ms = persistent_ms; + persistent_ms = tegra_rtc_read_ms(); + delta = persistent_ms - last_persistent_ms; + + timespec_add_ns(tsp, delta * NSEC_PER_MSEC); + *ts = *tsp; +} + +static irqreturn_t tegra_timer_interrupt(int irq, void *dev_id) +{ + struct clock_event_device *evt = (struct clock_event_device *)dev_id; + timer_writel(1<<30, TIMER3_BASE + TIMER_PCR); + evt->event_handler(evt); + return IRQ_HANDLED; +} + +static struct irqaction tegra_timer_irq = { + .name = "timer0", + .flags = IRQF_DISABLED | IRQF_TIMER | IRQF_TRIGGER_HIGH, + .handler = tegra_timer_interrupt, + .dev_id = &tegra_clockevent, +}; + +static void __init tegra20_init_timer(struct device_node *np) +{ + struct clk *clk; + unsigned long rate; + int ret; + + timer_reg_base = of_iomap(np, 0); + if (!timer_reg_base) { + pr_err("Can't map timer registers\n"); + BUG(); + } + + tegra_timer_irq.irq = irq_of_parse_and_map(np, 2); + if (tegra_timer_irq.irq <= 0) { + pr_err("Failed to map timer IRQ\n"); + BUG(); + } + + clk = of_clk_get(np, 0); + if (IS_ERR(clk)) { + pr_warn("Unable to get timer clock. Assuming 12Mhz input clock.\n"); + rate = 12000000; + } else { + clk_prepare_enable(clk); + rate = clk_get_rate(clk); + } + + of_node_put(np); + + switch (rate) { + case 12000000: + timer_writel(0x000b, TIMERUS_USEC_CFG); + break; + case 13000000: + timer_writel(0x000c, TIMERUS_USEC_CFG); + break; + case 19200000: + timer_writel(0x045f, TIMERUS_USEC_CFG); + break; + case 26000000: + timer_writel(0x0019, TIMERUS_USEC_CFG); + break; + default: + WARN(1, "Unknown clock rate"); + } + + setup_sched_clock(tegra_read_sched_clock, 32, 1000000); + + if (clocksource_mmio_init(timer_reg_base + TIMERUS_CNTR_1US, + "timer_us", 1000000, 300, 32, clocksource_mmio_readl_up)) { + pr_err("Failed to register clocksource\n"); + BUG(); + } + + ret = setup_irq(tegra_timer_irq.irq, &tegra_timer_irq); + if (ret) { + pr_err("Failed to register timer IRQ: %d\n", ret); + BUG(); + } + + tegra_clockevent.cpumask = cpu_all_mask; + tegra_clockevent.irq = tegra_timer_irq.irq; + clockevents_config_and_register(&tegra_clockevent, 1000000, + 0x1, 0x1fffffff); +} +CLOCKSOURCE_OF_DECLARE(tegra20_timer, "nvidia,tegra20-timer", tegra20_init_timer); + +static void __init tegra20_init_rtc(struct device_node *np) +{ + struct clk *clk; + + rtc_base = of_iomap(np, 0); + if (!rtc_base) { + pr_err("Can't map RTC registers"); + BUG(); + } + + /* + * rtc registers are used by read_persistent_clock, keep the rtc clock + * enabled + */ + clk = of_clk_get(np, 0); + if (IS_ERR(clk)) + pr_warn("Unable to get rtc-tegra clock\n"); + else + clk_prepare_enable(clk); + + of_node_put(np); + + register_persistent_clock(NULL, tegra_read_persistent_clock); +} +CLOCKSOURCE_OF_DECLARE(tegra20_rtc, "nvidia,tegra20-rtc", tegra20_init_rtc); + +#ifdef CONFIG_PM +static u32 usec_config; + +void tegra_timer_suspend(void) +{ + usec_config = timer_readl(TIMERUS_USEC_CFG); +} + +void tegra_timer_resume(void) +{ + timer_writel(usec_config, TIMERUS_USEC_CFG); +} +#endif diff --git a/drivers/clocksource/time-armada-370-xp.c b/drivers/clocksource/time-armada-370-xp.c new file mode 100644 index 000000000..47a673070 --- /dev/null +++ b/drivers/clocksource/time-armada-370-xp.c @@ -0,0 +1,301 @@ +/* + * Marvell Armada 370/XP SoC timer handling. + * + * Copyright (C) 2012 Marvell + * + * Lior Amsalem <alior@marvell.com> + * Gregory CLEMENT <gregory.clement@free-electrons.com> + * Thomas Petazzoni <thomas.petazzoni@free-electrons.com> + * + * This file is licensed under the terms of the GNU General Public + * License version 2. This program is licensed "as is" without any + * warranty of any kind, whether express or implied. + * + * Timer 0 is used as free-running clocksource, while timer 1 is + * used as clock_event_device. + */ + +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/kernel.h> +#include <linux/clk.h> +#include <linux/timer.h> +#include <linux/clockchips.h> +#include <linux/interrupt.h> +#include <linux/of.h> +#include <linux/of_irq.h> +#include <linux/of_address.h> +#include <linux/irq.h> +#include <linux/module.h> + +#include <asm/sched_clock.h> +#include <asm/localtimer.h> +#include <linux/percpu.h> +/* + * Timer block registers. + */ +#define TIMER_CTRL_OFF 0x0000 +#define TIMER0_EN 0x0001 +#define TIMER0_RELOAD_EN 0x0002 +#define TIMER0_25MHZ 0x0800 +#define TIMER0_DIV(div) ((div) << 19) +#define TIMER1_EN 0x0004 +#define TIMER1_RELOAD_EN 0x0008 +#define TIMER1_25MHZ 0x1000 +#define TIMER1_DIV(div) ((div) << 22) +#define TIMER_EVENTS_STATUS 0x0004 +#define TIMER0_CLR_MASK (~0x1) +#define TIMER1_CLR_MASK (~0x100) +#define TIMER0_RELOAD_OFF 0x0010 +#define TIMER0_VAL_OFF 0x0014 +#define TIMER1_RELOAD_OFF 0x0018 +#define TIMER1_VAL_OFF 0x001c + +#define LCL_TIMER_EVENTS_STATUS 0x0028 +/* Global timers are connected to the coherency fabric clock, and the + below divider reduces their incrementing frequency. */ +#define TIMER_DIVIDER_SHIFT 5 +#define TIMER_DIVIDER (1 << TIMER_DIVIDER_SHIFT) + +/* + * SoC-specific data. + */ +static void __iomem *timer_base, *local_base; +static unsigned int timer_clk; +static bool timer25Mhz = true; + +/* + * Number of timer ticks per jiffy. + */ +static u32 ticks_per_jiffy; + +static struct clock_event_device __percpu **percpu_armada_370_xp_evt; + +static u32 notrace armada_370_xp_read_sched_clock(void) +{ + return ~readl(timer_base + TIMER0_VAL_OFF); +} + +/* + * Clockevent handling. + */ +static int +armada_370_xp_clkevt_next_event(unsigned long delta, + struct clock_event_device *dev) +{ + u32 u; + /* + * Clear clockevent timer interrupt. + */ + writel(TIMER0_CLR_MASK, local_base + LCL_TIMER_EVENTS_STATUS); + + /* + * Setup new clockevent timer value. + */ + writel(delta, local_base + TIMER0_VAL_OFF); + + /* + * Enable the timer. + */ + u = readl(local_base + TIMER_CTRL_OFF); + u = ((u & ~TIMER0_RELOAD_EN) | TIMER0_EN | + TIMER0_DIV(TIMER_DIVIDER_SHIFT)); + writel(u, local_base + TIMER_CTRL_OFF); + + return 0; +} + +static void +armada_370_xp_clkevt_mode(enum clock_event_mode mode, + struct clock_event_device *dev) +{ + u32 u; + + if (mode == CLOCK_EVT_MODE_PERIODIC) { + + /* + * Setup timer to fire at 1/HZ intervals. + */ + writel(ticks_per_jiffy - 1, local_base + TIMER0_RELOAD_OFF); + writel(ticks_per_jiffy - 1, local_base + TIMER0_VAL_OFF); + + /* + * Enable timer. + */ + + u = readl(local_base + TIMER_CTRL_OFF); + + writel((u | TIMER0_EN | TIMER0_RELOAD_EN | + TIMER0_DIV(TIMER_DIVIDER_SHIFT)), + local_base + TIMER_CTRL_OFF); + } else { + /* + * Disable timer. + */ + u = readl(local_base + TIMER_CTRL_OFF); + writel(u & ~TIMER0_EN, local_base + TIMER_CTRL_OFF); + + /* + * ACK pending timer interrupt. + */ + writel(TIMER0_CLR_MASK, local_base + LCL_TIMER_EVENTS_STATUS); + } +} + +static struct clock_event_device armada_370_xp_clkevt = { + .name = "armada_370_xp_per_cpu_tick", + .features = CLOCK_EVT_FEAT_ONESHOT | CLOCK_EVT_FEAT_PERIODIC, + .shift = 32, + .rating = 300, + .set_next_event = armada_370_xp_clkevt_next_event, + .set_mode = armada_370_xp_clkevt_mode, +}; + +static irqreturn_t armada_370_xp_timer_interrupt(int irq, void *dev_id) +{ + /* + * ACK timer interrupt and call event handler. + */ + struct clock_event_device *evt = *(struct clock_event_device **)dev_id; + + writel(TIMER0_CLR_MASK, local_base + LCL_TIMER_EVENTS_STATUS); + evt->event_handler(evt); + + return IRQ_HANDLED; +} + +/* + * Setup the local clock events for a CPU. + */ +static int __cpuinit armada_370_xp_timer_setup(struct clock_event_device *evt) +{ + u32 u; + int cpu = smp_processor_id(); + + /* Use existing clock_event for cpu 0 */ + if (!smp_processor_id()) + return 0; + + u = readl(local_base + TIMER_CTRL_OFF); + if (timer25Mhz) + writel(u | TIMER0_25MHZ, local_base + TIMER_CTRL_OFF); + else + writel(u & ~TIMER0_25MHZ, local_base + TIMER_CTRL_OFF); + + evt->name = armada_370_xp_clkevt.name; + evt->irq = armada_370_xp_clkevt.irq; + evt->features = armada_370_xp_clkevt.features; + evt->shift = armada_370_xp_clkevt.shift; + evt->rating = armada_370_xp_clkevt.rating, + evt->set_next_event = armada_370_xp_clkevt_next_event, + evt->set_mode = armada_370_xp_clkevt_mode, + evt->cpumask = cpumask_of(cpu); + + *__this_cpu_ptr(percpu_armada_370_xp_evt) = evt; + + clockevents_config_and_register(evt, timer_clk, 1, 0xfffffffe); + enable_percpu_irq(evt->irq, 0); + + return 0; +} + +static void armada_370_xp_timer_stop(struct clock_event_device *evt) +{ + evt->set_mode(CLOCK_EVT_MODE_UNUSED, evt); + disable_percpu_irq(evt->irq); +} + +static struct local_timer_ops armada_370_xp_local_timer_ops __cpuinitdata = { + .setup = armada_370_xp_timer_setup, + .stop = armada_370_xp_timer_stop, +}; + +void __init armada_370_xp_timer_init(void) +{ + u32 u; + struct device_node *np; + int res; + + np = of_find_compatible_node(NULL, NULL, "marvell,armada-370-xp-timer"); + timer_base = of_iomap(np, 0); + WARN_ON(!timer_base); + local_base = of_iomap(np, 1); + + if (of_find_property(np, "marvell,timer-25Mhz", NULL)) { + /* The fixed 25MHz timer is available so let's use it */ + u = readl(local_base + TIMER_CTRL_OFF); + writel(u | TIMER0_25MHZ, + local_base + TIMER_CTRL_OFF); + u = readl(timer_base + TIMER_CTRL_OFF); + writel(u | TIMER0_25MHZ, + timer_base + TIMER_CTRL_OFF); + timer_clk = 25000000; + } else { + unsigned long rate = 0; + struct clk *clk = of_clk_get(np, 0); + WARN_ON(IS_ERR(clk)); + rate = clk_get_rate(clk); + u = readl(local_base + TIMER_CTRL_OFF); + writel(u & ~(TIMER0_25MHZ), + local_base + TIMER_CTRL_OFF); + + u = readl(timer_base + TIMER_CTRL_OFF); + writel(u & ~(TIMER0_25MHZ), + timer_base + TIMER_CTRL_OFF); + + timer_clk = rate / TIMER_DIVIDER; + timer25Mhz = false; + } + + /* + * We use timer 0 as clocksource, and private(local) timer 0 + * for clockevents + */ + armada_370_xp_clkevt.irq = irq_of_parse_and_map(np, 4); + + ticks_per_jiffy = (timer_clk + HZ / 2) / HZ; + + /* + * Set scale and timer for sched_clock. + */ + setup_sched_clock(armada_370_xp_read_sched_clock, 32, timer_clk); + + /* + * Setup free-running clocksource timer (interrupts + * disabled). + */ + writel(0xffffffff, timer_base + TIMER0_VAL_OFF); + writel(0xffffffff, timer_base + TIMER0_RELOAD_OFF); + + u = readl(timer_base + TIMER_CTRL_OFF); + + writel((u | TIMER0_EN | TIMER0_RELOAD_EN | + TIMER0_DIV(TIMER_DIVIDER_SHIFT)), timer_base + TIMER_CTRL_OFF); + + clocksource_mmio_init(timer_base + TIMER0_VAL_OFF, + "armada_370_xp_clocksource", + timer_clk, 300, 32, clocksource_mmio_readl_down); + + /* Register the clockevent on the private timer of CPU 0 */ + armada_370_xp_clkevt.cpumask = cpumask_of(0); + clockevents_config_and_register(&armada_370_xp_clkevt, + timer_clk, 1, 0xfffffffe); + + percpu_armada_370_xp_evt = alloc_percpu(struct clock_event_device *); + + + /* + * Setup clockevent timer (interrupt-driven). + */ + *__this_cpu_ptr(percpu_armada_370_xp_evt) = &armada_370_xp_clkevt; + res = request_percpu_irq(armada_370_xp_clkevt.irq, + armada_370_xp_timer_interrupt, + armada_370_xp_clkevt.name, + percpu_armada_370_xp_evt); + if (!res) { + enable_percpu_irq(armada_370_xp_clkevt.irq, 0); +#ifdef CONFIG_LOCAL_TIMERS + local_timer_register(&armada_370_xp_local_timer_ops); +#endif + } +} diff --git a/drivers/clocksource/timer-marco.c b/drivers/clocksource/timer-marco.c new file mode 100644 index 000000000..97738dbf3 --- /dev/null +++ b/drivers/clocksource/timer-marco.c @@ -0,0 +1,299 @@ +/* + * System timer for CSR SiRFprimaII + * + * Copyright (c) 2011 Cambridge Silicon Radio Limited, a CSR plc group company. + * + * Licensed under GPLv2 or later. + */ + +#include <linux/kernel.h> +#include <linux/interrupt.h> +#include <linux/clockchips.h> +#include <linux/clocksource.h> +#include <linux/bitops.h> +#include <linux/irq.h> +#include <linux/clk.h> +#include <linux/slab.h> +#include <linux/of.h> +#include <linux/of_irq.h> +#include <linux/of_address.h> +#include <asm/sched_clock.h> +#include <asm/localtimer.h> +#include <asm/mach/time.h> + +#define SIRFSOC_TIMER_32COUNTER_0_CTRL 0x0000 +#define SIRFSOC_TIMER_32COUNTER_1_CTRL 0x0004 +#define SIRFSOC_TIMER_MATCH_0 0x0018 +#define SIRFSOC_TIMER_MATCH_1 0x001c +#define SIRFSOC_TIMER_COUNTER_0 0x0048 +#define SIRFSOC_TIMER_COUNTER_1 0x004c +#define SIRFSOC_TIMER_INTR_STATUS 0x0060 +#define SIRFSOC_TIMER_WATCHDOG_EN 0x0064 +#define SIRFSOC_TIMER_64COUNTER_CTRL 0x0068 +#define SIRFSOC_TIMER_64COUNTER_LO 0x006c +#define SIRFSOC_TIMER_64COUNTER_HI 0x0070 +#define SIRFSOC_TIMER_64COUNTER_LOAD_LO 0x0074 +#define SIRFSOC_TIMER_64COUNTER_LOAD_HI 0x0078 +#define SIRFSOC_TIMER_64COUNTER_RLATCHED_LO 0x007c +#define SIRFSOC_TIMER_64COUNTER_RLATCHED_HI 0x0080 + +#define SIRFSOC_TIMER_REG_CNT 6 + +static const u32 sirfsoc_timer_reg_list[SIRFSOC_TIMER_REG_CNT] = { + SIRFSOC_TIMER_WATCHDOG_EN, + SIRFSOC_TIMER_32COUNTER_0_CTRL, + SIRFSOC_TIMER_32COUNTER_1_CTRL, + SIRFSOC_TIMER_64COUNTER_CTRL, + SIRFSOC_TIMER_64COUNTER_RLATCHED_LO, + SIRFSOC_TIMER_64COUNTER_RLATCHED_HI, +}; + +static u32 sirfsoc_timer_reg_val[SIRFSOC_TIMER_REG_CNT]; + +static void __iomem *sirfsoc_timer_base; + +/* disable count and interrupt */ +static inline void sirfsoc_timer_count_disable(int idx) +{ + writel_relaxed(readl_relaxed(sirfsoc_timer_base + SIRFSOC_TIMER_32COUNTER_0_CTRL + 4 * idx) & ~0x7, + sirfsoc_timer_base + SIRFSOC_TIMER_32COUNTER_0_CTRL + 4 * idx); +} + +/* enable count and interrupt */ +static inline void sirfsoc_timer_count_enable(int idx) +{ + writel_relaxed(readl_relaxed(sirfsoc_timer_base + SIRFSOC_TIMER_32COUNTER_0_CTRL + 4 * idx) | 0x7, + sirfsoc_timer_base + SIRFSOC_TIMER_32COUNTER_0_CTRL + 4 * idx); +} + +/* timer interrupt handler */ +static irqreturn_t sirfsoc_timer_interrupt(int irq, void *dev_id) +{ + struct clock_event_device *ce = dev_id; + int cpu = smp_processor_id(); + + /* clear timer interrupt */ + writel_relaxed(BIT(cpu), sirfsoc_timer_base + SIRFSOC_TIMER_INTR_STATUS); + + if (ce->mode == CLOCK_EVT_MODE_ONESHOT) + sirfsoc_timer_count_disable(cpu); + + ce->event_handler(ce); + + return IRQ_HANDLED; +} + +/* read 64-bit timer counter */ +static cycle_t sirfsoc_timer_read(struct clocksource *cs) +{ + u64 cycles; + + writel_relaxed((readl_relaxed(sirfsoc_timer_base + SIRFSOC_TIMER_64COUNTER_CTRL) | + BIT(0)) & ~BIT(1), sirfsoc_timer_base + SIRFSOC_TIMER_64COUNTER_CTRL); + + cycles = readl_relaxed(sirfsoc_timer_base + SIRFSOC_TIMER_64COUNTER_RLATCHED_HI); + cycles = (cycles << 32) | readl_relaxed(sirfsoc_timer_base + SIRFSOC_TIMER_64COUNTER_RLATCHED_LO); + + return cycles; +} + +static int sirfsoc_timer_set_next_event(unsigned long delta, + struct clock_event_device *ce) +{ + int cpu = smp_processor_id(); + + writel_relaxed(0, sirfsoc_timer_base + SIRFSOC_TIMER_COUNTER_0 + + 4 * cpu); + writel_relaxed(delta, sirfsoc_timer_base + SIRFSOC_TIMER_MATCH_0 + + 4 * cpu); + + /* enable the tick */ + sirfsoc_timer_count_enable(cpu); + + return 0; +} + +static void sirfsoc_timer_set_mode(enum clock_event_mode mode, + struct clock_event_device *ce) +{ + switch (mode) { + case CLOCK_EVT_MODE_ONESHOT: + /* enable in set_next_event */ + break; + default: + break; + } + + sirfsoc_timer_count_disable(smp_processor_id()); +} + +static void sirfsoc_clocksource_suspend(struct clocksource *cs) +{ + int i; + + for (i = 0; i < SIRFSOC_TIMER_REG_CNT; i++) + sirfsoc_timer_reg_val[i] = readl_relaxed(sirfsoc_timer_base + sirfsoc_timer_reg_list[i]); +} + +static void sirfsoc_clocksource_resume(struct clocksource *cs) +{ + int i; + + for (i = 0; i < SIRFSOC_TIMER_REG_CNT - 2; i++) + writel_relaxed(sirfsoc_timer_reg_val[i], sirfsoc_timer_base + sirfsoc_timer_reg_list[i]); + + writel_relaxed(sirfsoc_timer_reg_val[SIRFSOC_TIMER_REG_CNT - 2], + sirfsoc_timer_base + SIRFSOC_TIMER_64COUNTER_LOAD_LO); + writel_relaxed(sirfsoc_timer_reg_val[SIRFSOC_TIMER_REG_CNT - 1], + sirfsoc_timer_base + SIRFSOC_TIMER_64COUNTER_LOAD_HI); + + writel_relaxed(readl_relaxed(sirfsoc_timer_base + SIRFSOC_TIMER_64COUNTER_CTRL) | + BIT(1) | BIT(0), sirfsoc_timer_base + SIRFSOC_TIMER_64COUNTER_CTRL); +} + +static struct clock_event_device sirfsoc_clockevent = { + .name = "sirfsoc_clockevent", + .rating = 200, + .features = CLOCK_EVT_FEAT_ONESHOT, + .set_mode = sirfsoc_timer_set_mode, + .set_next_event = sirfsoc_timer_set_next_event, +}; + +static struct clocksource sirfsoc_clocksource = { + .name = "sirfsoc_clocksource", + .rating = 200, + .mask = CLOCKSOURCE_MASK(64), + .flags = CLOCK_SOURCE_IS_CONTINUOUS, + .read = sirfsoc_timer_read, + .suspend = sirfsoc_clocksource_suspend, + .resume = sirfsoc_clocksource_resume, +}; + +static struct irqaction sirfsoc_timer_irq = { + .name = "sirfsoc_timer0", + .flags = IRQF_TIMER | IRQF_NOBALANCING, + .handler = sirfsoc_timer_interrupt, + .dev_id = &sirfsoc_clockevent, +}; + +#ifdef CONFIG_LOCAL_TIMERS + +static struct irqaction sirfsoc_timer1_irq = { + .name = "sirfsoc_timer1", + .flags = IRQF_TIMER | IRQF_NOBALANCING, + .handler = sirfsoc_timer_interrupt, +}; + +static int __cpuinit sirfsoc_local_timer_setup(struct clock_event_device *ce) +{ + /* Use existing clock_event for cpu 0 */ + if (!smp_processor_id()) + return 0; + + ce->irq = sirfsoc_timer1_irq.irq; + ce->name = "local_timer"; + ce->features = sirfsoc_clockevent.features; + ce->rating = sirfsoc_clockevent.rating; + ce->set_mode = sirfsoc_timer_set_mode; + ce->set_next_event = sirfsoc_timer_set_next_event; + ce->shift = sirfsoc_clockevent.shift; + ce->mult = sirfsoc_clockevent.mult; + ce->max_delta_ns = sirfsoc_clockevent.max_delta_ns; + ce->min_delta_ns = sirfsoc_clockevent.min_delta_ns; + + sirfsoc_timer1_irq.dev_id = ce; + BUG_ON(setup_irq(ce->irq, &sirfsoc_timer1_irq)); + irq_set_affinity(sirfsoc_timer1_irq.irq, cpumask_of(1)); + + clockevents_register_device(ce); + return 0; +} + +static void sirfsoc_local_timer_stop(struct clock_event_device *ce) +{ + sirfsoc_timer_count_disable(1); + + remove_irq(sirfsoc_timer1_irq.irq, &sirfsoc_timer1_irq); +} + +static struct local_timer_ops sirfsoc_local_timer_ops __cpuinitdata = { + .setup = sirfsoc_local_timer_setup, + .stop = sirfsoc_local_timer_stop, +}; +#endif /* CONFIG_LOCAL_TIMERS */ + +static void __init sirfsoc_clockevent_init(void) +{ + clockevents_calc_mult_shift(&sirfsoc_clockevent, CLOCK_TICK_RATE, 60); + + sirfsoc_clockevent.max_delta_ns = + clockevent_delta2ns(-2, &sirfsoc_clockevent); + sirfsoc_clockevent.min_delta_ns = + clockevent_delta2ns(2, &sirfsoc_clockevent); + + sirfsoc_clockevent.cpumask = cpumask_of(0); + clockevents_register_device(&sirfsoc_clockevent); +#ifdef CONFIG_LOCAL_TIMERS + local_timer_register(&sirfsoc_local_timer_ops); +#endif +} + +/* initialize the kernel jiffy timer source */ +static void __init sirfsoc_marco_timer_init(void) +{ + unsigned long rate; + u32 timer_div; + struct clk *clk; + + /* timer's input clock is io clock */ + clk = clk_get_sys("io", NULL); + + BUG_ON(IS_ERR(clk)); + rate = clk_get_rate(clk); + + BUG_ON(rate < CLOCK_TICK_RATE); + BUG_ON(rate % CLOCK_TICK_RATE); + + /* Initialize the timer dividers */ + timer_div = rate / CLOCK_TICK_RATE - 1; + writel_relaxed(timer_div << 16, sirfsoc_timer_base + SIRFSOC_TIMER_64COUNTER_CTRL); + writel_relaxed(timer_div << 16, sirfsoc_timer_base + SIRFSOC_TIMER_32COUNTER_0_CTRL); + writel_relaxed(timer_div << 16, sirfsoc_timer_base + SIRFSOC_TIMER_32COUNTER_1_CTRL); + + /* Initialize timer counters to 0 */ + writel_relaxed(0, sirfsoc_timer_base + SIRFSOC_TIMER_64COUNTER_LOAD_LO); + writel_relaxed(0, sirfsoc_timer_base + SIRFSOC_TIMER_64COUNTER_LOAD_HI); + writel_relaxed(readl_relaxed(sirfsoc_timer_base + SIRFSOC_TIMER_64COUNTER_CTRL) | + BIT(1) | BIT(0), sirfsoc_timer_base + SIRFSOC_TIMER_64COUNTER_CTRL); + writel_relaxed(0, sirfsoc_timer_base + SIRFSOC_TIMER_COUNTER_0); + writel_relaxed(0, sirfsoc_timer_base + SIRFSOC_TIMER_COUNTER_1); + + /* Clear all interrupts */ + writel_relaxed(0xFFFF, sirfsoc_timer_base + SIRFSOC_TIMER_INTR_STATUS); + + BUG_ON(clocksource_register_hz(&sirfsoc_clocksource, CLOCK_TICK_RATE)); + + BUG_ON(setup_irq(sirfsoc_timer_irq.irq, &sirfsoc_timer_irq)); + + sirfsoc_clockevent_init(); +} + +static void __init sirfsoc_of_timer_init(struct device_node *np) +{ + sirfsoc_timer_base = of_iomap(np, 0); + if (!sirfsoc_timer_base) + panic("unable to map timer cpu registers\n"); + + sirfsoc_timer_irq.irq = irq_of_parse_and_map(np, 0); + if (!sirfsoc_timer_irq.irq) + panic("No irq passed for timer0 via DT\n"); + +#ifdef CONFIG_LOCAL_TIMERS + sirfsoc_timer1_irq.irq = irq_of_parse_and_map(np, 1); + if (!sirfsoc_timer1_irq.irq) + panic("No irq passed for timer1 via DT\n"); +#endif + + sirfsoc_marco_timer_init(); +} +CLOCKSOURCE_OF_DECLARE(sirfsoc_marco_timer, "sirf,marco-tick", sirfsoc_of_timer_init ); diff --git a/drivers/clocksource/timer-prima2.c b/drivers/clocksource/timer-prima2.c new file mode 100644 index 000000000..760882665 --- /dev/null +++ b/drivers/clocksource/timer-prima2.c @@ -0,0 +1,215 @@ +/* + * System timer for CSR SiRFprimaII + * + * Copyright (c) 2011 Cambridge Silicon Radio Limited, a CSR plc group company. + * + * Licensed under GPLv2 or later. + */ + +#include <linux/kernel.h> +#include <linux/interrupt.h> +#include <linux/clockchips.h> +#include <linux/clocksource.h> +#include <linux/bitops.h> +#include <linux/irq.h> +#include <linux/clk.h> +#include <linux/err.h> +#include <linux/slab.h> +#include <linux/of.h> +#include <linux/of_irq.h> +#include <linux/of_address.h> +#include <asm/sched_clock.h> +#include <asm/mach/time.h> + +#define SIRFSOC_TIMER_COUNTER_LO 0x0000 +#define SIRFSOC_TIMER_COUNTER_HI 0x0004 +#define SIRFSOC_TIMER_MATCH_0 0x0008 +#define SIRFSOC_TIMER_MATCH_1 0x000C +#define SIRFSOC_TIMER_MATCH_2 0x0010 +#define SIRFSOC_TIMER_MATCH_3 0x0014 +#define SIRFSOC_TIMER_MATCH_4 0x0018 +#define SIRFSOC_TIMER_MATCH_5 0x001C +#define SIRFSOC_TIMER_STATUS 0x0020 +#define SIRFSOC_TIMER_INT_EN 0x0024 +#define SIRFSOC_TIMER_WATCHDOG_EN 0x0028 +#define SIRFSOC_TIMER_DIV 0x002C +#define SIRFSOC_TIMER_LATCH 0x0030 +#define SIRFSOC_TIMER_LATCHED_LO 0x0034 +#define SIRFSOC_TIMER_LATCHED_HI 0x0038 + +#define SIRFSOC_TIMER_WDT_INDEX 5 + +#define SIRFSOC_TIMER_LATCH_BIT BIT(0) + +#define SIRFSOC_TIMER_REG_CNT 11 + +static const u32 sirfsoc_timer_reg_list[SIRFSOC_TIMER_REG_CNT] = { + SIRFSOC_TIMER_MATCH_0, SIRFSOC_TIMER_MATCH_1, SIRFSOC_TIMER_MATCH_2, + SIRFSOC_TIMER_MATCH_3, SIRFSOC_TIMER_MATCH_4, SIRFSOC_TIMER_MATCH_5, + SIRFSOC_TIMER_INT_EN, SIRFSOC_TIMER_WATCHDOG_EN, SIRFSOC_TIMER_DIV, + SIRFSOC_TIMER_LATCHED_LO, SIRFSOC_TIMER_LATCHED_HI, +}; + +static u32 sirfsoc_timer_reg_val[SIRFSOC_TIMER_REG_CNT]; + +static void __iomem *sirfsoc_timer_base; + +/* timer0 interrupt handler */ +static irqreturn_t sirfsoc_timer_interrupt(int irq, void *dev_id) +{ + struct clock_event_device *ce = dev_id; + + WARN_ON(!(readl_relaxed(sirfsoc_timer_base + SIRFSOC_TIMER_STATUS) & BIT(0))); + + /* clear timer0 interrupt */ + writel_relaxed(BIT(0), sirfsoc_timer_base + SIRFSOC_TIMER_STATUS); + + ce->event_handler(ce); + + return IRQ_HANDLED; +} + +/* read 64-bit timer counter */ +static cycle_t sirfsoc_timer_read(struct clocksource *cs) +{ + u64 cycles; + + /* latch the 64-bit timer counter */ + writel_relaxed(SIRFSOC_TIMER_LATCH_BIT, sirfsoc_timer_base + SIRFSOC_TIMER_LATCH); + cycles = readl_relaxed(sirfsoc_timer_base + SIRFSOC_TIMER_LATCHED_HI); + cycles = (cycles << 32) | readl_relaxed(sirfsoc_timer_base + SIRFSOC_TIMER_LATCHED_LO); + + return cycles; +} + +static int sirfsoc_timer_set_next_event(unsigned long delta, + struct clock_event_device *ce) +{ + unsigned long now, next; + + writel_relaxed(SIRFSOC_TIMER_LATCH_BIT, sirfsoc_timer_base + SIRFSOC_TIMER_LATCH); + now = readl_relaxed(sirfsoc_timer_base + SIRFSOC_TIMER_LATCHED_LO); + next = now + delta; + writel_relaxed(next, sirfsoc_timer_base + SIRFSOC_TIMER_MATCH_0); + writel_relaxed(SIRFSOC_TIMER_LATCH_BIT, sirfsoc_timer_base + SIRFSOC_TIMER_LATCH); + now = readl_relaxed(sirfsoc_timer_base + SIRFSOC_TIMER_LATCHED_LO); + + return next - now > delta ? -ETIME : 0; +} + +static void sirfsoc_timer_set_mode(enum clock_event_mode mode, + struct clock_event_device *ce) +{ + u32 val = readl_relaxed(sirfsoc_timer_base + SIRFSOC_TIMER_INT_EN); + switch (mode) { + case CLOCK_EVT_MODE_PERIODIC: + WARN_ON(1); + break; + case CLOCK_EVT_MODE_ONESHOT: + writel_relaxed(val | BIT(0), sirfsoc_timer_base + SIRFSOC_TIMER_INT_EN); + break; + case CLOCK_EVT_MODE_SHUTDOWN: + writel_relaxed(val & ~BIT(0), sirfsoc_timer_base + SIRFSOC_TIMER_INT_EN); + break; + case CLOCK_EVT_MODE_UNUSED: + case CLOCK_EVT_MODE_RESUME: + break; + } +} + +static void sirfsoc_clocksource_suspend(struct clocksource *cs) +{ + int i; + + writel_relaxed(SIRFSOC_TIMER_LATCH_BIT, sirfsoc_timer_base + SIRFSOC_TIMER_LATCH); + + for (i = 0; i < SIRFSOC_TIMER_REG_CNT; i++) + sirfsoc_timer_reg_val[i] = readl_relaxed(sirfsoc_timer_base + sirfsoc_timer_reg_list[i]); +} + +static void sirfsoc_clocksource_resume(struct clocksource *cs) +{ + int i; + + for (i = 0; i < SIRFSOC_TIMER_REG_CNT - 2; i++) + writel_relaxed(sirfsoc_timer_reg_val[i], sirfsoc_timer_base + sirfsoc_timer_reg_list[i]); + + writel_relaxed(sirfsoc_timer_reg_val[SIRFSOC_TIMER_REG_CNT - 2], sirfsoc_timer_base + SIRFSOC_TIMER_COUNTER_LO); + writel_relaxed(sirfsoc_timer_reg_val[SIRFSOC_TIMER_REG_CNT - 1], sirfsoc_timer_base + SIRFSOC_TIMER_COUNTER_HI); +} + +static struct clock_event_device sirfsoc_clockevent = { + .name = "sirfsoc_clockevent", + .rating = 200, + .features = CLOCK_EVT_FEAT_ONESHOT, + .set_mode = sirfsoc_timer_set_mode, + .set_next_event = sirfsoc_timer_set_next_event, +}; + +static struct clocksource sirfsoc_clocksource = { + .name = "sirfsoc_clocksource", + .rating = 200, + .mask = CLOCKSOURCE_MASK(64), + .flags = CLOCK_SOURCE_IS_CONTINUOUS, + .read = sirfsoc_timer_read, + .suspend = sirfsoc_clocksource_suspend, + .resume = sirfsoc_clocksource_resume, +}; + +static struct irqaction sirfsoc_timer_irq = { + .name = "sirfsoc_timer0", + .flags = IRQF_TIMER, + .irq = 0, + .handler = sirfsoc_timer_interrupt, + .dev_id = &sirfsoc_clockevent, +}; + +/* Overwrite weak default sched_clock with more precise one */ +static u32 notrace sirfsoc_read_sched_clock(void) +{ + return (u32)(sirfsoc_timer_read(NULL) & 0xffffffff); +} + +static void __init sirfsoc_clockevent_init(void) +{ + sirfsoc_clockevent.cpumask = cpumask_of(0); + clockevents_config_and_register(&sirfsoc_clockevent, CLOCK_TICK_RATE, + 2, -2); +} + +/* initialize the kernel jiffy timer source */ +static void __init sirfsoc_prima2_timer_init(struct device_node *np) +{ + unsigned long rate; + struct clk *clk; + + /* timer's input clock is io clock */ + clk = clk_get_sys("io", NULL); + + BUG_ON(IS_ERR(clk)); + + rate = clk_get_rate(clk); + + BUG_ON(rate < CLOCK_TICK_RATE); + BUG_ON(rate % CLOCK_TICK_RATE); + + sirfsoc_timer_base = of_iomap(np, 0); + if (!sirfsoc_timer_base) + panic("unable to map timer cpu registers\n"); + + sirfsoc_timer_irq.irq = irq_of_parse_and_map(np, 0); + + writel_relaxed(rate / CLOCK_TICK_RATE / 2 - 1, sirfsoc_timer_base + SIRFSOC_TIMER_DIV); + writel_relaxed(0, sirfsoc_timer_base + SIRFSOC_TIMER_COUNTER_LO); + writel_relaxed(0, sirfsoc_timer_base + SIRFSOC_TIMER_COUNTER_HI); + writel_relaxed(BIT(0), sirfsoc_timer_base + SIRFSOC_TIMER_STATUS); + + BUG_ON(clocksource_register_hz(&sirfsoc_clocksource, CLOCK_TICK_RATE)); + + setup_sched_clock(sirfsoc_read_sched_clock, 32, CLOCK_TICK_RATE); + + BUG_ON(setup_irq(sirfsoc_timer_irq.irq, &sirfsoc_timer_irq)); + + sirfsoc_clockevent_init(); +} +CLOCKSOURCE_OF_DECLARE(sirfsoc_prima2_timer, "sirf,prima2-tick", sirfsoc_prima2_timer_init); diff --git a/drivers/clocksource/vt8500_timer.c b/drivers/clocksource/vt8500_timer.c new file mode 100644 index 000000000..64f553f04 --- /dev/null +++ b/drivers/clocksource/vt8500_timer.c @@ -0,0 +1,168 @@ +/* + * arch/arm/mach-vt8500/timer.c + * + * Copyright (C) 2012 Tony Prisk <linux@prisktech.co.nz> + * Copyright (C) 2010 Alexey Charkov <alchark@gmail.com> + * + * 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 + */ + +/* + * This file is copied and modified from the original timer.c provided by + * Alexey Charkov. Minor changes have been made for Device Tree Support. + */ + +#include <linux/io.h> +#include <linux/irq.h> +#include <linux/interrupt.h> +#include <linux/clocksource.h> +#include <linux/clockchips.h> +#include <linux/delay.h> +#include <asm/mach/time.h> + +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/of_irq.h> + +#define VT8500_TIMER_OFFSET 0x0100 +#define VT8500_TIMER_HZ 3000000 +#define TIMER_MATCH_VAL 0x0000 +#define TIMER_COUNT_VAL 0x0010 +#define TIMER_STATUS_VAL 0x0014 +#define TIMER_IER_VAL 0x001c /* interrupt enable */ +#define TIMER_CTRL_VAL 0x0020 +#define TIMER_AS_VAL 0x0024 /* access status */ +#define TIMER_COUNT_R_ACTIVE (1 << 5) /* not ready for read */ +#define TIMER_COUNT_W_ACTIVE (1 << 4) /* not ready for write */ +#define TIMER_MATCH_W_ACTIVE (1 << 0) /* not ready for write */ + +#define msecs_to_loops(t) (loops_per_jiffy / 1000 * HZ * t) + +static void __iomem *regbase; + +static cycle_t vt8500_timer_read(struct clocksource *cs) +{ + int loops = msecs_to_loops(10); + writel(3, regbase + TIMER_CTRL_VAL); + while ((readl((regbase + TIMER_AS_VAL)) & TIMER_COUNT_R_ACTIVE) + && --loops) + cpu_relax(); + return readl(regbase + TIMER_COUNT_VAL); +} + +static struct clocksource clocksource = { + .name = "vt8500_timer", + .rating = 200, + .read = vt8500_timer_read, + .mask = CLOCKSOURCE_MASK(32), + .flags = CLOCK_SOURCE_IS_CONTINUOUS, +}; + +static int vt8500_timer_set_next_event(unsigned long cycles, + struct clock_event_device *evt) +{ + int loops = msecs_to_loops(10); + cycle_t alarm = clocksource.read(&clocksource) + cycles; + while ((readl(regbase + TIMER_AS_VAL) & TIMER_MATCH_W_ACTIVE) + && --loops) + cpu_relax(); + writel((unsigned long)alarm, regbase + TIMER_MATCH_VAL); + + if ((signed)(alarm - clocksource.read(&clocksource)) <= 16) + return -ETIME; + + writel(1, regbase + TIMER_IER_VAL); + + return 0; +} + +static void vt8500_timer_set_mode(enum clock_event_mode mode, + struct clock_event_device *evt) +{ + switch (mode) { + case CLOCK_EVT_MODE_RESUME: + case CLOCK_EVT_MODE_PERIODIC: + break; + case CLOCK_EVT_MODE_ONESHOT: + case CLOCK_EVT_MODE_UNUSED: + case CLOCK_EVT_MODE_SHUTDOWN: + writel(readl(regbase + TIMER_CTRL_VAL) | 1, + regbase + TIMER_CTRL_VAL); + writel(0, regbase + TIMER_IER_VAL); + break; + } +} + +static struct clock_event_device clockevent = { + .name = "vt8500_timer", + .features = CLOCK_EVT_FEAT_ONESHOT, + .rating = 200, + .set_next_event = vt8500_timer_set_next_event, + .set_mode = vt8500_timer_set_mode, +}; + +static irqreturn_t vt8500_timer_interrupt(int irq, void *dev_id) +{ + struct clock_event_device *evt = dev_id; + writel(0xf, regbase + TIMER_STATUS_VAL); + evt->event_handler(evt); + + return IRQ_HANDLED; +} + +static struct irqaction irq = { + .name = "vt8500_timer", + .flags = IRQF_DISABLED | IRQF_TIMER | IRQF_IRQPOLL, + .handler = vt8500_timer_interrupt, + .dev_id = &clockevent, +}; + +static void __init vt8500_timer_init(struct device_node *np) +{ + int timer_irq; + + regbase = of_iomap(np, 0); + if (!regbase) { + pr_err("%s: Missing iobase description in Device Tree\n", + __func__); + of_node_put(np); + return; + } + timer_irq = irq_of_parse_and_map(np, 0); + if (!timer_irq) { + pr_err("%s: Missing irq description in Device Tree\n", + __func__); + of_node_put(np); + return; + } + + writel(1, regbase + TIMER_CTRL_VAL); + writel(0xf, regbase + TIMER_STATUS_VAL); + writel(~0, regbase + TIMER_MATCH_VAL); + + if (clocksource_register_hz(&clocksource, VT8500_TIMER_HZ)) + pr_err("%s: vt8500_timer_init: clocksource_register failed for %s\n", + __func__, clocksource.name); + + clockevent.cpumask = cpumask_of(0); + + if (setup_irq(timer_irq, &irq)) + pr_err("%s: setup_irq failed for %s\n", __func__, + clockevent.name); + clockevents_config_and_register(&clockevent, VT8500_TIMER_HZ, + 4, 0xf0000000); +} + +CLOCKSOURCE_OF_DECLARE(vt8500, "via,vt8500-timer", vt8500_timer_init); |
