diff options
author | Linus Torvalds | 2020-01-27 16:47:05 -0800 |
---|---|---|
committer | Linus Torvalds | 2020-01-27 16:47:05 -0800 |
commit | e279160f491392f1345f6eb4b0eeec5a6a2ecdd7 (patch) | |
tree | 018410c41f919774f70e6350fc251556bf789944 /drivers/clocksource | |
parent | 534b0a8b677443c0aa8c4c71ff7887f08a2b9b41 (diff) | |
parent | fd928f3e32ba09381b287f8b732418434d932855 (diff) |
Merge tag 'timers-core-2020-01-27' of git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip
Pull timer updates from Thomas Gleixner:
"The timekeeping and timers departement provides:
- Time namespace support:
If a container migrates from one host to another then it expects
that clocks based on MONOTONIC and BOOTTIME are not subject to
disruption. Due to different boot time and non-suspended runtime
these clocks can differ significantly on two hosts, in the worst
case time goes backwards which is a violation of the POSIX
requirements.
The time namespace addresses this problem. It allows to set offsets
for clock MONOTONIC and BOOTTIME once after creation and before
tasks are associated with the namespace. These offsets are taken
into account by timers and timekeeping including the VDSO.
Offsets for wall clock based clocks (REALTIME/TAI) are not provided
by this mechanism. While in theory possible, the overhead and code
complexity would be immense and not justified by the esoteric
potential use cases which were discussed at Plumbers '18.
The overhead for tasks in the root namespace (ie where host time
offsets = 0) is in the noise and great effort was made to ensure
that especially in the VDSO. If time namespace is disabled in the
kernel configuration the code is compiled out.
Kudos to Andrei Vagin and Dmitry Sofanov who implemented this
feature and kept on for more than a year addressing review
comments, finding better solutions. A pleasant experience.
- Overhaul of the alarmtimer device dependency handling to ensure
that the init/suspend/resume ordering is correct.
- A new clocksource/event driver for Microchip PIT64
- Suspend/resume support for the Hyper-V clocksource
- The usual pile of fixes, updates and improvements mostly in the
driver code"
* tag 'timers-core-2020-01-27' of git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip: (71 commits)
alarmtimer: Make alarmtimer_get_rtcdev() a stub when CONFIG_RTC_CLASS=n
alarmtimer: Use wakeup source from alarmtimer platform device
alarmtimer: Make alarmtimer platform device child of RTC device
alarmtimer: Update alarmtimer_get_rtcdev() docs to reflect reality
hrtimer: Add missing sparse annotation for __run_timer()
lib/vdso: Only read hrtimer_res when needed in __cvdso_clock_getres()
MIPS: vdso: Define BUILD_VDSO32 when building a 32bit kernel
clocksource/drivers/hyper-v: Set TSC clocksource as default w/ InvariantTSC
clocksource/drivers/hyper-v: Untangle stimers and timesync from clocksources
clocksource/drivers/timer-microchip-pit64b: Fix sparse warning
clocksource/drivers/exynos_mct: Rename Exynos to lowercase
clocksource/drivers/timer-ti-dm: Fix uninitialized pointer access
clocksource/drivers/timer-ti-dm: Switch to platform_get_irq
clocksource/drivers/timer-ti-dm: Convert to devm_platform_ioremap_resource
clocksource/drivers/em_sti: Fix variable declaration in em_sti_probe
clocksource/drivers/em_sti: Convert to devm_platform_ioremap_resource
clocksource/drivers/bcm2835_timer: Fix memory leak of timer
clocksource/drivers/cadence-ttc: Use ttc driver as platform driver
clocksource/drivers/timer-microchip-pit64b: Add Microchip PIT64B support
clocksource/drivers/hyper-v: Reserve PAGE_SIZE space for tsc page
...
Diffstat (limited to 'drivers/clocksource')
-rw-r--r-- | drivers/clocksource/Kconfig | 76 | ||||
-rw-r--r-- | drivers/clocksource/Makefile | 1 | ||||
-rw-r--r-- | drivers/clocksource/bcm2835_timer.c | 5 | ||||
-rw-r--r-- | drivers/clocksource/em_sti.c | 7 | ||||
-rw-r--r-- | drivers/clocksource/exynos_mct.c | 2 | ||||
-rw-r--r-- | drivers/clocksource/hyperv_timer.c | 84 | ||||
-rw-r--r-- | drivers/clocksource/timer-cadence-ttc.c | 26 | ||||
-rw-r--r-- | drivers/clocksource/timer-microchip-pit64b.c | 451 | ||||
-rw-r--r-- | drivers/clocksource/timer-ti-dm.c | 20 |
9 files changed, 590 insertions, 82 deletions
diff --git a/drivers/clocksource/Kconfig b/drivers/clocksource/Kconfig index 5fdd76cb1768..cc909e465823 100644 --- a/drivers/clocksource/Kconfig +++ b/drivers/clocksource/Kconfig @@ -88,7 +88,7 @@ config ROCKCHIP_TIMER select TIMER_OF select CLKSRC_MMIO help - Enables the support for the rockchip timer driver. + Enables the support for the Rockchip timer driver. config ARMADA_370_XP_TIMER bool "Armada 370 and XP timer driver" if COMPILE_TEST @@ -162,13 +162,13 @@ config NPCM7XX_TIMER select CLKSRC_MMIO help Enable 24-bit TIMER0 and TIMER1 counters in the NPCM7xx architecture, - While TIMER0 serves as clockevent and TIMER1 serves as clocksource. + where TIMER0 serves as clockevent and TIMER1 serves as clocksource. config CADENCE_TTC_TIMER bool "Cadence TTC timer driver" if COMPILE_TEST depends on COMMON_CLK help - Enables support for the cadence ttc driver. + Enables support for the Cadence TTC driver. config ASM9260_TIMER bool "ASM9260 timer driver" if COMPILE_TEST @@ -190,10 +190,10 @@ config CLKSRC_DBX500_PRCMU bool "Clocksource PRCMU Timer" if COMPILE_TEST depends on HAS_IOMEM help - Use the always on PRCMU Timer as clocksource + Use the always on PRCMU Timer as clocksource. config CLPS711X_TIMER - bool "Cirrus logic timer driver" if COMPILE_TEST + bool "Cirrus Logic timer driver" if COMPILE_TEST select CLKSRC_MMIO help Enables support for the Cirrus Logic PS711 timer. @@ -205,11 +205,11 @@ config ATLAS7_TIMER Enables support for the Atlas7 timer. config MXS_TIMER - bool "Mxs timer driver" if COMPILE_TEST + bool "MXS timer driver" if COMPILE_TEST select CLKSRC_MMIO select STMP_DEVICE help - Enables support for the Mxs timer. + Enables support for the MXS timer. config PRIMA2_TIMER bool "Prima2 timer driver" if COMPILE_TEST @@ -238,10 +238,10 @@ config KEYSTONE_TIMER Enables support for the Keystone timer. config INTEGRATOR_AP_TIMER - bool "Integrator-ap timer driver" if COMPILE_TEST + bool "Integrator-AP timer driver" if COMPILE_TEST select CLKSRC_MMIO help - Enables support for the Integrator-ap timer. + Enables support for the Integrator-AP timer. config CLKSRC_EFM32 bool "Clocksource for Energy Micro's EFM32 SoCs" if !ARCH_EFM32 @@ -283,8 +283,8 @@ config CLKSRC_NPS select TIMER_OF if OF help NPS400 clocksource support. - Got 64 bit counter with update rate up to 1000MHz. - This counter is accessed via couple of 32 bit memory mapped registers. + It has a 64-bit counter with update rate up to 1000MHz. + This counter is accessed via couple of 32-bit memory-mapped registers. config CLKSRC_STM32 bool "Clocksource for STM32 SoCs" if !ARCH_STM32 @@ -305,14 +305,14 @@ config ARC_TIMERS help These are legacy 32-bit TIMER0 and TIMER1 counters found on all ARC cores (ARC700 as well as ARC HS38). - TIMER0 serves as clockevent while TIMER1 provides clocksource + TIMER0 serves as clockevent while TIMER1 provides clocksource. config ARC_TIMERS_64BIT bool "Support for 64-bit counters in ARC HS38 cores" if COMPILE_TEST depends on ARC_TIMERS select TIMER_OF help - This enables 2 different 64-bit timers: RTC (for UP) and GFRC (for SMP) + This enables 2 different 64-bit timers: RTC (for UP) and GFRC (for SMP). RTC is implemented inside the core, while GFRC sits outside the core in ARConnect IP block. Driver automatically picks one of them for clocksource as appropriate. @@ -390,7 +390,7 @@ config ARM_GLOBAL_TIMER select TIMER_OF if OF depends on ARM help - This options enables support for the ARM global timer unit + This option enables support for the ARM global timer unit. config ARM_TIMER_SP804 bool "Support for Dual Timer SP804 module" if COMPILE_TEST @@ -403,14 +403,14 @@ config CLKSRC_ARM_GLOBAL_TIMER_SCHED_CLOCK depends on ARM_GLOBAL_TIMER default y help - Use ARM global timer clock source as sched_clock + Use ARM global timer clock source as sched_clock. config ARMV7M_SYSTICK bool "Support for the ARMv7M system time" if COMPILE_TEST select TIMER_OF if OF select CLKSRC_MMIO help - This options enables support for the ARMv7M system timer unit + This option enables support for the ARMv7M system timer unit. config ATMEL_PIT bool "Atmel PIT support" if COMPILE_TEST @@ -460,7 +460,7 @@ config VF_PIT_TIMER bool select CLKSRC_MMIO help - Support for Period Interrupt Timer on Freescale Vybrid Family SoCs. + Support for Periodic Interrupt Timer on Freescale Vybrid Family SoCs. config OXNAS_RPS_TIMER bool "Oxford Semiconductor OXNAS RPS Timers driver" if COMPILE_TEST @@ -470,7 +470,7 @@ config OXNAS_RPS_TIMER This enables support for the Oxford Semiconductor OXNAS RPS timers. config SYS_SUPPORTS_SH_CMT - bool + bool config MTK_TIMER bool "Mediatek timer driver" if COMPILE_TEST @@ -490,13 +490,13 @@ config SPRD_TIMER Enables support for the Spreadtrum timer driver. config SYS_SUPPORTS_SH_MTU2 - bool + bool config SYS_SUPPORTS_SH_TMU - bool + bool config SYS_SUPPORTS_EM_STI - bool + bool config CLKSRC_JCORE_PIT bool "J-Core PIT timer driver" if COMPILE_TEST @@ -523,7 +523,7 @@ config SH_TIMER_MTU2 help This enables build of a clockevent driver for the Multi-Function Timer Pulse Unit 2 (MTU2) hardware available on SoCs from Renesas. - This hardware comes with 16 bit-timer registers. + This hardware comes with 16-bit timer registers. config RENESAS_OSTM bool "Renesas OSTM timer driver" if COMPILE_TEST @@ -580,7 +580,7 @@ config CLKSRC_TANGO_XTAL select TIMER_OF select CLKSRC_MMIO help - This enables the clocksource for Tango SoC + This enables the clocksource for Tango SoC. config CLKSRC_PXA bool "Clocksource for PXA or SA-11x0 platform" if COMPILE_TEST @@ -591,24 +591,24 @@ config CLKSRC_PXA platforms. config H8300_TMR8 - bool "Clockevent timer for the H8300 platform" if COMPILE_TEST - depends on HAS_IOMEM + bool "Clockevent timer for the H8300 platform" if COMPILE_TEST + depends on HAS_IOMEM help This enables the 8 bits timer for the H8300 platform. config H8300_TMR16 - bool "Clockevent timer for the H83069 platform" if COMPILE_TEST - depends on HAS_IOMEM + bool "Clockevent timer for the H83069 platform" if COMPILE_TEST + depends on HAS_IOMEM help This enables the 16 bits timer for the H8300 platform with the - H83069 cpu. + H83069 CPU. config H8300_TPU - bool "Clocksource for the H8300 platform" if COMPILE_TEST - depends on HAS_IOMEM + bool "Clocksource for the H8300 platform" if COMPILE_TEST + depends on HAS_IOMEM help This enables the clocksource for the H8300 platform with the - H8S2678 cpu. + H8S2678 CPU. config CLKSRC_IMX_GPT bool "Clocksource using i.MX GPT" if COMPILE_TEST @@ -666,8 +666,8 @@ config CSKY_MP_TIMER help Say yes here to enable C-SKY SMP timer driver used for C-SKY SMP system. - csky,mptimer is not only used in SMP system, it also could be used - single core system. It's not a mmio reg and it use mtcr/mfcr instruction. + csky,mptimer is not only used in SMP system, it also could be used in + single core system. It's not a mmio reg and it uses mtcr/mfcr instruction. config GX6605S_TIMER bool "Gx6605s SOC system timer driver" if COMPILE_TEST @@ -697,4 +697,14 @@ config INGENIC_TIMER help Support for the timer/counter unit of the Ingenic JZ SoCs. +config MICROCHIP_PIT64B + bool "Microchip PIT64B support" + depends on OF || COMPILE_TEST + select CLKSRC_MMIO + help + This option enables Microchip PIT64B timer for Atmel + based system. It supports the oneshot, the periodic + modes and high resolution. It is used as a clocksource + and a clockevent. + endmenu diff --git a/drivers/clocksource/Makefile b/drivers/clocksource/Makefile index 4dfe4225ece7..713686faa549 100644 --- a/drivers/clocksource/Makefile +++ b/drivers/clocksource/Makefile @@ -88,3 +88,4 @@ obj-$(CONFIG_RISCV_TIMER) += timer-riscv.o obj-$(CONFIG_CSKY_MP_TIMER) += timer-mp-csky.o obj-$(CONFIG_GX6605S_TIMER) += timer-gx6605s.o obj-$(CONFIG_HYPERV_TIMER) += hyperv_timer.o +obj-$(CONFIG_MICROCHIP_PIT64B) += timer-microchip-pit64b.o diff --git a/drivers/clocksource/bcm2835_timer.c b/drivers/clocksource/bcm2835_timer.c index 2b196cbfadb6..b235f446ee50 100644 --- a/drivers/clocksource/bcm2835_timer.c +++ b/drivers/clocksource/bcm2835_timer.c @@ -121,7 +121,7 @@ static int __init bcm2835_timer_init(struct device_node *node) ret = setup_irq(irq, &timer->act); if (ret) { pr_err("Can't set up timer IRQ\n"); - goto err_iounmap; + goto err_timer_free; } clockevents_config_and_register(&timer->evt, freq, 0xf, 0xffffffff); @@ -130,6 +130,9 @@ static int __init bcm2835_timer_init(struct device_node *node) return 0; +err_timer_free: + kfree(timer); + err_iounmap: iounmap(base); return ret; diff --git a/drivers/clocksource/em_sti.c b/drivers/clocksource/em_sti.c index 9039df4f90e2..ab190dffb1ed 100644 --- a/drivers/clocksource/em_sti.c +++ b/drivers/clocksource/em_sti.c @@ -279,9 +279,7 @@ static void em_sti_register_clockevent(struct em_sti_priv *p) static int em_sti_probe(struct platform_device *pdev) { struct em_sti_priv *p; - struct resource *res; - int irq; - int ret; + int irq, ret; p = devm_kzalloc(&pdev->dev, sizeof(*p), GFP_KERNEL); if (p == NULL) @@ -295,8 +293,7 @@ static int em_sti_probe(struct platform_device *pdev) return irq; /* map memory, let base point to the STI instance */ - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - p->base = devm_ioremap_resource(&pdev->dev, res); + p->base = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(p->base)) return PTR_ERR(p->base); diff --git a/drivers/clocksource/exynos_mct.c b/drivers/clocksource/exynos_mct.c index 74cb299f5089..a267fe31ef13 100644 --- a/drivers/clocksource/exynos_mct.c +++ b/drivers/clocksource/exynos_mct.c @@ -4,7 +4,7 @@ * Copyright (c) 2011 Samsung Electronics Co., Ltd. * http://www.samsung.com * - * EXYNOS4 MCT(Multi-Core Timer) support + * Exynos4 MCT(Multi-Core Timer) support */ #include <linux/interrupt.h> diff --git a/drivers/clocksource/hyperv_timer.c b/drivers/clocksource/hyperv_timer.c index 287d8d58c21a..9d808d595ca8 100644 --- a/drivers/clocksource/hyperv_timer.c +++ b/drivers/clocksource/hyperv_timer.c @@ -66,7 +66,7 @@ static int hv_ce_set_next_event(unsigned long delta, { u64 current_tick; - current_tick = hyperv_cs->read(NULL); + current_tick = hv_read_reference_counter(); current_tick += delta; hv_init_timer(0, current_tick); return 0; @@ -302,22 +302,33 @@ EXPORT_SYMBOL_GPL(hv_stimer_global_cleanup); * the other that uses the TSC reference page feature as defined in the * TLFS. The MSR version is for compatibility with old versions of * Hyper-V and 32-bit x86. The TSC reference page version is preferred. + * + * The Hyper-V clocksource ratings of 250 are chosen to be below the + * TSC clocksource rating of 300. In configurations where Hyper-V offers + * an InvariantTSC, the TSC is not marked "unstable", so the TSC clocksource + * is available and preferred. With the higher rating, it will be the + * default. On older hardware and Hyper-V versions, the TSC is marked + * "unstable", so no TSC clocksource is created and the selected Hyper-V + * clocksource will be the default. */ -struct clocksource *hyperv_cs; -EXPORT_SYMBOL_GPL(hyperv_cs); +u64 (*hv_read_reference_counter)(void); +EXPORT_SYMBOL_GPL(hv_read_reference_counter); -static struct ms_hyperv_tsc_page tsc_pg __aligned(PAGE_SIZE); +static union { + struct ms_hyperv_tsc_page page; + u8 reserved[PAGE_SIZE]; +} tsc_pg __aligned(PAGE_SIZE); struct ms_hyperv_tsc_page *hv_get_tsc_page(void) { - return &tsc_pg; + return &tsc_pg.page; } EXPORT_SYMBOL_GPL(hv_get_tsc_page); -static u64 notrace read_hv_clock_tsc(struct clocksource *arg) +static u64 notrace read_hv_clock_tsc(void) { - u64 current_tick = hv_read_tsc_page(&tsc_pg); + u64 current_tick = hv_read_tsc_page(hv_get_tsc_page()); if (current_tick == U64_MAX) hv_get_time_ref_count(current_tick); @@ -325,20 +336,50 @@ static u64 notrace read_hv_clock_tsc(struct clocksource *arg) return current_tick; } +static u64 notrace read_hv_clock_tsc_cs(struct clocksource *arg) +{ + return read_hv_clock_tsc(); +} + static u64 read_hv_sched_clock_tsc(void) { - return read_hv_clock_tsc(NULL) - hv_sched_clock_offset; + return read_hv_clock_tsc() - hv_sched_clock_offset; +} + +static void suspend_hv_clock_tsc(struct clocksource *arg) +{ + u64 tsc_msr; + + /* Disable the TSC page */ + hv_get_reference_tsc(tsc_msr); + tsc_msr &= ~BIT_ULL(0); + hv_set_reference_tsc(tsc_msr); +} + + +static void resume_hv_clock_tsc(struct clocksource *arg) +{ + phys_addr_t phys_addr = virt_to_phys(&tsc_pg); + u64 tsc_msr; + + /* Re-enable the TSC page */ + hv_get_reference_tsc(tsc_msr); + tsc_msr &= GENMASK_ULL(11, 0); + tsc_msr |= BIT_ULL(0) | (u64)phys_addr; + hv_set_reference_tsc(tsc_msr); } static struct clocksource hyperv_cs_tsc = { .name = "hyperv_clocksource_tsc_page", - .rating = 400, - .read = read_hv_clock_tsc, + .rating = 250, + .read = read_hv_clock_tsc_cs, .mask = CLOCKSOURCE_MASK(64), .flags = CLOCK_SOURCE_IS_CONTINUOUS, + .suspend= suspend_hv_clock_tsc, + .resume = resume_hv_clock_tsc, }; -static u64 notrace read_hv_clock_msr(struct clocksource *arg) +static u64 notrace read_hv_clock_msr(void) { u64 current_tick; /* @@ -350,15 +391,20 @@ static u64 notrace read_hv_clock_msr(struct clocksource *arg) return current_tick; } +static u64 notrace read_hv_clock_msr_cs(struct clocksource *arg) +{ + return read_hv_clock_msr(); +} + static u64 read_hv_sched_clock_msr(void) { - return read_hv_clock_msr(NULL) - hv_sched_clock_offset; + return read_hv_clock_msr() - hv_sched_clock_offset; } static struct clocksource hyperv_cs_msr = { .name = "hyperv_clocksource_msr", - .rating = 400, - .read = read_hv_clock_msr, + .rating = 250, + .read = read_hv_clock_msr_cs, .mask = CLOCKSOURCE_MASK(64), .flags = CLOCK_SOURCE_IS_CONTINUOUS, }; @@ -371,8 +417,8 @@ static bool __init hv_init_tsc_clocksource(void) if (!(ms_hyperv.features & HV_MSR_REFERENCE_TSC_AVAILABLE)) return false; - hyperv_cs = &hyperv_cs_tsc; - phys_addr = virt_to_phys(&tsc_pg); + hv_read_reference_counter = read_hv_clock_tsc; + phys_addr = virt_to_phys(hv_get_tsc_page()); /* * The Hyper-V TLFS specifies to preserve the value of reserved @@ -389,7 +435,7 @@ static bool __init hv_init_tsc_clocksource(void) hv_set_clocksource_vdso(hyperv_cs_tsc); clocksource_register_hz(&hyperv_cs_tsc, NSEC_PER_SEC/100); - hv_sched_clock_offset = hyperv_cs->read(hyperv_cs); + hv_sched_clock_offset = hv_read_reference_counter(); hv_setup_sched_clock(read_hv_sched_clock_tsc); return true; @@ -411,10 +457,10 @@ void __init hv_init_clocksource(void) if (!(ms_hyperv.features & HV_MSR_TIME_REF_COUNT_AVAILABLE)) return; - hyperv_cs = &hyperv_cs_msr; + hv_read_reference_counter = read_hv_clock_msr; clocksource_register_hz(&hyperv_cs_msr, NSEC_PER_SEC/100); - hv_sched_clock_offset = hyperv_cs->read(hyperv_cs); + hv_sched_clock_offset = hv_read_reference_counter(); hv_setup_sched_clock(read_hv_sched_clock_msr); } EXPORT_SYMBOL_GPL(hv_init_clocksource); diff --git a/drivers/clocksource/timer-cadence-ttc.c b/drivers/clocksource/timer-cadence-ttc.c index 88fe2e9ba9a3..38858e141731 100644 --- a/drivers/clocksource/timer-cadence-ttc.c +++ b/drivers/clocksource/timer-cadence-ttc.c @@ -15,6 +15,8 @@ #include <linux/of_irq.h> #include <linux/slab.h> #include <linux/sched_clock.h> +#include <linux/module.h> +#include <linux/of_platform.h> /* * This driver configures the 2 16/32-bit count-up timers as follows: @@ -464,13 +466,7 @@ static int __init ttc_setup_clockevent(struct clk *clk, return 0; } -/** - * 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 int __init ttc_timer_init(struct device_node *timer) +static int __init ttc_timer_probe(struct platform_device *pdev) { unsigned int irq; void __iomem *timer_baseaddr; @@ -478,6 +474,7 @@ static int __init ttc_timer_init(struct device_node *timer) static int initialized; int clksel, ret; u32 timer_width = 16; + struct device_node *timer = pdev->dev.of_node; if (initialized) return 0; @@ -532,4 +529,17 @@ static int __init ttc_timer_init(struct device_node *timer) return 0; } -TIMER_OF_DECLARE(ttc, "cdns,ttc", ttc_timer_init); +static const struct of_device_id ttc_timer_of_match[] = { + {.compatible = "cdns,ttc"}, + {}, +}; + +MODULE_DEVICE_TABLE(of, ttc_timer_of_match); + +static struct platform_driver ttc_timer_driver = { + .driver = { + .name = "cdns_ttc_timer", + .of_match_table = ttc_timer_of_match, + }, +}; +builtin_platform_driver_probe(ttc_timer_driver, ttc_timer_probe); diff --git a/drivers/clocksource/timer-microchip-pit64b.c b/drivers/clocksource/timer-microchip-pit64b.c new file mode 100644 index 000000000000..bd63d3484838 --- /dev/null +++ b/drivers/clocksource/timer-microchip-pit64b.c @@ -0,0 +1,451 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * 64-bit Periodic Interval Timer driver + * + * Copyright (C) 2019 Microchip Technology Inc. and its subsidiaries + * + * Author: Claudiu Beznea <claudiu.beznea@microchip.com> + */ + +#include <linux/clk.h> +#include <linux/clockchips.h> +#include <linux/interrupt.h> +#include <linux/of_address.h> +#include <linux/of_irq.h> +#include <linux/sched_clock.h> +#include <linux/slab.h> + +#define MCHP_PIT64B_CR 0x00 /* Control Register */ +#define MCHP_PIT64B_CR_START BIT(0) +#define MCHP_PIT64B_CR_SWRST BIT(8) + +#define MCHP_PIT64B_MR 0x04 /* Mode Register */ +#define MCHP_PIT64B_MR_CONT BIT(0) +#define MCHP_PIT64B_MR_ONE_SHOT (0) +#define MCHP_PIT64B_MR_SGCLK BIT(3) +#define MCHP_PIT64B_MR_PRES GENMASK(11, 8) + +#define MCHP_PIT64B_LSB_PR 0x08 /* LSB Period Register */ + +#define MCHP_PIT64B_MSB_PR 0x0C /* MSB Period Register */ + +#define MCHP_PIT64B_IER 0x10 /* Interrupt Enable Register */ +#define MCHP_PIT64B_IER_PERIOD BIT(0) + +#define MCHP_PIT64B_ISR 0x1C /* Interrupt Status Register */ + +#define MCHP_PIT64B_TLSBR 0x20 /* Timer LSB Register */ + +#define MCHP_PIT64B_TMSBR 0x24 /* Timer MSB Register */ + +#define MCHP_PIT64B_PRES_MAX 0x10 +#define MCHP_PIT64B_LSBMASK GENMASK_ULL(31, 0) +#define MCHP_PIT64B_PRES_TO_MODE(p) (MCHP_PIT64B_MR_PRES & ((p) << 8)) +#define MCHP_PIT64B_MODE_TO_PRES(m) ((MCHP_PIT64B_MR_PRES & (m)) >> 8) +#define MCHP_PIT64B_DEF_CS_FREQ 5000000UL /* 5 MHz */ +#define MCHP_PIT64B_DEF_CE_FREQ 32768 /* 32 KHz */ + +#define MCHP_PIT64B_NAME "pit64b" + +/** + * struct mchp_pit64b_timer - PIT64B timer data structure + * @base: base address of PIT64B hardware block + * @pclk: PIT64B's peripheral clock + * @gclk: PIT64B's generic clock + * @mode: precomputed value for mode register + */ +struct mchp_pit64b_timer { + void __iomem *base; + struct clk *pclk; + struct clk *gclk; + u32 mode; +}; + +/** + * mchp_pit64b_clkevt - PIT64B clockevent data structure + * @timer: PIT64B timer + * @clkevt: clockevent + */ +struct mchp_pit64b_clkevt { + struct mchp_pit64b_timer timer; + struct clock_event_device clkevt; +}; + +#define to_mchp_pit64b_timer(x) \ + ((struct mchp_pit64b_timer *)container_of(x,\ + struct mchp_pit64b_clkevt, clkevt)) + +/* Base address for clocksource timer. */ +static void __iomem *mchp_pit64b_cs_base; +/* Default cycles for clockevent timer. */ +static u64 mchp_pit64b_ce_cycles; + +static inline u64 mchp_pit64b_cnt_read(void __iomem *base) +{ + unsigned long flags; + u32 low, high; + + raw_local_irq_save(flags); + + /* + * When using a 64 bit period TLSB must be read first, followed by the + * read of TMSB. This sequence generates an atomic read of the 64 bit + * timer value whatever the lapse of time between the accesses. + */ + low = readl_relaxed(base + MCHP_PIT64B_TLSBR); + high = readl_relaxed(base + MCHP_PIT64B_TMSBR); + + raw_local_irq_restore(flags); + + return (((u64)high << 32) | low); +} + +static inline void mchp_pit64b_reset(struct mchp_pit64b_timer *timer, + u64 cycles, u32 mode, u32 irqs) +{ + u32 low, high; + + low = cycles & MCHP_PIT64B_LSBMASK; + high = cycles >> 32; + + writel_relaxed(MCHP_PIT64B_CR_SWRST, timer->base + MCHP_PIT64B_CR); + writel_relaxed(mode | timer->mode, timer->base + MCHP_PIT64B_MR); + writel_relaxed(high, timer->base + MCHP_PIT64B_MSB_PR); + writel_relaxed(low, timer->base + MCHP_PIT64B_LSB_PR); + writel_relaxed(irqs, timer->base + MCHP_PIT64B_IER); + writel_relaxed(MCHP_PIT64B_CR_START, timer->base + MCHP_PIT64B_CR); +} + +static u64 mchp_pit64b_clksrc_read(struct clocksource *cs) +{ + return mchp_pit64b_cnt_read(mchp_pit64b_cs_base); +} + +static u64 mchp_pit64b_sched_read_clk(void) +{ + return mchp_pit64b_cnt_read(mchp_pit64b_cs_base); +} + +static int mchp_pit64b_clkevt_shutdown(struct clock_event_device *cedev) +{ + struct mchp_pit64b_timer *timer = to_mchp_pit64b_timer(cedev); + + writel_relaxed(MCHP_PIT64B_CR_SWRST, timer->base + MCHP_PIT64B_CR); + + return 0; +} + +static int mchp_pit64b_clkevt_set_periodic(struct clock_event_device *cedev) +{ + struct mchp_pit64b_timer *timer = to_mchp_pit64b_timer(cedev); + + mchp_pit64b_reset(timer, mchp_pit64b_ce_cycles, MCHP_PIT64B_MR_CONT, + MCHP_PIT64B_IER_PERIOD); + + return 0; +} + +static int mchp_pit64b_clkevt_set_next_event(unsigned long evt, + struct clock_event_device *cedev) +{ + struct mchp_pit64b_timer *timer = to_mchp_pit64b_timer(cedev); + + mchp_pit64b_reset(timer, evt, MCHP_PIT64B_MR_ONE_SHOT, + MCHP_PIT64B_IER_PERIOD); + + return 0; +} + +static void mchp_pit64b_clkevt_suspend(struct clock_event_device *cedev) +{ + struct mchp_pit64b_timer *timer = to_mchp_pit64b_timer(cedev); + + writel_relaxed(MCHP_PIT64B_CR_SWRST, timer->base + MCHP_PIT64B_CR); + if (timer->mode & MCHP_PIT64B_MR_SGCLK) + clk_disable_unprepare(timer->gclk); + clk_disable_unprepare(timer->pclk); +} + +static void mchp_pit64b_clkevt_resume(struct clock_event_device *cedev) +{ + struct mchp_pit64b_timer *timer = to_mchp_pit64b_timer(cedev); + + clk_prepare_enable(timer->pclk); + if (timer->mode & MCHP_PIT64B_MR_SGCLK) + clk_prepare_enable(timer->gclk); +} + +static irqreturn_t mchp_pit64b_interrupt(int irq, void *dev_id) +{ + struct mchp_pit64b_clkevt *irq_data = dev_id; + + /* Need to clear the interrupt. */ + readl_relaxed(irq_data->timer.base + MCHP_PIT64B_ISR); + + irq_data->clkevt.event_handler(&irq_data->clkevt); + + return IRQ_HANDLED; +} + +static void __init mchp_pit64b_pres_compute(u32 *pres, u32 clk_rate, + u32 max_rate) +{ + u32 tmp; + + for (*pres = 0; *pres < MCHP_PIT64B_PRES_MAX; (*pres)++) { + tmp = clk_rate / (*pres + 1); + if (tmp <= max_rate) + break; + } + + /* Use the bigest prescaler if we didn't match one. */ + if (*pres == MCHP_PIT64B_PRES_MAX) + *pres = MCHP_PIT64B_PRES_MAX - 1; +} + +/** + * mchp_pit64b_init_mode - prepare PIT64B mode register value to be used at + * runtime; this includes prescaler and SGCLK bit + * + * PIT64B timer may be fed by gclk or pclk. When gclk is used its rate has to + * be at least 3 times lower that pclk's rate. pclk rate is fixed, gclk rate + * could be changed via clock APIs. The chosen clock (pclk or gclk) could be + * divided by the internal PIT64B's divider. + * + * This function, first tries to use GCLK by requesting the desired rate from + * PMC and then using the internal PIT64B prescaler, if any, to reach the + * requested rate. If PCLK/GCLK < 3 (condition requested by PIT64B hardware) + * then the function falls back on using PCLK as clock source for PIT64B timer + * choosing the highest prescaler in case it doesn't locate one to match the + * requested frequency. + * + * Below is presented the PIT64B block in relation with PMC: + * + * PIT64B + * PMC +------------------------------------+ + * +----+ | +-----+ | + * | |-->gclk -->|-->| | +---------+ +-----+ | + * | | | | MUX |--->| Divider |->|timer| | + * | |-->pclk -->|-->| | +---------+ +-----+ | + * +----+ | +-----+ | + * | ^ | + * | sel | + * +------------------------------------+ + * + * Where: + * - gclk rate <= pclk rate/3 + * - gclk rate could be requested from PMC + * - pclk rate is fixed (cannot be requested from PMC) + */ +static int __init mchp_pit64b_init_mode(struct mchp_pit64b_timer *timer, + unsigned long max_rate) +{ + unsigned long pclk_rate, diff = 0, best_diff = ULONG_MAX; + long gclk_round = 0; + u32 pres, best_pres = 0; + + pclk_rate = clk_get_rate(timer->pclk); + if (!pclk_rate) + return -EINVAL; + + timer->mode = 0; + + /* Try using GCLK. */ + gclk_round = clk_round_rate(timer->gclk, max_rate); + if (gclk_round < 0) + goto pclk; + + if (pclk_rate / gclk_round < 3) + goto pclk; + + mchp_pit64b_pres_compute(&pres, gclk_round, max_rate); + best_diff = abs(gclk_round / (pres + 1) - max_rate); + best_pres = pres; + + if (!best_diff) { + timer->mode |= MCHP_PIT64B_MR_SGCLK; + goto done; + } + +pclk: + /* Check if requested rate could be obtained using PCLK. */ + mchp_pit64b_pres_compute(&pres, pclk_rate, max_rate); + diff = abs(pclk_rate / (pres + 1) - max_rate); + + if (best_diff > diff) { + /* Use PCLK. */ + best_pres = pres; + } else { + /* Use GCLK. */ + timer->mode |= MCHP_PIT64B_MR_SGCLK; + clk_set_rate(timer->gclk, gclk_round); + } + +done: + timer->mode |= MCHP_PIT64B_PRES_TO_MODE(best_pres); + + pr_info("PIT64B: using clk=%s with prescaler %u, freq=%lu [Hz]\n", + timer->mode & MCHP_PIT64B_MR_SGCLK ? "gclk" : "pclk", best_pres, + timer->mode & MCHP_PIT64B_MR_SGCLK ? + gclk_round / (best_pres + 1) : pclk_rate / (best_pres + 1)); + + return 0; +} + +static int __init mchp_pit64b_init_clksrc(struct mchp_pit64b_timer *timer, + u32 clk_rate) +{ + int ret; + + mchp_pit64b_reset(timer, ULLONG_MAX, MCHP_PIT64B_MR_CONT, 0); + + mchp_pit64b_cs_base = timer->base; + + ret = clocksource_mmio_init(timer->base, MCHP_PIT64B_NAME, clk_rate, + 210, 64, mchp_pit64b_clksrc_read); + if (ret) { + pr_debug("clksrc: Failed to register PIT64B clocksource!\n"); + + /* Stop timer. */ + writel_relaxed(MCHP_PIT64B_CR_SWRST, + timer->base + MCHP_PIT64B_CR); + + return ret; + } + + sched_clock_register(mchp_pit64b_sched_read_clk, 64, clk_rate); + + return 0; +} + +static int __init mchp_pit64b_init_clkevt(struct mchp_pit64b_timer *timer, + u32 clk_rate, u32 irq) +{ + struct mchp_pit64b_clkevt *ce; + int ret; + + ce = kzalloc(sizeof(*ce), GFP_KERNEL); + if (!ce) + return -ENOMEM; + + mchp_pit64b_ce_cycles = DIV_ROUND_CLOSEST(clk_rate, HZ); + + ce->timer.base = timer->base; + ce->timer.pclk = timer->pclk; + ce->timer.gclk = timer->gclk; + ce->timer.mode = timer->mode; + ce->clkevt.name = MCHP_PIT64B_NAME; + ce->clkevt.features = CLOCK_EVT_FEAT_ONESHOT | CLOCK_EVT_FEAT_PERIODIC; + ce->clkevt.rating = 150; + ce->clkevt.set_state_shutdown = mchp_pit64b_clkevt_shutdown; + ce->clkevt.set_state_periodic = mchp_pit64b_clkevt_set_periodic; + ce->clkevt.set_next_event = mchp_pit64b_clkevt_set_next_event; + ce->clkevt.suspend = mchp_pit64b_clkevt_suspend; + ce->clkevt.resume = mchp_pit64b_clkevt_resume; + ce->clkevt.cpumask = cpumask_of(0); + ce->clkevt.irq = irq; + + ret = request_irq(irq, mchp_pit64b_interrupt, IRQF_TIMER, + "pit64b_tick", ce); + if (ret) { + pr_debug("clkevt: Failed to setup PIT64B IRQ\n"); + kfree(ce); + return ret; + } + + clockevents_config_and_register(&ce->clkevt, clk_rate, 1, ULONG_MAX); + + return 0; +} + +static int __init mchp_pit64b_dt_init_timer(struct device_node *node, + bool clkevt) +{ + u32 freq = clkevt ? MCHP_PIT64B_DEF_CE_FREQ : MCHP_PIT64B_DEF_CS_FREQ; + struct mchp_pit64b_timer timer; + unsigned long clk_rate; + u32 irq = 0; + int ret; + + /* Parse DT node. */ + timer.pclk = of_clk_get_by_name(node, "pclk"); + if (IS_ERR(timer.pclk)) + return PTR_ERR(timer.pclk); + + timer.gclk = of_clk_get_by_name(node, "gclk"); + if (IS_ERR(timer.gclk)) + return PTR_ERR(timer.gclk); + + timer.base = of_iomap(node, 0); + if (!timer.base) + return -ENXIO; + + if (clkevt) { + irq = irq_of_parse_and_map(node, 0); + if (!irq) { + ret = -ENODEV; + goto io_unmap; + } + } + + /* Initialize mode (prescaler + SGCK bit). To be used at runtime. */ + ret = mchp_pit64b_init_mode(&timer, freq); + if (ret) + goto irq_unmap; + + ret = clk_prepare_enable(timer.pclk); + if (ret) + goto irq_unmap; + + if (timer.mode & MCHP_PIT64B_MR_SGCLK) { + ret = clk_prepare_enable(timer.gclk); + if (ret) + goto pclk_unprepare; + + clk_rate = clk_get_rate(timer.gclk); + } else { + clk_rate = clk_get_rate(timer.pclk); + } + clk_rate = clk_rate / (MCHP_PIT64B_MODE_TO_PRES(timer.mode) + 1); + + if (clkevt) + ret = mchp_pit64b_init_clkevt(&timer, clk_rate, irq); + else + ret = mchp_pit64b_init_clksrc(&timer, clk_rate); + + if (ret) + goto gclk_unprepare; + + return 0; + +gclk_unprepare: + if (timer.mode & MCHP_PIT64B_MR_SGCLK) + clk_disable_unprepare(timer.gclk); +pclk_unprepare: + clk_disable_unprepare(timer.pclk); +irq_unmap: + irq_dispose_mapping(irq); +io_unmap: + iounmap(timer.base); + + return ret; +} + +static int __init mchp_pit64b_dt_init(struct device_node *node) +{ + static int inits; + + switch (inits++) { + case 0: + /* 1st request, register clockevent. */ + return mchp_pit64b_dt_init_timer(node, true); + case 1: + /* 2nd request, register clocksource. */ + return mchp_pit64b_dt_init_timer(node, false); + } + + /* The rest, don't care. */ + return -EINVAL; +} + +TIMER_OF_DECLARE(mchp_pit64b, "microchip,sam9x60-pit64b", mchp_pit64b_dt_init); diff --git a/drivers/clocksource/timer-ti-dm.c b/drivers/clocksource/timer-ti-dm.c index 5394d9dbdfbc..269a994d6a99 100644 --- a/drivers/clocksource/timer-ti-dm.c +++ b/drivers/clocksource/timer-ti-dm.c @@ -780,7 +780,6 @@ static int omap_dm_timer_probe(struct platform_device *pdev) { unsigned long flags; struct omap_dm_timer *timer; - struct resource *mem, *irq; struct device *dev = &pdev->dev; const struct dmtimer_platform_data *pdata; int ret; @@ -796,24 +795,16 @@ static int omap_dm_timer_probe(struct platform_device *pdev) return -ENODEV; } - irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0); - if (unlikely(!irq)) { - dev_err(dev, "%s: no IRQ resource.\n", __func__); - return -ENODEV; - } - - mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); - if (unlikely(!mem)) { - dev_err(dev, "%s: no memory resource.\n", __func__); - return -ENODEV; - } - timer = devm_kzalloc(dev, sizeof(*timer), GFP_KERNEL); if (!timer) return -ENOMEM; + timer->irq = platform_get_irq(pdev, 0); + if (timer->irq < 0) + return timer->irq; + timer->fclk = ERR_PTR(-ENODEV); - timer->io_base = devm_ioremap_resource(dev, mem); + timer->io_base = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(timer->io_base)) return PTR_ERR(timer->io_base); @@ -836,7 +827,6 @@ static int omap_dm_timer_probe(struct platform_device *pdev) if (pdata) timer->errata = pdata->timer_errata; - timer->irq = irq->start; timer->pdev = pdev; pm_runtime_enable(dev); |