summaryrefslogtreecommitdiff
path: root/zephyr/drivers/cros_cbi/cros_cbi_fw_config.c
blob: 2eacbcf6d42323ac021d16fdc434eabf3a9ef724 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
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;
}