Check in waveform tools

This commit is contained in:
Wenting Zhang 2024-05-14 18:32:10 -04:00
parent 6cccb21440
commit a8f35f2604
15 changed files with 2292 additions and 0 deletions

View file

@ -887,6 +887,11 @@ Here is a list of helpful references related to driving EPDs:
* An early tool for reading Eink's wbf file format: https://github.com/fread-ink/inkwave
* A more up-to-date Eink's wbf format parser: https://patchwork.kernel.org/project/linux-arm-kernel/patch/20220413221916.50995-2-samuel@sholland.org/
On the topic of color screen resolution, color mapping, subpixel rendering:
* Klompenhouwer, Michiel A., and Erno H. A. Langendijk. “59.4: Comparing the Effective Resolution of Various RGB Subpixel Layouts.” SID International Symposium Digest of Technical Papers, vol. 39, no. 1, 2008, pp. 90710, https://doi.org/10.1889/1.3069822.
* Lai, Chih-chang, and Ching-chih Tsai. “A Modified Stripe-RGBW TFT-LCD with Image-Processing Engine for Mobile Phone Displays.” IEEE Transactions on Consumer Electronics, vol. 53, no. 4, 2007, pp. 162833, https://doi.org/10.1109/TCE.2007.4429262.
## License
This document, other than refereneces explicitly given with their corresponding license, is released into public domain.

View file

@ -0,0 +1,7 @@
all: mxc_wvfm_asm
mxc_wvfm_asm: main.c ini.c csv.c fread_csv_line.c split.c
gcc -O1 -g main.c ini.c csv.c fread_csv_line.c split.c -o mxc_wvfm_asm
clean:
rm -f mxc_wvfm_asm

View file

@ -0,0 +1,143 @@
#include <stdlib.h>
#include <string.h>
void free_csv_line( char **parsed ) {
char **ptr;
for ( ptr = parsed; *ptr; ptr++ ) {
free( *ptr );
}
free( parsed );
}
static int count_fields( const char *line ) {
const char *ptr;
int cnt, fQuote;
for ( cnt = 1, fQuote = 0, ptr = line; *ptr; ptr++ ) {
if ( fQuote ) {
if ( *ptr == '\"' ) {
if ( ptr[1] == '\"' ) {
ptr++;
continue;
}
fQuote = 0;
}
continue;
}
switch( *ptr ) {
case '\"':
fQuote = 1;
continue;
case ',':
cnt++;
continue;
default:
continue;
}
}
if ( fQuote ) {
return -1;
}
return cnt;
}
/*
* Given a string containing no linebreaks, or containing line breaks
* which are escaped by "double quotes", extract a NULL-terminated
* array of strings, one for every cell in the row.
*/
char **parse_csv( const char *line ) {
char **buf, **bptr, *tmp, *tptr;
const char *ptr;
int fieldcnt, fQuote, fEnd;
fieldcnt = count_fields( line );
if ( fieldcnt == -1 ) {
return NULL;
}
buf = malloc( sizeof(char*) * (fieldcnt+1) );
if ( !buf ) {
return NULL;
}
tmp = malloc( strlen(line) + 1 );
if ( !tmp ) {
free( buf );
return NULL;
}
bptr = buf;
for ( ptr = line, fQuote = 0, *tmp = '\0', tptr = tmp, fEnd = 0; ; ptr++ ) {
if ( fQuote ) {
if ( !*ptr ) {
break;
}
if ( *ptr == '\"' ) {
if ( ptr[1] == '\"' ) {
*tptr++ = '\"';
ptr++;
continue;
}
fQuote = 0;
}
else {
*tptr++ = *ptr;
}
continue;
}
switch( *ptr ) {
case '\"':
fQuote = 1;
continue;
case '\0':
fEnd = 1;
case ',':
*tptr = '\0';
*bptr = strdup( tmp );
if ( !*bptr ) {
for ( bptr--; bptr >= buf; bptr-- ) {
free( *bptr );
}
free( buf );
free( tmp );
return NULL;
}
bptr++;
tptr = tmp;
if ( fEnd ) {
break;
} else {
continue;
}
default:
*tptr++ = *ptr;
continue;
}
if ( fEnd ) {
break;
}
}
*bptr = NULL;
free( tmp );
return buf;
}

View file

@ -0,0 +1,12 @@
#ifndef CSV_DOT_H_INCLUDE_GUARD
#define CSV_DOT_H_INCLUDE_GUARD
#define CSV_ERR_LONGLINE 0
#define CSV_ERR_NO_MEMORY 1
char **parse_csv( const char *line );
void free_csv_line( char **parsed );
char **split_on_unescaped_newlines(const char *txt);
char *fread_csv_line(FILE *fp, int max_line_size, int *done, int *err, int rst);
#endif

View file

@ -0,0 +1,107 @@
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include "csv.h"
#define READ_BLOCK_SIZE 65536
#define QUICK_GETC( ch, fp )\
do\
{\
if ( read_ptr == read_end ) {\
fread_len = fread( read_buf, sizeof(char), READ_BLOCK_SIZE, fp );\
if ( fread_len < READ_BLOCK_SIZE ) {\
read_buf[fread_len] = '\0';\
}\
read_ptr = read_buf;\
}\
ch = *read_ptr++;\
}\
while(0)
/*
* Given a file pointer, read a CSV line from that file.
* File may include newlines escaped with "double quotes".
*
* Warning: This function is optimized for the use case where
* you repeatedly call it until the file is exhausted. It is
* very suboptimal for the use case of just grabbing one single
* line of CSV and stopping. Also, this function advances the
* file position (in the fseek/ftell sense) unpredictably. You
* should not change the file position between calls to
* fread_csv_line (e.g., don't use "getc" on the file in between
* calls to fread_csv_line).
*
* Other arguments:
* size_t max_line_size: Maximum line size, in bytes.
* int *done: Pointer to an int that will be set to 1 when file is exhausted.
* int *err: Pointer to an int where error code will be written.
*
* Warning: Calling this function on an exhausted file (as indicated by the
* 'done' flag) is undefined behavior.
*
* See csv.h for definitions of error codes.
*/
char *fread_csv_line(FILE *fp, int max_line_size, int *done, int *err, int rst) {
static FILE *bookmark;
static char read_buf[READ_BLOCK_SIZE], *read_ptr, *read_end;
static int fread_len, prev_max_line_size = -1;
static char *buf;
char *bptr, *limit;
char ch;
int fQuote;
if ( max_line_size > prev_max_line_size ) {
if ( prev_max_line_size != -1 ) {
free( buf );
}
buf = malloc( max_line_size + 1 );
if ( !buf ) {
*err = CSV_ERR_NO_MEMORY;
prev_max_line_size = -1;
return NULL;
}
prev_max_line_size = max_line_size;
}
bptr = buf;
limit = buf + max_line_size;
if (( bookmark != fp ) || ( rst )) {
read_ptr = read_end = read_buf + READ_BLOCK_SIZE;
bookmark = fp;
}
for ( fQuote = 0; ; ) {
QUICK_GETC(ch, fp);
if ( !ch || (ch == '\n' && !fQuote)) {
break;
}
if ( bptr >= limit ) {
free( buf );
*err = CSV_ERR_LONGLINE;
return NULL;
}
*bptr++ = ch;
if ( fQuote ) {
if ( ch == '\"' ) {
QUICK_GETC(ch, fp);
if ( ch != '\"' ) {
if ( !ch || ch == '\n' ) {
break;
}
fQuote = 0;
}
*bptr++ = ch;
}
} else if ( ch == '\"' ) {
fQuote = 1;
}
}
*done = !ch;
*bptr = '\0';
return strdup(buf);
}

View file

@ -0,0 +1,298 @@
/* inih -- simple .INI file parser
SPDX-License-Identifier: BSD-3-Clause
Copyright (C) 2009-2020, Ben Hoyt
inih is released under the New BSD license (see LICENSE.txt). Go to the project
home page for more info:
https://github.com/benhoyt/inih
*/
#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS)
#define _CRT_SECURE_NO_WARNINGS
#endif
#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include "ini.h"
#if !INI_USE_STACK
#if INI_CUSTOM_ALLOCATOR
#include <stddef.h>
void* ini_malloc(size_t size);
void ini_free(void* ptr);
void* ini_realloc(void* ptr, size_t size);
#else
#include <stdlib.h>
#define ini_malloc malloc
#define ini_free free
#define ini_realloc realloc
#endif
#endif
#define MAX_SECTION 50
#define MAX_NAME 50
/* Used by ini_parse_string() to keep track of string parsing state. */
typedef struct {
const char* ptr;
size_t num_left;
} ini_parse_string_ctx;
/* Strip whitespace chars off end of given string, in place. Return s. */
static char* rstrip(char* s)
{
char* p = s + strlen(s);
while (p > s && isspace((unsigned char)(*--p)))
*p = '\0';
return s;
}
/* Return pointer to first non-whitespace char in given string. */
static char* lskip(const char* s)
{
while (*s && isspace((unsigned char)(*s)))
s++;
return (char*)s;
}
/* Return pointer to first char (of chars) or inline comment in given string,
or pointer to NUL at end of string if neither found. Inline comment must
be prefixed by a whitespace character to register as a comment. */
static char* find_chars_or_comment(const char* s, const char* chars)
{
#if INI_ALLOW_INLINE_COMMENTS
int was_space = 0;
while (*s && (!chars || !strchr(chars, *s)) &&
!(was_space && strchr(INI_INLINE_COMMENT_PREFIXES, *s))) {
was_space = isspace((unsigned char)(*s));
s++;
}
#else
while (*s && (!chars || !strchr(chars, *s))) {
s++;
}
#endif
return (char*)s;
}
/* Similar to strncpy, but ensures dest (size bytes) is
NUL-terminated, and doesn't pad with NULs. */
static char* strncpy0(char* dest, const char* src, size_t size)
{
/* Could use strncpy internally, but it causes gcc warnings (see issue #91) */
size_t i;
for (i = 0; i < size - 1 && src[i]; i++)
dest[i] = src[i];
dest[i] = '\0';
return dest;
}
/* See documentation in header file. */
int ini_parse_stream(ini_reader reader, void* stream, ini_handler handler,
void* user)
{
/* Uses a fair bit of stack (use heap instead if you need to) */
#if INI_USE_STACK
char line[INI_MAX_LINE];
int max_line = INI_MAX_LINE;
#else
char* line;
size_t max_line = INI_INITIAL_ALLOC;
#endif
#if INI_ALLOW_REALLOC && !INI_USE_STACK
char* new_line;
size_t offset;
#endif
char section[MAX_SECTION] = "";
char prev_name[MAX_NAME] = "";
char* start;
char* end;
char* name;
char* value;
int lineno = 0;
int error = 0;
#if !INI_USE_STACK
line = (char*)ini_malloc(INI_INITIAL_ALLOC);
if (!line) {
return -2;
}
#endif
#if INI_HANDLER_LINENO
#define HANDLER(u, s, n, v) handler(u, s, n, v, lineno)
#else
#define HANDLER(u, s, n, v) handler(u, s, n, v)
#endif
/* Scan through stream line by line */
while (reader(line, (int)max_line, stream) != NULL) {
#if INI_ALLOW_REALLOC && !INI_USE_STACK
offset = strlen(line);
while (offset == max_line - 1 && line[offset - 1] != '\n') {
max_line *= 2;
if (max_line > INI_MAX_LINE)
max_line = INI_MAX_LINE;
new_line = ini_realloc(line, max_line);
if (!new_line) {
ini_free(line);
return -2;
}
line = new_line;
if (reader(line + offset, (int)(max_line - offset), stream) == NULL)
break;
if (max_line >= INI_MAX_LINE)
break;
offset += strlen(line + offset);
}
#endif
lineno++;
start = line;
#if INI_ALLOW_BOM
if (lineno == 1 && (unsigned char)start[0] == 0xEF &&
(unsigned char)start[1] == 0xBB &&
(unsigned char)start[2] == 0xBF) {
start += 3;
}
#endif
start = lskip(rstrip(start));
if (strchr(INI_START_COMMENT_PREFIXES, *start)) {
/* Start-of-line comment */
}
#if INI_ALLOW_MULTILINE
else if (*prev_name && *start && start > line) {
/* Non-blank line with leading whitespace, treat as continuation
of previous name's value (as per Python configparser). */
if (!HANDLER(user, section, prev_name, start) && !error)
error = lineno;
}
#endif
else if (*start == '[') {
/* A "[section]" line */
end = find_chars_or_comment(start + 1, "]");
if (*end == ']') {
*end = '\0';
strncpy0(section, start + 1, sizeof(section));
*prev_name = '\0';
#if INI_CALL_HANDLER_ON_NEW_SECTION
if (!HANDLER(user, section, NULL, NULL) && !error)
error = lineno;
#endif
}
else if (!error) {
/* No ']' found on section line */
error = lineno;
}
}
else if (*start) {
/* Not a comment, must be a name[=:]value pair */
end = find_chars_or_comment(start, "=:");
if (*end == '=' || *end == ':') {
*end = '\0';
name = rstrip(start);
value = end + 1;
#if INI_ALLOW_INLINE_COMMENTS
end = find_chars_or_comment(value, NULL);
if (*end)
*end = '\0';
#endif
value = lskip(value);
rstrip(value);
/* Valid name[=:]value pair found, call handler */
strncpy0(prev_name, name, sizeof(prev_name));
if (!HANDLER(user, section, name, value) && !error)
error = lineno;
}
else if (!error) {
/* No '=' or ':' found on name[=:]value line */
#if INI_ALLOW_NO_VALUE
*end = '\0';
name = rstrip(start);
if (!HANDLER(user, section, name, NULL) && !error)
error = lineno;
#else
error = lineno;
#endif
}
}
#if INI_STOP_ON_FIRST_ERROR
if (error)
break;
#endif
}
#if !INI_USE_STACK
ini_free(line);
#endif
return error;
}
/* See documentation in header file. */
int ini_parse_file(FILE* file, ini_handler handler, void* user)
{
return ini_parse_stream((ini_reader)fgets, file, handler, user);
}
/* See documentation in header file. */
int ini_parse(const char* filename, ini_handler handler, void* user)
{
FILE* file;
int error;
file = fopen(filename, "r");
if (!file)
return -1;
error = ini_parse_file(file, handler, user);
fclose(file);
return error;
}
/* An ini_reader function to read the next line from a string buffer. This
is the fgets() equivalent used by ini_parse_string(). */
static char* ini_reader_string(char* str, int num, void* stream) {
ini_parse_string_ctx* ctx = (ini_parse_string_ctx*)stream;
const char* ctx_ptr = ctx->ptr;
size_t ctx_num_left = ctx->num_left;
char* strp = str;
char c;
if (ctx_num_left == 0 || num < 2)
return NULL;
while (num > 1 && ctx_num_left != 0) {
c = *ctx_ptr++;
ctx_num_left--;
*strp++ = c;
if (c == '\n')
break;
num--;
}
*strp = '\0';
ctx->ptr = ctx_ptr;
ctx->num_left = ctx_num_left;
return str;
}
/* See documentation in header file. */
int ini_parse_string(const char* string, ini_handler handler, void* user) {
ini_parse_string_ctx ctx;
ctx.ptr = string;
ctx.num_left = strlen(string);
return ini_parse_stream((ini_reader)ini_reader_string, &ctx, handler,
user);
}

