diff mbox

[06/10] ARM: clps711x: Add CLPS711X clocksource driver

Message ID 1374172501-26796-7-git-send-email-shc_work@mail.ru (mailing list archive)
State New, archived
Headers show

Commit Message

Alexander Shiyan July 18, 2013, 6:34 p.m. UTC
This adds the clocksource driver for Cirrus Logic CLPS711X series SoCs.
Designed primarily for migration CLPS711X subarch for multiplatform & DT,
for this as the "OF" and "not-OF" calls implemented.

Signed-off-by: Alexander Shiyan <shc_work@mail.ru>
---
 arch/arm/Kconfig                      |   2 -
 drivers/clocksource/Kconfig           |   6 ++
 drivers/clocksource/Makefile          |   1 +
 drivers/clocksource/clps711x-clksrc.c | 151 ++++++++++++++++++++++++++++++++++
 4 files changed, 158 insertions(+), 2 deletions(-)
 create mode 100644 drivers/clocksource/clps711x-clksrc.c

Comments

Daniel Lezcano July 19, 2013, 2:38 p.m. UTC | #1
Hi Alex,

(Added Thomas and John in Cc in case they want to review the patch).

On 07/18/2013 08:34 PM, Alexander Shiyan wrote:
> This adds the clocksource driver for Cirrus Logic CLPS711X series SoCs.
> Designed primarily for migration CLPS711X subarch for multiplatform & DT,
> for this as the "OF" and "not-OF" calls implemented.

When a new driver is submitted, I ask for a more detailed changelog
about the driver itself: how it works, any specificities, etc ...

That will help people to understand the code better at least for the review.

> Signed-off-by: Alexander Shiyan <shc_work@mail.ru>
> ---
>  arch/arm/Kconfig                      |   2 -
>  drivers/clocksource/Kconfig           |   6 ++
>  drivers/clocksource/Makefile          |   1 +
>  drivers/clocksource/clps711x-clksrc.c | 151 ++++++++++++++++++++++++++++++++++
>  4 files changed, 158 insertions(+), 2 deletions(-)
>  create mode 100644 drivers/clocksource/clps711x-clksrc.c
> 
> diff --git a/arch/arm/Kconfig b/arch/arm/Kconfig
> index dfb1e7f..5c60cb7 100644
> --- a/arch/arm/Kconfig
> +++ b/arch/arm/Kconfig
> @@ -370,10 +370,8 @@ config ARCH_CLPS711X
>  	bool "Cirrus Logic CLPS711x/EP721x/EP731x-based"
>  	select ARCH_REQUIRE_GPIOLIB
>  	select AUTO_ZRELADDR
> -	select CLKSRC_MMIO
>  	select COMMON_CLK
>  	select CPU_ARM720T
> -	select GENERIC_CLOCKEVENTS
>  	select MFD_SYSCON
>  	select MULTI_IRQ_HANDLER
>  	select SPARSE_IRQ
> diff --git a/drivers/clocksource/Kconfig b/drivers/clocksource/Kconfig
> index b7b9b04..bd6dc82 100644
> --- a/drivers/clocksource/Kconfig
> +++ b/drivers/clocksource/Kconfig
> @@ -41,6 +41,12 @@ config VT8500_TIMER
>  config CADENCE_TTC_TIMER
>  	bool
>  
> +config CLKSRC_CLPS711X
> +	def_bool y if ARCH_CLPS711X
> +	select CLKSRC_MMIO
> +	select CLKSRC_OF if OF
> +	select GENERIC_CLOCKEVENTS
> +
>  config CLKSRC_NOMADIK_MTU
>  	bool
>  	depends on (ARCH_NOMADIK || ARCH_U8500)
> diff --git a/drivers/clocksource/Makefile b/drivers/clocksource/Makefile
> index 8b00c5c..da6c102 100644
> --- a/drivers/clocksource/Makefile
> +++ b/drivers/clocksource/Makefile
> @@ -12,6 +12,7 @@ obj-$(CONFIG_CLKBLD_I8253)	+= i8253.o
>  obj-$(CONFIG_CLKSRC_MMIO)	+= mmio.o
>  obj-$(CONFIG_DW_APB_TIMER)	+= dw_apb_timer.o
>  obj-$(CONFIG_DW_APB_TIMER_OF)	+= dw_apb_timer_of.o
> +obj-$(CONFIG_CLKSRC_CLPS711X)	+= clps711x-clksrc.o
>  obj-$(CONFIG_CLKSRC_NOMADIK_MTU)	+= nomadik-mtu.o
>  obj-$(CONFIG_CLKSRC_DBX500_PRCMU)	+= clksrc-dbx500-prcmu.o
>  obj-$(CONFIG_ARMADA_370_XP_TIMER)	+= time-armada-370-xp.o
> diff --git a/drivers/clocksource/clps711x-clksrc.c b/drivers/clocksource/clps711x-clksrc.c
> new file mode 100644
> index 0000000..1749b0b
> --- /dev/null
> +++ b/drivers/clocksource/clps711x-clksrc.c
> @@ -0,0 +1,151 @@
> +/*
> + *  CLPS711X clocksource driver
> + *
> + *  Copyright (C) 2013 Alexander Shiyan <shc_work@mail.ru>
> + *
> + * 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.
> + */
> +
> +#include <linux/clk.h>
> +#include <linux/clockchips.h>
> +#include <linux/clocksource.h>
> +#include <linux/interrupt.h>
> +#include <linux/irq.h>
> +#include <linux/io.h>
> +#include <linux/ioport.h>
> +#include <linux/of_address.h>
> +#include <linux/of_irq.h>
> +#include <linux/sched_clock.h>
> +#include <linux/slab.h>
> +
> +#include <linux/mfd/syscon/clps711x.h>
> +
> +#define CLPS711X_SYSCON1	(0x0100)
> +#define CLPS711X_TC1D		(0x0300)
> +#define CLPS711X_TC2D		(0x0340)

