| .. | .. |
|---|
| 1 | +// SPDX-License-Identifier: GPL-2.0-only |
|---|
| 1 | 2 | /* |
|---|
| 2 | 3 | * Copyright (C) 2012 Russell King |
|---|
| 3 | | - * |
|---|
| 4 | | - * This program is free software; you can redistribute it and/or modify |
|---|
| 5 | | - * it under the terms of the GNU General Public License version 2 as |
|---|
| 6 | | - * published by the Free Software Foundation. |
|---|
| 7 | 4 | * |
|---|
| 8 | 5 | * Armada 510 (aka Dove) variant support |
|---|
| 9 | 6 | */ |
|---|
| 10 | 7 | #include <linux/clk.h> |
|---|
| 11 | 8 | #include <linux/io.h> |
|---|
| 12 | | -#include <drm/drm_crtc_helper.h> |
|---|
| 9 | +#include <drm/drm_probe_helper.h> |
|---|
| 13 | 10 | #include "armada_crtc.h" |
|---|
| 14 | 11 | #include "armada_drm.h" |
|---|
| 15 | 12 | #include "armada_hw.h" |
|---|
| 16 | 13 | |
|---|
| 14 | +struct armada510_variant_data { |
|---|
| 15 | + struct clk *clks[4]; |
|---|
| 16 | + struct clk *sel_clk; |
|---|
| 17 | +}; |
|---|
| 18 | + |
|---|
| 17 | 19 | static int armada510_crtc_init(struct armada_crtc *dcrtc, struct device *dev) |
|---|
| 18 | 20 | { |
|---|
| 21 | + struct armada510_variant_data *v; |
|---|
| 19 | 22 | struct clk *clk; |
|---|
| 23 | + int idx; |
|---|
| 20 | 24 | |
|---|
| 21 | | - clk = devm_clk_get(dev, "ext_ref_clk1"); |
|---|
| 22 | | - if (IS_ERR(clk)) |
|---|
| 23 | | - return PTR_ERR(clk) == -ENOENT ? -EPROBE_DEFER : PTR_ERR(clk); |
|---|
| 25 | + v = devm_kzalloc(dev, sizeof(*v), GFP_KERNEL); |
|---|
| 26 | + if (!v) |
|---|
| 27 | + return -ENOMEM; |
|---|
| 24 | 28 | |
|---|
| 25 | | - dcrtc->extclk[0] = clk; |
|---|
| 29 | + dcrtc->variant_data = v; |
|---|
| 26 | 30 | |
|---|
| 27 | | - /* Lower the watermark so to eliminate jitter at higher bandwidths */ |
|---|
| 28 | | - armada_updatel(0x20, (1 << 11) | 0xff, dcrtc->base + LCD_CFG_RDREG4F); |
|---|
| 31 | + if (dev->of_node) { |
|---|
| 32 | + struct property *prop; |
|---|
| 33 | + const char *s; |
|---|
| 34 | + |
|---|
| 35 | + of_property_for_each_string(dev->of_node, "clock-names", prop, |
|---|
| 36 | + s) { |
|---|
| 37 | + if (!strcmp(s, "ext_ref_clk0")) |
|---|
| 38 | + idx = 0; |
|---|
| 39 | + else if (!strcmp(s, "ext_ref_clk1")) |
|---|
| 40 | + idx = 1; |
|---|
| 41 | + else if (!strcmp(s, "plldivider")) |
|---|
| 42 | + idx = 2; |
|---|
| 43 | + else if (!strcmp(s, "axibus")) |
|---|
| 44 | + idx = 3; |
|---|
| 45 | + else |
|---|
| 46 | + continue; |
|---|
| 47 | + |
|---|
| 48 | + clk = devm_clk_get(dev, s); |
|---|
| 49 | + if (IS_ERR(clk)) |
|---|
| 50 | + return PTR_ERR(clk) == -ENOENT ? -EPROBE_DEFER : |
|---|
| 51 | + PTR_ERR(clk); |
|---|
| 52 | + v->clks[idx] = clk; |
|---|
| 53 | + } |
|---|
| 54 | + } else { |
|---|
| 55 | + clk = devm_clk_get(dev, "ext_ref_clk1"); |
|---|
| 56 | + if (IS_ERR(clk)) |
|---|
| 57 | + return PTR_ERR(clk) == -ENOENT ? -EPROBE_DEFER : |
|---|
| 58 | + PTR_ERR(clk); |
|---|
| 59 | + |
|---|
| 60 | + v->clks[1] = clk; |
|---|
| 61 | + } |
|---|
| 62 | + |
|---|
| 63 | + /* |
|---|
| 64 | + * Lower the watermark so to eliminate jitter at higher bandwidths. |
|---|
| 65 | + * Disable SRAM read wait state to avoid system hang with external |
|---|
| 66 | + * clock. |
|---|
| 67 | + */ |
|---|
| 68 | + armada_updatel(CFG_DMA_WM(0x20), CFG_SRAM_WAIT | CFG_DMA_WM_MASK, |
|---|
| 69 | + dcrtc->base + LCD_CFG_RDREG4F); |
|---|
| 29 | 70 | |
|---|
| 30 | 71 | /* Initialise SPU register */ |
|---|
| 31 | 72 | writel_relaxed(ADV_HWC32ENABLE | ADV_HWC32ARGB | ADV_HWC32BLEND, |
|---|
| .. | .. |
|---|
| 34 | 75 | return 0; |
|---|
| 35 | 76 | } |
|---|
| 36 | 77 | |
|---|
| 78 | +static const u32 armada510_clk_sels[] = { |
|---|
| 79 | + SCLK_510_EXTCLK0, |
|---|
| 80 | + SCLK_510_EXTCLK1, |
|---|
| 81 | + SCLK_510_PLL, |
|---|
| 82 | + SCLK_510_AXI, |
|---|
| 83 | +}; |
|---|
| 84 | + |
|---|
| 85 | +static const struct armada_clocking_params armada510_clocking = { |
|---|
| 86 | + /* HDMI requires -0.6%..+0.5% */ |
|---|
| 87 | + .permillage_min = 994, |
|---|
| 88 | + .permillage_max = 1005, |
|---|
| 89 | + .settable = BIT(0) | BIT(1), |
|---|
| 90 | + .div_max = SCLK_510_INT_DIV_MASK, |
|---|
| 91 | +}; |
|---|
| 92 | + |
|---|
| 37 | 93 | /* |
|---|
| 38 | 94 | * Armada510 specific SCLK register selection. |
|---|
| 39 | 95 | * This gets called with sclk = NULL to test whether the mode is |
|---|
| 40 | 96 | * supportable, and again with sclk != NULL to set the clocks up for |
|---|
| 41 | 97 | * that. The former can return an error, but the latter is expected |
|---|
| 42 | 98 | * not to. |
|---|
| 43 | | - * |
|---|
| 44 | | - * We currently are pretty rudimentary here, always selecting |
|---|
| 45 | | - * EXT_REF_CLK_1 for LCD0 and erroring LCD1. This needs improvement! |
|---|
| 46 | 99 | */ |
|---|
| 47 | 100 | static int armada510_crtc_compute_clock(struct armada_crtc *dcrtc, |
|---|
| 48 | 101 | const struct drm_display_mode *mode, uint32_t *sclk) |
|---|
| 49 | 102 | { |
|---|
| 50 | | - struct clk *clk = dcrtc->extclk[0]; |
|---|
| 51 | | - int ret; |
|---|
| 103 | + struct armada510_variant_data *v = dcrtc->variant_data; |
|---|
| 104 | + unsigned long desired_khz = mode->crtc_clock; |
|---|
| 105 | + struct armada_clk_result res; |
|---|
| 106 | + int ret, idx; |
|---|
| 52 | 107 | |
|---|
| 53 | | - if (dcrtc->num == 1) |
|---|
| 54 | | - return -EINVAL; |
|---|
| 108 | + idx = armada_crtc_select_clock(dcrtc, &res, &armada510_clocking, |
|---|
| 109 | + v->clks, ARRAY_SIZE(v->clks), |
|---|
| 110 | + desired_khz); |
|---|
| 111 | + if (idx < 0) |
|---|
| 112 | + return idx; |
|---|
| 55 | 113 | |
|---|
| 56 | | - if (IS_ERR(clk)) |
|---|
| 57 | | - return PTR_ERR(clk); |
|---|
| 58 | | - |
|---|
| 59 | | - if (dcrtc->clk != clk) { |
|---|
| 60 | | - ret = clk_prepare_enable(clk); |
|---|
| 61 | | - if (ret) |
|---|
| 62 | | - return ret; |
|---|
| 63 | | - dcrtc->clk = clk; |
|---|
| 64 | | - } |
|---|
| 114 | + ret = clk_prepare_enable(res.clk); |
|---|
| 115 | + if (ret) |
|---|
| 116 | + return ret; |
|---|
| 65 | 117 | |
|---|
| 66 | 118 | if (sclk) { |
|---|
| 67 | | - uint32_t rate, ref, div; |
|---|
| 119 | + clk_set_rate(res.clk, res.desired_clk_hz); |
|---|
| 68 | 120 | |
|---|
| 69 | | - rate = mode->clock * 1000; |
|---|
| 70 | | - ref = clk_round_rate(clk, rate); |
|---|
| 71 | | - div = DIV_ROUND_UP(ref, rate); |
|---|
| 72 | | - if (div < 1) |
|---|
| 73 | | - div = 1; |
|---|
| 121 | + *sclk = res.div | armada510_clk_sels[idx]; |
|---|
| 74 | 122 | |
|---|
| 75 | | - clk_set_rate(clk, ref); |
|---|
| 76 | | - *sclk = div | SCLK_510_EXTCLK1; |
|---|
| 123 | + /* We are now using this clock */ |
|---|
| 124 | + v->sel_clk = res.clk; |
|---|
| 125 | + swap(dcrtc->clk, res.clk); |
|---|
| 77 | 126 | } |
|---|
| 127 | + |
|---|
| 128 | + clk_disable_unprepare(res.clk); |
|---|
| 78 | 129 | |
|---|
| 79 | 130 | return 0; |
|---|
| 80 | 131 | } |
|---|
| 81 | 132 | |
|---|
| 82 | 133 | static void armada510_crtc_disable(struct armada_crtc *dcrtc) |
|---|
| 83 | 134 | { |
|---|
| 84 | | - if (!IS_ERR(dcrtc->clk)) { |
|---|
| 135 | + if (dcrtc->clk) { |
|---|
| 85 | 136 | clk_disable_unprepare(dcrtc->clk); |
|---|
| 86 | | - dcrtc->clk = ERR_PTR(-EINVAL); |
|---|
| 137 | + dcrtc->clk = NULL; |
|---|
| 87 | 138 | } |
|---|
| 88 | 139 | } |
|---|
| 89 | 140 | |
|---|
| 90 | 141 | static void armada510_crtc_enable(struct armada_crtc *dcrtc, |
|---|
| 91 | 142 | const struct drm_display_mode *mode) |
|---|
| 92 | 143 | { |
|---|
| 93 | | - if (IS_ERR(dcrtc->clk)) { |
|---|
| 94 | | - dcrtc->clk = dcrtc->extclk[0]; |
|---|
| 95 | | - WARN_ON(clk_prepare_enable(dcrtc->clk)); |
|---|
| 144 | + struct armada510_variant_data *v = dcrtc->variant_data; |
|---|
| 145 | + |
|---|
| 146 | + if (!dcrtc->clk && v->sel_clk) { |
|---|
| 147 | + if (!WARN_ON(clk_prepare_enable(v->sel_clk))) |
|---|
| 148 | + dcrtc->clk = v->sel_clk; |
|---|
| 96 | 149 | } |
|---|
| 97 | 150 | } |
|---|
| 98 | 151 | |
|---|