View file

@ -0,0 +1,150 @@
/* inih -- simple .INI file parser
SPDX-License-Identifier: BSD-3-Clause
Copyright (C) 2009-2020, Ben Hoyt
inih is released under the New BSD license (see LICENSE.txt). Go to the project
home page for more info:
https://github.com/benhoyt/inih
*/
#ifndef INI_H
#define INI_H
/* Make this header file easier to include in C++ code */
#ifdef __cplusplus
extern "C" {
#endif
#include <stdio.h>
/* Nonzero if ini_handler callback should accept lineno parameter. */
#ifndef INI_HANDLER_LINENO
#define INI_HANDLER_LINENO 0
#endif
/* Typedef for prototype of handler function. */
#if INI_HANDLER_LINENO
typedef int (*ini_handler)(void* user, const char* section,
const char* name, const char* value,
int lineno);
#else
typedef int (*ini_handler)(void* user, const char* section,
const char* name, const char* value);
#endif
/* Typedef for prototype of fgets-style reader function. */
typedef char* (*ini_reader)(char* str, int num, void* stream);
/* Parse given INI-style file. May have [section]s, name=value pairs
(whitespace stripped), and comments starting with ';' (semicolon). Section
is "" if name=value pair parsed before any section heading. name:value
pairs are also supported as a concession to Python's configparser.
For each name=value pair parsed, call handler function with given user
pointer as well as section, name, and value (data only valid for duration
of handler call). Handler should return nonzero on success, zero on error.
Returns 0 on success, line number of first error on parse error (doesn't
stop on first error), -1 on file open error, or -2 on memory allocation
error (only when INI_USE_STACK is zero).
*/
int ini_parse(const char* filename, ini_handler handler, void* user);
/* Same as ini_parse(), but takes a FILE* instead of filename. This doesn't
close the file when it's finished -- the caller must do that. */
int ini_parse_file(FILE* file, ini_handler handler, void* user);
/* Same as ini_parse(), but takes an ini_reader function pointer instead of
filename. Used for implementing custom or string-based I/O (see also
ini_parse_string). */
int ini_parse_stream(ini_reader reader, void* stream, ini_handler handler,
void* user);
/* Same as ini_parse(), but takes a zero-terminated string with the INI data
instead of a file. Useful for parsing INI data from a network socket or
already in memory. */
int ini_parse_string(const char* string, ini_handler handler, void* user);
/* Nonzero to allow multi-line value parsing, in the style of Python's
configparser. If allowed, ini_parse() will call the handler with the same
name for each subsequent line parsed. */
#ifndef INI_ALLOW_MULTILINE
#define INI_ALLOW_MULTILINE 1
#endif
/* Nonzero to allow a UTF-8 BOM sequence (0xEF 0xBB 0xBF) at the start of
the file. See https://github.com/benhoyt/inih/issues/21 */
#ifndef INI_ALLOW_BOM
#define INI_ALLOW_BOM 1
#endif
/* Chars that begin a start-of-line comment. Per Python configparser, allow
both ; and # comments at the start of a line by default. */
#ifndef INI_START_COMMENT_PREFIXES
#define INI_START_COMMENT_PREFIXES ";#"
#endif
/* Nonzero to allow inline comments (with valid inline comment characters
specified by INI_INLINE_COMMENT_PREFIXES). Set to 0 to turn off and match
Python 3.2+ configparser behaviour. */
#ifndef INI_ALLOW_INLINE_COMMENTS
#define INI_ALLOW_INLINE_COMMENTS 1
#endif
#ifndef INI_INLINE_COMMENT_PREFIXES
#define INI_INLINE_COMMENT_PREFIXES ";"
#endif
/* Nonzero to use stack for line buffer, zero to use heap (malloc/free). */
#ifndef INI_USE_STACK
#define INI_USE_STACK 1
#endif
/* Maximum line length for any line in INI file (stack or heap). Note that
this must be 3 more than the longest line (due to '\r', '\n', and '\0'). */
#ifndef INI_MAX_LINE
#define INI_MAX_LINE 200
#endif
/* Nonzero to allow heap line buffer to grow via realloc(), zero for a
fixed-size buffer of INI_MAX_LINE bytes. Only applies if INI_USE_STACK is
zero. */
#ifndef INI_ALLOW_REALLOC
#define INI_ALLOW_REALLOC 0
#endif
/* Initial size in bytes for heap line buffer. Only applies if INI_USE_STACK
is zero. */
#ifndef INI_INITIAL_ALLOC
#define INI_INITIAL_ALLOC 200
#endif
/* Stop parsing on first error (default is to keep parsing). */
#ifndef INI_STOP_ON_FIRST_ERROR
#define INI_STOP_ON_FIRST_ERROR 0
#endif
/* Nonzero to call the handler at the start of each new section (with
name and value NULL). Default is to only call the handler on
each name=value pair. */
#ifndef INI_CALL_HANDLER_ON_NEW_SECTION
#define INI_CALL_HANDLER_ON_NEW_SECTION 0
#endif
/* Nonzero to allow a name without a value (no '=' or ':' on the line) and
call the handler with value NULL in this case. Default is to treat
no-value lines as an error. */
#ifndef INI_ALLOW_NO_VALUE
#define INI_ALLOW_NO_VALUE 0
#endif
/* Nonzero to use custom ini_malloc, ini_free, and ini_realloc memory
allocation functions (INI_USE_STACK must also be 0). These functions must
have the same signatures as malloc/free/realloc and behave in a similar
way. ini_realloc is only needed if INI_ALLOW_REALLOC is set. */
#ifndef INI_CUSTOM_ALLOCATOR
#define INI_CUSTOM_ALLOCATOR 0
#endif
#ifdef __cplusplus
}
#endif
#endif /* INI_H */

