util/sconfig: Add support for discontiguous FW_CONFIG fields

Sooner or later, some board was going to need extra FW_CONFIG bits for
a field that was already in production, so this patch adds support for
adding extra (unused) bits to a field.

The extra are appended via a syntax like:
`field FIELD_NAME START0 END0 | START1 END1 | START2 END2 ...`
and the suffixed bits are all treated as if they are contiguous when
defining option values.

BUG=b:185190978
TEST=Modified volteer fw_config to the following:
field AUDIO 8 10 | 29 29 | 31 31
        option NONE 0
	option MAX98357_ALC5682I_I2S 1
        option MAX98373_ALC5682I_I2S 2
	option MAX98373_ALC5682_SNDW 3
        option MAX98373_ALC5682I_I2S_UP4 4
        option MAX98360_ALC5682I_I2S 5
        option RT1011_ALC5682I_I2S 6
        option AUDIO_FOO 7
	option AUDIO_BAR 8
        option AUDIO_QUUX 9
        option AUDIO_BLAH1 10
        option AUDIO_BLAH2 15
        option AUDIO_BLAH3 16
        option AUDIO_BLAH4 31
end

which yielded (in static_fw_config.h):
 FW_CONFIG_FIELD_AUDIO_MASK 0xa0000700
 FW_CONFIG_FIELD_AUDIO_OPTION_NONE_VALUE 0x0
 FW_CONFIG_FIELD_AUDIO_OPTION_MAX98357_ALC5682I_I2S_VALUE 0x100
 FW_CONFIG_FIELD_AUDIO_OPTION_MAX98373_ALC5682I_I2S_VALUE 0x200
 FW_CONFIG_FIELD_AUDIO_OPTION_MAX98373_ALC5682_SNDW_VALUE 0x300
 FW_CONFIG_FIELD_AUDIO_OPTION_MAX98373_ALC5682I_I2S_UP4_VALUE 0x400
 FW_CONFIG_FIELD_AUDIO_OPTION_MAX98360_ALC5682I_I2S_VALUE 0x500
 FW_CONFIG_FIELD_AUDIO_OPTION_RT1011_ALC5682I_I2S_VALUE 0x600
 FW_CONFIG_FIELD_AUDIO_OPTION_AUDIO_FOO_VALUE 0x700
 FW_CONFIG_FIELD_AUDIO_OPTION_AUDIO_BAR_VALUE 0x20000000
 FW_CONFIG_FIELD_AUDIO_OPTION_AUDIO_QUUX_VALUE 0x20000100
 FW_CONFIG_FIELD_AUDIO_OPTION_AUDIO_BLAH1_VALUE 0x20000200
 FW_CONFIG_FIELD_AUDIO_OPTION_AUDIO_BLAH2_VALUE 0x20000700
 FW_CONFIG_FIELD_AUDIO_OPTION_AUDIO_BLAH3_VALUE 0x80000000
 FW_CONFIG_FIELD_AUDIO_OPTION_AUDIO_BLAH4_VALUE 0xa0000700

Change-Id: I5ed76706347ee9642198efc77139abdc3af1b8a6
Signed-off-by: Tim Wawrzynczak <twawrzynczak@chromium.org>
Reviewed-on: https://review.coreboot.org/c/coreboot/+/52747
Reviewed-by: Duncan Laurie <duncan@iceblink.org>
Tested-by: build bot (Jenkins) <no-reply@coreboot.org>
This commit is contained in:
Tim Wawrzynczak
2021-04-28 14:03:07 -06:00
committed by Patrick Georgi
parent 54c4ecb9f2
commit 13e240c602
8 changed files with 537 additions and 361 deletions

View File

