1 // SPDX-License-Identifier: GPL-2.0+
2 /*
3  * Copyright (C) 2024 Marek Behún <kabel@kernel.org>
4  */
5 
6 #include <asm/unaligned.h>
7 #include <ctype.h>
8 #include <linux/compiler.h>
9 #include <linux/kernel.h>
10 #include <eeprom_field.h>
11 #include <eeprom_layout.h>
12 #include <u-boot/crc.h>
13 
14 #define _DEF_FIELD(_n, _s, _t) \
15 	{ _n, _s, NULL, eeprom_field_print_ ## _t, eeprom_field_update_ ## _t }
16 
eeprom_field_print_ramsz(const struct eeprom_field * field)17 static void eeprom_field_print_ramsz(const struct eeprom_field *field)
18 {
19 	printf(PRINT_FIELD_SEGMENT, field->name);
20 	printf("%u\n", get_unaligned_le32(field->buf));
21 }
22 
eeprom_field_update_ramsz(struct eeprom_field * field,char * value)23 static int eeprom_field_update_ramsz(struct eeprom_field *field, char *value)
24 {
25 	u32 sz;
26 
27 	if (value[0] == '1' || value[0] == '2' || value[0] == '4')
28 		sz = value[0] - '0';
29 	else
30 		return -1;
31 
32 	if (value[1] != '\0')
33 		return -1;
34 
35 	put_unaligned_le32(sz, field->buf);
36 
37 	return 0;
38 }
39 
eeprom_field_print_region(const struct eeprom_field * field)40 static void eeprom_field_print_region(const struct eeprom_field *field)
41 {
42 	eeprom_field_print_ascii(field);
43 }
44 
eeprom_field_update_region(struct eeprom_field * field,char * value)45 static int eeprom_field_update_region(struct eeprom_field *field, char *value)
46 {
47 	if (strlen(value) != 2) {
48 		printf("%s: has to be 2 characters\n", field->name);
49 		return -1;
50 	}
51 
52 	memcpy(field->buf, value, 2);
53 	memset(&field->buf[2], '\0', 2);
54 
55 	return 0;
56 }
57 
eeprom_field_print_ddr_speed(const struct eeprom_field * field)58 static void eeprom_field_print_ddr_speed(const struct eeprom_field *field)
59 {
60 	printf(PRINT_FIELD_SEGMENT, field->name);
61 
62 	if (field->buf[0] == '\0' || field->buf[0] == 0xff)
63 		puts("(empty, defaults to 1600K)\n");
64 	else
65 		printf("%.5s\n", field->buf);
66 }
67 
68 bool omnia_valid_ddr_speed(const char *name);
69 void omnia_print_ddr_speeds(void);
70 
eeprom_field_update_ddr_speed(struct eeprom_field * field,char * value)71 static int eeprom_field_update_ddr_speed(struct eeprom_field *field,
72 					 char *value)
73 {
74 	if (value[0] == '\0') {
75 		/* setting default value */
76 		memset(field->buf, 0xff, field->size);
77 
78 		return 0;
79 	}
80 
81 	if (!omnia_valid_ddr_speed(value)) {
82 		printf("%s: invalid setting, supported values are:\n  ",
83 		       field->name);
84 		omnia_print_ddr_speeds();
85 
86 		return -1;
87 	}
88 
89 	strncpy(field->buf, value, field->size);
90 
91 	return 0;
92 }
93 
eeprom_field_print_bool(const struct eeprom_field * field)94 static void eeprom_field_print_bool(const struct eeprom_field *field)
95 {
96 	unsigned char val = field->buf[0];
97 
98 	printf(PRINT_FIELD_SEGMENT, field->name);
99 
100 	if (val == 0xff)
101 		puts("(empty, defaults to 0)\n");
102 	else
103 		printf("%u\n", val);
104 }
105 
eeprom_field_update_bool(struct eeprom_field * field,char * value)106 static int eeprom_field_update_bool(struct eeprom_field *field, char *value)
107 {
108 	unsigned char *val = &field->buf[0];
109 
110 	if (value[0] == '\0') {
111 		/* setting default value */
112 		*val = 0xff;
113 
114 		return 0;
115 	}
116 
117 	if (value[1] != '\0')
118 		return -1;
119 
120 	if (value[0] == '1' || value[0] == '0')
121 		*val = value[0] - '0';
122 	else
123 		return -1;
124 
125 	return 0;
126 }
127 
128 static struct eeprom_field omnia_layout[] = {
129 	_DEF_FIELD("Magic constant", 4, bin),
130 	_DEF_FIELD("RAM size in GB", 4, ramsz),
131 	_DEF_FIELD("Wi-Fi Region", 4, region),
132 	_DEF_FIELD("CRC32 checksum", 4, bin),
133 	_DEF_FIELD("DDR speed", 5, ddr_speed),
134 	_DEF_FIELD("Use old DDR training", 1, bool),
135 	_DEF_FIELD("Extended reserved fields", 38, reserved),
136 	_DEF_FIELD("Extended CRC32 checksum", 4, bin),
137 };
138 
139 static struct eeprom_field *crc_field = &omnia_layout[3];
140 static struct eeprom_field *ext_crc_field =
141 	&omnia_layout[ARRAY_SIZE(omnia_layout) - 1];
142 
omnia_update_field(struct eeprom_layout * layout,char * field_name,char * new_data)143 static int omnia_update_field(struct eeprom_layout *layout, char *field_name,
144 			      char *new_data)
145 {
146 	struct eeprom_field *field;
147 	int err;
148 
149 	if (!new_data)
150 		return 0;
151 
152 	if (!field_name)
153 		return -1;
154 
155 	field = eeprom_layout_find_field(layout, field_name, true);
156 	if (!field)
157 		return -1;
158 
159 	err = field->update(field, new_data);
160 	if (err) {
161 		printf("Invalid data for field %s\n", field_name);
162 		return err;
163 	}
164 
165 	if (field < crc_field) {
166 		u32 crc = crc32(0, layout->data, 12);
167 		put_unaligned_le32(crc, crc_field->buf);
168 	}
169 
170 	if (field < ext_crc_field) {
171 		u32 crc = crc32(0, layout->data, 60);
172 		put_unaligned_le32(crc, ext_crc_field->buf);
173 	}
174 
175 	return 0;
176 }
177 
eeprom_layout_assign(struct eeprom_layout * layout,int)178 void eeprom_layout_assign(struct eeprom_layout *layout, int)
179 {
180 	layout->fields = omnia_layout;
181 	layout->num_of_fields = ARRAY_SIZE(omnia_layout);
182 	layout->update = omnia_update_field;
183 	layout->data_size = 64;
184 }
185 
eeprom_layout_detect(unsigned char *)186 int eeprom_layout_detect(unsigned char *)
187 {
188 	/* Turris Omnia has only one version of EEPROM layout */
189 	return 0;
190 }
191