i²c的分析
总线的结构
i2c 从soc里连接处两条线SDA串行数据线和SCL串行时钟线 从器件都有唯一的设备地址,和soc构成了主从关系
我们分为5种信号
开始信号 一个S电频(SCL高电频时,SDA从高电平向低电平跳),加上7位设备地址,加上第九位读写(SDA=0读,SDA=1写)
读信号
写信号
结束信号 发一个P电频 SCL为高时,SDA从低向高跳变
ACK信号 每个八位的数据信号后面,跟着的第九位是ACK信号(表示已经收到数据)
基本的数据传送方式 其中开始和 读写都是9个周期
驱动分析
可以直接写个基本的驱动 用ioremap 来控制寄存器 ,但是为了更好的融入内核,里面已经有想关的代码
i2c 的驱动和设备总线驱动类似,分为了 适配器(之前的dev) drv(设备驱动) 用总线 i2c_bus_type 进行适配
代码编写
之前的函数已经不提倡继续使用
int (*attach_adapter)(struct i2c_adapter *)
int (*detach_adapter)(struct i2c_adapter *)
我们改为新的probe函数
驱动程序 clien还有些探究
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/i2c.h>
#include <linux/slab.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/interrupt.h>
#include <linux/input.h>
#include <linux/wait.h>
#include <linux/sched.h>
#include <linux/platform_device.h>
#include <asm/io.h>
#include <asm/uaccess.h>
#define DEVICE_STYLE_ONE TRUE // 注册设备节点风格1
struct i2c_client *client;
unsigned short ft5x0x_i2c[]={0x38,I2C_CLIENT_END}; //地址?
struct eeprom_i2c_driver {
int major;
#ifndef DEVICE_STYLE_ONE
struct class *cls;
#endif
struct device *dev;
struct i2c_client *client;
};
struct eeprom_i2c_driver *at24_drv;
#if defined (DEVICE_STYLE_ONE)
struct class at24_class = {
.name = "at24_cls",
};
void at24_dev_release(struct device *dev)
{
// do nothing
}
#endif
int
at24_drv_open(struct inode *inode, struct file *filp)
{
// 硬件初始化,eeprom属于上电即可工作的器件,不需要初始化
return 0;
}
ssize_t at24_drv_read(struct file *filp, char __user *buf, size_t count, loff_t *fops)
{
int ret = -1;
if(0 > count || 2048 < count)
return -EFAULT;
char *temp = kzalloc(count, GFP_KERNEL);
if(NULL == temp){
printk("read kzalloc failed !\n");
return -ENOMEM;
}
ret = i2c_master_recv(at24_drv->client, temp, count);
if(0 > ret){
printk("i2c_master_recv failed !\n");
kfree(temp);
return ret;
}
ret = copy_to_user(buf, temp, count);
if(0 < ret){
printk("copy to user failed !\n");
ret = -EBUSY;
}
kfree(temp);
return count;
}
ssize_t at24_drv_write(struct file *filp, const char __user *buf, size_t count, loff_t *fops)
{
int ret = -1;
if(0 > count || 2048 < count)
return -EFAULT;
char *temp = kzalloc(count, GFP_KERNEL);
if(NULL == temp){
printk("read kzalloc failed !\n");
return -ENOMEM;
}
ret = copy_from_user(temp, buf, count);
if(0 < ret){
printk("copy from user failed !\n");
kfree(temp);
return -EBUSY;
}
ret = i2c_master_send(at24_drv->client, temp, count);
if(0 > ret){
printk("i2c write failed !\n");
kfree(temp);
return ret;
}
kfree(temp);
return count;
}
/**
* 我们可以通过llseek来指定读取时的偏移地址
* 为了代码简洁,这里暂不做处理
*/
struct file_operations at24_fops = {
.open = at24_drv_open,
.read = at24_drv_read,
.write = at24_drv_write,
};
int
eeprom_i2c_probe(struct i2c_client *client, const struct i2c_device_id *devid)
{
int ret = -1;
printk("-----%s active------\n", __func__);
// 0, 申请设备对象空间
at24_drv = kzalloc(sizeof(struct eeprom_i2c_driver), GFP_KERNEL); //驱动里面内存申请 GFP_KERNEL无内存进行休眠
if(NULL == at24_drv){
printk("kzalloc failed !\n");
return -ENOMEM;
}
// 1, 申请设备号
at24_drv->major = register_chrdev(0, "at24_chrdev", &at24_fops); //字符设备注册
if(at24_drv->major < 0){
printk("register char device failed !\n");
ret = -ENODEV;
goto err1;
}
#if defined (DEVICE_STYLE_ONE) // 代码走这里
// 2, 创建设备类
ret = class_register(&at24_class); //寄存器
if(ret){
printk("class register failed !\n");
goto err2;
}
// 3, 创建设备节点
dev_set_name(&at24_drv->dev, "at24_dev"); //设置drv
at24_drv->dev->devt = MKDEV(at24_drv->major, 0);
at24_drv->dev->class = &at24_class,
at24_drv->dev->parent = NULL,
at24_drv->dev->release = at24_dev_release,
device_initialize(&at24_drv->dev);
device_add(&at24_drv->dev);
#else
// 2, 创建设备类
at24_drv->cls = class_create(THIS_MODULE, "at24_cls");
if(IS_ERR(at24_drv->cls)){
printk("class create failed !\n");
ret = PTR_ERR(at24_drv->cls);
goto err2;
}
// 3, 创建设备节点
at24_drv->dev = device_create(at24_drv->cls, NULL, MKDEV(at24_drv->major, 0), NULL, "at24_dev");
if(IS_ERR(at24_drv->dev)){
printk("device create failed !\n");
ret = PTR_ERR(at24_drv->dev);
class_destroy(at24_drv->cls);
goto err2;
}
#endif
// 4, 记录当前设备对象
at24_drv->client = client;
return 0;
err2:
unregister_chrdev(at24_drv->major, "at24_chrdev");
err1:
kfree(at24_drv);
return ret;
}
int
eeprom_i2c_remove(struct i2c_client *client)
{
#if defined(DEVICE_STYLE_ONE)
device_del(&at24_drv->dev);
class_unregister(&at24_class);
#else
device_destroy(at24_drv->cls, MKDEV(at24_drv->major, 0));
class_destroy(at24_drv->cls);
#endif
unregister_chrdev(at24_drv->major, "at24_chrdev");
kfree(at24_drv);
return 0;
}
const struct i2c_device_id eeprom_table[] = {
{"at24c02a", 0x2},//用来实现i2c_client与i2c_driver匹配绑定,
{"at24c04a", 0x4},//当i2c_client中的name成员和i2c_driver中id_table中name成员相同的时候,就匹配上了
};
struct i2c_driver eeprom_driver = {
.probe = eeprom_i2c_probe,
.remove = eeprom_i2c_remove,
.driver = {
.name = "at24_drv", //平台驱动用这个匹配,这里好像没啥用
},
.id_table = eeprom_table, //用于匹配的表
};
static void __exit
eeprom_i2c_exit(void)
{
i2c_del_driver(&eeprom_driver);
}
static int __init
eeprom_i2c_init(void)
{
printk("i2c_init\n");
//return i2c_add_driver(&eeprom_driver);
return i2c_add_driver(i2c_register_driver(THIS_MODULE,&at24_drv));
}
module_init(eeprom_i2c_init);
module_exit(eeprom_i2c_exit);
MODULE_LICENSE("GPL");
应用程序
#include <linux/i2c-dev.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#define I2C_SLAVE 0x0703
void print_usage(char *str)
{
printf("%s r : read at24c02 addresss 0\n", str);
printf("%s w val : write at24c02 addresss 0\n", str);
}
int main(int argc,char **argv)
{
int fd;
unsigned char val;//字节
char register_addr = 0x08; /* Device register to access 片内地址*/
int res;
char wbuf[10];
char rbuf[10];
if (argc < 2){
print_usage(argv[0]);
exit(1);
}
/*打开设备文件*/
fd = open("/dev/at24_dev", O_RDWR);
if (fd < 0)
{
perror("open failed");
exit(1);
}
if (strcmp(argv[1], "r") == 0){
if (write(fd, ®ister_addr, 1)!=1) {
perror("write failed");
exit(1);
}
if (read(fd, rbuf, 1) != 1) {
perror("read failed");
exit(1);
} else {
printf("rbuf =0x%x\n",rbuf[0]);
}
}
else if ((strcmp(argv[1], "w") == 0) && (argc == 3))
{
// ./test w 0x99
val = strtoul(argv[2], NULL, 0);
wbuf[0] = register_addr; // 片内地址0x08
wbuf[1] = val;
if (write(fd, wbuf, 2)!=2) {
perror("write failed");
exit(1);
}
printf("write data ok!\n");
}
close(fd);
return 0;
}
上机调试
更改内核
先把之前的驱动编译为 可装载(M)
Device Drivers —>
<> I2C support —>//i2c-core.c
<> I2C device interface//通用i2c从设备驱动–主要用于调试(可选)
I2C Hardware Bus support —>
<*> S3C2410 I2C Driver