IIO子系统-BMI088驱动完善

上述我们的驱动对用户接口的实现使用的是字符设备驱动,对于 BMI088 陀螺仪,我们可以注册一个 /dev/bmi088_gyro 节点,然后用户层通过 open()read() 读取 6 字节原始角速度数据。这种方式简单直接,非常适合入门 SPI 通信、寄存器读写和字符设备驱动框架。

但是,当设备逐渐复杂之后,字符设备驱动的问题就会暴露出来:

复制代码
1. 用户层不知道 read 出来的 6 个字节分别代表什么;
2. x/y/z 三轴数据格式需要自己约定;
3. 原始值到物理单位的转换比例需要用户层自己维护;
4. 采样率、量程、滤波带宽等参数缺少标准接口;
5. 后续如果要支持 FIFO、中断、buffer、trigger,需要自己重新造轮子。

BMI088 属于典型 IMU 传感器,包含加速度计和陀螺仪。对于这类传感器,Linux 内核中已经提供了更合适的框架 ------ IIO 子系统

IIO,全称 Industrial I/O ,主要用于 ADC、DAC、加速度计、陀螺仪、磁力计、气压计等采集类设备。Linux 官方文档中说明,IIO 设备会通过 /sys/bus/iio/devices/iio:deviceX/ 目录向用户空间暴露标准属性,例如 namesampling_frequency_available 等;每个 IIO 设备可以包含一个或多个 channel,channel 用 struct iio_chan_spec 描述。

1.IIO子系统框架

一个基础 IIO 驱动通常包含以下几部分:

复制代码
1. 驱动私有数据结构体;
2. SPI/I2C 寄存器读写函数;
3. 硬件初始化函数;
4. IIO channel 描述;
5. IIO read_raw() 回调函数;
6. iio_info 操作函数表;
7. probe() 中分配并注册 iio_dev;
8. 设备树匹配表和 spi_driver 结构体。

1.1驱动私有数据

在IIO子系统框架下和不在IIO子系统框架下对驱动私有数据的处理是不同的(构造方式不同)。

私有数据是什么?

在Linux内核中,probe函数执行完毕后,内核并不会帮你记住某个设备的具体状态(比如当前打开了没有、SPI指针是多少、互斥锁初始化了没)。因此,驱动必须自己搞一块内存,把所有该设备独有的、运行时需要用的变量全部塞进去。

非IIO子系统下

在之前的非IIO子系统中,我们对驱动私有数据的处理是,在probe中:

cpp 复制代码
dev = devm_kzalloc(&spi->dev, sizeof(*dev), GFP_KERNEL);

给你的私有数据结构体分配内存。

cpp 复制代码
spi_set_drvdata(spi, dev);

这是把你的私有数据挂到 spi_device 上。

在IIO子系统情况下,我们对私有数据的处理是这样的:

IIO 版驱动中,可以定义如下私有数据:

cpp 复制代码
struct bmi088_gyro_data {
    struct spi_device *spi;
    struct mutex lock;
};

在 IIO 驱动中,私有数据一般通过下面方式分配:

cpp 复制代码
indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*data));
if (!indio_dev)
    return -ENOMEM;

data = iio_priv(indio_dev);
data->spi = spi;
mutex_init(&data->lock);

其中:

cpp 复制代码
devm_iio_device_alloc()

函数作用(极其关键):给iio设备分配一块内容,并将iio设备绑定的spi设备上,即iio设备的生命周期随spi设备的生命周期的变化,除此之外在iio设备后面分配一块内存用于存放驱动私有数据。

然后:

cpp 复制代码
data = iio_priv(indio_dev);

就是从 indio_dev 中取出驱动私有数据。

1.2 IIO Channel:描述 BMI088 有哪些数据通道

IIO 驱动里最重要的结构之一是:

cpp 复制代码
struct iio_chan_spec

它用来描述一个传感器通道。

BMI088 陀螺仪有 3 个角速度通道:

复制代码
x 轴角速度
y 轴角速度
z 轴角速度

所以可以定义如下 channel 数组:

