This thread has been locked.

If you have a related question, please click the "Ask a related question" button in the top right corner. The newly created question will be automatically linked to this question.

Using OMAP4 General-Purpose Timers

I'm not sure if this is the correct forum for this question.  If not, please move it to the correct place.

I am attempting to use one of the OMAP4's general-purpose timers to do the timing for communication over GPIO pins between the PandaBoard ES and a custom chip.  It has some pretty particular timings, and I need a clock speed of at least ~2 MHz.

Right now I'm just trying to set up a clock for the reading portion of communication.  I have a kernel driver which attaches an interrupt request to a general purpose timer and flips the GPIO pin we're using as a clock on the interrupt.  This works well for speeds up to ~60 KHz, but then won't go faster, and the PandaBoard seems to freeze up if I try speeds above 100 KHz.  The code I'm using is below.

(1) Is this the right way to do this?  I need to synchronize manipulation of the GPIO pins with a clock.

(2) Why can't I drive the GPIO pin faster?  Is my kernel module written badly, so the interrupts/flipping the pins is too expensive, or am I not using the clock/timer correctly?

EDIT: I also am only able to run this kernel module once per boot.  After I load the module, then unload it, if I load it a second time, it complains that it cannot find an available clock.  I cannot load the module again until I reboot.  The clock is released in the exit function, so I am not sure why this is happening.

Thanks for your help.

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

#include "skywalker.h"

static struct skywalker_data sky_ptr;

#define TIMER_MAX 0xFFFFFFFF

static void timer_handler(void)
{
  /* Reset the timer interrupt status. */
  omap_dm_timer_write_status(timer_ptr, OMAP_TIMER_INT_OVERFLOW);
  omap_dm_timer_read_status(timer_ptr);

  /* Toggle clock pin. */
  gpio_set_value(sky_ptr.pin, sky_ptr.adc_clk_val);
  sky_ptr.adc_clk_val = !sky_ptr.adc_clk_val;
}

static irqreturn_t timer_irq_handler(int irq, void *dev_id)
{
  timer_handler();

  return IRQ_HANDLED;
}

static int set_clk_freq(int freq)
{
  // set preload, and autoreload of the timer
  sky_ptr.period = sky_ptr.timer_rate / (4*freq);
  sky_ptr.load = TIMER_MAX+1 - sky_ptr.period;
  omap_dm_timer_set_load(timer_ptr, 1, sky_ptr.load);
  // store the new frequency
  sky_ptr.frequency = freq;

  return 0;
}

static int gpio_setup_pin(uint32_t gpio_number)
{
  int err;

  if (gpio_is_valid(gpio_number)) {
    printk(KERN_INFO "skywalker module: setting up gpio pin %i...",gpio_number);
    err = gpio_request(gpio_number,"skyIRQ");
    if (err) {
      printk(KERN_WARNING "skywalker module: failed to request GPIO %i\n",gpio_number);
      return -1;
    }

    err = gpio_direction_output(gpio_number,0);

    if (err) {
      printk(KERN_WARNING "skywalker module: failed to set GPIO to ouput\n");
      return -1;
    }

    sky_ptr.pin = gpio_number;
  } else {
    printk(KERN_DEBUG "skywalker module: requested GPIO is not valid\n");
    return -1;
  }

  printk(KERN_INFO "skywalker module: setup DONE\n");

  return 0;
}

static int __init skywalker_start(void)
{
  int rv = 0;
  uint32_t gt_rate;

  printk(KERN_INFO "Loading Skywalker Module... \n");

  // request any timer
  timer_ptr = omap_dm_timer_request();

  if (timer_ptr == NULL) {
    // no timers available
    printk(KERN_ERR "skywalker module: No more gp timers available, bailing out\n");
    return -1;
  }

  // set the clock source to the system clock
  rv = omap_dm_timer_set_source(timer_ptr, OMAP_TIMER_SRC_SYS_CLK);
  if (rv) {
    printk(KERN_ERR "skywalker module: could not set source\n");
    return rv;
  }

  // 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
  rv = request_irq(timer_irq, timer_irq_handler, IRQF_DISABLED | IRQF_TIMER , "skywalker", timer_irq_handler);
  if (rv) {
    printk(KERN_WARNING "skywalker module: request_irq failed (on irq %d), bailing out\n", timer_irq);
    return rv;
  }

  /* Get clock rate. */
  gt_rate = clk_get_rate(omap_dm_timer_get_fclk(timer_ptr));
  sky_ptr.timer_rate = gt_rate;

  /* Set frequency. */
  set_clk_freq(1000);

  /* Set up timer to trigger interrupt on overflow. */
  omap_dm_timer_set_int_enable(timer_ptr, OMAP_TIMER_INT_OVERFLOW);

  omap_dm_timer_start(timer_ptr);

  printk(KERN_INFO "skywalker module: GP Timer initialized and started (%lu Hz, IRQ %d)\n",
      (long unsigned)gt_rate, timer_irq);

  printk(KERN_INFO "skywalker module: IRQ with frequency %u load %u, and period %u\n", sky_ptr.frequency, sky_ptr.load, sky_ptr.period);

  gpio_setup_pin(121);
  sky_ptr.pin = 121;

  sky_ptr.adc_clk_val = 1;

  return 0;
}

static void __exit skywalker_end(void)
{
  printk(KERN_INFO "Exiting skywalker Module. \n");

  omap_dm_timer_stop(timer_ptr);

  free_irq(timer_irq, timer_irq_handler);

  omap_dm_timer_free(timer_ptr);

  gpio_free(sky_ptr.pin);
}

module_init(skywalker_start);
module_exit(skywalker_end);

MODULE_LICENSE("GPL");