Commit 995cf4db authored by Alessandro Rubini's avatar Alessandro Rubini

kernel: add a clocksource module for WR time

Signed-off-by: Alessandro Rubini's avatarAlessandro Rubini <rubini@gnudd.com>
parent d1f405a9
DIRS = wr_vic wr_nic wr_rtu at91_softpwm wr_pstats
DIRS = wr_vic wr_nic wr_rtu at91_softpwm wr_pstats wr_clocksource
# We may "LINUX ?= /usr/src/linux-wrswitch", but it's better to leave it empty
......
obj-m := wr_clocksource.o
# accept WRN_DEBUG from the environment. It turns pr_debug() into printk.
ifdef WRN_DEBUG
ccflags-y += -DDEBUG
endif
# What follows is standard stuff
export ARCH ?= arm
export CROSS_COMPILE ?= $(CROSS_COMPILE_ARM)
all modules:
$(MAKE) CONFIG_DEBUG_SECTION_MISMATCH=y \
-C $(LINUX) SUBDIRS=$(shell /bin/pwd) modules
# looking at preprocessed output is helpful for bug hunting
preprocess:
$(MAKE) CONFIG_DEBUG_SECTION_MISMATCH=y \
-C $(LINUX) SUBDIRS=$(shell /bin/pwd) $(wr-nic-objs:.o=.i)
# We might "$(MAKE) -C $(LINUX)" but "make clean" with no LINUX defined
# is sometimes useful to have
clean:
rm -f *.mod.c *.o *.ko *.i .*cmd Module.symvers modules.order *~
rm -rf .tmp_versions
/* Alessandro Rubini for CERN 2014, GPLv2 or later */
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/init.h>
#include <linux/timer.h>
#include <linux/jiffies.h>
#include <linux/vmalloc.h>
#include <linux/clocksource.h>
/* We need these two defined in order to include nic-hardware.h */
#define WR_IS_NODE 0
#define WR_IS_SWITCH 1
/* We have no centralized defines yet: pick frequency and registers base */
#include "../wr_nic/nic-hardware.h"
#define WRCS_FREQUENCY REFCLK_FREQ
#define WRCS_TICK_NS (NSEC_PER_SEC / WRCS_FREQUENCY)
static int wrcs_stats;
module_param(wrcs_stats, int, 0644);
MODULE_PARM_DESC(wrcs_stats, "Count how often the clocksource is being read");
static __iomem struct PPSG_WB *wrcs_ppsg;
static int wrcs_is_registered; /* no need for atomic_t or whatever */
/* If so requested, print statistics once per second */
static inline void wrcs_do_stats(void)
{
static unsigned long nextp;
static int ncalls;
if (!wrcs_stats)
return;
if (!nextp)
nextp = jiffies + HZ;
/* This, when enabled, shows around 400 calls per second */
if (time_after_eq(jiffies, nextp)) {
pr_info("%s: called %i times\n", __func__,
ncalls);
ncalls = 0;
nextp += HZ;
}
ncalls++;
}
static cycle_t wrcs_read(struct clocksource *cs)
{
static uint32_t offset, last, this;
wrcs_do_stats();
/* FIXME: identify a time jump by monitoring the tick counter */
/*
* Turn the counter into a 32-bit one (see cs->mask below).
* We reset at 0x3b9aca0, so without this we should use mask = 0x1f
* and mac_idle = 32 ticks = 512ns. Unaffordable.
*/
this = readl(&wrcs_ppsg->CNTR_NSEC);
if (this < last)
offset += WRCS_FREQUENCY;
last = this;
return offset + this;
}
static struct clocksource wrcs_cs = {
.name = "white-rabbit",
.rating = 450, /* perfect... */
.read = wrcs_read,
/* no enable/disable */
.mask = 0xffffffff, /* We fake a 32-bit thing */
.max_idle_ns = 900 * 1000 * 1000, /* well, 1s... */
.flags = CLOCK_SOURCE_IS_CONTINUOUS,
};
/*
* The timer is used to check when does WR synchronize. When that
* happens, we set time of day and register our clocksource. Time
* jumps after synchronization are not well supported.
*/
static void wrcs_timer_fn(unsigned long unused);
static DEFINE_TIMER(wrcs_timer, wrcs_timer_fn, 0, 0);
static void wrcs_timer_fn(unsigned long unused)
{
uint32_t ticks, tai_l, tai_h;
int64_t tai;
/* Read ppsg, all fields consistently se we can use the value */
do {
tai_l = readl(&wrcs_ppsg->CNTR_UTCLO);
tai_h = readl(&wrcs_ppsg->CNTR_UTCHI);
ticks = readl(&wrcs_ppsg->CNTR_NSEC);
} while (readl(&wrcs_ppsg->CNTR_UTCLO) != tai_l);
tai = (typeof(tai))tai_h << 32 | tai_l;
/* If we are before 2010 (date +%s --date=2010-01-01), try again */
if (tai < 1262300400LL) {
mod_timer(&wrcs_timer, jiffies + HZ);
return;
}
clocksource_register(&wrcs_cs);
wrcs_is_registered = 1;
/* And don't restart the timer */
}
static int wrcs_init(void)
{
wrcs_ppsg = ioremap(FPGA_BASE_PPSG, FPGA_SIZE_PPSG);
if (!wrcs_ppsg) {
pr_err("WR Clocksource: can't remap PPS registers\n");
return -EIO;
}
clocksource_calc_mult_shift(&wrcs_cs, WRCS_FREQUENCY, 1);
/* Fire the timer */
mod_timer(&wrcs_timer, jiffies + HZ);
return 0;
}
static void wrcs_exit(void)
{
del_timer_sync(&wrcs_timer);
if (wrcs_is_registered)
clocksource_unregister(&wrcs_cs);
iounmap(wrcs_ppsg);
}
module_init(wrcs_init);
module_exit(wrcs_exit);
MODULE_LICENSE("GPL");
/* Hack: this is not exported by current kernel. Define a local copy */
void
clocks_calc_mult_shift(u32 *mult, u32 *shift, u32 from, u32 to, u32 maxsec)
{
u64 tmp;
u32 sft, sftacc= 32;
/*
* Calculate the shift factor which is limiting the conversion
* range:
*/
tmp = ((u64)maxsec * from) >> 32;
while (tmp) {
tmp >>=1;
sftacc--;
}
/*
* Find the conversion shift/mult pair which has the best
* accuracy and fits the maxsec conversion range:
*/
for (sft = 32; sft > 0; sft--) {
tmp = (u64) to << sft;
tmp += from / 2;
do_div(tmp, from);
if ((tmp >> sftacc) == 0)
break;
}
*mult = tmp;
*shift = sft;
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment