当前位置: 首页 > news >正文

Linux驱动学习day18(I2C设备ap3216c驱动编写)

一、ap3216c驱动程序

下面这段读函数的代码主要是发送读信号给client并从client中读到信号。由于是char kernelbuf,所以这边读到16位的字节用两个buf来存,kernel_buf[0] = val & 0xff; 从读到的数据取出低8位字节放到这个位置。kernel_buf[1] = (val >> 8) & 0xff;从读到的数据取出高8位字节放到这个位置。

static ssize_t ap3216c_read (struct file *file, char __user *buf, size_t size, loff_t *off)
{printk(KERN_INFO "[ap3126c_drv] %s %s:%d\n" , __FILE__ , __FUNCTION__ , __LINE__);char kernel_buf[6];int val;if(size != 6){return -EINVAL;}val = i2c_smbus_read_word_data(ap3216c_client, 0xA);kernel_buf[0] = val & 0xff;kernel_buf[1] = (val >> 8) & 0xff;val = i2c_smbus_read_word_data(ap3216c_client, 0xC);kernel_buf[2] = val & 0xff;kernel_buf[3] = (val >> 8) & 0xff;val = i2c_smbus_read_word_data(ap3216c_client, 0xE);kernel_buf[4] = val & 0xff;kernel_buf[5] = (val >> 8) & 0xff;if(copy_to_user(buf, kernel_buf, size) != 0){return -1;} return size;
}
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/of_device.h>
#include <linux/slab.h>
#include <linux/delay.h>
#include <linux/mutex.h>
#include <linux/mod_devicetable.h>
#include <linux/log2.h>
#include <linux/fs.h>
#include <linux/bitops.h>
#include <linux/jiffies.h>
#include <linux/property.h>
#include <linux/acpi.h>
#include <linux/i2c.h>
#include <linux/nvmem-provider.h>
#include <linux/regmap.h>
#include <linux/platform_data/at24.h>
#include <linux/pm_runtime.h>
#include <linux/gpio/consumer.h>static int major;
static struct class *ap3216c_class;
static struct i2c_client *ap3216c_client;static const struct of_device_id ap3216c_of_match[] = {{ .compatible = "com_name,chip_name",		.data = NULL },{ /* END OF LIST */ },
};static const struct i2c_device_id ap3216c_ids[] = {{ "chip_name",	(kernel_ulong_t)NULL },{ /* END OF LIST */ }
};static int ap3216c_open (struct inode *inode, struct file *file)
{/* reset */i2c_smbus_write_byte_data(ap3216c_client, 0, 0x4);mdelay(20);/* enable */i2c_smbus_write_byte_data(ap3216c_client, 0, 0x3);return 0;
}static ssize_t ap3216c_read (struct file *file, char __user *buf, size_t size, loff_t *off)
{printk(KERN_INFO "[ap3126c_drv] %s %s:%d\n" , __FILE__ , __FUNCTION__ , __LINE__);char kernel_buf[6];int val;if(size != 6){return -EINVAL;}val = i2c_smbus_read_word_data(ap3216c_client, 0xA);kernel_buf[0] = val & 0xff;kernel_buf[1] = (val >> 8) & 0xff;val = i2c_smbus_read_word_data(ap3216c_client, 0xC);kernel_buf[2] = val & 0xff;kernel_buf[3] = (val >> 8) & 0xff;val = i2c_smbus_read_word_data(ap3216c_client, 0xE);kernel_buf[4] = val & 0xff;kernel_buf[5] = (val >> 8) & 0xff;if(copy_to_user(buf, kernel_buf, size) != 0){return -1;} return size;
}static struct file_operations i2c_example_ops = {.owner   = THIS_MODULE,.open    = ap3216c_open,.read    = ap3216c_read,
}static int ap3216c_probe(struct i2c_client *client)
{printk(KERN_INFO "[ap3126c_drv] %s %s:%d\n" , __FILE__ , __FUNCTION__ , __LINE__);ap3216c_client = client;/* register_chrdev */major = register_chrdev(0 , "ap3216c_drv" , &i2c_example_ops);ap3216c_class = class_create(THIS_MODULE, "ap3216c_class");device_create(ap3216c_class, NULL, MKDEV(major, 0), NULL, "ap3216c");return 0;
}static int ap3216c_remove(struct i2c_client *client)
{printk(KERN_INFO "[ap3126c_drv] %s %s:%d\n" , __FILE__ , __FUNCTION__ , __LINE__);device_destroy(ap3216c_class, MKDEV(major, 0));class_destroy(ap3216c_class);unregister_chrdev(major, "ap3216c_drv");return 0;
}static struct i2c_driver ap3216c_driver = {.driver = {.name = "ap3216c",.of_match_table = ap3216c_of_match,},.probe_new = ap3216c_probe,.remove = ap3216c_remove,.id_table = ap3216c_ids,
};static int __init ap3216c_init(void)
{printk(KERN_INFO "[ap3126c_drv] %s %s:%d\n" , __FILE__ , __FUNCTION__ , __LINE__);return i2c_add_driver(&ap3216c_driver);
}
module_init(ap3216c_init);static void __exit ap3216c_exit(void)
{printk(KERN_INFO "[ap3126c_drv] %s %s:%d\n" , __FILE__ , __FUNCTION__ , __LINE__);i2c_del_driver(&ap3216c_driver);
}
module_exit(ap3216c_exit);MODULE_LICENSE("GPL");

二、I2C_Adapter驱动框架

2.1、核心结构体

在Linux中,总是使用一个结构体来描述一个对象,在I2C中也是如此,使用i2c_adapter结构体描述一个i2c总线(i2c适配器、i2c控制器)。

2.1.1 i2c_adapter结构体

2.1.2 i2c_algorithm结构体

master_xfer:这是最重要的函数,实现了一般的I2C传输,用来传输一个或者多个i2c_msg。

2.2 驱动框架

分配、设置、注册 i2c_adapter 结构体。一切都和字符设备驱动框架一样,只不过完善的是不同的结构体,有了设备树之后方便许多了。

2.2.1 设备树中添加一个i2c结点

2.2.2 写驱动程序注册platform_driver,并且在probe中分配、设置、注册 i2c_adapter 结构体

/* 不确认adapter中的nr成员使用,nr = -1系统会自动分配 */
i2c_add_adapter /* 确定使用的数字 */
i2c_add_numbered_adapter

2.2.3 完善i2c_adapter结构体中的重要成员

static int i2c_bus_virtual_probe(struct platform_device *pdev)
{printk("%s %s %d\n" , __FILE__ , __FUNCTION__ , __LINE__);/* register set i2c_adapter  */g_adap = kzalloc(sizeof(*g_adap), GFP_KERNEL);g_adap->owner = THIS_MODULE;g_adap->class = I2C_CLASS_HWMON | I2C_CLASS_SPD;g_adap->nr = -1;snprintf(g_adap->name, sizeof(g_adap->name), "i2c_virtual");g_adap->algo = &i2c_bus_virtual_algorithm;i2c_add_adapter(g_adap);return 0;
}

2.2.4 i2c_algorithm结构体中实现master_xfer函数

这里主要有三种情况,下面列出各个情况使用i2ctool的命令:

1、写错地址:

i2cset -y -f 7 0x55 1 0x56

由于这个驱动程序中只有一个0x50的虚拟设备,所以地址不是0x50会直接返回错误号。

2、写数据到virtual_eeporm中

i2cset -y -f 7 0x50 1 0x56

该命令的意思是,使用i2c 7 控制器,往0x50设备中的1地址写入0x56。 

3、从virtual_eeporm中读取数据

i2cget -y -f 7 0x50 1

读数据比较复杂,模拟eeporm来读写数据,当收到这个指令时,其实会收到两条msg。第一条msg是写信号,写入要读取的地址。第二条信号是读信号,读之前写的地址中的数据。

msgs[0].addr  = 0x50;
msgs[0].flags = 0;  // 写
msgs[0].len   = 1;
msgs[0].buf   = [ 0x03 ];  // 要读取的寄存器地址msgs[1].addr  = 0x50;
msgs[1].flags = I2C_M_RD;  // 读
msgs[1].len   = 1;
msgs[1].buf   = [ ]  // 将读取到的数据存入这里
static int eeporm_cur_addr = 0;
static char eeporm_buffer[512];static eeporm_emulate_xfer(struct i2c_adapter *adap , struct i2c_msg *msgs)
{int i;if(msgs->flags & I2C_M_RD){msgs->buf[i] = eeporm_buffer[eeporm_cur_addr++];if(eeporm_cur_addr == 512)eeporm_cur_addr = 0;}else{eeporm_cur_addr = msgs->buf[0];for(i = 1 ; i < msgs->len ; i++){eeporm_buffer[eeporm_cur_addr++] = msgs->buf[i];if(eeporm_cur_addr == 512)eeporm_cur_addr = 0;}}return 0;
}static int i2c_bus_virtual_master_xfer(struct i2c_adapter *adap, struct i2c_msg msgs[], int num)
{int i;int j;while (i < num){if(msgs.addr != 0x50){i = -EIO;return i;}/* 两条指令 i2cget -y -f 7 0x50 1*/if( (i + 1 < num) && !(msgs[i].flags == I2C_M_RD)&& (msgs[i + 1].flags == I2C_M_RD)){eeporm_cur_addr = msgs[i][0];for(j = 0 ; j < msgs[i + 1]->len ; i++){msgs[i+1].buf[j] = eeporm_buffer[eeporm_cur_addr++];if(eeporm_cur_addr == 512)eeporm_cur_addr = 0;}i += 2;}else{/* 只有一条指令的时候 */eeporm_emulate_xfer(adap , &msg[i]);i++;}}return 0;
}
static int i2c_bus_virtual_master_xfer(struct i2c_adapter *adap, struct i2c_msg msgs[], int num){int i;for(i = 0 ; i < num ; i++){if (msgs[i].addr == 0x50) {   eeporm_emulate_xfer(adap , &msgs[i]);}else{i = -EIO;break;}}return i;}

