嵌入式Linux驅動筆記(二十一)------GPIO和Pinctrl子系統的分析和思考

你好!這裏是風箏的博客,

歡迎和我一起交流。


好久都沒有寫東西了,最近來廣州某公司實習,順便記錄下吧。
吐槽下,因爲是二級保密單位,公司裏電腦不給聯網,賊難受。。。。。。

不過第一次接觸真正的產品開發,正式的工程項目,還是有很多值得我學習的地方的。
公司用的是聯芯的一套方案,分配電腦後,師傅給了我一個簡單的任務:給一臺手機(Android6.0)移植光線&距離傳感器驅動和充電呼吸燈芯片的驅動。
因爲是移植,所以,,,,,,,,我覺得我可以叫設備樹工程師…
任務很簡單,但是我卻對代碼陷入了沉思。

當我拿到一份代碼方案時,最簡單的,操作GPIO。
當我最初學習的時候,用的是S3C2440,從網上很多資料還是知道GPIO的宏,如:S3C2410_GPF(4)

在我使用全志 H3的芯片,大部分我都是直接操作寄存器了,而且網上也有資料可循。
現在我在公司接觸的這塊芯片,網上根本沒有資料,資料就是廠家提供的說明文檔,那以操作GPIO爲例,我該怎麼操作一個GPIO呢?

其實有經驗的人都知道:模仿!
看工程下別人寫的代碼就知道了,這算是一種取巧捷徑,那我們現在是思考,該怎麼做呢?
因爲公司是完全要求保密的,代碼我也拷不出來,我就以我手上的sun8i-h3代碼分析吧:
首先,打開手冊,查找芯片手冊裏關於GPIO的章節:
address
這裏我們可以看出GPIO的基地址是:0x01C20800
像我公司裏用的芯片,是普通GPIO有一個基地址,可複用的GPIO又是另一個基地址,所以有gpio節點和pinctrl節點。
但是現在我們sun8i-h3都放在一個基地址了,所以gpio、pinctrl描述的是同一個節點。
我們去內核搜索一下,grep一下0x01C20800這個地址即可:
pio
接着我們可以去找下他的compatible屬性,搜索一下 “&pio”很快就找到:
compatible
compatible屬性是:“allwinner,sun8i-h3-pinctrl”
那麼可以安心去找他的代碼了:

static const struct sunxi_pinctrl_desc sun8i_h3_pinctrl_data = {
	.pins = sun8i_h3_pins,//數組內容放在pins字段
	.npins = ARRAY_SIZE(sun8i_h3_pins),
	.irq_banks = 2,
	.irq_read_needs_mux = true
};

static int sun8i_h3_pinctrl_probe(struct platform_device *pdev)
{
	return sunxi_pinctrl_init(pdev,
				  &sun8i_h3_pinctrl_data);
}

static const struct of_device_id sun8i_h3_pinctrl_match[] = {
	{ .compatible = "allwinner,sun8i-h3-pinctrl", },
	{}
};

static struct platform_driver sun8i_h3_pinctrl_driver = {
	.probe	= sun8i_h3_pinctrl_probe,
	.driver	= {
		.name		= "sun8i-h3-pinctrl",
		.of_match_table	= sun8i_h3_pinctrl_match,
	},
};

這裏面有個數組需要注意一下,那就是:sun8i_h3_pins

