Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
298 changes: 260 additions & 38 deletions components/squeezelite/external/dac_external.c
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/*
/*
* Squeezelite for esp32
*
* (c) Sebastien 2019
Expand All @@ -8,7 +8,7 @@
* https://opensource.org/licenses/MIT
*
*/

#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <driver/i2s.h>
Expand Down Expand Up @@ -39,31 +39,31 @@ static const struct {
char *controlset;
} codecs[] = {
{ "es8388", true,
"{\"init\":[ \
{\"reg\":8,\"val\":0}, {\"reg\":2,\"val\":243}, {\"reg\":43,\"val\":128}, {\"reg\":0,\"val\":5}, \
"{\"init\":[ \
{\"reg\":8,\"val\":0}, {\"reg\":2,\"val\":243}, {\"reg\":43,\"val\":128}, {\"reg\":0,\"val\":5}, \
{\"reg\":1,\"val\":64}, {\"reg\":4,\"val\":60},"
#if BYTES_PER_FRAME == 8
"{\"reg\":23,\"val\":32},"
#else
#else
"{\"reg\":23,\"val\":24},"
#endif
"{\"reg\":24,\"val\":2}, \
"{\"reg\":24,\"val\":2}, \
{\"reg\":26,\"val\":0}, {\"reg\":27,\"val\":0}, {\"reg\":25,\"val\":50}, {\"reg\":38,\"val\":0}, \
{\"reg\":39,\"val\":184}, {\"reg\":42,\"val\":184}, {\"reg\":46,\"val\":30}, {\"reg\":47,\"val\":30}, \
{\"reg\":48,\"val\":30}, {\"reg\":49,\"val\":30}, {\"reg\":2,\"val\":170}]}" },
{ NULL, false, NULL }
{ NULL, false, NULL }
};

/****************************************************************************************
* init
*/
static bool init(char *config, int i2c_port_num, i2s_config_t *i2s_config, bool *mck) {
char *p;
static bool init(char *config, int i2c_port_num, i2s_config_t *i2s_config, bool *mck) {
char *p;

i2c_addr = adac_init(config, i2c_port_num);

ESP_LOGI(TAG, "DAC on I2C @%d", i2c_addr);

p = config_alloc_get_str("dac_controlset", CONFIG_DAC_CONTROLSET, NULL);

if ((!p || !*p) && (p = strcasestr(config, "model")) != NULL) {
Expand All @@ -72,22 +72,22 @@ static bool init(char *config, int i2c_port_num, i2s_config_t *i2s_config, bool
sscanf(p, "%*[^=]=%31[^,]", model);
for (i = 0; *model && ((p = codecs[i].controlset) != NULL) && strcasecmp(codecs[i].model, model); i++);
if (p) *mck = codecs[i].mclk;
}
}

i2c_json = cJSON_Parse(p);

if (!i2c_json) {
ESP_LOGW(TAG, "no i2c controlset found");
return true;
}
if (!i2c_json_execute("init")) {
}

if (!i2c_json_execute("init")) {
ESP_LOGE(TAG, "could not intialize DAC");
return false;
}
}

return true;
}
}

/****************************************************************************************
* power
Expand All @@ -103,7 +103,7 @@ static void power(adac_power_e mode) {
static void speaker(bool active) {
if (active) i2c_json_execute("speakeron");
else i2c_json_execute("speakeroff");
}
}

/****************************************************************************************
* headset
Expand All @@ -114,24 +114,53 @@ static void headset(bool active) {
}

/****************************************************************************************
*
*
*/
bool i2c_json_execute(char *set) {
cJSON *json_set = cJSON_GetObjectItemCaseSensitive(i2c_json, set);
cJSON *item;

if (!json_set) return true;

typedef enum {
I2C_JSON_ARRAY_INVALID = 0,
I2C_JSON_ARRAY_OBJECTS,
I2C_JSON_ARRAY_FLAT
} i2c_json_array_mode_t;

static i2c_json_array_mode_t i2c_json_array_mode(cJSON *array) {
if (!cJSON_IsArray(array)) return I2C_JSON_ARRAY_INVALID;

bool all_objects = true;
bool none_objects = true;

for (cJSON *item = array->child; item; item = item->next) {
if (cJSON_IsObject(item)) {
none_objects = false;
} else {
all_objects = false;
}

if (!all_objects && !none_objects) {
return I2C_JSON_ARRAY_INVALID;
}
}

return all_objects ? I2C_JSON_ARRAY_OBJECTS :
none_objects ? I2C_JSON_ARRAY_FLAT :
I2C_JSON_ARRAY_INVALID;
}


static bool i2c_json_execute_objects(cJSON *json_set) {
if (!cJSON_IsArray(json_set)) return false;

cJSON *item;
cJSON_ArrayForEach(item, json_set) {
cJSON *action;

// is this a delay
if ((action = cJSON_GetObjectItemCaseSensitive(item, "delay")) != NULL) {
vTaskDelay(pdMS_TO_TICKS(action->valueint));
ESP_LOGI(TAG, "DAC waiting %d ms", action->valueint);
continue;
}

// is this a gpio toggle
if ((action = cJSON_GetObjectItemCaseSensitive(item, "gpio")) != NULL) {
cJSON *level = cJSON_GetObjectItemCaseSensitive(item, "level");
Expand All @@ -141,24 +170,24 @@ bool i2c_json_execute(char *set) {
gpio_set_level_x(action->valueint, level->valueint);
continue;
}

action= cJSON_GetObjectItemCaseSensitive(item, "reg");
cJSON *val = cJSON_GetObjectItemCaseSensitive(item, "val");

// this is gpio register setting or crap
if (cJSON_IsArray(val)) {
cJSON *value;
cJSON *value;
uint8_t *data = malloc(cJSON_GetArraySize(val));
int count = 0;

if (!data) continue;

cJSON_ArrayForEach(value, val) {
data[count++] = value->valueint;
data[count++] = value->valueint;
}

adac_write(i2c_addr, action->valueint, data, count);
free(data);
free(data);
} else {
cJSON *mode = cJSON_GetObjectItemCaseSensitive(item, "mode");

Expand All @@ -177,6 +206,199 @@ bool i2c_json_execute(char *set) {
}
}
}

return true;
}
}


typedef enum {
CMD_NONE = 0,
CMD_DELAY,
CMD_GPIO,
CMD_AND,
CMD_OR
} i2c_cmd_type_t;

static i2c_cmd_type_t i2c_resolve_command(const char *s) {
if (!strcasecmp(s, "d")) return CMD_DELAY;
if (!strcasecmp(s, "g")) return CMD_GPIO;
if (!strcmp(s, "&")) return CMD_AND;
if (!strcmp(s, "|")) return CMD_OR;
return CMD_NONE;
}

/**
* @brief Execute a flattened I2C instruction sequence using compressed JSON format.
*
* This function accepts an array containing a mixture of register operations,
* delays, bitwise modifications, and burst writes in a compressed format.
*
* Supported syntax elements (in a single flat array):
*
* - Register write:
* [reg, val]
* Writes a single byte value to a register.
*
* - Burst write:
* [reg, [val1, val2, ..., valN]]
* Writes multiple bytes to a register in one burst. The second item must be a JSON array.
*
* - Delay:
* ["d", duration_ms]
* Pauses execution for the specified number of milliseconds.
*
* - Bitwise operations:
* ["&", reg, val] — Read-modify-write: AND value into register
* ["|", reg, val] — Read-modify-write: OR value into register
*
* - GPIO config:
* ["g", gpio, state]
* Sets gpio to state
*
* Notes:
* - All entries are parsed in order from the start of the array.
* - Invalid formatting or unexpected types will cause the function to return `false`.
* - Only numeric register addresses and values are allowed.
*
* Example input:
* [
* 0, 128,
* "d", 50,
* "|", 2, 240,
* "&", 2, 15,
* "g", 22, 0,
* 25, [4, 5, 6, 7],
* 1, 0
* ]
*
* This will:
* - Write 128 to reg 0
* - Delay 50ms
* - OR 240 into reg 2
* - AND 15 into reg 2
* - Set GPIO 22 to level 0
* - Burst write [4,5,6,7] to reg 25
* - Write 0 to reg 1
*/

static bool i2c_json_execute_array(cJSON *array) {
if (!cJSON_IsArray(array)) return false;

cJSON *cursor = array->child;
while (cursor) {
if (cJSON_IsString(cursor)) {
i2c_cmd_type_t cmd = i2c_resolve_command(cursor->valuestring);
switch (cmd) {
case CMD_DELAY: {
cursor = cursor->next;
if (!cursor || !cJSON_IsNumber(cursor)) return false;
vTaskDelay(pdMS_TO_TICKS(cursor->valueint));
ESP_LOGI(TAG, "DAC waiting %d ms", cursor->valueint);
cursor = cursor->next;
break;
}

case CMD_GPIO: {
cursor = cursor->next;
if (!cursor || !cJSON_IsNumber(cursor)) return false;
int gpio = cursor->valueint;

cursor = cursor->next;
if (!cursor || !cJSON_IsNumber(cursor)) return false;
int level = cursor->valueint;

if (gpio < GPIO_NUM_MAX) {
gpio_pad_select_gpio(gpio);
gpio_set_direction_x(gpio, GPIO_MODE_OUTPUT);
gpio_set_level_x(gpio, level);
}

ESP_LOGI(TAG, "Set GPIO %d to %d", gpio, level);
cursor = cursor->next;
break;
}

case CMD_AND:
case CMD_OR: {
cursor = cursor->next;
if (!cursor || !cJSON_IsNumber(cursor)) return false;
int reg = cursor->valueint;

cursor = cursor->next;
if (!cursor || !cJSON_IsNumber(cursor)) return false;
int val = cursor->valueint;

uint8_t data = adac_read_byte(i2c_addr, reg);
data = (cmd == CMD_AND) ? (data & val) : (data | val);
adac_write_byte(i2c_addr, reg, data);
cursor = cursor->next;
break;
}

default:
return false;
}
continue;
}


// ── Burst write: reg, [val1, val2, ...] ──
if (cJSON_IsNumber(cursor)) {
int reg = cursor->valueint;
cursor = cursor->next;
if (!cursor) return false;

if (cJSON_IsArray(cursor)) {
int len = cJSON_GetArraySize(cursor);
if (len == 0) return false;
uint8_t *data = malloc(len);
if (!data) return false;

cJSON *val_item = cursor->child;
for (int i = 0; i < len; i++) {
if (!val_item || !cJSON_IsNumber(val_item)) {
free(data);
return false;
}
data[i] = val_item->valueint;
val_item = val_item->next;
}

adac_write(i2c_addr, reg, data, len);
free(data);
cursor = cursor->next;
continue;
}

// ── Normal register write: reg, val ──
if (!cJSON_IsNumber(cursor)) return false;
int val = cursor->valueint;
adac_write_byte(i2c_addr, reg, val);
cursor = cursor->next;
continue;
}
return false; // Unknown pattern
}
return true;
}


bool i2c_json_execute(char *set) {
cJSON *json_set = cJSON_GetObjectItemCaseSensitive(i2c_json, set);
if (!json_set) return true;

if (!cJSON_IsArray(json_set)) {
ESP_LOGE(TAG, "i2c_json_execute must be called on a JSON array");
return false;
}

i2c_json_array_mode_t mode = i2c_json_array_mode(json_set);
if (mode == I2C_JSON_ARRAY_FLAT) {
return i2c_json_execute_array(json_set);
} else if (mode == I2C_JSON_ARRAY_OBJECTS) {
return i2c_json_execute_objects(json_set);
} else {
ESP_LOGE(TAG, "Mixed array elements (objects + scalars) not supported");
return false;
}
}