Pages

Saturday, May 31, 2014

Using Rapsberry Pi's GPIO as an interrupt source II

In my previous post I discussed about the experiment I am going to make to use one of the GPIO pins as an interrupt source for my device driver. The code for this driver is listed below:


#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/delay.h>
 
#include <linux/interrupt.h>
#include <linux/gpio.h>
 
 
// The GPIO_23 is usually not used (pin 11 on P5 pinout raspberry pi rev. 2 
// board), we will use this to generate interrupt (rising edge). Refer  to
// the raspberry pi spec sheets.
#define GPIO_INT_GPIO23                23
 
// We need to define what string will be displayed in /proc/interrupt. 
// You may want to display the number of times our interrupt is called
// via cat /proc/interrupt
#define GPIO_INT_GPIO23_DESC           "GPIO_23 Interrupt switch"
 
// String below is not needed for now, it's needed for more complex cases. We
// can ignore this for now.
#define GPIO_INT_GPIO23_DEVICE_DESC    "GPIO23"
 
 
/****************************************************************************/
/* Interrupts variables block                                               */
/****************************************************************************/
short int irq_any_gpio    = 0;

/****************************************************************************/
/* Tasklets - provides the actual processing of the IRQ using the           */
/* bottom half approach (lower priority handlers). These are non re-entrant */
/* handlers (different from sofirqs). This is where we are going to set the */
/* processing of our leds (actually just output something to leds).         */
/****************************************************************************/

/* Forward declaration of the led_output_hander, we use this
 * for declaring a tasklet below.
 */

static void led_output_handler(unsigned long data);

DECLARE_TASKLET(ledtasklet, led_output_handler, 0L);

static void led_output_handler(unsigned long data)
{
   // We need to make sure that our tasklet is not scheduled again
   tasklet_disable(&ledtasklet);

   printk("%s: Takslets executed!\n", __FUNCTION__);
   
   // TODO: Set the led buttons here

   tasklet_enable(&ledtasklet);   
}


/****************************************************************************/
/* IRQ handler                                                              */
/****************************************************************************/
static irqreturn_t gpio23_irq_handler(int irq, void *dev_id, struct pt_regs *regs) 
{
 
   unsigned long flags;
   
   // disable hard interrupts (remember them in flag 'flags')
   local_irq_save(flags);
   
   // We defer handling of our IRQ to tasklets
   tasklet_schedule(&ledtasklet);
 
   // restore hard interrupts
   local_irq_restore(flags);
 
   return IRQ_HANDLED;
}
 
 
/****************************************************************************/
/* This is our GPIO initialization function for configuring GPIO17 as our   */
/* interrupt source.                                                        */
/****************************************************************************/
void gpio23_int_config(void) 
{
 // We first need to request the GPIO base that we require GPIO17
 // to be exported or made available.   
 if (gpio_request(GPIO_INT_GPIO23, GPIO_INT_GPIO23_DESC)) {
  printk("%s: GPIO request faiure: %s\n", __FUNCTION__, GPIO_INT_GPIO17_DESC);
  return;
 }
 // After a successful request, we need to instruct the kernel that this
 // pin will be used as an input source.
 if (gpio_direction_input(GPIO_INT_GPIO23) < 0)
 {
  printk("%s: Error setting GPIO direction!\n", __FUNCTION__);
  return;
 }

 if ( (irq_any_gpio = gpio_to_irq(GPIO_INT_GPIO23)) < 0 ) 
 {
  printk("%s: GPIO to IRQ mapping failure %s\n", __FUNCTION__, GPIO_INT_GPIO23_DESC);
  return;
 }
 
   printk("%s: Mapped interrupt %d\n", __FUNCTION__, irq_any_gpio);
 
   if (request_irq(irq_any_gpio,
                   (irq_handler_t ) gpio23_irq_handler,
                   IRQF_TRIGGER_RISING,
                   GPIO_INT_GPIO23_DESC,
                   GPIO_INT_GPIO23_DEVICE_DESC)) 
 {
  printk("%s: Irq Request failure\n", __FUNCTION__);
  return;
 }
 
   return;
}
 
 
/****************************************************************************/
/* This function releases interrupts.                                       */
/****************************************************************************/
void gpio23_int_release(void) {
 
   free_irq(irq_any_gpio, GPIO_INT_GPIO23_DEVICE_DESC);
   gpio_free(GPIO_INT_GPIO23);
 
   return;
}
 
 
/****************************************************************************/
/* Module init / cleanup block.                                             */
/****************************************************************************/
int gpio23_init(void) {
 
   printk(KERN_NOTICE "Hello !\n");
   gpio23_int_config();
 
   return 0;
}
 
void gpio23_cleanup(void) {
   printk(KERN_NOTICE "Goodbye\n");
   gpio23_int_release();
 
   return;
}
 
 
module_init(gpio23_init);
module_exit(gpio23_cleanup);
 
 
/****************************************************************************/
/* Module licensing/description block.                                      */
/****************************************************************************/
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Vergil Cola <vpcola@gmail.com>");
MODULE_DESCRIPTION("Sample GPIO Interrupt Handler");


Alternatively, you may also download the source and Makefile from git here.

For the example device driver above, there's a slight change on hardware we described from the previous posts. I hade the Xtrinsic sensor board installed and this board also makes use of GPIO17 for generating interrupts. I have to use a different interrupt source for this experiment - GPIO23. I also made changes to the input push button, I added a debounce circuitry using a Schmitt trigger, the schematics of this circuit is described below:


When I first experimented with the push button to trigger interrupts, I initially got 5 or more interrupt triggers in the device driver, and immediately realized that I would need a debounce circuit before the feeding the output GPIO23 pin. There is also a twist to the input login now, our previous hardware is by default logic "1" and "0" when turned on, for this modified circuit, our output from the push button circuit is now logic "1" when turned on (this is due to the inverting schmitt trigger above).

Our device driver above is noting more than print a kernel message, it doesn't do anything special at the moment, but I will make this as a template for my future projects.

Here's a summary of what the device driver above does :

- The device driver's module initialization and cleanup is handled by the gpio23_init() and gpio23_cleanup() functions respectively. The gpio23_init() function calls gpio23_init_config() which initializes our gpio23, and assigns an interrupt handler when the interrupt is received.

- In the gpio23_int_config() function, we first request that gpio pin 23 needs to be made available, this is done by the gpio_request() function. This step is similar to invoking and modifying the export attribute in /sys from my previous post.

- After the gpio has been requested, the driver then asks the kernel to mark the direction of this GPIO pin. In our case since this will be an interrupt source, we call gpio_direction_input(), this step is similar to modifying the direction kernel attribute in my previous post.

- Once the direction is set, we can then call gpio_to_irq(). This call asks the kernel to assign an IRQ number to our gpio pin. Normally interrupts are linux are soft interrupts, this means that in these systems interrupts are not hard wired to the processors interrupt pins. They are sometimes managed by a programmable interrupt controller or PIC. For the raspberry pi, this is managed by the SOC. Once the kernel has assigned an interrupt, we can now assign an interrupt handler.

