kernel: backport NVMEM patches queued for the v6.4
[openwrt/openwrt.git] / target / linux / generic / backport-5.15 / 811-v6.4-0002-nvmem-core-introduce-NVMEM-layouts.patch
1 From 266570f496b90dea8fda893c2cf7c28d63ae2bd9 Mon Sep 17 00:00:00 2001
2 From: Michael Walle <michael@walle.cc>
3 Date: Tue, 4 Apr 2023 18:21:21 +0100
4 Subject: [PATCH] nvmem: core: introduce NVMEM layouts
5
6 NVMEM layouts are used to generate NVMEM cells during runtime. Think of
7 an EEPROM with a well-defined conent. For now, the content can be
8 described by a device tree or a board file. But this only works if the
9 offsets and lengths are static and don't change. One could also argue
10 that putting the layout of the EEPROM in the device tree is the wrong
11 place. Instead, the device tree should just have a specific compatible
12 string.
13
14 Right now there are two use cases:
15 (1) The NVMEM cell needs special processing. E.g. if it only specifies
16 a base MAC address offset and you need to add an offset, or it
17 needs to parse a MAC from ASCII format or some proprietary format.
18 (Post processing of cells is added in a later commit).
19 (2) u-boot environment parsing. The cells don't have a particular
20 offset but it needs parsing the content to determine the offsets
21 and length.
22
23 Co-developed-by: Miquel Raynal <miquel.raynal@bootlin.com>
24 Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
25 Signed-off-by: Michael Walle <michael@walle.cc>
26 Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
27 Link: https://lore.kernel.org/r/20230404172148.82422-14-srinivas.kandagatla@linaro.org
28 Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
29 ---
30 Documentation/driver-api/nvmem.rst | 15 ++++
31 drivers/nvmem/Kconfig | 4 +
32 drivers/nvmem/Makefile | 1 +
33 drivers/nvmem/core.c | 120 +++++++++++++++++++++++++++++
34 drivers/nvmem/layouts/Kconfig | 5 ++
35 drivers/nvmem/layouts/Makefile | 4 +
36 include/linux/nvmem-consumer.h | 7 ++
37 include/linux/nvmem-provider.h | 51 ++++++++++++
38 8 files changed, 207 insertions(+)
39 create mode 100644 drivers/nvmem/layouts/Kconfig
40 create mode 100644 drivers/nvmem/layouts/Makefile
41
42 --- a/Documentation/driver-api/nvmem.rst
43 +++ b/Documentation/driver-api/nvmem.rst
44 @@ -189,3 +189,18 @@ ex::
45 =====================
46
47 See Documentation/devicetree/bindings/nvmem/nvmem.txt
48 +
49 +8. NVMEM layouts
50 +================
51 +
52 +NVMEM layouts are yet another mechanism to create cells. With the device
53 +tree binding it is possible to specify simple cells by using an offset
54 +and a length. Sometimes, the cells doesn't have a static offset, but
55 +the content is still well defined, e.g. tag-length-values. In this case,
56 +the NVMEM device content has to be first parsed and the cells need to
57 +be added accordingly. Layouts let you read the content of the NVMEM device
58 +and let you add cells dynamically.
59 +
60 +Another use case for layouts is the post processing of cells. With layouts,
61 +it is possible to associate a custom post processing hook to a cell. It
62 +even possible to add this hook to cells not created by the layout itself.
63 --- a/drivers/nvmem/Kconfig
64 +++ b/drivers/nvmem/Kconfig
65 @@ -21,6 +21,10 @@ config NVMEM_SYSFS
66 This interface is mostly used by userspace applications to
67 read/write directly into nvmem.
68
69 +# Layouts
70 +
71 +source "drivers/nvmem/layouts/Kconfig"
72 +
73 # Devices
74
75 config NVMEM_APPLE_EFUSES
76 --- a/drivers/nvmem/Makefile
77 +++ b/drivers/nvmem/Makefile
78 @@ -5,6 +5,7 @@
79
80 obj-$(CONFIG_NVMEM) += nvmem_core.o
81 nvmem_core-y := core.o
82 +obj-y += layouts/
83
84 # Devices
85 obj-$(CONFIG_NVMEM_APPLE_EFUSES) += nvmem-apple-efuses.o
86 --- a/drivers/nvmem/core.c
87 +++ b/drivers/nvmem/core.c
88 @@ -40,6 +40,7 @@ struct nvmem_device {
89 nvmem_reg_write_t reg_write;
90 nvmem_cell_post_process_t cell_post_process;
91 struct gpio_desc *wp_gpio;
92 + struct nvmem_layout *layout;
93 void *priv;
94 };
95
96 @@ -74,6 +75,9 @@ static LIST_HEAD(nvmem_lookup_list);
97
98 static BLOCKING_NOTIFIER_HEAD(nvmem_notifier);
99
100 +static DEFINE_SPINLOCK(nvmem_layout_lock);
101 +static LIST_HEAD(nvmem_layouts);
102 +
103 static int __nvmem_reg_read(struct nvmem_device *nvmem, unsigned int offset,
104 void *val, size_t bytes)
105 {
106 @@ -728,6 +732,101 @@ static int nvmem_add_cells_from_of(struc
107 return 0;
108 }
109
110 +int __nvmem_layout_register(struct nvmem_layout *layout, struct module *owner)
111 +{
112 + layout->owner = owner;
113 +
114 + spin_lock(&nvmem_layout_lock);
115 + list_add(&layout->node, &nvmem_layouts);
116 + spin_unlock(&nvmem_layout_lock);
117 +
118 + return 0;
119 +}
120 +EXPORT_SYMBOL_GPL(__nvmem_layout_register);
121 +
122 +void nvmem_layout_unregister(struct nvmem_layout *layout)
123 +{
124 + spin_lock(&nvmem_layout_lock);
125 + list_del(&layout->node);
126 + spin_unlock(&nvmem_layout_lock);
127 +}
128 +EXPORT_SYMBOL_GPL(nvmem_layout_unregister);
129 +
130 +static struct nvmem_layout *nvmem_layout_get(struct nvmem_device *nvmem)
131 +{
132 + struct device_node *layout_np, *np = nvmem->dev.of_node;
133 + struct nvmem_layout *l, *layout = NULL;
134 +
135 + layout_np = of_get_child_by_name(np, "nvmem-layout");
136 + if (!layout_np)
137 + return NULL;
138 +
139 + spin_lock(&nvmem_layout_lock);
140 +
141 + list_for_each_entry(l, &nvmem_layouts, node) {
142 + if (of_match_node(l->of_match_table, layout_np)) {
143 + if (try_module_get(l->owner))
144 + layout = l;
145 +
146 + break;
147 + }
148 + }
149 +
150 + spin_unlock(&nvmem_layout_lock);
151 + of_node_put(layout_np);
152 +
153 + return layout;
154 +}
155 +
156 +static void nvmem_layout_put(struct nvmem_layout *layout)
157 +{
158 + if (layout)
159 + module_put(layout->owner);
160 +}
161 +
162 +static int nvmem_add_cells_from_layout(struct nvmem_device *nvmem)
163 +{
164 + struct nvmem_layout *layout = nvmem->layout;
165 + int ret;
166 +
167 + if (layout && layout->add_cells) {
168 + ret = layout->add_cells(&nvmem->dev, nvmem, layout);
169 + if (ret)
170 + return ret;
171 + }
172 +
173 + return 0;
174 +}
175 +
176 +#if IS_ENABLED(CONFIG_OF)
177 +/**
178 + * of_nvmem_layout_get_container() - Get OF node to layout container.
179 + *
180 + * @nvmem: nvmem device.
181 + *
182 + * Return: a node pointer with refcount incremented or NULL if no
183 + * container exists. Use of_node_put() on it when done.
184 + */
185 +struct device_node *of_nvmem_layout_get_container(struct nvmem_device *nvmem)
186 +{
187 + return of_get_child_by_name(nvmem->dev.of_node, "nvmem-layout");
188 +}
189 +EXPORT_SYMBOL_GPL(of_nvmem_layout_get_container);
190 +#endif
191 +
192 +const void *nvmem_layout_get_match_data(struct nvmem_device *nvmem,
193 + struct nvmem_layout *layout)
194 +{
195 + struct device_node __maybe_unused *layout_np;
196 + const struct of_device_id *match;
197 +
198 + layout_np = of_nvmem_layout_get_container(nvmem);
199 + match = of_match_node(layout->of_match_table, layout_np);
200 +
201 + return match ? match->data : NULL;
202 +}
203 +EXPORT_SYMBOL_GPL(nvmem_layout_get_match_data);
204 +
205 /**
206 * nvmem_register() - Register a nvmem device for given nvmem_config.
207 * Also creates a binary entry in /sys/bus/nvmem/devices/dev-name/nvmem
208 @@ -834,6 +933,12 @@ struct nvmem_device *nvmem_register(cons
209 goto err_put_device;
210 }
211
212 + /*
213 + * If the driver supplied a layout by config->layout, the module
214 + * pointer will be NULL and nvmem_layout_put() will be a noop.
215 + */
216 + nvmem->layout = config->layout ?: nvmem_layout_get(nvmem);
217 +
218 if (config->cells) {
219 rval = nvmem_add_cells(nvmem, config->cells, config->ncells);
220 if (rval)
221 @@ -854,12 +959,17 @@ struct nvmem_device *nvmem_register(cons
222 if (rval)
223 goto err_remove_cells;
224
225 + rval = nvmem_add_cells_from_layout(nvmem);
226 + if (rval)
227 + goto err_remove_cells;
228 +
229 blocking_notifier_call_chain(&nvmem_notifier, NVMEM_ADD, nvmem);
230
231 return nvmem;
232
233 err_remove_cells:
234 nvmem_device_remove_all_cells(nvmem);
235 + nvmem_layout_put(nvmem->layout);
236 if (config->compat)
237 nvmem_sysfs_remove_compat(nvmem, config);
238 err_put_device:
239 @@ -881,6 +991,7 @@ static void nvmem_device_release(struct
240 device_remove_bin_file(nvmem->base_dev, &nvmem->eeprom);
241
242 nvmem_device_remove_all_cells(nvmem);
243 + nvmem_layout_put(nvmem->layout);
244 device_unregister(&nvmem->dev);
245 }
246
247 @@ -1246,6 +1357,15 @@ struct nvmem_cell *of_nvmem_cell_get(str
248 return ERR_PTR(-EINVAL);
249 }
250
251 + /* nvmem layouts produce cells within the nvmem-layout container */
252 + if (of_node_name_eq(nvmem_np, "nvmem-layout")) {
253 + nvmem_np = of_get_next_parent(nvmem_np);
254 + if (!nvmem_np) {
255 + of_node_put(cell_np);
256 + return ERR_PTR(-EINVAL);
257 + }
258 + }
259 +
260 nvmem = __nvmem_device_get(nvmem_np, device_match_of_node);
261 of_node_put(nvmem_np);
262 if (IS_ERR(nvmem)) {
263 --- /dev/null
264 +++ b/drivers/nvmem/layouts/Kconfig
265 @@ -0,0 +1,5 @@
266 +# SPDX-License-Identifier: GPL-2.0
267 +
268 +menu "Layout Types"
269 +
270 +endmenu
271 --- /dev/null
272 +++ b/drivers/nvmem/layouts/Makefile
273 @@ -0,0 +1,4 @@
274 +# SPDX-License-Identifier: GPL-2.0
275 +#
276 +# Makefile for nvmem layouts.
277 +#
278 --- a/include/linux/nvmem-consumer.h
279 +++ b/include/linux/nvmem-consumer.h
280 @@ -239,6 +239,7 @@ struct nvmem_cell *of_nvmem_cell_get(str
281 const char *id);
282 struct nvmem_device *of_nvmem_device_get(struct device_node *np,
283 const char *name);
284 +struct device_node *of_nvmem_layout_get_container(struct nvmem_device *nvmem);
285 #else
286 static inline struct nvmem_cell *of_nvmem_cell_get(struct device_node *np,
287 const char *id)
288 @@ -251,6 +252,12 @@ static inline struct nvmem_device *of_nv
289 {
290 return ERR_PTR(-EOPNOTSUPP);
291 }
292 +
293 +static inline struct device_node *
294 +of_nvmem_layout_get_container(struct nvmem_device *nvmem)
295 +{
296 + return ERR_PTR(-EOPNOTSUPP);
297 +}
298 #endif /* CONFIG_NVMEM && CONFIG_OF */
299
300 #endif /* ifndef _LINUX_NVMEM_CONSUMER_H */
301 --- a/include/linux/nvmem-provider.h
302 +++ b/include/linux/nvmem-provider.h
303 @@ -88,6 +88,7 @@ struct nvmem_cell_info {
304 * @stride: Minimum read/write access stride.
305 * @priv: User context passed to read/write callbacks.
306 * @ignore_wp: Write Protect pin is managed by the provider.
307 + * @layout: Fixed layout associated with this nvmem device.
308 *
309 * Note: A default "nvmem<id>" name will be assigned to the device if
310 * no name is specified in its configuration. In such case "<id>" is
311 @@ -109,6 +110,7 @@ struct nvmem_config {
312 bool read_only;
313 bool root_only;
314 bool ignore_wp;
315 + struct nvmem_layout *layout;
316 struct device_node *of_node;
317 bool no_of_node;
318 nvmem_reg_read_t reg_read;
319 @@ -142,6 +144,33 @@ struct nvmem_cell_table {
320 struct list_head node;
321 };
322
323 +/**
324 + * struct nvmem_layout - NVMEM layout definitions
325 + *
326 + * @name: Layout name.
327 + * @of_match_table: Open firmware match table.
328 + * @add_cells: Will be called if a nvmem device is found which
329 + * has this layout. The function will add layout
330 + * specific cells with nvmem_add_one_cell().
331 + * @owner: Pointer to struct module.
332 + * @node: List node.
333 + *
334 + * A nvmem device can hold a well defined structure which can just be
335 + * evaluated during runtime. For example a TLV list, or a list of "name=val"
336 + * pairs. A nvmem layout can parse the nvmem device and add appropriate
337 + * cells.
338 + */
339 +struct nvmem_layout {
340 + const char *name;
341 + const struct of_device_id *of_match_table;
342 + int (*add_cells)(struct device *dev, struct nvmem_device *nvmem,
343 + struct nvmem_layout *layout);
344 +
345 + /* private */
346 + struct module *owner;
347 + struct list_head node;
348 +};
349 +
350 #if IS_ENABLED(CONFIG_NVMEM)
351
352 struct nvmem_device *nvmem_register(const struct nvmem_config *cfg);
353 @@ -156,6 +185,14 @@ void nvmem_del_cell_table(struct nvmem_c
354 int nvmem_add_one_cell(struct nvmem_device *nvmem,
355 const struct nvmem_cell_info *info);
356
357 +int __nvmem_layout_register(struct nvmem_layout *layout, struct module *owner);
358 +#define nvmem_layout_register(layout) \
359 + __nvmem_layout_register(layout, THIS_MODULE)
360 +void nvmem_layout_unregister(struct nvmem_layout *layout);
361 +
362 +const void *nvmem_layout_get_match_data(struct nvmem_device *nvmem,
363 + struct nvmem_layout *layout);
364 +
365 #else
366
367 static inline struct nvmem_device *nvmem_register(const struct nvmem_config *c)
368 @@ -179,5 +216,19 @@ static inline int nvmem_add_one_cell(str
369 return -EOPNOTSUPP;
370 }
371
372 +static inline int nvmem_layout_register(struct nvmem_layout *layout)
373 +{
374 + return -EOPNOTSUPP;
375 +}
376 +
377 +static inline void nvmem_layout_unregister(struct nvmem_layout *layout) {}
378 +
379 +static inline const void *
380 +nvmem_layout_get_match_data(struct nvmem_device *nvmem,
381 + struct nvmem_layout *layout)
382 +{
383 + return NULL;
384 +}
385 +
386 #endif /* CONFIG_NVMEM */
387 #endif /* ifndef _LINUX_NVMEM_PROVIDER_H */