162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Copyright (C) 2012 Russell King
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Armada 510 (aka Dove) variant support
662306a36Sopenharmony_ci */
762306a36Sopenharmony_ci#include <linux/clk.h>
862306a36Sopenharmony_ci#include <linux/io.h>
962306a36Sopenharmony_ci#include <linux/of.h>
1062306a36Sopenharmony_ci#include <drm/drm_probe_helper.h>
1162306a36Sopenharmony_ci#include "armada_crtc.h"
1262306a36Sopenharmony_ci#include "armada_drm.h"
1362306a36Sopenharmony_ci#include "armada_hw.h"
1462306a36Sopenharmony_ci
1562306a36Sopenharmony_cistruct armada510_variant_data {
1662306a36Sopenharmony_ci	struct clk *clks[4];
1762306a36Sopenharmony_ci	struct clk *sel_clk;
1862306a36Sopenharmony_ci};
1962306a36Sopenharmony_ci
2062306a36Sopenharmony_cistatic int armada510_crtc_init(struct armada_crtc *dcrtc, struct device *dev)
2162306a36Sopenharmony_ci{
2262306a36Sopenharmony_ci	struct armada510_variant_data *v;
2362306a36Sopenharmony_ci	struct clk *clk;
2462306a36Sopenharmony_ci	int idx;
2562306a36Sopenharmony_ci
2662306a36Sopenharmony_ci	v = devm_kzalloc(dev, sizeof(*v), GFP_KERNEL);
2762306a36Sopenharmony_ci	if (!v)
2862306a36Sopenharmony_ci		return -ENOMEM;
2962306a36Sopenharmony_ci
3062306a36Sopenharmony_ci	dcrtc->variant_data = v;
3162306a36Sopenharmony_ci
3262306a36Sopenharmony_ci	if (dev->of_node) {
3362306a36Sopenharmony_ci		struct property *prop;
3462306a36Sopenharmony_ci		const char *s;
3562306a36Sopenharmony_ci
3662306a36Sopenharmony_ci		of_property_for_each_string(dev->of_node, "clock-names", prop,
3762306a36Sopenharmony_ci					    s) {
3862306a36Sopenharmony_ci			if (!strcmp(s, "ext_ref_clk0"))
3962306a36Sopenharmony_ci				idx = 0;
4062306a36Sopenharmony_ci			else if (!strcmp(s, "ext_ref_clk1"))
4162306a36Sopenharmony_ci				idx = 1;
4262306a36Sopenharmony_ci			else if (!strcmp(s, "plldivider"))
4362306a36Sopenharmony_ci				idx = 2;
4462306a36Sopenharmony_ci			else if (!strcmp(s, "axibus"))
4562306a36Sopenharmony_ci				idx = 3;
4662306a36Sopenharmony_ci			else
4762306a36Sopenharmony_ci				continue;
4862306a36Sopenharmony_ci
4962306a36Sopenharmony_ci			clk = devm_clk_get(dev, s);
5062306a36Sopenharmony_ci			if (IS_ERR(clk))
5162306a36Sopenharmony_ci				return PTR_ERR(clk) == -ENOENT ? -EPROBE_DEFER :
5262306a36Sopenharmony_ci					PTR_ERR(clk);
5362306a36Sopenharmony_ci			v->clks[idx] = clk;
5462306a36Sopenharmony_ci		}
5562306a36Sopenharmony_ci	} else {
5662306a36Sopenharmony_ci		clk = devm_clk_get(dev, "ext_ref_clk1");
5762306a36Sopenharmony_ci		if (IS_ERR(clk))
5862306a36Sopenharmony_ci			return PTR_ERR(clk) == -ENOENT ? -EPROBE_DEFER :
5962306a36Sopenharmony_ci				PTR_ERR(clk);
6062306a36Sopenharmony_ci
6162306a36Sopenharmony_ci		v->clks[1] = clk;
6262306a36Sopenharmony_ci	}
6362306a36Sopenharmony_ci
6462306a36Sopenharmony_ci	/*
6562306a36Sopenharmony_ci	 * Lower the watermark so to eliminate jitter at higher bandwidths.
6662306a36Sopenharmony_ci	 * Disable SRAM read wait state to avoid system hang with external
6762306a36Sopenharmony_ci	 * clock.
6862306a36Sopenharmony_ci	 */
6962306a36Sopenharmony_ci	armada_updatel(CFG_DMA_WM(0x20), CFG_SRAM_WAIT | CFG_DMA_WM_MASK,
7062306a36Sopenharmony_ci		       dcrtc->base + LCD_CFG_RDREG4F);
7162306a36Sopenharmony_ci
7262306a36Sopenharmony_ci	/* Initialise SPU register */
7362306a36Sopenharmony_ci	writel_relaxed(ADV_HWC32ENABLE | ADV_HWC32ARGB | ADV_HWC32BLEND,
7462306a36Sopenharmony_ci		       dcrtc->base + LCD_SPU_ADV_REG);
7562306a36Sopenharmony_ci
7662306a36Sopenharmony_ci	return 0;
7762306a36Sopenharmony_ci}
7862306a36Sopenharmony_ci
7962306a36Sopenharmony_cistatic const u32 armada510_clk_sels[] = {
8062306a36Sopenharmony_ci	SCLK_510_EXTCLK0,
8162306a36Sopenharmony_ci	SCLK_510_EXTCLK1,
8262306a36Sopenharmony_ci	SCLK_510_PLL,
8362306a36Sopenharmony_ci	SCLK_510_AXI,
8462306a36Sopenharmony_ci};
8562306a36Sopenharmony_ci
8662306a36Sopenharmony_cistatic const struct armada_clocking_params armada510_clocking = {
8762306a36Sopenharmony_ci	/* HDMI requires -0.6%..+0.5% */
8862306a36Sopenharmony_ci	.permillage_min = 994,
8962306a36Sopenharmony_ci	.permillage_max = 1005,
9062306a36Sopenharmony_ci	.settable = BIT(0) | BIT(1),
9162306a36Sopenharmony_ci	.div_max = SCLK_510_INT_DIV_MASK,
9262306a36Sopenharmony_ci};
9362306a36Sopenharmony_ci
9462306a36Sopenharmony_ci/*
9562306a36Sopenharmony_ci * Armada510 specific SCLK register selection.
9662306a36Sopenharmony_ci * This gets called with sclk = NULL to test whether the mode is
9762306a36Sopenharmony_ci * supportable, and again with sclk != NULL to set the clocks up for
9862306a36Sopenharmony_ci * that.  The former can return an error, but the latter is expected
9962306a36Sopenharmony_ci * not to.
10062306a36Sopenharmony_ci */
10162306a36Sopenharmony_cistatic int armada510_crtc_compute_clock(struct armada_crtc *dcrtc,
10262306a36Sopenharmony_ci	const struct drm_display_mode *mode, uint32_t *sclk)
10362306a36Sopenharmony_ci{
10462306a36Sopenharmony_ci	struct armada510_variant_data *v = dcrtc->variant_data;
10562306a36Sopenharmony_ci	unsigned long desired_khz = mode->crtc_clock;
10662306a36Sopenharmony_ci	struct armada_clk_result res;
10762306a36Sopenharmony_ci	int ret, idx;
10862306a36Sopenharmony_ci
10962306a36Sopenharmony_ci	idx = armada_crtc_select_clock(dcrtc, &res, &armada510_clocking,
11062306a36Sopenharmony_ci				       v->clks, ARRAY_SIZE(v->clks),
11162306a36Sopenharmony_ci				       desired_khz);
11262306a36Sopenharmony_ci	if (idx < 0)
11362306a36Sopenharmony_ci		return idx;
11462306a36Sopenharmony_ci
11562306a36Sopenharmony_ci	ret = clk_prepare_enable(res.clk);
11662306a36Sopenharmony_ci	if (ret)
11762306a36Sopenharmony_ci		return ret;
11862306a36Sopenharmony_ci
11962306a36Sopenharmony_ci	if (sclk) {
12062306a36Sopenharmony_ci		clk_set_rate(res.clk, res.desired_clk_hz);
12162306a36Sopenharmony_ci
12262306a36Sopenharmony_ci		*sclk = res.div | armada510_clk_sels[idx];
12362306a36Sopenharmony_ci
12462306a36Sopenharmony_ci		/* We are now using this clock */
12562306a36Sopenharmony_ci		v->sel_clk = res.clk;
12662306a36Sopenharmony_ci		swap(dcrtc->clk, res.clk);
12762306a36Sopenharmony_ci	}
12862306a36Sopenharmony_ci
12962306a36Sopenharmony_ci	clk_disable_unprepare(res.clk);
13062306a36Sopenharmony_ci
13162306a36Sopenharmony_ci	return 0;
13262306a36Sopenharmony_ci}
13362306a36Sopenharmony_ci
13462306a36Sopenharmony_cistatic void armada510_crtc_disable(struct armada_crtc *dcrtc)
13562306a36Sopenharmony_ci{
13662306a36Sopenharmony_ci	if (dcrtc->clk) {
13762306a36Sopenharmony_ci		clk_disable_unprepare(dcrtc->clk);
13862306a36Sopenharmony_ci		dcrtc->clk = NULL;
13962306a36Sopenharmony_ci	}
14062306a36Sopenharmony_ci}
14162306a36Sopenharmony_ci
14262306a36Sopenharmony_cistatic void armada510_crtc_enable(struct armada_crtc *dcrtc,
14362306a36Sopenharmony_ci	const struct drm_display_mode *mode)
14462306a36Sopenharmony_ci{
14562306a36Sopenharmony_ci	struct armada510_variant_data *v = dcrtc->variant_data;
14662306a36Sopenharmony_ci
14762306a36Sopenharmony_ci	if (!dcrtc->clk && v->sel_clk) {
14862306a36Sopenharmony_ci		if (!WARN_ON(clk_prepare_enable(v->sel_clk)))
14962306a36Sopenharmony_ci			dcrtc->clk = v->sel_clk;
15062306a36Sopenharmony_ci	}
15162306a36Sopenharmony_ci}
15262306a36Sopenharmony_ci
15362306a36Sopenharmony_ciconst struct armada_variant armada510_ops = {
15462306a36Sopenharmony_ci	.has_spu_adv_reg = true,
15562306a36Sopenharmony_ci	.init = armada510_crtc_init,
15662306a36Sopenharmony_ci	.compute_clock = armada510_crtc_compute_clock,
15762306a36Sopenharmony_ci	.disable = armada510_crtc_disable,
15862306a36Sopenharmony_ci	.enable = armada510_crtc_enable,
15962306a36Sopenharmony_ci};
160