NUC972触摸屏驱动移植过程分析(二)

今天继续分析NUC972的触摸屏驱动移植过程,上一节主要分析了触摸屏需要数据,今天来分析一下驱动部分,上一节我们已经了解了触摸屏一般有I2C接口和gpio接口,所以触摸屏既是一个I2C设备,也是一个input设备,linux中把触摸屏整体归为input设备,在input驱动中又包含了I2C驱动和gpio按键驱动,今天首先分析一下I2C驱动部分。

I2C驱动在linux内核中整的还是挺复杂的,按我的理解,作为一般的驱动开发者,需要抓住三部分:第一、I2C核心部分驱动;第二、具体的硬件平台的I2C适配器部分(其实一般都是集成到SOC上的),每个适配器驱动对应一个I2C硬件接口;第三、I2C客户端设备驱动,也就是具体和I2C接口连接的硬件设备的驱动。I2C核心部分驱动类似一个领导,负责协调I2C适配器驱动与I2C客户端驱动之间的数据交互,提供相应的接口;I2C适配器驱动就是SOC上的I2C接口的驱动;I2C客户端驱动就是连接的具体的I2C设备的具体操作方式。I2C核心部分驱动属于linux内核的框架制定者所写,作为一般的驱动开发人员是不需要修改的(当然,框架设计者除外,不过那么高级的开发人员,肯定是不屑于看我这个小白写的东西的,哈哈哈,开个玩笑),这一部分的驱动代码在kernel/i2c这个文件夹里面,主要包括i2c-core.c和i2c-dev.c。其实适配器驱动代码,一般也不需要板级驱动开发者开发的,因为一般的方案提供商或者是芯片生产商,在原版的linux内核的基础上,根据自己的平台开发好了,就比如我用的nuc972这个片子,上面自带两个I2C硬件接口,在nuc970系列的BSP中已经做好了两个硬件I2C适配器的驱动,在kernel/drivers/i2c/busses这个文件夹里面,i2c-nuc970-p0.c和i2c-nuc970-p0.c分别是I2C0与I2C1适配器的驱动代码。另外,在内核中包含了使用SOC的gpio接口模拟I2C时序的模拟I2C适配器的驱动代码,同样是在这个文件夹里面,i2c-gpio.c这个文件就是通用的使用gpio模拟I2C通信的适配器驱动代码。而第三部分的I2C客户端的驱动一般是需要板级驱动移植开发者着重分析和开发的。

        通过以上的分析,可以看得出来,I2C驱动框架中其实是包含了两套driver+device的结构,分别是I2C适配器设备+I2C适配器驱动、I2C客户端设备+I2C客户端驱动。I2C适配器在内核中是被虚拟成platform_device,相对应的驱动是platform_driver,I2C客户端设备是一个标准的I2C设备。内核启动时,注册platform总线,从kernel/driver/base/init.c这个文件中可以看出来:

void __init driver_init(void)
{
	/* These are the core pieces */
	devtmpfs_init();
	devices_init();
	buses_init();
	classes_init();
	firmware_init();
	hypervisor_init();

	/* These are also core pieces, but must come after the
	 * core core pieces.
	 */
	platform_bus_init();
	cpu_dev_init();
	memory_dev_init();
}

内核使用platform_bus_init()这个函数对platform总线进行了初始化。将这个函数追进去,可以看到对platform总线进行了注册,声明了总线的match方法,这段代码在kernel/drivers/base/platform.c中:

struct bus_type platform_bus_type = {
	.name		= "platform",
	.dev_attrs	= platform_dev_attrs,
	.match		= platform_match,
	.uevent		= platform_uevent,
	.pm		= &platform_dev_pm_ops,
};
EXPORT_SYMBOL_GPL(platform_bus_type);

int __init platform_bus_init(void)
{
	int error;

	early_platform_cleanup();

	error = device_register(&platform_bus);
	if (error)
		return error;
	error =  bus_register(&platform_bus_type);
	if (error)
		device_unregister(&platform_bus);
	return error;
}

从这段代码中可以看出来,在platform_bus_init这个函数中,注册了平台总线的match方法和uevent方法等,再往上追match方法对应的函数:

static int platform_match(struct device *dev, struct device_driver *drv)
{
	struct platform_device *pdev = to_platform_device(dev);
	struct platform_driver *pdrv = to_platform_driver(drv);

	/* Attempt an OF style match first */
	if (of_driver_match_device(dev, drv))
		return 1;

	/* Then try ACPI style match */
	if (acpi_driver_match_device(dev, drv))
		return 1;

	/* Then try to match against the id table */
	if (pdrv->id_table)
		return platform_match_id(pdrv->id_table, pdev) != NULL;

	/* fall-back to driver name match */
	return (strcmp(pdev->name, drv->name) == 0);
}

从这段代码中可以看出来platform总线驱动和设备的具体的match方法,id_table的匹配规则优先级高于name的匹配规则。可能说到这里,各位看官迷糊了,现在是在说I2C,说这么多平台总线的事儿干啥,不要急,下面就来看I2C总线的相关内容,基于ncu970的BSP,nuc972的两个硬件I2C适配器驱动的代码包含在kernel/drivers/i2c/busses文件夹中,上面有提到,分别是i2c-nuc970-p0.c和i2c-nuc970-p1.c,这两个文件几乎相同,咱们这里就只看第一个文件吧,这个文件有几百行,具体的可以自己去看看,咱们这里只是了解一下框架,只贴最后几行吧:

static int nuc970_i2c0_probe(struct platform_device *pdev)
{
	struct nuc970_i2c *i2c;
	struct nuc970_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 nuc970_i2c), GFP_KERNEL);
	if (!i2c) {
		dev_err(&pdev->dev, "no memory for state\n");
		return -ENOMEM;
	}

	strlcpy(i2c->adap.name, "nuc970-i2c0", sizeof(i2c->adap.name));
	i2c->adap.owner   = THIS_MODULE;
	i2c->adap.algo    = &nuc970_i2c0_algorithm;
	i2c->adap.retries = 2;
	i2c->adap.class   = I2C_CLASS_HWMON | I2C_CLASS_SPD;

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

	/* find the clock and enable it */
	
	i2c->dev = &pdev->dev;
	i2c->clk = clk_get(NULL, "i2c0");
	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_prepare(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;

	ret = clk_get_rate(i2c->clk)/(pdata->bus_freq * 5) - 1;
	writel(ret & 0xffff, i2c->regs + DIVIDER);

	/* 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, nuc970_i2c_irq, IRQF_SHARED,
			  dev_name(&pdev->dev), i2c);

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

	/* 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_irq;
	}

	platform_set_drvdata(pdev, i2c);

	dev_info(&pdev->dev, "%s: nuc970 I2C adapter\n",
						dev_name(&i2c->adap.dev));
	return 0;

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

/* nuc970_i2c0_remove
 *
 * called when device is removed from the bus
*/

static int nuc970_i2c0_remove(struct platform_device *pdev)
{
	struct nuc970_i2c *i2c = platform_get_drvdata(pdev);

	i2c_del_adapter(&i2c->adap);
	free_irq(i2c->irq, i2c);

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

	iounmap(i2c->regs);

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

	return 0;
}

static struct platform_driver nuc970_i2c0_driver = {
	.probe		= nuc970_i2c0_probe,
	.remove		= nuc970_i2c0_remove,
	.driver		= {
		.name	= "nuc970-i2c0",
		.owner	= THIS_MODULE,
	},
};
module_platform_driver(nuc970_i2c0_driver);

MODULE_DESCRIPTION("nuc970 I2C Bus driver");
MODULE_AUTHOR("Wan ZongShun, <[email protected]>");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:nuc970-i2c0");

从代码的最后几行可以看出来,nuc970系列的I2C0接口确实是被虚拟成了一个platform设备,具体的设备数据在kernel/arch/arm/mach-nuc970/dev.c这个文件里面,如下代码:

static struct resource nuc970_i2c0_resource[] = {
        [0] = {
                .start = NUC970_PA_I2C0,
                .end   = NUC970_PA_I2C0 + NUC970_SZ_I2C0 - 1,
                .flags = IORESOURCE_MEM,
        },
        [1] = {
                .start = IRQ_I2C0,
                .end   = IRQ_I2C0,
                .flags = IORESOURCE_IRQ,
        }
};

static struct nuc970_platform_i2c nuc970_i2c0_data = {
	.bus_num = 0,
	.bus_freq = 100000,
};

struct platform_device nuc970_device_i2c0 = {
        .name		  = "nuc970-i2c0",
        .id		  = -1,
        .num_resources	  = ARRAY_SIZE(nuc970_i2c0_resource),
        .resource	  = nuc970_i2c0_resource,
		.dev = {
        	.platform_data = &nuc970_i2c0_data,
    	}
};

在这里定义了nuc970系列的i2c0接口必须的硬件资源,可以明显的看出来是一个platform设备。而platform总线在匹配device与driver时,是根据name = "nuc970-i2c0"这个名字字段来匹配的。在总线上,只要driver与device同时出现这个名字,platform总线的match方法就能让他们匹配起来,干柴烈火,就燃烧起来了,正常情况下,后果就是启动了platform_driver中的probe方法,根据上面对应的probe方法对应的函数,可以很清楚的看出来,使用ret = i2c_add_numbered_adapter(&i2c->adap)这个函数向内核添加了一个I2C适配器。至此,I2C0这个硬件适配器已经被成功添加到内核中,并添加了一个名字为nuc970-i2c0的I2C总线。

对于GPIO模拟的I2C适配器驱动,同样是这个道理,驱动内具体的实现方法不太一样,这里就不详细分析了。

下面再看一下I2C总线的注册过程,在kernel/drivers/i2c/i2c-core.c这个文件中,包含了I2C总线初始化的代码:

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

static void __exit i2c_exit(void)
{
	i2c_del_driver(&dummy_driver);
#ifdef CONFIG_I2C_COMPAT
	class_compat_unregister(i2c_adapter_compat_class);
#endif
	bus_unregister(&i2c_bus_type);
}

/* We must initialize early, because some subsystems register i2c drivers
 * in subsys_initcall() code, but are linked (and initialized) before i2c.
 */
postcore_initcall(i2c_init);
module_exit(i2c_exit);

这段代码可以很清楚的看出来在内核中注册了i2c_bus,并注册了一个名字为dummy的I2C总线。根据注册的i2c_bus_type,网上追代码:

struct bus_type i2c_bus_type = {
	.name		= "i2c",
	.match		= i2c_device_match,
	.probe		= i2c_device_probe,
	.remove		= i2c_device_remove,
	.shutdown	= i2c_device_shutdown,
	.pm		= &i2c_device_pm_ops,
};

可以看出来注册I2C总线时,分别配置了match、probe、remove等方法,网上追match方法实现的代码:

static int i2c_device_match(struct device *dev, struct device_driver *drv)
{
	struct i2c_client	*client = i2c_verify_client(dev);
	struct i2c_driver	*driver;

	if (!client)
		return 0;

	/* Attempt an OF style match */
	if (of_driver_match_device(dev, drv))
		return 1;

	/* Then ACPI style match */
	if (acpi_driver_match_device(dev, drv))
		return 1;

	driver = to_i2c_driver(drv);
	/* match on an id table if there is one */
	if (driver->id_table)
		return i2c_match_id(driver->id_table, client) != NULL;

	return 0;
}

从上面的代码可以很清楚的看出来,i2c的device与driver之间的匹配是根据id_table进行匹配的,其实将i2c_match_id这个函数追进去,可以看出来,也是根据名字进行匹配的。

说到这里,I2C适配器端的驱动与设备基本上已经分析完成了,下面继续分析I2C客户端的驱动与设备数据,即触摸屏芯片TSC2007的驱动(终于说到触摸屏芯片了)。首先看一下TSC2007驱动代码,在kernel/drivers/input/touchscreen文件夹里面,tsc2007.c这个文件,同样的,这里只看框架,所以只看一下最后几行代码:

static int tsc2007_probe(struct i2c_client *client,
				   const struct i2c_device_id *id)
{
	struct tsc2007 *ts;
	struct tsc2007_platform_data *pdata = client->dev.platform_data;
	struct input_dev *input_dev;
	int err;

	if (!pdata) {
		dev_err(&client->dev, "platform data is required!\n");
		return -EINVAL;
	}

	if (!i2c_check_functionality(client->adapter,
				     I2C_FUNC_SMBUS_READ_WORD_DATA))
		return -EIO;

	ts = kzalloc(sizeof(struct tsc2007), GFP_KERNEL);
	input_dev = input_allocate_device();
	if (!ts || !input_dev) {
		err = -ENOMEM;
		goto err_free_mem;
	}

/***********************************************************************/
#if 1
#warning hack for MDK972
	if (gpio_is_valid(NUC970_PE15)) {

		err = gpio_request_one(NUC970_PE15, GPIOF_IN, "nuc970-ts2007");
		if (err < 0) {
			dev_err(&client->dev, "Failed to request GPIO %d, error %d\n",
				NUC970_PE15, err);
			return err;
		}
#if 1
		if (1) {
			err = gpio_set_debounce(NUC970_PE15,
					1 * 1000);
			/* use timer if gpiolib doesn't provide debounce */
			if (err < 0)
				dev_err(&client->dev, "Failed to set_debounce for GPIO %d, error %d\n",
					NUC970_PE15, err);
		}
#endif

		client->irq = gpio_to_irq(NUC970_PE15);
		if (client->irq < 0) {
			err = client->irq;
			dev_err(&client->dev,
				"Unable to get irq number for GPIO %d, error %d\n",
				NUC970_PE15, err);
			goto err_free_mem;
		}

	}
#endif
/***********************************************************************/

	ts->client = client;
	ts->irq = client->irq;
	ts->input = input_dev;
	init_waitqueue_head(&ts->wait);

	ts->model             = pdata->model;
	ts->x_plate_ohms      = pdata->x_plate_ohms;
	ts->max_rt            = pdata->max_rt ? : MAX_12BIT;
	ts->poll_delay        = pdata->poll_delay ? : 1;
	ts->poll_period       = pdata->poll_period ? : 1;
	ts->get_pendown_state = pdata->get_pendown_state;
	ts->clear_penirq      = pdata->clear_penirq;

	if (pdata->x_plate_ohms == 0) {
		dev_err(&client->dev, "x_plate_ohms is not set up in platform data");
		err = -EINVAL;
		goto err_free_mem;
	}

	snprintf(ts->phys, sizeof(ts->phys),
		 "%s/input0", dev_name(&client->dev));

	input_dev->name = "TSC2007 Touchscreen";
	input_dev->phys = ts->phys;
	input_dev->id.bustype = BUS_I2C;

	input_dev->open = tsc2007_open;
	input_dev->close = tsc2007_close;

	input_set_drvdata(input_dev, ts);

	input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS);
	input_dev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH);

	input_set_abs_params(input_dev, ABS_X, 0, MAX_12BIT, pdata->fuzzx, 0);
	input_set_abs_params(input_dev, ABS_Y, 0, MAX_12BIT, pdata->fuzzy, 0);
	input_set_abs_params(input_dev, ABS_PRESSURE, 0, MAX_12BIT,
			pdata->fuzzz, 0);

	if (pdata->init_platform_hw)
		pdata->init_platform_hw();

	err = request_threaded_irq(ts->irq, tsc2007_hard_irq, tsc2007_soft_irq,
				   IRQF_ONESHOT, client->dev.driver->name, ts);
	if (err < 0) {
		dev_err(&client->dev, "irq %d busy?\n", ts->irq);
		goto err_free_mem;
	}

	tsc2007_stop(ts);

	err = input_register_device(input_dev);
	if (err)
		goto err_free_irq;

	i2c_set_clientdata(client, ts);

	return 0;

 err_free_irq:
	free_irq(ts->irq, ts);
	if (pdata->exit_platform_hw)
		pdata->exit_platform_hw();
 err_free_mem:
	input_free_device(input_dev);
	kfree(ts);
	return err;
}

static int tsc2007_remove(struct i2c_client *client)
{
	struct tsc2007	*ts = i2c_get_clientdata(client);
	struct tsc2007_platform_data *pdata = client->dev.platform_data;

	free_irq(ts->irq, ts);

	if (pdata->exit_platform_hw)
		pdata->exit_platform_hw();

	input_unregister_device(ts->input);
	kfree(ts);

	return 0;
}

static const struct i2c_device_id tsc2007_idtable[] = {
	{ "tsc2007", 0 },
	{ }
};

MODULE_DEVICE_TABLE(i2c, tsc2007_idtable);

static struct i2c_driver tsc2007_driver = {
	.driver = {
		.owner	= THIS_MODULE,
		.name	= "tsc2007"
	},
	.id_table	= tsc2007_idtable,
	.probe		= tsc2007_probe,
	.remove		= tsc2007_remove,
};

module_i2c_driver(tsc2007_driver);

MODULE_AUTHOR("Kwangwoo Lee <[email protected]>");
MODULE_DESCRIPTION("TSC2007 TouchScreen Driver");
MODULE_LICENSE("GPL");

tsc2007的驱动是向内核的添加了i2c的id_table,作为总线match时使用,并注册了一个名字为tsc2007的i2c_driver,里面包含probe方法、remove方法,这是驱动部分。下面再看一下i2c设备的device部分,这部分的数据在kernel/arch/arm/mach-nuc970文件夹里面的dev.c中:

static struct i2c_board_info __initdata nuc970_i2c_clients2[] =
{
	{
		I2C_BOARD_INFO("tsc2007", 0x48),
		.platform_data	= &tsc2007_info,
		/* irq number is run-time assigned */
	},
#ifdef CONFIG_SENSOR_OV7725
	{I2C_BOARD_INFO("ov7725",  0x21),},
#endif
#ifdef CONFIG_SENSOR_OV5640
	{I2C_BOARD_INFO("ov5640",  0x3c),},
#endif
#ifdef CONFIG_SENSOR_NT99141
	{I2C_BOARD_INFO("nt99141", 0x2a),},
#endif
#ifdef CONFIG_SENSOR_NT99050
	{I2C_BOARD_INFO("nt99050", 0x21),},
#endif
	{I2C_BOARD_INFO("lm75a", 0x4e),},
	
    {I2C_BOARD_INFO("ds1307", 0x68),},
};

从上面的代码中可以看出来,这个总线上包含的不仅仅是触摸屏一个I2C设备,I2C_BOARD_INFO就是一个宏定义,填充结构体i2c_board_info的名字和地址字段。这个是就是tsc2007需要传入驱动的数据,下面看一下i2c device的注册:

i2c_register_board_info(2, nuc970_i2c_clients2, ARRAY_SIZE(nuc970_i2c_clients2));

其实就是这个函数,意思就是想I2C适配器号码为2的总线注册这些I2C客户端设备,将这个函数追进去,其实就是向相应i2c总线的id_table对应的链表中增加元素:

int __init
i2c_register_board_info(int busnum,
	struct i2c_board_info const *info, unsigned len)
{
	int status;

	down_write(&__i2c_board_lock);

	/* dynamic bus numbers will be assigned after the last static one */
	if (busnum >= __i2c_first_dynamic_bus_num)
		__i2c_first_dynamic_bus_num = busnum + 1;

	for (status = 0; len; len--, info++) {
		struct i2c_devinfo	*devinfo;

		devinfo = kzalloc(sizeof(*devinfo), GFP_KERNEL);
		if (!devinfo) {
			pr_debug("i2c-core: can't register boardinfo!\n");
			status = -ENOMEM;
			break;
		}

		devinfo->busnum = busnum;
		devinfo->board_info = *info;
		list_add_tail(&devinfo->list, &__i2c_board_list);
	}

	up_write(&__i2c_board_lock);

	return status;
}

到此为止,i2c客户端的driver与device都已经注册完毕,和平台总线一样的流程,i2c总线通过match绑定的匹配方法对driver与device进行匹配,当driver与device中都出现“tsc2007”这个字符时,同样的情况,同样的干柴烈火,同样的启动驱动中的probe方法对应的函数,就可是使用i2c_device设备中的数据对i2c driver中的相应字段进行填充。

好了,到这里,触摸屏驱动中和I2C相关的内容基本上就分析完了,在这里总结一下,I2C的驱动分为两部分,第一部分是I2C适配器的驱动,也就是soc上的I2C接口的驱动,在内核中是一个platform设备,对应的也是platform驱动,当I2C适配器的设备与驱动匹配成功后,会向内核添加相对应的I2C总线,这个是实实在在的i2c总线,这样I2C适配器的设备与驱动就匹配完成了。第二部分是I2C客户端的设备与驱动,I2c客户端的驱动是i2c总线驱动,设备是i2c设备,通过设备数据向相应的设备总线注册i2c device,驱动端向内核中注册相应的驱动,i2c总线的match方法根据名字进行匹配,一旦匹配成功,I2C部分的初始化基本上就完成了。

好了,今天就说这么多吧。

猜你喜欢

转载自blog.csdn.net/b7376811/article/details/86607529
今日推荐