Commit | Line | Data |
---|---|---|
fc29fd82 MR |
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 | ||
8ec0faf2 | 48 | static const struct bus_type nvmem_layout_bus_type = { |
fc29fd82 MR |
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->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->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(layout->dev.of_node); | |
74 | kfree(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(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, 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(layout_dn, "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(of_nvmem_layout_skip_table, layout_dn)) { | |
132 | pr_debug("%s() - skipping %pOF node\n", __func__, layout_dn); | |
133 | return 0; | |
134 | } | |
135 | ||
136 | if (of_node_check_flag(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, layout_dn); | |
145 | if (ret) | |
146 | return ret; | |
147 | ||
148 | of_node_set_flag(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(nvmem->dev.of_node, "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(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(dev->of_node, OF_POPULATED_BUS); | |
190 | device_unregister(dev); | |
191 | } | |
192 | ||
193 | int nvmem_layout_bus_register(void) | |
194 | { | |
195 | return bus_register(&nvmem_layout_bus_type); | |
196 | } | |
197 | ||
198 | void nvmem_layout_bus_unregister(void) | |
199 | { | |
200 | bus_unregister(&nvmem_layout_bus_type); | |
201 | } |