// SPDX-License-Identifier: GPL-2.0-only /* * Copyright (C) 2025 Icenowy Zheng */ #include #include #include #include #include "vs_crtc.h" #include "vs_dc.h" #include "vs_dc_top_regs.h" #include "vs_drm.h" #include "vs_hwdb.h" static const struct regmap_config vs_dc_regmap_cfg = { .reg_bits = 32, .val_bits = 32, .reg_stride = sizeof(u32), /* VSDC_OVL_CONFIG_EX(1) */ .max_register = 0x2544, }; static const struct of_device_id vs_dc_driver_dt_match[] = { { .compatible = "verisilicon,dc" }, {}, }; MODULE_DEVICE_TABLE(of, vs_dc_driver_dt_match); static irqreturn_t vs_dc_irq_handler(int irq, void *private) { struct vs_dc *dc = private; u32 irqs; regmap_read(dc->regs, VSDC_TOP_IRQ_ACK, &irqs); vs_drm_handle_irq(dc, irqs); return IRQ_HANDLED; } static int vs_dc_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct vs_dc *dc; void __iomem *regs; unsigned int port_count, i; /* pix%u */ char pixclk_name[14]; int irq, ret; if (!dev->of_node) { dev_err(dev, "can't find DC devices\n"); return -ENODEV; } port_count = of_graph_get_port_count(dev->of_node); if (!port_count) { dev_err(dev, "can't find DC downstream ports\n"); return -ENODEV; } if (port_count > VSDC_MAX_OUTPUTS) { dev_err(dev, "too many DC downstream ports than possible\n"); return -EINVAL; } ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32)); if (ret) { dev_err(dev, "No suitable DMA available\n"); return ret; } dc = devm_kzalloc(dev, sizeof(*dc), GFP_KERNEL); if (!dc) return -ENOMEM; dc->rsts[0].id = "core"; dc->rsts[1].id = "axi"; dc->rsts[2].id = "ahb"; ret = devm_reset_control_bulk_get_optional_shared(dev, VSDC_RESET_COUNT, dc->rsts); if (ret) { dev_err(dev, "can't get reset lines\n"); return ret; } dc->core_clk = devm_clk_get_enabled(dev, "core"); if (IS_ERR(dc->core_clk)) { dev_err(dev, "can't get core clock\n"); return PTR_ERR(dc->core_clk); } dc->axi_clk = devm_clk_get_enabled(dev, "axi"); if (IS_ERR(dc->axi_clk)) { dev_err(dev, "can't get axi clock\n"); return PTR_ERR(dc->axi_clk); } dc->ahb_clk = devm_clk_get_enabled(dev, "ahb"); if (IS_ERR(dc->ahb_clk)) { dev_err(dev, "can't get ahb clock\n"); return PTR_ERR(dc->ahb_clk); } irq = platform_get_irq(pdev, 0); if (irq < 0) { dev_err(dev, "can't get irq\n"); return irq; } ret = reset_control_bulk_deassert(VSDC_RESET_COUNT, dc->rsts); if (ret) { dev_err(dev, "can't deassert reset lines\n"); return ret; } regs = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(regs)) { dev_err(dev, "can't map registers"); ret = PTR_ERR(regs); goto err_rst_assert; } dc->regs = devm_regmap_init_mmio(dev, regs, &vs_dc_regmap_cfg); if (IS_ERR(dc->regs)) { ret = PTR_ERR(dc->regs); goto err_rst_assert; } ret = vs_fill_chip_identity(dc->regs, &dc->identity); if (ret) goto err_rst_assert; dev_info(dev, "Found DC%x rev %x customer %x\n", dc->identity.model, dc->identity.revision, dc->identity.customer_id); if (port_count > dc->identity.display_count) { dev_err(dev, "too many downstream ports than HW capability\n"); ret = -EINVAL; goto err_rst_assert; } for (i = 0; i < dc->identity.display_count; i++) { snprintf(pixclk_name, sizeof(pixclk_name), "pix%u", i); dc->pix_clk[i] = devm_clk_get(dev, pixclk_name); if (IS_ERR(dc->pix_clk[i])) { dev_err(dev, "can't get pixel clk %u\n", i); ret = PTR_ERR(dc->pix_clk[i]); goto err_rst_assert; } } ret = devm_request_irq(dev, irq, vs_dc_irq_handler, 0, dev_name(dev), dc); if (ret) { dev_err(dev, "can't request irq\n"); goto err_rst_assert; } dev_set_drvdata(dev, dc); ret = vs_drm_initialize(dc, pdev); if (ret) goto err_rst_assert; return 0; err_rst_assert: reset_control_bulk_assert(VSDC_RESET_COUNT, dc->rsts); return ret; } static void vs_dc_remove(struct platform_device *pdev) { struct vs_dc *dc = dev_get_drvdata(&pdev->dev); vs_drm_finalize(dc); dev_set_drvdata(&pdev->dev, NULL); reset_control_bulk_assert(VSDC_RESET_COUNT, dc->rsts); } static void vs_dc_shutdown(struct platform_device *pdev) { struct vs_dc *dc = dev_get_drvdata(&pdev->dev); vs_drm_shutdown_handler(dc); } static struct platform_driver vs_dc_platform_driver = { .probe = vs_dc_probe, .remove = vs_dc_remove, .shutdown = vs_dc_shutdown, .driver = { .name = "verisilicon-dc", .of_match_table = vs_dc_driver_dt_match, }, }; module_platform_driver(vs_dc_platform_driver); MODULE_AUTHOR("Icenowy Zheng "); MODULE_DESCRIPTION("Verisilicon display controller driver"); MODULE_LICENSE("GPL");