1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * Greybus Firmware Core Bundle Driver. |
4 | * |
5 | * Copyright 2016 Google Inc. |
6 | * Copyright 2016 Linaro Ltd. |
7 | */ |
8 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt |
9 | |
10 | #include <linux/firmware.h> |
11 | #include <linux/greybus.h> |
12 | #include "firmware.h" |
13 | #include "spilib.h" |
14 | |
15 | struct gb_fw_core { |
16 | struct gb_connection *download_connection; |
17 | struct gb_connection *mgmt_connection; |
18 | struct gb_connection *spi_connection; |
19 | struct gb_connection *cap_connection; |
20 | }; |
21 | |
22 | static struct spilib_ops *spilib_ops; |
23 | |
24 | struct gb_connection *to_fw_mgmt_connection(struct device *dev) |
25 | { |
26 | struct gb_fw_core *fw_core = dev_get_drvdata(dev); |
27 | |
28 | return fw_core->mgmt_connection; |
29 | } |
30 | |
31 | static int gb_fw_spi_connection_init(struct gb_connection *connection) |
32 | { |
33 | int ret; |
34 | |
35 | if (!connection) |
36 | return 0; |
37 | |
38 | ret = gb_connection_enable(connection); |
39 | if (ret) |
40 | return ret; |
41 | |
42 | ret = gb_spilib_master_init(connection, dev: &connection->bundle->dev, |
43 | ops: spilib_ops); |
44 | if (ret) { |
45 | gb_connection_disable(connection); |
46 | return ret; |
47 | } |
48 | |
49 | return 0; |
50 | } |
51 | |
52 | static void gb_fw_spi_connection_exit(struct gb_connection *connection) |
53 | { |
54 | if (!connection) |
55 | return; |
56 | |
57 | gb_spilib_master_exit(connection); |
58 | gb_connection_disable(connection); |
59 | } |
60 | |
61 | static int gb_fw_core_probe(struct gb_bundle *bundle, |
62 | const struct greybus_bundle_id *id) |
63 | { |
64 | struct greybus_descriptor_cport *cport_desc; |
65 | struct gb_connection *connection; |
66 | struct gb_fw_core *fw_core; |
67 | int ret, i; |
68 | u16 cport_id; |
69 | u8 protocol_id; |
70 | |
71 | fw_core = kzalloc(size: sizeof(*fw_core), GFP_KERNEL); |
72 | if (!fw_core) |
73 | return -ENOMEM; |
74 | |
75 | /* Parse CPorts and create connections */ |
76 | for (i = 0; i < bundle->num_cports; i++) { |
77 | cport_desc = &bundle->cport_desc[i]; |
78 | cport_id = le16_to_cpu(cport_desc->id); |
79 | protocol_id = cport_desc->protocol_id; |
80 | |
81 | switch (protocol_id) { |
82 | case GREYBUS_PROTOCOL_FW_MANAGEMENT: |
83 | /* Disallow multiple Firmware Management CPorts */ |
84 | if (fw_core->mgmt_connection) { |
85 | dev_err(&bundle->dev, |
86 | "multiple management CPorts found\n" ); |
87 | ret = -EINVAL; |
88 | goto err_destroy_connections; |
89 | } |
90 | |
91 | connection = gb_connection_create(bundle, cport_id, |
92 | handler: gb_fw_mgmt_request_handler); |
93 | if (IS_ERR(ptr: connection)) { |
94 | ret = PTR_ERR(ptr: connection); |
95 | dev_err(&bundle->dev, |
96 | "failed to create management connection (%d)\n" , |
97 | ret); |
98 | goto err_destroy_connections; |
99 | } |
100 | |
101 | fw_core->mgmt_connection = connection; |
102 | break; |
103 | case GREYBUS_PROTOCOL_FW_DOWNLOAD: |
104 | /* Disallow multiple Firmware Download CPorts */ |
105 | if (fw_core->download_connection) { |
106 | dev_err(&bundle->dev, |
107 | "multiple download CPorts found\n" ); |
108 | ret = -EINVAL; |
109 | goto err_destroy_connections; |
110 | } |
111 | |
112 | connection = gb_connection_create(bundle, cport_id, |
113 | handler: gb_fw_download_request_handler); |
114 | if (IS_ERR(ptr: connection)) { |
115 | dev_err(&bundle->dev, "failed to create download connection (%ld)\n" , |
116 | PTR_ERR(connection)); |
117 | } else { |
118 | fw_core->download_connection = connection; |
119 | } |
120 | |
121 | break; |
122 | case GREYBUS_PROTOCOL_SPI: |
123 | /* Disallow multiple SPI CPorts */ |
124 | if (fw_core->spi_connection) { |
125 | dev_err(&bundle->dev, |
126 | "multiple SPI CPorts found\n" ); |
127 | ret = -EINVAL; |
128 | goto err_destroy_connections; |
129 | } |
130 | |
131 | connection = gb_connection_create(bundle, cport_id, |
132 | NULL); |
133 | if (IS_ERR(ptr: connection)) { |
134 | dev_err(&bundle->dev, "failed to create SPI connection (%ld)\n" , |
135 | PTR_ERR(connection)); |
136 | } else { |
137 | fw_core->spi_connection = connection; |
138 | } |
139 | |
140 | break; |
141 | case GREYBUS_PROTOCOL_AUTHENTICATION: |
142 | /* Disallow multiple CAP CPorts */ |
143 | if (fw_core->cap_connection) { |
144 | dev_err(&bundle->dev, "multiple Authentication CPorts found\n" ); |
145 | ret = -EINVAL; |
146 | goto err_destroy_connections; |
147 | } |
148 | |
149 | connection = gb_connection_create(bundle, cport_id, |
150 | NULL); |
151 | if (IS_ERR(ptr: connection)) { |
152 | dev_err(&bundle->dev, "failed to create Authentication connection (%ld)\n" , |
153 | PTR_ERR(connection)); |
154 | } else { |
155 | fw_core->cap_connection = connection; |
156 | } |
157 | |
158 | break; |
159 | default: |
160 | dev_err(&bundle->dev, "invalid protocol id (0x%02x)\n" , |
161 | protocol_id); |
162 | ret = -EINVAL; |
163 | goto err_destroy_connections; |
164 | } |
165 | } |
166 | |
167 | /* Firmware Management connection is mandatory */ |
168 | if (!fw_core->mgmt_connection) { |
169 | dev_err(&bundle->dev, "missing management connection\n" ); |
170 | ret = -ENODEV; |
171 | goto err_destroy_connections; |
172 | } |
173 | |
174 | ret = gb_fw_download_connection_init(connection: fw_core->download_connection); |
175 | if (ret) { |
176 | /* We may still be able to work with the Interface */ |
177 | dev_err(&bundle->dev, "failed to initialize firmware download connection, disable it (%d)\n" , |
178 | ret); |
179 | gb_connection_destroy(connection: fw_core->download_connection); |
180 | fw_core->download_connection = NULL; |
181 | } |
182 | |
183 | ret = gb_fw_spi_connection_init(connection: fw_core->spi_connection); |
184 | if (ret) { |
185 | /* We may still be able to work with the Interface */ |
186 | dev_err(&bundle->dev, "failed to initialize SPI connection, disable it (%d)\n" , |
187 | ret); |
188 | gb_connection_destroy(connection: fw_core->spi_connection); |
189 | fw_core->spi_connection = NULL; |
190 | } |
191 | |
192 | ret = gb_cap_connection_init(connection: fw_core->cap_connection); |
193 | if (ret) { |
194 | /* We may still be able to work with the Interface */ |
195 | dev_err(&bundle->dev, "failed to initialize CAP connection, disable it (%d)\n" , |
196 | ret); |
197 | gb_connection_destroy(connection: fw_core->cap_connection); |
198 | fw_core->cap_connection = NULL; |
199 | } |
200 | |
201 | ret = gb_fw_mgmt_connection_init(connection: fw_core->mgmt_connection); |
202 | if (ret) { |
203 | /* We may still be able to work with the Interface */ |
204 | dev_err(&bundle->dev, "failed to initialize firmware management connection, disable it (%d)\n" , |
205 | ret); |
206 | goto err_exit_connections; |
207 | } |
208 | |
209 | greybus_set_drvdata(bundle, data: fw_core); |
210 | |
211 | /* FIXME: Remove this after S2 Loader gets runtime PM support */ |
212 | if (!(bundle->intf->quirks & GB_INTERFACE_QUIRK_NO_PM)) |
213 | gb_pm_runtime_put_autosuspend(bundle); |
214 | |
215 | return 0; |
216 | |
217 | err_exit_connections: |
218 | gb_cap_connection_exit(connection: fw_core->cap_connection); |
219 | gb_fw_spi_connection_exit(connection: fw_core->spi_connection); |
220 | gb_fw_download_connection_exit(connection: fw_core->download_connection); |
221 | err_destroy_connections: |
222 | gb_connection_destroy(connection: fw_core->mgmt_connection); |
223 | gb_connection_destroy(connection: fw_core->cap_connection); |
224 | gb_connection_destroy(connection: fw_core->spi_connection); |
225 | gb_connection_destroy(connection: fw_core->download_connection); |
226 | kfree(objp: fw_core); |
227 | |
228 | return ret; |
229 | } |
230 | |
231 | static void gb_fw_core_disconnect(struct gb_bundle *bundle) |
232 | { |
233 | struct gb_fw_core *fw_core = greybus_get_drvdata(bundle); |
234 | int ret; |
235 | |
236 | /* FIXME: Remove this after S2 Loader gets runtime PM support */ |
237 | if (!(bundle->intf->quirks & GB_INTERFACE_QUIRK_NO_PM)) { |
238 | ret = gb_pm_runtime_get_sync(bundle); |
239 | if (ret) |
240 | gb_pm_runtime_get_noresume(bundle); |
241 | } |
242 | |
243 | gb_fw_mgmt_connection_exit(connection: fw_core->mgmt_connection); |
244 | gb_cap_connection_exit(connection: fw_core->cap_connection); |
245 | gb_fw_spi_connection_exit(connection: fw_core->spi_connection); |
246 | gb_fw_download_connection_exit(connection: fw_core->download_connection); |
247 | |
248 | gb_connection_destroy(connection: fw_core->mgmt_connection); |
249 | gb_connection_destroy(connection: fw_core->cap_connection); |
250 | gb_connection_destroy(connection: fw_core->spi_connection); |
251 | gb_connection_destroy(connection: fw_core->download_connection); |
252 | |
253 | kfree(objp: fw_core); |
254 | } |
255 | |
256 | static const struct greybus_bundle_id gb_fw_core_id_table[] = { |
257 | { GREYBUS_DEVICE_CLASS(GREYBUS_CLASS_FW_MANAGEMENT) }, |
258 | { } |
259 | }; |
260 | |
261 | static struct greybus_driver gb_fw_core_driver = { |
262 | .name = "gb-firmware" , |
263 | .probe = gb_fw_core_probe, |
264 | .disconnect = gb_fw_core_disconnect, |
265 | .id_table = gb_fw_core_id_table, |
266 | }; |
267 | |
268 | static int fw_core_init(void) |
269 | { |
270 | int ret; |
271 | |
272 | ret = fw_mgmt_init(); |
273 | if (ret) { |
274 | pr_err("Failed to initialize fw-mgmt core (%d)\n" , ret); |
275 | return ret; |
276 | } |
277 | |
278 | ret = cap_init(); |
279 | if (ret) { |
280 | pr_err("Failed to initialize component authentication core (%d)\n" , |
281 | ret); |
282 | goto fw_mgmt_exit; |
283 | } |
284 | |
285 | ret = greybus_register(&gb_fw_core_driver); |
286 | if (ret) |
287 | goto cap_exit; |
288 | |
289 | return 0; |
290 | |
291 | cap_exit: |
292 | cap_exit(); |
293 | fw_mgmt_exit: |
294 | fw_mgmt_exit(); |
295 | |
296 | return ret; |
297 | } |
298 | module_init(fw_core_init); |
299 | |
300 | static void __exit fw_core_exit(void) |
301 | { |
302 | greybus_deregister(&gb_fw_core_driver); |
303 | cap_exit(); |
304 | fw_mgmt_exit(); |
305 | } |
306 | module_exit(fw_core_exit); |
307 | |
308 | MODULE_ALIAS("greybus:firmware" ); |
309 | MODULE_AUTHOR("Viresh Kumar <viresh.kumar@linaro.org>" ); |
310 | MODULE_DESCRIPTION("Greybus Firmware Bundle Driver" ); |
311 | MODULE_LICENSE("GPL v2" ); |
312 | |