https://blog.csdn.net/b7376811/article/details/86607529
今天继续分析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,
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
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部分的初始化基本上就完成了。
好了,今天就说这么多吧。