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:
We got an error message: Device not configured
Let’s try to read directly from our device:
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:
i2c
successfully open device /dev/iic1
, but syscall ioctl I2CRSTCARD
returns an error.
Let’s try to read directly from our device:
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); }
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); }
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:
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
.
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.
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?).
You should study a description of your device’s behavior.
Maybe you need to use flags IIC_M_NOSTOP (do not send a I2C stop after message) or IIC_M_NOSTART (do not send a I2C start before message) in ioctl call.
You can find its in iic.h (http://svnweb.freebsd.org/base/head/sys/dev/iicbus/iic.h?view=markup)
In reality functions «open» and «close» do nothing with iic bus.
They just change the counter in the driver structure.
http://svnweb.freebsd.org/base/head/sys/dev/iicbus/iic.c?view=markup
Thanks, It is working on my RPI-B.
Welcome 🙂
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
Did you reboot your system after removing i2c devices? From my point of view, system builds the device map when starting.
Nice, clean, clear, profi