Programming Interrupts in Raspberry Pi using a simple Kernel Character Device Driver



Introduction


Everyone, in Embedded Electronics domain today, must be familiar with a unique development board among us, called the Raspberry Pi, which is a credit-card-sized single-board computer developed in the UK by the Raspberry Pi Foundation.The Raspberry Pi has a Broadcom BCM2835 system on a chip (SoC),which includes an ARM1176JZF-S 700 MHz processor (The firmware includes a number of "Turbo" modes so that the user can attempt overclocking, up to 1 GHz, without affecting the warranty), VideoCore IV GPU and was originally shipped with 256 megabytes of RAM, later upgraded to 512 MB.It does not include a built-in hard disk or solid-state drive, but uses an SD card for booting and long-term storage. 




     "Raspberry Pi" Computer Model-B Rev1


The Setup


The Raspberry Pi,Model-B Rev 1, i am using is running on Debian flavor of Linux, called the "Wheezy"(Wonder, if it was having a running nose).To access, the signals on the Board, i have used the Expansion Pins exposed on the Board, as shown by the location on the Board of the major ICs, connectors and headers, respectively.



  Location on the PCB of connectors and major ICs
      
I am communicating with the Raspberry Pi using the serial console terminal via a USB2RS232 Adapter that one can easily get as a variety of adapters available in the Market. However, there was a problem that i had faced initially, since the UART Signals of Rx and Tx,(Pin nos. 10 and 8,exposed on the Top side Expansion Header, as shown in the fig.)of Expansion Header were of TTL 

Expansion Header Pins of Raspberry Pi
nature and hence required a conversion TTL Voltage levels to RS232.  


Upon, this end i tapped in the UART RxD and TxD to a TTL to RS232 converter circuit board, that i had assembled upon.
Now, this RS232 Output of the board could be fed via a USB2RS232 Adapter to my Laptop and i could communicate with a serial console terminal program like Putty, TeraTerm or Minicom, which i am using on my Ubuntu machine. And, Viola it worked !








Here, are below snaps of how i communicate with my Raspberry Pi, via Serial Console Terminal,or via the board shell on my Laptop.




The exposed Header signals of UART RxD, TxD,
+5V and Gnd fed to TTL to RS232 Converter Circuit which
is given to the RS232toUSB Adapter and the USB Adapter
plugged in to my Laptop.









So, this is all about the initial setup. To, begin with, the purpose of this small project is to show how a Simple Character Device Driver can be cross compiled for Raspberry Pi and inserted in the form of a Kernel Module with the installed Kernel. An Interrupt will also be requested and registered with the Kernel at the time of installing the Device Driver as a Kernel Module. Now, firstly, we would need a Kernel source, which must have the same version as that of the Kernel of my currently running Raspberry Pi.


Cross-Compiling the Linux Kernel


The Kernel version can be found by executing the command, uname -a. Now, the version of the Kernel shown as the output of the command #uname -a must match with the version of the Kernel source that we are trying to cross compile to and insert our module with. By looking into the 'Makefile' inside the Kernel Source directory, we can get that info, as shown below:

Kernel Makefile through which we can know kernel version, here for me the RPi was having a version 3.6.11, and the same can be found here.
So, we have our Kernel Source against which the module can be built against,as we will see shortly. Now, firstly, the Kernel has to be cross-compiled, since i am trying to compile it on my x86 machine, with Ubuntu installed, and the target is ARM Based, Raspberry Pi. 

  • Get the tarball,fromGIT Hub, https://github.com/raspberrypi/linux/tree/rpi-3.6.y, and download it in the form of ZIP.
  • Get the tools tarball.
  • Extract the Toolchain and the Linux Source, #tar -xvzf rpi-3.6.y.tar.gz and #tar -xvzf master.tar.gz (tools). These tools are used as the cross compile toolchain.
  • Set the environment variable CCPREFIX:                       export CCPREFIX= /home/rajiv/RPI/tools-master/arm-bcm2708-linux-gnueabi/bin/arm-bcm2708-linux-gnueabi-
  • Set the environment variable KERNEL_SRC:                     export KERNEL_SRC= /home/rajiv/RPI/linux-rpi-3.6.y

GIT Hub from which Rpi Kernel versions can be downloaded from.

Exporting the Kernel and Cross Compile Toolchain variable