static const struct sunxi_desc_pin sun8i_h3_pins[] = {
	SUNXI_PIN(SUNXI_PINCTRL_PIN(A, 0),
		  SUNXI_FUNCTION(0x0, "gpio_in"),
		  SUNXI_FUNCTION(0x1, "gpio_out"),
		  SUNXI_FUNCTION(0x2, "uart2"),		/* TX */
		  SUNXI_FUNCTION(0x3, "jtag"),		/* MS */
		  SUNXI_FUNCTION_IRQ_BANK(0x6, 0, 0)),	/* PA_EINT0 */
	SUNXI_PIN(SUNXI_PINCTRL_PIN(A, 1),
		  SUNXI_FUNCTION(0x0, "gpio_in"),
		  SUNXI_FUNCTION(0x1, "gpio_out"),
		  SUNXI_FUNCTION(0x2, "uart2"),		/* RX */
		  SUNXI_FUNCTION(0x3, "jtag"),		/* CK */
		  SUNXI_FUNCTION_IRQ_BANK(0x6, 0, 1)),	/* PA_EINT1 */
	SUNXI_PIN(SUNXI_PINCTRL_PIN(A, 2),
		  SUNXI_FUNCTION(0x0, "gpio_in"),
		  SUNXI_FUNCTION(0x1, "gpio_out"),
		  SUNXI_FUNCTION(0x2, "uart2"),		/* RTS */
		  SUNXI_FUNCTION(0x3, "jtag"),		/* DO */
		  SUNXI_FUNCTION_IRQ_BANK(0x6, 0, 2)),	/* PA_EINT2 */
	SUNXI_PIN(SUNXI_PINCTRL_PIN(A, 3),
		  SUNXI_FUNCTION(0x0, "gpio_in"),
		  SUNXI_FUNCTION(0x1, "gpio_out"),
		  SUNXI_FUNCTION(0x2, "uart2"),		/* CTS */
		  SUNXI_FUNCTION(0x3, "jtag"),		/* DI */
		  SUNXI_FUNCTION_IRQ_BANK(0x6, 0, 3)),	/* PA_EINT3 */
	SUNXI_PIN(SUNXI_PINCTRL_PIN(A, 4),
		  SUNXI_FUNCTION(0x0, "gpio_in"),
		  SUNXI_FUNCTION(0x1, "gpio_out"),
		  SUNXI_FUNCTION(0x2, "uart0"),		/* TX */
		  SUNXI_FUNCTION_IRQ_BANK(0x6, 0, 4)),	/* PA_EINT4 */
	//......		  
}

看到那些"管腳映射結構體"數組裏的參數,A0、A1…是不是覺得有有那麼一絲絲熟悉?
裏面的參數可以解讀爲:以A6爲例
配置0x0爲輸入,0x1爲輸出,0x2爲複用爲串口,0x6爲中斷線配置,和芯片手冊上是完全對應的!
進入probe函數看,挑一些重點的來寫吧:

int sunxi_pinctrl_init_with_variant(struct platform_device *pdev,
				    const struct sunxi_pinctrl_desc *desc,
				    unsigned long variant)
{
	pctl = devm_kzalloc(&pdev->dev, sizeof(*pctl), GFP_KERNEL);
	//......
	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	pctl->membase = devm_ioremap_resource(&pdev->dev, res);//基地址,也就是0x01C20800 
	pctl->dev = &pdev->dev;
	pctl->desc = desc;
	pctl->variant = variant;
	//......
	ret = sunxi_pinctrl_build_state(pdev);//重要!和pinctrl子系統有關,懶得分析了,後面給鏈接,講得挺好的
	//......
	for (i = 0, pin_idx = 0; i < pctl->desc->npins; i++) {//取出數組內容
		const struct sunxi_desc_pin *pin = pctl->desc->pins + i;//數組內容
		if (pin->variant && !(pctl->variant & pin->variant))
			continue;

		pins[pin_idx++] = pin->pin;
	}
	//......
	pctrl_desc->name = dev_name(&pdev->dev);
	pctrl_desc->owner = THIS_MODULE;
	pctrl_desc->pins = pins;//裏面就是之前數組裏的內容
	pctrl_desc->npins = pctl->ngroups;
	pctrl_desc->confops = &sunxi_pconf_ops;//和pinctrl裏pinctrl_select_state函數使用有關,即(pinctrl_select_state)
	pctrl_desc->pctlops = &sunxi_pctrl_ops;//和pinctrl裏pinctrl_select_state函數使用有關
	pctrl_desc->pmxops =  &sunxi_pmx_ops;//和pinctrl裏pinctrl_select_state函數使用有關

	pctl->pctl_dev = devm_pinctrl_register(&pdev->dev, pctrl_desc, pctl);//和pinctrl有關,註冊
	//......
	last_pin = pctl->desc->pins[pctl->desc->npins - 1].pin.number;
	pctl->chip->owner = THIS_MODULE;
	pctl->chip->request = gpiochip_generic_request,
	pctl->chip->free = gpiochip_generic_free,
	pctl->chip->direction_input = sunxi_pinctrl_gpio_direction_input,
	pctl->chip->direction_output = sunxi_pinctrl_gpio_direction_output,
	pctl->chip->get = sunxi_pinctrl_gpio_get,
	pctl->chip->set = sunxi_pinctrl_gpio_set,
	pctl->chip->of_xlate = sunxi_pinctrl_gpio_of_xlate,
	pctl->chip->to_irq = sunxi_pinctrl_gpio_to_irq,
	pctl->chip->of_gpio_n_cells = 3,//cells參數個數
	pctl->chip->can_sleep = false,
	pctl->chip->ngpio = round_up(last_pin, PINS_PER_BANK) -
			    pctl->desc->pin_base;
	pctl->chip->label = dev_name(&pdev->dev);
	pctl->chip->parent = &pdev->dev;
	pctl->chip->base = pctl->desc->pin_base;

	ret = gpiochip_add_data(pctl->chip, pctl);

	for (i = 0; i < pctl->desc->npins; i++) {
		const struct sunxi_desc_pin *pin = pctl->desc->pins + i;

		ret = gpiochip_add_pin_range(pctl->chip, dev_name(&pdev->dev),
					     pin->pin.number - pctl->desc->pin_base,
					     pin->pin.number, 1);//建立動態鏈表chip->pin_ranges,node爲gpio_pin_range,其成員range包含pin腳基地址。
		if (ret)
			goto gpiochip_error;
	}
	//後面就是一些和irq相關的東西了
}