- Assigning an interrupt handler in the above code is done by the request_irq() function. This kernel function accepts the newly assigned interrupt number as the first parameter, a pointer to the interrupt handler function gpio23_irq_handler(), whether the interrupt will be triggered on the rising edge of gpio23, and the interrupt descriptors. Note that in the above driver, we trigger on the rising edge since normally our circuit now is active low by default (because of the hex inverter).

- Our interrupt handler gpio23_irq_handler() is nothing more than a stub handler. What it does is to schedule the handling of this function via a tasklet - led_output_handler(), which currently does nothing but display something in the logs.

The driver above demonstrates split processing of the interrupt handler routine. Interrupts normally need to run and return the fastest way possible so as not to be a bottleneck in the kernel. It is for this reason that we defer the processing of the interrupt to tasklets, so that our interrupt handler can run and return from the IRQ handler function right away. Tasklets are deferrable functions which implements the bottom-half of the IRQ routine, the top-half being the IRQ handlers themselves. I'll put up more experiments of implementing tasklets and later as we go along.

Ok, so now we can compile and load this device driver. 



You may need to transfer the generated module gpiointerrupt.ko to your raspberry pi. Use insmod command to load the driver, once loaded, the /proc/interrupts lists all the interrupts that has been installed by the kernel:


From the screenshot above, note that our driver is loaded and mapped our interrupt service routine to interrupt 19. If you examine /proc/interrupts, you will also notice that our GPIO23 interrupt is given interrupt number 192 by the kernel. The second column in the display lists how many times the interrupt was called. 

When the button is pressed, notice that we do a printk from our tasklet - this lets us know that our tasklet is called and the interrupt was handled.



You can tail the /var/log/messages for kernel messages from our device driver. We can also examing /proc/interrupts to monitor how many times our button was pressed (notice column 2).


In summary, the raspberry pi provides an opportunity to develop device drivers that operate on hardware that generates interrupts. I will be using this template driver later to do more experiments, perhaps explore on using work queues in the kernel - which during my time as driver writer 10 years ago,  was not available.

Using Rapsberry Pi's GPIO as an interrupt source

Normally its quite difficult to implement device drivers that handles interrupts in the Linux desktop since there are only a few hardware that can generate interrupt (parallel port, etc.). The raspberry pi presents a unique learning opportunity for me to experiment writing device drivers that implements interrupt handlers by assigning one of the GPIO pins as an interrupt source. Before going into details of my experiments, we first need to understand how the GPIO in the raspberry pi are configured, and how to handle kernel interrupts in general.

The GPIO (general purpose input/output) are generally user configurable input/output pins in an embedded system used to communicate with external devices. In linux control of these GPIO input/output pins are done by the  soc, and in the case of the raspberry pi, they are connected to the bcm2835/bcm2708 soc. To control these gpio pins, let us first examine how we can do this in user space.

1) To enable the GPIO pins to be used, we need to export this pin in the kernel. If you browse to /sys/class/gpio on your raspberry pi, you will notice the kernel attributes defined:


pi@raspberrypi /sys/class/gpio $ ls -lrt
total 0
-rwxrwx--- 1 root gpio 4096 Jan  1  1970 unexport
lrwxrwxrwx 1 root gpio    0 Jan  1  1970 gpiochip0 -> ../../devices/virtual/gpio/gpiochip0
-rwxrwx--- 1 root gpio 4096 Jan  1  1970 export
pi@raspberrypi /sys/class/gpio $

Notice the presence of the export and unexport kernel attributes.

2) For this experiment, we need to configure GPIO pin #23, from the raspberry pi as an input pin. Attached is the pinout for the raspberry pi.



You can connect this pin to a push button switch with a pull-up resistor from the circuit shown below:


3) Since we are going to be needing GPIO23 as input, we first need to export this pin via the command:

pi@raspberrypi /sys/class/gpio $ echo 23 > ./export
pi@raspberrypi /sys/class/gpio $ ls
export  gpio23  gpiochip0  unexport
pi@raspberrypi /sys/class/gpio $

Once we export GPIO23 above, the following ls command displays a new directory link to gpio23.

4) We now need to set the direction of this GPIO23 pin as input. To do that navigate into the newly create directory and modify the direction using the command:

pi@raspberrypi /sys/class/gpio $ cd gpio23
pi@raspberrypi /sys/class/gpio/gpio23 $ ls
active_low  direction  edge  power  subsystem  uevent  value
pi@raspberrypi /sys/class/gpio/gpio23 $ echo "in" > ./direction
pi@raspberrypi /sys/class/gpio/gpio23 $

The last command assigns GPIO23 as an input pin.

5) To test if this setup is working, examine the value of this pin by the following:

pi@raspberrypi /sys/class/gpio/gpio23 $ cat value
1
pi@raspberrypi /sys/class/gpio/gpio23 $

Press the button and issue the command below should show that the button was pressed.

pi@raspberrypi /sys/class/gpio/gpio23 $ cat value
0
pi@raspberrypi /sys/class/gpio/gpio23 $

Ok, so now that we have the setup working, we can now use GPIO23 as our interrupt source for our device driver.. 

Will discuss this in my next post.

Writing a Linux Device Driver for the Raspberry Pi II

In my last post, I talked about writing a device driver to access an arbitrary hardware on the i2c bus at address 0x21. The sample hardware only outputs an 8 bit data to the LED's and input data from a row of dip switches. These are described in detail from my previous blog here.

As mentioned in my previous blog, I needed to modify the kernel so as to add a boad_info during board initializtion of raspberry pi so that that kernel knows our device address and the name of our driver. But before we can get to experiment our driver, lets look first on how the current raspberry pi and the i2c drivers are accessed from userspace. 

1) Make sure that the broadcom drivers for i2c are not blocked by the kernel during initialization - you probably have to edit and comment out the entries for the broadcom i2c and spi drivers in raspi-blacklist.conf :

pi@raspberrypi /etc/modprobe.d $ cat raspi-blacklist.conf
# blacklist spi and i2c by default (many users don't need them)

#blacklist spi-bcm2708
#blacklist i2c-bcm2708

pi@raspberrypi /etc/modprobe.d $

You would need to either mark the first character in the line above with "#" to make it a comment line, or remove them completely. 

2) The raspbian distribution already contains some default drivers for the raspberry pi, the i2c-dev kernel module will be loaded as soon as you enable support for i2c above. This device driver contains basic access to the i2c bus that is present on the header. There are also some userspace tools where we can examine the i2cbus, if not already installed, you may download them by:


pi@raspberrypi /etc/modprobe.d $ sudo apt-get i2c-tools

3) You also need to load the i2c-bcm2708 and the i2c-dev driver at boot time by editing the file /etc/modules : 

pi@raspberrypi /etc/modprobe.d $ cat /etc/modules
# /etc/modules: kernel modules to load at boot time.
#
# This file contains the names of kernel modules that should be loaded
# at boot time, one per line. Lines beginning with "#" are ignored.
# Parameters can be specified after the module name.

snd-bcm2835
i2c-bcm2708
i2c-dev
pi@raspberrypi /etc/modprobe.d $


4) Once all the drivers and modules are configured above, we can go ahead and reboot the raspberry pi.

We can check whether the drivers are loaded by issuing the command:

