kplindegaard / smbus2

A drop-in replacement for smbus-cffi/smbus-python in pure Python
MIT License
244 stars 68 forks source link

FreeBSD support #66

Open tykling opened 3 years ago

tykling commented 3 years ago

Hello :)

I would love to see FreeBSD support in smbus2 so Python i2c stuff would be easier in FreeBSD.

Before I do too much I wanted to ask if you are interested in accepting a PR for FreeBSD support (if I get that far)?

I've dug up https://github.com/freebsd/freebsd-src/blob/main/sys/dev/iicbus/iic.h which is used by https://github.com/freebsd/freebsd-src/blob/098dbd7ff7f3da9dda03802cdb2d8755f816eada/usr.sbin/i2c/i2c.c - the latter works well for communicating with i2c devices from the console. So I think the basics are in place.

I am not sure how to continue from here though, any pointers would be much appreciated! My ultimate goal is getting FreeBSD support in the Qwiic_py package for SparkFuns QWIIC ecosystem of sensors.

Thank you! :+1:

kplindegaard commented 3 years ago

Hi @tykling. Pardon my lack of experience with FreeBSD.

Let's leave SMBus support out of the discussion for now and concentrate on the pure i2c capabilities. The smbus2 lib as well as the example you linked to communicate in similar ways over i2c:

That indicated to me that it should be possible to smbus2's i2c_rdwr right out of the box.... You know, the examples shown in the I2C section in the readme. Have you tested those?

tykling commented 3 years ago

Hello :) Thank you for the input, it is much appreciated!

I've been trying to make this work. My i2c device is on address 48 and working well:

[root@pizero1 ~]# i2c -sv
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: 48 60 
[root@pizero1 ~]# i2c -a 48 -d r -c 2 -m tr
0c 19 
[root@pizero1 ~]# 

I am using the first i2c example from the README:

[root@pizero1 ~/smbus2]# cat i2ctest.py 
from smbus2 import SMBus, i2c_msg

with SMBus(1) as bus:
    msg = i2c_msg.read(48, 2)
    bus.i2c_rdwr(msg)
[root@pizero1 ~/smbus2]# 

With no changes to smbus2 I get the following error - which is understandable since it is using a Linux ioctl:

(smbus2_venv) [root@pizero1 ~/smbus2]# python i2ctest.py 
Traceback (most recent call last):
  File "i2ctest.py", line 5, in <module>
    bus.i2c_rdwr(msg)
  File "/root/smbus2/smbus2/smbus2.py", line 663, in i2c_rdwr
    ioctl(self.fd, I2C_RDWR, ioctl_data)
OSError: [Errno 25] Inappropriate ioctl for device
(smbus2_venv) [root@pizero1 ~/smbus2]# 

It took me a while to resolve the correct ioctl macro I2CRDWR on FreeBSD - finally found the integer with help from a friend and this little program:

[root@pizero1 ~]# cat resolvemacro.c 
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>

typedef unsigned char u_char;

#include <dev/iicbus/iic.h>

int main(int argc, char *argv[])
{
        printf("0x%lx\n", I2CRDWR);
        return EXIT_SUCCESS;
}
[root@pizero1 ~]# cc -Wall -O2 resolvemacro.c 
[root@pizero1 ~]# ./a.out 
0x80086906
[root@pizero1 ~]# 

After putting the value 0x80086906 in this line: https://github.com/kplindegaard/smbus2/blob/master/smbus2/smbus2.py#L33 the error changes, so I think the ioctl is correct:

(smbus2_venv) [root@pizero1 ~/smbus2]# python i2ctest.py 
Traceback (most recent call last):
  File "i2ctest.py", line 5, in <module>
    bus.i2c_rdwr(msg)
  File "/root/smbus2/smbus2/smbus2.py", line 663, in i2c_rdwr
    ioctl(self.fd, I2C_RDWR, ioctl_data)
OSError: [Errno 5] Input/output error
(smbus2_venv) [root@pizero1 ~/smbus2]# 

Something is still wrong though.

This is how far I got in the weekend. I will keep at it, but if you have any ideas I am all ears :)

Thanks! :+1:

tykling commented 3 years ago

OK, playing a bit more with it, I appear to have discovered that it does work, but on some unexpected addresses. Changing my test script to loop over 0-1024 and reading from each turns up my sensor which has i2c address 0x48, but it responds on 144 and 145, and again on 400 and 401, and again on 656 and 657 and so on.

The sensor which has i2c address 0x60 responds on 192 and 193, and again on 448 and 449, and so on.

(smbus2_venv) [root@pizero1 ~/smbus2]# cat i2ctest.py 
from smbus2 import SMBus, i2c_msg

with SMBus("/dev/iic0") as bus:
    for i in range(0,1024):
        msg = i2c_msg.read(i, 2)
        try:
            bus.i2c_rdwr(msg)
            print(f"response from {i}")
            for value in msg:
                print(value)
        except:
            #print("fail")
            pass

(smbus2_venv) [root@pizero1 ~/smbus2]# 
(smbus2_venv) [root@pizero1 ~/smbus2]# python i2ctest.py 
response from 144
9
198
response from 145
9
198
response from 192
0
0
response from 193
0
0
response from 400
9
198
response from 401
9
198
response from 448
0
0
response from 449
0
0
response from 656
9
198
response from 657
9
198
response from 704
0
0
response from 705
0
0
response from 912
9
199
response from 913
9
199
response from 960
0
0
response from 961
0
0
(smbus2_venv) [root@pizero1 ~/smbus2]# 

Great, finally some progress \o/ :+1: Can you spot why:

  1. There appears to be an offset so the two sensors I have connected on addresses 48 and 60 appear to be responding on different addresses. The sensor on 48 is responding on 144 and 145, the sensor on 60 is responding on 192 and 193 (and again every time it overflows of course).
  2. Why is each sensor active on two different addresses? Is this somehow related to the read/write bit being "counted"?

Thanks again!

valpackett commented 3 years ago

Is this somehow related to the read/write bit being "counted"?

Yes, you're not supposed to pass raw addresses anywhere, the kernel always works with (addr << 1) | rw. See iic(4).

It's a bit confusing that you mixed hex and dec in the comment ;) but yeah, 0x60 << 1 == 192. With your code how it is, it essentially takes (addr << 1) | rw from the user instead of taking addr.

BTW, SMBus is a completely separate API on completely separate devices, see smb(4). The iicsmb driver can do SMBus on top of I2C controllers, but note that there is no correlation guaranteed between /dev/iicN and /dev/smbN device numbers. So the Linux thing of supporting both I2C and SMBus API on the same /dev/i2c-N device won't work :/

tykling commented 3 years ago

Hello :) I haven't forgotten or given up on this. I took over my new house on february 15th and haven't had much time since. I will get back to it asap!

brd commented 2 years ago

@tykling I hope you are enjoying the new house :) I am wondering if you have had a chance to get back to this as I am interested as well.

0xc0decafe commented 2 years ago

As I was missing this as well and had some spare time, I've been so bold and created a PR implementing support for FreeBSD. Tested on a Raspberry PI 3B. Have fun!

brd commented 2 years ago

Thank you @0xc0decafe! #81