cpp 复制代码
static const struct iio_chan_spec bmi088_gyro_channels[] = {
    {
        .type = IIO_ANGL_VEL,
        .modified = 1,
        .channel2 = IIO_MOD_X,
        .info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
        .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE),
    },
    {
        .type = IIO_ANGL_VEL,
        .modified = 1,
        .channel2 = IIO_MOD_Y,
        .info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
        .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE),
    },
    {
        .type = IIO_ANGL_VEL,
        .modified = 1,
        .channel2 = IIO_MOD_Z,
        .info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
        .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE),
    },
};

参数详解:

https://blog.csdn.net/myingrid/article/details/162460210?sharetype=blogdetail&sharerId=162460210&sharerefer=PC&sharesource=myingrid&spm=1011.2480.3001.8118

1.3 iio_info:user读取时调用的函数

iio_info类似于字符设备中的file_operations;

IIO 驱动中还需要定义一个 struct iio_info

cpp 复制代码
static const struct iio_info bmi088_gyro_info = {
    .read_raw = bmi088_gyro_read_raw,
};

它类似字符设备驱动中的 file_operations

字符设备中:

cpp 复制代码
static const struct file_operations fops = {
    .open = xxx_open,
    .read = xxx_read,
};

IIO 驱动中:

cpp 复制代码
static const struct iio_info bmi088_gyro_info = {
    .read_raw = bmi088_gyro_read_raw,
};

也就是说:

cpp 复制代码
用户读取 /dev/bmi088_gyro
        |
        v
调用 file_operations.read

用户读取 in_anglvel_x_raw
        |
        v
调用 iio_info.read_raw

1.4 read_raw():IIO读取数据的核心

read_raw() 是 IIO direct mode 中最核心的函数。

示例代码如下:

cpp 复制代码
static int bmi088_gyro_read_raw(struct iio_dev *indio_dev,
                                struct iio_chan_spec const *chan,
                                int *val, int *val2, long mask)
{
    struct bmi088_gyro_data *data = iio_priv(indio_dev);
    int ret;

    switch (mask) {
    case IIO_CHAN_INFO_RAW:
        ret = bmi088_gyro_read_axis(data, chan->channel2, val);
        if (ret < 0)
            return ret;

        return IIO_VAL_INT;

    case IIO_CHAN_INFO_SCALE:
        *val = 0;
        *val2 = BMI088_GYRO_SCALE_2000DPS_NANO;
        return IIO_VAL_INT_PLUS_NANO;

    default:
        return -EINVAL;
    }
}

这个函数里面有两个关键参数:

复制代码
chan:表示用户正在访问哪个通道;
mask:表示用户正在访问这个通道的哪个属性。

读取raw数据:

当用户执行:

复制代码
cat /sys/bus/iio/devices/iio:device0/in_anglvel_x_raw

IIO 框架会调用:

cpp 复制代码
bmi088_gyro_read_raw(indio_dev, chan, val, val2, IIO_CHAN_INFO_RAW);

此时:

cpp 复制代码
mask = IIO_CHAN_INFO_RAW
chan->channel2 = IIO_MOD_X

所以代码进入:

cpp 复制代码
case IIO_CHAN_INFO_RAW:
    ret = bmi088_gyro_read_axis(data, chan->channel2, val);

然后驱动通过 SPI 读取 BMI088 数据寄存器,并返回 x 轴原始值。

如果用户读取:

复制代码
cat in_anglvel_y_raw

那么:

复制代码
chan->channel2 = IIO_MOD_Y

如果用户读取:

复制代码
cat in_anglvel_z_raw

那么:

复制代码
chan->channel2 = IIO_MOD_Z

所以可以总结为:

复制代码
chan 决定读哪个轴;
mask 决定读 raw 还是 scale。

1.5 注册IIO设备步骤

probe函数如下:

cpp 复制代码
static int bmi088_gyro_probe(struct spi_device *spi)
{
    struct iio_dev *indio_dev;
    struct bmi088_gyro_data *data;
    int ret;

    /*
     * 1. 配置 SPI 基本参数
     *
     * 如果你的设备树里已经配置了 spi-cpol / spi-cpha,
     * 这里可以根据实际情况删除 spi->mode 这一行。
     *
     * BMI088 常见使用 SPI_MODE_0。
     */
    spi->mode = SPI_MODE_0;
    spi->bits_per_word = 8;

    if (!spi->max_speed_hz)
        spi->max_speed_hz = 10000000;

    ret = spi_setup(spi);
    if (ret < 0) {
        dev_err(&spi->dev, "spi_setup failed: %d\n", ret);
        return ret;
    }

    /*
     * 2. 分配 IIO 设备
     *
     * indio_dev 是 IIO 核心设备;
     * data 是驱动私有数据,通过 iio_priv(indio_dev) 获取。
     */
    indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*data));
    if (!indio_dev)
        return -ENOMEM;

    data = iio_priv(indio_dev);
    data->spi = spi;
    mutex_init(&data->lock);

    spi_set_drvdata(spi, indio_dev);

    /*
     * 3. 初始化 BMI088 gyroscope 硬件
     */
    ret = bmi088_gyro_hardware_init(spi);
    if (ret < 0)
        return ret;

    /*
     * 4. 填充 IIO 设备信息
     */
    indio_dev->name = BMI088_GYRO_DRV_NAME;
    indio_dev->info = &bmi088_gyro_info;
    indio_dev->modes = INDIO_DIRECT_MODE;
    indio_dev->channels = bmi088_gyro_channels;
    indio_dev->num_channels = ARRAY_SIZE(bmi088_gyro_channels);

    /*
     * 5. 注册 IIO 设备
     *
     * 注册成功后,会在 /sys/bus/iio/devices/ 下生成 iio:deviceX。
     */
    ret = devm_iio_device_register(&spi->dev, indio_dev);
    if (ret < 0) {
        dev_err(&spi->dev, "Failed to register IIO device: %d\n", ret);
        return ret;
    }

    dev_info(&spi->dev, "BMI088 gyroscope IIO driver probed\n");

    return 0;
}

步骤如下:

1. 配置 SPI 参数

cpp 复制代码
spi->mode = SPI_MODE_0;
spi->bits_per_word = 8;

if (!spi->max_speed_hz)
    spi->max_speed_hz = 10000000;

ret = spi_setup(spi);

这里配置 SPI 模式、每字位数和最大频率。

如果设备树中已经配置了 spi-max-frequencyspi-cpolspi-cpha 等属性,则这里可以根据实际情况简化。


2. 分配 IIO 设备

cpp 复制代码
indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*data));
if (!indio_dev)
    return -ENOMEM;

这一步分配 IIO 设备对象,同时分配驱动私有数据。


3. 初始化私有数据

cpp 复制代码
data = iio_priv(indio_dev);
data->spi = spi;
mutex_init(&data->lock);

这里把 SPI 设备指针保存到私有数据中,后面 read_raw() 读寄存器时会用到。


4. 初始化 BMI088 硬件

cpp 复制代码
ret = bmi088_gyro_hardware_init(spi);
if (ret < 0)
    return ret;

硬件初始化主要包括:

复制代码
1. 软件复位;
2. 读取 CHIP_ID;
3. 设置 normal mode;
4. 配置量程;
5. 配置带宽和采样率。

5. 填充 IIO 设备信息

cpp 复制代码
indio_dev->name = BMI088_GYRO_DRV_NAME;
indio_dev->info = &bmi088_gyro_info;
indio_dev->modes = INDIO_DIRECT_MODE;
indio_dev->channels = bmi088_gyro_channels;
indio_dev->num_channels = ARRAY_SIZE(bmi088_gyro_channels);

含义如下:

复制代码
name         :IIO 设备名称;
info         :IIO 操作函数表;
modes        :当前使用 direct mode;
channels     :通道数组;
num_channels :通道数量。

6. 注册 IIO 设备

cpp 复制代码
ret = devm_iio_device_register(&spi->dev, indio_dev);

注册成功后,IIO 框架会自动生成:

复制代码
/sys/bus/iio/devices/iio:deviceX/

并根据 channelsinfo_mask 自动生成:

复制代码
in_anglvel_x_raw
in_anglvel_y_raw
in_anglvel_z_raw
in_anglvel_scale

2. 完整代码示例

cpp 复制代码
#include <linux/module.h>
#include <linux/spi/spi.h>
#include <linux/delay.h>
#include <linux/mutex.h>
#include <linux/slab.h>
#include <linux/bitops.h>

#include <linux/iio/iio.h>

/*
 * BMI088 Gyroscope register definitions
 */
#define BMI088_GYRO_DRV_NAME        "bmi088_gyro_iio"