2.2.5 完整代码

#include <linux/debugfs.h>
#include <linux/delay.h>
#include <linux/i2c.h>
#include <linux/i2c-algo-bit.h>
#include <linux/platform_data/i2c-gpio.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/platform_device.h>
#include <linux/gpio/consumer.h>
#include <linux/of.h>static struct i2c_adapter *g_adap;
static char eeporm_buffer[512];
static int eeporm_cur_addr = 0;static const struct of_device_id i2c_bus_virtual_dt_ids[] = {{ .compatible = "xupt,i2c_virtual", },{ /* sentinel */ }
};static int eeporm_emulate_xfer(struct i2c_adapter *adap , struct i2c_msg *msgs)
{int i;if(msgs->flags & I2C_M_RD){for(i = 0 ; i < msgs->len ; i++){msgs->buf[i] = eeporm_buffer[eeporm_cur_addr++];if(eeporm_cur_addr == 512)eeporm_cur_addr = 0;}}else{eeporm_cur_addr = msgs->buf[0];for(i = 1 ; i < msgs->len ; i++){eeporm_buffer[eeporm_cur_addr++] = msgs->buf[i];if(eeporm_cur_addr == 512)eeporm_cur_addr = 0;}}return 0;
}// static int i2c_bus_virtual_master_xfer(struct i2c_adapter *adap, struct i2c_msg msgs[], int num)
// {
//     int i;
//     for(i = 0 ; i < num ; i++)
//     {
//         if (msgs[i].addr == 0x50) 
//         {   
//             eeporm_emulate_xfer(adap , &msgs[i]);
//         }
//         else
//         {
//             i = -EIO;
//             break;
//         }
//     }//     return i;
// }
static int i2c_bus_virtual_master_xfer(struct i2c_adapter *adap, struct i2c_msg msgs[], int num)
{int i = 0;int j;printk(KERN_INFO"num of msgs = %d" , num);while (i < num) {if (msgs[i].addr != 0x50)return -EIO;if ((i + 1 < num) &&!(msgs[i].flags & I2C_M_RD) &&(msgs[i+1].flags & I2C_M_RD)) {eeporm_cur_addr = msgs[i].buf[0];for (j = 0; j < msgs[i+1].len; j++) {msgs[i+1].buf[j] = eeporm_buffer[eeporm_cur_addr++];if (eeporm_cur_addr == 512)eeporm_cur_addr = 0;}i += 2;  }else {eeporm_emulate_xfer(adap, &msgs[i]);i++;}}return num;
}static u32 i2c_bus_virtual_functionality(struct i2c_adapter *adap)
{return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL |I2C_FUNC_PROTOCOL_MANGLING | I2C_FUNC_NOSTART;
}static const struct i2c_algorithm i2c_bus_virtual_algorithm = {.master_xfer	= i2c_bus_virtual_master_xfer,.functionality	= i2c_bus_virtual_functionality,
};static int i2c_bus_virtual_probe(struct platform_device *pdev)
{printk("%s %s %d\n" , __FILE__ , __FUNCTION__ , __LINE__);/* register set i2c_adapter  */g_adap = kzalloc(sizeof(*g_adap), GFP_KERNEL);g_adap->owner = THIS_MODULE;g_adap->class = I2C_CLASS_HWMON | I2C_CLASS_SPD;g_adap->nr = -1;snprintf(g_adap->name, sizeof(g_adap->name), "i2c_virtual");g_adap->algo = &i2c_bus_virtual_algorithm;i2c_add_adapter(g_adap);return 0;
}static int i2c_bus_virtual_remove(struct platform_device *pdev)
{printk("%s %s %d\n" , __FILE__ , __FUNCTION__ , __LINE__);i2c_del_adapter(g_adap);kfree(g_adap);return 0;
}static struct platform_driver i2c_bus_virtual_driver = {.driver		= {.name	= "xupt,i2c_virtual",.of_match_table	= of_match_ptr(i2c_bus_virtual_dt_ids),},.probe		= i2c_bus_virtual_probe,.remove		= i2c_bus_virtual_remove,
};static int __init i2c_bus_virtual_init(void)
{int ret;printk("%s %s %d\n" , __FILE__ , __FUNCTION__ , __LINE__);ret = platform_driver_register(&i2c_bus_virtual_driver);if (ret)printk(KERN_ERR "i2c_bus_virtual_driver: probe failed: %d\n", ret);return ret;
}
module_init(i2c_bus_virtual_init);static void __exit i2c_bus_virtual_exit(void)
{printk("%s %s %d\n" , __FILE__ , __FUNCTION__ , __LINE__);platform_driver_unregister(&i2c_bus_virtual_driver);
}
module_exit(i2c_bus_virtual_exit);MODULE_LICENSE("GPL");

