概述
接触这么久的内核代码,还没有真正分析一个完整的驱动源码,都是零零散散写只言片字。本文就作一个尝试,写一写Linux内核源码分析层面的文章。
本文介绍基于Intel baytrail系列的e3800系列的SOC的LPC驱动。后续文章将进行该系列的WDT和GPIO驱动分析。
一、概述
ICH是I/O Controller Hub,用于Intel南桥芯片,扩展IO。目前有很多代,从ICH0到ICH11。ICH支持LPC接口(Low Pin Count interface)——本文分析的Linux内核中驱动名称就是lpc_ich。这些属于Intel架构方面的知识,非笔者强项,所以就不再扩展说明。
从e3800手册(参考文后资料)35章节中可以看到,LPC是一个PCI设备,PCI寻址为00:1f.0,ID号为8086:0f1c。它的配置空间有ACPI、PMC、GPIO、ILB、SPI等基地址,偏移地位分别是0x40、0x44、0x48、0x50、0x54。我们从这些偏移地址可以获取到对应模块的IO地址(或内存地址),从而进一步完成驱动功能。比如有一片FLASH是接到e3800的SPI控制器,就应该从LPC的0x54偏移处获取到SPI基地址,之后就能控制SPI寄存器了。
另外说一点,e3800有内部看门狗功能。驱动名称是iTCO_wdt。它隐藏于ACPI中,笔者也是无意中发现了。
下面使用3.17.1版本内核跟踪分析源码。
二、lpc_ich驱动代码
lpc_ich驱动源码文件是drivers/mfd/lpc_ich.c。它是一个PCI驱动,其驱动入口定义如下:
static struct pci_driver lpc_ich_driver = {
.name = "lpc_ich",
.id_table = lpc_ich_ids,
.probe = lpc_ich_probe,
.remove = lpc_ich_remove,
};
module_pci_driver(lpc_ich_driver);
其中module_pci_driver是注册pci驱动的封装函数。而pci_driver包含了探测和删除函数,以及PCI设备ID表lpc_ich_ids。其结构体定义如下:
static const struct pci_device_id lpc_ich_ids[] = {
{ PCI_VDEVICE(INTEL, 0x2410), LPC_ICH},
...
{ PCI_VDEVICE(INTEL, 0x0f1c), LPC_BAYTRAIL},
{ 0, }, /* End of list */
};
其中的LPC_ICH和LPC_BAYTRAIL等用于区别不同的类别——因为lpc_ich适用于许多不同的设备。这些值在lpc_chipset_info数组中可以找到对应的定义。lpc_chipset_info是lpc_ich_info结构体数据。声明如下:
struct lpc_ich_info {
char name[32];
unsigned int iTCO_version;
unsigned int gpio_version;
u8 use_gpio;
};
其中iTCO_version和gpio_version分别指定了iTCO看门狗和GPIO的版本。当然,如果其值为0则表示没有此设备。lpc_chipset_info数组定义如下:
static struct lpc_ich_info lpc_chipset_info[] = {
[LPC_ICH] = {
.name = "ICH",
.iTCO_version = 1,
},
...
[LPC_BAYTRAIL] = {
.name = "Bay Trail SoC",
.iTCO_version = 3,
},
};
比如,ID号为0f1c的PCI设备对应着LPC_BAYTRAIL,其名称为Bay Trail SoC,同时定义了iTCO_version(但没有定义gpio_version),版本号为3(iTCO看门狗驱动将在后续文章中介绍)。
2.1 探测函数
内核启动时会扫描PCI设备,当存在lpc_ich_ids中的ID时,就会调用lpc_ich_probe函数。其主要代码功能描述如下。
1、调用devm_kzalloc申请lpc_ich_priv空间,lpc_ich_priv声明如下:
struct lpc_ich_priv {
int chipset;
int abase; /* ACPI base */
int actrl_pbase; /* ACPI control or PMC base */
int gbase; /* GPIO base */
int gctrl; /* GPIO control */
int abase_save; /* Cached ACPI base value */
int actrl_pbase_save; /* Cached ACPI control or PMC base value */
int gctrl_save; /* Cached GPIO control value */
};
从上图可以看到,lpc设备有ACPI、GPIO的基地址,——而这都在结构体中体现,当然这个结构体并没有SPI基地址,如有需要则可以自行添加。
2、根据手册定义赋值ACPI、GPIO基地址:
priv->chipset = id->driver_data; // 根据lpc_ich_ids传递给chipset
priv->actrl_pbase_save = -1;
priv->abase_save = -1;
priv->abase = ACPIBASE; //ACPI基地址
priv->actrl_pbase = ACPICTRL_PMCBASE;
priv->gctrl_save = -1;
if (priv->chipset <= LPC_ICH5) {
priv->gbase = GPIOBASE_ICH0;
priv->gctrl = GPIOCTRL_ICH0;
} else {
priv->gbase = GPIOBASE_ICH6; // GPIO基地址
priv->gctrl = GPIOCTRL_ICH6;
}
3、根据lpc_chipset_info定义初始化WDT和GPIO
if (lpc_chipset_info[priv->chipset].iTCO_version) {
ret = lpc_ich_init_wdt(dev);
if (!ret)
cell_added = true;
}
if (lpc_chipset_info[priv->chipset].gpio_version) {
ret = lpc_ich_init_gpio(dev);
if (!ret)
cell_added = true;
}
在lpc_ich_init_wdt和lpc_ich_init_gpio中,会初始化相关的IO资源(或memory资源),最后调用mfd_add_devices函数注册mfd设备。该函数带有mfd_cell结构体,定义如下:
static struct mfd_cell lpc_ich_cells[] = {
[LPC_WDT] = {
.name = "iTCO_wdt",
.num_resources = ARRAY_SIZE(wdt_ich_res),
.resources = wdt_ich_res,
.ignore_resource_conflicts = true,
},
[LPC_GPIO] = {
.name = "gpio_ich",
.num_resources = ARRAY_SIZE(gpio_ich_res),
.resources = gpio_ich_res,
.ignore_resource_conflicts = true,
},
};
从结构体中可以清晰看到,LPC含有WDT设备、GPIO设备。名称分别为iTCO_wdt和gpio_ich。而在drivers/watchdog/和drivers/gpio目录中,我们看到有这2个设备的驱动。
2.2 GPIO设备初始化
priv->gbase = GPIOBASE_ICH6; // GPIO基地址
priv->gctrl = GPIOCTRL_ICH6;
其定义是:
#define GPIOBASE_ICH6 0x48
#define GPIOCTRL_ICH6 0x4C
前文也提及有0x48地址,所有地址都可以在手册对应章节中找到。
直接初始化GPIO在函数lpc_ich_init_gpio中。这个函数对ACPI(GPE0)和GPIO都进行初始化。使用的resource结构体gpio_ich_res是一个数组,包含了GPE0和GPIO。这里只看GPIO的部分,主要代码功能描述如下。
1、读取GPIO基地址值,即通过LPC这个PCI设备的配置空间偏移值GPIOBASE_ICH6。
/* Setup GPIO base register */
pci_read_config_dword(dev, priv->gbase, &base_addr_cfg);
base_addr = base_addr_cfg & 0x0000ff80;
另外提一点,对于Linux的PCI驱动,一般使用pci_read_config_dword、pci_write_config_dword等函数进行读写,该类函数形式相近。dword表示读写32字节,word读写16字节,byte读写8字节。
2、设置resource,即把前面获取到的base_addr赋值给resource的start成员变量。
res = &gpio_ich_res[ICH_RES_GPIO];
res->start = base_addr;
switch (lpc_chipset_info[priv->chipset].gpio_version) {
case ICH_V5_GPIO:
case ICH_V10CORP_GPIO:
res->end = res->start + 128 - 1;
break;
default:
res->end = res->start + 64 - 1;
break;
}
3、检查GPIO冲突。
ret = lpc_ich_check_conflict_gpio(res);
4、使能GPIO:
lpc_ich_enable_gpio_space(dev);
即使能GPIO地址解码,
e3800手册中对GPIO使能解释如下:
bit 1 Enable (EN): When set, decode of the IO range pointed to by the GBASE is enabled.
函数代码如下:static void lpc_ich_enable_gpio_space(struct pci_dev *dev)
{
struct lpc_ich_priv *priv = pci_get_drvdata(dev);
u8 reg_save;
pci_read_config_byte(dev, priv->gctrl, ®_save);
pci_write_config_byte(dev, priv->gctrl, reg_save | 0x10);
priv->gctrl_save = reg_save;
}
对比发现,其使能并不是0x10,而是0x02。所以在实际驱动开发中,是需要修改的。——这或许就是公司嵌入式工程师价值所在吧,他们并不是外人所认知的那么简单。并非一句“Linux内核都是开源”就能概括的。
5、添加mfd设备。 lpc_ich_finalize_cell(dev, &lpc_ich_cells[LPC_GPIO]);
ret = mfd_add_devices(&dev->dev, -1, &lpc_ich_cells[LPC_GPIO],
1, NULL, 0, NULL);
其中lpc_ich_finalize_cell是设置mfd_cell结构体的成员platform_data。从上文知道,lpc_chipset_info保存着名称和一些模块的版本号。这样,在对应的驱动中就能获取到这个些值从而进行不同的处理。最后调用mfd_add_devices添加lpc_ich_cells[LPC_GPIO]——即GPIO的platform设备。
三、总结
mfd是多功能设备,它将一些类似的模块结合到一起,其本质上还是platform设备。由platform设备模型原理知道,platform设备和platform驱动通过name来匹配,匹配时会调用到驱动的probe函数。关于mfd,将会后续文章进行分析。
对于lpc_ich驱动而言。当系统启动时扫描到LPC设备时,就会注册WDT和GPIO设备。从而调用到对应驱动的probe函数,进而使之正常工作。——当然,前提是把WDT和GPIO驱动添加到内核中。
资源参考:
1、baytrail手册:http://www.intel.com/content/www/us/en/embedded/products/bay-trail/atom-e3800-family-datasheet.html
2、ICH的wifi介绍:https://en.wikipedia.org/wiki/I/O_Controller_Hub
3、内核源码官网:https://www.kernel.org
4、内核源码查询:http://lxr.free-electrons.com/source/?v=3.17
李迟 2016.12.5 周一 晚
最后
以上就是高挑狗为你收集整理的我的内核学习笔记7:Intel LPC驱动lpc_ich分析一、概述二、lpc_ich驱动代码三、总结的全部内容,希望文章能够帮你解决我的内核学习笔记7:Intel LPC驱动lpc_ich分析一、概述二、lpc_ich驱动代码三、总结所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复