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