timer: Add driver for Nomadik Multi Timer Unit (MTU)

The Nomadik Multi Timer Unit (MTU) provides 4 decrementing
free-running timers. It is used in ST-Ericsson Ux500 SoCs.

The driver uses the first timer to implement UCLASS_TIMER.

Signed-off-by: Stephan Gerhold <stephan@gerhold.net>
Reviewed-by: Linus Walleij <linus.walleij@linaro.org>
diff --git a/drivers/timer/Kconfig b/drivers/timer/Kconfig
index 96cc492..6370244 100644
--- a/drivers/timer/Kconfig
+++ b/drivers/timer/Kconfig
@@ -127,6 +127,15 @@
 	  hardware ways, nor got from device tree at the time when device
 	  tree is not available yet.
 
+config NOMADIK_MTU_TIMER
+	bool "Nomadik MTU Timer"
+	depends on TIMER
+	help
+	  Enables support for the Nomadik Multi Timer Unit (MTU),
+	  used in ST-Ericsson Ux500 SoCs.
+	  The MTU provides 4 decrementing free-running timers.
+	  At the moment, only the first timer is used by the driver.
+
 config OMAP_TIMER
 	bool "Omap timer support"
 	depends on TIMER
diff --git a/drivers/timer/Makefile b/drivers/timer/Makefile
index fa35bea..c22ffeb 100644
--- a/drivers/timer/Makefile
+++ b/drivers/timer/Makefile
@@ -12,6 +12,7 @@
 obj-$(CONFIG_CADENCE_TTC_TIMER)	+= cadence-ttc.o
 obj-$(CONFIG_DESIGNWARE_APB_TIMER)	+= dw-apb-timer.o
 obj-$(CONFIG_MPC83XX_TIMER) += mpc83xx_timer.o
+obj-$(CONFIG_NOMADIK_MTU_TIMER)	+= nomadik-mtu-timer.o
 obj-$(CONFIG_OMAP_TIMER)	+= omap-timer.o
 obj-$(CONFIG_RENESAS_OSTM_TIMER) += ostm_timer.o
 obj-$(CONFIG_RISCV_TIMER) += riscv_timer.o
diff --git a/drivers/timer/nomadik-mtu-timer.c b/drivers/timer/nomadik-mtu-timer.c
new file mode 100644
index 0000000..8648f1f
--- /dev/null
+++ b/drivers/timer/nomadik-mtu-timer.c
@@ -0,0 +1,114 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2019 Stephan Gerhold <stephan@gerhold.net>
+ *
+ * Based on arch/arm/cpu/armv7/u8500/timer.c:
+ * Copyright (C) 2010 Linaro Limited
+ * John Rigby <john.rigby@linaro.org>
+ *
+ * Based on Linux kernel source and internal ST-Ericsson U-Boot source:
+ * Copyright (C) 2009 Alessandro Rubini
+ * Copyright (C) 2010 ST-Ericsson
+ * Copyright (C) 2010 Linus Walleij for ST-Ericsson
+ */
+
+#include <common.h>
+#include <dm.h>
+#include <timer.h>
+#include <asm/io.h>
+
+#define MTU_NUM_TIMERS		4
+
+/* The timers */
+struct nomadik_mtu_timer_regs {
+	u32 lr;		/* Load register */
+	u32 cv;		/* Current value */
+	u32 cr;		/* Control register */
+	u32 bglr;	/* Background load register */
+};
+
+/* The MTU that contains the timers */
+struct nomadik_mtu_regs {
+	u32 imsc;	/* Interrupt mask set/clear */
+	u32 ris;	/* Raw interrupt status */
+	u32 mis;	/* Masked interrupt status */
+	u32 icr;	/* Interrupt clear register */
+
+	struct nomadik_mtu_timer_regs timers[MTU_NUM_TIMERS];
+};
+
+/* Bits for the control register */
+#define MTU_CR_ONESHOT		BIT(0)	/* if 0 = wraps reloading from BGLR */
+#define MTU_CR_32BITS		BIT(1)	/* if 0 = 16-bit counter */
+
+#define MTU_CR_PRESCALE_SHIFT	2
+#define MTU_CR_PRESCALE_1	(0 << MTU_CR_PRESCALE_SHIFT)
+#define MTU_CR_PRESCALE_16	(1 << MTU_CR_PRESCALE_SHIFT)
+#define MTU_CR_PRESCALE_256	(2 << MTU_CR_PRESCALE_SHIFT)
+
+#define MTU_CR_PERIODIC		BIT(6)	/* if 0 = free-running */
+#define MTU_CR_ENABLE		BIT(7)
+
+struct nomadik_mtu_priv {
+	struct nomadik_mtu_timer_regs *timer;
+};
+
+static int nomadik_mtu_get_count(struct udevice *dev, u64 *count)
+{
+	struct nomadik_mtu_priv *priv = dev_get_priv(dev);
+
+	/* Decrementing counter: invert the value */
+	*count = timer_conv_64(~readl(&priv->timer->cv));
+
+	return 0;
+}
+
+static int nomadik_mtu_probe(struct udevice *dev)
+{
+	struct timer_dev_priv *uc_priv = dev_get_uclass_priv(dev);
+	struct nomadik_mtu_priv *priv = dev_get_priv(dev);
+	struct nomadik_mtu_regs *mtu;
+	fdt_addr_t addr;
+	u32 prescale;
+
+	addr = dev_read_addr(dev);
+	if (addr == FDT_ADDR_T_NONE)
+		return -EINVAL;
+
+	mtu = (struct nomadik_mtu_regs *)addr;
+	priv->timer = mtu->timers; /* Use first timer */
+
+	if (!uc_priv->clock_rate)
+		return -EINVAL;
+
+	/* Use divide-by-16 counter if tick rate is more than 32 MHz */
+	if (uc_priv->clock_rate > 32000000) {
+		uc_priv->clock_rate /= 16;
+		prescale = MTU_CR_PRESCALE_16;
+	} else {
+		prescale = MTU_CR_PRESCALE_1;
+	}
+
+	/* Configure a free-running, auto-wrap counter with selected prescale */
+	writel(MTU_CR_ENABLE | prescale | MTU_CR_32BITS, &priv->timer->cr);
+
+	return 0;
+}
+
+static const struct timer_ops nomadik_mtu_ops = {
+	.get_count = nomadik_mtu_get_count,
+};
+
+static const struct udevice_id nomadik_mtu_ids[] = {
+	{ .compatible = "st,nomadik-mtu" },
+	{}
+};
+
+U_BOOT_DRIVER(nomadik_mtu) = {
+	.name = "nomadik_mtu",
+	.id = UCLASS_TIMER,
+	.of_match = nomadik_mtu_ids,
+	.priv_auto_alloc_size = sizeof(struct nomadik_mtu_priv),
+	.probe = nomadik_mtu_probe,
+	.ops = &nomadik_mtu_ops,
+};