diff mbox

[v3,2/2] clocksource: Add renesas-ostm timer driver

Message ID 20170123135423.28780-3-chris.brandt@renesas.com (mailing list archive)
State Superseded
Delegated to: Geert Uytterhoeven
Headers show

Commit Message

Chris Brandt Jan. 23, 2017, 1:54 p.m. UTC
This patch adds a OSTM driver for the Renesas architecture.

Signed-off-by: Chris Brandt <chris.brandt@renesas.com>

---
v2:
* changed implementation to be independent channel nodes
---
 arch/arm/mach-shmobile/Kconfig     |   1 +
 drivers/clocksource/Kconfig        |  12 ++
 drivers/clocksource/Makefile       |   1 +
 drivers/clocksource/renesas-ostm.c | 349 +++++++++++++++++++++++++++++++++++++
 4 files changed, 363 insertions(+)
 create mode 100644 drivers/clocksource/renesas-ostm.c

Comments

Daniel Lezcano Jan. 23, 2017, 5:52 p.m. UTC | #1
On Mon, Jan 23, 2017 at 08:54:23AM -0500, Chris Brandt wrote:
> This patch adds a OSTM driver for the Renesas architecture.

As it is a new driver, please give technical details for the log.
 
Replace ioread/write by readl/writel in the code.

> Signed-off-by: Chris Brandt <chris.brandt@renesas.com>
> 
> ---
> v2:
> * changed implementation to be independent channel nodes
> ---
>  arch/arm/mach-shmobile/Kconfig     |   1 +
>  drivers/clocksource/Kconfig        |  12 ++
>  drivers/clocksource/Makefile       |   1 +
>  drivers/clocksource/renesas-ostm.c | 349 +++++++++++++++++++++++++++++++++++++
>  4 files changed, 363 insertions(+)
>  create mode 100644 drivers/clocksource/renesas-ostm.c
> 
> diff --git a/arch/arm/mach-shmobile/Kconfig b/arch/arm/mach-shmobile/Kconfig
> index 2bb4b09..b928634 100644
> --- a/arch/arm/mach-shmobile/Kconfig
> +++ b/arch/arm/mach-shmobile/Kconfig
> @@ -57,6 +57,7 @@ config ARCH_R7S72100
>  	select PM
>  	select PM_GENERIC_DOMAINS
>  	select SYS_SUPPORTS_SH_MTU2
> +	select SYS_SUPPORTS_RENESAS_OSTM

- select SYS_SUPPORTS_RENESAS_OSTM
+ select RENESAS_OSTM

>  
>  config ARCH_R8A73A4
>  	bool "R-Mobile APE6 (R8A73A40)"
> diff --git a/drivers/clocksource/Kconfig b/drivers/clocksource/Kconfig
> index 4866f7a..95c8d56 100644
> --- a/drivers/clocksource/Kconfig
> +++ b/drivers/clocksource/Kconfig
> @@ -431,6 +431,9 @@ config MTK_TIMER
>  config SYS_SUPPORTS_SH_MTU2
>          bool
>  
> +config SYS_SUPPORTS_RENESAS_OSTM
> +        bool
> +

-config SYS_SUPPORTS_RENESAS_OSTM
-        bool
-

>  config SYS_SUPPORTS_SH_TMU
>          bool
>  
> @@ -467,6 +470,15 @@ config SH_TIMER_MTU2
>  	  Timer Pulse Unit 2 (MTU2) hardware available on SoCs from Renesas.
>  	  This hardware comes with 16 bit-timer registers.
>  
> +config RENESAS_OSTM
> +	bool "Renesas OSTM timer driver" if COMPILE_TEST
> +	depends on GENERIC_CLOCKEVENTS
> +	select CLKSRC_MMIO
> +	default SYS_SUPPORTS_RENESAS_OSTM

- default SYS_SUPPORTS_RENESAS_OSTM

Except I missing something, I don' the point of having this intermediate
config option.

> +	help
> +	  This enables the build of the OSTM timer driver.
> +	  It creates a clock source and clock event device.
> +
>  config SH_TIMER_TMU
>  	bool "Renesas TMU timer driver" if COMPILE_TEST
>  	depends on GENERIC_CLOCKEVENTS
> diff --git a/drivers/clocksource/Makefile b/drivers/clocksource/Makefile
> index a14111e..bbd163b 100644
> --- a/drivers/clocksource/Makefile
> +++ b/drivers/clocksource/Makefile
> @@ -8,6 +8,7 @@ obj-$(CONFIG_CS5535_CLOCK_EVENT_SRC)	+= cs5535-clockevt.o
>  obj-$(CONFIG_CLKSRC_JCORE_PIT)		+= jcore-pit.o
>  obj-$(CONFIG_SH_TIMER_CMT)	+= sh_cmt.o
>  obj-$(CONFIG_SH_TIMER_MTU2)	+= sh_mtu2.o
> +obj-$(CONFIG_RENESAS_OSTM)	+= renesas-ostm.o
>  obj-$(CONFIG_SH_TIMER_TMU)	+= sh_tmu.o
>  obj-$(CONFIG_EM_TIMER_STI)	+= em_sti.o
>  obj-$(CONFIG_CLKBLD_I8253)	+= i8253.o
> diff --git a/drivers/clocksource/renesas-ostm.c b/drivers/clocksource/renesas-ostm.c
> new file mode 100644
> index 0000000..37f2461
> --- /dev/null
> +++ b/drivers/clocksource/renesas-ostm.c
> @@ -0,0 +1,349 @@
> +/*
> + * Renesas Timer Support - OSTM
> + *
> + * 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
> + *
> + * 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.
> + *
> + */
> +
> +#include <linux/platform_device.h>
> +#include <linux/clk.h>
> +#include <linux/clockchips.h>
> +#include <linux/interrupt.h>
> +#include <linux/slab.h>
> +#include <linux/module.h>

Please remove everything module related here and below. This driver is not
supposed to be removed and it is compiled in with the Kconfig option.

> +#include <linux/pm_runtime.h>
> +#include <linux/sched_clock.h>
> +
> +/*
> + * The OSTM contains independent channels.
> + * The first OSTM channel probed will be set up as a free running
> + * clocksource. Additionally we will use this clocksource for the system
> + * schedule timer sched_clock().
> + *
> + * The second (or more) channel probed will be set up as an interrupt
> + * driven clock event.
> + */
> +
> +struct ostm_device {
> +	struct platform_device *pdev;
> +	int irq;
> +	struct clk *clk;
> +	unsigned long rate;
> +	void __iomem *base;
> +	unsigned long ticks_per_jiffy;
> +	struct clock_event_device ced;
> +};

You can probably reduce the size of this structure by removing some fields
which are used only at init time.

