diff options
Diffstat (limited to 'drivers/staging/greybus/fw-core.c')
-rw-r--r-- | drivers/staging/greybus/fw-core.c | 311 |
1 files changed, 311 insertions, 0 deletions
diff --git a/drivers/staging/greybus/fw-core.c b/drivers/staging/greybus/fw-core.c new file mode 100644 index 000000000..57bebf246 --- /dev/null +++ b/drivers/staging/greybus/fw-core.c @@ -0,0 +1,311 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Greybus Firmware Core Bundle Driver. + * + * Copyright 2016 Google Inc. + * Copyright 2016 Linaro Ltd. + */ +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/firmware.h> +#include <linux/greybus.h> +#include "firmware.h" +#include "spilib.h" + +struct gb_fw_core { + struct gb_connection *download_connection; + struct gb_connection *mgmt_connection; + struct gb_connection *spi_connection; + struct gb_connection *cap_connection; +}; + +static struct spilib_ops *spilib_ops; + +struct gb_connection *to_fw_mgmt_connection(struct device *dev) +{ + struct gb_fw_core *fw_core = dev_get_drvdata(dev); + + return fw_core->mgmt_connection; +} + +static int gb_fw_spi_connection_init(struct gb_connection *connection) +{ + int ret; + + if (!connection) + return 0; + + ret = gb_connection_enable(connection); + if (ret) + return ret; + + ret = gb_spilib_master_init(connection, &connection->bundle->dev, + spilib_ops); + if (ret) { + gb_connection_disable(connection); + return ret; + } + + return 0; +} + +static void gb_fw_spi_connection_exit(struct gb_connection *connection) +{ + if (!connection) + return; + + gb_spilib_master_exit(connection); + gb_connection_disable(connection); +} + +static int gb_fw_core_probe(struct gb_bundle *bundle, + const struct greybus_bundle_id *id) +{ + struct greybus_descriptor_cport *cport_desc; + struct gb_connection *connection; + struct gb_fw_core *fw_core; + int ret, i; + u16 cport_id; + u8 protocol_id; + + fw_core = kzalloc(sizeof(*fw_core), GFP_KERNEL); + if (!fw_core) + return -ENOMEM; + + /* Parse CPorts and create connections */ + for (i = 0; i < bundle->num_cports; i++) { + cport_desc = &bundle->cport_desc[i]; + cport_id = le16_to_cpu(cport_desc->id); + protocol_id = cport_desc->protocol_id; + + switch (protocol_id) { + case GREYBUS_PROTOCOL_FW_MANAGEMENT: + /* Disallow multiple Firmware Management CPorts */ + if (fw_core->mgmt_connection) { + dev_err(&bundle->dev, + "multiple management CPorts found\n"); + ret = -EINVAL; + goto err_destroy_connections; + } + + connection = gb_connection_create(bundle, cport_id, + gb_fw_mgmt_request_handler); + if (IS_ERR(connection)) { + ret = PTR_ERR(connection); + dev_err(&bundle->dev, + "failed to create management connection (%d)\n", + ret); + goto err_destroy_connections; + } + + fw_core->mgmt_connection = connection; + break; + case GREYBUS_PROTOCOL_FW_DOWNLOAD: + /* Disallow multiple Firmware Download CPorts */ + if (fw_core->download_connection) { + dev_err(&bundle->dev, + "multiple download CPorts found\n"); + ret = -EINVAL; + goto err_destroy_connections; + } + + connection = gb_connection_create(bundle, cport_id, + gb_fw_download_request_handler); + if (IS_ERR(connection)) { + dev_err(&bundle->dev, "failed to create download connection (%ld)\n", + PTR_ERR(connection)); + } else { + fw_core->download_connection = connection; + } + + break; + case GREYBUS_PROTOCOL_SPI: + /* Disallow multiple SPI CPorts */ + if (fw_core->spi_connection) { + dev_err(&bundle->dev, + "multiple SPI CPorts found\n"); + ret = -EINVAL; + goto err_destroy_connections; + } + + connection = gb_connection_create(bundle, cport_id, + NULL); + if (IS_ERR(connection)) { + dev_err(&bundle->dev, "failed to create SPI connection (%ld)\n", + PTR_ERR(connection)); + } else { + fw_core->spi_connection = connection; + } + + break; + case GREYBUS_PROTOCOL_AUTHENTICATION: + /* Disallow multiple CAP CPorts */ + if (fw_core->cap_connection) { + dev_err(&bundle->dev, "multiple Authentication CPorts found\n"); + ret = -EINVAL; + goto err_destroy_connections; + } + + connection = gb_connection_create(bundle, cport_id, + NULL); + if (IS_ERR(connection)) { + dev_err(&bundle->dev, "failed to create Authentication connection (%ld)\n", + PTR_ERR(connection)); + } else { + fw_core->cap_connection = connection; + } + + break; + default: + dev_err(&bundle->dev, "invalid protocol id (0x%02x)\n", + protocol_id); + ret = -EINVAL; + goto err_destroy_connections; + } + } + + /* Firmware Management connection is mandatory */ + if (!fw_core->mgmt_connection) { + dev_err(&bundle->dev, "missing management connection\n"); + ret = -ENODEV; + goto err_destroy_connections; + } + + ret = gb_fw_download_connection_init(fw_core->download_connection); + if (ret) { + /* We may still be able to work with the Interface */ + dev_err(&bundle->dev, "failed to initialize firmware download connection, disable it (%d)\n", + ret); + gb_connection_destroy(fw_core->download_connection); + fw_core->download_connection = NULL; + } + + ret = gb_fw_spi_connection_init(fw_core->spi_connection); + if (ret) { + /* We may still be able to work with the Interface */ + dev_err(&bundle->dev, "failed to initialize SPI connection, disable it (%d)\n", + ret); + gb_connection_destroy(fw_core->spi_connection); + fw_core->spi_connection = NULL; + } + + ret = gb_cap_connection_init(fw_core->cap_connection); + if (ret) { + /* We may still be able to work with the Interface */ + dev_err(&bundle->dev, "failed to initialize CAP connection, disable it (%d)\n", + ret); + gb_connection_destroy(fw_core->cap_connection); + fw_core->cap_connection = NULL; + } + + ret = gb_fw_mgmt_connection_init(fw_core->mgmt_connection); + if (ret) { + /* We may still be able to work with the Interface */ + dev_err(&bundle->dev, "failed to initialize firmware management connection, disable it (%d)\n", + ret); + goto err_exit_connections; + } + + greybus_set_drvdata(bundle, fw_core); + + /* FIXME: Remove this after S2 Loader gets runtime PM support */ + if (!(bundle->intf->quirks & GB_INTERFACE_QUIRK_NO_PM)) + gb_pm_runtime_put_autosuspend(bundle); + + return 0; + +err_exit_connections: + gb_cap_connection_exit(fw_core->cap_connection); + gb_fw_spi_connection_exit(fw_core->spi_connection); + gb_fw_download_connection_exit(fw_core->download_connection); +err_destroy_connections: + gb_connection_destroy(fw_core->mgmt_connection); + gb_connection_destroy(fw_core->cap_connection); + gb_connection_destroy(fw_core->spi_connection); + gb_connection_destroy(fw_core->download_connection); + kfree(fw_core); + + return ret; +} + +static void gb_fw_core_disconnect(struct gb_bundle *bundle) +{ + struct gb_fw_core *fw_core = greybus_get_drvdata(bundle); + int ret; + + /* FIXME: Remove this after S2 Loader gets runtime PM support */ + if (!(bundle->intf->quirks & GB_INTERFACE_QUIRK_NO_PM)) { + ret = gb_pm_runtime_get_sync(bundle); + if (ret) + gb_pm_runtime_get_noresume(bundle); + } + + gb_fw_mgmt_connection_exit(fw_core->mgmt_connection); + gb_cap_connection_exit(fw_core->cap_connection); + gb_fw_spi_connection_exit(fw_core->spi_connection); + gb_fw_download_connection_exit(fw_core->download_connection); + + gb_connection_destroy(fw_core->mgmt_connection); + gb_connection_destroy(fw_core->cap_connection); + gb_connection_destroy(fw_core->spi_connection); + gb_connection_destroy(fw_core->download_connection); + + kfree(fw_core); +} + +static const struct greybus_bundle_id gb_fw_core_id_table[] = { + { GREYBUS_DEVICE_CLASS(GREYBUS_CLASS_FW_MANAGEMENT) }, + { } +}; + +static struct greybus_driver gb_fw_core_driver = { + .name = "gb-firmware", + .probe = gb_fw_core_probe, + .disconnect = gb_fw_core_disconnect, + .id_table = gb_fw_core_id_table, +}; + +static int fw_core_init(void) +{ + int ret; + + ret = fw_mgmt_init(); + if (ret) { + pr_err("Failed to initialize fw-mgmt core (%d)\n", ret); + return ret; + } + + ret = cap_init(); + if (ret) { + pr_err("Failed to initialize component authentication core (%d)\n", + ret); + goto fw_mgmt_exit; + } + + ret = greybus_register(&gb_fw_core_driver); + if (ret) + goto cap_exit; + + return 0; + +cap_exit: + cap_exit(); +fw_mgmt_exit: + fw_mgmt_exit(); + + return ret; +} +module_init(fw_core_init); + +static void __exit fw_core_exit(void) +{ + greybus_deregister(&gb_fw_core_driver); + cap_exit(); + fw_mgmt_exit(); +} +module_exit(fw_core_exit); + +MODULE_ALIAS("greybus:firmware"); +MODULE_AUTHOR("Viresh Kumar <viresh.kumar@linaro.org>"); +MODULE_DESCRIPTION("Greybus Firmware Bundle Driver"); +MODULE_LICENSE("GPL v2"); |