/* * JR-IDE Project * - (c) 2017 Alan Hightower * * 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 3 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 . */ #include #include #include #include #include "machxo2_internal.h" #define DEBUG 1 #ifdef DEBUG #include #define LOGERR(fmt, args...) fprintf(stderr, "\nERROR: " fmt "\n\n", ## args) #define LOGDBG(fmt, args...) printf(fmt "\n", ## args) #else #define LOGERR(fmt, args...) #define LOGDBG(fmt, args...) #endif struct _st_xo2_handle { const xo2_info_t *info; xo2_exchange_cb exchange; void *user_ctx; }; uint32_t xo2_jpf_usercode (void *image_ptr) { machxo2_hdr_t *image = (machxo2_hdr_t *) image_ptr; return image->user_code; } static const xo2_info_t *_lookup_device (uint32_t device_id) { int i; for (i = 0; i < MachXO2_DEVICE_COUNT; i++) { if ((device_id == xo2_device_info[i].heze_id) || (device_id == xo2_device_info[i].hc_id)) return &xo2_device_info[i]; } return NULL; } xo2_handle_t *xo2_open (xo2_exchange_cb cb, void *user_ctx) { int sts = -1; xo2_handle_t *xo2; xo2 = (xo2_handle_t *) malloc (sizeof (xo2_handle_t)); memset (xo2, 0, sizeof (xo2_handle_t)); xo2->exchange = cb; xo2->user_ctx = user_ctx; do { uint32_t device_id = 0; if ((sts = xo2_read_device_id (xo2, &device_id))) break; if (!(xo2->info = _lookup_device (device_id))) { LOGERR("Unknown device ID 0x%08x", device_id); sts = -1; break; } sts = 0; } while (0); if (sts) { free (xo2); return NULL; } return xo2; } /** * Get descriptive name for the detected device * * Returns a name that can eb used to describe the overall part number * of the XO2 device detected by the open call. * * @param xo2 Open API handle * * @return Device type description string */ const char *xo2_name (xo2_handle_t *xo2) { return xo2->info->name; } /** * Close open API handle * * Closes the open API handle and frees any resources associated with it * * @param xo2 Open API handle */ void xo2_close (xo2_handle_t *xo2) { if (!xo2) return; free (xo2); return; } /** * Read the 4 byte Device ID from the XO2 configuration logic block. * This function assembles the command sequence that allows reading * the XO2 Device ID from the configuration logic. The command is * first written to the XO2, then a read of 4 bytes is perfromed to * return the value. * * @param xo2 Open API handle * @param value pointer to the 32 bit integer to return the ID value in * * @return 0 on success, !0 on error */ int xo2_read_device_id (xo2_handle_t *xo2, uint32_t *value) { uint8_t cmd[4], data[4]; cmd[0] = 0xE0; // Read Device ID opcode cmd[1] = 0x00; // arg0 cmd[2] = 0x00; // arg1 cmd[3] = 0x00; // arg2 int sts; if (!(sts = xo2->exchange (xo2->user_ctx, cmd, 4, data, 4))) *value = (data[0] << 24) | (data[1] << 16) | (data[2] << 8) | data[3]; return sts; } /** * Read the 4 byte USERCODE from the XO2 Configuration logic block. * This function assembles the command sequence that allows reading the XO2 USERCODE * value from the configuration Flash sector. The command is first written to the XO2, then a read * of 4 bytes is perfromed to return the value. * * @param xo2 Open API handle * @param value pointer to the 32 bit integer to return the USERCODE value in * * @return 0 on success, !0 on error */ int xo2_read_user_code (xo2_handle_t *xo2, uint32_t *value) { uint8_t cmd[4], data[4]; cmd[0] = 0xC0; // Read USERCODE opcode cmd[1] = 0x00; // arg0 cmd[2] = 0x00; // arg1 cmd[3] = 0x00; // arg2 int sts; if (!(sts = xo2->exchange (xo2->user_ctx, cmd, 4, data, 4))) *value = (data[0] << 24) | (data[1] << 16) | (data[2] << 8) | data[3]; return sts; } /** * Read the 8 byte (64 bit) TraceID from the XO2 Feature Row. * This function assembles the command sequence that allows reading the XO2 TraceID * value from the Feature Row Flash sector. The command is first written to the XO2, then a read * of 8 bytes is perfromed to return the value. * The TraceID is set in the Global Settings of Spreadsheet view. * The first byte read back (pVal[0]) can be set by the user in Spreadsheet view. * The remaining 7 bytes are unique for each silicon die. * * @param xo2 Open API handle * @param value pointer to the 8 byte array to return the TraceID value in * * @return 0 on success, !0 on error */ int xo2_read_trace_id (xo2_handle_t *xo2, uint8_t *value) { uint8_t cmd[4]; cmd[0] = 0x19; // Read TraceID opcode cmd[1] = 0x00; // arg0 cmd[2] = 0x00; // arg1 cmd[3] = 0x00; // arg2 return xo2->exchange (xo2->user_ctx, cmd, 4, value, 8); } /** * Read the 4 byte Status Register from the XO2 Configuration logic block. * This function assembles the command sequence that allows reading the XO2 Status Register. * The command is first written to the XO2, then a read of 4 bytes is perfromed to return the value. * * @param xo2 Open API handle * @param value pointer to the 32 bit integer to return the Status Register value in. * * @return 0 on success, !0 on error */ int xo2_status_read (xo2_handle_t *xo2, uint32_t *value) { uint8_t cmd[4], data[4]; cmd[0] = 0x3C; // Read Status Register opcode cmd[1] = 0x00; // arg0 cmd[2] = 0x00; // arg1 cmd[3] = 0x00; // arg2 int sts; if (!(sts = xo2->exchange (xo2->user_ctx, cmd, 4, data, 4))) *value = (data[0] << 24) | (data[1] << 16) | (data[2] << 8) | data[3]; return sts; } /** * Wait for the Status register to report no longer busy. * Also check bit 13 for FAIL indication. Return error if an error * condition is detected. Also return if exceed timeout. * * @param xo2 Open API handle * @param timeout_ms Number of milliseconds to wait before returning error * * @return 0 on success, !0 on error */ int xo2_status_wait (xo2_handle_t *xo2, int timeout_ms) { int sts; uint32_t value; do { if ((sts = xo2_status_read (xo2, &value)) || (value & 0x2000)) return -1; // read failure or FAILURE bit if (value & 0x1000) // BUSY usleep (1000); } while (--timeout_ms && (value & 0x1000)); // BUSY return (value & 0x1000) ? -1 : 0; } /** * Enable access to Configuration Logic in Transparent mode or Offline mode. * This function issues one of the Enable Configuration Interface commands * depending on the value of mode. Transparent mode allows the XO2 to * continue operating in user mode while access to the config logic is * performed. Offline mode halts all user logic, and tri-states I/Os, * while access is occurring. * * @param xo2 Open API handle * @param mode specify XO2_MODE_TRANSPARENT or XO2_MODE_OFFLINE * * @return 0 on success, !0 on error */ int xo2_mode_set (xo2_handle_t *xo2, int mode) { uint8_t cmd[4]; if (mode & XO2_MODE_TRANSPARENT) cmd[0] = 0x74; else cmd[0] = 0xC6; cmd[1] = 0x08; // arg0 cmd[2] = 0x00; // arg1 cmd[3] = 0x00; // arg2 int sts = xo2->exchange (xo2->user_ctx, cmd, 4, NULL, 0); if (mode & XO2_MODE_TRANSPARENT) { if (sts) return sts; sts = xo2_status_wait (xo2, 2000); } else { int retries = 50; while ((sts = xo2_status_wait (xo2, 500)) && retries--); } if (sts) return sts; LOGDBG ("Entered %s flash config mode", (mode & XO2_MODE_TRANSPARENT) ? "transparent" : "offline"); return 0; } /** * Reset the Address Regsiter to point to the first UFM or config * page (sector 1, page 0). * * @param xo2 Open API handle * @param mode which address to update: XO2_SECTOR_CFG or XO2_SECTOR_UFM * * @return 0 on success, !0 on error */ int xo2_page_reset (xo2_handle_t *xo2, XO2_SECTOR_MODE mode) { uint8_t cmd[4]; if (mode == XO2_SECTOR_CFG) cmd[0] = 0x46; else cmd[0] = 0x47; cmd[1] = 0x00; // arg0 cmd[2] = 0x00; // arg1 cmd[3] = 0x00; // arg2 return xo2->exchange (xo2->user_ctx, cmd, 4, NULL, 0); } /** * Set the current Page Address in either Cfg Flash or UFM. * The specific page is set for the next read/write operation. * This is probably only useful for the UFM since skipping around * in the configuration sector is very unlikely. * * @param xo2 Open API handle * @param mode which address to update: XO2_SECTOR_CFG or XO2_SECTOR_UFM * @param page the page number to set address pointer to * * @return 0 on success, !0 on error */ int xo2_page_set (xo2_handle_t *xo2, XO2_SECTOR_MODE mode, int page) { uint8_t cmd[8]; cmd[0] = 0xB4; // opcode cmd[1] = 0x00; // arg0 cmd[2] = 0x00; // arg1 cmd[3] = 0x00; // arg2 if (mode == XO2_SECTOR_CFG) cmd[4] = 0x00; else cmd[4] = 0x40; cmd[5] = 0x00; cmd[6] = page >> 8; cmd[7] = page & 0xff; return xo2->exchange (xo2->user_ctx, cmd, 8, NULL, 0); } /** * Read the next page (16 bytes) from the UFM or config memory. * * @param xo2 Open API handle * @param mode which address to update: XO2_SECTOR_CFG or XO2_SECTOR_UFM * @param data pointer to the 16 byte array to return the UFM page bytes in. * * @return 0 on success, !0 on error * * @note There is no advantage to reading multiple pages since the interface * is most likely so slow. The I2C only runs at 100kHz. */ int xo2_page_read (xo2_handle_t *xo2, XO2_SECTOR_MODE mode, uint8_t *data) { uint8_t cmd[4]; if (mode == XO2_SECTOR_CFG) cmd[0] = 0x73; else cmd[0] = 0xCA; cmd[1] = 0x00; // arg0 cmd[2] = 0x00; // arg1 cmd[3] = 0x01; // arg2 = 1 page return xo2->exchange (xo2->user_ctx, cmd, 4, data, XO2_PAGE_SIZE); } /** * Write a page (16 bytes) into the current UFM or config memory page. * Page address can be set using SetAddress command. * Page address advances to next page after programming is completed. * * @param xo2 Open API handle * @param mode which address to update: XO2_SECTOR_CFG or XO2_SECTOR_UFM * @param data pointer to the 16 byte array to write into the UFM page. * @param timeout_ms Number of milliseconds to wait before returning error * * @return 0 on success, !0 on error * * @note Programming must be done on a page basis. Pages must be erased to 0's first. */ int xo2_page_write (xo2_handle_t *xo2, XO2_SECTOR_MODE mode, uint8_t *data, int timeout_ms) { uint8_t cmd[4 + XO2_PAGE_SIZE]; if (mode == XO2_SECTOR_CFG) cmd[0] = 0x70; else cmd[0] = 0xC9; cmd[1] = 0x00; // arg0 cmd[2] = 0x00; // arg1 cmd[3] = 0x01; // arg2 memcpy (&cmd[4], data, XO2_PAGE_SIZE); int sts; if (!(sts = xo2->exchange (xo2->user_ctx, cmd, 4 + XO2_PAGE_SIZE, NULL, 0))) { usleep (1000); sts = xo2_status_wait (xo2, timeout_ms); } return sts; } /** * Disable access to Configuration Logic Interface. * * This function issues the Disable Configuration Interface command and * registers that the interface is no longer available for certain commands * that require it to be enabled. It then issues a the bypass command. * * @param xo2 Open API handle * * @return 0 on success, !0 on error */ int xo2_config_exit (xo2_handle_t *xo2) { uint8_t cmd[3]; cmd[0] = 0x26; // Disable Config Interface opcode cmd[1] = 0x00; // arg0 cmd[2] = 0x00; // arg1 int sts; if ((sts = xo2->exchange (xo2->user_ctx, cmd, 3, NULL, 0))) return sts; cmd[0] = 0xFF; // Bypass opcode return xo2->exchange (xo2->user_ctx, cmd, 1, NULL, 0); } /** * Issue the Refresh command that updates the SRAM from Flash and * boots the XO2. * * @param xo2 Open API handle * * @return 0 on success, !0 on error */ int xo2_refresh (xo2_handle_t *xo2) { uint8_t cmd[3]; cmd[0] = 0x79; // Refresh opcode cmd[1] = 0x00; // arg0 cmd[2] = 0x00; // arg1 int sts; if ((sts = xo2->exchange (xo2->user_ctx, cmd, 3, NULL, 0))) return sts; usleep (xo2->info->trefresh * 1000000ULL); return xo2_status_wait (xo2, 2000); } /** * Issue the Done command that updates the Program DONE bit. * Typically used after programming the Cfg Flash and before * closing access to the configuration interface. * * @param xo2 Open API handle * @param timeout_ms Number of milliseconds to wait before returning error * * @return 0 on success, !0 on error */ int xo2_done (xo2_handle_t *xo2, int timeout_ms) { uint8_t cmd[4]; cmd[0] = 0x5E; // Program DONE bit opcode cmd[1] = 0x00; // arg0 cmd[2] = 0x00; // arg1 cmd[3] = 0x00; // arg2 int sts; if (!(sts = xo2->exchange (xo2->user_ctx, cmd, 4, NULL, 0))) { usleep (200); sts = xo2_status_wait (xo2, timeout_ms); } return sts; } /** * Erase any/all entire sectors of the XO2 Flash or SRAM memory. * * @param xo2 Open API handle * @param mode bit map of what sector contents to erase * * @return 0 on success, !0 on error * * @note Erases bits to a value of 0. Any bit that is a 0 can then be programmed to a 1. */ int xo2_flash_erase (xo2_handle_t *xo2, int mode) { uint8_t cmd[4]; cmd[0] = 0x0E; // Erase Flash opcode cmd[1] = mode; // arg0 = which sectors to clear cmd[2] = 0x00; // arg1 cmd[3] = 0x00; // arg2 int sts; if ((sts = xo2->exchange (xo2->user_ctx, cmd, 4, NULL, 0))) return sts; if (mode & XO2_MODE_ERASE_CONFIG) usleep (xo2->info->cfg_erase * 1000ULL); else if (mode & XO2_MODE_ERASE_UFM) usleep (xo2->info->ufm_erase * 1000ULL); else usleep (50000ULL); return xo2_status_wait (xo2, 10000); } /** * Clear a failed programming attempt. * Call when programming fails. This erases the Config, UFM, Feature Row * sectors of the XO2 Flash and clears the SRAM by doing a Refresh with * the now blank flash contents. This will put the part into a factory * blank state. Factory blank state has I2C and SPI enabled for * configuration, so a new programming cycle can be re-attempted. * * @return 0 on success, !0 on error */ int xo2_factory_reset (xo2_handle_t *xo2) { int sts; if ((sts = xo2_mode_set (xo2, XO2_MODE_OFFLINE))) return sts; int mode = XO2_MODE_ERASE_UFM | XO2_MODE_ERASE_CONFIG | XO2_MODE_ERASE_FEATROW; return xo2_flash_erase (xo2, mode); } /** * Set the Feature Row. * This function assembles the command sequence that allows programming the XO2 FEATURE * bits and the FEATURE_BITS in the Feature Row. The 8 FEATURE bytes and 2 FEATURE_BITS bytes * must be properly formatted or possible * lock-up of the XO2 could occur. Only the values obtained from properly parsing the JEDEC * file should be used. * * @param xo2 Open API handle * @param pXO2 pointer to the XO2 device to access * @param pFeature pointer to the Feature Row structure containing the encoded data to write * into the Feature and FEATURE_BITS fields in the Feature Row. * * @return 0 on success, !0 on error * * @note The Feature Row must first be erased */ int xo2_feature_write (xo2_handle_t *xo2, const uint8_t *feature_row, const uint8_t *feature_bits) { uint8_t cmd[12]; cmd[0] = 0xE4; // program Feature opcode cmd[1] = 0x00; // arg0 cmd[2] = 0x00; // arg1 cmd[3] = 0x00; // arg2 memcpy (&cmd[4], feature_row, 8); int sts; if ((sts = xo2->exchange (xo2->user_ctx, cmd, 12, NULL, 0))) return sts; usleep (200); cmd[0] = 0xF8; // program FEATURE_BITS opcode cmd[1] = 0x00; // arg0 cmd[2] = 0x00; // arg1 cmd[3] = 0x00; // arg2 cmd[4] = feature_bits[0]; cmd[5] = feature_bits[1]; if ((sts = xo2->exchange (xo2->user_ctx, cmd, 6, NULL, 0))) return sts; usleep (200); return xo2_status_wait (xo2, 2000); } /** * Read the Feature Row contents. * This function assembles the command sequence that allows reading back the Feature Row * contents. The FEATURE bytes and FEATURE_BITS fields are returned in a XO2 specific stucture. * Uses would be to verify successful XO2ECAcmd_FeatureWrite() programing. Or to read back * and preserve during an update. * @param xo2 Open API handle * @param pXO2 pointer to the XO2 device to access * @param pFeature pointer to the Feature Row structure that will be loaded with the * feature row contents. * * @return 0 on success, !0 on error */ int xo2_feature_read (xo2_handle_t *xo2, uint8_t *feature_row, uint8_t *feature_bits) { uint8_t cmd[4]; cmd[0] = 0xE7; // Read Feature opcode cmd[1] = 0x00; // arg0 cmd[2] = 0x00; // arg1 cmd[3] = 0x00; // arg2 int sts; if ((sts = xo2->exchange (xo2->user_ctx, cmd, 4, feature_row, 8))) return sts; cmd[0] = 0xFB; // Read FEATURE_BITS opcode cmd[1] = 0x00; // arg0 cmd[2] = 0x00; // arg1 cmd[3] = 0x00; // arg2 return xo2->exchange (xo2->user_ctx, cmd, 4, feature_bits, 2); } /** * Erase, program, and/or verify flash sections of device. * * The caller can select to erase, program, and/or verify individual * sectors of flash of the device. The part is first placed into either * transparent or offline config mode. Transparent leaves the part * operating from SRAM while the flash is programmed in background * mode. However the feature bits cannot be programmed in this mode. * Offline mode tri-states all pins and suspends user logic while being * programmed. * * @param xo2 Open API handle * @param pProgJED reference to the converted XO2 JEDEC file data * @param mode bitmap of what to erase/program and whether to verify or not * * @note If programming fails, an attempt is made to close confgiuration mode. * The user may wish to retry or erase the Cfg and UFM flash sectors * in an attempt to return the device to a blank state. * * @return 0 on success, !0 on error */ int xo2_program (xo2_handle_t *xo2, const void *image_ptr, int mode) { int sts, reset_device = 0; unsigned int page; const machxo2_hdr_t *image = (const machxo2_hdr_t *) image_ptr; uint8_t *cfg_start = (uint8_t *) (image + 1); uint8_t *ufm_start = cfg_start + (XO2_PAGE_SIZE * image->cfg_pages); uint8_t buffer[XO2_PAGE_SIZE], *ptr; if (mode & XO2_MODE_TRANSPARENT) { sts = xo2_mode_set (xo2, XO2_MODE_TRANSPARENT); // Prevent erasing the Feature Row in Transparent mode. The user logic is running and // operating per the settings of the current Feature Row. Erasing it can lead to // instability in the running design. Use Offline mode (design halted) when re- // programming Feature Row (changing device behavior). mode &= ~(XO2_MODE_ERASE_FEATROW | XO2_MODE_PROGRAM_FEATROW); } else { sts = xo2_mode_set (xo2, XO2_MODE_OFFLINE); mode |= XO2_MODE_ERASE_SRAM; } if (sts) return sts; do { if (mode & (XO2_MODE_ERASE_CONFIG | XO2_MODE_ERASE_UFM | XO2_MODE_ERASE_FEATROW)) { reset_device = 1; LOGDBG ("Erasing device"); if ((sts = xo2_flash_erase (xo2, mode))) break; } if (mode & XO2_MODE_PROGRAM_CONFIG) { reset_device = 1; LOGDBG ("Config Sector Program"); if ((sts = xo2_page_reset (xo2, XO2_SECTOR_CFG))) break; ptr = cfg_start; for (page = 0; page < image->cfg_pages; page++) { // LOGDBG ("Writing config page: %d", page + 1); if ((sts = xo2_page_write (xo2, XO2_SECTOR_CFG, ptr, 2000))) break; ptr += XO2_PAGE_SIZE; } if (page != image->cfg_pages) break; } if (mode & XO2_MODE_VERIFY_CONFIG) { LOGDBG ("Config Sector Verify"); if ((sts = xo2_page_reset (xo2, XO2_SECTOR_CFG))) break; ptr = cfg_start; for (page = 0; page < image->cfg_pages; page++) { // LOGDBG ("Verify config page: %d", page + 1); if ((sts = xo2_page_read (xo2, XO2_SECTOR_CFG, buffer))) return sts; if (memcmp (buffer, ptr, XO2_PAGE_SIZE)) { LOGERR("Verify config page %d error", page + 1); sts = -1; break; } ptr += XO2_PAGE_SIZE; } if (page != image->cfg_pages) break; } if ((mode & XO2_MODE_PROGRAM_UFM) && image->ufm_pages) { reset_device = 1; LOGDBG("UFM Sector Program"); if ((sts = xo2_page_reset (xo2, XO2_SECTOR_UFM))) break; ptr = ufm_start; for (page = 0; page < image->ufm_pages; page++) { // LOGDBG ("Writing UFM page: %d", page + 1); if ((sts = xo2_page_write (xo2, XO2_SECTOR_UFM, ptr, 2000))) break; ptr += XO2_PAGE_SIZE; } if (page != image->ufm_pages) break; } if ((mode & XO2_MODE_VERIFY_UFM) && image->ufm_pages) { LOGDBG("UFM Sector Verify"); if ((sts = xo2_page_reset (xo2, XO2_SECTOR_UFM))) break; ptr = ufm_start; for (page = 0; page < image->ufm_pages; page++) { // LOGDBG ("Verify UFM page: %d", page + 1); if ((sts = xo2_page_read (xo2, XO2_SECTOR_UFM, buffer))) return sts; if (memcmp (buffer, ptr, XO2_PAGE_SIZE)) { LOGERR("Verify UFMPage(%d) error", page + 1); sts = -1; break; } ptr += XO2_PAGE_SIZE; } if (page != image->ufm_pages) break; } if (mode & XO2_MODE_PROGRAM_FEATROW) { reset_device = 1; LOGDBG ("Feature row program"); if ((sts = xo2_feature_write (xo2, image->feature_row, image->feature_bits))) break; } if (mode & XO2_MODE_VERIFY_FEATROW) { LOGDBG ("Feature row verify"); uint8_t feature_row[8], feature_bits[2]; if ((sts = xo2_feature_read (xo2, feature_row, feature_bits))) break; if (memcmp (feature_row, image->feature_row, 8)) { LOGERR("Feature row verify failure"); sts = -1; break; } if (memcmp (feature_bits, image->feature_bits, 2)) { LOGERR("Feature bits verify failure"); sts = -1; break; } } if (!reset_device) break; // FINALIZE and CLOSE CONFIG INTERFACE // Set DONE bit indicating valid design loaded into flash if ((sts = xo2_done (xo2, 2000))) break; // Boot design to user mode (i.e. like hitting PROGRAM pin) // Refresh command will clear SRAM, load from Flash, set Done, exit config mode. // Sometimes it needs to be called more than once. // But eventually it boots and Done goes high. int loops = 10; do { if (!xo2_refresh (xo2)) return 0; } while (--loops); sts = 0; } while (0); xo2_config_exit (xo2); return sts; } int xo2_file_info (const void *buffer, uint32_t *user_code, uint32_t *device_id) { const machxo2_hdr_t *hdr = (const machxo2_hdr_t *) buffer; if (user_code) *user_code = hdr->user_code; if (device_id) *device_id = hdr->device_id; return 0; }