1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * NVMEM layout bus handling |
4 | * |
5 | * Copyright (C) 2023 Bootlin |
6 | * Author: Miquel Raynal <miquel.raynal@bootlin.com |
7 | */ |
8 | |
9 | #include <linux/device.h> |
10 | #include <linux/dma-mapping.h> |
11 | #include <linux/nvmem-consumer.h> |
12 | #include <linux/nvmem-provider.h> |
13 | #include <linux/of.h> |
14 | #include <linux/of_device.h> |
15 | #include <linux/of_irq.h> |
16 | |
17 | #include "internals.h" |
18 | |
19 | #define to_nvmem_layout_driver(drv) \ |
20 | (container_of((drv), struct nvmem_layout_driver, driver)) |
21 | #define to_nvmem_layout_device(_dev) \ |
22 | container_of((_dev), struct nvmem_layout, dev) |
23 | |
24 | static int nvmem_layout_bus_match(struct device *dev, struct device_driver *drv) |
25 | { |
26 | return of_driver_match_device(dev, drv); |
27 | } |
28 | |
29 | static int nvmem_layout_bus_probe(struct device *dev) |
30 | { |
31 | struct nvmem_layout_driver *drv = to_nvmem_layout_driver(dev->driver); |
32 | struct nvmem_layout *layout = to_nvmem_layout_device(dev); |
33 | |
34 | if (!drv->probe || !drv->remove) |
35 | return -EINVAL; |
36 | |
37 | return drv->probe(layout); |
38 | } |
39 | |
40 | static void nvmem_layout_bus_remove(struct device *dev) |
41 | { |
42 | struct nvmem_layout_driver *drv = to_nvmem_layout_driver(dev->driver); |
43 | struct nvmem_layout *layout = to_nvmem_layout_device(dev); |
44 | |
45 | return drv->remove(layout); |
46 | } |
47 | |
48 | static const struct bus_type nvmem_layout_bus_type = { |
49 | .name = "nvmem-layout" , |
50 | .match = nvmem_layout_bus_match, |
51 | .probe = nvmem_layout_bus_probe, |
52 | .remove = nvmem_layout_bus_remove, |
53 | }; |
54 | |
55 | int nvmem_layout_driver_register(struct nvmem_layout_driver *drv) |
56 | { |
57 | drv->driver.bus = &nvmem_layout_bus_type; |
58 | |
59 | return driver_register(drv: &drv->driver); |
60 | } |
61 | EXPORT_SYMBOL_GPL(nvmem_layout_driver_register); |
62 | |
63 | void nvmem_layout_driver_unregister(struct nvmem_layout_driver *drv) |
64 | { |
65 | driver_unregister(drv: &drv->driver); |
66 | } |
67 | EXPORT_SYMBOL_GPL(nvmem_layout_driver_unregister); |
68 | |
69 | static void nvmem_layout_release_device(struct device *dev) |
70 | { |
71 | struct nvmem_layout *layout = to_nvmem_layout_device(dev); |
72 | |
73 | of_node_put(node: layout->dev.of_node); |
74 | kfree(objp: layout); |
75 | } |
76 | |
77 | static int nvmem_layout_create_device(struct nvmem_device *nvmem, |
78 | struct device_node *np) |
79 | { |
80 | struct nvmem_layout *layout; |
81 | struct device *dev; |
82 | int ret; |
83 | |
84 | layout = kzalloc(size: sizeof(*layout), GFP_KERNEL); |
85 | if (!layout) |
86 | return -ENOMEM; |
87 | |
88 | /* Create a bidirectional link */ |
89 | layout->nvmem = nvmem; |
90 | nvmem->layout = layout; |
91 | |
92 | /* Device model registration */ |
93 | dev = &layout->dev; |
94 | device_initialize(dev); |
95 | dev->parent = &nvmem->dev; |
96 | dev->bus = &nvmem_layout_bus_type; |
97 | dev->release = nvmem_layout_release_device; |
98 | dev->coherent_dma_mask = DMA_BIT_MASK(32); |
99 | dev->dma_mask = &dev->coherent_dma_mask; |
100 | device_set_node(dev, of_fwnode_handle(of_node_get(np))); |
101 | of_device_make_bus_id(dev); |
102 | of_msi_configure(dev, np: dev->of_node); |
103 | |
104 | ret = device_add(dev); |
105 | if (ret) { |
106 | put_device(dev); |
107 | return ret; |
108 | } |
109 | |
110 | return 0; |
111 | } |
112 | |
113 | static const struct of_device_id of_nvmem_layout_skip_table[] = { |
114 | { .compatible = "fixed-layout" , }, |
115 | {} |
116 | }; |
117 | |
118 | static int nvmem_layout_bus_populate(struct nvmem_device *nvmem, |
119 | struct device_node *layout_dn) |
120 | { |
121 | int ret; |
122 | |
123 | /* Make sure it has a compatible property */ |
124 | if (!of_get_property(node: layout_dn, name: "compatible" , NULL)) { |
125 | pr_debug("%s() - skipping %pOF, no compatible prop\n" , |
126 | __func__, layout_dn); |
127 | return 0; |
128 | } |
129 | |
130 | /* Fixed layouts are parsed manually somewhere else for now */ |
131 | if (of_match_node(matches: of_nvmem_layout_skip_table, node: layout_dn)) { |
132 | pr_debug("%s() - skipping %pOF node\n" , __func__, layout_dn); |
133 | return 0; |
134 | } |
135 | |
136 | if (of_node_check_flag(n: layout_dn, OF_POPULATED_BUS)) { |
137 | pr_debug("%s() - skipping %pOF, already populated\n" , |
138 | __func__, layout_dn); |
139 | |
140 | return 0; |
141 | } |
142 | |
143 | /* NVMEM layout buses expect only a single device representing the layout */ |
144 | ret = nvmem_layout_create_device(nvmem, np: layout_dn); |
145 | if (ret) |
146 | return ret; |
147 | |
148 | of_node_set_flag(n: layout_dn, OF_POPULATED_BUS); |
149 | |
150 | return 0; |
151 | } |
152 | |
153 | struct device_node *of_nvmem_layout_get_container(struct nvmem_device *nvmem) |
154 | { |
155 | return of_get_child_by_name(node: nvmem->dev.of_node, name: "nvmem-layout" ); |
156 | } |
157 | EXPORT_SYMBOL_GPL(of_nvmem_layout_get_container); |
158 | |
159 | /* |
160 | * Returns the number of devices populated, 0 if the operation was not relevant |
161 | * for this nvmem device, an error code otherwise. |
162 | */ |
163 | int nvmem_populate_layout(struct nvmem_device *nvmem) |
164 | { |
165 | struct device_node *layout_dn; |
166 | int ret; |
167 | |
168 | layout_dn = of_nvmem_layout_get_container(nvmem); |
169 | if (!layout_dn) |
170 | return 0; |
171 | |
172 | /* Populate the layout device */ |
173 | device_links_supplier_sync_state_pause(); |
174 | ret = nvmem_layout_bus_populate(nvmem, layout_dn); |
175 | device_links_supplier_sync_state_resume(); |
176 | |
177 | of_node_put(node: layout_dn); |
178 | return ret; |
179 | } |
180 | |
181 | void nvmem_destroy_layout(struct nvmem_device *nvmem) |
182 | { |
183 | struct device *dev; |
184 | |
185 | if (!nvmem->layout) |
186 | return; |
187 | |
188 | dev = &nvmem->layout->dev; |
189 | of_node_clear_flag(n: dev->of_node, OF_POPULATED_BUS); |
190 | device_unregister(dev); |
191 | } |
192 | |
193 | int nvmem_layout_bus_register(void) |
194 | { |
195 | return bus_register(bus: &nvmem_layout_bus_type); |
196 | } |
197 | |
198 | void nvmem_layout_bus_unregister(void) |
199 | { |
200 | bus_unregister(bus: &nvmem_layout_bus_type); |
201 | } |
202 | |