可以看出,這個函數,一開始
a)把GPIO的基地址取出來,放到pctl->membase裏。

b)sunxi_pinctrl_build_state函數,這是和pinctrl子系統有關的,裏面差不多是:將pctl->desc的子項數據(即sunxi_pinctrl_desc數據)複製到pctl相應子項(sunxi_pinctrl結構),文章末尾我會放一篇文章鏈接,講的還不錯,看那個即可。

c)把之前描述GPIO的數組放到pctrl_desc結構體中,以及填充confops、pctlops和pmxops三個字段,這三個字段就厲害了,當我們使用pinctrl_select_state(pinctrl的API)函數時就是調用的這些!

d)填充pctl->chip裏面的各個字段,其中字段:direction_input、direction_output和get、set字段是我們所熟悉的,當我們使用GPIO的函數,如:GPIO_SET_VALUE或者GPIO_GET_VALUE時就是調用到裏面的回調函數!還有注意of_xlate這個字段,我們在獲取設備樹節點時也要用到!而且其中of_gpio_n_cells字段也讓我們知道,設備樹裏描述GPIO的屬性是三個參數。

e)調用gpiochip_add_data,其實如果我們不是從設備樹裏通過compatible屬性來反推代碼的話,也可以直接搜索這個函數,因爲每個平臺都會調用這個函數來添加GPIO,這裏就是GPIO驅動將通過chip來與pinctrl聯繫(bsp工程師通過gpiochip_add將gpio chip添加到gpio子系統中)

舉個例子,當我們要從設備樹獲取GPIO時,API爲:
of_get_named_gpio_flags函數

int of_get_named_gpio_flags(struct device_node *np, const char *list_name,
			    int index, enum of_gpio_flags *flags)
{
	struct gpio_desc *desc;

	desc = of_get_named_gpiod_flags(np, list_name, index, flags);//獲取描述

	if (IS_ERR(desc))
		return PTR_ERR(desc);
	else
		return desc_to_gpio(desc);//這裏纔是通過描述得到具體的GPIO
}

其中

struct gpio_desc *of_get_named_gpiod_flags(struct device_node *np,
		     const char *propname, int index, enum of_gpio_flags *flags)
{
	ret = of_parse_phandle_with_args(np, propname, "#gpio-cells", index,
					 &gpiospec);//獲取參數,存放在gpiospec裏
	chip = of_find_gpiochip_by_xlate(&gpiospec);
	desc = of_xlate_and_get_gpiod_flags(chip, &gpiospec, flags);
	return desc;
}

重點就在of_xlate_and_get_gpiod_flags函數裏:

static struct gpio_desc *of_xlate_and_get_gpiod_flags(struct gpio_chip *chip,
					struct of_phandle_args *gpiospec,
					enum of_gpio_flags *flags)
{
	int ret;

	if (chip->of_gpio_n_cells != gpiospec->args_count)
		return ERR_PTR(-EINVAL);

	ret = chip->of_xlate(chip, gpiospec, flags);
	if (ret < 0)
		return ERR_PTR(ret);

	return gpiochip_get_desc(chip, ret);
}

可以看到,這裏就是會調用到我們之前說的of_xlate回調函數了,即sunxi_pinctrl_gpio_of_xlate函數:

static int sunxi_pinctrl_gpio_of_xlate(struct gpio_chip *gc,
				const struct of_phandle_args *gpiospec,
				u32 *flags)
{
	int pin, base;

	base = PINS_PER_BANK * gpiospec->args[0];
	pin = base + gpiospec->args[1];

	if (pin > gc->ngpio)
		return -EINVAL;

	if (flags)
		*flags = gpiospec->args[2];

	return pin;
}