#define GYRO_CHIP_ID_REG            0x00
#define GYRO_DATA_START_REG         0x02  /* 0x02~0x07: X_LSB X_MSB Y_LSB Y_MSB Z_LSB Z_MSB */
#define GYRO_RANGE_REG              0x0F
#define GYRO_BANDWIDTH_REG          0x10
#define GYRO_LPM1_REG               0x11
#define GYRO_SOFTRESET_REG          0x14

#define BMI088_GYRO_CHIP_ID         0x0F

/*
 * 当前驱动里配置的量程:
 * GYRO_RANGE_REG = 0x00 -> ±2000 dps
 *
 * IIO 中 angular velocity 的标准单位通常按 rad/s 表示。
 *
 * ±2000 dps = ±2000 degree/s
 *            = ±34.906585 rad/s
 *
 * raw 范围近似为 -32768 ~ 32767
 *
 * scale = 34.906585 / 32768
 *       ≈ 0.001065264 rad/s per LSB
 *
 * IIO_VAL_INT_PLUS_NANO 表示:
 * val + val2 / 1e9
 *
 * 所以这里返回:
 * 0 + 1065264 / 1e9
 */
#define BMI088_GYRO_SCALE_2000DPS_NANO  1065264

struct bmi088_gyro_data {
    struct spi_device *spi;
    struct mutex lock;
};

/*
 * BMI088 gyroscope SPI read
 *
 * SPI 是边发边收:
 * 第 1 字节发送寄存器地址,收到的数据无效;
 * 后续发送 dummy byte,用来产生 SCLK,从机返回真正数据。
 */
static int bmi088_gyro_read_regs(struct spi_device *spi, u8 reg,
                                 u8 *buf, size_t len)
{
    struct spi_transfer t = {0};
    struct spi_message m;
    u8 *tx_buf;
    u8 *rx_buf;
    int ret;

    tx_buf = kzalloc(len + 1, GFP_KERNEL);
    rx_buf = kzalloc(len + 1, GFP_KERNEL);
    if (!tx_buf || !rx_buf) {
        ret = -ENOMEM;
        goto free_bufs;
    }

    tx_buf[0] = reg | 0x80;          /* bit7 = 1 表示读 */
    memset(tx_buf + 1, 0xFF, len);   /* dummy bytes,用来产生时钟 */

    t.tx_buf = tx_buf;
    t.rx_buf = rx_buf;
    t.len = len + 1;

    spi_message_init(&m);
    spi_message_add_tail(&t, &m);

    ret = spi_sync(spi, &m);
    if (ret == 0) {
        /*
         * rx_buf[0] 是发送寄存器地址时收到的无效数据;
         * 真正有效数据从 rx_buf[1] 开始。
         */
        memcpy(buf, rx_buf + 1, len);
    } else {
        dev_err(&spi->dev, "SPI read failed: %d\n", ret);
    }

free_bufs:
    kfree(rx_buf);
    kfree(tx_buf);
    return ret;
}

/*
 * BMI088 gyroscope SPI write
 */
static int bmi088_gyro_write_reg(struct spi_device *spi, u8 reg, u8 val)
{
    u8 buf[2];

    buf[0] = reg & 0x7F;  /* bit7 = 0 表示写 */
    buf[1] = val;

    return spi_write(spi, buf, sizeof(buf));
}

/*
 * 读取 6 字节原始陀螺仪数据,并根据轴选择返回 x/y/z
 */
static int bmi088_gyro_read_axis(struct bmi088_gyro_data *data,
                                 int axis, int *val)
{
    u8 raw[6];
    s16 x, y, z;
    int ret;

    mutex_lock(&data->lock);

    ret = bmi088_gyro_read_regs(data->spi, GYRO_DATA_START_REG, raw, 6);

    mutex_unlock(&data->lock);

    if (ret < 0)
        return ret;

    x = (s16)((raw[1] << 8) | raw[0]);
    y = (s16)((raw[3] << 8) | raw[2]);
    z = (s16)((raw[5] << 8) | raw[4]);

    switch (axis) {
    case IIO_MOD_X:
        *val = x;
        break;
    case IIO_MOD_Y:
        *val = y;
        break;
    case IIO_MOD_Z:
        *val = z;
        break;
    default:
        return -EINVAL;
    }

    return 0;
}