三、使用GPIO模拟I2C控制器

reg本意是用来表示寄存器register的地址信息:<起始地址><大小>后来也可以用来分辨设备,
比如对于12C设备,可以用reg来表示它的设备地址。

3.1 设置引脚为GPIO功能

3.2 GPIO设为输出、开极或者开漏

如果没有在设备树中指定,系统的i2c-gpio.c驱动代码中会帮忙设置其flags。

3.3 要有上拉电阻

3.4 设备树

i2c_gpio{compatible = "i2c-gpio";gpios = <&gpio4 20 0&gpio4 21 0>;i2c-gpio,delay-us = <5>;  /* ~100KHz */#address-cells = <1>;#size-cells    = <0>;
}

四、I2C控制器内部结构抽象 

GPIO 模拟 I2C 在数据多、中断多或需要高性能时非常不适合,因此大多数芯片都内置 I2C 控制器用于高效通信。

像之前的使用GPIO来模拟I2C,在传输过程中不允许有中断产生,整个过程如果有很多数据就会导致占用系统资源过多,系统变得缓慢。所以一般的芯片中都会有I2C控制器,发送数据的时候,会直接发送到发送寄存器,然后再发送到移位寄存器上给SDA发送,这时候发送数据函数就可以休眠,系统就会去做其他的事情,发送数据结束之后会产生中断。

4.1 I2C控制器操作方法

4.1.1 使能时钟、设置时钟

4.1.2 发送数据

把数据写入tx_register,等待中断发生。中断发生后,判断状态:是否发生错误、是否得到回应信号(ACK)。把下一个数据写入txregister,等待中断:如此循环。

4.1.3 接收数据

设置controller_register,进入接收模式,启动接收,等待中断发生。中断发生后,判断状态,读取rxregister得到数据 如此循环。

http://www.lryc.cn/news/581780.html

相关文章:

  • Next.js面试常问内容详解
  • 深度特征提取在LIDC-IDRI数据集多分类任务中的优化细节
  • 面向对象与面向过程程序设计语言:核心概念、对比分析与应用指南
  • 深度学习篇---Yolov系列
  • rxcpp--基础
  • 【机器学习笔记Ⅰ】2 线性回归模型
  • LeetCode 287. 寻找重复数(不修改数组 + O(1) 空间)
  • Android studio升级AGP需要注意哪些
  • 编程基础:继承
  • Modbus_TCP_V5 新功能
  • C++之路:多态与虚函数
  • 在phpstudy环境下配置搭建XDEBUG配合PHPSTORM的调试环境
  • 【Bluedroid】蓝牙 GATT 客户端注册机制与流程详解(BTA_GATTC_AppRegister)
  • Solidity——pure 不消耗gas的情况、call和sendTransaction区别
  • 【算法刷题记录(简单题)003】统计大写字母个数(java代码实现)
  • Node.js特训专栏-实战进阶:13. ORM/ODM工具选型与使用
  • AI做美观PPT:3步流程+工具测评+避坑指南
  • 【论文笔记】【强化微调】Pixel Reasoner:早期 tool call 的调用
  • CppCon 2018 学习:Undefined Behavior is Not an Error
  • 【系统分析师】2022年真题:论文及解题思路
  • (二) TDOA(到达时间差)、AoA(到达角度)、RSSI(接收信号强度)、TOF(飞行时间) 四种定位技术的原理详解及对比
  • 手动使用 Docker 启动 MinIO 分布式集群(推荐生产环境)
  • 从前端转go开发的学习路线
  • 2025 BSidesMumbaiCTF re 部分wp
  • NLP文本预处理
  • Spring AI(12)——调用多模态模型识别和生成图像
  • MyBatis实战指南(九)MyBatis+JSP+MySQL 前端页面实现数据库的增加与删除显示数据
  • 分布式会话的演进和最佳事件,含springBoot 实现(Java版本)
  • 【网络安全】不要在 XSS 中使用 alert(1)
  • 电池预测 | 第33讲 Matlab基于CNN-LSTM-Attention的锂电池剩余寿命预测,附锂电池最新文章汇集