【Linux驱动】I2C子系统与触摸屏驱动

由于学习触摸屏驱动涉及到中断以及I2C相关的知识,因此先介绍一下I2C的驱动框架。

触摸屏与I2C总线的关系

关于I2C的基础概念和原理参考我的这篇博客:【裸机】嵌入式相关问题汇总(二、I2C通信概念)

硬件连接
SoC和触摸屏的连接方式如下图。其中触摸屏IC中烧录的固件的功能,一方面控制触摸屏面板,另一方面接收来自主设备的I2C信号。对于我们开发来说,需要关注的只有跟我们交互的I2C总线,其他并不关心。在这里插入图片描述
驱动软件结构
在这里插入图片描述
可以看到,编写I2C相关的驱动需要两个驱动:I2C控制器(主机)、I2C设备(从机),从机的驱动其实是被动需要提供的。然而i2c-core中均已支持了这两个驱动的注册。

I2C驱动框架

内核中提供两种I2C驱动实现方法(drivers/i2c目录下):
1)第一种叫i2c-dev,这种方法只是封装了主机(I2C master,一般是SoC中内置的I2C控制器)的I2C基本操作,并且向应用层提供相应的操作接口(访问slave硬件设备的基本接口);
2)第二种I2C驱动是所有的I2C操作都放在驱动层实现,直接向应用层提供最终结果。应用层甚至不需要知道这里面有I2C存在,譬如电容式触摸屏驱动,直接向应用层提供/dev/input/event1的操作接口,应用层编程的人根本不知道event1中涉及到了I2C。这种是我们后续分析的重点。

I2C子系统的4个关键结构体:

struct i2c_adapter				I2C适配器,表示SoC内部的I2C控制器相关的寄存器等信息结构体
struct i2c_algorithm			I2C算法(I2C控制器操作从机的一种时序之类的方法),在i2c_adapter是一个指针,
								可以指向不同的algo,这也说明一个主机可以操作不同算法的多个从机
struct i2c_client				I2C(从机)设备信息,类似于平台总线的 platform_device
struct i2c_driver				I2C(从机)设备驱动,类似于平台总线的 platform_driver

由此可以看出,I2C总线的设计思想与platform总线的设计思想非常相似。可以想象 i2c-core.c 中,分别提供 i2c_client 和i2c_driver 的注册接口,然后每次注册时都去对方的链表中查找匹配(match函数),匹配中之后将device硬件信息通过类似probe函数传入driver进行初始化。

i2c-core.c分析

static int __init i2c_init(void)
{
	int retval;

	retval = bus_register(&i2c_bus_type);
	if (retval)
		return retval;
#ifdef CONFIG_I2C_COMPAT
	i2c_adapter_compat_class = class_compat_register("i2c-adapter");
	if (!i2c_adapter_compat_class) {
		retval = -ENOMEM;
		goto bus_err;
	}
#endif
	retval = i2c_add_driver(&dummy_driver);
	if (retval)
		goto class_err;
	return 0;

class_err:
#ifdef CONFIG_I2C_COMPAT
	class_compat_unregister(i2c_adapter_compat_class);
bus_err:
#endif
	bus_unregister(&i2c_bus_type);
	return retval;
}

该函数为 i2c-core 的初始化加载函数,其中最重要的是调用了这一句bus_register(&i2c_bus_type);,而 i2c_bus_type 中又包含 match函数与 probe函数(这完全类似于platform总线的设计,说明上面的猜想是正确的)。
在这里插入图片描述
当然,想知道如何匹配的可以看match函数,这里直接给出结论:当任何一个 driver 或者 client 去注册时,I2C总线都会调用match函数去对 client.name 和 driver.id_table.name 进行循环匹配(driver 中的i d_table 是可以有多个的,一个驱动设备多款设备)。
以及 probe函数中会进一步调用 driver 的 probe函数driver->probe()去实现。

i2c控制器驱动

i2c-s3c2410.c源码分析
这个文件是I2C控制器的驱动文件,实现为platform驱动。

1、平台总线方式注册
2、找到 driver 和 device,并且确认其配对过程
3、probe函数
1)填充一个i2c_adapter结构体(i2c->adap.algo 为i2c通信的算法以及功能),并且调用接口去注册之;
2)从 platform_device 接收硬件信息,做必要的处理(request_mem_region & ioremap、request_irq等);
3)对硬件做初始化(s3c24xx_i2c_init,直接操作210内部I2C控制器的寄存器);

