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
15struct 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
22static struct spilib_ops *spilib_ops;
23
24struct 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
31static 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
52static 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
61static 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
217err_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);
221err_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
231static 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
256static const struct greybus_bundle_id gb_fw_core_id_table[] = {
257	{ GREYBUS_DEVICE_CLASS(GREYBUS_CLASS_FW_MANAGEMENT) },
258	{ }
259};
260
261static 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
268static 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
291cap_exit:
292	cap_exit();
293fw_mgmt_exit:
294	fw_mgmt_exit();
295
296	return ret;
297}
298module_init(fw_core_init);
299
300static void __exit fw_core_exit(void)
301{
302	greybus_deregister(&gb_fw_core_driver);
303	cap_exit();
304	fw_mgmt_exit();
305}
306module_exit(fw_core_exit);
307
308MODULE_ALIAS("greybus:firmware");
309MODULE_AUTHOR("Viresh Kumar <viresh.kumar@linaro.org>");
310MODULE_DESCRIPTION("Greybus Firmware Bundle Driver");
311MODULE_LICENSE("GPL v2");
312