summaryrefslogtreecommitdiff
path: root/zephyr/drivers/cros_cbi/cros_cbi_fw_config.c
diff options
context:
space:
mode:
Diffstat (limited to 'zephyr/drivers/cros_cbi/cros_cbi_fw_config.c')
-rw-r--r--zephyr/drivers/cros_cbi/cros_cbi_fw_config.c189
1 files changed, 189 insertions, 0 deletions
diff --git a/zephyr/drivers/cros_cbi/cros_cbi_fw_config.c b/zephyr/drivers/cros_cbi/cros_cbi_fw_config.c
new file mode 100644
index 0000000000..2eacbcf6d4
--- /dev/null
+++ b/zephyr/drivers/cros_cbi/cros_cbi_fw_config.c
@@ -0,0 +1,189 @@
+/* Copyright 2022 The Chromium OS Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include <drivers/cros_cbi.h>
+#include <logging/log.h>
+
+#include "cros_board_info.h"
+#include "cros_cbi_ssfc.h"
+#include "cros_cbi_common.h"
+
+LOG_MODULE_REGISTER(cros_cbi_fw_config, LOG_LEVEL_ERR);
+
+/*
+ * Validation macros.
+ * These are moved out of header files to avoid exposing them outside
+ * this file scope.
+ */
+
+/*
+ * Statically count the number of bits set in a 32 bit constant expression.
+ */
+#define BIT_SET(v, b) ((v >> b) & 1)
+#define BIT_COUNT(v) \
+ (BIT_SET(v, 31) + BIT_SET(v, 30) + BIT_SET(v, 29) + BIT_SET(v, 28) + \
+ BIT_SET(v, 27) + BIT_SET(v, 26) + BIT_SET(v, 25) + BIT_SET(v, 24) + \
+ BIT_SET(v, 23) + BIT_SET(v, 22) + BIT_SET(v, 21) + BIT_SET(v, 20) + \
+ BIT_SET(v, 19) + BIT_SET(v, 18) + BIT_SET(v, 17) + BIT_SET(v, 16) + \
+ BIT_SET(v, 15) + BIT_SET(v, 14) + BIT_SET(v, 13) + BIT_SET(v, 12) + \
+ BIT_SET(v, 11) + BIT_SET(v, 10) + BIT_SET(v, 9) + BIT_SET(v, 8) + \
+ BIT_SET(v, 7) + BIT_SET(v, 6) + BIT_SET(v, 5) + BIT_SET(v, 4) + \
+ BIT_SET(v, 3) + BIT_SET(v, 2) + BIT_SET(v, 1) + BIT_SET(v, 0))
+
+/*
+ * Shorthand macros to access properties on the field node.
+ */
+#define FW_START(id) DT_PROP(id, start)
+#define FW_SIZE(id) DT_PROP(id, size)
+
+/*
+ * Calculate the mask of the field from the size.
+ */
+#define FW_MASK(id) ((1 << FW_SIZE(id)) - 1)
+
+/*
+ * Calculate the mask and shift it to the correct start bit.
+ */
+#define FW_SHIFT_MASK(id) (FW_MASK(id) << FW_START(id))
+
+/*
+ * For a child "named-cbi-fw-config-value" node, retrieve the
+ * size of the parent field this value is associated with.
+ */
+#define FW_PARENT_SIZE(id) DT_PROP(DT_PARENT(id), size)
+
+/*
+ * Validation check to ensure total field sizes do not exceed 32 bits.
+ * The FOREACH loop is nested, one to iterate through all the
+ * fw_config nodes, and another for the child field nodes in each
+ * of the fw_config nodes.
+ */
+#define PLUS_FIELD_SIZE(inst) + DT_PROP(inst, size)
+#define FIELDS_ALL_SIZE(inst) \
+ DT_FOREACH_CHILD_STATUS_OKAY(inst, PLUS_FIELD_SIZE)
+
+/*
+ * Result is the sum of all the field sizes.
+ */
+#define TOTAL_FW_CONFIG_NODES_SIZE \
+ (0 DT_FOREACH_STATUS_OKAY(CBI_FW_CONFIG_COMPAT, FIELDS_ALL_SIZE))
+
+BUILD_ASSERT(TOTAL_FW_CONFIG_NODES_SIZE <= 32,
+ "CBI FW Config is bigger than 32 bits");
+
+/*
+ * Validation check to ensure there are no overlapping fields.
+ * OR together all the masks, count the bits, and compare against the
+ * total of the sizes. They should match.
+ */
+#define OR_FIELD_SHIFT_MASK(id) | FW_SHIFT_MASK(id)
+#define FIELDS_ALL_BITS_SET(inst) \
+ DT_FOREACH_CHILD_STATUS_OKAY(inst, OR_FIELD_SHIFT_MASK)
+
+#define TOTAL_BITS_SET \
+ (0 DT_FOREACH_STATUS_OKAY(CBI_FW_CONFIG_COMPAT, \
+ FIELDS_ALL_BITS_SET))
+
+BUILD_ASSERT(BIT_COUNT(TOTAL_BITS_SET) == TOTAL_FW_CONFIG_NODES_SIZE,
+ "CBI FW Config has overlapping fields");
+
+/*
+ * Validation for each assigned field values.
+ * The value must fit within the parent's defined size.
+ */
+#define FW_VALUE_BUILD_ASSERT(inst) \
+ BUILD_ASSERT(DT_PROP(inst, value) < \
+ (1 << FW_PARENT_SIZE(inst)), \
+ "CBI FW Config value too big");
+
+DT_FOREACH_STATUS_OKAY(CBI_FW_CONFIG_VALUE_COMPAT, FW_VALUE_BUILD_ASSERT)
+
+/*
+ * Define union bit fields based on the device tree entries. Example:
+ * cbi-fw-config {
+ * compatible = "named-cbi-fw-config";
+ *
+ * fan {
+ * enum-name = "FW_CONFIG_FAN";
+ * start = <0>;
+ * size = <1>;
+ * fan_present {
+ * enum-name = "FW_FAN_PRESENT"
+ * compatible = "named-cbi-fw-config-value";
+ * value = <1>;
+ * };
+ * };
+ * };
+ *
+ * Creates
+ * switch (field_id) {
+ * case FW_CONFIG_FAN:
+ * return (value >> 0) & 1;
+ * ...
+ * }
+ *
+ */
+
+/*
+ * The per-field case statement.
+ * Extract the field value using the start and size.
+ */
+#define FW_FIELD_CASE(id, cached, value) \
+ case CBI_FW_CONFIG_ENUM(id): \
+ *value = (cached >> FW_START(id)) & FW_MASK(id); \
+ break;
+
+/*
+ * Create a case for every child of this "named-cbi-fw-config" node.
+ */
+#define FW_FIELD_NODES(inst, cached, value) \
+ DT_FOREACH_CHILD_STATUS_OKAY_VARGS(inst, FW_FIELD_CASE, cached, value)
+
+void cros_cbi_fw_config_init(const struct device *dev)
+{
+ struct cros_cbi_data *data = (struct cros_cbi_data *)(dev->data);
+
+ if (cbi_get_fw_config(&data->cached_fw_config) != EC_SUCCESS)
+ /*
+ * Missing fw config will defaults to all zeros.
+ */
+ data->cached_fw_config = 0;
+
+ LOG_INF("Read CBI FW Config : 0x%08X\n", data->cached_fw_config);
+}
+
+static int cros_cbi_fw_config_get_field(
+ uint32_t cached_fw_config,
+ enum cbi_fw_config_field_id field_id,
+ uint32_t *value)
+{
+ switch (field_id) {
+ /*
+ * Iterate through all the the "named-cbi-fw-config" nodes,
+ * and create cases for all of their child nodes.
+ */
+ DT_FOREACH_STATUS_OKAY_VARGS(CBI_FW_CONFIG_COMPAT,
+ FW_FIELD_NODES,
+ cached_fw_config,
+ value)
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+int cros_cbi_ec_get_fw_config(const struct device *dev,
+ enum cbi_fw_config_field_id field_id,
+ uint32_t *value)
+{
+ struct cros_cbi_data *data = (struct cros_cbi_data *)(dev->data);
+ int rc;
+
+ rc = cros_cbi_fw_config_get_field(data->cached_fw_config,
+ field_id, value);
+ if (rc)
+ LOG_ERR("CBI FW Config field not found: %d\n", field_id);
+ return rc;
+}