pi@raspberrypi /etc/modprobe.d $ sudo lsmod
Module                  Size  Used by
i2c_dev                 5769  0
snd_bcm2835            18264  0
snd_soc_bcm2708_i2s     5625  0
regmap_mmio             2818  1 snd_soc_bcm2708_i2s
snd_soc_core          127841  1 snd_soc_bcm2708_i2s
snd_compress            8155  1 snd_soc_core
regmap_i2c              1661  1 snd_soc_core
snd_pcm_dmaengine       5505  1 snd_soc_core
regmap_spi              1913  1 snd_soc_core
snd_pcm                83845  3 snd_bcm2835,snd_soc_core,snd_pcm_dmaengine
snd_page_alloc          5132  1 snd_pcm
snd_seq                55484  0
snd_seq_device          6469  1 snd_seq
snd_timer              20998  2 snd_pcm,snd_seq
leds_gpio               2079  0
led_class               4118  1 leds_gpio
snd                    61878  7 snd_bcm2835,snd_soc_core,snd_timer,snd_pcm,snd_seq,snd_seq_device,snd_compress
spi_bcm2708             4960  0
i2c_bcm2708             4757  0
pi@raspberrypi /etc/modprobe.d $


If the drivers i2c_bcm2708 or i2c_dev is not listed above, you may go back and retrace your steps. You can also load the drivers by using insmod or modprobe commands:

pi@raspberrypi /etc/modprobe.d $ sudo insmod i2c_bcm2708

Now that the drivers are loaded, we can use the i2c-tools package to check examine the i2c bus, write to i2c devices and read values from them.

1) To examine the devices in our i2c bus, issue the command :


pi@raspberrypi ~ $ sudo i2cdetect -y 1
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:          -- -- -- -- -- -- -- -- -- -- -- 0e --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- 21 -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: 60 -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- --
pi@raspberrypi ~ $


The command above detects devices on the i2c bus (bus 1), there are actually 3 separate i2c buses on the raspberry pi, but only bus 1 is available on the pi's header. If you have the older version of the raspberry pi, the i2c bus available on the header might be bus 0. 

As noted above, we have several devices on our i2c bus. Our device is listed on 0x21, while the other two devices - 0x60 and 0x0e is for the xtrinsic sensor evaluation board that I currently wired out from the pi cobbler. Here is now my updated setup:




I have brought the header to the breadboard via the pi cobbler. We still have the same setup as before - the led's and dip switches are still connected via the MCP23017 at address 0x21 (bottom left corner). The other items on the breadboard includes - a debounce circuit for the push button connected to on of the raspberry pi's gpio, and a freescale semiconductors' xtrinsic sensor board attached to another cobbler  in the middle of the picture (the header pinout is replicated from the orginal pinouts for the cobbler in the bottom right). 

1) To check whether our circuit is functioning, we'll try to use the i2c-tools package first to set/get values from our circuit. First we need to set the direction register for our LEDs and dip switches. This register is register 0x0 (sub address) to set the direction bits of PORTA, and 0x01 to set the direction bits of PORTB. A value of 0 indicates an output bit, 1 for input. So for our test circuit, we need to set register 0x0 with 0x00 for the LED's and register 0x01 with 0xFF for the dip switches. This is done via the i2cset command.


pi@raspberrypi ~ $ sudo i2cset -y 1 0x21 0x00 0x00
pi@raspberrypi ~ $ sudo i2cset -y 1 0x21 0x01 0xFF

2) Now lets test our LEDs, to turn on our leds, we need to send logic 1 to each bit to register 0x14 for PORTA. For example if we need to turn on the low nibble :


pi@raspberrypi ~ $ sudo i2cset -y 1 0x21 0x14 0x0f
pi@raspberrypi ~ $


So we now have the first 4 bits of our LEDs turned on. 


3) Reading from our dip switches is accomplished by i2cget,  to test, we need to set the values of our dip switches :



Notice I've set the first three bits to logic 1 on the board (in my DIP switch setup, the LSB is towards the left, MSB is to the right).

We can read these values by:

pi@raspberrypi ~ $ sudo i2cget -y 1 0x21 0x13
0x07
pi@raspberrypi ~ $

So the value of 0x07 is now read from our dip switches.

Now that our hardware is functional, we can now go ahead and load our device driver. You can get the souce code for this device driver in :

https://github.com/vpcola/chip_i2c

Note: please check my cross compiler post on how I developed drivers for the raspbery pi in my previous blog. Also read section 5 and 6 in the readme on how I compiled the device driver. If you have successfully compiled the device driver, you can then transfer this to your raspberry pi.

1) To load our device driver, issue the insmod command from the command line:

pi@raspberrypi ~ $ sudo insmod chip_i2c.ko
pi@raspberrypi ~ $ tail /var/log/messages
May 31 02:11:00 raspberrypi kernel: [92130.457195] chip_i2c: chip_i2c_probe
May 31 02:11:00 raspberrypi kernel: [92130.457246] chip_i2c 1-0021: chip_init_client
May 31 02:11:00 raspberrypi kernel: [92130.457265] chip_i2c 1-0021: chip_write_value
May 31 02:11:00 raspberrypi kernel: [92130.457639] chip_i2c 1-0021: chip_write_value : write reg [00] with val [00] returned [0]
May 31 02:11:00 raspberrypi kernel: [92130.457661] chip_i2c 1-0021: chip_write_value
May 31 02:11:00 raspberrypi kernel: [92130.458013] chip_i2c 1-0021: chip_write_value : write reg [01] with val [ff] returned [0]
pi@raspberrypi ~ $

As shown above, our device driver was loaded in the kernel, the driver's initialization routine is invoked (by initializing the direction registers for PORTA and PORTB).

2) The driver provides 2 different methods of accessing the device from user space - one is through the /dev and the other through the use of kernel attributes in /sys:


pi@raspberrypi ~ $ ls -lrt /dev/chip*
crw------- 1 root root 248, 0 May 31 02:11 /dev/chip_i2c_leds
pi@raspberrypi ~ $

Note that by default, in the device driver code this node is create with only root access, therefore you would need to login as root in your raspberry pic to write to the device file. Writing to the leds now can be as easy as:


pi@raspberrypi ~ $ su
Password:
root@raspberrypi:/home/pi# printf "\7" > /dev/chip_i2c_leds
root@raspberrypi:/home/pi#

Leds 1-2 should be lighted after the command above.

3) The other method of accessing the driver is through the exported attributes in /sys. In the /sys
directory contains a hierarchial view of the kernels drivers in memory. To access our driver's attributes, navigate into: 


pi@raspberrypi /sys/bus/i2c/drivers/chip_i2c $ ls -lrt
total 0
--w------- 1 root root 4096 May 31 02:11 uevent
--w------- 1 root root 4096 May 31 02:27 unbind
lrwxrwxrwx 1 root root    0 May 31 02:27 module -> ../../../../module/chip_i2c
--w------- 1 root root 4096 May 31 02:27 bind
lrwxrwxrwx 1 root root    0 May 31 02:27 1-0021 -> ../../../../devices/platform/bcm2708_i2c.1/i2c-1/1-0021
pi@raspberrypi /sys/bus/i2c/drivers/chip_i2c $

As you can see, our driver attaches itself into the i2c bus. Navigate further into the 1-0021 directory (1-0021 is the bus + device address), and you can see the device attributes that has been exported by the driver:


pi@raspberrypi /sys/bus/i2c/drivers/chip_i2c/1-0021 $ ls
chip_led  chip_switch  driver  modalias  name  power  subsystem  uevent
pi@raspberrypi /sys/bus/i2c/drivers/chip_i2c/1-0021 $

The variables chip_led and chip_switch are the two driver attributes that corresponds to the led and dip switches on our hardware. In our driver code, we specify that these two attributes are accessable from a non-root user, so it makes it easy for us to modify this in user space.

To turn on our leds using the chip_led attribute, simply run the command:


pi@raspberrypi /sys/bus/i2c/drivers/chip_i2c/1-0021 $ echo 255 > ./chip_led

Which should turn on all of our LEDs.

Simillarly, to read the dip switch settings, you can read the chip_switch attribute by:


pi@raspberrypi /sys/bus/i2c/drivers/chip_i2c/1-0021 $ cat ./chip_switch
7
pi@raspberrypi /sys/bus/i2c/drivers/chip_i2c/1-0021 $

Which returned the value of 7 as the settings of our dip switches.

To summarize, the device driver that was developed was only to provide easy access to the arbitrary hardware. By providing device entries in /dev and /sys directories, we are able to access our hardware directly from user space. For the raspberry pi, there is already an i2c-dev device driver which allows access to i2c devices, for most requirements this would suffice. Most user space programs will simply poll the device file /dev/i2c-0 or /dev/i2c-1 to read and write values to the device on the i2c bus. However, if the hardware requires explicit initialization, or it invokes interrupts on occasions, then we probably need to create a device driver to handle these interrupts in the kernel. 

In the next blog, I'll discuss about my adventures in handling these hardware interrupts in the kernel by creating a device driver connected to the raspberry pi's gpio pin. 

Thursday, May 29, 2014

Cross compiling for the Raspberry PI

Developing C/C++ applications for the raspberry pi can be done on the PI itself, you will need to get gcc package and related tools. However, compiling applications on the PI is slow and some applications might not even compile at all due to the slow processor. It is for this reason that most applications are compiled on a different machine with a much more capable processor than on the pi itself. 

Before I discuss about testing the device drivers on my previous post, I'd like to show my setup on how the device drivers are compiled and linked in my Fedora 19 box, targetting applications for the raspberry pi.

On my Fedora box (Fedora 19, 64 bit), I first downloaded a cross compiler from the following link:


Download and unzip it to the directory of your choice, once that is done. In the arm-bcm2708 directory, you would find the different compiler packages that can target the raspberry pi. Depending on your distribution,  you may want to copy one of these to your tools dir. In my case since I have a 64 bit Fedora, I choose gcc-linaro-arm-linux-gnueabihf-raspbian-x64 and copied it to /opt/cross/raspberrypi. I then created a gcc soft link to point to this directory. I'd like to keep my cross compilers, kernel source and application sources in one directory, so this is the reason why I keep everything in /opt/cross/raspberry


Next we need to modify build variables, I usually invoke a shell command to set my environment variables for doing raspberry targets. So in my home directory, I create this shell script:


The setraspberryenv.sh simply sets the environment variables, mostly build variables to point to the correct compiler (in this case the arm-linux-gnueabihf-gcc, etc.). You can create an alias in your .bashrc file to invoke this shell script, in my case I added an entry in my .bashrc:

alias pidev=". ~/bin/setraspberry.sh"

Once that is done and the environment variables set, we can now go ahead and download the Raspbian Linux kernel.


[vco@localhost raspberry]$ git clone git://github.com/raspberrypi/linux.git

Once that's done, we can now modify and build our kernel (Note that in my previous post, we need to modify some files in the kernel /opt/cross/raspberry/linux/arch/arm/mach-bcm2708/bcm2708.c file.).

After making the changes above, we also need to get the current configuration of our raspberry pi and use this configuration to build our kernel. You must first login to your raspberry pi via console and invoke this command:


pi@raspberrypi ~ $ zcat /proc/config.gz > .config

Then copy the .config file to the root of your downloaded kernel source

[vco@localhost raspberry]$ cd linux
[vco@localhost linux]$ scp pi@192.168.0.115:/home/pi/.config .

Now we can start building the kernel


[vco@localhost linux]$ pidev
[vco@localhost linux]$ make ARCH=arm CROSS_COMPILE=${CCPREFIX}

During compilation, you maybe prompted for some kernel build options which you either have to turn on or off. In my case, I just accepted the default for all options. At this point, you can go ahead make coffee or whatever, kernel compilation can take time. When finished, we also need to compile the modules after the kernel compilation.


[vco@localhost linux]$ make ARCH=arm CROSS_COMPILE=${CCPREFIX} modules

After successful compilation of the kernel and the loadable modules, you can grab the copy of it from:



The zImage file is what you need, it contains the compressed image of the kernel.

Next we will transfer our image to the Raspberry Pi's SD card and replace the current kernel image that is running. To do this, I took out the SD card from the raspberry pi and plugged it into my Fedora development machine. There are two partitions on this SD card, one Fat16 partition, which is used by the boot loader for loading the kernel image and other firmware files. And another linux partition which contains the root file system of the raspberry pi.

To access these partitions form my Fedora box, I mounted them on my Fedora machine like this:


[root@localhost linux]# mount /dev/sdb1 -t vfat /media/raspiboot
[root@localhost linux]# mount /dev/sdb2 /media/raspiroot

The two commands above mounts the two partions on the sd card to /media/raspiroot and /media/raspiboot (you need to become root of course to do this).

During the boot process of the raspberry pi, the main ARM microprocessor is currently disabled, instead the GPU is the one that takes over during this boot process. Before activating the ARM processor, it does some initialization sequence, reads the SD card for the kernel - kernel.img, load the kernel at 0x8000 memory address, and finally releasing the hold on the reset switch to let the kernel run at the loaded memory address.

To replace the kernel, we only need to copy the genrated zImage file and ovewrite the kernel.img on the FAT16 partition of the SD card.


[root@localhost linux]# cd arch/arm/boot
[root@localhost linux]# cp zImage /media/raspiboot/kernel.img

If you wish to backup the old kernel, you can copy it to a different file before copy overwrite command above.

Before we can boot to our new kernel, we also need to copy the modules and transfer it to the root filesystem of the raspberry pi.


[root@localhost linux]# make ARCH=arm CROSS_COMPILE=${CCPREFIX} INSTALL_MOD_PATH=/media/raspiroot modules_install
The avobe command installs the kernel modules in /media/raspiroot which we have mounted earlier. 

Once done, unmount /media/raspiboot and /media/raspiroot and plug the card to the raspberry pi and reboot. We can go ahead and boot the new kernel and proceed to compiling the device drivers in my previous blog.

Writing a Linux kernel device driver for the Raspberry Pi

In this experiment, I'm going to write a device driver for my raspberry pi. The driver is for a set of leds and dip switches that are connected to the i2c bus on the raspberry pi headers.


In the circuit, the SCL and SDA lines are connected to the MCP23017 chip, these i2c lines are connected to the second i2c bus (i2c-1) which are available on the headers. The circuit is hardwired to i2c device address of 0x21 (Note that A0..A2 is "001") since the MCP base addresses starts at 0x20.

