ZynqMP で Linuxの起動時に "zynqmp-pinctrl firmware:zynqmp-firmware:pinctrl: Invalid IO Standard requested for pin XX" が発生する件

# はじめに

次のような条件で Linux を起動したときに

  * ZynqMP
  * linux-xlnx のバージョンが v2021.1 以降
  * PMUFW のバージョンが v2020.2 以前

ログに次のようなメッセージが大量に出ることがあります。

```console
[    4.204401] zynqmp-pinctrl firmware:zynqmp-firmware:pinctrl: Invalid IO Standard requested for pin 64
[    4.213654] zynqmp-pinctrl firmware:zynqmp-firmware:pinctrl: Invalid IO Standard requested for pin 65
[    4.222907] zynqmp-pinctrl firmware:zynqmp-firmware:pinctrl: Invalid IO Standard requested for pin 66
[    4.232157] zynqmp-pinctrl firmware:zynqmp-firmware:pinctrl: Invalid IO Standard requested for pin 67
[    4.241399] zynqmp-pinctrl firmware:zynqmp-firmware:pinctrl: Invalid IO Standard requested for pin 68
[    4.250644] zynqmp-pinctrl firmware:zynqmp-firmware:pinctrl: Invalid IO Standard requested for pin 69
[    4.259897] zynqmp-pinctrl firmware:zynqmp-firmware:pinctrl: Invalid IO Standard requested for pin 70
[    4.269141] zynqmp-pinctrl firmware:zynqmp-firmware:pinctrl: Invalid IO Standard requested for pin 71
[    4.278385] zynqmp-pinctrl firmware:zynqmp-firmware:pinctrl: Invalid IO Standard requested for pin 72
[    4.287629] zynqmp-pinctrl firmware:zynqmp-firmware:pinctrl: Invalid IO Standard requested for pin 73
[    4.296874] zynqmp-pinctrl firmware:zynqmp-firmware:pinctrl: Invalid IO Standard requested for pin 74
[    4.306118] zynqmp-pinctrl firmware:zynqmp-firmware:pinctrl: Invalid IO Standard requested for pin 75
[    4.315533] zynqmp-pinctrl firmware:zynqmp-firmware:pinctrl: Invalid IO Standard requested for pin 76
[    4.324797] zynqmp-pinctrl firmware:zynqmp-firmware:pinctrl: Invalid IO Standard requested for pin 77
     :
     :
```

原因は PMUFW のバグです(詳細は後述)。
このメッセージはログに警告を出すだけですので、無視しても問題ありません。なお、この PMUFW バグはバージョン v2021.1 以降で修正されています。

# 不具合のメカニズム

## linux-xlnx v2021.1

linux-xlnx v2021.1 は Linux Kernel 5.10 をベースに Xilinx が独自にいろいろ追加したものです。
そのうちの drivers/pinctrl/pinctrl-zynqmp.c に次のようなところがあります。

```C:drivers/pinctrl/pinctrl-zynqmp.c
/**
 * zynqmp_pinconf_cfg_set() - Set requested config for the pin
 * @pctldev:		Pincontrol device pointer.
 * @pin:		Pin number.
 * @configs:		Configuration to set.
 * @num_configs:	Number of configurations.
 *
 * Loop through all configurations and call firmware API
 * to set requested configurations for the pin.
 *
 * Return: 0 on success else error code.
 */
static int zynqmp_pinconf_cfg_set(struct pinctrl_dev *pctldev,
				  unsigned int pin, unsigned long *configs,
				  unsigned int num_configs)
{
	int i, ret;

	if (pin >= zynqmp_desc.npins)
		return -EOPNOTSUPP;

	for (i = 0; i < num_configs; i++) {
		unsigned int param = pinconf_to_config_param(configs[i]);
		unsigned int arg = pinconf_to_config_argument(configs[i]);
		unsigned int value;

		switch (param) {
		:
		(中略)
		:
		case PIN_CONFIG_IOSTANDARD:
			dev_warn(pctldev->dev,
				 "'io-standard' will be deprecated post 2021.2 release, instead use 'power-source'.\n");
			param = PM_PINCTRL_CONFIG_VOLTAGE_STATUS;
			ret = zynqmp_pm_pinctrl_get_config(pin, param, &value);
			if (arg != value)
				dev_warn(pctldev->dev,
					 "Invalid IO Standard requested for pin %d\n",
					 pin);
			break;
		case PIN_CONFIG_POWER_SOURCE:
			param = PM_PINCTRL_CONFIG_VOLTAGE_STATUS;
			ret = zynqmp_pm_pinctrl_get_config(pin, param, &value);

			if (arg != value)
				dev_warn(pctldev->dev,
					 "Invalid IO Standard requested for pin %d\n",
					 pin);

			break;
		:
		(中略)
		:
		}

		param = pinconf_to_config_param(configs[i]);
		arg = pinconf_to_config_argument(configs[i]);
		if (ret)
			dev_warn(pctldev->dev,
				 "failed to set: pin %u param %u value %u\n",
				 pin, param, arg);
	}

	return 0;
}
```

