Ultra96-V2 で Linux からパワーオフする方法

# はじめに


Ultra96-V2 はZynqMP の I/O ポートからの出力信号がパワーオン/オフLSI の KILL_N入力ポートに接続されています。この信号をＬにすれば Ultra96-V2 をパワーオフ状態にすることができます。

しかし、通常の方法で BOOT.BIN と Linux をビルドした場合はこの信号を制御することが出来ないようで、 Linux から poweroff  コマンド等によってシャットダウンを実行しても Ultra96-V2 がパワーオフ状態になりません。ちなみにパワーオフにする時はパワーボタン(SW4)を長押しする必要がありますが、小さいボタンを長押しするのはけっこう指が痛いです(これが動機)。

この記事では BOOT.BIN に含まれる PMUFW(Platform Management Unit Firmware) に手を入れることによって、Linux から poweroff コマンド等によってシャットダウンを実行した時にこの信号を L にして自動的に Ultra96-V2 をパワーオフ状態にする方法を示します。

また、Linux 起動中にパワーボタンを押したときに Linux をシャットダウンする方法も示します。


# パワーオフのしくみ



## パワーオフまわりの回路


Fig.1 に Ultra96-V2 の パワーオフまわりの回路を示します。


![Fig.1 Ultra96-V2 の MIO34_POWER_KILL_N 信号](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24981/258b7531-7f38-fa8b-8a83-b0a30ce0f6be.png "Fig.1 Ultra96-V2 の MIO34_POWER_KILL_N 信号")

Fig.1 Ultra96-V2 の MIO34_POWER_KILL_N 信号

<br />

パワーオン/オフするLSI(SLG4G42480V)の KILL_N ポートは ZynqMP のMIO34 ポートにレベル変換回路を通じて接続されています。ZynqMP の MIO34 ポートからの出力信号はプルアップされていて、電源投入直後から MIO34 のコンフィギュレーションが行われるまでの期間は H に固定されます。

ZynqMP の MIO34 ポートから L を出力することで、パワーオン/オフする LSI の KILL_N 入力ポートが L になって Ultra96-V2 をパワーオフ状態にします。

パワーオン/オフするLSI(SLG4G42480V)の INT ポートは ZynqMP の MIO26 ポートに接続されています。


## ZynqMP の MIO26/MIO34 ポートの設定


Fig.2 に Ultra96-V2 で ZynqMP の MIO26ポートとMIO34 ポートが Vivado でどのように設定されているかを示します。


![Fig.2 Ultra96-V2 の ZynqMP の MIO26/MIO34 ポートの設定](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24981/b36d31fb-b110-a07d-eaeb-5d35baab0d14.png "Fig.2 Ultra96-V2 の ZynqMP の MIO26/MIO34 ポートの設定")

Fig.2 Ultra96-V2 の ZynqMP の MIO26/MIO34 ポートの設定

<br />



Ultra96-V2 では ZynqMP の MIO26 ポートは PMU(Platform Management Unit) の GPI0に、MIO34 ポートは GPO2 に接続されるように設定されています。




## PMU(Platform Management Unit)


PMU(Platform Management Unit) は、プロセッサ間割り込みや電力管理レジスタなどを利用して ZynqMP 内のリソースのパワーアップ、リセット、モニター(監視)を行うためにZynqMP 内に組み込まれたサブシステムです。PMU は、MicroBlaze プロセッサ、32KByte ROM、128KByte RAM で構成されています。ZynqMP の電源投入直後は、まず PMU が起動して ROM 内部のステージ0ブートローダーを実行します。ステージ0ブートローダは、ストレージ(SD-Card等)にある BOOT.BIN に含まれるステージ１ブートローダー(FSBL)を内部RAMにロードして APU (Application Processing Unit) に制御を移します。




## PMUFW(Platform Management Unit Firmware)


ステージ１ブートローダー(FSBL)は BOOT.BIN に含まれる PMUFW(Platform Management Unit Firmware) を PMU の RAM にロードします。PMU はその後、 PMUFW に従ってZynqMP 内のリソースのパワーアップ、リセット、モニター(監視)を行います。また、APU や RPU からの各種要求をシステムコール(PM API call) として受け付けます。

Linux でパワーオフをする際、最終的にこの PMUFW のシャットダウン PM API call を呼び出します。

PMUFW のソースコードは  [PMUFW の構築]  を参考にして得てください。


### PmProcessRequest()


PMU は PM API call を受け付けると、PMUFW の PmProcessRequest() を呼び出します。PmProcessRequest()  は pm_core.c に定義されています。 

PM API call がシャットダウン要求だったとき、PMU は PmSystemShutdown() を呼び出しています。


```C:pm_core.c
/**
 * PmProcessApiCall() - Called to process PM API call
 * @master  Pointer to a requesting master structure
 * @pload   Pointer to array of integers with the information about the pm call
 *          (api id + arguments of the api)
 *
 * @note    Called to process PM API call. If specific PM API receives less
 *          than 4 arguments, extra arguments are ignored.
 */
void PmProcessRequest(PmMaster *const master, const u32 *pload)
{
	(中略)
	case PM_SYSTEM_SHUTDOWN:
		PmSystemShutdown(master, pload[1], pload[2]);
		break;
	(中略)
}
```



