* [rulkc] [PATCH v1 00/11] Amlogic: ADNL and Optimus protocols support
@ 2025-07-29 18:28 avkrasnov
2025-07-29 18:29 ` [rulkc] [PATCH v1 01/11] arch: arm: meson: sm: add commands to reboot device in different modes avkrasnov
` (11 more replies)
0 siblings, 12 replies; 13+ messages in thread
From: avkrasnov @ 2025-07-29 18:28 UTC (permalink / raw)
This patchset adds support of two Amlogic firmware burning protocols:
ADNL and Optimus. Each protocol is supported on the following SoC:
axg, g12a, g12b, sm1 - Optimus.
a1, s4, a5, c1, c2, c3, sc2, t3, t7, p1 - ADNL.
Both work in fastboot manner, but significant feature is that they are
supported by current Amlogic BootROMs. As transport both use USB:
device is switched to gadget mode and then it can communicate with PC.
On PC side protocols are supported by:
1) https://github.com/superna9999/pyamlboot (with some limitation)
For example:
pyamlboot/./ubt.py --img <image> --wipe all --reset
Note, image has special format, specific for Amlogic.
2) Proprietary vendor tools.
As protocols are supported by BootROM, it allows to "resurrect" devices
when boot image (BL2, Uboot) missed/invalid. Typical flow for such cases
is (both protocols):
DEVICE PC
*-------------------*
| |
| Runs update tool, |
| waits for device |
| on USB bus. |
| |
*-------------------*
|
*---------------------* |
| Power on |<-----<------/
*---------------------*
|
|
*---------------------*
| |
| Boot ROM switches |
| device to USB gadget|>----->------\
| mode and waits for | |
| commands. | |
| | |
*---------------------* |
|
*-------------------*
| Detects device on |
| USB bus. |
*-------------------*
|
|
*-------------------*
| Sends BL2 image |
| over USB. |
*-------------------*
|
*---------------------* |
| Boot ROM runs |<-----<------/
| BL2 image. |
*---------------------*
|
|
*---------------------*
| BL2 waits for |>----->------\
| Uboot image (FIP). | |
*---------------------* |
|
*-------------------*
| Sends Uboot image |
| over USB. |
*-------------------*
|
*---------------------* |
| BL2 runs received |<-----<------/
| Uboot image. |
*---------------------*
|
|
*---------------------*
| Uboot waits for |>----->------\
| commands. | |
*---------------------* |
|
*-------------------*
| Sends commands to |
| over USB. |
*-------------------*
|
*---------------------* |
| Uboot processes | |
| commands: erases, | |
| writes NAND, checks |<-----<------/
| CRC of written data |
| etc. |
*---------------------*
|
|
*---------------------*
| Uboot does reboot |
*---------------------*
There is also another mode to operate both protocols when we run not
from BootROM, but from Uboot shell. To do that, we enter protocol
specific command in shell ("adnl" or "optimus"). In this case Uboot
switches device to gadget mode, write some value to special register
and does reboot. Then BootROM checks above mentioned register and then
switches device to gadget mode. After that flow from above is executed.
This mode has same purposes as image update by 'fastboot'.
Limitations:
1) Only limited set of each protocol commands are supported -
it is enough to update image. But, for example 'secureboot'
feature is not implemented.
2) ADNL was tested on A1 SoC only.
3) Optimus was tested on AXG SoC only.
For more details of each protocol pls see the following:
ADNL - drivers/usb/gadget/amlogic/adnl/f_adnl.c
Optimus - drivers/usb/gadget/amlogic/optimus/f_optimus.c.
Arseniy Krasnov (8):
arch: arm: meson: use 'meson_sm_call()' in 'meson_sm_pwrdm_set()'
usb: gadget: fastboot: make part of USB fastboot code shared
usb: gadget: amlogic: common code for Amlogic flashing commands
mtd: rawnand: meson: move some defines to dedicated header
arch: arm: meson: bootloader write support
arm: meson: a1: add A1_SYSCTRL_SEC_STICKY_REG2
usb: gadget: amlogic: implement ADNL protocol
arm: meson: a1: ADNL protocol support
Vladimir Mitrofanov (3):
arch: arm: meson: sm: add commands to reboot device in different modes
usb: gadget: amlogic: implement Optimus protocol
arch: arm: axg: Optimus protocol support
arch/arm/include/asm/arch-meson/a1.h | 19 +
arch/arm/include/asm/arch-meson/axg.h | 12 +
arch/arm/include/asm/arch-meson/nand.h | 34 +
arch/arm/include/asm/arch-meson/rawnand.h | 38 +
arch/arm/include/asm/arch-meson/sm.h | 98 +-
arch/arm/include/asm/arch-meson/spinand.h | 43 +
arch/arm/mach-meson/Kconfig | 31 +
arch/arm/mach-meson/Makefile | 4 +-
arch/arm/mach-meson/board-a1.c | 32 +-
arch/arm/mach-meson/board-axg.c | 12 +
arch/arm/mach-meson/board-common.c | 32 +
arch/arm/mach-meson/rawnand.c | 291 ++++++
arch/arm/mach-meson/sm.c | 43 +-
arch/arm/mach-meson/spinand.c | 158 ++++
cmd/Kconfig | 14 +
cmd/meson/Makefile | 2 +
cmd/meson/adnl.c | 27 +
cmd/meson/gadget.c | 183 ++++
cmd/meson/gadget.h | 28 +
cmd/meson/optimus.c | 21 +
drivers/mtd/nand/raw/meson_nand.c | 13 +-
drivers/sm/meson-sm.c | 3 +
drivers/usb/gadget/Kconfig | 2 +
drivers/usb/gadget/Makefile | 4 +-
drivers/usb/gadget/amlogic/Kconfig | 12 +
drivers/usb/gadget/amlogic/adnl/Kconfig | 29 +
drivers/usb/gadget/amlogic/adnl/Makefile | 4 +
drivers/usb/gadget/amlogic/adnl/adnl.h | 124 +++
.../gadget/amlogic/adnl/adnl_buff_manager.c | 316 +++++++
drivers/usb/gadget/amlogic/adnl/adnl_media.c | 235 +++++
.../usb/gadget/amlogic/adnl/adnl_storage.c | 140 +++
drivers/usb/gadget/amlogic/adnl/f_adnl.c | 835 ++++++++++++++++++
drivers/usb/gadget/amlogic/optimus/Kconfig | 12 +
drivers/usb/gadget/amlogic/optimus/Makefile | 7 +
.../usb/gadget/amlogic/optimus/f_optimus.c | 687 ++++++++++++++
.../gadget/amlogic/optimus/optimus_download.c | 188 ++++
.../gadget/amlogic/optimus/optimus_download.h | 86 ++
drivers/usb/gadget/f_fastboot.c | 295 +------
drivers/usb/gadget/f_fastboot_common.c | 320 +++++++
drivers/usb/gadget/f_fastboot_common.h | 71 ++
include/meson/sm.h | 3 +
41 files changed, 4202 insertions(+), 306 deletions(-)
create mode 100644 arch/arm/include/asm/arch-meson/nand.h
create mode 100644 arch/arm/include/asm/arch-meson/rawnand.h
create mode 100644 arch/arm/include/asm/arch-meson/spinand.h
create mode 100644 arch/arm/mach-meson/rawnand.c
create mode 100644 arch/arm/mach-meson/spinand.c
create mode 100644 cmd/meson/adnl.c
create mode 100644 cmd/meson/gadget.c
create mode 100644 cmd/meson/gadget.h
create mode 100644 cmd/meson/optimus.c
create mode 100644 drivers/usb/gadget/amlogic/Kconfig
create mode 100644 drivers/usb/gadget/amlogic/adnl/Kconfig
create mode 100644 drivers/usb/gadget/amlogic/adnl/Makefile
create mode 100644 drivers/usb/gadget/amlogic/adnl/adnl.h
create mode 100644 drivers/usb/gadget/amlogic/adnl/adnl_buff_manager.c
create mode 100644 drivers/usb/gadget/amlogic/adnl/adnl_media.c
create mode 100644 drivers/usb/gadget/amlogic/adnl/adnl_storage.c
create mode 100644 drivers/usb/gadget/amlogic/adnl/f_adnl.c
create mode 100644 drivers/usb/gadget/amlogic/optimus/Kconfig
create mode 100644 drivers/usb/gadget/amlogic/optimus/Makefile
create mode 100644 drivers/usb/gadget/amlogic/optimus/f_optimus.c
create mode 100644 drivers/usb/gadget/amlogic/optimus/optimus_download.c
create mode 100644 drivers/usb/gadget/amlogic/optimus/optimus_download.h
create mode 100644 drivers/usb/gadget/f_fastboot_common.c
create mode 100644 drivers/usb/gadget/f_fastboot_common.h
--
2.30.1
^ permalink raw reply [flat|nested] 13+ messages in thread
* [rulkc] [PATCH v1 01/11] arch: arm: meson: sm: add commands to reboot device in different modes
2025-07-29 18:28 [rulkc] [PATCH v1 00/11] Amlogic: ADNL and Optimus protocols support avkrasnov
@ 2025-07-29 18:29 ` avkrasnov
2025-07-29 18:29 ` [rulkc] [PATCH v1 02/11] arch: arm: meson: use 'meson_sm_call()' in 'meson_sm_pwrdm_set()' avkrasnov
` (10 subsequent siblings)
11 siblings, 0 replies; 13+ messages in thread
From: avkrasnov @ 2025-07-29 18:29 UTC (permalink / raw)
From: Vladimir Mitrofanov <vvmitrofanov at salutedevices.com>
There are several commands to reboot device in specific mode
(cold, normal, etc.), these commands helps to enter flashing mode
and set boot source device:
1) MESON_SMC_CMD_BL1_FIRST_BOOT_SRC_SET
This command tells BL1 that it must load BL2 from USB data path. It is
used in ADNL protocol logic when burning process is started from the
U-Boot command line: when 'adnl' is entered, this command tells BL1 to
load BL2 from USB, then board is rebooted. After reboot BL2 and next
stage loaders will be loaded from USB, sticky register has value that
we are in ADNL mode, thus booted U-Boot enters ADNL mode to start data
burning.
Used in adnl-based SoC families: a1, s4, c1, с2, sc2, t7, etc.
2) MESON_SMC_CMD_REBOOT_MODE_SET
Used in optimus-based SoC families: axg, g12a, g12b, sm1, etc.
3) MESON_SMC_CMD_USB_BOOT_MODE_SET
Used in optimus-based SoC families: axg, g12a, g12b, sm1, etc.
Signed-off-by: Arseniy Krasnov <avkrasnov at salutedevices.com>
Signed-off-by: Vladimir Mitrofanov <vvmitrofanov at salutedevices.com>
Signed-off-by: Evgeny Bachinin <EABachinin at salutedevices.com>
---
arch/arm/include/asm/arch-meson/sm.h | 98 +++++++++++++++++++++++++++-
arch/arm/mach-meson/sm.c | 39 +++++++++++
drivers/sm/meson-sm.c | 3 +
include/meson/sm.h | 3 +
4 files changed, 142 insertions(+), 1 deletion(-)
diff --git a/arch/arm/include/asm/arch-meson/sm.h b/arch/arm/include/asm/arch-meson/sm.h
index 4d614955fc2..087f8b4d5fa 100644
--- a/arch/arm/include/asm/arch-meson/sm.h
+++ b/arch/arm/include/asm/arch-meson/sm.h
@@ -83,7 +83,7 @@ int meson_sm_get_serial(void *buffer, size_t size);
*/
int meson_sm_get_chip_id(struct meson_sm_chip_id *chip_id);
-enum {
+enum meson_reboot_mode {
REBOOT_REASON_COLD = 0,
REBOOT_REASON_NORMAL = 1,
REBOOT_REASON_RECOVERY = 2,
@@ -134,4 +134,100 @@ int meson_sm_pwrdm_set(size_t index, int cmd);
#define meson_sm_pwrdm_on(index) \
meson_sm_pwrdm_set(index, PWRDM_ON)
+/*
+ * Below is the list of SCPI_CMD commands, issued by U-Boot and passed to SCP
+ * co-processor via SMC to BL31 (TF-A). BL31 routes these commands to SCP
+ * via mailbox.
+ *
+ * Commands instruct the SCP how to set/control POC pins in order to choose
+ * first boot source, specified by the particular command below. These POC pins
+ * are readable by BootROM and used to determine the boot sequence. Usually,
+ * the normal default boot sequence, seen on production device is
+ * NAND/eMMC -> SD -> USB
+ *
+ * Commands below change the first boot source.
+ *
+ * NOTE:
+ * these commands are used along with meson_sm_set_bl1_first_boot_source() on
+ * Amlogic SoCs where ADNL burning protocol is utilized.
+ * Refer to meson_sm_set_usb_boot_mode() for the same functionality but on SoCs
+ * with Optimus burning protocol.
+ */
+
+/**
+ * define SCPI_CMD_USB_BOOT - sets the first boot source into USB with timeout
+ *
+ * Additionally, force the BootROM (BL1) to wait for the Host's enumeration
+ * with timeout
+ */
+#define SCPI_CMD_USB_BOOT 0xB0
+
+/**
+ * define SCPI_CMD_USB_UNBOOT - sets the first boot source into USB w/o timeout
+ *
+ * Additionally, force the BootROM to wait for the Host's enumeration with
+ * uninterruptible wait, i.e. forever
+ */
+#define SCPI_CMD_USB_UNBOOT 0xB1
+
+/**
+ * define SCPI_CMD_SDCARD_BOOT - sets the first boot source into SD card
+ */
+#define SCPI_CMD_SDCARD_BOOT 0xB2
+
+/**
+ * define SCPI_CMD_CLEAR_BOOT - sets the first boot source into default one
+ *
+ * BootROM follows a pre-defined boot sequence stored in SoC ROM code.
+ */
+#define SCPI_CMD_CLEAR_BOOT 0xB3
+
+/**
+ * meson_sm_set_bl1_first_boot_source - sets the first boot source for BootROM.
+ * @cmd: command to set first boot source visible to BootROM during boot.
+ * Use SCPI_CMD_* as commands.
+ *
+ * BootROM checks POC-pins (POC0, POC1, POC2) to determine in which order it
+ * must probe the various methods of booting the system. The order is usually
+ * pre-defined. But function changes the first boot source of that pre-defined
+ * order.
+ *
+ * @return: zero on success or error code on failure.
+ */
+int meson_sm_set_bl1_first_boot_source(u64 cmd);
+
+/**
+ * meson_sm_reboot - reboots device in a appropriate mode
+ * All possible modes described in &enum meson_reboot_mode
+ *
+ * @mode: reboot mode.
+ * @return: zero on success or error code on failure.
+ */
+int meson_sm_reboot(u64 mode);
+
+/**
+ * enum meson_usb_boot_mode - modes for meson_sm_set_usb_boot_mode()
+ * @FORCE_USB_BOOT: Forces BootROM to boot with firmware transferred by PC
+ * burning tool via USB. This mode has influence despite of the
+ * previously selected boot source. This mode persists between
+ * reboots and must be cleared.
+ * @CLEAR_USB_BOOT: Clears previous mode.
+ *
+ * These modes are used in conjunction with meson_sm_set_usb_boot_mode() on
+ * Amlogic SoCs where Optimus burning protocol is utilized.
+ */
+enum meson_usb_boot_mode {
+ CLEAR_USB_BOOT = 1,
+ FORCE_USB_BOOT = 2,
+};
+
+/**
+ * meson_sm_set_usb_boot_mode - sets the mode of usb interface that will be used during boot
+ * All possible modes described in &enum meson_usb_boot_mode
+ *
+ * @mode: reboot mode.
+ * @return: zero on success or error code on failure.
+ */
+int meson_sm_set_usb_boot_mode(u64 mode);
+
#endif /* __MESON_SM_H__ */
diff --git a/arch/arm/mach-meson/sm.c b/arch/arm/mach-meson/sm.c
index b1f91ca29ce..53b07e1dfe9 100644
--- a/arch/arm/mach-meson/sm.c
+++ b/arch/arm/mach-meson/sm.c
@@ -236,6 +236,17 @@ int meson_sm_get_reboot_reason(void)
return FIELD_GET(REBOOT_REASON_MASK, reason);
}
+static int meson_sm_call(u32 smc, struct pt_regs *regs, s32 *retval)
+{
+ struct udevice *dev;
+
+ dev = meson_get_sm_device();
+ if (IS_ERR(dev))
+ return PTR_ERR(dev);
+
+ return sm_call(dev, smc, retval, regs);
+}
+
int meson_sm_pwrdm_set(size_t index, int cmd)
{
struct udevice *dev;
@@ -256,3 +267,31 @@ int meson_sm_pwrdm_set(size_t index, int cmd)
return err;
}
+
+int meson_sm_set_bl1_first_boot_source(u64 cmd)
+{
+ struct pt_regs regs = { 0 };
+
+ regs.regs[1] = cmd;
+
+ return meson_sm_call(MESON_SMC_CMD_BL1_FIRST_BOOT_SRC_SET,
+ ®s, NULL);
+}
+
+int meson_sm_reboot(u64 mode)
+{
+ struct pt_regs regs = { 0 };
+
+ regs.regs[1] = mode;
+
+ return meson_sm_call(MESON_SMC_CMD_REBOOT_MODE_SET, ®s, NULL);
+}
+
+int meson_sm_set_usb_boot_mode(u64 mode)
+{
+ struct pt_regs regs = { 0 };
+
+ regs.regs[1] = mode;
+
+ return meson_sm_call(MESON_SMC_CMD_USB_BOOT_MODE_SET, ®s, NULL);
+}
diff --git a/drivers/sm/meson-sm.c b/drivers/sm/meson-sm.c
index 87eba1486db..f413d8335b3 100644
--- a/drivers/sm/meson-sm.c
+++ b/drivers/sm/meson-sm.c
@@ -168,8 +168,11 @@ static const struct meson_sm_data meson_sm_gxbb_data = {
.cmd = {
SET_CMD(MESON_SMC_CMD_EFUSE_READ, 0x82000030),
SET_CMD(MESON_SMC_CMD_EFUSE_WRITE, 0x82000031),
+ SET_CMD(MESON_SMC_CMD_USB_BOOT_MODE_SET, 0x82000043),
SET_CMD(MESON_SMC_CMD_CHIP_ID_GET, 0x82000044),
+ SET_CMD(MESON_SMC_CMD_BL1_FIRST_BOOT_SRC_SET, 0x82000087),
SET_CMD(MESON_SMC_CMD_PWRDM_SET, 0x82000093),
+ SET_CMD(MESON_SMC_CMD_REBOOT_MODE_SET, 0x84000009),
},
};
diff --git a/include/meson/sm.h b/include/meson/sm.h
index fbaab1f1ee0..b9621f95cbe 100644
--- a/include/meson/sm.h
+++ b/include/meson/sm.h
@@ -13,6 +13,9 @@ enum meson_smc_cmd {
MESON_SMC_CMD_EFUSE_WRITE, /* write efuse memory */
MESON_SMC_CMD_CHIP_ID_GET, /* readh chip unique id */
MESON_SMC_CMD_PWRDM_SET, /* do command at specified power domain */
+ MESON_SMC_CMD_BL1_FIRST_BOOT_SRC_SET, /* setup first boot source for BootROM */
+ MESON_SMC_CMD_REBOOT_MODE_SET, /* Reboot device in desired mode */
+ MESON_SMC_CMD_USB_BOOT_MODE_SET, /* Run boot from USB device */
MESON_SMC_CMD_COUNT,
};
--
2.30.1
^ permalink raw reply [flat|nested] 13+ messages in thread
* [rulkc] [PATCH v1 02/11] arch: arm: meson: use 'meson_sm_call()' in 'meson_sm_pwrdm_set()'
2025-07-29 18:28 [rulkc] [PATCH v1 00/11] Amlogic: ADNL and Optimus protocols support avkrasnov
2025-07-29 18:29 ` [rulkc] [PATCH v1 01/11] arch: arm: meson: sm: add commands to reboot device in different modes avkrasnov
@ 2025-07-29 18:29 ` avkrasnov
2025-07-29 18:29 ` [rulkc] [PATCH v1 03/11] usb: gadget: fastboot: make part of USB fastboot code shared avkrasnov
` (9 subsequent siblings)
11 siblings, 0 replies; 13+ messages in thread
From: avkrasnov @ 2025-07-29 18:29 UTC (permalink / raw)
Use more generic function in 'meson_sm_pwrdm_set()'.
Signed-off-by: Arseniy Krasnov <avkrasnov at salutedevices.com>
---
arch/arm/mach-meson/sm.c | 8 +-------
1 file changed, 1 insertion(+), 7 deletions(-)
diff --git a/arch/arm/mach-meson/sm.c b/arch/arm/mach-meson/sm.c
index 53b07e1dfe9..0a781e9c47b 100644
--- a/arch/arm/mach-meson/sm.c
+++ b/arch/arm/mach-meson/sm.c
@@ -249,22 +249,16 @@ static int meson_sm_call(u32 smc, struct pt_regs *regs, s32 *retval)
int meson_sm_pwrdm_set(size_t index, int cmd)
{
- struct udevice *dev;
struct pt_regs regs = { 0 };
int err;
- dev = meson_get_sm_device();
- if (IS_ERR(dev))
- return PTR_ERR(dev);
-
regs.regs[1] = index;
regs.regs[2] = cmd;
- err = sm_call(dev, MESON_SMC_CMD_PWRDM_SET, NULL, ®s);
+ err = meson_sm_call(MESON_SMC_CMD_PWRDM_SET, ®s, NULL);
if (err)
pr_err("Failed to %s power domain ind=%zu (%d)\n", cmd == PWRDM_ON ?
"enable" : "disable", index, err);
-
return err;
}
--
2.30.1
^ permalink raw reply [flat|nested] 13+ messages in thread
* [rulkc] [PATCH v1 03/11] usb: gadget: fastboot: make part of USB fastboot code shared
2025-07-29 18:28 [rulkc] [PATCH v1 00/11] Amlogic: ADNL and Optimus protocols support avkrasnov
2025-07-29 18:29 ` [rulkc] [PATCH v1 01/11] arch: arm: meson: sm: add commands to reboot device in different modes avkrasnov
2025-07-29 18:29 ` [rulkc] [PATCH v1 02/11] arch: arm: meson: use 'meson_sm_call()' in 'meson_sm_pwrdm_set()' avkrasnov
@ 2025-07-29 18:29 ` avkrasnov
2025-07-29 18:29 ` [rulkc] [PATCH v1 04/11] usb: gadget: amlogic: common code for Amlogic flashing commands avkrasnov
` (8 subsequent siblings)
11 siblings, 0 replies; 13+ messages in thread
From: avkrasnov @ 2025-07-29 18:29 UTC (permalink / raw)
This is preparation for fastboot-based flashing protocols (such as Amlogic
ADNL). When device enters ADNL mode, it becomes "fastboot" device from
USB point of view, so to avoid copy-paste of USB part of fastboot protocol,
reimplement it as shared code between fastboot and ADNL implementations.
Signed-off-by: Arseniy Krasnov <avkrasnov at salutedevices.com>
---
drivers/usb/gadget/Makefile | 2 +-
drivers/usb/gadget/f_fastboot.c | 295 +----------------------
drivers/usb/gadget/f_fastboot_common.c | 320 +++++++++++++++++++++++++
drivers/usb/gadget/f_fastboot_common.h | 71 ++++++
4 files changed, 403 insertions(+), 285 deletions(-)
create mode 100644 drivers/usb/gadget/f_fastboot_common.c
create mode 100644 drivers/usb/gadget/f_fastboot_common.h
diff --git a/drivers/usb/gadget/Makefile b/drivers/usb/gadget/Makefile
index 4bda224ff1a..6c16ad83f6e 100644
--- a/drivers/usb/gadget/Makefile
+++ b/drivers/usb/gadget/Makefile
@@ -27,7 +27,7 @@ obj-$(CONFIG_USB_GADGET_DOWNLOAD) += g_dnl.o
obj-$(CONFIG_USB_FUNCTION_THOR) += f_thor.o
obj-$(CONFIG_DFU_OVER_USB) += f_dfu.o
obj-$(CONFIG_USB_FUNCTION_MASS_STORAGE) += f_mass_storage.o
-obj-$(CONFIG_USB_FUNCTION_FASTBOOT) += f_fastboot.o
+obj-$(CONFIG_USB_FUNCTION_FASTBOOT) += f_fastboot.o f_fastboot_common.o
obj-$(CONFIG_USB_FUNCTION_SDP) += f_sdp.o
obj-$(CONFIG_USB_FUNCTION_ROCKUSB) += f_rockusb.o
obj-$(CONFIG_USB_FUNCTION_ACM) += f_acm.o
diff --git a/drivers/usb/gadget/f_fastboot.c b/drivers/usb/gadget/f_fastboot.c
index 3c76d42c5ef..9298658b569 100644
--- a/drivers/usb/gadget/f_fastboot.c
+++ b/drivers/usb/gadget/f_fastboot.c
@@ -23,29 +23,7 @@
#include <linux/compiler.h>
#include <g_dnl.h>
#include <rabbith.h>
-
-#define FASTBOOT_INTERFACE_CLASS 0xff
-#define FASTBOOT_INTERFACE_SUB_CLASS 0x42
-#define FASTBOOT_INTERFACE_PROTOCOL 0x03
-
-#define RX_ENDPOINT_MAXIMUM_PACKET_SIZE_2_0 (0x0200)
-#define RX_ENDPOINT_MAXIMUM_PACKET_SIZE_1_1 (0x0040)
-#define TX_ENDPOINT_MAXIMUM_PACKET_SIZE (0x0040)
-
-#define EP_BUFFER_SIZE 4096
-/*
- * EP_BUFFER_SIZE must always be an integral multiple of maxpacket size
- * (64 or 512 or 1024), else we break on certain controllers like DWC3
- * that expect bulk OUT requests to be divisible by maxpacket size.
- */
-
-struct f_fastboot {
- struct usb_function usb_function;
-
- /* IN/OUT EP's and corresponding requests */
- struct usb_ep *in_ep, *out_ep;
- struct usb_request *in_req, *out_req;
-};
+#include "f_fastboot_common.h"
static char fb_ext_prop_name[] = "DeviceInterfaceGUID";
static char fb_ext_prop_data[] = "{4866319A-F4D6-4374-93B9-DC2DEB361BA9}";
@@ -66,113 +44,7 @@ static struct usb_os_desc_table fb_os_desc_table = {
.os_desc = &fb_os_desc,
};
-static inline struct f_fastboot *func_to_fastboot(struct usb_function *f)
-{
- return container_of(f, struct f_fastboot, usb_function);
-}
-
-static struct f_fastboot *fastboot_func;
-
-static struct usb_endpoint_descriptor fs_ep_in = {
- .bLength = USB_DT_ENDPOINT_SIZE,
- .bDescriptorType = USB_DT_ENDPOINT,
- .bEndpointAddress = USB_DIR_IN,
- .bmAttributes = USB_ENDPOINT_XFER_BULK,
- .wMaxPacketSize = cpu_to_le16(64),
-};
-
-static struct usb_endpoint_descriptor fs_ep_out = {
- .bLength = USB_DT_ENDPOINT_SIZE,
- .bDescriptorType = USB_DT_ENDPOINT,
- .bEndpointAddress = USB_DIR_OUT,
- .bmAttributes = USB_ENDPOINT_XFER_BULK,
- .wMaxPacketSize = cpu_to_le16(64),
-};
-
-static struct usb_endpoint_descriptor hs_ep_in = {
- .bLength = USB_DT_ENDPOINT_SIZE,
- .bDescriptorType = USB_DT_ENDPOINT,
- .bEndpointAddress = USB_DIR_IN,
- .bmAttributes = USB_ENDPOINT_XFER_BULK,
- .wMaxPacketSize = cpu_to_le16(512),
-};
-
-static struct usb_endpoint_descriptor hs_ep_out = {
- .bLength = USB_DT_ENDPOINT_SIZE,
- .bDescriptorType = USB_DT_ENDPOINT,
- .bEndpointAddress = USB_DIR_OUT,
- .bmAttributes = USB_ENDPOINT_XFER_BULK,
- .wMaxPacketSize = cpu_to_le16(512),
-};
-
-static struct usb_interface_descriptor interface_desc = {
- .bLength = USB_DT_INTERFACE_SIZE,
- .bDescriptorType = USB_DT_INTERFACE,
- .bInterfaceNumber = 0x00,
- .bAlternateSetting = 0x00,
- .bNumEndpoints = 0x02,
- .bInterfaceClass = FASTBOOT_INTERFACE_CLASS,
- .bInterfaceSubClass = FASTBOOT_INTERFACE_SUB_CLASS,
- .bInterfaceProtocol = FASTBOOT_INTERFACE_PROTOCOL,
-};
-
-static struct usb_descriptor_header *fb_fs_function[] = {
- (struct usb_descriptor_header *)&interface_desc,
- (struct usb_descriptor_header *)&fs_ep_in,
- (struct usb_descriptor_header *)&fs_ep_out,
- NULL,
-};
-
-static struct usb_descriptor_header *fb_hs_function[] = {
- (struct usb_descriptor_header *)&interface_desc,
- (struct usb_descriptor_header *)&hs_ep_in,
- (struct usb_descriptor_header *)&hs_ep_out,
- NULL,
-};
-
-/* Super speed */
-static struct usb_endpoint_descriptor ss_ep_in = {
- .bLength = USB_DT_ENDPOINT_SIZE,
- .bDescriptorType = USB_DT_ENDPOINT,
- .bEndpointAddress = USB_DIR_IN,
- .bmAttributes = USB_ENDPOINT_XFER_BULK,
- .wMaxPacketSize = cpu_to_le16(1024),
-};
-
-static struct usb_endpoint_descriptor ss_ep_out = {
- .bLength = USB_DT_ENDPOINT_SIZE,
- .bDescriptorType = USB_DT_ENDPOINT,
- .bEndpointAddress = USB_DIR_OUT,
- .bmAttributes = USB_ENDPOINT_XFER_BULK,
- .wMaxPacketSize = cpu_to_le16(1024),
-};
-
-static struct usb_ss_ep_comp_descriptor fb_ss_bulk_comp_desc = {
- .bLength = sizeof(fb_ss_bulk_comp_desc),
- .bDescriptorType = USB_DT_SS_ENDPOINT_COMP,
-};
-
-static struct usb_descriptor_header *fb_ss_function[] = {
- (struct usb_descriptor_header *)&interface_desc,
- (struct usb_descriptor_header *)&ss_ep_in,
- (struct usb_descriptor_header *)&fb_ss_bulk_comp_desc,
- (struct usb_descriptor_header *)&ss_ep_out,
- (struct usb_descriptor_header *)&fb_ss_bulk_comp_desc,
- NULL,
-};
-
-static struct usb_endpoint_descriptor *
-fb_ep_desc(struct usb_gadget *g, struct usb_endpoint_descriptor *fs,
- struct usb_endpoint_descriptor *hs,
- struct usb_endpoint_descriptor *ss)
-{
- if (gadget_is_superspeed(g) && g->speed >= USB_SPEED_SUPER)
- return ss;
-
- if (gadget_is_dualspeed(g) && g->speed == USB_SPEED_HIGH)
- return hs;
- return fs;
-}
+static struct fastboot_funcs *fastboot_func;
/*
* static strings, in UTF-8
@@ -196,179 +68,34 @@ static struct usb_gadget_strings *fastboot_strings[] = {
static void rx_handler_command(struct usb_ep *ep, struct usb_request *req);
-static void fastboot_complete(struct usb_ep *ep, struct usb_request *req)
-{
- int status = req->status;
- if (!status)
- return;
- printf("status: %d ep '%s' trans: %d\n", status, ep->name, req->actual);
-}
-
static int fastboot_bind(struct usb_configuration *c, struct usb_function *f)
{
- int id;
- struct usb_gadget *gadget = c->cdev->gadget;
- struct f_fastboot *f_fb = func_to_fastboot(f);
- const char *s;
-
- /* DYNAMIC interface numbers assignments */
- id = usb_interface_id(c, f);
- if (id < 0)
- return id;
- interface_desc.bInterfaceNumber = id;
-
- /* Enable OS and Extended Properties Feature Descriptor */
- c->cdev->use_os_string = 1;
- f->os_desc_table = &fb_os_desc_table;
- f->os_desc_n = 1;
- f->os_desc_table->if_id = id;
- INIT_LIST_HEAD(&fb_os_desc.ext_prop);
- fb_ext_prop.name_len = strlen(fb_ext_prop.name) * 2 + 2;
- fb_os_desc.ext_prop_len = 10 + fb_ext_prop.name_len;
- fb_os_desc.ext_prop_count = 1;
- fb_ext_prop.data_len = strlen(fb_ext_prop.data) * 2 + 2;
- fb_os_desc.ext_prop_len += fb_ext_prop.data_len + 4;
- list_add_tail(&fb_ext_prop.entry, &fb_os_desc.ext_prop);
-
- id = usb_string_id(c->cdev);
- if (id < 0)
- return id;
- fastboot_string_defs[0].id = id;
- interface_desc.iInterface = id;
-
- f_fb->in_ep = usb_ep_autoconfig(gadget, &fs_ep_in);
- if (!f_fb->in_ep)
- return -ENODEV;
- f_fb->in_ep->driver_data = c->cdev;
-
- f_fb->out_ep = usb_ep_autoconfig(gadget, &fs_ep_out);
- if (!f_fb->out_ep)
- return -ENODEV;
- f_fb->out_ep->driver_data = c->cdev;
-
- f->descriptors = fb_fs_function;
-
- if (gadget_is_dualspeed(gadget)) {
- /* Assume endpoint addresses are the same for both speeds */
- hs_ep_in.bEndpointAddress = fs_ep_in.bEndpointAddress;
- hs_ep_out.bEndpointAddress = fs_ep_out.bEndpointAddress;
- /* copy HS descriptors */
- f->hs_descriptors = fb_hs_function;
- }
-
- if (gadget_is_superspeed(gadget)) {
- ss_ep_in.bEndpointAddress = fs_ep_in.bEndpointAddress;
- ss_ep_out.bEndpointAddress = fs_ep_out.bEndpointAddress;
- f->ss_descriptors = fb_ss_function;
- }
-
- s = env_get("serial#");
- if (s)
- g_dnl_set_serialnumber((char *)s);
-
- return 0;
+ return fastboot_common_bind(c, f, &fb_os_desc_table, &fb_os_desc,
+ (struct usb_string **)&fastboot_string_defs,
+ &fb_ext_prop);
}
static void fastboot_unbind(struct usb_configuration *c, struct usb_function *f)
{
- f->os_desc_table = NULL;
- list_del(&fb_os_desc.ext_prop);
+ fastboot_common_unbind(c, f, &fb_os_desc);
memset(fastboot_func, 0, sizeof(*fastboot_func));
}
static void fastboot_disable(struct usb_function *f)
{
- struct f_fastboot *f_fb = func_to_fastboot(f);
-
- usb_ep_disable(f_fb->out_ep);
- usb_ep_disable(f_fb->in_ep);
-
- if (f_fb->out_req) {
- free(f_fb->out_req->buf);
- usb_ep_free_request(f_fb->out_ep, f_fb->out_req);
- f_fb->out_req = NULL;
- }
- if (f_fb->in_req) {
- free(f_fb->in_req->buf);
- usb_ep_free_request(f_fb->in_ep, f_fb->in_req);
- f_fb->in_req = NULL;
- }
-}
-
-static struct usb_request *fastboot_start_ep(struct usb_ep *ep)
-{
- struct usb_request *req;
-
- req = usb_ep_alloc_request(ep, 0);
- if (!req)
- return NULL;
-
- req->length = EP_BUFFER_SIZE;
- req->buf = memalign(CONFIG_SYS_CACHELINE_SIZE, EP_BUFFER_SIZE);
- if (!req->buf) {
- usb_ep_free_request(ep, req);
- return NULL;
- }
-
- memset(req->buf, 0, req->length);
- return req;
+ fastboot_common_disable(f);
}
static int fastboot_set_alt(struct usb_function *f,
unsigned interface, unsigned alt)
{
- int ret;
- struct usb_composite_dev *cdev = f->config->cdev;
- struct usb_gadget *gadget = cdev->gadget;
- struct f_fastboot *f_fb = func_to_fastboot(f);
- const struct usb_endpoint_descriptor *d;
-
- debug("%s: func: %s intf: %d alt: %d\n",
- __func__, f->name, interface, alt);
-
- d = fb_ep_desc(gadget, &fs_ep_out, &hs_ep_out, &ss_ep_out);
- ret = usb_ep_enable(f_fb->out_ep, d);
- if (ret) {
- puts("failed to enable out ep\n");
- return ret;
- }
-
- f_fb->out_req = fastboot_start_ep(f_fb->out_ep);
- if (!f_fb->out_req) {
- puts("failed to alloc out req\n");
- ret = -EINVAL;
- goto err;
- }
- f_fb->out_req->complete = rx_handler_command;
-
- d = fb_ep_desc(gadget, &fs_ep_in, &hs_ep_in, &ss_ep_in);
- ret = usb_ep_enable(f_fb->in_ep, d);
- if (ret) {
- puts("failed to enable in ep\n");
- goto err;
- }
-
- f_fb->in_req = fastboot_start_ep(f_fb->in_ep);
- if (!f_fb->in_req) {
- puts("failed alloc req in\n");
- ret = -EINVAL;
- goto err;
- }
- f_fb->in_req->complete = fastboot_complete;
-
- ret = usb_ep_queue(f_fb->out_ep, f_fb->out_req, 0);
- if (ret)
- goto err;
-
- return 0;
-err:
- fastboot_disable(f);
- return ret;
+ return fastboot_common_set_alt(f, interface, alt,
+ rx_handler_command);
}
static int fastboot_add(struct usb_configuration *c)
{
- struct f_fastboot *f_fb = fastboot_func;
+ struct fastboot_funcs *f_fb = fastboot_func;
int status;
debug("%s: cdev: 0x%p\n", __func__, c->cdev);
@@ -515,7 +242,7 @@ static void multiresponse_on_complete(struct usb_ep *ep, struct usb_request *req
/* If response is final OKAY/FAIL response disconnect this handler and unset cmd */
if (!strncmp("OKAY", response, 4) || !strncmp("FAIL", response, 4)) {
multiresponse_cmd = -1;
- fastboot_func->in_req->complete = fastboot_complete;
+ fastboot_func->in_req->complete = fastboot_common_complete;
}
}
diff --git a/drivers/usb/gadget/f_fastboot_common.c b/drivers/usb/gadget/f_fastboot_common.c
new file mode 100644
index 00000000000..9e509503d2d
--- /dev/null
+++ b/drivers/usb/gadget/f_fastboot_common.c
@@ -0,0 +1,320 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * (C) Copyright 2008 - 2009
+ * Windriver, <www.windriver.com>
+ * Tom Rix <Tom.Rix at windriver.com>
+ *
+ * Copyright 2011 Sebastian Andrzej Siewior <bigeasy at linutronix.de>
+ *
+ * Copyright 2014 Linaro, Ltd.
+ * Rob Herring <robh at kernel.org>
+ *
+ * Copyright (c) 2025 SaluteDevices, Inc.
+ * Author: Arseniy Krasnov <avkrasnov at salutedevices.com>
+ */
+
+#include <env.h>
+#include "f_fastboot_common.h"
+
+static struct usb_endpoint_descriptor fs_ep_in = {
+ .bLength = USB_DT_ENDPOINT_SIZE,
+ .bDescriptorType = USB_DT_ENDPOINT,
+ .bEndpointAddress = USB_DIR_IN,
+ .bmAttributes = USB_ENDPOINT_XFER_BULK,
+ .wMaxPacketSize = cpu_to_le16(64),
+};
+
+static struct usb_endpoint_descriptor fs_ep_out = {
+ .bLength = USB_DT_ENDPOINT_SIZE,
+ .bDescriptorType = USB_DT_ENDPOINT,
+ .bEndpointAddress = USB_DIR_OUT,
+ .bmAttributes = USB_ENDPOINT_XFER_BULK,
+ .wMaxPacketSize = cpu_to_le16(64),
+};
+
+static struct usb_interface_descriptor interface_desc = {
+ .bLength = USB_DT_INTERFACE_SIZE,
+ .bDescriptorType = USB_DT_INTERFACE,
+ .bInterfaceNumber = 0x00,
+ .bAlternateSetting = 0x00,
+ .bNumEndpoints = 0x02,
+ .bInterfaceClass = FASTBOOT_INTERFACE_CLASS,
+ .bInterfaceSubClass = FASTBOOT_INTERFACE_SUB_CLASS,
+ .bInterfaceProtocol = FASTBOOT_INTERFACE_PROTOCOL,
+};
+
+static struct usb_descriptor_header *fb_fs_function[] = {
+ (struct usb_descriptor_header *)&interface_desc,
+ (struct usb_descriptor_header *)&fs_ep_in,
+ (struct usb_descriptor_header *)&fs_ep_out,
+ NULL,
+};
+
+static struct usb_endpoint_descriptor hs_ep_in = {
+ .bLength = USB_DT_ENDPOINT_SIZE,
+ .bDescriptorType = USB_DT_ENDPOINT,
+ .bEndpointAddress = USB_DIR_IN,
+ .bmAttributes = USB_ENDPOINT_XFER_BULK,
+ .wMaxPacketSize = cpu_to_le16(512),
+};
+
+static struct usb_endpoint_descriptor hs_ep_out = {
+ .bLength = USB_DT_ENDPOINT_SIZE,
+ .bDescriptorType = USB_DT_ENDPOINT,
+ .bEndpointAddress = USB_DIR_OUT,
+ .bmAttributes = USB_ENDPOINT_XFER_BULK,
+ .wMaxPacketSize = cpu_to_le16(512),
+};
+
+static struct usb_descriptor_header *fb_hs_function[] = {
+ (struct usb_descriptor_header *)&interface_desc,
+ (struct usb_descriptor_header *)&hs_ep_in,
+ (struct usb_descriptor_header *)&hs_ep_out,
+ NULL,
+};
+
+/* Super speed */
+static struct usb_endpoint_descriptor ss_ep_in = {
+ .bLength = USB_DT_ENDPOINT_SIZE,
+ .bDescriptorType = USB_DT_ENDPOINT,
+ .bEndpointAddress = USB_DIR_IN,
+ .bmAttributes = USB_ENDPOINT_XFER_BULK,
+ .wMaxPacketSize = cpu_to_le16(1024),
+};
+
+static struct usb_endpoint_descriptor ss_ep_out = {
+ .bLength = USB_DT_ENDPOINT_SIZE,
+ .bDescriptorType = USB_DT_ENDPOINT,
+ .bEndpointAddress = USB_DIR_OUT,
+ .bmAttributes = USB_ENDPOINT_XFER_BULK,
+ .wMaxPacketSize = cpu_to_le16(1024),
+};
+
+static struct usb_ss_ep_comp_descriptor fb_ss_bulk_comp_desc = {
+ .bLength = sizeof(fb_ss_bulk_comp_desc),
+ .bDescriptorType = USB_DT_SS_ENDPOINT_COMP,
+};
+
+static struct usb_descriptor_header *fb_ss_function[] = {
+ (struct usb_descriptor_header *)&interface_desc,
+ (struct usb_descriptor_header *)&ss_ep_in,
+ (struct usb_descriptor_header *)&fb_ss_bulk_comp_desc,
+ (struct usb_descriptor_header *)&ss_ep_out,
+ (struct usb_descriptor_header *)&fb_ss_bulk_comp_desc,
+ NULL,
+};
+
+static struct usb_endpoint_descriptor *fastboot_common_ep_desc(struct usb_gadget *g,
+ struct usb_endpoint_descriptor *fs,
+ struct usb_endpoint_descriptor *hs,
+ struct usb_endpoint_descriptor *ss)
+{
+ if (gadget_is_superspeed(g) && g->speed >= USB_SPEED_SUPER)
+ return ss;
+
+ if (gadget_is_dualspeed(g) && g->speed == USB_SPEED_HIGH)
+ return hs;
+ return fs;
+}
+
+void fastboot_common_complete(struct usb_ep *ep, struct usb_request *req)
+{
+ int status = req->status;
+
+ if (!status)
+ return;
+
+ printf("status: %d ep '%s' trans: %d\n", status, ep->name, req->actual);
+}
+
+int fastboot_common_bind(struct usb_configuration *c, struct usb_function *f,
+ struct usb_os_desc_table *fb_os_desc_table,
+ struct usb_os_desc *fb_os_desc,
+ struct usb_string **fastboot_string_defs,
+ struct usb_os_desc_ext_prop *fb_ext_prop)
+{
+ int id;
+ struct usb_gadget *gadget = c->cdev->gadget;
+ struct fastboot_funcs *f_fb = func_to_fastboot_funcs(f);
+ const char *s;
+
+ /* DYNAMIC interface numbers assignments */
+ id = usb_interface_id(c, f);
+ if (id < 0)
+ return id;
+
+ interface_desc.bInterfaceNumber = id;
+
+ /* Enable OS and Extended Properties Feature Descriptor */
+ c->cdev->use_os_string = 1;
+ f->os_desc_table = fb_os_desc_table;
+ f->os_desc_n = 1;
+ f->os_desc_table->if_id = id;
+ INIT_LIST_HEAD(&fb_os_desc->ext_prop);
+ fb_ext_prop->name_len = strlen(fb_ext_prop->name) * 2 + 2;
+ fb_os_desc->ext_prop_len = 10 + fb_ext_prop->name_len;
+ fb_os_desc->ext_prop_count = 1;
+ fb_ext_prop->data_len = strlen(fb_ext_prop->data) * 2 + 2;
+ fb_os_desc->ext_prop_len += fb_ext_prop->data_len + 4;
+ list_add_tail(&fb_ext_prop->entry, &fb_os_desc->ext_prop);
+
+ id = usb_string_id(c->cdev);
+ if (id < 0)
+ return id;
+
+ fastboot_string_defs[0]->id = id;
+ interface_desc.iInterface = id;
+
+ f_fb->in_ep = usb_ep_autoconfig(gadget, &fs_ep_in);
+ if (!f_fb->in_ep)
+ return -ENODEV;
+
+ f_fb->in_ep->driver_data = c->cdev;
+
+ f_fb->out_ep = usb_ep_autoconfig(gadget, &fs_ep_out);
+ if (!f_fb->out_ep)
+ return -ENODEV;
+
+ f_fb->out_ep->driver_data = c->cdev;
+
+ f->descriptors = fb_fs_function;
+
+ if (gadget_is_dualspeed(gadget)) {
+ /* Assume endpoint addresses are the same for both speeds */
+ hs_ep_in.bEndpointAddress = fs_ep_in.bEndpointAddress;
+ hs_ep_out.bEndpointAddress = fs_ep_out.bEndpointAddress;
+ /* copy HS descriptors */
+ f->hs_descriptors = fb_hs_function;
+ }
+
+ if (gadget_is_superspeed(gadget)) {
+ ss_ep_in.bEndpointAddress = fs_ep_in.bEndpointAddress;
+ ss_ep_out.bEndpointAddress = fs_ep_out.bEndpointAddress;
+ f->ss_descriptors = fb_ss_function;
+ }
+
+ s = env_get("serial#");
+ if (s)
+ g_dnl_set_serialnumber((char *)s);
+
+ return 0;
+}
+
+void fastboot_common_unbind(struct usb_configuration *c,
+ struct usb_function *f,
+ struct usb_os_desc *fb_os_desc)
+{
+ f->os_desc_table = NULL;
+ list_del(&fb_os_desc->ext_prop);
+}
+
+static void __fastboot_common_disable(struct usb_function *f, bool disable_out, bool disable_in)
+{
+ struct fastboot_funcs *f_fb = func_to_fastboot_funcs(f);
+
+ if (disable_out)
+ usb_ep_disable(f_fb->out_ep);
+
+ if (disable_in)
+ usb_ep_disable(f_fb->in_ep);
+
+ if (f_fb->out_req) {
+ free(f_fb->out_req->buf);
+ usb_ep_free_request(f_fb->out_ep, f_fb->out_req);
+ f_fb->out_req = NULL;
+ }
+ if (f_fb->in_req) {
+ free(f_fb->in_req->buf);
+ usb_ep_free_request(f_fb->in_ep, f_fb->in_req);
+ f_fb->in_req = NULL;
+ }
+}
+
+void fastboot_common_disable(struct usb_function *f)
+{
+ return __fastboot_common_disable(f, true, true);
+}
+
+static struct usb_request *fastboot_common_start_ep(struct usb_ep *ep)
+{
+ struct usb_request *req;
+
+ req = usb_ep_alloc_request(ep, 0);
+ if (!req)
+ return NULL;
+
+ req->length = EP_BUFFER_SIZE;
+ req->buf = memalign(CONFIG_SYS_CACHELINE_SIZE, EP_BUFFER_SIZE);
+ if (!req->buf) {
+ usb_ep_free_request(ep, req);
+ return NULL;
+ }
+
+ memset(req->buf, 0, req->length);
+ return req;
+}
+
+int fastboot_common_set_alt(struct usb_function *f,
+ unsigned int interface, unsigned int alt,
+ void (*rx_handler)(struct usb_ep *ep, struct usb_request *req))
+{
+ int ret;
+ struct usb_composite_dev *cdev = f->config->cdev;
+ struct usb_gadget *gadget = cdev->gadget;
+ struct fastboot_funcs *f_adnl = func_to_fastboot_funcs(f);
+ const struct usb_endpoint_descriptor *d;
+ bool disable_out = false;
+ bool disable_in = false;
+
+ debug("%s: func: %s intf: %u alt: %u\n",
+ __func__, f->name, interface, alt);
+
+ d = fastboot_common_ep_desc(gadget, &fs_ep_out, &hs_ep_out, &ss_ep_out);
+ ret = usb_ep_enable(f_adnl->out_ep, d);
+ if (ret) {
+ puts("failed to enable out ep\n");
+ return ret;
+ }
+
+ disable_out = true;
+
+ f_adnl->out_req = fastboot_common_start_ep(f_adnl->out_ep);
+ if (!f_adnl->out_req) {
+ puts("failed to alloc out req\n");
+ ret = -EINVAL;
+ goto err;
+ }
+
+ f_adnl->out_req->complete = rx_handler;
+
+ d = fastboot_common_ep_desc(gadget, &fs_ep_in, &hs_ep_in, &ss_ep_in);
+ ret = usb_ep_enable(f_adnl->in_ep, d);
+ if (ret) {
+ puts("failed to enable in ep\n");
+ goto err;
+ }
+
+ disable_in = true;
+
+ f_adnl->in_req = fastboot_common_start_ep(f_adnl->in_ep);
+ if (!f_adnl->in_req) {
+ puts("failed alloc req in\n");
+ ret = -EINVAL;
+ goto err;
+ }
+ f_adnl->in_req->complete = fastboot_common_complete;
+
+ ret = usb_ep_queue(f_adnl->out_ep, f_adnl->out_req, 0);
+ if (ret)
+ goto err;
+
+ return 0;
+err:
+ __fastboot_common_disable(f, disable_out, disable_in);
+ return ret;
+}
+
+struct fastboot_funcs *func_to_fastboot_funcs(struct usb_function *f)
+{
+ return container_of(f, struct fastboot_funcs, usb_function);
+}
diff --git a/drivers/usb/gadget/f_fastboot_common.h b/drivers/usb/gadget/f_fastboot_common.h
new file mode 100644
index 00000000000..dba7d7003ba
--- /dev/null
+++ b/drivers/usb/gadget/f_fastboot_common.h
@@ -0,0 +1,71 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * (C) Copyright 2008 - 2009
+ * Windriver, <www.windriver.com>
+ * Tom Rix <Tom.Rix at windriver.com>
+ *
+ * Copyright 2011 Sebastian Andrzej Siewior <bigeasy at linutronix.de>
+ *
+ * Copyright 2014 Linaro, Ltd.
+ * Rob Herring <robh at kernel.org>
+ *
+ * Copyright (c) 2025 SaluteDevices, Inc.
+ * Author: Arseniy Krasnov <avkrasnov at salutedevices.com>
+ */
+
+#ifndef __F_FASTBOOT_COMMON_H_
+#define __F_FASTBOOT_COMMON_H_
+#include <g_dnl.h>
+
+/*
+ * EP_BUFFER_SIZE must always be an integral multiple of maxpacket size
+ * (64 or 512 or 1024), else we break on certain controllers like DWC3
+ * that expect bulk OUT requests to be divisible by maxpacket size.
+ */
+#define EP_BUFFER_SIZE 4096
+
+#define FASTBOOT_INTERFACE_CLASS 0xff
+#define FASTBOOT_INTERFACE_SUB_CLASS 0x42
+#define FASTBOOT_INTERFACE_PROTOCOL 0x03
+
+struct fastboot_funcs {
+ struct usb_function usb_function;
+
+ /* IN/OUT EP's and corresponding requests */
+ struct usb_ep *in_ep, *out_ep;
+ struct usb_request *in_req, *out_req;
+};
+
+void fastboot_common_complete(struct usb_ep *ep, struct usb_request *req);
+
+/*
+ * Initializes USB function 'f' with provided resources and binds it to the
+ * USB configuration 'c'.
+ */
+int fastboot_common_bind(struct usb_configuration *c, struct usb_function *f,
+ struct usb_os_desc_table *fb_os_desc_table,
+ struct usb_os_desc *fb_os_desc,
+ struct usb_string **fastboot_string_defs,
+ struct usb_os_desc_ext_prop *fb_ext_prop);
+
+/* Reverse for 'fastboot_bind_common()'. */
+void fastboot_common_unbind(struct usb_configuration *c,
+ struct usb_function *f,
+ struct usb_os_desc *fb_os_desc);
+
+/*
+ * Setups endpoints, allocates memory and sets callbacks for both IN and
+ * OUT requests.
+ */
+int fastboot_common_set_alt(struct usb_function *f,
+ unsigned int interface, unsigned int alt,
+ void (*rx_handler)(struct usb_ep *ep,
+ struct usb_request *req));
+
+/* Disables provided USB function 'f'. */
+void fastboot_common_disable(struct usb_function *f);
+
+/* Returns parent struct of 'f'. */
+struct fastboot_funcs *func_to_fastboot_funcs(struct usb_function *f);
+
+#endif /* __F_FASTBOOT_COMMON_H_ */
--
2.30.1
^ permalink raw reply [flat|nested] 13+ messages in thread
* [rulkc] [PATCH v1 04/11] usb: gadget: amlogic: common code for Amlogic flashing commands
2025-07-29 18:28 [rulkc] [PATCH v1 00/11] Amlogic: ADNL and Optimus protocols support avkrasnov
` (2 preceding siblings ...)
2025-07-29 18:29 ` [rulkc] [PATCH v1 03/11] usb: gadget: fastboot: make part of USB fastboot code shared avkrasnov
@ 2025-07-29 18:29 ` avkrasnov
2025-07-29 18:29 ` [rulkc] [PATCH v1 05/11] mtd: rawnand: meson: move some defines to dedicated header avkrasnov
` (7 subsequent siblings)
11 siblings, 0 replies; 13+ messages in thread
From: avkrasnov @ 2025-07-29 18:29 UTC (permalink / raw)
Amlogic has Optimus and ADNL protocols to update firmware image.
Both are operatable by special commands and such commands use same
approaches, so let's implement such code before implementing both
protocols.
Signed-off-by: Arseniy Krasnov <avkrasnov at salutedevices.com>
---
cmd/meson/gadget.c | 183 +++++++++++++++++++++++++++++++++++++++++++++
cmd/meson/gadget.h | 28 +++++++
2 files changed, 211 insertions(+)
create mode 100644 cmd/meson/gadget.c
create mode 100644 cmd/meson/gadget.h
diff --git a/cmd/meson/gadget.c b/cmd/meson/gadget.c
new file mode 100644
index 00000000000..6155043ae5a
--- /dev/null
+++ b/cmd/meson/gadget.c
@@ -0,0 +1,183 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (c) 2025 SaluteDevices, Inc.
+ * Author: Arseniy Krasnov <avkrasnov at salutedevices.com>
+ */
+
+#include <hexdump.h>
+#include <command.h>
+#include <g_dnl.h>
+#include <mtd.h>
+#include <time.h>
+#include <asm/arch/sm.h>
+#include <asm/types.h>
+#include <linux/delay.h>
+
+#define BURN_BOOT_UDELAY (2 * 1000 * 1000)
+static unsigned long burn_identify_time_ms;
+static unsigned long burn_start_time_ms;
+
+void amlogic_gadget_set_times(unsigned long start_time, unsigned long identify_time)
+{
+ burn_start_time_ms = start_time;
+ burn_identify_time_ms = identify_time;
+}
+
+/*
+ * This function utilizes serial from inside Amlogic chip_id with one
+ * distinctive feature that this serial must be in big-endian.
+ *
+ * This is needed, because serial string, utilized in BootROM, must match
+ * the serial string, used inside U-Boot. But BootROM utilizes serial string
+ * in big-endian: being in BootROM try
+ * - for new SoCs (a1, s4, etc - those which utilize ADNL protocol): run
+ * host command 'fastboot devices'
+ * - for old SoCs (axg, g12a, g12b, etc - those, utilizing optimus protocol):
+ * send "chipid" command to bootROM
+ */
+static int amlogic_serial_hex(char serial_str[SM_SERIAL_SIZE * 2 + 1])
+{
+ u8 serial[SM_SERIAL_SIZE];
+ int ret;
+
+ /*
+ * ADNL/OPTIMUS uses serial value in big-endian order.
+ * meson_sm_get_serial() returns exactly such value.
+ */
+ ret = meson_sm_get_serial(serial, sizeof(serial));
+ if (ret)
+ return ret;
+
+ bin2hex(serial_str, serial, sizeof(serial));
+ return ret;
+}
+
+int amlogic_gadget_run(const char *mode, int argc, char *const argv[])
+{
+ char serial_string[SM_SERIAL_SIZE * 2 + 1] = { 0 };
+ unsigned long identify_timeout_ms;
+ unsigned long start_timeout_ms;
+ struct udevice *udc;
+ char *saved_serial;
+ int ret;
+
+ if (mtd_probe_devices()) {
+ pr_err("Mtd devices probe failed\n");
+ return CMD_RET_FAILURE;
+ }
+
+ /*
+ * During ADNL/OPTIMUS burning process, device enters USB gadget mode
+ * twice: first time when it was stopped in ROM boot, second -
+ * here, after ROM and BL2 stages are done and we are ready
+ * to run BL33 (U-boot) stage. From the PC side, client waits
+ * reentering to USB gadget mode (in BL33 stage). Without delay
+ * below, PC client waits for USB device forever with the
+ * following message.
+ *
+ * OPTIMUS do not use timeout described below
+ * MSG[TLS]Waiting for DNL[<serial>] with timeout 0
+ *
+ * At the same time, there is no difference (from the PC point
+ * of view) between USB device state with or w/o this delay, so
+ * seems this delay is needed only to make PC client work
+ * correctly due to is internal logic.
+ */
+ udelay(BURN_BOOT_UDELAY);
+
+ ret = amlogic_serial_hex(serial_string);
+ if (ret)
+ return CMD_RET_FAILURE;
+
+ saved_serial = env_get("serial#");
+ if (saved_serial) {
+ saved_serial = strdup(saved_serial);
+ if (!saved_serial)
+ return CMD_RET_FAILURE;
+ }
+
+ /*
+ * Use "force" flag, because 'serial#' is protected
+ * from both delete and rewrite. This variable is used
+ * by USB gadget code as serial number of USB device.
+ */
+ ret = run_commandf("env set -f serial# %s", serial_string);
+ if (ret) {
+ ret = CMD_RET_FAILURE;
+ goto exit_ret;
+ }
+
+ amlogic_gadget_set_times(get_timer(0), 0);
+
+ ret = udc_device_get_by_index(0, &udc);
+ if (ret) {
+ pr_err("Failed to get UDC device: %d\n", ret);
+ ret = CMD_RET_FAILURE;
+ goto exit_ret;
+ }
+
+ g_dnl_clear_detach();
+ ret = g_dnl_register(mode);
+ if (ret) {
+ ret = CMD_RET_FAILURE;
+ goto exit_ret;
+ }
+
+ if (!g_dnl_board_usb_cable_connected()) {
+ pr_err("Usb cable isn't detected\n");
+ ret = CMD_RET_FAILURE;
+ goto exit;
+ }
+
+ identify_timeout_ms = (argc == 3) ? simple_strtoul(argv[2], NULL, 0) : 0;
+ start_timeout_ms = (argc >= 2) ? simple_strtoul(argv[1], NULL, 0) : 0;
+
+ while (1) {
+ if (g_dnl_detach())
+ break;
+
+ if (ctrlc())
+ break;
+
+ schedule();
+
+ if (start_timeout_ms && burn_start_time_ms) {
+ unsigned long start_delta = get_timer(burn_start_time_ms);
+
+ if (start_delta > start_timeout_ms) {
+ pr_err("Burn init timeout: %lu > %lu\n",
+ start_delta, start_timeout_ms);
+ ret = CMD_RET_FAILURE;
+ goto exit;
+ }
+ }
+
+ if (identify_timeout_ms && burn_identify_time_ms) {
+ unsigned long identify_delta_ms = get_timer(burn_identify_time_ms);
+
+ if (identify_delta_ms > identify_timeout_ms) {
+ pr_err("Burn identify timeout: %lu > %lu\n",
+ identify_delta_ms, identify_timeout_ms);
+ ret = CMD_RET_FAILURE;
+ goto exit;
+ }
+ }
+
+ dm_usb_gadget_handle_interrupts(udc);
+ }
+
+ ret = CMD_RET_SUCCESS;
+
+exit:
+ g_dnl_unregister();
+ g_dnl_clear_detach();
+ udc_device_put(udc);
+
+exit_ret:
+ if (saved_serial) {
+ env_set("serial#", saved_serial);
+ free(saved_serial);
+ }
+
+ return ret;
+}
diff --git a/cmd/meson/gadget.h b/cmd/meson/gadget.h
new file mode 100644
index 00000000000..a3771fb4746
--- /dev/null
+++ b/cmd/meson/gadget.h
@@ -0,0 +1,28 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (c) 2025 SaluteDevices, Inc.
+ * Author: Arseniy Krasnov <avkrasnov at salutedevices.com>
+ */
+
+#ifndef __GADGET_H__
+#define __GADGET_H__
+
+/**
+ * amlogic_gadget_set_times() - set timeouts for ADNL/Optimus protocols
+ *
+ * @start_time: timeout in ms, until device enters ADNL/Optimus mode
+ * @identify_time: timeout in ms, until device gets first command from host
+ */
+void amlogic_gadget_set_times(unsigned long start_time, unsigned long identify_time);
+
+/**
+ * amlogic_gadget_run() - switch device to USB gadget mode
+ *
+ * @mode: USB function name.
+ * @argc: number of arguments in 'argv'
+ * @argv: arguments, passed from command
+ * Return: 0 (CMD_RET_SUCCESS) if there is no error.
+ */
+int amlogic_gadget_run(const char *mode, int argc, char *const argv[]);
+
+#endif /* __GADGET_H__ */
--
2.30.1
^ permalink raw reply [flat|nested] 13+ messages in thread
* [rulkc] [PATCH v1 05/11] mtd: rawnand: meson: move some defines to dedicated header
2025-07-29 18:28 [rulkc] [PATCH v1 00/11] Amlogic: ADNL and Optimus protocols support avkrasnov
` (3 preceding siblings ...)
2025-07-29 18:29 ` [rulkc] [PATCH v1 04/11] usb: gadget: amlogic: common code for Amlogic flashing commands avkrasnov
@ 2025-07-29 18:29 ` avkrasnov
2025-07-29 18:29 ` [rulkc] [PATCH v1 06/11] arch: arm: meson: bootloader write support avkrasnov
` (6 subsequent siblings)
11 siblings, 0 replies; 13+ messages in thread
From: avkrasnov @ 2025-07-29 18:29 UTC (permalink / raw)
We need these values and macro to correctly write "bootloader"
partition in Optimus protocol, so let's export them to arch-meson
header and also provide more detailed information about each
macro.
Signed-off-by: Arseniy Krasnov <avkrasnov at salutedevices.com>
---
arch/arm/include/asm/arch-meson/rawnand.h | 38 +++++++++++++++++++++++
drivers/mtd/nand/raw/meson_nand.c | 13 +-------
2 files changed, 39 insertions(+), 12 deletions(-)
create mode 100644 arch/arm/include/asm/arch-meson/rawnand.h
diff --git a/arch/arm/include/asm/arch-meson/rawnand.h b/arch/arm/include/asm/arch-meson/rawnand.h
new file mode 100644
index 00000000000..81bf6ef7e37
--- /dev/null
+++ b/arch/arm/include/asm/arch-meson/rawnand.h
@@ -0,0 +1,38 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * (C) Copyright 2025 SaluteDevices, Inc.
+ */
+
+#ifndef __MESON_RAWNAND_H__
+#define __MESON_RAWNAND_H__
+
+#include <asm/arch/nand.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/rawnand.h>
+
+/* M2N means 'memory to NAND', e.g. write. */
+#define NFC_CMD_M2N ((0 << 17) | BIT(21))
+/* N2M means 'NAND to memory', e.g. read. */
+#define NFC_CMD_N2M ((1 << 17) | BIT(21))
+
+/*
+ * Macro to create value for read/write command.
+ *
+ * 'cmd_dir' - direction, e.g. read or write.
+ * 'ran' - scrambler mode (enabled or not).
+ * 'bch' - ECC mode (strength and size).
+ * 'short_mode' - short mode (enabled or not).
+ * 'page_size' - size of page.
+ * 'pages' - number of pages to access.
+ */
+#define CMDRWGEN(cmd_dir, ran, bch, short_mode, page_size, pages) \
+ ( \
+ (cmd_dir) | \
+ ((ran)) | \
+ ((bch) << 14) | \
+ ((short_mode) << 13) | \
+ (((page_size) & 0x7F) << 6) | \
+ ((pages) & 0x3F) \
+ )
+
+#endif
diff --git a/drivers/mtd/nand/raw/meson_nand.c b/drivers/mtd/nand/raw/meson_nand.c
index 28c851f103b..81122315f45 100644
--- a/drivers/mtd/nand/raw/meson_nand.c
+++ b/drivers/mtd/nand/raw/meson_nand.c
@@ -11,6 +11,7 @@
#include <nand.h>
#include <asm/io.h>
+#include <asm/arch/rawnand.h>
#include <dm.h>
#include <dm/device_compat.h>
#include <dm/ofnode.h>
@@ -34,8 +35,6 @@
#define NFC_CMD_AIL ((2 << 16) | (3 << 20))
#define NFC_CMD_AIH ((3 << 16) | (3 << 20))
#define NFC_CMD_SEED ((8 << 16) | (3 << 20))
-#define NFC_CMD_M2N ((0 << 17) | (2 << 20))
-#define NFC_CMD_N2M ((1 << 17) | (2 << 20))
#define NFC_CMD_RB BIT(20)
#define NFC_CMD_SCRAMBLER_ENABLE BIT(19)
#define NFC_CMD_SCRAMBLER_DISABLE 0
@@ -61,16 +60,6 @@
#define NFC_REG_PINS 0x30
#define NFC_REG_VER 0x38
-#define CMDRWGEN(cmd_dir, ran, bch, short_mode, page_size, pages) \
- ( \
- (cmd_dir) | \
- (ran) | \
- ((bch) << 14) | \
- ((short_mode) << 13) | \
- (((page_size) & 0x7f) << 6) | \
- ((pages) & 0x3f) \
- )
-
#define GENCMDDADDRL(adl, addr) ((adl) | ((addr) & 0xffff))
#define GENCMDDADDRH(adh, addr) ((adh) | (((addr) >> 16) & 0xffff))
#define GENCMDIADDRL(ail, addr) ((ail) | ((addr) & 0xffff))
--
2.30.1
^ permalink raw reply [flat|nested] 13+ messages in thread
* [rulkc] [PATCH v1 06/11] arch: arm: meson: bootloader write support
2025-07-29 18:28 [rulkc] [PATCH v1 00/11] Amlogic: ADNL and Optimus protocols support avkrasnov
` (4 preceding siblings ...)
2025-07-29 18:29 ` [rulkc] [PATCH v1 05/11] mtd: rawnand: meson: move some defines to dedicated header avkrasnov
@ 2025-07-29 18:29 ` avkrasnov
2025-07-29 18:29 ` [rulkc] [PATCH v1 07/11] arm: meson: a1: add A1_SYSCTRL_SEC_STICKY_REG2 avkrasnov
` (5 subsequent siblings)
11 siblings, 0 replies; 13+ messages in thread
From: avkrasnov @ 2025-07-29 18:29 UTC (permalink / raw)
BootROM of Amlogic SoCs that use ADNL/Optimus protocols needs
special layout of "bootloader" partition. So let's implement
functions that support bootloader writing on such SoCs. This is
prerequisite for ADNL/Optimus implementation.
We place such functions to 'arch/arm/mach-meson', because this
code is also needed by 'fastboot' protocol which can also write
"bootloader" partition.
Signed-off-by: Arseniy Krasnov <avkrasnov at salutedevices.com>
---
arch/arm/include/asm/arch-meson/nand.h | 34 +++
arch/arm/include/asm/arch-meson/spinand.h | 43 ++++
arch/arm/mach-meson/Kconfig | 31 +++
arch/arm/mach-meson/Makefile | 4 +-
arch/arm/mach-meson/rawnand.c | 291 ++++++++++++++++++++++
arch/arm/mach-meson/spinand.c | 158 ++++++++++++
6 files changed, 559 insertions(+), 2 deletions(-)
create mode 100644 arch/arm/include/asm/arch-meson/nand.h
create mode 100644 arch/arm/include/asm/arch-meson/spinand.h
create mode 100644 arch/arm/mach-meson/rawnand.c
create mode 100644 arch/arm/mach-meson/spinand.c
diff --git a/arch/arm/include/asm/arch-meson/nand.h b/arch/arm/include/asm/arch-meson/nand.h
new file mode 100644
index 00000000000..1f5a20d237b
--- /dev/null
+++ b/arch/arm/include/asm/arch-meson/nand.h
@@ -0,0 +1,34 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * (C) Copyright 2023-2024 SaluteDevices, Inc.
+ */
+
+#ifndef __MESON_NAND_H__
+#define __MESON_NAND_H__
+
+#define BOOT_LOADER "bootloader"
+#define BOOT_BL2 "bl2"
+#define BOOT_SPL "spl"
+#define BOOT_TPL "tpl"
+#define BOOT_FIP "fip"
+
+#define BL2_COPY_NUM (CONFIG_MESON_BL2_COPY_NUM)
+#define BL2_IMAGE_SIZE_PER_COPY (CONFIG_MESON_BL2_IMAGE_SIZE)
+#define BL2_TOTAL_PAGES 1024
+
+#define TPL_COPY_NUM (CONFIG_MESON_TPL_COPY_NUM)
+#define TPL_SIZE_PER_COPY 0x200000
+
+#define NAND_RSV_BLOCK_NUM 48
+
+/**
+ * meson_bootloader_write - write 'bootloader' partition to NAND
+ * according to the required layout. It will
+ * write BL2, TPL and info pages.
+ *
+ * @buf: buffer with BL2 and TPL
+ * @length: buffer length
+ * @return: 0 on success, -errno otherwise.
+ */
+int meson_bootloader_write(unsigned char *buf, size_t length);
+#endif
diff --git a/arch/arm/include/asm/arch-meson/spinand.h b/arch/arm/include/asm/arch-meson/spinand.h
new file mode 100644
index 00000000000..1628c8bd4e0
--- /dev/null
+++ b/arch/arm/include/asm/arch-meson/spinand.h
@@ -0,0 +1,43 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * (C) Copyright 2024 SaluteDevices, Inc.
+ */
+
+#ifndef __MESON_SPINAND_H__
+#define __MESON_SPINAND_H__
+
+#include <stdint.h>
+#include <string.h>
+#include <asm/types.h>
+#include <asm/arch-meson/nand.h>
+#include <linux/compiler_attributes.h>
+#include <linux/mtd/mtd.h>
+
+struct meson_spi_nand_info_page {
+ char magic[8]; /* Magic header of info page. */
+ /* Info page version, +1 when you update this struct. */
+ u8 version; /* 1 for now. */
+ u8 mode; /* 1 discrete, 0 compact. */
+ u8 bl2_num; /* bl2 copy number. */
+ u8 fip_num; /* fip copy number. */
+ union {
+ struct {
+ u8 rd_max; /* spi nand max read io. */
+ u8 oob_offset; /* User bytes offset. */
+ u8 planes_per_lun; /* number of planes per LUN. */
+ u8 rsv; /* Reserved gap. */
+ u32 fip_start; /* Start pages. */
+ u32 fip_pages; /* Pages per fip. */
+ u32 page_size; /* spi nand page size (bytes). */
+ u32 page_per_blk; /* Page number per block. */
+ u32 oob_size; /* Valid oob size (bytes). */
+ u32 bbt_start; /* BBT start pages. */
+ u32 bbt_valid; /* BBT valid offset pages. */
+ u32 bbt_size; /* BBT occupied bytes. */
+ } __packed spinand; /* spi nand. */
+ struct {
+ u32 reserved;
+ } emmc;
+ } dev;
+} __packed;
+#endif
diff --git a/arch/arm/mach-meson/Kconfig b/arch/arm/mach-meson/Kconfig
index 7570f48e25f..883ee78f49b 100644
--- a/arch/arm/mach-meson/Kconfig
+++ b/arch/arm/mach-meson/Kconfig
@@ -93,4 +93,35 @@ config SYS_BOARD
Based on this option board/<CONFIG_SYS_VENDOR>/<CONFIG_SYS_BOARD> will
be used.
+config MESON_BL2_COPY_NUM
+ depends on ADNL || FASTBOOT_FLASH
+ int "Number of BL2 copies written to storage"
+ default 8
+ help
+ The ADNL / Optimus / fastboot protocol writes several copies of BL2
+ bootloader during firmware update process.
+
+config MESON_TPL_COPY_NUM
+ depends on ADNL || FASTBOOT_FLASH
+ int "Number of TPL copies written to storage"
+ default 4
+ help
+ The ADNL / Optimus / fastboot protocol writes several copies of TPL
+ bootloader during firmware update process.
+
+config MESON_BL2_IMAGE_SIZE
+ depends on ADNL || FASTBOOT_FLASH
+ hex "Size of BL2 image"
+ default 0x10000
+ help
+ Size of BL2 image, passed by ADNL / Optimus / fastboot protocols.
+
+config MESON_BL2_IMAGE_OFFSET
+ depends on ADNL || FASTBOOT_FLASH
+ hex "Offset from info page to BL2 image"
+ default 0x800
+ help
+ For each copy of BL2, this is offset from info page to BL2 image. Used
+ by Optimus and fastboot protocols.
+
endif
diff --git a/arch/arm/mach-meson/Makefile b/arch/arm/mach-meson/Makefile
index 535b0878b91..f63f91dde35 100644
--- a/arch/arm/mach-meson/Makefile
+++ b/arch/arm/mach-meson/Makefile
@@ -4,6 +4,6 @@
obj-y += board-common.o sm.o board-info.o
obj-$(CONFIG_MESON_GX) += board-gx.o
-obj-$(CONFIG_MESON_AXG) += board-axg.o
+obj-$(CONFIG_MESON_AXG) += board-axg.o rawnand.o
obj-$(CONFIG_MESON_G12A) += board-g12a.o
-obj-$(CONFIG_MESON_A1) += board-a1.o
+obj-$(CONFIG_MESON_A1) += board-a1.o spinand.o
diff --git a/arch/arm/mach-meson/rawnand.c b/arch/arm/mach-meson/rawnand.c
new file mode 100644
index 00000000000..6b91674200e
--- /dev/null
+++ b/arch/arm/mach-meson/rawnand.c
@@ -0,0 +1,291 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * (C) Copyright 2024 SaluteDevices, Inc.
+ */
+
+#include <nand.h>
+#include <asm/types.h>
+#include <asm/arch/rawnand.h>
+#include <linux/compiler_attributes.h>
+#include <linux/mtd/mtd.h>
+#include <linux/sizes.h>
+
+struct raw_nand_setup {
+ union {
+ u32 d32;
+ struct {
+ unsigned cmd:22; /* NAND controller cmd to read. */
+ unsigned large_page:1; /* Page is large. */
+ unsigned no_rb:1; /* No RB. */
+ unsigned a2:1; /* A2 cmd enable flag. */
+ unsigned reserved25:1; /* Gap. */
+ unsigned page_list:1; /* Page list is enabled. */
+ unsigned sync_mode:2; /* Always 0 according vendor doc. */
+ unsigned size:2; /* Always 0 according vendor doc. */
+ unsigned active:1; /* Always 0 according vendor doc. */
+ } b;
+ } cfg;
+ u16 id;
+ u16 max;
+} __packed;
+
+struct raw_nand_cmd {
+ unsigned char type;
+ unsigned char val;
+} __packed;
+
+struct raw_ext_info {
+ u32 read_info;
+ u32 new_type;
+ u32 page_per_blk;
+ u32 xlc;
+ u32 ce_mask;
+ u32 boot_num;
+ u32 each_boot_pages;
+ /* for compatible reason */
+ u32 bbt_occupy_pages;
+ u32 bbt_start_block;
+} __packed;
+
+struct raw_fip_info {
+ u16 version;
+ u16 mode;
+ u32 fip_start;
+} __packed;
+
+struct raw_nand_info_page {
+ struct raw_nand_setup nand_setup;
+ unsigned char page_list[16];
+ struct raw_nand_cmd retry_usr[32];
+ struct raw_ext_info ext_info;
+ struct raw_fip_info fip_info;
+ u32 ddrp_start_page;
+} __packed;
+
+static void meson_raw_fill_oobbuf_with_magic(u8 *oobbuf, size_t oobavail)
+{
+ for (size_t j = 0; j < oobavail; j += 2) {
+ oobbuf[j] = 0x55;
+ oobbuf[j + 1] = 0xaa;
+ }
+}
+
+static int meson_raw_raw_nand_info_page_prepare(struct mtd_info *mtd, void *oobbuf)
+{
+ struct nand_chip *nand_chip = mtd_to_nand(mtd->parent);
+ struct raw_nand_info_page *p_nand_page0;
+ struct raw_ext_info *ext_info;
+ struct raw_nand_setup *nand_setup;
+ struct raw_fip_info *fip_info;
+ u32 config_data;
+
+ p_nand_page0 = oobbuf;
+ memset(p_nand_page0, 0x0, sizeof(struct raw_nand_info_page));
+ nand_setup = &p_nand_page0->nand_setup;
+ ext_info = &p_nand_page0->ext_info;
+
+ config_data = CMDRWGEN(NFC_CMD_N2M, 0, 1, 0, nand_chip->ecc.size >> 3,
+ nand_chip->ecc.steps);
+
+ config_data = config_data | BIT(23) | BIT(22) | BIT(21);
+ nand_setup->cfg.d32 = config_data;
+ nand_setup->id = 0;
+ nand_setup->max = 0;
+
+ ext_info->read_info = 1;
+ ext_info->page_per_blk = mtd->erasesize / mtd->writesize;
+ ext_info->ce_mask = 0x01;
+ ext_info->xlc = 1;
+ ext_info->boot_num = TPL_COPY_NUM;
+ ext_info->each_boot_pages = TPL_SIZE_PER_COPY / mtd->writesize;
+ ext_info->bbt_occupy_pages = 0x1;
+ ext_info->bbt_start_block = 0x14; /* Amlogic keeps BBT in hardcoded offset. */
+
+ p_nand_page0->ddrp_start_page = 0xb00;
+
+ fip_info = &p_nand_page0->fip_info;
+ fip_info->version = 1;
+ fip_info->mode = 1; /* Discrete mode. */
+ fip_info->fip_start = BL2_TOTAL_PAGES +
+ (NAND_RSV_BLOCK_NUM * ext_info->page_per_blk);
+
+ return 0;
+}
+
+static int meson_raw_write_info_page(struct mtd_info *mtd, loff_t offset, size_t *actual)
+{
+ int ret;
+ struct mtd_oob_ops ops;
+ u8 *datbuf = (u8 *)calloc(mtd->writesize, sizeof(u8));
+
+ if (!datbuf) {
+ pr_err("Failed to allocate data buf for info page\n");
+ return -ENOMEM;
+ }
+
+ meson_raw_raw_nand_info_page_prepare(mtd, datbuf);
+
+ memset(&ops, 0, sizeof(struct mtd_oob_ops));
+ ops.len = mtd->writesize;
+ ops.datbuf = datbuf;
+ ops.ooblen = mtd->oobavail;
+ ops.mode = MTD_OPS_AUTO_OOB;
+ ops.oobbuf = calloc(mtd->oobavail, sizeof(u8));
+
+ if (!ops.oobbuf) {
+ pr_err("Failed to allocate OOB buf for info page\n");
+ free(datbuf);
+ return -ENOMEM;
+ }
+
+ meson_raw_fill_oobbuf_with_magic(ops.oobbuf, mtd->oobavail);
+
+ ret = mtd_write_oob(mtd, offset, &ops);
+ *actual = ops.retlen;
+
+ free(ops.oobbuf);
+ free(datbuf);
+
+ return ret;
+}
+
+static int meson_raw_write_bl2(unsigned char *src_data)
+{
+ int ret;
+ size_t i;
+ nand_erase_options_t opts;
+ struct mtd_info *mtd;
+ size_t bl2_copy_limit;
+ struct nand_chip *nand;
+ size_t oobbuff_size;
+ struct mtd_oob_ops ops;
+ size_t src_size = BL2_IMAGE_SIZE_PER_COPY;
+ loff_t dest = 0;
+ size_t copy_num = BL2_COPY_NUM;
+
+ pr_info("Writing BL2\n");
+
+ mtd = get_mtd_device_nm(BOOT_LOADER);
+ if (IS_ERR_OR_NULL(mtd)) {
+ pr_err("Failed to get mtd device \"bootloader\" ret: %ld\n", PTR_ERR(mtd));
+ return PTR_ERR(mtd);
+ }
+
+ if (mtd->oobavail & 1) {
+ pr_err("Invalid OOB available bytes: %u\n", mtd->oobavail);
+ return -ENODEV;
+ }
+
+ memset(&opts, 0, sizeof(opts));
+ opts.offset = dest;
+ opts.length = mtd->size;
+ if (nand_erase_opts(mtd, &opts)) {
+ pr_err("Failed to erase \"bootloader\"\n");
+ return -EIO;
+ }
+
+ bl2_copy_limit = mtd->size / BL2_COPY_NUM;
+
+ memset(&ops, 0, sizeof(struct mtd_oob_ops));
+ ops.len = src_size;
+ ops.datbuf = src_data;
+ ops.ooblen = mtd->oobavail * (src_size / mtd->writesize + 1);
+ ops.mode = MTD_OPS_AUTO_OOB;
+
+ nand = mtd_to_nand(mtd->parent);
+ oobbuff_size = (mtd->writesize / nand->ecc.size) * ops.ooblen;
+
+ ops.oobbuf = calloc(oobbuff_size, sizeof(u8));
+
+ if (!ops.oobbuf) {
+ pr_err("Failed to allocate OOB buf for BL2\n");
+ return -ENOMEM;
+ }
+
+ meson_raw_fill_oobbuf_with_magic(ops.oobbuf, ops.ooblen);
+
+ for (i = 0; i < copy_num; i++) {
+ size_t actual = 0;
+ loff_t offset_in_partition = dest + i * bl2_copy_limit;
+
+ if (mtd_block_isbad(mtd, offset_in_partition)) {
+ pr_info("Skip badblock at %llx\n", offset_in_partition);
+ continue;
+ }
+
+ ret = meson_raw_write_info_page(mtd, offset_in_partition, &actual);
+ if (ret)
+ goto free_oobbuf;
+
+ offset_in_partition += CONFIG_MESON_BL2_IMAGE_OFFSET +
+ (actual - mtd->writesize);
+ ret = mtd_write_oob(mtd, offset_in_partition, &ops);
+ if (ret)
+ goto free_oobbuf;
+ }
+
+free_oobbuf:
+ free(ops.oobbuf);
+
+ return ret;
+}
+
+static int meson_raw_write_tpl(unsigned char *src_data, size_t src_size)
+{
+ int ret;
+ size_t i;
+ struct mtd_info *mtd;
+ nand_erase_options_t opts;
+ loff_t dest = 0;
+ size_t copy_num = TPL_COPY_NUM;
+
+ pr_info("Writing TPL\n");
+ mtd = get_mtd_device_nm(BOOT_TPL);
+
+ if (IS_ERR_OR_NULL(mtd)) {
+ pr_err("Failed to get mtd device \"tpl\" ret: %ld\n", PTR_ERR(mtd));
+ return PTR_ERR(mtd);
+ }
+
+ memset(&opts, 0, sizeof(nand_erase_options_t));
+ opts.offset = dest;
+ opts.length = mtd->size;
+ if (nand_erase_opts(mtd, &opts)) {
+ pr_err("Failed to erase \"tpl\"\n");
+ return -EIO;
+ }
+
+ for (i = 0; i < copy_num; i++) {
+ loff_t offset_in_partition = dest + i * TPL_SIZE_PER_COPY;
+
+ ret = nand_write_skip_bad(mtd, offset_in_partition, &src_size,
+ NULL, mtd->size / TPL_COPY_NUM, src_data, 0);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+int meson_bootloader_write(unsigned char *buf, size_t length)
+{
+ int ret;
+
+ if (!buf) {
+ pr_err("Empty buffer is received\n");
+ return -EINVAL;
+ }
+
+ ret = meson_raw_write_bl2(buf);
+ if (ret)
+ return ret;
+
+ /* Write FIP. FIP is also called TPL */
+ ret = meson_raw_write_tpl(buf + BL2_IMAGE_SIZE_PER_COPY,
+ length - BL2_IMAGE_SIZE_PER_COPY);
+ if (ret)
+ return ret;
+
+ pr_info("\"Bootloader (BL2+TPL)\" is written\n");
+ return 0;
+}
diff --git a/arch/arm/mach-meson/spinand.c b/arch/arm/mach-meson/spinand.c
new file mode 100644
index 00000000000..dbad4ba0e9f
--- /dev/null
+++ b/arch/arm/mach-meson/spinand.c
@@ -0,0 +1,158 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * (C) Copyright 2023 SaluteDevices, Inc.
+ */
+
+#include <nand.h>
+#include <asm/arch/spinand.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/nand.h>
+#include <linux/sizes.h>
+
+#define SPINAND_MAGIC "AMLIFPG"
+#define SPINAND_INFO_VER 1
+
+static void meson_bootloader_setup_info_page(struct meson_spi_nand_info_page *info_page,
+ struct mtd_info *mtd)
+{
+ struct nand_device *nand_dev;
+ u32 page_per_blk;
+
+ nand_dev = mtd_to_nanddev(mtd->parent);
+ page_per_blk = mtd->erasesize / mtd->writesize;
+ info_page->version = SPINAND_INFO_VER;
+ /* DISCRETE only */
+ info_page->mode = 1;
+ info_page->bl2_num = BL2_COPY_NUM;
+ info_page->fip_num = TPL_COPY_NUM;
+ info_page->dev.spinand.rd_max = 2;
+ info_page->dev.spinand.fip_start = BL2_TOTAL_PAGES + NAND_RSV_BLOCK_NUM * page_per_blk;
+ info_page->dev.spinand.fip_pages = TPL_SIZE_PER_COPY / mtd->writesize;
+ info_page->dev.spinand.page_size = mtd->writesize;
+ info_page->dev.spinand.page_per_blk = page_per_blk;
+ info_page->dev.spinand.oob_size = mtd->oobsize;
+ info_page->dev.spinand.oob_offset = 0;
+ info_page->dev.spinand.bbt_start = 0;
+ info_page->dev.spinand.bbt_valid = 0;
+ info_page->dev.spinand.bbt_size = 0;
+ info_page->dev.spinand.planes_per_lun = nand_dev->memorg.planes_per_lun;
+}
+
+static int meson_store_boot_write_bl2(struct mtd_info *mtd, void *buf, unsigned int copy_idx,
+ struct meson_spi_nand_info_page *info_page)
+{
+ unsigned int size_per_copy;
+ loff_t offset;
+ size_t retlen;
+ int ret;
+
+ size_per_copy = mtd->writesize * (BL2_TOTAL_PAGES / BL2_COPY_NUM);
+ offset = mtd->offset + (copy_idx * size_per_copy);
+ ret = mtd_write(mtd, offset, BL2_IMAGE_SIZE_PER_COPY, &retlen, buf);
+ if (ret)
+ return ret;
+
+ /*
+ * Info page is written directly after each BL2 image. For
+ * example let:
+ * BL2_TOTAL_PAGES = 1024
+ * BL2_COPY_NUM = 8
+ * BL2_IMAGE_SIZE_PER_COPY = 65536 = 0x10000
+ * size_per_copy = 2048 * (1024 / 8) = 0x40000
+ *
+ * BL2 image will be written to BL2_COPY_NUM
+ * locations:
+ * 0x00000
+ * 0x40000
+ * 0x80000
+ * 0xc0000
+ * 0x100000
+ * 0x140000
+ * 0x180000
+ * 0x1c0000
+ *
+ * Info pages will be written to BL2_COPY_NUM
+ * locations:
+ * 0x10000
+ * 0x50000
+ * 0x90000
+ * 0xd0000
+ * 0x110000
+ * 0x150000
+ * 0x190000
+ * 0x1d0000
+ *
+ * Here is the same, in picture:
+ *
+ * BL2_TOTAL_PAGES == 1024
+ * BL2_COPY_NUM == 8
+ * /---------------------------------------\
+ * | |
+ *
+ * |AB |AB |AB |AB |AB |AB |AB |AB |
+ * |____|____|____|____|____|____|____|____|
+ * ^ ^
+ * | |
+ * |____|__ size_per_copy == 0x40000
+ *
+ * A - is BL2 image. It is BL2_IMAGE_SIZE_PER_COPY == 0x10000 bytes.
+ * B - is info page.
+ */
+ offset += BL2_IMAGE_SIZE_PER_COPY;
+
+ return mtd_write(mtd, offset, sizeof(*info_page),
+ &retlen, (u8 *)info_page);
+}
+
+static int meson_store_boot_write_tpl(void *buf, size_t size, unsigned int copy_idx)
+{
+ loff_t offset;
+ struct mtd_info *mtd;
+ loff_t limit;
+ size_t retlen = 0;
+
+ mtd = get_mtd_device_nm(BOOT_TPL);
+ if (IS_ERR_OR_NULL(mtd))
+ return -ENODEV;
+
+ offset = mtd->offset + (copy_idx * TPL_SIZE_PER_COPY);
+ limit = offset + TPL_SIZE_PER_COPY;
+
+ return nand_write_skip_bad(mtd->parent, offset, &size, &retlen,
+ limit, buf, 0);
+}
+
+int meson_bootloader_write(unsigned char *buf, size_t length)
+{
+ struct meson_spi_nand_info_page info_page;
+ unsigned int copy_idx;
+ struct mtd_info *mtd;
+ int ret;
+
+ /* 'buf' contains BL2 and TPL. If there is only BL2 or
+ * even less - input data is invalid.
+ */
+ if (length <= BL2_IMAGE_SIZE_PER_COPY)
+ return -EINVAL;
+
+ mtd = get_mtd_device_nm(BOOT_LOADER);
+ if (IS_ERR_OR_NULL(mtd))
+ return -ENODEV;
+
+ meson_bootloader_setup_info_page(&info_page, mtd);
+
+ for (copy_idx = 0; copy_idx < BL2_COPY_NUM; copy_idx++) {
+ ret = meson_store_boot_write_bl2(mtd, buf, copy_idx, &info_page);
+ if (ret)
+ return ret;
+ }
+
+ for (copy_idx = 0; copy_idx < TPL_COPY_NUM; copy_idx++) {
+ ret = meson_store_boot_write_tpl(buf + BL2_IMAGE_SIZE_PER_COPY,
+ length - BL2_IMAGE_SIZE_PER_COPY, copy_idx);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
--
2.30.1
^ permalink raw reply [flat|nested] 13+ messages in thread
* [rulkc] [PATCH v1 07/11] arm: meson: a1: add A1_SYSCTRL_SEC_STICKY_REG2
2025-07-29 18:28 [rulkc] [PATCH v1 00/11] Amlogic: ADNL and Optimus protocols support avkrasnov
` (5 preceding siblings ...)
2025-07-29 18:29 ` [rulkc] [PATCH v1 06/11] arch: arm: meson: bootloader write support avkrasnov
@ 2025-07-29 18:29 ` avkrasnov
2025-07-29 18:29 ` [rulkc] [PATCH v1 08/11] usb: gadget: amlogic: implement ADNL protocol avkrasnov
` (4 subsequent siblings)
11 siblings, 0 replies; 13+ messages in thread
From: avkrasnov @ 2025-07-29 18:29 UTC (permalink / raw)
This register is used by ADNL protocol, to check boot source
of current U-boot instance.
Signed-off-by: Arseniy Krasnov <avkrasnov at salutedevices.com>
---
arch/arm/include/asm/arch-meson/a1.h | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/arch/arm/include/asm/arch-meson/a1.h b/arch/arm/include/asm/arch-meson/a1.h
index 86d1a68de8c..16e209ed932 100644
--- a/arch/arm/include/asm/arch-meson/a1.h
+++ b/arch/arm/include/asm/arch-meson/a1.h
@@ -13,6 +13,12 @@
#define A1_SYSCTRL_ADDR(off) (A1_SYSCTRL_BASE + ((off) << 2))
#define A1_SYSCTRL_SEC_STATUS_REG4 A1_SYSCTRL_ADDR(0xc4)
+/*
+ * Checked during boot. Tells, that previous code, e.g.
+ * executed before reboot requested to continue boot
+ * from USB. Used by ADNL protocol.
+ */
+#define A1_SYSCTRL_SEC_STICKY_REG2 A1_SYSCTRL_ADDR(0xe2)
#define A1_SYSCTRL_MEM_SIZE_MASK 0xFFFF0000
#define A1_SYSCTRL_MEM_SIZE_SHIFT 16
--
2.30.1
^ permalink raw reply [flat|nested] 13+ messages in thread
* [rulkc] [PATCH v1 08/11] usb: gadget: amlogic: implement ADNL protocol
2025-07-29 18:28 [rulkc] [PATCH v1 00/11] Amlogic: ADNL and Optimus protocols support avkrasnov
` (6 preceding siblings ...)
2025-07-29 18:29 ` [rulkc] [PATCH v1 07/11] arm: meson: a1: add A1_SYSCTRL_SEC_STICKY_REG2 avkrasnov
@ 2025-07-29 18:29 ` avkrasnov
2025-07-29 18:29 ` [rulkc] [PATCH v1 09/11] arm: meson: a1: ADNL protocol support avkrasnov
` (3 subsequent siblings)
11 siblings, 0 replies; 13+ messages in thread
From: avkrasnov @ 2025-07-29 18:29 UTC (permalink / raw)
This adds support for ADNL (Amlogic DowNLoad) protocol. This protocol
is supported by some Amlogic SoCs (a1, s4, c1, с2, sc2, t7, etc) in
BootRom code (e.g. it is already supported in "out of the box" mode).
It has similar goals as fastboot - upload and update firmware images
on the device. Difference is that, it is specific for Amlogic SoCs,
and for example it allows to upload and run image starting from ROM
boot stage, it also uses SoC specific registers and SMC commands.
Signed-off-by: Arseniy Krasnov <avkrasnov at salutedevices.com>
---
cmd/Kconfig | 7 +
cmd/meson/Makefile | 1 +
cmd/meson/adnl.c | 27 +
drivers/usb/gadget/Kconfig | 2 +
drivers/usb/gadget/Makefile | 1 +
drivers/usb/gadget/amlogic/Kconfig | 11 +
drivers/usb/gadget/amlogic/adnl/Kconfig | 29 +
drivers/usb/gadget/amlogic/adnl/Makefile | 4 +
drivers/usb/gadget/amlogic/adnl/adnl.h | 124 +++
.../gadget/amlogic/adnl/adnl_buff_manager.c | 316 +++++++
drivers/usb/gadget/amlogic/adnl/adnl_media.c | 235 +++++
.../usb/gadget/amlogic/adnl/adnl_storage.c | 140 +++
drivers/usb/gadget/amlogic/adnl/f_adnl.c | 835 ++++++++++++++++++
13 files changed, 1732 insertions(+)
create mode 100644 cmd/meson/adnl.c
create mode 100644 drivers/usb/gadget/amlogic/Kconfig
create mode 100644 drivers/usb/gadget/amlogic/adnl/Kconfig
create mode 100644 drivers/usb/gadget/amlogic/adnl/Makefile
create mode 100644 drivers/usb/gadget/amlogic/adnl/adnl.h
create mode 100644 drivers/usb/gadget/amlogic/adnl/adnl_buff_manager.c
create mode 100644 drivers/usb/gadget/amlogic/adnl/adnl_media.c
create mode 100644 drivers/usb/gadget/amlogic/adnl/adnl_storage.c
create mode 100644 drivers/usb/gadget/amlogic/adnl/f_adnl.c
diff --git a/cmd/Kconfig b/cmd/Kconfig
index 9e8a9c3cb3a..2f195d03848 100644
--- a/cmd/Kconfig
+++ b/cmd/Kconfig
@@ -1146,6 +1146,13 @@ config CMD_FASTBOOT
See doc/android/fastboot.rst for more information.
+config CMD_ADNL
+ bool "ADNL - Amlogic download protocol"
+ depends on ADNL
+ help
+ This command, "adnl", enables the ADNL listen mode, providing
+ the ability to listen in ADNL mode.
+
config CMD_FLASH
bool "flinfo, erase, protect"
default y
diff --git a/cmd/meson/Makefile b/cmd/meson/Makefile
index ee26c175cfe..1cf984378a3 100644
--- a/cmd/meson/Makefile
+++ b/cmd/meson/Makefile
@@ -3,3 +3,4 @@
# Copyright (c) 2022, SberDevices. All rights reserved.
obj-y += sm.o
+obj-$(CONFIG_CMD_ADNL) += gadget.o adnl.o
diff --git a/cmd/meson/adnl.c b/cmd/meson/adnl.c
new file mode 100644
index 00000000000..13aa1f2113e
--- /dev/null
+++ b/cmd/meson/adnl.c
@@ -0,0 +1,27 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (c) 2024 SaluteDevices, Inc.
+ * Author: Arseniy Krasnov <avkrasnov at salutedevices.com>
+ */
+
+#include <command.h>
+
+#include "gadget.h"
+
+static int do_adnl(struct cmd_tbl *cmdtp, int flag, int argc,
+ char *const argv[])
+{
+ return amlogic_gadget_run("usb_dnl_adnl", argc, argv);
+}
+
+U_BOOT_LONGHELP(adnl,
+ "- run as an ADNL USB device\n\n"
+ "With:\n"
+ "\t[init timeout]: time between command start and moment when\n"
+ "\t device enters gadget mode, in ms\n"
+ "\t[identify timeout]: time between moment when device enters gadget\n"
+ "\t mode and first 'identify' command from PC, in ms\n");
+
+U_BOOT_CMD(adnl, 3, 1, do_adnl,
+ "ADNL protocol mode",
+ adnl_help_text);
diff --git a/drivers/usb/gadget/Kconfig b/drivers/usb/gadget/Kconfig
index c815764c2bc..6391ceb9cfa 100644
--- a/drivers/usb/gadget/Kconfig
+++ b/drivers/usb/gadget/Kconfig
@@ -289,6 +289,8 @@ config USBNET_HOST_ADDR
endif # USB_ETHER
+source "drivers/usb/gadget/amlogic/Kconfig"
+
endif # USB_GADGET
if SPL_USB_GADGET
diff --git a/drivers/usb/gadget/Makefile b/drivers/usb/gadget/Makefile
index 6c16ad83f6e..2b6705c52be 100644
--- a/drivers/usb/gadget/Makefile
+++ b/drivers/usb/gadget/Makefile
@@ -28,6 +28,7 @@ obj-$(CONFIG_USB_FUNCTION_THOR) += f_thor.o
obj-$(CONFIG_DFU_OVER_USB) += f_dfu.o
obj-$(CONFIG_USB_FUNCTION_MASS_STORAGE) += f_mass_storage.o
obj-$(CONFIG_USB_FUNCTION_FASTBOOT) += f_fastboot.o f_fastboot_common.o
+obj-$(CONFIG_ADNL) += f_fastboot_common.o amlogic/adnl/
obj-$(CONFIG_USB_FUNCTION_SDP) += f_sdp.o
obj-$(CONFIG_USB_FUNCTION_ROCKUSB) += f_rockusb.o
obj-$(CONFIG_USB_FUNCTION_ACM) += f_acm.o
diff --git a/drivers/usb/gadget/amlogic/Kconfig b/drivers/usb/gadget/amlogic/Kconfig
new file mode 100644
index 00000000000..460dc6fe235
--- /dev/null
+++ b/drivers/usb/gadget/amlogic/Kconfig
@@ -0,0 +1,11 @@
+# SPDX-License-Identifier: GPL-2.0+
+#
+# (C) Copyright 2024 SaluteDevices, Inc.
+
+choice
+ prompt "Amlogic burning protocols"
+ optional
+
+source "drivers/usb/gadget/amlogic/adnl/Kconfig"
+
+endchoice
diff --git a/drivers/usb/gadget/amlogic/adnl/Kconfig b/drivers/usb/gadget/amlogic/adnl/Kconfig
new file mode 100644
index 00000000000..21438b716ef
--- /dev/null
+++ b/drivers/usb/gadget/amlogic/adnl/Kconfig
@@ -0,0 +1,29 @@
+# SPDX-License-Identifier: GPL-2.0+
+#
+# (C) Copyright 2023 SaluteDevices, Inc.
+
+config ADNL
+ bool "Enable ADNL protocol"
+ depends on USB_GADGET
+ depends on MESON_A1
+ depends on MTD_SPI_NAND
+ help
+ This enables ADNL protocol.
+
+if ADNL
+
+config ADNL_BUF_ADDR
+ hex "Define ADNL buffer address"
+ help
+ The ADNL protocol requires a large memory buffer for
+ downloads. Define this to the starting RAM address to use for
+ downloaded images.
+
+config ADNL_BUF_SIZE
+ hex "Define ADNL buffer size"
+ help
+ The ADNL protocol requires a large memory buffer for
+ downloads. This buffer should be as large as possible for a
+ platform. Define this to the size of available RAM for ADNL.
+
+endif
diff --git a/drivers/usb/gadget/amlogic/adnl/Makefile b/drivers/usb/gadget/amlogic/adnl/Makefile
new file mode 100644
index 00000000000..2b8c933a35d
--- /dev/null
+++ b/drivers/usb/gadget/amlogic/adnl/Makefile
@@ -0,0 +1,4 @@
+# SPDX-License-Identifier: GPL-2.0+
+
+subdir-ccflags-y += -I$(src)/../ -I$(src)/../../ -I$(src)/../../../../../
+obj-y += adnl_buff_manager.o adnl_media.o adnl_storage.o f_adnl.o
diff --git a/drivers/usb/gadget/amlogic/adnl/adnl.h b/drivers/usb/gadget/amlogic/adnl/adnl.h
new file mode 100644
index 00000000000..38d57282d31
--- /dev/null
+++ b/drivers/usb/gadget/amlogic/adnl/adnl.h
@@ -0,0 +1,124 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (c) 2023 SaluteDevices, Inc.
+ * Author: Arseniy Krasnov <avkrasnov at salutedevices.com>
+ */
+
+#ifndef _ADNL_H_
+#define _ADNL_H_
+
+#include <stdarg.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <linux/printk.h>
+#include <linux/types.h>
+#include <linux/mtd/mtd.h>
+#include <u-boot/sha1.h>
+
+#define ADNL_FAIL_STR "FAIL"
+
+/**
+ * adnl_store_logic_write() - Write non-boot partition.
+ *
+ * Write requested partition. This is kind of wrapper over
+ * 'nand_write_skip_bad()'.
+ *
+ * @name: partition name.
+ * @off: offset to write data.
+ * @size: size of buffer.
+ * @buf: buffer with data to write.
+ *
+ * @return: 0 on success, -errno otherwise.
+ */
+int adnl_store_logic_write(const char *name, loff_t off, size_t size, void *buf);
+
+/**
+ * adnl_store_logic_read() - Read non-boot partition.
+ *
+ * Read requested partition. This is kind of wrapper over
+ * 'nand_read_skip_bad()'.
+ *
+ * @name: partition name.
+ * @off: offset to read data from.
+ * @size: size of buffer.
+ * @buf: buffer to read to.
+ *
+ * @return: 0 on success, -errno otherwise.
+ */
+int adnl_store_logic_read(const char *name, loff_t off, size_t size, void *buf);
+
+extern char *fb_response_str;
+
+#define ADNL_ERR(fmt, ...) pr_err("[ADNL][ERR]: " fmt, ##__VA_ARGS__)
+#define ADNL_WARN(fmt, ...) pr_warn("[ADNL][WARN] " fmt, ##__VA_ARGS__)
+#define ADNL_DBG(fmt, ...) pr_debug("[ADNL][DBG] " fmt, ##__VA_ARGS__)
+
+/* This output macro doesn't depend on CONFIG_LOGLEVEL. */
+#define ADNL_MSG(fmt, ...) printf("[ADNL][MSG] " fmt, ##__VA_ARGS__)
+
+#define RESPONSE_LEN 128
+
+#define ADNL_DOWNLOAD_MEM_BASE CONFIG_ADNL_BUF_ADDR
+#define ADNL_DOWNLOAD_MEM_SIZE CONFIG_ADNL_BUF_SIZE
+#define ADNL_DOWNLOAD_SPARE_SZ (8UL << 10)
+
+#define ADNL_PART_IMG_FMT_RAW 0xabcd
+
+#define ADNL_PART_NAME_LEN 32
+
+struct adnl_img_common_param {
+ char part_name[ADNL_PART_NAME_LEN];
+ s64 img_sz_total;
+ s64 part_start_off;
+};
+
+struct adnl_img_dw_param {
+ struct adnl_img_common_param common_inf;
+ int img_fmt;
+};
+
+union adnl_img_trans_param {
+ struct adnl_img_common_param common_inf;
+ struct adnl_img_dw_param download;
+};
+
+struct adnl_usb_dw_info {
+ int data_size;
+ s64 file_offset;
+ char *data_buf;
+};
+
+int adnl_buffman_img_init(union adnl_img_trans_param *img_param);
+int adnl_buffman_fill_download_info(struct adnl_usb_dw_info **download_inf);
+int adnl_buffman_data_complete_download(const struct adnl_usb_dw_info *download_inf);
+int adnl_verify_partition_img(const char *verify_alg, const char *origsum_str,
+ char *ack);
+
+#define BULK_EP_MPS 512
+#define DWC_BLK_MAX_LEN (8 * BULK_EP_MPS)
+
+/**
+ * adnl_bootloader_read_n_verify() - Read boot partition.
+ *
+ * Read boot partition. This also checks, that all copies
+ * of both BL2 and TPL are valid (e.g. same).
+ *
+ * @buf: buffer to read to.
+ * @buf_len: size of buffer. Must be at least 'bootloader_len * 2'.
+ * @bootloader_len: size of BL2 + TPL images.
+ *
+ * @return: 0 on success, -errno otherwise.
+ */
+int adnl_bootloader_read_n_verify(u8 *buf, size_t buf_len, size_t bootloader_len);
+int adnl_media_check_image_size(s64 img_sz, const char *part_name);
+
+int adnl_mwrite_cmd_parser(unsigned long img_size, unsigned long part_off,
+ const char *img_format, const char *media,
+ const char *part_name, char *ack);
+
+#define ADNL_ERASE_NOTHING 0
+#define ADNL_ERASE_NORMAL 1
+#define ADNL_ERASE_ALL 3
+int adnl_erase_storage(int erase_mode);
+
+#endif /* _ADNL_H_ */
diff --git a/drivers/usb/gadget/amlogic/adnl/adnl_buff_manager.c b/drivers/usb/gadget/amlogic/adnl/adnl_buff_manager.c
new file mode 100644
index 00000000000..9e7a26ceb8e
--- /dev/null
+++ b/drivers/usb/gadget/amlogic/adnl/adnl_buff_manager.c
@@ -0,0 +1,316 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (c) 2023 SaluteDevices, Inc.
+ * Author: Arseniy Krasnov <avkrasnov at salutedevices.com>
+ */
+
+#include <fastboot.h>
+#include <hexdump.h>
+#include <mtd.h>
+#include <nand.h>
+#include <string.h>
+#include <time.h>
+#include <vsprintf.h>
+#include <asm/arch/spinand.h>
+#include <linux/errno.h>
+#include <linux/mtd/mtd.h>
+#include <u-boot/sha1.h>
+
+#include "adnl.h"
+
+#define RAW_IMG_TRANSFER_LEN (128 << 10)
+#define KB (1UL << 10)
+#define MB (KB << 10)
+
+static struct {
+ union adnl_img_trans_param img_trans_param;
+ int data_buf_cap;
+ char *data_buf;
+ int inited;
+} img_transfer_info;
+
+static struct adnl_usb_dw_info adnl_usb_dw_info;
+static s64 raw_img_file_offset;
+
+static int adnl_img_verify_sha1sum_other(int img_fmt, sha1_context *ctx,
+ s64 img_total_len,
+ char *part, s64 part_base)
+{
+ unsigned char *verify_buff = (unsigned char *)ADNL_DOWNLOAD_MEM_BASE;
+
+ if (!strncmp(BOOT_LOADER, part, ADNL_PART_NAME_LEN)) {
+ int ret;
+
+ ret = adnl_bootloader_read_n_verify(verify_buff, ADNL_DOWNLOAD_MEM_SIZE,
+ img_total_len);
+ if (ret)
+ return ret;
+
+ sha1_update(ctx, verify_buff, img_total_len);
+ return 0;
+ } else {
+ size_t verify_buff_len;
+ size_t this_verify_len;
+ size_t verify_len;
+
+ if (img_fmt != ADNL_PART_IMG_FMT_RAW) {
+ fastboot_response(ADNL_FAIL_STR, fb_response_str,
+ "unknown image format %x\n", img_fmt);
+ return -EINVAL;
+ }
+
+ this_verify_len = 0;
+ verify_len = 0;
+ /* We use 2MB buffer for each SHA1 step.
+ * Seems this is optimal value here. It
+ * was taken from vendor's bootloader.
+ */
+ verify_buff_len = 2 * MB;
+
+ /* Read partition part by part and update SHA1. */
+ for (; verify_len < img_total_len; verify_len += this_verify_len) {
+ int ret;
+
+ this_verify_len = img_total_len - verify_len;
+ this_verify_len = (this_verify_len > verify_buff_len) ?
+ verify_buff_len : this_verify_len;
+
+ ret = adnl_store_logic_read(part, verify_len + part_base,
+ this_verify_len, verify_buff);
+
+ if (ret) {
+ fastboot_response(ADNL_FAIL_STR, fb_response_str,
+ "store logic read failed\n");
+ return ret;
+ }
+
+ sha1_update(ctx, verify_buff, this_verify_len);
+ }
+ }
+
+ return 0;
+}
+
+static int adnl_buffman_img_get_sha1sum(unsigned char *verify_sum)
+{
+ static sha1_context ctx;
+ struct adnl_img_common_param *img_common_param;
+ struct adnl_img_dw_param *img_down_param;
+ int ret;
+
+ img_down_param = &img_transfer_info.img_trans_param.download;
+ img_common_param = &img_down_param->common_inf;
+
+ sha1_starts(&ctx);
+
+ ret = adnl_img_verify_sha1sum_other(img_down_param->img_fmt, &ctx,
+ img_common_param->img_sz_total,
+ img_common_param->part_name,
+ img_common_param->part_start_off);
+ if (ret)
+ return ret;
+
+ sha1_finish(&ctx, verify_sum);
+
+ return 0;
+}
+
+int adnl_verify_partition_img(const char *verify_alg, const char *origsum_str,
+ char *ack)
+{
+ unsigned char gensum[SHA1_SUM_LEN] = { 0 };
+ char gensum_str[SHA1_SUM_LEN * 2 + 1];
+ int ret;
+
+ ret = strcmp(verify_alg, "sha1sum");
+ if (ret) {
+ fastboot_response(ADNL_FAIL_STR, ack,
+ "algorithm '%s' unsupported\n", verify_alg);
+ return -EINVAL;
+ }
+
+ if (strlen(origsum_str) != SHA1_SUM_LEN * 2) {
+ fastboot_response(ADNL_FAIL_STR, ack,
+ "invalid hash len for algo %s\n", verify_alg);
+ return -EINVAL;
+ }
+
+ ret = adnl_buffman_img_get_sha1sum(gensum);
+ if (ret) {
+ ADNL_ERR("failed to check SHA1: %d", ret);
+ return ret;
+ }
+
+ bin2hex(gensum_str, gensum, SHA1_SUM_LEN);
+
+ ret = strncmp(gensum_str, origsum_str, SHA1_SUM_LEN * 2);
+ if (ret) {
+ fastboot_response(ADNL_FAIL_STR, ack, "checksum not match %s != %s\n",
+ gensum_str, origsum_str);
+ return -EINVAL;
+ }
+
+ ADNL_MSG("checksum is correct\n");
+ return 0;
+}
+
+static int adnl_nand_erase(const char *partition)
+{
+ struct erase_info ei = { 0 };
+ struct mtd_info *mtd;
+
+ mtd = get_mtd_device_nm(partition);
+ if (IS_ERR(mtd))
+ return -ENODEV;
+
+ ei.mtd = mtd;
+ ei.len = mtd->size;
+ ei.addr = 0;
+
+ return mtd_erase(mtd, &ei);
+}
+
+int adnl_buffman_img_init(union adnl_img_trans_param *img_param)
+{
+ struct adnl_img_common_param *common_inf;
+ char *part_name;
+ s64 img_size;
+ int ret;
+
+ common_inf = &img_param->common_inf;
+
+ memcpy(&img_transfer_info.img_trans_param, img_param,
+ sizeof(union adnl_img_trans_param));
+ img_transfer_info.data_buf = (char *)ADNL_DOWNLOAD_MEM_BASE;
+ img_transfer_info.data_buf_cap = ADNL_DOWNLOAD_MEM_SIZE;
+ img_transfer_info.inited = 1;
+ img_size = common_inf->img_sz_total;
+ part_name = common_inf->part_name;
+
+ ret = adnl_media_check_image_size(img_size, part_name);
+ if (ret) {
+ ADNL_ERR("failed to check image size\n");
+ return ret;
+ }
+
+ ADNL_MSG("erasing MTD partition '%s'\n", part_name);
+
+ ret = adnl_nand_erase(part_name);
+ if (ret) {
+ ADNL_ERR("erasing MTD partition '%s' failed %i\n", part_name, ret);
+ return ret;
+ }
+
+ if (!strncmp(part_name, BOOT_LOADER, ADNL_PART_NAME_LEN)) {
+ /*
+ * If passed partition name is "bootloader", then
+ * erase "tpl" partition also. This is needed,
+ * because image for "bootloader" partition also
+ * contains image for "tpl".
+ */
+ part_name = BOOT_TPL;
+ ret = adnl_nand_erase(part_name);
+ if (ret) {
+ ADNL_ERR("erasing MTD partition '%s' failed %i\n", part_name, ret);
+ return ret;
+ }
+ }
+
+ raw_img_file_offset = 0;
+
+ return 0;
+}
+
+static int adnl_buffman_fill_download_info_rawimg(struct adnl_img_dw_param *img_param)
+{
+ struct adnl_img_common_param *cmn_inf = &img_param->common_inf;
+ s64 left_len;
+
+ adnl_usb_dw_info.file_offset = raw_img_file_offset;
+ left_len = cmn_inf->img_sz_total - raw_img_file_offset;
+
+ if (strncmp(BOOT_LOADER, cmn_inf->part_name, ADNL_PART_NAME_LEN))
+ /* For non "bootloader" partitions send data with
+ * smaller blocks - such partitions are significantly
+ * bigger.
+ */
+ adnl_usb_dw_info.data_size = min_t(s64, left_len,
+ RAW_IMG_TRANSFER_LEN);
+ else
+ adnl_usb_dw_info.data_size = left_len;
+
+ adnl_usb_dw_info.data_buf = (char *)ADNL_DOWNLOAD_MEM_BASE;
+
+ /* For next raw img download. */
+ raw_img_file_offset += adnl_usb_dw_info.data_size;
+
+ return 0;
+}
+
+int adnl_buffman_fill_download_info(struct adnl_usb_dw_info **download_inf)
+{
+ struct adnl_img_dw_param *img_param;
+ int ret;
+
+ *download_inf = NULL;
+
+ if (!img_transfer_info.inited) {
+ fastboot_response(ADNL_FAIL_STR, fb_response_str,
+ "invalid state %d\n",
+ img_transfer_info.inited);
+ return -EINVAL;
+ }
+
+ img_param = &img_transfer_info.img_trans_param.download;
+
+ if (img_param->img_fmt != ADNL_PART_IMG_FMT_RAW) {
+ fastboot_response(ADNL_FAIL_STR, fb_response_str,
+ "unknown image format %x\n", img_param->img_fmt);
+ return -EINVAL;
+ }
+
+ ret = adnl_buffman_fill_download_info_rawimg(img_param);
+ if (ret)
+ return ret;
+
+ *download_inf = &adnl_usb_dw_info;
+
+ return ret;
+}
+
+static int adnl_buffman_complete_raw(const struct adnl_usb_dw_info *download_inf,
+ const char *part_name,
+ u8 *data_buf,
+ int this_transfer_len)
+{
+ /* 'bootloader' partition has special layout on storage, so we
+ * have special handler to work with this partition.
+ */
+ if (!strncmp(BOOT_LOADER, part_name, ADNL_PART_NAME_LEN))
+ return meson_bootloader_write(data_buf, this_transfer_len);
+ else
+ return adnl_store_logic_write(part_name,
+ download_inf->file_offset,
+ this_transfer_len, data_buf);
+}
+
+int adnl_buffman_data_complete_download(const struct adnl_usb_dw_info *download_inf)
+{
+ struct adnl_img_common_param *cmn_inf;
+ struct adnl_img_dw_param *down_inf;
+
+ down_inf = &img_transfer_info.img_trans_param.download;
+
+ if (down_inf->img_fmt != ADNL_PART_IMG_FMT_RAW) {
+ fastboot_response(ADNL_FAIL_STR, fb_response_str,
+ "unknown image format %x\n", down_inf->img_fmt);
+ return -EINVAL;
+ }
+
+ cmn_inf = &img_transfer_info.img_trans_param.common_inf;
+
+ return adnl_buffman_complete_raw(download_inf,
+ cmn_inf->part_name,
+ download_inf->data_buf,
+ download_inf->data_size);
+}
diff --git a/drivers/usb/gadget/amlogic/adnl/adnl_media.c b/drivers/usb/gadget/amlogic/adnl/adnl_media.c
new file mode 100644
index 00000000000..ec8b8cd630a
--- /dev/null
+++ b/drivers/usb/gadget/amlogic/adnl/adnl_media.c
@@ -0,0 +1,235 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (c) 2023 SaluteDevices, Inc.
+ * Author: Arseniy Krasnov <avkrasnov at salutedevices.com>
+ */
+
+#include <fastboot.h>
+#include <nand.h>
+#include <stdio.h>
+#include <string.h>
+#include <vsprintf.h>
+#include <asm/arch/spinand.h>
+#include <linux/mtd/mtd.h>
+#include <linux/sizes.h>
+
+#include "adnl.h"
+
+static int adnl_store_boot_read_tpl(void *buf, size_t size, unsigned int copy_idx)
+{
+ size_t retlen;
+ loff_t offset, limit;
+ struct mtd_info *mtd;
+
+ mtd = get_mtd_device_nm(BOOT_TPL);
+ if (IS_ERR_OR_NULL(mtd))
+ return -ENODEV;
+
+ offset = mtd->offset + (copy_idx * TPL_SIZE_PER_COPY);
+ limit = offset + TPL_SIZE_PER_COPY;
+
+ return nand_read_skip_bad(mtd->parent, offset, &size, &retlen, limit, buf);
+}
+
+static int adnl_store_boot_read_bl2(void *buf, unsigned int copy_idx)
+{
+ unsigned int size_per_copy;
+ struct mtd_info *mtd;
+ loff_t offset;
+ size_t length;
+
+ mtd = get_mtd_device_nm(BOOT_LOADER);
+ if (IS_ERR_OR_NULL(mtd))
+ return -ENODEV;
+
+ size_per_copy = mtd->writesize * (BL2_TOTAL_PAGES / BL2_COPY_NUM);
+ offset = mtd->offset + (copy_idx * size_per_copy);
+ length = BL2_IMAGE_SIZE_PER_COPY;
+
+ return nand_read(mtd->parent, offset, &length, buf);
+}
+
+int adnl_bootloader_read_n_verify(u8 *buf, size_t buf_len, size_t bootloader_len)
+{
+ unsigned int copy_idx;
+ int ret;
+
+ /* 'bootloader_len' consists of BL2 and TPL. This function
+ * reads BL2 to 'buf' and then places TPL directly after it.
+ * Also this function checks, that all copies of BL2 and TPL
+ * are same. To do that, we need extra memory in 'buf': first
+ * copy is read to the start of the buffer, then every next
+ * copy is placed after the first one and compared to it.
+ *
+ * 'buf': | |
+ * |________________________________|
+ * ^ ^
+ * |________________________________|
+ * 'buf_len'
+ *
+ *
+ * 1) During BL2 read and checks:
+ *
+ * | BL2 1st | |
+ * |_________|_________________________|
+ *
+ *
+ * | BL2 1st | BL2 Nst | |
+ * |_________|_________|_______________|
+ * ^ ^ ^
+ * |_________|_________|
+ * 'memcmp()'
+ *
+ * 2) During TPL read and checks:
+ *
+ * | BL2 1st | TPL 1st | |
+ * |_________|_________|_______________|
+ *
+ *
+ * | BL2 1st | TPL 1st | TPL Nst | |
+ * |_________|_________|_________|_____|
+ * ^ ^ ^
+ * |_________|_________|
+ * 'memcmp()'
+ *
+ * 3) Final state of the buffer:
+ *
+ * | BL2 1st | TPL 1st | |
+ * |_________|_________|_______________|
+ * ^ ^
+ * |___________________|
+ * 'bootloader_len'
+ *
+ * For simple and rude check this is enough:
+ */
+ if ((bootloader_len << 1) > buf_len)
+ return -EINVAL;
+
+ for (copy_idx = 0; copy_idx < BL2_COPY_NUM; copy_idx++) {
+ void *data_buf = buf + BL2_IMAGE_SIZE_PER_COPY * !!copy_idx;
+
+ ret = adnl_store_boot_read_bl2(data_buf, copy_idx);
+ if (ret) {
+ fastboot_response(ADNL_FAIL_STR, fb_response_str,
+ "failed to read part BL2:'%u'\n", copy_idx);
+ return ret;
+ }
+
+ /* All copies must be valid. Otherwise - something goes wrong. */
+ if (copy_idx) {
+ if (memcmp(buf, data_buf, BL2_IMAGE_SIZE_PER_COPY)) {
+ fastboot_response(ADNL_FAIL_STR, fb_response_str,
+ "boot part 'BL2':'%u' mismatch\n",
+ copy_idx);
+ return -EIO;
+ }
+ }
+ }
+
+ /* Now we place TPL to 'buf'. 'bootloader_len' will be TPL length. */
+ buf += BL2_IMAGE_SIZE_PER_COPY;
+ bootloader_len -= BL2_IMAGE_SIZE_PER_COPY;
+
+ for (copy_idx = 0; copy_idx < TPL_COPY_NUM; copy_idx++) {
+ void *data_buf = buf + bootloader_len * !!copy_idx;
+
+ ret = adnl_store_boot_read_tpl(data_buf, bootloader_len, copy_idx);
+ if (ret) {
+ fastboot_response(ADNL_FAIL_STR, fb_response_str,
+ "failed to read part TPL:'%i'\n", copy_idx);
+ return ret;
+ }
+
+ /* All copies must be valid. Otherwise - something goes wrong. */
+ if (copy_idx) {
+ if (memcmp(buf, data_buf, bootloader_len)) {
+ fastboot_response(ADNL_FAIL_STR, fb_response_str,
+ "boot part TPL:'%i' mismatch\n", copy_idx);
+ return -EIO;
+ }
+ }
+ }
+
+ return 0;
+}
+
+static uint64_t adnl_store_part_size(const char *part_name)
+{
+ struct mtd_info *mtd;
+
+ if (!strcmp(part_name, BOOT_BL2) ||
+ !strcmp(part_name, BOOT_SPL)) {
+ mtd = get_mtd_device_nm(BOOT_LOADER);
+ } else if (!strcmp(part_name, BOOT_TPL) ||
+ !strcmp(part_name, BOOT_FIP)) {
+ mtd = get_mtd_device_nm(BOOT_TPL);
+ } else {
+ mtd = get_mtd_device_nm(part_name);
+ }
+
+ if (IS_ERR_OR_NULL(mtd))
+ return 0;
+
+ return mtd->size;
+}
+
+int adnl_media_check_image_size(s64 img_sz, const char *part_name)
+{
+ u64 part_cap;
+
+ if (!strcmp(BOOT_LOADER, part_name)) {
+ unsigned int boot_size = BL2_IMAGE_SIZE_PER_COPY + TPL_SIZE_PER_COPY;
+
+ if (img_sz > boot_size) {
+ fastboot_response(ADNL_FAIL_STR, fb_response_str,
+ "image size %lld > boot size %u\n",
+ img_sz, boot_size);
+ return -EINVAL;
+ }
+ return 0;
+ }
+
+ part_cap = adnl_store_part_size(part_name);
+ if (!part_cap) {
+ ADNL_ERR("failed to get size for part %s\n", part_name);
+ return -ENODEV;
+ }
+
+ if (img_sz > part_cap) {
+ ADNL_ERR("img size %lld > part size %llu\n", img_sz, part_cap);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+int adnl_mwrite_cmd_parser(unsigned long img_size, unsigned long part_off,
+ const char *img_format, const char *media,
+ const char *part_name, char *ack)
+{
+ union adnl_img_trans_param img_trans_param = { 0 };
+ struct adnl_img_dw_param *img_download_param;
+ struct adnl_img_common_param *common_info;
+
+ if (strcmp(img_format, "normal")) {
+ fastboot_response(ADNL_FAIL_STR, ack,
+ "unknown image format %s", img_format);
+ return -EINVAL;
+ }
+
+ if (strcmp(media, "store")) {
+ fastboot_response(ADNL_FAIL_STR, ack,
+ "unknown media '%s'", media);
+ return -EINVAL;
+ }
+
+ img_download_param = &img_trans_param.download;
+ common_info = &img_download_param->common_inf;
+ common_info->img_sz_total = img_size;
+ common_info->part_start_off = part_off;
+
+ img_download_param->img_fmt = ADNL_PART_IMG_FMT_RAW;
+ strlcpy(common_info->part_name, part_name, ADNL_PART_NAME_LEN);
+
+ return adnl_buffman_img_init(&img_trans_param);
+}
diff --git a/drivers/usb/gadget/amlogic/adnl/adnl_storage.c b/drivers/usb/gadget/amlogic/adnl/adnl_storage.c
new file mode 100644
index 00000000000..99dc4af7627
--- /dev/null
+++ b/drivers/usb/gadget/amlogic/adnl/adnl_storage.c
@@ -0,0 +1,140 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (c) 2023 SaluteDevices, Inc.
+ * Author: Arseniy Krasnov <avkrasnov at salutedevices.com>
+ */
+
+#include <nand.h>
+#include <string.h>
+#include <asm/arch/spinand.h>
+#include <linux/printk.h>
+#include <linux/mtd/mtd.h>
+#include <linux/sizes.h>
+#include <linux/mtd/spinand.h>
+
+#include "adnl.h"
+
+static int adnl_store_get_offset(const char *partname,
+ loff_t *retoff, loff_t off)
+{
+ struct mtd_info *mtd_info;
+
+ if (!strcmp(partname, BOOT_BL2))
+ mtd_info = get_mtd_device_nm(BOOT_LOADER);
+ else
+ mtd_info = get_mtd_device_nm(partname);
+
+ if (IS_ERR_OR_NULL(mtd_info))
+ return -ENODEV;
+
+ *retoff = off + mtd_info->offset;
+
+ return 0;
+}
+
+int adnl_store_logic_write(const char *name, loff_t off, size_t size, void *buf)
+{
+ struct mtd_info *mtd;
+ size_t retlen;
+ loff_t offset;
+ int ret;
+
+ ret = adnl_store_get_offset(name, &offset, off);
+ if (ret)
+ return ret;
+
+ mtd = get_mtd_device_nm(name);
+ if (IS_ERR_OR_NULL(mtd))
+ return -ENODEV;
+
+ return nand_write_skip_bad(mtd, off, &size, &retlen, mtd->size, buf, 0);
+}
+
+int adnl_store_logic_read(const char *name, loff_t off, size_t size, void *buf)
+{
+ struct mtd_info *mtd;
+ size_t retlen;
+ loff_t offset;
+ int ret;
+
+ ret = adnl_store_get_offset(name, &offset, off);
+ if (ret)
+ return ret;
+
+ mtd = get_mtd_device_nm(name);
+ if (IS_ERR_OR_NULL(mtd))
+ return -ENODEV;
+
+ return nand_read_skip_bad(mtd, off, &size, &retlen, mtd->size, buf);
+}
+
+static bool adnl_must_skip_block(struct mtd_info *mtd, off_t offset)
+{
+ if (mtd_block_isbad(mtd, offset))
+ return true;
+
+ if (mtd_block_isreserved(mtd, offset))
+ return true;
+
+ return false;
+}
+
+int adnl_erase_storage(int erase_mode)
+{
+ switch (erase_mode) {
+ case ADNL_ERASE_NOTHING:
+ ADNL_MSG("Erase nothing...\n");
+ return 0;
+ case ADNL_ERASE_ALL:
+ /* Full erase includes erasing reserved areas:
+ * 1) "key" area.
+ * 2) Bad block table.
+ * 3) Some other blocks, marked as "bad" or "reserved".
+ *
+ * Currently it is not supported.
+ */
+ ADNL_MSG("Full erase is not supported, fallback to normal\n");
+ /* Fall through */
+ case ADNL_ERASE_NORMAL: {
+ struct mtd_info *mtd;
+ off_t offset;
+
+ ADNL_MSG("Erase normal...\n");
+ /*
+ * "bootloader" is always present in ADNL, so use it to
+ * get MTD device for the entire NAND chip.
+ */
+ mtd = get_mtd_device_nm(BOOT_LOADER);
+ if (!mtd || !mtd->parent)
+ return -ENODEV;
+
+ mtd = mtd->parent;
+
+ for (offset = 0; offset < mtd->size; offset += mtd->erasesize) {
+ struct erase_info erase_op = {};
+ int ret;
+
+ if (adnl_must_skip_block(mtd, offset))
+ continue;
+
+ erase_op.mtd = mtd;
+ erase_op.addr = offset;
+ erase_op.len = mtd->erasesize;
+
+ ret = mtd_erase(mtd, &erase_op);
+ if (ret) {
+ ADNL_ERR("Can't erase block at %lx\n",
+ offset);
+ return ret;
+ }
+ }
+
+ ADNL_MSG("Erased ok\n");
+
+ return 0;
+ }
+ default:
+ ADNL_ERR("Unknown erase mode %d\n", erase_mode);
+ return -EINVAL;
+ }
+}
diff --git a/drivers/usb/gadget/amlogic/adnl/f_adnl.c b/drivers/usb/gadget/amlogic/adnl/f_adnl.c
new file mode 100644
index 00000000000..0b772acc4e4
--- /dev/null
+++ b/drivers/usb/gadget/amlogic/adnl/f_adnl.c
@@ -0,0 +1,835 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (c) 2023 SaluteDevices, Inc.
+ * Author: Arseniy Krasnov <avkrasnov at salutedevices.com>
+ */
+/*
+ * Implementation of ADNL protocol.
+ *
+ * ADNL is Amlogic protocol for updating firmware images. It
+ * looks like fastboot, but:
+ * 1) Works only on Amlogic boards, as it uses SoC specific features in
+ * its commands (for example special registers, SMC calls, bootROM
+ * behaviour).
+ * 2) It uses Amlogic specific image format to update firmware - such
+ * image contains different images inside - BL2, U-boot and partitions.
+ * 3) It works only over USB transport.
+ * 4) Minimal part (to run BL2 and U-boot from memory) is implemented in
+ * the bootROM code inside Amlogic boards, so this protocol is a good
+ * tool to "resurrect" boards with for example corrupted bootloader.
+ *
+ * There are two possible operating modes for this protocol:
+ * * Updating from bootROM.
+ * 1) Device stops booting in the boot ROM and enters USB gadget mode
+ * introducing itself as ADNL device.
+ * 2) PC adnl-client sends BL2 and U-boot to the board, which are
+ * executed in memory and finally U-boot checks sticky registers
+ * and when it detects that we in ADNL mode, it runs command 'adnl'
+ * (e.g. without continue booting to the command line or autoboot
+ * sequence).
+ * 3) Boards enters USB gadget mode again still introducing itself as
+ * ADNL device.
+ * 4) PC application continue ADNL operations (see NOTES#1 for more
+ * details).
+ *
+ * * Updating from U-boot command line.
+ * 1) U-boot runs 'adnl' command (run from script or entered by user).
+ * 2) ADNL logic in U-boot tells BL1 not to load BL2 and next loaders
+ * from storage, but receive it by USB data path. This feature is
+ * implemented by the special SMC call.
+ * 3) ADNL logic in U-boot reboots board.
+ * 4) BL1 receives BL2 and U-boot from USB. Now Goto 2) from list
+ * above (e.g. 'Updating from bootROM').
+ *
+ * Protocol operation flow:
+ *
+ * To start protocol operations PC sends several commands:
+ * 'getvar identify' - tells PC some hardcoded fw version.
+ * 'oem setvar burnsteps' - performs writes to sticky registers.
+ * 'oem disk_initial' - this is suppressed in this implementation by
+ * always success wrapper.
+ * 'getvar secureboot' - this is suppressed in this implementation by
+ * always success wrapper.
+ *
+ * After this exchange, stage for data transmission and writing is
+ * started. Data is transmitted and written in 'per-partition' way.
+ *
+ * To send and write each partition the following commands sequence is
+ * executed:
+ *
+ * 1) PC sends 'oem mwrite <size> normal store <part name>'. This will
+ * erase MTD partition and tells ADNL logic on the board to receive
+ * specified number of bytes and later write all of them to the
+ * <part name>. More, <size> here is size of the entire image.
+ *
+ * 2) Devices replies 'OKAY'.
+ *
+ * 3) PC sends 'mwrite verify=addsum'.
+ *
+ * 4) Device replies 'DATAOUT:<size>:<offs>', where <size> is number
+ * of bytes which device is ready to process and <offs> is offset in
+ * image to send data from. Note, <size> here is size of entire image
+ * only for "bootloader" image, for another images it is 128KB. See
+ * RAW_IMG_TRANSFER_LEN.
+ *
+ * 5) PC sends data. There are no protocol commands in this type of
+ * packet - just raw data. Size of packet is USB specific and is
+ * not related to the <size> from 2).
+ *
+ * 6) Device replies with empty packet.
+ *
+ * 7) Device checks that requested amount of data (<size> from 4)) is
+ * received. If no, goto 3) (e.g. continue receiving data from PC).
+ * If so, device performs simple checksum verification using algorithm
+ * from 'adnl_csum_special()' (because of parameter 'verify=addsum') from
+ * 3). Original checksum is transferred in the tail bytes of the last
+ * USB packet for the current portion of data. If verification is
+ * successful, device write data to storage and replies 'OKAY'.
+ *
+ * 8) If not all bytes from 1) are processed, goto 3) (e.g. receive next
+ * portion of data from PC starting from next offset in the image).
+ *
+ * 9) PC sends 'oem verify <algo> <hash>'. It means check partition from
+ * 1) with algorithm <algo>, it's hash must be equal to the <hash>.
+ * Currently only 'sha1sum' is supported as <algo>. Note, in this step
+ * device reads whole partition part by part (as verification algo
+ * allows) from storage and calculates its hash. If hash verification
+ * is successful, device replies 'OKAY'.
+ *
+ * NOTES#2:
+ * Current implementation has limited set of ADNL features and commands.
+ * It was tested only on A1 SoC with one image of specific layout/content
+ * (BL2, TPL, etc.).
+ *
+ * NOTES#3:
+ * "bootloader" partition has special layout: BL2 and TPL are saved as
+ * several copies of each other with specific alignment.
+ *
+ * NOTES#4:
+ * This implementation requires that data buffer address and size to be
+ * set in the config file. This buffers will be used for both IN and OUT
+ * USB data.
+ */
+
+#include <cli.h>
+#include <command.h>
+#include <env.h>
+#include <fastboot.h>
+#include <g_dnl.h>
+#include <asm/arch-meson/a1.h>
+#include <asm/arch-meson/sm.h>
+#include <asm/io.h>
+#include <linux/delay.h>
+#include <u-boot/sha1.h>
+
+#include "adnl.h"
+#include "f_fastboot_common.h"
+#include "cmd/meson/gadget.h"
+
+#define ADNL_CMD_GETVAR "getvar:"
+#define ADNL_CMD_OEM "oem"
+#define ADNL_CMD_DOWNLOAD "download:"
+#define ADNL_CMD_UPDATE "update"
+#define ADNL_CMD_REBOOT "reboot"
+#define ADNL_CMD_MWRITE "mwrite"
+
+static char adnl_ext_prop_name[] = "DeviceInterfaceGUID";
+static char adnl_ext_prop_data[] = "{4866319A-F4D6-4374-93B9-DC2DEB361BA9}";
+
+static struct usb_os_desc_ext_prop adnl_ext_prop = {
+ .type = 1,
+ .name = adnl_ext_prop_name, /* NUL-terminated Unicode String (REG_SZ) */
+ .data = adnl_ext_prop_data,
+};
+
+/* 16 bytes of "Compatible ID" and "Subcompatible ID" */
+static char adnl_cid[16] = {'W', 'I', 'N', 'U', 'S', 'B'};
+static struct usb_os_desc adnl_os_desc = {
+ .ext_compat_id = adnl_cid,
+};
+
+static struct usb_os_desc_table adnl_os_desc_table = {
+ .os_desc = &adnl_os_desc,
+};
+
+static struct fastboot_funcs *adnl_func;
+static unsigned int download_size;
+static unsigned int download_bytes;
+
+static const char adnl_name[] = "Amlogic DNL";
+
+static struct usb_string adnl_string_defs[] = {
+ [0].s = adnl_name,
+ { } /* end of list */
+};
+
+static struct usb_gadget_strings stringtab_adnl = {
+ .language = 0x0409, /* en-us */
+ .strings = adnl_string_defs,
+};
+
+static struct usb_gadget_strings *adnl_strings[] = {
+ &stringtab_adnl,
+ NULL,
+};
+
+static char response_str[RESPONSE_LEN + 1];
+char *fb_response_str = &response_str[4];
+
+enum {
+ MWRITE_DATA_CHECK_ALG_NONE = 0, /* Not need checksum. */
+ MWRITE_DATA_CHECK_ALG_ADDSUM,
+};
+
+struct adnl_mwrite_info {
+ unsigned int total_bytes;
+ unsigned int transferred_bytes;
+ unsigned int data_check_alg;
+ void *priv;
+};
+
+static struct adnl_mwrite_info adnl_mwrite_info;
+static struct adnl_usb_dw_info *adnl_usb_dw_info;
+
+static int adnl_tx_write(const char *buffer, unsigned int buffer_size)
+{
+ struct usb_request *in_req = adnl_func->in_req;
+ int ret;
+
+ memcpy(in_req->buf, buffer, buffer_size);
+ in_req->length = buffer_size;
+
+ ret = usb_ep_queue(adnl_func->in_ep, in_req, 0);
+ if (ret)
+ ADNL_ERR("error %d on queueing %u bytes from tx buf\n", ret,
+ buffer_size);
+
+ return 0;
+}
+
+static int adnl_tx_write_str(const char *buffer)
+{
+ return adnl_tx_write(buffer, strlen(buffer));
+}
+
+static void adnl_rx_handler_command(struct usb_ep *ep, struct usb_request *req);
+
+static int dwc_blk_len(int left_sz)
+{
+ if (left_sz >= DWC_BLK_MAX_LEN)
+ return DWC_BLK_MAX_LEN;
+
+ if (left_sz >= BULK_EP_MPS)
+ return (left_sz / BULK_EP_MPS) * BULK_EP_MPS;
+
+ return left_sz;
+}
+
+static unsigned int adnl_csum_special(const void *p_buf, const unsigned int size)
+{
+ const unsigned int *data = (const unsigned int *)p_buf;
+ unsigned int word_len = size >> 2;
+ unsigned int rest = size & 0x3;
+ unsigned int sum = 0;
+
+ while (word_len--)
+ sum += *data++;
+
+ if (rest)
+ sum += (*data) & GENMASK(rest * 8, 0);
+
+ return sum;
+}
+
+#define EP_CMD_LEN_MAX 256
+/* This is input handler for raw image data. */
+static void rx_handler_mwrite(struct usb_ep *ep, struct usb_request *req)
+{
+ unsigned int transfer_size = req->actual;
+ char *data_buf = adnl_usb_dw_info->data_buf;
+ int ret = 0;
+
+ if (req->status) {
+ ADNL_ERR("Bad status: %d\n", req->status);
+ return;
+ }
+
+ adnl_mwrite_info.transferred_bytes += transfer_size;
+
+ /* Check if transfer is done */
+ if (adnl_mwrite_info.transferred_bytes == adnl_mwrite_info.total_bytes &&
+ adnl_mwrite_info.data_check_alg > MWRITE_DATA_CHECK_ALG_NONE) {
+ /* This is one before last packet with data. */
+ req->length = 4; /* For rx addsum. */
+ req->buf += transfer_size;
+
+ req->buf = (char *)ALIGN((s64)req->buf, 8);
+ } else if (adnl_mwrite_info.transferred_bytes >= adnl_mwrite_info.total_bytes) {
+ fastboot_okay(NULL, response_str);
+ response_str[4] = 0;
+
+ /* Calculate checksum for just transferred part of data. */
+ if (adnl_mwrite_info.data_check_alg == MWRITE_DATA_CHECK_ALG_ADDSUM) {
+ unsigned int data_len = adnl_mwrite_info.total_bytes;
+ unsigned int gensum = adnl_csum_special(data_buf, data_len);
+ unsigned int origsum;
+
+ origsum = *(unsigned int *)ALIGN((s64)(data_buf + data_len), 8);
+
+ if (gensum != origsum) {
+ ADNL_MSG("checksum not match %x != %x\n", gensum, origsum);
+ fastboot_response(ADNL_FAIL_STR, response_str,
+ "checksum not match %x != %x\n",
+ gensum, origsum);
+ ret = -EINVAL;
+ }
+ }
+
+ /* Checksum verification is done, write this part of image. */
+ if (!ret)
+ ret = adnl_buffman_data_complete_download(adnl_usb_dw_info);
+
+ if (ret)
+ fastboot_fail(NULL, response_str);
+ else
+ fastboot_okay(NULL, response_str);
+
+ adnl_tx_write_str(response_str);
+ ADNL_MSG("response[%d][%s]\n", ret, response_str);
+ ADNL_DBG("mwrite 0x%x bytes [%s]\n", adnl_mwrite_info.transferred_bytes,
+ ret ? "FAILED" : "OK");
+ /* Set default receive callback for handling ADNL commands.
+ * We are ready to receive next portion of data or image is
+ * already transferred.
+ */
+ req->complete = adnl_rx_handler_command;
+ req->length = EP_CMD_LEN_MAX;
+
+ if (adnl_mwrite_info.priv)
+ req->buf = (char *)adnl_mwrite_info.priv;
+ } else {
+ unsigned int left_len = adnl_mwrite_info.total_bytes -
+ adnl_mwrite_info.transferred_bytes;
+
+ /* Continue receiving data. */
+ req->length = dwc_blk_len(left_len);
+ req->buf += transfer_size;
+ }
+
+ req->actual = 0;
+ usb_ep_queue(ep, req, 0);
+}
+
+static int adnl_bind(struct usb_configuration *c, struct usb_function *f)
+{
+ return fastboot_common_bind(c, f, &adnl_os_desc_table,
+ &adnl_os_desc,
+ (struct usb_string **)&adnl_string_defs,
+ &adnl_ext_prop);
+}
+
+static void adnl_unbind(struct usb_configuration *c, struct usb_function *f)
+{
+ fastboot_common_unbind(c, f, &adnl_os_desc);
+ memset(adnl_func, 0, sizeof(*adnl_func));
+}
+
+static void adnl_disable(struct usb_function *f)
+{
+ fastboot_common_disable(f);
+}
+
+static int adnl_set_alt(struct usb_function *f,
+ unsigned int interface, unsigned int alt)
+{
+ amlogic_gadget_set_times(0, get_timer(0));
+ return fastboot_common_set_alt(f, interface, alt, adnl_rx_handler_command);
+}
+
+static int adnl_add(struct usb_configuration *c)
+{
+ struct fastboot_funcs *f_adnl = adnl_func;
+ int status;
+
+ if (!f_adnl) {
+ f_adnl = memalign(CONFIG_SYS_CACHELINE_SIZE, sizeof(*f_adnl));
+ if (!f_adnl)
+ return -ENOMEM;
+
+ adnl_func = f_adnl;
+ memset(f_adnl, 0, sizeof(*f_adnl));
+ }
+
+ g_dnl_set_product("DNL");
+ f_adnl->usb_function.name = "f_adnl";
+ f_adnl->usb_function.bind = adnl_bind;
+ f_adnl->usb_function.unbind = adnl_unbind;
+ f_adnl->usb_function.set_alt = adnl_set_alt;
+ f_adnl->usb_function.disable = adnl_disable;
+ f_adnl->usb_function.strings = adnl_strings;
+
+ status = usb_add_function(c, &f_adnl->usb_function);
+ if (status) {
+ free(f_adnl);
+ adnl_func = NULL;
+ }
+
+ return status;
+}
+
+DECLARE_GADGET_BIND_CALLBACK(usb_dnl_adnl, adnl_add);
+
+struct cmd_getvar_dispatch_info {
+ char *cmd;
+ void (*cb)(char *response, size_t *reply_len);
+};
+
+static void cb_getvar_identify(char *response, size_t *reply_len)
+{
+ const char adnl_ver[] = {5, 0, 0, 16, 0, 0, 0, 0};
+
+ ADNL_MSG("executing identify cmd\n");
+
+ memcpy(response, adnl_ver, sizeof(adnl_ver));
+ *reply_len = sizeof(adnl_ver);
+ amlogic_gadget_set_times(0, 0);
+}
+
+static int adnl_is_secureboot_enabled(void)
+{
+ /* TODO: support secureboot. */
+ return 0;
+}
+
+static void cb_getvar_secureboot(char *response, size_t *reply_len)
+{
+ unsigned int secureboot_enable;
+
+ secureboot_enable = adnl_is_secureboot_enabled();
+ memcpy(response, &secureboot_enable, sizeof(unsigned int));
+ *reply_len = sizeof(unsigned int);
+}
+
+static const struct cmd_getvar_dispatch_info cmd_getvar_dispatch_info[] = {
+ {
+ .cmd = "identify",
+ .cb = cb_getvar_identify,
+ },
+ {
+ .cmd = "secureboot",
+ .cb = cb_getvar_secureboot,
+ },
+};
+
+static void cb_getvar(struct usb_request *req)
+{
+ void (*func_cb)(char *response, size_t *reply_len);
+ char *cmd = req->buf;
+ char cmd_buf[RESPONSE_LEN];
+ char *response = response_str;
+ size_t chars_left;
+ size_t reply_len;
+ int i;
+
+ strcpy(response, "OKAY");
+ chars_left = sizeof(response_str) - strlen(response) - 1;
+
+ memcpy(cmd_buf, cmd, strnlen(cmd, RESPONSE_LEN - 1) + 1);
+ cmd = cmd_buf;
+ strsep(&cmd, ":");
+
+ if (!cmd || cmd[0] == '\0') {
+ fastboot_response(ADNL_FAIL_STR, response, "missing var\n");
+ adnl_tx_write_str(response);
+ return;
+ }
+
+ func_cb = NULL;
+
+ for (i = 0; i < ARRAY_SIZE(cmd_getvar_dispatch_info); i++) {
+ if (!strncmp(cmd_getvar_dispatch_info[i].cmd, cmd,
+ strlen(cmd_getvar_dispatch_info[i].cmd))) {
+ func_cb = cmd_getvar_dispatch_info[i].cb;
+ break;
+ }
+ }
+
+ reply_len = 0;
+
+ if (!func_cb) {
+ ADNL_ERR("unknown variable: %s\n", cmd);
+ adnl_tx_write_str(ADNL_FAIL_STR "unknown variable");
+ } else {
+ reply_len = chars_left;
+ func_cb(response_str + 4, &reply_len);
+ reply_len += 4;
+ }
+
+ if (!reply_len)
+ reply_len = strlen(response) + 1;
+
+ adnl_tx_write(response, reply_len);
+}
+
+static int adnl_set_var(const char *name, const char *value)
+{
+ if (!strcmp("burnsteps", name)) {
+ unsigned int val = simple_strtoul(value, NULL, 0);
+
+ writel(val, A1_SYSCTRL_SEC_STICKY_REG2);
+
+ return 0;
+ }
+
+ return -EINVAL;
+}
+
+static void cb_oem_cmd(struct usb_request *req)
+{
+ char *argv[CONFIG_SYS_MAXARGS + 1];
+ char tmp[RESPONSE_LEN + 1];
+ char *cmd = req->buf;
+ char *ack = response_str + 4;
+ char *cmd_buf = tmp;
+ int ret, argc;
+
+ ack[0] = '\0';
+ memcpy(cmd_buf, cmd, strnlen(cmd, RESPONSE_LEN) + 1);
+ strsep(&cmd_buf, " ");
+ response_str[4] = 0;
+
+ argc = cli_simple_parse_line(cmd_buf, argv);
+
+ if (!argc) {
+ fastboot_fail("oem no command at all", response_str);
+ ADNL_ERR("%s\n", response_str);
+ return;
+ }
+
+ if (!strcmp("mwrite", argv[0])) {
+ if (argc < 5) {
+ ret = -EINVAL;
+ } else {
+ unsigned long img_size;
+ unsigned long part_off;
+
+ img_size = simple_strtoul(argv[1], NULL, 0);
+ part_off = argc > 5 ? simple_strtoul(argv[5], NULL, 0) : 0;
+ ret = adnl_mwrite_cmd_parser(img_size, part_off, argv[2],
+ argv[3], argv[4], ack);
+ }
+ } else if (!strcmp("verify", argv[0])) {
+ ret = adnl_verify_partition_img(argv[1], argv[2], ack);
+ } else if (!strcmp("disk_initial", argv[0])) {
+ int erase_mode = argc > 1 ? simple_strtoul(argv[1], NULL, 0) : 0;
+
+ ret = adnl_erase_storage(erase_mode);
+ } else if (!strcmp("save_setting", argv[0])) {
+ ADNL_MSG("saveenv not implemented\n");
+ ret = 0;
+ } else if (!strcmp("setvar", argv[0])) {
+ if (argc < 2)
+ ret = -EINVAL;
+ else
+ ret = adnl_set_var(argv[1], argv[2]);
+ } else {
+ strsep(&cmd, " ");
+ ret = run_command(cmd, 0);
+ if (ret)
+ fastboot_response(ADNL_FAIL_STR, ack,
+ "fail in cmd[%s]", cmd);
+ }
+
+ if (ret)
+ fastboot_fail(NULL, response_str);
+ else
+ fastboot_okay(NULL, response_str);
+
+ adnl_tx_write_str(response_str);
+ ADNL_MSG("response[%d][%s]\n", ret, response_str);
+}
+
+static void cb_aml_media_write(struct usb_request *req)
+{
+ char *cmd = req->buf;
+ const char *field;
+ int ret;
+
+ ADNL_DBG("cmd cb_mwrite[%s]\n", cmd);
+
+ strsep(&cmd, ":");
+ field = cmd;
+ response_str[4] = 0;
+
+ adnl_mwrite_info.data_check_alg = MWRITE_DATA_CHECK_ALG_NONE;
+ adnl_mwrite_info.transferred_bytes = 0;
+
+ for (strsep(&cmd, "="); cmd; ) {
+ if (!strcmp(field, "verify")) {
+ if (!strcmp("addsum", cmd)) {
+ adnl_mwrite_info.data_check_alg = MWRITE_DATA_CHECK_ALG_ADDSUM;
+ } else {
+ fastboot_response(ADNL_FAIL_STR, fb_response_str,
+ "unknown data check algo '%s'\n", cmd);
+ fastboot_fail(NULL, response_str);
+ return;
+ }
+ } else {
+ snprintf(response_str, sizeof(response_str),
+ ADNL_FAIL_STR "unknown field '%s'\n", field);
+ ADNL_ERR("%s", response_str);
+ ret = -EINVAL;
+ goto _exit;
+ }
+
+ strsep(&cmd, ",");
+ strsep(&cmd, "=");
+ }
+
+ ret = adnl_buffman_fill_download_info(&adnl_usb_dw_info);
+ if (ret || !adnl_usb_dw_info) {
+ fastboot_response(ADNL_FAIL_STR, fb_response_str,
+ "Fail in buffman get, ret %d", ret);
+ goto _exit;
+ }
+
+ adnl_mwrite_info.total_bytes = adnl_usb_dw_info->data_size;
+ ret = 0;
+
+_exit:
+ if (ret) {
+ fastboot_fail(NULL, response_str);
+ } else if (!adnl_usb_dw_info->data_size) {
+ fastboot_okay(NULL, response_str);
+ ADNL_MSG("OK in Partition Image\n");
+ } else {
+ unsigned int left_len;
+
+ snprintf(response_str, sizeof(response_str),
+ "DATAOUT0x%x:0x%llx", adnl_usb_dw_info->data_size,
+ adnl_usb_dw_info->file_offset);
+ req->complete = rx_handler_mwrite;
+ left_len = adnl_mwrite_info.total_bytes -
+ adnl_mwrite_info.transferred_bytes;
+ req->length = dwc_blk_len(left_len);
+
+ if (!adnl_mwrite_info.priv)
+ adnl_mwrite_info.priv = req->buf;
+
+ req->buf = adnl_usb_dw_info->data_buf;
+ }
+
+ adnl_tx_write_str(response_str);
+}
+
+static unsigned int rx_bytes_expected(void)
+{
+ int rx_remain = download_size - download_bytes;
+
+ if (rx_remain < 0)
+ return 0;
+
+ if (rx_remain > EP_BUFFER_SIZE)
+ return EP_BUFFER_SIZE;
+
+ return rx_remain;
+}
+
+#define BYTES_PER_DOT 0x20000
+static void rx_handler_dl_image(struct usb_ep *ep, struct usb_request *req)
+{
+ unsigned int transfer_size = download_size - download_bytes;
+ unsigned char *buffer = req->buf;
+ unsigned int buffer_size = req->actual;
+ unsigned int pre_dot_num, now_dot_num;
+
+ if (req->status) {
+ ADNL_ERR("Bad status: %d\n", req->status);
+ return;
+ }
+
+ if (buffer_size < transfer_size)
+ transfer_size = buffer_size;
+
+ memcpy((u8 *)CONFIG_FASTBOOT_BUF_ADDR + download_bytes,
+ buffer, transfer_size);
+
+ pre_dot_num = download_bytes / BYTES_PER_DOT;
+ download_bytes += transfer_size;
+ now_dot_num = download_bytes / BYTES_PER_DOT;
+
+ if (pre_dot_num != now_dot_num) {
+ putc('.');
+
+ /* 65 is progress bar size, same as in TFTP. */
+ if (!(now_dot_num % 65))
+ putc('\n');
+ }
+
+ /* Check if transfer is done */
+ if (download_bytes >= download_size) {
+ char response[RESPONSE_LEN];
+ static char EP_CMD_BUF[EP_CMD_LEN_MAX * 2];
+
+ /*
+ * Reset global transfer variable, keep download_bytes because
+ * it will be used in the next possible flashing command
+ */
+ download_size = 0;
+ req->complete = adnl_rx_handler_command;
+ req->length = EP_CMD_LEN_MAX;
+ req->buf = &EP_CMD_BUF[0];
+
+ sprintf(response, "OKAY");
+ adnl_tx_write_str(response);
+
+ ADNL_MSG("\ndownloading of %u bytes finished\n", download_bytes);
+ } else {
+ req->length = rx_bytes_expected();
+
+ if (req->length < ep->maxpacket)
+ req->length = ep->maxpacket;
+ }
+
+ req->actual = 0;
+ usb_ep_queue(ep, req, 0);
+}
+
+static void cb_download(struct usb_request *req)
+{
+ char *cmd = req->buf;
+ char response[RESPONSE_LEN];
+
+ strsep(&cmd, ":");
+ download_size = simple_strtoul(cmd, NULL, 16);
+ download_bytes = 0;
+
+ if (!download_size) {
+ fastboot_fail(response, "data invalid size");
+ } else if (download_size > ADNL_DOWNLOAD_MEM_SIZE) {
+ download_size = 0;
+ fastboot_fail(response, "data too large");
+ } else {
+ sprintf(response, "DATA%08x", download_size);
+ req->complete = rx_handler_dl_image;
+ req->buf = (char *)CONFIG_ADNL_BUF_ADDR;
+ req->length = rx_bytes_expected();
+ }
+
+ adnl_tx_write_str(response);
+}
+
+static void adnl_complete_do_reboot(struct usb_ep *ep, struct usb_request *req)
+{
+ do_reset(NULL, 0, 0, NULL);
+}
+
+static void adnl_complete_do_reboot_bootloader(struct usb_ep *ep, struct usb_request *req)
+{
+ run_command("reboot bootloader", 0);
+}
+
+static void adnl_complete_do_reboot_bl1usb(struct usb_ep *ep, struct usb_request *req)
+{
+ /*
+ * Reboot into bootROM (BL1) with USB as first boot source,
+ * awaiting forever the host connection.
+ */
+ meson_sm_set_bl1_first_boot_source(SCPI_CMD_USB_UNBOOT);
+ do_reset(NULL, 0, 0, NULL);
+}
+
+static void cb_reboot(struct usb_request *req)
+{
+ char *cmd = req->buf;
+
+ ADNL_MSG("reboot '%s'\n", cmd);
+
+ strsep(&cmd, "-");
+
+ if (!cmd) {
+ adnl_func->in_req->complete = adnl_complete_do_reboot;
+ } else if (!strcmp("bootloader", cmd)) {
+ adnl_func->in_req->complete = adnl_complete_do_reboot_bootloader;
+ } else if (!strcmp("romusb", cmd)) {
+ adnl_func->in_req->complete = adnl_complete_do_reboot_bl1usb;
+ } else {
+ fastboot_fail("unsupported reboot cmd", response_str);
+ adnl_tx_write_str(response_str);
+ return;
+ }
+
+ adnl_tx_write_str("OKAY");
+}
+
+struct cmd_dispatch_info {
+ const char *cmd;
+ void (*const cb)(struct usb_request *req);
+};
+
+static const struct cmd_dispatch_info cmd_dispatch_info[] = {
+ {
+ .cmd = ADNL_CMD_GETVAR,
+ .cb = cb_getvar,
+ },
+ {
+ .cmd = ADNL_CMD_OEM,
+ .cb = cb_oem_cmd,
+ },
+ {
+ .cmd = ADNL_CMD_DOWNLOAD,
+ .cb = cb_download,
+ },
+ {
+ .cmd = ADNL_CMD_UPDATE,
+ .cb = cb_download,
+ },
+ {
+ .cmd = ADNL_CMD_REBOOT,
+ .cb = cb_reboot,
+ },
+ {
+ .cmd = ADNL_CMD_MWRITE,
+ .cb = cb_aml_media_write,
+ },
+};
+
+static void adnl_rx_handler_command(struct usb_ep *ep, struct usb_request *req)
+{
+ void (*func_cb)(struct usb_request *req);
+ char *cmdbuf = req->buf;
+ int i;
+
+ cmdbuf[req->actual] = '\0';
+ func_cb = NULL;
+
+ for (i = 0; i < ARRAY_SIZE(cmd_dispatch_info); i++) {
+ if (!strncmp(cmd_dispatch_info[i].cmd, cmdbuf,
+ strlen(cmd_dispatch_info[i].cmd))) {
+ ADNL_MSG("got cmd '%s'\n", cmd_dispatch_info[i].cmd);
+ func_cb = cmd_dispatch_info[i].cb;
+ break;
+ }
+ }
+
+ if (!func_cb) {
+ ADNL_ERR("unknown command: %s\n", cmdbuf);
+ adnl_tx_write_str(ADNL_FAIL_STR "unknown command");
+ } else {
+ if (req->actual < req->length) {
+ u8 *buf = (u8 *)req->buf;
+
+ buf[req->actual] = 0;
+ func_cb(req);
+ } else {
+ ADNL_ERR("buffer overflow\n");
+ adnl_tx_write_str(ADNL_FAIL_STR "buffer overflow");
+ }
+ }
+
+ if (!req->status) {
+ *cmdbuf = '\0';
+ req->actual = 0;
+ usb_ep_queue(ep, req, 0);
+ }
+}
--
2.30.1
^ permalink raw reply [flat|nested] 13+ messages in thread
* [rulkc] [PATCH v1 09/11] arm: meson: a1: ADNL protocol support
2025-07-29 18:28 [rulkc] [PATCH v1 00/11] Amlogic: ADNL and Optimus protocols support avkrasnov
` (7 preceding siblings ...)
2025-07-29 18:29 ` [rulkc] [PATCH v1 08/11] usb: gadget: amlogic: implement ADNL protocol avkrasnov
@ 2025-07-29 18:29 ` avkrasnov
2025-07-29 18:29 ` [rulkc] [PATCH v1 10/11] usb: gadget: amlogic: implement Optimus protocol avkrasnov
` (2 subsequent siblings)
11 siblings, 0 replies; 13+ messages in thread
From: avkrasnov @ 2025-07-29 18:29 UTC (permalink / raw)
During ADNL protocol operations, BootROM may request to enter
ADNL mode after boot. So to support this, let's check for values
in sticky registers after boot (such registers could be updated
earlier by boot ROM, telling U-boot to enter ADNL mode.
Signed-off-by: Arseniy Krasnov <avkrasnov at salutedevices.com>
---
arch/arm/include/asm/arch-meson/a1.h | 13 +++++++++++
arch/arm/mach-meson/board-a1.c | 32 +++++++++++++++++++++++++++-
arch/arm/mach-meson/board-common.c | 17 +++++++++++++++
3 files changed, 61 insertions(+), 1 deletion(-)
diff --git a/arch/arm/include/asm/arch-meson/a1.h b/arch/arm/include/asm/arch-meson/a1.h
index 16e209ed932..4a22aaf5627 100644
--- a/arch/arm/include/asm/arch-meson/a1.h
+++ b/arch/arm/include/asm/arch-meson/a1.h
@@ -12,6 +12,7 @@
/* SYSCTRL registers */
#define A1_SYSCTRL_ADDR(off) (A1_SYSCTRL_BASE + ((off) << 2))
+#define A1_SYSCTRL_SEC_STATUS_REG1 A1_SYSCTRL_ADDR(0xc1)
#define A1_SYSCTRL_SEC_STATUS_REG4 A1_SYSCTRL_ADDR(0xc4)
/*
* Checked during boot. Tells, that previous code, e.g.
@@ -20,7 +21,19 @@
*/
#define A1_SYSCTRL_SEC_STICKY_REG2 A1_SYSCTRL_ADDR(0xe2)
+#define A1_AO_BOOT_DEVICE 0xF
#define A1_SYSCTRL_MEM_SIZE_MASK 0xFFFF0000
#define A1_SYSCTRL_MEM_SIZE_SHIFT 16
+#if CONFIG_IS_ENABLED(ADNL)
+/**
+ * is_tpl_loaded_from_usb - Checks, that this instance of U-boot was
+ * loaded from USB during firmware update
+ * process.
+ *
+ * returns: true, if so.
+ */
+int is_tpl_loaded_from_usb(void);
+#endif
+
#endif /* __MESON_A1_H__ */
diff --git a/arch/arm/mach-meson/board-a1.c b/arch/arm/mach-meson/board-a1.c
index f848c0f068e..9cc410f1e57 100644
--- a/arch/arm/mach-meson/board-a1.c
+++ b/arch/arm/mach-meson/board-a1.c
@@ -3,6 +3,7 @@
* (C) Copyright 2023 SberDevices, Inc.
*/
+#include <command.h>
#include <asm/arch/a1.h>
#include <asm/arch/boot.h>
#include <asm/armv8/mmu.h>
@@ -23,9 +24,38 @@ void meson_init_reserved_memory(__maybe_unused void *fdt)
int meson_get_boot_device(void)
{
- return -ENOSYS;
+ return readl(A1_SYSCTRL_SEC_STATUS_REG4) & A1_AO_BOOT_DEVICE;
}
+#if CONFIG_IS_ENABLED(ADNL)
+
+static bool is_boot_device_usb(void)
+{
+ return meson_get_boot_device() == BOOT_DEVICE_USB;
+}
+
+#define MESON_ADNL_BL1_USB_PROTO_DNL_BIT BIT(12)
+
+static bool is_bl1_usb_protocol_DNL(void)
+{
+ u32 val = readl(A1_SYSCTRL_SEC_STATUS_REG1);
+
+ return !(val & MESON_ADNL_BL1_USB_PROTO_DNL_BIT);
+}
+
+int is_tpl_loaded_from_usb(void)
+{
+ if (!is_boot_device_usb())
+ return false;
+
+ if (!is_bl1_usb_protocol_DNL())
+ return false;
+
+ return true;
+}
+
+#endif
+
static struct mm_region a1_mem_map[] = {
{
.virt = 0x00000000UL,
diff --git a/arch/arm/mach-meson/board-common.c b/arch/arm/mach-meson/board-common.c
index 39774c43049..0624d557e40 100644
--- a/arch/arm/mach-meson/board-common.c
+++ b/arch/arm/mach-meson/board-common.c
@@ -3,6 +3,7 @@
* (C) Copyright 2016 Beniamino Galvani <b.galvani at gmail.com>
*/
+#include <command.h>
#include <cpu_func.h>
#include <fastboot.h>
#include <init.h>
@@ -14,9 +15,11 @@
#include <asm/ptrace.h>
#include <linux/libfdt.h>
#include <linux/err.h>
+#include <asm/arch/a1.h>
#include <asm/arch/mem.h>
#include <asm/arch/sm.h>
#include <asm/armv8/mmu.h>
+#include <asm/io.h>
#include <asm/unaligned.h>
#include <efi_loader.h>
#include <u-boot/crc.h>
@@ -141,10 +144,24 @@ __weak int meson_board_late_init(void)
return 0;
}
+static void meson_board_try_enter_adnl(void)
+{
+#if (IS_ENABLED(CONFIG_CMD_ADNL))
+ if (!is_tpl_loaded_from_usb())
+ return;
+
+ meson_sm_set_bl1_first_boot_source(SCPI_CMD_CLEAR_BOOT);
+
+ run_command("adnl", 0);
+#endif
+}
+
int board_late_init(void)
{
meson_set_boot_source();
+ meson_board_try_enter_adnl();
+
return meson_board_late_init();
}
--
2.30.1
^ permalink raw reply [flat|nested] 13+ messages in thread
* [rulkc] [PATCH v1 10/11] usb: gadget: amlogic: implement Optimus protocol
2025-07-29 18:28 [rulkc] [PATCH v1 00/11] Amlogic: ADNL and Optimus protocols support avkrasnov
` (8 preceding siblings ...)
2025-07-29 18:29 ` [rulkc] [PATCH v1 09/11] arm: meson: a1: ADNL protocol support avkrasnov
@ 2025-07-29 18:29 ` avkrasnov
2025-07-29 18:29 ` [rulkc] [PATCH v1 11/11] arch: arm: axg: Optimus protocol support avkrasnov
2025-07-31 1:07 ` [rulkc] [PATCH v1 00/11] Amlogic: ADNL and Optimus protocols support slava
11 siblings, 0 replies; 13+ messages in thread
From: avkrasnov @ 2025-07-29 18:29 UTC (permalink / raw)
From: Vladimir Mitrofanov <vvmitrofanov at salutedevices.com>
Add Amlogic's Optimus protocol support for USB transport.
Signed-off-by: Vladimir Mitrofanov <vvmitrofanov at salutedevices.com>
Signed-off-by: Arseniy Krasnov <avkrasnov at salutedevices.com>
---
cmd/Kconfig | 7 +
cmd/meson/Makefile | 1 +
cmd/meson/optimus.c | 21 +
drivers/usb/gadget/Makefile | 1 +
drivers/usb/gadget/amlogic/Kconfig | 1 +
drivers/usb/gadget/amlogic/optimus/Kconfig | 12 +
drivers/usb/gadget/amlogic/optimus/Makefile | 7 +
.../usb/gadget/amlogic/optimus/f_optimus.c | 687 ++++++++++++++++++
.../gadget/amlogic/optimus/optimus_download.c | 188 +++++
.../gadget/amlogic/optimus/optimus_download.h | 86 +++
10 files changed, 1011 insertions(+)
create mode 100644 cmd/meson/optimus.c
create mode 100644 drivers/usb/gadget/amlogic/optimus/Kconfig
create mode 100644 drivers/usb/gadget/amlogic/optimus/Makefile
create mode 100644 drivers/usb/gadget/amlogic/optimus/f_optimus.c
create mode 100644 drivers/usb/gadget/amlogic/optimus/optimus_download.c
create mode 100644 drivers/usb/gadget/amlogic/optimus/optimus_download.h
diff --git a/cmd/Kconfig b/cmd/Kconfig
index 2f195d03848..159e52d5298 100644
--- a/cmd/Kconfig
+++ b/cmd/Kconfig
@@ -1153,6 +1153,13 @@ config CMD_ADNL
This command, "adnl", enables the ADNL listen mode, providing
the ability to listen in ADNL mode.
+config CMD_OPTIMUS
+ bool "OPTIMUS - Amlogic download protocol"
+ depends on OPTIMUS
+ help
+ This command, "optimus", enables the OPTIMUS listen mode, providing
+ the ability to communicate with host by OPTIMUS protocol.
+
config CMD_FLASH
bool "flinfo, erase, protect"
default y
diff --git a/cmd/meson/Makefile b/cmd/meson/Makefile
index 1cf984378a3..611d1549736 100644
--- a/cmd/meson/Makefile
+++ b/cmd/meson/Makefile
@@ -4,3 +4,4 @@
obj-y += sm.o
obj-$(CONFIG_CMD_ADNL) += gadget.o adnl.o
+obj-$(CONFIG_CMD_OPTIMUS) += gadget.o optimus.o
diff --git a/cmd/meson/optimus.c b/cmd/meson/optimus.c
new file mode 100644
index 00000000000..97a1435c0ab
--- /dev/null
+++ b/cmd/meson/optimus.c
@@ -0,0 +1,21 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (c) 2024 SaluteDevices, Inc.
+ * Author: Vladimir Mitrofanov <vvmitrofanov at salutedevices.com>
+ */
+
+#include <command.h>
+
+#include "gadget.h"
+
+static int do_optimus(struct cmd_tbl *cmdtp, int flag, int argc,
+ char *const argv[])
+{
+ return amlogic_gadget_run("usb_dnl_optimus", argc, argv);
+}
+
+U_BOOT_LONGHELP(optimus,
+ "- run as an OPTIMUS USB device\n\n");
+
+U_BOOT_CMD(optimus, 1, 1, do_optimus,
+ "OPTIMUS protocol mode", optimus_help_text);
diff --git a/drivers/usb/gadget/Makefile b/drivers/usb/gadget/Makefile
index 2b6705c52be..f5899a96221 100644
--- a/drivers/usb/gadget/Makefile
+++ b/drivers/usb/gadget/Makefile
@@ -22,6 +22,7 @@ obj-$(CONFIG_USB_GADGET_DWC2_OTG) += dwc2_udc_otg.o
obj-$(CONFIG_USB_GADGET_DWC2_OTG_PHY) += dwc2_udc_otg_phy.o
obj-$(CONFIG_USB_GADGET_MAX3420) += max3420_udc.o
obj-$(CONFIG_USB_RENESAS_USBHS) += rcar/
+obj-$(CONFIG_OPTIMUS) += f_fastboot_common.o amlogic/optimus/
ifndef CONFIG_XPL_BUILD
obj-$(CONFIG_USB_GADGET_DOWNLOAD) += g_dnl.o
obj-$(CONFIG_USB_FUNCTION_THOR) += f_thor.o
diff --git a/drivers/usb/gadget/amlogic/Kconfig b/drivers/usb/gadget/amlogic/Kconfig
index 460dc6fe235..5a16fb25998 100644
--- a/drivers/usb/gadget/amlogic/Kconfig
+++ b/drivers/usb/gadget/amlogic/Kconfig
@@ -7,5 +7,6 @@ choice
optional
source "drivers/usb/gadget/amlogic/adnl/Kconfig"
+source "drivers/usb/gadget/amlogic/optimus/Kconfig"
endchoice
diff --git a/drivers/usb/gadget/amlogic/optimus/Kconfig b/drivers/usb/gadget/amlogic/optimus/Kconfig
new file mode 100644
index 00000000000..6a730670d34
--- /dev/null
+++ b/drivers/usb/gadget/amlogic/optimus/Kconfig
@@ -0,0 +1,12 @@
+# SPDX-License-Identifier: GPL-2.0+
+#
+# (C) Copyright 2024 SaluteDevices, Inc.
+
+config OPTIMUS
+ bool "Enable OPTIMUS protocol"
+ depends on USB_GADGET
+ depends on MESON_AXG
+ help
+ This enables usb optimus function protocol
+ "Optimus" protocol is used to flash Amlogic devices
+ from bootRom or from U-Boot mode
diff --git a/drivers/usb/gadget/amlogic/optimus/Makefile b/drivers/usb/gadget/amlogic/optimus/Makefile
new file mode 100644
index 00000000000..f48806414aa
--- /dev/null
+++ b/drivers/usb/gadget/amlogic/optimus/Makefile
@@ -0,0 +1,7 @@
+# SPDX-License-Identifier: GPL-2.0+
+
+obj-y += f_optimus_cb.o
+
+subdir-ccflags-y += -I$(src)/../ -I$(src)/../../
+f_optimus_cb-y += f_optimus.o
+f_optimus_cb-y += optimus_download.o
diff --git a/drivers/usb/gadget/amlogic/optimus/f_optimus.c b/drivers/usb/gadget/amlogic/optimus/f_optimus.c
new file mode 100644
index 00000000000..9912a691301
--- /dev/null
+++ b/drivers/usb/gadget/amlogic/optimus/f_optimus.c
@@ -0,0 +1,687 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2024 SaluteDevices, Inc.
+ * Author: Vladimir Mitrofanov <vvmitrofanov at salutedevices.com>
+ */
+/*
+ * OPTIMUS - is Amlogic protocol for updating firmware.
+ * This protocol is used for flashing devices based on Amlogic SoC.
+ * Optimus uses specific image format. In this implementation USB
+ * is used for data transfer.
+ *
+ * There are two ways to switch device to flashing mode:
+ * 1. Enter bootROM flashing mode (how this should be done depends on device)
+ * 2. Run from TPL (ex. from U-boot)
+ *
+ * Flashing steps
+ * 1. Device has to be switched to Optimus mode (as described earlier).
+ * 2. Host detects it as "Amlogic DNL" device, by sending identify request.
+ * 3. Host sends u-boot. BootROM loads it to RAM and executes u-boot.
+ * Finally u-boot detects that we are in Optimus update process, so it enters
+ * Optimus mode to continue fw update.
+ * 4. Now host starts to send protocol commands.
+ * First host sends some preparation commands like "low_power", "echo 1234" etc.
+ * The order of this commands depends on host software.
+ * Host may start flashing from any partition of the image (depends on host)
+ * 5. Host sends "bootloader_is_old" and "erase_bootloader". Device is preparing
+ * buffers and storage to receive "bootloader". "Bootloader" consists of two
+ * parts "bl2" and "tpl". As "tpl" could be used u-boot.
+ * 6. Host sends "download" command that contains information about full size of
+ * partition that will be sent. Partition is sending by chunks. Every chunk
+ * writes to permanent memory.
+ * "Bootloader" is exception. It should be fully received before writing to storage.
+ * After getting full "bootloader" device starts flashing it in to permanent memory.
+ * 7. After sending each partition of firmware Host sends "download get_status".
+ * 8. For every success operation Device respond with "success" status.
+ * 9. Host repeats steps form 6 - 8 with other partitions (A, B, ...).
+ * This partitions are sending by chunks, but in contrast ("bootloader") they
+ * are transmitting and writing to permanent memory by block of 64KiB (the size
+ * depends on host software).
+ * 10. After all partitions are received Host sends "save_setting" to restore
+ * environment to default state.
+ *
+ * NOTES#1:
+ * Current implementation has limited set of OPTIMUS features and commands.
+ * It was tested only on A113 SoC with one image of specific layout/content
+ * (BL2, TPL, etc.).
+ *
+ * NOTES#2:
+ * "bootloader" partition has special layout: BL2 and TPL are saved as
+ * several copies of each other with specific alignment.
+ *
+ * NOTES#3:
+ * This implementation requires that data buffer address and size to be
+ * set in the config file. This buffers will be used for both IN and OUT
+ * USB data.
+ */
+
+#include <cli.h>
+#include <command.h>
+#include <env.h>
+#include <fastboot.h>
+#include <g_dnl.h>
+#include <log.h>
+#include <asm/arch-meson/sm.h>
+#include <asm/io.h>
+#include <linux/delay.h>
+#include <linux/mtd/mtd.h>
+#include <u-boot/sha1.h>
+
+#include "f_fastboot_common.h"
+#include "optimus_download.h"
+
+#define CMD_DOWNLOAD "download"
+#define CMD_LOWPOWER "low_power"
+#define CMD_BL_IS_OLD "bootloader_is_old"
+#define CMD_ERASE_BL "erase_bootloader"
+#define CMD_RESET "reset"
+#define CMD_UPLOAD "upload"
+#define CMD_VERIFY "verify"
+#define CMD_DISK_INITIAL "disk_initial"
+#define CMD_SAVE_SET "save_setting"
+#define CMD_BURN_COMPL "burn_complete"
+#define CMD_ECHO_1234 "echo"
+#define CMD_SETENV "setenv"
+#define CMD_SAVE "save"
+#define CMD_RPMB_RESET "rpmb_reset"
+#define CMD_AMLMMC "amlmmc"
+#define CMD_NAND "nand"
+#define CMD_SUB_GETSTATUS "get_status"
+#define CMD_SUB_STORE "store"
+
+#define MAX_RESPONSE_LEN 512
+#define MAX_CMD_LEN 60
+#define SPACE_IN_HEX 0x20
+
+#define REQ_WRITE_MEM 0x01
+#define REQ_READ_MEM 0x02
+#define REQ_RUN_IN_ADDR 0x05
+#define REQ_WR_LARGE_MEM 0x11
+#define REQ_RD_LARGE_MEM 0x12
+#define REQ_IDENTIFY_HOST 0x20
+#define REQ_TPL_CMD 0x30
+#define REQ_TPL_STAT 0x31
+#define REQ_BULKCMD 0x34
+#define REQ_WRITE_MEDIA 0x32
+#define REQ_READ_MEDIA 0x33
+
+typedef void (*cmd_handler) (const struct usb_request *req);
+
+static cmd_handler get_handler(const char *cmd);
+
+static const char opti_name[] = "WorldCup Device";
+static const char resp_success[] = "success";
+/* Pay attention \":\" is in the end. Protocol specific. */
+static const char resp_failed[] = "failed:";
+
+static struct usb_os_desc_ext_prop opti_ext_prop = {
+ .type = 1, /* NULL-terminated Unicode String (REG_SZ) */
+ .name = "DeviceInterfaceGUID",
+ .data = "{4866319A-F4D6-4374-93B9-DC2DEB361BA9}",
+};
+
+/* 16 bytes of "Compatible ID" and "Subcompatible ID" */
+static char opti_cid[16] = {'W', 'I', 'N', 'U', 'S', 'B'};
+static struct usb_os_desc opti_os_desc = {
+ .ext_compat_id = opti_cid,
+};
+
+static struct usb_os_desc_table opti_os_desc_table = {
+ .os_desc = &opti_os_desc,
+};
+
+static struct fastboot_funcs *opti_func;
+
+static struct usb_string opti_string_defs[] = {
+ [0].s = opti_name,
+ { } /* end of list */
+};
+
+static struct usb_gadget_strings stringtab_opti = {
+ .language = 0x0409, /* en-us */
+ .strings = opti_string_defs,
+};
+
+static struct usb_gadget_strings *opti_strings[] = {
+ &stringtab_opti,
+ NULL,
+};
+
+/* This function is oriented to send string response of desired size ending by zero bytes */
+static int opti_tx_write(const char *buffer, size_t buffer_size)
+{
+ int ret;
+ struct usb_request *in_req = opti_func->in_req;
+ size_t resp_len = strnlen(buffer, MAX_RESPONSE_LEN);
+
+ if (!buffer_size || buffer_size > MAX_RESPONSE_LEN)
+ buffer_size = MAX_RESPONSE_LEN;
+
+ resp_len = min_t(size_t, resp_len, buffer_size);
+ memset(in_req->buf, 0, MAX_RESPONSE_LEN);
+ memcpy(in_req->buf, buffer, resp_len);
+
+ in_req->length = buffer_size;
+
+ ret = usb_ep_queue(opti_func->in_ep, in_req, 0);
+ if (ret)
+ OPTI_ERR("Response with size %zu failed: %d", buffer_size, ret);
+
+ return ret;
+}
+
+static void opti_rx_handler_command(struct usb_ep *ep, struct usb_request *req);
+
+static int opti_bind(struct usb_configuration *c, struct usb_function *f)
+{
+ return fastboot_common_bind(c, f, &opti_os_desc_table,
+ &opti_os_desc, (struct usb_string **)&opti_string_defs,
+ &opti_ext_prop);
+}
+
+static void opti_unbind(struct usb_configuration *c, struct usb_function *f)
+{
+ fastboot_common_unbind(c, f, &opti_os_desc);
+ memset(opti_func, 0, sizeof(*opti_func));
+}
+
+static void opti_disable(struct usb_function *f)
+{
+ fastboot_common_disable(f);
+}
+
+static int opti_set_alt(struct usb_function *f,
+ unsigned int interface, unsigned int alt)
+{
+ int ret;
+ struct usb_configuration *c;
+
+ if (interface)
+ return 0;
+
+ ret = fastboot_common_set_alt(f, interface, alt, opti_rx_handler_command);
+
+ c = f->config;
+
+ if (!c->interface[0])
+ return ret;
+
+ for (size_t i = 1; i < MAX_CONFIG_INTERFACES; i++) {
+ if (!c->interface[i])
+ c->interface[i] = c->interface[0];
+ }
+
+ return ret;
+}
+
+void complete_tpl_cmd(struct usb_ep *ep, struct usb_request *req)
+{
+ cmd_handler cb;
+
+ ((char *)req->buf)[req->actual] = '\0';
+ cb = get_handler(req->buf);
+
+ if (!cb) {
+ OPTI_ERR("Unknown command: %.*s", MAX_CMD_LEN, (char *)req->buf);
+ return;
+ }
+
+ cb(req);
+}
+
+void complete_write_media(struct usb_ep *ep, struct usb_request *req)
+{
+ struct optimus_chunk in;
+ struct optimus_img *img;
+
+ in = *((struct optimus_chunk *)req->buf);
+
+ img = optimus_get_img();
+
+ img->cur_chunk_meta = in;
+
+ if (optimus_chunk_alloc_buf(img->fsize))
+ return;
+
+ opti_func->out_req->length = min_t(u32, in.data_length, EP_BUFFER_SIZE);
+ opti_func->out_req->complete = opti_rx_handler_command;
+
+ OPTI_MSG_ERASE("Image %s received:%*zd%%%c", img->name,
+ 4, img->dsize * 100 / img->fsize, SPACE_IN_HEX);
+
+ usb_ep_queue(opti_func->out_ep, opti_func->out_req, 0);
+}
+
+static int optimus_setup(struct usb_function *f, const struct usb_ctrlrequest *ctrl)
+{
+ struct usb_gadget *gadget = f->config->cdev->gadget;
+ struct usb_request *req = f->config->cdev->req;
+ u16 len = le16_to_cpu(ctrl->wLength);
+ u16 w_value = le16_to_cpu(ctrl->wValue);
+ int value;
+
+ u8 resp_buf[MAX_RESPONSE_LEN];
+ u8 host_identity[] = {0, 7, 0, 16, 0, 0, 0, 0};
+
+ switch (ctrl->bRequest) {
+ case REQ_IDENTIFY_HOST:
+ value = min_t(u16, len, MAX_RESPONSE_LEN);
+ memcpy(req->buf, host_identity, value);
+ break;
+ case REQ_TPL_CMD:
+ value = len;
+ req->complete = complete_tpl_cmd;
+ break;
+ case REQ_TPL_STAT:
+ value = (!len) ? 0 : len - 1;
+ memset(req->buf, 0, len);
+ memcpy(req->buf, resp_success, sizeof(resp_success) - 1);
+ break;
+ case REQ_BULKCMD:
+ value = len;
+ req->complete = complete_tpl_cmd;
+ break;
+ case REQ_READ_MEDIA:
+ value = len;
+ memset(resp_buf, 0, sizeof(resp_buf));
+ opti_tx_write(resp_buf, w_value);
+ break;
+ case REQ_WRITE_MEDIA:
+ /* This is main request to receive image chunks of data */
+ value = len;
+ req->complete = complete_write_media;
+ break;
+ case REQ_WRITE_MEM:
+ case REQ_READ_MEM:
+ case REQ_WR_LARGE_MEM:
+ case REQ_RUN_IN_ADDR:
+ value = len;
+ memset(req->buf, 0, len);
+ break;
+ default:
+ value = 0;
+ OPTI_ERR("\nUnknown request: bRequest %#x", ctrl->bRequest);
+ }
+
+ req->length = value;
+ req->zero = 0;
+
+ value = usb_ep_queue(gadget->ep0, req, GFP_KERNEL);
+ if (value < 0) {
+ OPTI_ERR("Response \"ep0\" failed (%d)", value);
+ req->status = 0;
+ }
+
+ return value;
+}
+
+int opti_add(struct usb_configuration *c)
+{
+ struct fastboot_funcs *f_opti = opti_func;
+ int status;
+
+ OPTI_MSG("Start usb \"optimus\"");
+ if (!f_opti) {
+ f_opti = memalign(CONFIG_SYS_CACHELINE_SIZE, sizeof(*f_opti));
+ if (!f_opti)
+ return -ENOMEM;
+
+ opti_func = f_opti;
+ memset(f_opti, 0, sizeof(*f_opti));
+ }
+
+ g_dnl_set_product("DNL");
+ f_opti->usb_function.name = "f_opti";
+ f_opti->usb_function.bind = opti_bind;
+ f_opti->usb_function.unbind = opti_unbind;
+ f_opti->usb_function.set_alt = opti_set_alt;
+ f_opti->usb_function.disable = opti_disable;
+ f_opti->usb_function.strings = opti_strings;
+ f_opti->usb_function.setup = optimus_setup;
+
+ status = usb_add_function(c, &f_opti->usb_function);
+ if (status) {
+ free(f_opti);
+ opti_func = NULL;
+ }
+
+ return status;
+}
+
+DECLARE_GADGET_BIND_CALLBACK(usb_dnl_optimus, opti_add);
+
+static void cb_download(const struct usb_request *req)
+{
+ char *cmd[10];
+ char *token;
+ u16 i;
+ unsigned int img_size;
+ const char delim[] = {SPACE_IN_HEX, '\x00'};
+ struct optimus_img *img;
+
+ /* Finalizing received buffer by \x00 */
+ token = req->buf;
+ token[req->actual] = '\x00';
+
+ i = 0;
+ token = strtok(req->buf, delim);
+ while (token && i < ARRAY_SIZE(cmd)) {
+ cmd[i] = token;
+ token = strtok(NULL, delim);
+ i++;
+ }
+
+ img = optimus_get_img();
+
+ if (i >= 1 && !strncmp(cmd[1], CMD_SUB_GETSTATUS, sizeof(CMD_SUB_GETSTATUS) - 1)) {
+ if (img->dsize == img->fsize) {
+ opti_tx_write(resp_success, img->cur_chunk_meta.ack_len);
+ OPTI_MSG("Partition \'%s\' is fully received", img->name);
+ } else {
+ opti_tx_write(resp_failed, img->cur_chunk_meta.ack_len);
+ OPTI_MSG("Partition \'%s\' receiving failed", img->name);
+ }
+ } else if (i >= 4 && !strcmp(cmd[1], CMD_SUB_STORE)) {
+ img_size = simple_strtoul(cmd[4], NULL, 10);
+ /* Init first image chunk by full image name and full image size. */
+ optimus_chunk_init(cmd[2], img_size);
+ }
+}
+
+static void cb_lowpower(const struct usb_request *req)
+{
+ struct optimus_img *img;
+
+ img = optimus_get_img();
+ opti_tx_write(resp_success, img->cur_chunk_meta.ack_len);
+}
+
+static void cb_bl_is_old(const struct usb_request *req)
+{
+ struct optimus_img *img;
+
+ img = optimus_get_img();
+ opti_tx_write(resp_success, img->cur_chunk_meta.ack_len);
+}
+
+static void cb_erase_bl(const struct usb_request *req)
+{
+ struct optimus_img *img;
+
+ img = optimus_get_img();
+ opti_tx_write(resp_success, img->cur_chunk_meta.ack_len);
+}
+
+static void cb_reset(const struct usb_request *req)
+{
+ meson_sm_set_usb_boot_mode(FORCE_USB_BOOT);
+ meson_sm_reboot(REBOOT_REASON_NORMAL);
+}
+
+static void cb_upload(const struct usb_request *req)
+{
+ const char **param_list;
+ size_t resp_len = 0;
+ char *req_buf_ch;
+
+ req_buf_ch = (char *)req->buf;
+ req_buf_ch[MAX_RESPONSE_LEN - 1] = '\0';
+ param_list = str_to_list(req_buf_ch);
+
+ if (param_list) {
+ size_t param_count = 0;
+
+ while (param_list[param_count])
+ param_count++;
+
+ if (param_count > 4)
+ resp_len = simple_strtoul(param_list[4], NULL, 0);
+ } else {
+ resp_len = 0x4;
+ }
+
+ str_free_list(param_list);
+
+ opti_tx_write(resp_success, resp_len);
+}
+
+static void cb_verify(const struct usb_request *req)
+{
+ struct optimus_img *img;
+
+ img = optimus_get_img();
+ opti_tx_write(resp_success, img->cur_chunk_meta.ack_len);
+}
+
+static bool opti_must_skip_block(struct mtd_info *mtd, off_t offset)
+{
+ if (mtd_block_isbad(mtd, offset))
+ return true;
+
+ if (mtd_block_isreserved(mtd, offset))
+ return true;
+
+ return false;
+}
+
+static void cb_disk_initial(const struct usb_request *req)
+{
+ struct optimus_img *img;
+ struct mtd_info *mtd;
+ off_t offset;
+
+ mtd = get_mtd_device_nm("bootloader");
+ if (!mtd || !mtd->parent)
+ return;
+
+ mtd = mtd->parent;
+
+ for (offset = 0; offset < mtd->size; offset += mtd->erasesize) {
+ struct erase_info erase_op = {};
+ int ret;
+
+ if (opti_must_skip_block(mtd, offset))
+ continue;
+
+ erase_op.mtd = mtd;
+ erase_op.addr = offset;
+ erase_op.len = mtd->erasesize;
+
+ ret = mtd_erase(mtd, &erase_op);
+ if (ret) {
+ OPTI_ERR("Can't erase block at %lx\n",
+ offset);
+ return;
+ }
+ }
+
+ img = optimus_get_img();
+ opti_tx_write(resp_success, img->cur_chunk_meta.ack_len);
+}
+
+static void cb_save_setting(const struct usb_request *req)
+{
+ struct optimus_img *img;
+
+ img = optimus_get_img();
+ opti_tx_write(resp_success, img->cur_chunk_meta.ack_len);
+}
+
+static void cb_burn_complete(const struct usb_request *req)
+{
+ struct optimus_img *img;
+
+ img = optimus_get_img();
+ opti_tx_write(resp_success, img->cur_chunk_meta.ack_len);
+}
+
+static void cb_echo_1234(const struct usb_request *req)
+{
+ struct optimus_img *img;
+
+ img = optimus_get_img();
+ opti_tx_write(resp_success, img->cur_chunk_meta.ack_len);
+}
+
+static void cb_setenv(const struct usb_request *req)
+{
+ struct optimus_img *img;
+
+ img = optimus_get_img();
+ env_set("firstboot", "1");
+ opti_tx_write(resp_success, img->cur_chunk_meta.ack_len);
+}
+
+static void cb_save(const struct usb_request *req)
+{
+ struct optimus_img *img;
+
+ img = optimus_get_img();
+ env_save();
+ opti_tx_write(resp_success, img->cur_chunk_meta.ack_len);
+}
+
+static void cb_rpmb_reset(const struct usb_request *req)
+{
+ struct optimus_img *img;
+
+ img = optimus_get_img();
+ opti_tx_write(resp_success, img->cur_chunk_meta.ack_len);
+}
+
+static void cb_amlmmc(const struct usb_request *req)
+{
+ struct optimus_img *img;
+
+ img = optimus_get_img();
+ opti_tx_write(resp_success, img->cur_chunk_meta.ack_len);
+}
+
+static void cb_nand(const struct usb_request *req)
+{
+ struct optimus_img *img;
+
+ img = optimus_get_img();
+ opti_tx_write(resp_success, img->cur_chunk_meta.ack_len);
+}
+
+static void opti_rx_handler_command(struct usb_ep *ep, struct usb_request *req)
+{
+ enum optimus_chunk_state ret = optimus_chunk_process(req->buf, req->length);
+ struct optimus_img *img;
+
+ img = optimus_get_img();
+
+ switch (ret) {
+ case OPTI_CHUNK_STATE_COMPLETE:
+ req->complete = NULL;
+ opti_tx_write("OK!!", img->cur_chunk_meta.ack_len);
+ break;
+ case OPTI_CHUNK_STATE_IN_PROGRESS:
+ req->length = img->req_length;
+ req->complete = opti_rx_handler_command;
+ if (!usb_ep_queue(ep, req, 0))
+ break;
+ case OPTI_CHUNK_STATE_ERROR:
+ req->complete = NULL;
+ opti_tx_write("FALSE!!", img->cur_chunk_meta.ack_len);
+ break;
+ case OPTI_CHUNK_STATE_NEED_INIT:
+ break;
+ }
+}
+
+struct cmd_dispatch_info {
+ const char *cmd;
+ cmd_handler cb;
+};
+
+static const struct cmd_dispatch_info cmd_dispatch_info[] = {
+ {
+ .cmd = CMD_RESET,
+ .cb = cb_reset,
+ },
+ {
+ .cmd = CMD_DOWNLOAD,
+ .cb = cb_download,
+ },
+ {
+ .cmd = CMD_LOWPOWER,
+ .cb = cb_lowpower,
+ },
+ {
+ .cmd = CMD_BL_IS_OLD,
+ .cb = cb_bl_is_old,
+ },
+ {
+ .cmd = CMD_ERASE_BL,
+ .cb = cb_erase_bl,
+ },
+ {
+ .cmd = CMD_UPLOAD,
+ .cb = cb_upload,
+ },
+ {
+ .cmd = CMD_VERIFY,
+ .cb = cb_verify,
+ },
+ {
+ .cmd = CMD_DISK_INITIAL,
+ .cb = cb_disk_initial,
+ },
+ {
+ .cmd = CMD_SAVE_SET,
+ .cb = cb_save_setting,
+ },
+ {
+ .cmd = CMD_BURN_COMPL,
+ .cb = cb_burn_complete,
+ },
+ {
+ .cmd = CMD_ECHO_1234,
+ .cb = cb_echo_1234,
+ },
+ {
+ .cmd = CMD_SETENV,
+ .cb = cb_setenv,
+ },
+ {
+ .cmd = CMD_SAVE,
+ .cb = cb_save,
+ },
+ {
+ .cmd = CMD_RPMB_RESET,
+ .cb = cb_rpmb_reset,
+ },
+ {
+ .cmd = CMD_AMLMMC,
+ .cb = cb_amlmmc,
+ },
+ {
+ .cmd = CMD_NAND,
+ .cb = cb_nand,
+ },
+};
+
+static cmd_handler get_handler(const char *cmd)
+{
+ u32 i;
+ const char *str_ptr;
+
+ if (!cmd)
+ return NULL;
+
+ str_ptr = skip_spaces(cmd);
+
+ for (i = 0; i < ARRAY_SIZE(cmd_dispatch_info); i++) {
+ int ret;
+
+ ret = strncmp(str_ptr, cmd_dispatch_info[i].cmd, strlen(cmd_dispatch_info[i].cmd));
+ if (!ret) {
+ OPTI_MSG("In cmd: '%s'", cmd_dispatch_info[i].cmd);
+ return cmd_dispatch_info[i].cb;
+ }
+ }
+
+ return NULL;
+}
diff --git a/drivers/usb/gadget/amlogic/optimus/optimus_download.c b/drivers/usb/gadget/amlogic/optimus/optimus_download.c
new file mode 100644
index 00000000000..c78e39ed23e
--- /dev/null
+++ b/drivers/usb/gadget/amlogic/optimus/optimus_download.c
@@ -0,0 +1,188 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2024 SaluteDevices, Inc.
+ * Author: Vladimir Mitrofanov <vvmitrofanov at salutedevices.com>
+ */
+
+#include <command.h>
+#include <log.h>
+#include <mtd.h>
+#include <nand.h>
+#include <asm/arch/rawnand.h>
+#include <linux/err.h>
+#include <linux/delay.h>
+#include <linux/libfdt.h>
+#include <linux/mtd/nand.h>
+#include <linux/mtd/rawnand.h>
+
+#include "f_fastboot_common.h"
+#include "optimus_download.h"
+
+static struct optimus_img flash_img = { .ptr = NULL };
+
+struct optimus_img *optimus_get_img(void)
+{
+ return &flash_img;
+}
+
+static int optimus_write_normal_image(u32 data_sz, u8 *data)
+{
+ struct optimus_img *img;
+ size_t written_length;
+ size_t length;
+ struct mtd_info *mtd;
+ int ret;
+
+ if (!data_sz)
+ return 0;
+
+ img = optimus_get_img();
+
+ mtd = get_mtd_device_nm(img->name);
+
+ if (IS_ERR_OR_NULL(mtd)) {
+ OPTI_ERR("Failed to get mtd device \"%s\" ret: %ld", img->name, PTR_ERR(mtd));
+ return PTR_ERR(mtd);
+ }
+
+ /* Need use intermediate storage to write conversion of types pointers */
+ length = data_sz;
+
+ written_length = 0;
+ ret = nand_write_skip_bad(mtd, img->media_offset, &length,
+ &written_length, mtd->size, data, 0);
+ img->media_offset += written_length;
+
+ return ret;
+}
+
+void optimus_chunk_init(const char *name, unsigned int size)
+{
+ struct optimus_img *img;
+
+ img = optimus_get_img();
+
+ img->fsize = size;
+
+ memset(img->name, 0, sizeof(img->name));
+ strncpy(img->name, name, sizeof(img->name) - 1);
+
+ img->dsize = 0;
+ img->media_offset = 0;
+}
+
+int optimus_chunk_alloc_buf(unsigned int size)
+{
+ struct optimus_img *img;
+ size_t full_size;
+
+ img = optimus_get_img();
+
+ img->fsize = size;
+ img->cur_chunk_data_len = 0;
+
+ if (!strcmp(img->name, BOOT_LOADER)) {
+ img->type = OPTI_IMG_TYPE_BOOTLOADER;
+ full_size = img->fsize;
+ } else {
+ img->type = OPTI_IMG_TYPE_NORMAL;
+ full_size = img->cur_chunk_meta.data_length;
+ }
+
+ if (!img->ptr)
+ img->ptr = (u8 *)malloc(full_size);
+
+ if (!img->ptr) {
+ OPTI_ERR("No memory for data chunk");
+ return -ENOMEM;
+ }
+
+ return 0;
+}
+
+static void optimus_chunk_free(void)
+{
+ struct optimus_img *img;
+
+ img = optimus_get_img();
+
+ free(img->ptr);
+ img->ptr = NULL;
+}
+
+static unsigned int optimus_csum_special(const void *p_buf, const unsigned int size)
+{
+ const unsigned int *data = (const unsigned int *)p_buf;
+ unsigned int word_len = size >> 2;
+ unsigned int rest = size & 0x3;
+ unsigned int sum = 0;
+
+ while (word_len--)
+ sum += *data++;
+
+ if (rest)
+ sum += (*data) & GENMASK(rest * 8, 0);
+
+ return sum;
+}
+
+enum optimus_chunk_state optimus_chunk_process(const void *data, unsigned int size)
+{
+ struct optimus_img *img;
+ int rw_ret;
+ u32 len_diff;
+ u32 check_sum;
+ u32 pos;
+
+ img = optimus_get_img();
+
+ if (!img->ptr)
+ return OPTI_CHUNK_STATE_NEED_INIT;
+
+ if (img->type == OPTI_IMG_TYPE_BOOTLOADER)
+ pos = img->dsize;
+ else
+ pos = img->cur_chunk_data_len;
+
+ if (img->ptr + pos + size <= img->ptr + img->fsize)
+ memcpy(img->ptr + pos, data, size);
+ else
+ return OPTI_CHUNK_STATE_ERROR;
+
+ img->dsize += size;
+ img->cur_chunk_data_len += size;
+ if (img->cur_chunk_data_len < img->cur_chunk_meta.data_length) {
+ len_diff = img->cur_chunk_meta.data_length - img->cur_chunk_data_len;
+ img->req_length = min_t(u32, len_diff, EP_BUFFER_SIZE);
+ return OPTI_CHUNK_STATE_IN_PROGRESS;
+ }
+
+ /* All chunk data received. */
+ if (img->type == OPTI_IMG_TYPE_BOOTLOADER)
+ pos = img->dsize - img->cur_chunk_data_len;
+ else
+ pos = 0;
+
+ check_sum = optimus_csum_special(img->ptr + pos, img->cur_chunk_data_len);
+ if (check_sum != img->cur_chunk_meta.chksum)
+ return OPTI_CHUNK_STATE_ERROR;
+
+ if (img->type == OPTI_IMG_TYPE_BOOTLOADER && size) {
+ /* Not full bootloader image. Chunk is only part. */
+ if (img->dsize < img->fsize)
+ return OPTI_CHUNK_STATE_COMPLETE;
+
+ /* We write bootloader after getting full image */
+ rw_ret = meson_bootloader_write(img->ptr, img->fsize);
+ optimus_chunk_free();
+ if (rw_ret)
+ return OPTI_CHUNK_STATE_ERROR;
+ } else {
+ rw_ret = optimus_write_normal_image((u32)img->cur_chunk_data_len, img->ptr);
+ optimus_chunk_free();
+ if (rw_ret)
+ return OPTI_CHUNK_STATE_ERROR;
+ }
+
+ return OPTI_CHUNK_STATE_COMPLETE;
+}
diff --git a/drivers/usb/gadget/amlogic/optimus/optimus_download.h b/drivers/usb/gadget/amlogic/optimus/optimus_download.h
new file mode 100644
index 00000000000..3a8682df045
--- /dev/null
+++ b/drivers/usb/gadget/amlogic/optimus/optimus_download.h
@@ -0,0 +1,86 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2024 SaluteDevices, Inc. All rights reserved.
+ * Author: Vladimir Mitrofanov <vvmitrofanov at salutedevices.com>
+ */
+
+#ifndef __OPTIMUS_DOWNLOAD_H__
+#define __OPTIMUS_DOWNLOAD_H__
+
+#define OPTI_PREF "[OPTI][MSG] "
+#define ERASE_SEQ "\x1B[2K\r"
+#define __OPTI_MSG(prefix, fmt, ...) printf(prefix fmt, ##__VA_ARGS__)
+
+#define OPTI_MSG(fmt, ...) __OPTI_MSG("\n" OPTI_PREF, fmt, ##__VA_ARGS__)
+#define OPTI_MSG_ERASE(fmt, ...) __OPTI_MSG(ERASE_SEQ "" OPTI_PREF, fmt, ##__VA_ARGS__)
+#define OPTI_ERR(fmt, ...) pr_err("\n[OPTI][ERR]: " fmt, ##__VA_ARGS__)
+
+enum optimus_img_type {
+ OPTI_IMG_TYPE_BOOTLOADER,
+ OPTI_IMG_TYPE_NORMAL,
+};
+
+enum optimus_chunk_state {
+ OPTI_CHUNK_STATE_COMPLETE,
+ OPTI_CHUNK_STATE_IN_PROGRESS,
+ OPTI_CHUNK_STATE_NEED_INIT,
+ OPTI_CHUNK_STATE_ERROR,
+};
+
+struct optimus_chunk {
+ u32 retry_times; /* Unused in this version. */
+ u32 data_length; /* Length of data in this chunk. */
+ u32 seq_num; /* Unused in this version. */
+ u32 chksum; /* Checksum (see 'optimus_csum_special()'. */
+ u16 chksum_alg; /* Unused in this version. */
+ u16 ack_len; /* ACK packet length, expected by sender. */
+} __packed;
+
+struct optimus_img {
+ u8 *ptr; /* Data buffer. */
+ size_t dsize; /* Downloaded size. */
+ size_t fsize; /* Image full size. */
+ char name[256]; /* Current partition name. */
+ enum optimus_img_type type; /* Current partition type. */
+
+ struct optimus_chunk cur_chunk_meta; /* Current chunk. */
+ u32 cur_chunk_data_len; /* Downloaded size for current chunk. */
+ u32 req_length; /* Rest to download, limited by EP buffer. */
+ loff_t media_offset; /* Current offset to write this image. */
+};
+
+/**
+ * optimus_get_img() - Get current Optimus image (context).
+ *
+ * @return: pointer to Optimus image (context).
+ */
+struct optimus_img *optimus_get_img(void);
+
+/**
+ * optimus_chunk_init() - Initialize "chunk" to receive data.
+ *
+ * @name: "chunk" name.
+ * @size: full size of data to receive.
+ */
+void optimus_chunk_init(const char *name, unsigned int size);
+
+/**
+ * optimus_chunk_alloc_buf() - Alloc buffer inside "chunk" for data.
+ *
+ * @size: full size of data.
+ *
+ * @return: 0 on success, -errno otherwise.
+ */
+int optimus_chunk_alloc_buf(unsigned int size);
+
+/**
+ * optimus_chunk_process() - Pass new portion of data to "chunk".
+ *
+ * @data: new portion of data.
+ * @size: size of data.
+ *
+ * @return: new state of "chunk".
+ */
+enum optimus_chunk_state optimus_chunk_process(const void *data, unsigned int size);
+
+#endif /* __OPTIMUS_DOWNLOAD_H__ */
--
2.30.1
^ permalink raw reply [flat|nested] 13+ messages in thread
* [rulkc] [PATCH v1 11/11] arch: arm: axg: Optimus protocol support
2025-07-29 18:28 [rulkc] [PATCH v1 00/11] Amlogic: ADNL and Optimus protocols support avkrasnov
` (9 preceding siblings ...)
2025-07-29 18:29 ` [rulkc] [PATCH v1 10/11] usb: gadget: amlogic: implement Optimus protocol avkrasnov
@ 2025-07-29 18:29 ` avkrasnov
2025-07-31 1:07 ` [rulkc] [PATCH v1 00/11] Amlogic: ADNL and Optimus protocols support slava
11 siblings, 0 replies; 13+ messages in thread
From: avkrasnov @ 2025-07-29 18:29 UTC (permalink / raw)
From: Vladimir Mitrofanov <vvmitrofanov at salutedevices.com>
During Optimus protocol operations, BootROM may request to enter
Optimus mode after boot. So to support this, let's check for values
in sticky registers after boot (such registers could be updated
earlier by boot ROM, telling U-boot to enter Optimus mode).
Signed-off-by: Vladimir Mitrofanov <vvmitrofanov at salutedevices.com>
Signed-off-by: Arseniy Krasnov <avkrasnov at salutedevices.com>
---
arch/arm/include/asm/arch-meson/axg.h | 12 ++++++++++++
arch/arm/mach-meson/board-axg.c | 12 ++++++++++++
arch/arm/mach-meson/board-common.c | 15 +++++++++++++++
3 files changed, 39 insertions(+)
diff --git a/arch/arm/include/asm/arch-meson/axg.h b/arch/arm/include/asm/arch-meson/axg.h
index 12042de9359..e99cd3aa780 100644
--- a/arch/arm/include/asm/arch-meson/axg.h
+++ b/arch/arm/include/asm/arch-meson/axg.h
@@ -23,6 +23,7 @@
#define AXG_AO_SEC_GP_CFG3 AXG_AO_ADDR(0x93)
#define AXG_AO_SEC_GP_CFG4 AXG_AO_ADDR(0x94)
#define AXG_AO_SEC_GP_CFG5 AXG_AO_ADDR(0x95)
+#define AXG_AO_SEC_GP_CFG7 AXG_AO_ADDR(0x97)
#define AXG_AO_BOOT_DEVICE 0xF
#define AXG_AO_MEM_SIZE_MASK 0xFFFF0000
@@ -31,4 +32,15 @@
#define AXG_AO_BL31_RSVMEM_SIZE_SHIFT 16
#define AXG_AO_BL32_RSVMEM_SIZE_MASK 0xFFFF
+#if CONFIG_IS_ENABLED(CMD_OPTIMUS)
+/**
+ * is_tpl_loaded_from_usb - Checks, that this instance of U-boot was
+ * loaded from USB during firmware update
+ * process.
+ *
+ * returns: true, if so.
+ */
+int is_tpl_loaded_from_usb(void);
+#endif
+
#endif /* __AXG_H__ */
diff --git a/arch/arm/mach-meson/board-axg.c b/arch/arm/mach-meson/board-axg.c
index 6535539184c..13eb66dfc21 100644
--- a/arch/arm/mach-meson/board-axg.c
+++ b/arch/arm/mach-meson/board-axg.c
@@ -4,12 +4,14 @@
* (C) Copyright 2018 Neil Armstrong <narmstrong at baylibre.com>
*/
+#include <command.h>
#include <init.h>
#include <net.h>
#include <asm/arch/boot.h>
#include <asm/arch/eth.h>
#include <asm/arch/axg.h>
#include <asm/arch/mem.h>
+#include <asm/arch-meson/sm.h>
#include <asm/global_data.h>
#include <asm/io.h>
#include <asm/armv8/mmu.h>
@@ -62,6 +64,16 @@ phys_size_t get_effective_memsize(void)
>> AXG_AO_MEM_SIZE_SHIFT) * SZ_1M;
}
+#if CONFIG_IS_ENABLED(CMD_OPTIMUS)
+int is_tpl_loaded_from_usb(void)
+{
+ const int boot_id = meson_get_boot_device();
+ const unsigned int force_usb_boot = readl(AXG_AO_SEC_GP_CFG7);
+
+ return (boot_id == BOOT_DEVICE_USB || (force_usb_boot & BIT(31)));
+}
+#endif
+
static struct mm_region axg_mem_map[] = {
{
.virt = 0x0UL,
diff --git a/arch/arm/mach-meson/board-common.c b/arch/arm/mach-meson/board-common.c
index 0624d557e40..639c7a64507 100644
--- a/arch/arm/mach-meson/board-common.c
+++ b/arch/arm/mach-meson/board-common.c
@@ -16,6 +16,7 @@
#include <linux/libfdt.h>
#include <linux/err.h>
#include <asm/arch/a1.h>
+#include <asm/arch/axg.h>
#include <asm/arch/mem.h>
#include <asm/arch/sm.h>
#include <asm/armv8/mmu.h>
@@ -144,6 +145,18 @@ __weak int meson_board_late_init(void)
return 0;
}
+static void meson_board_try_enter_optimus(void)
+{
+#if (IS_ENABLED(CONFIG_CMD_OPTIMUS))
+ if (!is_tpl_loaded_from_usb())
+ return;
+
+ meson_sm_set_usb_boot_mode(CLEAR_USB_BOOT);
+
+ run_command("optimus", 0);
+#endif
+}
+
static void meson_board_try_enter_adnl(void)
{
#if (IS_ENABLED(CONFIG_CMD_ADNL))
@@ -162,6 +175,8 @@ int board_late_init(void)
meson_board_try_enter_adnl();
+ meson_board_try_enter_optimus();
+
return meson_board_late_init();
}
--
2.30.1
^ permalink raw reply [flat|nested] 13+ messages in thread
* [rulkc] [PATCH v1 00/11] Amlogic: ADNL and Optimus protocols support
2025-07-29 18:28 [rulkc] [PATCH v1 00/11] Amlogic: ADNL and Optimus protocols support avkrasnov
` (10 preceding siblings ...)
2025-07-29 18:29 ` [rulkc] [PATCH v1 11/11] arch: arm: axg: Optimus protocol support avkrasnov
@ 2025-07-31 1:07 ` slava
11 siblings, 0 replies; 13+ messages in thread
From: slava @ 2025-07-31 1:07 UTC (permalink / raw)
On Tue, 2025-07-29 at 21:28 +0300, Arseniy Krasnov wrote:
> This patchset adds support of two Amlogic firmware burning protocols:
> ADNL and Optimus. Each protocol is supported on the following SoC:
>
I think that description is missing some important information. It is
not clear why these two protocols implementation should be added in the
kernel. Do they faster than another one? Is it possible to share some
benchmarking results, then? Do they essential for supporting some
hardware? Do they provide some really fancy new features?
And, also, why two protocols implementation have been sent in one
patchset?
Thanks,
Slava.
> axg, g12a, g12b, sm1 - Optimus.
> a1, s4, a5, c1, c2, c3, sc2, t3, t7, p1 - ADNL.
>
> Both work in fastboot manner, but significant feature is that they
> are
> supported by current Amlogic BootROMs. As transport both use USB:
> device is switched to gadget mode and then it can communicate with
> PC.
> On PC side protocols are supported by:
>
> 1) https://github.com/superna9999/pyamlboot (with some limitation)
>
> For example:
> pyamlboot/./ubt.py --img <image> --wipe all --reset
>
> Note, image has special format, specific for Amlogic.
>
> 2) Proprietary vendor tools.
>
> As protocols are supported by BootROM, it allows to "resurrect"
> devices
> when boot image (BL2, Uboot) missed/invalid. Typical flow for such
> cases
> is (both protocols):
>
> DEVICE PC
>
> *-------------------*
> | |
> | Runs update tool, |
> | waits for device |
> | on USB bus. |
> | |
> *-------------------*
> |
> *---------------------* |
> > Power on |<-----<------/
> *---------------------*
> |
> |
> *---------------------*
> > |
> > Boot ROM switches |
> > device to USB gadget|>----->------\
> > mode and waits for | |
> > commands. | |
> > | |
> *---------------------* |
> |
> *-------------------*
> | Detects device on |
> | USB bus. |
> *-------------------*
> |
> |
> *-------------------*
> | Sends BL2 image |
> | over USB. |
> *-------------------*
> |
> *---------------------* |
> > Boot ROM runs |<-----<------/
> > BL2 image. |
> *---------------------*
> |
> |
> *---------------------*
> > BL2 waits for |>----->------\
> > Uboot image (FIP). | |
> *---------------------* |
> |
> *-------------------*
> | Sends Uboot image |
> | over USB. |
> *-------------------*
> |
> *---------------------* |
> > BL2 runs received |<-----<------/
> > Uboot image. |
> *---------------------*
> |
> |
> *---------------------*
> > Uboot waits for |>----->------\
> > commands. | |
> *---------------------* |
> |
> *-------------------*
> | Sends commands to |
> | over USB. |
> *-------------------*
> |
> *---------------------* |
> > Uboot processes | |
> > commands: erases, | |
> > writes NAND, checks |<-----<------/
> > CRC of written data |
> > etc. |
> *---------------------*
> |
> |
> *---------------------*
> > Uboot does reboot |
> *---------------------*
>
> There is also another mode to operate both protocols when we run not
> from BootROM, but from Uboot shell. To do that, we enter protocol
> specific command in shell ("adnl" or "optimus"). In this case Uboot
> switches device to gadget mode, write some value to special register
> and does reboot. Then BootROM checks above mentioned register and
> then
> switches device to gadget mode. After that flow from above is
> executed.
> This mode has same purposes as image update by 'fastboot'.
>
> Limitations:
> 1) Only limited set of each protocol commands are supported -
> it is enough to update image. But, for example 'secureboot'
> feature is not implemented.
> 2) ADNL was tested on A1 SoC only.
> 3) Optimus was tested on AXG SoC only.
>
> For more details of each protocol pls see the following:
> ADNL - drivers/usb/gadget/amlogic/adnl/f_adnl.c
> Optimus - drivers/usb/gadget/amlogic/optimus/f_optimus.c.
>
> Arseniy Krasnov (8):
> arch: arm: meson: use 'meson_sm_call()' in 'meson_sm_pwrdm_set()'
> usb: gadget: fastboot: make part of USB fastboot code shared
> usb: gadget: amlogic: common code for Amlogic flashing commands
> mtd: rawnand: meson: move some defines to dedicated header
> arch: arm: meson: bootloader write support
> arm: meson: a1: add A1_SYSCTRL_SEC_STICKY_REG2
> usb: gadget: amlogic: implement ADNL protocol
> arm: meson: a1: ADNL protocol support
>
> Vladimir Mitrofanov (3):
> arch: arm: meson: sm: add commands to reboot device in different
> modes
> usb: gadget: amlogic: implement Optimus protocol
> arch: arm: axg: Optimus protocol support
>
> arch/arm/include/asm/arch-meson/a1.h | 19 +
> arch/arm/include/asm/arch-meson/axg.h | 12 +
> arch/arm/include/asm/arch-meson/nand.h | 34 +
> arch/arm/include/asm/arch-meson/rawnand.h | 38 +
> arch/arm/include/asm/arch-meson/sm.h | 98 +-
> arch/arm/include/asm/arch-meson/spinand.h | 43 +
> arch/arm/mach-meson/Kconfig | 31 +
> arch/arm/mach-meson/Makefile | 4 +-
> arch/arm/mach-meson/board-a1.c | 32 +-
> arch/arm/mach-meson/board-axg.c | 12 +
> arch/arm/mach-meson/board-common.c | 32 +
> arch/arm/mach-meson/rawnand.c | 291 ++++++
> arch/arm/mach-meson/sm.c | 43 +-
> arch/arm/mach-meson/spinand.c | 158 ++++
> cmd/Kconfig | 14 +
> cmd/meson/Makefile | 2 +
> cmd/meson/adnl.c | 27 +
> cmd/meson/gadget.c | 183 ++++
> cmd/meson/gadget.h | 28 +
> cmd/meson/optimus.c | 21 +
> drivers/mtd/nand/raw/meson_nand.c | 13 +-
> drivers/sm/meson-sm.c | 3 +
> drivers/usb/gadget/Kconfig | 2 +
> drivers/usb/gadget/Makefile | 4 +-
> drivers/usb/gadget/amlogic/Kconfig | 12 +
> drivers/usb/gadget/amlogic/adnl/Kconfig | 29 +
> drivers/usb/gadget/amlogic/adnl/Makefile | 4 +
> drivers/usb/gadget/amlogic/adnl/adnl.h | 124 +++
> .../gadget/amlogic/adnl/adnl_buff_manager.c | 316 +++++++
> drivers/usb/gadget/amlogic/adnl/adnl_media.c | 235 +++++
> .../usb/gadget/amlogic/adnl/adnl_storage.c | 140 +++
> drivers/usb/gadget/amlogic/adnl/f_adnl.c | 835
> ++++++++++++++++++
> drivers/usb/gadget/amlogic/optimus/Kconfig | 12 +
> drivers/usb/gadget/amlogic/optimus/Makefile | 7 +
> .../usb/gadget/amlogic/optimus/f_optimus.c | 687 ++++++++++++++
> .../gadget/amlogic/optimus/optimus_download.c | 188 ++++
> .../gadget/amlogic/optimus/optimus_download.h | 86 ++
> drivers/usb/gadget/f_fastboot.c | 295 +------
> drivers/usb/gadget/f_fastboot_common.c | 320 +++++++
> drivers/usb/gadget/f_fastboot_common.h | 71 ++
> include/meson/sm.h | 3 +
> 41 files changed, 4202 insertions(+), 306 deletions(-)
> create mode 100644 arch/arm/include/asm/arch-meson/nand.h
> create mode 100644 arch/arm/include/asm/arch-meson/rawnand.h
> create mode 100644 arch/arm/include/asm/arch-meson/spinand.h
> create mode 100644 arch/arm/mach-meson/rawnand.c
> create mode 100644 arch/arm/mach-meson/spinand.c
> create mode 100644 cmd/meson/adnl.c
> create mode 100644 cmd/meson/gadget.c
> create mode 100644 cmd/meson/gadget.h
> create mode 100644 cmd/meson/optimus.c
> create mode 100644 drivers/usb/gadget/amlogic/Kconfig
> create mode 100644 drivers/usb/gadget/amlogic/adnl/Kconfig
> create mode 100644 drivers/usb/gadget/amlogic/adnl/Makefile
> create mode 100644 drivers/usb/gadget/amlogic/adnl/adnl.h
> create mode 100644
> drivers/usb/gadget/amlogic/adnl/adnl_buff_manager.c
> create mode 100644 drivers/usb/gadget/amlogic/adnl/adnl_media.c
> create mode 100644 drivers/usb/gadget/amlogic/adnl/adnl_storage.c
> create mode 100644 drivers/usb/gadget/amlogic/adnl/f_adnl.c
> create mode 100644 drivers/usb/gadget/amlogic/optimus/Kconfig
> create mode 100644 drivers/usb/gadget/amlogic/optimus/Makefile
> create mode 100644 drivers/usb/gadget/amlogic/optimus/f_optimus.c
> create mode 100644
> drivers/usb/gadget/amlogic/optimus/optimus_download.c
> create mode 100644
> drivers/usb/gadget/amlogic/optimus/optimus_download.h
> create mode 100644 drivers/usb/gadget/f_fastboot_common.c
> create mode 100644 drivers/usb/gadget/f_fastboot_common.h
^ permalink raw reply [flat|nested] 13+ messages in thread
end of thread, other threads:[~2025-07-31 1:07 UTC | newest]
Thread overview: 13+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2025-07-29 18:28 [rulkc] [PATCH v1 00/11] Amlogic: ADNL and Optimus protocols support avkrasnov
2025-07-29 18:29 ` [rulkc] [PATCH v1 01/11] arch: arm: meson: sm: add commands to reboot device in different modes avkrasnov
2025-07-29 18:29 ` [rulkc] [PATCH v1 02/11] arch: arm: meson: use 'meson_sm_call()' in 'meson_sm_pwrdm_set()' avkrasnov
2025-07-29 18:29 ` [rulkc] [PATCH v1 03/11] usb: gadget: fastboot: make part of USB fastboot code shared avkrasnov
2025-07-29 18:29 ` [rulkc] [PATCH v1 04/11] usb: gadget: amlogic: common code for Amlogic flashing commands avkrasnov
2025-07-29 18:29 ` [rulkc] [PATCH v1 05/11] mtd: rawnand: meson: move some defines to dedicated header avkrasnov
2025-07-29 18:29 ` [rulkc] [PATCH v1 06/11] arch: arm: meson: bootloader write support avkrasnov
2025-07-29 18:29 ` [rulkc] [PATCH v1 07/11] arm: meson: a1: add A1_SYSCTRL_SEC_STICKY_REG2 avkrasnov
2025-07-29 18:29 ` [rulkc] [PATCH v1 08/11] usb: gadget: amlogic: implement ADNL protocol avkrasnov
2025-07-29 18:29 ` [rulkc] [PATCH v1 09/11] arm: meson: a1: ADNL protocol support avkrasnov
2025-07-29 18:29 ` [rulkc] [PATCH v1 10/11] usb: gadget: amlogic: implement Optimus protocol avkrasnov
2025-07-29 18:29 ` [rulkc] [PATCH v1 11/11] arch: arm: axg: Optimus protocol support avkrasnov
2025-07-31 1:07 ` [rulkc] [PATCH v1 00/11] Amlogic: ADNL and Optimus protocols support slava
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®