lpc32xx: add support for board work_92105

Work_92105 from Work Microwave is an LPC3250-
based board with the following features:
- 64MB or 128MB SDR DRAM
- 1 GB SLC NAND, managed through MLC controller.
- Ethernet
- Ethernet + PHY SMSC8710
- I2C:
  - EEPROM (24M01-compatible)
  - RTC (DS1374-compatible)
  - Temperature sensor (DS620)
  - DACs (2 x MAX518)
- SPI (through SSP interface)
  - Port expander MAX6957
- LCD display (HD44780-compatible), controlled
  through the port expander and DACs

This board has SPL support, and uses the LPC32XX boot
image format.

Signed-off-by: Albert ARIBAUD (3ADEV) <albert.aribaud@3adev.fr>
diff --git a/arch/arm/Kconfig b/arch/arm/Kconfig
index 80b0d34..086ca85 100644
--- a/arch/arm/Kconfig
+++ b/arch/arm/Kconfig
@@ -132,6 +132,11 @@
 	bool "Support devkit3250"
 	select CPU_ARM926EJS
 
+config TARGET_WORK_92105
+	bool "Support work_92105"
+	select CPU_ARM926EJS
+	select SUPPORT_SPL
+
 config TARGET_MX25PDK
 	bool "Support mx25pdk"
 	select CPU_ARM926EJS
@@ -872,6 +877,7 @@
 source "board/wandboard/Kconfig"
 source "board/warp/Kconfig"
 source "board/woodburn/Kconfig"
+source "board/work-microwave/work_92105/Kconfig"
 source "board/xaeniax/Kconfig"
 source "board/xilinx/zynqmp/Kconfig"
 source "board/zipitz2/Kconfig"
diff --git a/arch/arm/cpu/arm926ejs/lpc32xx/Makefile b/arch/arm/cpu/arm926ejs/lpc32xx/Makefile
index 314f004..4837377 100644
--- a/arch/arm/cpu/arm926ejs/lpc32xx/Makefile
+++ b/arch/arm/cpu/arm926ejs/lpc32xx/Makefile
@@ -6,3 +6,5 @@
 #
 
 obj-y   = cpu.o clk.o devices.o timer.o
+
+obj-$(CONFIG_SPL_BUILD) += dram.o lowlevel_init.o
diff --git a/arch/arm/cpu/arm926ejs/lpc32xx/clk.c b/arch/arm/cpu/arm926ejs/lpc32xx/clk.c
index b7a44d5..1ef8a36 100644
--- a/arch/arm/cpu/arm926ejs/lpc32xx/clk.c
+++ b/arch/arm/cpu/arm926ejs/lpc32xx/clk.c
@@ -98,6 +98,40 @@
 	return get_hclk_pll_rate() / get_periph_clk_div();
 }
 
+unsigned int get_sdram_clk_rate(void)
+{
+	unsigned int src_clk;
+
+	if (!(readl(&clk->pwr_ctrl) & CLK_PWR_NORMAL_RUN))
+		return get_sys_clk_rate();
+
+	src_clk = get_hclk_pll_rate();
+
+	if (readl(&clk->sdramclk_ctrl) & CLK_SDRAM_DDR_SEL) {
+		/* using DDR */
+		switch (readl(&clk->hclkdiv_ctrl) & CLK_HCLK_DDRAM_MASK) {
+		case CLK_HCLK_DDRAM_HALF:
+			return src_clk/2;
+		case CLK_HCLK_DDRAM_NOMINAL:
+			return src_clk;
+		default:
+			return 0;
+		}
+	} else {
+		/* using SDR */
+		switch (readl(&clk->hclkdiv_ctrl) & CLK_HCLK_ARM_PLL_DIV_MASK) {
+		case CLK_HCLK_ARM_PLL_DIV_4:
+			return src_clk/4;
+		case CLK_HCLK_ARM_PLL_DIV_2:
+			return src_clk/2;
+		case CLK_HCLK_ARM_PLL_DIV_1:
+			return src_clk;
+		default:
+			return 0;
+		}
+	}
+}
+
 int get_serial_clock(void)
 {
 	return get_periph_clk_rate();
diff --git a/arch/arm/cpu/arm926ejs/lpc32xx/cpu.c b/arch/arm/cpu/arm926ejs/lpc32xx/cpu.c
index eec4d9e..f757474 100644
--- a/arch/arm/cpu/arm926ejs/lpc32xx/cpu.c
+++ b/arch/arm/cpu/arm926ejs/lpc32xx/cpu.c
@@ -9,6 +9,7 @@
 #include <asm/arch/cpu.h>
 #include <asm/arch/clk.h>
 #include <asm/arch/wdt.h>
+#include <asm/arch/sys_proto.h>
 #include <asm/io.h>
 
 static struct clk_pm_regs *clk = (struct clk_pm_regs *)CLK_PM_BASE;
diff --git a/arch/arm/cpu/arm926ejs/lpc32xx/dram.c b/arch/arm/cpu/arm926ejs/lpc32xx/dram.c
new file mode 100644
index 0000000..1eea8e2
--- /dev/null
+++ b/arch/arm/cpu/arm926ejs/lpc32xx/dram.c
@@ -0,0 +1,77 @@
+/*
+ * LPC32xx dram init
+ *
+ * (C) Copyright 2014  DENX Software Engineering GmbH
+ * Written-by: Albert ARIBAUD <albert.aribaud@3adev.fr>
+ *
+ * This is called by SPL to gain access to the SDR DRAM.
+ *
+ * This code runs from SRAM.
+ *
+ * Actual CONFIG_LPC32XX_SDRAM_* parameters must be provided
+ * by the board configuration file.
+ *
+ * SPDX-License-Identifier:	GPL-2.0+
+ */
+
+#include <common.h>
+#include <netdev.h>
+#include <asm/arch/cpu.h>
+#include <asm/arch/clk.h>
+#include <asm/arch/wdt.h>
+#include <asm/arch/emc.h>
+#include <asm/io.h>
+
+static struct clk_pm_regs *clk = (struct clk_pm_regs *)CLK_PM_BASE;
+static struct emc_regs *emc = (struct emc_regs *)EMC_BASE;
+
+void ddr_init(struct emc_dram_settings *dram)
+{
+	uint32_t ck;
+
+	/* Enable EMC interface and choose little endian mode */
+	writel(1, &emc->ctrl);
+	writel(0, &emc->config);
+	/* Select maximum EMC Dynamic Memory Refresh Time */
+	writel(0x7FF, &emc->refresh);
+	/* Determine CLK */
+	ck = get_sdram_clk_rate();
+	/* Configure SDRAM */
+	writel(dram->cmddelay, &clk->sdramclk_ctrl);
+	writel(dram->config0, &emc->config0);
+	writel(dram->rascas0, &emc->rascas0);
+	writel(dram->rdconfig, &emc->read_config);
+	/* Set timings */
+	writel((ck / dram->trp) & 0x0000000F, &emc->t_rp);
+	writel((ck / dram->tras) & 0x0000000F, &emc->t_ras);
+	writel((ck / dram->tsrex) & 0x0000007F, &emc->t_srex);
+	writel((ck / dram->twr) & 0x0000000F, &emc->t_wr);
+	writel((ck / dram->trc) & 0x0000001F, &emc->t_rc);
+	writel((ck / dram->trfc) & 0x0000001F, &emc->t_rfc);
+	writel((ck / dram->txsr) & 0x000000FF, &emc->t_xsr);
+	writel(dram->trrd, &emc->t_rrd);
+	writel(dram->tmrd, &emc->t_mrd);
+	writel(dram->tcdlr, &emc->t_cdlr);
+	/* Dynamic refresh */
+	writel((((ck / dram->refresh) >> 4) & 0x7FF), &emc->refresh);
+	udelay(10);
+	/* Force all clocks, enable inverted ck, issue NOP command */
+	writel(0x00000193, &emc->control);
+	udelay(100);
+	/* Keep all clocks enabled, issue a PRECHARGE ALL command */
+	writel(0x00000113, &emc->control);
+	/* Fast dynamic refresh for at least a few SDRAM ck cycles */
+	writel((((128) >> 4) & 0x7FF), &emc->refresh);
+	udelay(10);
+	/* set correct dynamic refresh timing */
+	writel((((ck / dram->refresh) >> 4) & 0x7FF), &emc->refresh);
+	udelay(10);
+	/* set normal mode to CAS=3 */
+	writel(0x00000093, &emc->control);
+	readl(EMC_DYCS0_BASE | dram->mode);
+	/* set extended mode to all zeroes */
+	writel(0x00000093, &emc->control);
+	readl(EMC_DYCS0_BASE | dram->emode);
+	/* stop forcing clocks, keep inverted clock, issue normal mode */
+	writel(0x00000010, &emc->control);
+}
diff --git a/arch/arm/cpu/arm926ejs/lpc32xx/lowlevel_init.S b/arch/arm/cpu/arm926ejs/lpc32xx/lowlevel_init.S
new file mode 100644
index 0000000..4b8053e
--- /dev/null
+++ b/arch/arm/cpu/arm926ejs/lpc32xx/lowlevel_init.S
@@ -0,0 +1,45 @@
+/*
+ * WORK Microwave work_92105 board low level init
+ *
+ * (C) Copyright 2014  DENX Software Engineering GmbH
+ * Written-by: Albert ARIBAUD <albert.aribaud@3adev.fr>
+ *
+ * Low level init is called from SPL to set up the clocks.
+ * On entry, the LPC3250 is in Direct Run mode with all clocks
+ * running at 13 MHz; on exit, ARM clock is 208 MHz, HCLK is
+ * 104 MHz and PCLK is 13 MHz.
+ *
+ * This code must run from SRAM so that the clock changes do
+ * not prevent it from executing.
+ *
+ * SPDX-License-Identifier:	GPL-2.0+
+ */
+
+.globl lowlevel_init
+
+lowlevel_init:
+
+	/* Set ARM, HCLK, PCLK dividers for normal mode */
+	ldr	r0, =0x0000003D
+	ldr	r1, =0x40004040
+	str	r0, [r1]
+
+	/* Start HCLK PLL for 208 MHz */
+	ldr	r0, =0x0001401E
+	ldr	r1, =0x40004058
+	str	r0, [r1]
+
+	/* wait for HCLK PLL to lock */
+1:
+	ldr	r0, [r1]
+	ands	r0, r0, #1
+	beq	1b
+
+	/* switch to normal mode */
+	ldr	r1, =0x40004044
+	ldr	r0, [r1]
+	orr	r0, #0x00000004
+	str	r0, [r1]
+
+	/* Return to U-boot via saved link register */
+	mov	pc, lr
diff --git a/arch/arm/include/asm/arch-lpc32xx/clk.h b/arch/arm/include/asm/arch-lpc32xx/clk.h
index 2cb5703..9449869 100644
--- a/arch/arm/include/asm/arch-lpc32xx/clk.h
+++ b/arch/arm/include/asm/arch-lpc32xx/clk.h
@@ -71,6 +71,7 @@
 };
 
 /* HCLK Divider Control Register bits */