### PmSystemShutdown()


PmSystemShutdown() は pm_core.c にあります。


```C:pm_core.c
/**
 * PmSystemShutdown() - Request system shutdown or restart
 * @master  Master requesting system shutdown
 * @type    Shutdown type
 * @subtype Shutdown subtype
 */
static void PmSystemShutdown(PmMaster* const master, const u32 type,
			     const u32 subtype)
{
	s32 status = XST_SUCCESS;
	PmInfo("%s> SystemShutdown(%lu, %lu)\\r\\n", master->name, type, subtype);
	/* For shutdown type the subtype is irrelevant: shut the caller down */
	if (PMF_SHUTDOWN_TYPE_SHUTDOWN == type) {
		status = PmMasterFsm(master, PM_MASTER_EVENT_FORCE_DOWN);
#if defined(BOARD_SHUTDOWN_PIN) && defined(BOARD_SHUTDOWN_PIN_STATE)
		if (PMF_SHUTDOWN_SUBTYPE_SYSTEM == subtype) {
			PmKillBoardPower();
		}
#endif
		goto done;
	}
	if (PMF_SHUTDOWN_TYPE_RESET != type) {
		status = XST_INVALID_PARAM;
		goto done;
	}
	/* Now distinguish the restart scope depending on the subtype */
	switch (subtype) {
	case PMF_SHUTDOWN_SUBTYPE_SUBSYSTEM:
		status = PmMasterRestart(master);
		break;
	case PMF_SHUTDOWN_SUBTYPE_PS_ONLY:
		XPfw_ResetPsOnly();
		break;
	case PMF_SHUTDOWN_SUBTYPE_SYSTEM:
		XPfw_ResetSystem();
		break;
	default:
		PmLog(PM_ERRNO_INVALID_SUBTYPE, subtype, master->name);
		status = XST_INVALID_PARAM;
		break;
	}
done:
	IPI_RESPONSE1(master->ipiMask, status);
}

```


ここで注目するのは、BOARD_SHUTDOWN_PIN と BOARD_SHUTDOWN_PIN_STATE が define されている時に実行される部分です。この部分では、 PM API call の type が PMF_SHUTDOWN_TYPE_SHUTDOWN かつ subtype が PMF_SHUTDOWN_SUBTYPE_SYSTEM だった場合に PmKillBoardPower() が呼び出されます。


### PmKillBoardPower()


PmKillBoardPower() は pm_core.c にあります。


```C:pm_core.c
/**
 * PmKillBoardPower() - Power-off board by sending KILL signal to power chip
 */
#if defined(BOARD_SHUTDOWN_PIN) && defined(BOARD_SHUTDOWN_PIN_STATE)
static void PmKillBoardPower(void)
{
	u32 reg = XPfw_Read32(PMU_LOCAL_GPO1_READ);
	u32 mask = PMU_IOMODULE_GPO1_MIO_0_MASK << BOARD_SHUTDOWN_PIN;
	u32 value = BOARD_SHUTDOWN_PIN_STATE << BOARD_SHUTDOWN_PIN;
	u32 mioPinOffset;
	mioPinOffset = IOU_SLCR_MIO_PIN_34_OFFSET + (BOARD_SHUTDOWN_PIN - 2U)*4U;
	reg = (reg & (~mask)) | (mask & value);
	XPfw_Write32(PMU_IOMODULE_GPO1, reg);
	/* Configure board shutdown pin to be controlled by the PMU */
	XPfw_RMW32((IOU_SLCR_BASE + mioPinOffset),
			0x000000FEU, 0x00000008U);
}
#endif

```




この PmKillBoardPower() こそが、シャットダウン PM API call 時に、指定された MIO(Ultra96-V2 では MIO34 ポート)を L にしてボードのパワーをオフにする部分です。具体的には GPO レジスタのポートに対応するビット(Ultra96-V2 では GPO2) に指定された値(Ultra96-V2 では 0) をセットした後、指定されたMIO(Ultra96-V2 では MIO34 ポート) を出力モードにしています。


### XPfw_PmWakeHandler()


ZynqMP の MIO26 ポートからの入力信号は PMU の GPI0 に接続されています。この信号が入力されたとき、PMU に割り込みが入ります。その割り込みハンドラ XPfw_PmWakeHandler() は pm_binding.c にあります。


```C:pm_binding.c
/**
 * XPfw_PmWakeHandler() - Call from GPI1 interrupt to process wake request
 * @srcMask     Value read from GPI1 register which determines interrupt source
 *
 * @return      Status of performing wake-up (XST_INVALID_PARAM if wake is a
 *              processor wake event but processor is not found, status of
 *              performing wake otherwise)
 *
 * @note    Call from GPI1 interrupt routine to process wake request. Must not
 *          clear GPI1 interrupt before this function returns.
 *          If the wake source is one of GIC wakes, source of the interrupt
 *          (peripheral that actually generated interrupt to GIC) cannot be
 *          determined, and target should be immediately woken-up (target is
 *          processor whose GIC wake bit is set in srcMask). If the wake is the
 *          FPD GIC Proxy interrupt, the APU needs to be woken up.
 */
s32 XPfw_PmWakeHandler(const u32 srcMask)
{
	s32 status = XST_INVALID_PARAM;
#if defined(PMU_MIO_INPUT_PIN) && (PMU_MIO_INPUT_PIN >= 0U) \\
				&& (PMU_MIO_INPUT_PIN <= 5U)
	if ((PMU_IOMODULE_GPI1_MIO_WAKE_0_MASK << PMU_MIO_INPUT_PIN) == srcMask) {
		PmShutdownInterruptHandler();
		return XST_SUCCESS;
	}
#endif
	if (0U != (PMU_IOMODULE_GPI1_GIC_WAKES_ALL_MASK & srcMask))  {
		/* Processor GIC wake */
		PmProc* proc = PmProcGetByWakeMask(srcMask);
		if ((NULL != proc) && (NULL != proc->master)) {
			status = PmMasterWakeProc(proc);
		} else {
			status = XST_INVALID_PARAM;
		}
	} else if (0U != (PMU_IOMODULE_GPI1_FPD_WAKE_GIC_PROXY_MASK & srcMask)) {
		status = PmMasterWake(&pmMasterApu_g);
	} else if (0U != (PMU_IOMODULE_GPI1_MIO_WAKE_ALL_MASK & srcMask)) {
		status = PmExternWakeMasters();
	} else if (0U != (PMU_IOMODULE_GPI1_USB_0_WAKE_MASK & srcMask)) {
		status = PmWakeMasterBySlave(&pmSlaveUsb0_g.slv);
	} else if (0U != (PMU_IOMODULE_GPI1_USB_1_WAKE_MASK & srcMask)) {
		status = PmWakeMasterBySlave(&pmSlaveUsb1_g.slv);
	} else {
	}
	return status;
}

```


ここで注目する点は、PMU_MIO_INPUT_PIN が定義されていてその値が0から5の範囲だったら、呼び出される PmShutdownInterruptHandler() です。


### PmShutdownInterruptHandler()


PmShutdownInterruptHandler() は pm_core.c にあります。 


```C:pm_core.c
/**
 * PmShutdownInterruptHandler() - Send suspend request to all active masters
 */
void PmShutdownInterruptHandler(void)
{
#if defined(PMU_MIO_INPUT_PIN) && (PMU_MIO_INPUT_PIN >= 0U) \\
				&& (PMU_MIO_INPUT_PIN <= 5U)
	/*
	 * Default status of MIO26 pin is 1. So MIO wake event bit in GPI1
	 * register is always 1, which is used to identify shutdown event.
	 *
	 * GPI event occurs only when any bit of GPI register changes from
	 * 0 to 1. When any GPI1 event occurs Gpi1InterruptHandler() checks
	 * GPI1 register and process interrupts for the bits which are 1.
	 * Because of MIO wake bit is 1 in GPI1 register, shutdown handler
	 * will be called every time when any of GPI1 event occurs.
	 *
	 * There is no way to identify which bit cause GPI1 interrupt.
	 * So every time Gpi1InterruptHandler() is checking bit which are 1
	 * And calls respective handlers.
	 *
	 * To handle such case avoid power off when any other (other than MIO
	 * wake)bit in GPI1 register is 1. If no other bit is 1 in GPI1 register
	 * and still PMU gets GPI1 interrupt means that MIO26 pin state is
	 * changed from (1 to 0 and 0 to 1). In this case it is confirmed that
	 * it is event for shutdown only and not because of other events.
	 * There are chances that some shutdown events are missed (1 out of 50)
	 * but it should not harm.
	 */
	if (XPfw_Read32(PMU_IOMODULE_GPI1) !=
	    (PMU_IOMODULE_GPI1_MIO_WAKE_0_MASK << PMU_MIO_INPUT_PIN)) {
		return;
	}
#endif

	u32 rpu_mode = XPfw_Read32(RPU_RPU_GLBL_CNTL);
	if (PM_MASTER_STATE_ACTIVE == PmMasterIsActive(&pmMasterApu_g)) {
		PmInitSuspendCb(&pmMasterApu_g,
				SUSPEND_REASON_SYS_SHUTDOWN, 1U, 0U, 0U);
	}
	if (0U == (rpu_mode & RPU_RPU_GLBL_CNTL_SLSPLIT_MASK)) {
		if (PM_MASTER_STATE_ACTIVE == PmMasterIsActive(&pmMasterRpu0_g)) {
			PmInitSuspendCb(&pmMasterRpu0_g,
					SUSPEND_REASON_SYS_SHUTDOWN, 1U, 0U, 0U);
		}
		if (PM_MASTER_STATE_ACTIVE == PmMasterIsActive(&pmMasterRpu1_g)) {
			PmInitSuspendCb(&pmMasterRpu1_g,
					SUSPEND_REASON_SYS_SHUTDOWN, 1U, 0U, 0U);
		}
	} else {
		if (PM_MASTER_STATE_ACTIVE == PmMasterIsActive(&pmMasterRpu_g)) {
			PmInitSuspendCb(&pmMasterRpu_g,
					SUSPEND_REASON_SYS_SHUTDOWN, 1U, 0U, 0U);
		}
	}
}

```


