主要内容---i2c子系统--e2prom编程
i2c子系统是按照i2c时序的实现读写数据的
i2c子系统是先发高位,再发低位;
硬件一般是7位。
i2c驱动模型:
i2c_driver(从设备驱动层):申请设备号,申请设备节点,申请fops
i2c_core(核心层):维护I2C总线
i2c_adapter(适配器层):i2c控制器层
1, i2c协议讲解
2, i2c子系统框架
3, i2c子系统驱动编程--从设备驱动at24c02
4, i2c子系统框架代码流程
----------------------------------------------
i2c驱动:
i2c设备比较多:
ts, camera, e2prom, gsensor, hdmi..
2, i2c子系统框架
应用层
----------------------------------------
i2c driver层: 从设备驱动层
1,和用户进行交互
2, 知道要传输给从设备的数据是什么
不知道如何通过硬件操作给从设备
------------------------------------------
i2c core层:维护了一个i2c 总线
drivers/i2c/i2c-core.c
----------------------------------------
i2c adapter: i2c控制器层
drivers/i2c/busses/i2c-s3c2410.c
1,与硬件交互,并且负责硬件i2c控制器的初始化
2,知道如何将数据给从设备,但是不知道数据是什么
确保低两层内核自带--zImage:
make menuconfig
Device Drivers --->
<*> I2C support ---> // i2c-core.c
I2C Hardware Bus support --->
<*> S3C2410 I2C Driver //i2c-s3c2410.c
make zImage -j2
更新内核:
cp -raf arch/arm/boot/zImage /tftpboot/zImage
3, i2c子系统驱动编程--从设备驱动at24c02
1, 间接为i2c_client提供信息,有了信息后,系统会自动的创建i2c client
static struct i2c_board_info smdkv210_i2c_devs0[] __initdata = {
{ I2C_BOARD_INFO("at24c02a", 0x50), // 名字用于进行匹配, 0x50表示从设备地址
//.type = "24c08",
//.addr = 0x50
}, /* Samsung S524AD0XD1 */
{ I2C_BOARD_INFO("wm8580", 0x1b), },
};
static struct i2c_board_info smdkv210_i2c_devs1[] __initdata = {
/* To Be Updated */
};
static struct i2c_board_info smdkv210_i2c_devs2[] __initdata = {
/* To Be Updated */
};
arch/arm/mach-s5pv210/mach-smdkv210.c
|
smdkv210_machine_init()
|
i2c_register_board_info(0, smdkv210_i2c_devs0,ARRAY_SIZE(smdkv210_i2c_devs0));
i2c_register_board_info(1, smdkv210_i2c_devs1,ARRAY_SIZE(smdkv210_i2c_devs1));
i2c_register_board_info(2, smdkv210_i2c_devs2, ARRAY_SIZE(smdkv210_i2c_devs2));
make zImage -j2
更新内核:
cp -raf arch/arm/boot/zImage /tftpboot/zImage
如何查看:
/sys/bus/i2c/devices/0-0050
[root@farsight 0-0050]# cat name
at24c02a
2, 构建i2c_driver,注册到总线
extern void i2c_del_driver(struct i2c_driver *);
static inline int i2c_add_driver(struct i2c_driver *driver)
struct i2c_driver {//描述一个i2c从设备的驱动
int (*probe)(struct i2c_client *, const struct i2c_device_id *);
int (*remove)(struct i2c_client *);
struct device_driver driver;//父类
const struct i2c_device_id *id_table;//比对的列表
}
int i2c_master_send(const struct i2c_client *client, const char *buf, int count)
int i2c_master_recv(const struct i2c_client *client, char *buf, int count)
|
int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
struct i2c_client {//描述一个从设备信息的对象---里面所有的成员都是自动初始化
unsigned short addr; // 7bit的从设备地址
char name[I2C_NAME_SIZE];//名字,用于和driver匹配
struct i2c_adapter *adapter; //指向创建自己的adapter
struct i2c_driver *driver; // 和自己匹配的driver
struct device dev; // 父类
}
struct i2c_adapter {//描述的是一个控制器
const struct i2c_algorithm *algo;
|
int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs,int num);
struct device dev; //父类
int nr;//控制器的编号
}
struct i2c_msg {//传送给从设备的数据包
__u16 addr; //从设备地址
__u16 flags;//读还是写
__u16 len; // 数据的字节数
__u8 *buf; //数据缓冲指针
};
任务: 总线匹配之后,调用了drv中的 probe()
i2c_add_driver(&at24_drv);或者platform_driver_register(struct platform_driver * drv)
如何跟读内核的代码技巧:
1, 看主线--主要的技术点
2, 出错判断和变量不看
3, 看不懂不看
4, 大胆去猜函数的作用
5, 找度娘或者找谷歌
6, 做好笔记和注释,已经总结
i2c 子系统的框架代码
----------------------------------------------
at24_drv.c:---4
-------------------------------------------
i2c-core.c--2
postcore_initcall(i2c_init);
module_exit(i2c_exit);
i2c_init(void)
|
bus_register(&i2c_bus_type);//构建了i2c 总线--并且定义了匹配方法
i2c_add_driver(&dummy_driver); //增加的了一个i2c driver-什么也没做
--------------------------------------------
i2c-s3c2410.c ---3
subsys_initcall(i2c_adap_s3c_init);
|
platform_driver_register(&s3c24xx_i2c_driver);
s3c24xx_i2c_probe,
|
//获取到平台自定义数据
pdata = pdev->dev.platform_data;
// 1,分配一个adapter
//分配一个全局的设备对象--分配i2c_adapter
i2c = kzalloc(sizeof(struct s3c24xx_i2c), GFP_KERNEL);
// 2, 初始化 adatper
strlcpy(i2c->adap.name, "s3c2410-i2c", sizeof(i2c->adap.name));
i2c->adap.owner = THIS_MODULE;
i2c->adap.algo = &s3c24xx_i2c_algorithm; //算法
i2c->adap.retries = 2;
i2c->adap.class = I2C_CLASS_HWMON | I2C_CLASS_SPD;
i2c->tx_setup = 50;
//获取内存资源
platform_get_resource(pdev, IORESOURCE_MEM, 0);
i2c->regs = ioremap(res->start, resource_size(res));
//获取中断资源
i2c->irq = ret = platform_get_irq(pdev, 0);
//申请中断
request_irq(i2c->irq, s3c24xx_i2c_irq, IRQF_DISABLED,
dev_name(&pdev->dev), i2c);
// 3,注册 adapter
i2c_add_numbered_adapter(&i2c->adap);
|
i2c_register_adapter(adap);
|
// i2c_add_adapter是谁调用的
//i2c adapter的注册
i2c_add_adapter(struct i2c_adapter * adapter)
|
i2c_register_adapter(adapter);
|
rt_mutex_init(&adap->bus_lock);
mutex_init(&adap->userspace_clients_lock);
INIT_LIST_HEAD(&adap->userspace_clients);
// /sys/bus/i2c/devices/i2c-0,1, 2
dev_set_name(&adap->dev, "i2c-%d", adap->nr);
adap->dev.bus = &i2c_bus_type;
adap->dev.type = &i2c_adapter_type;
//将当前的adapter注册到i2c总线中去
res = device_register(&adap->dev);
i2c_scan_static_board_info(adap);
|
list_for_each_entry(devinfo, &__i2c_board_list, list) {
if (devinfo->busnum == adapter->nr&& !i2c_new_device(adapter,&devinfo->board_info)
bus_for_each_drv(&i2c_bus_type, NULL, adap, __process_new_adapter);
总结:
adapter在注册的时候,会遍历__i2c_board_list链表
如果adapter的编号和链表中节点的号码一致,就会构建i2c client并注册client
i2c client中成员的值来自于board_info
//i2c_new_device是谁调用了
// i2c client需要被构建出来,并且注册到总线
struct i2c_client *i2c_new_device(struct i2c_adapter *adap, struct i2c_board_info const *info)
|
struct i2c_client *client;
//分配i2c client对象
client = kzalloc(sizeof *client, GFP_KERNEL);
//将i2c_adapter关联到i2c_client
client->adapter = adap;
client->dev.platform_data = info->platform_data;
client->flags = info->flags;
//从设备地址是来自于i2c_board_info
client->addr = info->addr;
client->irq = info->irq;
//client的名字也来自于i2c_board_info
strlcpy(client->name, info->type, sizeof(client->name));
//检查从设备地址的合法性
i2c_check_client_addr_validity(client);
i2c_check_addr_busy(adap, client->addr);
//初始化client中的父类
client->dev.parent = &client->adapter->dev;
//指定父类对应的总线,指定i2c总线
client->dev.bus = &i2c_bus_type;
client->dev.type = &i2c_client_type;
client->dev.of_node = info->of_node;
// /sys/bus/i2c/devices/0-0050
dev_set_name(&client->dev, "%d-%04x", i2c_adapter_id(adap),
client->addr);
//注册到i2c总线
status = device_register(&client->dev);
return client;
总结:
i2c_new_device函数中
1, 构建了i2c client
2, 初始化i2c client,通过i2c_board_info来初始化
3, 注册到i2c 总线
--------------------------------------------------------------
static struct i2c_board_info smdkv210_i2c_devs0[] __initdata = {
{ I2C_BOARD_INFO("at24c02a", 0x50), },// 名字用于进行匹配, 0x50表示从设备地址
{ I2C_BOARD_INFO("wm8580", 0x1b), },
};
mach-smdkv210.c--1
|
smdkv210_machine_init(void)
|
i2c_register_board_info(0, smdkv210_i2c_devs0,ARRAY_SIZE(smdkv210_i2c_devs0));
|i2c_register_board_info(int busnum,struct i2c_board_info const *info, unsigned len)
for (status = 0; len; len--, info++)//遍历数组
struct i2c_devinfo *devinfo;
//主要代码
devinfo = kzalloc(sizeof(*devinfo), GFP_KERNEL);
devinfo->busnum = busnum;//表示总线编号--0, 1,2
devinfo->board_info = *info;//将数组中成员赋值给节点
list_add_tail(&devinfo->list, &__i2c_board_list);
i2c_register_board_info(1, smdkv210_i2c_devs1,
ARRAY_SIZE(smdkv210_i2c_devs1));
i2c_register_board_info(2, smdkv210_i2c_devs2,
ARRAY_SIZE(smdkv210_i2c_devs2));
platform_add_devices(smdkv210_devices,
ARRAY_SIZE(smdkv210_devices));
总结:i2c_register_board_info做了什么:
根据数组中的成员构建一个节点对象(记录i2c client所需要的信息以及编号)
将节点对象注册到__i2c_board_list链表中去
platform_add_devices()注册了三个i2c的pdev
框架流程图:
代码示例:
at24_drv_app.c:
#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_e2prom", 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;
}
at24_i2c_e2prom.c:
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/slab.h>
#include <linux/gpio.h>
#include <linux/i2c.h>
#include <asm/io.h>
#include <asm/uaccess.h>
// 全局的设备对象
struct i2c_e2prom{
int dev_major;
struct class *cls;
struct device *dev;
struct i2c_client *client;//记录当前匹配的client
};
struct i2c_e2prom *at24_dev;
//编写一个类似i2c_master_recv/i2c_master_send
int at24_i2c_read(struct i2c_client *client, char *buf, int size)
{
int ret;
struct i2c_adapter *adapter = client->adapter;
struct i2c_msg msg;
msg.addr = client->addr;
msg.flags = I2C_M_RD;
msg.len = size;
msg.buf = buf;
// 参数1---适配器
//参数2--消息包
// 参数3--消息的个数
ret = i2c_transfer(adapter, &msg, 1);
return ret==1?size:ret;
}
int at24_i2c_write(struct i2c_client *client, char *buf, int size)
{
int ret;
struct i2c_adapter *adapter = client->adapter;
struct i2c_msg msg;
msg.addr = client->addr;
msg.flags = 0;
msg.len = size;
msg.buf = buf;
ret = i2c_transfer(adapter, &msg, 1);
return ret==1?size:ret;
}
int at24_e2prom_drv_open (struct inode *inode, struct file *filp)
{
return 0;
}
ssize_t at24_e2prom_drv_read(struct file *filp, char __user *buf, size_t count, loff_t *fpos)
{
printk("-----------%s-----------\n", __FUNCTION__);
int ret;
if(count < 0 || count > 256)
return -EINVAL;
char *tmp = kzalloc(count, GFP_KERNEL);
// 1, 从硬件中获取数据
ret = at24_i2c_read(at24_dev->client, tmp, count);
if(ret < 0)
{
printk("at24_i2c_read error\n");
goto err_free;
}
// 2 ,将数据给用户
ret = copy_to_user(buf, tmp, count);
if(ret > 0)
{
printk("copy_to_user error\n");
goto err_free;
}
kfree(tmp);
return count;
err_free:
kfree(tmp);
return ret;
}
ssize_t at24_e2prom_drv_write(struct file *filp, const char __user *buf, size_t count, loff_t *fpos)
{
printk("-----------%s-----------\n", __FUNCTION__);
int ret;
if(count < 0 || count > 256)
return -EINVAL;
char *tmp = kzalloc(count, GFP_KERNEL);
// 1, 从用户空间将数据获取到
ret = copy_from_user(tmp, buf, count);
if(ret > 0)
{
printk("copy_from_user error\n");
goto err_free;
}
// 2, 将数据写入硬件中去
ret = at24_i2c_write(at24_dev->client, tmp, count);
if(ret < 0)
{
printk("at24_i2c_write error\n");
goto err_free;
}
kfree(tmp);
return count;
err_free:
kfree(tmp);
return ret;
}
int at24_e2prom_drv_close(struct inode *inode, struct file *filp)
{
return 0;
}
const struct file_operations at24_fops = {
.open = at24_e2prom_drv_open,
.read = at24_e2prom_drv_read,
.write = at24_e2prom_drv_write,
.release = at24_e2prom_drv_close,
};
int at24_drv_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
printk("-----id->name = %s, id->driver_data = 0x%x\n",id->name, id->driver_data);
/*
// 申请设备号
// 创建设备文件
// 硬件初始化
// 实现fops
*/
at24_dev = kzalloc(sizeof(struct i2c_e2prom), GFP_KERNEL);
at24_dev->dev_major = register_chrdev(0,"at24_drv", &at24_fops);
at24_dev->cls = class_create(THIS_MODULE, "at24_cls");
at24_dev->dev = device_create(at24_dev->cls, NULL,
MKDEV(at24_dev->dev_major, 0),NULL, "at24_e2prom");
//记录当前client
at24_dev->client = client;
// 硬件初始化 ---e2prom只要上电就可以功能
// i2c系统中为从设备传输数据的方法
return 0;
}
int at24_drv_remove(struct i2c_client *client)
{
device_destroy(at24_dev->cls, MKDEV(at24_dev->dev_major, 0));
class_destroy(at24_dev->cls );
unregister_chrdev(at24_dev->dev_major, "at24_drv");
kfree(at24_dev);
return 0;
}
const struct i2c_device_id at24_id_table[] = {
{"at24c02a", 0x2222},
{"at24c04a", 0x4444},
{"at24c08a", 0x8888},
};
struct i2c_driver at24_drv = {
.probe = at24_drv_probe,
.remove = at24_drv_remove,
.driver = {
.name = "at24_e2prom_drv", //不会用于比对
// /sys/bus/i2c/drivers/at24_e2prom_drv
},
.id_table = at24_id_table,
};
static int __init at24_drv_init(void)
{
//注册一个i2c driver
return i2c_add_driver(&at24_drv);
}
static void __exit at24_drv_exit(void)
{
i2c_del_driver(&at24_drv);
}
module_init(at24_drv_init);
module_exit(at24_drv_exit);
MODULE_LICENSE("GPL");