文章目录

  • 一、IIC驱动实验
    • 简介
  • 二、Linux I2C 驱动框架简介
    • 1、 I2C 总线驱动
    • 2、I2C 设备驱动
  • 三、设备驱动编写
    • 1、设备树编写
    • 2、设备树编写测试
  • 四、驱动编写
  • 五、测试
  • 总结
  • 联系

一、IIC驱动实验

简介

Linux 下的 I2C 驱动框架,按照指定的框架去编写 I2C 设备驱动总线驱动实现CPU与器件通信的底层,类似单片机IIC协议的底层时序实现过程,单片机需要自己编写但是linux下不需要自己编写,自己需要实现的只是设备树的设备信息和驱动器件的底层寄存器(这里要求我们按照IIC框架编写)

i2c 总线驱动由芯片厂商提供(驱动复杂,官方提供了经过测试的驱动,我们直接用) 。本篇文章编写的是IIC的oled设备驱动

二、Linux I2C 驱动框架简介

1、 I2C 总线驱动

属于CPU级别的代码,CPU核心算法外设级别

  • 一般 SOC 的 I2C 总线驱动都是由半导体厂商编写的,比如 I.MX6U 的 I2C 适配器驱动 NXP已经编写好了,这个不需要用户去编写。

大体工作过程:
I2C 总线驱动重点是 I2C 适配器(也就是 SOC 的 I2C 接口控制器)驱动,这里要用到两个重要的数据结构:i2c_adapter 和 i2c_algorithm,Linux 内核将 SOC 的 I2C 适配器(控制器)抽象成 i2c_adapter,实现初始化结构体的成员变量到达实现功能的效果。i2c_adapter 结构体定义在 include/linux/i2c.h 文件中

  • i2c_adapter is the structure used to identify a physical i2c bus along
  • with the access algorithms necessary to access it.
struct i2c_adapter {struct module *owner;unsigned int class;       /* classes to allow probing for */const struct i2c_algorithm *algo; /* the algorithm to access the bus */void *algo_data;/* data fields that are valid for all devices    */struct rt_mutex bus_lock;int timeout;         /* in jiffies */int retries;struct device dev;      /* the adapter device */int nr;char name[48];struct completion dev_released;struct mutex userspace_clients_lock;struct list_head userspace_clients;struct i2c_bus_recovery_info *bus_recovery_info;const struct i2c_adapter_quirks *quirks;
};
struct i2c_algorithm {/* If an adapter algorithm can't do I2C-level access, set master_xferto NULL. If an adapter algorithm can do SMBus access, setsmbus_xfer. If set to NULL, the SMBus protocol is simulatedusing common I2C messages *//* master_xfer should return the number of messages successfullyprocessed, or a negative value on error */int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs,int num);int (*smbus_xfer) (struct i2c_adapter *adap, u16 addr,unsigned short flags, char read_write,u8 command, int size, union i2c_smbus_data *data);/* To determine what the adapter supports */u32 (*functionality) (struct i2c_adapter *);#if IS_ENABLED(CONFIG_I2C_SLAVE)int (*reg_slave)(struct i2c_client *client);int (*unreg_slave)(struct i2c_client *client);
#endif
};

在这个结构体里面有i2c_algorithm 结构体,就是具体实现设备驱动程序可以使用这些 API 函数来完成读写操作。i2c_algorithm 就是 I2C 适配器与 IIC 设备进行通信的方法。

  • 综上所述,I2C 总线驱动,或者说 I2C 适配器驱动的主要工作就是初始化 i2c_adapter 结构体变量,然后设置
    i2c_algorithm 中的 master_xfer 函数。完成以后通过 i2c_add_numbered_adapter或
    i2c_add_adapter 这两个函数向系统注册设置好的 i2c_adapte

2、I2C 设备驱动