> +
> +static void __iomem *system_clock;	/* For sched_clock() */
> +
> +/* OSTM REGISTERS */
> +#define	OSTM_CMP		0x000	/* RW,32 */
> +#define	OSTM_CNT		0x004	/* R,32 */
> +#define	OSTM_TE			0x010	/* R,8 */
> +#define	OSTM_TS			0x014	/* W,8 */
> +#define	OSTM_TT			0x018	/* W,8 */
> +#define	OSTM_CTL		0x020	/* RW,8 */
> +
> +#define	TE			0x01
> +#define	TS			0x01
> +#define	TT			0x01
> +#define	CTL_PERIODIC		0x00
> +#define	CTL_ONESHOT		0x02
> +#define	CTL_FREERUN		0x02
> +
> +static struct ostm_device *ced_to_ostm(struct clock_event_device *ced)
> +{
> +	return container_of(ced, struct ostm_device, ced);
> +}
> +
> +static int __init ostm_init_clksrc(struct ostm_device *ostm)
> +{
> +	int ret;
> +
> +	/* irq not used (clock sources don't use interrupts) */
> +
> +	/* stop counter */

One line comment is usually in the network code. Please use multiline.

/*
 * Bla bla
 */

> +	iowrite8(TT, ostm->base + OSTM_TT);
> +	while (ioread8(ostm->base + OSTM_TE) & TE)
> +		;

Comment here for this dangerous loop. Explain why we can't be stuck here.

> +
> +	/* setup as freerun */
> +	iowrite32(0, ostm->base + OSTM_CMP);
> +	iowrite8(CTL_FREERUN, ostm->base + OSTM_CTL);
> +	iowrite8(TS, ostm->base + OSTM_TS);
> +
> +	/* register */
> +	ret = clocksource_mmio_init(ostm->base + OSTM_CNT,
> +			"ostm", ostm->rate,
> +			300, 32, clocksource_mmio_readl_up);
> +
> +	return ret;

return clocksource_mmio_init(...);

> +}
> +
> +static u64 notrace ostm_read_sched_clock(void)
> +{
> +	return ioread32(system_clock);
> +}
> +
> +static int __init ostm_init_sched_clock(struct ostm_device *ostm)
> +{
> +	unsigned long flags;
> +
> +	system_clock = ostm->base + OSTM_CNT;
> +	local_irq_save(flags);
> +	local_irq_disable();

1. local_irq_disable() is not needed, local_irq_save() saves the irq flags and
disables the irq.

2. Why do you need to disable the irq here ?

> +	sched_clock_register(ostm_read_sched_clock, 32, ostm->rate);
> +	local_irq_restore(flags);
> +
> +	return 0;
> +}
> +
> +static void ostm_clkevt_timer_stop(struct ostm_device *ostm)
> +{
> +	if (ioread8(ostm->base + OSTM_TE) & TE) {
> +		iowrite8(TT, ostm->base + OSTM_TT);
> +		while (ioread8(ostm->base + OSTM_TE) & TE)
> +			;

Same comment as above.

> +	}
> +}
> +
> +static int ostm_clock_event_next(unsigned long delta,
> +				     struct clock_event_device *ced)
> +{
> +	struct ostm_device *ostm = ced_to_ostm(ced);
> +
> +	WARN_ON(!clockevent_state_oneshot(ced));

Pointless check. It is up to the time framework to handle that and that is the
case.

> +
> +	ostm_clkevt_timer_stop(ostm);
> +
> +	iowrite32(delta, ostm->base + OSTM_CMP);
> +	iowrite8(CTL_ONESHOT, ostm->base + OSTM_CTL);
> +	iowrite8(TS, ostm->base + OSTM_TS);
> +
> +	return 0;
> +}
> +
> +static int ostm_shutdown(struct clock_event_device *ced)
> +{
> +	struct ostm_device *ostm = ced_to_ostm(ced);
> +
> +	ostm_clkevt_timer_stop(ostm);
> +
> +	return 0;
> +}
> +static int ostm_set_periodic(struct clock_event_device *ced)
> +{
> +	struct ostm_device *ostm = ced_to_ostm(ced);
> +
> +	if (clockevent_state_oneshot(ced) || clockevent_state_periodic(ced))
> +		ostm_clkevt_timer_stop(ostm);
> +
> +	iowrite32(ostm->ticks_per_jiffy - 1, ostm->base + OSTM_CMP);
> +	iowrite8(CTL_PERIODIC, ostm->base + OSTM_CTL);
> +	iowrite8(TS, ostm->base + OSTM_TS);
> +
> +	return 0;
> +}
> +
> +static int ostm_set_oneshot(struct clock_event_device *ced)
> +{
> +	struct ostm_device *ostm = ced_to_ostm(ced);
> +
> +	ostm_clkevt_timer_stop(ostm);
> +
> +	return 0;
> +}
> +
> +static irqreturn_t ostm_timer_interrupt(int irq, void *dev_id)
> +{
> +	struct ostm_device *ostm = dev_id;
> +
> +	if (clockevent_state_oneshot(&ostm->ced))
> +		ostm_clkevt_timer_stop(ostm);
> +
> +	/* notify clockevent layer */
> +	if (ostm->ced.event_handler)
> +		ostm->ced.event_handler(&ostm->ced);
> +
> +	return IRQ_HANDLED;
> +}
> +
> +static int __init ostm_init_clkevt(struct ostm_device *ostm)
> +{
> +	struct clock_event_device *ced = &ostm->ced;
> +	int ret = -ENXIO;
> +
> +	ret = request_irq(ostm->irq, ostm_timer_interrupt,
> +			  IRQF_TIMER | IRQF_IRQPOLL | IRQF_NOBALANCING,
> +			  dev_name(&ostm->pdev->dev), ostm);

	devm_request_irq

Are you sure the IRQF_NOBALANCING flag should be used ? By default the
interrupt is pinned to cpu0 below. If this timer is used as a broadcast timer
for a system with deep idle, you may want to add the DYNIRQ flag feature to
improve the wakeups on the system.

> +	if (ret) {
> +		dev_err(&ostm->pdev->dev, "failed to request irq\n");
> +		return ret;
> +	}
> +
> +	ced->name = "ostm";
> +	ced->features = CLOCK_EVT_FEAT_ONESHOT | CLOCK_EVT_FEAT_PERIODIC;
> +	ced->set_state_shutdown = ostm_shutdown;
> +	ced->set_state_periodic = ostm_set_periodic;
> +	ced->set_state_oneshot = ostm_set_oneshot;
> +	ced->set_next_event = ostm_clock_event_next;
> +	ced->shift = 32;
> +	ced->rating = 300;
> +	ced->cpumask = cpumask_of(0);
> +	clockevents_config_and_register(ced, ostm->rate, 0xf, 0xffffffff);
> +
> +	return 0;
> +}
> +
> +static int __init ostm_probe(struct platform_device *pdev)
> +{
> +	struct ostm_device *ostm;
> +	struct resource *res;
> +	int ret = -EFAULT;
> +
> +	if (!is_early_platform_device(pdev)) {
> +		pm_runtime_set_active(&pdev->dev);
> +		pm_runtime_enable(&pdev->dev);
> +	}

Can you clarify why the 'early' is needed here ?

I don't see the pm_runtime_get/pm_runtime_put in the corresponding function.

What is the benefit of using pm_runtime in this driver ? Isn't the timer
supposed to be in an always-on power domain ?

> +	ostm = platform_get_drvdata(pdev);
> +	if (ostm) {
> +		dev_info(&pdev->dev, "kept as earlytimer\n");
> +		ret = 0;
> +		goto out;
> +	}
> +
> +	ostm = kzalloc(sizeof(*ostm), GFP_KERNEL);

	devm_kzalloc ?

> +	if (!ostm) {
> +		dev_err(&pdev->dev, "failed to allocate memory\n");

A memory failure allocation always triggers a dumpstack (IIRC), so this
error message is pointless.

> +		return -ENOMEM;
> +	}
> +
> +	ostm->pdev = pdev;
> +	platform_set_drvdata(ostm->pdev, ostm);
> +
> +	res = platform_get_resource(ostm->pdev, IORESOURCE_MEM, 0);
> +	if (!res) {
> +		dev_err(&ostm->pdev->dev, "failed to get I/O memory\n");
> +		goto err;
> +	}
> +
> +	ostm->base = ioremap_nocache(res->start, resource_size(res));

	devm_ioremap_nocache ?

> +	if (!ostm->base) {
> +		dev_err(&ostm->pdev->dev, "failed to remap I/O memory\n");
> +		goto err;
> +	}
> +
> +	ostm->irq = platform_get_irq(ostm->pdev, 0);
> +	if (ostm->irq < 0) {
> +		dev_err(&ostm->pdev->dev, "failed to get irq\n");
> +		goto err;
> +	}
> +
> +	ostm->clk = devm_clk_get(&pdev->dev, NULL);
> +	if (IS_ERR(ostm->clk)) {
> +		dev_err(&ostm->pdev->dev, "failed to get clock\n");
> +		ostm->clk = NULL;
> +		goto err;
> +	}
> +
> +	ret = clk_prepare_enable(ostm->clk);
> +	if (ret) {
> +		dev_err(&ostm->pdev->dev, "failed to enable clock\n");
> +		goto err;
> +	}
> +
> +	ostm->rate = clk_get_rate(ostm->clk);
> +	ostm->ticks_per_jiffy = (ostm->rate + HZ / 2) / HZ;
> +
> +	/* First probed device will be used as system clocksource */
> +	if (!system_clock) {
> +		/* use as clocksource */
> +		ret = ostm_init_clksrc(ostm);
> +
> +		/* use as system scheduling clock */
> +		if (!ret)
> +			ret = ostm_init_sched_clock(ostm);
> +
> +		if (ret) {
> +			dev_err(&pdev->dev, "failed to use as sched_clock\n");
> +			system_clock = (void *)-1; /* prevent future attempts */
> +			ret = 0;	/* still works as clocksource */
> +		}

This error code check is unnecessary complex. ostm_init_sched_clock always
return zero.

	if (!system_clock) {
		system_clock = ostm_init_sched_clock(ostm);
		ret = ostm_init_clksrc(ostm)
		if (ret)
			dev_err("Failed to initialize the clocksource\n");
	} else {

		...
	}

This is not perfect but until I send the clksrc_of/clkevt_of split changes, we
have to deal with these kind of hacks.

> +		if (!ret)
> +			dev_info(&pdev->dev, "used for clocksource\n");
> +	} else {
> +		/* use as clock event */
> +		ret = ostm_init_clkevt(ostm);
> +
> +		if (!ret)
> +			dev_info(&pdev->dev, "used for clock events\n");
> +	}
> +
> +err:
> +	if (ret) {
> +		if (ostm->clk)
> +			clk_disable_unprepare(ostm->clk);
> +		if (ostm->base)
> +			iounmap(ostm->base);
> +		kfree(ostm);
> +		platform_set_drvdata(pdev, NULL);

As iomap, kzalloc were done with the devm, then the rollback will be handled
with the device framework.

> +		pm_runtime_idle(&pdev->dev);
> +		return ret;
> +	}
> +
> +	if (is_early_platform_device(pdev))
> +		return ret;
> +
> +out:
> +	pm_runtime_irq_safe(&pdev->dev);
> +
> +	return ret;
> +}
> +
> +static int ostm_remove(struct platform_device *pdev)
> +{
> +	return -EBUSY;	/* cannot unregister clockevent */
> +}
> +
> +static const struct of_device_id ostm_of_table[] __maybe_unused = {
> +	{ .compatible = "renesas,ostm" },
> +	{ }
> +};
> +MODULE_DEVICE_TABLE(of, ostm_of_table);
> +
> +static struct platform_driver ostm_timer = {
> +	.probe		= ostm_probe,
> +	.remove		= ostm_remove,
> +	.driver	= {
> +		.name	= "ostm",
> +		.of_match_table = of_match_ptr(ostm_of_table),
> +	},
> +};
> +
> +static int __init ostm_init(void)
> +{
> +	return platform_driver_register(&ostm_timer);
> +}
> +
> +static void __exit ostm_exit(void)
> +{
> +	platform_driver_unregister(&ostm_timer);
> +}
> +
> +early_platform_init("earlytimer", &ostm_timer);
> +subsys_initcall(ostm_init);
> +module_exit(ostm_exit);
> +
> +MODULE_AUTHOR("Chris Brandt");
> +MODULE_DESCRIPTION("Renesas OSTM Timer Driver");
> +MODULE_LICENSE("GPL v2");

