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