/* s3c24xx_i2c_probe
 *
 * called by the bus driver when a suitable device is found
*/

static int s3c24xx_i2c_probe(struct platform_device *pdev)
{
	struct s3c24xx_i2c *i2c;
	struct s3c2410_platform_i2c *pdata;
	struct resource *res;
	int ret;

	pdata = pdev->dev.platform_data;
	if (!pdata) {
		dev_err(&pdev->dev, "no platform data\n");
		return -EINVAL;
	}

	i2c = kzalloc(sizeof(struct s3c24xx_i2c), GFP_KERNEL);
	if (!i2c) {
		dev_err(&pdev->dev, "no memory for state\n");
		return -ENOMEM;
	}

	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;

	spin_lock_init(&i2c->lock);
	init_waitqueue_head(&i2c->wait);

	/* find the clock and enable it */

	i2c->dev = &pdev->dev;
	i2c->clk = clk_get(&pdev->dev, "i2c");

	if (IS_ERR(i2c->clk)) {
		dev_err(&pdev->dev, "cannot get clock\n");
		ret = -ENOENT;
		goto err_noclk;
	}

	dev_dbg(&pdev->dev, "clock source %p\n", i2c->clk);

	clk_enable(i2c->clk);

	/* map the registers */

	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	if (res == NULL) {
		dev_err(&pdev->dev, "cannot find IO resource\n");
		ret = -ENOENT;
		goto err_clk;
	}

	i2c->ioarea = request_mem_region(res->start, resource_size(res),
					 pdev->name);

	if (i2c->ioarea == NULL) {
		dev_err(&pdev->dev, "cannot request IO\n");
		ret = -ENXIO;
		goto err_clk;
	}

	i2c->regs = ioremap(res->start, resource_size(res));

	if (i2c->regs == NULL) {
		dev_err(&pdev->dev, "cannot map IO\n");
		ret = -ENXIO;
		goto err_ioarea;
	}

	dev_dbg(&pdev->dev, "registers %p (%p, %p)\n",
		i2c->regs, i2c->ioarea, res);

	/* setup info block for the i2c core */

	i2c->adap.algo_data = i2c;
	i2c->adap.dev.parent = &pdev->dev;

	/* initialise the i2c controller */

	ret = s3c24xx_i2c_init(i2c);
	if (ret != 0)
		goto err_iomap;

	/* find the IRQ for this unit (note, this relies on the init call to
	 * ensure no current IRQs pending
	 */

	i2c->irq = ret = platform_get_irq(pdev, 0);
	if (ret <= 0) {
		dev_err(&pdev->dev, "cannot find IRQ\n");
		goto err_iomap;
	}

	ret = request_irq(i2c->irq, s3c24xx_i2c_irq, IRQF_DISABLED,
			  dev_name(&pdev->dev), i2c);

	if (ret != 0) {
		dev_err(&pdev->dev, "cannot claim IRQ %d\n", i2c->irq);
		goto err_iomap;
	}

	ret = s3c24xx_i2c_register_cpufreq(i2c);
	if (ret < 0) {
		dev_err(&pdev->dev, "failed to register cpufreq notifier\n");
		goto err_irq;
	}

	/* Note, previous versions of the driver used i2c_add_adapter()
	 * to add the bus at any number. We now pass the bus number via
	 * the platform data, so if unset it will now default to always
	 * being bus 0.
	 */

	i2c->adap.nr = pdata->bus_num;

	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;
	}

	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;

 err_cpufreq:
	s3c24xx_i2c_deregister_cpufreq(i2c);

 err_irq:
	free_irq(i2c->irq, i2c);

 err_iomap:
	iounmap(i2c->regs);

 err_ioarea:
	release_resource(i2c->ioarea);
	kfree(i2c->ioarea);

 err_clk:
	clk_disable(i2c->clk);
	clk_put(i2c->clk);

 err_noclk:
	kfree(i2c);
	return ret;
}

触摸屏IC驱动

gslX680.c源码分析
这是一个通过I2C总线写的驱动。需要匹配 i2c_driver 和 i2c_client,根据之前分析的匹配条件是 client 的 name 与 driver 中的 id_table 中的 name 匹配。
在这里插入图片描述

