Working with I2C in FreeBSD on Raspberry Pi

Unix is well-documented system, but on C language

There was no support for I2C in FreeBSD-10.0-RELEASE for Raspberry Pi.
It appeared in FreeBSD-10.0-STABLE later.

Here is instruction how to use it with FreeBSD on Raspberry Pi.

During FreeBSD boot we can see iic devices:

iichb0: <BCM2708/2835 BSC controller> mem 0x20205000-0x2020501f irq 61 on simplebus0
iicbus0: <OFW I2C bus> on iichb0
iic0: <I2C generic I/O> on iicbus0
iichb1: <BCM2708/2835 BSC controller> mem 0x20204000-0x2020401f irq 61 on simplebus0
iicbus1: <OFW I2C bus> on iichb1
iic1: <I2C generic I/O> on iicbus1

There are two i2c device: iic0 and iic1.
For Raspberry A and Raspberry B rev. 1 we should use iic0.
And for Raspberry B rev. 2 we should use iic1.

I have an i2c device with address 0x68 attached to bus. I know that it works properly.

There are i2c(8) utility in FreeBSD.
The i2c utility can be used to perform raw data transfers (read or write) with devices on the I2C bus.
It can also scan the bus for available devices and reset the I2C controller.

First, let’s try to scan our i2c bus for devices:

i2c1

We got an error message: Device not configured

Let’s try to read directly from our device:

i2c2

We got two error messages:
error sending stop condition
error sending start condition

Two main questions are: «What Shall We Do?» and «Who is to Blame?» 🙂
Does it work in FreeBSD on Raspberry Pi? And how can we work with it?

Let’s make some investigations.

Configuration file for default kernel includes support for ktrace(1) — utility for kernel process tracing.
ktrace produces file ktrace.out. This file is not human readable.
We need to use kdump(1) utility.

Let’s see what happens when i2c try to work.
We will run ktrace -t+ <command> and then run kdump.

First we will try to scan i2c bus:

i2c4
i2c5

i2c successfully open device /dev/iic1, but syscall ioctl I2CRSTCARD returns an error.

Let’s try to read directly from our device:

i2c6
i2c7

i2c successfully open device /dev/iic1, but syscalls ioctl I2CSTART and I2CSTOP return an error.

Manual for IIC(4) says that to control it we can use the following ioctls:

    I2CSTART
    I2CRPTSTART
    I2CSTOP
    I2CRSTCARD
    I2CWRITE
    I2CREAD
    I2CRDWR

ioctl returns an error for I2CRPTSTART, I2CSTART, I2CSTOP.

Let’s try to use I2CREAD.
I wrote a small program to work with this syscall. It will try to read all devices on iic bus.
When we can read one byte from device we will print its address.

#include <sys/cdefs.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <errno.h>
#include <unistd.h>
#include <dev/iicbus/iic.h>

int main ( int argc, char **argv )  {

  int i, fd;
  char dev[] = "/dev/iic1";
  char buf;
  struct iiccmd cmd = { 0, 1, 0, &buf };

  if ( (fd = open(dev, O_RDWR)) < 0 )  {
    perror("open");
    exit(-1);
  }

  for ( i = 1; i < 128; i++ )  {
    cmd.slave = i;
    ioctl(fd, I2CSTART, &cmd);
    if ( ioctl(fd, I2CREAD, &cmd) >= 0 )  printf( "%02x\n", i );
  }

  close(fd);
  exit(0);
}

i2c8

As we see syscall ioctl I2CREAD returns an error.

There is only one option to try: ioctl I2CRDWR

Manual for iic(4) does not explain in detail how to work with these syscall, but google helps 🙂

To read data from device we need to use iic_rdwr_data with two iic_msg.
First to set an offset in device and second to get data.

  struct iic_msg msg[2];
  struct iic_rdwr_data rdwr;

  msg[0].slave = slave;
  msg[0].flags = !IIC_M_RD;
  msg[0].len = sizeof( offset );
  msg[0].buf = &offset;

  msg[1].slave = slave;
  msg[1].flags = IIC_M_RD;
  msg[1].len = sizeof( buf );
  msg[1].buf = buf;

  rdwr.msgs = msg;
  rdwr.nmsgs = 2;

  if ( ioctl(fd, I2CRDWR, &rdwr) < 0 )  {
    perror("I2CRDWR");
    return(-1);
  }

To write data to device we need to use iic_rdwr_data with one iic_msg.
Both offset and data will be in one two-byte buf.

  uint8_t buf[2];
  struct iic_msg msg;
  struct iic_rdwr_data rdwr;

  buf[0] = offset;
  buf[1] = val;
  msg.slave = slave;
  msg.flags = 0;
  msg.len = sizeof( buf );
  msg.buf = buf;

  rdwr.msgs = &msg;
  rdwr.nmsgs = 1;

  if ( ioctl(fd, I2CRDWR, &rdwr) < 0 )  {
    perror("I2CRDWR");
    return(-1);
  }

I have written a small library to read and write data by I2C bus.
It is available here: https://github.com/vzaigrin/libi2c.git

Let’s try to scan iic bus using syscall ioctl I2CRDWR

#include <sys/cdefs.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <errno.h>
#include <unistd.h>
#include <dev/iicbus/iic.h>