+#define CLK_HCLK_DDRAM_MASK		(0x3 << 7)
 #define CLK_HCLK_DDRAM_HALF		(0x2 << 7)
 #define CLK_HCLK_DDRAM_NOMINAL		(0x1 << 7)
 #define CLK_HCLK_DDRAM_STOPPED		(0x0 << 7)
@@ -158,11 +159,15 @@
 /* SSP Clock Control Register bits */
 #define CLK_SSP0_ENABLE_CLOCK		(1 << 0)
 
+/* SDRAMCLK register bits */
+#define CLK_SDRAM_DDR_SEL		(1 << 1)
+
 unsigned int get_sys_clk_rate(void);
 unsigned int get_hclk_pll_rate(void);
 unsigned int get_hclk_clk_div(void);
 unsigned int get_hclk_clk_rate(void);
 unsigned int get_periph_clk_div(void);
 unsigned int get_periph_clk_rate(void);
+unsigned int get_sdram_clk_rate(void);
 
 #endif /* _LPC32XX_CLK_H */
diff --git a/arch/arm/include/asm/arch-lpc32xx/cpu.h b/arch/arm/include/asm/arch-lpc32xx/cpu.h
index 1067107..0b5dca1 100644
--- a/arch/arm/include/asm/arch-lpc32xx/cpu.h
+++ b/arch/arm/include/asm/arch-lpc32xx/cpu.h
@@ -27,6 +27,7 @@
 #define HS_UART7_BASE	0x4001C000	/* High speed UART 7 registers base */
 #define RTC_BASE	0x40024000	/* RTC registers base               */
 #define GPIO_BASE	0x40028000	/* GPIO registers base              */
