Thread (15 messages) flat view 15 messages, 5 authors, 2012-07-27

[PATCH 1/8] ARM: support for Moschip MCS814x SoCs

From: Thomas Petazzoni <hidden>
Date: 2012-07-16 12:29:41
Also in: linux-devicetree

Hello Florian,

Le Sun, 15 Jul 2012 16:49:07 +0200,
Florian Fainelli [off-list ref] a ?crit :
quoted hunk ↗ jump to hunk
diff --git a/arch/arm/mach-mcs814x/Makefile.boot b/arch/arm/mach-mcs814x/Makefile.boot
new file mode 100644
index 0000000..414db8b
--- /dev/null
+++ b/arch/arm/mach-mcs814x/Makefile.boot
@@ -0,0 +1,3 @@
+    zreladdr-y := 0x00008000
+ params_phys-y := 0x00000008
+ initrd_phys-y := 0x00400000
params_phys-y and initrd_phys-y are useless for DT-based platforms, if
I'm correct.
quoted hunk ↗ jump to hunk
diff --git a/arch/arm/mach-mcs814x/clock.c b/arch/arm/mach-mcs814x/clock.c
new file mode 100644
index 0000000..eb30ae2
--- /dev/null
+++ b/arch/arm/mach-mcs814x/clock.c
@@ -0,0 +1,271 @@
+/*
+ * Moschip MCS814x clock routines
+ *
+ * Copyright (C) 2012, Florian Fainelli <florian@openwrt.org>
+ *
+ * Licensed under GPLv2
+ */
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/export.h>
+#include <linux/spinlock.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/clkdev.h>
+#include <linux/clk.h>
+
+#include <mach/mcs814x.h>
+
+#include "common.h"
+
+#define KHZ	1000
+#define MHZ	(KHZ * KHZ)
+
+struct clk_ops {
+	unsigned long (*get_rate)(struct clk *clk);
+	int (*set_rate)(struct clk *clk, unsigned long rate);
+	struct clk *(*get_parent)(struct clk *clk);
+	int (*enable)(struct clk *clk, int enable);
+};
+
+struct clk {
+	struct clk *parent;		/* parent clk */
+	unsigned long rate;		/* clock rate in Hz */
+	unsigned long divider;		/* clock divider */
+	u32 usecount;			/* reference count */
+	struct clk_ops *ops;		/* clock operation */
+	u32 enable_reg;			/* clock enable register */
+	u32 enable_mask;		/* clock enable mask */
+};
+
+static unsigned long clk_divide_parent(struct clk *clk)
+{
+	if (clk->parent && clk->divider)
+		return clk_get_rate(clk->parent) / clk->divider;
+	else
+		return 0;
+}
+
+static int clk_local_onoff_enable(struct clk *clk, int enable)
+{
+	u32 tmp;
+
+	/* no enable_reg means the clock is always enabled */
+	if (!clk->enable_reg)
+		return 0;
+
+	tmp = __raw_readl(mcs814x_sysdbg_base + clk->enable_reg);
+	if (!enable)
+		tmp &= ~clk->enable_mask;
+	else
+		tmp |= clk->enable_mask;
+
+	__raw_writel(tmp, mcs814x_sysdbg_base + clk->enable_reg);
+
+	return 0;
+}
+
+static struct clk_ops default_clk_ops = {
+	.get_rate	= clk_divide_parent,
+	.enable		= clk_local_onoff_enable,
+};
+
+static DEFINE_SPINLOCK(clocks_lock);
+
+static const unsigned long cpu_freq_table[] = {
+	175000,
+	300000,
+	125000,
+	137500,
+	212500,
+	250000,
+	162500,
+	187500,
+	162500,
+	150000,
+	225000,
+	237500,
+	200000,
+	262500,
+	275000,
+	287500
+};
+
+static struct clk clk_cpu;
+
+/* System clock is fixed at 50Mhz */
+static struct clk clk_sys = {
+	.rate	= 50 * MHZ,
+};
+
+static struct clk clk_sdram;
+
+static struct clk clk_timer0 = {
+	.parent	= &clk_sdram,
+	.divider = 2,
+	.ops	= &default_clk_ops,
+};
+
+static struct clk clk_timer1_2 = {
+	.parent	= &clk_sys,
+};
+
+/* Watchdog clock is system clock / 128 */
+static struct clk clk_wdt = {
+	.parent	= &clk_sys,
+	.divider = 128,
+	.ops	= &default_clk_ops,
+};
+
+static struct clk clk_emac = {
+	.ops		= &default_clk_ops,
+	.enable_reg	= SYSDBG_SYSCTL,
+	.enable_mask	= SYSCTL_EMAC,
+};
+
+static struct clk clk_ephy = {
+	.ops		= &default_clk_ops,
+	.enable_reg	= SYSDBG_PLL_CTL,
+	.enable_mask	= ~SYSCTL_EPHY,	/* active low */
+};
+
+static struct clk clk_cipher = {
+	.ops		= &default_clk_ops,
+	.enable_reg	= SYSDBG_SYSCTL,
+	.enable_mask	= SYSCTL_CIPHER,
+};
+
+#define CLK(_dev, _con, _clk)	\
+{ .dev_id = (_dev), .con_id = (_con), .clk = (_clk) },
+
+static struct clk_lookup mcs814x_chip_clks[] = {
+	CLK("cpu", NULL, &clk_cpu)
+	CLK("sys", NULL, &clk_sys)
+	CLK("sdram", NULL, &clk_sdram)
+	/* 32-bits timer0 */
+	CLK("timer0", NULL, &clk_timer0)
+	/* 16-bits timer1 */
+	CLK("timer1", NULL, &clk_timer1_2)
+	/* 64-bits timer2, same as timer 1 */
+	CLK("timer2", NULL, &clk_timer1_2)
+	CLK(NULL, "wdt", &clk_wdt)
+	CLK(NULL, "emac", &clk_emac)
+	CLK(NULL, "ephy", &clk_ephy)
+	CLK(NULL, "cipher", &clk_cipher)
+};
+
+static void local_clk_disable(struct clk *clk)
+{
+	WARN_ON(!clk->usecount);
+
+	if (clk->usecount > 0) {
+		clk->usecount--;
+
+		if ((clk->usecount == 0) && (clk->ops->enable))
+			clk->ops->enable(clk, 0);
+
+		if (clk->parent)
+			local_clk_disable(clk->parent);
+	}
+}
+
+static int local_clk_enable(struct clk *clk)
+{
+	int ret = 0;
+
+	if (clk->parent)
+		ret = local_clk_enable(clk->parent);
+
+	if (ret)
+		return ret;
+
+	if ((clk->usecount == 0) && (clk->ops->enable))
+		ret = clk->ops->enable(clk, 1);
+
+	if (!ret)
+		clk->usecount++;
+	else if (clk->parent && clk->parent->ops->enable)
+		local_clk_disable(clk->parent);
+
+	return ret;
+}
+
+int clk_enable(struct clk *clk)
+{
+	int ret;
+	unsigned long flags;
+
+	spin_lock_irqsave(&clocks_lock, flags);
+	ret = local_clk_enable(clk);
+	spin_unlock_irqrestore(&clocks_lock, flags);
+
+	return ret;
+}
+EXPORT_SYMBOL(clk_enable);
+
+void clk_disable(struct clk *clk)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&clocks_lock, flags);
+	local_clk_disable(clk);
+	spin_unlock_irqrestore(&clocks_lock, flags);
+}
+EXPORT_SYMBOL(clk_disable);
+
+unsigned long clk_get_rate(struct clk *clk)
+{
+	if (unlikely(IS_ERR_OR_NULL(clk)))
+		return 0;
+
+	if (clk->rate)
+		return clk->rate;
+
+	if (clk->ops && clk->ops->get_rate)
+		return clk->ops->get_rate(clk);
+
+	return clk_get_rate(clk->parent);
+}
+EXPORT_SYMBOL(clk_get_rate);
+
+struct clk *clk_get_parent(struct clk *clk)
+{
+	unsigned long flags;
+
+	if (unlikely(IS_ERR_OR_NULL(clk)))
+		return NULL;
+
+	if (!clk->ops || !clk->ops->get_parent)
+		return clk->parent;
+
+	spin_lock_irqsave(&clocks_lock, flags);
+	clk->parent = clk->ops->get_parent(clk);
+	spin_unlock_irqrestore(&clocks_lock, flags);
+
+	return clk->parent;
+}
+EXPORT_SYMBOL(clk_get_parent);
You should rather use the new clock framework instead of providing your
own implementation of the clk_*() API. And therefore your clock driver
should be in drivers/clk/ instead.
+struct cpu_mode {
+	const char *name;
+	int gpio_start;
+	int gpio_end;
+};
+
+static const struct cpu_mode cpu_modes[] = {
+	{
+		.name		= "I2S",
+		.gpio_start	= 4,
+		.gpio_end	= 8,
+	},
+	{
+		.name		= "UART",
+		.gpio_start	= 4,
+		.gpio_end	= 9,
+	},
+	{
+		.name		= "External MII",
+		.gpio_start	= 0,
+		.gpio_end	= 16,
+	},
+	{
+		.name		= "Normal",
+		.gpio_start	= -1,
+		.gpio_end	= -1,
+	},
+};
+
+void __init mcs814x_init_machine(void)
+{
+	u32 bs2, cpu_mode;
+	int gpio;
+
+	bs2 = __raw_readl(mcs814x_sysdbg_base + SYSDBG_BS2);
+	cpu_mode = (bs2 >> CPU_MODE_SHIFT) & CPU_MODE_MASK;
+
+	pr_info("CPU mode: %s\n", cpu_modes[cpu_mode].name);
+
+	/* request the gpios since the pins are muxed for functionnality */
+	for (gpio = cpu_modes[cpu_mode].gpio_start;
+		gpio == cpu_modes[cpu_mode].gpio_end; gpio++) {
+		if (gpio != -1)
+			gpio_request(gpio, cpu_modes[cpu_mode].name);
I am not sure here, but shouldn't this be done with the pinctrl
subsystem instead?
+void __init mcs814x_map_io(void)
+{
+	iotable_init(mcs814x_io_desc, ARRAY_SIZE(mcs814x_io_desc));
+
+	mcs814x_sysdbg_base = ioremap(MCS814X_IO_START + MCS814X_SYSDBG,
+					MCS814X_SYSDBG_SIZE);
+	if (!mcs814x_sysdbg_base)
+		panic("unable to remap sysdbg base");
Any reason to have a static mapping initialized with iotable_init() and
then a dynamic mapping initialized with ioremap() ? I thought that
ioremap() wasn't ready at the ->map_io() time, and it was therefore the
reason we had static mappings.
quoted hunk ↗ jump to hunk
diff --git a/arch/arm/mach-mcs814x/include/mach/entry-macro.S b/arch/arm/mach-mcs814x/include/mach/entry-macro.S
new file mode 100644
index 0000000..cbad566
--- /dev/null
+++ b/arch/arm/mach-mcs814x/include/mach/entry-macro.S
@@ -0,0 +1,29 @@
+#include <mach/mcs814x.h>
+                .macro  disable_fiq
+                .endm
+
+		.macro arch_ret_to_user, tmp1, tmp2
+		.endm
+
+		.macro  get_irqnr_preamble, base, tmp
+		ldr	\base, =mcs814x_intc_base@ base virtual address of INTC
+		ldr	\base, [\base]
+		.endm
+
+		.macro  get_irqnr_and_base, irqnr, irqstat, base, tmp
+		mov	\tmp, #MCS814X_IRQ_STS0	 @ load tmp with STS0 register offset
+		ldr	\irqstat, [\base, \tmp]	 @ load value at base + tmp
+		tst	\irqstat, \irqstat       @ test if no active IRQ's
+		beq	1002f                    @ if no active irqs return with status 0
+		mov	\irqnr, #0               @ start from irq zero
+		mov	\tmp,   #1               @ the mask initially 1
+1001:
+		tst     \irqstat, \tmp           @ and with mask
+		addeq   \irqnr, \irqnr, #1       @ if  zero then add one to nr
+		moveq   \tmp,   \tmp, lsl #1     @ shift mask one to left
+		beq     1001b                    @ if  zero then loop again
+		mov     \irqstat, \tmp           @ save the return mask
+		mov	\tmp, #MCS814X_IRQ_STS0  @ load tmp with ICR offset
+		str     \irqstat,  [\base, \tmp] @ clear irq with selected mask
+1002:
+                .endm
Hum, you should instead use the MULTI_IRQ_HANDLER feature so that this
can be written in C.
quoted hunk ↗ jump to hunk
diff --git a/arch/arm/mach-mcs814x/include/mach/hardware.h b/arch/arm/mach-mcs814x/include/mach/hardware.h
new file mode 100644
index 0000000..529f648
--- /dev/null
+++ b/arch/arm/mach-mcs814x/include/mach/hardware.h
@@ -0,0 +1,16 @@
+/*
+ * Copyright (C) 2003 Artec Design Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ */
+
+#ifndef __ASM_ARCH_HARDWARE_H
+#define __ASM_ARCH_HARDWARE_H
This #define name no longer looks consistent with where the file is
located.
quoted hunk ↗ jump to hunk
+#include "mcs814x.h"
+
+#endif
+
diff --git a/arch/arm/mach-mcs814x/include/mach/irqs.h b/arch/arm/mach-mcs814x/include/mach/irqs.h
new file mode 100644
index 0000000..78021d1
--- /dev/null
+++ b/arch/arm/mach-mcs814x/include/mach/irqs.h
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2003 Artec Design Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ */
+
+#ifndef __ASM_ARCH_IRQS_H
+#define __ASM_ARCH_IRQS_H
+
+#define FIQ_START	0
+
+#define NR_IRQS		32
I think this shouldn't be needed if you use SPARSE_IRQ support.
+#define IRQ_PCI_INTA            22
+#define IRQ_PCI_INTB            23
+#define IRQ_PCI_INTC            24
+#define IRQ_PCI_INTD            26
And these probably belong to the DT somehow?
quoted hunk ↗ jump to hunk
diff --git a/arch/arm/mach-mcs814x/timer.c b/arch/arm/mach-mcs814x/timer.c
new file mode 100644
index 0000000..e8408e4
--- /dev/null
+++ b/arch/arm/mach-mcs814x/timer.c
@@ -0,0 +1,133 @@
+/*
+ * Moschip MCS814x timer routines
+ *
+ * Copyright (C) 2012, Florian Fainelli <florian@openwrt.org>
+ *
+ * Licensed under GPLv2
+ */
+#include <linux/kernel.h>
+#include <linux/interrupt.h>
+#include <linux/time.h>
+#include <linux/timex.h>
+#include <linux/irq.h>
+#include <linux/err.h>
+#include <linux/clk.h>
+#include <linux/io.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+
+#include <asm/mach/time.h>
+#include <mach/mcs814x.h>
+
+/* Timer block registers */
+#define TIMER_VAL	0x00
+#define TIMER_CTL	0x04
+
+static u32 last_reload;
+static u32 timer_correct;
+static u32 clock_rate;
+static u32 timer_reload_value;
+static void __iomem *mcs814x_timer_base;
+
+static inline unsigned long ticks2usecs(u32 x)
+{
+	return x / (clock_rate / 1000000);
+}
+
+/*
+ * Returns number of ms since last clock interrupt.  Note that interrupts
+ * will have been disabled by do_gettimeoffset()
+ */
+static unsigned long mcs814x_gettimeoffset(void)
+{
+	u32 ticks = __raw_readl(mcs814x_timer_base + TIMER_VAL);
+
+	if (ticks < last_reload)
+		return ticks2usecs(ticks + (u32)(0xffffffff - last_reload));
+	else
+		return ticks2usecs(ticks - last_reload);
+}
+
+
+static irqreturn_t mcs814x_timer_interrupt(int irq, void *dev_id)
+{
+	u32 count = __raw_readl(mcs814x_timer_base + TIMER_VAL);
+
+	/* take into account delay up to this moment */
+	last_reload = count + timer_correct + timer_reload_value;
+
+	if (last_reload < timer_reload_value)
+		last_reload = timer_reload_value;
+	else if (timer_correct == 0)
+		timer_correct = __raw_readl(mcs814x_timer_base + TIMER_VAL) -
+					count;
+
+	__raw_writel(last_reload, mcs814x_timer_base + TIMER_VAL);
+
+	timer_tick();
+
+	return IRQ_HANDLED;
+}
+
+static struct irqaction mcs814x_timer_irq = {
+	.name		= "mcs814x-timer",
+	.flags		= IRQF_DISABLED | IRQF_TIMER | IRQF_IRQPOLL,
+	.handler	= mcs814x_timer_interrupt,
+};
+
+static struct of_device_id mcs814x_timer_ids[] = {
+	{ .compatible = "moschip,mcs814x-timer" },
+	{ /* sentinel */ },
+};
+
+static void __init mcs814x_of_timer_init(void)
+{
+	struct device_node *np;
+	const unsigned int *intspec;
+
+	np = of_find_matching_node(NULL, mcs814x_timer_ids);
+	if (!np)
+		panic("unable to find compatible timer node in dtb");
+
+	mcs814x_timer_base = of_iomap(np, 0);
+	if (!mcs814x_timer_base)
+		panic("unable to remap timer cpu registers");
+
+	intspec = of_get_property(np, "interrupts", NULL);
+	if (!intspec)
+		panic("no interrupts property for timer");
+
+	mcs814x_timer_irq.irq = be32_to_cpup(intspec);
+}
+
+static void __init mcs814x_timer_init(void)
+{
+	struct clk *clk;
+
+	clk = clk_get_sys("timer0", NULL);
+	if (IS_ERR_OR_NULL(clk))
+		panic("unable to get timer0 clock");
+
+	clock_rate = clk_get_rate(clk);
+	clk_put(clk);
+
+	mcs814x_of_timer_init();
+
+	pr_info("Timer frequency: %d (kHz)\n", clock_rate / 1000);
+
+	timer_reload_value = 0xffffffff - (clock_rate / HZ);
+
+	/* disable timer */
+	__raw_writel(0, mcs814x_timer_base + TIMER_CTL);
+	__raw_writel(timer_reload_value, mcs814x_timer_base + TIMER_VAL);
+	last_reload = timer_reload_value;
+
+	setup_irq(mcs814x_timer_irq.irq, &mcs814x_timer_irq);
+	/* enable timer, stop timer in debug mode */
+	__raw_writel(0x03, mcs814x_timer_base + TIMER_CTL);
+}
+
+struct sys_timer mcs814x_timer = {
+	.init	= mcs814x_timer_init,
+	.offset	= mcs814x_gettimeoffset,
+};
I am surprised that this doesn't use the clocksource and clockevents
infrastructure. It probably should, and be implemented in
drivers/clocksource/.

Thomas
-- 
Thomas Petazzoni, Free Electrons
Kernel, drivers, real-time and embedded Linux
development, consulting, training and support.
http://free-electrons.com
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help