Help with DT Overlay for IMU on Custom Carrier Board

Hi, I have been stuck looking for a way forward with getting an IMU working with the LSM6DSL IMU.

Thus far, my progress is that I have a gpio expander being recognized and working.

Following is the dts file I am using:

// SPDX-License-Identifier: GPL-2.0-or-later OR MIT
/*
 * Copyright 2023 Your Company Name
 */

/dts-v1/;
/plugin/;

/ {
    compatible = "toradex,verdin-imx8mm";
};

&i2c1 {
    status = "okay";

    pca953x: gpio@21 {
        compatible = "nxp,pca953x";
        reg = <0x21>;
        gpio-controller;
        #gpio-cells = <2>;
    };

    lsm6_imu: imu@6a {
        compatible = "st,lsm6dsl";
        reg = <0x6a>;
        interrupt-parent = <&pca953x>;
        interrupts = <504 0x2>, <505 0x2>; // INT1 on pin 9 (504), INT2 on pin 10 (505) (0x2 for falling edge)
        cs-gpios = <&pca953x 503 0>; // CS on pin 8 (503), 0 for active low
    };
};

trying to configure without the interrupt parent, interrupts and cs-gpios gives error that the driver isnt able to read the whoami register.

including said lines removes the error but the IMU does not show up in the iio devices.

I am also attaching the schematic for anyone to be able to help.

image

I took the gpio pin numbers from 496 as it appears on the gpiochip496 inside /sys/dev/gpio

Apparently, GPIO expander is working as i tested it by exporting a pin and using echo to change its values.

Hi @geopaxpvtltd ,

I couldn’t find any driver that uses the compatible field nxp,pca953x as you defined in you overlay. I looked into kernel versions toradex_5.4-2.3.x-imx and toradex_5.15-2.2.x-imx which are used on BSP 5 and 6, respectively.

Make sure the compatible field is valid. You can take a look at the gpio-pca95xx bindings here:

I took the gpio pin numbers from 496 as it appears on the gpiochip496 inside /sys/dev/gpio

You put the GPIO interrupt pin numbers (504 and 505) based on gpiochip496 as it appears on /sys/dev/gpio? I’m not sure that’s the numbering scheme used here.

From what I understood reading this document interrupts.txt « interrupt-controller « bindings « devicetree « Documentation - linux-toradex.git - Linux kernel for Apalis, Colibri and Verdin modules the index should be relative to the controller. So for example if GPIO1 IO3 is used as an interrupt the value of the fields should be something like this:

	interrupt-parent = <&gpio1>;
	/* PMIC PCA9450 PMIC_nINT GPIO1_IO3 */
	interrupts = <3 IRQ_TYPE_LEVEL_LOW>;

In fact, I got the example above from the Verdin iMX8M Mini device tree.

Also, what BSP version are you working on? Are you using Torizon OS? Which version?

Best regards,
Lucas Akira

Thank you for your reply @lucas_a.tx

So far I had just been shooting in the dark with trial and errors.

anyway, I changed the gpio compatible field to nxp,pcal6416. but now it gives the following errors in dmesg but the gpiochip496 remains in the /sys/class/gpio:

// SPDX-License-Identifier: GPL-2.0-or-later OR MIT
/*
 * Copyright 2023 Your Company Name
 */

/dts-v1/;
/plugin/;

#include <dt-bindings/gpio/gpio.h>
#include <dt-bindings/interrupt-controller/irq.h>

/ {
    compatible = "toradex,verdin-imx8mm";
};

