S5PV210芯片I2C适配器驱动分析(i2c-s3c2410.c)
Posted 正在起飞的蜗牛
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了S5PV210芯片I2C适配器驱动分析(i2c-s3c2410.c)相关的知识,希望对你有一定的参考价值。
1、什么是适配器驱动
(1)适配器驱动就是用来控制Soc上的I2C控制器的,封装I2C控制器的通信方式;
(2)适配器驱动会向I2C核心层注册,I2C核心层会管理内核中所有注册的适配器驱动,每一个注册的适配器驱动就代表Soc中的一个I2C控制器;
(3)I2C接口设备的驱动会通过I2C总线匹配上对应的适配器,然后调用适配器提供的数据收发接口进行通信;
2、适配器驱动怎么和Soc上的I2C控制器对应
/*********gslX680.c**************/
#define GSLX680_I2C_NAME "gslX680"
static struct i2c_driver gsl_ts_driver =
.driver =
.name = GSLX680_I2C_NAME,
.owner = THIS_MODULE,
,
#ifndef CONFIG_HAS_EARLYSUSPEND
.suspend = gsl_ts_suspend,
.resume = gsl_ts_resume,
#endif
.probe = gsl_ts_probe,
.remove = __devexit_p(gsl_ts_remove),
.id_table = gsl_ts_id,
;
//函数调用关系
gsl_ts_init() //驱动加载函数
i2c_add_driver(&gsl_ts_driver); //向I2C总线注册gslX680驱动
gsl_ts_probe() //当gslX680驱动在I2C总线上匹配上struct i2c_client时就会调用probe方法
/*********内核注册i2c_board_info信息************/
static struct i2c_board_info i2c_devs1[] __initdata =
I2C_BOARD_INFO("gslX680", 0x40), //gslX680是用来和驱动匹配的名字,0x40是设备在I2C总线上的地址
,
;
smdkc110_machine_init() //struct machine_desc->init_machine()
i2c_register_board_info(1, i2c_devs1, ARRAY_SIZE(i2c_devs1)); //向编号是1的适配器注册i2c_board_info信息
(1)上面是gslX680触摸屏驱动代码和在I2C总线上的匹配相关部分的代码;
(2)struct i2c_board_info结构体:会用来构成struct i2c_client结构体,将来I2C接口设备的驱动在I2C总线上匹配设备时,就会用名字来进行匹配,
哪一个适配器上有该驱动的名字就会和哪个适配器匹配成功,并且还会把struct i2c_board_info结构体里的信息传给I2C驱动,比如:设备地址、中断号;
(3)每个适配器都会注册struct i2c_board_info结构体信息,将来struct i2c_board_info结构体里的名字会和I2C驱动的名字进行匹配, I2C驱动和适配器匹配上,
将来I2C驱动通信就通过匹配上的适配器,也就是硬件上I2C接口设备接在哪一个I2C控制器;
3、适配器驱动的加载过程
static struct platform_device_id s3c24xx_driver_ids[] =
.name = "s3c2410-i2c",
.driver_data = TYPE_S3C2410,
,
.name = "s3c2440-i2c",
.driver_data = TYPE_S3C2440,
, ,
;
static struct platform_driver s3c24xx_i2c_driver =
.probe = s3c24xx_i2c_probe,
.remove = s3c24xx_i2c_remove,
.id_table = s3c24xx_driver_ids, //在platform总线上匹配设备时使用
.driver =
.owner = THIS_MODULE,
.name = "s3c-i2c",
.pm = S3C24XX_DEV_PM_OPS,
,
;
i2c_adap_s3c_init() //驱动加载函数
platform_driver_register(&s3c24xx_i2c_driver); //利用平台总线进行注册
s3c24xx_i2c_probe() //在平台总线上匹配上设备后调用probe函数
i2c_add_numbered_adapter(&i2c->adap); //利用platform_device传过来的数据构建适配器结构体,并向I2C核心层注册
(1) 在内核的struct machine_desc->init_machine()函数中会注册plat_device,会有多个plat_device和platform总线驱动s3c24xx_i2c_driver匹配上,
基本上是Soc有几个I2C控制器就匹配上几次,也就是会向I2C核心层注册多个适配器;
(2)虽然被匹配上多次,但是每次plat_device传过来的数据都是不同的,包括I2C控制器的寄存器物理地址、中断号、适配器编号等;
4、I2C总线驱动描述结构体
struct s3c24xx_i2c
spinlock_t lock;
wait_queue_head_t wait; //为了实现同步,使i2c->algorithm->master_xfer函数能在中断中的传输过程完成时返回
unsigned int suspended:1;
//记录待传输数据的信息
struct i2c_msg *msg; //消息队列
unsigned int msg_num; //消息队列中的消息数
unsigned int msg_idx; //当前传输的消息是序列中的第几条
unsigned int msg_ptr; //传输的是当前的第几个字节
unsigned int tx_setup; //传输建立延时
unsigned int irq; //使用的中断号
enum s3c24xx_i2c_state state; //当前传输的状态,进行到哪一步
unsigned long clkrate;
void __iomem *regs; //用于访问内核IO内存的实际地址
struct clk *clk;
struct device *dev;
struct resource *ioarea; //IO内存资源
struct i2c_adapter adap; //对应的适配器
;
5、适配器驱动的probe方法实现
static int s3c24xx_i2c_probe(struct platform_device *pdev)
struct s3c24xx_i2c *i2c;
struct s3c2410_platform_i2c *pdata;
struct resource *res;
int ret;
//解析处platform总线设备传递的信息
pdata = pdev->dev.platform_data;
i2c = kzalloc(sizeof(struct s3c24xx_i2c), GFP_KERNEL);
//设置适配器的名字
strlcpy(i2c->adap.name, "s3c2410-i2c", sizeof(i2c->adap.name));
//构建适配器结构体adap
i2c->adap.owner = THIS_MODULE;
i2c->adap.algo = &s3c24xx_i2c_algorithm; //设置适配器的通信方法
i2c->adap.retries = 2; //重发次数是2
i2c->adap.class = I2C_CLASS_HWMON | I2C_CLASS_SPD;
i2c->tx_setup = 50;
//初始化自旋锁和等待队列
spin_lock_init(&i2c->lock);
init_waitqueue_head(&i2c->wait);
/* 获取I2C控制器的时钟并使能 */
i2c->dev = &pdev->dev;
i2c->clk = clk_get(&pdev->dev, "i2c"); //获取时钟系统给I2C控制器的提供的时钟频率
clk_enable(i2c->clk); //使能时钟
/* 获取I2C控制器的IO地址资源 */
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
······
//申请寄存器的物理地址
i2c->ioarea = request_mem_region(res->start, resource_size(res),
pdev->name);
//动态映射物理地址
i2c->regs = ioremap(res->start, resource_size(res));
//在master_xfer等方法中就能通过适配器的algo_data获取对应的i2c对象
i2c->adap.algo_data = i2c;
i2c->adap.dev.parent = &pdev->dev;
//初始化芯片的I2C控制器:使能中断、ACK使能、设置对应的GPIO、初始化时钟等
ret = s3c24xx_i2c_init(i2c);
if (ret != 0)
goto err_iomap;
//获取中断号资源
i2c->irq = ret = platform_get_irq(pdev, 0);
if (ret <= 0)
dev_err(&pdev->dev, "cannot find IRQ\\n");
goto err_iomap;
//申请中断号并绑定中断处理程序s3c24xx_i2c_irq
ret = request_irq(i2c->irq, s3c24xx_i2c_irq, IRQF_DISABLED,
dev_name(&pdev->dev), i2c);
//利用内核通知链机制,当CPU频率变化时,时I2C时钟设置能做出调整
ret = s3c24xx_i2c_register_cpufreq(i2c);
if (ret < 0)
dev_err(&pdev->dev, "failed to register cpufreq notifier\\n");
goto err_irq;
//从platform总线设备获得适配器的总线编号
i2c->adap.nr = pdata->bus_num;
//向I2C核心层注册适配器
ret = i2c_add_numbered_adapter(&i2c->adap);
if (ret < 0)
dev_err(&pdev->dev, "failed to add bus to i2c core\\n");
goto err_cpufreq;
//将i2c保存到pdev->dev->p->driver_data
platform_set_drvdata(pdev, i2c);
clk_disable(i2c->clk);
dev_info(&pdev->dev, "%s: S3C I2C adapter\\n", dev_name(&i2c->adap.dev));
return 0;
······
6、I2C总线通信方法
6.1、适配器的通信方法
static const struct i2c_algorithm s3c24xx_i2c_algorithm =
.master_xfer = s3c24xx_i2c_xfer,
.functionality = s3c24xx_i2c_func,
;
6.2、数据通信的函数调用关系
s3c24xx_i2c_xfer() //adap->algo->master_xfer
s3c24xx_i2c_doxfer()
s3c24xx_i2c_set_master() //确保当前I2C控制器处于空闲,才继续下面的操作
s3c24xx_i2c_enable_irq() //使能i2C-Bus的Tx/Rx中断
s3c24xx_i2c_message_start() //开启I2C通信
wait_event_timeout(i2c->wait, i2c->msg_num == 0, HZ * 5); //发送函数进入等待,直到消息发送完成或者超过5s返回
在中断程序中唤醒等待队列:
s3c24xx_i2c_irq() //适配器绑定的中断处理函数
i2c_s3c_irq_nextbyte() //发送数据
s3c24xx_i2c_stop() //当数据收发完成或者通信出错时停止本次传输
s3c24xx_i2c_master_complete() //本次传输完成
wake_up(&i2c->wait); //唤醒等待队列
(1)I2C适配器描述结构体struct i2c_adapter中algo变量是适配器的通信方法,在驱动程序的prob函数中赋值;
(2)通信是以消息(struct i2c_msg)为单位进行的,消息描述结构体会指明要发送的设备的地址、数据传输方向等;
6.3、通信过程
(1)首先是I2C设备驱动调用适配器的adap->algo->master_xfer进行发送消息,master_xfer方法在进行一些初始化后,开启本次I2C通信,然后就进入等待队列,直到消息发送完成或者5秒超时后返回;
(2)具体的发送是在probe方法绑定了中断函数中进行,这个中断函数是I2C控制器的中断处理函数,在发送完数据或者出错后,就会唤醒之前在等待s3c24xx_i2c_doxfer()函数;
以上是关于S5PV210芯片I2C适配器驱动分析(i2c-s3c2410.c)的主要内容,如果未能解决你的问题,请参考以下文章
S5PV210芯片的DRAM控制器介绍初始化DDR的流程分析
S5PV210芯片的uboot烧录脚本目录(sd_fusing)完整解析