linux学习(7)---i2c子系统

主要内容---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, &register_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");

猜你喜欢

转载自blog.csdn.net/linken_yue/article/details/82320850
今日推荐