glider/utils/mxc_waveform_asm/main.c

448 lines
15 KiB
C

/*******************************************************************************
* Freescale/NXP i.MX EPDC waveform assembler
*
* This tools converts human readable .csv waveform file into .fw file used
* by i.MX EPDC driver.
*
* Copyright 2021 Wenting Zhang
*
* This 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 software 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
* the software. If not, see <http://www.gnu.org/licenses/>.
*
* This file is partially derived from Linux kernel driver, with the following
* copyright information:
* Copyright (C) 2014-2016 Freescale Semiconductor, Inc.
* Copyright 2017 NXP
*
* inih is used in this project, which is licensed under BSD-3-Clause.
* csv_parser is used in this project, which is licensed under MIT.
******************************************************************************/
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <libgen.h>
#include <inttypes.h>
#include "ini.h"
#include "csv.h"
#define MAX_MODES (32) // Maximum waveform modes supported
#define MAX_TEMPS (32) // Maximum temperature ranges supported
#define MAX_CSV_LINE (1024)
#define GREYSCALE_BPP (2)
#define GREYSCALE_LEVEL (16)
typedef struct {
unsigned int wi0;
unsigned int wi1;
unsigned int wi2;
unsigned int wi3;
unsigned int wi4;
unsigned int wi5;
unsigned int wi6;
unsigned int xwia: 24; // address of extra waveform information
unsigned int cs1: 8; // checksum 1
unsigned int wmta: 24;
unsigned int fvsn: 8;
unsigned int luts: 8;
unsigned int mc: 8; // mode count
unsigned int trc: 8; // temperature range count
unsigned int advanced_wfm_flags: 8;
unsigned int eb: 8;
unsigned int sb: 8;
unsigned int reserved0_1: 8;
unsigned int reserved0_2: 8;
unsigned int reserved0_3: 8;
unsigned int reserved0_4: 8;
unsigned int reserved0_5: 8;
unsigned int cs2: 8; // checksum 2
} waveform_data_header_t;
typedef struct {
waveform_data_header_t wdh;
uint8_t data[]; /* Temperature Range Table + Waveform Data */
} waveform_data_file_t;
typedef struct {
char *prefix;
int modes;
char **mode_names;
int *frame_counts; // frame_counts[mode * temps + temp]
int temps;
int *temp_ranges;
uint8_t ***luts; // luts[mode][temp][frame count * 256 + dst * 16 + src]
} context_t;
static int ini_parser_handler(void* user, const char* section, const char* name,
const char* value) {
context_t* pcontext = (context_t*)user;
if (strcmp(section, "WAVEFORM") == 0) {
if (strcmp(name, "VERSION") == 0) {
assert(strcmp(value, "1.0") == 0);
}
else if (strcmp(name, "PREFIX") == 0) {
pcontext->prefix = strdup(value);
}
else if (strcmp(name, "MODES") == 0) {
// Allocate memory for modes
pcontext->modes = atoi(value);
assert(pcontext->modes <= MAX_MODES);
pcontext->mode_names = malloc(sizeof(char*) * pcontext->modes);
assert(pcontext->mode_names);
}
else if (strcmp(name, "TEMPS") == 0) {
// Allocate memory for temp ranges
pcontext->temps = atoi(value);
assert(pcontext->temps <= MAX_TEMPS);
pcontext->temp_ranges = malloc(sizeof(int) * pcontext->temps);
assert(pcontext->temp_ranges);
pcontext->frame_counts = malloc(sizeof(int) * pcontext->modes *
pcontext->temps);
assert(pcontext->frame_counts);
}
else {
size_t len = strlen(name);
if ((len >= 7) && (name[0] == 'T') &&
(strncmp(name + (len - 5), "RANGE", 5) == 0)) {
// Temperature Range
char *temp_id_s = strdup(name);
temp_id_s[len - 5] = '\0';
int temp_id = atoi(temp_id_s + 1);
free(temp_id_s);
pcontext->temp_ranges[temp_id] = atoi(value);
}
else {
fprintf(stderr, "Unknown name %s=%s\n", name, value);
return 0; // Unknown name
}
}
}
else if (strncmp(section, "MODE", 4) == 0) {
int mode_id = atoi(section + 4);
assert(mode_id >= 0);
assert(mode_id < pcontext->modes);
size_t len = strlen(name);
if (strcmp(name, "NAME") == 0) {
// Mode Name
pcontext->mode_names[mode_id] = strdup(value);
}
else if ((len >= 4) && (name[0] == 'T') &&
(strncmp(name + (len - 2), "FC", 2) == 0)) {
// Frame Count
char *temp_id_s = strdup(name);
temp_id_s[len - 2] = '\0';
int temp_id = atoi(temp_id_s + 1);
free(temp_id_s);
pcontext->frame_counts[pcontext->temps * mode_id + temp_id]
= atoi(value);
}
}
else {
fprintf(stderr, "Unknown section %s\n", section);
return 0; // Unknown section
}
return 1;
}
static void write_uint64_le(uint8_t* dst, uint64_t val) {
dst[7] = (val >> 56) & 0xff;
dst[6] = (val >> 48) & 0xff;
dst[5] = (val >> 40) & 0xff;
dst[4] = (val >> 32) & 0xff;
dst[3] = (val >> 24) & 0xff;
dst[2] = (val >> 16) & 0xff;
dst[1] = (val >> 8) & 0xff;
dst[0] = (val) & 0xff;
}
static void parse_range(const char* str, int* begin, int* end) {
// Parse range specified in the waveform.
// Example:
// 2 - 2 to 2
// 0:15 - 0 to 15
// 4:7 - 4 to 7
char* delim = strchr(str, ':');
if (delim) {
*begin = atoi(str);
*end = atoi(delim + 1);
}
else {
*begin = atoi(str);
*end = *begin;
}
}
static void load_waveform_csv(const char* filename, int frame_count,
uint8_t* lut) {
FILE* fp = fopen(filename, "r");
assert(fp);
// Unspecified parts of LUT will be filled with 3 instead of 0 for debugging
memset(lut, 3, frame_count * 256);
char* line;
int done = 0;
int err = 0;
int rst = 1; // Reset fread_csv_line internal state in the first call
while (!done) {
line = fread_csv_line(fp, MAX_CSV_LINE, &done, &err, rst);
rst = 0;
if (!line) continue;
char** parsed = parse_csv(line);
if (!parsed) continue;
// Parse source/ destination range
int src0, src1, dst0, dst1;
// Skip empty lines
if (!parsed[0]) continue;
if (!parsed[1]) continue;
parse_range(parsed[0], &src0, &src1);
parse_range(parsed[1], &dst0, &dst1);
// Fill in LUT
for (int i = 0; i < frame_count; i++) {
assert(parsed[i]);
uint8_t val = atoi(parsed[i + 2]);
for (int src = src0; src <= src1; src++) {
for (int dst = dst0; dst <= dst1; dst++) {
lut[i * 256 + dst * 16 + src] = val;
}
}
}
free_csv_line(parsed);
free(line);
}
fclose(fp);
}
static void dump_lut(int frame_count, uint8_t* lut) {
for (int src = 0; src < 16; src++) {
for (int dst = 0; dst < 16; dst++) {
printf("%x -> %x: ", src, dst);
for (int frame = 0; frame < frame_count; frame++) {
printf("%d ", lut[frame * 256 + dst * 16 + src]);
}
printf("\n");
}
}
}
static void copy_lut(uint8_t* dst, uint8_t* src, size_t src_count, int ver) {
if (ver == 1) {
memcpy(dst, src, src_count);
}
else {
for (size_t i = 0; i < src_count / 2; i++) {
uint8_t val;
val = *src++;
val <<= 4;
val |= *src++;
*dst++ = val;
}
}
}
int main(int argc, char *argv[]) {
context_t context;
printf("Freescale/NXP i.MX EPDC waveform assembler\n");
// Load waveform descriptor
if (argc < 4) {
fprintf(stderr, "Usage: mxc_wvfm_asm version input_file output_file\n");
fprintf(stderr, "version: EPDC version, possible values: v1, v2\n");
fprintf(stderr, "input_file: Waveform file, in .iwf format\n");
fprintf(stderr, "output_file: MXC EPDC firmware file, in .fw format\n");
fprintf(stderr, "Example: mxc_wvfm_asm v1 e060scm_desc.iwf epdc_E060SCM.fw\n");
return 1;
}
char* ver_string = argv[1];
char* input_fn = argv[2];
char* output_fn = argv[3];
int ver;
if (strcmp(ver_string, "v1") == 0) {
ver = 1;
}
else if (strcmp(ver_string, "v2") == 0) {
ver = 2;
}
else {
fprintf(stderr, "Invalid EPDC version %s\n", ver_string);
fprintf(stderr, "Possible values: v1, v2.\n");
fprintf(stderr, "i.MX6DL/SL uses EPDCv1, i.MX7D uses EPDCv2. "
"i.MX5 EPDC is not supported.\n");
return 1;
}
if (ini_parse(input_fn, ini_parser_handler, &context) < 0) {
fprintf(stderr, "Failed to load waveform descriptor.\n");
return 1;
}
// Set default name if not provided
char* default_name = "Unknown";
for (int i = 0; i < context.modes; i++) {
if (!context.mode_names[i])
context.mode_names[i] = strdup(default_name);
}
// Print loaded info
printf("Prefix: %s\n", context.prefix);
for (int i = 0; i < context.modes; i++) {
printf("Mode %d: %s\n", i, context.mode_names[i]);
for (int j = 0; j < context.temps; j++) {
printf("\tTemp %d: %d frames\n", j,
context.frame_counts[i * context.temps + j]);
}
}
for (int i = 0; i < context.temps; i++) {
printf("Temp %d: %d degC\n", i, context.temp_ranges[i]);
}
assert(context.modes < 100);
assert(context.temps < 100);
// Load actual waveform
char *dir = dirname(input_fn); // Return val of dirname shall not be free()d
size_t dirlen = strlen(dir);
context.luts = malloc(context.modes * sizeof(uint8_t**));
assert(context.luts);
for (int i = 0; i < context.modes; i++) {
context.luts[i] = malloc(context.temps * sizeof(uint8_t*));
assert(context.luts[i]);
for (int j = 0; j < context.temps; j++) {
int frame_count = context.frame_counts[i * context.temps + j];
context.luts[i][j] = malloc(frame_count * 256); // LUT always in 8b
assert(context.luts[i][j]);
char* fn = malloc(dirlen + strlen(context.prefix) + 14);
assert(fn);
sprintf(fn, "%s/%s_M%d_T%d.csv", dir, context.prefix, i, j);
printf("Loading %s...\n", fn);
load_waveform_csv(fn, frame_count, context.luts[i][j]);
free(fn);
}
}
// Calculate file size and offset
uint64_t header_size = sizeof(waveform_data_header_t);
uint64_t temp_table_size = sizeof(uint8_t) * context.temps;
uint64_t mode_offset_table_size = sizeof(uint64_t) * context.modes;
uint64_t temp_offset_table_size = sizeof(uint64_t) * context.temps;
// 1st level mode offset table
uint64_t* mode_offset_table = malloc(mode_offset_table_size);
// global offset table
uint64_t* data_offset_table =
malloc(sizeof(uint64_t) * context.modes * context.temps);
uint64_t total_size = 0;
uint64_t data_region_offset = temp_table_size + 1;
total_size += mode_offset_table_size;
uint64_t lut_size = (ver == 1) ? 256 : 128;
for (int i = 0; i < context.modes; i++) {
// Set the offset of the current mode
mode_offset_table[i] = total_size;
total_size += temp_offset_table_size;
for (int j = 0; j < context.temps; j++) {
uint64_t data_size = context.frame_counts[i * context.temps + j]
* lut_size + sizeof(uint64_t);
data_offset_table[i * context.temps + j] = total_size;
printf("Mode %d Temp %d data offset %08"PRIx64" (%"PRId64")\n",
i, j, total_size, total_size);
total_size += data_size;
}
}
total_size += header_size + temp_table_size + 1;
printf("Total file size %"PRId64"\n", total_size);
// Allocate memory for waveform buffer
waveform_data_file_t* pwvfm_file = malloc(total_size);
assert(pwvfm_file);
// Fill waveform header
memset(&pwvfm_file->wdh, 0, sizeof(waveform_data_header_t));
pwvfm_file->wdh.trc = context.temps - 1;
pwvfm_file->wdh.mc = context.modes - 1;
// Other fields (including checksums) are generally directly imported from
// wbf file. They are not used in MXC EPDC driver. No need to fill them.
// Fill temperature table
for (int i = 0; i < context.temps; i++) {
pwvfm_file->data[i] = context.temp_ranges[i];
}
// Set 1 byte padding
pwvfm_file->data[context.temps] = 0;
// Fill waveform offset table and temp offset table
uint8_t* wvfm_data_region = &pwvfm_file->data[data_region_offset];
for (int i = 0; i < context.modes; i++) {
write_uint64_le(&wvfm_data_region[i * 8],
mode_offset_table[i]);
for (int j = 0; j < context.temps; j++) {
write_uint64_le(&wvfm_data_region[mode_offset_table[i] + j * 8],
data_offset_table[i * context.temps + j]);
}
}
// Fill waveform data
for (int i = 0; i < context.modes; i++) {
for (int j = 0; j < context.temps; j++) {
size_t index = i * context.temps + j;
uint8_t* wvfm_wr_ptr = &wvfm_data_region[data_offset_table[index]];
int frame_count = context.frame_counts[index];
write_uint64_le(wvfm_wr_ptr, frame_count);
wvfm_wr_ptr += 8;
copy_lut(wvfm_wr_ptr, context.luts[i][j], frame_count * 256, ver);
}
}
// Write waveform file
FILE *outFile = fopen(output_fn, "wb");
assert(outFile);
size_t written = fwrite((uint8_t *)pwvfm_file, total_size, 1, outFile);
assert(written == 1);
fclose(outFile);
printf("Finished.\n");
// Free buffers
free(pwvfm_file);
free(context.prefix);
for (int i = 0; i < context.modes; i++) {
free(context.mode_names[i]);
for (int j = 0; j < context.temps; j++) {
free(context.luts[i][j]);
}
free(context.luts[i]);
}
free(context.mode_names);
free(context.luts);
free(context.frame_counts);
free(context.temp_ranges);
free(mode_offset_table);
free(data_offset_table);
return 0;
}