&i2c1 {
    status = "okay";

    pcal6416: gpio@21 {
        compatible = "nxp,pcal6416";
        reg = <0x21>;
        gpio-controller;
        #gpio-cells = <2>;
    };

    lsm6_imu: imu@6a {
        compatible = "st,lsm6dsl";
        reg = <0x6a>;
        interrupt-parent = <&pcal6416>;
        interrupts = <504 IRQ_TYPE_EDGE_FALLING>, <505 IRQ_TYPE_EDGE_FALLING>; // INT1 on pin 9 (504), INT2 on pin 10 (505)
        cs-gpios = <&pcal6416 503 GPIO_ACTIVE_LOW>; // CS on pin 8 (503)
    };
};
torizon@verdin-imx8mm-14756428:/$ dmesg | grep -i pca
[    1.215051] pca953x 0-0021: supply vcc not found, using dummy regulator
[    1.215188] pca953x 0-0021: using no AI
[    1.216070] pca953x 0-0021: failed writing register
[    1.235246] nxp-pca9450 0-0025: pca9450a probed.
[    1.241782] pca953x 3-0021: using no AI

previously, i was struggling with getting the include statements to work but then i discovered the include-directory in the yaml file. My tcbuild.yaml is:

# Documentation https://developer.toradex.com/torizon/os-customization/torizoncore-builder-workflow/#customizing-the-configuration-file
# Configuration file outline:
# ⚠️ Mouse hover to the properties to see documentation
# ⚠️ ctrl+space inside a object or property to get the autocompletion tips
input:
  # Input section items (required).
  # ⚠️ change it for your needs
  easy-installer:
    # >> Choose one of the options (REQUIRED):
    # >> (1) Image as local folder or tarball.
    # local: torizon-core-docker-verdin-imx8mm-Tezi_6.3.0+build.4.tar
    # >> (2) Remote file (optionally with a filename and or a sha256 checksum):
    # remote: "https://artifacts.toradex.com/.../torizon-core-docker-apalis-imx8-Tezi_5.1.0%2Bbuild.1.tar"
    # remote: "https://artifacts.toradex.com/.../torizon-core-docker-colibri-imx6-Tezi_5.0.0-devel-202009%2Bbuild.2.tar;filename=torizon-core-docker-colibri-imx6-Tezi_5.0.0-devel-202009+build.2.tar;sha256sum=368595fa3fb00af9604e70311de3b04df6b30b280deec2f8918c46f479026ddb"
    # >> (3) Image specification (URL will be generated by the tool)
    toradex-feed:
      version: "6.4.0"
      release: monthly
      machine: verdin-imx8mm
      distro: torizon-rt
      variant: torizon-core-docker
      build-number: "13"
      build-date: "202309"

customization:
  # Customization section items.
  splash-screen: splashScreen.png
  # >> Directories overlayed to the base OSTree
  filesystem:
     - changes1/
  device-tree:
    # >> Directories where to look for include files.
    include-dirs:
      - linux/include/
    # >> Custom device tree source:
    # custom: device-trees/dts-arm32/imx6ull-colibri-eval-v3.dts
    # >> Device-tree overlays configuration:
    overlays:
      # >> Whether to ignore all overlays from the base image (or ostree
      # >> archive in the future).
      # clear: false
      # >> Specific overlays not to use from base image (useful only when
      # >> clear is false and not DT has been selected).
      # remove:
        # - apalis-imx8_hdmi_overlay.dtbo
      # >> Overlays to add to output image.
      add:
        # - device-trees/overlays/display-edt7_overlay.dts
        - custom-device-trees/overlays/geopax-imx8mm_carrierV2-overlay.dts
        - custom-device-trees/overlays/verdin-imx8mm_nau8822-btl_overlay.dts
  kernel:
    # >> Custom kernel arguments.
    # arguments:
      # - key1=val1
      # - key2=val2
    # >> Modules to build and possibly load automatically.
    # modules:
      # - source-dir: virtual_touchscreen/
      #   autoload: false
    modules:
      - source-dir: gpio-pca953x
        autoload: true
      - source-dir: power-pac193x
        autoload: true
      - source-dir: wifi-bw112d
        autoload: true

output:
  # Output section items (required).
  # ⚠️ change it for your needs
  easy-installer:
    local: GeopaxTorizon-TCB-CUSTOM
  ostree:
    branch: geopax-torizon-cont
    commit-subject: GeopaxTorizon