PIN_CONFIG_IOSTANDARD と PIN_CONFIG_POWER_SOURCE は v2021.1 に新に追加されたパラメーターです。
これは次のような働きをします。

  1. device tree の pinctrl に io-standard プロパティ または power-source プロパティがある場合
  2. PMUFW に ピンの電源電圧の設定(3.3V なのか 1.8V のか)を問い合わせて
  3. device tree で指定された通りの設定であるかをチェックして
  4. device tree で指定されていない設定だった場合は警告を出します。

見ての通り、単に警告を出すだけで、特にエラーで失敗するようなことはありません。

## IOU_SLCR Module

ZynqMP のI/Oピンは、26ピンごとにグループに分けて管理されています。このグループをバンクと言います。そして、バンクごとに次のような属性を設定するレジスタが用意されています。

 * 出力ドライブ電流(2/4/8/12mA)
 * 入力属性(CMOS or Schmitt)
 * プルアップダウン設定(プルアップ or プルダウン or なし)
 * スルーレート(fast or slow)
 * 電源電圧(3.3V or 2.5V or 1.8V)

例えばバンク０の設定レジスタは次のようにになっています。

|  Name      | Address   |Width|Type|Field Name      |Description |
|:-----------|:----------|-----|----|:---------------|-------------|
|bank0_ctrl0 |0xFF18_0138|  26 | rw |drive0          |drive1 と共に 出力ドライブ電流を指定します. <br>drive0[n]=0 drive1[n]=0 = 2 mA <br>drive0[n]=0 drive1[n]=1 = 4 mA<br>drive0[n]=1 drive1[n]=0 = 8 mA<br>drive0[n]=1 drive1[n]=1 = 12 mA<br>ピン毎に指定できます。<br>drive0[0] controls MIO pin 0. <br>.. <br>drive0[25] controls MIO pin 25. |
|bank0_ctrl1 |0xFF18_013C|  26 | rw |driver1         |drive0 と共に 出力ドライブ電流を指定します. <br>drive0[n]=0 drive1[n]=0 = 2 mA <br>drive0[n]=0 drive1[n]=1 = 4 mA<br>drive0[n]=1 drive1[n]=0 = 8 mA<br>drive0[n]=1 drive1[n]=1 = 12 mA<br>ピン毎に指定できます。<br>drive1[0] controls MIO pin 0. <br>.. <br>drive1[25] controls MIO pin 25. |
|bank0_ctrl3 |0xFF18_0140|  26 | rw |schmitt_cmos_n  |入力属性を CMOS か Shmitt かを設定します. <br> 0 = CMOS. <br> 1 = Schmitt(Hysteresis). <br>ピン毎に指定できます。<br>schmitt_cmos_n[0] controls MIO pin 0. <br>.. <br>schmitt_cmos_n[25] controls MIO pin 25. |
|bank0_ctrl4 |0xFF18_0144|  26 | rw |pull_high_low_n |内部プルアップ/プルダウンを指定します. <br>0 = プルダウン. <br>1 = プルアップ. <br>ピン毎に指定できます。<br>pull_high_low_n[0] controls MIO pin 0. <br>.. <br>pull_high_low_n[25] controls MIO pin 25. |
|bank0_ctrl5 |0xFF18_0148|  26 | rw |pull_enable     |内部プルアップ/プルダウンを有効するかどうかを指定します. <br>0 = 無効. <br>1 = 有効. <br>ピン毎に指定できます。<br>pull_enable[0] controls MIO pin 0. <br>.. <br>pull_enable[25] controls MIO pin 25. |
|bank0_ctrl6 |0xFF18_014C|  26 | rw |slow_fast_slew_n|スルーレートを指定します. <br>0 = fast slew rate. <br>1 = slow slew rate. <br>ピン毎に指定できます。<br>slow_fast_slew_n[0] controls MIO pin 0. <br>.. <br>slow_fast_slew_n[25] controls MIO pin 25. |
|bank0_status|0xFF18_0150|   1 | ro |voltage_mode    |電源電圧がどうなっているかを示します. <br>0 = 2.5V or 3.3V. <br>1 = 1.8V. <br>**Bit[0] がバンクの電源電圧を示します.** <br>**Bit[31:1]は使われていません.** |

ZynqMP の場合、上のレジスタと同様のものが bank0 〜 bank2 まであります。

ここでの注目点は bank0_status の voltage_mode です。bank0_ctrl0 などはピン毎に指定できるようになっています。しかし、I/Oの電源電圧の設定はバンク単位で行われ、ピン毎の設定はできません。そのため bank0_status は最下位の１ビットのみ有効です。(ここで勘の良い方はピンときたかもしれません。)

## PMUFW v2020.2

PMUFW のピンの状態を返す関数は pm_pinctrl.c にあります。さて、v2020.2 の pm_pinctrl.c は次のようになっています。

```C:pm_pinctrl.c
    :
    (前略)
    :
static PmPinParam pmPinParams[PINCTRL_MAX_CONFIG] = {
	[PINCTRL_CONFIG_SLEW_RATE] = {
		.offset = 0x14U,
		.flags = 0U,
	},
	[PINCTRL_CONFIG_BIAS_STATUS] = {
		.offset = 0x10U,
		.flags = 0U,
	},
	[PINCTRL_CONFIG_PULL_CTRL] = {
		.offset = 0xCU,
		.flags = 0U,
	},
	[PINCTRL_CONFIG_SCHMITT_CMOS] = {
		.offset = 0x8U,
		.flags = 0U,
	},
	[PINCTRL_CONFIG_DRIVE_STRENGTH] = {
		.offset = 0x0U,
		.flags = PM_PIN_PARAM_2_BITS,
	},
	[PINCTRL_CONFIG_VOLTAGE_STATUS] = {
		.offset = 0x18U,
		.flags = PM_PIN_PARAM_RO,
	},
};
    :
    (中略)
    :
/**
 * PmPinCtrlGetParam() - Get PIN configuration parameter value
 * @pinId	ID of the PIN
 * @paramId	ID of the PIN parameter
 * @value	Location to store the parameter value
 *
 * @return	XST_SUCCESS or
 *		XST_INVALID_PARAM if provided argument is invalid
 *
 * @note	See note in PmPinCtrlSetParam().
 */
s32 PmPinCtrlGetParam(const u32 pinId, const u32 paramId, u32* const value)
{
	s32 status = XST_SUCCESS;
	u32 addr, val, shift;

	if ((paramId >= ARRAY_SIZE(pmPinParams)) ||
	    (pinId >= ARRAY_SIZE(pmPinMuxCtrl))) {
		status = XST_INVALID_PARAM;
		goto done;
	}

	shift = pinId % PM_PIN_PARAM_PER_REG;
	addr = PM_PIN_PARAM_GET_ADDR(pinId, pmPinParams[paramId].offset);
	val = XPfw_Read32(addr);

	if (IOU_SLCR_BANK1_CTRL5 == addr) {
		SWAP_BITS_BANK1_CTRL5(val);
	}

	*value = (val >> shift) & 0x1U;

	if (0U != (PM_PIN_PARAM_2_BITS & pmPinParams[paramId].flags)) {
		addr += 4U;
		val = XPfw_Read32(addr);
		val = (val >> shift) & 0x1U;
		*value = (*value << 1U) | val;
	}

done:
	return status;
}
    :
    (後略)
    :
```

案の定 paramId が PINCTRL_CONFIG_VOLTAGE_STATUS の場合でも、他のレジスタと同じようにピン毎にフィールドが分かれているかのように値を得ています。しかし bank0_status/bank1_status/bank2_status レジスタ は最下位の１ビットのみ有効です。そのため、pinId に 1〜25、27〜51、53〜77 が指定された場合は常に 0 を返すという地雷が仕込まれています。

