一、修改设备树

1、在pinctrl子系统对应节点添加子节点,即在 iomuxc 、iomuxc_snvs 节点添加新的子节点,即添加设备对应的 IO 。

示例:


pinctrl_i2c1: i2c1grp { 
  fsl,pins = < 
   MX6UL_PAD_UART4_TX_DATA__I2C1_SCL 0x4001b8b0
   MX6UL_PAD_UART4_RX_DATA__I2C1_SDA 0x4001b8b0
  >;
};
 
节点标签:节点名{
        引脚属性字符串 = <
            使用的引脚_引脚复用功能 电气特性(IO 的上/下拉、驱动能力和速度等)
        >;
}
 
注意:节点标签,节点名可自定义,但节点标签前缀一定要为 pinctrl_

2、在 I2C 、SPI 等节点下添加子节点,杂项驱动(MISC)在根节点下添加节点,配置设备相关属性信息:

示例:


&i2c1 {
 clock-frequency = ;
 pinctrl-names = "default";
 pinctrl-0 = <&pinctrl_i2c1>;
 status = "okay";
 
 ap3216c@1e { 
  compatible = "iot,ap3216c"; 
  reg = ;
 }; 
 
};
 
 
&节点标签 {
 属性 = 值;
 pinctrl-names = "default";    //设备的状态(state),default(默认状态)和sleep(休眠状态)。
 pinctrl-0 = <&节点标签>;        //"default"状态,对应的引脚在pinctrl-0定义
 pinctrl-1 = <&节点标签>;     //"sleep"状态,对应的引脚在pinctrl-1定义
 status = "okay";               //状态值
 
        /*追加子节点-i2c1上的设备*/
 子节点名@地址 { 
        兼容性属性 = 匹配字符串;
  reg = ;    //地址
 }; 
 
};

3、编译设备树


make dtbs
二、编写设备驱动、应用程序

1、编写设备驱动程序


/* 驱动的probe函数 */
static int xxx_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
 /* 具体实现 */
}
 
/* 驱动的remove函数 */
static int xxx_remove(struct i2c_client *client)
{
 /* 具体实现 */
}
 
/* 传统匹配方式ID列表 */
static const struct i2c_device_id xxx_id[] = {
 {"xxx,xxx", 0},  //匹配在设备树对应xxx节点下定义的compatible 
 {}
};
 
/* 设备树匹配列表 */
static const struct of_device_id ap3216c_of_match[] = {
 { .compatible = "iot,xxx" },    //匹配在设备树对应xxx节点下定义的compatible
 { /* Sentinel */ }
};
 
//=======================================================================================
/* i2c驱动结构体 */ 
static struct i2c_driver xxx_driver = {
 .probe = xxx_probe,        //探测函数(查询、注册、创建设备等)
 .remove = xxx_remove,    //卸载函数(删除设备)
 .driver = {
   .owner = THIS_MODULE,  //当前模块
      .name = "xxx",        //设备名
      .of_match_table = xxx_of_match, //设备树匹配表(匹配设备树里面的定义)
     },
 .id_table = xxx_id,    //id匹配表
};
//=======================================================================================
     
/* 驱动入口函数 */
static int __init xxx_init(void)
{
 int ret = 0;
 
 ret = i2c_add_driver(&xxx_driver);
 return ret;
}
 
/* 驱动出口函数 */
static void __exit xxx_exit(void)
{
 i2c_del_driver(&xxx_driver);
}
 
module_init(xxx_init);    //指定驱动入口函数
module_exit(xxx_exit);    //指定驱动出口函数
 
MODULE_LICENSE("GPL");    //LICENSE
MODULE_AUTHOR("pjw");     //作者信息

(1)根据结构体需要编写对应的 probe函数、remove函数、设备树匹配表、ID匹配表等。

  • platform 有对应的 platform_driver 结构体。
  • I2C 有对应的 i2c_driver 结构体。
  • SPI 有对应的 spi_driver 结构体。

(2)指定驱动入口函数和出口函数。

  • platform 使用 platform_driver_register(注册)函数、platform_driver_unregister(注销)函数。
  • I2C 使用 i2c_add_driver(注册)函数、i2c_del_driver (注销)函数。
  • SPI 使用 spi_register_driver(注册)函数、spi_unregister_driver(注销)函数。

2、编写应用程序

3、编写Makefile

三、编译

make -j12    //编译驱动
arm-linux-gnueabihf-gcc xxxApp.c -o xxxApp    //编译应用程序
四、测试设备驱动

1、Linux 系统选择通过 TFTP 从网络启动,并且使用 NFS 挂载网络根文件系统

(1)将编译出的 imx6ull-iot-emmc.dtb (arch/arm/boot/dts/目录)与 zImage(arch/arm/boot/目录)复制到 tftp 文件夹

(2)设置 bootargs 环境变量,根文件系统从 nfs/roofts 启动


setenv bootargs 'console=ttymxc0,115200 root=/dev/nfs rw nfsroot=192.168.137.18:/home/pjw/linux/nfs/rootfs ip=192.168.137.20:192.168.137.18:192.168.1.1:255.255.255.0::eth0:off'

(3)设置 bootcmd 环境变量,通过 TFTP 启动内核和设备树


setenv bootcmd 'tftp 80800000 zImage;tftp 83000000 imx6ull-iot-emmc.dtb;bootz 80800000 - 83000000'

(4)使用新的 imx6ull-alientek-emmc.dtb 启动 Linux 内核。启动成功以后进入 /proc/device-tree/ 目录中查看节点。

2、加载驱动模块


depmod                 //第一次加载驱动的时候需要运行此命令
modprobe xxx.ko         //加载驱动 xxx
 
lsmod                   //查看当前系统中存在的模块
cat /proc/devices        //查看当前系统中的设备

3、测试应用程序


./xxxApp /dev/xxx