After this, now we begin the Kernel cross-compilation.


  • First, clean the source:                                 #make ARCH=arm CROSS_COMPILE=${CCPREFIX} distclean
  • Then set the correct config file for the Board, there are lots of config file that one can find inside '/arch/arm/configs/' folder.                             #make ARCH=arm CROSS_COMPILE=${CCPREFIX} RPI_defconfig
  • Otherwise, place the following config file that i have attached in the form of .txt here(https://drive.google.com/file/d/0B6Cu_2GN5atpMzUxOXJCSmlzOEk/edit?usp=sharing), copy and place it inside '/arch/arm/configs' directory of the Linux Kernel Source.
  • After, this start the compilation                        
  • #make ARCH=arm CROSS_COMPILE=${CCPREFIX} 
  • #make ARCH=arm CROSS_COMPILE=${CCPREFIX} modules_install
  • #make ARCH=arm CROSS_COMPILE=${CCPREFIX} install
  • Finally, the kernel image will be built in the following path, 'arch/arm/boot/' as 'zimage', rename it as 'kernel.img'.
  • Finally, built Linux Kernel Image, cross-compiled for ARM
After, this we begin writing the Character Device Driver, which can be built as a module to the above cross compiled RPI Kernel. One, of the first things is to assign a MAJOR and MINOR number to our Character Device Driver. One of the basic features of the Linux kernel is that it abstracts the handling of devices. All hardware devices look like regular files in Linux. 
Char devices are accessed through names in the filesystem. Those names are called special files or device files or simply nodes of the filesystem tree; they are conventionally located in the /dev directory. Special files for char drivers are identified by a "c" in the first column of the output of ls -l. Block devices appear in /dev as well, but they are identified by a "b.

If you issue the ls -l command, you'll see two numbers (separated by a comma) in the device file entries before the date of the last modification, where the file length normally appears. These numbers are the major and minor device number for the particular device. The following listing shows a few devices as they appear on a typical system. Their major numbers are 1, 4, 7, and 10, while the minors are 1, 3, 5, 64, 65, and 129.

 crw-rw-rw-    1 root     root       1,   3 Apr 11  2013 null
 crw-------    1 root     root      10,   1 Apr 11  2013 psaux
 crw-------    1 root     root       4,   1 Oct 28 03:04 tty1
 crw-rw-rw-    1 root     tty        4,  64 Apr 11  2013 ttys0
 crw-rw----    1 root     uucp       4,  65 Apr 11  2013 ttyS1
 crw--w----    1 vcsa     tty        7,   1 Apr 11  2013 vcs1
 crw--w----    1 vcsa     tty        7, 129 Apr 11  2013 vcsa1
 crw-rw-rw-    1 root     root       1,   5 Apr 11  2013 zero

A typical /proc/devices file looks like the following:
#cat /proc/devices
Character devices:
 1 mem
 2 pty
 3 ttyp
 4 ttyS
 6 lp
 7 vcs
 10 misc
 13 input
 14 sound
 21 sg
180 usb

Block devices:
 2 fd
 8 sd
 11 sr
 65 sd
 66 sd

We will use the following numbers as MAJOR and MINOR number for my character device driver:
#define MY_MAJOR 89
#define MY_MINOR 0

Next, we define the module author and module description for the same:
MODULE_LICENSE("GPL");
MODULE_AUTHOR("RBiswasx");
MODULE_DESCRIPTION("A Simple Character Device Driver module");

Next, we need to define the fops, file_operations structure. It defines what operations (e.g. 'open','read','write' in our example) are available for that kernel driver. The file_operations structure is defined in 'linux/fs.h', and holds pointers to functions defined by the driver that perform various operations on the device. Each, field of the structure corresponds to the address of some function defined by the driver to handle a requested operation.
struct file_operations my_fops = {
        read    :       my_read,
        write   :       my_write,
        open    :       my_open,
        release :       my_close,
owner   :       THIS_MODULE
        };
A pointer to a struct file_operations is commonly named 'fops'. Here, i have named it as 'my_fops'.

Followed,by the declaration of the function prototypes:
static int my_open(struct inode *, struct file *);
static ssize_t my_read(struct file *, char *, size_t, loff_t *);
static ssize_t my_write(struct file *, const char *, size_t, loff_t *);
static int my_close(struct inode *, struct file *);

As, discussed earlier, char devices are accessed through device files, usually located in '/dev'. The MAJOR number tells you which driver handles which device file. The MINOR number is used only by the driver itself to distinguish which device it's operating on, just in case the driver handles more than one device.


Initializing the Module: init_module()


Adding a driver to your system means registering it with the kernel. You can do this by using 'register_chrdev' function, defined in 'linux/fs.h'.

int register_chrdev (unsigned int major, const char* name, struct file_operations* fops);

In my code, i have used the following way to register my character device driver, using 'register_chrdev_region',it is something you should do to avoid conflict with other device drivers which may have played by the rules and been allocated the numbers you're trying to use.

        dev_t devno;
        devno = MKDEV(MY_MAJOR, MY_MINOR);
        register_chrdev_region(devno, 1, "mydevice");
        cdev_init(&my_cdev, &my_fops);

The variable 'my_cdev' is of type 'struct cdev'. 'struct cdev' is one of the elements of the inode structure. As you probably may know already, an inode structure is used by the kernel internally to represent files. The 'struct cdev' is the kernel's internal structure that represents char devices. So this field is a pointer to that structure while the inode refers to the char device file. Therefore if the kernel has to invoke the device it has to register a structure of this type.Hence, globally i declared it as:
        struct cdev my_cdev;


'init_module' of my character device driver

After, registering the character device driver using the above kernel functions,in the 'init_module()', i am trying to map the GPIO Base Address of Raspberry Pi using 'ioremap' function call. The purpose of doing this is, since GPIO Registers are Memory-Mapped in ARM Platform,and not I/O Mapped like in x86,hence to read, or use the GPIO Pins(as INPUT or OUTPUT) exposed on the Expansion Header of Raspberry Pi Board, we have to map these physical addresses using 'ioremap'. This 'GPIO_BASE', address i have defined in the header file 'RPI.h'.

#define BCM2708_PERI_BASE       0x20000000
#define GPIO_BASE              (BCM2708_PERI_BASE + 0x200000) // GPIO controller 


These addresses,i have found from the BroadCom BCM2835, the SOC present in Raspberry Pi, manual. BCM2835-ARM-Peripherals.pdf
In page no. 6 of the following manual, it says,

"Physical addresses range from 0x20000000 to 0x20FFFFFF for peripherals. The bus addresses for peripherals are set up to map onto the peripheral bus address range starting at 0x7E000000. Thus a peripheral advertised here at bus address 0x7Ennnnnn is available at physical address 0x20nnnnnn."


Physical Address range in BCM2835 Manual

So the BCM2708_PERI_BASE macro contains the physical address value at which the peripheral registers start. This is the address we will need to use in our program. The virtual address value is 0x7E000000, and it is these virtual addresses that will be found in the datasheet.

Now, in the same manual at page 90,the virtual address for GPIO Base is 0x7E200000. This means that the offset to the physical address will be 0x200000 which explains the GPIO_BASE.


GPIO Virtual Base Address of 0x7E200000 mapped to Physical Address 0x200000
Next,we define a struct of the type bcm2835_peripheral, which will contain the information about the location of the registers in our header file 'RPI.h'.

// IO Access
struct bcm2835_peripheral {
    unsigned long addr_p;
    int mem_fd;
    void *map;
    volatile unsigned int *addr;
};

struct bcm2835_peripheral gpio = {GPIO_BASE};

This struct variable, gpio is being used in 'init_module()'
gpio.map     = ioremap(GPIO_BASE, 4096);
gpio.addr    = (volatile unsigned int *)gpio.map;

Now,we will be able to use the memory-mapped 'gpio.addr' register to configure the GPIO Pins as Input or Output. For this i have used few macros in 'RPI.h' header file as follows:


// GPIO setup macros. Always use INP_GPIO(x) before using OUT_GPIO(x)
#define INP_GPIO(g) *(gpio.addr + ((g)/10)) &= ~(7<<(((g)%10)*3))
#define OUT_GPIO(g) *(gpio.addr + ((g)/10)) |=  (1<<(((g)%10)*3))
#define SET_GPIO_ALT(g,a) *(gpio.addr + (((g)/10))) |= (((a)<=3?(a) + 4:(a)==4?3:2)<<(((g)%10)*3)) 
#define GPIO_SET *(gpio.addr + 7) // sets bits which are 1 ignores bits which are 0
#define GPIO_CLR *(gpio.addr + 10)// clears bits which are 1 ignores bits which are 0 
#define GPIO_READ(g) *(gpio.addr + 13) &= (1<<(g))

On referring to BCM2835 Manual,page 90,we see that there are 5 register banks of 32-bit each, with each register bank mapped mapped to 10 GPIO Pins.


The 5 register banks of GPIO, with each bank mapped to 10 GPIO Pins
Hence, that accounts for using '*(gpio.addr + ((g)/10))' in the macros, since 'g/10'=1, accounts for a jump of 4-byte, to account for the 32-bit alignment of the registers. 
Now, consider the macro:

#define INP_GPIO(g) *(gpio.addr + ((g)/10)) &= ~(7<<(((g)%10)*3))
GPIO Function Select Register, Register Bank 0
There are three GPFSEL bits per pin (000: input, 001: output). The location of these three bits inside the GPFSEL register is given by ((g)%10)*3 (three times the remainder, remember the modulo % operator).
So to set pin "g" as an input, we need to set these bits to "000". This is done with a AND-operation between the GPSEL register and a string of which everything except those bits are 1. This string is created by bit shifting 7, which is binary 111, over ((g)%10)*3 places, and taking the inverse of that.

Hence, that was the explanation about how we are going to set any GPIO as INPUT/OUTPUT and READ/WRITE to it. In the 'init_module()', i have used Pin 4 and 24 as OUTPUT.


INP_GPIO(4); // Green LED
OUT_GPIO(4);
printk("*****GPIO 4 as GREEN Light ******************\n");
INP_GPIO(24);
   OUT_GPIO(24);

Next, in the 'init_module()',we do the most interesting part which is to request an interrupt from the Kernel and associate it with any GPIO Pin. This is done in the following function call:
r_int_config();

In,the following function,'r_int_config()'we are firstly requesting GPIO which if successful will allow us to use the GPIO Pin, if it is available and not being currently used for some other purpose. 


   if (gpio_request(GPIO_ANY_GPIO, GPIO_ANY_GPIO_DESC)) {
      printk("GPIO request failure: %s\n", GPIO_ANY_GPIO_DESC);
      return;
   }

where,GPIO_ANY_GPIO is defined as 17th GPIO Pin exposed on Expansion Header of Raspberry Pi, and GPIO_ANY_GPIO_DESC is defined as "MyInterrupt".

Next,we map this GPIO to any Interrupt using 'gpio_to_irq' function which will map this GPIO Pin to an interrupt number,as decided by the Kernel, based on the availability of the interrupt.


   if ( (irq_any_gpio = gpio_to_irq(GPIO_ANY_GPIO)) < 0 ) {
      printk("GPIO to IRQ mapping failure %s\n",   
      GPIO_ANY_GPIO_DESC);
      return;
   }

So,now we have an irq number, which is associated with the GPIO. The definition of this function, 'gpio_to_irq' can be found inside 'linux-rpi-3.6.y/arch/arm/mach-bcm2708/include/mach', since BCM2708 is the family to which the SOC of Raspberry Pi, BCM2835 belongs to:
'gpio_to_irq' function defined inside 'gpio.h' file
The function 'gpio_to_irq',returns 'GPIO_IRQ_START+gpio', now for BCM2708 platform,the value of 'GPIO_IRQ_START' is defined in 'irqs.h' file as:

#define GPIO_IRQ_START   (HARD_IRQS + FIQ_IRQS)

The following snapshot illustrates the relationship more appropriately:
After doing 'grep' command, we can figure out the value of 'GPIO_IRQ_START'
After,we map this GPIO to any Interrupt using 'gpio_to_irq' function,we request for an irq,using 'request_irq' function, as shown below:


   if (request_irq(irq_any_gpio,
                   (irq_handler_t ) r_irq_handler,
                   IRQF_TRIGGER_HIGH,
                   GPIO_ANY_GPIO_DESC,
                   GPIO_ANY_GPIO_DEVICE_DESC)) {
      printk("Irq Request failure\n");
      return;
   }

Here,'irq_any_gpio'is the interrupt number,'r_irq_handler'is the interrupt handler function pointer,'IRQF_TRIGGER_HIGH'is the kind of Interrupt Signal we are assigning this interrupt to,which in this case is a HIGH Level signal,as the Interrupt Signal from the Interrupt source.'GPIO_ANY_GPIO_DESC' is the interrupt name which i have given as "MyInterrupt", and 'GPIO_ANY_GPIO_DEVICE_DESC'is the device with which the Interrupt is associated with, which in this case is associated with my character device driver,"mydevice".


So, this was all about initializing the character device driver, in 'init_module'.

#insmod Hello.ko
#mknod /dev/mydevice c 89 0


Removing the Module: cleanup_module()


Next,we look into the 'cleanup_module'.Not much is done here,except unmapping the GPIO_BASE address space that we had mapped using 'ioremap' in 'init_module'.


if (gpio.addr){
        /* release the mapping */
        iounmap(gpio.addr);

}

Also,we unregister the character device driver,with major and minor numbers,MY_MAJOR and MY_MINOR.

dev_t devno;
devno = MKDEV(MY_MAJOR, MY_MINOR);
unregister_chrdev_region(devno, 1);

cdev_del(&my_cdev);

And,finally we release the interrupt also

r_int_release();

In the function, 'r_int_release()'we free the irq


   free_irq(irq_any_gpio, GPIO_ANY_GPIO_DEVICE_DESC);
   gpio_free(GPIO_ANY_GPIO);


Opening,Writing,Reading and Closing the Module: my_open(),my_write(),my_read() and my_close().


Next,we see the fops functions one by one,to begin with 'my_open(struct inode *inod, struct file *fil)'.Here, nothing much is done,except reading the Major and Minor Number of the character driver.Further,code can be added to increase the count of the Module,if more than one user space program is trying to access it.

int major;
                int minor;
                major = imajor(inod);
                minor = iminor(inod);

                printk("\n*****Some body is opening me at major %d  minor %d*****\n",major, minor);

Next,we see the fops function, 'my_write(struct file *filp, const char *buff, size_t len, loff_t *off)'.Here,we try and read a String from the user space,using 'copy_from_user'.


memset(msg, 0, 100);
count =copy_from_user(msg, buff, len);

The 'buff' is the character pointer to the string from the user space and 'len' is the string length,and this string is copied to the kernel space array,'msg'.


Next,we see the fops function, 'my_read(struct file *filp, char *buff, size_t len, loff_t *off)'.Here,we put the string that we had copied in 'msg' buffer,back to the user space using 'copy_to_user'.

                major = MAJOR(filp->f_dentry->d_inode->i_rdev);
count = copy_to_user(buff, msg, len);
                printk("*****Some body is reading me at major %d*****\n",major);


Next,we see the fops function, 'my_close(struct inode *inod, struct file *fil)'.Here, nothing much is done,except reading the Major and Minor Number of the character driver.Further,code can be added to decrease the count of the Module,if more than one user space program is trying to access it.


                int major;
                major = MAJOR(fil->f_dentry->d_inode->i_rdev);
                printk("*****Some body is closing me at major %d*****\n",major);


Inside the Interrupt Handler: r_irq_handler()


Now,lets look in the interrupt handler routine,'irqreturn_t r_irq_handler(int irq, void *dev_id, struct pt_regs *regs)'.Firstly,we limit the interrupt signal,which if say,was a kind of sudden spike or impulse in the signal,then the signal should be ignored and not be considered an interrupt.


   unsigned int interrupt_time = millis();
   if (interrupt_time - last_interrupt_time < 1000) 
   {
     printk(KERN_NOTICE "Ignored Interrupt!!!!! [%d]%s \n", irq, (char *) dev_id);
     return IRQ_HANDLED;
   }
   last_interrupt_time = interrupt_time;

After,this we save the status of the local interrupts(remember them in flag'flags').

   unsigned long flags;
   local_irq_save(flags);

After,this we check the status of the GPIO Pin 23,which we have set as INPUT,which gets a Timer signal from my interrupt source,after every 13 seconds,based on which GPIO Pin 4 will be SET or CLEARed.


   if (((val=GPIO_READ(23)) & 0xffffffff))
   printk(KERN_NOTICE "Interrupt [%d] for device %s was triggered !.\n", irq, (char *) dev_id);
   val =0;
   if (((val=GPIO_READ(23)) & 0xffffffff))
          GPIO_SET = 1 << 4;
   else
          GPIO_CLR = 1 << 4;

This GPIO Pin 4 is controlling the Green LED on the Bread Board.


Timer Signal coming from Interrupt source,left to GPIO Pin 23,based on which GPIO Pin 4 is SET in the Interrupt Handler.

Finally,in the interrupt handler,we restore the local interrupts and return.

   // restore hard interrupts
   local_irq_restore(flags);
   return IRQ_HANDLED;


Deferring work in the Interrupt Handler, using TASKLETS: r_irq_handler()


Interrupt Handlers run asynchronously and thus interrupt other potentially important code,including other interrupt handlers.Therefore,to avoid stalling the interrupted code for too long,interrupt handlers need to run as quickly as possible.

Since, Interrupt Handlers do not run in process context,therefore they cannot block.Due,to these requirements,we need a way to defer the more critical part of Interrupt Handler using a mechanism called Bottom Half Processing.


Top Half runs in Interrupt Context,while Bottom Half runs in Kernel Context

Tasklets are a bottom-half mechanism built on top of softirqs.As, already mentioned,they have nothing to do with tasks. Tasklets are similar in nature and work in a similar manner to softirqs; however,they have a simpler interface and relaxed locking rules.

Softirqs are required only for very high-frequency and highly threaded use.Because Tasklets are implemented on top of Softirqs, they are Softirqs. Unlike,Softirqs,two of the same Tasklets can never run concurrently on a Multi-Processor core system,although two different Tasklets can run at the same time on two different Processors,say one on ARM and another on X86 core. This is one major difference between Tasklets and SoftIRQs.

To implement TASKLETS,we will use the Kernel function,DECLARE_TASKLET which creates a TASKLET statically,we shall name our tasklet as 'mytasklet':

DECLARE_TASKLET(mytasklet, my_tasklet_handler, 0); 

where, 'my_tasklet_handler' is the function pointer to the Tasklet Handler.The function prototype of Tasklet Handler is declared in the beginning.

static int my_open(struct inode *, struct file *);
static ssize_t my_read(struct file *, char *, size_t, loff_t *);
static ssize_t my_write(struct file *, const char *, size_t, loff_t *);
static int my_close(struct inode *, struct file *);

static void my_tasklet_handler(unsigned long );

Now,in the Interrupt Handler,we just have to schedule the TASKLET to be run by the Kernel at some predefined time,using tasklet_schedule(&mytasklet)
Here,is the Interrupt Handler code:

static irqreturn_t r_irq_handler(int irq, void *dev_id, struct pt_regs *regs) 
{  
   unsigned int interrupt_time = millis();
   if (interrupt_time - last_interrupt_time < 1000) 
        return IRQ_HANDLED;
   last_interrupt_time = interrupt_time;
   local_irq_save(flags);
   tasklet_schedule(&mytasklet);
   local_irq_restore(flags);

   return IRQ_HANDLED;
}

Inside the Tasklet Handler we do the main processing as was done earlier inside the Interrupt Handler.One most important thing to remember here is that,before we begin to do anything inside the Tasklet Handler,we first disable the Tasklet,using tasklet_disable(&mytasklet),which will ensure that while the Tasklet is in RUN State,it doesnt gets scheduled again.And,after the processing we enable the Tasklet before we return from the Tasklet Handler,using tasklet_enable(&mytasklet).Here,is how we do it:

DECLARE_TASKLET(mytasklet, my_tasklet_handler, 0);

static void my_tasklet_handler(unsigned long flag)
{
   tasklet_disable(&mytasklet);   
   if (((val=GPIO_READ(23)) & 0xffffffff))
   printk(KERN_NOTICE "Interrupt for device was triggered !.\n");
   val =0;
   if (((val=GPIO_READ(23)) & 0xffffffff))
          GPIO_SET = 1 << 4;
   else
          GPIO_CLR = 1 << 4;
   tasklet_enable(&mytasklet);   

}

I have uploaded the Module Code covering new implementation using TASKLETS in the Resources section of the Blog.One can download the same and verify it.

The Makefile: Makefile


The Makefile for the module is pretty simple,but important without which you won't be able to generate the '.ko',kernel object for the Module.

#make 

The Makefile looks as follows:


obj-m := Hello.o
all:
@$(MAKE) ARCH=arm CROSS_COMPILE=${CCPREFIX} -C /home/rajiv/RPI/linux-rpi-3.6.y M=$(PWD) modules
clean:

make ARCH=arm CROSS_COMPILE=${CCPREFIX} -C /home/rajiv/RPI/linux-rpi-3.6.y M=$(PWD) clean


In the User Space Program: Test.c



The User Space program to use this Character Device Driver is a simple Program which takes as an Input a User String and using 'write' system call passes this string to the Kernel Space.Using 'read' system call this string is read back again from the Kernel Space. 


User Space Program to access the Driver.
The,User Space Program is compiled on the Raspberry Pi,using 'gcc'.

#gcc -o Test Test.c

To,execute it:

./Test String_name


Resources: 


The entire Device Driver Project can be accessed and downloaded from here:

'Hello.c' Module Program,Bottom Half implementation in Interrupt Handler using TASKLETS:
https://drive.google.com/file/d/0B6Cu_2GN5atpcnNUTTVaVDNWVXM/edit?usp=sharing

'Hello.c' Module Program:
https://drive.google.com/file/d/0B6Cu_2GN5atpSWdEcU9nRE4wTzg/edit?usp=sharing

MakeFile:
https://drive.google.com/file/d/0B6Cu_2GN5atpZUdBcDVCaHlQV1U/edit?usp=sharing

Header File,'RPI.h':
https://drive.google.com/file/d/0B6Cu_2GN5atpakRkSXZZNFZJVzg/edit?usp=sharing

NOTE: I am editing this Section here... 


The Entire Kernel Sources that i had used is shared publicly and uploaded in the Google Drive. Below is the Link:

https://drive.google.com/file/d/0B2QVXoZzx8J2U2F0MGNjbUh4dmc/view?usp=sharing

Also the Cross-Compile Toolchain for building the Kernel Sources, that I had used can be downloaded from the below link:


https://github.com/raspberrypi/tools/archive/master.tar.gz


Furthermore,i have made and posted the Video of my Project on U-Tube,the link to which is as follows:

http://www.youtube.com/watch?v=TvDHnrfgr08


41 comments:

  1. I am now trying to Defer work done in the Interrupt routine by having a Top Half and Bottom Half processing in the Interrupts, using Tasklets. However, in my Tasklet handler i am getting a crash: a Prefetch abort due to bad mode. Anyone with any ideas ?

    ReplyDelete
  2. Hi
    I wanted to make PI shutdown harware using interrupt. Could you please help me on this. I am ready to pay for the same for the consultation.
    It will be great if you could help me . please call me @08971965432 we can discuss further.

    ReplyDelete
  3. Ok...So, say a GPIO Pin will receive an interrupt signal based on which Raspberry Pi should shut down..Is tht your requirement..? Catching the interrupt signal on any GPIO through my Device driver is easy and already implemented in the following project along with registering and catching interrupt signals to their Interrupt Handler, now the second part, taking the action of shutting down Rpi inside the Interrupt handler, i may have look into the TRM Manual of BCM2835....

    ReplyDelete
  4. I am able to defer work using TASKLETS for Bottom Half Processing in the Interrupt Handler..The problem was too many interrupts were coming due to which a lot of tasklets were getting scheduled, hence inside the tasklet handler, first do, tasklet_disable(&mytasklet) and then do the processing and after doing the processing, do tasklet_enable(&mytasklet)....this resolved the Prefetch abort crash tht i was encountering...i will shortly update the blog to include TASKLETS....

    ReplyDelete
  5. Dear Tijo,

    I have implemented a shutdown mechanism using signals. In the user space application program, i will get the Process Id/Task ID of my running application using getpid() system call,

    /* setup the signal handler for SIG_TEST
    * SA_SIGINFO -> we want the signal handler function with 3 arguments
    */
    struct sigaction sig;
    sig.sa_sigaction = receiveData;
    sig.sa_flags = SA_SIGINFO;

    sigaction(SIG_TEST, &sig, NULL);
    memset(buf, 0, 100);
    fp =open("/dev/mydevice", O_RDWR);

    sprintf(buf, "%i", getpid());
    if (write(fp, buf, strlen(buf) + 1) < 0) {
    perror("fwrite");
    return -1;
    }

    and this Process ID/Task ID will be written to the Device Driver in the write routine, using copyfromuser().

    In the Device Driver Module, in Kernel Space, based on this Process ID/Task ID, i will try and get the Task/Process Structure in the Interrupt Handler Bottom Half Handler/Tasklet Handler using find_get_pid() and pid_task(), Kernel APIs. Using the Task/Process Structure of the Application process running in the User Space, i will send Signal to it.

    rcu_read_lock();
    pid_struct = find_get_pid(pid);
    t = pid_task(pid_struct,PIDTYPE_PID);
    if(t == NULL){
    printk("no such pid\n");
    rcu_read_unlock();
    }else{
    rcu_read_unlock();
    if(send_sig_info(SIG_TEST, &info, t) < 0)
    printk("error sending signal\n");
    }

    In the User Space, in our Application Program the Signal Handler will catch the signal and perform the shutdown using execve() system call:

    void receiveData(int n, siginfo_t *info, void *unused)
    {
    //flag =1;
    char *argv[] = { "/bin/sh", "-c", "shutdown -P -a -h now", 0 };
    char *envp[] =
    {
    "HOME=/",
    "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
    0
    };
    //printf("Received Signal from Kernel Module with value::%i\n", info->si_int);
    execve(argv[0], &argv[0], envp);
    }

    Here's the User Space Application Program:

    https://drive.google.com/file/d/0B6Cu_2GN5atpd1VPMjBmajR6RWM/edit?usp=sharing

    And, the Kernel Device Driver, do check the Tasklet Handler code:

    https://drive.google.com/file/d/0B6Cu_2GN5atpd1VPMjBmajR6RWM/edit?usp=sharing

    Thanks,
    Rajiv.

    ReplyDelete
  6. Hi Rajiv,
    teh 2nd link seems to be wrong (it also points to Test.c), could you provide the proper link to your kernel device driver!?
    Thx!
    cheers
    Joeri

    ReplyDelete
    Replies
    1. Hi Joeri,

      Here is the link to my Kernel Device Driver Module:

      https://docs.google.com/file/d/0B6Cu_2GN5atpSWdEcU9nRE4wTzg/edit

      Delete
  7. Here is the Link to the Kernel Device driver, in which i have deferred work in the Interrupt Handler using Bottom Half mechanism of Tasklets:

    https://docs.google.com/file/d/0B6Cu_2GN5atpcnNUTTVaVDNWVXM/edit

    ReplyDelete
  8. If the above links are not working, then please mail me at: youther.biswas8@gmail.com

    I will send you the files via Mail.

    Thanks,
    Rajiv.

    ReplyDelete
  9. Hi

    Do we need to copy the new image to the raspberry pi inorder to run the modules that we develop on x86 m/c

    regards
    Sobin

    ReplyDelete
    Replies
    1. Yes, the Module.ko is cross compiled, using a cross compiler toolchain, and it needs to be transferred to uhr Raspberry Pi...copy the .ko, to uhr home folder of your Root Partition of the SD Card...Bootup the Rpi, and go to the home directory, and through command line, do insmod module.ko...

      Delete
  10. This comment has been removed by the author.

    ReplyDelete
  11. Hi Rajiv,

    Because too many quality blog entries on development are lost in the vastness of the web, CodeProject is bringing together the best to give them the exposure they deserve. CodeProject has over 10 million members browsing over 40,000 articles, each article having hundreds of views.

    I've been looking at your blog and there are some phenomenal entries (like this one http://blogsmayan.blogspot.ca/p/programming-interrupts-in-raspberry-pi.html) that we'd love to have on CodeProject.

    I'd be happy to give you the details (you can also see our FAQ here http://www.codeproject.com/Articles/66029/Code-Project-Technical-Blog-FAQ), or answer any questions you might have.

    ReplyDelete
    Replies
    1. Hi Sean,

      Sorry, for a little late reply. Sure will follow this up on Code Project.
      My Blog here is too much in detail covering every aspect of Cross Compiling a Kernel,
      followed by developing a Device Driver and later registering Interrupts to it, Controlling the
      GPIO, understanding the BroadCOM TRM Manual and all.

      It took a lot of time to compile so much of information, around a month, this year, January, 2014.
      It may some time to compile it in Code Project, in total.

      Regards,
      Rajiv.

      Delete
  12. Hi Rajiv,
    great stuff. I'll surely try this with my RPi sometime. I was curious - where did you learn about low-level ARM stuff?

    ReplyDelete
    Replies
    1. Have been working on these things for quite some time now...Wanted to share using some Hardware, and system programming on Linux. Surely, trying and self-initiative to build small electronics workshop at Home, having as many components, modules, Test equipments and open source beautiful H/W Platforms like Rpi, Beagle Bone,Beagle BoardxM, et al with their wide Open support via Communities, forums, Data sheets, Technical Reference Manuals have helped me a lot to Experiment...All goes in the spirit of Experimenting...

      Delete
  13. Hey very nicely explained and demonstrated I have gone through your blog. Its lovely work. I am following your blog and I am trying to develope simple GPIO DD for my Raspberry Pi. I have understood the program but I have never tried building DD using cross compiling methode. I did it on Lunux machine and I am beginner. Please guide me to work this project on my RPi. So in cross compilation method I should download the source code of the same kernel which is running on Target board (in my case target board is Raspberry Pi B+, kernel is raspbian weezy 3.18). I have Image of kernel which I dumped on SD card and that Image is installed on RPi. Now I should find the same Kernel source (3.18) and should install in Home directory of my host computer. And then I should buid module for GPIO and insert it in the Host computer RPi Kernel to test the working, right? Then how to put this module in target Kernel? Kindly guide

    ReplyDelete
    Replies
    1. Good Morning and Thank you. If you look at the Resources Section and towards the Makefile section, u will see, i have given the Makefile of the DD. Now, that after, you have downloaded the source of the Kernel, having same Major and Minor number as the Kernel Image running in the back of the RaspberryPi, using "uname -a", as mentioned in the Blog or opening the Makefile of the Kernel source root directory, as mentioned in "Cross-Compiling the Linux Kernel" section of the Blog. Download and extract the Cross-Compile Toolchain, that i have mentioned. First, Build and cross-compile the Kernel Source. This is necessary, since the Device Driver, ".ko" kernel object, will be linked against the Built Kernel objects.

      Thts why i have begun the blog by first, building and cross-compiling the Kernel. The blog has been written in this way, keeping in mind what would be required before you jump to the next step. After this, use the 'Hello.c", beginning Driver program from the "Resources" section, go through it and the Makefile that i have given to cross compile this dummy driver, to begin with.Once it is successfully built, copy the generated "Hello.ko", "Hello.mod.c", "Hello.mod.o", "Module", "modules.order" files, if the Module is built successfully on the host machine, and copy it to your SD Card, running RaspberryPi on Debian. Create some folder inside "/home/pi", like "MyDriver", and copy the above files, from your Host Machine using a Card Reader. Now, after copying them, take out the SD Card and Startup the Raspberry Pi. After this, go to the folder inside "/home/pi", where you have copied the Module.ko and other files. Now in this section, "Initializing the Module: init_module()", towards the end, i have explained the mknod and insmod command to create a Device Node in /dev directory, and to insert and register the Driver with the Kernel.

      Thanks,
      Rajiv.

      Delete
    2. Ohhh Awesome :) Now I got it. Copying driver files to Kernel on SD. And then normal procedure to insert the module and enable permission in device tree by mknod.
      Thank you man.. you explained so nicely. Many Thanks :)))))))

      Delete
    3. But is it not possible to compile and make module on the Target Kernel directly. I mean we can access kernel on Raspberry Pi in Putty by SSH then can't we develop and Make module on target kernel directly? Then there will be no need of cross compilation.Kindly Guide.
      Thank You

      Delete
    4. This comment has been removed by the author.

      Delete
  14. Dear rajiv
    I am getting following error
    CHK include/config/kernel.release
    CHK include/generated/uapi/linux/version.h
    CHK include/generated/utsrelease.h
    make[1]: `include/generated/mach-types.h' is up to date.
    CC kernel/bounds.s
    gcc: error: unrecognized argument in option ‘-mabi=apcs-gnu’
    gcc: note: valid arguments to ‘-mabi=’ are: ms sysv
    gcc: error: unrecognized command line option ‘-mlittle-endian’
    gcc: error: unrecognized command line option ‘-mapcs’
    gcc: error: unrecognized command line option ‘-mno-sched-prolog’
    make[1]: *** [kernel/bounds.s] Error 1
    make: *** [prepare0] Error 2

    For "make ARCH=arm CROSS_COMPILE=${CCPREFIX}" command
    I loaded cofig file from target kernel /proc folder using scp command. I also did environment setups in .bashrc as follow
    export CCPREFIX= /home/ganesh/RPI/tools-master/arm-bcm2708-linux-gnueabi/bin/arm-bcm2708-linux-gnueabi-
    export KERNEL_SRC= /home/ganesh/RPI/linux-rpi-3.18.y

    Please help me to resolve this error.

    ReplyDelete
    Replies
    1. Looks like your Toolchain is not properly configured. Are you trying to Cross Compile the Module, and you are getting these errors? Did you check my Makefile, tht i have shared, :

      obj-m := Hello.o
      all:
      @$(MAKE) ARCH=arm CROSS_COMPILE=${CCPREFIX} -C /home/rajiv/RPI/linux-rpi-3.6.y M=$(PWD) modules
      clean:

      make ARCH=arm CROSS_COMPILE=${CCPREFIX} -C /home/rajiv/RPI/linux-rpi-3.6.y M=$(PWD) clean

      Did you first build and cross-compile the Kernel ? Are you using the config file that i have shared, and just placed that in your, arch/arm/configs directory ? What is value of {PATH} variable, do an echo $PATH and tell me the value, also, do an echo of {CCPREFIX} variable, i think, this error is because, there is another Toolchain present, apart from this Toolchain that you are trying to add for your compilation. Check, if there is another Toolchain that is also being present in the PATH Variable .
      Otherwise, you can follow the points as i have earlier mentioned in my blog:

      First, clean the source: #make ARCH=arm CROSS_COMPILE=${CCPREFIX} distclean
      Then set the correct config file for the Board, there are lots of config file that one can find inside '/arch/arm/configs/' folder. #make ARCH=arm CROSS_COMPILE=${CCPREFIX} RPI_defconfig
      Otherwise, place the following config file that i have attached in the form of .txt here(https://drive.google.com/file/d/0B6Cu_2GN5atpMzUxOXJCSmlzOEk/edit?usp=sharing), copy and place it inside '/arch/arm/configs' directory of the Linux Kernel Source.
      After, this start the compilation
      #make ARCH=arm CROSS_COMPILE=${CCPREFIX}
      #make ARCH=arm CROSS_COMPILE=${CCPREFIX} modules_install
      #make ARCH=arm CROSS_COMPILE=${CCPREFIX} install
      Finally, the kernel image will be built in the following path, 'arch/arm/boot/' as 'zimage', rename it as 'kernel.img'.

      Delete
  15. Hi. This is a wonderful document. I am having problems with getting the file downloaded. Your document says to get the following:

    "Get the tarball,fromGIT Hub, https://github.com/raspberrypi/linux/tree/rpi-3.6.y, and download it in the form of ZIP.
    Get the tools tarball.
    Extract the Toolchain and the Linux Source, #tar -xvzf rpi-3.6.y.tar.gz and #tar -xvzf master.tar.gz (tools). These tools are used as the cross compile toolchain."

    When I do "uname -a" on my Raspberry PI I get "Linux raspberrypi 3.18.11-v7+ #781 SMP PREEMPT Tue Apr 21 18:07:59 BST 2015 armv71 GNU/LINUX"

    So I want to get the Linux-rpi-3.18.y version. I opened my browser to "https://github.com/raspberrypi/linux/tree/rpi-3.18.y" and then tried clicking on the Download as ZIP. If I then copy that over to my Ubuntu machine and try to use the "tar" command I get errors. So then I tried the "unzip" command but it crashes with an error due to a symlink being too long. So I went back to my PC and attempted to unzip it there and then I was going to zip each major directoy - but when I unzipped it not all of the directories were present. So then I decided to use git to clone the repository from github. I selected the "https://github.com/raspberrypi/linux.git" which did download the entire contents, BUT, it was build 4.0.9 - not my build.

    Do you know how I can get the correct files?

    Secondly, what do you mean by "toolchain" ?? I don't see any master.tar.gz in what I downloaded and I don't see it in the zip file either.
    I think there must be some convention to all of this that I do not understand. I just don't see how you were able to get the files. I'm hoping that by some miracle you will see this and let me know. Thanks SO much!

    ReplyDelete
    Replies
    1. Hi Bob,

      In the point mentioned "• Get the Tools Tarball." we just need to click on "Tools", the tarball download link is embedded in the Highlighted Tools keyword.

      On clicking it, it would have redirected you to the .tar zip containing the Compiler tool chain required for BCM2835..

      Thanks,
      Rajiv.

      Delete
  16. Hi rajiv
    I am also getting the same error as shown by Ganesh Kalbhor.Did u find any solution to it?@Ganesh@rajivI am using raspberrypi2 with kernel version 3.18.7-v7+

    ReplyDelete
    Replies
    1. I think the Kernel Sources are different which would introduce errors. The config file that I have used is tightly packed with the Kernel source which is BCM2835 kernel that I had taken from GIT. It was specifically customized with driver level changes and architecture level changes for BCM2835 which is the SOC variant from which RPI 1, Model B's BCM2708 Microprocessor has been derieved...thus would ask you to take a diff of the Kernel sources, my source 3.6 from the GIT link that i have given and your 3.18 ...the architecture and driver level changes have to be taken into account.... Rpi2 architecture is different, as its based on BCM2836..thus the config file might not work because I used Rpi1 for this Project..please check my above reply to Bob for Tool chain..

      Delete
    2. Dear,

      the memory assigned to the peripherals changes (BCM2708_PERI_BASE in RPi.h) in RPi 2 and 3. It starts in 0x3F000000 instead of 0x20000000, i think that if you change this, the code will work properly becouse i had worked with a similar code to do my Raspberry projects and it works properly.

      Delete
  17. Hi Rajiv
    Can i use the same config file as shared by you for kernel version 3.18.7

    ReplyDelete
    Replies
    1. Please go through my above comment...please give a check with 3.6 kernel..if it works, after that go for 3.18 kernel..If it doesn't work..it means Kernel source needs some code changes..or some files.. take a diff of the Kernel Sources..to see what might be the differences and integrate them..it will be difficult to figure it out in one go..Trial and error approach would be required..

      Delete
  18. ok rajiv, i will try,
    thank u for ur support.

    ReplyDelete
  19. Hi Rajeev,

    This is great document, again i am revisiting your blog :)
    I would like to know if you are able to get minicom port on pi using the UART and second thing can we use telnet on pi using Ethernet cable

    ReplyDelete
    Replies
    1. Sorry for the late reply...To answer your queries straightaway, might we ask which RaspberryPi version are we checking with..?...For my Raspberry Pi 1,Model B, Rev1, that i have used in the above project..I am able to use the Expansion Header Pins, P1-08(Tx) and P1-10(Rx), and Gnd Pin,P1-06 for TTL UART...Please check if you have connected the Gnd Pin, the Gnd Pin needs to be shorted between the Interfacing Module and your RaspberryPi Board, so as to make the Gnd the reference common...otherwise the signal levels won't have a reference...secondly, check if your Module Board is using TTL UART Signals or RS-232...If its TTL UART signal level then its fine, since RaspberryPi works at 3.3V, thus Header pins are driven at 3.3 V...but RS232 requires +/- 12V levels, and so a MAX-232 converter module, as i have shown in "The Setup" section of the Blog is required...Also the Baud Rate should be set at 115200, 8N1 and No Software or Hardware flow control...so setup the Minicom using "minicom -s" command...then setup these settings, after connecting the Gnd, Tx, Rx correctly..

      Now, coming to the second part...Telnet requires Telnet Daemon, Telnetd...The Telnet Daemon uses Pseudoterminal devices, called “pty” to provide a Terminal for the shell and other programs to run in, when invoked from the Network via this daemon....Please see if /dev/ptmx exists and that it is a character device file with a Major number of 5 and Minor of 2; if it’s not present, then create it using the command:

      #mknod /dev/ptmx c 5 2

      The support for the devpts pseudofilesystem is enabled in the Kernel by looking for devpts entry in the /proc/filesystems file,or add the support by turning on CONFIG_UNIX98_PTYS in the Linux Kernel Build option.
      UNIX98_PTYS=y
      DEVPTS_FS=y
      CONFIG_UNIX98_PTYS=y

      Check if /dev/pts directory exists and that the devpts pseudofilesystem is mounted on it correctly, either by issuing the mount command and looking for the devpts entry or mounting it using the command:

      #mkdir /dev/pts && mount –t devpts none /dev/pts

      See, if Telnetd daemon Binary is present in /usr/sbin or /usr/bin and properly configure inetd or xinetd. In the file /etc/xinetd.d/telnet, in this file,find the line for disable and change it from the value "yes" to "no". After changing the above value(s), we will need to restart the xinetd deamon. As the root user, type the following command:

      #/etc/init.d/xinetd reload

      Please check if the above things are marked present and configured...Then we may issue telnet to connect to the Host...

      Thanks,
      Rajiv.

      Delete
  20. Hi Rajeev.

    Thanks for kind reply

    I built the kernel, i renamed it as kernel.img. i replaced it with the kernel.img present inside SD Card but when i booted with that i am getting error .. mmc0 init error.

    Any other way to replace the kernel. Am i doing correct step.

    Regarding the TTL Connector. I checked website.
    http://www.ebay.in/itm/151961567428?aff_source=Sok-Goog
    Can i buy this

    ReplyDelete
    Replies
    1. Yes, better....Bit pricey stuff, but good, eliminates RS-232 Port..what I was having was RS232-to-USB Adapter, which i connected to a small MAX-232 Module, i had purchased from a small Mom&Pop Electronic store to convert RS232 Signals to TTL Signals..But this Module does it directly...hence if RS-232 signals are required would need a MAX-232 Module to which on one side give these TTL signals as Input and get the RS-232 Signal at the RS-232 Port...But anyways, RS-232 is quite obsolete today, Sobin...Hardly any boards, nor anything comes with RS-232 port nowadays..Thus RS-232 would never be required, as much like TTL UART...Thus go with this Adapter, small and compact stuff..

      I have uploaded the Kernel sources, for this Problem...Please download the Kernel sources in the form of tar.gz, from the below shared Google Drive:

      https://drive.google.com/file/d/0B2QVXoZzx8J2U2F0MGNjbUh4dmc/view?usp=sharing

      Please confirm if you are able to Download it,and then try cross compiling it on x86 using the Tools, that i have mentioned in the Blog, since Rpi1, Model B, takes a lot of time to build...

      Delete
  21. hi

    I tried with different kernel but i m getting other issues, but want to go with 3.6.y that time it worked , i used the tool to convert the zimage to kernel.img. But still there is issue in boot. btw which kernel are you using.

    ReplyDelete
    Replies
    1. I have uploaded the Kernel sources, for this Problem...Please download the Kernel sources in the form of tar.gz, from the below shared Google Drive:

      https://drive.google.com/file/d/0B2QVXoZzx8J2U2F0MGNjbUh4dmc/view?usp=sharing

      Then after that, extract the sources, using

      #tar xvjf linux-rpi-3.6.y.tar.gz

      And try Cross compiling it using the Tools, Tools can be downloaded by clicking on "tools" in the
      "Get the tools tarball." line in the Section, "Cross-Compiling the Linux Kernel" of the Blog.

      Thanks,
      Rajiv.

      Delete
  22. NOTE:

    Please check the "Resources" Section of the Blog...

    I have uploaded the Kernel Sources for kernel 3.6 in the form of tar.gz. Please reply if people are facing any issues while downloading the Sources and the Cross-Compile Toolchain.

    Thanks,
    Rajiv.

    ReplyDelete
  23. Hi Rajeev ,

    Thank you very much sharing your knowledge on this kind of platforms.

    Information give is really helpful and clear to understand.

    I have real struggles with respective to understanding the data sheet part where in which it talks about following:

    #1: Every gpio has 40 registers[refere: Table 6-1 GPIO Register Assignment] which are common for all gpios can be used to SET/CLEAR/GPFSEL[0-5] etc,but what is the purpose of GPIO function select register which has 10 FSEL (0-9) registers [refer: Table 6-2 – GPIO Alternate function select register 0].

    #2: How can I reach to a given GPIO through memorymapped region? , is something like below
    Case#1 :GPIO4 with alternate function 5 as Input:
    as ( 4 < 10) it will use "0x7E200000" with FSEL4 register[refer: Table 6-2 – GPIO Alternate function select register 0] having 000 and 010 = GPIO Pin 9 takes alternate function 5.

    case#2 :GPIO27 with alternate function 3 as Output:
    as ( 27 < 30) it will use "0x7E20000C" with FSEL3 register[refer: Table 6-2 – GPIO Alternate function select register 0] having 001 and 111 = GPIO Pin 9 takes alternate function 5.

    Please let me know whether my understanding is correct or not?

    #3: what is the purpose of following tables

    Table 6-3 – GPIO Alternate function select register 1
    Table 6-4 – GPIO Alternate function select register 2
    Table 6-5 – GPIO Alternate function select register 3
    Table 6-6 – GPIO Alternate function select register 4
    Table 6-7 – GPIO Alternate function select register 5

    BR,
    Srisowj

    ReplyDelete
    Replies
    1. Hi Sri San,

      I will try to answer your queries with respect to my Understanding..So here we go,

      #1: Every gpio has 40 registers[refere: Table 6-1 GPIO Register Assignment] which are common for all gpios can be used to SET/CLEAR/GPFSEL[0-5] etc,but what is the purpose of GPIO function select register which has 10 FSEL (0-9) registers [refer: Table 6-2 – GPIO Alternate function select register 0].

      The GPIO Controller has 41 Registers. Not each GPIO Pin has 40 Registers. Each of these GPIO Controller Registers are Memory Mapped with Address starting from virtual address 0x7E20 0000 mapped to Physical address of 0x2020 0000. Now, the GPIO Controller controls the 54 GPIOs, using these Registers. To access, this Huge number of 54 GPIOs, out of which most are used internally and only 20 are exposed on the Expansion Header of RaspberryPi, the GPIO Controller has divided them into Sets. So, the first GPFSEL0, will control the GPIO Pins 0 to GPIO Pin 9. The GPFSEL{n} Register is responsible for setting/resetting the Alternate Functions and Input/Outputs. Similarly, GPFSEL1 will control the Alternate/Inp/Out functions of the GPIO Pins 10 to GPIO Pins 19. Similarly, GPFSEL2 will control the set 3, for GPIO Pins 20 to GPIO Pins 29 and so on...

      The Printing Error in the DataSheet is the description given under Table 6-2, where it is written as "Table 6-2 - GPIO Alternate function select register 0"...The word "Alternate" is wrongly printed and should be Omitted...

      Therefore keep the word, Alternate omitted from your mind, while analyzing the Function select table...There are a few printing errors, and Broadcom agrees to these errors.

      Please omit the Word, "Alternate", the Heading below for the Table stands for

      Table 6-3 – GPIO function select register 1
      Table 6-4 – GPIO function select register 2
      Table 6-5 – GPIO function select register 3
      Table 6-6 – GPIO function select register 4
      Table 6-7 – GPIO function select register 5

      I hope i am able to clear this slight confusion created due to misprinting.

      Regards,
      Rajiv.

      Delete
    2. Contd......

      Rest everything is correct, if we omit "Alternate" word which is the cause of confusion, and everything is pretty straightforward from the Data sheet...The Macros to set these registers is given in the Blog and in the Sources...along with their explanation..Please go through the Section of my Blog, "Initializing the Module: init_module()", from Paragraph 3....

      // GPIO setup macros. Always use INP_GPIO(x) before using OUT_GPIO(x)
      #define INP_GPIO(g) *(gpio.addr + ((g)/10)) &= ~(7<<(((g)%10)*3))
      #define OUT_GPIO(g) *(gpio.addr + ((g)/10)) |= (1<<(((g)%10)*3))
      #define SET_GPIO_ALT(g,a) *(gpio.addr + (((g)/10))) |= (((a)<=3?(a) + 4:(a)==4?3:2)<<(((g)%10)*3))
      #define GPIO_SET *(gpio.addr + 7) // sets bits which are 1 ignores bits which are 0
      #define GPIO_CLR *(gpio.addr + 10)// clears bits which are 1 ignores bits which are 0
      #define GPIO_READ(g) *(gpio.addr + 13) &= (1<<(g))

      Thanks,
      Rajiv.

      Delete