UPGRADE YOUR BROWSER

We have detected your current browser version is not the latest one. Xilinx.com uses the latest web technologies to bring you the best online experience possible. Please upgrade to a Xilinx.com supported browser:Chrome, Firefox, Internet Explorer 11, Safari. Thank you!

cancel
Showing results for 
Search instead for 
Did you mean: 
Visitor tymonz
Visitor
7,546 Views
Registered: ‎08-12-2015

TTC Driver on Linux Problem

Dear Colleagues,

 

I have few questions about Linux Kernel Modules which can work with xlnx-linux.

Currently I'm working on MicroZed Zynq-7000 with Petalinux 2015.2 and SDK.

 

I wanted to do some basic kernel driver which can handle interrupts from TTC. Before that I checked registers with aplication that use mmap(). I read interrupt simply by testing Interrupt flag on appropriate register in infinity loop. Everything worked fine so I decided to go further and do kernel driver for TTC.

 

To do it I take pattern from this topic: http://forums.xilinx.com/t5/Embedded-Linux/AR-50572-Axi-timer-interrupts-in-linux/td-p/335077

 

So I remake this source code and this is what I get:

 

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/io.h>
#include <linux/fs.h>           //required for fops
#include <linux/uaccess.h>      //required for 'cpoy_from_user' and 'copy_to_user'
#include <linux/signal.h>       //required for kernel-to-userspace signals
 
MODULE_LICENSE("GPL");
 
#define IRQ_NUM         42              // interrupt line
#define TIMER_BASE      0xF8001000
 
#define TIMER_SIZE      0x0000FFFF
 
#define LOAD_VAL        111     // address load value (500000000 cycles) ~5 sec
 
#define TTC1_CLOCK_CONTROL              0xF8001000
#define TTC1_COUNTER_CONTROL            0xF800100C
#define TTC1_COUNTER_VALUE              0xF8001018
#define TTC1_INTERVAL_COUNTER_VALUE     0xF8001024
#define TTC1_INTERRUPT_REGISTER         0xF8001054
#define TTC1_INTERRUPT_ENABLE           0xF8001060
 
unsigned long *pTIMER_TTC_CLOCK_CONTROL;                // pointer to timer clock control register
unsigned long *pTIMER_TTC_COUNTER_CONTROL;              // pointer to timer counter control register
unsigned long *pTIMER_INTERVAL_VALUE;                   // pointer to timer Interval load register
unsigned long *pTIMER_COUNTER_VALUE;                    // pointer to timer Counter load register
unsigned long *pTIMER_INTERRUPT_ENABLE;                 // pointer to timer Interrupt enable register
unsigned long *pTIMER_INTERRUPT_REGISTER;               // pointer to timer Interrupt register load register
 
#define DEVICE_NAME "ttc"               // device name
#define TTC_MAJOR 150                   // device major number // 22
#define BUF_LEN 80                      // max buffer length
#define SUCCESS 0                       // success return value
 
//unsigned long *pSYSCALL_Virtual;      // base address
static int Device_Open = 0;             // device status
 
static char msg[BUF_LEN];               // the msg the device will give when asked
static char *msg_Ptr;
static int intcount;
 
// write routine (called when write() is used in user-space)
ssize_t ttc_write(struct file *flip, const char *buf, size_t length, loff_t *offset)
{
        printk("ttc_write.\n");                                 // debug: procedure call message
        if (copy_from_user(msg, buf, length) != 0)              // read buffer from user space
                return -EFAULT;                                 // return error if it failed
        printk(msg);                            // debug: what string is received
 
        if (msg == 49)                          // enable timer command when msg = "1"
        {
                printk("Driver enables timer.\n");                              // print timer status message
                iowrite32(LOAD_VAL,pTIMER_INTERVAL_VALUE);// load interval value
                iowrite32(0x00000022,pTIMER_TTC_COUNTER_CONTROL);               // set counter and start
                iowrite32(0x00000001,pTIMER_INTERRUPT_ENABLE);  // Enable Interrupt in INTREGISTER
               
                                                               
                return SUCCESS;                                
        }
        else if (msg == 48)                             // disable timer command when msg = "0"
        {
                printk("Driver disables timer.\n");             // print timer status message
                iowrite32(0x00000023,pTIMER_TTC_COUNTER_CONTROL);       // Generate mode, upcounter, reload generate value
                                                                // no load, enable IRQ, disable Timer
                return SUCCESS;                                 // return 0 to application
        }
        else
        {
                printk("Driver received wrong value.\n");       // Print error message
                return -EFAULT;                                 // unknown value received
        }
}
 