The device driver I'm writing is part of a learning exercise ( I haven't been writing firmware for the last 7 years and kind'a missed it).

I've already made an example userland application that accesses the leds in my last post, but why the need for a device driver? Based on the simple circuit above, we might come into situations wherein our hardware needs to fire an interrupt say if something goes wrong (Note that on the above circuit the INTA and INTB are not wired, I'll wire them up later). In userland, you would have to do this by polling the input lines and watching for changes, and this is costly (polling consumes CPU time). Using the device driver approach, you can get the device to sleep and only wake up when an interrupt occurs, this saves CPU time as well as power consumption. Though the simple circuit above doesn't yet do that, but I'll add support for interrupts later. For now, here's what I aim to accomplish by writing the device driver:

  • Use a character device driver to provide access to the array of LEDs directly. Opening a device file in /dev/<char dev> and writing to it directly.
  • Use the /sys to export kernel attributes/variables so we can access the LED's and dip switches directly from userspace.
The code for this device driver is shown below:

/*
 * Chip I2C Driver
 *
 * Copyright (C) 2014 Vergil Cola (vpcola@gmail.com)
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, version 2 of the License.
 *
 * This driver shows how to create a minimal i2c driver for Raspberry Pi.
 * The arbitrary i2c hardware sits on 0x21 using the MCP23017 chip. 
 *
 * PORTA is connected to output leds while PORTB of MCP23017 is connected
 * to dip switches.
 *
 */

#define DEBUG 1

#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/jiffies.h>
#include <linux/i2c.h>
#include <linux/mutex.h>
#include <linux/err.h>
#include <linux/sysfs.h>
#include <linux/device.h>
#include <linux/fs.h>


#define CHIP_I2C_DEVICE_NAME    "chip_i2c"

/* Define the addresses to scan. Of course, we know that our
 * hardware is found on 0x21, the chip_i2c_detect() function
 * below is used by the kernel to enumerate the i2c bus, the function
 * returns 0 for success or -ENODEV if the device is not found.
 * The kernel enumerates this array for i2c addresses. This 
 * structure is also passed as a member to the i2c_driver struct.
 **/
static const unsigned short normal_i2c[] = { 0x20, 0x21, I2C_CLIENT_END };

/* Our drivers id table */
static const struct i2c_device_id chip_i2c_id[] = {
    { "chip_i2c", 0 },
    {}
};

MODULE_DEVICE_TABLE(i2c, chip_i2c_id);

/* Each client has that uses the driver stores data in this structure */
struct chip_data {
 struct mutex update_lock;
 unsigned long led_last_updated; /* In jiffies */
    unsigned long switch_last_read; /* In jiffies */
    int kind;
    /* TODO: additional client driver data here */
};

/**
 * The following variables are used by the exposed 
 * fileops (character device driver) functions to
 * allow our driver to be opened by normal file operations
 * - open/close/read/write from user space.
 */
static struct class * chip_i2c_class = NULL;
static struct device * chip_i2c_device = NULL;
static int chip_i2c_major;

/* Define the global i2c_client structure used by this
 * driver. We use this for the file operations (chardev)
 * functions to access the i2c_client.
 */
static struct i2c_client * chip_i2c_client = NULL;

/* We define a mutex so that only one process at a time
* can access our driver at /dev. Any other process
* attempting to open this driver will return -EBUSY.
*/
static DEFINE_MUTEX(chip_i2c_mutex);

/* We define the MCP23017 registers. We only need to set the
 * direction registers for input and output
 **/
#define REG_CHIP_DIR_PORTA 0x00
#define REG_CHIP_DIR_PORTB  0x01

#define REG_CHIP_PORTA_LIN  0x12
#define REG_CHIP_PORTB_LIN  0x13
#define REG_CHIP_PORTA_LOUT 0x14
#define REG_CHIP_PORTB_LOUT 0x15


/* Input/Output functions of our driver to read/write
 * data on the i2c bus. We us the i2c_smbus_read_byte_data()
 * and i2c_smbus_write_byte_data() (i2c.h) for doing the 
 * low level i2c read/write to our device. To make sure no 
 * other client is writing/reading from the device at the same time, 
 * we use the client data's mutex for synchronization.
 *
 * The chip_read_value() function reads the status of the
 * dip switches connected to PORTB of MCP23017 while the
 * chip_write_value() sets the value of PORTA (leds).
 */
int chip_read_value(struct i2c_client *client, u8 reg)
{
    struct chip_data *data = i2c_get_clientdata(client);
    int val = 0;

    dev_info(&client->dev, "%s\n", __FUNCTION__);

    mutex_lock(&data->update_lock);
    val = i2c_smbus_read_byte_data(client, reg);
    mutex_unlock(&data->update_lock);

    dev_info(&client->dev, "%s : read reg [%02x] returned [%d]\n", 
            __FUNCTION__, reg, val);

    return val;
}

int chip_write_value(struct i2c_client *client, u8 reg, u16 value)
{
    struct chip_data *data = i2c_get_clientdata(client);
    int ret = 0;

    dev_info(&client->dev, "%s\n", __FUNCTION__);

    mutex_lock(&data->update_lock);
    ret =  i2c_smbus_write_byte_data(client, reg, value);
    mutex_unlock(&data->update_lock);

    dev_info(&client->dev, "%s : write reg [%02x] with val [%02x] returned [%d]\n", 
            __FUNCTION__, reg, value, ret);

    return ret;
}

/* The following functions are used by this device drivers
* to provide a char device functionality.
*/
static int chip_i2c_open(struct inode * inode, struct file *fp)
{
   printk("%s: Attempt to open our device\n", __FUNCTION__);
   /* Our driver only allows writing to our LED's */
   if ((fp->f_flags & O_ACCMODE) != O_WRONLY)
       return -EACCES;

   /* We need to ensure that only one process can 
    * access the file handle at one time
    */
   if (!mutex_trylock(&chip_i2c_mutex))
   {
       printk("%s: Device currently in use!\n", __FUNCTION__);
       return -EBUSY;
   }

   /* We olso need to check if the chip driver (client)
    * is already loaded, otherwise write/read to/from
    * i2c device will fail.
    */
   if (chip_i2c_client == NULL)
       return -ENODEV;

   return 0;
}

static int chip_i2c_close(struct inode * inode, struct file * fp)
{
   printk("%s: Freeing /dev resource\n", __FUNCTION__);

   mutex_unlock(&chip_i2c_mutex);
   return 0;
}

/* Our file op write function, note that we only write the 
* last byte sent to the leds and discard the rest.
*/
static ssize_t chip_i2c_write(struct file * fp, const char __user * buf,
        size_t count, loff_t * offset)
{
    int x, numwrite = 0;
    char * tmp;

    /* We'll limit the number of bytes written out */
    if (count > 512)
        count = 512;

    tmp = memdup_user(buf, count);
    if (IS_ERR(tmp))
        return PTR_ERR(tmp);

    printk("%s: Write operation with [%d] bytes\n", __FUNCTION__, count);
    for (x = 0; x < count; x++)
        if (chip_write_value(chip_i2c_client, REG_CHIP_PORTA_LOUT, (u16) tmp[x]) == 0)
            numwrite++;

    return numwrite;
}

/* Our file operations table, thiw will used by the 
 * initializzation code (probe) to create a character
 * device on /dev. 
 */