PmShutdownInterruptHandler() では、 各プロセッサ(APU、RPU0、RPU1、RPU) の状態をチェックして、各々のプロセッサがアクティブ状態ならばサスペンド状態になるように要求しています。Linux が動いている APU は、このサスペンド要求を受け付けてシャットダウンシーケンスを実行します。


### xpfw_config.h


PMU_MIO_INPUT_PIN、BOARD_SHUTDOWN_PIN および BOARD_SHUTDOWN_PIN_STATE は xpfw_config.h で define されています。


```C:xpfw_config.h
	(前略)
/*
 * PMU Firmware code include options
 *
 * PMU Firmware by default disables some functionality and enables some
 * Here we are listing all the build flags with the default option.
 * User can modify these flags to enable or disable any module/functionality
 * 	- ENABLE_PM : Enables Power Management Module
 * 	- ENABLE_EM : Enables Error Management Module
 * 	- ENABLE_SCHEDULER : Enables the scheduler
 * 	- ENABLE_RECOVERY : Enables WDT based restart of APU sub-system
 *	- ENABLE_RECOVERY_RESET_SYSTEM : Enables WDT based restart of system
 *	- ENABLE_RECOVERY_RESET_PS_ONLY : Enables WDT based restart of PS
 * 	- ENABLE_ESCALATION : Enables escalation of sub-system restart to
 * 	                      SRST/PS-only if the first restart attempt fails
 * 	- ENABLE_WDT : Enables WDT based restart functionality for PMU
 * 	- ENABLE_STL : Enables STL Module
 * 	- ENABLE_RTC_TEST : Enables RTC Event Handler Test Module
 * 	- ENABLE_IPI_CRC_VAL : Enables CRC calculation for IPI messages
 * 	- ENABLE_FPGA_LOAD : Enables FPGA bit stream loading feature
 * 	- ENABLE_SECURE : Enables security features
 * 	- XPU_INTR_DEBUG_PRINT_ENABLE : Enables debug for XMPU/XPPU functionality
 *
 * 	- PM_LOG_LEVEL : Enables print based debug functions for PM. Possible
 *			values are: 1 (alerts), 2 (errors), 3 (warnings),
 *			4 (info). Higher numbers include the debug scope of
 *			lower number, i.e. enabling 3 (warnings) also enables
 *			1 (alerts) and 2 (errors).
 * 	- IDLE_PERIPHERALS : Enables idling peripherals before PS or System reset
 * 	- ENABLE_NODE_IDLING : Enables idling and reset of nodes before force
 * 	                       of a sub-system
 * 	- DEBUG_MODE : This macro enables PM debug prints if XPFW_DEBUG_DETAILED
 * 	               macro is also defined
 *	- ENABLE_POS : Enables Power Off Suspend feature
 *	- ENABLE_DDR_SR_WR : Enables DDR self refresh over warm restart feature
 *	- ENABLE_UNUSED_RPU_PWR_DWN : Enables unused RPU power down feature
 *	- DISABLE_CLK_PERMS : Disable clock permission checking (it is not safe
 *			to ever disable clock permission checking). Do this at
 *			your own responsibility.
 *	- ENABLE_EFUSE_ACCESS : Enables efuse access feature
 *
 * 	These macros are specific to ZCU100 design where it uses GPO1[2] as a
 * 	board power line and
 * 	- PMU_MIO_INPUT_PIN : Enables board shutdown related code for ZCU100
 * 	- BOARD_SHUTDOWN_PIN : Tells board shutdown pin. In case of ZCU100,
 * 	                       GPO1[2] is the board power line.
 * 	- BOARD_SHUTDOWN_PIN_STATE : Tells what should be the state of board power
 * 	                             line when system shutdown request comes
 */

#define	ENABLE_PM_VAL					(1U)
#define	ENABLE_EM_VAL					(0U)
#define	ENABLE_SCHEDULER_VAL			(0U)
#define	ENABLE_RECOVERY_VAL				(0U)
#define	ENABLE_RECOVERY_RESET_SYSTEM_VAL		(0U)
#define	ENABLE_RECOVERY_RESET_PS_ONLY_VAL		(0U)
#define	ENABLE_ESCALATION_VAL			(0U)
#define CHECK_HEALTHY_BOOT_VAL			(0U)
#define	ENABLE_WDT_VAL					(0U)
#define ENABLE_CUSTOM_MOD_VAL			(0U)
#define	ENABLE_STL_VAL					(0U)
#define	ENABLE_RTC_TEST_VAL				(0U)
#define	ENABLE_IPI_CRC_VAL				(0U)
#define	ENABLE_FPGA_LOAD_VAL			(1U)
#define	ENABLE_SECURE_VAL				(1U)
#define ENABLE_EFUSE_ACCESS				(0U)
#define	XPU_INTR_DEBUG_PRINT_ENABLE_VAL	(0U)
#define	PM_LOG_LEVEL_VAL				(0U)
#define	IDLE_PERIPHERALS_VAL			(0U)
#define	ENABLE_NODE_IDLING_VAL			(0U)
#define	DEBUG_MODE_VAL					(0U)
#define	ENABLE_POS_VAL					(0U)
#define	ENABLE_DDR_SR_WR_VAL				(0U)
#define DISABLE_CLK_PERMS_VAL				(0U)
#define ENABLE_UNUSED_RPU_PWR_DWN_VAL			(1U)
#define	PMU_MIO_INPUT_PIN_VAL			(0U)
#define BOARD_SHUTDOWN_PIN_VAL       (1U)
#define BOARD_SHUTDOWN_PIN_STATE_VAL (1U)
#define	PMU_MIO_INPUT_PIN_VAL			(0U)
#define	BOARD_SHUTDOWN_PIN_VAL			(0U)
#define	BOARD_SHUTDOWN_PIN_STATE_VAL	(0U)
	:
	(中略)
	:
#if PMU_MIO_INPUT_PIN_VAL
#define PMU_MIO_INPUT_PIN			0U
#endif
#if BOARD_SHUTDOWN_PIN_VAL
#define BOARD_SHUTDOWN_PIN			2U
#endif
#if BOARD_SHUTDOWN_PIN_STATE_VAL
#define BOARD_SHUTDOWN_PIN_STATE	0U
#endif
	:
	(後略)
```


