diff --git a/airoha/image/Makefile b/airoha/image/Makefile
index abc98aa63e31b5bba54215df27d9f2128cf9f133..dceddf2ec753c938275e5907250a911cd91acea5 100755
--- a/airoha/image/Makefile
+++ b/airoha/image/Makefile
@@ -11,7 +11,7 @@ DTS_DIR := ../dts
 DEVICE_VARS += DEVICE_UBI_LAYOUT UBOOT_TARGET GPT_LAYOUT_STR GPT_PRIMARY_ENTRY_OFFSET GPT_ALTERNATE_OFFSET
 
 define airoha-emmc-production
-  append-uboot-nand | pad-to 2M | append_emmc_production_env_and_sw $(1)
+  append_emmc_production_image $(1)
 endef
 
 define airoha-nand-production
@@ -26,7 +26,6 @@ endef
 
 define Device/Default-airoha-emmc
   $(Device/emmc)
-  BLOCKSIZE := 512
   ARTIFACT/u-boot-emmc.fip := append-uboot-emmc
   ARTIFACTS := u-boot-emmc.fip u-boot-nand.bin u-boot-ram.itb bootext.ram
   IMAGE/production.img := $$(call airoha-emmc-production)
diff --git a/airoha/image/iopsys-image-common.mk b/airoha/image/iopsys-image-common.mk
index 155fb5f21f88d60afa62d9f2dbf5a64fcfb738d7..fb779e84035025d5294e5a45083188a956f87000 100644
--- a/airoha/image/iopsys-image-common.mk
+++ b/airoha/image/iopsys-image-common.mk
@@ -209,31 +209,54 @@ define Build/append-ubi-image
 endef
 
 # %(1) boardid to set, default is $(firstword $(DEVICE_DTS))
-define Build/append_emmc_production_env_and_sw
-	echo "Build/emmc_production_env_and_sw[$@]: $(1)"
+define Build/append_emmc_production_image
+	echo "Build/append_emmc_production_image[$@]: $(1)"
+# create $@ if it does not exist already
+	touch $@
+# generate sysupgrade image to put inside production image,
+# round image size to 1K, because ptgen uses sizes in KB
 	mv $@ $@.bak
 	$(Build/iopsys-fit-upgrade-image)
-	$(call Build/pad-to,$(BLOCKSIZE))
+	$(call Build/pad-to,1K)
 	mv $@ $@.sysupgrade.itb
 	mv $@.bak $@
+# create GPT image with env1, env2, sysupgrade partitions
+#  * GPT image size will be calculated automatically (-d 0)
+#  * put Primary GPT entry table at 512K offset (-e 512K)
+#  * use a hack to not mark any partition as legacy boot (-a 100)
+#  * env1 partition will start at 2048K offset (-p 512K@2M)
+#  * env2 partition will start at 2560K offset (just after env1)
+#  * sysupgrade partition will start at 3M offset (just after env2).
+#    ptgen uses sizes in KB, so divide sysupgrade image size to 1024
+	ptgen -o $@.img -g -d 0 -e 512K -a 100 -N env1 -p 512K@2M -N env2 -p 512K -N sysupgrade -p "$$(( $$(stat --format=%s $@.sysupgrade.itb) / 1024 ))K"
+# write bootloader with 2K offset to GPT image (after PMBR and PGPT header, but before Primary GPT entry table)
+	dd bs=2K seek=1 conv=notrunc of=$@.img if=$(STAGING_DIR_IMAGE)/$(UBOOT_TARGET)-$(DEVICE_NAME)-u-boot-emmc.fip
+# generate 1-st environment (-s 0)
+# override once value with the value required for converting production image to normal system (-O "...")
 	./mk-env-img.sh \
 		-b $(if $(1),$(firstword $(1)),$(firstword $(DEVICE_DTS))) \
 		-t $(STAGING_DIR_IMAGE)/$(UBOOT_TARGET)-env_dump.bin \
 		-c $(STAGING_DIR_IMAGE)/$(UBOOT_TARGET)-env_dump.config \
 		-s 0 -o $@.env1 \
-		-O "read mmc 0.0 \$${loadaddr} 0x1800 $$(( $$(stat --format=%s $@.sysupgrade.itb) / $(BLOCKSIZE) )) && run update_itb_prepare do_update_bank2 do_update_bank1 && env set once true && env save"
+		-O "mmc dev 0 && part size mmc 0 sysupgrade __size && read mmc "0#sysupgrade" \$${loadaddr} 0 \$${__size} && env delete __size && source \$${loadaddr}:u-boot-script && run __script_emmc_write_production && env set once true && env save"
+# write 1-st environment with 2048K offset (env1 partition) to GPT image
+	dd bs=2048K seek=1 conv=notrunc of=$@.img if=$@.env1
+# generate 2-nd environment (-s 1)
+# override once value with the value required for converting production image to normal system (-O "...")
 	./mk-env-img.sh \
 		-b $(if $(1),$(firstword $(1)),$(firstword $(DEVICE_DTS))) \
 		-t $(STAGING_DIR_IMAGE)/$(UBOOT_TARGET)-env_dump.bin \
 		-c $(STAGING_DIR_IMAGE)/$(UBOOT_TARGET)-env_dump.config \
 		-s 1 -o $@.env2 \
-		-O "read mmc 0.0 \$${loadaddr} 0x1800 $$(( $$(stat --format=%s $@.sysupgrade.itb) / $(BLOCKSIZE) )) && run update_itb_prepare do_update_bank2 do_update_bank1 && env set once true && env save"
-	cat $@.env1 >> $@
-	$(call Build/pad-to,2560K)
-	cat $@.env2 >> $@
-	$(call Build/pad-to,3M)
-	cat $@.sysupgrade.itb >> $@
-
+		-O "mmc dev 0 && part size mmc 0 sysupgrade __size && read mmc "0#sysupgrade" \$${loadaddr} 0 \$${__size} && env delete __size && source \$${loadaddr}:u-boot-script && run __script_emmc_write_production && env set once true && env save"
+# write 2-nd environment with 2560K offset (env2 partition) to GPT image
+	dd bs=2560K seek=1 conv=notrunc of=$@.img if=$@.env2
+# write sysupgrade image with 3M offset (sysupgrade partition) to GPT image
+	dd bs=3M seek=1 conv=notrunc of=$@.img if=$@.sysupgrade.itb
+# add GPT image to the current target file
+	cat $@.img >> $@
+# remove temporary files
+	rm $@.env1 $@.env2 $@.sysupgrade.itb $@.img
 endef
 
 define Build/append-ff-pad-to