static const struct file_operations chip_i2c_fops = {
    .owner = THIS_MODULE,
    .llseek = no_llseek,
    .write = chip_i2c_write,
    .open = chip_i2c_open,
    .release = chip_i2c_close
};


/* Our driver attributes/variables are currently exported via sysfs. 
 * For this driver, we export two attributes - chip_led and chip_switch
 * to correspond to MCP23017's PORTA (led) and PORTB(dip switches).
 *
 * The sysfs filesystem is a convenient way to examine these attributes
 * in kernel space from user space. They also provide a mechanism for 
 * setting data form user space to kernel space. 
 **/
static ssize_t set_chip_led(struct device *dev, 
    struct device_attribute * devattr,
    const char * buf, 
    size_t count)
{
    struct i2c_client * client = to_i2c_client(dev);
    int value, err;

    dev_dbg(&client->dev, "%s\n", __FUNCTION__);

    err = kstrtoint(buf, 10, &value);
    if (err < 0)
        return err;

    dev_dbg(&client->dev, "%s: write to i2c with val %d\n", 
        __FUNCTION__,
        value);

    chip_write_value(client, REG_CHIP_PORTA_LOUT, (u16) value);

    return count;
}

static ssize_t get_chip_switch(struct device *dev, 
    struct device_attribute *dev_attr,
    char * buf)
{
    struct i2c_client * client = to_i2c_client(dev);
    int value = 0;

    dev_dbg(&client->dev, "%s\n", __FUNCTION__);

    value = chip_read_value(client, REG_CHIP_PORTB_LIN);

    dev_info(&client->dev,"%s: read returned with %d!\n", 
        __FUNCTION__, 
        value);
    // Copy the result back to buf
    return sprintf(buf, "%d\n", value);
}

/* chip led is write only */
static DEVICE_ATTR(chip_led, S_IWUGO, NULL, set_chip_led);
/* chip switch is read only */
static DEVICE_ATTR(chip_switch, S_IRUGO, get_chip_switch, NULL);


/* This function is called to initialize our driver chip
 * MCP23017.
 *
 * For MCP23017 to function, we first need to setup the 
 * direction register at register address 0x0 (PORTA) and
 * 0x01 (PORTB). Bit '1' represents input while '0' is latched
 * output, so we need to write 0x00 for PORTA (led out), and
 * all bits set for PORTB - 0xFF.
 */
static void chip_init_client(struct i2c_client *client)
{
    /* Set the direction registers to PORTA = out (0x00),
     * PORTB = in (0xFF)
     */
    dev_info(&client->dev, "%s\n", __FUNCTION__);

    chip_write_value(client, REG_CHIP_DIR_PORTA, 0x00);
    chip_write_value(client, REG_CHIP_DIR_PORTB, 0xFF);
}


/* The following functions are callback functions of our driver. 
 * Upon successful detection of kernel (via the chip_detect function below). 
 * The kernel calls the chip_i2c_probe(), the driver's duty here 
 * is to allocate the client's data, initialize
 * the data structures needed, and to call chip_init_client() which
 * will initialize our hardware. 
 *
 * This function is also needed to initialize sysfs files on the system.
 */
static int chip_i2c_probe(struct i2c_client *client,
    const struct i2c_device_id *id)
{
    int retval = 0;
    struct device * dev = &client->dev;
    struct chip_data *data = NULL;

    printk("chip_i2c: %s\n", __FUNCTION__);

    /* Allocate the client's data here */
    data = devm_kzalloc(&client->dev, sizeof(struct chip_data), GFP_KERNEL);
    if(!data)
        return -ENOMEM;

    /* Initialize client's data to default */
    i2c_set_clientdata(client, data);
    /* Initialize the mutex */
    mutex_init(&data->update_lock);

    /* If our driver requires additional data initialization
     * we do it here. For our intents and purposes, we only 
     * set the data->kind which is taken from the i2c_device_id.
     **/
    data->kind = id->driver_data;

    /* initialize our hardware */
    chip_init_client(client);

    /* In our arbitrary hardware, we only have
     * one instance of this existing on the i2c bus.
     * Therefore we set the global pointer of this
     * client.
     */
    chip_i2c_client = client;

    /* We now create our character device driver */
    chip_i2c_major = register_chrdev(0, CHIP_I2C_DEVICE_NAME,
        &chip_i2c_fops);
    if (chip_i2c_major < 0)
    {
        retval = chip_i2c_major;
        printk("%s: Failed to register char device!\n", __FUNCTION__);
        goto out;
    }

    chip_i2c_class = class_create(THIS_MODULE, CHIP_I2C_DEVICE_NAME);
    if (IS_ERR(chip_i2c_class))
    {
        retval = PTR_ERR(chip_i2c_class);
        printk("%s: Failed to create class!\n", __FUNCTION__);
        goto unreg_chrdev;
    }

    chip_i2c_device = device_create(chip_i2c_class, NULL, 
        MKDEV(chip_i2c_major, 0),
        NULL,
        CHIP_I2C_DEVICE_NAME "_leds");
    if (IS_ERR(chip_i2c_device))
    {
        retval = PTR_ERR(chip_i2c_device);
        printk("%s: Failed to create device!\n", __FUNCTION__);
        goto unreg_class;
    }

    /* Initialize the mutex for /dev fops clients */
    mutex_init(&chip_i2c_mutex);


    // We now register our sysfs attributs. 
    device_create_file(dev, &dev_attr_chip_led);
    device_create_file(dev, &dev_attr_chip_switch);

    return 0;
    /* Cleanup on failed operations */

unreg_class:
    class_unregister(chip_i2c_class);
    class_destroy(chip_i2c_class);
unreg_chrdev:
    unregister_chrdev(chip_i2c_major, CHIP_I2C_DEVICE_NAME);
    printk("%s: Driver initialization failed!\n", __FUNCTION__);
out:
    return retval;
}

/* This function is called whenever the bus or the driver is
 * removed from the system. We perform cleanup here and 
 * unregister our sysfs hooks/attributes.
 **/
static int chip_i2c_remove(struct i2c_client * client)
{
    struct device * dev = &client->dev;

    printk("chip_i2c: %s\n", __FUNCTION__);

    chip_i2c_client = NULL;

    device_remove_file(dev, &dev_attr_chip_led);
    device_remove_file(dev, &dev_attr_chip_switch);

    device_destroy(chip_i2c_class, MKDEV(chip_i2c_major, 0));
    class_unregister(chip_i2c_class);
    class_destroy(chip_i2c_class);
    unregister_chrdev(chip_i2c_major, CHIP_I2C_DEVICE_NAME);

    return 0;
}

/* This callback function is called by the kernel 
 * to detect the chip at a given device address. 
 * However since we know that our device is currently 
 * hardwired to 0x21, there is really nothing to detect.
 * We simply return -ENODEV if the address is not 0x21.
 */
static int chip_i2c_detect(struct i2c_client * client, 
    struct i2c_board_info * info)
{
    struct i2c_adapter *adapter = client->adapter;
    int address = client->addr;
    const char * name = NULL;

    printk("chip_i2c: %s!\n", __FUNCTION__);