可以簡單看出,我們的設備樹裏GPIO的屬性有三個參數,第一個是base,第二個是pin引腳,第三個是flag標誌了。
看了下設備樹,也確實是這樣寫的:
a10
設備樹這裏描述的就是A10了,從芯片文檔我們可知,分爲好幾組IO口,第0組就是A。也就是base是0,pin是10,flags是GPIO_ACTIVE_HIGH。

再者,of_get_named_gpiod_flags函數的返回值爲gpio_desc結構體,會通過調用desc_to_gpio函數通過gpio_desc得到具體的GPIO:

int desc_to_gpio(const struct gpio_desc *desc)
{
	//gdev->base在gpiochip_add_data函數填充
	return desc->gdev->base + (desc - &desc->gdev->descs[0]);
}

###同理
    我們在設置某個GPIO爲高時:

static inline void gpio_set_value(unsigned int gpio, int value)
{
	__gpio_set_value(gpio, value);
}

__gpio_set_value
    -->gpiod_set_raw_value
        -->_gpiod_set_raw_value
            -->chip->set(chip, gpio_chip_hwgpio(desc), value);//調用set回調函數

gpio_chip_hwgpio:獲取該gpio號對應於該chip的offset,由於chip的起始號不一定就是開始與系統全局desc的起點,需要這個函數轉換一下
其中set回調函數就是sunxi_pinctrl_gpio_set函數了:

static void sunxi_pinctrl_gpio_set(struct gpio_chip *chip,
				unsigned offset, int value)
{
	struct sunxi_pinctrl *pctl = gpiochip_get_data(chip);
	u32 reg = sunxi_data_reg(offset);
	u8 index = sunxi_data_offset(offset);
	unsigned long flags;
	u32 regval;

	raw_spin_lock_irqsave(&pctl->lock, flags);

	regval = readl(pctl->membase + reg);

	if (value)
		regval |= BIT(index);
	else
		regval &= ~(BIT(index));

	writel(regval, pctl->membase + reg);

	raw_spin_unlock_irqrestore(&pctl->lock, flags);
}

裏面基本就是:
先讀取GPIO寄存器的值
修改值
再寫回去!
其中讀寫的地址爲(pctl->membase + reg),這裏pctl->membase就是我們文章一開始分析時獲取到的0x01C20800 地址了。

.

關於什麼使用pinctrl,有這麼幾個API:

struct pinctrl *devm_pinctrl_get(struct device *dev)

pinctrl_lookup_state //尋找一個pin的配置

pinctrl_select_state // 設置選擇一個pin的配置

.


關於pinctrl的文章,可以看看這個:
鏈接:
gpio子系統和pinctrl子系統(一)
gpio子系統和pinctrl子系統(二)
gpio子系統和pinctrl子系統(三)

Linux內核中提供了pinctrl子系統,目的是爲了統一各SoC廠商的pin腳管理,避免各SoC廠商各自實現相同的pin腳管理子系統,減少SoC廠商系統移植工作量。
功能:
  1. 管理系統中所有可以控制的pin。在系統初始化的時候,枚舉所有可以控制的pin,並標識這些pin。
  2. 管理這些pin的複用(Multiplexing)。對於SOC而言,其引腳除了配置成普通GPIO之外,若干個引腳還可以組成一個pin group,形成特定的功能。例如pin number是{ 0, 8, 16, 24 }這四個引腳組合形成一個pin group,提供SPI的功能。pin control subsystem要管理所有的pin group。
  3. 配置這些pin的特性。例如配置該引腳上的pull-up/down電阻,配置drive strength等。
  4. 與GPIO子系統的交互
  5. 實現pin中斷

關於爲什麼引進pinctrl,看這篇文章:Pinctrl基礎簡介

即:Gpiolib方式的缺點在於:當同一套代碼對應多個board設計時,需要在board--gpiomux.c文件中加宏進行區分。對於不同平臺項目,在board-msm8974-gpiomux.c文件中添加了很多宏控。
pinctrl方式可以避免代碼中的這種冗餘代碼,它將board--gpiomux.c文件中的配置信息移到-pinctrl.dtsi;這樣,針對不同project的board設計,分別在各自project的-pinctrl.dtsi中定義各自的gpio配置信息。
.
gpio子系統讓驅動工程師不用關心底層gpio chip的具體實現,讓bsp工程師不用關心上層驅動工程師的使用方式。pinctrl子系統幫我們管理了pin信息,包括了pin的mux和conf,同時也透明的處理了與gpio子系統的關聯以及設備模型的關聯。

瞭解更多,請點擊:蝸窩科技

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章