does this mean I should do something like this:

&i2c1 {
    status = "okay";

    pcal6416: gpio@21 {
        compatible = "nxp,pcal6416";
        reg = <0x21>;
        gpio-controller;
        #gpio-cells = <2>;
    };

    lsm6_imu: imu@6a {
        compatible = "st,lsm6dsl";
        reg = <0x6a>;
        interrupt-parent = <&pcal6416>;
        interrupts = <9 IRQ_TYPE_EDGE_FALLING>, <10 IRQ_TYPE_EDGE_FALLING>; // INT1 on pin 9 (504), INT2 on pin 10 (505)
        cs-gpios = <&pcal6416 8 GPIO_ACTIVE_LOW>; // CS on pin 8 (503)
    };
};

Hi @geopaxpvtltd ,

From your tcbuild.yaml file I see that you’re compiling the pca953x driver as an out-of-tree module, but this driver is already included in our Torizon OS 6.4.0 image:

torizon@verdin-imx8mp-14777535:~$ sudo tdx-info

Software summary
------------------------------------------------------------
Bootloader:               U-Boot
Kernel version:           5.15.129-6.4.0+git.67c3153d20ff #1-TorizonCore SMP PREEMPT Wed Sep 27 12:30:36 UTC 2023
Kernel command line:      root=LABEL=otaroot rootfstype=ext4 quiet logo.nologo vt.global_cursor_default=0 plymouth.ignore-serial-consoles splash fbcon=map:3 ostree=/ostree/boot.1/torizon/460c0c1dde52ce6a4fd14e6ff7399ad1c879175a9ffa047acbb97523d9376591/0
Distro name:              NAME="TorizonCore"
Distro version:           VERSION_ID=6.4.0-build.5
Hostname:                 verdin-imx8mp-14777535
------------------------------------------------------------

Hardware info
------------------------------------------------------------
HW model:                 Toradex Verdin iMX8M Plus WB on Verdin Development Board
Toradex version:          0058 V1.1A
Serial number:            14777535
Processor arch:           aarch64
------------------------------------------------------------
torizon@verdin-imx8mp-14777535:~$ zcat /proc/config.gz | grep -i pca953x
CONFIG_GPIO_PCA953X=y
CONFIG_GPIO_PCA953X_IRQ=y

I’m not sure how the kernel behaves when you compile and include a driver as a module when the kernel already has it. Maybe that’s related to the issue?

does this mean I should do something like this

I think so, if the indexes correspond to the interrupt-parent index. Can you test it?

Best regards,
Lucas Akira

Oh that has no effect. I was just trying to learn that. I have removed it and made a new build. but the issue still remains.

&i2c1 {
    status = "okay";

    pcal6416: gpio@21 {
        compatible = "nxp,pcal6416";
        #gpio-cells = <2>;
        gpio-controller;
        reg = <0x21>;
    };

    lsm6_imu: imu@6a {
        compatible = "st,lsm6dsl";
        reg = <0x6a>;
        interrupt-parent = <&pcal6416>;
        interrupts = <9 IRQ_TYPE_EDGE_FALLING>, <10 IRQ_TYPE_EDGE_FALLING>; // INT1 on pin 9 (504), INT2 on pin 10 (505)
        cs-gpios = <&pcal6416 8 GPIO_ACTIVE_LOW>; // CS on pin 8 (503)
    };
};

the gpiochip496 inside sys/class/gpio is still available.

the imu doesnt show up in the iio devices.

lsmod says the driver is being loaded.

torizon@verdin-imx8mm-14756428:~$ lsmod | grep st_
st_lsm6dsx_spi         20480  0
st_lsm6dsx_i2c         20480  0
st_lsm6dsx             45056  2 st_lsm6dsx_i2c,st_lsm6dsx_spi
kfifo_buf              16384  2 industrialio_triggered_buffer,st_lsm6dsx
industrialio           90112  5 industrialio_triggered_buffer,kfifo_buf,st_lsm6dsx,ti_ads1015,power_pac193x