View file

@ -0,0 +1,447 @@
/*******************************************************************************
* 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;
}

View file

@ -0,0 +1,85 @@
#include <stdlib.h>
#include <string.h>
/*
* Given a string which might contain unescaped newlines, split it up into
* lines which do not contain unescaped newlines, returned as a
* NULL-terminated array of malloc'd strings.
*/
char **split_on_unescaped_newlines(const char *txt) {
const char *ptr, *lineStart;
char **buf, **bptr;
int fQuote, nLines;
/* First pass: count how many lines we will need */
for ( nLines = 1, ptr = txt, fQuote = 0; *ptr; ptr++ ) {
if ( fQuote ) {
if ( *ptr == '\"' ) {
if ( ptr[1] == '\"' ) {
ptr++;
continue;
}
fQuote = 0;
}
} else if ( *ptr == '\"' ) {
fQuote = 1;
} else if ( *ptr == '\n' ) {
nLines++;
}
}
buf = malloc( sizeof(char*) * (nLines+1) );
if ( !buf ) {
return NULL;
}
/* Second pass: populate results */
lineStart = txt;
for ( bptr = buf, ptr = txt, fQuote = 0; ; ptr++ ) {
if ( fQuote ) {
if ( *ptr == '\"' ) {
if ( ptr[1] == '\"' ) {
ptr++;
continue;
}
fQuote = 0;
continue;
} else if ( *ptr ) {
continue;
}
}
if ( *ptr == '\"' ) {
fQuote = 1;
} else if ( *ptr == '\n' || !*ptr ) {
size_t len = ptr - lineStart;
if ( len == 0 ) {
*bptr = NULL;
return buf;
}
*bptr = malloc( len + 1 );
if ( !*bptr ) {
for ( bptr--; bptr >= buf; bptr-- ) {
free( *bptr );
}
free( buf );
return NULL;
}
memcpy( *bptr, lineStart, len );
(*bptr)[len] = '\0';
if ( *ptr ) {
lineStart = ptr + 1;
bptr++;
} else {
bptr[1] = NULL;
return buf;
}
}
}
}

View file

@ -0,0 +1,6 @@
all: mxc_wvfm_dump
mxc_wvfm_dump: main.c
gcc -O1 -g main.c -o mxc_wvfm_dump
clean:
rm -f mxc_wvfm_dump

View file

