[PATCH v3 2/2] clocksource: Add support for Xilinx AXI Timer
Sean Anderson
sean.anderson at seco.com
Tue May 11 12:19:03 PDT 2021
On 5/11/21 3:12 PM, Sean Anderson wrote:
> This adds generic clocksource and clockevent support for Xilinx LogiCORE IP
> AXI soft timers commonly found on Xilinx FPGAs. This timer is also the
> primary timer for Microblaze processors. This commit also adds support for
> configuring this timer as a PWM (though this could be split off if
> necessary). This whole driver lives in clocksource because it is primarily
> clocksource stuff now (even though it started out as a PWM driver). I think
> teasing apart the driver would not be worth it since they share so many
> functions.
>
> This driver configures timer 0 (which is always present) as a clocksource,
> and timer 1 (which might be missing) as a clockevent. I don't know if this
> is the correct priority for these timers, or whether we should be using a
> more dynamic allocation scheme.
>
> At the moment clock control is very basic: we just enable the clock during
> probe and pin the frequency. In the future, someone could add support for
> disabling the clock when not in use. Cascade mode is also unsupported.
>
> This driver was written with reference to Xilinx DS764 for v1.03.a [1].
>
> [1] https://www.xilinx.com/support/documentation/ip_documentation/axi_timer/v1_03_a/axi_timer_ds764.pdf
>
> Signed-off-by: Sean Anderson <sean.anderson at seco.com>
> ---
> Please let me know if I should organize this differently or if it should
> be broken up.
>
> Changes in v3:
> - Add clockevent and clocksource support
> - Rewrite probe to only use a device_node, since timers may need to be
> initialized before we have proper devices. This does bloat the code a bit
> since we can no longer rely on helpers such as dev_err_probe. We also
> cannot rely on device resources being free'd on failure, so we must free
> them manually.
> - We now access registers through xilinx_timer_(read|write). This allows us
> to deal with endianness issues, as originally seen in the microblaze
> driver. CAVEAT EMPTOR: I have not tested this on big-endian!
> - Remove old microblaze driver
>
> Changes in v2:
> - Don't compile this module by default for arm64
> - Add dependencies on COMMON_CLK and HAS_IOMEM
> - Add comment explaining why we depend on !MICROBLAZE
> - Add comment describing device
> - Rename TCSR_(SET|CLEAR) to TCSR_RUN_(SET|CLEAR)
> - Use NSEC_TO_SEC instead of defining our own
> - Use TCSR_RUN_MASK to check if the PWM is enabled, as suggested by Uwe
> - Cast dividends to u64 to avoid overflow
> - Check for over- and underflow when calculating TLR
> - Set xilinx_pwm_ops.owner
> - Don't set pwmchip.base to -1
> - Check range of xlnx,count-width
> - Ensure the clock is always running when the pwm is registered
> - Remove debugfs file :l
> - Report errors with dev_error_probe
>
> arch/microblaze/kernel/Makefile | 2 +-
> arch/microblaze/kernel/timer.c | 326 ---------------
> drivers/clocksource/Kconfig | 15 +
> drivers/clocksource/Makefile | 1 +
> drivers/clocksource/timer-xilinx.c | 650 +++++++++++++++++++++++++++++
> 5 files changed, 667 insertions(+), 327 deletions(-)
> delete mode 100644 arch/microblaze/kernel/timer.c
> create mode 100644 drivers/clocksource/timer-xilinx.c
>
> diff --git a/arch/microblaze/kernel/Makefile b/arch/microblaze/kernel/Makefile
> index 15a20eb814ce..986b1f21d90e 100644
> --- a/arch/microblaze/kernel/Makefile
> +++ b/arch/microblaze/kernel/Makefile
> @@ -17,7 +17,7 @@ extra-y := head.o vmlinux.lds
> obj-y += dma.o exceptions.o \
> hw_exception_handler.o irq.o \
> process.o prom.o ptrace.o \
> - reset.o setup.o signal.o sys_microblaze.o timer.o traps.o unwind.o
> + reset.o setup.o signal.o sys_microblaze.o traps.o unwind.o
>
> obj-y += cpu/
>
> diff --git a/arch/microblaze/kernel/timer.c b/arch/microblaze/kernel/timer.c
> deleted file mode 100644
> index f8832cf49384..000000000000
> --- a/arch/microblaze/kernel/timer.c
> +++ /dev/null
> @@ -1,326 +0,0 @@
> -/*
> - * Copyright (C) 2007-2013 Michal Simek <monstr at monstr.eu>
> - * Copyright (C) 2012-2013 Xilinx, Inc.
> - * Copyright (C) 2007-2009 PetaLogix
> - * Copyright (C) 2006 Atmark Techno, Inc.
> - *
> - * This file is subject to the terms and conditions of the GNU General Public
> - * License. See the file "COPYING" in the main directory of this archive
> - * for more details.
> - */
> -
> -#include <linux/interrupt.h>
> -#include <linux/delay.h>
> -#include <linux/sched.h>
> -#include <linux/sched/clock.h>
> -#include <linux/sched_clock.h>
> -#include <linux/clk.h>
> -#include <linux/clockchips.h>
> -#include <linux/of_address.h>
> -#include <linux/of_irq.h>
> -#include <linux/timecounter.h>
> -#include <asm/cpuinfo.h>
> -
> -static void __iomem *timer_baseaddr;
> -
> -static unsigned int freq_div_hz;
> -static unsigned int timer_clock_freq;
> -
> -#define TCSR0 (0x00)
> -#define TLR0 (0x04)
> -#define TCR0 (0x08)
> -#define TCSR1 (0x10)
> -#define TLR1 (0x14)
> -#define TCR1 (0x18)
> -
> -#define TCSR_MDT (1<<0)
> -#define TCSR_UDT (1<<1)
> -#define TCSR_GENT (1<<2)
> -#define TCSR_CAPT (1<<3)
> -#define TCSR_ARHT (1<<4)
> -#define TCSR_LOAD (1<<5)
> -#define TCSR_ENIT (1<<6)
> -#define TCSR_ENT (1<<7)
> -#define TCSR_TINT (1<<8)
> -#define TCSR_PWMA (1<<9)
> -#define TCSR_ENALL (1<<10)
> -
> -static unsigned int (*read_fn)(void __iomem *);
> -static void (*write_fn)(u32, void __iomem *);
> -
> -static void timer_write32(u32 val, void __iomem *addr)
> -{
> - iowrite32(val, addr);
> -}
> -
> -static unsigned int timer_read32(void __iomem *addr)
> -{
> - return ioread32(addr);
> -}
> -
> -static void timer_write32_be(u32 val, void __iomem *addr)
> -{
> - iowrite32be(val, addr);
> -}
> -
> -static unsigned int timer_read32_be(void __iomem *addr)
> -{
> - return ioread32be(addr);
> -}
> -
> -static inline void xilinx_timer0_stop(void)
> -{
> - write_fn(read_fn(timer_baseaddr + TCSR0) & ~TCSR_ENT,
> - timer_baseaddr + TCSR0);
> -}
> -
> -static inline void xilinx_timer0_start_periodic(unsigned long load_val)
> -{
> - if (!load_val)
> - load_val = 1;
> - /* loading value to timer reg */
> - write_fn(load_val, timer_baseaddr + TLR0);
> -
> - /* load the initial value */
> - write_fn(TCSR_LOAD, timer_baseaddr + TCSR0);
> -
> - /* see timer data sheet for detail
> - * !ENALL - don't enable 'em all
> - * !PWMA - disable pwm
> - * TINT - clear interrupt status
> - * ENT- enable timer itself
> - * ENIT - enable interrupt
> - * !LOAD - clear the bit to let go
> - * ARHT - auto reload
> - * !CAPT - no external trigger
> - * !GENT - no external signal
> - * UDT - set the timer as down counter
> - * !MDT0 - generate mode
> - */
> - write_fn(TCSR_TINT|TCSR_ENIT|TCSR_ENT|TCSR_ARHT|TCSR_UDT,
> - timer_baseaddr + TCSR0);
> -}
> -
> -static inline void xilinx_timer0_start_oneshot(unsigned long load_val)
> -{
> - if (!load_val)
> - load_val = 1;
> - /* loading value to timer reg */
> - write_fn(load_val, timer_baseaddr + TLR0);
> -
> - /* load the initial value */
> - write_fn(TCSR_LOAD, timer_baseaddr + TCSR0);
> -
> - write_fn(TCSR_TINT|TCSR_ENIT|TCSR_ENT|TCSR_ARHT|TCSR_UDT,
> - timer_baseaddr + TCSR0);
> -}
> -
> -static int xilinx_timer_set_next_event(unsigned long delta,
> - struct clock_event_device *dev)
> -{
> - pr_debug("%s: next event, delta %x\n", __func__, (u32)delta);
> - xilinx_timer0_start_oneshot(delta);
> - return 0;
> -}
> -
> -static int xilinx_timer_shutdown(struct clock_event_device *evt)
> -{
> - pr_info("%s\n", __func__);
> - xilinx_timer0_stop();
> - return 0;
> -}
> -
> -static int xilinx_timer_set_periodic(struct clock_event_device *evt)
> -{
> - pr_info("%s\n", __func__);
> - xilinx_timer0_start_periodic(freq_div_hz);
> - return 0;
> -}
> -
> -static struct clock_event_device clockevent_xilinx_timer = {
> - .name = "xilinx_clockevent",
> - .features = CLOCK_EVT_FEAT_ONESHOT |
> - CLOCK_EVT_FEAT_PERIODIC,
> - .shift = 8,
> - .rating = 300,
> - .set_next_event = xilinx_timer_set_next_event,
> - .set_state_shutdown = xilinx_timer_shutdown,
> - .set_state_periodic = xilinx_timer_set_periodic,
> -};
> -
> -static inline void timer_ack(void)
> -{
> - write_fn(read_fn(timer_baseaddr + TCSR0), timer_baseaddr + TCSR0);
> -}
> -
> -static irqreturn_t timer_interrupt(int irq, void *dev_id)
> -{
> - struct clock_event_device *evt = &clockevent_xilinx_timer;
> - timer_ack();
> - evt->event_handler(evt);
> - return IRQ_HANDLED;
> -}
> -
> -static __init int xilinx_clockevent_init(void)
> -{
> - clockevent_xilinx_timer.mult =
> - div_sc(timer_clock_freq, NSEC_PER_SEC,
> - clockevent_xilinx_timer.shift);
> - clockevent_xilinx_timer.max_delta_ns =
> - clockevent_delta2ns((u32)~0, &clockevent_xilinx_timer);
> - clockevent_xilinx_timer.max_delta_ticks = (u32)~0;
> - clockevent_xilinx_timer.min_delta_ns =
> - clockevent_delta2ns(1, &clockevent_xilinx_timer);
> - clockevent_xilinx_timer.min_delta_ticks = 1;
> - clockevent_xilinx_timer.cpumask = cpumask_of(0);
> - clockevents_register_device(&clockevent_xilinx_timer);
> -
> - return 0;
> -}
> -
> -static u64 xilinx_clock_read(void)
> -{
> - return read_fn(timer_baseaddr + TCR1);
> -}
> -
> -static u64 xilinx_read(struct clocksource *cs)
> -{
> - /* reading actual value of timer 1 */
> - return (u64)xilinx_clock_read();
> -}
> -
> -static struct timecounter xilinx_tc = {
> - .cc = NULL,
> -};
> -
> -static u64 xilinx_cc_read(const struct cyclecounter *cc)
> -{
> - return xilinx_read(NULL);
> -}
> -
> -static struct cyclecounter xilinx_cc = {
> - .read = xilinx_cc_read,
> - .mask = CLOCKSOURCE_MASK(32),
> - .shift = 8,
> -};
> -
> -static int __init init_xilinx_timecounter(void)
> -{
> - xilinx_cc.mult = div_sc(timer_clock_freq, NSEC_PER_SEC,
> - xilinx_cc.shift);
> -
> - timecounter_init(&xilinx_tc, &xilinx_cc, sched_clock());
> -
> - return 0;
> -}
> -
> -static struct clocksource clocksource_microblaze = {
> - .name = "xilinx_clocksource",
> - .rating = 300,
> - .read = xilinx_read,
> - .mask = CLOCKSOURCE_MASK(32),
> - .flags = CLOCK_SOURCE_IS_CONTINUOUS,
> -};
> -
> -static int __init xilinx_clocksource_init(void)
> -{
> - int ret;
> -
> - ret = clocksource_register_hz(&clocksource_microblaze,
> - timer_clock_freq);
> - if (ret) {
> - pr_err("failed to register clocksource");
> - return ret;
> - }
> -
> - /* stop timer1 */
> - write_fn(read_fn(timer_baseaddr + TCSR1) & ~TCSR_ENT,
> - timer_baseaddr + TCSR1);
> - /* start timer1 - up counting without interrupt */
> - write_fn(TCSR_TINT|TCSR_ENT|TCSR_ARHT, timer_baseaddr + TCSR1);
> -
> - /* register timecounter - for ftrace support */
> - return init_xilinx_timecounter();
> -}
> -
> -static int __init xilinx_timer_init(struct device_node *timer)
> -{
> - struct clk *clk;
> - static int initialized;
> - u32 irq;
> - u32 timer_num = 1;
> - int ret;
> -
> - if (initialized)
> - return -EINVAL;
> -
> - initialized = 1;
> -
> - timer_baseaddr = of_iomap(timer, 0);
> - if (!timer_baseaddr) {
> - pr_err("ERROR: invalid timer base address\n");
> - return -ENXIO;
> - }
> -
> - write_fn = timer_write32;
> - read_fn = timer_read32;
> -
> - write_fn(TCSR_MDT, timer_baseaddr + TCSR0);
> - if (!(read_fn(timer_baseaddr + TCSR0) & TCSR_MDT)) {
> - write_fn = timer_write32_be;
> - read_fn = timer_read32_be;
> - }
> -
> - irq = irq_of_parse_and_map(timer, 0);
> - if (irq <= 0) {
> - pr_err("Failed to parse and map irq");
> - return -EINVAL;
> - }
> -
> - of_property_read_u32(timer, "xlnx,one-timer-only", &timer_num);
> - if (timer_num) {
> - pr_err("Please enable two timers in HW\n");
> - return -EINVAL;
> - }
> -
> - pr_info("%pOF: irq=%d\n", timer, irq);
> -
> - clk = of_clk_get(timer, 0);
> - if (IS_ERR(clk)) {
> - pr_err("ERROR: timer CCF input clock not found\n");
> - /* If there is clock-frequency property than use it */
> - of_property_read_u32(timer, "clock-frequency",
> - &timer_clock_freq);
> - } else {
> - timer_clock_freq = clk_get_rate(clk);
> - }
> -
> - if (!timer_clock_freq) {
> - pr_err("ERROR: Using CPU clock frequency\n");
> - timer_clock_freq = cpuinfo.cpu_clock_freq;
> - }
> -
> - freq_div_hz = timer_clock_freq / HZ;
> -
> - ret = request_irq(irq, timer_interrupt, IRQF_TIMER, "timer",
> - &clockevent_xilinx_timer);
> - if (ret) {
> - pr_err("Failed to setup IRQ");
> - return ret;
> - }
> -
> - ret = xilinx_clocksource_init();
> - if (ret)
> - return ret;
> -
> - ret = xilinx_clockevent_init();
> - if (ret)
> - return ret;
> -
> - sched_clock_register(xilinx_clock_read, 32, timer_clock_freq);
> -
> - return 0;
> -}
> -
> -TIMER_OF_DECLARE(xilinx_timer, "xlnx,xps-timer-1.00.a",
> - xilinx_timer_init);
> diff --git a/drivers/clocksource/Kconfig b/drivers/clocksource/Kconfig
> index 39aa21d01e05..35c95671d242 100644
> --- a/drivers/clocksource/Kconfig
> +++ b/drivers/clocksource/Kconfig
> @@ -693,4 +693,19 @@ config MICROCHIP_PIT64B
> modes and high resolution. It is used as a clocksource
> and a clockevent.
>
> +config XILINX_TIMER
> + tristate "Xilinx AXI Timer support"
> + depends on HAS_IOMEM && COMMON_CLK
> + default y if MICROBLAZE
> + help
> + Clocksource, clockevent, and PWM drivers for Xilinx LogiCORE
> + IP AXI Timers. This timer is typically a soft core which may
> + be present in Xilinx FPGAs. This device may also be present in
> + Microblaze soft processors. If you don't have this IP in your
> + design, choose N.
> +
> + To use this device as the primary clocksource for your system,
> + choose Y here. Otherwise, this driver will not be available
> + early enough during boot. To compile this driver as a module,
> + choose M here: the module will be called timer-xilinx.
> endmenu
> diff --git a/drivers/clocksource/Makefile b/drivers/clocksource/Makefile
> index c17ee32a7151..717f01c0ac41 100644
> --- a/drivers/clocksource/Makefile
> +++ b/drivers/clocksource/Makefile
> @@ -58,6 +58,7 @@ obj-$(CONFIG_MILBEAUT_TIMER) += timer-milbeaut.o
> obj-$(CONFIG_SPRD_TIMER) += timer-sprd.o
> obj-$(CONFIG_NPCM7XX_TIMER) += timer-npcm7xx.o
> obj-$(CONFIG_RDA_TIMER) += timer-rda.o
> +obj-$(CONFIG_XILINX_TIMER) += timer-xilinx.o
>
> obj-$(CONFIG_ARC_TIMERS) += arc_timer.o
> obj-$(CONFIG_ARM_ARCH_TIMER) += arm_arch_timer.o
> diff --git a/drivers/clocksource/timer-xilinx.c b/drivers/clocksource/timer-xilinx.c
> new file mode 100644
> index 000000000000..b410c6af9c63
> --- /dev/null
> +++ b/drivers/clocksource/timer-xilinx.c
> @@ -0,0 +1,650 @@
> +// SPDX-License-Identifier: GPL-2.0+
> +/*
> + * Copyright (C) 2021 Sean Anderson <sean.anderson at seco.com>
> + *
> + * For Xilinx LogiCORE IP AXI Timer documentation, refer to DS764:
> + * https://www.xilinx.com/support/documentation/ip_documentation/axi_timer/v1_03_a/axi_timer_ds764.pdf
> + *
> + * Hardware limitations:
> + * - When in cascade mode we cannot read the full 64-bit counter in one go
> + * - When changing both duty cycle and period, we may end up with one cycle
> + * with the old duty cycle and the new period.
> + * - Cannot produce 100% duty cycle.
> + * - Only produces "normal" output.
> + */
> +#include <linux/bitfield.h>
> +#include <linux/clk.h>
> +#include <linux/clk-provider.h>
> +#include <linux/clockchips.h>
> +#include <linux/clocksource.h>
> +#include <linux/device.h>
> +#include <linux/interrupt.h>
> +#include <linux/module.h>
> +#include <linux/of_irq.h>
> +#include <linux/of_address.h>
> +#include <linux/platform_device.h>
> +#include <linux/pwm.h>
> +#include <linux/sched_clock.h>
> +#include <asm/io.h>
> +#if IS_ENABLED(CONFIG_MICROBLAZE)
> +#include <asm/cpuinfo.h>
> +#endif
> +
> +/* A replacement for dev_err_probe, since we don't always have a device */
> +#define xilinx_timer_err(np, err, fmt, ...) ({ \
> + pr_err("%pOF: error %d: " fmt, (np), (int)(err), ##__VA_ARGS__); \
> + err; \
> +})
> +
> +#define TCSR0 0x00
> +#define TLR0 0x04
> +#define TCR0 0x08
> +#define TCSR1 0x10
> +#define TLR1 0x14
> +#define TCR1 0x18
> +
> +#define TCSR_MDT BIT(0)
> +#define TCSR_UDT BIT(1)
> +#define TCSR_GENT BIT(2)
> +#define TCSR_CAPT BIT(3)
> +#define TCSR_ARHT BIT(4)
> +#define TCSR_LOAD BIT(5)
> +#define TCSR_ENIT BIT(6)
> +#define TCSR_ENT BIT(7)
> +#define TCSR_TINT BIT(8)
> +#define TCSR_PWMA BIT(9)
> +#define TCSR_ENALL BIT(10)
> +#define TCSR_CASC BIT(11)
> +
> +/*
> + * The idea here is to capture whether the PWM is actually running (e.g.
> + * because we or the bootloader set it up) and we need to be careful to ensure
> + * we don't cause a glitch. According to the device data sheet, to enable the
> + * PWM we need to
> + *
> + * - Set both timers to generate mode (MDT=1)
> + * - Set both timers to PWM mode (PWMA=1)
> + * - Enable the generate out signals (GENT=1)
> + *
> + * In addition,
> + *
> + * - The timer must be running (ENT=1)
> + * - The timer must auto-reload TLR into TCR (ARHT=1)
> + * - We must not be in the process of loading TLR into TCR (LOAD=0)
> + * - Cascade mode must be disabled (CASC=0)
> + *
> + * If any of these differ from usual, then the PWM is either disabled, or is
> + * running in a mode that this driver does not support.
> + */
> +#define TCSR_PWM_SET (TCSR_GENT | TCSR_ARHT | TCSR_ENT | TCSR_PWMA)
> +#define TCSR_PWM_CLEAR (TCSR_MDT | TCSR_LOAD)
> +#define TCSR_PWM_MASK (TCSR_PWM_SET | TCSR_PWM_CLEAR)
> +
> +/**
> + * struct xilinx_timer_priv - Private data for Xilinx AXI timer driver
> + * @cs: Clocksource device
> + * @ce: Clockevent device
> + * @pwm: PWM controller chip
> + * @clk: Parent clock
> + * @regs: Base address of this device
> + * @width: Width of the counters, in bits
> + * @XILINX_TIMER_ONE: We have only one timer.
> + * @XILINX_TIMER_PWM: Configured as a PWM.
> + * @XILINX_TIMER_CLK: We were missing a device tree clock and created our own
> + * @flags: Flags for what type of device we are
> + */
> +struct xilinx_timer_priv {
> + union {
> + struct {
> + struct clocksource cs;
> + struct clock_event_device ce;
> + };
> + struct pwm_chip pwm;
> + };
> + struct clk *clk;
> + void __iomem *regs;
> + u32 (*read)(const volatile void __iomem *addr);
> + void (*write)(u32 value, volatile void __iomem *addr);
> + unsigned int width;
> + enum {
> + XILINX_TIMER_ONE = BIT(0),
> + XILINX_TIMER_PWM = BIT(1),
> + XILINX_TIMER_CLK = BIT(2),
> + } flags;
> +};
> +
> +static inline struct xilinx_timer_priv
> +*xilinx_pwm_chip_to_priv(struct pwm_chip *chip)
> +{
> + return container_of(chip, struct xilinx_timer_priv, pwm);
> +}
> +
> +static inline struct xilinx_timer_priv
> +*xilinx_clocksource_to_priv(struct clocksource *cs)
> +{
> + return container_of(cs, struct xilinx_timer_priv, cs);
> +}
> +
> +static inline struct xilinx_timer_priv
> +*xilinx_clockevent_to_priv(struct clock_event_device *ce)
> +{
> + return container_of(ce, struct xilinx_timer_priv, ce);
> +}
> +
> +static u32 xilinx_ioread32be(const volatile void __iomem *addr)
> +{
> + return ioread32be(addr);
> +}
> +
> +static void xilinx_iowrite32be(u32 value, volatile void __iomem *addr)
> +{
> + iowrite32be(value, addr);
> +}
> +
> +static inline u32 xilinx_timer_read(struct xilinx_timer_priv *priv,
> + int offset)
> +{
> + return priv->read(priv->regs + offset);
> +}
> +
> +static inline void xilinx_timer_write(struct xilinx_timer_priv *priv,
> + u32 value, int offset)
> +{
> + priv->write(value, priv->regs + offset);
> +}
> +
> +static inline u64 xilinx_timer_max(struct xilinx_timer_priv *priv)
> +{
> + return BIT_ULL(priv->width) - 1;
> +}
> +
> +static int xilinx_timer_tlr_cycles(struct xilinx_timer_priv *priv, u32 *tlr,
> + u32 tcsr, u64 cycles)
> +{
> + u64 max_count = xilinx_timer_max(priv);
> +
> + if (cycles < 2 || cycles > max_count + 2)
> + return -ERANGE;
> +
> + if (tcsr & TCSR_UDT)
> + *tlr = cycles - 2;
> + else
> + *tlr = max_count - cycles + 2;
> +
> + return 0;
> +}
> +
> +static bool xilinx_timer_pwm_enabled(u32 tcsr0, u32 tcsr1)
> +{
> + return ((TCSR_PWM_MASK | TCSR_CASC) & tcsr0) == TCSR_PWM_SET &&
> + (TCSR_PWM_MASK & tcsr1) == TCSR_PWM_SET;
> +}
> +
> +static int xilinx_timer_tlr_period(struct xilinx_timer_priv *priv, u32 *tlr,
> + u32 tcsr, unsigned int period)
> +{
> + u64 cycles = DIV_ROUND_DOWN_ULL((u64)period * clk_get_rate(priv->clk),
> + NSEC_PER_SEC);
> +
> + return xilinx_timer_tlr_cycles(priv, tlr, tcsr, cycles);
> +}
> +
> +static unsigned int xilinx_timer_get_period(struct xilinx_timer_priv *priv,
> + u32 tlr, u32 tcsr)
> +{
> + u64 cycles;
> +
> + if (tcsr & TCSR_UDT)
> + cycles = tlr + 2;
> + else
> + cycles = xilinx_timer_max(priv) - tlr + 2;
> +
> + return DIV_ROUND_UP_ULL(cycles * NSEC_PER_SEC,
> + clk_get_rate(priv->clk));
> +}
> +
> +static int xilinx_pwm_apply(struct pwm_chip *chip, struct pwm_device *unused,
> + const struct pwm_state *state)
> +{
> + int ret;
> + struct xilinx_timer_priv *priv = xilinx_pwm_chip_to_priv(chip);
> + u32 tlr0, tlr1;
> + u32 tcsr0 = xilinx_timer_read(priv, TCSR0);
> + u32 tcsr1 = xilinx_timer_read(priv, TCSR1);
> + bool enabled = xilinx_timer_pwm_enabled(tcsr0, tcsr1);
> +
> + if (state->polarity != PWM_POLARITY_NORMAL)
> + return -EINVAL;
> +
> + ret = xilinx_timer_tlr_period(priv, &tlr0, tcsr0, state->period);
> + if (ret)
> + return ret;
> +
> + ret = xilinx_timer_tlr_period(priv, &tlr1, tcsr1, state->duty_cycle);
> + if (ret)
> + return ret;
> +
> + xilinx_timer_write(priv, tlr0, TLR0);
> + xilinx_timer_write(priv, tlr1, TLR1);
> +
> + if (state->enabled) {
> + /* Only touch the TCSRs if we aren't already running */
> + if (!enabled) {
> + /* Load TLR into TCR */
> + xilinx_timer_write(priv, tcsr0 | TCSR_LOAD, TCSR0);
> + xilinx_timer_write(priv, tcsr1 | TCSR_LOAD, TCSR1);
> + /* Enable timers all at once with ENALL */
> + tcsr0 = (TCSR_PWM_SET & ~TCSR_ENT) | (tcsr0 & TCSR_UDT);
> + tcsr1 = TCSR_PWM_SET | TCSR_ENALL | (tcsr1 & TCSR_UDT);
> + xilinx_timer_write(priv, tcsr0, TCSR0);
> + xilinx_timer_write(priv, tcsr1, TCSR1);
> + }
> + } else {
> + xilinx_timer_write(priv, 0, TCSR0);
> + xilinx_timer_write(priv, 0, TCSR1);
> + }
> +
> + return 0;
> +}
> +
> +static void xilinx_pwm_get_state(struct pwm_chip *chip,
> + struct pwm_device *unused,
> + struct pwm_state *state)
> +{
> + struct xilinx_timer_priv *priv = xilinx_pwm_chip_to_priv(chip);
> + u32 tlr0 = xilinx_timer_read(priv, TLR0);
> + u32 tlr1 = xilinx_timer_read(priv, TLR1);
> + u32 tcsr0 = xilinx_timer_read(priv, TCSR0);
> + u32 tcsr1 = xilinx_timer_read(priv, TCSR1);
> +
> + state->period = xilinx_timer_get_period(priv, tlr0, tcsr0);
> + state->duty_cycle = xilinx_timer_get_period(priv, tlr1, tcsr1);
> + state->enabled = xilinx_timer_pwm_enabled(tcsr0, tcsr1);
> + state->polarity = PWM_POLARITY_NORMAL;
> +}
> +
> +static const struct pwm_ops xilinx_pwm_ops = {
> + .apply = xilinx_pwm_apply,
> + .get_state = xilinx_pwm_get_state,
> + .owner = THIS_MODULE,
> +};
> +
> +static int xilinx_pwm_init(struct device *dev,
> + struct xilinx_timer_priv *priv)
> +{
> + int ret;
> +
> + if (!dev)
> + return -EPROBE_DEFER;
> +
> + priv->pwm.dev = dev;
> + priv->pwm.ops = &xilinx_pwm_ops;
> + priv->pwm.npwm = 1;
> + ret = pwmchip_add(&priv->pwm);
> + if (ret)
> + xilinx_timer_err(dev->of_node, ret,
> + "could not register pwm chip\n");
> + return ret;
> +}
> +
> +static irqreturn_t xilinx_timer_handler(int irq, void *dev)
> +{
> + struct xilinx_timer_priv *priv = dev;
> + u32 tcsr1 = xilinx_timer_read(priv, TCSR1);
> +
> + /* Acknowledge interrupt */
> + xilinx_timer_write(priv, tcsr1 | TCSR_TINT, TCSR1);
> + priv->ce.event_handler(&priv->ce);
> + return IRQ_HANDLED;
> +}
> +
> +static int xilinx_clockevent_next_event(unsigned long evt,
> + struct clock_event_device *ce)
> +{
> + struct xilinx_timer_priv *priv = xilinx_clockevent_to_priv(ce);
> +
> + xilinx_timer_write(priv, evt, TLR1);
> + xilinx_timer_write(priv, TCSR_LOAD, TCSR1);
> + xilinx_timer_write(priv, TCSR_ENIT | TCSR_ENT, TCSR1);
> + return 0;
> +}
> +
> +static int xilinx_clockevent_state_periodic(struct clock_event_device *ce)
> +{
> + int ret;
> + u32 tlr1;
> + struct xilinx_timer_priv *priv = xilinx_clockevent_to_priv(ce);
> +
> + ret = xilinx_timer_tlr_cycles(priv, &tlr1, 0,
> + clk_get_rate(priv->clk) / HZ);
> + if (ret)
> + return ret;
> +
> + xilinx_timer_write(priv, tlr1, TLR1);
> + xilinx_timer_write(priv, TCSR_LOAD, TCSR1);
> + xilinx_timer_write(priv, TCSR_ARHT | TCSR_ENIT | TCSR_ENT, TCSR1);
> + return 0;
> +}
> +
> +static int xilinx_clockevent_shutdown(struct clock_event_device *ce)
> +{
> + xilinx_timer_write(xilinx_clockevent_to_priv(ce), 0, TCSR1);
> + return 0;
> +}
> +
> +static const struct clock_event_device xilinx_clockevent_base = {
> + .name = "xilinx_clockevent",
> + .features = CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT,
> + .set_next_event = xilinx_clockevent_next_event,
> + .set_state_periodic = xilinx_clockevent_state_periodic,
> + .set_state_shutdown = xilinx_clockevent_shutdown,
> + .rating = 300,
> + .cpumask = cpu_possible_mask,
> + .owner = THIS_MODULE,
> +};
> +
> +static int xilinx_clockevent_init(struct device_node *np,
> + struct xilinx_timer_priv *priv)
> +{
> + int ret = of_irq_get(np, 0);
> +
> + if (ret < 0)
> + return xilinx_timer_err(np, ret, "could not get irq\n");
> +
> + ret = request_irq(ret, xilinx_timer_handler, IRQF_TIMER,
> + np->full_name, priv);
> + if (ret)
> + return xilinx_timer_err(np, ret, "could not request irq\n");
> +
> + memcpy(&priv->ce, &xilinx_clockevent_base, sizeof(priv->ce));
> + clockevents_config_and_register(&priv->ce,
> + clk_get_rate(priv->clk), 2,
> + min_t(u64,
> + xilinx_timer_max(priv) + 2,
> + ULONG_MAX));
> + return 0;
> +}
> +
> +static u64 xilinx_clocksource_read(struct clocksource *cs)
> +{
> + return xilinx_timer_read(xilinx_clocksource_to_priv(cs), TCR0);
> +}
> +
> +static const struct clocksource xilinx_clocksource_base = {
> + .read = xilinx_clocksource_read,
> + .name = "xilinx_clocksource",
> + .rating = 300,
> + .flags = CLOCK_SOURCE_IS_CONTINUOUS,
> + .owner = THIS_MODULE,
> +};
> +
> +static int xilinx_clocksource_init(struct xilinx_timer_priv *priv)
> +{
> + xilinx_timer_write(priv, 0, TLR0);
> + /* Load TLR and clear any interrupts */
> + xilinx_timer_write(priv, TCSR_LOAD | TCSR_TINT, TCSR0);
> + /* Start the timer counting up with auto-reload */
> + xilinx_timer_write(priv, TCSR_ARHT | TCSR_ENT, TCSR0);
> +
> + memcpy(&priv->cs, &xilinx_clocksource_base, sizeof(priv->cs));
> + priv->cs.mask = xilinx_timer_max(priv);
> + return clocksource_register_hz(&priv->cs, clk_get_rate(priv->clk));
> +}
> +
> +static struct clk *xilinx_timer_clock_init(struct device_node *np,
> + struct xilinx_timer_priv *priv)
> +{
> + int ret;
> + u32 freq;
> + struct clk_hw *hw;
> + struct clk *clk = of_clk_get_by_name(np, "s_axi_aclk");
> +
> + if (!IS_ERR(clk) || PTR_ERR(clk) == -EPROBE_DEFER)
> + return clk;
> +
> + pr_warn("%pOF: missing s_axi_aclk, falling back to clock-frequency\n",
> + np);
> + ret = of_property_read_u32(np, "clock-frequency", &freq);
> + if (ret) {
> +#if IS_ENABLED(CONFIG_MICROBLAZE)
> + pr_warn("%pOF: missing clock-frequency, falling back to /cpus/timebase-frequency\n",
> + np);
> + freq = cpuinfo.cpu_clock_freq;
> +#else
> + return ERR_PTR(ret);
> +#endif
> + }
> +
> + priv->flags |= XILINX_TIMER_CLK;
> + hw = __clk_hw_register_fixed_rate(NULL, np, "s_axi_aclk", NULL, NULL,
> + NULL, 0, freq, 0, 0);
> + if (IS_ERR(hw))
> + return ERR_CAST(hw);
> + return hw->clk;
> +}
> +
> +static struct xilinx_timer_priv *xilinx_timer_init(struct device *dev,
> + struct device_node *np)
> +{
> + bool pwm;
> + int i, ret;
> + struct xilinx_timer_priv *priv;
> + u32 one_timer, tcsr0;
> +
> + priv = kzalloc(sizeof(*priv), GFP_KERNEL);
> + if (!priv)
> + return ERR_PTR(-ENOMEM);
> +
> + priv->regs = of_iomap(np, 0);
> + if (!priv->regs) {
> + ret = -ENXIO;
> + goto err_priv;
> + } else if (IS_ERR(priv->regs)) {
> + ret = PTR_ERR(priv->regs);
> + goto err_priv;
> + }
> +
> + priv->read = ioread32;
> + priv->write = iowrite32;
> + /*
> + * We aren't using the interrupts yet, so use ENIT to detect endianness
> + */
> + tcsr0 = xilinx_timer_read(priv, TCSR0);
> + if (swab32(tcsr0) & TCSR_ENIT) {
> + ret = xilinx_timer_err(np, -EOPNOTSUPP,
> + "cannot determine endianness\n");
> + goto err_priv;
> + }
> +
> + xilinx_timer_write(priv, tcsr0 | TCSR_ENIT, TCSR0);
> + if (!(xilinx_timer_read(priv, TCSR0) & TCSR_ENIT)) {
> + priv->read = xilinx_ioread32be;
> + priv->write = xilinx_iowrite32be;
> + }
> +
> + /*
> + * For backwards compatibility, allow xlnx,one-timer-only = <bool>;
> + * However, the preferred way is to use the xlnx,single-timer flag.
> + */
> + one_timer = of_property_read_bool(np, "xlnx,single-timer");
> + if (!one_timer) {
> + ret = of_property_read_u32(np, "xlnx,one-timer-only", &one_timer);
> + if (ret) {
> + ret = xilinx_timer_err(np, ret, "xlnx,one-timer-only");
> + goto err_priv;
> + }
> + }
> +
> + pwm = of_property_read_bool(np, "xlnx,pwm");
> + if (one_timer && pwm) {
> + ret = xilinx_timer_err(np, -EINVAL,
> + "pwm mode not possible with one timer\n");
> + goto err_priv;
> + }
> +
> + priv->flags = FIELD_PREP(XILINX_TIMER_ONE, one_timer) |
> + FIELD_PREP(XILINX_TIMER_PWM, pwm);
> +
> + for (i = 0; pwm && i < 2; i++) {
> + char int_fmt[] = "xlnx,gen%u-assert";
> + char bool_fmt[] = "xlnx,gen%u-active-low";
> + char buf[max(sizeof(int_fmt), sizeof(bool_fmt))];
> + u32 gen;
> +
> + /*
> + * Allow xlnx,gen?-assert = <bool>; for backwards
> + * compatibility. However, the preferred way is to use the
> + * xlnx,gen?-active-low flag.
> + */
> + snprintf(buf, sizeof(buf), bool_fmt, i);
> + gen = !of_property_read_bool(np, buf);
> + if (gen) {
> + snprintf(buf, sizeof(buf), int_fmt, i);
> + ret = of_property_read_u32(np, buf, &gen);
> + if (ret && ret != -EINVAL) {
> + xilinx_timer_err(np, ret, "%s\n", buf);
> + goto err_priv;
> + }
> + }
> +
> + if (!gen) {
> + ret = xilinx_timer_err(np, -EINVAL,
> + "generateout%u must be active high\n",
> + i);
> + goto err_priv;
> + }
> + }
> +
> + ret = of_property_read_u32(np, "xlnx,count-width", &priv->width);
> + if (ret) {
> + xilinx_timer_err(np, ret, "xlnx,count-width\n");
> + goto err_priv;
> + } else if (priv->width < 8 || priv->width > 32) {
> + ret = xilinx_timer_err(np, -EINVAL, "invalid counter width\n");
> + goto err_priv;
> + }
> +
> + priv->clk = xilinx_timer_clock_init(np, priv);
> + if (IS_ERR(priv->clk)) {
> + ret = xilinx_timer_err(np, PTR_ERR(priv->clk), "clock\n");
> + goto err_priv;
> + }
> +
> + ret = clk_prepare_enable(priv->clk);
> + if (ret) {
> + xilinx_timer_err(np, ret, "clock enable failed\n");
> + goto err_clk;
> + }
> + clk_rate_exclusive_get(priv->clk);
> +
> + if (pwm) {
> + ret = xilinx_pwm_init(dev, priv);
> + } else {
> + ret = xilinx_clocksource_init(priv);
> + if (!ret && !one_timer) {
> + ret = xilinx_clockevent_init(np, priv);
> + if (ret)
> + priv->flags |= XILINX_TIMER_ONE;
> + }
> + }
> +
> + if (!ret)
> + return priv;
> +
> + clk_rate_exclusive_put(priv->clk);
> + clk_disable_unprepare(priv->clk);
> +err_clk:
> + if (priv->flags & XILINX_TIMER_CLK)
> + clk_unregister_fixed_rate(priv->clk);
> + else
> + clk_put(priv->clk);
> +err_priv:
> + kfree(priv);
> + return ERR_PTR(ret);
> +}
> +
> +static int xilinx_timer_probe(struct platform_device *pdev)
> +{
> + struct xilinx_timer_priv *priv =
> + xilinx_timer_init(&pdev->dev, pdev->dev.of_node);
> +
> + if (IS_ERR(priv))
> + return PTR_ERR(priv);
> +
> + platform_set_drvdata(pdev, priv);
> + return 0;
> +}
> +
> +static int xilinx_timer_remove(struct platform_device *pdev)
> +{
> + struct xilinx_timer_priv *priv = platform_get_drvdata(pdev);
> +
> + if (IS_ENABLED(CONFIG_XILINX_PWM) && priv->flags & XILINX_TIMER_PWM) {
> + pwmchip_remove(&priv->pwm);
> + } else {
> + if (!(priv->flags & XILINX_TIMER_ONE)) {
> + int cpu;
> +
> + for_each_cpu(cpu, priv->ce.cpumask)
> + clockevents_unbind_device(&priv->ce, cpu);
> + }
> + clocksource_unregister(&priv->cs);
> + }
> +
> + clk_rate_exclusive_put(priv->clk);
> + clk_disable_unprepare(priv->clk);
> + if (priv->flags & XILINX_TIMER_CLK)
> + clk_unregister_fixed_rate(priv->clk);
> + else
> + clk_put(priv->clk);
> + return 0;
> +}
> +
> +static const struct of_device_id xilinx_timer_of_match[] = {
> + { .compatible = "xlnx,xps-timer-1.00.a", },
> + { .compatible = "xlnx,axi-timer-2.0" },
> + {},
> +};
> +MODULE_DEVICE_TABLE(of, xilinx_timer_of_match);
> +
> +static struct platform_driver xilinx_timer_driver = {
> + .probe = xilinx_timer_probe,
> + .remove = xilinx_timer_remove,
> + .driver = {
> + .name = "xilinx-timer",
> + .of_match_table = of_match_ptr(xilinx_timer_of_match),
> + },
> +};
> +module_platform_driver(xilinx_timer_driver);
> +
> +static struct xilinx_timer_priv *xilinx_sched = (void *)-EAGAIN;
> +
> +static u64 xilinx_sched_read(void)
> +{
> + return xilinx_timer_read(xilinx_sched, TCSR0);
This should be TCR0.
--Sean
> +}
> +
> +static int __init xilinx_timer_register(struct device_node *np)
> +{
> + struct xilinx_timer_priv *priv;
> +
> + if (xilinx_sched != ERR_PTR(-EAGAIN))
> + return -EPROBE_DEFER;
> +
> + priv = xilinx_timer_init(NULL, np);
> + if (IS_ERR(priv))
> + return PTR_ERR(priv);
> + of_node_set_flag(np, OF_POPULATED);
> +
> + xilinx_sched = priv;
> + sched_clock_register(xilinx_sched_read, priv->width,
> + clk_get_rate(priv->clk));
> + return 0;
> +}
> +
> +TIMER_OF_DECLARE(xilinx_xps_timer, "xlnx,xps-timer-1.00.a", xilinx_timer_register);
> +TIMER_OF_DECLARE(xilinx_axi_timer, "xlnx,axi-timer-2.0", xilinx_timer_register);
> +
> +MODULE_ALIAS("platform:xilinx-timer");
> +MODULE_DESCRIPTION("Xilinx LogiCORE IP AXI Timer driver");
> +MODULE_LICENSE("GPL v2");
>
More information about the linux-arm-kernel
mailing list