there are no messages in dmesg:


torizon@verdin-imx8mm-14756428:/dev$ dmesg | grep -i lsm
[    0.000616] LSM: Security Framework initializing

However, when i remove the interrupt parent, interrupts and cs gpios from the dts, it gives messages like unable to read whoami register

torizon@verdin-imx8mm-14756428:~$ dmesg | grep -i lsm
[    0.000613] LSM: Security Framework initializing
[    6.806207] st_lsm6dsx_i2c 0-006a: supply vdd not found, using dummy regulator
[    6.806790] st_lsm6dsx_i2c 0-006a: supply vddio not found, using dummy regulator
[    6.870904] st_lsm6dsx_i2c 0-006a: failed to read whoami register

@lucas_a.tx

Update!

I got it to partially work using the following dts:

// SPDX-License-Identifier: GPL-2.0-or-later OR MIT
/*
 * Copyright 2023 Your Company Name
 */

/dts-v1/;
/plugin/;

#include <dt-bindings/gpio/gpio.h>
#include <dt-bindings/interrupt-controller/irq.h>

/ {
    compatible = "toradex,verdin-imx8mm";
};

&i2c4 {
    #address-cells = <1>;
    #size-cells = <0>;
    status = "okay";
    

    gpio_exp_cb: gpio_exp_cb@21 {
        compatible = "nxp,pcal6416";
        #gpio-cells = <2>;
        gpio-controller;
        reg = <0x21>;
        status = "okay";
    };

    lsm6_imu: imu@6a {
        compatible = "st,lsm6dsl";
        reg = <0x6a>;
        status = "okay";
        // interrupt-parent = <&gpio_exp_cb>;
        // interrupts = <9 IRQ_TYPE_LEVEL_HIGH>, <10 IRQ_TYPE_LEVEL_HIGH>; // INT1 on pin 9 (504), INT2 on pin 10 (505)
        // cs-gpios = <&gpio_exp_cb 8 GPIO_ACTIVE_HIGH>; // CS on pin 8 (503)
    };
};

Could you explain how it started working? I am using the i2c1 that is exposed byt the verdin modul. why did it work with &i2c4? and what difference do the address cells and size cells makes? and where does one get these values from?

the following is the log from kernel:

[    1.215289] rtc-ds1307 0-0032: hctosys: unable to read the hardware clock
[    1.226596] i2c i2c-3: Failed to register i2c client pcal6416 at 0x21 (-16)
[    1.226605] i2c i2c-3: of_i2c: Failure registering /soc@0/bus@30800000/i2c@30a50000/gpio-expander@21
[    1.226617] i2c i2c-3: Failed to create I2C device for /soc@0/bus@30800000/i2c@30a50000/gpio-expander@21
[    2.525680] regulator-dummy: Underflow of regulator enable count
[    2.751940] [drm:drm_bridge_attach] *ERROR* failed to attach bridge /soc@0/bus@32c00000/mipi_dsi@32e10000 to encoder DSI-34: -517
[    2.751964] imx_sec_dsim_drv 32e10000.mipi_dsi: Failed to attach bridge: 32e10000.mipi_dsi
[    2.751970] imx_sec_dsim_drv 32e10000.mipi_dsi: failed to bind sec dsim bridge: -517
Starting version 250.5+
[    6.648232] nau8822 3-001a: Failed to issue reset: -6
[    6.648291] ina2xx 3-0040: error configuring the device: -6
[    9.785024] mcp251xfd spi2.0 (unnamed net_device) (uninitialized): Failed to detect MCP251xFD (osc=0x00000000).

it says failed to register i2c client pcal6416 but the gpios seem to be working? also, the imu appears in the iio devices

Hi @geopaxpvtltd ,

I got it to partially work using the following dts

What exactly is not completely working? Are the interrupts working, or you don’t need them?