BOARD_SHUTDOWN_PIN は GPO のポート番号です。Ultra96-V2 では MIO34 ポートに接続されている GPO2 を示す 2 を設定しています。

BOARD_SHUTDOWN_PIN_STATE は　GPOポートに出力する値です。Ultra96-V2 では 0 (Low) を出力します。

PMU_MIO_INPUT_PIN は GPI のポート番号です。Ultra96-V2 では MIO26 ポートに接続されている GPI0 を示す 0 を設定しています。


# BOOT.BINの修正



## PMUFW の修正


PMUFW の構築には Sample FPGA Design のハードウェア情報が必要です。[Simple FPGA Design の構築] を参考にして target/Ultra96-V2/build-v2019.1/fpga/project.sdk/design_1\_wrapper.hdf を作っておきます。

PMUFW を構築する際に、xpfw_config.h を修正します。具体的には、 PMU_MIO_INPUT_PIN_VAL 、 BOARD_SHUTDOWN_PIN_VAL および  BOARD_SHUTDOWN_PIN_STATE_VAL に 1U を設定します。

ビルドする際は次のようなスクリプトを使うと良いでしょう。


```Tcl:target/Ultra96-V2/build-v2019.1/fpga/build_zynqmp_pmufw.hsi
#!/usr/bin/tclsh
set app_name          "pmufw"
set app_type          "zynqmp_pmufw"
set hwspec_file       "design_1_wrapper.hdf"
set proc_name         "psu_pmu_0"
set project_name      "project"
set project_dir       [pwd]
set sdk_workspace     [file join $project_dir $project_name.sdk]
set app_dir           [file join $sdk_workspace $app_name]
set app_release_dir   [file join [pwd] ".." ]
set app_release_elf   "zynqmp_pmufw.elf"
set board_shutdown    true
set board_power_sw    true
set hw_design         [hsi::open_hw_design [file join $sdk_workspace $hwspec_file]]
hsi::generate_app -hw $hw_design -os standalone -proc $proc_name -app $app_type -dir $app_dir
if {$board_shutdown || $board_power_sw} {
    file copy -force [file join $app_dir "xpfw_config.h"] [file join $app_dir "xpfw_config.h.org"] 
    set xpfw_config_old [open [file join $app_dir "xpfw_config.h.org"]  r]
    set xpfw_config_new [open [file join $app_dir "xpfw_config.h.new"]  w]
    while {[gets $xpfw_config_old line] >= 0} {
        if       {$board_shutdown && [regexp {^#define\\s+BOARD_SHUTDOWN_PIN_VAL\\s+\\S+}       $line]} {
            puts $xpfw_config_new "#define BOARD_SHUTDOWN_PIN_VAL       (1U)"
        } elseif {$board_shutdown && [regexp {^#define\\s+BOARD_SHUTDOWN_PIN_STATE_VAL\\s+\\S+} $line]} {
            puts $xpfw_config_new "#define BOARD_SHUTDOWN_PIN_STATE_VAL (1U)"
        } elseif {$board_power_sw && [regexp {^#define\\s+PMU_MIO_INPUT_PIN_VAL\\s+\\S+}        $line]} {
            puts $xpfw_config_new "#define PMU_MIO_INPUT_PIN_VAL        (1U)"
        } else {
            puts $xpfw_config_new $line
        }
    }
    close $xpfw_config_old
    close $xpfw_config_new
    file rename -force [file join $app_dir "xpfw_config.h.new"] [file join $app_dir "xpfw_config.h"] 
}
exec make -C $app_dir all >&@ stdout
file copy -force [file join $app_dir "executable.elf"] [file join $app_release_dir $app_release_elf]

```