int main ( int argc, char **argv )  {

  int i, fd;
  char dev[] = "/dev/iic1";
  uint8_t buf[2] = { 0, 0 };
  struct iic_msg msg[2];
  struct iic_rdwr_data rdwr;

  msg[0].flags = !IIC_M_RD;
  msg[0].len = sizeof(buf);
  msg[0].buf = buf;

  msg[1].flags = IIC_M_RD;
  msg[1].len = sizeof(buf);
  msg[1].buf = buf;

  rdwr.nmsgs = 2;
  
  if ( (fd = open(dev, O_RDWR)) < 0 )  {
    perror("open");
    exit(-1);
  }

  for ( i = 1; i < 128; i++ )  {
    msg[0].slave = i;
    msg[1].slave = i;
    rdwr.msgs = msg;
    if ( ioctl(fd, I2CRDWR, &rdwr) >= 0 )  printf( "%02x\n", i );
  }

  close(fd);
  exit(0);
}

i2c9

It works!

Now it’s time to try to understand why it doesn’t work.

FreeBSD on ARM uses FDT — Flattened Device Tree.
There is our device tree:

i2c3

Device iic uses iicbus, iicbus uses iichb, iichb is a BCM2708/2835 controller.

Configuration file for default kernel includes this devices:
device iic
device iicbus
device bcm2835_bsc

Device iic is a character device.
In source code of iic device we can see that ioctl uses the following syscalls:

    iicbus_start for I2CSTART
    iicbus_stop for I2CSTOP
    iicbus_reset for I2CRSTCARD
    iicbus_write for I2CWRITE
    iicbus_read for I2CREAD
    iicbus_transfer for I2CRDWR

Next step is to study source code for iicbus device.
But in iicbus interface we see only DEVMETHOD(iicbus_transfer, iicbus_transfer)
There are no interfaces for calls iicbus_start, iicbus_stop, iicbus_reset, iicbus_write and iicbus_read.

And in the source code of bcm2835_bsc we can find only transfer method.

That’s why ioctl I2CSTART, I2CSTOP, I2CRSTCARD, I2CWRITE and I2CREAD doesn’t work.

So, we can use I2C in FreeBSD on Raspberry Pi only by call ioctl I2CRDWR.

Working with I2C in FreeBSD on Raspberry Pi: 12 комментариев

  1. hi vadim!

    thanks for your work and nice post! I have the problem that I can not use the ioctl(fd, I2CRDWR,… or any other ioctl to the opened fd after I called ioctl(fd, I2CRDWR…) for the first time on the fresh open FD. So using https://github.com/vzaigrin/libi2c.git read function on a open FD of a iic dev to read out two registers on the same i2c addr gives me an error. even closing the FD after the first read call (tried it with sleep inside too) and trying to re-open it does fail. (ktrace showes errors like «6 Device not configured).

    ps. if i open, and call i2c read only once — everything runs fine and the returned data is correct.
    ps. I can scan the i2c bus (I2CRDWR) with your new/patches i2c -scan … so I2CRDWR to different i2c addrs seems to work… hmm

    am i doing something wrong? (open, i2c_read(fd, addr,0…), i2c_read(fd, addr,[0 or 1 or …]…) -> error)
    does close(fd) after the ioctl(I2CRDWR) realy close the fd?
    should I close the fd after every read/write i2c call?

    Greets sead

    • Hi

      Thank you for comment. You are the first who commented this blog 🙂

      When I read or write some data from one device on iic bus, I open device in the beginning of the program, then do some read or write and then close device at the end of program.
      You can see example in my program to work with DS1307 RTC device here: https://github.com/vzaigrin/ds1307.git

      As I see we don’t need to close iic device after each read or write.

      Try to read from your device using updated i2c utility. Does it work?

      Do you try to read one byte per operation or some bytes?
      Current version of my libi2c read and write one byte.
      I plan to update it to work with some bytes per operation.

  2. Hi, I will send you some details in 1 or 2 weeks — I switched finaly, and a little depressed to raspberian (I ‘lost’ the weekend with trying i2c 1st on NetBSD then FreeBSD — which of course I would pref) — heh but i need to get my project *servos* (literally) moving… :]

    lets not start OS wars (hehe linux already lost it) — but I like the simple «write/read» fd communication style thx
    — Yes, the ‘scan the bus for devices» function of the updated i2c utility does work -> thx
    — Yes, one, bye one — 1 bye per read write op
    — My problems started when I called ioctl(I2CRDWR…) on a open FD twice (the ioctl drops errors about the fd) — so to reproduce
    it should be possible to just call the ioctl with the offset and read i2c msgs in struct (can also be the same as the 1st call) two times.
    (so even if I closed and reopened the FD to try the same ioctl aggain — the (2nd) OPEN failed — it seems to me like the ‘kernel level’
    processing of the I2CRDWR ctl was in a kind of waiting or freezed state — so the FD was blocked (even for closing?).

  3. I tried scanning the bus for devices with the above mentioned code and then I tried (i2c -s). I got different values for the addresses. When I disconnect the I2C device, I still get all the same addresses with the code. What could be the reason for this discrepancy and why do I still get the same address values with the for loop, even when I removed all the devices?

    output:
    root@amd-sham:/home/sham1810/i2c # i2c -s -v
    dev: /dev/iic0, addr: 0x0, r/w: r, offset: 0x00, width: 8, count: 1
    Hardware may not support START/STOP scanning; trying less-reliable read method.
    Scanning I2C devices on /dev/iic0: 1c 2e 30 54 57 68

    Code:
    root@amd-sham:/home/sham1810/i2c # ./a.out
    38
    39
    5c
    5d
    6c
    6d

Оставьте комментарий