// read routine (called when read() is used in user-space)
ssize_t ttc_read(struct file *flip, char *buf, size_t length, loff_t *offset)
{
        unsigned long timerval;
        // read timer value
 
        printk("ttc_read.\n");                          // debug: procedure call message
        timerval = ioread32(pTIMER_COUNTER_VALUE);
        printk("Read() call value : %lu Cycles\n",timerval);    // display timer value
       
        if (copy_to_user(buf, &msg, length) != 0)               // send counter value
                return -EFAULT;
        else
                return 0;
}
 
// open routine (called when a device opens /dev/ttc)
static int ttc_open(struct inode *inode, struct file *file)
{
        if (Device_Open)                                        // if the device is allready open,
                return -EBUSY;                                  // return with an error
 
        Device_Open++;                                          // 'open' device
        sprintf(msg,"You tried to open the ttc module.\n");     // print open message
        msg_Ptr = msg;                                         
        try_module_get(THIS_MODULE);
        return 0;
}
 
// close routine (called whne a device closes /dev/ttc)
static int ttc_close(struct inode *inode, struct file *file)
{
        Device_Open--;                                          // 'close' device
 
        module_put(THIS_MODULE);
        return 0;
}
 
// device init and file operations
struct file_operations ttc_fops = {
        .read = ttc_read,               // read()
        .write = ttc_write,             // write()
        .open = ttc_open,               // open()
        .release = ttc_close,           // close()
};
 
// timer interrupt handler
static irqreturn_t irq_handler(int irq,void*dev_id)            
{      
        unsigned long temp;
        unsigned long timervalue;
     
        timervalue = ioread32(pTIMER_COUNTER_VALUE);                    // Read Timer/Counter Register
        printk("Interrupt! Timer counter value : %lu Cycles\n",(timervalue-LOAD_VAL));  // Display timer value
 
        temp = ioread32(pTIMER_INTERRUPT_REGISTER);                             // clear timer IRQ
 
        intcount++;
        if (intcount>=1000000000)                                       // after 100 interrupts
        {
                printk("1000 interrupts have been registered.\nDisabling timer");// print timer status message
                //iowrite32(0x00000050,pTIMER_TCSR0);           //disable timer;
                iowrite32(0x00000023,TTC1_COUNTER_CONTROL);
        }
 
        return IRQ_HANDLED;
}
 
// init module      
static int __init mod_init(void)  
{
        unsigned long temp;
        printk(KERN_ERR "Init TTC module. \n");
 
        if (request_irq(IRQ_NUM,irq_handler,IRQF_DISABLED, DEVICE_NAME, NULL))          //request timer interrupt
        {
                printk(KERN_ERR "Not Registered IRQ. \n");
                return -EBUSY;
        }
        printk(KERN_ERR "Registered IRQ. \n");
 
        pTIMER_TTC_CLOCK_CONTROL = ioremap_nocache(TTC1_CLOCK_CONTROL,0x4);             // map timer clk control register
        pTIMER_TTC_COUNTER_CONTROL = ioremap_nocache(TTC1_COUNTER_CONTROL,0x4);         // map timer counter control register
        pTIMER_COUNTER_VALUE = ioremap_nocache(TTC1_COUNTER_VALUE,0x4);                 // map timer count register
        pTIMER_INTERVAL_VALUE = ioremap_nocache(TTC1_INTERVAL_COUNTER_VALUE,0x4);       // map timer interval value register
        pTIMER_INTERRUPT_ENABLE = ioremap_nocache(TTC1_INTERRUPT_ENABLE,0x4);           // map timer interrupt control register
        pTIMER_INTERRUPT_REGISTER = ioremap_nocache(TTC1_INTERRUPT_REGISTER,0x4);       // map timer interrupt register
        iowrite32(LOAD_VAL, pTIMER_INTERVAL_VALUE);                                     // place load value in load register
        temp = ioread32(pTIMER_INTERVAL_VALUE);                                         // debug: read load value to check
        printk("Load value: %lu.\n",temp);                                              // debug: print the read load value
 
        //iowrite32(0x000000B0, pTIMER_TCSR0);                  // load TLR
        //iowrite32(0x000000D0, pTIMER_TCSR0);                  // Generate mode,downcounter,reload generate
                                                                // value,no load,enable IRQ,enable Timer
        intcount = 0;                                   // set interrupt count to 0, driver will unload on 100 interrupts
 
        // manual node creation
        if (register_chrdev(TTC_MAJOR, DEVICE_NAME, &ttc_fops))
                printk("Error: cannot register to major device 22.\n");
       
        printk("Type: mknod /dev/%s c %d 0\n",DEVICE_NAME, TTC_MAJOR);
        printk("And remove it after unloading the module.\n");
 
        return SUCCESS;
}
 
