diff options
Diffstat (limited to 'drivers/gpu/drm/bridge/imx/imx8mp-hdmi-tx.c')
| -rw-r--r-- | drivers/gpu/drm/bridge/imx/imx8mp-hdmi-tx.c | 87 |
1 files changed, 74 insertions, 13 deletions
diff --git a/drivers/gpu/drm/bridge/imx/imx8mp-hdmi-tx.c b/drivers/gpu/drm/bridge/imx/imx8mp-hdmi-tx.c index 13bc570c5473..32fd3554e267 100644 --- a/drivers/gpu/drm/bridge/imx/imx8mp-hdmi-tx.c +++ b/drivers/gpu/drm/bridge/imx/imx8mp-hdmi-tx.c @@ -5,11 +5,13 @@ */ #include <linux/clk.h> +#include <linux/component.h> #include <linux/mod_devicetable.h> #include <linux/module.h> #include <linux/platform_device.h> #include <drm/bridge/dw_hdmi.h> #include <drm/drm_modes.h> +#include <drm/drm_of.h> struct imx8mp_hdmi { struct dw_hdmi_plat_data plat_data; @@ -23,6 +25,7 @@ imx8mp_hdmi_mode_valid(struct dw_hdmi *dw_hdmi, void *data, const struct drm_display_mode *mode) { struct imx8mp_hdmi *hdmi = (struct imx8mp_hdmi *)data; + long round_rate; if (mode->clock < 13500) return MODE_CLOCK_LOW; @@ -30,8 +33,14 @@ imx8mp_hdmi_mode_valid(struct dw_hdmi *dw_hdmi, void *data, if (mode->clock > 297000) return MODE_CLOCK_HIGH; - if (clk_round_rate(hdmi->pixclk, mode->clock * 1000) != - mode->clock * 1000) + round_rate = clk_round_rate(hdmi->pixclk, mode->clock * 1000); + /* imx8mp's pixel clock generator (fsl-samsung-hdmi) cannot generate + * all possible frequencies, so allow some tolerance to support more + * modes. + * Allow 0.5% difference allowed in various standards (VESA, CEA861) + * 0.5% = 5/1000 tolerance (mode->clock is 1/1000) + */ + if (abs(round_rate - mode->clock * 1000) > mode->clock * 5) return MODE_CLOCK_RANGE; /* We don't support double-clocked and Interlaced modes */ @@ -72,10 +81,45 @@ static const struct dw_hdmi_phy_ops imx8mp_hdmi_phy_ops = { .update_hpd = dw_hdmi_phy_update_hpd, }; +static int imx8mp_dw_hdmi_bind(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct imx8mp_hdmi *hdmi = dev_get_drvdata(dev); + int ret; + + ret = component_bind_all(dev, &hdmi->plat_data); + if (ret) + return dev_err_probe(dev, ret, "component_bind_all failed!\n"); + + hdmi->dw_hdmi = dw_hdmi_probe(pdev, &hdmi->plat_data); + if (IS_ERR(hdmi->dw_hdmi)) { + component_unbind_all(dev, &hdmi->plat_data); + return PTR_ERR(hdmi->dw_hdmi); + } + + return 0; +} + +static void imx8mp_dw_hdmi_unbind(struct device *dev) +{ + struct imx8mp_hdmi *hdmi = dev_get_drvdata(dev); + + dw_hdmi_remove(hdmi->dw_hdmi); + + component_unbind_all(dev, &hdmi->plat_data); +} + +static const struct component_master_ops imx8mp_dw_hdmi_ops = { + .bind = imx8mp_dw_hdmi_bind, + .unbind = imx8mp_dw_hdmi_unbind, +}; + static int imx8mp_dw_hdmi_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct dw_hdmi_plat_data *plat_data; + struct component_match *match = NULL; + struct device_node *remote; struct imx8mp_hdmi *hdmi; hdmi = devm_kzalloc(dev, sizeof(*hdmi), GFP_KERNEL); @@ -95,28 +139,46 @@ static int imx8mp_dw_hdmi_probe(struct platform_device *pdev) plat_data->priv_data = hdmi; plat_data->phy_force_vendor = true; - hdmi->dw_hdmi = dw_hdmi_probe(pdev, plat_data); - if (IS_ERR(hdmi->dw_hdmi)) - return PTR_ERR(hdmi->dw_hdmi); - platform_set_drvdata(pdev, hdmi); + /* port@2 is for hdmi_pai device */ + remote = of_graph_get_remote_node(pdev->dev.of_node, 2, 0); + if (!remote) { + hdmi->dw_hdmi = dw_hdmi_probe(pdev, plat_data); + if (IS_ERR(hdmi->dw_hdmi)) + return PTR_ERR(hdmi->dw_hdmi); + } else { + drm_of_component_match_add(dev, &match, component_compare_of, remote); + + of_node_put(remote); + + return component_master_add_with_match(dev, &imx8mp_dw_hdmi_ops, match); + } + return 0; } static void imx8mp_dw_hdmi_remove(struct platform_device *pdev) { struct imx8mp_hdmi *hdmi = platform_get_drvdata(pdev); + struct device_node *remote; - dw_hdmi_remove(hdmi->dw_hdmi); + remote = of_graph_get_remote_node(pdev->dev.of_node, 2, 0); + if (remote) { + of_node_put(remote); + + component_master_del(&pdev->dev, &imx8mp_dw_hdmi_ops); + } else { + dw_hdmi_remove(hdmi->dw_hdmi); + } } -static int __maybe_unused imx8mp_dw_hdmi_pm_suspend(struct device *dev) +static int imx8mp_dw_hdmi_pm_suspend(struct device *dev) { return 0; } -static int __maybe_unused imx8mp_dw_hdmi_pm_resume(struct device *dev) +static int imx8mp_dw_hdmi_pm_resume(struct device *dev) { struct imx8mp_hdmi *hdmi = dev_get_drvdata(dev); @@ -126,8 +188,7 @@ static int __maybe_unused imx8mp_dw_hdmi_pm_resume(struct device *dev) } static const struct dev_pm_ops imx8mp_dw_hdmi_pm_ops = { - SET_SYSTEM_SLEEP_PM_OPS(imx8mp_dw_hdmi_pm_suspend, - imx8mp_dw_hdmi_pm_resume) + SYSTEM_SLEEP_PM_OPS(imx8mp_dw_hdmi_pm_suspend, imx8mp_dw_hdmi_pm_resume) }; static const struct of_device_id imx8mp_dw_hdmi_of_table[] = { @@ -138,11 +199,11 @@ MODULE_DEVICE_TABLE(of, imx8mp_dw_hdmi_of_table); static struct platform_driver imx8mp_dw_hdmi_platform_driver = { .probe = imx8mp_dw_hdmi_probe, - .remove_new = imx8mp_dw_hdmi_remove, + .remove = imx8mp_dw_hdmi_remove, .driver = { .name = "imx8mp-dw-hdmi-tx", .of_match_table = imx8mp_dw_hdmi_of_table, - .pm = &imx8mp_dw_hdmi_pm_ops, + .pm = pm_ptr(&imx8mp_dw_hdmi_pm_ops), }, }; |
