@@ -9,6 +9,7 @@ loongson-y := \
lsdc_gfxpll.o \
lsdc_i2c.o \
lsdc_irq.o \
+ lsdc_output.o \
lsdc_output_7a1000.o \
lsdc_output_7a2000.o \
lsdc_plane.o \
@@ -4,6 +4,7 @@
*/
#include <linux/pci.h>
+#include <linux/platform_device.h>
#include <video/nomodeset.h>
@@ -19,15 +20,29 @@ module_param_named(vblank, loongson_vblank, int, 0400);
static int __init loongson_module_init(void)
{
+ int ret;
+
if (!loongson_modeset || video_firmware_drivers_only())
return -ENODEV;
- return pci_register_driver(&lsdc_pci_driver);
+ ret = platform_driver_register(&lsdc_output_port_platform_driver);
+ if (ret)
+ return ret;
+
+ ret = pci_register_driver(&lsdc_pci_driver);
+ if (ret) {
+ platform_driver_unregister(&lsdc_output_port_platform_driver);
+ return ret;
+ }
+
+ return 0;
}
module_init(loongson_module_init);
static void __exit loongson_module_exit(void)
{
pci_unregister_driver(&lsdc_pci_driver);
+
+ platform_driver_unregister(&lsdc_output_port_platform_driver);
}
module_exit(loongson_module_exit);
@@ -8,5 +8,6 @@
extern int loongson_vblank;
extern struct pci_driver lsdc_pci_driver;
+extern struct platform_driver lsdc_output_port_platform_driver;
#endif
@@ -3,7 +3,9 @@
* Copyright (C) 2023 Loongson Technology Corporation Limited
*/
+#include <linux/component.h>
#include <linux/pci.h>
+#include <linux/platform_device.h>
#include <linux/vgaarb.h>
#include <drm/drm_aperture.h>
@@ -257,12 +259,39 @@ static unsigned int lsdc_vga_set_decode(struct pci_dev *pdev, bool state)
return VGA_RSRC_NORMAL_IO | VGA_RSRC_NORMAL_MEM;
}
+static int loongson_drm_master_bind(struct device *dev)
+{
+ int ret;
+
+ ret = component_bind_all(dev, NULL);
+ if (ret) {
+ dev_err(dev, "master bind all failed: %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static void loongson_drm_master_unbind(struct device *dev)
+{
+ component_unbind_all(dev, NULL);
+
+ return;
+}
+
+const struct component_master_ops loongson_drm_master_ops = {
+ .bind = loongson_drm_master_bind,
+ .unbind = loongson_drm_master_unbind,
+};
+
static int lsdc_pci_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
{
+ struct component_match *matches = NULL;
const struct lsdc_desc *descp;
struct drm_device *ddev;
struct lsdc_device *ldev;
int ret;
+ int i;
descp = lsdc_device_probe(pdev, ent->driver_data);
if (IS_ERR_OR_NULL(descp))
@@ -285,6 +314,25 @@ static int lsdc_pci_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
if (IS_ERR(ldev))
return PTR_ERR(ldev);
+ for (i = 0; i < descp->num_of_crtc; ++i) {
+ ret = lsdc_output_preinit(&pdev->dev, descp, i,
+ &ldev->childs[i]);
+ if (ret)
+ return ret;
+ }
+
+ for (i = 0; i < descp->num_of_crtc; ++i) {
+ component_match_add(&pdev->dev, &matches,
+ component_compare_dev,
+ &ldev->childs[i]->dev);
+ }
+
+ ret = component_master_add_with_match(&pdev->dev,
+ &loongson_drm_master_ops,
+ matches);
+
+ dev_info(&pdev->dev, "loongson add component: %u\n", ret);
+
ddev = &ldev->base;
pci_set_drvdata(pdev, ddev);
@@ -322,9 +370,20 @@ static int lsdc_pci_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
static void lsdc_pci_remove(struct pci_dev *pdev)
{
struct drm_device *ddev = pci_get_drvdata(pdev);
+ struct lsdc_device *ldev = to_lsdc(ddev);
+ unsigned int i;
drm_dev_unregister(ddev);
drm_atomic_helper_shutdown(ddev);
+
+ component_master_del(&pdev->dev, &loongson_drm_master_ops);
+
+ for (i = 0; i < ldev->descp->num_of_crtc; ++i) {
+ if (ldev->childs[i]) {
+ platform_device_unregister(ldev->childs[i]);
+ ldev->childs[i] = NULL;
+ }
+ }
}
static void lsdc_pci_shutdown(struct pci_dev *pdev)
@@ -173,11 +173,6 @@ struct lsdc_cursor {
struct lsdc_device *ldev;
};
-struct lsdc_output {
- struct drm_encoder encoder;
- struct drm_connector connector;
-};
-
static inline struct lsdc_output *
connector_to_lsdc_output(struct drm_connector *connector)
{
@@ -290,6 +285,7 @@ struct lsdc_device {
resource_size_t gtt_size;
struct lsdc_display_pipe dispipe[LSDC_NUM_CRTC];
+ struct platform_device *childs[LSDC_NUM_CRTC];
struct lsdc_gem gem;
new file mode 100644
@@ -0,0 +1,142 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+#include <linux/delay.h>
+#include <linux/component.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+
+#include "lsdc_drv.h"
+#include "lsdc_output.h"
+
+static struct lsdc_output_desc ls7a1000_output_port_desc[2] = {
+ {
+ .pipe = 0,
+ .type = "DVO-0",
+ },
+ {
+ .pipe = 1,
+ .type = "DVO-1",
+ },
+};
+
+static struct lsdc_output_desc ls7a2000_output_port_desc[2] = {
+ {
+ .pipe = 0,
+ .type = "HDMI-or-VGA-0",
+ },
+ {
+ .pipe = 1,
+ .type = "HDMI-1",
+ },
+};
+
+int lsdc_output_preinit(struct device *parent,
+ const struct lsdc_desc *descp,
+ unsigned int index,
+ struct platform_device **ppdev)
+{
+ struct lsdc_output_desc *output_port_info;
+ int ret;
+
+ switch (to_loongson_gfx(descp)->chip_id) {
+ case CHIP_LS7A1000:
+ output_port_info = &ls7a1000_output_port_desc[index];
+ break;
+ case CHIP_LS7A2000:
+ output_port_info = &ls7a2000_output_port_desc[index];
+ break;
+ default:
+ output_port_info = NULL;
+ break;
+ };
+
+ ret = loongson_create_platform_device(parent,
+ "lsdc-output-port",
+ index,
+ NULL,
+ (void *)output_port_info,
+ ppdev);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+/*
+ * dev stand for the port@0, port@1, ..., port@n of the dispplay controller
+ */
+static int lsdc_output_port_bind(struct device *dev,
+ struct device *master,
+ void *data)
+{
+ return 0;
+}
+
+static void lsdc_output_port_unbind(struct device *dev,
+ struct device *master,
+ void *data)
+{
+}
+
+static const struct component_ops lsdc_output_port_component_ops = {
+ .bind = lsdc_output_port_bind,
+ .unbind = lsdc_output_port_unbind,
+};
+
+static int lsdc_output_port_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct lsdc_output *output;
+ struct lsdc_output_desc *descp;
+ int ret;
+
+ descp = *(void **)dev->platform_data;
+ if (!descp) {
+ dev_err(dev, "No platform specific data for output port\n");
+ return -ENODEV;
+ }
+
+ output = devm_kzalloc(dev, sizeof(*output), GFP_KERNEL);
+ if (!output)
+ return -ENOMEM;
+
+ output->dev = dev;
+ output->descp = descp;
+
+ ret = component_add(dev, &lsdc_output_port_component_ops);
+ if (ret) {
+ devm_kfree(dev, output);
+ dev_err(dev, "failed to register component: %d\n", ret);
+ return ret;
+ }
+
+ dev_set_drvdata(dev, output);
+
+ return 0;
+}
+
+static void lsdc_output_port_remove(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct lsdc_output *output;
+
+ component_del(dev, &lsdc_output_port_component_ops);
+
+ output = dev_get_drvdata(dev);
+ if (output)
+ devm_kfree(dev, output);
+}
+
+static const struct platform_device_id lsdc_output_port_ids[] = {
+ { .name = "lsdc-output-port" },
+ { },
+};
+
+struct platform_driver lsdc_output_port_platform_driver = {
+ .driver = {
+ .name = "lsdc-output-port",
+ },
+ .probe = lsdc_output_port_probe,
+ .remove_new = lsdc_output_port_remove,
+ .id_table = lsdc_output_port_ids,
+};
@@ -6,7 +6,22 @@
#ifndef __LSDC_OUTPUT_H__
#define __LSDC_OUTPUT_H__
-#include "lsdc_drv.h"
+#include <drm/drm_encoder.h>
+#include <drm/drm_connector.h>
+
+struct lsdc_desc;
+
+struct lsdc_output_desc {
+ u32 pipe;
+ const char type[32];
+};
+
+struct lsdc_output {
+ struct device *dev;
+ struct drm_encoder encoder;
+ struct drm_connector connector;
+ struct lsdc_output_desc *descp;
+};
int ls7a1000_output_init(struct drm_device *ddev,
struct lsdc_display_pipe *dispipe,
@@ -18,4 +33,9 @@ int ls7a2000_output_init(struct drm_device *ldev,
struct i2c_adapter *ddc,
unsigned int index);
+int lsdc_output_preinit(struct device *parent,
+ const struct lsdc_desc *descp,
+ unsigned int index,
+ struct platform_device **ppdev);
+
#endif
Introduce the component framework to bind childs and siblings, for better modularity and paper over the deferral probe problems if it need to attach exterinal module someday. Hardware units come with PCI(e) are actually all ready to drive, but there has some board specific modules will return -EPROBE_DEFER. We need all other submodules ready to attach before we can register the drm device to userspace. The idea is to devide the exterinal module dependent part and exterinal module independent part clearly, for example, the display controller and the builtin GPIO-I2C just belong to exterinal module independent part. While the output is belong to exterinal module dependent part. Also for better reflecting the hardware, we intend to abstract the output ports as child devices. The output ports may consists of encoder phy and level shift, while the GPU and VPU are standalone siblings. As those units are relative separate hardware units from display controller itself. By design, the display controller PCI(e) is selected as the component master, gpio-i2c go with master. The manually created virtual child device are functional as agents for the master, it could return the -EPROBE_DEFER back to the component core. This allows the master don't have to tear down everything, the majority setups work can be preserved. The potential cyclic dependency problem can be solved with such framework. Signed-off-by: Sui Jingfeng <sui.jingfeng@linux.dev> --- drivers/gpu/drm/loongson/Makefile | 1 + drivers/gpu/drm/loongson/loongson_module.c | 17 ++- drivers/gpu/drm/loongson/loongson_module.h | 1 + drivers/gpu/drm/loongson/lsdc_drv.c | 59 +++++++++ drivers/gpu/drm/loongson/lsdc_drv.h | 6 +- drivers/gpu/drm/loongson/lsdc_output.c | 142 +++++++++++++++++++++ drivers/gpu/drm/loongson/lsdc_output.h | 22 +++- 7 files changed, 241 insertions(+), 7 deletions(-) create mode 100644 drivers/gpu/drm/loongson/lsdc_output.c