linux内核I2C总线驱动(一)
一般外面讲的i2c都是怎么去写驱动端和设备端,我们今天来详细说明一下,整个的框架,从调用soc内部的地址开始,告诉你是怎么来的,应该怎么修改,去适配自己的soc
开始吧!
主要学习的是分析代码的思路
直接看源码太麻烦了,我们先看一下soc手册怎么说的,以我手头的6818为例子
手册下载中。。。load。。。
我们按照这个总线地址去源码追一下看看
kernel-3.4.39\arch\arm\mach-s5p6818\include\mach\s5p6818.h
#define PHY_BASEADDR_I2C0 (0xC00A4000)
#define PHY_BASEADDR_I2C1 (0xC00A5000)
#define PHY_BASEADDR_I2C2 (0xC00A6000)
那么我们看看都有谁调用了这些宏
\kernel-3.4.39\arch\arm\mach-s5p6818\devices.c
static struct resource s3c_i2c0_resource[] = {
[0] = DEFINE_RES_MEM(PHY_BASEADDR_I2C0, SZ_256),
[1] = DEFINE_RES_IRQ(IRQ_PHY_I2C0),
};
\kernel-3.4.39\arch\arm\mach-s5p6818\devices.c
static struct resource s3c_i2c1_resource[] = {
[0] = DEFINE_RES_MEM(PHY_BASEADDR_I2C1, SZ_4K),
[1] = DEFINE_RES_IRQ(IRQ_PHY_I2C1),
};
这里呼啦呼啦一套宏,其实很简单目的就是给struct resource结构体赋值
struct resource {
resource_size_t start;
resource_size_t end;
const char *name;
unsigned long flags;
struct resource *parent, *sibling, *child;
};
相当于在这个位置,记录了i2c总线的开始地址,结束地址,大小,和名字
但是struct resource *parent, *sibling, *child;这部分没有初始化还
这里的标志是
kernel-3.4.39\include\linux\ ioport.h
#define IORESOURCE_MEM 0x00000200
IORESOURCE_MEM 指的是属于外设或者用于和设备通讯的支持直接寻址的地址空间
我们已经拥有了设备信息,然后呢,是谁使用了这个
s3c_i2c1_resource
kernel-3.4.39\arch\arm\mach-s5p6818\ devices.c
static struct platform_device i2c_device_ch1 = {
//.name = DEV_NAME_I2C,
.name = "s3c2440-i2c",
.id = 1,
.num_resources = ARRAY_SIZE(s3c_i2c1_resource),
.resource = s3c_i2c1_resource,
.dev = {
.platform_data = &i2c_data_ch1
},
};
开始啦,开始啦,开始走平台总线模型啦
kernel-3.4.39\include\linux\ platform_device.h
struct platform_device {
const char * name;
int id;
struct device dev;
u32 num_resources;
struct resource * resource;
const struct platform_device_id *id_entry;
/* MFD cell pointer */
struct mfd_cell *mfd_cell;
/* arch specific additions */
struct pdev_archdata archdata;
};
这里把,名称初始化为 "s3c2440-i2c",id= 1,num_resources是地址大小,resource是总线地址信息,.dev的platform_data初始化为&i2c_data_ch1
没有信息的是
const struct platform_device_id *id_entry;
struct mfd_cell *mfd_cell;
struct pdev_archdata archdata;
\kernel-3.4.39\arch\arm\mach-s5p6818\ devices.c
void i2c_cfg_gpio1(struct platform_device *dev)
{
nxp_soc_gpio_set_io_func( I2C1_SCL & 0xff, 1);
nxp_soc_gpio_set_io_func( I2C1_SDA & 0xff, 1);
} 设置引脚复用
struct s3c2410_platform_i2c i2c_data_ch1 = {
.bus_num = 1,
.flags = 0,
.slave_addr = 0x10,
.frequency = CFG_I2C1_CLK,
.sda_delay = 100,
.cfg_gpio = i2c_cfg_gpio1,
};
其实这里信息已经比较齐全了,我们继续追i2c_device_ch1是被谁调用的
kernel-3.4.39\arch\arm\mach-s5p6818\ devices.c
static struct platform_device *i2c_devices[] = {
#if defined(CONFIG_I2C_NXP_PORT0)
&i2c_device_ch0,
#endif
#if defined(CONFIG_I2C_NXP_PORT1)
&i2c_device_ch1,
#endif
#if defined(CONFIG_I2C_NXP_PORT2)
&i2c_device_ch2,
#endif
};
绕来绕去封了好几层,其实就是为了整合,这里是确认哪些通道被打开了
我们这里出现了两个调用
kernel-3.4.39\arch\arm\plat-s5p6818\topeet\device.c
#if defined(CONFIG_I2C_NXP) || defined (CONFIG_I2C_SLSI)
platform_add_devices(i2c_devices, ARRAY_SIZE(i2c_devices));
#endif
\kernel-3.4.39\arch\arm\mach-s5p6818\devices.c
#if defined(CONFIG_I2C_NXP) || defined(CONFIG_I2C_SLSI)
printk("mach: add device i2c bus (array:%d) \n", ARRAY_SIZE(i2c_devices));
platform_add_devices(i2c_devices, ARRAY_SIZE(i2c_devices));
这里一下子注册了三个平台总线
其实是循环调用platform_device_register实现的注册,名称很直白“平台设备注册”
这里平台部分其实就告一段落了,然后是匹配了提供接口呢
我们知道是靠这。Name去匹配的,那么我们去找找驱动端被
\kernel-3.4.39\drivers\i2c\busses\ i2c-s3c2410.c
static struct platform_device_id s3c24xx_driver_ids[] = {
{
.name = "s3c2410-i2c",
.driver_data = TYPE_S3C2410,
}, {
.name = "s3c2440-i2c",
.driver_data = TYPE_S3C2440,
}, { },
};官方的描述为“平台总线位的设备驱动程序”
kernel-3.4.39\drivers\i2c\busses\ i2c-s3c2410.c
static struct platform_driver s3c24xx_i2c_driver = {
.probe = s3c24xx_i2c_probe,
.remove = s3c24xx_i2c_remove,
.id_table = s3c24xx_driver_ids,
.driver = {
.owner = THIS_MODULE,
.name = "s3c-i2c",
.pm = S3C24XX_DEV_PM_OPS,
.of_match_table = s3c24xx_i2c_match,
},
};
明显看出是通过.id_tab去匹配的
static int s3c24xx_i2c_probe(struct platform_device *pdev)
当找到合适的设备时,由总线驱动程序调用,初始化I2C
static int s3c24xx_i2c_remove(struct platform_device *pdev)
当设备从总线中移除时调用,反初始化?
也有of_match_table的匹配方法,这里没有使用到。。。
kernel-3.4.39\drivers\i2c\busses\i2c-s3c2410.c
static int __init i2c_adap_s3c_init(void)
{
int ret = 0;
ret = platform_driver_register(&s3c24xx_i2c_driver);
return ret;
}
在这个模块的初始化部分进行驱动注册,到这里其实驱动部分也已经结束了。。。。。。
是不是发现问题了?我们常见的master_xfer呢?不急不急,另外说一下,设备树的同学,在prob函数内可以解析设备树,并初始化喔,不会写设备树也可以看看这个函数,然后决定如何写设备树喔,嘻嘻。
在proc中:
static const struct i2c_algorithm s3c24xx_i2c_algorithm = {
.master_xfer = s3c24xx_i2c_xfer,
.functionality = s3c24xx_i2c_func,
};
这个结构体也是非常的关键,这个结构体里面的函数s3c24xx_i2c_xfer是适配器算法的实现,这个函数实现了适配器与I2C CORE的连接。
s3c24xx_i2c_func是指该适配器所支持的功能。
s3c24xx_i2c_xfer这个函数实质是实现I2C数据的发送与接收的处理过程。不同的处理器实现的方法不同,主要表现在寄存器的设置与中断的处理方法上
依然在prob中
注册具有静态总线号的i2c适配器的函数,这样就能够使用我们后面的i2c_adapter等方法了啦。
idr_get_new_aboveidr机制整数id与指针建立关系
i2c_register_adapter注册i2c适配器
下周我们讲一下熟悉的I2C-core部分的细节吧,饿了吃饭去。。。。。。