[RFC PATCHv3 4/6] clocksource: Add TI-Nspire timer drivers
Linus Walleij
linus.walleij at linaro.org
Tue May 14 04:03:42 EDT 2013
On Sun, May 12, 2013 at 6:22 AM, Daniel Tang <dt.tangr at gmail.com> wrote:
> Signed-off-by: Daniel Tang <dt.tangr at gmail.com>
A commit message does not hurt.
> drivers/clocksource/Makefile | 1 +
> drivers/clocksource/nspire-classic-timer.c | 199 +++++++++++++++++++++++++++++
This subsystem is managed by Thomas Gleixner and John Stultz so you
should include
them on the To: line in reviewes.
> +++ b/drivers/clocksource/nspire-classic-timer.c
(...)
> +struct nspire_timer {
> + void __iomem *base;
> + void __iomem *timer1, *timer2;
> + void __iomem *interrupt_regs;
> +
> + int irqnr;
> +
> + struct clk *clk;
> + struct clock_event_device clkevt;
> + struct irqaction clkevt_irq;
> +
> + char clocksource_name[64];
> + char clockevent_name[64];
> +};
> +
> +static int nspire_timer_set_event(unsigned long delta,
> + struct clock_event_device *dev)
> +{
> + unsigned long flags;
> + struct nspire_timer *timer = container_of(dev,
> + struct nspire_timer,
> + clkevt);
> +
> + local_irq_save(flags);
> +
> + writel(delta, timer->timer1 + IO_CURRENT_VAL);
> + writel(CNTL_RUN_TIMER | CNTL_DEC | CNTL_MATCH1,
> + timer->timer1 + IO_CONTROL);
> +
> + local_irq_restore(flags);
> +
> + return 0;
> +}
> +static void nspire_timer_set_mode(enum clock_event_mode mode,
> + struct clock_event_device *evt)
> +{
> + evt->mode = mode;
So you support any mode?
Atleast handle CLOCK_EVT_MODE_SHUTDOWN and CLOCK_EVT_UNUSED
like any well-behaved clock event driver.
> +}
> +
> +static irqreturn_t nspire_timer_interrupt(int irq, void *dev_id)
> +{
> + struct nspire_timer *timer = dev_id;
> +
> + writel((1<<0), timer->interrupt_regs + IO_INTR_ACK);
write(0x1, timer->interrupt_regs + IO_INTR_ACK);
is no less intelligible, if you really want to point out that you're
setting bit 0, include <linux/bitops.h> and use BIT(0).
If there is a status bit you're clearing, maybe you should check
it first to watch for spurious IRQs?
> + writel(CNTL_STOP_TIMER, timer->timer1 + IO_CONTROL);
> +
> + if (timer->clkevt.event_handler)
> + timer->clkevt.event_handler(&timer->clkevt);
> +
> + return IRQ_HANDLED;
> +}
> +
> +static int __init nspire_timer_add(struct device_node *node)
> +{
> + struct nspire_timer *timer;
> + struct resource res;
> + int ret;
> +
> + timer = kzalloc(sizeof(*timer), GFP_ATOMIC);
> + if (!timer)
> + return -ENOMEM;
> +
> + timer->base = of_iomap(node, 0);
> + if (!timer->base) {
> + ret = -EINVAL;
> + goto error_free;
> + }
> + timer->timer1 = timer->base + IO_TIMER1;
> + timer->timer2 = timer->base + IO_TIMER2;
> +
> + timer->clk = of_clk_get(node, 0);
> + if (IS_ERR(timer->clk)) {
> + ret = PTR_ERR(timer->clk);
> + pr_err("Timer clock not found! (error %d)\n", ret);
> + goto error_unmap;
> + }
> +
> + timer->interrupt_regs = of_iomap(node, 1);
Check for errors.
> + timer->irqnr = irq_of_parse_and_map(node, 0);
Check for errors.
> +
> + of_address_to_resource(node, 0, &res);
> + scnprintf(timer->clocksource_name, sizeof(timer->clocksource_name),
> + "%llx.%s_clocksource",
> + (unsigned long long)res.start, node->name);
> +
> + scnprintf(timer->clockevent_name, sizeof(timer->clockevent_name),
> + "%llx.%s_clockevent",
> + (unsigned long long)res.start, node->name);
> +
> + if (timer->interrupt_regs && timer->irqnr) {
> + timer->clkevt.name = timer->clockevent_name;
> + timer->clkevt.set_next_event = nspire_timer_set_event;
> + timer->clkevt.set_mode = nspire_timer_set_mode;
> + timer->clkevt.rating = 200;
> + timer->clkevt.shift = 32;
Delete this shift. It will be set by clockevents_config_and_register().
> + timer->clkevt.cpumask = cpu_all_mask;
> + timer->clkevt.features =
> + CLOCK_EVT_FEAT_ONESHOT;
> +
> + writel(CNTL_STOP_TIMER, timer->timer1 + IO_CONTROL);
> + writel(0, timer->timer1 + IO_DIVIDER);
For a 16-bit counter I don't think it's wise to set the divider to 0
unless the clock frequency is very low. See further comments
below. (Assumes this is some prescaler.)
> + /* Mask out interrupts except the one we're using */
> + writel((1<<0), timer->interrupt_regs + IO_INTR_MSK);
Just 0x1 or BIT(0).
> + writel(0x3F, timer->interrupt_regs + IO_INTR_ACK);
Hm magic number, I guess it's clearing all IRQ lines...
> +
> + /* Interrupt to occur when timer value matches 0 */
> + writel(0, timer->base + IO_MATCH1);
> +
> + timer->clkevt_irq.name = timer->clockevent_name;
> + timer->clkevt_irq.handler = nspire_timer_interrupt;
> + timer->clkevt_irq.dev_id = timer;
> + timer->clkevt_irq.flags =
> + IRQF_DISABLED | IRQF_TIMER | IRQF_IRQPOLL;
> +
> + setup_irq(timer->irqnr, &timer->clkevt_irq);
> +
> + clockevents_config_and_register(&timer->clkevt,
> + clk_get_rate(timer->clk), 0x0001, 0xfffe);
> + pr_info("Added %s as clockevent\n", timer->clockevent_name);
> + }
> +
> + writel(CNTL_RUN_TIMER | CNTL_FOREVER | CNTL_INC,
> + timer->timer2 + IO_CONTROL);
> +
> + clocksource_mmio_init(timer->timer2 + IO_CURRENT_VAL,
> + timer->clocksource_name,
> + clk_get_rate(timer->clk),
> + 200, 16,
> + clocksource_mmio_readw_up);
If this timer is really just 16 bits, it's a *pretty* good idea to use
the prescaler (I guess this is what IO_DIVIDER is) beacuse else you
will get short sleep times with CONFIG_NO_HZ_IDLE on this system,
and wake up unnecessarily often.
The same goes for the clock event.
See how we calculate the prescaler in arch/arm/mach-integrator/integrator_ap.c
for example.
Yours,
Linus Walleij
More information about the linux-arm-kernel
mailing list