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>
diff --git a/util/sconfig/main.c b/util/sconfig/main.c
index 91407bc..111bb50 100644
--- a/util/sconfig/main.c
+++ b/util/sconfig/main.c
@@ -398,42 +398,77 @@
 	}
 }
 
-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 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 @@
 	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 @@
 		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 @@
 	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: ",