Alignment.

> +
> +static struct {
> +	void __iomem	*tc1d;
> +	int		irq;
> +} *clps711x_clksrc;

You don't need to define this structure, the struct clock_event_device
already contains a field with irq.

> +static u32 notrace clps711x_sched_clock_read(void)
> +{
> +	return ~readw(clps711x_clksrc->tc1d);
> +}
> +
> +static void clps711x_clockevent_set_mode(enum clock_event_mode mode,
> +					 struct clock_event_device *evt)
> +{
> +	disable_irq(clps711x_clksrc->irq);

Do you really need to disable the interrupt to re-enable it right after ?

> +> +	switch (mode) {
> +	case CLOCK_EVT_MODE_PERIODIC:
> +		enable_irq(clps711x_clksrc->irq);
> +		break;
> +	case CLOCK_EVT_MODE_ONESHOT:
> +		/* Not supported */
> +	case CLOCK_EVT_MODE_SHUTDOWN:
> +	case CLOCK_EVT_MODE_UNUSED:
> +	case CLOCK_EVT_MODE_RESUME:
> +		/* Left event sources disabled, no more interrupts appear */
> +		break;
> +	}
> +}

I am not expert in interrupts, but it is possible this interrupt could
be shared ?

There isn't a register to enable/disable the timer ?

> +
> +static struct clock_event_device clps711x_clockevent = {
> +	.name		= "clps711x-clockevent",
> +	.rating		= 300,
> +	.features	= CLOCK_EVT_FEAT_PERIODIC,
> +	.set_mode	= clps711x_clockevent_set_mode,
> +};
> +
> +static irqreturn_t clps711x_timer_interrupt(int irq, void *dev_id)
> +{
> +	clps711x_clockevent.event_handler(&clps711x_clockevent);
> +
> +	return IRQ_HANDLED;
> +}
> +
> +static struct irqaction clps711x_timer_irq = {
> +	.name		= "clps711x-timer",
> +	.flags		= IRQF_TIMER | IRQF_IRQPOLL,

Why do you need IRQF_IRQPOOL ?

> +	.handler	= clps711x_timer_interrupt,
> +};
> +
> +static void __init _clps711x_clksrc_init(phys_addr_t phys_base, int irq,
> +					 struct clk *tc1, struct clk *tc2)
> +{
> +	unsigned long tc1_rate, tc2_rate;
> +	void __iomem *tc2d, *syscon1;
> +	u32 tmp;
> +
> +	BUG_ON(IS_ERR(tc1) || IS_ERR(tc2));
> +
> +	BUG_ON(!request_mem_region(phys_base + CLPS711X_TC1D, SZ_2, NULL));
> +	BUG_ON(!request_mem_region(phys_base + CLPS711X_TC2D, SZ_2, NULL));
> +
> +	clps711x_clksrc = kzalloc(sizeof(*clps711x_clksrc), GFP_KERNEL);
> +	BUG_ON(!clps711x_clksrc);
> +
> +	clps711x_clksrc->tc1d = ioremap(phys_base + CLPS711X_TC1D, SZ_2);
> +	BUG_ON(!clps711x_clksrc->tc1d);
> +
> +	tc2d = ioremap(phys_base + CLPS711X_TC2D, SZ_2);
> +	BUG_ON(!tc2d);
> +
> +	syscon1 = ioremap(phys_base + CLPS711X_SYSCON1, SZ_4);
> +	BUG_ON(!syscon1);
> +
> +	clps711x_clksrc->irq = irq;

	clps711x_clockevent.irq = irq;

	clps711x_clockevent.cpumask = ?

> +	tmp = readl(syscon1);
> +	/* TC1 in free running mode */
> +	tmp &= ~SYSCON1_TC1M;
> +	/* TC2 in prescale mode */
> +	tmp |= SYSCON1_TC2M;
> +	writel(tmp, syscon1);

Could you encapsulate these calls inside a static inline function with a
more detailed comment please ?

> +
> +	tc1_rate = clk_get_rate(tc1);
> +	tc2_rate = clk_get_rate(tc2);
> +
> +	clocksource_mmio_init(clps711x_clksrc->tc1d, "clps711x-clocksource",
> +			      tc1_rate, 300, 16, clocksource_mmio_readw_down);
> +
> +	setup_sched_clock(clps711x_sched_clock_read, 16, tc1_rate);
> +
> +	/* Set Timer prescaler */
> +	writew(DIV_ROUND_CLOSEST(tc2_rate, HZ), tc2d);
> +
> +	clockevents_config_and_register(&clps711x_clockevent, tc2_rate, 0, 0);
> +
> +	BUG_ON(setup_irq(clps711x_clksrc->irq, &clps711x_timer_irq));
> +}
> +
> +void __init clps711x_clksrc_init(phys_addr_t phys_base, int irq)

Is this function called somewhere ? I don't see the function definition ?

