mirror of
https://github.com/torvalds/linux.git
synced 2026-07-27 09:36:22 +02:00
Updates for clocksource/clockevent drivers:
- Add devm helpers for clocksources, which allows to simplify driver
teardown and probe failure handling.
- More module conversion work
- Update the support for the ARM EL2 virtual timer including the required
ACPI changes.
- Add clockevent and clocksource support for the TI Dual Mode Timer
- Fix the support for multiple watchdog instances in the TEGRA186 driver
- Add D1 timer support to the SUN5I driver
- The usual devicetree updates, cleanups and small fixes all over the place
-----BEGIN PGP SIGNATURE-----
iQJEBAABCgAuFiEEQp8+kY+LLUocC4bMphj1TA10mKEFAmotjBUQHHRnbHhAa2Vy
bmVsLm9yZwAKCRCmGPVMDXSYoaXND/0Z+wx27W4g4n9OTc//dfG+iV4eg05On9Fg
LhlnyanjvIiZwWEBR0AdLDFHgxCqQV0fHpgldsz/Qlk/5uXcpsrGIhqwAiWynppU
SLcZyW5XrZp4syTQfIxaAwAqMzGfW3nHWEkaRYCo/Q8oTDfvrXzY0AWYoK+bDkL3
NmnIuNGdLD+VoD4E3tn0/Q/MTtA4+DI9Cq98S5lOOchkO01ZOxx9OTalhAGFEvOo
zk6YSW2qeUtvc3stahPSvAOaC6EcAalp7wNjWDeHV/c2kUmoKlGpsgvb8Mt+0vPD
exNdctuTFGU2zrS1s0IB22n8RkSCzloxiTBs4Ec88ecn+cLIwq3oeuqchNiH/Quw
YzQWuYhG1ML5i5/g3sAnYu+Hj5DOvB/0IBiYlCxKH4rf49RtBn1AQWt4OzdKzuQS
/fnYqw+4N5XCodHzQbFv7Kbs+21lXXYubt9RLvSl6QYc4xfJRfWCS7rJdXUExw3D
5visGs+tUozi8C23t+BCWJ7jJsd0uNk51gmvOPUI3ybgL5YFwz1YdY4OwAAQT57i
MGiDbC4nMz7nlyWkl40ZVjOvl8bNvCGozq30tRux+CPmpYUi/87ToraKZy8ulSra
+26/ekATKXSfcyUh+TinA10AnnmCdxUyfA1WpZ6z/bCVdZqqHXK6HBf0OY8+u3um
zhrXH2BJBQ==
=4vQE
-----END PGP SIGNATURE-----
Merge tag 'timers-clocksource-2026-06-13' of gitolite.kernel.org:pub/scm/linux/kernel/git/tip/tip
Pull clocksource updates from Thomas Gleixner:
"Updates for clocksource/clockevent drivers:
- Add devm helpers for clocksources, which allows to simplify driver
teardown and probe failure handling.
- More module conversion work
- Update the support for the ARM EL2 virtual timer including the
required ACPI changes.
- Add clockevent and clocksource support for the TI Dual Mode Timer
- Fix the support for multiple watchdog instances in the TEGRA186
driver
- Add D1 timer support to the SUN5I driver
- The usual devicetree updates, cleanups and small fixes all over the
place"
* tag 'timers-clocksource-2026-06-13' of gitolite.kernel.org:pub/scm/linux/kernel/git/tip/tip: (24 commits)
clocksource: move NXP timer selection to drivers/clocksource
clocksource/drivers/timer-tegra186: Reserve and service a kernel watchdog
clocksource/drivers/timer-tegra186: Register all accessible watchdog timers
clocksource/drivers/timer-tegra186: Correct num_wdts for Tegra186 and Tegra234
clocksource/drivers/timer-tegra186: Fix support for multiple watchdog instances
clocksource/drivers/timer-ti-dm: Add clockevent support
clocksource/drivers/timer-ti-dm: Add clocksource support
clocksource/drivers/timer-ti-dm: Fix property name in comment
dt-bindings: timer: arm,arch_timer: Fix requirements for interrupt description
clocksource/drivers/arm_arch_timer: Default to EL2 virtual timer when running VHE
ACPI: GTDT: Parse information related to the EL2 virtual timer
ACPI: GTDT: Account for GTDTv3 size when walking the platform timer descriptors
clocksource: Add devm_clocksource_register_*() helpers
clocksource/drivers/sun5i: Add D1 hstimer support
dt-bindings: timer: allwinner,sun5i-a13-hstimer: add H616 and D1
dt-bindings: timer: Add StarFive JHB100 clint
dt-bindings: timer: renesas,rz-mtu3: document RZ/{T2H,N2H}
dt-bindings: timer: renesas,rz-mtu3: Remove TCIU8 interrupt
dt-bindings: timer: Remove sifive,fine-ctr-bits property
clocksource/drivers/timer-of: Make the code compatible with modules
...
This commit is contained in:
commit
f20e2fdaae
|
|
@ -15,9 +15,13 @@ properties:
|
|||
oneOf:
|
||||
- const: allwinner,sun5i-a13-hstimer
|
||||
- const: allwinner,sun7i-a20-hstimer
|
||||
- const: allwinner,sun20i-d1-hstimer
|
||||
- items:
|
||||
- const: allwinner,sun6i-a31-hstimer
|
||||
- const: allwinner,sun7i-a20-hstimer
|
||||
- items:
|
||||
- const: allwinner,sun50i-h616-hstimer
|
||||
- const: allwinner,sun20i-d1-hstimer
|
||||
|
||||
reg:
|
||||
maxItems: 1
|
||||
|
|
@ -45,7 +49,10 @@ required:
|
|||
if:
|
||||
properties:
|
||||
compatible:
|
||||
const: allwinner,sun5i-a13-hstimer
|
||||
anyOf:
|
||||
- const: allwinner,sun5i-a13-hstimer
|
||||
- contains:
|
||||
const: allwinner,sun20i-d1-hstimer
|
||||
|
||||
then:
|
||||
properties:
|
||||
|
|
|
|||
|
|
@ -10,13 +10,8 @@ maintainers:
|
|||
- Marc Zyngier <marc.zyngier@arm.com>
|
||||
- Mark Rutland <mark.rutland@arm.com>
|
||||
description: |+
|
||||
ARM cores may have a per-core architected timer, which provides per-cpu timers,
|
||||
or a memory mapped architected timer, which provides up to 8 frames with a
|
||||
physical and optional virtual timer per frame.
|
||||
|
||||
The per-core architected timer is attached to a GIC to deliver its
|
||||
per-processor interrupts via PPIs. The memory mapped timer is attached to a GIC
|
||||
to deliver its interrupts via SPIs.
|
||||
The per-core architected timer is expected to deliver per-CPU interrupts
|
||||
(commonly to a GIC to deliver its per-processor interrupts as PPIs).
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
|
|
@ -33,13 +28,13 @@ properties:
|
|||
- const: arm,armv7-timer
|
||||
|
||||
interrupts:
|
||||
minItems: 1
|
||||
minItems: 2
|
||||
items:
|
||||
- description: secure timer irq
|
||||
- description: non-secure timer irq
|
||||
- description: virtual timer irq
|
||||
- description: hypervisor timer irq
|
||||
- description: hypervisor virtual timer irq
|
||||
- description: EL1 secure physical timer irq, if EL3 is implemented
|
||||
- description: EL1 non-secure physical timer irq
|
||||
- description: EL1 virtual timer irq
|
||||
- description: EL2 physical timer irq, if EL2 is implemented
|
||||
- description: EL2 virtual timer irq, if FEAT_VHE is implemented
|
||||
|
||||
interrupt-names:
|
||||
oneOf:
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ properties:
|
|||
oneOf:
|
||||
- const: fsl,imx1-gpt
|
||||
- const: fsl,imx21-gpt
|
||||
- const: fsl,imx25-epit
|
||||
- items:
|
||||
- const: fsl,imx27-gpt
|
||||
- const: fsl,imx21-gpt
|
||||
|
|
|
|||
|
|
@ -112,6 +112,8 @@ properties:
|
|||
- renesas,r9a07g043-mtu3 # RZ/{G2UL,Five}
|
||||
- renesas,r9a07g044-mtu3 # RZ/G2{L,LC}
|
||||
- renesas,r9a07g054-mtu3 # RZ/V2L
|
||||
- renesas,r9a09g077-mtu3 # RZ/T2H
|
||||
- renesas,r9a09g087-mtu3 # RZ/N2H
|
||||
- const: renesas,rz-mtu3
|
||||
|
||||
reg:
|
||||
|
|
@ -162,7 +164,6 @@ properties:
|
|||
- description: MTU8.TGRC input capture/compare match
|
||||
- description: MTU8.TGRD input capture/compare match
|
||||
- description: MTU8.TCNT overflow
|
||||
- description: MTU8.TCNT underflow
|
||||
|
||||
interrupt-names:
|
||||
items:
|
||||
|
|
@ -209,7 +210,6 @@ properties:
|
|||
- const: tgic8
|
||||
- const: tgid8
|
||||
- const: tciv8
|
||||
- const: tciu8
|
||||
|
||||
clocks:
|
||||
maxItems: 1
|
||||
|
|
@ -233,7 +233,22 @@ required:
|
|||
- interrupt-names
|
||||
- clocks
|
||||
- power-domains
|
||||
- resets
|
||||
|
||||
allOf:
|
||||
- if:
|
||||
properties:
|
||||
compatible:
|
||||
contains:
|
||||
enum:
|
||||
- renesas,r9a07g043-mtu3
|
||||
- renesas,r9a07g044-mtu3
|
||||
- renesas,r9a07g054-mtu3
|
||||
then:
|
||||
required:
|
||||
- resets
|
||||
else:
|
||||
properties:
|
||||
resets: false
|
||||
|
||||
additionalProperties: false
|
||||
|
||||
|
|
@ -287,8 +302,7 @@ examples:
|
|||
<GIC_SPI 209 IRQ_TYPE_EDGE_RISING>,
|
||||
<GIC_SPI 210 IRQ_TYPE_EDGE_RISING>,
|
||||
<GIC_SPI 211 IRQ_TYPE_EDGE_RISING>,
|
||||
<GIC_SPI 212 IRQ_TYPE_EDGE_RISING>,
|
||||
<GIC_SPI 213 IRQ_TYPE_EDGE_RISING>;
|
||||
<GIC_SPI 212 IRQ_TYPE_EDGE_RISING>;
|
||||
interrupt-names = "tgia0", "tgib0", "tgic0", "tgid0", "tciv0", "tgie0",
|
||||
"tgif0",
|
||||
"tgia1", "tgib1", "tciv1", "tciu1",
|
||||
|
|
@ -298,7 +312,7 @@ examples:
|
|||
"tgiu5", "tgiv5", "tgiw5",
|
||||
"tgia6", "tgib6", "tgic6", "tgid6", "tciv6",
|
||||
"tgia7", "tgib7", "tgic7", "tgid7", "tciv7",
|
||||
"tgia8", "tgib8", "tgic8", "tgid8", "tciv8", "tciu8";
|
||||
"tgia8", "tgib8", "tgic8", "tgid8", "tciv8";
|
||||
clocks = <&cpg CPG_MOD R9A07G044_MTU_X_MCK_MTU3>;
|
||||
power-domains = <&cpg>;
|
||||
resets = <&cpg R9A07G044_MTU_X_PRESET_MTU3>;
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ properties:
|
|||
- starfive,jh7100-clint # StarFive JH7100
|
||||
- starfive,jh7110-clint # StarFive JH7110
|
||||
- starfive,jh8100-clint # StarFive JH8100
|
||||
- starfive,jhb100-clint # StarFive JHB100
|
||||
- tenstorrent,blackhole-clint # Tenstorrent Blackhole
|
||||
- const: sifive,clint0 # SiFive CLINT v0 IP block
|
||||
- items:
|
||||
|
|
@ -72,22 +73,6 @@ properties:
|
|||
minItems: 1
|
||||
maxItems: 4095
|
||||
|
||||
sifive,fine-ctr-bits:
|
||||
maximum: 15
|
||||
description: The width in bits of the fine counter.
|
||||
|
||||
if:
|
||||
properties:
|
||||
compatible:
|
||||
contains:
|
||||
const: sifive,clint2
|
||||
then:
|
||||
required:
|
||||
- sifive,fine-ctr-bits
|
||||
else:
|
||||
properties:
|
||||
sifive,fine-ctr-bits: false
|
||||
|
||||
additionalProperties: false
|
||||
|
||||
required:
|
||||
|
|
|
|||
|
|
@ -227,27 +227,6 @@ config SOC_VF610
|
|||
help
|
||||
This enables support for Freescale Vybrid VF610 processor.
|
||||
|
||||
choice
|
||||
prompt "Clocksource for scheduler clock"
|
||||
depends on SOC_VF610
|
||||
default VF_USE_ARM_GLOBAL_TIMER
|
||||
|
||||
config VF_USE_ARM_GLOBAL_TIMER
|
||||
bool "Use ARM Global Timer"
|
||||
depends on ARCH_MULTI_V7
|
||||
select ARM_GLOBAL_TIMER
|
||||
select CLKSRC_ARM_GLOBAL_TIMER_SCHED_CLOCK
|
||||
help
|
||||
Use the ARM Global Timer as clocksource
|
||||
|
||||
config VF_USE_PIT_TIMER
|
||||
bool "Use PIT timer"
|
||||
select NXP_PIT_TIMER
|
||||
help
|
||||
Use SoC Periodic Interrupt Timer (PIT) as clocksource
|
||||
|
||||
endchoice
|
||||
|
||||
endif
|
||||
|
||||
endif
|
||||
|
|
|
|||
|
|
@ -34,14 +34,33 @@ struct acpi_gtdt_descriptor {
|
|||
void *platform_timer;
|
||||
};
|
||||
|
||||
struct gtdt_v3 {
|
||||
struct acpi_table_gtdt gtdt_v2;
|
||||
struct acpi_gtdt_el2 el2_vtimer;
|
||||
};
|
||||
|
||||
static struct acpi_gtdt_descriptor acpi_gtdt_desc __initdata;
|
||||
|
||||
static __init struct acpi_gtdt_el2 *gtdt_to_el2_vtimer(struct acpi_table_gtdt *gtdt)
|
||||
{
|
||||
if (gtdt->header.revision < 3)
|
||||
return NULL;
|
||||
|
||||
return &container_of(gtdt, struct gtdt_v3, gtdt_v2)->el2_vtimer;
|
||||
}
|
||||
|
||||
static __init bool platform_timer_valid(void *platform_timer)
|
||||
{
|
||||
struct acpi_gtdt_header *gh = platform_timer;
|
||||
void *platform_timer_begin;
|
||||
|
||||
return (platform_timer >= (void *)(acpi_gtdt_desc.gtdt + 1) &&
|
||||
platform_timer < acpi_gtdt_desc.gtdt_end &&
|
||||
if (acpi_gtdt_desc.gtdt->header.revision >= 3)
|
||||
platform_timer_begin = container_of(acpi_gtdt_desc.gtdt, struct gtdt_v3, gtdt_v2) + 1;
|
||||
else
|
||||
platform_timer_begin = acpi_gtdt_desc.gtdt + 1;
|
||||
|
||||
return (platform_timer >= platform_timer_begin &&
|
||||
platform_timer + sizeof(*gh) <= acpi_gtdt_desc.gtdt_end &&
|
||||
gh->length != 0 &&
|
||||
platform_timer + gh->length <= acpi_gtdt_desc.gtdt_end);
|
||||
}
|
||||
|
|
@ -101,6 +120,7 @@ static int __init map_gt_gsi(u32 interrupt, u32 flags)
|
|||
int __init acpi_gtdt_map_ppi(int type)
|
||||
{
|
||||
struct acpi_table_gtdt *gtdt = acpi_gtdt_desc.gtdt;
|
||||
struct acpi_gtdt_el2 *el2_vtimer = gtdt_to_el2_vtimer(gtdt);
|
||||
|
||||
switch (type) {
|
||||
case ARCH_TIMER_PHYS_NONSECURE_PPI:
|
||||
|
|
@ -113,6 +133,12 @@ int __init acpi_gtdt_map_ppi(int type)
|
|||
case ARCH_TIMER_HYP_PPI:
|
||||
return map_gt_gsi(gtdt->non_secure_el2_interrupt,
|
||||
gtdt->non_secure_el2_flags);
|
||||
case ARCH_TIMER_HYP_VIRT_PPI:
|
||||
if (el2_vtimer && el2_vtimer->virtual_el2_timer_gsiv)
|
||||
return map_gt_gsi(el2_vtimer->virtual_el2_timer_gsiv,
|
||||
el2_vtimer->virtual_el2_timer_flags);
|
||||
|
||||
return 0;
|
||||
default:
|
||||
pr_err("Failed to map timer interrupt: invalid type.\n");
|
||||
}
|
||||
|
|
@ -130,6 +156,7 @@ int __init acpi_gtdt_map_ppi(int type)
|
|||
bool __init acpi_gtdt_c3stop(int type)
|
||||
{
|
||||
struct acpi_table_gtdt *gtdt = acpi_gtdt_desc.gtdt;
|
||||
struct acpi_gtdt_el2 *el2_vtimer = gtdt_to_el2_vtimer(gtdt);
|
||||
|
||||
switch (type) {
|
||||
case ARCH_TIMER_PHYS_NONSECURE_PPI:
|
||||
|
|
@ -141,6 +168,10 @@ bool __init acpi_gtdt_c3stop(int type)
|
|||
case ARCH_TIMER_HYP_PPI:
|
||||
return !(gtdt->non_secure_el2_flags & ACPI_GTDT_ALWAYS_ON);
|
||||
|
||||
case ARCH_TIMER_HYP_VIRT_PPI:
|
||||
return el2_vtimer && el2_vtimer->virtual_el2_timer_gsiv &&
|
||||
!(el2_vtimer->virtual_el2_timer_flags & ACPI_GTDT_ALWAYS_ON);
|
||||
|
||||
default:
|
||||
pr_err("Failed to get c3stop info: invalid type.\n");
|
||||
}
|
||||
|
|
@ -166,6 +197,13 @@ int __init acpi_gtdt_init(struct acpi_table_header *table,
|
|||
u32 cnt = 0;
|
||||
|
||||
gtdt = container_of(table, struct acpi_table_gtdt, header);
|
||||
|
||||
if ((gtdt->header.revision >= 3 && gtdt->header.length < sizeof(struct gtdt_v3)) ||
|
||||
(gtdt->header.revision == 2 && gtdt->header.length < sizeof(*gtdt))) {
|
||||
pr_err(FW_BUG "GTDT with invalid size %d\n", gtdt->header.length);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
acpi_gtdt_desc.gtdt = gtdt;
|
||||
acpi_gtdt_desc.gtdt_end = (void *)table + table->length;
|
||||
acpi_gtdt_desc.platform_timer = NULL;
|
||||
|
|
|
|||
|
|
@ -793,4 +793,35 @@ config RTK_SYSTIMER
|
|||
this option only when building for a Realtek platform or for compilation
|
||||
testing.
|
||||
|
||||
choice
|
||||
prompt "NXP clocksource for scheduler clock"
|
||||
depends on SOC_VF610 || ARCH_S32
|
||||
# Default to Global Timer for Vybrid (32-bit)
|
||||
default VF_USE_ARM_GLOBAL_TIMER if SOC_VF610
|
||||
# Default to None for S32 (64-bit)
|
||||
default VF_TIMER_NONE if ARCH_S32
|
||||
|
||||
config VF_USE_ARM_GLOBAL_TIMER
|
||||
bool "Use NXP Vybrid Global Timer"
|
||||
depends on ARCH_MULTI_V7 && SOC_VF610
|
||||
select ARM_GLOBAL_TIMER
|
||||
select CLKSRC_ARM_GLOBAL_TIMER_SCHED_CLOCK
|
||||
help
|
||||
Use the NXP Vybrid Global Timer as clocksource.
|
||||
|
||||
config VF_USE_PIT_TIMER
|
||||
bool "Use NXP PIT timer"
|
||||
select NXP_PIT_TIMER
|
||||
help
|
||||
Use NXP Periodic Interrupt Timer (PIT) as clocksource.
|
||||
|
||||
config VF_TIMER_NONE
|
||||
bool "None (Use standard Arch Timer)"
|
||||
depends on ARCH_S32
|
||||
help
|
||||
Do not use any specific NXP timer driver. Use the standard
|
||||
ARM Architected Timer instead.
|
||||
|
||||
endchoice
|
||||
|
||||
endmenu
|
||||
|
|
|
|||
|
|
@ -688,6 +688,7 @@ static void __arch_timer_setup(struct clock_event_device *clk)
|
|||
clk->irq = arch_timer_ppi[arch_timer_uses_ppi];
|
||||
switch (arch_timer_uses_ppi) {
|
||||
case ARCH_TIMER_VIRT_PPI:
|
||||
case ARCH_TIMER_HYP_VIRT_PPI:
|
||||
clk->set_state_shutdown = arch_timer_shutdown_virt;
|
||||
clk->set_state_oneshot_stopped = arch_timer_shutdown_virt;
|
||||
sne = erratum_handler(set_next_event_virt);
|
||||
|
|
@ -879,7 +880,7 @@ static void __init arch_timer_banner(void)
|
|||
pr_info("cp15 timer running at %lu.%02luMHz (%s).\n",
|
||||
(unsigned long)arch_timer_rate / 1000000,
|
||||
(unsigned long)(arch_timer_rate / 10000) % 100,
|
||||
(arch_timer_uses_ppi == ARCH_TIMER_VIRT_PPI) ? "virt" : "phys");
|
||||
arch_timer_ppi_names[arch_timer_uses_ppi]);
|
||||
}
|
||||
|
||||
u32 arch_timer_get_rate(void)
|
||||
|
|
@ -912,7 +913,8 @@ static void __init arch_counter_register(void)
|
|||
int width;
|
||||
|
||||
if ((IS_ENABLED(CONFIG_ARM64) && !is_hyp_mode_available()) ||
|
||||
arch_timer_uses_ppi == ARCH_TIMER_VIRT_PPI) {
|
||||
arch_timer_uses_ppi == ARCH_TIMER_VIRT_PPI ||
|
||||
arch_timer_uses_ppi == ARCH_TIMER_HYP_VIRT_PPI) {
|
||||
if (arch_timer_counter_has_wa()) {
|
||||
rd = arch_counter_get_cntvct_stable;
|
||||
scr = raw_counter_get_cntvct_stable;
|
||||
|
|
@ -1023,6 +1025,7 @@ static int __init arch_timer_register(void)
|
|||
ppi = arch_timer_ppi[arch_timer_uses_ppi];
|
||||
switch (arch_timer_uses_ppi) {
|
||||
case ARCH_TIMER_VIRT_PPI:
|
||||
case ARCH_TIMER_HYP_VIRT_PPI:
|
||||
err = request_percpu_irq(ppi, arch_timer_handler_virt,
|
||||
"arch_timer", arch_timer_evt);
|
||||
break;
|
||||
|
|
@ -1090,25 +1093,34 @@ static int __init arch_timer_common_init(void)
|
|||
/**
|
||||
* arch_timer_select_ppi() - Select suitable PPI for the current system.
|
||||
*
|
||||
* 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.
|
||||
* On AArch32, 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 (though KVM host
|
||||
* support has long been removed).
|
||||
*
|
||||
* On ARMv8.1 with VH extensions, the kernel runs in HYP. VHE
|
||||
* accesses to CNTP_*_EL1 registers are silently redirected to
|
||||
* their CNTHP_*_EL2 counterparts, and use a different PPI
|
||||
* number.
|
||||
* On ARMv8.1 with FEAT_VHE, the kernel runs in EL2. Accesses to
|
||||
* CNTV_*_EL1 registers are silently redirected to their CNTHV_*_EL2
|
||||
* counterparts, and the timer uses a different PPI number. Similar
|
||||
* thing happen when using the EL2 physical timer. Note that a bunch
|
||||
* of DTs out there omit the virtual EL2 timer, so fallback gracefully
|
||||
* on the physical timer.
|
||||
*
|
||||
* Without VHE, if no interrupt provided for virtual timer, we'll have
|
||||
* to stick to the physical timer. It'd better be accessible...
|
||||
*
|
||||
* If no interrupt provided for virtual timer, we'll have to
|
||||
* stick to the physical timer. It'd better be accessible...
|
||||
* For arm64 we never use the secure interrupt.
|
||||
*
|
||||
* Return: a suitable PPI type for the current system.
|
||||
*/
|
||||
static enum arch_timer_ppi_nr __init arch_timer_select_ppi(void)
|
||||
{
|
||||
if (is_kernel_in_hyp_mode())
|
||||
if (is_kernel_in_hyp_mode()) {
|
||||
if (arch_timer_ppi[ARCH_TIMER_HYP_VIRT_PPI])
|
||||
return ARCH_TIMER_HYP_VIRT_PPI;
|
||||
|
||||
pr_warn_once(FW_BUG "VHE-capable CPU without EL2 virtual timer interrupt\n");
|
||||
return ARCH_TIMER_HYP_PPI;
|
||||
}
|
||||
|
||||
if (!is_hyp_mode_available() && arch_timer_ppi[ARCH_TIMER_VIRT_PPI])
|
||||
return ARCH_TIMER_VIRT_PPI;
|
||||
|
|
@ -1200,14 +1212,9 @@ static int __init arch_timer_acpi_init(struct acpi_table_header *table)
|
|||
if (ret)
|
||||
return ret;
|
||||
|
||||
arch_timer_ppi[ARCH_TIMER_PHYS_NONSECURE_PPI] =
|
||||
acpi_gtdt_map_ppi(ARCH_TIMER_PHYS_NONSECURE_PPI);
|
||||
|
||||
arch_timer_ppi[ARCH_TIMER_VIRT_PPI] =
|
||||
acpi_gtdt_map_ppi(ARCH_TIMER_VIRT_PPI);
|
||||
|
||||
arch_timer_ppi[ARCH_TIMER_HYP_PPI] =
|
||||
acpi_gtdt_map_ppi(ARCH_TIMER_HYP_PPI);
|
||||
/* The GTDT parser can't be bothered with the secure timer */
|
||||
for (int i = ARCH_TIMER_PHYS_NONSECURE_PPI; i < ARCH_TIMER_MAX_TIMER_PPI; i++)
|
||||
arch_timer_ppi[i] = acpi_gtdt_map_ppi(i);
|
||||
|
||||
arch_timer_populate_kvm_info();
|
||||
|
||||
|
|
@ -1253,10 +1260,14 @@ int kvm_arch_ptp_get_crosststamp(u64 *cycle, struct timespec64 *ts,
|
|||
if (!IS_ENABLED(CONFIG_HAVE_ARM_SMCCC_DISCOVERY))
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
if (arch_timer_uses_ppi == ARCH_TIMER_VIRT_PPI)
|
||||
switch (arch_timer_uses_ppi) {
|
||||
case ARCH_TIMER_VIRT_PPI:
|
||||
case ARCH_TIMER_HYP_VIRT_PPI:
|
||||
ptp_counter = KVM_PTP_VIRT_COUNTER;
|
||||
else
|
||||
break;
|
||||
default:
|
||||
ptp_counter = KVM_PTP_PHYS_COUNTER;
|
||||
}
|
||||
|
||||
arm_smccc_1_1_invoke(ARM_SMCCC_VENDOR_HYP_KVM_PTP_FUNC_ID,
|
||||
ptp_counter, &hvc_res);
|
||||
|
|
|
|||
|
|
@ -21,21 +21,25 @@ u64 clocksource_mmio_readl_up(struct clocksource *c)
|
|||
{
|
||||
return (u64)readl_relaxed(to_mmio_clksrc(c)->reg);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(clocksource_mmio_readl_up);
|
||||
|
||||
u64 clocksource_mmio_readl_down(struct clocksource *c)
|
||||
{
|
||||
return ~(u64)readl_relaxed(to_mmio_clksrc(c)->reg) & c->mask;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(clocksource_mmio_readl_down);
|
||||
|
||||
u64 clocksource_mmio_readw_up(struct clocksource *c)
|
||||
{
|
||||
return (u64)readw_relaxed(to_mmio_clksrc(c)->reg);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(clocksource_mmio_readw_up);
|
||||
|
||||
u64 clocksource_mmio_readw_down(struct clocksource *c)
|
||||
{
|
||||
return ~(u64)readw_relaxed(to_mmio_clksrc(c)->reg) & c->mask;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(clocksource_mmio_readw_down);
|
||||
|
||||
/**
|
||||
* clocksource_mmio_init - Initialize a simple mmio based clocksource
|
||||
|
|
@ -46,9 +50,9 @@ u64 clocksource_mmio_readw_down(struct clocksource *c)
|
|||
* @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,
|
||||
u64 (*read)(struct clocksource *))
|
||||
int clocksource_mmio_init(void __iomem *base, const char *name,
|
||||
unsigned long hz, int rating, unsigned bits,
|
||||
u64 (*read)(struct clocksource *))
|
||||
{
|
||||
struct clocksource_mmio *cs;
|
||||
|
||||
|
|
@ -68,3 +72,4 @@ int __init clocksource_mmio_init(void __iomem *base, const char *name,
|
|||
|
||||
return clocksource_register_hz(&cs->clksrc, hz);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(clocksource_mmio_init);
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@
|
|||
*
|
||||
* Free the irq resource
|
||||
*/
|
||||
static __init void timer_of_irq_exit(struct of_timer_irq *of_irq)
|
||||
static void timer_of_irq_exit(struct of_timer_irq *of_irq)
|
||||
{
|
||||
struct timer_of *to = container_of(of_irq, struct timer_of, of_irq);
|
||||
|
||||
|
|
@ -41,8 +41,8 @@ static __init void timer_of_irq_exit(struct of_timer_irq *of_irq)
|
|||
*
|
||||
* Returns 0 on success, < 0 otherwise
|
||||
*/
|
||||
static __init int timer_of_irq_init(struct device_node *np,
|
||||
struct of_timer_irq *of_irq)
|
||||
static int timer_of_irq_init(struct device_node *np,
|
||||
struct of_timer_irq *of_irq)
|
||||
{
|
||||
int ret;
|
||||
struct timer_of *to = container_of(of_irq, struct timer_of, of_irq);
|
||||
|
|
@ -82,7 +82,7 @@ static __init int timer_of_irq_init(struct device_node *np,
|
|||
*
|
||||
* Disables and releases the refcount on the clk
|
||||
*/
|
||||
static __init void timer_of_clk_exit(struct of_timer_clk *of_clk)
|
||||
static void timer_of_clk_exit(struct of_timer_clk *of_clk)
|
||||
{
|
||||
of_clk->rate = 0;
|
||||
clk_disable_unprepare(of_clk->clk);
|
||||
|
|
@ -98,8 +98,8 @@ static __init void timer_of_clk_exit(struct of_timer_clk *of_clk)
|
|||
*
|
||||
* Returns 0 on success, < 0 otherwise
|
||||
*/
|
||||
static __init int timer_of_clk_init(struct device_node *np,
|
||||
struct of_timer_clk *of_clk)
|
||||
static int timer_of_clk_init(struct device_node *np,
|
||||
struct of_timer_clk *of_clk)
|
||||
{
|
||||
int ret;
|
||||
|
||||
|
|
@ -137,13 +137,13 @@ static __init int timer_of_clk_init(struct device_node *np,
|
|||
goto out;
|
||||
}
|
||||
|
||||
static __init void timer_of_base_exit(struct of_timer_base *of_base)
|
||||
static void timer_of_base_exit(struct of_timer_base *of_base)
|
||||
{
|
||||
iounmap(of_base->base);
|
||||
}
|
||||
|
||||
static __init int timer_of_base_init(struct device_node *np,
|
||||
struct of_timer_base *of_base)
|
||||
static int timer_of_base_init(struct device_node *np,
|
||||
struct of_timer_base *of_base)
|
||||
{
|
||||
of_base->base = of_base->name ?
|
||||
of_io_request_and_map(np, of_base->index, of_base->name) :
|
||||
|
|
@ -156,7 +156,7 @@ static __init int timer_of_base_init(struct device_node *np,
|
|||
return 0;
|
||||
}
|
||||
|
||||
int __init timer_of_init(struct device_node *np, struct timer_of *to)
|
||||
int timer_of_init(struct device_node *np, struct timer_of *to)
|
||||
{
|
||||
int ret = -EINVAL;
|
||||
int flags = 0;
|
||||
|
|
@ -200,6 +200,7 @@ int __init timer_of_init(struct device_node *np, struct timer_of *to)
|
|||
timer_of_base_exit(&to->of_base);
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(timer_of_init);
|
||||
|
||||
/**
|
||||
* timer_of_cleanup - release timer_of resources
|
||||
|
|
@ -208,7 +209,7 @@ int __init timer_of_init(struct device_node *np, struct timer_of *to)
|
|||
* Release the resources that has been used in timer_of_init().
|
||||
* This function should be called in init error cases
|
||||
*/
|
||||
void __init timer_of_cleanup(struct timer_of *to)
|
||||
void timer_of_cleanup(struct timer_of *to)
|
||||
{
|
||||
if (to->flags & TIMER_OF_IRQ)
|
||||
timer_of_irq_exit(&to->of_irq);
|
||||
|
|
@ -219,3 +220,4 @@ void __init timer_of_cleanup(struct timer_of *to)
|
|||
if (to->flags & TIMER_OF_BASE)
|
||||
timer_of_base_exit(&to->of_base);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(timer_of_cleanup);
|
||||
|
|
|
|||
|
|
@ -65,9 +65,8 @@ static inline unsigned long timer_of_period(struct timer_of *to)
|
|||
return to->of_clk.period;
|
||||
}
|
||||
|
||||
extern int __init timer_of_init(struct device_node *np,
|
||||
struct timer_of *to);
|
||||
int timer_of_init(struct device_node *np, struct timer_of *to);
|
||||
|
||||
extern void __init timer_of_cleanup(struct timer_of *to);
|
||||
void timer_of_cleanup(struct timer_of *to);
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -225,7 +225,7 @@ static int rttm_enable_clocksource(struct clocksource *cs)
|
|||
return 0;
|
||||
}
|
||||
|
||||
struct rttm_cs rttm_cs = {
|
||||
static struct rttm_cs rttm_cs = {
|
||||
.to = {
|
||||
.flags = TIMER_OF_BASE | TIMER_OF_CLOCK,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -18,21 +18,30 @@
|
|||
#include <linux/slab.h>
|
||||
#include <linux/platform_device.h>
|
||||
|
||||
#define TIMER_IRQ_EN_REG 0x00
|
||||
#define TIMER_IRQ_EN_REG 0x00
|
||||
#define TIMER_IRQ_EN(val) BIT(val)
|
||||
#define TIMER_IRQ_ST_REG 0x04
|
||||
#define TIMER_CTL_REG(val) (0x20 * (val) + 0x10)
|
||||
#define TIMER_IRQ_ST_REG 0x04
|
||||
#define TIMER_CTL_REG(val, offset) (0x20 * (val) + 0x10 + (offset))
|
||||
#define TIMER_CTL_ENABLE BIT(0)
|
||||
#define TIMER_CTL_RELOAD BIT(1)
|
||||
#define TIMER_CTL_CLK_PRES(val) (((val) & 0x7) << 4)
|
||||
#define TIMER_CTL_ONESHOT BIT(7)
|
||||
#define TIMER_INTVAL_LO_REG(val) (0x20 * (val) + 0x14)
|
||||
#define TIMER_INTVAL_HI_REG(val) (0x20 * (val) + 0x18)
|
||||
#define TIMER_CNTVAL_LO_REG(val) (0x20 * (val) + 0x1c)
|
||||
#define TIMER_CNTVAL_HI_REG(val) (0x20 * (val) + 0x20)
|
||||
#define TIMER_INTVAL_LO_REG(val, offset) (0x20 * (val) + 0x14 + (offset))
|
||||
#define TIMER_INTVAL_HI_REG(val, offset) (0x20 * (val) + 0x18 + (offset))
|
||||
#define TIMER_CNTVAL_LO_REG(val, offset) (0x20 * (val) + 0x1c + (offset))
|
||||
#define TIMER_CNTVAL_HI_REG(val, offset) (0x20 * (val) + 0x20 + (offset))
|
||||
|
||||
#define TIMER_SYNC_TICKS 3
|
||||
|
||||
/**
|
||||
* struct sunxi_timer_quirks - Differences between SoC variants.
|
||||
*
|
||||
* @from_ctl_base_offset: offset applied from ctl register onwards
|
||||
*/
|
||||
struct sunxi_timer_quirks {
|
||||
u32 from_ctl_base_offset;
|
||||
};
|
||||
|
||||
struct sun5i_timer {
|
||||
void __iomem *base;
|
||||
struct clk *clk;
|
||||
|
|
@ -40,6 +49,7 @@ struct sun5i_timer {
|
|||
u32 ticks_per_jiffy;
|
||||
struct clocksource clksrc;
|
||||
struct clock_event_device clkevt;
|
||||
const struct sunxi_timer_quirks *quirks;
|
||||
};
|
||||
|
||||
#define nb_to_sun5i_timer(x) \
|
||||
|
|
@ -57,28 +67,36 @@ struct sun5i_timer {
|
|||
*/
|
||||
static void sun5i_clkevt_sync(struct sun5i_timer *ce)
|
||||
{
|
||||
u32 old = readl(ce->base + TIMER_CNTVAL_LO_REG(1));
|
||||
u32 offset = ce->quirks->from_ctl_base_offset;
|
||||
u32 old = readl(ce->base + TIMER_CNTVAL_LO_REG(1, offset));
|
||||
|
||||
while ((old - readl(ce->base + TIMER_CNTVAL_LO_REG(1))) < TIMER_SYNC_TICKS)
|
||||
while ((old - readl(ce->base + TIMER_CNTVAL_LO_REG(1, offset))) <
|
||||
TIMER_SYNC_TICKS)
|
||||
cpu_relax();
|
||||
}
|
||||
|
||||
static void sun5i_clkevt_time_stop(struct sun5i_timer *ce, u8 timer)
|
||||
{
|
||||
u32 val = readl(ce->base + TIMER_CTL_REG(timer));
|
||||
writel(val & ~TIMER_CTL_ENABLE, ce->base + TIMER_CTL_REG(timer));
|
||||
u32 offset = ce->quirks->from_ctl_base_offset;
|
||||
u32 val = readl(ce->base + TIMER_CTL_REG(timer, offset));
|
||||
|
||||
writel(val & ~TIMER_CTL_ENABLE,
|
||||
ce->base + TIMER_CTL_REG(timer, offset));
|
||||
|
||||
sun5i_clkevt_sync(ce);
|
||||
}
|
||||
|
||||
static void sun5i_clkevt_time_setup(struct sun5i_timer *ce, u8 timer, u32 delay)
|
||||
{
|
||||
writel(delay, ce->base + TIMER_INTVAL_LO_REG(timer));
|
||||
u32 offset = ce->quirks->from_ctl_base_offset;
|
||||
|
||||
writel(delay, ce->base + TIMER_INTVAL_LO_REG(timer, offset));
|
||||
}
|
||||
|
||||
static void sun5i_clkevt_time_start(struct sun5i_timer *ce, u8 timer, bool periodic)
|
||||
{
|
||||
u32 val = readl(ce->base + TIMER_CTL_REG(timer));
|
||||
u32 offset = ce->quirks->from_ctl_base_offset;
|
||||
u32 val = readl(ce->base + TIMER_CTL_REG(timer, offset));
|
||||
|
||||
if (periodic)
|
||||
val &= ~TIMER_CTL_ONESHOT;
|
||||
|
|
@ -86,7 +104,7 @@ static void sun5i_clkevt_time_start(struct sun5i_timer *ce, u8 timer, bool perio
|
|||
val |= TIMER_CTL_ONESHOT;
|
||||
|
||||
writel(val | TIMER_CTL_ENABLE | TIMER_CTL_RELOAD,
|
||||
ce->base + TIMER_CTL_REG(timer));
|
||||
ce->base + TIMER_CTL_REG(timer, offset));
|
||||
}
|
||||
|
||||
static int sun5i_clkevt_shutdown(struct clock_event_device *clkevt)
|
||||
|
|
@ -141,8 +159,9 @@ static irqreturn_t sun5i_timer_interrupt(int irq, void *dev_id)
|
|||
static u64 sun5i_clksrc_read(struct clocksource *clksrc)
|
||||
{
|
||||
struct sun5i_timer *cs = clksrc_to_sun5i_timer(clksrc);
|
||||
u32 offset = cs->quirks->from_ctl_base_offset;
|
||||
|
||||
return ~readl(cs->base + TIMER_CNTVAL_LO_REG(1));
|
||||
return ~readl(cs->base + TIMER_CNTVAL_LO_REG(1, offset));
|
||||
}
|
||||
|
||||
static int sun5i_rate_cb(struct notifier_block *nb,
|
||||
|
|
@ -173,12 +192,13 @@ static int sun5i_setup_clocksource(struct platform_device *pdev,
|
|||
unsigned long rate)
|
||||
{
|
||||
struct sun5i_timer *cs = platform_get_drvdata(pdev);
|
||||
u32 offset = cs->quirks->from_ctl_base_offset;
|
||||
void __iomem *base = cs->base;
|
||||
int ret;
|
||||
|
||||
writel(~0, base + TIMER_INTVAL_LO_REG(1));
|
||||
writel(~0, base + TIMER_INTVAL_LO_REG(1, offset));
|
||||
writel(TIMER_CTL_ENABLE | TIMER_CTL_RELOAD,
|
||||
base + TIMER_CTL_REG(1));
|
||||
base + TIMER_CTL_REG(1, offset));
|
||||
|
||||
cs->clksrc.name = pdev->dev.of_node->name;
|
||||
cs->clksrc.rating = 340;
|
||||
|
|
@ -237,6 +257,7 @@ static int sun5i_setup_clockevent(struct platform_device *pdev,
|
|||
|
||||
static int sun5i_timer_probe(struct platform_device *pdev)
|
||||
{
|
||||
const struct sunxi_timer_quirks *quirks;
|
||||
struct device *dev = &pdev->dev;
|
||||
struct sun5i_timer *st;
|
||||
struct reset_control *rstc;
|
||||
|
|
@ -273,11 +294,18 @@ static int sun5i_timer_probe(struct platform_device *pdev)
|
|||
return -EINVAL;
|
||||
}
|
||||
|
||||
quirks = of_device_get_match_data(&pdev->dev);
|
||||
if (!quirks) {
|
||||
dev_err(&pdev->dev, "Failed to determine the quirks to use\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
st->base = timer_base;
|
||||
st->ticks_per_jiffy = DIV_ROUND_UP(rate, HZ);
|
||||
st->clk = clk;
|
||||
st->clk_rate_cb.notifier_call = sun5i_rate_cb;
|
||||
st->clk_rate_cb.next = NULL;
|
||||
st->quirks = quirks;
|
||||
|
||||
ret = devm_clk_notifier_register(dev, clk, &st->clk_rate_cb);
|
||||
if (ret) {
|
||||
|
|
@ -286,6 +314,9 @@ static int sun5i_timer_probe(struct platform_device *pdev)
|
|||
}
|
||||
|
||||
rstc = devm_reset_control_get_optional_exclusive(dev, NULL);
|
||||
if (IS_ERR(rstc))
|
||||
return dev_err_probe(dev, PTR_ERR(rstc),
|
||||
"failed to get reset\n");
|
||||
if (rstc)
|
||||
reset_control_deassert(rstc);
|
||||
|
||||
|
|
@ -311,9 +342,27 @@ static void sun5i_timer_remove(struct platform_device *pdev)
|
|||
clocksource_unregister(&st->clksrc);
|
||||
}
|
||||
|
||||
static const struct sunxi_timer_quirks sun5i_sun7i_hstimer_quirks = {
|
||||
.from_ctl_base_offset = 0x0,
|
||||
};
|
||||
|
||||
static const struct sunxi_timer_quirks sun20i_d1_hstimer_quirks = {
|
||||
.from_ctl_base_offset = 0x10,
|
||||
};
|
||||
|
||||
static const struct of_device_id sun5i_timer_of_match[] = {
|
||||
{ .compatible = "allwinner,sun5i-a13-hstimer" },
|
||||
{ .compatible = "allwinner,sun7i-a20-hstimer" },
|
||||
{
|
||||
.compatible = "allwinner,sun5i-a13-hstimer",
|
||||
.data = &sun5i_sun7i_hstimer_quirks,
|
||||
},
|
||||
{
|
||||
.compatible = "allwinner,sun7i-a20-hstimer",
|
||||
.data = &sun5i_sun7i_hstimer_quirks,
|
||||
},
|
||||
{
|
||||
.compatible = "allwinner,sun20i-d1-hstimer",
|
||||
.data = &sun20i_d1_hstimer_quirks,
|
||||
},
|
||||
{},
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, sun5i_timer_of_match);
|
||||
|
|
|
|||
|
|
@ -57,6 +57,15 @@
|
|||
#define WDTUR 0x00c
|
||||
#define WDTUR_UNLOCK_PATTERN 0x0000c45a
|
||||
|
||||
#define TEGRA186_KERNEL_WDT_TIMEOUT 120
|
||||
|
||||
/* WDT security configuration registers */
|
||||
#define WDTSCR(x) (0xf02c + (x) * 4)
|
||||
#define WDTSCR_SEC_WEN BIT(28)
|
||||
#define WDTSCR_SEC_REN BIT(27)
|
||||
#define WDTSCR_SEC_G1W BIT(9)
|
||||
#define WDTSCR_SEC_G1R BIT(1)
|
||||
|
||||
struct tegra186_timer_soc {
|
||||
unsigned int num_timers;
|
||||
unsigned int num_wdts;
|
||||
|
|
@ -75,6 +84,7 @@ struct tegra186_wdt {
|
|||
void __iomem *regs;
|
||||
unsigned int index;
|
||||
bool locked;
|
||||
bool is_kernel_wdt;
|
||||
|
||||
struct tegra186_tmr *tmr;
|
||||
};
|
||||
|
|
@ -89,7 +99,7 @@ struct tegra186_timer {
|
|||
struct device *dev;
|
||||
void __iomem *regs;
|
||||
|
||||
struct tegra186_wdt *wdt;
|
||||
struct tegra186_wdt **wdts;
|
||||
struct clocksource usec;
|
||||
struct clocksource tsc;
|
||||
struct clocksource osc;
|
||||
|
|
@ -149,7 +159,8 @@ static void tegra186_wdt_enable(struct tegra186_wdt *wdt)
|
|||
u32 value;
|
||||
|
||||
/* unmask hardware IRQ, this may have been lost across powergate */
|
||||
value = TKEIE_WDT_MASK(wdt->index, 1);
|
||||
value = readl(tegra->regs + TKEIE(wdt->tmr->hwirq));
|
||||
value |= TKEIE_WDT_MASK(wdt->index, 1);
|
||||
writel(value, tegra->regs + TKEIE(wdt->tmr->hwirq));
|
||||
|
||||
/* clear interrupt */
|
||||
|
|
@ -174,6 +185,10 @@ static void tegra186_wdt_enable(struct tegra186_wdt *wdt)
|
|||
value &= ~WDTCR_PERIOD_MASK;
|
||||
value |= WDTCR_PERIOD(1);
|
||||
|
||||
/* enable local interrupt for kernel watchdog */
|
||||
if (wdt->is_kernel_wdt)
|
||||
value |= WDTCR_LOCAL_INT_ENABLE;
|
||||
|
||||
/* enable system POR reset */
|
||||
value |= WDTCR_SYSTEM_POR_RESET_ENABLE;
|
||||
|
||||
|
|
@ -211,6 +226,16 @@ static int tegra186_wdt_ping(struct watchdog_device *wdd)
|
|||
return 0;
|
||||
}
|
||||
|
||||
static irqreturn_t tegra186_wdt_irq(int irq, void *data)
|
||||
{
|
||||
struct tegra186_wdt *wdt = data;
|
||||
|
||||
tegra186_wdt_disable(wdt);
|
||||
tegra186_wdt_enable(wdt);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static int tegra186_wdt_set_timeout(struct watchdog_device *wdd,
|
||||
unsigned int timeout)
|
||||
{
|
||||
|
|
@ -297,6 +322,23 @@ static const struct watchdog_ops tegra186_wdt_ops = {
|
|||
.get_timeleft = tegra186_wdt_get_timeleft,
|
||||
};
|
||||
|
||||
static bool tegra186_wdt_is_accessible(struct tegra186_timer *tegra, unsigned int index)
|
||||
{
|
||||
u32 value;
|
||||
|
||||
value = readl_relaxed(tegra->regs + WDTSCR(index));
|
||||
|
||||
/* Check OS write access if write blocking is enabled. */
|
||||
if ((value & WDTSCR_SEC_WEN) && !(value & WDTSCR_SEC_G1W))
|
||||
return false;
|
||||
|
||||
/* Check OS read access if read blocking is enabled. */
|
||||
if ((value & WDTSCR_SEC_REN) && !(value & WDTSCR_SEC_G1R))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static struct tegra186_wdt *tegra186_wdt_create(struct tegra186_timer *tegra,
|
||||
unsigned int index)
|
||||
{
|
||||
|
|
@ -336,10 +378,6 @@ static struct tegra186_wdt *tegra186_wdt_create(struct tegra186_timer *tegra,
|
|||
if (err < 0)
|
||||
return ERR_PTR(err);
|
||||
|
||||
err = devm_watchdog_register_device(tegra->dev, &wdt->base);
|
||||
if (err < 0)
|
||||
return ERR_PTR(err);
|
||||
|
||||
return wdt;
|
||||
}
|
||||
|
||||
|
|
@ -421,8 +459,11 @@ static int tegra186_timer_usec_init(struct tegra186_timer *tegra)
|
|||
|
||||
static int tegra186_timer_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct tegra186_wdt *kernel_wdt = NULL;
|
||||
struct device *dev = &pdev->dev;
|
||||
struct tegra186_timer *tegra;
|
||||
unsigned int i;
|
||||
int irq;
|
||||
int err;
|
||||
|
||||
tegra = devm_kzalloc(dev, sizeof(*tegra), GFP_KERNEL);
|
||||
|
|
@ -441,12 +482,33 @@ static int tegra186_timer_probe(struct platform_device *pdev)
|
|||
if (err < 0)
|
||||
return err;
|
||||
|
||||
/* create a watchdog using a preconfigured timer */
|
||||
tegra->wdt = tegra186_wdt_create(tegra, 0);
|
||||
if (IS_ERR(tegra->wdt)) {
|
||||
err = PTR_ERR(tegra->wdt);
|
||||
dev_err(dev, "failed to create WDT: %d\n", err);
|
||||
return err;
|
||||
irq = err;
|
||||
|
||||
tegra->wdts = devm_kcalloc(dev, tegra->soc->num_wdts, sizeof(*tegra->wdts), GFP_KERNEL);
|
||||
if (!tegra->wdts)
|
||||
return -ENOMEM;
|
||||
|
||||
for (i = 0; i < tegra->soc->num_wdts; i++) {
|
||||
if (!tegra186_wdt_is_accessible(tegra, i)) {
|
||||
dev_warn(dev, "WDT%u is not accessible\n", i);
|
||||
continue;
|
||||
}
|
||||
|
||||
tegra->wdts[i] = tegra186_wdt_create(tegra, i);
|
||||
if (IS_ERR(tegra->wdts[i]))
|
||||
return dev_err_probe(dev, PTR_ERR(tegra->wdts[i]),
|
||||
"failed to create WDT%u\n", i);
|
||||
|
||||
/* Reserve the first accessible WDT for the Kernel. */
|
||||
if (!kernel_wdt) {
|
||||
kernel_wdt = tegra->wdts[i];
|
||||
kernel_wdt->is_kernel_wdt = true;
|
||||
} else {
|
||||
err = devm_watchdog_register_device(dev, &tegra->wdts[i]->base);
|
||||
if (err < 0)
|
||||
return dev_err_probe(dev, err,
|
||||
"failed to register WDT%u\n", i);
|
||||
}
|
||||
}
|
||||
|
||||
err = tegra186_timer_tsc_init(tegra);
|
||||
|
|
@ -467,8 +529,22 @@ static int tegra186_timer_probe(struct platform_device *pdev)
|
|||
goto unregister_osc;
|
||||
}
|
||||
|
||||
if (kernel_wdt) {
|
||||
err = devm_request_irq(dev, irq, tegra186_wdt_irq, 0,
|
||||
dev_name(dev), kernel_wdt);
|
||||
if (err < 0) {
|
||||
dev_err(dev, "failed to request kernel WDT IRQ: %d\n", err);
|
||||
goto unregister_usec;
|
||||
}
|
||||
|
||||
tegra186_wdt_set_timeout(&kernel_wdt->base, TEGRA186_KERNEL_WDT_TIMEOUT);
|
||||
tegra186_wdt_enable(kernel_wdt);
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
unregister_usec:
|
||||
clocksource_unregister(&tegra->usec);
|
||||
unregister_osc:
|
||||
clocksource_unregister(&tegra->osc);
|
||||
unregister_tsc:
|
||||
|
|
@ -488,9 +564,14 @@ static void tegra186_timer_remove(struct platform_device *pdev)
|
|||
static int __maybe_unused tegra186_timer_suspend(struct device *dev)
|
||||
{
|
||||
struct tegra186_timer *tegra = dev_get_drvdata(dev);
|
||||
unsigned int i;
|
||||
|
||||
if (watchdog_active(&tegra->wdt->base))
|
||||
tegra186_wdt_disable(tegra->wdt);
|
||||
for (i = 0; i < tegra->soc->num_wdts; i++) {
|
||||
struct tegra186_wdt *wdt = tegra->wdts[i];
|
||||
|
||||
if (wdt && (wdt->is_kernel_wdt || watchdog_active(&wdt->base)))
|
||||
tegra186_wdt_disable(wdt);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -498,9 +579,14 @@ static int __maybe_unused tegra186_timer_suspend(struct device *dev)
|
|||
static int __maybe_unused tegra186_timer_resume(struct device *dev)
|
||||
{
|
||||
struct tegra186_timer *tegra = dev_get_drvdata(dev);
|
||||
unsigned int i;
|
||||
|
||||
if (watchdog_active(&tegra->wdt->base))
|
||||
tegra186_wdt_enable(tegra->wdt);
|
||||
for (i = 0; i < tegra->soc->num_wdts; i++) {
|
||||
struct tegra186_wdt *wdt = tegra->wdts[i];
|
||||
|
||||
if (wdt && (wdt->is_kernel_wdt || watchdog_active(&wdt->base)))
|
||||
tegra186_wdt_enable(wdt);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -510,12 +596,12 @@ static SIMPLE_DEV_PM_OPS(tegra186_timer_pm_ops, tegra186_timer_suspend,
|
|||
|
||||
static const struct tegra186_timer_soc tegra186_timer = {
|
||||
.num_timers = 10,
|
||||
.num_wdts = 3,
|
||||
.num_wdts = 2,
|
||||
};
|
||||
|
||||
static const struct tegra186_timer_soc tegra234_timer = {
|
||||
.num_timers = 16,
|
||||
.num_wdts = 3,
|
||||
.num_wdts = 2,
|
||||
};
|
||||
|
||||
static const struct of_device_id tegra186_timer_of_match[] = {
|
||||
|
|
|
|||
|
|
@ -226,7 +226,7 @@ static bool __init dmtimer_is_preferred(struct device_node *np)
|
|||
* Some omap3 boards with unreliable oscillator must not use the counter_32k
|
||||
* or dmtimer1 with 32 KiHz source. Additionally, the boards with unreliable
|
||||
* oscillator should really set counter_32k as disabled, and delete dmtimer1
|
||||
* ti,always-on property, but let's not count on it. For these quirky cases,
|
||||
* ti,timer-alwon property, but let's not count on it. For these quirky cases,
|
||||
* we prefer using the always-on secure dmtimer12 with the internal 32 KiHz
|
||||
* clock as the clocksource, and any available dmtimer as clockevent.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -20,8 +20,11 @@
|
|||
|
||||
#include <linux/clk.h>
|
||||
#include <linux/clk-provider.h>
|
||||
#include <linux/clocksource.h>
|
||||
#include <linux/clockchips.h>
|
||||
#include <linux/cpu_pm.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/err.h>
|
||||
|
|
@ -29,6 +32,7 @@
|
|||
#include <linux/of.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/platform_data/dmtimer-omap.h>
|
||||
#include <linux/sched_clock.h>
|
||||
|
||||
#include <clocksource/timer-ti-dm.h>
|
||||
#include <linux/delay.h>
|
||||
|
|
@ -148,6 +152,21 @@ static u32 omap_reserved_systimers;
|
|||
static LIST_HEAD(omap_timer_list);
|
||||
static DEFINE_SPINLOCK(dm_timer_lock);
|
||||
|
||||
struct dmtimer_clocksource {
|
||||
struct clocksource dev;
|
||||
struct dmtimer *timer;
|
||||
unsigned int loadval;
|
||||
};
|
||||
|
||||
struct omap_dm_timer_clockevent {
|
||||
struct clock_event_device dev;
|
||||
struct dmtimer *timer;
|
||||
u32 period;
|
||||
};
|
||||
|
||||
static bool omap_dm_timer_clockevent_setup;
|
||||
static void __iomem *omap_dm_timer_sched_clock_counter;
|
||||
|
||||
enum {
|
||||
REQUEST_ANY = 0,
|
||||
REQUEST_BY_ID,
|
||||
|
|
@ -1185,6 +1204,192 @@ static const struct dev_pm_ops omap_dm_timer_pm_ops = {
|
|||
|
||||
static const struct of_device_id omap_timer_match[];
|
||||
|
||||
static struct dmtimer_clocksource *omap_dm_timer_to_clocksource(struct clocksource *cs)
|
||||
{
|
||||
return container_of(cs, struct dmtimer_clocksource, dev);
|
||||
}
|
||||
|
||||
static u64 omap_dm_timer_read_cycles(struct clocksource *cs)
|
||||
{
|
||||
struct dmtimer_clocksource *clksrc = omap_dm_timer_to_clocksource(cs);
|
||||
struct dmtimer *timer = clksrc->timer;
|
||||
|
||||
return (u64)__omap_dm_timer_read_counter(timer);
|
||||
}
|
||||
|
||||
static u64 notrace omap_dm_timer_read_sched_clock(void)
|
||||
{
|
||||
/* Posted mode is not active here, so we can read directly */
|
||||
return readl_relaxed(omap_dm_timer_sched_clock_counter);
|
||||
}
|
||||
|
||||
static void omap_dm_timer_clocksource_suspend(struct clocksource *cs)
|
||||
{
|
||||
struct dmtimer_clocksource *clksrc = omap_dm_timer_to_clocksource(cs);
|
||||
struct dmtimer *timer = clksrc->timer;
|
||||
|
||||
clksrc->loadval = __omap_dm_timer_read_counter(timer);
|
||||
__omap_dm_timer_stop(timer);
|
||||
}
|
||||
|
||||
static void omap_dm_timer_clocksource_resume(struct clocksource *cs)
|
||||
{
|
||||
struct dmtimer_clocksource *clksrc = omap_dm_timer_to_clocksource(cs);
|
||||
struct dmtimer *timer = clksrc->timer;
|
||||
|
||||
dmtimer_write(timer, OMAP_TIMER_COUNTER_REG, clksrc->loadval);
|
||||
dmtimer_write(timer, OMAP_TIMER_CTRL_REG, OMAP_TIMER_CTRL_ST | OMAP_TIMER_CTRL_AR);
|
||||
}
|
||||
|
||||
static void omap_dm_timer_clocksource_unregister(void *data)
|
||||
{
|
||||
struct clocksource *cs = data;
|
||||
|
||||
clocksource_unregister(cs);
|
||||
}
|
||||
|
||||
static int omap_dm_timer_setup_clocksource(struct dmtimer *timer)
|
||||
{
|
||||
struct device *dev = &timer->pdev->dev;
|
||||
struct dmtimer_clocksource *clksrc;
|
||||
int err;
|
||||
|
||||
__omap_dm_timer_init_regs(timer);
|
||||
|
||||
timer->reserved = 1;
|
||||
|
||||
clksrc = devm_kzalloc(dev, sizeof(*clksrc), GFP_KERNEL);
|
||||
if (!clksrc)
|
||||
return -ENOMEM;
|
||||
|
||||
clksrc->timer = timer;
|
||||
|
||||
clksrc->dev.name = "omap_dm_timer";
|
||||
clksrc->dev.rating = 300;
|
||||
clksrc->dev.read = omap_dm_timer_read_cycles;
|
||||
clksrc->dev.mask = CLOCKSOURCE_MASK(32);
|
||||
clksrc->dev.flags = CLOCK_SOURCE_IS_CONTINUOUS;
|
||||
clksrc->dev.suspend = omap_dm_timer_clocksource_suspend;
|
||||
clksrc->dev.resume = omap_dm_timer_clocksource_resume;
|
||||
|
||||
dmtimer_write(timer, OMAP_TIMER_COUNTER_REG, 0);
|
||||
dmtimer_write(timer, OMAP_TIMER_LOAD_REG, 0);
|
||||
dmtimer_write(timer, OMAP_TIMER_CTRL_REG, OMAP_TIMER_CTRL_ST | OMAP_TIMER_CTRL_AR);
|
||||
|
||||
omap_dm_timer_sched_clock_counter = timer->func_base + _OMAP_TIMER_COUNTER_OFFSET;
|
||||
sched_clock_register(omap_dm_timer_read_sched_clock, 32, timer->fclk_rate);
|
||||
|
||||
err = clocksource_register_hz(&clksrc->dev, timer->fclk_rate);
|
||||
if (err)
|
||||
return dev_err_probe(dev, err, "Could not register as clocksource\n");
|
||||
|
||||
err = devm_add_action_or_reset(dev, omap_dm_timer_clocksource_unregister, &clksrc->dev);
|
||||
if (err)
|
||||
return dev_err_probe(dev, err, "Could not register clocksource_unregister action\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct omap_dm_timer_clockevent *to_dm_timer_clockevent(struct clock_event_device *evt)
|
||||
{
|
||||
return container_of(evt, struct omap_dm_timer_clockevent, dev);
|
||||
}
|
||||
|
||||
static int omap_dm_timer_evt_set_next_event(unsigned long cycles,
|
||||
struct clock_event_device *evt)
|
||||
{
|
||||
struct omap_dm_timer_clockevent *clkevt = to_dm_timer_clockevent(evt);
|
||||
struct dmtimer *timer = clkevt->timer;
|
||||
|
||||
dmtimer_write(timer, OMAP_TIMER_COUNTER_REG, 0xffffffff - cycles);
|
||||
dmtimer_write(timer, OMAP_TIMER_CTRL_REG, OMAP_TIMER_CTRL_ST);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int omap_dm_timer_evt_shutdown(struct clock_event_device *evt)
|
||||
{
|
||||
struct omap_dm_timer_clockevent *clkevt = to_dm_timer_clockevent(evt);
|
||||
struct dmtimer *timer = clkevt->timer;
|
||||
|
||||
__omap_dm_timer_stop(timer);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int omap_dm_timer_evt_set_periodic(struct clock_event_device *evt)
|
||||
{
|
||||
struct omap_dm_timer_clockevent *clkevt = to_dm_timer_clockevent(evt);
|
||||
struct dmtimer *timer = clkevt->timer;
|
||||
|
||||
omap_dm_timer_evt_shutdown(evt);
|
||||
|
||||
omap_dm_timer_set_load(&timer->cookie, clkevt->period);
|
||||
dmtimer_write(timer, OMAP_TIMER_COUNTER_REG, clkevt->period);
|
||||
dmtimer_write(timer, OMAP_TIMER_CTRL_REG,
|
||||
OMAP_TIMER_CTRL_AR | OMAP_TIMER_CTRL_ST);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static irqreturn_t omap_dm_timer_evt_interrupt(int irq, void *dev_id)
|
||||
{
|
||||
struct omap_dm_timer_clockevent *clkevt = dev_id;
|
||||
struct dmtimer *timer = clkevt->timer;
|
||||
|
||||
__omap_dm_timer_write_status(timer, OMAP_TIMER_INT_OVERFLOW);
|
||||
|
||||
clkevt->dev.event_handler(&clkevt->dev);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static int omap_dm_timer_setup_clockevent(struct dmtimer *timer)
|
||||
{
|
||||
struct device *dev = &timer->pdev->dev;
|
||||
struct omap_dm_timer_clockevent *clkevt;
|
||||
int ret;
|
||||
|
||||
clkevt = devm_kzalloc(dev, sizeof(*clkevt), GFP_KERNEL);
|
||||
if (!clkevt)
|
||||
return -ENOMEM;
|
||||
|
||||
timer->reserved = 1;
|
||||
clkevt->timer = timer;
|
||||
|
||||
clkevt->dev.name = "omap_dm_timer";
|
||||
clkevt->dev.features = CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT;
|
||||
clkevt->dev.rating = 300;
|
||||
clkevt->dev.set_next_event = omap_dm_timer_evt_set_next_event;
|
||||
clkevt->dev.set_state_shutdown = omap_dm_timer_evt_shutdown;
|
||||
clkevt->dev.set_state_periodic = omap_dm_timer_evt_set_periodic;
|
||||
clkevt->dev.set_state_oneshot = omap_dm_timer_evt_shutdown;
|
||||
clkevt->dev.set_state_oneshot_stopped = omap_dm_timer_evt_shutdown;
|
||||
clkevt->dev.tick_resume = omap_dm_timer_evt_shutdown;
|
||||
clkevt->dev.cpumask = cpu_possible_mask;
|
||||
clkevt->period = 0xffffffff - DIV_ROUND_CLOSEST(timer->fclk_rate, HZ);
|
||||
|
||||
__omap_dm_timer_init_regs(timer);
|
||||
__omap_dm_timer_stop(timer);
|
||||
__omap_dm_timer_enable_posted(timer);
|
||||
|
||||
ret = devm_request_irq(dev, timer->irq, omap_dm_timer_evt_interrupt,
|
||||
IRQF_TIMER, "omap_dm_timer_clockevent", clkevt);
|
||||
if (ret) {
|
||||
dev_err(dev, "Failed to request interrupt: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
__omap_dm_timer_int_enable(timer, OMAP_TIMER_INT_OVERFLOW);
|
||||
|
||||
clockevents_config_and_register(&clkevt->dev, timer->fclk_rate,
|
||||
3,
|
||||
0xffffffff);
|
||||
|
||||
omap_dm_timer_clockevent_setup = true;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* omap_dm_timer_probe - probe function called for every registered device
|
||||
* @pdev: pointer to current timer platform device
|
||||
|
|
@ -1272,6 +1477,18 @@ static int omap_dm_timer_probe(struct platform_device *pdev)
|
|||
|
||||
timer->pdev = pdev;
|
||||
|
||||
if (timer->capability & OMAP_TIMER_ALWON && !IS_ERR_OR_NULL(timer->fclk)) {
|
||||
if (!omap_dm_timer_sched_clock_counter) {
|
||||
ret = omap_dm_timer_setup_clocksource(timer);
|
||||
if (ret)
|
||||
return ret;
|
||||
} else if (!omap_dm_timer_clockevent_setup) {
|
||||
ret = omap_dm_timer_setup_clockevent(timer);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
pm_runtime_enable(dev);
|
||||
|
||||
if (!timer->reserved) {
|
||||
|
|
|
|||
|
|
@ -236,6 +236,9 @@ clocks_calc_mult_shift(u32 *mult, u32 *shift, u32 from, u32 to, u32 minsec);
|
|||
*/
|
||||
extern int
|
||||
__clocksource_register_scale(struct clocksource *cs, u32 scale, u32 freq);
|
||||
extern int
|
||||
__devm_clocksource_register_scale(struct device *dev, struct clocksource *cs,
|
||||
u32 scale, u32 freq);
|
||||
extern void
|
||||
__clocksource_update_freq_scale(struct clocksource *cs, u32 scale, u32 freq);
|
||||
|
||||
|
|
@ -258,6 +261,18 @@ static inline int clocksource_register_khz(struct clocksource *cs, u32 khz)
|
|||
return __clocksource_register_scale(cs, 1000, khz);
|
||||
}
|
||||
|
||||
static inline int devm_clocksource_register_hz(struct device *dev,
|
||||
struct clocksource *cs, u32 hz)
|
||||
{
|
||||
return __devm_clocksource_register_scale(dev, cs, 1, hz);
|
||||
}
|
||||
|
||||
static inline int devm_clocksource_register_khz(struct device *dev,
|
||||
struct clocksource *cs, u32 khz)
|
||||
{
|
||||
return __devm_clocksource_register_scale(dev, cs, 1000, khz);
|
||||
}
|
||||
|
||||
static inline void __clocksource_update_freq_hz(struct clocksource *cs, u32 hz)
|
||||
{
|
||||
__clocksource_update_freq_scale(cs, 1, hz);
|
||||
|
|
|
|||
|
|
@ -1338,6 +1338,26 @@ int __clocksource_register_scale(struct clocksource *cs, u32 scale, u32 freq)
|
|||
}
|
||||
EXPORT_SYMBOL_GPL(__clocksource_register_scale);
|
||||
|
||||
static void __devm_clocksource_unregister(void *data)
|
||||
{
|
||||
struct clocksource *cs = data;
|
||||
|
||||
clocksource_unregister(cs);
|
||||
}
|
||||
|
||||
int __devm_clocksource_register_scale(struct device *dev, struct clocksource *cs,
|
||||
u32 scale, u32 freq)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = __clocksource_register_scale(cs, scale, freq);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return devm_add_action_or_reset(dev, __devm_clocksource_unregister, cs);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(__devm_clocksource_register_scale);
|
||||
|
||||
/*
|
||||
* Unbind clocksource @cs. Called with clocksource_mutex held
|
||||
*/
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user