/*
 * IIO read_raw 回调函数
 *
 * 用户读取下面这些 sysfs 节点时,会进入这个函数:
 *
 * in_anglvel_x_raw
 * in_anglvel_y_raw
 * in_anglvel_z_raw
 * in_anglvel_scale
 */
static int bmi088_gyro_read_raw(struct iio_dev *indio_dev,
                                struct iio_chan_spec const *chan,
                                int *val, int *val2, long mask)
{
    struct bmi088_gyro_data *data = iio_priv(indio_dev);
    int ret;

    switch (mask) {
    case IIO_CHAN_INFO_RAW:
        ret = bmi088_gyro_read_axis(data, chan->channel2, val);
        if (ret < 0)
            return ret;

        /*
         * 返回整数 raw 值。
         */
        return IIO_VAL_INT;

    case IIO_CHAN_INFO_SCALE:
        /*
         * 当前配置是 ±2000 dps。
         *
         * scale ≈ 0.001065264 rad/s per LSB
         */
        *val = 0;
        *val2 = BMI088_GYRO_SCALE_2000DPS_NANO;
        return IIO_VAL_INT_PLUS_NANO;

    default:
        return -EINVAL;
    }
}

static const struct iio_info bmi088_gyro_info = {
    .read_raw = bmi088_gyro_read_raw,
};

/*
 * 定义 IIO 通道
 *
 * type = IIO_ANGL_VEL 表示角速度
 *
 * modified = 1 表示这是带方向修饰的通道
 *
 * channel2 = IIO_MOD_X/Y/Z 表示 X/Y/Z 轴
 *
 * info_mask_separate:
 *   每个轴都有自己的 raw 节点
 *
 * info_mask_shared_by_type:
 *   三个角速度轴共享同一个 scale
 */
static const struct iio_chan_spec bmi088_gyro_channels[] = {
    {
        .type = IIO_ANGL_VEL,
        .modified = 1,
        .channel2 = IIO_MOD_X,
        .info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
        .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE),
    },
    {
        .type = IIO_ANGL_VEL,
        .modified = 1,
        .channel2 = IIO_MOD_Y,
        .info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
        .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE),
    },
    {
        .type = IIO_ANGL_VEL,
        .modified = 1,
        .channel2 = IIO_MOD_Z,
        .info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
        .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE),
    },
};

/*
 * BMI088 gyroscope hardware initialization
 */
static int bmi088_gyro_hardware_init(struct spi_device *spi)
{
    u8 chip_id = 0;
    int ret;

    /*
     * 1. 软件复位
     *
     * 这一步不是绝对必须,但工程上建议保留。
     * 作用是让陀螺仪从一个干净状态重新开始。
     */
    ret = bmi088_gyro_write_reg(spi, GYRO_SOFTRESET_REG, 0xB6);
    if (ret < 0)
        return ret;

    msleep(30);

    /*
     * 2. 读取 CHIP_ID,确认 SPI 通信和器件身份
     */
    ret = bmi088_gyro_read_regs(spi, GYRO_CHIP_ID_REG, &chip_id, 1);
    if (ret < 0)
        return ret;

    if (chip_id != BMI088_GYRO_CHIP_ID) {
        dev_err(&spi->dev,
                "Gyro Chip ID mismatch! Got 0x%02X, expected 0x%02X\n",
                chip_id, BMI088_GYRO_CHIP_ID);
        return -ENODEV;
    }

    /*
     * 3. 设置电源模式为 Normal Mode
     *
     * BMI088 gyro 上电后默认就是 normal mode。
     * 这里再次写入,是为了防御式初始化。
     */
    ret = bmi088_gyro_write_reg(spi, GYRO_LPM1_REG, 0x00);
    if (ret < 0)
        return ret;

    msleep(1);

    /*
     * 4. 配置量程:±2000 dps
     */
    ret = bmi088_gyro_write_reg(spi, GYRO_RANGE_REG, 0x00);
    if (ret < 0)
        return ret;

    /*
     * 5. 配置 ODR 和带宽
     *
     * 0x00: ODR 2000 Hz, filter bandwidth 532 Hz
     */
    ret = bmi088_gyro_write_reg(spi, GYRO_BANDWIDTH_REG, 0x00);
    if (ret < 0)
        return ret;

    dev_info(&spi->dev,
             "BMI088 gyroscope initialized successfully, chip_id=0x%02X\n",
             chip_id);

    return 0;
}