> +{
> +	struct clk *tc1 = clk_get(NULL, "tc1");
> +	struct clk *tc2 = clk_get(NULL, "tc2");
> +
> +	_clps711x_clksrc_init(phys_base, irq, tc1, tc2);
> +}
> +
> +#ifdef CONFIG_CLKSRC_OF
> +static void __init clps711x_clksrc_init_dt(struct device_node *np)
> +{
> +	struct clk *tc1 = of_clk_get_by_name(np, "tc1");
> +	struct clk *tc2 = of_clk_get_by_name(np, "tc2");
> +	struct resource res_io, res_irq;
> +
> +	BUG_ON(!of_address_to_resource(np, 0, &res_io));
> +
> +	BUG_ON(!of_irq_to_resource(np, 0, &res_irq));
> +
> +	_clps711x_clksrc_init(res_io.start, res_irq.start, tc1, tc2);
> +}
> +CLOCKSOURCE_OF_DECLARE(clps711x, "cirrus,clps711x-clocksource",
> +		       clps711x_clksrc_init_dt);
> +#endif
>
Alexander Shiyan July 20, 2013, 3:44 a.m. UTC | #2
On Fri, 19 Jul 2013 16:38:54 +0200
Daniel Lezcano <daniel.lezcano@linaro.org> wrote:
> (Added Thomas and John in Cc in case they want to review the patch).
> 
> On 07/18/2013 08:34 PM, Alexander Shiyan wrote:
> > This adds the clocksource driver for Cirrus Logic CLPS711X series SoCs.
> > Designed primarily for migration CLPS711X subarch for multiplatform & DT,
> > for this as the "OF" and "not-OF" calls implemented.
> 
> When a new driver is submitted, I ask for a more detailed changelog
> about the driver itself: how it works, any specificities, etc ...
> 
> That will help people to understand the code better at least for the review.

Basically, this series is a port of the CLPS711X subsystems in places specially
designed for this purpose. Additionally added to the standard description of the
subsystem for use with DT.
As for specs, I'm a little confused by this question. In this case, I'm doing
is not anything new for this CPU. The CPU has two timers and I use both to
provide the correct time sources.

> > Signed-off-by: Alexander Shiyan <shc_work@mail.ru>
> > ---
> >  arch/arm/Kconfig                      |   2 -
> >  drivers/clocksource/Kconfig           |   6 ++
> >  drivers/clocksource/Makefile          |   1 +
> >  drivers/clocksource/clps711x-clksrc.c | 151 ++++++++++++++++++++++++++++++++++
> >  4 files changed, 158 insertions(+), 2 deletions(-)
> >  create mode 100644 drivers/clocksource/clps711x-clksrc.c
[...]
> > +#define CLPS711X_SYSCON1	(0x0100)
> > +#define CLPS711X_TC1D		(0x0300)
> > +#define CLPS711X_TC2D		(0x0340)
> 
> Alignment.

Alignment? Do not see any problems.
Wrong alignment in this case is the result of quote.

> > +static struct {
> > +	void __iomem	*tc1d;
> > +	int		irq;
> > +} *clps711x_clksrc;
> 
> You don't need to define this structure, the struct clock_event_device
> already contains a field with irq.

Thank you, I will know.

[...]
> > +static void clps711x_clockevent_set_mode(enum clock_event_mode mode,
> > +					 struct clock_event_device *evt)
> > +{
> > +	disable_irq(clps711x_clksrc->irq);
> 
> Do you really need to disable the interrupt to re-enable it right after ?

That was the original implementation. Perhaps the best solution is to disable
interrupt by CLOCK_EVT_MODE_SHUTDOWN and enable it back by CLOCK_EVT_MODE_RESUME.

> > +> +	switch (mode) {
> > +	case CLOCK_EVT_MODE_PERIODIC:
> > +		enable_irq(clps711x_clksrc->irq);
> > +		break;
> > +	case CLOCK_EVT_MODE_ONESHOT:
> > +		/* Not supported */
> > +	case CLOCK_EVT_MODE_SHUTDOWN:
> > +	case CLOCK_EVT_MODE_UNUSED:
> > +	case CLOCK_EVT_MODE_RESUME:
> > +		/* Left event sources disabled, no more interrupts appear */
> > +		break;
> > +	}
> > +}
> 
> I am not expert in interrupts, but it is possible this interrupt could
> be shared ?

Timer interrupt? No.

> There isn't a register to enable/disable the timer ?

Unfortunately, no. The timer is running always, except STDBY CPU state,
so we can control it only by enable/disable interrupt.

