hdmi_pll.c 5.1 KB
Newer Older
1 2 3 4 5 6 7 8 9 10
/*
 * HDMI PLL
 *
 * Copyright (C) 2013 Texas Instruments Incorporated
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 as published by
 * the Free Software Foundation.
 */

T
Tomi Valkeinen 已提交
11 12
#define DSS_SUBSYS_NAME "HDMIPLL"

13 14 15 16 17
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/err.h>
#include <linux/io.h>
#include <linux/platform_device.h>
18 19
#include <linux/clk.h>

20 21 22
#include <video/omapdss.h>

#include "dss.h"
23
#include "hdmi.h"
24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40

void hdmi_pll_dump(struct hdmi_pll_data *pll, struct seq_file *s)
{
#define DUMPPLL(r) seq_printf(s, "%-35s %08x\n", #r,\
		hdmi_read_reg(pll->base, r))

	DUMPPLL(PLLCTRL_PLL_CONTROL);
	DUMPPLL(PLLCTRL_PLL_STATUS);
	DUMPPLL(PLLCTRL_PLL_GO);
	DUMPPLL(PLLCTRL_CFG1);
	DUMPPLL(PLLCTRL_CFG2);
	DUMPPLL(PLLCTRL_CFG3);
	DUMPPLL(PLLCTRL_SSC_CFG1);
	DUMPPLL(PLLCTRL_SSC_CFG2);
	DUMPPLL(PLLCTRL_CFG4);
}

41 42
void hdmi_pll_compute(struct hdmi_pll_data *pll,
	unsigned long target_tmds, struct dss_pll_clock_info *pi)
43
{
44 45 46 47
	unsigned long fint, clkdco, clkout;
	unsigned long target_bitclk, target_clkdco;
	unsigned long min_dco;
	unsigned n, m, mf, m2, sd;
48 49 50 51
	unsigned long clkin;
	const struct dss_pll_hw *hw = pll->pll.hw;

	clkin = clk_get_rate(pll->pll.clkin);
52 53 54 55

	DSSDBG("clkin %lu, target tmds %lu\n", clkin, target_tmds);

	target_bitclk = target_tmds * 10;
56

57
	/* Fint */
58
	n = DIV_ROUND_UP(clkin, hw->fint_max);
59
	fint = clkin / n;
60

61
	/* adjust m2 so that the clkdco will be high enough */
62
	min_dco = roundup(hw->clkdco_min, fint);
63 64 65
	m2 = DIV_ROUND_UP(min_dco, target_bitclk);
	if (m2 == 0)
		m2 = 1;
66

67 68
	target_clkdco = target_bitclk * m2;
	m = target_clkdco / fint;
69

70
	clkdco = fint * m;
71

72 73 74
	/* adjust clkdco with fractional mf */
	if (WARN_ON(target_clkdco - clkdco > fint))
		mf = 0;
75
	else
76 77 78 79
		mf = (u32)div_u64(262144ull * (target_clkdco - clkdco), fint);

	if (mf > 0)
		clkdco += (u32)div_u64((u64)mf * fint, 262144);
80

81 82 83 84 85 86 87 88 89
	clkout = clkdco / m2;

	/* sigma-delta */
	sd = DIV_ROUND_UP(fint * m, 250000000);

	DSSDBG("N = %u, M = %u, M.f = %u, M2 = %u, SD = %u\n",
		n, m, mf, m2, sd);
	DSSDBG("Fint %lu, clkdco %lu, clkout %lu\n", fint, clkdco, clkout);

90 91 92 93 94
	pi->n = n;
	pi->m = m;
	pi->mf = mf;
	pi->mX[0] = m2;
	pi->sd = sd;
95

96
	pi->fint = fint;
97
	pi->clkdco = clkdco;
98
	pi->clkout[0] = clkout;
99 100
}

101
static int hdmi_pll_enable(struct dss_pll *dsspll)
102
{
103
	struct hdmi_pll_data *pll = container_of(dsspll, struct hdmi_pll_data, pll);
104
	struct hdmi_wp_data *wp = pll->wp;
105 106
	u16 r = 0;

107 108
	dss_ctrl_pll_enable(DSS_PLL_HDMI, true);

109 110 111 112 113 114 115
	r = hdmi_wp_set_pll_pwr(wp, HDMI_PLLPWRCMD_BOTHON_ALLCLKS);
	if (r)
		return r;

	return 0;
}

