1

I have a PCA9535 GPIO Expander board connected through I²C to my Raspberry Pi. I am able to set the GPIO expander output pins (to high) using the i2cset command:

sudo i2cset 1 0x20 0x02 0xFF   // 0x20 (gpio expander), and register 0x02

I came across a kernel driver for the PCA953X and loaded the kernel module gpio-pca953x.ko + modified the /boot/config.txt to include the dts overlay for the expander. When I run i2detect -y 1 (for i2c-1 bus), I can see "UU" at the 0x20 slave address, which corroborates that the driver is managing it.

If I wanted to do something comparable to what I did with the i2cset command programmatically, what API / interface should I use such that I make use of the PCA953X driver that's installed? As you can see from the code below, nothing there suggests that I would be utilizing PCA953X:

  int file;
  int adapter_nr = 1; 
  char filename[20];

  snprintf(filename, 19, "/dev/i2c-%d", adapter_nr);
  file = open(filename, O_RDWR);
  if (file < 0) {
    exit(1);
  }

  // Writes 0xFF to register 0x02
  uint8_t cmd[2]  = {0x02, 0XFF};
  struct i2c_msg msg = {
    .addr = 0x20,
    .flags = 0,
    .len = sizeof(cmd)/sizeof(uint8_t),
    .buf = cmd
  };
 
  struct i2c_rdwr_ioctl_data tx = {
    .msgs = &msg,
    .nmsgs = 1
  };

  ioctl(file, I2C_RDWR, &tx);

Is there a programming route that involves utilizing the PCA953X driver? What does having that module actually do?

0andriy
  • 4,183
  • 1
  • 24
  • 37
Tung
  • 5,334
  • 1
  • 34
  • 41
  • @Rob, Thanks for the feedback. I felt this fell under "a specific programming problem," as I wanted to be able to send commands to the i2c device programmatically, presumably through the help of the PCA953X driver. Which exchange site would you suggest? – Tung Sep 07 '22 at 05:14
  • @Rob this is unquestionably about programming. It might be a candidate to close because of "seeking recommendations" but is certainly on-topic here. Did you type your comment in response to a different question and then paste it in the wrong window or something? – Tom V Sep 07 '22 at 09:06
  • @Tung the documentation is https://www.kernel.org/doc/Documentation/i2c/dev-interface and you should read the source code to i2cset for an example of how to use it. – Tom V Sep 07 '22 at 09:06
  • Sorry. Read it too fast before going to bed. – Rob Sep 07 '22 at 09:33
  • @TomV, thanks! I did actually come across i2-dev documentation and a few others that showed how to directly send i2c messages to the i2c bus through ioctl, but this route seems like it's not making use of the PCA9535 gpio expander driver (i.e. if I unload PCA953x module, my code would still work). There's a gap in my understanding of the PCA953x driver, and how one makes use of it programmatically. I will update my question to call out programming with PCA953x driver. – Tung Sep 07 '22 at 15:34
  • 1
    @Tung in that case you need to read the kernel documentation for the GPIO ioctls and your device might be named something like /dev/gpiochipN – Tom V Sep 07 '22 at 17:22

1 Answers1

0

Thanks to @TomV for pointing it out in the comments. The PCA953X driver provides a new character device in the form of "gpiochipN". In my case, it was gpiochip2. I had not noticed this extra gpiochip in /dev as I was not looking for it. Because this is a character device, I was able to use ioctl to interact with the lines:

 int fd, ret;
 fd = open("/dev/gpiochip2", O_RDONLY);
 if (fd < 0) {
     printf("Unable to open: %s", strerror(errno));
     return;
 }

 struct gpiohandle_request req;
 req.lineoffsets[0] = 0;
 req.lineoffsets[1] = 1;
 req.lineoffsets[2] = 2;
 req.lineoffsets[3] = 3;
 req.lineoffsets[4] = 4;
 req.lineoffsets[5] = 5;
 req.lineoffsets[6] = 6;
 req.lineoffsets[7] = 7;
 req.lines = 8;
 req.flags = GPIOHANDLE_REQUEST_OUTPUT;
 ret = ioctl(fd, GPIO_GET_LINEHANDLE_IOCTL, &req);
 if (-1 == ret) {
   printf("Failed to get line handle:%s\n", strerror(ret));
   close(fd);
   return;
 }

 // Sets all 8 lines to high (equivalent to setting register 0x3 to 0b11111111 or 0xFF)
 struct gpiohandle_data data;
 data.values[0] = 1;
 data.values[1] = 1;
 data.values[2] = 1;
 data.values[3] = 1;
 data.values[4] = 1;
 data.values[5] = 1;
 data.values[6] = 1;
 data.values[7] = 1;
 ret = ioctl(req.fd, GPIOHANDLE_SET_LINE_VALUES_IOCTL, &data);
 if (-1 == ret) {
   printf("Failed to set line value\n");
 }
 else {
   close(req.fd);
 }

 close(fd);

The above code interacts with the gpiochip2 character device, which is made possible by having the PCA953X installed. Without it, I would have to read and write to the registers through the i2c bus as was posted in my original question.

0andriy
  • 4,183
  • 1
  • 24
  • 37
Tung
  • 5,334
  • 1
  • 34
  • 41
  • While the above seems more lengthy, the first advantage I can see is that I can control the output of specific pins whereas, going the register route, I would need to read the existing register, and then flip the bit corresponding to the pin that I want to toggle to preserve the values of the other pins. – Tung Sep 09 '22 at 07:26
  • Also you can utilize `libgpiod`. Not so much code needs to be written and more flexible. – 0andriy Sep 09 '22 at 19:44
  • Communicating with hardware directly in multi-tasking OS is equal to shooting yourself into foot. Either don't use OS' driver (not recommended in 99.99% cases), or don't access device directly. – 0andriy Sep 09 '22 at 19:46