Maybe you can try with builtin_platform ?

  -- Daniel
Chris Brandt Jan. 24, 2017, 4:45 a.m. UTC | #2
Hello Daniel,

Thank you for the review.
Your suggestions were very helpful to me.

I basically made all the changes, but I had some questions on
the Kconfig and multi-line commenting.


On Monday, January 23, 2017, Daniel Lezcano wrote:
> > This patch adds a OSTM driver for the Renesas architecture.

> 

> As it is a new driver, please give technical details for the log.


OK.


> Replace ioread/write by readl/writel in the code.


OK.


> > @@ -467,6 +470,15 @@ config SH_TIMER_MTU2

> >  	  Timer Pulse Unit 2 (MTU2) hardware available on SoCs from Renesas.

> >  	  This hardware comes with 16 bit-timer registers.

> >

> > +config RENESAS_OSTM

> > +	bool "Renesas OSTM timer driver" if COMPILE_TEST

> > +	depends on GENERIC_CLOCKEVENTS

> > +	select CLKSRC_MMIO

> > +	default SYS_SUPPORTS_RENESAS_OSTM

> 

> - default SYS_SUPPORTS_RENESAS_OSTM

> 

> Except I missing something, I don' the point of having this intermediate

> config option.


I was basically following what all the other clocksource drivers do.
Selecting an ARCH will then set SYS_SUPPORTS_xxTIMERxx which will then
force xxTIMERxx=y.
But, if "COMPILE_TEST" is set, you can choose what clocksource timers
you want to build in.

Is your suggestion to do away with the COMPILE_TEST ability and
just force RENESAS_OSTM=y if ARCH_R7S72100 is selected?



> > +#include <linux/module.h>

> 

> Please remove everything module related here and below. This driver is not

> supposed to be removed and it is compiled in with the Kconfig option.


Good point. I will remove.

I guess if you can't compile it as a module, you don't need things like
'MODULE_LICENSE'.


> > +struct ostm_device {

> > +	struct platform_device *pdev;

> > +	int irq;

> > +	struct clk *clk;

> > +	unsigned long rate;

> > +	void __iomem *base;

> > +	unsigned long ticks_per_jiffy;

> > +	struct clock_event_device ced;

> > +};

> 

> You can probably reduce the size of this structure by removing some fields

> which are used only at init time.


Looks like I can get rid of 'clk', 'irq' and 'rate'.
Thanks.



> > +static int __init ostm_init_clksrc(struct ostm_device *ostm) {

> > +	int ret;

> > +

> > +	/* irq not used (clock sources don't use interrupts) */

> > +

> > +	/* stop counter */

> 

> One line comment is usually in the network code. Please use multiline.

> 

> /*

>  * Bla bla

>  */


I'm confused. Do you mean:

A) use multiline formatting for every single-line comment throughout
   the file?

B) use multiline for any place where you have 2 or more single-line
   comments back to back?


> > +	iowrite8(TT, ostm->base + OSTM_TT);

> > +	while (ioread8(ostm->base + OSTM_TE) & TE)

> > +		;

> 

> Comment here for this dangerous loop. Explain why we can't be stuck here.


OK, I'll add something in as it's pretty safe.

> 

> > +

> > +	/* setup as freerun */

> > +	iowrite32(0, ostm->base + OSTM_CMP);

> > +	iowrite8(CTL_FREERUN, ostm->base + OSTM_CTL);

> > +	iowrite8(TS, ostm->base + OSTM_TS);