@ -398,42 +398,77 @@ static void append_fw_config_field(struct fw_config_field *add)
}
}
struct fw_config_field *new_fw_config_field(const char *name,
unsigned int start_bit, unsigned int end_bit)
void append_fw_config_bits(struct fw_config_field_bits **bits,
unsigned int start_bit, unsigned int end_bit)
{
struct fw_config_field_bits *new_bits = S_ALLOC(sizeof(*new_bits));
new_bits->start_bit = start_bit;
new_bits->end_bit = end_bit;
new_bits->next = NULL;
if (*bits == NULL) {
*bits = new_bits;
return;
}
struct fw_config_field_bits *tmp = *bits;
while (tmp->next)
tmp = tmp->next;
tmp->next = new_bits;
}
int fw_config_masks_overlap(struct fw_config_field *existing,
unsigned int start_bit, unsigned int end_bit)
{
struct fw_config_field_bits *bits = existing->bits;
while (bits) {
if (start_bit <= bits->end_bit && end_bit >= bits->start_bit) {
printf("ERROR: fw_config field [%u-%u] overlaps %s[%u-%u]\n",
start_bit, end_bit,
existing->name, bits->start_bit, bits->end_bit);
return 1;
}
bits = bits->next;
}
return 0;
}
struct fw_config_field *new_fw_config_field(const char *name, struct fw_config_field_bits *bits)
{
struct fw_config_field *field = find_fw_config_field(name);
/* Check that field is within 64 bits. */
if (start_bit > end_bit || end_bit > 63) {
printf("ERROR: fw_config field %s has invalid range %u-%u\n", name,
start_bit, end_bit);
exit(1);
}
struct fw_config_field_bits *tmp;
/* Don't allow re-defining a field, only adding new fields. */
if (field) {
printf("ERROR: fw_config field %s[%u-%u] already exists with range %u-%u\n",
name, start_bit, end_bit, field->start_bit, field->end_bit);
printf("ERROR: fw_config field %s already exists\n", name);
exit(1);
}
/* Check for overlap with an existing field. */
field = fw_config_fields;
while (field) {
/* Check if the mask overlaps. */
if (start_bit <= field->end_bit && end_bit >= field->start_bit) {
printf("ERROR: fw_config field %s[%u-%u] overlaps %s[%u-%u]\n",
name, start_bit, end_bit,
field->name, field->start_bit, field->end_bit);
/* Check that each field is within 64 bits. */
tmp = bits;
while (tmp) {
if (tmp->start_bit > tmp->end_bit || tmp->end_bit > 63) {
printf("ERROR: fw_config field %s has invalid range %u-%u\n", field->name,
tmp->start_bit, tmp->end_bit);
exit(1);
}
field = field->next;
/* Check for overlap with an existing field. */
struct fw_config_field *existing = fw_config_fields;
while (existing) {
if (fw_config_masks_overlap(existing, tmp->start_bit, tmp->end_bit))
exit(1);
existing = existing->next;
}
tmp = tmp->next;
}
field = S_ALLOC(sizeof(*field));
field->name = name;
field->start_bit = start_bit;
field->end_bit = end_bit;
field->bits = bits;
append_fw_config_field(field);
return field;
@ -453,13 +488,25 @@ static void append_fw_config_option_to_field(struct fw_config_field *field,
}
}
static uint64_t calc_max_field_value(const struct fw_config_field *field)
{
unsigned int bit_count = 0;
const struct fw_config_field_bits *bits = field->bits;
while (bits) {
bit_count += 1 + bits->end_bit - bits->start_bit;
bits = bits->next;
};
return (1ull << bit_count) - 1ull;
}
void add_fw_config_option(struct fw_config_field *field, const char *name, uint64_t value)
{
struct fw_config_option *option;
uint64_t field_max_value;
/* Check that option value fits within field mask. */
field_max_value = (1ull << (1ull + field->end_bit - field->start_bit)) - 1ull;
uint64_t field_max_value = calc_max_field_value(field);
if (value > field_max_value) {
printf("ERROR: fw_config option %s:%s value %" PRIx64 " larger than field max %"
PRIx64 "\n",
@ -525,6 +572,46 @@ void add_fw_config_probe(struct bus *bus, const char *field, const char *option)
append_fw_config_probe_to_dev(bus->dev, probe);
}
static uint64_t compute_fw_config_mask(const struct fw_config_field_bits *bits)
{
uint64_t mask = 0;
while (bits) {
/* Compute mask from start and end bit. */
uint64_t tmp = ((1ull << (1ull + bits->end_bit - bits->start_bit)) - 1ull);
tmp <<= bits->start_bit;
mask |= tmp;
bits = bits->next;
}
return mask;
}
static unsigned int bits_width(const struct fw_config_field_bits *bits)
{
return 1 + bits->end_bit - bits->start_bit;
}
static uint64_t calc_option_value(const struct fw_config_field *field,
const struct fw_config_option *option)
{
uint64_t value = 0;
uint64_t original = option->value;
struct fw_config_field_bits *bits = field->bits;
while (bits) {
const unsigned int width = bits_width(bits);
const uint64_t orig_mask = (1ull << width) - 1ull;
const uint64_t orig = (original & orig_mask);
value |= (orig << bits->start_bit);
original >>= width;
bits = bits->next;
}
return value;
}
static void emit_fw_config(FILE *fil)
{
struct fw_config_field *field = fw_config_fields;
@ -539,20 +626,16 @@ static void emit_fw_config(FILE *fil)
fprintf(fil, "#define FW_CONFIG_FIELD_%s_NAME \"%s\"\n",
field->name, field->name);
/* Compute mask from start and end bit. */
mask = ((1ull << (1ull + field->end_bit - field->start_bit)) - 1ull);
mask <<= field->start_bit;
mask = compute_fw_config_mask(field->bits);
fprintf(fil, "#define FW_CONFIG_FIELD_%s_MASK 0x%" PRIx64 "\n",
field->name, mask);
while (option) {
const uint64_t value = calc_option_value(field, option);
fprintf(fil, "#define FW_CONFIG_FIELD_%s_OPTION_%s_NAME \"%s\"\n",
field->name, option->name, option->name);
fprintf(fil, "#define FW_CONFIG_FIELD_%s_OPTION_%s_VALUE 0x%"
PRIx64 "\n", field->name, option->name,
option->value << field->start_bit);
PRIx64 "\n", field->name, option->name, value);
option = option->next;
}
@ -1879,6 +1962,7 @@ int main(int argc, char **argv)
if (override_devtree)
parse_override_devicetree(override_devtree, &override_root_dev);
FILE *autogen = fopen(outputc, "w");
if (!autogen) {
fprintf(stderr, "Could not open file '%s' for writing: ",