162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Copyright (C) 2006 Jim Cromie
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * This is a clocksource driver for the Geode SCx200's 1 or 27 MHz
662306a36Sopenharmony_ci * high-resolution timer.  The Geode SC-1100 (at least) has a buggy
762306a36Sopenharmony_ci * time stamp counter (TSC), which loses time unless 'idle=poll' is
862306a36Sopenharmony_ci * given as a boot-arg. In its absence, the Generic Timekeeping code
962306a36Sopenharmony_ci * will detect and de-rate the bad TSC, allowing this timer to take
1062306a36Sopenharmony_ci * over timekeeping duties.
1162306a36Sopenharmony_ci *
1262306a36Sopenharmony_ci * Based on work by John Stultz, and Ted Phelps (in a 2.6.12-rc6 patch)
1362306a36Sopenharmony_ci */
1462306a36Sopenharmony_ci
1562306a36Sopenharmony_ci#include <linux/clocksource.h>
1662306a36Sopenharmony_ci#include <linux/init.h>
1762306a36Sopenharmony_ci#include <linux/module.h>
1862306a36Sopenharmony_ci#include <linux/ioport.h>
1962306a36Sopenharmony_ci#include <linux/scx200.h>
2062306a36Sopenharmony_ci
2162306a36Sopenharmony_ci#define NAME "scx200_hrt"
2262306a36Sopenharmony_ci
2362306a36Sopenharmony_cistatic int mhz27;
2462306a36Sopenharmony_cimodule_param(mhz27, int, 0);	/* load time only */
2562306a36Sopenharmony_ciMODULE_PARM_DESC(mhz27, "count at 27.0 MHz (default is 1.0 MHz)");
2662306a36Sopenharmony_ci
2762306a36Sopenharmony_cistatic int ppm;
2862306a36Sopenharmony_cimodule_param(ppm, int, 0);	/* load time only */
2962306a36Sopenharmony_ciMODULE_PARM_DESC(ppm, "+-adjust to actual XO freq (ppm)");
3062306a36Sopenharmony_ci
3162306a36Sopenharmony_ci/* HiRes Timer configuration register address */
3262306a36Sopenharmony_ci#define SCx200_TMCNFG_OFFSET (SCx200_TIMER_OFFSET + 5)
3362306a36Sopenharmony_ci
3462306a36Sopenharmony_ci/* and config settings */
3562306a36Sopenharmony_ci#define HR_TMEN (1 << 0)	/* timer interrupt enable */
3662306a36Sopenharmony_ci#define HR_TMCLKSEL (1 << 1)	/* 1|0 counts at 27|1 MHz */
3762306a36Sopenharmony_ci#define HR_TM27MPD (1 << 2)	/* 1 turns off input clock (power-down) */
3862306a36Sopenharmony_ci
3962306a36Sopenharmony_ci/* The base timer frequency, * 27 if selected */
4062306a36Sopenharmony_ci#define HRT_FREQ   1000000
4162306a36Sopenharmony_ci
4262306a36Sopenharmony_cistatic u64 read_hrt(struct clocksource *cs)
4362306a36Sopenharmony_ci{
4462306a36Sopenharmony_ci	/* Read the timer value */
4562306a36Sopenharmony_ci	return (u64) inl(scx200_cb_base + SCx200_TIMER_OFFSET);
4662306a36Sopenharmony_ci}
4762306a36Sopenharmony_ci
4862306a36Sopenharmony_cistatic struct clocksource cs_hrt = {
4962306a36Sopenharmony_ci	.name		= "scx200_hrt",
5062306a36Sopenharmony_ci	.rating		= 250,
5162306a36Sopenharmony_ci	.read		= read_hrt,
5262306a36Sopenharmony_ci	.mask		= CLOCKSOURCE_MASK(32),
5362306a36Sopenharmony_ci	.flags		= CLOCK_SOURCE_IS_CONTINUOUS,
5462306a36Sopenharmony_ci	/* mult, shift are set based on mhz27 flag */
5562306a36Sopenharmony_ci};
5662306a36Sopenharmony_ci
5762306a36Sopenharmony_cistatic int __init init_hrt_clocksource(void)
5862306a36Sopenharmony_ci{
5962306a36Sopenharmony_ci	u32 freq;
6062306a36Sopenharmony_ci	/* Make sure scx200 has initialized the configuration block */
6162306a36Sopenharmony_ci	if (!scx200_cb_present())
6262306a36Sopenharmony_ci		return -ENODEV;
6362306a36Sopenharmony_ci
6462306a36Sopenharmony_ci	/* Reserve the timer's ISA io-region for ourselves */
6562306a36Sopenharmony_ci	if (!request_region(scx200_cb_base + SCx200_TIMER_OFFSET,
6662306a36Sopenharmony_ci			    SCx200_TIMER_SIZE,
6762306a36Sopenharmony_ci			    "NatSemi SCx200 High-Resolution Timer")) {
6862306a36Sopenharmony_ci		pr_warn("unable to lock timer region\n");
6962306a36Sopenharmony_ci		return -ENODEV;
7062306a36Sopenharmony_ci	}
7162306a36Sopenharmony_ci
7262306a36Sopenharmony_ci	/* write timer config */
7362306a36Sopenharmony_ci	outb(HR_TMEN | (mhz27 ? HR_TMCLKSEL : 0),
7462306a36Sopenharmony_ci	     scx200_cb_base + SCx200_TMCNFG_OFFSET);
7562306a36Sopenharmony_ci
7662306a36Sopenharmony_ci	freq = (HRT_FREQ + ppm);
7762306a36Sopenharmony_ci	if (mhz27)
7862306a36Sopenharmony_ci		freq *= 27;
7962306a36Sopenharmony_ci
8062306a36Sopenharmony_ci	pr_info("enabling scx200 high-res timer (%s MHz +%d ppm)\n", mhz27 ? "27":"1", ppm);
8162306a36Sopenharmony_ci
8262306a36Sopenharmony_ci	return clocksource_register_hz(&cs_hrt, freq);
8362306a36Sopenharmony_ci}
8462306a36Sopenharmony_ci
8562306a36Sopenharmony_cimodule_init(init_hrt_clocksource);
8662306a36Sopenharmony_ci
8762306a36Sopenharmony_ciMODULE_AUTHOR("Jim Cromie <jim.cromie@gmail.com>");
8862306a36Sopenharmony_ciMODULE_DESCRIPTION("clocksource on SCx200 HiRes Timer");
8962306a36Sopenharmony_ciMODULE_LICENSE("GPL");
90