[RFC PATCH v5 02/16] x86/hpet: Add helper function hpet_set_comparator_periodic()

From: Ricardo Neri
Date: Tue May 04 2021 - 15:07:34 EST


Programming an HPET channel as periodic requires setting the
HPET_TN_SETVAL bit in the channel configuration. Plus, the comparator
register must be written twice (once for the comparator value and once for
the periodic value). Since this programming might be needed in several
places (e.g., the HPET clocksource and the HPET-based hardlockup detector),
add a helper function for this purpose.

A helper function hpet_set_comparator_oneshot() could also be implemented.
However, such function would only program the comparator register and the
function would be quite small. Hence, it is better to not bloat the code
with such an obvious function.

Cc: "H. Peter Anvin" <hpa@xxxxxxxxx>
Cc: Ashok Raj <ashok.raj@xxxxxxxxx>
Cc: Andi Kleen <andi.kleen@xxxxxxxxx>
Cc: Tony Luck <tony.luck@xxxxxxxxx>
Cc: Stephane Eranian <eranian@xxxxxxxxxx>
Cc: Suravee Suthikulpanit <Suravee.Suthikulpanit@xxxxxxx>
Cc: "Ravi V. Shankar" <ravi.v.shankar@xxxxxxxxx>
Cc: x86@xxxxxxxxxx
Originally-by: Suravee Suthikulpanit <Suravee.Suthikulpanit@xxxxxxx>
Signed-off-by: Ricardo Neri <ricardo.neri-calderon@xxxxxxxxxxxxxxx>
---
When programming the HPET channel in periodic mode, a udelay(1) between
the two successive writes to HPET_Tn_CMP was introduced in commit
e9e2cdb41241 ("[PATCH] clockevents: i386 drivers"). The commit message
does not give any reason for such delay. The hardware specification does
not seem to require it. The refactoring in this patch simply carries such
delay.
---
Changes since v4:
* Implement function only for periodic mode. This removed extra logic to
to use a non-zero period value as a proxy for periodic mode
programming. (Thomas)
* Added a comment on the history of the udelay() when programming the
channel in periodic mode. (Ashok)

Changes since v3:
* Added back a missing hpet_writel() for time configuration.

Changes since v2:
* Introduced this patch.

Changes since v1:
* N/A
---
arch/x86/include/asm/hpet.h | 2 ++
arch/x86/kernel/hpet.c | 49 ++++++++++++++++++++++++++++---------
2 files changed, 39 insertions(+), 12 deletions(-)

diff --git a/arch/x86/include/asm/hpet.h b/arch/x86/include/asm/hpet.h
index be9848f0883f..486e001413c7 100644
--- a/arch/x86/include/asm/hpet.h
+++ b/arch/x86/include/asm/hpet.h
@@ -74,6 +74,8 @@ extern void hpet_disable(void);
extern unsigned int hpet_readl(unsigned int a);
extern void hpet_writel(unsigned int d, unsigned int a);
extern void force_hpet_resume(void);
+extern void hpet_set_comparator_periodic(int channel, unsigned int cmp,
+ unsigned int period);

#ifdef CONFIG_HPET_EMULATE_RTC

diff --git a/arch/x86/kernel/hpet.c b/arch/x86/kernel/hpet.c
index 326af9a55129..8be1d3d9162e 100644
--- a/arch/x86/kernel/hpet.c
+++ b/arch/x86/kernel/hpet.c
@@ -293,6 +293,39 @@ static void hpet_enable_legacy_int(void)
hpet_legacy_int_enabled = true;
}

+/**
+ * hpet_set_comparator_periodic() - Helper function to set periodic channel
+ * @channel: The HPET channel
+ * @cmp: The value to be written to the comparator/accumulator
+ * @period: Number of ticks per period
+ *
+ * Helper function for updating comparator, accumulator and period values.
+ *
+ * In periodic mode, HPET needs HPET_TN_SETVAL to be set before writing
+ * to the Tn_CMP to update the accumulator. Then, HPET needs a second
+ * write (with HPET_TN_SETVAL cleared) to Tn_CMP to set the period.
+ * The HPET_TN_SETVAL bit is automatically cleared after the first write.
+ *
+ * This function takes a 1 microsecond delay. However, this function is supposed
+ * to be called only once (or when reprogramming the timer) as it deals with a
+ * periodic timer channel.
+ *
+ * See the following documents:
+ * - Intel IA-PC HPET (High Precision Event Timers) Specification
+ * - AMD-8111 HyperTransport I/O Hub Data Sheet, Publication # 24674
+ */
+void hpet_set_comparator_periodic(int channel, unsigned int cmp, unsigned int period)
+{
+ unsigned int v = hpet_readl(HPET_Tn_CFG(channel));
+
+ hpet_writel(v | HPET_TN_SETVAL, HPET_Tn_CFG(channel));
+
+ hpet_writel(cmp, HPET_Tn_CMP(channel));
+
+ udelay(1);
+ hpet_writel(period, HPET_Tn_CMP(channel));
+}
+
static int hpet_clkevt_set_state_periodic(struct clock_event_device *evt)
{
unsigned int channel = clockevent_to_channel(evt)->num;
@@ -305,19 +338,11 @@ static int hpet_clkevt_set_state_periodic(struct clock_event_device *evt)
now = hpet_readl(HPET_COUNTER);
cmp = now + (unsigned int)delta;
cfg = hpet_readl(HPET_Tn_CFG(channel));
- cfg |= HPET_TN_ENABLE | HPET_TN_PERIODIC | HPET_TN_SETVAL |
- HPET_TN_32BIT;
+ cfg |= HPET_TN_ENABLE | HPET_TN_PERIODIC | HPET_TN_32BIT;
hpet_writel(cfg, HPET_Tn_CFG(channel));
- hpet_writel(cmp, HPET_Tn_CMP(channel));
- udelay(1);
- /*
- * HPET on AMD 81xx needs a second write (with HPET_TN_SETVAL
- * cleared) to T0_CMP to set the period. The HPET_TN_SETVAL
- * bit is automatically cleared after the first write.
- * (See AMD-8111 HyperTransport I/O Hub Data Sheet,
- * Publication # 24674)
- */
- hpet_writel((unsigned int)delta, HPET_Tn_CMP(channel));
+
+ hpet_set_comparator_periodic(channel, cmp, (unsigned int)delta);
+
hpet_start_counter();
hpet_print_config();

--
2.17.1