要找到 i2c_client ,还是得去 mach_xxx.c 中找(特别是 smdkc110_machine_init 函数),但现在找到的是 i2c_board_info 结构体。
在这里插入图片描述
在这里插入图片描述

struct i2c_board_info {
	char		type[I2C_NAME_SIZE];			// 设备名
	unsigned short	flags;						// 属性
	unsigned short	addr;						// 设备从地址
	void		*platform_data;					// 设备私有数据
	struct dev_archdata	*archdata;
#ifdef CONFIG_OF
	struct device_node *of_node;
#endif
	int		irq;								// 设备使用的IRQ号,对应CPU的EINT
};

可以看到 i2c_register_board_info 最后将 i2c_board_info 加入了 __i2c_board_list 链表。
查找这个链表,发现又回到了 i2c-core.c文件中,i2c_scan_static_board_info中调用了 i2c_new_device,完成 i2c_client 的注册。
在这里插入图片描述
最终,函数调用调用层次:

i2c_add_adapter/i2c_add_numbered_adapter		注册主机adapter时
	i2c_register_adapter
		i2c_scan_static_board_info				同时将 i2c_client 注册了
			i2c_new_device
				device_register

gslX680.c的probe

static int __devinit gsl_ts_probe(struct i2c_client *client,
			const struct i2c_device_id *id)
{
	struct gsl_ts *ts;
	int rc;

	print_info("GSLX680 Enter %s\n", __func__);
	if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
		dev_err(&client->dev, "I2C functionality not supported\n");
		return -ENODEV;
	}
 
	ts = kzalloc(sizeof(*ts), GFP_KERNEL);
	if (!ts)
		return -ENOMEM;
	print_info("==kzalloc success=\n");

	ts->client = client;
	i2c_set_clientdata(client, ts);
	ts->device_id = id->driver_data;

	rc = gslX680_ts_init(client, ts);
	if (rc < 0) {
		dev_err(&client->dev, "GSLX680 init failed\n");
		goto error_mutex_destroy;
	}	

	gsl_client = client;
	
	gslX680_init();
	init_chip(ts->client);
	check_mem_data(ts->client);
	
	rc=  request_irq(client->irq, gsl_ts_irq, IRQF_TRIGGER_RISING, client->name, ts);
	if (rc < 0) {
		print_info( "gsl_probe: request irq failed\n");
		goto error_req_irq_fail;
	}

	/* create debug attribute */
	//rc = device_create_file(&ts->input->dev, &dev_attr_debug_enable);

#ifdef CONFIG_HAS_EARLYSUSPEND
	ts->early_suspend.level = EARLY_SUSPEND_LEVEL_BLANK_SCREEN + 1;
	//ts->early_suspend.level = EARLY_SUSPEND_LEVEL_DISABLE_FB + 1;
	ts->early_suspend.suspend = gsl_ts_early_suspend;
	ts->early_suspend.resume = gsl_ts_late_resume;
	register_early_suspend(&ts->early_suspend);
#endif


#ifdef GSL_MONITOR
	print_info( "gsl_ts_probe () : queue gsl_monitor_workqueue\n");

	INIT_DELAYED_WORK(&gsl_monitor_work, gsl_monitor_worker);
	gsl_monitor_workqueue = create_singlethread_workqueue("gsl_monitor_workqueue");
	queue_delayed_work(gsl_monitor_workqueue, &gsl_monitor_work, 1000);
#endif

	print_info("[GSLX680] End %s\n", __func__);

	return 0;

//exit_set_irq_mode:	
error_req_irq_fail:
    free_irq(ts->irq, ts);	

error_mutex_destroy:
	input_free_device(ts->input);
	kfree(ts);
	return rc;
}

1、申请并填充自定义的触摸屏结构体ts;
2、gslX680_ts_init向input子系统注册输入设备;
3、gslX680_initinit_chipcheck_mem_data硬件初始化;
4、初始化workqueue,申请IRQ(上半部没有什么操作,直接激活下半部,下半部主要是获取触摸信息);

剩下关于触摸屏寄存器操作相关的,不同型号的芯片具有差异性,因此不具体分析。

  • 2
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值