iMX28: Initial support for iMX28 CPU

This patch supports:
- Timers
- Debug UART
- Clock

Signed-off-by: Marek Vasut <marek.vasut@gmail.com>
Cc: Stefano Babic <sbabic@denx.de>
Cc: Wolfgang Denk <wd@denx.de>
Cc: Detlev Zundel <dzu@denx.de>
diff --git a/arch/arm/cpu/arm926ejs/mx28/Makefile b/arch/arm/cpu/arm926ejs/mx28/Makefile
new file mode 100644
index 0000000..98504f9
--- /dev/null
+++ b/arch/arm/cpu/arm926ejs/mx28/Makefile
@@ -0,0 +1,46 @@
+#
+# (C) Copyright 2000-2006
+# Wolfgang Denk, DENX Software Engineering, wd@denx.de.
+#
+# See file CREDITS for list of people who contributed to this
+# project.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License as
+# published by the Free Software Foundation; either version 2 of
+# the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston,
+# MA 02111-1307 USA
+#
+
+include $(TOPDIR)/config.mk
+
+LIB	= $(obj)lib$(SOC).o
+
+COBJS	= clock.o mx28.o timer.o
+
+SRCS	:= $(START:.o=.S) $(COBJS:.o=.c)
+OBJS	:= $(addprefix $(obj),$(COBJS))
+START	:= $(addprefix $(obj),$(START))
+
+all:	$(obj).depend $(LIB)
+
+$(LIB):	$(OBJS)
+	$(call cmd_link_o_target, $(OBJS))
+
+#########################################################################
+
+# defines $(obj).depend target
+include $(SRCTREE)/rules.mk
+
+sinclude $(obj).depend
+
+#########################################################################
diff --git a/arch/arm/cpu/arm926ejs/mx28/clock.c b/arch/arm/cpu/arm926ejs/mx28/clock.c
new file mode 100644
index 0000000..f698506
--- /dev/null
+++ b/arch/arm/cpu/arm926ejs/mx28/clock.c
@@ -0,0 +1,355 @@
+/*
+ * Freescale i.MX28 clock setup code
+ *
+ * Copyright (C) 2011 Marek Vasut <marek.vasut@gmail.com>
+ * on behalf of DENX Software Engineering GmbH
+ *
+ * Based on code from LTIB:
+ * Copyright (C) 2010 Freescale Semiconductor, Inc.
+ *
+ * See file CREDITS for list of people who contributed to this
+ * project.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston,
+ * MA 02111-1307 USA
+ */
+
+#include <common.h>
+#include <asm/errno.h>
+#include <asm/io.h>
+#include <asm/arch/clock.h>
+#include <asm/arch/imx-regs.h>
+
+/* The PLL frequency is always 480MHz, see section 10.2 in iMX28 datasheet. */
+#define	PLL_FREQ_KHZ	480000
+#define	PLL_FREQ_COEF	18
+/* The XTAL frequency is always 24MHz, see section 10.2 in iMX28 datasheet. */
+#define	XTAL_FREQ_KHZ	24000
+
+#define	PLL_FREQ_MHZ	(PLL_FREQ_KHZ / 1000)
+#define	XTAL_FREQ_MHZ	(XTAL_FREQ_KHZ / 1000)
+
+static uint32_t mx28_get_pclk(void)
+{
+	struct mx28_clkctrl_regs *clkctrl_regs =
+		(struct mx28_clkctrl_regs *)MXS_CLKCTRL_BASE;
+
+	uint32_t clkctrl, clkseq, clkfrac;
+	uint32_t frac, div;
+
+	clkctrl = readl(&clkctrl_regs->hw_clkctrl_cpu);
+
+	/* No support of fractional divider calculation */
+	if (clkctrl &
+		(CLKCTRL_CPU_DIV_XTAL_FRAC_EN | CLKCTRL_CPU_DIV_CPU_FRAC_EN)) {
+		return 0;
+	}
+
+	clkseq = readl(&clkctrl_regs->hw_clkctrl_clkseq);
+
+	/* XTAL Path */
+	if (clkseq & CLKCTRL_CLKSEQ_BYPASS_CPU) {
+		div = (clkctrl & CLKCTRL_CPU_DIV_XTAL_MASK) >>
+			CLKCTRL_CPU_DIV_XTAL_OFFSET;
+		return XTAL_FREQ_MHZ / div;
+	}
+
+	/* REF Path */
+	clkfrac = readl(&clkctrl_regs->hw_clkctrl_frac0);
+	frac = clkfrac & CLKCTRL_FRAC0_CPUFRAC_MASK;
+	div = clkctrl & CLKCTRL_CPU_DIV_CPU_MASK;
+	return (PLL_FREQ_MHZ * PLL_FREQ_COEF / frac) / div;
+}
+
+static uint32_t mx28_get_hclk(void)
+{
+	struct mx28_clkctrl_regs *clkctrl_regs =
+		(struct mx28_clkctrl_regs *)MXS_CLKCTRL_BASE;
+
+	uint32_t div;
+	uint32_t clkctrl;
+
+	clkctrl = readl(&clkctrl_regs->hw_clkctrl_hbus);
+
+	/* No support of fractional divider calculation */
+	if (clkctrl & CLKCTRL_HBUS_DIV_FRAC_EN)
+		return 0;
+
+	div = clkctrl & CLKCTRL_HBUS_DIV_MASK;
+	return mx28_get_pclk() / div;
+}
+
+static uint32_t mx28_get_emiclk(void)
+{
+	struct mx28_clkctrl_regs *clkctrl_regs =
+		(struct mx28_clkctrl_regs *)MXS_CLKCTRL_BASE;
+
+	uint32_t frac, div;
+	uint32_t clkctrl, clkseq, clkfrac;
+
+	clkseq = readl(&clkctrl_regs->hw_clkctrl_clkseq);
+	clkctrl = readl(&clkctrl_regs->hw_clkctrl_emi);
+
+	/* XTAL Path */
+	if (clkseq & CLKCTRL_CLKSEQ_BYPASS_EMI) {
+		div = (clkctrl & CLKCTRL_EMI_DIV_XTAL_MASK) >>
+			CLKCTRL_EMI_DIV_XTAL_OFFSET;
+		return XTAL_FREQ_MHZ / div;
+	}
+
+	clkfrac = readl(&clkctrl_regs->hw_clkctrl_frac0);
+
+	/* REF Path */
+	frac = (clkfrac & CLKCTRL_FRAC0_EMIFRAC_MASK) >>
+		CLKCTRL_FRAC0_EMIFRAC_OFFSET;
+	div = clkctrl & CLKCTRL_EMI_DIV_EMI_MASK;
+	return (PLL_FREQ_MHZ * PLL_FREQ_COEF / frac) / div;
+}
+
+static uint32_t mx28_get_gpmiclk(void)
+{
+	struct mx28_clkctrl_regs *clkctrl_regs =
+		(struct mx28_clkctrl_regs *)MXS_CLKCTRL_BASE;
+
+	uint32_t frac, div;
+	uint32_t clkctrl, clkseq, clkfrac;
+
+	clkseq = readl(&clkctrl_regs->hw_clkctrl_clkseq);
+	clkctrl = readl(&clkctrl_regs->hw_clkctrl_gpmi);
+
+	/* XTAL Path */
+	if (clkseq & CLKCTRL_CLKSEQ_BYPASS_GPMI) {
+		div = clkctrl & CLKCTRL_GPMI_DIV_MASK;
+		return XTAL_FREQ_MHZ / div;
+	}
+
+	clkfrac = readl(&clkctrl_regs->hw_clkctrl_frac1);
+
+	/* REF Path */
+	frac = (clkfrac & CLKCTRL_FRAC1_GPMIFRAC_MASK) >>
+		CLKCTRL_FRAC1_GPMIFRAC_OFFSET;
+	div = clkctrl & CLKCTRL_GPMI_DIV_MASK;
+	return (PLL_FREQ_MHZ * PLL_FREQ_COEF / frac) / div;
+}
+
+/*
+ * Set IO clock frequency, in kHz
+ */
+void mx28_set_ioclk(enum mxs_ioclock io, uint32_t freq)
+{
+	struct mx28_clkctrl_regs *clkctrl_regs =
+		(struct mx28_clkctrl_regs *)MXS_CLKCTRL_BASE;
+	uint32_t div;
+
+	if (freq == 0)
+		return;
+
+	if (io > MXC_IOCLK1)
+		return;
+
+	div = (PLL_FREQ_KHZ * PLL_FREQ_COEF) / freq;
+
+	if (div < 18)
+		div = 18;
+
+	if (div > 35)
+		div = 35;
+
+	if (io == MXC_IOCLK0) {
+		writel(CLKCTRL_FRAC0_CLKGATEIO0,
+			&clkctrl_regs->hw_clkctrl_frac0_set);
+		clrsetbits_le32(&clkctrl_regs->hw_clkctrl_frac0,
+				CLKCTRL_FRAC0_IO0FRAC_MASK,
+				div << CLKCTRL_FRAC0_IO0FRAC_OFFSET);
+		writel(CLKCTRL_FRAC0_CLKGATEIO0,
+			&clkctrl_regs->hw_clkctrl_frac0_clr);
+	} else {
+		writel(CLKCTRL_FRAC0_CLKGATEIO1,
+			&clkctrl_regs->hw_clkctrl_frac0_set);
+		clrsetbits_le32(&clkctrl_regs->hw_clkctrl_frac0,
+				CLKCTRL_FRAC0_IO1FRAC_MASK,
+				div << CLKCTRL_FRAC0_IO1FRAC_OFFSET);
+		writel(CLKCTRL_FRAC0_CLKGATEIO1,
+			&clkctrl_regs->hw_clkctrl_frac0_clr);
+	}
+}
+
+/*
+ * Get IO clock, returns IO clock in kHz
+ */
+static uint32_t mx28_get_ioclk(enum mxs_ioclock io)
+{
+	struct mx28_clkctrl_regs *clkctrl_regs =
+		(struct mx28_clkctrl_regs *)MXS_CLKCTRL_BASE;
+	uint32_t tmp, ret;
+
+	if (io > MXC_IOCLK1)
+		return 0;
+
+	tmp = readl(&clkctrl_regs->hw_clkctrl_frac0);
+
+	if (io == MXC_IOCLK0)
+		ret = (tmp & CLKCTRL_FRAC0_IO0FRAC_MASK) >>
+			CLKCTRL_FRAC0_IO0FRAC_OFFSET;
+	else
+		ret = (tmp & CLKCTRL_FRAC0_IO1FRAC_MASK) >>
+			CLKCTRL_FRAC0_IO1FRAC_OFFSET;
+
+	return (PLL_FREQ_KHZ * PLL_FREQ_COEF) / ret;
+}
+
+/*
+ * Configure SSP clock frequency, in kHz
+ */
+void mx28_set_sspclk(enum mxs_sspclock ssp, uint32_t freq, int xtal)
+{
+	struct mx28_clkctrl_regs *clkctrl_regs =
+		(struct mx28_clkctrl_regs *)MXS_CLKCTRL_BASE;
+	uint32_t clk, clkreg;
+
+	if (ssp > MXC_SSPCLK3)
+		return;
+
+	clkreg = (uint32_t)(&clkctrl_regs->hw_clkctrl_ssp0) +
+			(ssp * sizeof(struct mx28_register));
+
+	clrbits_le32(clkreg, CLKCTRL_SSP_CLKGATE);
+	while (readl(clkreg) & CLKCTRL_SSP_CLKGATE)
+		;
+
+	if (xtal)
+		clk = XTAL_FREQ_KHZ;
+	else
+		clk = mx28_get_ioclk(ssp >> 1);
+
+	if (freq > clk)
+		return;
+
+	/* Calculate the divider and cap it if necessary */
+	clk /= freq;
+	if (clk > CLKCTRL_SSP_DIV_MASK)
+		clk = CLKCTRL_SSP_DIV_MASK;
+
+	clrsetbits_le32(clkreg, CLKCTRL_SSP_DIV_MASK, clk);
+	while (readl(clkreg) & CLKCTRL_SSP_BUSY)
+		;
+
+	if (xtal)
+		writel(CLKCTRL_CLKSEQ_BYPASS_SSP0 << ssp,
+			&clkctrl_regs->hw_clkctrl_clkseq_set);
+	else
+		writel(CLKCTRL_CLKSEQ_BYPASS_SSP0 << ssp,
+			&clkctrl_regs->hw_clkctrl_clkseq_clr);
+}
+
+/*
+ * Return SSP frequency, in kHz
+ */
+static uint32_t mx28_get_sspclk(enum mxs_sspclock ssp)
+{
+	struct mx28_clkctrl_regs *clkctrl_regs =
+		(struct mx28_clkctrl_regs *)MXS_CLKCTRL_BASE;
+	uint32_t clkreg;
+	uint32_t clk, tmp;
+
+	if (ssp > MXC_SSPCLK3)
+		return 0;
+
+	tmp = readl(&clkctrl_regs->hw_clkctrl_clkseq);
+	if (tmp & (CLKCTRL_CLKSEQ_BYPASS_SSP0 << ssp))
+		return XTAL_FREQ_KHZ;
+
+	clkreg = (uint32_t)(&clkctrl_regs->hw_clkctrl_ssp0) +
+			(ssp * sizeof(struct mx28_register));
+
+	tmp = readl(clkreg) & CLKCTRL_SSP_DIV_MASK;
+
+	if (tmp == 0)
+		return 0;
+
+	clk = mx28_get_ioclk(ssp >> 1);
+
+	return clk / tmp;
+}
+
+/*
+ * Set SSP/MMC bus frequency, in kHz)
+ */
+void mx28_set_ssp_busclock(unsigned int bus, uint32_t freq)
+{
+	struct mx28_ssp_regs *ssp_regs;
+	const uint32_t sspclk = mx28_get_sspclk(bus);
+	uint32_t reg;
+	uint32_t divide, rate, tgtclk;
+
+	ssp_regs = (struct mx28_ssp_regs *)(MXS_SSP0_BASE + (bus * 0x2000));
+
+	/*
+	 * SSP bit rate = SSPCLK / (CLOCK_DIVIDE * (1 + CLOCK_RATE)),
+	 * CLOCK_DIVIDE has to be an even value from 2 to 254, and
+	 * CLOCK_RATE could be any integer from 0 to 255.
+	 */
+	for (divide = 2; divide < 254; divide += 2) {
+		rate = sspclk / freq / divide;
+		if (rate <= 256)
+			break;
+	}
+
+	tgtclk = sspclk / divide / rate;
+	while (tgtclk > freq) {
+		rate++;
+		tgtclk = sspclk / divide / rate;
+	}
+	if (rate > 256)
+		rate = 256;
+
+	/* Always set timeout the maximum */
+	reg = SSP_TIMING_TIMEOUT_MASK |
+		(divide << SSP_TIMING_CLOCK_DIVIDE_OFFSET) |
+		((rate - 1) << SSP_TIMING_CLOCK_RATE_OFFSET);
+	writel(reg, &ssp_regs->hw_ssp_timing);
+
+	debug("SPI%d: Set freq rate to %d KHz (requested %d KHz)\n",
+		bus, tgtclk, freq);
+}
+
+uint32_t mxc_get_clock(enum mxc_clock clk)
+{
+	switch (clk) {
+	case MXC_ARM_CLK:
+		return mx28_get_pclk() * 1000000;
+	case MXC_GPMI_CLK:
+		return mx28_get_gpmiclk() * 1000000;
+	case MXC_AHB_CLK:
+	case MXC_IPG_CLK:
+		return mx28_get_hclk() * 1000000;
+	case MXC_EMI_CLK:
+		return mx28_get_emiclk();
+	case MXC_IO0_CLK:
+		return mx28_get_ioclk(MXC_IOCLK0);
+	case MXC_IO1_CLK:
+		return mx28_get_ioclk(MXC_IOCLK1);
+	case MXC_SSP0_CLK:
+		return mx28_get_sspclk(MXC_SSPCLK0);
+	case MXC_SSP1_CLK:
+		return mx28_get_sspclk(MXC_SSPCLK1);
+	case MXC_SSP2_CLK:
+		return mx28_get_sspclk(MXC_SSPCLK2);
+	case MXC_SSP3_CLK:
+		return mx28_get_sspclk(MXC_SSPCLK3);
+	}
+
+	return 0;
+}
diff --git a/arch/arm/cpu/arm926ejs/mx28/mx28.c b/arch/arm/cpu/arm926ejs/mx28/mx28.c
new file mode 100644
index 0000000..446ea8b
--- /dev/null
+++ b/arch/arm/cpu/arm926ejs/mx28/mx28.c
@@ -0,0 +1,193 @@
+/*
+ * Freescale i.MX28 common code
+ *
+ * Copyright (C) 2011 Marek Vasut <marek.vasut@gmail.com>
+ * on behalf of DENX Software Engineering GmbH
+ *
+ * Based on code from LTIB:
+ * Copyright (C) 2010 Freescale Semiconductor, Inc.
+ *
+ * See file CREDITS for list of people who contributed to this
+ * project.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston,
+ * MA 02111-1307 USA
+ */
+
+#include <common.h>
+#include <asm/errno.h>
+#include <asm/io.h>
+#include <asm/arch/clock.h>
+#include <asm/arch/gpio.h>
+#include <asm/arch/imx-regs.h>
+#include <asm/arch/sys_proto.h>
+
+/* 1 second delay should be plenty of time for block reset. */
+#define	RESET_MAX_TIMEOUT	1000000
+
+#define	MX28_BLOCK_SFTRST	(1 << 31)
+#define	MX28_BLOCK_CLKGATE	(1 << 30)
+
+/* Lowlevel init isn't used on i.MX28, so just have a dummy here */
+inline void lowlevel_init(void) {}
+
+void reset_cpu(ulong ignored) __attribute__((noreturn));
+
+void reset_cpu(ulong ignored)
+{
+
+	struct mx28_rtc_regs *rtc_regs =
+		(struct mx28_rtc_regs *)MXS_RTC_BASE;
+
+	/* Wait 1 uS before doing the actual watchdog reset */
+	writel(1, &rtc_regs->hw_rtc_watchdog);
+	writel(RTC_CTRL_WATCHDOGEN, &rtc_regs->hw_rtc_ctrl_set);
+
+	/* Endless loop, reset will exit from here */
+	for (;;)
+		;
+}
+
+int mx28_wait_mask_set(struct mx28_register *reg, uint32_t mask, int timeout)
+{
+	while (--timeout) {
+		if ((readl(&reg->reg) & mask) == mask)
+			break;
+		udelay(1);
+	}
+
+	return !timeout;
+}
+
+int mx28_wait_mask_clr(struct mx28_register *reg, uint32_t mask, int timeout)
+{
+	while (--timeout) {
+		if ((readl(&reg->reg) & mask) == 0)
+			break;
+		udelay(1);
+	}
+
+	return !timeout;
+}
+
+int mx28_reset_block(struct mx28_register *reg)
+{
+	/* Clear SFTRST */
+	writel(MX28_BLOCK_SFTRST, &reg->reg_clr);
+
+	if (mx28_wait_mask_clr(reg, MX28_BLOCK_SFTRST, RESET_MAX_TIMEOUT))
+		return 1;
+
+	/* Clear CLKGATE */
+	writel(MX28_BLOCK_CLKGATE, &reg->reg_clr);
+
+	/* Set SFTRST */
+	writel(MX28_BLOCK_SFTRST, &reg->reg_set);
+
+	/* Wait for CLKGATE being set */
+	if (mx28_wait_mask_set(reg, MX28_BLOCK_CLKGATE, RESET_MAX_TIMEOUT))
+		return 1;
+
+	/* Clear SFTRST */
+	writel(MX28_BLOCK_SFTRST, &reg->reg_clr);
+
+	if (mx28_wait_mask_clr(reg, MX28_BLOCK_SFTRST, RESET_MAX_TIMEOUT))
+		return 1;
+
+	/* Clear CLKGATE */
+	writel(MX28_BLOCK_CLKGATE, &reg->reg_clr);
+
+	if (mx28_wait_mask_clr(reg, MX28_BLOCK_CLKGATE, RESET_MAX_TIMEOUT))
+		return 1;
+
+	return 0;
+}
+
+#ifdef	CONFIG_ARCH_CPU_INIT
+int arch_cpu_init(void)
+{
+	struct mx28_clkctrl_regs *clkctrl_regs =
+		(struct mx28_clkctrl_regs *)MXS_CLKCTRL_BASE;
+
+	/*
+	 * Enable NAND clock
+	 */
+	/* Clear bypass bit */
+	writel(CLKCTRL_CLKSEQ_BYPASS_GPMI,
+		&clkctrl_regs->hw_clkctrl_clkseq_set);
+
+	/* Set GPMI clock to ref_gpmi / 12 */
+	clrsetbits_le32(&clkctrl_regs->hw_clkctrl_gpmi,
+		CLKCTRL_GPMI_CLKGATE | CLKCTRL_GPMI_DIV_MASK, 1);
+
+	udelay(1000);
+
+	return 0;
+}
+#endif
+
+#if defined(CONFIG_DISPLAY_CPUINFO)
+int print_cpuinfo(void)
+{
+	printf("Freescale i.MX28 family\n");
+	return 0;
+}
+#endif
+
+int do_mx28_showclocks(cmd_tbl_t *cmdtp, int flag, int argc, char *const argv[])
+{
+	printf("CPU:   %3d MHz\n", mxc_get_clock(MXC_ARM_CLK) / 1000000);
+	printf("BUS:   %3d MHz\n", mxc_get_clock(MXC_AHB_CLK) / 1000000);
+	printf("EMI:   %3d MHz\n", mxc_get_clock(MXC_EMI_CLK));
+	printf("GPMI:  %3d MHz\n", mxc_get_clock(MXC_GPMI_CLK) / 1000000);
+	return 0;
+}
+
+/*
+ * Initializes on-chip ethernet controllers.
+ */
+#ifdef	CONFIG_CMD_NET
+int cpu_eth_init(bd_t *bis)
+{
+	struct mx28_clkctrl_regs *clkctrl_regs =
+		(struct mx28_clkctrl_regs *)MXS_CLKCTRL_BASE;
+
+	/* Turn on ENET clocks */
+	clrbits_le32(&clkctrl_regs->hw_clkctrl_enet,
+		CLKCTRL_ENET_SLEEP | CLKCTRL_ENET_DISABLE);
+
+	/* Set up ENET PLL for 50 MHz */
+	/* Power on ENET PLL */
+	writel(CLKCTRL_PLL2CTRL0_POWER,
+		&clkctrl_regs->hw_clkctrl_pll2ctrl0_set);
+
+	udelay(10);
+
+	/* Gate on ENET PLL */
+	writel(CLKCTRL_PLL2CTRL0_CLKGATE,
+		&clkctrl_regs->hw_clkctrl_pll2ctrl0_clr);
+
+	/* Enable pad output */
+	setbits_le32(&clkctrl_regs->hw_clkctrl_enet, CLKCTRL_ENET_CLK_OUT_EN);
+
+	return 0;
+}
+#endif
+
+U_BOOT_CMD(
+	clocks,	CONFIG_SYS_MAXARGS, 1, do_mx28_showclocks,
+	"display clocks",
+	""
+);
diff --git a/arch/arm/cpu/arm926ejs/mx28/timer.c b/arch/arm/cpu/arm926ejs/mx28/timer.c
new file mode 100644
index 0000000..dbc904d
--- /dev/null
+++ b/arch/arm/cpu/arm926ejs/mx28/timer.c
@@ -0,0 +1,141 @@
+/*
+ * Freescale i.MX28 timer driver
+ *
+ * Copyright (C) 2011 Marek Vasut <marek.vasut@gmail.com>
+ * on behalf of DENX Software Engineering GmbH
+ *
+ * Based on code from LTIB:
+ * (C) Copyright 2009-2010 Freescale Semiconductor, Inc.
+ *
+ * See file CREDITS for list of people who contributed to this
+ * project.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.	 See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston,
+ * MA 02111-1307 USA
+ */
+
+#include <common.h>
+#include <asm/io.h>
+#include <asm/arch/imx-regs.h>
+#include <asm/arch/sys_proto.h>
+
+/* Maximum fixed count */
+#define TIMER_LOAD_VAL	0xffffffff
+
+DECLARE_GLOBAL_DATA_PTR;
+
+#define timestamp (gd->tbl)
+#define lastdec (gd->lastinc)
+
+/*
+ * This driver uses 1kHz clock source.
+ */
+#define	MX28_INCREMENTER_HZ		1000
+
+static inline unsigned long tick_to_time(unsigned long tick)
+{
+	return tick / (MX28_INCREMENTER_HZ / CONFIG_SYS_HZ);
+}
+
+static inline unsigned long time_to_tick(unsigned long time)
+{
+	return time * (MX28_INCREMENTER_HZ / CONFIG_SYS_HZ);
+}
+
+/* Calculate how many ticks happen in "us" microseconds */
+static inline unsigned long us_to_tick(unsigned long us)
+{
+	return (us * MX28_INCREMENTER_HZ) / 1000000;
+}
+
+int timer_init(void)
+{
+	struct mx28_timrot_regs *timrot_regs =
+		(struct mx28_timrot_regs *)MXS_TIMROT_BASE;
+
+	/* Reset Timers and Rotary Encoder module */
+	mx28_reset_block(&timrot_regs->hw_timrot_rotctrl_reg);
+
+	/* Set fixed_count to 0 */
+	writel(0, &timrot_regs->hw_timrot_fixed_count0);
+
+	/* Set UPDATE bit and 1Khz frequency */
+	writel(TIMROT_TIMCTRLn_UPDATE | TIMROT_TIMCTRLn_RELOAD |
+		TIMROT_TIMCTRLn_SELECT_1KHZ_XTAL,
+		&timrot_regs->hw_timrot_timctrl0);
+
+	/* Set fixed_count to maximal value */
+	writel(TIMER_LOAD_VAL, &timrot_regs->hw_timrot_fixed_count0);
+
+	return 0;
+}
+
+ulong get_timer(ulong base)
+{
+	struct mx28_timrot_regs *timrot_regs =
+		(struct mx28_timrot_regs *)MXS_TIMROT_BASE;
+
+	/* Current tick value */
+	uint32_t now = readl(&timrot_regs->hw_timrot_running_count0);
+
+	if (lastdec >= now) {
+		/*
+		 * normal mode (non roll)
+		 * move stamp forward with absolut diff ticks
+		 */
+		timestamp += (lastdec - now);
+	} else {
+		/* we have rollover of decrementer */
+		timestamp += (TIMER_LOAD_VAL - now) + lastdec;
+
+	}
+	lastdec = now;
+
+	return tick_to_time(timestamp) - base;
+}
+
+/* We use the HW_DIGCTL_MICROSECONDS register for sub-millisecond timer. */
+#define	MX28_HW_DIGCTL_MICROSECONDS	0x8001c0c0
+
+void __udelay(unsigned long usec)
+{
+	uint32_t old, new, incr;
+	uint32_t counter = 0;
+
+	old = readl(MX28_HW_DIGCTL_MICROSECONDS);
+
+	while (counter < usec) {
+		new = readl(MX28_HW_DIGCTL_MICROSECONDS);
+
+		/* Check if the timer wrapped. */
+		if (new < old) {
+			incr = 0xffffffff - old;
+			incr += new;
+		} else {
+			incr = new - old;
+		}
+
+		/*
+		 * Check if we are close to the maximum time and the counter
+		 * would wrap if incremented. If that's the case, break out
+		 * from the loop as the requested delay time passed.
+		 */
+		if (counter + incr < counter)
+			break;
+
+		counter += incr;
+		old = new;
+	}
+}