[...]
> > +static struct irqaction clps711x_timer_irq = {
> > +	.name		= "clps711x-timer",
> > +	.flags		= IRQF_TIMER | IRQF_IRQPOLL,
> 
> Why do you need IRQF_IRQPOOL ?

linux/interrupt.h:
 * IRQF_IRQPOLL - Interrupt is used for polling (only the interrupt that is
 *                registered first in an shared interrupt is considered for
 *                performance reasons)

I do not see need this flag, but in fact, this flag is traditionally used
in almost all subsystems of this type.

[...]
> > +	tmp = readl(syscon1);
> > +	/* TC1 in free running mode */
> > +	tmp &= ~SYSCON1_TC1M;
> > +	/* TC2 in prescale mode */
> > +	tmp |= SYSCON1_TC2M;
> > +	writel(tmp, syscon1);
> 
> Could you encapsulate these calls inside a static inline function with a
> more detailed comment please ?

Why static inline?
There is a comment line for each bit, You think this is not enough?

[...]
> > +void __init clps711x_clksrc_init(phys_addr_t phys_base, int irq)
> 
> Is this function called somewhere ? I don't see the function definition ?

It is defined in the patch [9/10] for non-DT case.

[...]

Thanks.
Daniel Lezcano July 20, 2013, 9:48 p.m. UTC | #3
On 07/20/2013 05:44 AM, Alexander Shiyan wrote:
> On Fri, 19 Jul 2013 16:38:54 +0200
> Daniel Lezcano <daniel.lezcano@linaro.org> wrote:
>> (Added Thomas and John in Cc in case they want to review the patch).
>>
>> On 07/18/2013 08:34 PM, Alexander Shiyan wrote:
>>> This adds the clocksource driver for Cirrus Logic CLPS711X series SoCs.
>>> Designed primarily for migration CLPS711X subarch for multiplatform & DT,
>>> for this as the "OF" and "not-OF" calls implemented.
>>
>> When a new driver is submitted, I ask for a more detailed changelog
>> about the driver itself: how it works, any specificities, etc ...
>>
>> That will help people to understand the code better at least for the review.
> 
> Basically, this series is a port of the CLPS711X subsystems in places specially
> designed for this purpose. Additionally added to the standard description of the
> subsystem for use with DT.
> As for specs, I'm a little confused by this question. In this case, I'm doing
> is not anything new for this CPU. The CPU has two timers and I use both to
> provide the correct time sources.

I understand there is nothing extra in the code but I was talking about
some specificity of the driver if there are (eg. timer must be disabled
with disable/enable irq).

>>> Signed-off-by: Alexander Shiyan <shc_work@mail.ru>
>>> ---
>>>  arch/arm/Kconfig                      |   2 -
>>>  drivers/clocksource/Kconfig           |   6 ++
>>>  drivers/clocksource/Makefile          |   1 +
>>>  drivers/clocksource/clps711x-clksrc.c | 151 ++++++++++++++++++++++++++++++++++
>>>  4 files changed, 158 insertions(+), 2 deletions(-)
>>>  create mode 100644 drivers/clocksource/clps711x-clksrc.c
> [...]
>>> +#define CLPS711X_SYSCON1	(0x0100)
>>> +#define CLPS711X_TC1D		(0x0300)
>>> +#define CLPS711X_TC2D		(0x0340)
>>
>> Alignment.
> 
> Alignment? Do not see any problems.
> Wrong alignment in this case is the result of quote.

Ok.

>>> +static struct {
>>> +	void __iomem	*tc1d;
>>> +	int		irq;
>>> +} *clps711x_clksrc;
>>
>> You don't need to define this structure, the struct clock_event_device
>> already contains a field with irq.
> 
> Thank you, I will know.
> 
> [...]
>>> +static void clps711x_clockevent_set_mode(enum clock_event_mode mode,
>>> +					 struct clock_event_device *evt)
>>> +{
>>> +	disable_irq(clps711x_clksrc->irq);
>>
>> Do you really need to disable the interrupt to re-enable it right after ?
> 
> That was the original implementation. Perhaps the best solution is to disable
> interrupt by CLOCK_EVT_MODE_SHUTDOWN and enable it back by CLOCK_EVT_MODE_RESUME.
> 
>>> +> +	switch (mode) {
>>> +	case CLOCK_EVT_MODE_PERIODIC:
>>> +		enable_irq(clps711x_clksrc->irq);
>>> +		break;
>>> +	case CLOCK_EVT_MODE_ONESHOT:
>>> +		/* Not supported */
>>> +	case CLOCK_EVT_MODE_SHUTDOWN:
>>> +	case CLOCK_EVT_MODE_UNUSED:
>>> +	case CLOCK_EVT_MODE_RESUME:
>>> +		/* Left event sources disabled, no more interrupts appear */
>>> +		break;
>>> +	}
>>> +}
>>
>> I am not expert in interrupts, but it is possible this interrupt could
>> be shared ?
> 
> Timer interrupt? No.
> 
>> There isn't a register to enable/disable the timer ?
> 
> Unfortunately, no. The timer is running always, except STDBY CPU state,
> so we can control it only by enable/disable interrupt.

Ok.

> [...]
>>> +static struct irqaction clps711x_timer_irq = {
>>> +	.name		= "clps711x-timer",
>>> +	.flags		= IRQF_TIMER | IRQF_IRQPOLL,
>>
>> Why do you need IRQF_IRQPOOL ?
> 
> linux/interrupt.h:
>  * IRQF_IRQPOLL - Interrupt is used for polling (only the interrupt that is
>  *                registered first in an shared interrupt is considered for
>  *                performance reasons)
> 
> I do not see need this flag, but in fact, this flag is traditionally used
> in almost all subsystems of this type.

So IIUC, IRQPOLL is to switch to polling mode when a lot of interrupts
occur, right ?

> [...]
>>> +	tmp = readl(syscon1);
>>> +	/* TC1 in free running mode */
>>> +	tmp &= ~SYSCON1_TC1M;
>>> +	/* TC2 in prescale mode */
>>> +	tmp |= SYSCON1_TC2M;
>>> +	writel(tmp, syscon1);
>>
>> Could you encapsulate these calls inside a static inline function with a
>> more detailed comment please ?
> 
> Why static inline?
> There is a comment line for each bit, You think this is not enough?

I prefer to encapsulate readl/writel calls inside a small function with
an explicit name, comments are still welcome.

> [...]
>>> +void __init clps711x_clksrc_init(phys_addr_t phys_base, int irq)
>>
>> Is this function called somewhere ? I don't see the function definition ?
> 
> It is defined in the patch [9/10] for non-DT case.

Ok, thanks.
Alexander Shiyan July 21, 2013, 4:30 a.m. UTC | #4
On Sat, 20 Jul 2013 23:48:30 +0200
Daniel Lezcano <daniel.lezcano@linaro.org> wrote:

> On 07/20/2013 05:44 AM, Alexander Shiyan wrote:
> > On Fri, 19 Jul 2013 16:38:54 +0200
> > Daniel Lezcano <daniel.lezcano@linaro.org> wrote:
> >> (Added Thomas and John in Cc in case they want to review the patch).
> >>
> >> On 07/18/2013 08:34 PM, Alexander Shiyan wrote:
> >>> This adds the clocksource driver for Cirrus Logic CLPS711X series SoCs.
> >>> Designed primarily for migration CLPS711X subarch for multiplatform & DT,
> >>> for this as the "OF" and "not-OF" calls implemented.
[...]
> >>> +static struct irqaction clps711x_timer_irq = {
> >>> +	.name		= "clps711x-timer",
> >>> +	.flags		= IRQF_TIMER | IRQF_IRQPOLL,
> >>
> >> Why do you need IRQF_IRQPOOL ?
> > 
> > linux/interrupt.h:
> >  * IRQF_IRQPOLL - Interrupt is used for polling (only the interrupt that is
> >  *                registered first in an shared interrupt is considered for
> >  *                performance reasons)
> > 
> > I do not see need this flag, but in fact, this flag is traditionally used
> > in almost all subsystems of this type.
> 
> So IIUC, IRQPOLL is to switch to polling mode when a lot of interrupts
> occur, right ?

I'm also not an expert on these flags, but realized appointment just like you.

> >>> +	tmp = readl(syscon1);
> >>> +	/* TC1 in free running mode */
> >>> +	tmp &= ~SYSCON1_TC1M;
> >>> +	/* TC2 in prescale mode */
> >>> +	tmp |= SYSCON1_TC2M;
> >>> +	writel(tmp, syscon1);
> >>
> >> Could you encapsulate these calls inside a static inline function with a
> >> more detailed comment please ?
> > 
> > Why static inline?
> > There is a comment line for each bit, You think this is not enough?
> 
> I prefer to encapsulate readl/writel calls inside a small function with
> an explicit name, comments are still welcome.

Ok.

[...]
Mark Rutland Aug. 2, 2013, 10:46 a.m. UTC | #5
On Thu, Jul 18, 2013 at 07:34:57PM +0100, Alexander Shiyan wrote:
> This adds the clocksource driver for Cirrus Logic CLPS711X series SoCs.
> Designed primarily for migration CLPS711X subarch for multiplatform & DT,
> for this as the "OF" and "not-OF" calls implemented.
> 
> Signed-off-by: Alexander Shiyan <shc_work@mail.ru>
> ---

[...]

> diff --git a/drivers/clocksource/clps711x-clksrc.c b/drivers/clocksource/clps711x-clksrc.c
> new file mode 100644
> index 0000000..1749b0b
> --- /dev/null
> +++ b/drivers/clocksource/clps711x-clksrc.c
> @@ -0,0 +1,151 @@
> +/*
> + *  CLPS711X clocksource driver
> + *
> + *  Copyright (C) 2013 Alexander Shiyan <shc_work@mail.ru>
> + *
> + * 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.
> + */
> +
> +#include <linux/clk.h>
> +#include <linux/clockchips.h>
> +#include <linux/clocksource.h>
> +#include <linux/interrupt.h>
> +#include <linux/irq.h>
> +#include <linux/io.h>
> +#include <linux/ioport.h>
> +#include <linux/of_address.h>
> +#include <linux/of_irq.h>
> +#include <linux/sched_clock.h>
> +#include <linux/slab.h>
> +
> +#include <linux/mfd/syscon/clps711x.h>
> +
> +#define CLPS711X_SYSCON1	(0x0100)
> +#define CLPS711X_TC1D		(0x0300)
> +#define CLPS711X_TC2D		(0x0340)
> +
> +static struct {
> +	void __iomem	*tc1d;
> +	int		irq;
> +} *clps711x_clksrc;
> +
> +static u32 notrace clps711x_sched_clock_read(void)
> +{
> +	return ~readw(clps711x_clksrc->tc1d);
> +}
> +
> +static void clps711x_clockevent_set_mode(enum clock_event_mode mode,
> +					 struct clock_event_device *evt)
> +{
> +	disable_irq(clps711x_clksrc->irq);
> +
> +	switch (mode) {
> +	case CLOCK_EVT_MODE_PERIODIC:
> +		enable_irq(clps711x_clksrc->irq);
> +		break;
> +	case CLOCK_EVT_MODE_ONESHOT:
> +		/* Not supported */
> +	case CLOCK_EVT_MODE_SHUTDOWN:
> +	case CLOCK_EVT_MODE_UNUSED:
> +	case CLOCK_EVT_MODE_RESUME:
> +		/* Left event sources disabled, no more interrupts appear */
> +		break;
> +	}
> +}
> +
> +static struct clock_event_device clps711x_clockevent = {
> +	.name		= "clps711x-clockevent",
> +	.rating		= 300,
> +	.features	= CLOCK_EVT_FEAT_PERIODIC,
> +	.set_mode	= clps711x_clockevent_set_mode,
> +};

This seems to be a global clockevent, or a CPU0-only clockevent. Please
set the cpumask to clarify this, or core clockevent code will scream at
you (see clockevents_register_device).

I assume this doesn't stop in low power states (CLOCK_EVT_FEAT_C3STOP)?

> +
> +static irqreturn_t clps711x_timer_interrupt(int irq, void *dev_id)
> +{
> +	clps711x_clockevent.event_handler(&clps711x_clockevent);
> +
> +	return IRQ_HANDLED;
> +}
> +
> +static struct irqaction clps711x_timer_irq = {
> +	.name		= "clps711x-timer",
> +	.flags		= IRQF_TIMER | IRQF_IRQPOLL,
> +	.handler	= clps711x_timer_interrupt,
> +};

Is there any reason you need to use irqaction and can't use
{request,free}_irq?

> +
> +static void __init _clps711x_clksrc_init(phys_addr_t phys_base, int irq,
> +					 struct clk *tc1, struct clk *tc2)
> +{
> +	unsigned long tc1_rate, tc2_rate;
> +	void __iomem *tc2d, *syscon1;
> +	u32 tmp;
> +
> +	BUG_ON(IS_ERR(tc1) || IS_ERR(tc2));

Is this the only timer possible in the SoCs it'll be used in? You might
not be capable of initialising this timer, but another timer may be able
to keep the system running...

I don't think the BUGs are necessary, and more robust failure handling
would be preferable.

> +
> +	BUG_ON(!request_mem_region(phys_base + CLPS711X_TC1D, SZ_2, NULL));
> +	BUG_ON(!request_mem_region(phys_base + CLPS711X_TC2D, SZ_2, NULL));
> +
> +	clps711x_clksrc = kzalloc(sizeof(*clps711x_clksrc), GFP_KERNEL);
> +	BUG_ON(!clps711x_clksrc);
> +
> +	clps711x_clksrc->tc1d = ioremap(phys_base + CLPS711X_TC1D, SZ_2);
> +	BUG_ON(!clps711x_clksrc->tc1d);
> +
> +	tc2d = ioremap(phys_base + CLPS711X_TC2D, SZ_2);
> +	BUG_ON(!tc2d);
> +
> +	syscon1 = ioremap(phys_base + CLPS711X_SYSCON1, SZ_4);
> +	BUG_ON(!syscon1);
> +
> +	clps711x_clksrc->irq = irq;
> +
> +	tmp = readl(syscon1);
> +	/* TC1 in free running mode */
> +	tmp &= ~SYSCON1_TC1M;
> +	/* TC2 in prescale mode */
> +	tmp |= SYSCON1_TC2M;
> +	writel(tmp, syscon1);
> +
> +	tc1_rate = clk_get_rate(tc1);
> +	tc2_rate = clk_get_rate(tc2);
> +
> +	clocksource_mmio_init(clps711x_clksrc->tc1d, "clps711x-clocksource",
> +			      tc1_rate, 300, 16, clocksource_mmio_readw_down);
> +
> +	setup_sched_clock(clps711x_sched_clock_read, 16, tc1_rate);
> +
> +	/* Set Timer prescaler */
> +	writew(DIV_ROUND_CLOSEST(tc2_rate, HZ), tc2d);
> +
> +	clockevents_config_and_register(&clps711x_clockevent, tc2_rate, 0, 0);
> +
> +	BUG_ON(setup_irq(clps711x_clksrc->irq, &clps711x_timer_irq));

I think the last two actions should be reordered, what if upon
registering the timer the clockevents layer decides to program the
timer? You don't have your interrupt set up, yet you'll try to disable
and enable it...

Thanks,
Mark.
Alexander Shiyan Aug. 4, 2013, 12:31 p.m. UTC | #6
On Fri, 2 Aug 2013 11:46:35 +0100
Mark Rutland <mark.rutland@arm.com> wrote:

> On Thu, Jul 18, 2013 at 07:34:57PM +0100, Alexander Shiyan wrote:
> > This adds the clocksource driver for Cirrus Logic CLPS711X series SoCs.
> > Designed primarily for migration CLPS711X subarch for multiplatform & DT,
> > for this as the "OF" and "not-OF" calls implemented.
> > 
> > Signed-off-by: Alexander Shiyan <shc_work@mail.ru>
> > ---
> 
> [...]

> > +static struct clock_event_device clps711x_clockevent = {
> > +	.name		= "clps711x-clockevent",
> > +	.rating		= 300,
> > +	.features	= CLOCK_EVT_FEAT_PERIODIC,
> > +	.set_mode	= clps711x_clockevent_set_mode,
> > +};
> 
> This seems to be a global clockevent, or a CPU0-only clockevent. Please
> set the cpumask to clarify this, or core clockevent code will scream at
> you (see clockevents_register_device).
> 
> I assume this doesn't stop in low power states (CLOCK_EVT_FEAT_C3STOP)?

This feature is marked as "x86(64) specific" in the header.

[...]
> > +static void __init _clps711x_clksrc_init(phys_addr_t phys_base, int irq,
> > +					 struct clk *tc1, struct clk *tc2)
> > +{
> > +	unsigned long tc1_rate, tc2_rate;
> > +	void __iomem *tc2d, *syscon1;
> > +	u32 tmp;
> > +
> > +	BUG_ON(IS_ERR(tc1) || IS_ERR(tc2));
> 
> Is this the only timer possible in the SoCs it'll be used in? You might
> not be capable of initialising this timer, but another timer may be able
> to keep the system running...

This CPU have two HW timers only. Both are used here.
Mark Rutland Aug. 5, 2013, 5:02 p.m. UTC | #7
On Sun, Aug 04, 2013 at 01:31:13PM +0100, Alexander Shiyan wrote:
> On Fri, 2 Aug 2013 11:46:35 +0100
> Mark Rutland <mark.rutland@arm.com> wrote:
> 
> > On Thu, Jul 18, 2013 at 07:34:57PM +0100, Alexander Shiyan wrote:
> > > This adds the clocksource driver for Cirrus Logic CLPS711X series SoCs.
> > > Designed primarily for migration CLPS711X subarch for multiplatform & DT,
> > > for this as the "OF" and "not-OF" calls implemented.
> > > 
> > > Signed-off-by: Alexander Shiyan <shc_work@mail.ru>
> > > ---
> > 
> > [...]
> 
> > > +static struct clock_event_device clps711x_clockevent = {
> > > +	.name		= "clps711x-clockevent",
> > > +	.rating		= 300,
> > > +	.features	= CLOCK_EVT_FEAT_PERIODIC,
> > > +	.set_mode	= clps711x_clockevent_set_mode,
> > > +};
> > 
> > This seems to be a global clockevent, or a CPU0-only clockevent. Please
> > set the cpumask to clarify this, or core clockevent code will scream at
> > you (see clockevents_register_device).
> > 
> > I assume this doesn't stop in low power states (CLOCK_EVT_FEAT_C3STOP)?
> 
> This feature is marked as "x86(64) specific" in the header.

While the misfeature was at one point x86-specific, it's rather generic
now.

If your device doesn't stop when the CPU is put into a low power state,
then that's far better and there's nothing to worry about. I just
thought I'd check now to save any future time spent debugging odd stalls
:)

> 
> [...]
> > > +static void __init _clps711x_clksrc_init(phys_addr_t phys_base, int irq,
> > > +					 struct clk *tc1, struct clk *tc2)
> > > +{
> > > +	unsigned long tc1_rate, tc2_rate;
> > > +	void __iomem *tc2d, *syscon1;
> > > +	u32 tmp;
> > > +
> > > +	BUG_ON(IS_ERR(tc1) || IS_ERR(tc2));
> > 
> > Is this the only timer possible in the SoCs it'll be used in? You might
> > not be capable of initialising this timer, but another timer may be able
> > to keep the system running...
> 
> This CPU have two HW timers only. Both are used here.

Ok. So there clocks aren't likely to be used in futures SoCs with other
clocks?

Thanks,
Mark.
diff mbox

Patch

diff --git a/arch/arm/Kconfig b/arch/arm/Kconfig
index dfb1e7f..5c60cb7 100644
--- a/arch/arm/Kconfig
+++ b/arch/arm/Kconfig
@@ -370,10 +370,8 @@  config ARCH_CLPS711X
 	bool "Cirrus Logic CLPS711x/EP721x/EP731x-based"
 	select ARCH_REQUIRE_GPIOLIB
 	select AUTO_ZRELADDR
-	select CLKSRC_MMIO
 	select COMMON_CLK
 	select CPU_ARM720T
-	select GENERIC_CLOCKEVENTS
 	select MFD_SYSCON
 	select MULTI_IRQ_HANDLER
 	select SPARSE_IRQ
diff --git a/drivers/clocksource/Kconfig b/drivers/clocksource/Kconfig
index b7b9b04..bd6dc82 100644
--- a/drivers/clocksource/Kconfig
+++ b/drivers/clocksource/Kconfig
@@ -41,6 +41,12 @@  config VT8500_TIMER
 config CADENCE_TTC_TIMER
 	bool
 
+config CLKSRC_CLPS711X
+	def_bool y if ARCH_CLPS711X
+	select CLKSRC_MMIO
+	select CLKSRC_OF if OF
+	select GENERIC_CLOCKEVENTS
+
 config CLKSRC_NOMADIK_MTU
 	bool
 	depends on (ARCH_NOMADIK || ARCH_U8500)
diff --git a/drivers/clocksource/Makefile b/drivers/clocksource/Makefile
index 8b00c5c..da6c102 100644
--- a/drivers/clocksource/Makefile
+++ b/drivers/clocksource/Makefile
@@ -12,6 +12,7 @@  obj-$(CONFIG_CLKBLD_I8253)	+= i8253.o
 obj-$(CONFIG_CLKSRC_MMIO)	+= mmio.o
 obj-$(CONFIG_DW_APB_TIMER)	+= dw_apb_timer.o
 obj-$(CONFIG_DW_APB_TIMER_OF)	+= dw_apb_timer_of.o
+obj-$(CONFIG_CLKSRC_CLPS711X)	+= clps711x-clksrc.o
 obj-$(CONFIG_CLKSRC_NOMADIK_MTU)	+= nomadik-mtu.o
 obj-$(CONFIG_CLKSRC_DBX500_PRCMU)	+= clksrc-dbx500-prcmu.o
 obj-$(CONFIG_ARMADA_370_XP_TIMER)	+= time-armada-370-xp.o
diff --git a/drivers/clocksource/clps711x-clksrc.c b/drivers/clocksource/clps711x-clksrc.c
new file mode 100644
index 0000000..1749b0b
--- /dev/null
+++ b/drivers/clocksource/clps711x-clksrc.c
@@ -0,0 +1,151 @@ 
+/*
+ *  CLPS711X clocksource driver
+ *
+ *  Copyright (C) 2013 Alexander Shiyan <shc_work@mail.ru>
+ *
+ * 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.
+ */
+
+#include <linux/clk.h>
+#include <linux/clockchips.h>
+#include <linux/clocksource.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/io.h>
+#include <linux/ioport.h>
+#include <linux/of_address.h>
+#include <linux/of_irq.h>
+#include <linux/sched_clock.h>
+#include <linux/slab.h>
+
+#include <linux/mfd/syscon/clps711x.h>
+
+#define CLPS711X_SYSCON1	(0x0100)
+#define CLPS711X_TC1D		(0x0300)
+#define CLPS711X_TC2D		(0x0340)
+
+static struct {
+	void __iomem	*tc1d;
+	int		irq;
+} *clps711x_clksrc;
+
+static u32 notrace clps711x_sched_clock_read(void)
+{
+	return ~readw(clps711x_clksrc->tc1d);
+}
+
+static void clps711x_clockevent_set_mode(enum clock_event_mode mode,
+					 struct clock_event_device *evt)
+{
+	disable_irq(clps711x_clksrc->irq);
+
+	switch (mode) {
+	case CLOCK_EVT_MODE_PERIODIC:
+		enable_irq(clps711x_clksrc->irq);
+		break;
+	case CLOCK_EVT_MODE_ONESHOT:
+		/* Not supported */
+	case CLOCK_EVT_MODE_SHUTDOWN:
+	case CLOCK_EVT_MODE_UNUSED:
+	case CLOCK_EVT_MODE_RESUME:
+		/* Left event sources disabled, no more interrupts appear */
+		break;
+	}
+}
+
+static struct clock_event_device clps711x_clockevent = {
+	.name		= "clps711x-clockevent",
+	.rating		= 300,
+	.features	= CLOCK_EVT_FEAT_PERIODIC,
+	.set_mode	= clps711x_clockevent_set_mode,
+};
+
+static irqreturn_t clps711x_timer_interrupt(int irq, void *dev_id)
+{
+	clps711x_clockevent.event_handler(&clps711x_clockevent);
+
+	return IRQ_HANDLED;
+}
+
+static struct irqaction clps711x_timer_irq = {
+	.name		= "clps711x-timer",
+	.flags		= IRQF_TIMER | IRQF_IRQPOLL,
+	.handler	= clps711x_timer_interrupt,
+};
+
+static void __init _clps711x_clksrc_init(phys_addr_t phys_base, int irq,
+					 struct clk *tc1, struct clk *tc2)
+{
+	unsigned long tc1_rate, tc2_rate;
+	void __iomem *tc2d, *syscon1;
+	u32 tmp;
+
+	BUG_ON(IS_ERR(tc1) || IS_ERR(tc2));
+
+	BUG_ON(!request_mem_region(phys_base + CLPS711X_TC1D, SZ_2, NULL));
+	BUG_ON(!request_mem_region(phys_base + CLPS711X_TC2D, SZ_2, NULL));
+
+	clps711x_clksrc = kzalloc(sizeof(*clps711x_clksrc), GFP_KERNEL);
+	BUG_ON(!clps711x_clksrc);
+
+	clps711x_clksrc->tc1d = ioremap(phys_base + CLPS711X_TC1D, SZ_2);
+	BUG_ON(!clps711x_clksrc->tc1d);
+
+	tc2d = ioremap(phys_base + CLPS711X_TC2D, SZ_2);
+	BUG_ON(!tc2d);
+
+	syscon1 = ioremap(phys_base + CLPS711X_SYSCON1, SZ_4);
+	BUG_ON(!syscon1);
+
+	clps711x_clksrc->irq = irq;
+
+	tmp = readl(syscon1);
+	/* TC1 in free running mode */
+	tmp &= ~SYSCON1_TC1M;
+	/* TC2 in prescale mode */
+	tmp |= SYSCON1_TC2M;
+	writel(tmp, syscon1);
+
+	tc1_rate = clk_get_rate(tc1);
+	tc2_rate = clk_get_rate(tc2);
+
+	clocksource_mmio_init(clps711x_clksrc->tc1d, "clps711x-clocksource",
+			      tc1_rate, 300, 16, clocksource_mmio_readw_down);
+
+	setup_sched_clock(clps711x_sched_clock_read, 16, tc1_rate);
+
+	/* Set Timer prescaler */
+	writew(DIV_ROUND_CLOSEST(tc2_rate, HZ), tc2d);
+
+	clockevents_config_and_register(&clps711x_clockevent, tc2_rate, 0, 0);
+
+	BUG_ON(setup_irq(clps711x_clksrc->irq, &clps711x_timer_irq));
+}
+
+void __init clps711x_clksrc_init(phys_addr_t phys_base, int irq)
+{
+	struct clk *tc1 = clk_get(NULL, "tc1");
+	struct clk *tc2 = clk_get(NULL, "tc2");
+
+	_clps711x_clksrc_init(phys_base, irq, tc1, tc2);
+}
+
+#ifdef CONFIG_CLKSRC_OF
+static void __init clps711x_clksrc_init_dt(struct device_node *np)
+{
+	struct clk *tc1 = of_clk_get_by_name(np, "tc1");
+	struct clk *tc2 = of_clk_get_by_name(np, "tc2");
+	struct resource res_io, res_irq;
+
+	BUG_ON(!of_address_to_resource(np, 0, &res_io));
+
+	BUG_ON(!of_irq_to_resource(np, 0, &res_irq));
+
+	_clps711x_clksrc_init(res_io.start, res_irq.start, tc1, tc2);
+}
+CLOCKSOURCE_OF_DECLARE(clps711x, "cirrus,clps711x-clocksource",
+		       clps711x_clksrc_init_dt);
+#endif