static int bmi088_gyro_probe(struct spi_device *spi)
{
    struct iio_dev *indio_dev;
    struct bmi088_gyro_data *data;
    int ret;

    /*
     * 1. 配置 SPI 基本参数
     *
     * 如果你的设备树里已经配置了 spi-cpol / spi-cpha,
     * 这里可以根据实际情况删除 spi->mode 这一行。
     *
     * BMI088 常见使用 SPI_MODE_0。
     */
    spi->mode = SPI_MODE_0;
    spi->bits_per_word = 8;

    if (!spi->max_speed_hz)
        spi->max_speed_hz = 10000000;

    ret = spi_setup(spi);
    if (ret < 0) {
        dev_err(&spi->dev, "spi_setup failed: %d\n", ret);
        return ret;
    }

    /*
     * 2. 分配 IIO 设备
     *
     * indio_dev 是 IIO 核心设备;
     * data 是驱动私有数据,通过 iio_priv(indio_dev) 获取。
     */
    indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*data));
    if (!indio_dev)
        return -ENOMEM;

    data = iio_priv(indio_dev);
    data->spi = spi;
    mutex_init(&data->lock);

    spi_set_drvdata(spi, indio_dev);

    /*
     * 3. 初始化 BMI088 gyroscope 硬件
     */
    ret = bmi088_gyro_hardware_init(spi);
    if (ret < 0)
        return ret;

    /*
     * 4. 填充 IIO 设备信息
     */
    indio_dev->name = BMI088_GYRO_DRV_NAME;
    indio_dev->info = &bmi088_gyro_info;
    indio_dev->modes = INDIO_DIRECT_MODE;
    indio_dev->channels = bmi088_gyro_channels;
    indio_dev->num_channels = ARRAY_SIZE(bmi088_gyro_channels);

    /*
     * 5. 注册 IIO 设备
     *
     * 注册成功后,会在 /sys/bus/iio/devices/ 下生成 iio:deviceX。
     */
    ret = devm_iio_device_register(&spi->dev, indio_dev);
    if (ret < 0) {
        dev_err(&spi->dev, "Failed to register IIO device: %d\n", ret);
        return ret;
    }

    dev_info(&spi->dev, "BMI088 gyroscope IIO driver probed\n");

    return 0;
}

/*
 * 如果你使用的是较新的内核,remove 可以省略;
 * 因为这里使用了 devm_iio_device_alloc 和 devm_iio_device_register,
 * 资源会自动释放。
 *
 * 如果你的内核要求 .remove 必须存在,可以加上下面这个函数。
 */
static void bmi088_gyro_remove(struct spi_device *spi)
{
    dev_info(&spi->dev, "BMI088 gyroscope IIO driver removed\n");
}

/*
 * 设备树匹配表
 *
 * 正式写法建议使用 "bosch,bmi088-gyro"。
 * 为了兼容你原来的设备树,这里也保留 "my-bmi088-gyro"。
 */
static const struct of_device_id bmi088_gyro_of_match[] = {
    { .compatible = "bosch,bmi088-gyro" },
    { .compatible = "my-bmi088-gyro" },
    { }
};
MODULE_DEVICE_TABLE(of, bmi088_gyro_of_match);

static const struct spi_device_id bmi088_gyro_id[] = {
    { "bmi088-gyro", 0 },
    { "my-bmi088-gyro", 0 },
    { }
};
MODULE_DEVICE_TABLE(spi, bmi088_gyro_id);

static struct spi_driver bmi088_gyro_driver = {
    .driver = {
        .name = BMI088_GYRO_DRV_NAME,
        .of_match_table = bmi088_gyro_of_match,
    },
    .probe = bmi088_gyro_probe,
    .remove = bmi088_gyro_remove,
    .id_table = bmi088_gyro_id,
};

module_spi_driver(bmi088_gyro_driver);

MODULE_AUTHOR("Embedded Developer");
MODULE_DESCRIPTION("BMI088 Gyroscope SPI IIO Driver");
MODULE_LICENSE("GPL");