// exit module
static void __exit mod_exit(void)              
{
        iounmap(pTIMER_TTC_CLOCK_CONTROL);                              // unregister timer hardware
        iounmap(pTIMER_TTC_COUNTER_CONTROL);
        iounmap(pTIMER_COUNTER_VALUE);
        iounmap(pTIMER_INTERVAL_VALUE);
        iounmap(pTIMER_INTERRUPT_ENABLE);
        iounmap(pTIMER_INTERRUPT_REGISTER);
        free_irq(IRQ_NUM, NULL);                                        // unregister timer interrupt
        unregister_chrdev(TTC_MAJOR, "ttc");                            // unregister device
        printk(KERN_ERR "Exit ttc Module. \n");                         // print unload message
}
 
 
module_init(mod_init);
module_exit(mod_exit);
 
MODULE_AUTHOR ("Tom");
MODULE_DESCRIPTION("Testdirver for the Xilinx AXI Timer IP core & system calls.");
MODULE_LICENSE("GPL v2");
MODULE_ALIAS("custom:ttc_dv"); 

Pastebin: http://pastebin.com/6wSJui4X

 

I can load and unload that module but write and read has some probles. But let's start from the beggining, I would be grateful if anyone could answer me or guide me in the following issues:

 

 

 

1. I know that system-top.dts shoud be modified because TTC needs to know how to handle interrupt, didn't it? but I have no idea where I can get the compatible label of my driver.

I guess that I have to add some lines in my driver. How it should look like? Now I have something like this:

 

/dts-v1/;
/include/ "system-conf.dtsi"
 
        &gem0 {
                phy-handle = <&phy0>;
                phy-mode = "rgmii-id";
               
                mdio {
                        #address-cells = <1>;
                        #size-cells = <0>;
                        phy0: phy@0 {
                                compatible = "marvell,88e1510";
                                device_type = "ethernet-phy";
                                reg = <0x0>;
                                marvell,reg-init = <3 16 0xff00 0x1e 3 17 0xfff0 0x00>;
                        };
                };
        };
        &gpio0 {       
                compatible = "generic-uio","uio","uio_pdrv_qenirq";
                reg = <0xe000a000 0x1000>;
                                };
        &ttc0 {
                        interrupt-parent = <&intc>;
                        compatible = "ttc_dv";
                        clocks = <&clkc 6>;
                        reg = <0xF8001000 0x1000>;
                                };

 but I am pretty sure that ttc_dv is wrong.

 

 

2. When I try to read from /dev/ttv using cat i get following error:

 

