From 995cf4dba0b01af66ed94fff0b485a75fdeb9c16 Mon Sep 17 00:00:00 2001 From: Alessandro Rubini <rubini@gnudd.com> Date: Wed, 3 Dec 2014 15:47:19 +0100 Subject: [PATCH] kernel: add a clocksource module for WR time Signed-off-by: Alessandro Rubini <rubini@gnudd.com> --- kernel/Makefile | 2 +- kernel/wr_clocksource/Makefile | 25 ++++ kernel/wr_clocksource/wr_clocksource.c | 173 +++++++++++++++++++++++++ 3 files changed, 199 insertions(+), 1 deletion(-) create mode 100644 kernel/wr_clocksource/Makefile create mode 100644 kernel/wr_clocksource/wr_clocksource.c diff --git a/kernel/Makefile b/kernel/Makefile index 7534f5d89..b04218a4e 100644 --- a/kernel/Makefile +++ b/kernel/Makefile @@ -1,5 +1,5 @@ -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 diff --git a/kernel/wr_clocksource/Makefile b/kernel/wr_clocksource/Makefile new file mode 100644 index 000000000..f96375737 --- /dev/null +++ b/kernel/wr_clocksource/Makefile @@ -0,0 +1,25 @@ +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 diff --git a/kernel/wr_clocksource/wr_clocksource.c b/kernel/wr_clocksource/wr_clocksource.c new file mode 100644 index 000000000..9a2a1508c --- /dev/null +++ b/kernel/wr_clocksource/wr_clocksource.c @@ -0,0 +1,173 @@ +/* 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; +} + -- GitLab