当前位置: 首页 > Linux内核研究, 嵌入式Linux > 正文

我的内核学习笔记7:Intel LPC驱动lpc_ich分析

接触这么久的内核代码,还没有真正分析一个完整的驱动源码,都是零零散散写只言片字。本文就作一个尝试,写一写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设备初始化

本节介绍一下LPC驱动中GPIO设备添加的过程。
在LPC探测函数lpc_ich_probe对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, &reg_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 周一 晚

近来经济拮据,如本文对阁下有帮助,可慷慨解囊赞助笔者以输出更多好文章。
支付宝[email protected] 或 微信fly_camel_fly 均可。感谢!
14373903313201                                  14373903313202

本文固定链接: http://www.latelee.org/embedded-linux/kernel-note-7%ef%bc%8dintel-lpc_ich-driver.html

目前暂无评论

发表评论

*

快捷键:Ctrl+Enter