您的位置:首页 > 运维架构 > 网站架构

iic子系统

2017-12-12 10:32 399 查看

I2C子系统分析

firefly-3399

linux内核版本:4.4

参考博客:

https://www.cnblogs.com/deng-tao/p/6130080.html

http://blog.csdn.net/w89436838/article/details/38660631

http://blog.csdn.net/wangpengqi/article/details/17711165

I2C硬件



一个I2C总线上挂载了多个I2C接口器件,但是这些I2C从设备的地址可能会重突,大部分以0xA开头的,所以在ARM的SOC上会有多个I2C适配器,每个I2C适配器上挂在1到2个从设备

I2C 总线在物理连接上非常简单,分别由SDA(串行数据线)和SCL(串行时钟线)及上拉电阻组成。通信原理是通过对SCL和SDA线高低电平时序的控制,来 产生I2C总线协议所需要的信号进行数据的传递。在总线空闲状态时,这两根线一般被上面所接的上拉电阻拉高,保持着高电平。

I2C子系统驱动



首先,I2C的架构和SPI的很相似,有适配器,设备,通过核心来进行连接,这样的分离架构可以使得Adapter和Device几乎没有耦合性,m+n个驱动可以形成m*n个搭配方式



I2C核心层:代码位于i2c-core.c ,注册了i2c总线,实现了i2c_adapter的注册函数,i2c_driver和i2c_device的注册函数

I2C适配器层:代码位于i2c-rk3x.c,提供i2c adapter的algorithm

I2C驱动层:代码位于i2c-dev.c(通用的驱动),注册了设备节点,提供了file_operations

I2C核心层

/**
*   i2c-core.c
*   注册了i2c总线,实现了i2c_adapter的注册函数,i2c_driver和i2c_device的注册函数
*   起到了连接上下层的作用,上层指的是i2c-dev.c中的通用用户层接口驱动程序,下层是i2c-rk3x.c中的soc内部适配器(bus)驱动程序
**/

struct bus_type i2c_bus_type = {
.name       = "i2c",                        //  总线的名字
.match      = i2c_device_match,             //  总线下设备与设备驱动的匹配函数
.probe      = i2c_device_probe,             //  总线层的probr函数
.remove     = i2c_device_remove,            //  总线卸载时执行的函数
.shutdown   = i2c_device_shutdown,          //  电源管理
};
static struct i2c_driver dummy_driver = {
.driver.name    = "dummy",
.probe      = dummy_probe,          //null实现
.remove     = dummy_remove,         //null实现
.id_table   = dummy_id,
};

postcore_initcall(i2c_init)
i2c_init
bus_register(&i2c_bus_type)                 //注册i2c总线  /sys/bus/i2c
class_compat_register("i2c-adapter")        //创建一个class /sys/class/i2c-adapter
i2c_add_driver(&dummy_driver)               //注册一个i2c_driver,null驱动
i2c_device_probe(struct device *dev)//当i2c总线下的i2c_device和i2c_driver匹配后执行
i2c_client  *client = i2c_verify_client(dev)                    // 通过device指针获取到对应的i2c_client指针
i2c_driver  *driver = = to_i2c_driver(dev->driver)              // 通过device->driver指针获取到对应的i2c_driver指针
...                                                             //中断处理,flag处理,clk处理,电源相关等
driver->probe(client, i2c_match_id(driver->id_table, client))   // 调用设备驱动层的probe函数
i2c_add_adapter(struct i2c_adapter *adapter)//提供给注册i2c适配器
__i2c_add_numbered_adapter(adapter)                         //如果i2c设备的id存在,执行这个
i2c_register_adapter(adapter)                               //负责动态分配id后执行这个
i2c_register_adapter(struct i2c_adapter *adap)              //最终都调用这个函数
unlikely(adap->name[0] == '\0')                         //i2c_adapter 名字检查
unlikely(!adap->algo)                                   //i2c_adapter通信算法检查
dev_set_name(&adap->dev, "i2c-%d", adap->nr)            //设置i2c_adapter中device的名字 "i2c-适配器的编号"
adap->dev.bus = &i2c_bus_type                           //i2c_adapter中device的bus设置为i2c_bus_type
adap->dev.type = &i2c_adapter_type                      //i2c_adapter中device的type设置
res = device_register(&adap->dev)                       //设备注册
class_compat_create_link(i2c_adapter_compat_class, &adap->dev,adap->dev.parent)     //把将一个设备连接到总线设备