i2c_client 就是描述设备信息的,i2c_driver 描述驱动内容,类似于 platform_driver。

  • 一个设备对应一个 i2c_client,每检测到一个 I2C 设备就会给这个 I2C 设备分配一个i2c_client。
  • i2c_driver 类似 platform_driver,是我们编写 I2C 设备驱动重点要处理的内容。
  • 对于我们 I2C 设备驱动编写人来说,重点工作就是构建 i2c_driver,构建完成以后需要向Linux 内核注册这个 i2c_driver。i2c_driver 注册函数为 int i2c_register_driver

三、设备驱动编写

这里以OLED为例驱动获取这个芯片数据

1、设备树编写

因为有设备树统一管理硬件设备信息,所以直接采用设备树编写驱动先修改设备树信息,第一就是IO 修改或添加,用于配置CPU外设信息以及引脚的电气属性。

首先查找iomuxc设备节点pinctrl 子系统编写引脚属性,添加属性数据

     pinctrl_i2c1: i2c1grp {fsl,pins = <MX6UL_PAD_UART4_TX_DATA__I2C1_SCL 0x4001b8b0MX6UL_PAD_UART4_RX_DATA__I2C1_SDA 0x4001b8b0>;};

第二步追加设备节点到总线
追加 ap3216c 子节点挂载到iic总线上面
记住同一总线不能挂载相同slave adress的从机ic设备
比如这里使用NXP官方板子evk设备树,其板子上面具有这个芯片

这个设备需要删除
追加信息

OLED@3c{compatible = "liqi,oled";reg =<0x3c>;}

2、设备树编写测试

  • make dtbs
  • 编译成功以后拷贝dtb到nfs目录远程加载
  • reboot重启开发板,进入IIC总线观察是否添加成功

总线挂载目录cd/sys/bus
i2c总线下挂载的设备目录

  • ls设备驱动都可以查看

    从这里设备就编写成功了

四、驱动编写

