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.
I'm the process of investigating using one of the DM Timers on the Beaglebone Black to generate a signal for a specified period. I'm currently using TI's Linux SDK v.8.
I found this example code for setting up a DM Timer and having it generate an interrupt every 3 seconds.
Unfortunately I'm not seeing my IRQ handler being called after I install my module, and to confirm /proc/interrupts indicates that no interrupts have occurred.
Is there something else I need to do to use the DM Timer, since this example code was written for an older 2.6 kernel?
Also how can I specify the specific DM Timer I want? The function omap_dm_timer_request_by_id no longer functions since with SDK v.8 the device tree is now being used. I'm assuming I need to make some changes to my device tree, but I'm sure what changes to make.
Ideally I want to use DM Timer 4, to set the I/O for a fixed time of 100 to 400 microseconds, and to get an interrupt when this is complete.
Thanks,
Sean Moffatt
I have been able to get the DM Timer module to generate a signal change for an exact time period. I've included my code below and changes to my device tree.
C Code
#include <linux/module.h> /* Needed by all Linux Kernel Modules */ #include <linux/platform_device.h> /* Needed to query the plaform configuration for addresses, interrupts,etc */ #include <linux/kernel.h> /* Needed for KERN_INFO */ #include <linux/init.h> /* Needed for the module_ macros */ /* ARM DM Timer Header */ #include <plat/dmtimer.h> /* Needed for clk functions */ #include <linux/clk.h> /* Interrupt Header */ #include <linux/interrupt.h> #define DRIVER_AUTHOR "Sean Moffatt" #define DRIVER_DESC "Test DM Timer" #define DRIVER_NAME "test_module_timer" static struct omap_dm_timer *timer_ptr = NULL; static int32_t timer_irq; static uint32_t timer_rate; static irqreturn_t timer_irq_handler( int irq, void * dev_id) { int status = 0; /* Read the current Status */ status = omap_dm_timer_read_status(timer_ptr); /* Clear the timer interrupt */ if (status == OMAP_TIMER_INT_MATCH) { omap_dm_timer_write_status(timer_ptr, OMAP_TIMER_INT_MATCH); } /* Stop the Timer */ omap_dm_timer_stop(timer_ptr); /* Indicate the Interrupt was handled */ return IRQ_HANDLED; } static int test_module_init(struct platform_device *pdev) { int ret = 1; struct clk *timer_clk; /* Need to request OMAP_TIMER_HAS_PWM to ensure we have an I/O Pin to toggle */ /* Still need to determine how to request a specific Timer*/ timer_ptr = omap_dm_timer_request_by_cap(OMAP_TIMER_HAS_PWM); if(timer_ptr == NULL){ /* no timers available */ printk(KERN_INFO "No more DM timers available!!\n"); return -1; } /* Set the Clock source to the System Clock */ ret = omap_dm_timer_set_source(timer_ptr, OMAP_TIMER_SRC_SYS_CLK); /* Determine what IRQ the timer triggers */ timer_irq = omap_dm_timer_get_irq(timer_ptr); /* Setup the IRQ handler */ ret = request_irq(timer_irq, timer_irq_handler, IRQF_TIMER, DRIVER_NAME, NULL); /* Setup the timer to trigger the IRQ on the match event */ omap_dm_timer_set_int_enable(timer_ptr, OMAP_TIMER_INT_MATCH); /* Get the Clock rate in Hz */ timer_clk = omap_dm_timer_get_fclk(timer_ptr); timer_rate = clk_get_rate(timer_clk); /* Setup the Output for the Timer pin */ /* Setup to toggle on the overflow and the compare match event */ omap_dm_timer_set_pwm(timer_ptr, 0, 1, OMAP_TIMER_TRIGGER_OVERFLOW_AND_COMPARE); /* Enable the Timer */ /* Needs to be done before we can write to the counter */ omap_dm_timer_enable(timer_ptr); /* Set the initial Count */ /* According to section 20.1.3.5 Pulse-Width Modulation, an overflow or match be used to toggle when a compare condition occurs*/ /* Therefore it we will trigger the overflow event almost immediately to ensure our toggle will be generated quickly */ omap_dm_timer_write_counter(timer_ptr, (0xFFFFFFFF - 5)); /* Setup the Load Register */ /* Setup as autoload to set the the counter back to 0 on an overflow */ omap_dm_timer_set_load(timer_ptr, 1, 0); /* Set the the compare register to 1 second */ /* This will ensure the signal toggle of 1 second after the overflow event*/ omap_dm_timer_set_match(timer_ptr, 1, timer_rate); /* Start the timer */ ret = omap_dm_timer_start(timer_ptr); printk(KERN_INFO "Timer Start ret %d\n", ret); /* * A non 0 returns means the call to init_modules failed; module can't be loaded */ return 0; } static int test_module_exit(struct platform_device *pdev) { int ret = 1; /* stop the timer */ ret = omap_dm_timer_stop(timer_ptr); printk(KERN_INFO "Timer Stop ret = %d\n", ret); /* Release the IRQ handler */ free_irq(timer_irq, NULL); printk(KERN_INFO "Free IRQ Done\n"); /* Release the timer */ ret = omap_dm_timer_free(timer_ptr); printk(KERN_INFO "Timer Free ret = %d\n", ret); return 0; } static const struct of_device_id test_module_timer_of_match[] = { { .compatible = "test,am33xx-test_module_timer" }, {}, }; static struct platform_driver test_module_timer_driver = { .driver = { .name = DRIVER_NAME, .owner = THIS_MODULE, .of_match_table = test_module_timer_of_match, }, .probe = test_module_init, .remove = test_module_exit, }; module_platform_driver(test_module_timer_driver); MODULE_LICENSE("GPL"); MODULE_AUTHOR(DRIVER_AUTHOR); /* Who wrote this module */ MODULE_DESCRIPTION(DRIVER_DESC); /* What is this module */
Device Tree
In the &am33x_pinmux{
timer4_pins: timer4_pins {
pinctrl-single,pins = <
0x090 (PIN_OUTPUT_PULLDOWN | MUX_MODE2) /* TIMER4 P8_07 */
>;
};
};
am33xx-test_module_timer {
compatible = "test,am33xx-test_module_timer";
status = "okay";
pinctrl-names = "default";
pinctrl-0 = <&timer4_pins>;
};
The only question I still have is how do I ensure my driver gets a specific DM Timer when call the omap_dm_timer driver? I assume this is some setting in the device tree.
Thanks,
Sean
I have determined how to request a specific timer, I just had to add the exact timer's device node to my device tree, and then parse to retrieve it via of_parse_phandle then call omap_dm_timer_request_by_node instead.
Below is my code for my Kernel Module. Following it is the change I made to the device tree.
#include <linux/module.h> /* Needed by all Linux Kernel Modules */ #include <linux/platform_device.h> /* Needed to query the plaform configuration for addresses, interrupts,etc */ #include <linux/kernel.h> /* Needed for KERN_INFO */ #include <linux/init.h> /* Needed for the module_ macros */ /* ARM DM Timer Header */ #include <plat/dmtimer.h> /* Needed for clk functions */ #include <linux/clk.h> /* Interrupt Header */ #include <linux/interrupt.h> #define DRIVER_AUTHOR "Sean Moffatt" #define DRIVER_DESC "Test DM Timer" #define DRIVER_NAME "test_module_timer" static struct omap_dm_timer *timer_ptr = NULL; static int32_t timer_irq; static uint32_t timer_rate; static irqreturn_t timer_irq_handler( int irq, void * dev_id) { int status = 0; /* Read the current Status */ status = omap_dm_timer_read_status(timer_ptr); /* Clear the timer interrupt */ if (status == OMAP_TIMER_INT_MATCH) { omap_dm_timer_write_status(timer_ptr, OMAP_TIMER_INT_MATCH); } /* Stop the Timer */ omap_dm_timer_stop(timer_ptr); /* Indicate the Interrupt was handled */ return IRQ_HANDLED; } static int test_module_init(struct platform_device *pdev) { int ret = 1; struct clk *timer_clk; struct device_node *timer_device_node; /* Attempt to request a timer based on the device node */ timer_device_node = of_parse_phandle(pdev->dev.of_node, "DMtimer", 0); timer_ptr = omap_dm_timer_request_by_node(timer_device_node); if(timer_ptr == NULL){ /* no timers available */ printk(KERN_INFO "Can't request specified DM Timer\n"); return -1; } /* Set the Clock source to the System Clock */ ret = omap_dm_timer_set_source(timer_ptr, OMAP_TIMER_SRC_SYS_CLK); /* Determine what IRQ the timer triggers */ timer_irq = omap_dm_timer_get_irq(timer_ptr); /* Setup the IRQ handler */ ret = request_irq(timer_irq, timer_irq_handler, IRQF_TIMER, DRIVER_NAME, NULL); /* Setup the timer to trigger the IRQ on the match event */ omap_dm_timer_set_int_enable(timer_ptr, OMAP_TIMER_INT_MATCH); /* Get the Clock rate in Hz */ timer_clk = omap_dm_timer_get_fclk(timer_ptr); timer_rate = clk_get_rate(timer_clk); /* Setup the Output for the Timer pin */ /* Setup to toggle on the overflow and the compare match event */ omap_dm_timer_set_pwm(timer_ptr, 0, 1, OMAP_TIMER_TRIGGER_OVERFLOW_AND_COMPARE); /* Enable the Timer */ /* Needs to be done before we can write to the counter */ omap_dm_timer_enable(timer_ptr); /* Set the initial Count */ /* According to section 20.1.3.5 Pulse-Width Modulation, an overflow or match be used to toggle when a compare condition occurs*/ /* Therefore it we will trigger the overflow event almost immediately to ensure our toggle will be generated quickly */ omap_dm_timer_write_counter(timer_ptr, (0xFFFFFFFF - 5)); /* Setup the Load Register */ /* Setup as autoload to set the the counter back to 0 on an overflow */ omap_dm_timer_set_load(timer_ptr, 1, 0); /* Set the the compare register to 1 second */ /* This will ensure the signal toggle of 1 second after the overflow event*/ omap_dm_timer_set_match(timer_ptr, 1, timer_rate); /* Start the timer */ ret = omap_dm_timer_start(timer_ptr); printk(KERN_INFO "Timer Start ret %d\n", ret); /* * A non 0 returns means the call to init_modules failed; module can't be loaded */ return 0; } static int test_module_exit(struct platform_device *pdev) { int ret = 1; /* stop the timer */ ret = omap_dm_timer_stop(timer_ptr); printk(KERN_INFO "Timer Stop ret = %d\n", ret); /* Release the IRQ handler */ free_irq(timer_irq, NULL); printk(KERN_INFO "Free IRQ Done\n"); /* Release the timer */ ret = omap_dm_timer_free(timer_ptr); printk(KERN_INFO "Timer Free ret = %d\n", ret); return 0; } static const struct of_device_id test_module_timer_of_match[] = { { .compatible = "test,am33xx-test_module_timer" }, {}, }; static struct platform_driver test_module_timer_driver = { .driver = { .name = DRIVER_NAME, .owner = THIS_MODULE, .of_match_table = test_module_timer_of_match, }, .probe = test_module_init, .remove = test_module_exit, }; module_platform_driver(test_module_timer_driver); MODULE_LICENSE("GPL"); MODULE_AUTHOR(DRIVER_AUTHOR); /* Who wrote this module */ MODULE_DESCRIPTION(DRIVER_DESC); /* What is this module */
Device Tree
In the &am33x_pinmux{
timer4_pins: timer4_pins {
pinctrl-single,pins = <
0x090 (PIN_OUTPUT_PULLDOWN | MUX_MODE2) /* TIMER4 P8_07 */
>;
};
};
am33xx-test_module_timer {
compatible = "test,am33xx-test_module_timer";
status = "okay";
pinctrl-names = "default";
pinctrl-0 = <&timer4_pins>;
DMtimer = <&timer4>;
};
Hi Sean,
The above details regarding using the DMTimer are great! I am trying to perform a similar task with slight modifications to the use of the dmtimer itself and your post has been invaluable.
Do you mind briefly outlining all the steps necessary to get your 'example' running? (I believe I am quite close - but I can't get my pinmux to 'stick' so I'm not sure if I'm missing a build step or what...)
Thanks!
Best Regards,
Mark-
Hi Mark,
I'm running TI SDK v.8 OS on a Beaglebone black.
I modified the am335x-boneblack.dts in the SDK's board-support/linux-3.14.26-g2489c02/arch/arm/boot/dts/ to add the following to the am33x_pinmux section.
timer4_pins: timer4_pins { pinctrl-single,pins = < 0x090 (PIN_OUTPUT_PULLDOWN | MUX_MODE2) /* TIMER4 P8_07 */ >; };
Then the following to the final section.
am33xx-test_module_timer { compatible = "test,am33xx-test_module_timer"; status = "okay"; pinctrl-names = "default"; pinctrl-0 = <&timer4_pins>; };
Then to compile the device tree I called the following in the board-support/linux-3.14.26-g2489c02/ directory.
export PATH=<TI SDK v.8 Install PATH>/ti-sdk-am335x-evm-08.00.00.00/linux-devkit/sysroots/i686-arago-linux/usr/bin/:$PATH
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- am335x-boneblack.dtb
Where <TI SDK v.8 Install PATH> is the location of where the SDK was installed in the system.
Then I copied board-support/linux-3.14.26-g2489c02/arch/arm/boot/dts/am335x-boneblack.dtb to the Target's /boot/ directory.
To compile the code I used the following Makefile.
MODULES := test_module_timer.o ARCH := arm ccflags-$(CONFIG_ARCH_MULTIPLATFORM) := -I$(srctree)/arch/arm/plat-omap/include CROSS_COMPILE := arm-linux-gnueabihf- obj-m := $(MODULES) #Path to the ARM compiled kernel ROOTDIR := ../linux-3.14.26-g2489c02 MAKEARCH := $(MAKE) ARCH=$(ARCH) -WALL CROSS_COMPILE=$(CROSS_COMPILE) all: modules modules: $(MAKEARCH) -C $(ROOTDIR) M=${shell pwd} modules clean: $(MAKEARCH) -C $(ROOTDIR) M=${shell pwd} clean
My environment was setup with the test directory containing kernel module source in test_module_timer.c at the same level as the linux source.
~/ti-sdk-am335x-evm-08.00.00.00/board-support $ ls extra-drivers linux-3.14.26-g2489c02 prebuilt-images test u-boot-2014.07-g7e537bf
In the test directory I called make to compile the kernel module.
Then on the target I loaded the test_module_timer.ko, and could verify the pinmuxing by the following command.
cat /sys/kernel/debug/pinctrl/44e10800.pinmux/pinmux-pins | grep -i timer pin 36 (44e10890.0): am33xx-test_module_timer.11 (GPIO UNCLAIMED) function timer4_pins group timer4_pins
I can also verify that the Timer interrupt by checking /proc/interrupts.
Before loading test_module_timer.ko
cat /proc/interrupts CPU0 28: 3025 INTC 12 edma 30: 13 INTC 14 edma_error 33: 0 INTC 17 47400000.dma-controller 34: 0 INTC 18 musb-hdrc.0.auto 35: 1 INTC 19 musb-hdrc.1.auto 44: 318 INTC 28 mmc1 52: 0 INTC 36 tilcdc 56: 0 INTC 40 4a100000.ethernet 57: 394 INTC 41 4a100000.ethernet 58: 174 INTC 42 4a100000.ethernet 59: 0 INTC 43 4a100000.ethernet 80: 8874 INTC 64 mmc0 84: 1688 INTC 68 gp_timer 86: 212 INTC 70 44e0b000.i2c 88: 222 INTC 72 OMAP UART0 91: 0 INTC 75 rtc0 92: 0 INTC 76 rtc0 93: 0 INTC 77 wkup_m3 94: 1 INTC 78 wkup_m3_txev 125: 0 INTC 109 53100000.sham 127: 0 INTC 111 48310000.rng 150: 0 44e07000.gpio 6 mmc0 Err: 0
After loading test_module_timer.ko
cat /proc/interrupts CPU0 28: 3058 INTC 12 edma 30: 13 INTC 14 edma_error 33: 0 INTC 17 47400000.dma-controller 34: 0 INTC 18 musb-hdrc.0.auto 35: 1 INTC 19 musb-hdrc.1.auto 44: 318 INTC 28 mmc1 52: 0 INTC 36 tilcdc 56: 0 INTC 40 4a100000.ethernet 57: 612 INTC 41 4a100000.ethernet 58: 257 INTC 42 4a100000.ethernet 59: 0 INTC 43 4a100000.ethernet 80: 9009 INTC 64 mmc0 84: 1951 INTC 68 gp_timer 86: 221 INTC 70 44e0b000.i2c 88: 222 INTC 72 OMAP UART0 91: 0 INTC 75 rtc0 92: 0 INTC 76 rtc0 93: 0 INTC 77 wkup_m3 94: 1 INTC 78 wkup_m3_txev 108: 1 INTC 92 test_module_timer 125: 0 INTC 109 53100000.sham 127: 0 INTC 111 48310000.rng 150: 0 44e07000.gpio 6 mmc0 Err: 0
Let me know if you need any more information.
Thanks,
Sean