> > +

> > +	/* register */

> > +	ret = clocksource_mmio_init(ostm->base + OSTM_CNT,

> > +			"ostm", ostm->rate,

> > +			300, 32, clocksource_mmio_readl_up);

> > +

> > +	return ret;

> 

> return clocksource_mmio_init(...);


OK.


> > +static int __init ostm_init_sched_clock(struct ostm_device *ostm) {

> > +	unsigned long flags;

> > +

> > +	system_clock = ostm->base + OSTM_CNT;

> > +	local_irq_save(flags);

> > +	local_irq_disable();

> 

> 1. local_irq_disable() is not needed, local_irq_save() saves the irq flags

> and disables the irq.

> 

> 2. Why do you need to disable the irq here ?


OK, I see that none of the other drivers are disabling irq, so I'll
take that code out.
Thanks.


> > +static int ostm_clock_event_next(unsigned long delta,

> > +				     struct clock_event_device *ced) {

> > +	struct ostm_device *ostm = ced_to_ostm(ced);

> > +

> > +	WARN_ON(!clockevent_state_oneshot(ced));

> 

> Pointless check. It is up to the time framework to handle that and that is

> the case.



OK. I saw other drivers doing it so I thought it was my responsibility.
I'll take that lineout.



> > +static int __init ostm_init_clkevt(struct ostm_device *ostm) {

> > +	struct clock_event_device *ced = &ostm->ced;

> > +	int ret = -ENXIO;

> > +

> > +	ret = request_irq(ostm->irq, ostm_timer_interrupt,

> > +			  IRQF_TIMER | IRQF_IRQPOLL | IRQF_NOBALANCING,

> > +			  dev_name(&ostm->pdev->dev), ostm);

> 

> 	devm_request_irq

> 

> Are you sure the IRQF_NOBALANCING flag should be used ? By default the

> interrupt is pinned to cpu0 below. If this timer is used as a broadcast

> timer for a system with deep idle, you may want to add the DYNIRQ flag

> feature to improve the wakeups on the system.


OK, thank you. After some reading, I understand IRQF_NOBALANCING better.

You're point is valid: Regardless of the fact that the SoCs I'll be using
this on are all single core, like you said below the cpu mask is set to cpu0.

I will remove IRQF_NOBALANCING.


> > +static int __init ostm_probe(struct platform_device *pdev) {

> > +	struct ostm_device *ostm;

> > +	struct resource *res;

> > +	int ret = -EFAULT;

> > +

> > +	if (!is_early_platform_device(pdev)) {

> > +		pm_runtime_set_active(&pdev->dev);

> > +		pm_runtime_enable(&pdev->dev);

> > +	}

> 

> Can you clarify why the 'early' is needed here ?


Actually, it means nothing.

I was using sh_mtu2.c and sh_cmt.c as reference and they were registering
an "earlytimer" which made me think the kernel would probe this driver
early in boot....but....now I see that is just a SH4 kernel specific thing.

So, I will remove all the early platform reference stuff.

Of course I still have the question of how are you supposed to get a
clocksource up and running early in boot. (but I'll figure that out later).


> I don't see the pm_runtime_get/pm_runtime_put in the corresponding

> function.

> 

> What is the benefit of using pm_runtime in this driver ? Isn't the timer

> supposed to be in an always-on power domain ?


When you register a clocksource, you can specify a .enable and .disable
callback where you can do runtime_pm. However...

A. I'm using clocksource_mmio_init, so no enable/dispable option is
   possible.
B. I just found out today that runtime pm is not going to work well on the
   SoC this driver was intended for, so it pointless anyway.

I'll remove all the runtime pm stuff.
Thank you for point this out.

> > +	ostm = platform_get_drvdata(pdev);

> > +	if (ostm) {

> > +		dev_info(&pdev->dev, "kept as earlytimer\n");

> > +		ret = 0;

> > +		goto out;

> > +	}

> > +

> > +	ostm = kzalloc(sizeof(*ostm), GFP_KERNEL);

> 

> 	devm_kzalloc ?


OK, thank you.
Means I can also get rid of '#include <linux/slab.h>'

> > +	if (!ostm) {

> > +		dev_err(&pdev->dev, "failed to allocate memory\n");

> 

> A memory failure allocation always triggers a dumpstack (IIRC), so this

> error message is pointless.


OK, removed.


> > +		return -ENOMEM;

> > +	}

> > +

> > +	ostm->pdev = pdev;

> > +	platform_set_drvdata(ostm->pdev, ostm);

> > +

> > +	res = platform_get_resource(ostm->pdev, IORESOURCE_MEM, 0);

> > +	if (!res) {

> > +		dev_err(&ostm->pdev->dev, "failed to get I/O memory\n");

> > +		goto err;

> > +	}

> > +

> > +	ostm->base = ioremap_nocache(res->start, resource_size(res));

> 

> 	devm_ioremap_nocache ?


OK, thank you.
Just need to also add '#include <linux/io.h>'

(looks like devm_ioremap and devm_ioremap_nocache is not as popular as
devm_kzalloc for some reason)


> > +	/* First probed device will be used as system clocksource */

> > +	if (!system_clock) {

> > +		/* use as clocksource */

> > +		ret = ostm_init_clksrc(ostm);

> > +

> > +		/* use as system scheduling clock */

> > +		if (!ret)

> > +			ret = ostm_init_sched_clock(ostm);

> > +

> > +		if (ret) {

> > +			dev_err(&pdev->dev, "failed to use as sched_clock\n");

> > +			system_clock = (void *)-1; /* prevent future attempts */

> > +			ret = 0;	/* still works as clocksource */

> > +		}

> 

> This error code check is unnecessary complex. ostm_init_sched_clock always

> return zero.


Good point.
And since sched_clock_register() always returns void, ostm_init_sched_clock
might was well return void as well.


> > +err:

> > +	if (ret) {

> > +		if (ostm->clk)

> > +			clk_disable_unprepare(ostm->clk);

> > +		if (ostm->base)

> > +			iounmap(ostm->base);

> > +		kfree(ostm);

> > +		platform_set_drvdata(pdev, NULL);

> 

> As iomap, kzalloc were done with the devm, then the rollback will be

> handled with the device framework.


I didn't realize that. Thank you.


> > +static int __init ostm_init(void)

> > +{

> > +	return platform_driver_register(&ostm_timer);

> > +}

> > +

> > +static void __exit ostm_exit(void)

> > +{

> > +	platform_driver_unregister(&ostm_timer);

> > +}

> > +

> > +early_platform_init("earlytimer", &ostm_timer);

> > +subsys_initcall(ostm_init); module_exit(ostm_exit);

> > +

> > +MODULE_AUTHOR("Chris Brandt");

> > +MODULE_DESCRIPTION("Renesas OSTM Timer Driver"); MODULE_LICENSE("GPL

> > +v2");

> 

> Maybe you can try with builtin_platform ?


Good idea. But, now I get a "Section mismatch" during link time so I'll
have to figure out why that is.



Thank you,
Chris
Daniel Lezcano Jan. 24, 2017, 2:32 p.m. UTC | #3
On Tue, Jan 24, 2017 at 04:45:47AM +0000, Chris Brandt wrote:

Hi Chris,

[ ... ]

> > > +	bool "Renesas OSTM timer driver" if COMPILE_TEST
> > > +	depends on GENERIC_CLOCKEVENTS
> > > +	select CLKSRC_MMIO
> > > +	default SYS_SUPPORTS_RENESAS_OSTM
> > 
> > - default SYS_SUPPORTS_RENESAS_OSTM
> > 
> > Except I missing something, I don' the point of having this intermediate
> > config option.
> 
> I was basically following what all the other clocksource drivers do.
> Selecting an ARCH will then set SYS_SUPPORTS_xxTIMERxx which will then
> force xxTIMERxx=y.

Yeah, little by little these options are clean up to be more consistent across
the different drivers. The sh/mtu drivers are different from the other drivers.

The idea with the config option is to let the platform to select the drivers,
and optionnaly allows the compilation on other architecture to increase the
compilation test coverage. That is the reason of COMPILE_TEST.

> But, if "COMPILE_TEST" is set, you can choose what clocksource timers
> you want to build in.
> 
> Is your suggestion to do away with the COMPILE_TEST ability and
> just force RENESAS_OSTM=y if ARCH_R7S72100 is selected?

Just like that:

config RENESAS_OSTM
	bool "Renesas OSTM timer driver" if COMPILE_TEST
	depends on GENERIC_CLOCKEVENTS
	select CLKSRC_MMIO
	help
	  Enables the support for the Renesas OSTM

And then from arch/arm/mach-shmobile/Kconfig:

	select RENESAS_OSTM


> > > +#include <linux/module.h>
> > 
> > Please remove everything module related here and below. This driver is not
> > supposed to be removed and it is compiled in with the Kconfig option.
> 
> Good point. I will remove.
> 
> I guess if you can't compile it as a module, you don't need things like
> 'MODULE_LICENSE'.

Right.

[ ... ]

> > > +static int __init ostm_init_clksrc(struct ostm_device *ostm) {
> > > +	int ret;
> > > +
> > > +	/* irq not used (clock sources don't use interrupts) */
> > > +
> > > +	/* stop counter */
> > 
> > One line comment is usually in the network code. Please use multiline.
> > 
> > /*
> >  * Bla bla
> >  */
> 
> I'm confused. Do you mean:
> 
> A) use multiline formatting for every single-line comment throughout
>    the file?
> 
> B) use multiline for any place where you have 2 or more single-line
>    comments back to back?

I think it is simpler if you just ignore my comment, remove all your comments
in this function, implement a couple of wrapper (eg. clksrc_stop, clksrc_start)
which will speak by themselves.

By the way, is it normal stopping the clockevent is the same code than stopping
the clocksource ?

[ ... ]

> > > +	if (!is_early_platform_device(pdev)) {
> > > +		pm_runtime_set_active(&pdev->dev);
> > > +		pm_runtime_enable(&pdev->dev);
> > > +	}
> > 
> > Can you clarify why the 'early' is needed here ?
> 
> Actually, it means nothing.
> 
> I was using sh_mtu2.c and sh_cmt.c as reference and they were registering
> an "earlytimer" which made me think the kernel would probe this driver
> early in boot....but....now I see that is just a SH4 kernel specific thing.
> 
> So, I will remove all the early platform reference stuff.
> 
> Of course I still have the question of how are you supposed to get a
> clocksource up and running early in boot. (but I'll figure that out later).

Until the hardware clocksource is registered, the jiffies are used as source of
time. That happens at the very early boot time. The clockevent must be
registered early to update the jiffies but you won't have to care about that if
you use the macro below.

[ ... ]

> > > +early_platform_init("earlytimer", &ostm_timer);
> > > +subsys_initcall(ostm_init); module_exit(ostm_exit);
> > > +
> > > +MODULE_AUTHOR("Chris Brandt");
> > > +MODULE_DESCRIPTION("Renesas OSTM Timer Driver"); MODULE_LICENSE("GPL
> > > +v2");
> > 
> > Maybe you can try with builtin_platform ?
> 
> Good idea. But, now I get a "Section mismatch" during link time so I'll
> have to figure out why that is.

Mmh, I think it would be more consistent to convert this to:

CLOCKSOURCE_OF_DECLARE(ostm, "renesas,ostm", ostm_init);

The only problem is to get the struct device associated to the of_node passed
as parameter to ostm_init in order to use the devm_* API.

I think of_find_device_by_node should return the platform_device, then
pdev->dev. If that works the other drivers will benefit from that to remove all
the rollback code everywhere.

  -- Daniel
Chris Brandt Jan. 24, 2017, 2:43 p.m. UTC | #4
Hi Daniel,

On Tuesday, January 24, 2017, Daniel Lezcano wrote:
> > > > +early_platform_init("earlytimer", &ostm_timer);

> > > > +subsys_initcall(ostm_init); module_exit(ostm_exit);

> > > > +

> > > > +MODULE_AUTHOR("Chris Brandt");

> > > > +MODULE_DESCRIPTION("Renesas OSTM Timer Driver");

> > > > +MODULE_LICENSE("GPL v2");

> > >

> > > Maybe you can try with builtin_platform ?

> >

> > Good idea. But, now I get a "Section mismatch" during link time so

> > I'll have to figure out why that is.

> 

> Mmh, I think it would be more consistent to convert this to:

> 

> CLOCKSOURCE_OF_DECLARE(ostm, "renesas,ostm", ostm_init);

> 

> The only problem is to get the struct device associated to the of_node

> passed as parameter to ostm_init in order to use the devm_* API.

> 

> I think of_find_device_by_node should return the platform_device, then

> pdev->dev. If that works the other drivers will benefit from that to

> pdev->remove all

> the rollback code everywhere.



So I realized that in order to use builtin_platform, I can't have any of the
functions in __init because the build system has no idea that I never plan
on removing or probing again after boot. But, even if I take out all the
__init from my code, I'm still calling clocksource_mmio_init which is __init
so I can never escape the "Section mismatch".

This leaves me with 2 options:

1) Pull the clocksource_mmio driver code into my driver and leave everything
   out of __init
2) Rewrite as a DT driver using CLOCKSOURCE_OF_DECLARE which puts most of
   the code in __init

Of course the better (lower system memory) answer is #2.

I'll try your trick about of_find_device_by_node because while there are
equivalent OF functions for mapping resources, I do like the auto-rollback
feature of demv

Thank you,
Chris
Chris Brandt Jan. 24, 2017, 8:19 p.m. UTC | #5
Hi Daniel,

On Tuesday, January 24, 2017, Daniel Lezcano wrote:
> > > > +early_platform_init("earlytimer", &ostm_timer);

> > > > +subsys_initcall(ostm_init); module_exit(ostm_exit);

> > > > +

> > > > +MODULE_AUTHOR("Chris Brandt");

> > > > +MODULE_DESCRIPTION("Renesas OSTM Timer Driver");

> > > > +MODULE_LICENSE("GPL v2");

> > >

> > > Maybe you can try with builtin_platform ?

> >

> > Good idea. But, now I get a "Section mismatch" during link time so

> > I'll have to figure out why that is.

> 

> Mmh, I think it would be more consistent to convert this to:

> 

> CLOCKSOURCE_OF_DECLARE(ostm, "renesas,ostm", ostm_init);

> 

> The only problem is to get the struct device associated to the of_node

> passed as parameter to ostm_init in order to use the devm_* API.

> 

> I think of_find_device_by_node should return the platform_device, then

> pdev->dev. If that works the other drivers will benefit from that to

> pdev->remove all

> the rollback code everywhere.



It was a good idea....but it will not work.

While CLOCKSOURCE_OF_DECLARE is good at putting the driver at the
front of the line for loading, it's too early in boot to detect
a platform_device.

of_find_device_by_node calls bus_find_device. But the first thing that
bus_find_device does is:

	if (!bus || !bus->p)
		return NULL;

But bus->p=0 so it returns immediately.


Of course changing the code over to:
        devm_kzalloc -> devm_kzalloc
devm_ioremap_nocache -> of_io_request_and_map
    platform_get_irq -> irq_of_parse_and_map
             dev_err -> pr_err


Then things work, but I'm back to managing the rollback code manually.


Any other ideas on how to get the corresponding platform_device for
a DT node?


Chris
Geert Uytterhoeven Jan. 25, 2017, 8:35 a.m. UTC | #6
Hi Chris,

On Tue, Jan 24, 2017 at 3:43 PM, Chris Brandt <Chris.Brandt@renesas.com> wrote:
> On Tuesday, January 24, 2017, Daniel Lezcano wrote:
>> > > > +early_platform_init("earlytimer", &ostm_timer);
>> > > > +subsys_initcall(ostm_init); module_exit(ostm_exit);
>> > > > +
>> > > > +MODULE_AUTHOR("Chris Brandt");
>> > > > +MODULE_DESCRIPTION("Renesas OSTM Timer Driver");
>> > > > +MODULE_LICENSE("GPL v2");
>> > >
>> > > Maybe you can try with builtin_platform ?
>> >
>> > Good idea. But, now I get a "Section mismatch" during link time so
>> > I'll have to figure out why that is.
>>
>> Mmh, I think it would be more consistent to convert this to:
>>
>> CLOCKSOURCE_OF_DECLARE(ostm, "renesas,ostm", ostm_init);
>>
>> The only problem is to get the struct device associated to the of_node
>> passed as parameter to ostm_init in order to use the devm_* API.
>>
>> I think of_find_device_by_node should return the platform_device, then
>> pdev->dev. If that works the other drivers will benefit from that to
>> pdev->remove all
>> the rollback code everywhere.
>
> So I realized that in order to use builtin_platform, I can't have any of the
> functions in __init because the build system has no idea that I never plan
> on removing or probing again after boot. But, even if I take out all the
> __init from my code, I'm still calling clocksource_mmio_init which is __init
> so I can never escape the "Section mismatch".

For single-probe drivers, you can use builtin_platform_driver_probe().

Gr{oetje,eeting}s,

                        Geert

--
Geert Uytterhoeven -- There's lots of Linux beyond ia32 -- geert@linux-m68k.org

In personal conversations with technical people, I call myself a hacker. But
when I'm talking to journalists I just say "programmer" or something like that.
                                -- Linus Torvalds
Geert Uytterhoeven Jan. 25, 2017, 8:37 a.m. UTC | #7
On Tue, Jan 24, 2017 at 9:19 PM, Chris Brandt <Chris.Brandt@renesas.com> wrote:
> On Tuesday, January 24, 2017, Daniel Lezcano wrote:
>> > > > +early_platform_init("earlytimer", &ostm_timer);
>> > > > +subsys_initcall(ostm_init); module_exit(ostm_exit);
>> > > > +
>> > > > +MODULE_AUTHOR("Chris Brandt");
>> > > > +MODULE_DESCRIPTION("Renesas OSTM Timer Driver");
>> > > > +MODULE_LICENSE("GPL v2");
>> > >
>> > > Maybe you can try with builtin_platform ?
>> >
>> > Good idea. But, now I get a "Section mismatch" during link time so
>> > I'll have to figure out why that is.
>>
>> Mmh, I think it would be more consistent to convert this to:
>>
>> CLOCKSOURCE_OF_DECLARE(ostm, "renesas,ostm", ostm_init);
>>
>> The only problem is to get the struct device associated to the of_node
>> passed as parameter to ostm_init in order to use the devm_* API.
>>
>> I think of_find_device_by_node should return the platform_device, then
>> pdev->dev. If that works the other drivers will benefit from that to
>> pdev->remove all
>> the rollback code everywhere.
>
> It was a good idea....but it will not work.
>
> While CLOCKSOURCE_OF_DECLARE is good at putting the driver at the
> front of the line for loading, it's too early in boot to detect
> a platform_device.

That's correct. All those *_OF_DECLARE() style initializations start
to break as soon as power and/or clock domains are involved.
That's one reason why some subsystems (e.g. clock) are moving away from it.

Gr{oetje,eeting}s,

                        Geert

--
Geert Uytterhoeven -- There's lots of Linux beyond ia32 -- geert@linux-m68k.org

In personal conversations with technical people, I call myself a hacker. But
when I'm talking to journalists I just say "programmer" or something like that.
                                -- Linus Torvalds
Daniel Lezcano Jan. 25, 2017, 9:56 a.m. UTC | #8
On Tue, Jan 24, 2017 at 08:19:50PM +0000, Chris Brandt wrote:
> Hi Daniel,
> 
> On Tuesday, January 24, 2017, Daniel Lezcano wrote:
> > > > > +early_platform_init("earlytimer", &ostm_timer);
> > > > > +subsys_initcall(ostm_init); module_exit(ostm_exit);
> > > > > +
> > > > > +MODULE_AUTHOR("Chris Brandt");
> > > > > +MODULE_DESCRIPTION("Renesas OSTM Timer Driver");
> > > > > +MODULE_LICENSE("GPL v2");
> > > >
> > > > Maybe you can try with builtin_platform ?
> > >
> > > Good idea. But, now I get a "Section mismatch" during link time so
> > > I'll have to figure out why that is.
> > 
> > Mmh, I think it would be more consistent to convert this to:
> > 
> > CLOCKSOURCE_OF_DECLARE(ostm, "renesas,ostm", ostm_init);
> > 
> > The only problem is to get the struct device associated to the of_node
> > passed as parameter to ostm_init in order to use the devm_* API.
> > 
> > I think of_find_device_by_node should return the platform_device, then
> > pdev->dev. If that works the other drivers will benefit from that to
> > pdev->remove all
> > the rollback code everywhere.
> 
> 
> It was a good idea....but it will not work.
> 
> While CLOCKSOURCE_OF_DECLARE is good at putting the driver at the
> front of the line for loading, it's too early in boot to detect
> a platform_device.
> 
> of_find_device_by_node calls bus_find_device. But the first thing that
> bus_find_device does is:
> 
> 	if (!bus || !bus->p)
> 		return NULL;
> 
> But bus->p=0 so it returns immediately.
> 
> 
> Of course changing the code over to:
>         devm_kzalloc -> devm_kzalloc
> devm_ioremap_nocache -> of_io_request_and_map
>     platform_get_irq -> irq_of_parse_and_map
>              dev_err -> pr_err
> 
> 
> Then things work, but I'm back to managing the rollback code manually.
> 
> 
> Any other ideas on how to get the corresponding platform_device for
> a DT node?

No :/

So up to you.
	- CLOCKSOURCE_OF_DECLARE consistent but need rollback
	or
	- platform_device but with another timer available at early time
Chris Brandt Jan. 25, 2017, 1:32 p.m. UTC | #9
Hi Geert,

On Wednesday, January 25, 2017, Geert Uytterhoeven wrote:
> > So I realized that in order to use builtin_platform, I can't have any

> > of the functions in __init because the build system has no idea that I

> > never plan on removing or probing again after boot. But, even if I

> > take out all the __init from my code, I'm still calling

> > clocksource_mmio_init which is __init so I can never escape the "Section

> mismatch".

> 

> For single-probe drivers, you can use builtin_platform_driver_probe().



Thank you for the info.


Chris
Chris Brandt Jan. 25, 2017, 2:02 p.m. UTC | #10
Hi Daniel,

On Wednesday, January 25, 2017, Daniel Lezcano wrote:
> > Then things work, but I'm back to managing the rollback code manually.

> >

> >

> > Any other ideas on how to get the corresponding platform_device for a

> > DT node?

> 

> No :/

> 

> So up to you.

> 	- CLOCKSOURCE_OF_DECLARE consistent but need rollback

> 	or

> 	- platform_device but with another timer available at early time


As far as I can tell, the rollback functions don't mind if I pass NULL
pointers to them. So with CLOCKSOURCE_OF_DECLARE, my error rollback at
the end of ostm_init is basically:


err:
	if (ret) {
		clk_disable_unprepare(ostm_clk);
		iounmap(ostm->base);
		kfree(ostm);
		return ret;
	}

	return 0;
}



If I go with CLOCKSOURCE_OF_DECLARE, I can at least get rid of the early boot message
"clocksource_probe: no matching clocksources found"

I'll go ahead and send a v4 today with all the changes you suggested.

Thank you for your help.

Regards,
Chris
diff mbox

Patch

diff --git a/arch/arm/mach-shmobile/Kconfig b/arch/arm/mach-shmobile/Kconfig
index 2bb4b09..b928634 100644
--- a/arch/arm/mach-shmobile/Kconfig
+++ b/arch/arm/mach-shmobile/Kconfig
@@ -57,6 +57,7 @@  config ARCH_R7S72100
 	select PM
 	select PM_GENERIC_DOMAINS
 	select SYS_SUPPORTS_SH_MTU2
+	select SYS_SUPPORTS_RENESAS_OSTM
 
 config ARCH_R8A73A4
 	bool "R-Mobile APE6 (R8A73A40)"
diff --git a/drivers/clocksource/Kconfig b/drivers/clocksource/Kconfig
index 4866f7a..95c8d56 100644
--- a/drivers/clocksource/Kconfig
+++ b/drivers/clocksource/Kconfig
@@ -431,6 +431,9 @@  config MTK_TIMER
 config SYS_SUPPORTS_SH_MTU2
         bool
 
+config SYS_SUPPORTS_RENESAS_OSTM
+        bool
+
 config SYS_SUPPORTS_SH_TMU
         bool
 
@@ -467,6 +470,15 @@  config SH_TIMER_MTU2
 	  Timer Pulse Unit 2 (MTU2) hardware available on SoCs from Renesas.
 	  This hardware comes with 16 bit-timer registers.
 
+config RENESAS_OSTM
+	bool "Renesas OSTM timer driver" if COMPILE_TEST
+	depends on GENERIC_CLOCKEVENTS
+	select CLKSRC_MMIO
+	default SYS_SUPPORTS_RENESAS_OSTM
+	help
+	  This enables the build of the OSTM timer driver.
+	  It creates a clock source and clock event device.
+
 config SH_TIMER_TMU
 	bool "Renesas TMU timer driver" if COMPILE_TEST
 	depends on GENERIC_CLOCKEVENTS
diff --git a/drivers/clocksource/Makefile b/drivers/clocksource/Makefile
index a14111e..bbd163b 100644
--- a/drivers/clocksource/Makefile
+++ b/drivers/clocksource/Makefile
@@ -8,6 +8,7 @@  obj-$(CONFIG_CS5535_CLOCK_EVENT_SRC)	+= cs5535-clockevt.o
 obj-$(CONFIG_CLKSRC_JCORE_PIT)		+= jcore-pit.o
 obj-$(CONFIG_SH_TIMER_CMT)	+= sh_cmt.o
 obj-$(CONFIG_SH_TIMER_MTU2)	+= sh_mtu2.o
+obj-$(CONFIG_RENESAS_OSTM)	+= renesas-ostm.o
 obj-$(CONFIG_SH_TIMER_TMU)	+= sh_tmu.o
 obj-$(CONFIG_EM_TIMER_STI)	+= em_sti.o
 obj-$(CONFIG_CLKBLD_I8253)	+= i8253.o
diff --git a/drivers/clocksource/renesas-ostm.c b/drivers/clocksource/renesas-ostm.c
new file mode 100644
index 0000000..37f2461
--- /dev/null
+++ b/drivers/clocksource/renesas-ostm.c
@@ -0,0 +1,349 @@ 
+/*
+ * Renesas Timer Support - OSTM
+ *
+ * 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
+ *
+ * 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.
+ *
+ */
+
+#include <linux/platform_device.h>
+#include <linux/clk.h>
+#include <linux/clockchips.h>
+#include <linux/interrupt.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/pm_runtime.h>
+#include <linux/sched_clock.h>
+
+/*
+ * The OSTM contains independent channels.
+ * The first OSTM channel probed will be set up as a free running
+ * clocksource. Additionally we will use this clocksource for the system
+ * schedule timer sched_clock().
+ *
+ * The second (or more) channel probed will be set up as an interrupt
+ * driven clock event.
+ */
+
+struct ostm_device {
+	struct platform_device *pdev;
+
+	int irq;
+	struct clk *clk;
+	unsigned long rate;
+	void __iomem *base;
+	unsigned long ticks_per_jiffy;
+	struct clock_event_device ced;
+};
+
+static void __iomem *system_clock;	/* For sched_clock() */
+
+/* OSTM REGISTERS */
+#define	OSTM_CMP		0x000	/* RW,32 */
+#define	OSTM_CNT		0x004	/* R,32 */
+#define	OSTM_TE			0x010	/* R,8 */
+#define	OSTM_TS			0x014	/* W,8 */
+#define	OSTM_TT			0x018	/* W,8 */
+#define	OSTM_CTL		0x020	/* RW,8 */
+
+#define	TE			0x01
+#define	TS			0x01
+#define	TT			0x01
+#define	CTL_PERIODIC		0x00
+#define	CTL_ONESHOT		0x02
+#define	CTL_FREERUN		0x02
+
+static struct ostm_device *ced_to_ostm(struct clock_event_device *ced)
+{
+	return container_of(ced, struct ostm_device, ced);
+}
+
+static int __init ostm_init_clksrc(struct ostm_device *ostm)
+{
+	int ret;
+
+	/* irq not used (clock sources don't use interrupts) */
+
+	/* stop counter */
+	iowrite8(TT, ostm->base + OSTM_TT);
+	while (ioread8(ostm->base + OSTM_TE) & TE)
+		;
+
+	/* setup as freerun */
+	iowrite32(0, ostm->base + OSTM_CMP);
+	iowrite8(CTL_FREERUN, ostm->base + OSTM_CTL);
+	iowrite8(TS, ostm->base + OSTM_TS);
+
+	/* register */
+	ret = clocksource_mmio_init(ostm->base + OSTM_CNT,
+			"ostm", ostm->rate,
+			300, 32, clocksource_mmio_readl_up);
+
+	return ret;
+}
+
+static u64 notrace ostm_read_sched_clock(void)
+{
+	return ioread32(system_clock);
+}
+
+static int __init ostm_init_sched_clock(struct ostm_device *ostm)
+{
+	unsigned long flags;
+
+	system_clock = ostm->base + OSTM_CNT;
+	local_irq_save(flags);
+	local_irq_disable();
+	sched_clock_register(ostm_read_sched_clock, 32, ostm->rate);
+	local_irq_restore(flags);
+
+	return 0;
+}
+
+static void ostm_clkevt_timer_stop(struct ostm_device *ostm)
+{
+	if (ioread8(ostm->base + OSTM_TE) & TE) {
+		iowrite8(TT, ostm->base + OSTM_TT);
+		while (ioread8(ostm->base + OSTM_TE) & TE)
+			;
+	}
+}
+
+static int ostm_clock_event_next(unsigned long delta,
+				     struct clock_event_device *ced)
+{
+	struct ostm_device *ostm = ced_to_ostm(ced);
+
+	WARN_ON(!clockevent_state_oneshot(ced));
+
+	ostm_clkevt_timer_stop(ostm);
+
+	iowrite32(delta, ostm->base + OSTM_CMP);
+	iowrite8(CTL_ONESHOT, ostm->base + OSTM_CTL);
+	iowrite8(TS, ostm->base + OSTM_TS);
+
+	return 0;
+}
+
+static int ostm_shutdown(struct clock_event_device *ced)
+{
+	struct ostm_device *ostm = ced_to_ostm(ced);
+
+	ostm_clkevt_timer_stop(ostm);
+
+	return 0;
+}
+static int ostm_set_periodic(struct clock_event_device *ced)
+{
+	struct ostm_device *ostm = ced_to_ostm(ced);
+
+	if (clockevent_state_oneshot(ced) || clockevent_state_periodic(ced))
+		ostm_clkevt_timer_stop(ostm);
+
+	iowrite32(ostm->ticks_per_jiffy - 1, ostm->base + OSTM_CMP);
+	iowrite8(CTL_PERIODIC, ostm->base + OSTM_CTL);
+	iowrite8(TS, ostm->base + OSTM_TS);
+
+	return 0;
+}
+
+static int ostm_set_oneshot(struct clock_event_device *ced)
+{
+	struct ostm_device *ostm = ced_to_ostm(ced);
+
+	ostm_clkevt_timer_stop(ostm);
+
+	return 0;
+}
+
+static irqreturn_t ostm_timer_interrupt(int irq, void *dev_id)
+{
+	struct ostm_device *ostm = dev_id;
+
+	if (clockevent_state_oneshot(&ostm->ced))
+		ostm_clkevt_timer_stop(ostm);
+
+	/* notify clockevent layer */
+	if (ostm->ced.event_handler)
+		ostm->ced.event_handler(&ostm->ced);
+
+	return IRQ_HANDLED;
+}
+
+static int __init ostm_init_clkevt(struct ostm_device *ostm)
+{
+	struct clock_event_device *ced = &ostm->ced;
+	int ret = -ENXIO;
+
+	ret = request_irq(ostm->irq, ostm_timer_interrupt,
+			  IRQF_TIMER | IRQF_IRQPOLL | IRQF_NOBALANCING,
+			  dev_name(&ostm->pdev->dev), ostm);
+	if (ret) {
+		dev_err(&ostm->pdev->dev, "failed to request irq\n");
+		return ret;
+	}
+
+	ced->name = "ostm";
+	ced->features = CLOCK_EVT_FEAT_ONESHOT | CLOCK_EVT_FEAT_PERIODIC;
+	ced->set_state_shutdown = ostm_shutdown;
+	ced->set_state_periodic = ostm_set_periodic;
+	ced->set_state_oneshot = ostm_set_oneshot;
+	ced->set_next_event = ostm_clock_event_next;
+	ced->shift = 32;
+	ced->rating = 300;
+	ced->cpumask = cpumask_of(0);
+	clockevents_config_and_register(ced, ostm->rate, 0xf, 0xffffffff);
+
+	return 0;
+}
+
+static int __init ostm_probe(struct platform_device *pdev)
+{
+	struct ostm_device *ostm;
+	struct resource *res;
+	int ret = -EFAULT;
+
+	if (!is_early_platform_device(pdev)) {
+		pm_runtime_set_active(&pdev->dev);
+		pm_runtime_enable(&pdev->dev);
+	}
+
+	ostm = platform_get_drvdata(pdev);
+	if (ostm) {
+		dev_info(&pdev->dev, "kept as earlytimer\n");
+		ret = 0;
+		goto out;
+	}
+
+	ostm = kzalloc(sizeof(*ostm), GFP_KERNEL);
+	if (!ostm) {
+		dev_err(&pdev->dev, "failed to allocate memory\n");
+		return -ENOMEM;
+	}
+
+	ostm->pdev = pdev;
+	platform_set_drvdata(ostm->pdev, ostm);
+
+	res = platform_get_resource(ostm->pdev, IORESOURCE_MEM, 0);
+	if (!res) {
+		dev_err(&ostm->pdev->dev, "failed to get I/O memory\n");
+		goto err;
+	}
+
+	ostm->base = ioremap_nocache(res->start, resource_size(res));
+	if (!ostm->base) {
+		dev_err(&ostm->pdev->dev, "failed to remap I/O memory\n");
+		goto err;
+	}
+
+	ostm->irq = platform_get_irq(ostm->pdev, 0);
+	if (ostm->irq < 0) {
+		dev_err(&ostm->pdev->dev, "failed to get irq\n");
+		goto err;
+	}
+
+	ostm->clk = devm_clk_get(&pdev->dev, NULL);
+	if (IS_ERR(ostm->clk)) {
+		dev_err(&ostm->pdev->dev, "failed to get clock\n");
+		ostm->clk = NULL;
+		goto err;
+	}
+
+	ret = clk_prepare_enable(ostm->clk);
+	if (ret) {
+		dev_err(&ostm->pdev->dev, "failed to enable clock\n");
+		goto err;
+	}
+
+	ostm->rate = clk_get_rate(ostm->clk);
+	ostm->ticks_per_jiffy = (ostm->rate + HZ / 2) / HZ;
+
+	/* First probed device will be used as system clocksource */
+	if (!system_clock) {
+		/* use as clocksource */
+		ret = ostm_init_clksrc(ostm);
+
+		/* use as system scheduling clock */
+		if (!ret)
+			ret = ostm_init_sched_clock(ostm);
+
+		if (ret) {
+			dev_err(&pdev->dev, "failed to use as sched_clock\n");
+			system_clock = (void *)-1; /* prevent future attempts */
+			ret = 0;	/* still works as clocksource */
+		}
+
+		if (!ret)
+			dev_info(&pdev->dev, "used for clocksource\n");
+	} else {
+		/* use as clock event */
+		ret = ostm_init_clkevt(ostm);
+
+		if (!ret)
+			dev_info(&pdev->dev, "used for clock events\n");
+	}
+
+err:
+	if (ret) {
+		if (ostm->clk)
+			clk_disable_unprepare(ostm->clk);
+		if (ostm->base)
+			iounmap(ostm->base);
+		kfree(ostm);
+		platform_set_drvdata(pdev, NULL);
+		pm_runtime_idle(&pdev->dev);
+		return ret;
+	}
+
+	if (is_early_platform_device(pdev))
+		return ret;
+
+out:
+	pm_runtime_irq_safe(&pdev->dev);
+
+	return ret;
+}
+
+static int ostm_remove(struct platform_device *pdev)
+{
+	return -EBUSY;	/* cannot unregister clockevent */
+}
+
+static const struct of_device_id ostm_of_table[] __maybe_unused = {
+	{ .compatible = "renesas,ostm" },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, ostm_of_table);
+
+static struct platform_driver ostm_timer = {
+	.probe		= ostm_probe,
+	.remove		= ostm_remove,
+	.driver	= {
+		.name	= "ostm",
+		.of_match_table = of_match_ptr(ostm_of_table),
+	},
+};
+
+static int __init ostm_init(void)
+{
+	return platform_driver_register(&ostm_timer);
+}
+
+static void __exit ostm_exit(void)
+{
+	platform_driver_unregister(&ostm_timer);
+}
+
+early_platform_init("earlytimer", &ostm_timer);
+subsys_initcall(ostm_init);
+module_exit(ostm_exit);
+
+MODULE_AUTHOR("Chris Brandt");
+MODULE_DESCRIPTION("Renesas OSTM Timer Driver");
+MODULE_LICENSE("GPL v2");