Could you explain how it started working? I am using the i2c1 that is exposed byt the verdin modul. why did it work with &i2c4?

I think you physically connected the IMU to the i2c4 pins from the start and you’re getting confused about the naming scheme of the peripherals. In the Verdin iMX8M Mini datasheet there’s the Verdin Standard Function names which is how we name the pins that have common functions for the whole Verdin SoM family.

But there’s also the pin Ball name, which is the name given by NXP and the device tree nodes are usually named after it.

The Verdin Standard names for the pins don’t necessarily follow the same peripheral numbering used in the ball name, as you can see in this table from the Verdin iMX8M Mini datasheet:

So the Verdin standard I2C1 pins actually correspond to the i2c4 node. The Verdin iMX8M Mini device tree even has a comment hinting at this:

/* Verdin I2C_1 */
&i2c4 {
	clock-frequency = <400000>;
	pinctrl-names = "default", "gpio";
	pinctrl-0 = <&pinctrl_i2c4>;
	pinctrl-1 = <&pinctrl_i2c4_gpio>;
	scl-gpios = <&gpio5 20 (GPIO_ACTIVE_HIGH | GPIO_OPEN_DRAIN)>;
	sda-gpios = <&gpio5 21 (GPIO_ACTIVE_HIGH | GPIO_OPEN_DRAIN)>;
[...]

and what difference do the address cells and size cells makes? and where does one get these values from?

These are generic device tree properties used by all addressable devices. Basically these two tell how fill the reg property. You can see more details here:

Each device sets these fields differently and you need to follow its corresponding devicetree binding documentation found in the kernel sources.


it says failed to register i2c client pcal6416 but the gpios seem to be working? also, the imu appears in the iio devices

If you take a look at the Verdin iMX8M Mini device tree sources you can see that there actually is a GPIO expander already defined at I2C4 address 0x21 called gpio-expander:

&i2c4 {
	clock-frequency = <400000>;
	pinctrl-names = "default", "gpio";
	pinctrl-0 = <&pinctrl_i2c4>;
	pinctrl-1 = <&pinctrl_i2c4_gpio>;
	scl-gpios = <&gpio5 20 (GPIO_ACTIVE_HIGH | GPIO_OPEN_DRAIN)>;
	sda-gpios = <&gpio5 21 (GPIO_ACTIVE_HIGH | GPIO_OPEN_DRAIN)>;

	gpio_expander_21: gpio-expander@21 {
		compatible = "nxp,pcal6416";
		#gpio-cells = <2>;
		gpio-controller;
		reg = <0x21>;
		vcc-supply = <&reg_3p3v>;
		status = "disabled";
	};
[...]

Incidentally, this expander refers to the same nxp,pcal6416 compatible field. I think the GPIO expander node defined in your overlay is getting probed first, then when gpio-expander tries to initialize it naturally fails as the 0x21 address is already in use.

This probably explains the ‘fail to register i2c client’ messages you saw.

Best regards,
Lucas Akira

1 Like

Thank you so much for your guidance @lucas_a.tx . I have one more question.

If there is a driver being added into the latest kernel for TPS25750, can i download the driver and add it as an external module?

pl see this:
https://lwn.net/Articles/944694/

Hi @geopaxpvtltd ,

Glad I was able to help!

If there is a driver being added into the latest kernel for TPS25750, can i download the driver and add it as an external module?

You can try to do this via TorizonCore Builder, but please be aware that things may not work completely: You’re trying to add a driver meant for a newer kernel version, so some functionalities and behaviors expected by the driver may or may not be present in the older kernel.

TorizonCore Builder wasn’t originally developed with this use-case in mind either, so we cannot recommend using it for this purpose.

Best regards,
Lucas Akira

1 Like

HI @lucas_a.tx !

I have one more question related to this. do I need to add pin hog properties for expander gpio pins if the said pins are being used in a container application?

secondly, for a pcal6416, when I add hogs for pins, they seem to work for the first gpio bank only. on the second bank, i get errors like:

[    1.304130] gpio-506 (imu_int2): hogged as output/low
[    1.304689] gpio-505 (imu_int1): hogged as output/low
[    1.305255] gpio-504 (imu_cs): hogged as output/low
[    1.410051] gpio gpiochip5: (3-0021): setup of own GPIO empty1 failed
[    1.416512] requesting hog GPIO empty1 (chip 3-0021, offset 7) failed, -110
[    1.423501] gpiochip_add_data_with_key: GPIOs 496..511 (3-0021) failed to register, -110

are gpio-hogs supposed to enable internal pulls on these gpios or are external pull resistors necessary?

Hi @geopaxpvtltd ,

You add pin hog properties in the GPIO expander to configure certain pin status on boot. This isn’t strictly required in order to use the pins in userspace, so you don’t need to define pin hogging if you don’t plan on doing automatic pin configuration during boot.

are gpio-hogs supposed to enable internal pulls on these gpios or are external pull resistors necessary?

I believe you can configure this behavior in the device tree via the gpios properties in the pin-hog node, as you can see in the example linked here:

You can check the kernel documentation for more details on the meaning of each field in gpios:

Best regards,
Lucas Akira

1 Like

Hi @lucas_a.tx !

Thank you for your patience with me. I have followed the documentation but I have been unable to get a pin hog working. even after setting the output as high, the actual output is low. there are no external pull resistors installed. The following is the definintion:

   gpio_exp_cb: gpio_exp_cb@21 {
      compatible = "nxp,pcal6416";
      #gpio-cells = <2>;
      gpio-controller;
      reg = <0x21>;
      status = "okay";
      gpio-line-names = "rad1_m1",
                        "rad1_m0",
                        "rad2_en",
                        "rad2_conf",
                        "rad2_pwr_en",
                        "rad1_pwr_en",
                        "rad1_aux";
                  rad1-aux-pin-hog {
                     gpio-hog;
                     gpios = <6 GPIO_ACTIVE_LOW>;
                     output-high;
                     line-name = "rad1_aux";
                  };

But the output as reported by the kernel debug is:
image

As you can see it report output lo even when it is defined as output-high in the dts.

Best Regards.

Hi @geopaxpvtltd ,

From your Device Tree I see that you have configured the pin as ACTIVE LOW, which means that a low physical level will be interpreted as logical value 1, and a high physical level will be interpreted as logical value 0.

So when you set output-high in the device tree you’re setting logical value 1 i.e. low physical level.

From this commit message we can see that the output you got (cat /sys/kernel/debug/gpio I believe) shows the physical level of the pins, not the logical:

This at least makes debugfs print if the line is active high or low. That is pretty helpful as what we display as “lo” or “hi” is the raw physical level of the line.

lo is physical low i.e. logical high, so your device tree is working and the pin is behaving as it should.


Checking the kernel source code we can see that the GPIO DT properties output-high/output-low set logical values:

  • These properties are handled by function of_parse_own_gpio() (gpiolib-of.c) and stored in variable dflags;
  • dflags is used in gpiod_configure_flags() (gpiolib.c) where it is turned into value and passed to gpiod_direction_output();
  • gpiod_direction_output() (gpiolib.c) has a comment mentioning that value is the logical value of the GPIO i.e. it takes ACTIVE_LOW into account if it is set:
/**
 * gpiod_direction_output - set the GPIO direction to output
 * @desc:	GPIO to set to output
 * @value:	initial output value of the GPIO
 *
 * Set the direction of the passed GPIO to output, such as gpiod_set_value() can
 * be called safely on it. The initial value of the output must be specified
 * as the logical value of the GPIO, i.e. taking its ACTIVE_LOW status into
 * account.
 *
 * Return 0 in case of success, else an error code.
 */

Best regards,
Lucas Akira

1 Like

Hi @lucas_a.tx !

This is some great explanation. Would have had me take at least a month to figure this out on my own.

Thank you for your time!

Best Regards.

1 Like