Home  >  Edison  >  Current State

LibIIO

IIO

IIO is the kernel framework providing drivers for IIO devices. Most IIO devices are either on a I2C or SPI bus. Once the kernel driver module is loaded and the driver probed the user space library libiiod can be used to access them.

We are trying to build drivers for every available IIO device, but these are being added quickly. The idea is that Intel Edison provides a unique platform to easily add an I2C or SPI device to experiment on. If something is missing please raise an issue or send a patch.

Since there are so many drivers we do not build them into the kernel, but provide them as modules. The reasoning here is, eMMC space is relatively cheap while RAM memory is easily consumed.

If you want to optimize for disk space and remove drivers you need to take a look at meta-intel-edison/meta-intel-edison-bsp/recipes-kernel/linux/files/iio.cfg.

The following is a walk through on how to get a I2C sensor to work.

Example getting lightsensor TSL2561 to work

Check if the device is supported by the kernel at all

The following site keep up-to-date lists of supported hardware for each kernel version cateee.net.

To find your device open lkddb-<kernel version>.list and find or grep your devices name. The latest kernel version is always here. Looking up TSL2561 we find:

i2c "tsl2561" : CONFIG_IIO CONFIG_SENSORS_TSL2563 : drivers/iio/light/tsl2563.c

This means, it’s a I2C device, supported by the IIO subsystem and support is builtin to the tsl2563 module.

Check it we are building the kernel module

Dive into meta-intel-edison/meta-intel-edison-bsp/recipes-kernel/linux/files/iio.cfg to see if we build the module:

CONFIG_SENSORS_TSL2563=m

This means, we build it as a module and the module is installed on the image.

Connect the device and try to find it from user space

This concerns hardware, so make sure you connect the device to the correct Edison-Arduino pin and correct voltage levels are selected.

In the case of the TSL2561 there’s many sources like GY 2561 where the module accepts 3.3V - 5V. We connect VCC (power header 3.3V), GND, SCL, SDA (digital header).

Load the i2c-dev module into the kernel

modprobe -i i2c-dev

and reset the power to the module

gpioset `gpiofind TRI_STATE_ALL`=0 && gpioset `gpiofind TRI_STATE_ALL`=1

then detect the device

root@edison:~# i2cdetect -y -r 6
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:                         -- -- -- -- -- -- -- -- 
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
30: -- -- -- -- -- -- -- -- -- 39 -- -- -- -- -- -- 
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
70: -- -- -- -- -- -- -- --                         

This means a device is present on address 39 and it’s not bound to any driver. In fact you can communicate to the device from user space now, for instance using MRAA. But that’s another example.

Bind the device from the command line

Using the info we have collected by now, insert the kernel module belonging to the device and bind it

modprobe tsl2563
echo "tsl2561 0x39" > /sys/bus/i2c/devices/i2c-6/new_device

The means the module tsl2563 is inserted, of which we know it provides support for the tsl2561 on address 0x39.

Now you will see:

root@edison:~# i2cdetect -y -r 6
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:                         -- -- -- -- -- -- -- -- 
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
30: -- -- -- -- -- -- -- -- -- UU -- -- -- -- -- -- 
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
70: -- -- -- -- -- -- -- --                         

The device is in use.

Communicate with the IIO device

For a quick lookup the iio_info command is provided.

root@edison:~# iio_info                      
Library version: 0.21 (git tag: 565bf68)
Compiled with backends: local xml ip usb serial
IIO context created with local backend.
Backend version: 0.21 (git tag: 565bf68)
Backend description string: Linux edison 6.1.0-rc6-edison-acpi-standard #1 SMP PREEMPT_DYNAMIC Mon Nov 21 00:02:16 UTC 2022 x86_64
IIO context has 2 attributes:
        local,kernel: 6.1.0-rc6-edison-acpi-standard
        uri: local:
IIO context has 4 devices:
...
        iio:device2: tsl2561
                3 channels found:
                        intensity_ir:  (input)
                        2 channel-specific attributes found:
                                attr  0: calibscale value: 1000
                                attr  1: raw value: 226
                        illuminance0:  (input)
                        1 channel-specific attributes found:
                                attr  0: input value: 29
                        intensity_both:  (input)
                        2 channel-specific attributes found:
                                attr  0: calibscale value: 1000
                                attr  1: raw value: 1149
                No trigger on this device
        iio_sysfs_trigger:
                0 channels found:
                2 device-specific attributes found:
                                attr  0: add_trigger ERROR: Permission denied (-13)
                                attr  1: remove_trigger ERROR: Permission denied (-13)
                No trigger on this device

This particular device has 2 light sensors, one IR and one ordinary light. From that the response of the human eye is calculated.

Python3 example on Edison

libiio tools are written in Python and can be used as an example code. For instance iio_attr.py

root@edison:~# iio_attr -c tsl2561 -u local: intensity_both raw
10455

Communicating with Edison from host iio_oscilloscope

See the instructions on how to build and use iio_oscilloscope or use the following for Ubuntu 22.10.

apt-get -y install libglib2.0-dev libgtk2.0-dev libgtkdatabox-dev libmatio-dev libfftw3-dev libxml2 libxml2-dev bison flex libavahi-common-dev libavahi-client-dev libcurl4-openssl-dev libjansson-dev cmake libaio-dev libserialport-dev
git clone https://github.com/analogdevicesinc/iio-oscilloscope.git
cd iio-oscilloscope
git checkout gtk3transition-master-rebase
mkdir build && cd build
cmake ..
cmake --build .
sudo cmake --install .

Start with:

LD_LIBRARY_PATH=/usr/local/lib /usr/local/bin/osc

You can access the edison through the debug tab.

Alternatively, from the command line:

ferry@delfion:~$ iio_attr -c tsl2561 -u ip:edison.local intensity_both raw
9747

Writing an ACPI ASL

Look for a similar device in meta-acpi/recipes-bsp/acpi-tables/samples/edison/, in this case something with an analog input and I2C bus. For instance ads1015.asl.

DefinitionBlock ("ads1015.aml", "SSDT", 5, "", "ADS1015", 1)
{
    External (\_SB.PCI0.I2C1, DeviceObj)

    Scope (\_SB.PCI0.I2C1)
    {
        Device (TIAD)
        {
            Name (_HID, "PRP0001")
            Name (_DDN, "TI ADS1015 ADC")
            Name (_CRS, ResourceTemplate () {
                I2cSerialBus (
                    0x48,                   // Bus address
                    ControllerInitiated,    // Don't care
                    400000,                 // 400 kHz
                    AddressingMode7Bit,     // 7-bit addressing
                    "\\_SB.PCI0.I2C1",      // I2C host controller
                    0                       // Must be 0
                )
            })

            /*
             * See Documentation/devicetree/bindings/iio/adc/ads1015.txt
             * for more information about these bindings.
             */
            Name (_DSD, Package () {
                ToUUID("daffd814-6eba-4d8c-8a91-bc9bbf4aa301"),
                Package () {
                    Package () { "compatible", Package () { "ti,ads1015" } },
                }
            })
        }
    }
}

Now look up documentation Documentation/devicetree/bindings/iio/light/amstaos,tsl2563.yaml

description: |
  Ambient light sensor with an i2c interface.

properties:
  compatible:
    enum:
      - amstaos,tsl2560
      - amstaos,tsl2561
      - amstaos,tsl2562
      - amstaos,tsl2563

  reg:
    maxItems: 1

  amstaos,cover-comp-gain:
    description: Multiplier for gain compensation
    $ref: /schemas/types.yaml#/definitions/uint32
    enum: [1, 16]

required:
  - compatible
  - reg

additionalProperties: false

examples:
  - |
    i2c {

      #address-cells = <1>;
      #size-cells = <0>;

      light-sensor@29 {
        compatible = "amstaos,tsl2563";
        reg = <0x29>;
        amstaos,cover-comp-gain = <16>;
      };
    };
...

And then change the things we know:

