/* SPDX-License-Identifier: GPL-2.0 */ /* * Copyright (c) 2019 MediaTek Inc. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* LVDS TOP */ #define LVDSTOP_REG00 0x000 #define LVDSTOP_REG01 0x004 #define LVDSTOP_REG02 0x008 #define LVDSTOP_REG03 0x00c #define LVDSTOP_REG04 0x010 #define LVDSTOP_REG05 0x014 #define RG_LVDS_CLKDIV_CTRL (0xf << 23) #define RG_FIFO_CTRL (0x7 << 20) #define RG_FIFO_EN (3 << 16) /* PATTERN GEN */ #define PATGEN_REG00 0x604 #define PATGEN_REG01 0x608 #define PATGEN_REG02 0x60c #define PATGEN_REG03 0x610 #define PATGEN_REG04 0x614 #define PATGEN_REG05 0x618 #define PATGEN_REG06 0x620 #define CFG_REG00 0x700 #define DETECT_REG0 0x704 #define DETECT_REG1 0x708 #define DETECT_REG2 0x70c #define TD_CTRL_MON 0x714 #define CRC_CHECK_REG0 0x738 #define CRC_CHECK_REG1 0x73c #define CRC_CHECK_REG2 0x740 #define DETECT_REG3 0x750 #define DETECT_REG4 0x754 #define MODE0 0x800 #define LVDS_CTRL 0x814 #define ANA_TEST 0x824 #define LVDS_CTRL00 0xa00 #define RG_NS_VESA_EN BIT(1) #define RG_DUAL BIT(12) #define LVDS_CTRL01 0xa04 #define LVDS_CTRL02 0xa08 #define CRC0 0xa10 #define CRC1 0xa14 #define CRC2 0xa18 #define CRC3 0xa1c #define LVDS_TEST01 0xa30 #define LVDS_TEST02 0xa34 #define CRC4 0xa38 #define CRC5 0xa3c #define CRC6 0xa40 #define LVDSTX_REG00 0xa80 #define LLV_DO_SEL 0x904 #define CKO_SEL 0x90c #define PN_SWAP 0x930 #define LVDS_CRC0 0x934 #define LVDS_CRC1 0x938 #define LVDS_CRC2 0x93c #define LVDS_CRC3 0x940 #define LVDS_CRC4 0x944 #define LVDS_CRC5 0x948 #define LVDS_CRC6 0x94c #define LVDS_MAX_LANE_CNT 8 struct mtk_lvds { struct drm_bridge bridge; struct drm_connector connector; struct drm_panel *panel; struct device *dev; struct phy *phy; struct clk *pix_clk_gate; struct clk *clkts_clk_gate; struct clk *lvdsdpi_sel; struct clk *lvds_d1; struct clk *lvds_d2; struct drm_display_mode mode; u32 lane_count; void __iomem *regs; bool powered; bool enabled; bool is_dual; }; static inline struct mtk_lvds * bridge_to_lvds(struct drm_bridge *bridge) { return container_of(bridge, struct mtk_lvds, bridge); } static inline struct mtk_lvds * connector_to_lvds(struct drm_connector *connector) { return container_of(connector, struct mtk_lvds, connector); } static enum drm_connector_status mtk_lvds_connector_detect( struct drm_connector *connector, bool force) { struct mtk_lvds *lvds = connector_to_lvds(connector); if (lvds->panel) return connector_status_connected; return connector_status_disconnected; } static const struct drm_connector_funcs mtk_lvds_connector_funcs = { /* .dpms = drm_atomic_helper_connector_dpms, */ .detect = mtk_lvds_connector_detect, .fill_modes = drm_helper_probe_single_connector_modes, .destroy = drm_connector_cleanup, .reset = drm_atomic_helper_connector_reset, .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, }; static int mtk_lvds_connector_get_modes(struct drm_connector *connector) { struct mtk_lvds *lvds = connector_to_lvds(connector); return drm_panel_get_modes(lvds->panel); } static struct drm_encoder *mtk_lvds_connector_best_encoder( struct drm_connector *connector) { struct mtk_lvds *lvds = connector_to_lvds(connector); return lvds->bridge.encoder; } static const struct drm_connector_helper_funcs mtk_lvds_connector_helper_funcs = { .get_modes = mtk_lvds_connector_get_modes, .best_encoder = mtk_lvds_connector_best_encoder, }; static bool mtk_lvds_bridge_mode_fixup(struct drm_bridge *bridge, const struct drm_display_mode *mode, struct drm_display_mode *adjusted_mode) { return true; } static int mtk_lvds_bridge_attach(struct drm_bridge *bridge) { struct mtk_lvds *lvds = bridge_to_lvds(bridge); int ret; if (!bridge->encoder) { DRM_ERROR("Parent encoder object not found"); return -ENODEV; } lvds->connector.polled = DRM_CONNECTOR_POLL_HPD; ret = drm_connector_init(bridge->dev, &lvds->connector, &mtk_lvds_connector_funcs, DRM_MODE_CONNECTOR_LVDS); if (ret) { DRM_ERROR("Failed to initialize connector with drm\n"); return ret; } drm_connector_helper_add(&lvds->connector, &mtk_lvds_connector_helper_funcs); drm_connector_attach_encoder(&lvds->connector, bridge->encoder); if (lvds->panel) drm_panel_attach(lvds->panel, &lvds->connector); return ret; } static void mtk_lvds_bridge_disable(struct drm_bridge *bridge) { struct mtk_lvds *lvds = bridge_to_lvds(bridge); int ret; if (!lvds->enabled) return; ret = pm_runtime_put_sync(lvds->dev); if (ret < 0) DRM_ERROR("Failed to disable power domain: %d\n", ret); if (drm_panel_disable(lvds->panel)) { DRM_ERROR("failed to disable panel\n"); return; } phy_power_off(lvds->phy); clk_disable_unprepare(lvds->clkts_clk_gate); clk_disable_unprepare(lvds->pix_clk_gate); clk_disable_unprepare(lvds->lvdsdpi_sel); lvds->enabled = false; } static void mtk_lvds_bridge_post_disable(struct drm_bridge *bridge) { struct mtk_lvds *lvds = bridge_to_lvds(bridge); if (!lvds->powered) return; if (drm_panel_unprepare(lvds->panel)) { DRM_ERROR("failed to unprepare panel\n"); return; } lvds->powered = false; } static void mtk_lvds_bridge_mode_set(struct drm_bridge *bridge, struct drm_display_mode *mode, struct drm_display_mode *adjusted_mode) { struct mtk_lvds *lvds = bridge_to_lvds(bridge); dev_dbg(lvds->dev, "cur info: name:%s, hdisplay:%d\n", adjusted_mode->name, adjusted_mode->hdisplay); dev_dbg(lvds->dev, "hsync_start:%d,hsync_end:%d, htotal:%d", adjusted_mode->hsync_start, adjusted_mode->hsync_end, adjusted_mode->htotal); dev_dbg(lvds->dev, "hskew:%d, vdisplay:%d\n", adjusted_mode->hskew, adjusted_mode->vdisplay); dev_dbg(lvds->dev, "vsync_start:%d, vsync_end:%d, vtotal:%d", adjusted_mode->vsync_start, adjusted_mode->vsync_end, adjusted_mode->vtotal); dev_dbg(lvds->dev, "vscan:%d, flag:%d\n", adjusted_mode->vscan, adjusted_mode->flags); drm_mode_copy(&lvds->mode, adjusted_mode); } static void mtk_lvds_pre_enable(struct drm_bridge *bridge) { struct mtk_lvds *lvds = bridge_to_lvds(bridge); if (lvds->powered) return; if (drm_panel_prepare(lvds->panel)) { DRM_ERROR("failed to prepare panel\n"); return; } lvds->powered = true; } static void mtk_lvds_bridge_enable(struct drm_bridge *bridge) { struct mtk_lvds *lvds = bridge_to_lvds(bridge); int ret; if (lvds->enabled) return; ret = pm_runtime_get_sync(lvds->dev); if (ret < 0) DRM_ERROR("Failed to enable power domain: %d\n", ret); ret = clk_prepare_enable(lvds->lvdsdpi_sel); if (ret) { dev_err(lvds->dev, "Failed to enable lvdsdpi_sel clock: %d\n", ret); return; } ret = clk_prepare_enable(lvds->pix_clk_gate); if (ret) { dev_err(lvds->dev, "Failed to enable pixel clock gate: %d\n", ret); clk_disable_unprepare(lvds->lvdsdpi_sel); return; } ret = clk_prepare_enable(lvds->clkts_clk_gate); if (ret) { dev_err(lvds->dev, "Failed to enable clkts clock gate: %d\n", ret); clk_disable_unprepare(lvds->pix_clk_gate); clk_disable_unprepare(lvds->lvdsdpi_sel); return; } phy_power_on(lvds->phy); writel((lvds->is_dual ? 3 : 2) << 16 | 3 << 20 | (lvds->is_dual ? 1 : 0) << 23, lvds->regs + LVDSTOP_REG05); writel((lvds->is_dual ? RG_DUAL : 0), lvds->regs + LVDS_CTRL00); writel(0x102ce4, lvds->regs + LVDS_CTRL02); if (drm_panel_enable(lvds->panel)) { DRM_ERROR("failed to enable panel\n"); phy_power_off(lvds->phy); clk_disable_unprepare(lvds->clkts_clk_gate); clk_disable_unprepare(lvds->pix_clk_gate); clk_disable_unprepare(lvds->lvdsdpi_sel); return; } lvds->enabled = true; } static const struct drm_bridge_funcs mtk_lvds_bridge_funcs = { .attach = mtk_lvds_bridge_attach, .mode_fixup = mtk_lvds_bridge_mode_fixup, .disable = mtk_lvds_bridge_disable, .post_disable = mtk_lvds_bridge_post_disable, .mode_set = mtk_lvds_bridge_mode_set, .pre_enable = mtk_lvds_pre_enable, .enable = mtk_lvds_bridge_enable, }; static int mtk_drm_lvds_probe(struct platform_device *pdev) { struct mtk_lvds *lvds; struct device *dev = &pdev->dev; struct device_node *port, *out_ep; struct device_node *panel_node = NULL; int ret; struct resource *mem; lvds = devm_kzalloc(dev, sizeof(*lvds), GFP_KERNEL); if (!lvds) return -ENOMEM; lvds->dev = dev; /* port@1 is lvds output port */ port = of_graph_get_port_by_id(dev->of_node, 1); if (port) { out_ep = of_get_child_by_name(port, "endpoint"); of_node_put(port); if (out_ep) { panel_node = of_graph_get_remote_port_parent(out_ep); of_node_put(out_ep); } } if (panel_node) { lvds->panel = of_drm_find_panel(panel_node); of_node_put(panel_node); if (!lvds->panel) return -EPROBE_DEFER; } lvds->is_dual = of_property_read_bool(dev->of_node, "mediatek,dual-channel"); mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); lvds->regs = devm_ioremap_resource(dev, mem); if (IS_ERR(lvds->regs)) { ret = PTR_ERR(lvds->regs); dev_err(dev, "Failed to ioremap mem resource: %d\n", ret); return ret; } lvds->pix_clk_gate = devm_clk_get(dev, "pixel"); if (IS_ERR(lvds->pix_clk_gate)) { ret = PTR_ERR(lvds->pix_clk_gate); dev_err(dev, "Failed to get pixel clock gate: %d\n", ret); return ret; } lvds->clkts_clk_gate = devm_clk_get(dev, "clkts"); if (IS_ERR(lvds->clkts_clk_gate)) { ret = PTR_ERR(lvds->clkts_clk_gate); dev_err(dev, "Failed to get clkts clock gate: %d\n", ret); return ret; } lvds->lvdsdpi_sel = devm_clk_get(dev, "lvdsdpi_sel"); if (IS_ERR(lvds->lvdsdpi_sel)) { ret = PTR_ERR(lvds->lvdsdpi_sel); dev_err(dev, "Failed to get lvdsdpi_sel clock: %d\n", ret); return ret; } lvds->lvds_d1 = devm_clk_get(dev, "lvds_d1"); if (IS_ERR(lvds->lvds_d1)) { ret = PTR_ERR(lvds->lvds_d1); dev_err(dev, "Failed to get lvds_d1 clock: %d\n", ret); return ret; } lvds->lvds_d2 = devm_clk_get(dev, "lvds_d2"); if (IS_ERR(lvds->lvds_d2)) { ret = PTR_ERR(lvds->lvds_d2); dev_err(dev, "Failed to get lvds_d2 clock: %d\n", ret); return ret; } if (lvds->is_dual) ret = clk_set_parent(lvds->lvdsdpi_sel, lvds->lvds_d2); else ret = clk_set_parent(lvds->lvdsdpi_sel, lvds->lvds_d1); if (ret < 0) { dev_err(&pdev->dev, "failed to clk_set_parent (%d)\n", ret); return ret; } ret = of_property_read_u32(dev->of_node, "lane-count", &lvds->lane_count); lvds->phy = devm_phy_get(dev, "lvds"); if (IS_ERR(lvds->phy)) { ret = PTR_ERR(lvds->phy); dev_err(dev, "Failed to get LVDS PHY: %d\n", ret); return ret; } platform_set_drvdata(pdev, lvds); lvds->bridge.funcs = &mtk_lvds_bridge_funcs; lvds->bridge.of_node = pdev->dev.of_node; ret = drm_bridge_add(&lvds->bridge); if (ret) { dev_err(dev, "failed to add bridge, ret = %d\n", ret); return ret; } pm_runtime_enable(dev); return 0; } static int mtk_drm_lvds_remove(struct platform_device *pdev) { struct mtk_lvds *lvds = platform_get_drvdata(pdev); pm_runtime_disable(&pdev->dev); drm_bridge_remove(&lvds->bridge); return 0; } static const struct of_device_id mtk_drm_lvds_of_ids[] = { { .compatible = "mediatek,mt8173-lvds", }, {} }; struct platform_driver mtk_lvds_driver = { .probe = mtk_drm_lvds_probe, .remove = mtk_drm_lvds_remove, .driver = { .name = "mediatek-drm-lvds", .of_match_table = mtk_drm_lvds_of_ids, }, };