/*************************************************************************** * Copyright (C) 2016 by Uladzimir Pylinski aka barthess * * barthess@yandex.ru * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * ***************************************************************************/ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include "imp.h" #include #include /* ****************************************************************************** * DEFINES ****************************************************************************** */ #define SECTOR_ERASE_TIMEOUT_MS (35 * 1000) #define XCF_PAGE_SIZE 32 #define XCF_DATA_SECTOR_SIZE (1024 * 1024) #define ID_XCF01S 0x05044093 #define ID_XCF02S 0x05045093 #define ID_XCF04S 0x05046093 #define ID_XCF08P 0x05057093 #define ID_XCF16P 0x05058093 #define ID_XCF32P 0x05059093 #define ID_MEANINGFUL_MASK 0x0FFFFFFF const char *xcf_name_list[] = { "XCF08P", "XCF16P", "XCF32P", "unknown" }; struct xcf_priv { bool probed; }; struct xcf_status { bool isc_error; /* false == OK, true == error */ bool prog_error; /* false == OK, true == error */ bool prog_busy; /* false == idle, true == busy */ bool isc_mode; /* false == normal mode, true == ISC mode */ }; /* ****************************************************************************** * GLOBAL VARIABLES ****************************************************************************** */ static const uint8_t CMD_BYPASS[2] = {0xFF, 0xFF}; static const uint8_t CMD_ISC_ADDRESS_SHIFT[2] = {0xEB, 0x00}; static const uint8_t CMD_ISC_DATA_SHIFT[2] = {0xED, 0x00}; static const uint8_t CMD_ISC_DISABLE[2] = {0xF0, 0x00}; static const uint8_t CMD_ISC_ENABLE[2] = {0xE8, 0x00}; static const uint8_t CMD_ISC_ERASE[2] = {0xEC, 0x00}; static const uint8_t CMD_ISC_PROGRAM[2] = {0xEA, 0x00}; static const uint8_t CMD_XSC_BLANK_CHECK[2] = {0x0D, 0x00}; static const uint8_t CMD_XSC_CONFIG[2] = {0xEE, 0x00}; static const uint8_t CMD_XSC_DATA_BTC[2] = {0xF2, 0x00}; static const uint8_t CMD_XSC_DATA_CCB[2] = {0x0C, 0x00}; static const uint8_t CMD_XSC_DATA_DONE[2] = {0x09, 0x00}; static const uint8_t CMD_XSC_DATA_SUCR[2] = {0x0E, 0x00}; static const uint8_t CMD_XSC_DATA_WRPT[2] = {0xF7, 0x00}; static const uint8_t CMD_XSC_OP_STATUS[2] = {0xE3, 0x00}; static const uint8_t CMD_XSC_READ[2] = {0xEF, 0x00}; static const uint8_t CMD_XSC_UNLOCK[2] = {0x55, 0xAA}; /* ****************************************************************************** * LOCAL FUNCTIONS ****************************************************************************** */ static const char *product_name(const struct flash_bank *bank) { switch (bank->target->tap->idcode & ID_MEANINGFUL_MASK) { case ID_XCF08P: return xcf_name_list[0]; case ID_XCF16P: return xcf_name_list[1]; case ID_XCF32P: return xcf_name_list[2]; default: return xcf_name_list[3]; } } static void fill_sector_table(struct flash_bank *bank) { /* Note: is_erased and is_protected fields must be set here to an unknown * state, they will be correctly filled from other API calls. */ int i = 0; for (i = 0; i < bank->num_sectors; i++) { bank->sectors[i].is_erased = -1; bank->sectors[i].is_protected = -1; } for (i = 0; i < bank->num_sectors; i++) { bank->sectors[i].size = XCF_DATA_SECTOR_SIZE; bank->sectors[i].offset = i * XCF_DATA_SECTOR_SIZE; } bank->size = bank->num_sectors * XCF_DATA_SECTOR_SIZE; } static struct xcf_status read_status(struct flash_bank *bank) { struct xcf_status ret; uint8_t irdata[2]; struct scan_field scan; scan.check_mask = NULL; scan.check_value = NULL; scan.num_bits = 16; scan.out_value = CMD_BYPASS; scan.in_value = irdata; jtag_add_ir_scan(bank->target->tap, &scan, TAP_IDLE); jtag_execute_queue(); ret.isc_error = ((irdata[0] >> 7) & 3) == 0b01; ret.prog_error = ((irdata[0] >> 5) & 3) == 0b01; ret.prog_busy = ((irdata[0] >> 4) & 1) == 0; ret.isc_mode = ((irdata[0] >> 3) & 1) == 1; return ret; } static int isc_enter(struct flash_bank *bank) { struct xcf_status status = read_status(bank); if (true == status.isc_mode) return ERROR_OK; else { struct scan_field scan; scan.check_mask = NULL; scan.check_value = NULL; scan.num_bits = 16; scan.out_value = CMD_ISC_ENABLE; scan.in_value = NULL; jtag_add_ir_scan(bank->target->tap, &scan, TAP_IDLE); jtag_execute_queue(); status = read_status(bank); if (false == status.isc_mode) { LOG_ERROR("*** XCF: FAILED to enter ISC mode"); return ERROR_FLASH_OPERATION_FAILED; } return ERROR_OK; } } static int isc_leave(struct flash_bank *bank) { struct xcf_status status = read_status(bank); if (false == status.isc_mode) return ERROR_OK; else { struct scan_field scan; scan.check_mask = NULL; scan.check_value = NULL; scan.num_bits = 16; scan.out_value = CMD_ISC_DISABLE; scan.in_value = NULL; jtag_add_ir_scan(bank->target->tap, &scan, TAP_IDLE); jtag_execute_queue(); alive_sleep(1); /* device needs 50 uS to leave ISC mode */ status = read_status(bank); if (true == status.isc_mode) { LOG_ERROR("*** XCF: FAILED to leave ISC mode"); return ERROR_FLASH_OPERATION_FAILED; } return ERROR_OK; } } static int sector_state(uint8_t wrpt, int sector) { if (((wrpt >> sector) & 1) == 1) return 0; else return 1; } static uint8_t fill_select_block(int first, int last) { uint8_t ret = 0; for (int i = first; i <= last; i++) ret |= 1 << i; return ret; } static int isc_read_register(struct flash_bank *bank, const uint8_t *cmd, uint8_t *data_buf, int num_bits) { struct scan_field scan; scan.check_mask = NULL; scan.check_value = NULL; scan.out_value = cmd; scan.in_value = NULL; scan.num_bits = 16; jtag_add_ir_scan(bank->target->tap, &scan, TAP_DRSHIFT); scan.out_value = NULL; scan.in_value = data_buf; scan.num_bits = num_bits; jtag_add_dr_scan(bank->target->tap, 1, &scan, TAP_IDLE); return jtag_execute_queue(); } static int isc_wait_erase_program(struct flash_bank *bank, int64_t timeout_ms) { uint8_t isc_default; int64_t t0 = timeval_ms(); int64_t dt; do { isc_read_register(bank, CMD_XSC_OP_STATUS, &isc_default, 8); if (((isc_default >> 2) & 1) == 1) return ERROR_OK; dt = timeval_ms() - t0; } while (dt <= timeout_ms); return ERROR_FLASH_OPERATION_FAILED; } /* * helper function for procedures without program jtag command at the end */ static int isc_set_register(struct flash_bank *bank, const uint8_t *cmd, const uint8_t *data_buf, int num_bits, int64_t timeout_ms) { struct scan_field scan; scan.check_mask = NULL; scan.check_value = NULL; scan.num_bits = 16; scan.out_value = cmd; scan.in_value = NULL; jtag_add_ir_scan(bank->target->tap, &scan, TAP_DRSHIFT); scan.num_bits = num_bits; scan.out_value = data_buf; scan.in_value = NULL; jtag_add_dr_scan(bank->target->tap, 1, &scan, TAP_IDLE); if (0 == timeout_ms) return jtag_execute_queue(); else return isc_wait_erase_program(bank, timeout_ms); } /* * helper function for procedures required program jtag command at the end */ static int isc_program_register(struct flash_bank *bank, const uint8_t *cmd, const uint8_t *data_buf, int num_bits, int64_t timeout_ms) { struct scan_field scan; scan.check_mask = NULL; scan.check_value = NULL; scan.num_bits = 16; scan.out_value = cmd; scan.in_value = NULL; jtag_add_ir_scan(bank->target->tap, &scan, TAP_DRSHIFT); scan.num_bits = num_bits; scan.out_value = data_buf; scan.in_value = NULL; jtag_add_dr_scan(bank->target->tap, 1, &scan, TAP_IRSHIFT); scan.num_bits = 16; scan.out_value = CMD_ISC_PROGRAM; scan.in_value = NULL; jtag_add_ir_scan(bank->target->tap, &scan, TAP_IDLE); if (0 == timeout_ms) return jtag_execute_queue(); else return isc_wait_erase_program(bank, timeout_ms); } static int isc_clear_protect(struct flash_bank *bank, int first, int last) { uint8_t select_block[3] = {0x0, 0x0, 0x0}; select_block[0] = fill_select_block(first, last); return isc_set_register(bank, CMD_XSC_UNLOCK, select_block, 24, 0); } static int isc_set_protect(struct flash_bank *bank, int first, int last) { uint8_t wrpt[2] = {0xFF, 0xFF}; for (int i = first; i <= last; i++) wrpt[0] &= ~(1 << i); return isc_program_register(bank, CMD_XSC_DATA_WRPT, wrpt, 16, 0); } static int isc_erase_sectors(struct flash_bank *bank, int first, int last) { uint8_t select_block[3] = {0, 0, 0}; select_block[0] = fill_select_block(first, last); int64_t timeout = SECTOR_ERASE_TIMEOUT_MS * (last - first + 1); return isc_set_register(bank, CMD_ISC_ERASE, select_block, 24, timeout); } static int isc_adr_shift(struct flash_bank *bank, int adr) { uint8_t adr_buf[3]; h_u24_to_le(adr_buf, adr); return isc_set_register(bank, CMD_ISC_ADDRESS_SHIFT, adr_buf, 24, 0); } static int isc_program_data_page(struct flash_bank *bank, const uint8_t *page_buf) { return isc_program_register(bank, CMD_ISC_DATA_SHIFT, page_buf, 8 * XCF_PAGE_SIZE, 100); } static void isc_data_read_out(struct flash_bank *bank, uint8_t *buffer, uint32_t count) { struct scan_field scan; /* Do not change this code with isc_read_register() call because it needs * transition to IDLE state before data retrieving. */ scan.check_mask = NULL; scan.check_value = NULL; scan.num_bits = 16; scan.out_value = CMD_XSC_READ; scan.in_value = NULL; jtag_add_ir_scan(bank->target->tap, &scan, TAP_IDLE); scan.num_bits = 8 * count; scan.out_value = NULL; scan.in_value = buffer; jtag_add_dr_scan(bank->target->tap, 1, &scan, TAP_IDLE); jtag_execute_queue(); } static int isc_set_data_done(struct flash_bank *bank, int sector) { uint8_t done = 0xFF; done &= ~(1 << sector); return isc_program_register(bank, CMD_XSC_DATA_DONE, &done, 8, 100); } static void flip_u8(uint8_t *out, const uint8_t *in, int len) { for (int i = 0; i < len; i++) out[i] = flip_u32(in[i], 8); } /* * Xilinx bin file contains simple fixed header for automatic bus width detection: * 16 bytes of 0xFF * 4 byte sync word 0xAA995566 or (bit reversed) 0x5599AA66 in MSC file * * Function presumes need of bit reversing if it can not exactly detects * the opposite. */ bool need_bit_reverse(const uint8_t *buffer) { const size_t L = 20; uint8_t reference[L]; memset(reference, 0xFF, 16); reference[16] = 0x55; reference[17] = 0x99; reference[18] = 0xAA; reference[19] = 0x66; if (0 == memcmp(reference, buffer, L)) return false; else return true; } /* * The page address to be programmed is determined by loading the * internal ADDRESS Register using an ISC_ADDRESS_SHIFT instruction sequence. * The page address automatically increments to the next 256-bit * page address after each programming sequence until the last address * in the 8 Mb block is reached. To continue programming the next block, * the next 8 Mb block's starting address must be loaded into the * internal ADDRESS register. */ static int read_write_data(struct flash_bank *bank, const uint8_t *w_buffer, uint8_t *r_buffer, bool write_flag, uint32_t offset, uint32_t count) { int dbg_count = count; int dbg_written = 0; int ret = ERROR_OK; uint8_t *page_buf = malloc(XCF_PAGE_SIZE); bool revbit = true; isc_enter(bank); if (offset % XCF_PAGE_SIZE != 0) { ret = ERROR_FLASH_DST_BREAKS_ALIGNMENT; goto EXIT; } if ((offset + count) > (uint32_t)(bank->num_sectors * XCF_DATA_SECTOR_SIZE)) { ret = ERROR_FLASH_DST_OUT_OF_BANK; goto EXIT; } if ((write_flag) && (0 == offset) && (count >= XCF_PAGE_SIZE)) revbit = need_bit_reverse(w_buffer); while (count > 0) { uint32_t sector_num = offset / XCF_DATA_SECTOR_SIZE; uint32_t sector_offset = offset - sector_num * XCF_DATA_SECTOR_SIZE; uint32_t sector_bytes = XCF_DATA_SECTOR_SIZE - sector_offset; if (count < sector_bytes) sector_bytes = count; isc_adr_shift(bank, offset); offset += sector_bytes; count -= sector_bytes; if (write_flag) { while (sector_bytes > 0) { int len; if (sector_bytes < XCF_PAGE_SIZE) { len = sector_bytes; memset(page_buf, 0xFF, XCF_PAGE_SIZE); } else len = XCF_PAGE_SIZE; if (revbit) flip_u8(page_buf, w_buffer, len); else memcpy(page_buf, w_buffer, len); w_buffer += len; sector_bytes -= len; ret = isc_program_data_page(bank, page_buf); if (ERROR_OK != ret) goto EXIT; else { LOG_DEBUG("written %d bytes from %d", dbg_written, dbg_count); dbg_written += len; } } } else { isc_data_read_out(bank, r_buffer, sector_bytes); flip_u8(r_buffer, r_buffer, sector_bytes); r_buffer += sector_bytes; } } /* Set 'done' flags for all data sectors because driver supports * only single revision. */ if (write_flag) { for (int i = 0; i < bank->num_sectors; i++) { ret = isc_set_data_done(bank, i); if (ERROR_OK != ret) goto EXIT; } } EXIT: free(page_buf); isc_leave(bank); return ret; } static uint16_t isc_read_ccb(struct flash_bank *bank) { uint8_t ccb[2]; isc_read_register(bank, CMD_XSC_DATA_CCB, ccb, 16); return le_to_h_u16(ccb); } static int gucr_num(const struct flash_bank *bank) { return bank->num_sectors; } static int sucr_num(const struct flash_bank *bank) { return bank->num_sectors + 1; } static int isc_program_ccb(struct flash_bank *bank, uint16_t ccb) { uint8_t buf[2]; h_u16_to_le(buf, ccb); return isc_program_register(bank, CMD_XSC_DATA_CCB, buf, 16, 100); } static int isc_program_singe_revision_sucr(struct flash_bank *bank) { uint8_t sucr[2] = {0xFC, 0xFF}; return isc_program_register(bank, CMD_XSC_DATA_SUCR, sucr, 16, 100); } static int isc_program_single_revision_btc(struct flash_bank *bank) { uint8_t buf[4]; uint32_t btc = 0xFFFFFFFF; btc &= ~0b1111; btc |= ((bank->num_sectors - 1) << 2); btc &= ~(1 << 4); h_u32_to_le(buf, btc); return isc_program_register(bank, CMD_XSC_DATA_BTC, buf, 32, 100); } static int fpga_configure(struct flash_bank *bank) { struct scan_field scan; scan.check_mask = NULL; scan.check_value = NULL; scan.num_bits = 16; scan.out_value = CMD_XSC_CONFIG; scan.in_value = NULL; jtag_add_ir_scan(bank->target->tap, &scan, TAP_IDLE); jtag_execute_queue(); return ERROR_OK; } /* ****************************************************************************** * EXPORTED FUNCTIONS ****************************************************************************** */ FLASH_BANK_COMMAND_HANDLER(xcf_flash_bank_command) { struct xcf_priv *priv; priv = malloc(sizeof(struct xcf_priv)); if (priv == NULL) { LOG_ERROR("no memory for flash bank info"); return ERROR_FAIL; } bank->driver_priv = priv; priv->probed = false; return ERROR_OK; } static int xcf_info(struct flash_bank *bank, char *buf, int buf_size) { const struct xcf_priv *priv = bank->driver_priv; if (false == priv->probed) { snprintf(buf, buf_size, "\nXCF flash bank not probed yet\n"); return ERROR_OK; } snprintf(buf, buf_size, "%s", product_name(bank)); return ERROR_OK; } static int xcf_probe(struct flash_bank *bank) { struct xcf_priv *priv = bank->driver_priv; uint32_t id; if (true == priv->probed) free(bank->sectors); priv->probed = false; if (bank->target->tap == NULL) { LOG_ERROR("Target has no JTAG tap"); return ERROR_FAIL; } /* check idcode and alloc memory for sector table */ if (!bank->target->tap->hasidcode) return ERROR_FLASH_OPERATION_FAILED; /* guess number of blocks using chip ID */ id = bank->target->tap->idcode; switch (id & ID_MEANINGFUL_MASK) { case ID_XCF08P: bank->num_sectors = 1; break; case ID_XCF16P: bank->num_sectors = 2; break; case ID_XCF32P: bank->num_sectors = 4; break; default: LOG_ERROR("Unknown flash device ID 0x%X", id); return ERROR_FAIL; break; } bank->sectors = malloc(bank->num_sectors * sizeof(struct flash_sector)); if (NULL == bank->sectors) { LOG_ERROR("No memory for sector table"); return ERROR_FAIL; } fill_sector_table(bank); priv->probed = true; /* REVISIT: Why is unchanged bank->driver_priv rewritten by same value? */ bank->driver_priv = priv; LOG_INFO("product name: %s", product_name(bank)); LOG_INFO("device id = 0x%X ", bank->target->tap->idcode); LOG_INFO("flash size = %d configuration bits", bank->num_sectors * XCF_DATA_SECTOR_SIZE * 8); LOG_INFO("number of sectors = %d", bank->num_sectors); return ERROR_OK; } static int xcf_auto_probe(struct flash_bank *bank) { struct xcf_priv *priv = bank->driver_priv; if (true == priv->probed) return ERROR_OK; else return xcf_probe(bank); } static int xcf_protect_check(struct flash_bank *bank) { uint8_t wrpt[2]; isc_enter(bank); isc_read_register(bank, CMD_XSC_DATA_WRPT, wrpt, 16); isc_leave(bank); for (int i = 0; i < bank->num_sectors; i++) bank->sectors[i].is_protected = sector_state(wrpt[0], i); return ERROR_OK; } static int xcf_erase_check(struct flash_bank *bank) { uint8_t blankreg; struct scan_field scan; isc_enter(bank); /* Do not change this code with isc_read_register() call because it needs * transition to IDLE state and pause before data retrieving. */ scan.check_mask = NULL; scan.check_value = NULL; scan.num_bits = 16; scan.out_value = CMD_XSC_BLANK_CHECK; scan.in_value = NULL; jtag_add_ir_scan(bank->target->tap, &scan, TAP_IDLE); jtag_execute_queue(); alive_sleep(500); /* device needs at least 0.5s to self check */ scan.num_bits = 8; scan.in_value = &blankreg; jtag_add_dr_scan(bank->target->tap, 1, &scan, TAP_IDLE); jtag_execute_queue(); isc_leave(bank); for (int i = 0; i < bank->num_sectors; i++) bank->sectors[i].is_erased = sector_state(blankreg, i); return ERROR_OK; } static int xcf_erase(struct flash_bank *bank, int first, int last) { if ((first >= bank->num_sectors) || (last >= bank->num_sectors) || (last < first)) return ERROR_FLASH_SECTOR_INVALID; else { isc_enter(bank); isc_clear_protect(bank, first, last); int ret = isc_erase_sectors(bank, first, last); isc_leave(bank); return ret; } } static int xcf_read(struct flash_bank *bank, uint8_t *buffer, uint32_t offset, uint32_t count) { return read_write_data(bank, NULL, buffer, false, offset, count); } static int xcf_write(struct flash_bank *bank, const uint8_t *buffer, uint32_t offset, uint32_t count) { return read_write_data(bank, buffer, NULL, true, offset, count); } static int xcf_protect(struct flash_bank *bank, int set, int first, int last) { int ret; isc_enter(bank); if (set) ret = isc_set_protect(bank, first, last); else { /* write protection may be removed only with following erase */ isc_clear_protect(bank, first, last); ret = isc_erase_sectors(bank, first, last); } isc_leave(bank); return ret; } COMMAND_HANDLER(xcf_handle_ccb_command) { if (!((CMD_ARGC == 1) || (CMD_ARGC == 5))) return ERROR_COMMAND_SYNTAX_ERROR; struct flash_bank *bank; int retval = CALL_COMMAND_HANDLER(flash_command_get_bank, 0, &bank); if (ERROR_OK != retval) return retval; uint16_t ccb = 0xFFFF; isc_enter(bank); uint16_t old_ccb = isc_read_ccb(bank); isc_leave(bank); if (CMD_ARGC == 1) { LOG_INFO("current CCB = 0x%X", old_ccb); return ERROR_OK; } else { /* skip over flash bank */ CMD_ARGC--; CMD_ARGV++; while (CMD_ARGC) { if (strcmp("external", CMD_ARGV[0]) == 0) ccb |= (1 << 0); else if (strcmp("internal", CMD_ARGV[0]) == 0) ccb &= ~(1 << 0); else if (strcmp("serial", CMD_ARGV[0]) == 0) ccb |= (3 << 1); else if (strcmp("parallel", CMD_ARGV[0]) == 0) ccb &= ~(3 << 1); else if (strcmp("slave", CMD_ARGV[0]) == 0) ccb |= (1 << 3); else if (strcmp("master", CMD_ARGV[0]) == 0) ccb &= ~(1 << 3); else if (strcmp("40", CMD_ARGV[0]) == 0) ccb |= (3 << 4); else if (strcmp("20", CMD_ARGV[0]) == 0) ccb &= ~(1 << 5); else return ERROR_COMMAND_SYNTAX_ERROR; CMD_ARGC--; CMD_ARGV++; } isc_enter(bank); int sector; /* GUCR sector */ sector = gucr_num(bank); isc_clear_protect(bank, sector, sector); int ret = isc_erase_sectors(bank, sector, sector); if (ERROR_OK != ret) goto EXIT; ret = isc_program_ccb(bank, ccb); if (ERROR_OK != ret) goto EXIT; ret = isc_program_single_revision_btc(bank); if (ERROR_OK != ret) goto EXIT; ret = isc_set_data_done(bank, sector); if (ERROR_OK != ret) goto EXIT; /* SUCR sector */ sector = sucr_num(bank); isc_clear_protect(bank, sector, sector); ret = isc_erase_sectors(bank, sector, sector); if (ERROR_OK != ret) goto EXIT; ret = isc_program_singe_revision_sucr(bank); if (ERROR_OK != ret) goto EXIT; ret = isc_set_data_done(bank, sector); if (ERROR_OK != ret) goto EXIT; EXIT: isc_leave(bank); return ret; } } COMMAND_HANDLER(xcf_handle_configure_command) { if (CMD_ARGC != 1) return ERROR_COMMAND_SYNTAX_ERROR; struct flash_bank *bank; int retval = CALL_COMMAND_HANDLER(flash_command_get_bank, 0, &bank); if (ERROR_OK != retval) return retval; return fpga_configure(bank); } static const struct command_registration xcf_exec_command_handlers[] = { { .name = "configure", .handler = xcf_handle_configure_command, .mode = COMMAND_EXEC, .usage = "bank_id", .help = "Initiate FPGA loading procedure." }, { .name = "ccb", .handler = xcf_handle_ccb_command, .mode = COMMAND_EXEC, .usage = "bank_id [('external'|'internal') " "('serial'|'parallel') " "('slave'|'master') " "('40'|'20')]", .help = "Write CCB register with supplied options and (silently) BTC " "register with single revision options. Display current " "CCB value when only bank_id supplied. " "Following options available: " "1) external or internal clock source; " "2) serial or parallel bus mode; " "3) slave or master mode; " "4) clock frequency in MHz for internal clock in master mode;" }, COMMAND_REGISTRATION_DONE }; static const struct command_registration xcf_command_handlers[] = { { .name = "xcf", .mode = COMMAND_ANY, .help = "Xilinx platform flash command group", .usage = "", .chain = xcf_exec_command_handlers }, COMMAND_REGISTRATION_DONE }; const struct flash_driver xcf_flash = { .name = "xcf", .usage = NULL, .commands = xcf_command_handlers, .flash_bank_command = xcf_flash_bank_command, .erase = xcf_erase, .protect = xcf_protect, .write = xcf_write, .read = xcf_read, .probe = xcf_probe, .auto_probe = xcf_auto_probe, .erase_check = xcf_erase_check, .protect_check = xcf_protect_check, .info = xcf_info, .free_driver_priv = default_flash_free_driver_priv, };