@ -0,0 +1,309 @@
/*******************************************************************************
* Freescale/NXP EPDC waveform firmware dumper
* Based on https://github.com/julbouln/ice40_eink_controller
*
* 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
******************************************************************************/
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <inttypes.h>
struct waveform_data_header {
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;
unsigned int cs1: 8;
unsigned int wmta: 24;
unsigned int fvsn: 8;
unsigned int luts: 8;
unsigned int mc: 8;
unsigned int trc: 8;
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;
};
struct mxcfb_waveform_data_file {
struct waveform_data_header wdh;
uint32_t *data; /* Temperature Range Table + Waveform Data */
};
static uint64_t read_uint64_le(uint8_t* src) {
return ((uint64_t)src[7] << 56) |
((uint64_t)src[6] << 48) |
((uint64_t)src[5] << 40) |
((uint64_t)src[4] << 32) |
((uint64_t)src[3] << 24) |
((uint64_t)src[2] << 16) |
((uint64_t)src[1] << 8) |
(uint64_t)src[0];
}
static uint8_t read_uint4(uint8_t* src, size_t addr) {
uint8_t val = src[addr >> 1];
if (addr & 1)
val = (val >> 4) & 0xf;
else
val = val & 0xf;
return val;
}
void dump_phases(FILE* fp, uint8_t* buffer, int phases, int ver) {
int i, j, k, l, x, y;
uint8_t luts[phases][16][16];
k = 0;
for (i = 0; i < phases * 256; i += 256) {
j = 0;
for (x = 0; x < 16; x++) {
for (y = 0; y < 16; y++) {
uint8_t val;
if (ver == 2)
val = read_uint4(buffer + 8, i + j);
else
val = buffer[8 + i + j];
luts[k][y][x] = val;
j++;
}
}
k++;
}
for (i = 0; i < 16; i++) {
for (j = 0; j < 16; j++) {
fprintf(fp, "%d,%d,", i, j);
for (k = 0; k < phases; k++) {
fprintf(fp, "%d,", luts[k][i][j]);
}
fprintf(fp, "\n");
}
}
}
int main(int argc, char **argv) {
fprintf(stderr, "MXC EPDC waveform dumper\n");
if (argc < 4) {
fprintf(stderr, "Usage: mxc_wvfm_dump version input_file output_prefix\n");
fprintf(stderr, "version: EPDC version, possible values: v1, v2\n");
fprintf(stderr, "input_file: MXC EPDC firmware file, in .fw format\n");
fprintf(stderr, "output_prefix: Prefix for output file name, without extension\n");
fprintf(stderr, "Example: mxc_wvfm_dump v1 epdc_E060SCM.fw e060scm\n");
return 1;
}
char *ver_string = argv[1];
char *fw = argv[2];
char *prefix = 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;
}
FILE * fp;
size_t file_size;
char * file_buffer;
size_t result;
fp = fopen(fw, "rb");
assert(fp);
// obtain file size:
fseek(fp, 0, SEEK_END);
file_size = ftell(fp);
fseek(fp, 0, SEEK_SET);
// allocate memory to contain the whole file:
file_buffer = (char*)malloc(sizeof(char) * file_size);
assert(file_buffer);
// copy the file into the buffer:
assert(fread(file_buffer, file_size, 1, fp) == 1);
fclose(fp);
/* the whole file is now loaded in the memory buffer. */
struct mxcfb_waveform_data_file *wv_file;
wv_file = (struct mxcfb_waveform_data_file *)file_buffer;
printf("wi0: %08x\n", wv_file->wdh.wi0);
printf("wi1: %08x\n", wv_file->wdh.wi1);
printf("wi2: %08x\n", wv_file->wdh.wi2);
printf("wi3: %08x\n", wv_file->wdh.wi3);
printf("wi4: %08x\n", wv_file->wdh.wi4);
printf("wi5: %08x\n", wv_file->wdh.wi5);
printf("wi6: %08x\n", wv_file->wdh.wi6);
printf("xwia: %d\n", wv_file->wdh.xwia);
printf("cs1: %d\n", wv_file->wdh.cs1);
printf("wmta: %d\n", wv_file->wdh.wmta);
printf("fvsn: %d\n", wv_file->wdh.fvsn);
printf("luts: %d\n", wv_file->wdh.luts);
printf("mc: %d\n", wv_file->wdh.mc);
printf("trc: %d\n", wv_file->wdh.trc);
printf("advanced_wfm_flags: %d\n", wv_file->wdh.advanced_wfm_flags);
printf("eb: %d\n", wv_file->wdh.eb);
printf("sb: %d\n", wv_file->wdh.sb);
printf("reserved0_1: %d\n", wv_file->wdh.reserved0_1);
printf("reserved0_2: %d\n", wv_file->wdh.reserved0_2);
printf("reserved0_3: %d\n", wv_file->wdh.reserved0_3);
printf("reserved0_4: %d\n", wv_file->wdh.reserved0_4);
printf("reserved0_5: %d\n", wv_file->wdh.reserved0_5);
printf("cs2: %d\n", wv_file->wdh.cs2);
int i, j;
int trt_entries; // temperature range table
int wv_data_offs; // offset for waveform data
int waveform_buffer_size; // size for waveform data
int mode_count = wv_file->wdh.mc + 1;
uint8_t *temp_range_bounds;
trt_entries = wv_file->wdh.trc + 1;
printf("Temperatures count: %d\n", trt_entries);
temp_range_bounds = (uint8_t*)malloc(trt_entries);
memcpy(temp_range_bounds, &wv_file->data, trt_entries);
for (i = 0; i < trt_entries; i++) {
printf("Temperature %d = %d°C\n", i, temp_range_bounds[i]);
}
wv_data_offs = sizeof(wv_file->wdh) + trt_entries + 1;
waveform_buffer_size = file_size - wv_data_offs;
printf("Waveform data offset: %d, size: %d\n", wv_data_offs, waveform_buffer_size);
if ((wv_file->wdh.luts & 0xC) == 0x4) {
printf("waveform 5bit\n");
} else {
printf("waveform 4bit\n");
}
uint8_t *waveform_buffer;
waveform_buffer = (uint8_t *)malloc(waveform_buffer_size);
memcpy(waveform_buffer, (uint8_t *)(file_buffer) + wv_data_offs,
waveform_buffer_size);
uint64_t* wv_modes = malloc(sizeof(uint64_t) * mode_count);
uint64_t addr;
// get modes addr
for (i = 0; i < mode_count; i++) {
addr = read_uint64_le(&waveform_buffer[i * 8]);
printf("wave #%d addr: %08"PRIx64"\n", i, addr);
wv_modes[i] = addr;
}
// get modes temp addr
uint64_t* wv_modes_temps = malloc(sizeof(uint64_t) * mode_count * trt_entries);
uint64_t* frame_counts = malloc(sizeof(uint64_t) * mode_count * trt_entries);
uint64_t last_addr = wv_modes[0];
for (i = 0; i < mode_count; i++) {
uint64_t m = wv_modes[i];
for (j = 0; j < trt_entries; j++) {
addr = read_uint64_le(&waveform_buffer[m + j * 8]);
wv_modes_temps[i * trt_entries + j] = addr;
uint64_t frame_count = read_uint64_le(&waveform_buffer[addr]);
frame_counts[i * trt_entries + j] = frame_count;
printf("wave #%d, temp #%d addr: %08"PRIx64", %"PRId64" phases (Addr diff = %"PRId64", Size = %"PRId64")\n", i, j,
addr, frame_count, addr - last_addr, frame_count * 256);
last_addr = addr;
}
}
char* fn = malloc(strlen(prefix) + 14);
sprintf(fn, "%s_desc.iwf", prefix);
fp = fopen(fn, "w");
assert(fp);
fprintf(fp, "[WAVEFORM]\n");
fprintf(fp, "VERSION = 1.0\n");
fprintf(fp, "PREFIX = %s\n", prefix);
fprintf(fp, "MODES = %d\n", mode_count);
fprintf(fp, "TEMPS = %d\n", trt_entries);
fprintf(fp, "\n");
for (int i = 0; i < trt_entries; i++) {
fprintf(fp, "T%dRANGE = %d\n", i, temp_range_bounds[i]);
}
fprintf(fp, "\n");
for (int i = 0; i < mode_count; i++) {
fprintf(fp, "[MODE%d]\n", i);
for (int j = 0; j < trt_entries; j++) {
fprintf(fp, "T%dFC = %"PRId64"\n", j, frame_counts[i * trt_entries + j]);
}
fprintf(fp, "\n");
}
fclose(fp);
for (int i = 0; i < mode_count; i++) {
for (int j = 0; j < trt_entries; j++) {
sprintf(fn, "%s_M%d_T%d.csv", prefix, i, j);
fp = fopen(fn, "w");
assert(fp);
size_t index = i * trt_entries + j;
uint64_t frame_count = frame_counts[index];
uint8_t* lut = &waveform_buffer[wv_modes_temps[index]];
dump_phases(fp, lut, frame_count, ver);
fclose(fp);
}
}
free(fn);
free(temp_range_bounds);
free(waveform_buffer);
free(frame_counts);
free(wv_modes);
free(wv_modes_temps);
free(file_buffer);
return 0;
}

View file

@ -0,0 +1,6 @@
all: wbf_flash_decompress
wbf_flash_decompress: main.c
gcc -O1 -g main.c -o wbf_flash_decompress
clean:
rm -f wbf_flash_decompress

View file

