| // 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 "firmware.h" |
| #include "greybus.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"); |