xref: /rk3399_rockchip-uboot/cmd/rockusb.c (revision bc670a7230ccec7f5b48bc8eebe4370902e40fd6)
1aa9b1b59SFrank Wang /*
2aa9b1b59SFrank Wang  * Copyright 2017 Rockchip Electronics Co., Ltd
3aa9b1b59SFrank Wang  * Frank Wang <frank.wang@rock-chips.com>
4aa9b1b59SFrank Wang  *
5aa9b1b59SFrank Wang  * SPDX-License-Identifier:	GPL-2.0+
6aa9b1b59SFrank Wang  */
7aa9b1b59SFrank Wang 
8aa9b1b59SFrank Wang #include <errno.h>
9aa9b1b59SFrank Wang #include <common.h>
10aa9b1b59SFrank Wang #include <command.h>
11aa9b1b59SFrank Wang #include <console.h>
12aa9b1b59SFrank Wang #include <g_dnl.h>
13aa9b1b59SFrank Wang #include <part.h>
14aa9b1b59SFrank Wang #include <usb.h>
15aa9b1b59SFrank Wang #include <usb_mass_storage.h>
16aa9b1b59SFrank Wang #include <rockusb.h>
17aa9b1b59SFrank Wang 
18aa9b1b59SFrank Wang static struct rockusb rkusb;
19aa9b1b59SFrank Wang static struct rockusb *g_rkusb;
20aa9b1b59SFrank Wang 
21aa9b1b59SFrank Wang static int rkusb_read_sector(struct ums *ums_dev,
22aa9b1b59SFrank Wang 			     ulong start, lbaint_t blkcnt, void *buf)
23aa9b1b59SFrank Wang {
24aa9b1b59SFrank Wang 	struct blk_desc *block_dev = &ums_dev->block_dev;
25aa9b1b59SFrank Wang 	lbaint_t blkstart = start + ums_dev->start_sector;
26b4609917SFrank Wang 	int ret;
27aa9b1b59SFrank Wang 
28957c1cf2SShunqian Zheng 	if ((blkstart + blkcnt) > RKUSB_READ_LIMIT_ADDR) {
29628c8271SJason Zhu 		memset(buf, 0xcc, blkcnt * SECTOR_SIZE);
30628c8271SJason Zhu 		return blkcnt;
31628c8271SJason Zhu 	} else {
32b4609917SFrank Wang 		ret = blk_dread(block_dev, blkstart, blkcnt, buf);
33b4609917SFrank Wang 		if (!ret)
34b4609917SFrank Wang 			ret = -EIO;
35b4609917SFrank Wang 		return ret;
36aa9b1b59SFrank Wang 	}
37628c8271SJason Zhu }
38aa9b1b59SFrank Wang 
39aa9b1b59SFrank Wang static int rkusb_write_sector(struct ums *ums_dev,
40aa9b1b59SFrank Wang 			      ulong start, lbaint_t blkcnt, const void *buf)
41aa9b1b59SFrank Wang {
42aa9b1b59SFrank Wang 	struct blk_desc *block_dev = &ums_dev->block_dev;
43aa9b1b59SFrank Wang 	lbaint_t blkstart = start + ums_dev->start_sector;
44177c8736SJon Lin 	int ret;
45aa9b1b59SFrank Wang 
46177c8736SJon Lin 	if (block_dev->if_type == IF_TYPE_MTD)
47177c8736SJon Lin 		block_dev->op_flag |= BLK_MTD_CONT_WRITE;
48b4609917SFrank Wang 
49177c8736SJon Lin 	ret = blk_dwrite(block_dev, blkstart, blkcnt, buf);
50b4609917SFrank Wang 	if (!ret)
51b4609917SFrank Wang 		ret = -EIO;
52*bc670a72SYifeng Zhao #if defined(CONFIG_SCSI) && defined(CONFIG_CMD_SCSI) && (defined(CONFIG_UFS))
53*bc670a72SYifeng Zhao 	if (block_dev->if_type == IF_TYPE_SCSI && block_dev->rawblksz == 4096) {
54*bc670a72SYifeng Zhao 		/* write loader to UFS BootA */
55*bc670a72SYifeng Zhao 		if (blkstart < 8192 && blkstart >= 64)
56*bc670a72SYifeng Zhao 			blk_write_devnum(IF_TYPE_SCSI, 1, blkstart, blkcnt, (ulong *)buf);
57*bc670a72SYifeng Zhao 	}
58*bc670a72SYifeng Zhao #endif
59177c8736SJon Lin 	if (block_dev->if_type == IF_TYPE_MTD)
60177c8736SJon Lin 		block_dev->op_flag &= ~(BLK_MTD_CONT_WRITE);
61177c8736SJon Lin 	return ret;
62aa9b1b59SFrank Wang }
63aa9b1b59SFrank Wang 
64aa9b1b59SFrank Wang static int rkusb_erase_sector(struct ums *ums_dev,
65aa9b1b59SFrank Wang 			      ulong start, lbaint_t blkcnt)
66aa9b1b59SFrank Wang {
67aa9b1b59SFrank Wang 	struct blk_desc *block_dev = &ums_dev->block_dev;
68aa9b1b59SFrank Wang 	lbaint_t blkstart = start + ums_dev->start_sector;
69aa9b1b59SFrank Wang 
70aa9b1b59SFrank Wang 	return blk_derase(block_dev, blkstart, blkcnt);
71aa9b1b59SFrank Wang }
72aa9b1b59SFrank Wang 
73aa9b1b59SFrank Wang static void rkusb_fini(void)
74aa9b1b59SFrank Wang {
75aa9b1b59SFrank Wang 	int i;
76aa9b1b59SFrank Wang 
77aa9b1b59SFrank Wang 	for (i = 0; i < g_rkusb->ums_cnt; i++)
78aa9b1b59SFrank Wang 		free((void *)g_rkusb->ums[i].name);
79aa9b1b59SFrank Wang 	free(g_rkusb->ums);
80aa9b1b59SFrank Wang 	g_rkusb->ums = NULL;
81aa9b1b59SFrank Wang 	g_rkusb->ums_cnt = 0;
82f5b174d4SFrank Wang 	g_rkusb = NULL;
83aa9b1b59SFrank Wang }
84aa9b1b59SFrank Wang 
85aa9b1b59SFrank Wang #define RKUSB_NAME_LEN 16
86aa9b1b59SFrank Wang 
87aa9b1b59SFrank Wang static int rkusb_init(const char *devtype, const char *devnums_part_str)
88aa9b1b59SFrank Wang {
89aa9b1b59SFrank Wang 	char *s, *t, *devnum_part_str, *name;
90aa9b1b59SFrank Wang 	struct blk_desc *block_dev;
91aa9b1b59SFrank Wang 	disk_partition_t info;
92aa9b1b59SFrank Wang 	int partnum, cnt;
93aa9b1b59SFrank Wang 	int ret = -1;
94aa9b1b59SFrank Wang 	struct ums *ums_new;
95aa9b1b59SFrank Wang 
96aa9b1b59SFrank Wang 	s = strdup(devnums_part_str);
97aa9b1b59SFrank Wang 	if (!s)
98aa9b1b59SFrank Wang 		return -1;
99aa9b1b59SFrank Wang 
100aa9b1b59SFrank Wang 	t = s;
101aa9b1b59SFrank Wang 	g_rkusb->ums_cnt = 0;
102aa9b1b59SFrank Wang 
103aa9b1b59SFrank Wang 	for (;;) {
104aa9b1b59SFrank Wang 		devnum_part_str = strsep(&t, ",");
105aa9b1b59SFrank Wang 		if (!devnum_part_str)
106aa9b1b59SFrank Wang 			break;
107*bc670a72SYifeng Zhao #if defined(CONFIG_SCSI) && defined(CONFIG_CMD_SCSI) && (defined(CONFIG_UFS))
108*bc670a72SYifeng Zhao 		if (!strcmp(devtype, "scsi")) {
109*bc670a72SYifeng Zhao 			block_dev= blk_get_devnum_by_typename(devtype, 0);
110*bc670a72SYifeng Zhao 			if (block_dev == NULL)
111*bc670a72SYifeng Zhao 				return -ENXIO;
112*bc670a72SYifeng Zhao 		} else
113*bc670a72SYifeng Zhao #endif
114*bc670a72SYifeng Zhao 		{
115aa9b1b59SFrank Wang 			partnum = blk_get_device_part_str(devtype, devnum_part_str,
116aa9b1b59SFrank Wang 						&block_dev, &info, 1);
117aa9b1b59SFrank Wang 			if (partnum < 0)
118aa9b1b59SFrank Wang 				goto cleanup;
119*bc670a72SYifeng Zhao 		}
120aa9b1b59SFrank Wang 
121aa9b1b59SFrank Wang 		/* f_mass_storage.c assumes SECTOR_SIZE sectors */
122aa9b1b59SFrank Wang 		if (block_dev->blksz != SECTOR_SIZE)
123aa9b1b59SFrank Wang 			goto cleanup;
124aa9b1b59SFrank Wang 
125aa9b1b59SFrank Wang 		ums_new = realloc(g_rkusb->ums, (g_rkusb->ums_cnt + 1) *
126aa9b1b59SFrank Wang 				  sizeof(*g_rkusb->ums));
127aa9b1b59SFrank Wang 		if (!ums_new)
128aa9b1b59SFrank Wang 			goto cleanup;
129aa9b1b59SFrank Wang 		g_rkusb->ums = ums_new;
130aa9b1b59SFrank Wang 		cnt = g_rkusb->ums_cnt;
131aa9b1b59SFrank Wang 
132dd31eefeSFrank Wang 		/* Expose all partitions for rockusb command */
133aa9b1b59SFrank Wang 		g_rkusb->ums[cnt].start_sector = 0;
134aa9b1b59SFrank Wang 		g_rkusb->ums[cnt].num_sectors = block_dev->lba;
135aa9b1b59SFrank Wang 
136aa9b1b59SFrank Wang 		g_rkusb->ums[cnt].read_sector = rkusb_read_sector;
137aa9b1b59SFrank Wang 		g_rkusb->ums[cnt].write_sector = rkusb_write_sector;
138aa9b1b59SFrank Wang 		g_rkusb->ums[cnt].erase_sector = rkusb_erase_sector;
139aa9b1b59SFrank Wang 
140aa9b1b59SFrank Wang 		name = malloc(RKUSB_NAME_LEN);
141aa9b1b59SFrank Wang 		if (!name)
142aa9b1b59SFrank Wang 			goto cleanup;
143aa9b1b59SFrank Wang 		snprintf(name, RKUSB_NAME_LEN, "rkusb disk %d", cnt);
144aa9b1b59SFrank Wang 		g_rkusb->ums[cnt].name = name;
145aa9b1b59SFrank Wang 		g_rkusb->ums[cnt].block_dev = *block_dev;
146aa9b1b59SFrank Wang 
147aa9b1b59SFrank Wang 		printf("RKUSB: LUN %d, dev %d, hwpart %d, sector %#x, count %#x\n",
148aa9b1b59SFrank Wang 		       g_rkusb->ums_cnt,
149aa9b1b59SFrank Wang 		       g_rkusb->ums[cnt].block_dev.devnum,
150aa9b1b59SFrank Wang 		       g_rkusb->ums[cnt].block_dev.hwpart,
151aa9b1b59SFrank Wang 		       g_rkusb->ums[cnt].start_sector,
152aa9b1b59SFrank Wang 		       g_rkusb->ums[cnt].num_sectors);
153aa9b1b59SFrank Wang 
154aa9b1b59SFrank Wang 		g_rkusb->ums_cnt++;
155aa9b1b59SFrank Wang 	}
156aa9b1b59SFrank Wang 
157aa9b1b59SFrank Wang 	if (g_rkusb->ums_cnt)
158aa9b1b59SFrank Wang 		ret = 0;
159aa9b1b59SFrank Wang 
160aa9b1b59SFrank Wang cleanup:
161aa9b1b59SFrank Wang 	free(s);
162aa9b1b59SFrank Wang 	if (ret < 0)
163aa9b1b59SFrank Wang 		rkusb_fini();
164aa9b1b59SFrank Wang 
165aa9b1b59SFrank Wang 	return ret;
166aa9b1b59SFrank Wang }
167aa9b1b59SFrank Wang 
16836c87911Swilliam.wu void rkusb_force_to_usb2(bool enable)
16936c87911Swilliam.wu {
17079553f2dSWilliam Wu 	if (g_rkusb)
17136c87911Swilliam.wu 		g_rkusb->force_usb2 = enable;
17236c87911Swilliam.wu }
17336c87911Swilliam.wu 
17436c87911Swilliam.wu bool rkusb_force_usb2_enabled(void)
17536c87911Swilliam.wu {
17679553f2dSWilliam Wu 	if (!g_rkusb)
17779553f2dSWilliam Wu 		return true;
17879553f2dSWilliam Wu 
17936c87911Swilliam.wu 	return g_rkusb->force_usb2;
18036c87911Swilliam.wu }
18136c87911Swilliam.wu 
18236c87911Swilliam.wu void rkusb_switch_to_usb3_enable(bool enable)
18336c87911Swilliam.wu {
18479553f2dSWilliam Wu 	if (g_rkusb)
18536c87911Swilliam.wu 		g_rkusb->switch_usb3 = enable;
18636c87911Swilliam.wu }
18736c87911Swilliam.wu 
18836c87911Swilliam.wu bool rkusb_switch_usb3_enabled(void)
18936c87911Swilliam.wu {
19079553f2dSWilliam Wu 	if (!g_rkusb)
19179553f2dSWilliam Wu 		return false;
19279553f2dSWilliam Wu 
19336c87911Swilliam.wu 	return g_rkusb->switch_usb3;
19436c87911Swilliam.wu }
19536c87911Swilliam.wu 
196aa9b1b59SFrank Wang static int do_rkusb(cmd_tbl_t *cmdtp, int flag, int argc, char *const argv[])
197aa9b1b59SFrank Wang {
198aa9b1b59SFrank Wang 	const char *usb_controller;
199aa9b1b59SFrank Wang 	const char *devtype;
200aa9b1b59SFrank Wang 	const char *devnum;
201aa9b1b59SFrank Wang 	unsigned int controller_index;
202aa9b1b59SFrank Wang 	int rc;
203aa9b1b59SFrank Wang 	int cable_ready_timeout __maybe_unused;
204fc1a5563SJason Zhu 	const char *s;
205aa9b1b59SFrank Wang 
206aa9b1b59SFrank Wang 	if (argc != 4)
207aa9b1b59SFrank Wang 		return CMD_RET_USAGE;
208aa9b1b59SFrank Wang 
20936c87911Swilliam.wu re_enumerate:
210aa9b1b59SFrank Wang 	usb_controller = argv[1];
211aa9b1b59SFrank Wang 	devtype = argv[2];
212aa9b1b59SFrank Wang 	devnum	= argv[3];
213aa9b1b59SFrank Wang 
214535b44c0SJoseph Chen 	if (!strcmp(devtype, "mmc") && !strcmp(devnum, "1")) {
215535b44c0SJoseph Chen 		pr_err("Forbid to flash mmc 1(sdcard)\n");
216535b44c0SJoseph Chen 		return CMD_RET_FAILURE;
217208ee25dSYifeng Zhao 	} else if ((!strcmp(devtype, "nvme") || !strcmp(devtype, "scsi")) && !strcmp(devnum, "0")) {
218b6d00a7aSJoseph Chen 		/*
219b6d00a7aSJoseph Chen 		 * Add partnum ":0" to active 'allow_whole_dev' partition
220b6d00a7aSJoseph Chen 		 * search mechanism on multi storage, where there maybe not
221b6d00a7aSJoseph Chen 		 * valid partition table.
222b6d00a7aSJoseph Chen 		 */
223b6d00a7aSJoseph Chen 		devnum = "0:0";
224535b44c0SJoseph Chen 	}
225535b44c0SJoseph Chen 
226aa9b1b59SFrank Wang 	g_rkusb = &rkusb;
227aa9b1b59SFrank Wang 	rc = rkusb_init(devtype, devnum);
228aa9b1b59SFrank Wang 	if (rc < 0)
229aa9b1b59SFrank Wang 		return CMD_RET_FAILURE;
230aa9b1b59SFrank Wang 
2311b01cf55SYifeng Zhao 	if (g_rkusb->ums[0].block_dev.if_type == IF_TYPE_MTD &&
2321b01cf55SYifeng Zhao 	    g_rkusb->ums[0].block_dev.devnum == BLK_MTD_NAND) {
2331b01cf55SYifeng Zhao #ifdef CONFIG_CMD_GO
2341b01cf55SYifeng Zhao 		pr_err("Enter bootrom rockusb...\n");
2351b01cf55SYifeng Zhao 		flushc();
2361b01cf55SYifeng Zhao 		run_command("rbrom", 0);
2371b01cf55SYifeng Zhao #else
2381b01cf55SYifeng Zhao 		pr_err("rockusb: count not support loader upgrade!\n");
2391b01cf55SYifeng Zhao #endif
2401b01cf55SYifeng Zhao 	}
2411b01cf55SYifeng Zhao 
242aa9b1b59SFrank Wang 	controller_index = (unsigned int)(simple_strtoul(
243aa9b1b59SFrank Wang 				usb_controller,	NULL, 0));
244b95d4446SJean-Jacques Hiblot 	rc = usb_gadget_initialize(controller_index);
245b95d4446SJean-Jacques Hiblot 	if (rc) {
24690aa625cSMasahiro Yamada 		pr_err("Couldn't init USB controller.");
247aa9b1b59SFrank Wang 		rc = CMD_RET_FAILURE;
248aa9b1b59SFrank Wang 		goto cleanup_rkusb;
249aa9b1b59SFrank Wang 	}
250aa9b1b59SFrank Wang 
251aa9b1b59SFrank Wang 	rc = fsg_init(g_rkusb->ums, g_rkusb->ums_cnt);
252aa9b1b59SFrank Wang 	if (rc) {
25390aa625cSMasahiro Yamada 		pr_err("fsg_init failed");
254aa9b1b59SFrank Wang 		rc = CMD_RET_FAILURE;
255aa9b1b59SFrank Wang 		goto cleanup_board;
256aa9b1b59SFrank Wang 	}
257aa9b1b59SFrank Wang 
25836c87911Swilliam.wu 	if (rkusb_switch_usb3_enabled()) {
25936c87911Swilliam.wu 		/* Maskrom usb3 serialnumber get from upgrade tool */
26036c87911Swilliam.wu 		rkusb_switch_to_usb3_enable(false);
26136c87911Swilliam.wu 	} else {
262fc1a5563SJason Zhu 		s = env_get("serial#");
26306c1e1b3SJason Zhu 		if (s) {
26406c1e1b3SJason Zhu 			char *sn = (char *)calloc(strlen(s) + 1, sizeof(char));
26506c1e1b3SJason Zhu 			char *sn_p = sn;
26606c1e1b3SJason Zhu 
26706c1e1b3SJason Zhu 			if (!sn)
26806c1e1b3SJason Zhu 				goto cleanup_board;
26906c1e1b3SJason Zhu 
27006c1e1b3SJason Zhu 			memcpy(sn, s, strlen(s));
27106c1e1b3SJason Zhu 			while (*sn_p) {
27206c1e1b3SJason Zhu 				if (*sn_p == '\\' || *sn_p == '/')
27306c1e1b3SJason Zhu 					*sn_p = '_';
27406c1e1b3SJason Zhu 				sn_p++;
27506c1e1b3SJason Zhu 			}
27606c1e1b3SJason Zhu 
27706c1e1b3SJason Zhu 			g_dnl_set_serialnumber(sn);
27806c1e1b3SJason Zhu 			free(sn);
27936c87911Swilliam.wu #if defined(CONFIG_SUPPORT_USBPLUG)
28036c87911Swilliam.wu 		} else {
28136c87911Swilliam.wu 			char sn[9] = "Rockchip";
28236c87911Swilliam.wu 
28336c87911Swilliam.wu 			g_dnl_set_serialnumber(sn);
28436c87911Swilliam.wu #endif
28536c87911Swilliam.wu 		}
28606c1e1b3SJason Zhu 	}
287fc1a5563SJason Zhu 
288aa9b1b59SFrank Wang 	rc = g_dnl_register("rkusb_ums_dnl");
289aa9b1b59SFrank Wang 	if (rc) {
29090aa625cSMasahiro Yamada 		pr_err("g_dnl_register failed");
291aa9b1b59SFrank Wang 		rc = CMD_RET_FAILURE;
292aa9b1b59SFrank Wang 		goto cleanup_board;
293aa9b1b59SFrank Wang 	}
294aa9b1b59SFrank Wang 
295aa9b1b59SFrank Wang 	/* Timeout unit: seconds */
296aa9b1b59SFrank Wang 	cable_ready_timeout = UMS_CABLE_READY_TIMEOUT;
297aa9b1b59SFrank Wang 
298aa9b1b59SFrank Wang 	if (!g_dnl_board_usb_cable_connected()) {
299aa9b1b59SFrank Wang 		puts("Please connect USB cable.\n");
300aa9b1b59SFrank Wang 
301aa9b1b59SFrank Wang 		while (!g_dnl_board_usb_cable_connected()) {
302aa9b1b59SFrank Wang 			if (ctrlc()) {
303aa9b1b59SFrank Wang 				puts("\rCTRL+C - Operation aborted.\n");
304aa9b1b59SFrank Wang 				rc = CMD_RET_SUCCESS;
305aa9b1b59SFrank Wang 				goto cleanup_register;
306aa9b1b59SFrank Wang 			}
307aa9b1b59SFrank Wang 			if (!cable_ready_timeout) {
308aa9b1b59SFrank Wang 				puts("\rUSB cable not detected.\nCommand exit.\n");
309aa9b1b59SFrank Wang 				rc = CMD_RET_SUCCESS;
310aa9b1b59SFrank Wang 				goto cleanup_register;
311aa9b1b59SFrank Wang 			}
312aa9b1b59SFrank Wang 
313aa9b1b59SFrank Wang 			printf("\rAuto exit in: %.2d s.", cable_ready_timeout);
314aa9b1b59SFrank Wang 			mdelay(1000);
315aa9b1b59SFrank Wang 			cable_ready_timeout--;
316aa9b1b59SFrank Wang 		}
317aa9b1b59SFrank Wang 		puts("\r\n");
318aa9b1b59SFrank Wang 	}
319aa9b1b59SFrank Wang 
320aa9b1b59SFrank Wang 	while (1) {
321aa9b1b59SFrank Wang 		usb_gadget_handle_interrupts(controller_index);
322aa9b1b59SFrank Wang 
323aa9b1b59SFrank Wang 		rc = fsg_main_thread(NULL);
324aa9b1b59SFrank Wang 		if (rc) {
32536c87911Swilliam.wu 			if (rc == -ENODEV && rkusb_usb3_capable() &&
32636c87911Swilliam.wu 			    !rkusb_force_usb2_enabled()) {
32736c87911Swilliam.wu 				printf("wait for usb3 connect timeout\n");
32836c87911Swilliam.wu 				rkusb_force_to_usb2(true);
32936c87911Swilliam.wu 				g_dnl_unregister();
33036c87911Swilliam.wu 				usb_gadget_release(controller_index);
33136c87911Swilliam.wu 				rkusb_fini();
33236c87911Swilliam.wu 				goto re_enumerate;
33336c87911Swilliam.wu 			}
33436c87911Swilliam.wu 
335aa9b1b59SFrank Wang 			/* Check I/O error */
336aa9b1b59SFrank Wang 			if (rc == -EIO)
337aa9b1b59SFrank Wang 				printf("\rCheck USB cable connection\n");
338aa9b1b59SFrank Wang 
339aa9b1b59SFrank Wang 			/* Check CTRL+C */
340aa9b1b59SFrank Wang 			if (rc == -EPIPE)
341aa9b1b59SFrank Wang 				printf("\rCTRL+C - Operation aborted\n");
342aa9b1b59SFrank Wang 
343aa9b1b59SFrank Wang 			rc = CMD_RET_SUCCESS;
344aa9b1b59SFrank Wang 			goto cleanup_register;
345aa9b1b59SFrank Wang 		}
34636c87911Swilliam.wu 
34736c87911Swilliam.wu 		if (rkusb_switch_usb3_enabled()) {
34836c87911Swilliam.wu 			printf("rockusb switch to usb3\n");
34936c87911Swilliam.wu 			g_dnl_unregister();
35036c87911Swilliam.wu 			usb_gadget_release(controller_index);
35136c87911Swilliam.wu 			rkusb_fini();
35236c87911Swilliam.wu 			goto re_enumerate;
35336c87911Swilliam.wu 		}
354aa9b1b59SFrank Wang 	}
355aa9b1b59SFrank Wang 
356aa9b1b59SFrank Wang cleanup_register:
357aa9b1b59SFrank Wang 	g_dnl_unregister();
358aa9b1b59SFrank Wang cleanup_board:
359b95d4446SJean-Jacques Hiblot 	usb_gadget_release(controller_index);
360aa9b1b59SFrank Wang cleanup_rkusb:
361aa9b1b59SFrank Wang 	rkusb_fini();
362aa9b1b59SFrank Wang 
363aa9b1b59SFrank Wang 	return rc;
364aa9b1b59SFrank Wang }
365aa9b1b59SFrank Wang 
366f6aff21fSJoseph Chen U_BOOT_CMD_ALWAYS(rockusb, 4, 1, do_rkusb,
367aa9b1b59SFrank Wang 		  "Use the rockusb Protocol",
368aa9b1b59SFrank Wang 		  "<USB_controller> <devtype> <dev[:part]>  e.g. rockusb 0 mmc 0\n"
369aa9b1b59SFrank Wang );
370