build_zynqmp_pmufw.hsi を Vivado で実行することにより、target/Ultra96-V2/build-v2019.1/zynqmp_pmufw.elf が生成されます。


```console
vivado% cd target/Ultra96-V2/build-v2019.1/fpga/
vivado% hsi -mode tcl -source build_zynqmp_pmufw.hsi
```




注) Vivado 2019.2 からは Vivado SDK が廃止されて Vitis に統合された関係上、上記の方法ではうまくいきません。主な違いは、hsi コマンドが廃止されて代わりに xsct コマンドを使うこと、ハードウェア情報ファイル (hwspec_file) の拡張子が .hdf ではなく .xsa になったことです。Vitis で FSBL をビルドする方法に関しては[「Vivado(Vitis) で Zynq FSBL(First Stage Boot Loader) をビルドするTclスクリプト」][VitisでFSBLをビルドするTclスクリプト]を参考にしてください。


```Tcl:target/Ultra96-V2/build-v2019.2/fpga/build_zynqmp_pmufw.tcl
	:
(target/Ultra96-V2/build-v2019.1/fpga/build_zynqmp_pmufw.hci と同じ)
	:
set hwspec_file       "design_1_wrapper.xsa"
	:
(target/Ultra96-V2/build-v2019.1/fpga/build_zynqmp_pmufw.hci と同じ)
	:
```



```console
vivado% cd target/Ultra96-V2/build-v2019.2/fpga/
vivado% xsct build_zynqmp_pmufw.tcl
```





## BOOT.BIN の構築


前節で構築した zynqmp_pmufw.elf を BOOT.BIN に組み込みます。具体的には [BOOT.BIN の構築] を参照してください。


# 結果


前節でビルドした BOOT.BIN を使って Linux をブートします。poweroff コマンドでシャットダウンすると Ultra96-V2 がパワーオフ状態になります。Ultra96-V2 がパワーオフ状態になると パワーオンLED (青いLED) が消灯し、空冷ファンが止まります。

また、Linux の起動中にパワーボタン(SW4) を押すと、Linux がシャットダウンシーケンスに移行し、その後Ultra96-V2 がパワーオフ状態になります。


```console
root@debian-fpga:~#  poweroff
[  OK  ] Stopped target Sound Card.
[  OK  ] Stopped tar■  OK  ] Stopped Daily man-db regeneration.
[  OK  ] Stopped target Multi-User System.
	:
	(中略)
	:
[  OK  ] Reached target Shutdown.
[  OK  ] Reached target Final Step.
[  OK  ] Started Power-Off.
[  OK  ] Reached target Power-Off.
[  226.399206] systemd-shutdow: 29 output lines suppressed due to ratelimiting
[  226.486399] systemd-shutdown[1]: Syncing filesystems and block devices.
[  226.581506] systemd-shutdown[1]: Sending SIGTERM to remaining processes...
[  226.596568] systemd-journald[1777]: Received SIGTERM from PID 1 (systemd-shutdow).
[  226.617967] systemd-shutdown[1]: Sending SIGKILL to remaining processes...
[  226.631598] systemd-shutdown[1]: Unmounting file systems.
[  226.639081] [3538]: Remounting '/' read-only in with options '(null)'.
[  226.678576] EXT4-fs (mmcblk0p2): re-mounted. Opts: (null)
[  226.698640] systemd-shutdown[1]: All filesystems unmounted.
[  226.704248] systemd-shutdown[1]: Deactivating swaps.
[  226.709451] systemd-shutdown[1]: All swaps deactivated.
[  226.714691] systemd-shutdown[1]: Detaching loop devices.
[  226.722646] systemd-shutdown[1]: All loop devices detached.
[  226.728233] systemd-shutdown[1]: Detaching DM devices.
[  226.745079] usb 1-1: USB disconnect, device number 2
[  226.750065] usb 1-1.4: USB disconnect, device number 3
[  226.869502] usb 2-1: USB disconnect, device number 2
[  226.911441] reboot: Power down

```



# 余談



## psu_init.c の不思議


[Simple FPGA Design の構築] で作ったハードウェア情報には、ZynqMP のポートの設定を行うための psu_init.c が含まれます。psu_init.c は ステージ1ブートローダー(FSBL) が ZynqMP のポートを設定するためのプログラムが含まれています。Ultra96-V2 用に作った psu_init.c には不思議な箇所があります。