116
static void hdmi_pll_disable(struct dss_pll *dsspll)
117
{
118
	struct hdmi_pll_data *pll = container_of(dsspll, struct hdmi_pll_data, pll);
119 120
	struct hdmi_wp_data *wp = pll->wp;

121
	hdmi_wp_set_pll_pwr(wp, HDMI_PLLPWRCMD_ALLOFF);
122 123

	dss_ctrl_pll_enable(DSS_PLL_HDMI, false);
124 125
}

126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152
static const struct dss_pll_ops dsi_pll_ops = {
	.enable = hdmi_pll_enable,
	.disable = hdmi_pll_disable,
	.set_config = dss_pll_write_config_type_b,
};

static const struct dss_pll_hw dss_omap4_hdmi_pll_hw = {
	.n_max = 255,
	.m_min = 20,
	.m_max = 4095,
	.mX_max = 127,
	.fint_min = 500000,
	.fint_max = 2500000,

	.clkdco_min = 500000000,
	.clkdco_low = 1000000000,
	.clkdco_max = 2000000000,

	.n_msb = 8,
	.n_lsb = 1,
	.m_msb = 20,
	.m_lsb = 9,

	.mX_msb[0] = 24,
	.mX_lsb[0] = 18,

	.has_selfreqdco = true,
153 154
};

155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176
static const struct dss_pll_hw dss_omap5_hdmi_pll_hw = {
	.n_max = 255,
	.m_min = 20,
	.m_max = 2045,
	.mX_max = 127,
	.fint_min = 620000,
	.fint_max = 2500000,

	.clkdco_min = 750000000,
	.clkdco_low = 1500000000,
	.clkdco_max = 2500000000UL,

	.n_msb = 8,
	.n_lsb = 1,
	.m_msb = 20,
	.m_lsb = 9,

	.mX_msb[0] = 24,
	.mX_lsb[0] = 18,

	.has_selfreqdco = true,
	.has_refsel = true,
177 178
};

179
static int dsi_init_pll_data(struct platform_device *pdev, struct hdmi_pll_data *hpll)
180
{
181 182 183
	struct dss_pll *pll = &hpll->pll;
	struct clk *clk;
	int r;
184

185 186 187 188
	clk = devm_clk_get(&pdev->dev, "sys_clk");
	if (IS_ERR(clk)) {
		DSSERR("can't get sys_clk\n");
		return PTR_ERR(clk);
189 190
	}

191
	pll->name = "hdmi";
T
Tomi Valkeinen 已提交
192
	pll->id = DSS_PLL_HDMI;
193 194 195
	pll->base = hpll->base;
	pll->clkin = clk;

196 197 198 199
	switch (omapdss_get_version()) {
	case OMAPDSS_VER_OMAP4430_ES1:
	case OMAPDSS_VER_OMAP4430_ES2:
	case OMAPDSS_VER_OMAP4:
200
		pll->hw = &dss_omap4_hdmi_pll_hw;
201 202 203
		break;

	case OMAPDSS_VER_OMAP5:
204
	case OMAPDSS_VER_DRA7xx:
205
		pll->hw = &dss_omap5_hdmi_pll_hw;
206 207 208 209 210 211
		break;

	default:
		return -ENODEV;
	}

212 213 214 215 216
	pll->ops = &dsi_pll_ops;

	r = dss_pll_register(pll);
	if (r)
		return r;
217 218 219 220

	return 0;
}

221 222
int hdmi_pll_init(struct platform_device *pdev, struct hdmi_pll_data *pll,
	struct hdmi_wp_data *wp)
223
{
224
	int r;
225 226
	struct resource *res;

227 228
	pll->wp = wp;

229
	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "pll");
230
	if (!res) {
T
Tomi Valkeinen 已提交
231 232
		DSSERR("can't get PLL mem resource\n");
		return -EINVAL;
233 234
	}

T
Tomi Valkeinen 已提交
235
	pll->base = devm_ioremap_resource(&pdev->dev, res);
236
	if (IS_ERR(pll->base)) {
237
		DSSERR("can't ioremap PLLCTRL\n");
238
		return PTR_ERR(pll->base);
239 240
	}

241 242 243 244 245 246
	r = dsi_init_pll_data(pdev, pll);
	if (r) {
		DSSERR("failed to init HDMI PLL\n");
		return r;
	}

247 248
	return 0;
}
249 250 251 252 253 254 255

void hdmi_pll_uninit(struct hdmi_pll_data *hpll)
{
	struct dss_pll *pll = &hpll->pll;

	dss_pll_unregister(pll);
}