|
| 1 | +// SPDX-License-Identifier: GPL-2.0 |
| 2 | +/* |
| 3 | + * AD9088 Calibration Data Dump Tool |
| 4 | + * |
| 5 | + * Copyright 2025 Analog Devices Inc. |
| 6 | + * |
| 7 | + * Usage: ad9088_cal_dump <calibration_file> |
| 8 | + * |
| 9 | + * This tool reads an AD9088 calibration data file and displays: |
| 10 | + * - Header information (magic, version, chip ID, configuration) |
| 11 | + * - Section offsets and sizes |
| 12 | + * - CRC validation |
| 13 | + * - Calibration data summary |
| 14 | + */ |
| 15 | + |
| 16 | +#include <stdio.h> |
| 17 | +#include <stdint.h> |
| 18 | +#include <stdlib.h> |
| 19 | +#include <string.h> |
| 20 | +#include <errno.h> |
| 21 | +#include <stdbool.h> |
| 22 | +#include <zlib.h> /* For CRC32 calculation */ |
| 23 | + |
| 24 | +/* Magic number for AD9088 calibration files */ |
| 25 | +#define AD9088_CAL_MAGIC 0x41443930 /* "AD90" */ |
| 26 | +#define AD9088_CAL_VERSION 2 |
| 27 | + |
| 28 | +/* Chip IDs */ |
| 29 | +#define CHIPID_AD9084 0x9084 |
| 30 | +#define CHIPID_AD9088 0x9088 |
| 31 | + |
| 32 | +/* Number of devices */ |
| 33 | +#define ADI_APOLLO_NUM_ADC_CAL_MODES 2 |
| 34 | +#define ADI_APOLLO_NUM_JTX_SERDES_12PACKS 4 |
| 35 | +#define ADI_APOLLO_NUM_SIDES 2 |
| 36 | + |
| 37 | +/* Calibration file header structure (must match kernel driver) */ |
| 38 | +struct ad9088_cal_header { |
| 39 | + uint32_t magic; /* Magic number "AD90" */ |
| 40 | + uint32_t version; /* File format version */ |
| 41 | + uint32_t chip_id; /* Chip ID (0x9084 or 0x9088) */ |
| 42 | + uint8_t is_8t8r; /* 1 = 8T8R, 0 = 4T4R */ |
| 43 | + uint8_t num_adcs; /* Number of ADCs */ |
| 44 | + uint8_t num_serdes_rx; /* Number of SERDES RX 12-packs */ |
| 45 | + uint8_t num_clk_cond; /* Number of clock conditioning sides */ |
| 46 | + uint8_t reserved1[4]; /* Reserved for future use */ |
| 47 | + |
| 48 | + /* Section offsets from start of file */ |
| 49 | + uint32_t adc_cal_offset; /* Offset to ADC cal data */ |
| 50 | + uint32_t serdes_rx_cal_offset; /* Offset to SERDES RX cal data */ |
| 51 | + uint32_t clk_cond_cal_offset; /* Offset to clock conditioning cal data */ |
| 52 | + |
| 53 | + /* Section sizes */ |
| 54 | + uint32_t adc_cal_size; /* Total size of all ADC cal data */ |
| 55 | + uint32_t serdes_rx_cal_size; /* Total size of all SERDES RX cal data */ |
| 56 | + uint32_t clk_cond_cal_size; /* Total size of all clock conditioning cal data */ |
| 57 | + |
| 58 | + uint32_t total_size; /* Total file size including CRC */ |
| 59 | + uint32_t reserved2[4]; /* Reserved for future use */ |
| 60 | +} __attribute__((packed)); |
| 61 | + |
| 62 | +static const char *chip_id_to_string(uint32_t chip_id) |
| 63 | +{ |
| 64 | + switch (chip_id) { |
| 65 | + case CHIPID_AD9084: |
| 66 | + return "AD9084"; |
| 67 | + case CHIPID_AD9088: |
| 68 | + return "AD9088"; |
| 69 | + default: |
| 70 | + return "Unknown"; |
| 71 | + } |
| 72 | +} |
| 73 | + |
| 74 | +static void print_header(const struct ad9088_cal_header *hdr) |
| 75 | +{ |
| 76 | + printf("=== AD9088 Calibration Data Header ===\n\n"); |
| 77 | + |
| 78 | + printf("Magic Number: 0x%08X ('%c%c%c%c') %s\n", |
| 79 | + hdr->magic, |
| 80 | + (hdr->magic >> 0) & 0xFF, |
| 81 | + (hdr->magic >> 8) & 0xFF, |
| 82 | + (hdr->magic >> 16) & 0xFF, |
| 83 | + (hdr->magic >> 24) & 0xFF, |
| 84 | + hdr->magic == AD9088_CAL_MAGIC ? "[OK]" : "[INVALID]"); |
| 85 | + |
| 86 | + printf("Version: %u %s\n", |
| 87 | + hdr->version, |
| 88 | + hdr->version == AD9088_CAL_VERSION ? "[OK]" : "[UNSUPPORTED]"); |
| 89 | + |
| 90 | + printf("Chip ID: 0x%04X (%s)\n", |
| 91 | + hdr->chip_id, |
| 92 | + chip_id_to_string(hdr->chip_id)); |
| 93 | + |
| 94 | + printf("Configuration: %s\n", |
| 95 | + hdr->is_8t8r ? "8T8R (8 TX, 8 RX)" : "4T4R (4 TX, 4 RX)"); |
| 96 | + |
| 97 | + printf("Number of ADCs: %u\n", hdr->num_adcs); |
| 98 | + printf("Number of SERDES RX: %u\n", hdr->num_serdes_rx); |
| 99 | + printf("Number of Clk Cond: %u\n", hdr->num_clk_cond); |
| 100 | + |
| 101 | + printf("\n=== Calibration Sections ===\n\n"); |
| 102 | + |
| 103 | + printf("ADC Calibration:\n"); |
| 104 | + printf(" Offset: 0x%08X (%u bytes)\n", hdr->adc_cal_offset, hdr->adc_cal_offset); |
| 105 | + printf(" Size: 0x%08X (%u bytes)\n", hdr->adc_cal_size, hdr->adc_cal_size); |
| 106 | + if (hdr->num_adcs > 0 && hdr->adc_cal_size > 0) { |
| 107 | + uint32_t per_mode = hdr->adc_cal_size / ADI_APOLLO_NUM_ADC_CAL_MODES; |
| 108 | + uint32_t per_adc = per_mode / hdr->num_adcs; |
| 109 | + printf(" Per Mode: %u bytes\n", per_mode); |
| 110 | + printf(" Per ADC: %u bytes\n", per_adc); |
| 111 | + } |
| 112 | + |
| 113 | + printf("\nSERDES RX Calibration:\n"); |
| 114 | + printf(" Offset: 0x%08X (%u bytes)\n", hdr->serdes_rx_cal_offset, hdr->serdes_rx_cal_offset); |
| 115 | + printf(" Size: 0x%08X (%u bytes)\n", hdr->serdes_rx_cal_size, hdr->serdes_rx_cal_size); |
| 116 | + if (hdr->num_serdes_rx > 0 && hdr->serdes_rx_cal_size > 0) { |
| 117 | + uint32_t per_serdes = hdr->serdes_rx_cal_size / hdr->num_serdes_rx; |
| 118 | + printf(" Per Pack: %u bytes\n", per_serdes); |
| 119 | + } |
| 120 | + |
| 121 | + printf("\nClock Conditioning Calibration:\n"); |
| 122 | + printf(" Offset: 0x%08X (%u bytes)\n", hdr->clk_cond_cal_offset, hdr->clk_cond_cal_offset); |
| 123 | + printf(" Size: 0x%08X (%u bytes)\n", hdr->clk_cond_cal_size, hdr->clk_cond_cal_size); |
| 124 | + if (hdr->num_clk_cond > 0 && hdr->clk_cond_cal_size > 0) { |
| 125 | + uint32_t per_side = hdr->clk_cond_cal_size / hdr->num_clk_cond; |
| 126 | + printf(" Per Side: %u bytes\n", per_side); |
| 127 | + } |
| 128 | + |
| 129 | + printf("\nTotal Size: 0x%08X (%u bytes)\n", |
| 130 | + hdr->total_size, hdr->total_size); |
| 131 | +} |
| 132 | + |
| 133 | +static void print_section_summary(const char *section_name, const uint8_t *data, |
| 134 | + uint32_t offset, uint32_t size, |
| 135 | + uint32_t num_items, const char *item_name) |
| 136 | +{ |
| 137 | + uint32_t i, per_item; |
| 138 | + bool all_zero = true; |
| 139 | + bool all_ff = true; |
| 140 | + |
| 141 | + if (size == 0 || num_items == 0) { |
| 142 | + printf("\n=== %s: Empty ===\n", section_name); |
| 143 | + return; |
| 144 | + } |
| 145 | + |
| 146 | + per_item = size / num_items; |
| 147 | + |
| 148 | + printf("\n=== %s ===\n", section_name); |
| 149 | + printf("Total size: %u bytes (%u items × %u bytes)\n\n", |
| 150 | + size, num_items, per_item); |
| 151 | + |
| 152 | + /* Check for all zeros or all 0xFF (likely uninitialized) */ |
| 153 | + for (i = 0; i < size && (all_zero || all_ff); i++) { |
| 154 | + if (data[i] != 0x00) |
| 155 | + all_zero = false; |
| 156 | + if (data[i] != 0xFF) |
| 157 | + all_ff = false; |
| 158 | + } |
| 159 | + |
| 160 | + if (all_zero) { |
| 161 | + printf("WARNING: All data is zero (possibly uninitialized)\n"); |
| 162 | + } else if (all_ff) { |
| 163 | + printf("WARNING: All data is 0xFF (possibly uninitialized)\n"); |
| 164 | + } |
| 165 | + |
| 166 | + /* Print first 16 bytes of each item */ |
| 167 | + for (i = 0; i < num_items; i++) { |
| 168 | + uint32_t item_offset = i * per_item; |
| 169 | + uint32_t j, bytes_to_show; |
| 170 | + |
| 171 | + printf("%s %u (offset 0x%08X):\n", item_name, i, |
| 172 | + offset + item_offset); |
| 173 | + |
| 174 | + bytes_to_show = per_item < 16 ? per_item : 16; |
| 175 | + printf(" "); |
| 176 | + for (j = 0; j < bytes_to_show; j++) { |
| 177 | + printf("%02X ", data[item_offset + j]); |
| 178 | + if ((j + 1) % 16 == 0) |
| 179 | + printf("\n "); |
| 180 | + } |
| 181 | + if (bytes_to_show < per_item) |
| 182 | + printf("... (%u more bytes)", per_item - bytes_to_show); |
| 183 | + printf("\n"); |
| 184 | + } |
| 185 | +} |
| 186 | + |
| 187 | +static int validate_and_dump(const char *filename) |
| 188 | +{ |
| 189 | + FILE *fp; |
| 190 | + uint8_t *data; |
| 191 | + size_t file_size, read_size; |
| 192 | + struct ad9088_cal_header *hdr; |
| 193 | + uint32_t crc_stored = 0, crc_calc = 0; |
| 194 | + int ret = 0; |
| 195 | + |
| 196 | + /* Open file */ |
| 197 | + fp = fopen(filename, "rb"); |
| 198 | + if (!fp) { |
| 199 | + fprintf(stderr, "Error: Cannot open file '%s': %s\n", |
| 200 | + filename, strerror(errno)); |
| 201 | + return 1; |
| 202 | + } |
| 203 | + |
| 204 | + /* Get file size */ |
| 205 | + fseek(fp, 0, SEEK_END); |
| 206 | + file_size = ftell(fp); |
| 207 | + fseek(fp, 0, SEEK_SET); |
| 208 | + |
| 209 | + printf("File: %s\n", filename); |
| 210 | + printf("Size: %zu bytes\n\n", file_size); |
| 211 | + |
| 212 | + /* Check minimum size */ |
| 213 | + if (file_size < sizeof(struct ad9088_cal_header) + 4) { |
| 214 | + fprintf(stderr, "Error: File too small (%zu bytes)\n", file_size); |
| 215 | + fclose(fp); |
| 216 | + return 1; |
| 217 | + } |
| 218 | + |
| 219 | + /* Read entire file */ |
| 220 | + data = malloc(file_size); |
| 221 | + if (!data) { |
| 222 | + fprintf(stderr, "Error: Cannot allocate memory\n"); |
| 223 | + fclose(fp); |
| 224 | + return 1; |
| 225 | + } |
| 226 | + |
| 227 | + read_size = fread(data, 1, file_size, fp); |
| 228 | + fclose(fp); |
| 229 | + |
| 230 | + if (read_size != file_size) { |
| 231 | + fprintf(stderr, "Error: Read only %zu of %zu bytes\n", |
| 232 | + read_size, file_size); |
| 233 | + free(data); |
| 234 | + return 1; |
| 235 | + } |
| 236 | + |
| 237 | + /* Parse header */ |
| 238 | + hdr = (struct ad9088_cal_header *)data; |
| 239 | + |
| 240 | + /* Validate magic number */ |
| 241 | + if (hdr->magic != AD9088_CAL_MAGIC) { |
| 242 | + fprintf(stderr, "Error: Invalid magic number 0x%08X (expected 0x%08X)\n", |
| 243 | + hdr->magic, AD9088_CAL_MAGIC); |
| 244 | + ret = 1; |
| 245 | + goto out_print_header; |
| 246 | + } |
| 247 | + |
| 248 | + /* Validate version */ |
| 249 | + if (hdr->version != AD9088_CAL_VERSION) { |
| 250 | + fprintf(stderr, "Warning: Unsupported version %u (expected %u)\n", |
| 251 | + hdr->version, AD9088_CAL_VERSION); |
| 252 | + } |
| 253 | + |
| 254 | + /* Validate total size */ |
| 255 | + if (hdr->total_size != file_size) { |
| 256 | + fprintf(stderr, "Warning: Size mismatch - header says %u, file is %zu\n", |
| 257 | + hdr->total_size, file_size); |
| 258 | + } |
| 259 | + |
| 260 | + /* Extract and verify CRC */ |
| 261 | + memcpy(&crc_stored, data + file_size - 4, 4); |
| 262 | + crc_calc = crc32(0L, data, file_size - 4); |
| 263 | + |
| 264 | + printf("=== CRC Validation ===\n\n"); |
| 265 | + printf("Stored CRC: 0x%08X\n", crc_stored); |
| 266 | + printf("Calculated CRC: 0x%08X\n", crc_calc); |
| 267 | + printf("Status: %s\n\n", crc_stored == crc_calc ? "[OK]" : "[FAILED]"); |
| 268 | + |
| 269 | + if (crc_stored != crc_calc) { |
| 270 | + fprintf(stderr, "Error: CRC mismatch!\n"); |
| 271 | + ret = 1; |
| 272 | + } |
| 273 | + |
| 274 | +out_print_header: |
| 275 | + /* Print header information */ |
| 276 | + print_header(hdr); |
| 277 | + |
| 278 | + /* Print section summaries if CRC is valid */ |
| 279 | + if (crc_stored == crc_calc && ret == 0) { |
| 280 | + /* ADC calibration */ |
| 281 | + if (hdr->adc_cal_size > 0 && hdr->adc_cal_offset < file_size) { |
| 282 | + uint32_t num_items = hdr->num_adcs * ADI_APOLLO_NUM_ADC_CAL_MODES; |
| 283 | + print_section_summary("ADC Calibration Data", |
| 284 | + data + hdr->adc_cal_offset, |
| 285 | + hdr->adc_cal_offset, |
| 286 | + hdr->adc_cal_size, |
| 287 | + num_items, |
| 288 | + "ADC Chan/Mode"); |
| 289 | + } |
| 290 | + |
| 291 | + /* SERDES RX calibration */ |
| 292 | + if (hdr->serdes_rx_cal_size > 0 && hdr->serdes_rx_cal_offset < file_size) { |
| 293 | + print_section_summary("SERDES RX Calibration Data", |
| 294 | + data + hdr->serdes_rx_cal_offset, |
| 295 | + hdr->serdes_rx_cal_offset, |
| 296 | + hdr->serdes_rx_cal_size, |
| 297 | + hdr->num_serdes_rx, |
| 298 | + "SERDES RX Pack"); |
| 299 | + } |
| 300 | + |
| 301 | + /* Clock conditioning calibration */ |
| 302 | + if (hdr->clk_cond_cal_size > 0 && hdr->clk_cond_cal_offset < file_size) { |
| 303 | + print_section_summary("Clock Conditioning Calibration Data", |
| 304 | + data + hdr->clk_cond_cal_offset, |
| 305 | + hdr->clk_cond_cal_offset, |
| 306 | + hdr->clk_cond_cal_size, |
| 307 | + hdr->num_clk_cond, |
| 308 | + "Clk Cond Side"); |
| 309 | + } |
| 310 | + } |
| 311 | + |
| 312 | + printf("\n"); |
| 313 | + free(data); |
| 314 | + return ret; |
| 315 | +} |
| 316 | + |
| 317 | +int main(int argc, char *argv[]) |
| 318 | +{ |
| 319 | + if (argc != 2) { |
| 320 | + fprintf(stderr, "Usage: %s <calibration_file>\n", argv[0]); |
| 321 | + fprintf(stderr, "\n"); |
| 322 | + fprintf(stderr, "Example:\n"); |
| 323 | + fprintf(stderr, " %s /lib/firmware/ad9088_cal.bin\n", argv[0]); |
| 324 | + fprintf(stderr, "\n"); |
| 325 | + return 1; |
| 326 | + } |
| 327 | + |
| 328 | + return validate_and_dump(argv[1]); |
| 329 | +} |
0 commit comments