1d2912cb1SThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only
2f76ee892STomi Valkeinen /*
3f76ee892STomi Valkeinen  * HDMI PHY
4f76ee892STomi Valkeinen  *
5f76ee892STomi Valkeinen  * Copyright (C) 2013 Texas Instruments Incorporated
6f76ee892STomi Valkeinen  */
7f76ee892STomi Valkeinen 
8f76ee892STomi Valkeinen #include <linux/kernel.h>
9f76ee892STomi Valkeinen #include <linux/err.h>
10f76ee892STomi Valkeinen #include <linux/io.h>
11f76ee892STomi Valkeinen #include <linux/platform_device.h>
12f76ee892STomi Valkeinen #include <linux/slab.h>
13b9058afcSTomi Valkeinen #include <linux/seq_file.h>
14b9058afcSTomi Valkeinen 
1562d9e44eSPeter Ujfalusi #include <video/omapfb_dss.h>
16f76ee892STomi Valkeinen 
17f76ee892STomi Valkeinen #include "dss.h"
18f76ee892STomi Valkeinen #include "hdmi.h"
19f76ee892STomi Valkeinen 
20f76ee892STomi Valkeinen struct hdmi_phy_features {
21f76ee892STomi Valkeinen 	bool bist_ctrl;
22f76ee892STomi Valkeinen 	bool ldo_voltage;
23f76ee892STomi Valkeinen 	unsigned long max_phy;
24f76ee892STomi Valkeinen };
25f76ee892STomi Valkeinen 
26f76ee892STomi Valkeinen static const struct hdmi_phy_features *phy_feat;
27f76ee892STomi Valkeinen 
hdmi_phy_dump(struct hdmi_phy_data * phy,struct seq_file * s)28f76ee892STomi Valkeinen void hdmi_phy_dump(struct hdmi_phy_data *phy, struct seq_file *s)
29f76ee892STomi Valkeinen {
30f76ee892STomi Valkeinen #define DUMPPHY(r) seq_printf(s, "%-35s %08x\n", #r,\
31f76ee892STomi Valkeinen 		hdmi_read_reg(phy->base, r))
32f76ee892STomi Valkeinen 
33f76ee892STomi Valkeinen 	DUMPPHY(HDMI_TXPHY_TX_CTRL);
34f76ee892STomi Valkeinen 	DUMPPHY(HDMI_TXPHY_DIGITAL_CTRL);
35f76ee892STomi Valkeinen 	DUMPPHY(HDMI_TXPHY_POWER_CTRL);
36f76ee892STomi Valkeinen 	DUMPPHY(HDMI_TXPHY_PAD_CFG_CTRL);
37f76ee892STomi Valkeinen 	if (phy_feat->bist_ctrl)
38f76ee892STomi Valkeinen 		DUMPPHY(HDMI_TXPHY_BIST_CONTROL);
39f76ee892STomi Valkeinen }
40f76ee892STomi Valkeinen 
hdmi_phy_parse_lanes(struct hdmi_phy_data * phy,const u32 * lanes)41f76ee892STomi Valkeinen int hdmi_phy_parse_lanes(struct hdmi_phy_data *phy, const u32 *lanes)
42f76ee892STomi Valkeinen {
43f76ee892STomi Valkeinen 	int i;
44f76ee892STomi Valkeinen 
45f76ee892STomi Valkeinen 	for (i = 0; i < 8; i += 2) {
46f76ee892STomi Valkeinen 		u8 lane, pol;
47f76ee892STomi Valkeinen 		int dx, dy;
48f76ee892STomi Valkeinen 
49f76ee892STomi Valkeinen 		dx = lanes[i];
50f76ee892STomi Valkeinen 		dy = lanes[i + 1];
51f76ee892STomi Valkeinen 
52f76ee892STomi Valkeinen 		if (dx < 0 || dx >= 8)
53f76ee892STomi Valkeinen 			return -EINVAL;
54f76ee892STomi Valkeinen 
55f76ee892STomi Valkeinen 		if (dy < 0 || dy >= 8)
56f76ee892STomi Valkeinen 			return -EINVAL;
57f76ee892STomi Valkeinen 
58f76ee892STomi Valkeinen 		if (dx & 1) {
59f76ee892STomi Valkeinen 			if (dy != dx - 1)
60f76ee892STomi Valkeinen 				return -EINVAL;
61f76ee892STomi Valkeinen 			pol = 1;
62f76ee892STomi Valkeinen 		} else {
63f76ee892STomi Valkeinen 			if (dy != dx + 1)
64f76ee892STomi Valkeinen 				return -EINVAL;
65f76ee892STomi Valkeinen 			pol = 0;
66f76ee892STomi Valkeinen 		}
67f76ee892STomi Valkeinen 
68f76ee892STomi Valkeinen 		lane = dx / 2;
69f76ee892STomi Valkeinen 
70f76ee892STomi Valkeinen 		phy->lane_function[lane] = i / 2;
71f76ee892STomi Valkeinen 		phy->lane_polarity[lane] = pol;
72f76ee892STomi Valkeinen 	}
73f76ee892STomi Valkeinen 
74f76ee892STomi Valkeinen 	return 0;
75f76ee892STomi Valkeinen }
76f76ee892STomi Valkeinen 
hdmi_phy_configure_lanes(struct hdmi_phy_data * phy)77f76ee892STomi Valkeinen static void hdmi_phy_configure_lanes(struct hdmi_phy_data *phy)
78f76ee892STomi Valkeinen {
79f76ee892STomi Valkeinen 	static const u16 pad_cfg_list[] = {
80f76ee892STomi Valkeinen 		0x0123,
81f76ee892STomi Valkeinen 		0x0132,
82f76ee892STomi Valkeinen 		0x0312,
83f76ee892STomi Valkeinen 		0x0321,
84f76ee892STomi Valkeinen 		0x0231,
85f76ee892STomi Valkeinen 		0x0213,
86f76ee892STomi Valkeinen 		0x1023,
87f76ee892STomi Valkeinen 		0x1032,
88f76ee892STomi Valkeinen 		0x3012,
89f76ee892STomi Valkeinen 		0x3021,
90f76ee892STomi Valkeinen 		0x2031,
91f76ee892STomi Valkeinen 		0x2013,
92f76ee892STomi Valkeinen 		0x1203,
93f76ee892STomi Valkeinen 		0x1302,
94f76ee892STomi Valkeinen 		0x3102,
95f76ee892STomi Valkeinen 		0x3201,
96f76ee892STomi Valkeinen 		0x2301,
97f76ee892STomi Valkeinen 		0x2103,
98f76ee892STomi Valkeinen 		0x1230,
99f76ee892STomi Valkeinen 		0x1320,
100f76ee892STomi Valkeinen 		0x3120,
101f76ee892STomi Valkeinen 		0x3210,
102f76ee892STomi Valkeinen 		0x2310,
103f76ee892STomi Valkeinen 		0x2130,
104f76ee892STomi Valkeinen 	};
105f76ee892STomi Valkeinen 
106f76ee892STomi Valkeinen 	u16 lane_cfg = 0;
107f76ee892STomi Valkeinen 	int i;
108f76ee892STomi Valkeinen 	unsigned lane_cfg_val;
109f76ee892STomi Valkeinen 	u16 pol_val = 0;
110f76ee892STomi Valkeinen 
111f76ee892STomi Valkeinen 	for (i = 0; i < 4; ++i)
112f76ee892STomi Valkeinen 		lane_cfg |= phy->lane_function[i] << ((3 - i) * 4);
113f76ee892STomi Valkeinen 
114f76ee892STomi Valkeinen 	pol_val |= phy->lane_polarity[0] << 0;
115f76ee892STomi Valkeinen 	pol_val |= phy->lane_polarity[1] << 3;
116f76ee892STomi Valkeinen 	pol_val |= phy->lane_polarity[2] << 2;
117f76ee892STomi Valkeinen 	pol_val |= phy->lane_polarity[3] << 1;
118f76ee892STomi Valkeinen 
119f76ee892STomi Valkeinen 	for (i = 0; i < ARRAY_SIZE(pad_cfg_list); ++i)
120f76ee892STomi Valkeinen 		if (pad_cfg_list[i] == lane_cfg)
121f76ee892STomi Valkeinen 			break;
122f76ee892STomi Valkeinen 
123f76ee892STomi Valkeinen 	if (WARN_ON(i == ARRAY_SIZE(pad_cfg_list)))
124f76ee892STomi Valkeinen 		i = 0;
125f76ee892STomi Valkeinen 
126f76ee892STomi Valkeinen 	lane_cfg_val = i;
127f76ee892STomi Valkeinen 
128f76ee892STomi Valkeinen 	REG_FLD_MOD(phy->base, HDMI_TXPHY_PAD_CFG_CTRL, lane_cfg_val, 26, 22);
129f76ee892STomi Valkeinen 	REG_FLD_MOD(phy->base, HDMI_TXPHY_PAD_CFG_CTRL, pol_val, 30, 27);
130f76ee892STomi Valkeinen }
131f76ee892STomi Valkeinen 
hdmi_phy_configure(struct hdmi_phy_data * phy,unsigned long hfbitclk,unsigned long lfbitclk)132f76ee892STomi Valkeinen int hdmi_phy_configure(struct hdmi_phy_data *phy, unsigned long hfbitclk,
133f76ee892STomi Valkeinen 	unsigned long lfbitclk)
134f76ee892STomi Valkeinen {
135f76ee892STomi Valkeinen 	u8 freqout;
136f76ee892STomi Valkeinen 
137f76ee892STomi Valkeinen 	/*
138f76ee892STomi Valkeinen 	 * Read address 0 in order to get the SCP reset done completed
139f76ee892STomi Valkeinen 	 * Dummy access performed to make sure reset is done
140f76ee892STomi Valkeinen 	 */
141f76ee892STomi Valkeinen 	hdmi_read_reg(phy->base, HDMI_TXPHY_TX_CTRL);
142f76ee892STomi Valkeinen 
143f76ee892STomi Valkeinen 	/*
144f76ee892STomi Valkeinen 	 * In OMAP5+, the HFBITCLK must be divided by 2 before issuing the
145f76ee892STomi Valkeinen 	 * HDMI_PHYPWRCMD_LDOON command.
146f76ee892STomi Valkeinen 	 */
147f76ee892STomi Valkeinen 	if (phy_feat->bist_ctrl)
148f76ee892STomi Valkeinen 		REG_FLD_MOD(phy->base, HDMI_TXPHY_BIST_CONTROL, 1, 11, 11);
149f76ee892STomi Valkeinen 
150f76ee892STomi Valkeinen 	/*
151f76ee892STomi Valkeinen 	 * If the hfbitclk != lfbitclk, it means the lfbitclk was configured
152f76ee892STomi Valkeinen 	 * to be used for TMDS.
153f76ee892STomi Valkeinen 	 */
154f76ee892STomi Valkeinen 	if (hfbitclk != lfbitclk)
155f76ee892STomi Valkeinen 		freqout = 0;
156f76ee892STomi Valkeinen 	else if (hfbitclk / 10 < phy_feat->max_phy)
157f76ee892STomi Valkeinen 		freqout = 1;
158f76ee892STomi Valkeinen 	else
159f76ee892STomi Valkeinen 		freqout = 2;
160f76ee892STomi Valkeinen 
161f76ee892STomi Valkeinen 	/*
162f76ee892STomi Valkeinen 	 * Write to phy address 0 to configure the clock
163f76ee892STomi Valkeinen 	 * use HFBITCLK write HDMI_TXPHY_TX_CONTROL_FREQOUT field
164f76ee892STomi Valkeinen 	 */
165f76ee892STomi Valkeinen 	REG_FLD_MOD(phy->base, HDMI_TXPHY_TX_CTRL, freqout, 31, 30);
166f76ee892STomi Valkeinen 
167f76ee892STomi Valkeinen 	/* Write to phy address 1 to start HDMI line (TXVALID and TMDSCLKEN) */
168f76ee892STomi Valkeinen 	hdmi_write_reg(phy->base, HDMI_TXPHY_DIGITAL_CTRL, 0xF0000000);
169f76ee892STomi Valkeinen 
170f76ee892STomi Valkeinen 	/* Setup max LDO voltage */
171f76ee892STomi Valkeinen 	if (phy_feat->ldo_voltage)
172f76ee892STomi Valkeinen 		REG_FLD_MOD(phy->base, HDMI_TXPHY_POWER_CTRL, 0xB, 3, 0);
173f76ee892STomi Valkeinen 
174f76ee892STomi Valkeinen 	hdmi_phy_configure_lanes(phy);
175f76ee892STomi Valkeinen 
176f76ee892STomi Valkeinen 	return 0;
177f76ee892STomi Valkeinen }
178f76ee892STomi Valkeinen 
179f76ee892STomi Valkeinen static const struct hdmi_phy_features omap44xx_phy_feats = {
180f76ee892STomi Valkeinen 	.bist_ctrl	=	false,
181f76ee892STomi Valkeinen 	.ldo_voltage	=	true,
182f76ee892STomi Valkeinen 	.max_phy	=	185675000,
183f76ee892STomi Valkeinen };
184f76ee892STomi Valkeinen 
185f76ee892STomi Valkeinen static const struct hdmi_phy_features omap54xx_phy_feats = {
186f76ee892STomi Valkeinen 	.bist_ctrl	=	true,
187f76ee892STomi Valkeinen 	.ldo_voltage	=	false,
188f76ee892STomi Valkeinen 	.max_phy	=	186000000,
189f76ee892STomi Valkeinen };
190f76ee892STomi Valkeinen 
hdmi_phy_get_features(void)191ee334e00SLadislav Michl static const struct hdmi_phy_features *hdmi_phy_get_features(void)
192f76ee892STomi Valkeinen {
193f76ee892STomi Valkeinen 	switch (omapdss_get_version()) {
194f76ee892STomi Valkeinen 	case OMAPDSS_VER_OMAP4430_ES1:
195f76ee892STomi Valkeinen 	case OMAPDSS_VER_OMAP4430_ES2:
196f76ee892STomi Valkeinen 	case OMAPDSS_VER_OMAP4:
197ee334e00SLadislav Michl 		return &omap44xx_phy_feats;
198f76ee892STomi Valkeinen 
199f76ee892STomi Valkeinen 	case OMAPDSS_VER_OMAP5:
200f76ee892STomi Valkeinen 	case OMAPDSS_VER_DRA7xx:
201ee334e00SLadislav Michl 		return &omap54xx_phy_feats;
202f76ee892STomi Valkeinen 
203f76ee892STomi Valkeinen 	default:
204ee334e00SLadislav Michl 		return NULL;
205f76ee892STomi Valkeinen 	}
206f76ee892STomi Valkeinen }
207f76ee892STomi Valkeinen 
hdmi_phy_init(struct platform_device * pdev,struct hdmi_phy_data * phy)208f76ee892STomi Valkeinen int hdmi_phy_init(struct platform_device *pdev, struct hdmi_phy_data *phy)
209f76ee892STomi Valkeinen {
210ee334e00SLadislav Michl 	phy_feat = hdmi_phy_get_features();
211ee334e00SLadislav Michl 	if (!phy_feat)
212ee334e00SLadislav Michl 		return -ENODEV;
213f76ee892STomi Valkeinen 
214*f215d600SQilong Zhang 	phy->base = devm_platform_ioremap_resource_byname(pdev, "phy");
215f76ee892STomi Valkeinen 	if (IS_ERR(phy->base)) {
216f76ee892STomi Valkeinen 		DSSERR("can't ioremap TX PHY\n");
217f76ee892STomi Valkeinen 		return PTR_ERR(phy->base);
218f76ee892STomi Valkeinen 	}
219f76ee892STomi Valkeinen 
220f76ee892STomi Valkeinen 	return 0;
221f76ee892STomi Valkeinen }
222