```C:psu_init.c
    /*
    * Register : MIO_PIN_33 @ 0XFF180084
    * Level 0 Mux Select 0= Level 1 Mux Output 1= gem0, Input, gem0_rgmii_rxd[
    * 0]- (RX RGMII data)
    *  PSU_IOU_SLCR_MIO_PIN_33_L0_SEL                              0
    * Level 1 Mux Select 0= Level 2 Mux Output 1= pcie, Input, pcie_reset_n- (
    * PCIE Reset signal)
    *  PSU_IOU_SLCR_MIO_PIN_33_L1_SEL                              0
    * Level 2 Mux Select 0= Level 3 Mux Output 1= pmu, Output, pmu_gpo[1]- (PM
    * U GPI) 2= test_scan, Input, test_scan_in[33]- (Test Scan Port) = test_sc
    * an, Output, test_scan_out[33]- (Test Scan Port) 3= csu, Input, csu_ext_t
    * amper- (CSU Ext Tamper)
    *  PSU_IOU_SLCR_MIO_PIN_33_L2_SEL                              1
    * Level 3 Mux Select 0= gpio1, Input, gpio_1_pin_in[7]- (GPIO bank 1) 0= g
    * pio1, Output, gpio_1_pin_out[7]- (GPIO bank 1) 1= can1, Input, can1_phy_
    * rx- (Can RX signal) 2= i2c1, Input, i2c1_sda_input- (SDA signal) 2= i2c1
    * , Output, i2c1_sda_out- (SDA signal) 3= swdt1, Output, swdt1_rst_out- (W
    * atch Dog Timer Output clock) 4= spi1, Output, spi1_n_ss_out[2]- (SPI Mas
    * ter Selects) 5= ttc3, Output, ttc3_wave_out- (TTC Waveform Clock) 6= ua1
    * , Input, ua1_rxd- (UART receiver serial input) 7= trace, Output, tracedq
    * [11]- (Trace Port Databus)
    *  PSU_IOU_SLCR_MIO_PIN_33_L3_SEL                              0
    * Configures MIO Pin 33 peripheral interface mapping
    * (OFFSET, MASK, VALUE)      (0XFF180084, 0x000000FEU ,0x00000008U)
    */
	PSU_Mask_Write(IOU_SLCR_MIO_PIN_33_OFFSET, 0x000000FEU, 0x00000008U);
/*##################################################################### */

    /*
    * Register : MIO_PIN_35 @ 0XFF18008C
    * Level 0 Mux Select 0= Level 1 Mux Output 1= gem0, Input, gem0_rgmii_rxd[
    * 2]- (RX RGMII data)
    *  PSU_IOU_SLCR_MIO_PIN_35_L0_SEL                              0
    * Level 1 Mux Select 0= Level 2 Mux Output 1= pcie, Input, pcie_reset_n- (
    * PCIE Reset signal)
    *  PSU_IOU_SLCR_MIO_PIN_35_L1_SEL                              0
    * Level 2 Mux Select 0= Level 3 Mux Output 1= pmu, Output, pmu_gpo[3]- (PM
    * U GPI) 2= test_scan, Input, test_scan_in[35]- (Test Scan Port) = test_sc
    * an, Output, test_scan_out[35]- (Test Scan Port) 3= dpaux, Input, dp_hot_
    * plug_detect- (Dp Aux Hot Plug)
    *  PSU_IOU_SLCR_MIO_PIN_35_L2_SEL                              0
    * Level 3 Mux Select 0= gpio1, Input, gpio_1_pin_in[9]- (GPIO bank 1) 0= g
    * pio1, Output, gpio_1_pin_out[9]- (GPIO bank 1) 1= can0, Output, can0_phy
    * _tx- (Can TX signal) 2= i2c0, Input, i2c0_sda_input- (SDA signal) 2= i2c
    * 0, Output, i2c0_sda_out- (SDA signal) 3= swdt0, Output, swdt0_rst_out- (
    * Watch Dog Timer Output clock) 4= spi1, Input, spi1_n_ss_in- (SPI Master
    * Selects) 4= spi1, Output, spi1_n_ss_out[0]- (SPI Master Selects) 5= ttc2
    * , Output, ttc2_wave_out- (TTC Waveform Clock) 6= ua0, Output, ua0_txd- (
    * UART transmitter serial output) 7= trace, Output, tracedq[13]- (Trace Po
    * rt Databus)
    *  PSU_IOU_SLCR_MIO_PIN_35_L3_SEL                              0
    * Configures MIO Pin 35 peripheral interface mapping
    * (OFFSET, MASK, VALUE)      (0XFF18008C, 0x000000FEU ,0x00000000U)
    */
	PSU_Mask_Write(IOU_SLCR_MIO_PIN_35_OFFSET, 0x000000FEU, 0x00000000U);
/*##################################################################### */

```


見ての通り、MIO34 ポートの設定がすっぽり抜けています。つまりステージ１ブートローダー(FSBL) では MIO34 ポートの設定は行われません。MIO34 ポートの設定が行われるのは PMUFW(Platform Management Unit Firmware) の  PmKillBoardPower() で、MIO34 ポートに L を出力する時です。その時まで、MIO34 ポートはオープン状態にあり、パワーオン/オフするLSI の KILL_N ポートはプルアップされているので H になっています。

これはブートシーケンスを考えてみれば当たり前の話で、ステージ１ブートローダー実行中は、PMUFW はまだ動いていないので、PMU の GPO2 はデフォルトの0になっています。この状態で MIO34 ポートの設定を行うと、MIO34 ポートには PMU の GPO2 に設定された0(=Low) が出力されてしまいます。

つまりステージ１ブートローダー実行中にパワーオフになってしまいます。これを防ぐために psu_init.c では MIO34 ポートの設定は行わないようになっているのでしょう。




