Linux-libre 5.3.12-gnu
[librecmc/linux-libre.git] / drivers / staging / greybus / fw-core.c
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 "firmware.h"
12 #include "greybus.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, &connection->bundle->dev,
43                                     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(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                                                 gb_fw_mgmt_request_handler);
93                         if (IS_ERR(connection)) {
94                                 ret = PTR_ERR(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                                                 gb_fw_download_request_handler);
114                         if (IS_ERR(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(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(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(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(fw_core->download_connection);
180                 fw_core->download_connection = NULL;
181         }
182
183         ret = gb_fw_spi_connection_init(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(fw_core->spi_connection);
189                 fw_core->spi_connection = NULL;
190         }
191
192         ret = gb_cap_connection_init(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(fw_core->cap_connection);
198                 fw_core->cap_connection = NULL;
199         }
200
201         ret = gb_fw_mgmt_connection_init(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, 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(fw_core->cap_connection);
219         gb_fw_spi_connection_exit(fw_core->spi_connection);
220         gb_fw_download_connection_exit(fw_core->download_connection);
221 err_destroy_connections:
222         gb_connection_destroy(fw_core->mgmt_connection);
223         gb_connection_destroy(fw_core->cap_connection);
224         gb_connection_destroy(fw_core->spi_connection);
225         gb_connection_destroy(fw_core->download_connection);
226         kfree(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(fw_core->mgmt_connection);
244         gb_cap_connection_exit(fw_core->cap_connection);
245         gb_fw_spi_connection_exit(fw_core->spi_connection);
246         gb_fw_download_connection_exit(fw_core->download_connection);
247
248         gb_connection_destroy(fw_core->mgmt_connection);
249         gb_connection_destroy(fw_core->cap_connection);
250         gb_connection_destroy(fw_core->spi_connection);
251         gb_connection_destroy(fw_core->download_connection);
252
253         kfree(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");