@ -0,0 +1,143 @@
// Eink waveform flash decompressor
// Copyright 2024 Wenting Zhang
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <assert.h>
#include <inttypes.h>
#include <ctype.h>
#define MAX_DECOMP_SIZE (0x100000)
static uint32_t read_uint16_be(uint8_t *ptr) {
uint32_t b0 = (uint32_t)(*ptr++) << 8;
uint32_t b1 = (uint32_t)(*ptr++);
return b0 | b1;
}
static uint32_t read_uint16_le(uint8_t *ptr) {
uint32_t b0 = (uint32_t)(*ptr++);
uint32_t b1 = (uint32_t)(*ptr++) << 8;
return b0 | b1;
}
static uint32_t read_uint32_be(uint8_t *ptr) {
uint32_t b0 = (uint32_t)(*ptr++) << 24;
uint32_t b1 = (uint32_t)(*ptr++) << 16;
uint32_t b2 = (uint32_t)(*ptr++) << 8;
uint32_t b3 = (uint32_t)(*ptr++);
return b0 | b1 | b2 | b3;
}
int main(int argc, char **argv) {
fprintf(stderr, "Eink waveform flash decompressor\n");
if (argc != 3) {
fprintf(stderr, "Usage: wbf_flash_decompress input_file output_file\n");
fprintf(stderr, "input_file: Compressed flash ROM (like a flash dump)\n");
fprintf(stderr, "output_file: WBF file name\n");
return 1;
}
char *bin = argv[1];
char *wbf = argv[2];
FILE * fp;
size_t file_size;
char * file_buffer;
size_t result;
fp = fopen(bin, "rb");
assert(fp);
// obtain file size:
fseek(fp, 0, SEEK_END);
file_size = ftell(fp);
fseek(fp, 0, SEEK_SET);
// allocate memory to contain the whole file:
file_buffer = (char*)malloc(sizeof(char) * file_size);
assert(file_buffer);
// copy the file into the buffer:
assert(fread(file_buffer, file_size, 1, fp) == 1);
fclose(fp);
printf("File size: %zu bytes\n", file_size);
uint8_t *ptr = (uint8_t *)file_buffer;
uint32_t compressed_len = read_uint32_be(ptr);
ptr += 4;
uint32_t header_version = read_uint32_be(ptr);
printf("Compressed length: %d bytes\n", compressed_len);
if ((compressed_len + 16) > file_size) {
printf("Error: file size smaller than expected.\n");
return -1;
}
else if ((compressed_len + 16) < file_size) {
printf("Warning: file size larger than expected.\n");
}
printf("Header version: %d\n", header_version);
if (header_version != 1) {
printf("Unsupported file version\n");
return -1;
}
// Start decompress
ptr = (uint8_t *)file_buffer + 16; // skip header
static uint8_t decomp_buffer[MAX_DECOMP_SIZE];
uint32_t wrptr = 0;
uint8_t *ptr_end = ptr + compressed_len;
while (ptr < ptr_end) {
uint32_t wrptr_snapshot = wrptr;
uint32_t offset = (uint32_t)read_uint16_le(ptr);
ptr += 2;
uint8_t len = *ptr++;
uint8_t byte = *ptr++;
printf("Ptr %d, Offset %d, Len %d, Byte 0x%02x\n", wrptr_snapshot, offset, len, byte);
if (len != 0) {
printf("Src: %d\n", wrptr_snapshot - offset);
}
for (int i = 0; i < len; i++) {
// len could be zero, in this case the loop doesn't execute
uint8_t rd = decomp_buffer[wrptr_snapshot - offset + i];
decomp_buffer[wrptr++] = rd;
}
decomp_buffer[wrptr++] = byte;
}
printf("Decompressed size: %d bytes\n", wrptr);
fp = fopen(wbf, "wb");
fwrite(decomp_buffer, wrptr, 1, fp);
fclose(fp);
printf("Done\n");
return 0;
}

View file

@ -0,0 +1,6 @@
all: wbf_wvfm_dump
wbf_wvfm_dump: main.c
gcc -O1 -g main.c -o wbf_wvfm_dump
clean:
rm -f wbf_wvfm_dump

View file

