Pages

Saturday, May 31, 2014

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. 

No comments: