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

Thomas Petazzoni thomas.petazzoni at free-electrons.com
Mon Jul 16 08:29:41 EDT 2012


Hello Florian,

Le Sun, 15 Jul 2012 16:49:07 +0200,
Florian Fainelli <florian at openwrt.org> a écrit :

> 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.

> 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 at 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.

> 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.

> 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.

> +#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?

> 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 at 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



More information about the linux-arm-kernel mailing list