DCP display hardware is controlled by firmware and exposed to Linux as a VirtIO function. The MSM DRM driver needs a platform driver to establish the transport, DMA/IOMMU address spaces, power-notify path, and display component.
Add the VirtIO-MMIO transport and DCP platform-function driver. Set up the DMA/IOMMU identity, IPCC client, VirtIO queues, HFI transport, firmware capability handling, and MSM DRM registration. Synchronize suspend, resume, remove, and shutdown with in-flight HFI activity. Signed-off-by: Jayant Shekhar <[email protected]> Assisted-by: LLM --- drivers/gpu/drm/msm/Makefile | 2 + drivers/gpu/drm/msm/dcp/dcp_drv.c | 245 ++++++++++++++++++++++++++++ drivers/gpu/drm/msm/dcp/dcp_drv.h | 19 +++ drivers/gpu/drm/msm/dcp/dcp_virtio.c | 302 +++++++++++++++++++++++++++++++++++ drivers/gpu/drm/msm/dcp/dcp_virtio.h | 22 +++ 5 files changed, 590 insertions(+) diff --git a/drivers/gpu/drm/msm/Makefile b/drivers/gpu/drm/msm/Makefile index 9910bd4f306d..e57aee483137 100644 --- a/drivers/gpu/drm/msm/Makefile +++ b/drivers/gpu/drm/msm/Makefile @@ -83,6 +83,8 @@ msm-display-$(CONFIG_DRM_MSM_DCP) += \ dcp/dcp_ipcc.o \ dcp/dcp_plane.o \ dcp/dcp_encoder.o \ + dcp/dcp_drv.o \ + dcp/dcp_virtio.o \ dcp/hfi/hfi_packer.o \ dcp/hfi/hfi_unpacker.o \ dcp/hfi/hfi_utils.o diff --git a/drivers/gpu/drm/msm/dcp/dcp_drv.c b/drivers/gpu/drm/msm/dcp/dcp_drv.c new file mode 100644 index 000000000000..e7016937a028 --- /dev/null +++ b/drivers/gpu/drm/msm/dcp/dcp_drv.c @@ -0,0 +1,245 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + * DCP VirtIO driver. + */ + +#include <linux/component.h> +#include <linux/device.h> +#include <linux/dma-mapping.h> +#include <linux/of.h> +#include <linux/of_platform.h> +#include <linux/panic_notifier.h> +#include <linux/pm_runtime.h> + +#define DCP_DPU_AUTOSUSPEND_DELAY_MS 5000 + +#include "dcp_drv.h" +#include "dcp_hfi.h" +#include "dcp_ipcc.h" +#include "dcp_virtio.h" +#include "msm_drv.h" +#include "msm_kms.h" + +int dcp_subsystem_dev_init(struct dcp_drv *drv) +{ + struct virtio_device *vdev = drv->vdev; + struct device_node *dcp_subsystem_node; + struct platform_device *pdev; + struct device *dev; + char busid[32]; + int ret; + + dcp_subsystem_node = of_get_child_by_name(vdev->dev.parent->of_node, + "dcp-subsystem"); + if (!dcp_subsystem_node) { + dev_err(&vdev->dev, "no dcp-subsystem DT node\n"); + return -ENODEV; + } + + snprintf(busid, sizeof(busid), "dcp-subsystem.%s", dev_name(vdev->dev.parent)); + + /* + * DCP is discovered through VirtIO, but display/HFI buffers must be + * mapped through the dcp-subsystem node's DMA/IOMMU identity. Create a + * platform device for that DT node so DMA API and msm_kms_init_vm() use + * the display function's IOMMU domain. + */ + pdev = of_platform_device_create(dcp_subsystem_node, busid, vdev->dev.parent); + if (!pdev) { + dev_err(&vdev->dev, "dcp_subsystem_dev creation failed\n"); + of_node_put(dcp_subsystem_node); + return -ENODEV; + } + dev = &pdev->dev; + + dev->dma_parms = &drv->dcp_subsystem_dma_parms; + dma_set_max_seg_size(dev, UINT_MAX); + + ret = of_dma_configure_id(dev, dcp_subsystem_node, true, NULL); + if (ret) { + dev_err(dev, "of_dma_configure_id failed: %d\n", ret); + goto err_destroy; + } + + if (!device_iommu_mapped(dev)) { + dev_err(dev, "no IOMMU group attached\n"); + ret = -ENODEV; + goto err_destroy; + } + + dev_dbg(dev, "dcp-subsystem: iommu_mapped=%d iommu_group=%d\n", + device_iommu_mapped(dev), + dev->iommu_group ? iommu_group_id(dev->iommu_group) : -1); + + pm_runtime_set_active(dev); + pm_runtime_enable(dev); + pm_runtime_set_autosuspend_delay(dev, DCP_DPU_AUTOSUSPEND_DELAY_MS); + pm_runtime_use_autosuspend(dev); + + drv->dcp_subsystem_dev = pdev; + drv->dcp_subsystem_node = dcp_subsystem_node; + + return 0; + +err_destroy: + of_platform_device_destroy(dev, NULL); + of_node_put(dcp_subsystem_node); + return ret; +} + +void dcp_subsystem_dev_deinit(struct dcp_drv *drv) +{ + if (!drv->dcp_subsystem_dev) + return; + + pm_runtime_disable(&drv->dcp_subsystem_dev->dev); + of_platform_device_destroy(&drv->dcp_subsystem_dev->dev, NULL); + of_node_put(drv->dcp_subsystem_node); + drv->dcp_subsystem_dev = NULL; +} + +int dcp_pm_prepare(struct device *vdev_dev) +{ + struct dcp_drv *drv = vdev_dev ? ((struct virtio_device *) + dev_to_virtio(vdev_dev))->priv : NULL; + + if (!drv || !drv->dcp_subsystem_dev) + return 0; + + return msm_kms_pm_prepare(&drv->dcp_subsystem_dev->dev); +} + +void dcp_pm_complete(struct device *vdev_dev) +{ + struct dcp_drv *drv = vdev_dev ? ((struct virtio_device *) + dev_to_virtio(vdev_dev))->priv : NULL; + + if (!drv || !drv->dcp_subsystem_dev) + return; + + msm_kms_pm_complete(&drv->dcp_subsystem_dev->dev); +} + +static int dcp_pm_ipcc_notify(struct dcp_drv *drv) +{ + int ret; + + if (!drv->ipcc_valid) + return 0; + + ret = dcp_ipcc_send_power_notify(&drv->ipcc); + if (ret) + dev_warn(&drv->vdev->dev, + "IPCC power notification failed during PM: %d\n", ret); + + return ret; +} + +int dcp_pm_suspend(struct device *vdev_dev) +{ + struct dcp_drv *drv = vdev_dev ? ((struct virtio_device *) + dev_to_virtio(vdev_dev))->priv : NULL; + + if (!drv || !drv->dcp_subsystem_dev) + return 0; + + return dcp_pm_ipcc_notify(drv); +} + +int dcp_pm_resume(struct device *vdev_dev) +{ + struct dcp_drv *drv = vdev_dev ? ((struct virtio_device *) + dev_to_virtio(vdev_dev))->priv : NULL; + + if (!drv || !drv->dcp_subsystem_dev) + return 0; + + return dcp_pm_ipcc_notify(drv); +} + +static int dcp_self_bind(struct device *master, struct device *dev, void *data) +{ + return 0; +} + +static void dcp_self_unbind(struct device *master, struct device *dev, + void *data) +{ +} + +static const struct component_ops dcp_self_ops = { + .bind = dcp_self_bind, + .unbind = dcp_self_unbind, +}; + +int dcp_component_register(struct dcp_drv *drv) +{ + struct device *dev = &drv->dcp_subsystem_dev->dev; + struct component_match *match = NULL; + int ret; + + /* + * DCP has no host-visible display subcomponents: firmware owns the + * underlying display blocks. The self component is only the minimal shim + * needed to enter the existing MSM DRM component-master init path without + * adding a DCP-specific entry point to common MSM DRM code. + */ + ret = component_add(dev, &dcp_self_ops); + if (ret) { + dev_err(dev, "component_add failed: %d\n", ret); + return ret; + } + + component_match_add(dev, &match, component_compare_dev, dev); + + ret = component_master_add_with_match(dev, &msm_drm_ops, match); + if (ret) { + dev_err(dev, "component_master_add_with_match failed: %d\n", + ret); + component_del(dev, &dcp_self_ops); + return ret; + } + + return 0; +} + +void dcp_component_unregister(struct dcp_drv *drv) +{ + struct device *dev = &drv->dcp_subsystem_dev->dev; + + component_master_del(dev, &msm_drm_ops); + component_del(dev, &dcp_self_ops); +} + +static int dcp_panic_notifier_cb(struct notifier_block *nb, + unsigned long action, void *data) +{ + struct dcp_drv *drv = container_of(nb, struct dcp_drv, panic_notifier); + + if (drv->hfi) + atomic_set(&drv->hfi->shutdown_in_progress, 1); + + return NOTIFY_OK; +} + +void dcp_panic_notifier_register(struct dcp_drv *drv) +{ + drv->panic_notifier.notifier_call = dcp_panic_notifier_cb; + atomic_notifier_chain_register(&panic_notifier_list, &drv->panic_notifier); +} + +void dcp_panic_notifier_unregister(struct dcp_drv *drv) +{ + atomic_notifier_chain_unregister(&panic_notifier_list, &drv->panic_notifier); +} + +void __init msm_dcp_register(void) +{ + dcp_virtio_register(); +} + +void __exit msm_dcp_unregister(void) +{ + dcp_virtio_unregister(); +} diff --git a/drivers/gpu/drm/msm/dcp/dcp_drv.h b/drivers/gpu/drm/msm/dcp/dcp_drv.h index af2db3cf9066..ca8e5bd284e0 100644 --- a/drivers/gpu/drm/msm/dcp/dcp_drv.h +++ b/drivers/gpu/drm/msm/dcp/dcp_drv.h @@ -26,6 +26,25 @@ struct dcp_drv { struct dcp_dpu_kms *kms; struct dcp_caps caps; struct notifier_block panic_notifier; + struct dcp_ipcc ipcc; + bool ipcc_valid; }; +int dcp_subsystem_dev_init(struct dcp_drv *drv); +void dcp_subsystem_dev_deinit(struct dcp_drv *drv); + +int dcp_component_register(struct dcp_drv *drv); +void dcp_component_unregister(struct dcp_drv *drv); + +int dcp_pm_prepare(struct device *vdev_dev); +void dcp_pm_complete(struct device *vdev_dev); +int dcp_pm_suspend(struct device *vdev_dev); +int dcp_pm_resume(struct device *vdev_dev); + +void dcp_panic_notifier_register(struct dcp_drv *drv); +void dcp_panic_notifier_unregister(struct dcp_drv *drv); + +void __init msm_dcp_register(void); +void __exit msm_dcp_unregister(void); + #endif /* __DCP_DRV_H__ */ diff --git a/drivers/gpu/drm/msm/dcp/dcp_virtio.c b/drivers/gpu/drm/msm/dcp/dcp_virtio.c new file mode 100644 index 000000000000..b2aeb03688c9 --- /dev/null +++ b/drivers/gpu/drm/msm/dcp/dcp_virtio.c @@ -0,0 +1,302 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + * DCP VirtIO device driver. + */ + +#include <linux/module.h> +#include <linux/err.h> +#include <linux/pm_runtime.h> +#include <linux/virtio.h> +#include <linux/virtio_config.h> +#include <linux/scatterlist.h> +#include <linux/slab.h> + +#include "dcp_drv.h" +#include "dcp_dpu_kms.h" +#include "dcp_hfi.h" +#include "dcp_ipcc.h" +#include "dcp_virtio.h" +#include "msm_drv.h" +#include "msm_kms.h" + +static void dcp_virtio_hfi_cleanup(struct dcp_drv *drv); + +static const unsigned int dcp_virtio_features[] = { + VIRTIO_F_VERSION_1, + VIRTIO_F_NOTIFICATION_DATA, + VIRTIO_F_RING_RESET, + VIRTIO_F_ACCESS_PLATFORM, +}; + +/* + * The VirtIO core owns transport discovery. This driver owns HFI setup and + * creates the DRM component only after firmware capabilities are available. + */ +static int dcp_virtio_probe(struct virtio_device *vdev) +{ + struct dcp_drv *drv; + struct virtqueue *vqs[DCP_NUM_VQS]; + struct virtqueue_info vqs_info[DCP_NUM_VQS] = { + [DCP_VQ_CMD] = { .name = "cmd", .callback = dcp_hfi_cmd_callback }, + [DCP_VQ_RESP] = { .name = "resp", .callback = dcp_hfi_resp_callback }, + }; + struct dcp_dpu_kms *dpu_kms; + struct msm_drm_private *priv; + int ret; + + dev_dbg(&vdev->dev, "DCP VirtIO probe started\n"); + + drv = devm_kzalloc(&vdev->dev, sizeof(*drv), GFP_KERNEL); + if (!drv) + return -ENOMEM; + + drv->vdev = vdev; + vdev->priv = drv; + + if (!virtio_has_feature(vdev, VIRTIO_F_VERSION_1)) { + dev_err(&vdev->dev, "VIRTIO_F_VERSION_1 not negotiated\n"); + return -ENODEV; + } + /* Access-platform is required for the firmware-visible IOVA mappings. */ + if (!virtio_has_feature(vdev, VIRTIO_F_ACCESS_PLATFORM)) { + dev_err(&vdev->dev, "VIRTIO_F_ACCESS_PLATFORM not negotiated\n"); + return -ENODEV; + } + + ret = dcp_subsystem_dev_init(drv); + if (ret) + return ret; + + ret = dcp_ipcc_init(&drv->ipcc, vdev->dev.parent); + if (ret) { + if (ret == -EPROBE_DEFER) + dev_err_probe(&vdev->dev, ret, + "IPCC not ready, deferring probe\n"); + else + dev_err(&vdev->dev, "IPCC init failed: %d\n", ret); + goto err_fn_dev; + } + drv->ipcc_valid = !!drv->ipcc.power_chan; + + ret = virtio_find_vqs(vdev, DCP_NUM_VQS, vqs, vqs_info, NULL); + if (ret) { + dev_err(&vdev->dev, "virtqueue allocation failed: %d\n", ret); + goto err_ipcc; + } + + ret = dcp_hfi_init(drv, vdev, vqs[DCP_VQ_CMD], vqs[DCP_VQ_RESP], + vdev->dev.parent); + if (ret) { + dev_err(&vdev->dev, "HFI init failed: %d\n", ret); + goto err_del_vqs; + } + + if (drv->ipcc_valid) + dcp_ipcc_set_resp_vq(&drv->ipcc, vqs[DCP_VQ_RESP]); + + /* + * Keep the device unready until HFI state and virtqueues are initialized. + * KMS registration below depends on synchronous firmware initialization. + */ + virtio_device_ready(vdev); + virtqueue_kick(drv->hfi->resp_vq); + + ret = dcp_hfi_send_device_init(drv->hfi); + if (ret) { + dev_err(&vdev->dev, "HFI DEVICE_INIT failed: %d\n", ret); + goto err_hfi_deinit; + } + + ret = dcp_hfi_get_capabilities(drv->hfi, &drv->caps); + if (ret) { + dev_err(&vdev->dev, "HFI GET_CAPS failed: %d\n", ret); + goto err_hfi_deinit; + } + + /* Register the display resources before exposing the DRM component. */ + ret = dcp_hfi_send_resource_register(drv->hfi); + if (ret) { + dev_err(&vdev->dev, "HFI RESOURCE_REGISTER failed: %d\n", ret); + goto err_hfi_deinit; + } + + dev_dbg(&vdev->dev, + "DCP caps: %u displays, max %ux%u, %u planes, features=0x%x\n", + drv->caps.num_crtcs, drv->caps.max_width, drv->caps.max_height, + drv->caps.max_planes, drv->caps.features); + + dpu_kms = devm_kzalloc(&drv->dcp_subsystem_dev->dev, sizeof(*dpu_kms), GFP_KERNEL); + if (!dpu_kms) { + ret = -ENOMEM; + goto err_hfi_deinit; + } + + dpu_kms->drv = drv; + dpu_kms->pm_dev = &drv->dcp_subsystem_dev->dev; + dpu_kms->hfi = drv->hfi; + dpu_kms->caps = &drv->caps; + dpu_kms->dpus_np = drv->dcp_subsystem_node; + dpu_kms->iommu_dev = &drv->dcp_subsystem_dev->dev; + + ret = msm_kms_init(&dpu_kms->base, &dcp_dpu_kms_funcs); + if (ret) { + dev_err(&vdev->dev, "msm_kms_init failed: %d\n", ret); + goto err_hfi_deinit; + } + + priv = devm_kzalloc(&drv->dcp_subsystem_dev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) { + ret = -ENOMEM; + goto err_hfi_deinit; + } + + priv->kms = &dpu_kms->base; + priv->kms_init = dcp_kms_init; + dev_set_drvdata(&drv->dcp_subsystem_dev->dev, priv); + dpu_kms->rpm_enabled = true; + + /* Component binding now sees initialized HFI, capabilities, and IOMMU state. */ + ret = dcp_component_register(drv); + if (ret) { + dev_err(&vdev->dev, "component register failed: %d\n", ret); + goto err_hfi_deinit; + } + + dcp_panic_notifier_register(drv); + + dev_dbg(&vdev->dev, "DCP VirtIO probe complete\n"); + return 0; + +err_hfi_deinit: + dcp_virtio_hfi_cleanup(drv); + goto err_ipcc; +err_del_vqs: + virtio_reset_device(vdev); + vdev->config->del_vqs(vdev); +err_ipcc: + if (drv->ipcc_valid) { + dcp_ipcc_deinit(&drv->ipcc); + drv->ipcc_valid = false; + } +err_fn_dev: + dcp_subsystem_dev_deinit(drv); + return ret; +} + +static void dcp_virtio_hfi_cleanup(struct dcp_drv *drv) +{ + struct virtio_device *vdev = drv->vdev; + + if (!drv->hfi) + return; + + dcp_hfi_stop(drv->hfi); + virtio_reset_device(vdev); + virtio_synchronize_cbs(vdev); + vdev->priv = NULL; + dcp_hfi_quiesce_queues(drv->hfi); + vdev->config->del_vqs(vdev); + dcp_hfi_deinit(drv->hfi); + drv->hfi = NULL; +} + +/* + * Remove DRM users before tearing down HFI, IPCC, or the VirtIO queues. + */ +static void dcp_virtio_remove(struct virtio_device *vdev) +{ + struct dcp_drv *drv = vdev->priv; + + dev_dbg(&vdev->dev, "DCP VirtIO remove\n"); + + if (!drv) + return; + + dcp_component_unregister(drv); + dcp_panic_notifier_unregister(drv); + + dcp_virtio_hfi_cleanup(drv); + + if (drv->ipcc_valid) { + dcp_ipcc_deinit(&drv->ipcc); + drv->ipcc_valid = false; + } + + dcp_subsystem_dev_deinit(drv); +} + +/* KMS shutdown must complete while the firmware transport is available. */ +static void dcp_virtio_shutdown(struct virtio_device *vdev) +{ + struct dcp_drv *drv = vdev->priv; + + if (!drv || !drv->dcp_subsystem_dev) + return; + + pm_runtime_get_sync(&drv->dcp_subsystem_dev->dev); + msm_kms_shutdown(drv->dcp_subsystem_dev); + pm_runtime_put_noidle(&drv->dcp_subsystem_dev->dev); +} + +/* + * Firmware capabilities may change independently of the VirtIO transport; + * refresh them without rebuilding the DRM device. + */ +static void dcp_virtio_config_changed(struct virtio_device *vdev) +{ + struct dcp_drv *drv = vdev->priv; + int ret; + + dev_dbg(&vdev->dev, "VirtIO config changed — re-querying DCP capabilities\n"); + + if (!drv || !drv->hfi) + return; + + ret = dcp_hfi_get_capabilities(drv->hfi, &drv->caps); + if (ret) + dev_err(&vdev->dev, + "config_changed: capability re-query failed: %d\n", ret); + else + dev_dbg(&vdev->dev, + "config_changed: DCP caps updated: %u displays, features=0x%x\n", + drv->caps.num_crtcs, drv->caps.features); +} + +static const struct virtio_device_id dcp_virtio_id_table[] = { + { VIRTIO_ID_QPSFI, VIRTIO_VENDOR_ID_DCP_HFI }, + { 0 }, +}; +MODULE_DEVICE_TABLE(virtio, dcp_virtio_id_table); + +static const struct dev_pm_ops dcp_virtio_pm_ops = { + .prepare = dcp_pm_prepare, + .complete = dcp_pm_complete, + .suspend = dcp_pm_suspend, + .resume = dcp_pm_resume, +}; + +static struct virtio_driver dcp_virtio_driver = { + .driver = { + .name = "qcom-dcp-hfi", + .owner = THIS_MODULE, + .pm = &dcp_virtio_pm_ops, + }, + .id_table = dcp_virtio_id_table, + .feature_table = dcp_virtio_features, + .feature_table_size = ARRAY_SIZE(dcp_virtio_features), + .probe = dcp_virtio_probe, + .remove = dcp_virtio_remove, + .shutdown = dcp_virtio_shutdown, + .config_changed = dcp_virtio_config_changed, +}; + +int dcp_virtio_register(void) +{ + return register_virtio_driver(&dcp_virtio_driver); +} + +void dcp_virtio_unregister(void) +{ + unregister_virtio_driver(&dcp_virtio_driver); +} diff --git a/drivers/gpu/drm/msm/dcp/dcp_virtio.h b/drivers/gpu/drm/msm/dcp/dcp_virtio.h new file mode 100644 index 000000000000..fe244bc5b51a --- /dev/null +++ b/drivers/gpu/drm/msm/dcp/dcp_virtio.h @@ -0,0 +1,22 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + * DCP VirtIO driver interface. + */ + +#ifndef __DCP_VIRTIO_H__ +#define __DCP_VIRTIO_H__ + +#include <linux/device.h> +#include <linux/virtio.h> +#include <linux/virtio_ids.h> + +struct dcp_drv; + +#define VIRTIO_ID_QPSFI 52 +#define VIRTIO_VENDOR_ID_DCP_HFI 1 + +int dcp_virtio_register(void); +void dcp_virtio_unregister(void); + +#endif /* __DCP_VIRTIO_H__ */ -- 2.43.0