## パワーオフはMIO34ポートのみ？


前節で説明した psu_init.c の内容やPMUFW のソースコード(特に xpfw_config.h)をみた範囲では、どうも ZynqMP で パワーオフする際の ポートは MIO34 ポートに固定されているようです。

もし ZynqMP を使ったボードを設計する時、パワーオフを ZynqMP から行いたい場合は MIO34 ポートを使うことを念頭に置いておいた方が良いかもしれません。




## Device Tree の LTC2954 ノード


Ultra96/Ultra96-V2 で Linux を起動する際に使用する Device Tree には次のようなノードがあります。


```devicetree:arch/arm64/boot/dts/xilinx/avnet-ultra96v2-rev1.dts
// SPDX-License-Identifier: GPL-2.0+
/*
 * dts file for Avnet Ultra96-V2 rev1
 *
 */
/dts-v1/;
	:
	(中略)
	:
	ltc2954: ltc2954 { /* U7 */
		compatible = "lltc,ltc2954", "lltc,ltc2952";
		status = "disabled";
		trigger-gpios = <&gpio 26 GPIO_ACTIVE_LOW>; /* INT line - input */
		/* If there is HW watchdog on mezzanine this signal should be connected there */
		watchdog-gpios = <&gpio 35 GPIO_ACTIVE_HIGH>; /* MIO on PAD */
		kill-gpios = <&gpio 34 GPIO_ACTIVE_LOW>; /* KILL signal - output */
	};
	:
	(後略)
```


このノードはあたかもパワーオン/オフするLSI を制御するデバイスドライバを定義しているように見えます。しかし、「ZynqMP の MIO26/MIO34 ポートの設定」で説明したとおり、Ultra96-V2 では MIO26/MIO34 ポートは PMU が制御するように設定されており、Linux が動作している APU からは制御できません。したがって、このノードは無意味なので、無効にしておく必要があります。具体的には、status プロパティに "disabled" を指定しておきます。


# 参考


* [「Ultra96 向け Debian GNU/Linux (v2018.2版) の構築(Boot Loader編)」 @Qiita][Boot Loader]
* [「Ultra96 向け Debian GNU/Linux (v2018.2版) の構築(Sample FPGA Design編)」@Qiita][Simple FPGA Design の構築]
* [「Ultra96 向け Debian GNU/Linux (v2018.2版) の構築(PMUFW編)」@Qiita][PMUFW の構築]
* [「Ultra96 向け Debian GNU/Linux (v2018.2版) の構築(BOOT.BIN編)」@Qiita][BOOT.BIN の構築]
* [「Vivado(Vitis) で Zynq FSBL(First Stage Boot Loader) をビルドするTclスクリプト」@Qiita][VitisでFSBLをビルドするTclスクリプト]
* [「ZynqMPのブートとパワーマネージメント」 @Vengineer ZynqMP勉強会資料(2016/2/20)][ZynqMPのブートとパワーマネージメント]
* [「Zynq UltraScale+ MPSoC Technical Reference Manual, UG1085(v1.7) December 22,2017」][UG1085]
* [「Zynq UltraScale+ MPSoC Software Developer Guide, UG1137(v5.0) November 15,2017)」][UG1137]


[Boot Loader]: https://qiita.com/ikwzm/items/9c78e83298906447fe5d "「Ultra96 向け Debian GNU/Linux (v2018.2版) の構築(Boot Loader編)」@Qiita"
[Simple FPGA Design の構築]: https://qiita.com/ikwzm/items/0489b02962c6f0455783 "「Ultra96 向け Debian GNU/Linux (v2018.2版) の構築(Sample FPGA Design編)」@Qiita"
[PMUFW の構築]: https://qiita.com/ikwzm/items/00024adf956696dd51c6 "「Ultra96 向け Debian GNU/Linux (v2018.2版) の構築(PMUFW編)」@Qiita"
[BOOT.BIN の構築]: https://qiita.com/ikwzm/items/af4420edd262e14feef9 "「Ultra96 向け Debian GNU/Linux (v2018.2版) の構築(BOOT.BIN編)」@Qiita"
[VitisでFSBLをビルドするTclスクリプト]: https://qiita.com/ikwzm/items/165481921c3e984a9e70 "「Vivado(Vitis) で Zynq FSBL(First Stage Boot Loader) をビルドするTclスクリプト」@Qiita"
[ZynqMPのブートとパワーマネージメント]: https://www.slideshare.net/ssuser479fa3/zynq-mp-58490589 "「ZynqMPのブートとパワーマネージメント」 @Vengineer ZynqMP勉強会資料(2016/2/20)"
[UG1085]: https://www.xilinx.com/support/documentation/user_guides/ug1085-zynq-ultrascale-trm.pdf "「Zynq UltraScale+ MPSoC Technical Reference Manual, UG1085(v1.7) December 22,2017」"
[UG1137]: https://www.xilinx.com/support/documentation/user_guides/ug1137-zynq-ultrascale-mpsoc-swdev.pdf "「Zynq UltraScale+ MPSoC Software Developer Guide, UG1137(v5.0) November 15,2017)」"