実は linux-xlnx の v2020.2 以前は device tree の pinctrl に io-standard プロパティがあっても特になにもしていませんでした。したがって PMUFW の地雷を踏むことはありませんでした。ところが linux-xlnx の v2021.1 になって、io-standard プロパティ または power-source プロパティがある場合は PMUFW に問い合わせるようになりました。見事に PMUFW の地雷を踏んでしまったわけです。


## PMUFW v2021.1

さすがに v2021.1 ではすかさず次のように修正されました。

```C:pm_pinctrl.c
    :
    (前略)
    :
static PmPinParam pmPinParams[PINCTRL_MAX_CONFIG] = {
	[PINCTRL_CONFIG_SLEW_RATE] = {
		.offset = 0x14U,
		.flags = 0U,
	},
	[PINCTRL_CONFIG_BIAS_STATUS] = {
		.offset = 0x10U,
		.flags = 0U,
	},
	[PINCTRL_CONFIG_PULL_CTRL] = {
		.offset = 0xCU,
		.flags = 0U,
	},
	[PINCTRL_CONFIG_SCHMITT_CMOS] = {
		.offset = 0x8U,
		.flags = 0U,
	},
	[PINCTRL_CONFIG_DRIVE_STRENGTH] = {
		.offset = 0x0U,
		.flags = PM_PIN_PARAM_2_BITS,
	},
	[PINCTRL_CONFIG_VOLTAGE_STATUS] = {
		.offset = 0x18U,
		.flags = PM_PIN_PARAM_RO | PM_PIN_PARAM_PER_BANK,
	},
};
    :
    (中略)
    :
/**
 * PmPinCtrlGetParam() - Get PIN configuration parameter value
 * @pinId	ID of the PIN
 * @paramId	ID of the PIN parameter
 * @value	Location to store the parameter value
 *
 * @return	XST_SUCCESS or
 *		XST_INVALID_PARAM if provided argument is invalid
 *
 * @note	See note in PmPinCtrlSetParam().
 */
s32 PmPinCtrlGetParam(const u32 pinId, const u32 paramId, u32* const value)
{
	s32 status = XST_SUCCESS;
	u32 addr, val, shift;

	if ((paramId >= ARRAY_SIZE(pmPinParams)) ||
	    (pinId >= ARRAY_SIZE(pmPinMuxCtrl))) {
		status = XST_INVALID_PARAM;
		goto done;
	}

	if (0U == (PM_PIN_PARAM_PER_BANK & pmPinParams[paramId].flags)) {
		shift = pinId % PM_PIN_PARAM_PER_REG;
	} else {
		shift = 0;
	}
	addr = PM_PIN_PARAM_GET_ADDR(pinId, pmPinParams[paramId].offset);
	val = XPfw_Read32(addr);

	if (IOU_SLCR_BANK1_CTRL5 == addr) {
		SWAP_BITS_BANK1_CTRL5(val);
	}

	*value = (val >> shift) & 0x1U;

	if (0U != (PM_PIN_PARAM_2_BITS & pmPinParams[paramId].flags)) {
		addr += 4U;
		val = XPfw_Read32(addr);
		val = (val >> shift) & 0x1U;
		*value = (*value << 1U) | val;
	}

done:
	return status;
}
    :
    (後略)
    :
```

PINCTRL_CONFIG_VOLTAGE_STATUS のフラグに PM_PIN_PARAM_PER_BANK ビットが追加され、このビットが 1 の時は shift=0 にして常に最下位ビットを読むように修正されました。

# 対処方法

以下のいずれかの方法で対処できます。

## 無視する

linux-xlnx v2021.1 の drivers/pinctrl/pinctrl-zynqmp.c を見てもわかるように、ログに警告を出すだけで、特にエラーで失敗しているわけではありません。気にならないのであれば無視してもかまいません。

## device tree を修正

device tree の pinctrl に io-standard プロパティまたは power-source プロパティがある場合のみ、この警告が出ます。もともと、このプロパティはピンの電源電圧をチェックしているだけなので、無くても支障ありません。device tree から io-standard プロパティまたは power-source プロパティを削除してしまいます。

## PMUFW を更新

前述のとおり PMUFW の v2021.1 では修正されています。もし可能であれば PMUFW を最新のものに変更したほうが良いでしょう。