总结:从上面的分析其实可以知道:i2c子系统内部存在着2个匹配过程:

(1)i2c总线下的设备与设备驱动之间的匹配(通过设备驱动的id_table),用到了设备树

(2)adapt
4000
er适配器与设备之间的匹配(通过适配器编号)

I2C适配器驱动

/**
*   i2c-rk3x.c
*   从设备树中获取soc中i2c适配器的设备资源,通过platform设备进行probe,然后获取资源最后注册i2c_adapter
**/
---- i2c-rk3x.c
i2c0: i2c@ff3c0000 {
compatible = "rockchip,rk3399-i2c";
reg = <0x0 0xff3c0000 0x0 0x1000>;
clocks =  <&pmucru SCLK_I2C0_PMU>, <&pmucru PCLK_I2C0_PMU>;
clock-names = "i2c", "pclk";
interrupts = <GIC_SPI 57 IRQ_TYPE_LEVEL_HIGH 0>;
pinctrl-names = "default";
pinctrl-0 = <&i2c0_xfer>;
#address-cells = <1>;
#size-cells = <0>;
status = "disabled";
};

i2c1: i2c@ff110000 {
compatible = "rockchip,rk3399-i2c";
reg = <0x0 0xff110000 0x0 0x1000>;
clocks = <&cru SCLK_I2C1>, <&cru PCLK_I2C1>;
clock-names = "i2c", "pclk";
interrupts = <GIC_SPI 59 IRQ_TYPE_LEVEL_HIGH 0>;
pinctrl-names = "default";
pinctrl-0 = <&i2c1_xfer>;
#address-cells = <1>;
#size-cells = <0>;
status = "disabled";
};

i2c3: i2c@ff130000 {
compatible = "rockchip,rk3399-i2c";
reg = <0x0 0xff130000 0x0 0x1000>;
clocks = <&cru SCLK_I2C3>, <&cru PCLK_I2C3>;
clock-names = "i2c", "pclk";
interrupts = <GIC_SPI 34 IRQ_TYPE_LEVEL_HIGH 0>;
pinctrl-names = "default";
pinctrl-0 = <&i2c3_xfer>;
#address-cells = <1>;
#size-cells = <0>;
status = "disabled";
};

&i2c1 {
status = "okay";
i2c-scl-rising-time-ns = <300>;
i2c-scl-falling-time-ns = <15>;

gsl3673: gsl3673@40 {
compatible = "GSL,GSL3673";
reg = <0x40>;
screen_max_x = <1536>;
screen_max_y = <2048>;
irq_gpio_number = <&gpio1 20 IRQ_TYPE_LEVEL_LOW>;
rst_gpio_number = <&gpio4 22 GPIO_ACTIVE_HIGH>;
};

rt5640: rt5640@1c {
#sound-dai-cells = <0>;
compatible = "realtek,rt5640";
reg = <0x1c>;
clocks = <&cru SCLK_I2S_8CH_OUT>;
clock-names = "mclk";
realtek,in1-differential;
pinctrl-names = "default";
pinctrl-0 = <&rt5640_hpcon>;
hp-con-gpio = <&gpio4 21 GPIO_ACTIVE_HIGH>;
//hp-det-gpio = <&gpio4 28 GPIO_ACTIVE_LOW>;
io-channels = <&saradc 4>;
hp-det-adc-value = <500>;
};
};

