這篇文章簡單講述 IIO 的用法,並且寫一些顯然的驅動程式來檢驗看看裡面的功能。
首先要用 iio_device_alloc
配置一個 struct iio_dev
。接著初始化他,包含提供 iio_chan_spec
與 iio_info
等資訊,最後交給 iio_device_register
註冊。所以第一步就是先配置一個 iio_dev
,這邊使用 devres 系列的函數,以方便管理:
struct device *dev = pdev-> dev;
struct iio_dev *iio;
iio = devm_iio_device_alloc(dev, 0);
這個函數在 drivers/iio/industrialio-core.c 中有說明:
"Managed iio_device_alloc. iio_dev allocated with this function is automatically freed on driver detach."
其中,第二個變數是幫配置的空間,可以用 iio_priv(struct iio_dev*)
這個函數取回。比如說可以定義:
struct dummy_iio_dev {
struct dev *dev;
struct gpio_desc *gpio;
};
然後把 iio_dev
的私有資料拿來配置給這個結構:
iio = devm_iio_device_alloc(dev, sizeof(dummy_iio_dev));
dummy = iio_priv(iio);
dummy->dev = dev;
dummy->gpiod = devm_gpiod_get_index(dev, "dummy", 0, GPIOD_IN);
之後的函數中如果有 iio_dev
這個結構,就可以直接用 iio_priv(iio_dev)
去找到 dummy_iio_dev
這個結構。
每一個 channel 都代表一種提供資料的方法。比如說如果只有一個 channel,而且類型是 IIO_PROXIMITY
:
static const struct iio_chan_spec dummy_chan_spec[] = {
{
.type = IIO_PROXIMITY,
.info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED),
},
};
那麼就會在 sys/bus/iio/devices/iio:deviceX/
底下建立一個 in_proximity_input
名稱的節點:
$ ls /sys/bus/iio/devices/iio\:device1/
dev name subsystem
in_proximity_input power uevent
如果這個感測器有多個數值可以給使用這讀取,那麼可以把 indexed
設成 1
,然後用 channels
把這幾個 channel 的編號給出來。比如說:
static const struct iio_chan_spec dummy_channels[] = {
{
.type = IIO_PROXIMITY,
.indexed = 1,
.channel = 0,
.info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED),
},
{
.type = IIO_PROXIMITY,
.indexed = 1,
.channel = 1,
.info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED),
},
};
這時候在 sysfs
對應的資料夾,就會有對應編號的檔案:
$ ls /sys/bus/iio/devices/iio\:device1/
dev name uevent
in_proximity0_input power
in_proximity1_input subsystem
除此之外,也有一些後綴可以用。比如說加速規有 x
, y
, z
3 個方向的加速度。如果想要加上這個資訊,可以把 modified
設成 1
,然後把 channel2
指定成 include/uapi/linux/iio/types.h 中的 enum iio_modifier
的成員。這樣就可以在 sysfs
中的檔案增加後綴。比如說,如果提供的 iio_channel_spec
如下:
static const struct iio_chan_spec dummy_channels[] = {
{
.type = IIO_PROXIMITY,
.modified = 1,
.channel2 = IIO_MOD_X,
.info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED),
},
{
.type = IIO_PROXIMITY,
.modified = 1,
.channel2 = IIO_MOD_Y,
.info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED),
},
};
那麼就會發現裡面多出了 x
跟 y
兩個不同的檔案:
$ ls /sys/bus/iio/devices/iio\:device1/
dev name uevent
in_proximity_x_input power
in_proximity_y_input subsystem
除此之外,這邊的 info_mask_*
中的資訊是提示 userspace 的使用者這裡的數值是什麼樣的數值。比如說是某個量測到的,需要再轉換的數值?一個需要某個偏移量來修正的數值?或是已經換算好的某個物理量?等等。
接下來要實作「當 userspace 對某個 channel read
時,該生出什麼資料給他?」這裡要實作一個原型像下面這樣的函數:
static int read_raw (struct iio_dev *iio, struct iio_chan_spec const *chan
int *val0, int *val1, long mask)
{
/* ... */
(*val0) = some_value;
return IIO_VAL_INT;
}
在這個函數中,chan
這個變數,就是指現在正在對哪個 channel 讀取。因此任務就是依照不同的 channel 給出不同的輸出。
依照給定的 chan
跟 mask
,把預期要給使用者讀取的數值,存放在 val0
跟 val1
指向的位置。
用回傳值指定這個數值的資料型態:比如說如果回傳 IIO_VAL_INT
,那就表示 *val0
當中存放的是 int
; 又比如如果回傳 IIO_VAL_INT_PLUS_NANO
,那麼回傳的值就會是個整數部分為 *val0
,小數部分為 (*val1)*10e-9
形式的定點數。
而有哪些數值型態可以在 include/linux/iio/types.h 當中找到。
舉例來說,如果實作的
static int dummy_read_raw (struct iio_dev *iio, struct iio_chan_spec const *chan,
int *val0, int *val1, long mask)
{
*val0 = 99999;
*val1 = 11111;
return IIO_VAL_INT;
}
那麼在讀取 sysfs
的檔案時,就只會看到 *val0
的數值,因此就是個整數:
$ cat /sys/bus/iio/devices/iio\:device1/in_proximity_input
99999
而如果換成 IIO_VAL_INT_PLUS_NANO
:
static int dummy_read_raw (struct iio_dev *iio, struct iio_chan_spec const *chan,
int *val0, int *val1, long mask)
{
*val0 = 99999;
*val1 = 11111;
return IIO_VAL_INT_PLUS_NANO;
}
使用者在 sysfs
讀取到的數值就會是以 *val0
為整數部分,(*val1)*10e-9
為小數部分的數值。以上面這個例子也就是:
$ cat /sys/bus/iio/devices/iio\:device1/in_proximity_input
99999.000011111
實作好上面的 read_raw
(或其他 iio_info
中的函數) 後,把他們填進 struct iio_info
中:
struct iio_info dummy_iio_info = {
.read_raw = dummy_read_raw,
};
把剛剛的 struct iio_chan_spec
跟 struct iio_info
填進 struct iio_dev
中,最後使用 iio_device_register
系列的函數註冊這個 iio_dev
(這邊是使用 devm_iio_device_register
):
static int dummy_iio_probe(struct platform_device *pdev)
{
...
struct iio_dev *iio;
iio = devm_iio_device_alloc(dev, 0);
iio -> name = pdev->name;
iio -> info = &dummy_iio_info;
iio -> modes = INDIO_DIRECT_MODE;
iio -> channels = dummy_channels;
iio -> num_channels = ARRAY_SIZE(dummy_channels);
return devm_iio_device_register(dev, iio);
}
在其他的狀況下,有可能要另外實作 remove
做資源清理。但這邊的狀況中,因為資源配置都是用 devm*
系列的函數做資源配置,所以在移除之後會自動處理清理的問題,因此就沒有實作 remove
。
雖然上面沒有講到 device tree,但畢竟要有裝置讓 probe
可以發生。這邊就修改 GPIO 那邊的裝置樹 (僅有修改名稱與輸入輸出而已),包含 pinctrl
的部分。
/dts-v1/;
/plugin/;
/ {
compatible="brcm,brcm2835";
fragment@0 {
target = <&gpio>;
__overlay__ {
dummy_gpio: dummy_gpio_pins {
brcm,pins = <0x11>;
brcm,function = <0x0>;
brcm,pull = <0x1>;
};
};
};
fragment@1 {
target-path = "/";
__overlay__ {
dummy_iio: dummy_iio_device {
dummy-gpios = <&gpio 0x11 0x0>;
compatible = "dummy_iio";
status = "ok";
pinctrl-0 = <&dummy_gpio>;
pinctrl-names = "default";
};
};
};
};
在這個模組中,裡面定義了 6 個 channel,分別去觀察在 *val0
與 *val1
相同,而iio_info
中的 write_raw
回傳數值不同時,分別會對使用者從 sysfs
中觀察到的結果有什麼影響:
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/gpio/consumer.h>
#include <linux/iio/consumer.h>
#include <linux/iio/iio.h>
#include <linux/of.h>
#define IIO_CHANNEL_DEFINE(num) {\
.type = IIO_PROXIMITY,\
.indexed = 1,\
.channel = (num),\
.info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED),\
}\
static const struct iio_chan_spec dummy_channels[] = {
IIO_CHANNEL_DEFINE(0),
IIO_CHANNEL_DEFINE(1),
IIO_CHANNEL_DEFINE(2),
IIO_CHANNEL_DEFINE(3),
IIO_CHANNEL_DEFINE(4),
IIO_CHANNEL_DEFINE(5),
};
static int dummy_read_raw (struct iio_dev *iio, struct iio_chan_spec const *chan,
int *val0, int *val1, long mask)
{
*val0 = 9876;
*val1 = 1234;
switch(chan -> channel) {
case 0:
return IIO_VAL_INT;
case 1:
return IIO_VAL_INT_PLUS_MICRO;
case 2:
return IIO_VAL_INT_PLUS_MICRO_DB;
case 3:
return IIO_VAL_INT_MULTIPLE;
case 4:
return IIO_VAL_FRACTIONAL;
case 5:
return IIO_VAL_FRACTIONAL_LOG2;
default:
return 0;
}
}
struct iio_info dummy_iio_info = {
.read_raw = dummy_read_raw,
};
static int dummy_iio_probe(struct platform_device *pdev)
{
struct device *dev = &(pdev-> dev);
struct iio_dev *iio;
iio = devm_iio_device_alloc(dev, 0);
if (!iio) {
dev_err(dev, "Failed to allocate IIO/.\n");
return -ENOMEM;
}
iio -> name = pdev->name;
iio -> info = &dummy_iio_info;
iio -> modes = INDIO_DIRECT_MODE;
iio -> channels = dummy_channels;
iio -> num_channels = ARRAY_SIZE(dummy_channels);
return devm_iio_device_register(dev, iio);
}
static const struct of_device_id dummy_ids[] = {
{.compatible = "dummy_iio",},
{}
};
static struct platform_driver dummy_driver = {
.driver = {
.name = "dummy driver",
.of_match_table = dummy_ids,
},
.probe = dummy_iio_probe
};
MODULE_LICENSE("GPL");
module_platform_driver(dummy_driver);
PWD := $(shell pwd)
KVERSION := $(shell uname -r)
KERNEL_DIR := /lib/modules/$(shell uname -r)/build
MODULE_NAME = dummy_iio
obj-m := $(MODULE_NAME).o
all:
make -C $(KERNEL_DIR) M=$(PWD) modules
clean:
make -C $(KERNEL_DIR) M=$(PWD) clean
修改裝置樹的部分參考前面的文章,這邊就不贅述。編譯模組並載入之後,去對應的 iio
裝置節點查看檔案。以這邊為例,是在 iio:device1
:
$ cd /sys/bus/iio/devices/iio:device1
首先列出底下的檔案:
$ ls
可以發現裡面有 6 個對應的 in_proximityN_input
,其中 N
是從 0 ~ 5 的整數:
$ ls
dev in_proximity3_input power
in_proximity0_input in_proximity4_input subsystem
in_proximity1_input in_proximity5_input uevent
in_proximity2_input name
如果列出所有的數值結果:
$ cat in_proximity{0..5}_input
9876
9876.001234
9876.001234 dB
9876 1234
8.003241491
0.000000000
IIO_VAL_INT
剛剛描述過。IIO_VAL_INT_PLUS_MICRO
則類似 IIO_VAL_INT_PLUS_NANO
,只是 val1
的單位變成由「奈」變成「微」;IIO_VAL_INT_PLUS_MICRO_DB
只是把數值後面多加上分貝IIO_VAL_INT_MULTIPLE
則是回傳兩個整數值;IIO_VAL_FRACTIONAL
回傳的是 (*val0/*val1
)。IIO_VAL_FRACTIONAL_LOG2
回傳的是 *val0 >>(*val1)
其中,關於 IIO_VAL_FRACTIONAL_LOG2
,如果把 *val1
改成 4
的話,會出現類似以下的輸出:
$ cat in_proximity{0..5}_input
9876
9876.000004
9876.000004 dB
9876 4
2469.000000000
617.250000000
其中,最後一個數值 617.250000000
恰好就是 9876/16。更多的實作細節可以在 drivers/iio/inkern.c 找到。