@ -0,0 +1,568 @@
/*******************************************************************************
* Eink waveform firmware dumper
* Based on https://github.com/fread-ink/inkwave and Linux kernel
*
* 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 2004-2013 Freescale Semiconductor, Inc.
* Copyright 2005-2017 Amazon Technologies, Inc.
* Copyright 2018, 2021 Marc Juul
* Copyright (C) 2022 Samuel Holland <samuel@sholland.org>
* Copyright 2024 Wenting Zhang
******************************************************************************/
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <assert.h>
#include <inttypes.h>
#include <ctype.h>
struct waveform_data_header {
uint32_t checksum:32; // 0
uint32_t filesize:32; // 4
uint32_t serial:32; // 8 serial number
uint32_t run_type:8; // 12
uint32_t fpl_platform:8; // 13
uint32_t fpl_lot:16; // 14
uint32_t mode_version_or_adhesive_run_num:8; // 16
uint32_t waveform_version:8; // 17
uint32_t waveform_subversion:8; // 18
uint32_t waveform_type:8; // 19
uint32_t fpl_size:8; // 20 (aka panel_size)
uint32_t mfg_code:8; // 21 (aka amepd_part_number)
uint32_t waveform_tuning_bias_or_rev:8; // 22
uint32_t fpl_rate:8; // 23 (aka frame_rate)
uint32_t unknown0:8;
uint32_t vcom_shifted:8;
uint32_t unknown1:16;
uint32_t xwia:24; // address of extra waveform information
uint32_t cs1:8; // checksum 1
uint32_t wmta:24;
uint32_t fvsn:8;
uint32_t luts:8;
uint32_t mc:8; // mode count (length of mode table - 1)
uint32_t trc:8; // temperature range count (length of temperature table - 1)
uint32_t advanced_wfm_flags:8;
uint32_t eb:8;
uint32_t sb:8;
uint32_t reserved0_1:8;
uint32_t reserved0_2:8;
uint32_t reserved0_3:8;
uint32_t reserved0_4:8;
uint32_t reserved0_5:8;
uint32_t cs2:8; // checksum 2
}__attribute__((packed));
#define HEADER_SIZE sizeof(struct waveform_data_header)
#define MODE_MAX 10
// Mode version to mode string
struct mode_name_lut_t {
uint8_t versions[2];
char *mode_strings[MODE_MAX];
};
// Partially derived from linux kernel drm_epd_helper.c
// All GL series (GL GLR GLD) are marked as GL as the underlying wavetable seems to be identical
// Thus it's impossible to identify the correct ordering
// -R/ -D ghosting reduction is outside of the scope of this tool
const struct mode_name_lut_t mode_name_lut[] = {
{
// Example: ED050SC3
.versions = {0x01},
.mode_strings = {"INIT", "DU", "GL8", "GC8"}
},
{
// Example: ED097TC1
.versions = {0x03},
.mode_strings = {"INIT", "DU", "GL16", "GL16", "A2"}
},
{
// Example: ET073TC1
.versions = {0x09},
.mode_strings = {"INIT", "DU", "GC16", "A2"}
},
{
// Untested
.versions = {0x12},
.mode_strings = {"INIT", "DU", "NULL", "GC16", "A2", "GL16", "GL16", "DU4"}
},
{
// Example: ES133UT1
.versions = {0x15},
.mode_strings = {"INIT", "DU", "GC16", "GL16", "A2", "DU4", "GC4"}
},
{
// Untested
.versions = {0x16},
.mode_strings = {"INIT", "DU", "GC16", "GL16", "GL16", "GC16", "A2"}
},
{
// Example: ES108FC1
.versions = {0x18, 0x20},
.mode_strings = {"INIT", "DU", "GC16", "GL16", "GL16", "GL16", "A2"}
},
{
// Example: ES103TC1
.versions = {0x19, 0x43},
.mode_strings = {"INIT", "DU", "GC16", "GL16", "GL16", "GL16", "A2", "DU4"}
},
{
// Untested
.versions = {0x23},
.mode_strings = {"INIT", "DU", "GC16", "GL16", "A2", "DU4"}
},
{
// Untested
.versions = {0x54},
.mode_strings = {"INIT", "DU", "GC16", "GL16", "GL16", "A2"}
},
{
// Example: ES120MC1
.versions = {0x48},
.mode_strings = {"INIT", "DU", "GC16", "GL16", "GL16", "NULL", "A2"}
}
};
#define MODE_NAME_LUTS (sizeof(mode_name_lut) / sizeof(*mode_name_lut))
#define MAX_TABLE_LENGTH (1024*1024)
static uint32_t read_pointer(uint8_t *ptr) {
uint32_t addr = ((uint32_t)ptr[2] << 16) | ((uint32_t)ptr[1] << 8) | ((uint32_t)ptr[0]);
uint8_t checksum = ptr[0] + ptr[1] + ptr[2];
if (checksum != ptr[3]) {
printf("Pointer checksum mismatch\n");
}
return addr;
}
static uint8_t read_uint4(uint8_t* src, size_t addr) {
uint8_t val = src[addr >> 1];
if (addr & 1)
val = (val >> 4) & 0xf;
else
val = val & 0xf;
return val;
}
void compute_crc_table(unsigned int* crc_table) {
unsigned c;
int n, k;
for (n = 0; n < 256; n++) {
c = (unsigned) n;
for (k = 0; k < 8; k++) {
if (c & 1) {
c = 0xedb88320L ^ (c >> 1);
}
else {
c = c >> 1;
}
}
crc_table[n] = c;
}
}
unsigned int update_crc(unsigned int* crc_table, unsigned crc,
unsigned char *buf, int len) {
char b;
unsigned c = crc ^ 0xffffffff;
int i;
for(i=0; i < len; i++) {
if(!buf) {
b = 0;
} else {
b = buf[i];
}
c = crc_table[(c ^ b) & 0xff] ^ (c >> 8);
}
return c ^ 0xffffffff;
}
unsigned crc32(unsigned char *buf, int len) {
static unsigned int crc_table[256];
compute_crc_table(crc_table);
return update_crc(crc_table, 0, buf, len);
}
void dump_phases(FILE* fp, uint8_t* buffer, int bpp, int phases, int phase_per_byte) {
int states;
int i, j, k, x, y;
states = (bpp == 4) ? 16 : 32;
uint8_t luts[phases][states][states];
uint8_t *ptr = buffer;
for (i = 0; i < phases; i ++) {
for (x = 0; x < states; x++) {
for (y = 0; y < states; y+=phase_per_byte) {
uint8_t val = *ptr++;
if (phase_per_byte == 4) {
luts[i][y+0][x] = (val >> 0) & 0x3;
luts[i][y+1][x] = (val >> 2) & 0x3;
luts[i][y+2][x] = (val >> 4) & 0x3;
luts[i][y+3][x] = (val >> 6) & 0x3;
}
else if (phase_per_byte == 2) {
luts[i][y+0][x] = (val >> 0) & 0xf;
luts[i][y+1][x] = (val >> 4) & 0xf;
}
}
}
}
for (i = 0; i < states; i++) {
for (j = 0; j < states; j++) {
fprintf(fp, "%d,%d,", i, j);
for (k = 0; k < phases; k++) {
fprintf(fp, "%d,", luts[k][i][j]);
}
fprintf(fp, "\n");
}
}
}
int main(int argc, char **argv) {
fprintf(stderr, "Eink wbf waveform dumper\n");
if (argc != 3) {
fprintf(stderr, "Usage: wbf_wvfm_dump input_file output_prefix\n");
fprintf(stderr, "input_file: MXC EPDC firmware file, in .wbf format\n");
fprintf(stderr, "output_prefix: Prefix for output file name, without extension\n");
fprintf(stderr, "Example: wbf_wvfm_dump E060SCM.wbf e060scm\n");
return 1;
}
char *fw = argv[1];
char *prefix = argv[2];
FILE * fp;
size_t file_size;
char * file_buffer;
size_t result;
fp = fopen(fw, "rb");
assert(fp);
// obtain file size:
fseek(fp, 0, SEEK_END);
file_size = ftell(fp);
fseek(fp, 0, SEEK_SET);
// allocate memory to contain the whole file:
file_buffer = (char*)malloc(sizeof(char) * file_size);
assert(file_buffer);
// copy the file into the buffer:
assert(fread(file_buffer, file_size, 1, fp) == 1);
fclose(fp);
/* the whole file is now loaded in the memory buffer. */
struct waveform_data_header *header;
header = (struct waveform_data_header *)file_buffer;
printf("File size: %zu bytes.\n", file_size);
printf("Reported size: %d bytes.\n", header->filesize);
printf("Serial number: %d\n", header->serial);
printf("Run type: 0x%x\n", header->run_type);
printf("Mfg code: 0x%x\n", header->mfg_code);
printf("FPL platform: 0x%x\n", header->fpl_platform);
printf("FPL lot: 0x%x\n", header->fpl_lot);
printf("FPL size: 0x%x\n", header->fpl_size);
printf("FPL rate: 0x%x\n", header->fpl_rate);
printf("Mode version: 0x%x\n", header->mode_version_or_adhesive_run_num);
printf("Waveform version: %d\n", header->waveform_version);
printf("Waveform sub-version: %d\n", header->waveform_subversion);
printf("Waveform type: %d\n", header->waveform_type);
printf("Number of modes: %d\n", header->mc + 1);
printf("Number of temperature ranges: %d\n", header->trc + 1);
int bpp = ((header->luts & 0xC) == 0x4) ? 5: 4;
printf("BPP: %d (LUTS = 0x%02x)\n", bpp, header->luts);
bool acep = false;
if (header->luts == 0x15) {
printf("Looks like you've supplied a waveform for ACeP screens\n");
printf("Expect the checksum for the header to fail.\n");
bpp = 5;
acep = true;
}
// Compare checksum
static unsigned int crc_table[256];
compute_crc_table(crc_table);
unsigned int crc;
if (header->filesize != 0) {
crc = update_crc(crc_table, 0, NULL, 4);
crc = update_crc(crc_table, crc, (unsigned char *)file_buffer + 4, header->filesize-4);
printf("Checksum: 0x%08x\n", crc);
if (crc == header->checksum) {
printf("Checksum match.\n");
} else {
printf("Checksum mismatch! Expected: 0x%08x\n", header->checksum);
}
}
else {
printf("File size reported to be 0 in the header. Checksum check skipped.\n");
}
// Try to find mode description
char **mode_string = NULL;
uint8_t mode_version = header->mode_version_or_adhesive_run_num;
for (int i = 0; i < MODE_NAME_LUTS; i++) {
if ((mode_name_lut[i].versions[0] == mode_version) ||
(mode_name_lut[i].versions[1] == mode_version)) {
mode_string = (char **)(mode_name_lut[i].mode_strings);
}
}
if (mode_string) {
printf("Known mode version, mode names available.\n");
}
else {
printf("Unknown mode version, mode names won't be available.\n");
}
// Right following the header is the temperature range table
uint8_t checksum = 0;
int i, j;
int trt_entries; // temperature range table
uint8_t *temp_range_bounds;
trt_entries = header->trc + 1;
printf("Temperatures count: %d\n", trt_entries);
temp_range_bounds = (uint8_t*)malloc(trt_entries + 2);
memcpy(temp_range_bounds, file_buffer + HEADER_SIZE, trt_entries + 2);
for (i = 0; i < trt_entries; i++) {
printf("Temperature %d = %d°C\n", i, temp_range_bounds[i]);
checksum += temp_range_bounds[i];
}
printf("End bound: %d°C\n", temp_range_bounds[trt_entries]);
checksum += temp_range_bounds[trt_entries];
if (checksum != temp_range_bounds[trt_entries + 1]) {
printf("Temperature table checksum mismatch\n");
}
char xwia_buffer[128];
uint8_t xwia_len = 0;
if (header->xwia != 0) {
printf("Extra waveform information present:\n");
uint8_t *ptr = (uint8_t *)file_buffer + header->xwia;
xwia_len = *ptr++;
checksum = xwia_len;
j = 0;
for (i = 0; i < xwia_len; i++) {
char c = *ptr++;
if (isprint(c)) {
xwia_buffer[j++] = c;
}
else {
xwia_buffer[j++] = '\\';
xwia_buffer[j++] = 'x';
xwia_buffer[j++] = (c / 16) + '0';
xwia_buffer[j++] = (c % 16) + '0';
}
checksum += c;
}
xwia_buffer[j++] = '\0';
printf("%s", xwia_buffer);
printf("\n");
if (checksum != *ptr++) {
printf("XWIA checksum mismatch\n");
}
}
int wv_data_offs; // offset for waveform data
int waveform_buffer_size; // size for waveform data
int mode_count = header->mc + 1;
wv_data_offs = HEADER_SIZE + trt_entries + 2 + 1 + xwia_len + 1;
// This should yield the same result
if ((header->xwia) && (wv_data_offs != header->xwia + 1 + xwia_len + 1))
printf("Warning: data offset calculation mismatch\n");
printf("Waveform data offset: %d\n", wv_data_offs);
uint8_t *ptr = (uint8_t *)file_buffer + wv_data_offs;
uint32_t* wv_modes = malloc(sizeof(uint32_t) * mode_count);
uint32_t addr;
// get modes addr
for (i = 0; i < mode_count; i++) {
addr = read_pointer(ptr);
printf("wave #%d addr: %u\n", i, addr);
wv_modes[i] = addr;
ptr += 4;
}
// get modes temp addr
// Table offsets, ordered as first shown in the wbf, may not use up all space
uint32_t *table_offsets = malloc(sizeof(uint32_t) * mode_count * trt_entries + 1);
int *frame_counts = malloc(sizeof(uint32_t) * mode_count * trt_entries);
int tables = 0;
// Table index for each mode x temp
int *wv_modes_temps = malloc(sizeof(int) * mode_count * trt_entries);
for (i = 0; i < mode_count; i++) {
ptr = (uint8_t *)file_buffer + wv_modes[i];
for (j = 0; j < trt_entries; j++) {
addr = read_pointer(ptr);
// Find the table ID correspond to the address
// Ideally this should be an hash table, but given the extremely small
// problem size here, linear search is more than good enough.
int id = -1;
for (int k = 0; k < tables; k++)
if (table_offsets[k] == addr) {
id = k;
break;
}
if (id == -1) {
// New table
id = tables; // Assign ID
tables++;
table_offsets[id] = addr;
}
wv_modes_temps[i * trt_entries + j] = id;
printf("wave #%d, temp #%d: wavetable %d\n", i, j, id);
ptr += 4;
}
}
char* fn = malloc(strlen(prefix) + 14);
static uint8_t derle_buffer[MAX_TABLE_LENGTH];
for (i = 0; i < tables; i++) {
printf("Parsing table %d, addr %d (0x%06x)\n", i, table_offsets[i], table_offsets[i]);
ptr = (uint8_t *)file_buffer + table_offsets[i];
// Parse table
bool rle_mode = true;
uint32_t idx = 0;
uint8_t checksum = 0;
while(1) {
uint8_t chr = *ptr++;
checksum += chr;
if (chr == 0xfc) {
// Toggle RLE mode
rle_mode = !rle_mode;
}
else if (chr == 0xff) {
// End of block
break;
}
else if (!rle_mode) {
derle_buffer[idx++] = chr;
}
else {
uint8_t len = *ptr++;
checksum += len;
for (j = 0; j < (int)len + 1; j++) {
derle_buffer[idx++] = chr;
}
}
}
printf("Total %d bytes. Checksum: 0x%02x\n", idx, checksum);
if (*ptr++ != checksum) {
printf("Checksum mismatch!\n");
}
int phase_per_byte = acep ? 2 : 4;
int transitions = ((bpp == 4) ? (16 * 16) : (32 * 32));
int phases = idx * phase_per_byte / transitions;
frame_counts[i] = phases;
// Dump phases
sprintf(fn, "%s_TB%d.csv", prefix, i);
fp = fopen(fn, "w");
assert(fp);
size_t index = i * trt_entries + j;
dump_phases(fp, derle_buffer, bpp, phases, phase_per_byte);
fclose(fp);
}
sprintf(fn, "%s_desc.iwf", prefix);
fp = fopen(fn, "w");
assert(fp);
fprintf(fp, "[WAVEFORM]\n");
fprintf(fp, "VERSION = 2.0\n");
fprintf(fp, "PREFIX = %s\n", prefix);
fprintf(fp, "NAME = %s\n", xwia_buffer);
fprintf(fp, "BPP = %d\n", bpp);
fprintf(fp, "MODES = %d\n", mode_count);
fprintf(fp, "TEMPS = %d\n", trt_entries);
fprintf(fp, "TABLES = %d\n", tables);
fprintf(fp, "\n");
for (int i = 0; i < trt_entries; i++) {
fprintf(fp, "T%dRANGE = %d\n", i, temp_range_bounds[i]);
}
fprintf(fp, "TUPBOUND = %d\n", temp_range_bounds[trt_entries]);
fprintf(fp, "\n");
for (int i = 0; i < tables; i++) {
fprintf(fp, "TB%dFC = %d\n", i, frame_counts[i]);
}
fprintf(fp, "\n");
for (int i = 0; i < mode_count; i++) {
fprintf(fp, "[MODE%d]\n", i);
if (mode_string) {
fprintf(fp, "NAME = %s\n", mode_string[i]);
}
for (int j = 0; j < trt_entries; j++) {
fprintf(fp, "T%dTABLE = %d\n", j, wv_modes_temps[i * trt_entries + j]);
}
fprintf(fp, "\n");
}
fclose(fp);
printf("All done!\n");
free(fn);
free(temp_range_bounds);
free(table_offsets);
free(frame_counts);
free(wv_modes);
free(wv_modes_temps);
free(file_buffer);
return 0;
}