From: Vladimir Mitrofanov <vvmitrofa...@salutedevices.com>

Add Amlogic's Optimus protocol support for USB transport.

Signed-off-by: Vladimir Mitrofanov <vvmitrofa...@salutedevices.com>
Signed-off-by: Arseniy Krasnov <avkras...@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 <vvmitrofa...@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 <vvmitrofa...@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 <vvmitrofa...@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 <vvmitrofa...@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

Reply via email to