]> git.karo-electronics.de Git - karo-tx-linux.git/blob - drivers/staging/greybus/fw-core.c
greybus: firmware: Add SPI protocol support
[karo-tx-linux.git] / drivers / staging / greybus / fw-core.c
1 /*
2  * Greybus Firmware Core Bundle Driver.
3  *
4  * Copyright 2016 Google Inc.
5  * Copyright 2016 Linaro Ltd.
6  *
7  * Released under the GPLv2 only.
8  */
9 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
10
11 #include <linux/firmware.h>
12 #include "firmware.h"
13 #include "greybus.h"
14 #include "spilib.h"
15
16 struct gb_fw_core {
17         struct gb_connection    *download_connection;
18         struct gb_connection    *mgmt_connection;
19         struct gb_connection    *spi_connection;
20 };
21
22 struct gb_connection *to_fw_mgmt_connection(struct device *dev)
23 {
24         struct gb_fw_core *fw_core = dev_get_drvdata(dev);
25
26         return fw_core->mgmt_connection;
27 }
28
29 static int gb_fw_spi_connection_init(struct gb_connection *connection)
30 {
31         int ret;
32
33         if (!connection)
34                 return 0;
35
36         ret = gb_connection_enable(connection);
37         if (ret)
38                 return ret;
39
40         ret = gb_spilib_master_init(connection, &connection->bundle->dev);
41         if (ret) {
42                 gb_connection_disable(connection);
43                 return ret;
44         }
45
46         return 0;
47 }
48
49 static void gb_fw_spi_connection_exit(struct gb_connection *connection)
50 {
51         if (!connection)
52                 return;
53
54         gb_spilib_master_exit(connection);
55         gb_connection_disable(connection);
56 }
57
58 static int gb_fw_core_probe(struct gb_bundle *bundle,
59                             const struct greybus_bundle_id *id)
60 {
61         struct greybus_descriptor_cport *cport_desc;
62         struct gb_connection *connection;
63         struct gb_fw_core *fw_core;
64         int ret, i;
65         u16 cport_id;
66         u8 protocol_id;
67
68         fw_core = kzalloc(sizeof(*fw_core), GFP_KERNEL);
69         if (!fw_core)
70                 return -ENOMEM;
71
72         /* Parse CPorts and create connections */
73         for (i = 0; i < bundle->num_cports; i++) {
74                 cport_desc = &bundle->cport_desc[i];
75                 cport_id = le16_to_cpu(cport_desc->id);
76                 protocol_id = cport_desc->protocol_id;
77
78                 switch (protocol_id) {
79                 case GREYBUS_PROTOCOL_FW_MANAGEMENT:
80                         /* Disallow multiple Firmware Management CPorts */
81                         if (fw_core->mgmt_connection) {
82                                 dev_err(&bundle->dev,
83                                         "multiple management CPorts found\n");
84                                 ret = -EINVAL;
85                                 goto err_destroy_connections;
86                         }
87
88                         connection = gb_connection_create(bundle, cport_id,
89                                                 gb_fw_mgmt_request_handler);
90                         if (IS_ERR(connection)) {
91                                 ret = PTR_ERR(connection);
92                                 dev_err(&bundle->dev,
93                                         "failed to create management connection (%d)\n",
94                                         ret);
95                                 goto err_destroy_connections;
96                         }
97
98                         fw_core->mgmt_connection = connection;
99                         break;
100                 case GREYBUS_PROTOCOL_FW_DOWNLOAD:
101                         /* Disallow multiple Firmware Download CPorts */
102                         if (fw_core->download_connection) {
103                                 dev_err(&bundle->dev,
104                                         "multiple download CPorts found\n");
105                                 ret = -EINVAL;
106                                 goto err_destroy_connections;
107                         }
108
109                         connection = gb_connection_create(bundle, cport_id,
110                                                 gb_fw_download_request_handler);
111                         if (IS_ERR(connection)) {
112                                 dev_err(&bundle->dev, "failed to create download connection (%ld)\n",
113                                         PTR_ERR(connection));
114                         } else {
115                                 fw_core->download_connection = connection;
116                         }
117
118                         break;
119                 case GREYBUS_PROTOCOL_SPI:
120                         /* Disallow multiple SPI CPorts */
121                         if (fw_core->spi_connection) {
122                                 dev_err(&bundle->dev,
123                                         "multiple SPI CPorts found\n");
124                                 ret = -EINVAL;
125                                 goto err_destroy_connections;
126                         }
127
128                         connection = gb_connection_create(bundle, cport_id,
129                                                           NULL);
130                         if (IS_ERR(connection)) {
131                                 dev_err(&bundle->dev, "failed to create SPI connection (%ld)\n",
132                                         PTR_ERR(connection));
133                         } else {
134                                 fw_core->spi_connection = connection;
135                         }
136
137                         break;
138                 default:
139                         dev_err(&bundle->dev, "invalid protocol id (0x%02x)\n",
140                                 protocol_id);
141                         ret = -EINVAL;
142                         goto err_free_fw_core;
143                 }
144         }
145
146         /* Firmware Management connection is mandatory */
147         if (!fw_core->mgmt_connection) {
148                 dev_err(&bundle->dev, "missing management connection\n");
149                 ret = -ENODEV;
150                 goto err_destroy_connections;
151         }
152
153         ret = gb_fw_download_connection_init(fw_core->download_connection);
154         if (ret) {
155                 /* We may still be able to work with the Interface */
156                 dev_err(&bundle->dev, "failed to initialize firmware download connection, disable it (%d)\n",
157                         ret);
158                 gb_connection_destroy(fw_core->download_connection);
159                 fw_core->download_connection = NULL;
160         }
161
162         ret = gb_fw_spi_connection_init(fw_core->spi_connection);
163         if (ret) {
164                 /* We may still be able to work with the Interface */
165                 dev_err(&bundle->dev, "failed to initialize SPI connection, disable it (%d)\n",
166                         ret);
167                 gb_connection_destroy(fw_core->spi_connection);
168                 fw_core->spi_connection = NULL;
169         }
170
171         ret = gb_fw_mgmt_connection_init(fw_core->mgmt_connection);
172         if (ret) {
173                 /* We may still be able to work with the Interface */
174                 dev_err(&bundle->dev, "failed to initialize firmware management connection, disable it (%d)\n",
175                         ret);
176                 goto err_exit_connections;
177         }
178
179         greybus_set_drvdata(bundle, fw_core);
180
181         return 0;
182
183 err_exit_connections:
184         gb_fw_spi_connection_exit(fw_core->spi_connection);
185         gb_fw_download_connection_exit(fw_core->download_connection);
186 err_destroy_connections:
187         gb_connection_destroy(fw_core->mgmt_connection);
188         gb_connection_destroy(fw_core->spi_connection);
189         gb_connection_destroy(fw_core->download_connection);
190 err_free_fw_core:
191         kfree(fw_core);
192
193         return ret;
194 }
195
196 static void gb_fw_core_disconnect(struct gb_bundle *bundle)
197 {
198         struct gb_fw_core *fw_core = greybus_get_drvdata(bundle);
199
200         gb_fw_mgmt_connection_exit(fw_core->mgmt_connection);
201         gb_fw_spi_connection_exit(fw_core->spi_connection);
202         gb_fw_download_connection_exit(fw_core->download_connection);
203
204         gb_connection_destroy(fw_core->mgmt_connection);
205         gb_connection_destroy(fw_core->spi_connection);
206         gb_connection_destroy(fw_core->download_connection);
207
208         kfree(fw_core);
209 }
210
211 static const struct greybus_bundle_id gb_fw_core_id_table[] = {
212         { GREYBUS_DEVICE_CLASS(GREYBUS_CLASS_FW_MANAGEMENT) },
213         { }
214 };
215
216 static struct greybus_driver gb_fw_core_driver = {
217         .name           = "gb-firmware",
218         .probe          = gb_fw_core_probe,
219         .disconnect     = gb_fw_core_disconnect,
220         .id_table       = gb_fw_core_id_table,
221 };
222
223 static int fw_core_init(void)
224 {
225         int ret;
226
227         ret = fw_mgmt_init();
228         if (ret) {
229                 pr_err("Failed to initialize fw-mgmt core (%d)\n", ret);
230                 return ret;
231         }
232
233         ret = greybus_register(&gb_fw_core_driver);
234         if (ret) {
235                 fw_mgmt_exit();
236                 return ret;
237         }
238
239         return 0;
240 }
241 module_init(fw_core_init);
242
243 static void __exit fw_core_exit(void)
244 {
245         greybus_deregister(&gb_fw_core_driver);
246         fw_mgmt_exit();
247 }
248 module_exit(fw_core_exit);
249
250 MODULE_ALIAS("greybus:firmware");
251 MODULE_AUTHOR("Viresh Kumar <viresh.kumar@linaro.org>");
252 MODULE_DESCRIPTION("Greybus Firmware Bundle Driver");
253 MODULE_LICENSE("GPL v2");