#include <linux/types.h>#include <linux/kernel.h>#include <linux/delay.h>#include <linux/ide.h>#include <linux/init.h>#include <linux/module.h>#include <linux/errno.h>#include <linux/gpio.h>#include <linux/cdev.h>#include <linux/device.h>#include <linux/of_gpio.h>#include <linux/semaphore.h>#include <linux/timer.h>#include <linux/i2c.h>#include <asm/mach/map.h>#include <asm/uaccess.h>#include <asm/io.h>#include "oledfont.h"#define OLED_CNT  1#define OLED_NAME  "oled_iic"#define OLED_CMD  0x00  //OLED写命令#define OLED_DATA 0x40 //OLED写数据#define Max_Column 128/* 字符设备结构体 */struct oled_dev {struct i2c_client *client; /* i2c 设备 */dev_t devid;            /* 设备号   */int major;           /* 主设备号 */int minor;            /* ci设备号 */struct cdev cdev;        /*cdev 结构体变量,这个变量就表示一个字符设备*/struct class *class; /* 类        */struct device *device;    /* 设备    */struct device_node   *nd; /* 设备节点 */void *private_data;  /* 私有数据 */};static struct oled_dev oledcdev;static s32 oled_write_byte(u8 reg, u8 para, u8 len){u8 data[2];struct i2c_msg msg;struct i2c_client *client = (struct i2c_client *)oledcdev.private_data;//此处将.probe函数中所保存的私有数据强制转换为i2c_client结构体data[0] = reg; //寄存器data[1] = para; //参数msg.addr = client->addr; //ap3216c地址, 设备树中的地址msg.flags = 0; //标记为写msg.buf = data; //要写入的数据缓冲区msg.len = len + 1; //要写入的数据长度return i2c_transfer(client->adapter, &msg, 1);//用于发送的client}void oled_init(void){u8 i;u8 data[] = {0xAE, 0x00, 0x10, 0x40, 0xB0, 0x81, 0xFF, 0xA1, 0xA6,0xA8, 0x3F, 0xC8, 0xD3, 0x00, 0xD5, 0x80, 0xD8, 0x05,0xD9, 0xF1, 0xDA, 0x12, 0xDB, 0x30, 0x8D, 0x14, 0xAF};for (i = 0; i < sizeof(data); i++){oled_write_byte(OLED_CMD, data[i], 1);}}void oled_clear(void){u8 i, n;for (i = 0; i < 8; i++){oled_write_byte(OLED_CMD, 0xb0 + i, 1); //设置页地址(0~7)oled_write_byte(OLED_CMD, 0x00, 1); //设置显示位置—列低地址oled_write_byte(OLED_CMD, 0x10, 1); //设置显示位置—列高地址for (n = 0; n < 128; n++){oled_write_byte(OLED_DATA, 0x00, 1);}}}void oled_set_pos(u8 x, u8 y){oled_write_byte(OLED_CMD, 0xb0 + y, 1);oled_write_byte(OLED_CMD, ((x & 0xf0) >> 4) | 0x10, 1);oled_write_byte(OLED_CMD, x & 0x0f, 1);}void oled_showchar(u8 x, u8 y, u8 chr){u8 c = 0, i = 0;c = chr - ' ';if (x > Max_Column - 1){x = 0;y = y + 2;}oled_set_pos(x, y);for (i = 0; i < 8; i++){oled_write_byte(OLED_DATA, F8X16[c * 16 + i], 1);}oled_set_pos(x, y + 1);for (i = 0; i < 8; i++){oled_write_byte(OLED_DATA, F8X16[c * 16 + i + 8], 1);}}void oled_showstring(u8 x, u8 y, u8 *chr){unsigned char j = 0;while (chr[j] != '\0'){oled_showchar(x, y, chr[j]);x += 8;if (x > 120){x = 0;y += 2;}j++;}}static int oled_open(struct inode *inode, struct file *filp){oled_init();oled_clear();printk("oled_init ");oled_showstring(0,2,"hello Linux iic");printk("oled_init success");return 0;}struct display_stru{//IIC从用户控件接收的数据格式int x;int y;char *buf;};static ssize_t oled_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *off){int ret;struct display_stru dis_format;ret = copy_from_user(&dis_format, buf, cnt);printk("dis_format.x = %d \r\n", dis_format.x);//调试打印,用于观察从用户控件获取的数据是否正确,可删除printk("dis_format.y = %d \r\n", dis_format.y);printk("dis_format.buf = %s \r\n", dis_format.buf);oled_showstring(dis_format.x, dis_format.y, dis_format.buf);return 0;}static int oled_release(struct inode *inode, struct file *filp){return 0;}/* Ops操作函数 */static const struct file_operations oled_ops = {.owner = THIS_MODULE,.open = oled_open,.read = oled_write,.release = oled_release,};/** @description     : i2c驱动的probe函数,当驱动与*                    设备匹配以后此函数就会执行* @param - client  : i2c设备* @param - id      : i2c设备ID* @return          : 0,成功;其他负值,失败*/static int oled_probe(struct i2c_client *client, const struct i2c_device_id *id){/* 1、构建设备号 */if (oledcdev.major) {oledcdev.devid = MKDEV(oledcdev.major, 0);//MKDEV 用于将给定的主设备号和次设备号的值组合成 dev_t 类型的设备号register_chrdev_region(oledcdev.devid, OLED_CNT, OLED_NAME);//参数 from 是要申请的起始设备号,也就是给定的设备号;//参数 count 是要申请的数量,一般都是一个;参数 name 是申请设备名字} else {alloc_chrdev_region(&oledcdev.devid, 0, OLED_CNT, OLED_NAME);oledcdev.major = MAJOR(oledcdev.devid);oledcdev.minor = MINOR(oledcdev.devid);}/* 2、注册设备 */cdev_init(&oledcdev.cdev, &oled_ops);/* 初始化 cdev 结构体变量 *///cdev 就是要初始化的 cdev 结构体变量,参数 fops 就是字符设备文件操作函数集合cdev_add(&oledcdev.cdev, oledcdev.devid, OLED_CNT);/* 3、创建类用于自动生成设备节点 */oledcdev.class = class_create(THIS_MODULE, OLED_NAME);//默认参数、类名与申请的设备名字相同即可if (IS_ERR(oledcdev.class)) {return PTR_ERR(oledcdev.class);}/* 4、创建设备 */oledcdev.device = device_create(oledcdev.class, NULL, oledcdev.devid, NULL, OLED_NAME);if (IS_ERR(oledcdev.device)) {return PTR_ERR(oledcdev.device);}oledcdev.private_data = client;printk("match success");return 0;}/** @description     : i2c驱动的remove函数,移除i2c驱动的时候此函数会执行* @param - client     : i2c设备* @return          : 0,成功;其他负值,失败*/static int oled_remove(struct i2c_client *client){/* 删除设备 */cdev_del(&oledcdev.cdev);unregister_chrdev_region(oledcdev.devid, 1);/* 注销掉类和设备 */device_destroy(oledcdev.class, oledcdev.devid);class_destroy(oledcdev.class);return 0;}/* 传统匹配方式ID列表 */static const struct i2c_device_id oled_id[] = {{"liqi,iic_oled", 0},  {}};/* 设备树匹配列表 */static const struct of_device_id oled_of_match[] = {{ .compatible = "liqi,iic_oled"},{ /* Sentinel */ }};/* i2c驱动结构体 */    static struct i2c_driver oled_driver = {.probe = oled_probe,.remove = oled_remove,.driver = {.owner = THIS_MODULE,.name = "liqi,iic_oled",.of_match_table = oled_of_match, },.id_table = oled_id,};/** @description  : 驱动入口函数* @param       : 无* @return       : 无*/static int __init liqi_oled_init(void){int ret = 0;ret = i2c_add_driver(&oled_driver);return ret;}/** @description  : 驱动出口函数* @param       : 无* @return       : 无*/static void __exit liqi_oled_exit(void){i2c_del_driver(&oled_driver);}module_init(liqi_oled_init);
module_exit(liqi_oled_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("liqi");

五、测试

总结

联系

Linux驱动BSP(I2C 驱动实验)相关推荐

  1. linux i2c 读写函数,Linux内核调用I2C驱动_实现MPU6050的数据读取

    0. 导语 最近一段时间都在恶补数据结构和C++,加上导师的事情比较多,Linux内核驱动的学习进程总是被阻碍.不过,十一假期终于没有人打扰,有这个奢侈的大块时间,可以一个人安安静静的在教研室看看Li ...

  2. 《Linux驱动:I2C驱动看这一篇就够了》

    文章目录 一,前言 二,IIC驱动的体系架构 2.1 IIC核心 2.2 IIC适配器 2.2.1 适配器驱动资源的初始化和注册 2.2.2 IIC适配器里的通信方法 2.2.3 IIC适配器和IIC ...

  3. linux用户空间flash驱动,全面掌握Linux驱动框架——字符设备驱动、I2C驱动、总线设备驱动、NAND FLASH驱动...

    原标题:全面掌握Linux驱动框架--字符设备驱动.I2C驱动.总线设备驱动.NAND FLASH驱动 字符设备驱动 哈~ 这几天都在发图,通过这种方式,我们希望能帮大家梳理学过的知识,全局的掌握Li ...

  4. S3C2440 Linux下的I2C驱动以及I2C体系下对EEPROM进行读写操作。

    这篇文档算上期末复习这段时间其实拖了有好久了,因为从一开始接触linux的i2c驱动体系我就各种凌乱,因为起初脑海中既没有整体框架也不熟悉相关体系下的结构,所以四处乱看,经常性的在看内核代码时看着看着 ...

  5. 第三章 rt-thread设备驱动模型-i2c驱动

    rt-thread设备驱动模型-i2c驱动 1. 回顾 前面两章分别介绍了rt-thread设备驱动框架的实现原理,以及介绍了一个简单的看门狗驱动程序,用来加深对驱动框架的理解.看门狗驱动程序最终归纳 ...

  6. CMOS摄像头驱动分析-i2c驱动

    CMOS摄像头驱动分析-i2c驱动 文章目录 CMOS摄像头驱动分析-i2c驱动 设备树内容 module_i2c_driver宏分析 ov2640_i2c_driver ov2640_probe 设 ...

  7. linux 触摸结构体,xboot-x4412ibox项目实战54-Linux触摸屏驱动之I2C驱动实验 - Powered by Discuz!...

    前面我们分析了linux触摸屏驱动的input子系统机制,本章节分析linux触摸屏驱动的i2c机制. 驱动源码路径: kernel/drivers/input/touchscreen/ft5x06_ ...

  8. linux驱动:i2c驱动(二)

    3.驱动源码分析 IPNC_RDK_V3.8.0.1/Source/ti_tools/ipnc_psp_arago/kernel/sound/soc/codecs/tlv320aic3x.c 3.1 ...

  9. linux i2c adapter 增加设备_Linux驱动之I2C驱动架构

    一.Linux的I2C体系结构 主要由三部分组成: (1) I2C核心 提供I2C控制器和设备驱动的注册和注销方法,I2C通信方法,与适配器无关的代码以及探测设备等. (2) I2C控制器驱动(适配器 ...

  10. linux下的I2C驱动记录(RK)

    现在做的是RK平台的I2C驱动,不是说linux每个外设对应一个设备文件吗?可是找了一下一直没找到,今天特别搞了一下.I2C设备是注册为platform平台驱动的. 下面是我用adb命令查看的 lin ...

最新文章

  1. 可能是GitHub上最好用的文字语法校验工具
  2. 六、爬虫中重要的解析库xpath和BeautifulSoup
  3. 应用安全-操作系统安全-漏洞修复整理
  4. JavaFX 2 GameTutorial第5部分
  5. 信息学奥赛一本通 1111:不高兴的津津 | 1926:【04NOIP普及组】不高兴的津津| OpenJudge NOI 1.9 03 | 洛谷 P1085 [NOIP2004 普及组] 不高兴的津津
  6. 十个网志流量统计工具一览
  7. 生命真的很脆弱,理性看待过劳
  8. 计算机联锁论文致谢,计算机联锁系统 毕业设计论文 定稿.doc
  9. oppo手机解锁_oppo手机密码解锁大全【图文】
  10. 第8代CPU i5-8250U 电脑安装核显 Windows 7 x64位驱动
  11. 利用python多个工作簿合并到一个工作簿
  12. 云服务器项目乱码,云服务器Ubuntu系统下中文乱码问题解决方案
  13. c语言中的整型常量和实型常量
  14. 奥托尼克斯接近开关型号_奥托尼克斯接近开关型号如何选
  15. 美育在计算机教育中应用,浅谈在小学信息技术课堂中有效实施美育.
  16. [艾兰岛]制作传送门之boss技能——kura酱长期更新
  17. python图像轮廓识别_Python+OpenCV图像处理(十六)—— 轮廓发现
  18. 南邮 OJ 1446 嘉娃的难题
  19. python学习的第一个星期
  20. Spring框架【超详细学习笔记】

热门文章

  1. 知识图谱和行业领域的结合产物-KGB知识图谱介绍
  2. 笔记本换SSD固态硬盘,系统速度可以提升吗?
  3. 获取本地视频文件的缩略图
  4. 游戏开发物语方案点数分配_《游戏开发物语》官方超全新手攻略和名词解析!...
  5. socket 10106问题解决日记
  6. mysql jdbc dao_MYSQL 之 JDBC(九):增删改查(七)DAO的补充和重构
  7. PHP防范XSS攻击
  8. 没有心的男人 2012-02-08 21:11:06
  9. 推荐一款制作精良、功能强大、毫秒精度、专业级的定时任务执行软件 —— 定时执行专家
  10. ETL——实现Kettle作业定时任务