本章以Jetson AGX Orin为例讲解I2C watchdog驱动程序的编程,主要分为以下三个部分。
硬件原理图
Block Diagram
SOC端的#pin脚定义
MCU端#pin脚定义
Linux下的I2C驱动框架
上图描述了linux I2C驱动框架,体系结构在linux中实现相当复杂。如何编写一个linux驱动程序呢?所谓Linux驱动个人认为主要是起承上启下的作用,对上提供接口API给内核,再由内核间接将接口提供给应用层,比如应用层访问/dev/watchdogX文件等;对下控制硬件设备。具体实现的流程套路如下:
1. 确定驱动结构:根据硬件设计结合分层/分离思想确定驱动的基本结构。
2. 确定驱动实例:驱动定义,初始化,注册,注销。
3. 向上提供接口:实现i2c设备的i2c_driver接口以及i2c设备对应的watchdog驱动
4. 向下控制硬件:根据寄存器配置方式实现控制逻辑。
具体代码分析
驱动大致流程:
1) 加载驱动(int函数)
2) 添加i2c驱动
3) 匹配目标硬件设备
4) 探测probe函数
5) 注册watchdog设备
6) 实现watchdog操作集(start,stop,set_timeout等)
7) 注销watchdog设备
8) 卸载驱动
i2c_driver: 代表的一个i2c设备驱动,类似于platform_driver,在i2c_driver注册到内核且名称与设备树匹配一致就会进入到probe函数,驱动卸载时要进入remove函数。所以编写驱动需要将probe,remove和用于匹配硬件的driver进行填充。
struct i2c_driver mcu_watchdog_driver = {
.driver = {
.name = "I2C MCU Watchdog",
.owner = THIS_MODULE,
.of_match_table = advwdt_dt_ids,
},
.probe = i2c_wtd_probe,
.remove = i2c_wtd_remove,
.id_table = advwdt_id,
};
static int __init watchdog_driver_init(void)
{
return i2c_add_driver(&mcu_watchdog_driver);
}
of_match_table:用于匹配设备树信息,匹配的顺序of_match_table> acpi_match_table> id_table>name。
static const struct of_device_id advwdt_dt_ids[] = {
{.compatible = "stm,i2c-watchdog"},
{}
};
设备树:添加硬件信息,slave地址等
hdr40_i2c1: i2c@c250000 {
i2cmcu@58 {
compatible = "stm,i2c-watchdog";
reg = <0x58>;
#address-cells = <1>;
#size-cells = <0>;
skip_mux_detect = "yes";
vcc-supply = <&p3737_vdd_1v8_sys>;
};
};
i2c_client:描述挂接在硬件i2c总线上的设备的设备信息,即i2c设备的设备对象。
watchdog_device: watchdog子系统提供了一系列操作,不需要用户再次编写,只需要向相应的结构体填写设备信息。
主要函数:注册,注销。
extern int watchdog_register_device(struct watchdog_device *);
extern void watchdog_unregister_device(struct watchdog_device *);
自定义驱动私有数据结构体。
struct mcu_wtd_data {
struct watchdog_device wdd;
struct i2c_client *client;
};
实现探测probe函数。
static int i2c_wtd_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
struct mcu_wtd_data *wd_data;
int ret = 0;
if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA))
return -ENODEV;
wd_data = devm_kzalloc(&client->dev, sizeof(struct mcu_wtd_data), GFP_KERNEL);
if (!wd_data)
return -ENOMEM;
wd_data->client = client;
watchdog_set_drvdata(&wd_data->wdd, wd_data);
i2c_set_clientdata(client, wd_data);
wd_data->wdd.info = &jetson_mcu_watchdog_info;
wd_data->wdd.ops = &jetson_mcu_watchdog_ops;
wd_data->wdd.max_timeout = MAX_TIMEOUT;
wd_data->wdd.min_timeout = MIN_TIMEOUT;
wd_data->wdd.timeout = DEFAULT_TIMEOUT;
wd_data->wdd.parent = &client->dev;
ret = watchdog_register_device(&wd_data->wdd);
if (ret) {
dev_err(&client->dev, "failed to register watchdog device\n");
return ret;
}
return 0;
}
定义watchdog_ops操作函数。
static const struct watchdog_ops jetson_mcu_watchdog_ops = {
.owner = THIS_MODULE,
.start = jetson_mcu_watchdog_start,
.stop = jetson_mcu_watchdog_stop,
.ping = jetson_mcu_watchdog_ping,
.set_timeout = jetson_mcu_watchdog_set_timeout,
};
实现start,stop,ping,set_timeout函数等。
static int jetson_mcu_watchdog_start(struct watchdog_device *wdd)
{
struct mcu_wtd_data *wd_data = watchdog_get_drvdata(wdd);
int ret = 0;
ret = i2c_smbus_write_byte_data(wd_data->client, WATCHDOG_CONTROL_REG, WATCHDOG_ENABLE);
if (ret < 0) {
dev_err(&wd_data->client->dev, "failed to start watchdog: %d\n", ret);
return ret;
}
return 0;
}
static int jetson_mcu_watchdog_stop(struct watchdog_device *wdd)
{
struct mcu_wtd_data *wd_data = watchdog_get_drvdata(wdd);
int ret = 0;
ret = i2c_smbus_write_byte_data(wd_data->client, WATCHDOG_CONTROL_REG, WATCHDOG_DISABLE);
if (ret < 0) {
dev_err(&wd_data->client->dev, "failed to stop watchdog: %d\n", ret);
return ret;
}
return 0;
}
static int jetson_mcu_watchdog_ping(struct watchdog_device *wdd)
{
struct mcu_wtd_data *wd_data = watchdog_get_drvdata(wdd);
int ret = 0;
ret = i2c_smbus_write_byte_data(wd_data->client, WATCHDOG_TIMEOUT_CLEAR_CONTROL_REG, WATCHDOG_CLEAR);
if (ret < 0) {
dev_err(&wd_data->client->dev, "failed to ping watchdog: %d\n", ret);
return ret;
}
return 0;
}
static int jetson_mcu_watchdog_set_timeout(struct watchdog_device *wdd, unsigned int timeout)
{
struct mcu_wtd_data *wd_data = watchdog_get_drvdata(wdd);
int ret = 0;
ret = i2c_smbus_write_byte_data(wd_data->client, WATCHDOG_TIMEOUT_RELOAD_REG, timeout);
if (ret < 0) {
dev_err(&wd_data->client->dev, "failed to set timeout watchdog: %d\n", ret);
return ret;
}
wdd->timeout = timeout;
return 0;
}
注销watchdog函数
static int i2c_wtd_remove(struct i2c_client *client)
{
struct mcu_wtd_data *wd_data = i2c_get_clientdata(client);
watchdog_unregister_device(&wd_data->wdd);
return 0;
}
驱动卸载函数
static void __exit watchdog_driver_exit(void)
{
i2c_del_driver(&mcu_watchdog_driver);
}
至此,我们的i2c watchdog驱动开发流程已经完毕,接下来就是通过用户层程序使用ioctl()进行访问/dev/watchdog节点来调试。
参考链接:
https://blog.csdn.net/changqing1990/article/details/127511793?spm=1001.2014.3001.5502
https://www.kernel.org/doc/Documentation/watchdog/watchdog-kernel-api.txt