ttc_read.
Read() call value : 0 Cycles
Unable to handle kernel paging request at virtual address 7f005000
pgd = ad8b4000
[7f005000] *pgd=3d820811, *pte=00000000, *ppte=00000000
Internal error: Oops - BUG: 7 [#1] PREEMPT SMP ARM
Modules linked in: TimerDriver(O) [last unloaded: petalinuxmodule]
CPU: 1 PID: 946 Comm: cat Tainted: G           O   3.19.0-xilinx-13843-g241b3b9-dirty #9
Hardware name: Xilinx Zynq Platform
task: ae599ac0 ti: ae5b2000 task.ti: ae5b2000
PC is at __copy_to_user_std+0x4c/0x3a8
LR is at 0x0
pc : [<801c542c>]    lr : [<00000000>]    psr: 200d0013
sp : ae5b3f2c  ip : 00000000  fp : 00000001
r10: 00000000  r9 : ae5b2000  r8 : 00000000
r7 : 00000000  r6 : 00000000  r5 : 00000000  r4 : 00000000
r3 : 00000000  r2 : 00000700  r1 : 7f004ff4  r0 : 7ec8a4f0
Flags: nzCv  IRQs on  FIQs on  Mode SVC_32  ISA ARM  Segment user
Control: 18c5387d  Table: 3d8b404a  DAC: 00000015
Process cat (pid: 946, stack limit = 0xae5b2238)
Stack: (0xae5b3f2c to 0xae5b4000)
3f20:                            7ec89c70 7f004770 7ec89c70 8000dee4 7ec89c70
3f40: 00001000 00000000 00001000 7f004060 7ec89c70 ae646d00 ae5b3f88 800b964c
3f60: ae646d00 7ec89c70 00001000 ae646d00 ae646d00 00001000 7ec89c70 8000dee4
3f80: ae5b2000 800b96f4 00000000 00000000 00001000 000aba18 00001000 7ec89c70
3fa0: 00000003 8000dd60 000aba18 00001000 00000003 7ec89c70 00001000 01000000
3fc0: 000aba18 00001000 7ec89c70 00000003 00000003 00000001 00000000 00000001
3fe0: 00000000 7ec89c54 00011a68 76e2b25c 600d0010 00000003 c2cfcc7d dd76766f
[<801c542c>] (__copy_to_user_std) from [<7f004060>] (ttc_read+0x60/0x80 [TimerDriver])
[<7f004060>] (ttc_read [TimerDriver]) from [<800b964c>] (vfs_read+0x84/0xec)
[<800b964c>] (vfs_read) from [<800b96f4>] (SyS_read+0x40/0x80)
[<800b96f4>] (SyS_read) from [<8000dd60>] (ret_fast_syscall+0x0/0x34)
Code: ba000002 f5d1f03c f5d1f05c f5d1f07c (e8b151f8)
---[ end trace 9bc6c212ed465a78 ]---
Segmentation fault

I see that is error with pagging virtual memory but where I should search the cause?

 

3. And the last one, in write function it seems that variable msg has some strange values. Debug line shows that msg is "1" but can't acces "Enable Timer". Log:

 

ttc_write.
1
u tried to open the ttc module.
Driver received wrong value.
sh: write error: Bad address

I'm aware that is a lot of huge questions and I am newbie, but I will be really glad if anyone could help me in some of this issues. Really appreciate.

 

Best regards,

tymonz

 

 

0 Kudos
3 Replies
Scholar sampatd
Scholar
7,529 Views
Registered: ‎09-05-2011

Re: TTC Driver on Linux Problem

1.

1. I know that system-top.dts shoud be modified because TTC needs to know how to handle interrupt, didn't it? but I have no idea where I can get the compatible label of my driver.

 

A matching compatible string should be present in your driver. Check the following link for more details:

http://stackoverflow.com/questions/19244847/from-where-platform-device-gets-it-name

 

 

Currently, the cadence ttc drivers support the TTC peripheral on zynq.

https://github.com/Xilinx/linux-xlnx/blob/89590ccd978a242819f95e26ea26ee609864bd52/drivers/clocksource/cadence_ttc_timer.c

 

The device tree binding document for this driver can be found here:

https://github.com/Xilinx/linux-xlnx/blob/89590ccd978a242819f95e26ea26ee609864bd52/Documentation/devicetree/bindings/timer/cadence%2Cttc-timer.txt

0 Kudos
Visitor tymonz
Visitor
7,502 Views
Registered: ‎08-12-2015

Re: TTC Driver on Linux Problem

@sampatd

 

Thank you for your reply,

so there is a built-in driver for ttc and Linux uses it. Now I see that ttc supports following things:

 

* T1: Timer 1, clocksource for generic timekeeping
 * T2: Timer 2, clockevent source for hrtimers
 * T3: Timer 3, <unused>

Do you know what is that and how I can acces such things in Linux?

The only way to access ttc from linux and use it for my own purposes is to modify this driver? I tried to copy that code to the external module but it seems there is a problem with definitions:

 

modulet: Unknown symbol clk_notifier_register (err 0)
modulet: Unknown symbol clockevents_update_freq (err 0)
modulet: Unknown symbol clk_prepare (err 0)
modulet: Unknown symbol sched_clock_register (err 0)
modulet: Unknown symbol clockevents_config_and_register (err 0)
modulet: Unknown symbol __clocksource_register_scale (err 0)
modulet: Unknown symbol clk_get_rate (err 0)
modulet: Unknown symbol of_property_read_u32_array (err 0)
modulet: Unknown symbol irq_of_parse_and_map (err 0)
modulet: Unknown symbol clk_enable (err 0)
modulet: Unknown symbol clk_unprepare (err 0)
modprobe: can't load module modulet (extra/modulet.ko): unknown symbol in module, or unknown parameter

I guess these function are reserved for built-in modules.

Could you tell me what will be the best way to use ttc for my own?

 

Best regards,

tymonz

0 Kudos
Scholar sampatd
Scholar
7,460 Views
Registered: ‎09-05-2011

Re: TTC Driver on Linux Problem

Referring to page 18 of UG1144, one of the hardware requirement for PetaLinux is that you enable Zynq TTC.
http://www.xilinx.com/support/documentation/sw_manuals/petalinux2015_2/ug1144-petalinux-tools-reference-guide.pdf

Once you build and boot PetaLinux, you would have, by default, enabled the cadence_ttc driver in the kernel. Hence, you should be able to use this driver. Although, there are certain features, such as pwm support, not included in this Linux driver.

For knowledge on TTC, I recommend the following:
https://forums.xilinx.com/t5/Xcell-Daily-Blog/Introduction-to-the-Zynq-Triple-Timer-Counter-Part-One-Adam/ba-p/407537

0 Kudos