static struct platform_driver rk3x_i2c_driver = {
.probe   = rk3x_i2c_probe,
.remove  = rk3x_i2c_remove,
.driver  = {
.name  = "rk3x-i2c",                //i2c_adapter驱动的名字
.of_match_table = rk3x_i2c_match,   //这个和上面的设备树匹配成功,接着执行rk3x_i2c_probe函数
.pm = &rk3x_i2c_pm_ops,
},
};
module_platform_driver(rk3x_i2c_driver)
rk3x_i2c_probe(struct platform_device *pdev)//根据经验,一般都是从设备树获取资源填充一个结构体去注册相应的控制器驱动
struct rk3x_i2c *i2c;                                                   //结构体是rockchip对本SoC中的i2c控制器的一个描述
i2c = devm_kzalloc(&pdev->dev, sizeof(struct rk3x_i2c), GFP_KERNEL);    //分配rk3x_i2c
i2c->adap.algo = &rk3x_i2c_algorithm;                                   //通信算法
i2c->adap.dev.of_node = np;                                             //设备树资源
i2c->adap.dev.p arent = &pdev->dev;                                     //设置父设备,最后的i2c-adapter时候挂在platform总线下
platform_get_resource(pdev, IORESOURCE_MEM, 0)                          //获取平台设备资源
i2c->regs = devm_ioremap_resource(&pdev->dev, mem)                      //映射地址后给i2c
platform_get_irq(pdev, 0)                                               //获取平台设备中的i2c中断号(这个中断是I2C控制器产生的中断)
devm_request_irq(&pdev->dev, irq, rk3x_i2c_irq,0, dev_name(&pdev->dev), i2c)            //申请中断
platform_set_drvdata(pdev, i2c);                                        //把rk3x_i2c赋给pdev->dev->private_data,在remove i2c_adapter中可以很方便的使用
i2c_add_adapter(&i2c->adap)                                             //注册i2c_adapter


I2C驱动程序

static const struct file_operations i2cdev_fops = {
.owner      = THIS_MODULE,
.llseek     = no_llseek,
.read       = i2cdev_read,
.write      = i2cdev_write,
.unlocked_ioctl = i2cdev_ioctl,
.open       = i2cdev_open,
.release    = i2cdev_release,
};

module_init(i2c_dev_init)
i2c_dev_init(void)//注册字符设备,创造存在i2c适配器的设备节点
register_chrdev(I2C_MAJOR, "i2c", &i2cdev_fops)         //分配设备号89,注册字符设备
class_create(THIS_MODULE, "i2c-dev")                    //创建/sys/class/i2c-dev,为后面创建device准备
i2c_for_each_dev(NULL, i2cdev_attach_adapter)           //对存在的i2c-adapter创建设备节点等
i2cdev_attach_adapter(struct device *dev, void *dummy)
adap = to_i2c_adapter(dev)                      //从device中得到i2c_adapter
i2c_dev = get_free_i2c_dev(adap)                //分配一个i2c_dev
i2c_dev->dev = device_create(i2c_dev_class, &adap->dev,MKDEV(I2C_MAJOR, adap->nr), NULL,"i2c-%d", adap->nr)  //创建设备节点

//在这里i2c-adapter就注册成功了,我们需要的/dev/i2c-x设备节点也出来了,就可以通过这个设备节点来进行读写了
//下面看一下i2c-dev.c中的read,write等函数是如何调用到i2c-adapter中的通信函数的

i2cdev_open(struct inode *inode, struct file *file)//open函数
unsigned int minor = iminor(inode);                         //得到次设备号
i2c_dev = i2c_dev_get_by_minor(minor)                       //通过次设备号获取i2c_dev
adap = i2c_get_adapter(i2c_dev->adap->nr)                   //通过i2c_adapter的编号获取i2c_adapter
client = kzalloc(sizeof(*client), GFP_KERNEL)               //分配一个i2c_client
snprintf(client->name, I2C_NAME_SIZE, "i2c-dev %d", adap->nr)   //分配i2c次设备的名字
client->adapter = adap
file->private_data = client                                 //i2c_client保存在file的私有数据,在open等函数可以取到

static ssize_t i2cdev_read(struct file *file, char __user *buf, size_t count,loff_t *offset)//read函数
struct i2c_client *client = file->private_data              //获取i2c_client函数
ret = i2c_master_recv(client, tmp, count)                   //从i2c次设备获取数据存到tmp
struct i2c_adapter *adap = client->i2c_adapter              //获取i2c_adapter
msg.addr = client->addr;                                    //填充i2c_msg
msg.flags = client->flags & I2C_M_TEN;
msg.flags |= I2C_M_RD;
msg.len = count;
msg.buf = buf;
ret = i2c_transfer(adap, &msg, 1)                           //使用i2c_msg进行传输
__i2c_transfer(adap, msgs, num)                             //判断I2c_adapter中的i2c_algorithm的成员master_xfer存在
adap->algo->master_xfer(adap, msgs, num)                    //i2c-rk3x.c的rk3x_i2c_xfer函数
ret = copy_to_user(buf, tmp, count) ? -EFAULT : ret         //数据从内核给用户

//open read 函数中没有次设备地址相关操作,个人猜测应该是先ioctl
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息