summaryrefslogtreecommitdiff
path: root/drivers/gpu/drm/armada/armada_510.c
diff options
context:
space:
mode:
authorRussell King <rmk+kernel@armlinux.org.uk>2018-07-14 11:15:35 +0100
committerRussell King <rmk+kernel@armlinux.org.uk>2019-06-22 11:51:20 +0100
commit1ba246f2fd2f6e6647b29e362a7d8c235e7fe9a7 (patch)
tree9d3880930a57576f3bb192fe0288b0576e66cdbe /drivers/gpu/drm/armada/armada_510.c
parent7f07ce0f04e25cd41a85ee780dbe85b40c9e3482 (diff)
drm/armada: improve Dove clock selection
Improve the Dove (Armada 510) LCD clock selection and divider calculation, limiting to the valid divisor values, and reporting an error if the clock is not achievable within the bounds of HDMI clocking requirements. Signed-off-by: Russell King <rmk+kernel@armlinux.org.uk>
Diffstat (limited to 'drivers/gpu/drm/armada/armada_510.c')
-rw-r--r--drivers/gpu/drm/armada/armada_510.c121
1 files changed, 86 insertions, 35 deletions
diff --git a/drivers/gpu/drm/armada/armada_510.c b/drivers/gpu/drm/armada/armada_510.c
index 370c422f64e3..6f8ad8fb19f1 100644
--- a/drivers/gpu/drm/armada/armada_510.c
+++ b/drivers/gpu/drm/armada/armada_510.c
@@ -14,15 +14,54 @@
#include "armada_drm.h"
#include "armada_hw.h"
+struct armada510_variant_data {
+ struct clk *clks[4];
+ struct clk *sel_clk;
+};
+
static int armada510_crtc_init(struct armada_crtc *dcrtc, struct device *dev)
{
+ struct armada510_variant_data *v;
struct clk *clk;
-
- clk = devm_clk_get(dev, "ext_ref_clk1");
- if (IS_ERR(clk))
- return PTR_ERR(clk) == -ENOENT ? -EPROBE_DEFER : PTR_ERR(clk);
-
- dcrtc->extclk[0] = clk;
+ int idx;
+
+ v = devm_kzalloc(dev, sizeof(*v), GFP_KERNEL);
+ if (!v)
+ return -ENOMEM;
+
+ dcrtc->variant_data = v;
+
+ if (dev->of_node) {
+ struct property *prop;
+ const char *s;
+
+ of_property_for_each_string(dev->of_node, "clock-names", prop,
+ s) {
+ if (!strcmp(s, "ext_ref_clk0"))
+ idx = 0;
+ else if (!strcmp(s, "ext_ref_clk1"))
+ idx = 1;
+ else if (!strcmp(s, "plldivider"))
+ idx = 2;
+ else if (!strcmp(s, "axibus"))
+ idx = 3;
+ else
+ continue;
+
+ clk = devm_clk_get(dev, s);
+ if (IS_ERR(clk))
+ return PTR_ERR(clk) == -ENOENT ? -EPROBE_DEFER :
+ PTR_ERR(clk);
+ v->clks[idx] = clk;
+ }
+ } else {
+ clk = devm_clk_get(dev, "ext_ref_clk1");
+ if (IS_ERR(clk))
+ return PTR_ERR(clk) == -ENOENT ? -EPROBE_DEFER :
+ PTR_ERR(clk);
+
+ v->clks[1] = clk;
+ }
/*
* Lower the watermark so to eliminate jitter at higher bandwidths.
@@ -39,65 +78,77 @@ static int armada510_crtc_init(struct armada_crtc *dcrtc, struct device *dev)
return 0;
}
+static const u32 armada510_clk_sels[] = {
+ SCLK_510_EXTCLK0,
+ SCLK_510_EXTCLK1,
+ SCLK_510_PLL,
+ SCLK_510_AXI,
+};
+
+static const struct armada_clocking_params armada510_clocking = {
+ /* HDMI requires -0.6%..+0.5% */
+ .permillage_min = 994,
+ .permillage_max = 1005,
+ .settable = BIT(0) | BIT(1),
+ .div_max = SCLK_510_INT_DIV_MASK,
+};
+
/*
* Armada510 specific SCLK register selection.
* This gets called with sclk = NULL to test whether the mode is
* supportable, and again with sclk != NULL to set the clocks up for
* that. The former can return an error, but the latter is expected
* not to.
- *
- * We currently are pretty rudimentary here, always selecting
- * EXT_REF_CLK_1 for LCD0 and erroring LCD1. This needs improvement!
*/
static int armada510_crtc_compute_clock(struct armada_crtc *dcrtc,
const struct drm_display_mode *mode, uint32_t *sclk)
{
- struct clk *clk = dcrtc->extclk[0];
- int ret;
+ struct armada510_variant_data *v = dcrtc->variant_data;
+ unsigned long desired_khz = mode->crtc_clock;
+ struct armada_clk_result res;
+ int ret, idx;
- if (dcrtc->num == 1)
- return -EINVAL;
+ idx = armada_crtc_select_clock(dcrtc, &res, &armada510_clocking,
+ v->clks, ARRAY_SIZE(v->clks),
+ desired_khz);
+ if (idx < 0)
+ return idx;
- if (IS_ERR(clk))
- return PTR_ERR(clk);
-
- if (dcrtc->clk != clk) {
- ret = clk_prepare_enable(clk);
- if (ret)
- return ret;
- dcrtc->clk = clk;
- }
+ ret = clk_prepare_enable(res.clk);
+ if (ret)
+ return ret;
if (sclk) {
- uint32_t rate, ref, div;
+ clk_set_rate(res.clk, res.desired_clk_hz);
- rate = mode->clock * 1000;
- ref = clk_round_rate(clk, rate);
- div = DIV_ROUND_UP(ref, rate);
- if (div < 1)
- div = 1;
+ *sclk = res.div | armada510_clk_sels[idx];
- clk_set_rate(clk, ref);
- *sclk = div | SCLK_510_EXTCLK1;
+ /* We are now using this clock */
+ v->sel_clk = res.clk;
+ swap(dcrtc->clk, res.clk);
}
+ clk_disable_unprepare(res.clk);
+
return 0;
}
static void armada510_crtc_disable(struct armada_crtc *dcrtc)
{
- if (!IS_ERR(dcrtc->clk)) {
+ if (dcrtc->clk) {
clk_disable_unprepare(dcrtc->clk);
- dcrtc->clk = ERR_PTR(-EINVAL);
+ dcrtc->clk = NULL;
}
}
static void armada510_crtc_enable(struct armada_crtc *dcrtc,
const struct drm_display_mode *mode)
{
- if (IS_ERR(dcrtc->clk)) {
- dcrtc->clk = dcrtc->extclk[0];
- WARN_ON(clk_prepare_enable(dcrtc->clk));
+ struct armada510_variant_data *v = dcrtc->variant_data;
+
+ if (!dcrtc->clk && v->sel_clk) {
+ if (!WARN_ON(clk_prepare_enable(v->sel_clk)))
+ dcrtc->clk = v->sel_clk;
}
}