OMAP Linux - dmtimer.h - using the GP Timers on the OMAP 3530 processor

Well, this isn't as complicated as I thought it might be.  The dmtimer.h/dmtimer.c files make this really simple >>IF<< you are in kernel mode :)  So here is a rundown on how this all works... since I can't find any example code anywhere... I find some references to it in the OMAP kernel code, because GP Timer 12 gets used for the kernel clock. (at least on the beagleboard)

At some point after the 2.6.28 kernel a crucial patch (http://patchwork.kernel.org/patch/9929/) has been added that does the EXPORT_SYMBOL_GPL for all of these dmtimer functions, which allows these functions to be found during module installation.  YAY!  I'm using the 2.6.28 kernel, so I had to apply that patch.

What this means is that the functions provided in dmtimer.c are available, and handle wrapping all of the hardware level counter interfaces.  On the beagleboard, the kernel appears to use GP Timer 12 to provide the preemtion interrupt.  You are going to want to use this interface for accessing the GP Timers because it does a lot of work for you (including locking and correct read/write behavior) instead of trying to hand code your own timer interface.

For reference here is dmtimer.h:

/*
* arch/arm/plat-omap/include/mach/dmtimer.h
*
* OMAP Dual-Mode Timers
*
* Copyright (C) 2005 Nokia Corporation
* Author: Lauri Leukkunen <lauri.leukkunen@nokia.com>
* PWM and clock framwork support by Timo Teras.
*
* 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.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
* NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 675 Mass Ave, Cambridge, MA 02139, USA.
*/

#ifndef __ASM_ARCH_DMTIMER_H
#define __ASM_ARCH_DMTIMER_H

/* clock sources */
#define OMAP_TIMER_SRC_SYS_CLK 0x00
#define OMAP_TIMER_SRC_32_KHZ 0x01
#define OMAP_TIMER_SRC_EXT_CLK 0x02

/* timer interrupt enable bits */
#define OMAP_TIMER_INT_CAPTURE (1 << 2)
#define OMAP_TIMER_INT_OVERFLOW (1 << 1)
#define OMAP_TIMER_INT_MATCH (1 << 0)

/* trigger types */
#define OMAP_TIMER_TRIGGER_NONE 0x00
#define OMAP_TIMER_TRIGGER_OVERFLOW 0x01
#define OMAP_TIMER_TRIGGER_OVERFLOW_AND_COMPARE 0x02

struct omap_dm_timer;
struct clk;

int omap_dm_timer_init(void);

struct omap_dm_timer *omap_dm_timer_request(void);
struct omap_dm_timer *omap_dm_timer_request_specific(int timer_id);
void omap_dm_timer_free(struct omap_dm_timer *timer);
void omap_dm_timer_enable(struct omap_dm_timer *timer);
void omap_dm_timer_disable(struct omap_dm_timer *timer);

int omap_dm_timer_get_irq(struct omap_dm_timer *timer);

u32 omap_dm_timer_modify_idlect_mask(u32 inputmask);
struct clk *omap_dm_timer_get_fclk(struct omap_dm_timer *timer);

void omap_dm_timer_trigger(struct omap_dm_timer *timer);
void omap_dm_timer_start(struct omap_dm_timer *timer);
void omap_dm_timer_stop(struct omap_dm_timer *timer);

void omap_dm_timer_set_source(struct omap_dm_timer *timer, int source);
void omap_dm_timer_set_load(struct omap_dm_timer *timer, int autoreload, unsigned int value);
void omap_dm_timer_set_load_start(struct omap_dm_timer *timer, int autoreload, unsigned int value);
void omap_dm_timer_set_match(struct omap_dm_timer *timer, int enable, unsigned int match);
void omap_dm_timer_set_pwm(struct omap_dm_timer *timer, int def_on, int toggle, int trigger);
void omap_dm_timer_set_prescaler(struct omap_dm_timer *timer, int prescaler);

void omap_dm_timer_set_int_enable(struct omap_dm_timer *timer, unsigned int value);

unsigned int omap_dm_timer_read_status(struct omap_dm_timer *timer);
void omap_dm_timer_write_status(struct omap_dm_timer *timer, unsigned int value);
unsigned int omap_dm_timer_read_counter(struct omap_dm_timer *timer);
void omap_dm_timer_write_counter(struct omap_dm_timer *timer, unsigned int value);

int omap_dm_timers_active(void);


#endif /* __ASM_ARCH_DMTIMER_H */

 

dm_timer_init() has to be called, but since that is called early on in kernel setup, I don't think any kernel-module level stuff should be calling that (and it is declared __init).

 

I'm going to go over an example procedure for using the GP Timers, and then give the same thing as example code below:

So the procedure (which I'm making up as I go, and worked for me) is to request a timer handle.  There are two options:  If you are doing a software-only timer, you can request the first available timer.   If you have hardware requirements (ie. you are using actual timer pads on the chip for I/O) then you can request a specific timer by ID.... using omap_dm_timer_request() and omap_dm_timer_request_specifc(), repectfuly, you will get NULL on failure.

Next, setup the clock source that will drive the timer by calling omap_dm_timer_set_source().  This can either be the system clock, the 32khz clock or an external clock source. 

Then the prescalar on the clock source can be set by calling omap_dm_timer_set_prescalar().  This is NOT OBVIOUS :(  Heh, you're going to have to look at the source (dmtimer.c) and see how it is using the prescalar value, and then look at the  TI doc (http://www.ti.com/litv/pdf/sprufa9b) on how the GP Timers work to understand how to get the prescalar you really want.  Anyways, if you use a prescalar value of 0 you will get a prescalar of 1:1.

The actual clock rate (in Hz) can be determined with omap_dm_timer_get_fclk() and clk_get_rate()... see the example below.  This is really useful for setting our timer to produce events at real-world interval rates by giving us a conversion from seconds to clock ticks.  It also allows us to write code that is not dependant on fore-knowledge of the system clock rate.  Our example we choose a 3 second repeating overflow interval using the timer preload (and auto reload). Note that setting the autoreload to zero will cause this to be in one-shot mode.

Then (for a common use) we can set the timer interrupt (for overflow) so that a IRQ handler get's called using request_irq.  I'm not going to go into IRQ's and handlers here... There is a lot of info about that elsewhere.

Then we just start the timer with omap_dm_timer_start().

Shutting down everything is just a matter of stopping the timer, releasing the IRQ and the timer.

Here it goes:

#include <linux/module.h>		
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/clk.h>
#include <linux/irq.h>
#include <linux/interrupt.h>
#include <asm/io.h>
#include <mach/dmtimer.h>
#include <linux/types.h>

// opaque pointer to timer object
static struct omap_dm_timer *timer_ptr;

// the IRQ # for our gp timer
static int32_t timer_irq;

// do some kernel module documentation
MODULE_AUTHOR("Adam J Kunen <adam@kunen.org>");
MODULE_DESCRIPTION("OMAP3530 GP Timer Test Module");
MODULE_LICENSE("GPL");



/* The interrupt handler.
This is pretty basic, we only get an interrupt when the timer overflows,
We are just going to print a really obnoxious message each time
*/
static irqreturn_t timer_irq_handler(int irq, void *dev_id)
{
// keep track of how many calls we had
static int32_t irq_counter = 0;

// reset the timer interrupt status
omap_dm_timer_write_status(timer_ptr, OMAP_TIMER_INT_OVERFLOW);
omap_dm_timer_read_status(timer_ptr); // YES, you really need to do this 'wasteful' read

// print obnoxious text
printk("Meow Meow Meow %d\n", irq_counter ++);

// tell the kernel it's handled
return IRQ_HANDLED;
}



// Initialize the kernel module
static int __init gptimer_test_init(void)
{
int ret = 0;
struct clk *gt_fclk;
uint32_t gt_rate;


printk("gptimer test: starting moudle init\n");

// request a timer (we are asking for ANY open timer, see dmtimer.c for details on how this works)
timer_ptr = omap_dm_timer_request();
if(timer_ptr == NULL){
// oops, no timers available
printk("gptimer test: No more gp timers available, bailing out\n");
return -1;
}

// set the clock source to system clock
omap_dm_timer_set_source(timer_ptr, OMAP_TIMER_SRC_SYS_CLK);

// set prescalar to 1:1
omap_dm_timer_set_prescaler(timer_ptr, 0);

// figure out what IRQ our timer triggers
timer_irq = omap_dm_timer_get_irq(timer_ptr);

// install our IRQ handler for our timer
ret = request_irq(timer_irq, timer_irq_handler, IRQF_DISABLED | IRQF_TIMER , "gptimer test", timer_irq_handler);
if(ret){
printk("gptimer test: request_irq failed (on irq %d), bailing out\n", timer_irq);
return ret;
}

// get clock rate in Hz
gt_fclk = omap_dm_timer_get_fclk(timer_ptr);
gt_rate = clk_get_rate(gt_fclk);

// set preload, and autoreload
// we set it to the clock rate in order to get 1 overflow every 3 seconds
omap_dm_timer_set_load(timer_ptr, 1, 0xFFFFFFFF - (3 * gt_rate));

// setup timer to trigger our IRQ on the overflow event
omap_dm_timer_set_int_enable(timer_ptr, OMAP_TIMER_INT_OVERFLOW);

// start the timer!
omap_dm_timer_start(timer_ptr);

// done!
printk("gptimer test: GP Timer initialized and started (%lu Hz, IRQ %d)\n", (long unsigned)gt_rate, timer_irq);

// return sucsess
return 0;
}

// Cleanup after ourselfs
static void __exit gptimer_test_exit(void)
{
printk("gptimer test: cleanup called\n");

// stop the timer
omap_dm_timer_stop(timer_ptr);

// release the IRQ handler
free_irq(timer_irq, timer_irq_handler);

// release the timer
omap_dm_timer_free(timer_ptr);
}

// provide the kernel with the entry/exit routines
module_init(gptimer_test_init);
module_exit(gptimer_test_exit);

 

So that's it.  There are a lot more ways to use timers, I would suggest reading the TI doc about the timers.  There are quite a few configuration options available for them, and many mor emodes of operation.  With 12 timers available on the OMAP 3530 there are quite a few real-time tasks that can be accomplished, even when you are using a non-realtime kernel like Linux.