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()函数;

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

正在起飞的蜗牛

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值