+#define MUX_BASE	0x40028100	/* MUX registers base               */
 #define WDT_BASE	0x4003C000	/* Watchdog timer registers base    */
 #define TIMER0_BASE	0x40044000	/* Timer0 registers base            */
 #define TIMER1_BASE	0x4004C000	/* Timer1 registers base            */
diff --git a/arch/arm/include/asm/arch-lpc32xx/emc.h b/arch/arm/include/asm/arch-lpc32xx/emc.h
index 82d9bcc..1a2bab2 100644
--- a/arch/arm/include/asm/arch-lpc32xx/emc.h
+++ b/arch/arm/include/asm/arch-lpc32xx/emc.h
@@ -76,4 +76,25 @@
 #define EMC_STAT_WAITWR(n)		(((n) - 2) & 0x1F)
 #define EMC_STAT_WAITTURN(n)		(((n) - 1) & 0x0F)
 
+/* EMC settings for DRAM */
+struct emc_dram_settings {
+	u32	cmddelay;
+	u32	config0;
+	u32	rascas0;
+	u32	rdconfig;
+	u32	trp;
+	u32	tras;
+	u32	tsrex;
+	u32	twr;
+	u32	trc;
+	u32	trfc;
+	u32	txsr;
+	u32	trrd;
+	u32	tmrd;
+	u32	tcdlr;
+	u32	refresh;
+	u32	mode;
+	u32	emode;
+};
+
 #endif /* _LPC32XX_EMC_H */
diff --git a/arch/arm/include/asm/arch-lpc32xx/mux.h b/arch/arm/include/asm/arch-lpc32xx/mux.h
new file mode 100644
index 0000000..dc1b5bc
--- /dev/null
+++ b/arch/arm/include/asm/arch-lpc32xx/mux.h
@@ -0,0 +1,18 @@
+/*
+ * LPC32xx MUX interface
+ *
+ * (C) Copyright 2015  DENX Software Engineering GmbH
+ * Written-by: Albert ARIBAUD <albert.aribaud@3adev.fr>
+ *
+ * SPDX-License-Identifier:	GPL-2.0+
+ */
+
+/**
+ * MUX register map for LPC32xx
+ */
+
+struct mux_regs {
+	u32 p_mux_set;
+	u32 p_mux_clr;
+	u32 p_mux_state;
+};
diff --git a/arch/arm/include/asm/arch-lpc32xx/sys_proto.h b/arch/arm/include/asm/arch-lpc32xx/sys_proto.h
index 86d5ee9..c3d890d 100644
--- a/arch/arm/include/asm/arch-lpc32xx/sys_proto.h
+++ b/arch/arm/include/asm/arch-lpc32xx/sys_proto.h
@@ -7,10 +7,14 @@
 #ifndef _LPC32XX_SYS_PROTO_H
 #define _LPC32XX_SYS_PROTO_H
 
+#include <asm/arch/emc.h>
+
 void lpc32xx_uart_init(unsigned int uart_id);
 void lpc32xx_mac_init(void);
 void lpc32xx_mlc_nand_init(void);
 void lpc32xx_i2c_init(unsigned int devnum);
 void lpc32xx_ssp_init(void);
-
+#if defined(CONFIG_SPL_BUILD)
+void ddr_init(const struct emc_dram_settings *dram);
+#endif
 #endif /* _LPC32XX_SYS_PROTO_H */