DefinitionBlock ("tsl2561.aml", "SSDT", 5, "", "TSL2561", 1)
{
/*
 * DefinitionBlock (AMLFileName, TableSignature, ComplianceRevision,
 * OEMID, TableID, OEMRevision)
 *
 * AMLFileName — Name of the AML file (string). Can be a null string.
 * TableID — A specific identifier for the table (8-character string)
 */
    External (\_SB.PCI0.I2C6, DeviceObj)
/*
 * I2C6 is defined in another table, will be resolved at table load
 */
    Scope (\_SB.PCI0.I2C6)
    {
        Device (AMLS)                       // A name for the device
        {
            Name (_HID, "PRP0001")
            Name (_DDN, "AMS TSL2561 Light Sensor")
            Name (_CRS, ResourceTemplate () {
                I2cSerialBus (
                    0x39,                   // Bus address
                    ControllerInitiated,    // Don't care
                    400000,                 // 400 kHz
                    AddressingMode7Bit,     // 7-bit addressing
                    "\\_SB.PCI0.I2C6",      // I2C host controller
                    0                       // Must be 0
                )
            })

            /*
             * See Documentation/devicetree/bindings/iio/light/amstaos,tsl2563.yaml
             * for more information about these bindings.
             */
            Name (_DSD, Package () {
                ToUUID("daffd814-6eba-4d8c-8a91-bc9bbf4aa301"),
                Package () {
                    Package () { "compatible", Package () { "amstaos,tsl2561" } },
                }
            })
        }
    }
}

Loading the ASL

First reset the power to the module, then manually load the table through configfs:

root@edison:~# gpioset `gpiofind TRI_STATE_ALL`=0 && gpioset `gpiofind TRI_STATE_ALL`=1
root@edison:~# mkdir /sys/kernel/config/acpi/table/tsl2561
root@edison:~# cat tsl2561.aml > /sys/kernel/config/acpi/table/tsl2561/aml
root@edison:~# journalctl -b -0 -e

Nov 26 21:48:42 edison kernel: ACPI: Host-directed Dynamic ACPI Table Load:
Nov 26 21:48:42 edison kernel: ACPI: SSDT 0xFFFF9F7C884E7800 000117 (v05        TSL2561  00000001 INTL 20200925)
Nov 26 21:48:42 edison kernel: tsl2563 i2c-PRP0001:02: model 5, rev. 0

Unloading the ASL

Once a table is loaded, you can not load it again. The nice thing about using configfs to load an ASL table is that you can unload it. After this you can load a modified This makes debugging a lot faster.

root@edison:~# rmdir /sys/kernel/config/acpi/table/tsl2561

Extending the ASL to support an interrupt

The TSL2561 has an additional interrupt pin that can be set to generate an interrupt (that in turn generates an event) when there is a significant change in light. We can extend the ASL to use one of the GPIO’s, in particular GP48 which is connected to the Intel-Arduino digital header marked IO7. Because this means programming the GPIO expanders and this is done in the arduino.asli we need to make a new ASL arduino-leds-ds2-tsl2561.asl from arduino-leds-ds2.asl and expand that. An example of how to expand with an additional pin can be found in adafruit-mi0283qt.asl. Finally we must convert tsl2561.asl into tsl2561.asli, expand that with the interrupt and include it in arduino-leds-ds2-tsl2561.asl.

arduino-leds-ds2-tsl2561.asl:

DefinitionBlock ("leds-ds2-ts2651.aml", "SSDT", 5, "", "LEDS-DS2-TSL2561", 1)
{
#define SPI_CLK_SEL_HOG	Name (DSMX, Package () { ToUUID("daffd814-6eba-4d8c-8a91-bc9bbf4aa301"), Package () { Package () { "gpio-hog", 1 }, Package () { "gpios", Package () { 11, 0 } }, Package () { "output-low", 1 }, Package () { "line-name", "ds2-mux" }, } })
#define SPI_CLK_SEL_REF	Package () { "ds2-mux", "DSMX" },
#define CONF_SPI_CLK_SEL

#define MUX18_DIR_HOG	Name (DSOE, Package () { ToUUID("daffd814-6eba-4d8c-8a91-bc9bbf4aa301"), Package () { Package () { "gpio-hog", 1 }, Package () { "gpios", Package () { 13, 0 } }, Package () { "output-high", 1 }, Package () { "line-name", "ds2-oe" }, } })
#define MUX18_DIR_REF	Package () { "ds2-oe", "DSOE" },
#define CONF_MUX18_DIR

#define DIG7_PU_PD_HOG	Name (LSPU, Package () { ToUUID("daffd814-6eba-4d8c-8a91-bc9bbf4aa301"), Package () { Package () { "gpio-hog", 1 }, Package () { "gpios", Package () { 7, 0 } }, Package () { "output-high", 1 }, Package () { "line-name", "lightsensor-pu" }, } })
#define DIG7_PU_PD_REF	Package () { "lightsensor-pu", "LSPU" },
#define CONF_DIG7_PU_PD

#define MUX32_DIR_HOG	Name (LSMX, Package () { ToUUID("daffd814-6eba-4d8c-8a91-bc9bbf4aa301"), Package () { Package () { "gpio-hog", 1 }, Package () { "gpios", Package () { 7, 0 } }, Package () { "output-low", 1 }, Package () { "line-name", "lightsensor-mux" }, } })
#define MUX32_DIR_REF	Package () { "lightsensor-mux", "LSMX" },
#define CONF_MUX32_DIR

    #include "arduino.asli"
    #include "leds.asli"
    #include "tsl2561.asli"
}

tsl2561.asli:

External (\_SB.PCI0.I2C6, DeviceObj)
/*
 * I2C6 is defined in another table, will be resolved at table load
 */
Scope (\_SB.PCI0.I2C6)
{
    Device (AMLS)                       // A name for the device
    {
        Name (_HID, "PRP0001")
        Name (_DDN, "AMS TSL2561 Light Sensor")
        Name (_CRS, ResourceTemplate () {
            I2cSerialBus (
                0x39,                   // Bus address
                ControllerInitiated,    // Don't care
                400000,                 // 400 kHz
                AddressingMode7Bit,     // 7-bit addressing
                "\\_SB.PCI0.I2C6",      // I2C host controller
                0                       // Must be 0
            )
            // Assign GPIO48 DIG7 on the arduino header as interrupt
            // Found in ft6236.asli example
            GpioInt(Level, ActiveLow, Exclusive, PullNone, 0,
                "\\_SB.PCI0.GPIO", 0, ResourceConsumer, , ) { 48 }

        })

        /*
            * See Documentation/devicetree/bindings/iio/light/amstaos,tsl2563.yaml
            * for more information about these bindings.
            */
        Name (_DSD, Package () {
            ToUUID("daffd814-6eba-4d8c-8a91-bc9bbf4aa301"),
            Package () {
                Package () { "compatible", Package () { "amstaos,tsl2561" } },
            }
        })
    }

    #include "gpioexp-dep.asli"

}

To make the interrupt work we need to set thresholds and enable it.

echo 10000 > /sys/bus/iio/devices/iio:device1/events/in_intensity_both_thresh_rising_value
echo 1000 > /sys/bus/iio/devices/iio:device1/events/in_intensity_both_thresh_falling_value
echo 1 >  /sys/bus/iio/devices/iio\:device1/events/in_intensity_both_thresh_falling_en
echo 1 >  /sys/bus/iio/devices/iio\:device1/events/in_intensity_both_thresh_rising_en

This makes the sensor generate an interrupt every 1.6 sec. when the light intensity is outside the 1000 - 10000 range. You can check that using:

cat /proc/interrupts | grep tsl
304:          0        216  gpio-merrifield  48  tsl2563_event

Or monitor using iio_event_monitor, an application that is part of the kernel sources under tools/iio/

./iio_event_monitor -a /dev/iio\:device1
Event: time: 1670103049318599238, type: intensity, channel: 0, evtype: thresh, direction: either