    if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA))
        return -ENODEV;

    // Since our address is hardwired to 0x21
    // we update the name of the driver. This must
    // match the name of the chip_driver struct below
    // in order for this driver to be loaded.
    if (address == 0x21)
    {
        name = CHIP_I2C_DEVICE_NAME;
        dev_info(&adapter->dev,
            "Chip device found at 0x%02x\n", address);
    }else
        return -ENODEV;

    /* Upon successful detection, we coup the name of the
     * driver to the info struct.
     **/
    strlcpy(info->type, name, I2C_NAME_SIZE);
    return 0;
}


/* This is the main driver description table. It lists 
 * the device types, and the callback functions for this
 * device driver
 **/
static struct i2c_driver chip_driver = {
    .class      = I2C_CLASS_HWMON,
    .driver = {
            .name = CHIP_I2C_DEVICE_NAME,
    },
    .probe          = chip_i2c_probe,
    .remove         = chip_i2c_remove,
    .id_table       = chip_i2c_id,
    .detect      = chip_i2c_detect,
    .address_list   = normal_i2c,
};

/* The two functions below adds the driver
 * and perfom cleanup operations. Use them
 * if there are necessary routines that needs
 * to be called other than just calling 
 * i2c_add_driver(), etc.
 *
 * Otherwise, the module_i2c_driver() macro
 * will suffice.
 */
/*
static int __init chip_i2c_init(void)
{
    printk("chip: Entering init routine!\n");

    return i2c_add_driver(&chip_driver);
}
module_init(chip_i2c_init);

static void __exit chip_i2c_cleanup(void)
{
    printk("chip: Removing driver from kernel\n");

    return i2c_del_driver(&chip_driver);
}
module_exit(chip_i2c_cleanup);
*/
module_i2c_driver(chip_driver);

MODULE_AUTHOR("Vergil Cola <vpcola@gmail.com>");
MODULE_DESCRIPTION("Chip I2C Driver");
MODULE_LICENSE("GPL");


There are two sections on this driver, first is to provide a char device functionality, the second is to provide an instance of an i2c chip driver. Here's a summary of what the i2c chip driver portion of our device driver above does:

  1. Our chip driver defines a set of operations in struct i2c_driver . These operations include callback defined in our driver whenever the driver is probed (chip_i2c_probe), when the driver is removed/unloaded from the kernel (chip_i2c_remove), and a detect function (chip_i2c_detect) which is called by the board initialization routine - Will discuss this later. The i2c_driver also contains entries about the device class and the list of i2c addresses it will scan.
  2. Since our driver is very simple, we only have one hardwired instance of it at i2c address 0x21, there was really no need for the detect/probe routine. The detect/probe is a way for the device driver to locate the hardware instance on the bus when the address of the hardware is unkown. Also, i2c doesn't really have a way to dynamically detect hardware on the bus like PCI or USB had. Instantiation of the device must be done statically during board initialization - which I'll get into later.
  3. In the function chip_i2c_detect(), (during the kernel scanning function, the kernel calls this function sometimes repeatedly passing an i2c_client with a different address each time, the addresses is coming from the array normal_i2c). The function will check if the address in i2c_client is the actual hardware we are accessing. If found, we need to fill the board_info (from the board initialization routine) and return 0 for success, otherwise this function returns an error value. In our driver above, we populate the info with our driver name, note that this must match the i2c_driver field int the description table.
  4. Once our hardware is detected, the kernel calls chip_i2c_probe. In this function, the kernel would pass the valid i2c_client (which now contains the correct i2c address of our driver) and this would be our instance for our driver. So once we have the driver's instance, we need to do some initialization within this function. This function does a lot of things:
    • First we need to allocate memory for the data area
    • Call our initialization routine to set the direction registers for MCP23017 (chip_init_client).
    • Save the address of our i2c_client pointer (since we will be using it later on when writing to i2c) to a global variable (chip_i2c_client)
    • Register a character device driver (register_chrdev)
    • Dynamically create entries in the /dev directory for character device access from userland - class_create and device_create.
    • Create entries in the /sys for the device attributes - device_create_file.
  5. The cleanup routine chip_i2c_remove, simple undoes what was created in the chip_i2c_probe function.
The character device portion of this driver is defined by the file_operations table - chip_i2c_fops. 
  1. When applications in userland attempts to open the device, the kernel calls the chip_i2c_open function. In this function, we restrict access to two userland applications from opening the device, hence the need to protect it via mutex. 
  2. When a userland application tries to write to the device, the chip_i2c_write will be called. Since we already a pointer to the i2c_client struct, we can use this to pass it to the chip_i2c_write_value, which in turn writes to our device on the i2c bus. Note that I did not do any parameter checking if chip_i2c_write is called, might be a bug.
  3. Note that a read from the device is not provided at the moment for simplicity, this can be extended later on.
The device driver also export some device attributes via the /sys filesystem. This is accomplished by two functions set_chip_led and get_chip_swtich which will set the values of the LED's and retrieve the value of the dip switches. These functionality is exported via the chip_led and chip_switch variables.

As I mentioned earlier, this device would not work since i2c needs static information of driver  during board initialization (at startup). For this driver to work, we have to modify the kernel to support our driver. So in your kernel source tree, navigate to the file:

linux/arch/arm/mach-bcm2708/bcm2708.c

Find a spot after the raspberry pi's audio board info for the audio device is declared (in lines 689 - 695):

 675 #if defined(CONFIG_SND_BCM2708_SOC_IQAUDIO_DAC) || defined(CONFIG_SND_BCM2708_SOC_IQAUDIO_DAC_MODULE)
 676 static struct platform_device snd_rpi_iqaudio_dac_device = {
 677         .name = "snd-rpi-iqaudio-dac",
 678         .id = 0,
 679         .num_resources = 0,
 680 };
 681
 682 // Use the actual device name rather than generic driver name
 683 static struct i2c_board_info __initdata snd_pcm512x_i2c_devices[] = {
 684     {
 685         I2C_BOARD_INFO("pcm5122", 0x4c)
 686     },
 687 };
 688 #endif
 689
 690 // VCO -- we instantiate our driver info here
 691 static struct i2c_board_info __initdata chip_i2c_devices[] = {
 692     {
 693         I2C_BOARD_INFO("chip_i2c", 0x21)
 694     },
 695 };

