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:
Linus Torvalds 2026-06-15 13:34:03 +05:30
commit f20e2fdaae
19 changed files with 591 additions and 137 deletions

View File

@ -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:

View File

@ -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:

View File

@ -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

View File

@ -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>;

View File

@ -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:

View File

@ -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

View File

@ -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;

View File

@ -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

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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

View File

@ -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,
},

View File

@ -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);

View File

@ -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[] = {

View File

@ -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.
*

View File

@ -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) {

View File

@ -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);

View File

@ -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
*/