Allwinner H3 features a TV encoder similar to the one in earlier SoCs,
but with some different points about clocks:
- It has a mod clock and a bus clock.
- The mod clock must be at a fixed rate to generate signal.

Add support for it.

Signed-off-by: Icenowy Zheng <icen...@aosc.io>
---
 drivers/gpu/drm/sun4i/sun4i_tv.c | 65 +++++++++++++++++++++++++++++++++++++---
 1 file changed, 61 insertions(+), 4 deletions(-)

diff --git a/drivers/gpu/drm/sun4i/sun4i_tv.c b/drivers/gpu/drm/sun4i/sun4i_tv.c
index a9cad00d4ee8..c9943103f499 100644
--- a/drivers/gpu/drm/sun4i/sun4i_tv.c
+++ b/drivers/gpu/drm/sun4i/sun4i_tv.c
@@ -13,6 +13,7 @@
 #include <linux/clk.h>
 #include <linux/component.h>
 #include <linux/of_address.h>
+#include <linux/of_device.h>
 #include <linux/regmap.h>
 #include <linux/reset.h>
 
@@ -169,14 +170,23 @@ struct tv_mode {
        const struct resync_parameters  *resync_params;
 };
 
+struct sun4i_tv_quirks {
+       bool has_mod_clk;
+       bool fixed_clock;
+       unsigned long fixed_clock_rate;
+};
+
 struct sun4i_tv {
        struct drm_connector    connector;
        struct drm_encoder      encoder;
 
        struct clk              *clk;
+       struct clk              *mod_clk;
        struct regmap           *regs;
        struct reset_control    *reset;
 
+       const struct sun4i_tv_quirks *quirks;
+
        struct sun4i_drv        *drv;
 };
 
@@ -578,6 +588,10 @@ static int sun4i_tv_bind(struct device *dev, struct device 
*master,
        tv->drv = drv;
        dev_set_drvdata(dev, tv);
 
+       tv->quirks = of_device_get_match_data(dev);
+       if (!tv->quirks)
+               return -EINVAL;
+
        res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
        regs = devm_ioremap_resource(dev, res);
        if (IS_ERR(regs)) {
@@ -604,7 +618,10 @@ static int sun4i_tv_bind(struct device *dev, struct device 
*master,
                return ret;
        }
 
-       tv->clk = devm_clk_get(dev, NULL);
+       if (tv->quirks->has_mod_clk)
+               tv->clk = devm_clk_get(dev, "bus");
+       else
+               tv->clk = devm_clk_get(dev, NULL);
        if (IS_ERR(tv->clk)) {
                dev_err(dev, "Couldn't get the TV encoder clock\n");
                ret = PTR_ERR(tv->clk);
@@ -612,6 +629,26 @@ static int sun4i_tv_bind(struct device *dev, struct device 
*master,
        }
        clk_prepare_enable(tv->clk);
 
+       if (tv->quirks->has_mod_clk) {
+               tv->mod_clk = devm_clk_get(dev, "mod");
+               if (IS_ERR(tv->mod_clk)) {
+                       dev_err(dev, "Couldn't get the TV encoder mod clock\n");
+                       ret = PTR_ERR(tv->mod_clk);
+                       goto err_disable_clk;
+               };
+
+               if (tv->quirks->fixed_clock) {
+                       ret = clk_set_rate(tv->mod_clk,
+                                          tv->quirks->fixed_clock_rate);
+                       if (ret) {
+                               dev_err(dev, "Couldn't set TV encoder mod clock 
rate\n");
+                               goto err_disable_clk;
+                       }
+               }
+
+               clk_prepare_enable(tv->mod_clk);
+       }
+
        drm_encoder_helper_add(&tv->encoder,
                               &sun4i_tv_helper_funcs);
        ret = drm_encoder_init(drm,
@@ -621,14 +658,14 @@ static int sun4i_tv_bind(struct device *dev, struct 
device *master,
                               NULL);
        if (ret) {
                dev_err(dev, "Couldn't initialise the TV encoder\n");
-               goto err_disable_clk;
+               goto err_disable_mod_clk;
        }
 
        tv->encoder.possible_crtcs = drm_of_find_possible_crtcs(drm,
                                                                dev->of_node);
        if (!tv->encoder.possible_crtcs) {
                ret = -EPROBE_DEFER;
-               goto err_disable_clk;
+               goto err_disable_mod_clk;
        }
 
        drm_connector_helper_add(&tv->connector,
@@ -649,6 +686,9 @@ static int sun4i_tv_bind(struct device *dev, struct device 
*master,
 
 err_cleanup_connector:
        drm_encoder_cleanup(&tv->encoder);
+err_disable_mod_clk:
+       if (tv->quirks->has_mod_clk)
+               clk_disable_unprepare(tv->mod_clk);
 err_disable_clk:
        clk_disable_unprepare(tv->clk);
 err_assert_reset:
@@ -683,8 +723,25 @@ static int sun4i_tv_remove(struct platform_device *pdev)
        return 0;
 }
 
+static const struct sun4i_tv_quirks sun4i_a10_tv_quirks = {
+       /* Nothing special */
+};
+
+static const struct sun4i_tv_quirks sun8i_h3_tv_quirks = {
+       .has_mod_clk = true,
+       .fixed_clock = true,
+       .fixed_clock_rate = 216000000UL,
+};
+
 static const struct of_device_id sun4i_tv_of_table[] = {
-       { .compatible = "allwinner,sun4i-a10-tv-encoder" },
+       {
+               .compatible = "allwinner,sun4i-a10-tv-encoder",
+               .data = &sun4i_a10_tv_quirks,
+       },
+       {
+               .compatible = "allwinner,sun8i-h3-tv-encoder",
+               .data = &sun8i_h3_tv_quirks,
+       },
        { }
 };
 MODULE_DEVICE_TABLE(of, sun4i_tv_of_table);
-- 
2.12.2

Reply via email to