Once the board_info for our driver is defined, we need to insert it into the board's intialization routine:

 774 void __init bcm2708_init(void)
 775 {
 776     int i;
 777
 778 #if defined(CONFIG_BCM_VC_CMA)
 779     vc_cma_early_init();
 780 #endif
 781     printk("bcm2708.uart_clock = %d\n", uart_clock);
 782     pm_power_off = bcm2708_power_off;
 783
...
...
 842
 843 // VCO -- add chip_i2c
 844     i2c_register_board_info(1, chip_i2c_devices, ARRAY_SIZE(chip_i2c_devices));
 845

The routines above will insert the chip driver's information into the board initialization. The i2c_board_info now knows that our device "chip_i2c" is located at i2c address 0x21.

Ok, so now we have to compile the kernel, and the device driver and load it up on our raspberry pi (This will be another topic to discuss).

On my next post, I will try to illustrate how to test our driver from user space.

Code for the driver above is found here.



Writing a userland driver for Raspberry PI I2C device.

In my last post I used I2C bus on the raspberry pi header to extend IOs to other devices - may it be an input device (example dip switches) or an output device (array of leds). I did this by connecting the MCP23017 chip which communicates through the I2C bus thats present on the raspberry pi header.

The program assumes that the i2c-dev and i2c_bcm2708 drivers are loaded in the kernel, and that /dev/i2c-1 (Raspberry Pi Model B, Rev. 2) is created by the drivers to interface with userland applications.




Note that /dev/i2c-1 is writable only by the superuser, so running this program would require appropriate permissions. You can either add the user to the i2c group, or run the program as root.

The example C program below illustrates how data can be written to that device (led's) which is located at I2C address of 0x21. 

#include <stdio.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <linux/i2c-dev.h>

/* This function is borrowed from i2ctools package */

int main(int argc, char * argv[])
{
    int file, counter;
    const char * i2cbus="/dev/i2c-1";
    // Data write contains reg address byte[0] plus
    // actual data in byte [1]
    char buf[2];


    // Open the i2c
    file = open(i2cbus, O_WRONLY);
    if (file < 0)
    {
        printf("Failed to open file: %s\n", i2cbus);
        exit(1);
    }

    // Set the device address via IOCTL
    if (ioctl(file, I2C_SLAVE, 0x21) < 0)
    {
        printf("Failes setting device address\n");
        exit(1);
    }

    // We set the direction registers first
    buf[0] = 0x00; // Port b
    buf[1] = 0x00; // All bits set as output
    if (write(file, buf, 2) != 2)
    {
        printf("Failed to write to i2c device\n");
        exit(1);
    }

    for(counter = 0; counter < 100; counter++)
    {
        // We write to register 0x14 for the leds
        buf[0] = 0x14;
        buf[1] = (char) counter;

        if (write(file, buf, 2) != 2) 
        {
            printf("Failed to write data to device\n");
            exit(1);
        }

        sleep(1);
    }

    close(file);

}

In the above code, I opened the device /dev/i2c-1 just like any other file. To set the device address, I have to use an ioctl call passing in the I2C_SLAVE command and the device address as the value. Writing to the led's require two bytes, the first being the register address of the MCP23017 (0x14 for the led), and the second byte is the actual data written. In this case we are simply writing the counter to the 8 bit register which in turn turns the led on with the value of the counter.


You can get the above code from here.

Wednesday, May 28, 2014

I2C IO Expanders for Raspberry Pi

I've had my raspberry pi for over 6 months now, but so far I haven't played around with it other than turning it on/off occasionally. I had to admit my embedded linux/firmware skills is a bit outdated, the last time I played around with linux device drivers and VHDL was over almost 10 years ago. For the last few years, I've been developing applications in C++/Java and haven't really gotten into low level stuff. The raspberry pi provided a platform for me to revisit those lessons lost, retool and learn new things. It's time to go back to developing firmware and device drivers again.
The first thing I noticed about the raspberry pi was the lack of available GPIO's it exposed, I had a B board Revision 2.0 hardware and it only came with a 26 pin header. Some pins on the header are dedicated for other stuff - like SPI and UART. I booted on a stock kernel that came with package, and it was up and running. The first problem I ran into was debugging, the UART pins exposed on the raspberry pi headers are 3.3V and incompatible with the voltages with TTL levels on the serial port. My nearest parts supplier carried a FTDI raspberry pi cable that converts 3.3V levels to the PC's TTL 5V levels. This allows me to debug the kernel while its booting up - quite important if you're debugging device drivers. A jtag cable is a lot better, but I don't need it for now. The second problem IOs that you can connect to the header, luckly I2C is present on the GPIO's, and this is what we'll use to extend IOs for our devices.
I2C is a two wire protocol developed by Philips in the 1980's to let IC/components talk to each other. There are a lot of resources in the internet if you would like to learn more about this protocol, I'm not going to discuss that here.
Looking at the available components out there, I came across this MCP23017 chip manufactured my Microchip. This chip is an IO expander which uses the I2C bus, its I2C address is selectable from a set of A0-A2 pins on the chip (base address starts at 0x20). I've choosen I2C over SPI since we only have a few GPIO than can be used for the chip select pin on top of the SPI signals. The chip also provides 2 configurable input/output 8-bit ports, and two interrupt out pins which I can use to trigger interrupts on the raspberry pi.
With the list of parts needed for the project, I ordered the RS232 usb cable for the raspberry pi, a PI cobbler kit (so that I can connect the GPIO lines to my breadboard) and the MCP23017 chip from my local supplier (Element14). The GPIO cobbler kit requires some soldering, this will allow us to connect the GPIO headers directly to our breadboard. The FTDI RS232 cable is a bit expensive (SGD $20+), while the MCP23017 chips costs around SGD $2.
From the available parts I had with me, I quickly wired a test circuit for the MCP23017. The two 8 bit ports are connected to the led and dip switches. It's address is hard wired to 0x21 by connecting the A0-A2 pins to "001" (Note that the base address for the MCP23017 is 0x20). The resistors I choose are random, as they came from what's available with me at the moment, they'll serve as current limiters to the output of the MCP23017 chip as we don't want our LEDs to connect to ground directly. Here's the completed circuit I have wired up:

Here's the actual circuit wired up:


After loading the Pi with a custom kernel. I made sure that the i2c drivers are loaded at boot time by the raspberry pi. I found good tutorial on how to do that on Adafruit's website. Once i2c core drivers is enabled at the kernel, I can start turning on/off our LED's and getting the values of our DIP switches.

First up, I need to know if the i2c device is detected, I did this by running i2cdetect. Note that on the raspberry pi B rev. 2 board, i2c present on the headers are coming from i2c master at bus 1 (there are 3 on the pi, but only bus 2 is connected to the headers). This is the reason for the "i2cdetect -y 1" as I that's the only bus exported on the header.




Great! as noted, the address at 0x21 was detected. But gefore I can start turning on/off the leds or read from the dip switch, I first need to configure the direction of PortA and PortB pins on the MCP23017. I did this by accessing register 0x00 for PortA, setting bits '0' for output or '1' for input. Simillarly, PortB's direction register is at 0x01. To configure the led output and dip switch as input, I need to issue two I2C write commands to address 0x21, register 0x00, and value 0x00 (for setting PORTA as an output port) and an i2c write at address 0x21 with register 0x01 and 0xFF as value (for setting PORTB as input port). An i2c write command is done via the i2cset command below.


Now that I have set the direction register, I can now set the leds on or off. The register address of PORTA from the MCP23017 specs is located at 0x14, so to set all leds to on, I issued an i2c write to address 0x21, register 0x4 with 0xFF to turn on all leds.

>sudo i2cset -y 1 0x21 0x14 0xFF

Simillarly, to read values from the dip swiches, issue an i2cget command with address 0x21 and register 0x13 (PORTB reg).




So far so good. But how can we access these settings programmatically say via a C or C++ program? There are several ways of doing this, on the raspberry pi there's a driver for i2c that resides in /dev/i2c-0 or /dev/i2c-1, we can either use the instance of /dev/i2c-1 (bus 1) open it just like any other file in Linux, specify the address through a ioctl calls and write to it. Will try to discuss the user space code for accessing the leds and dip switches on my next post.