xref: /openbmc/linux/drivers/gpu/drm/omapdrm/dss/hdmi_phy.c (revision 03ab8e6297acd1bc0eedaa050e2a1635c576fd11)
1d2912cb1SThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only
29960aa7cSTomi Valkeinen /*
39960aa7cSTomi Valkeinen  * HDMI PHY
49960aa7cSTomi Valkeinen  *
51b409fdaSAlexander A. Klimov  * Copyright (C) 2013 Texas Instruments Incorporated - https://www.ti.com/
69960aa7cSTomi Valkeinen  */
79960aa7cSTomi Valkeinen 
89960aa7cSTomi Valkeinen #include <linux/kernel.h>
99960aa7cSTomi Valkeinen #include <linux/err.h>
109960aa7cSTomi Valkeinen #include <linux/io.h>
119960aa7cSTomi Valkeinen #include <linux/platform_device.h>
129960aa7cSTomi Valkeinen #include <linux/slab.h>
132d802453SArnd Bergmann #include <linux/seq_file.h>
149960aa7cSTomi Valkeinen 
1532043da7SPeter Ujfalusi #include "omapdss.h"
169960aa7cSTomi Valkeinen #include "dss.h"
179960aa7cSTomi Valkeinen #include "hdmi.h"
189960aa7cSTomi Valkeinen 
hdmi_phy_dump(struct hdmi_phy_data * phy,struct seq_file * s)199960aa7cSTomi Valkeinen void hdmi_phy_dump(struct hdmi_phy_data *phy, struct seq_file *s)
209960aa7cSTomi Valkeinen {
219960aa7cSTomi Valkeinen #define DUMPPHY(r) seq_printf(s, "%-35s %08x\n", #r,\
229960aa7cSTomi Valkeinen 		hdmi_read_reg(phy->base, r))
239960aa7cSTomi Valkeinen 
249960aa7cSTomi Valkeinen 	DUMPPHY(HDMI_TXPHY_TX_CTRL);
259960aa7cSTomi Valkeinen 	DUMPPHY(HDMI_TXPHY_DIGITAL_CTRL);
269960aa7cSTomi Valkeinen 	DUMPPHY(HDMI_TXPHY_POWER_CTRL);
279960aa7cSTomi Valkeinen 	DUMPPHY(HDMI_TXPHY_PAD_CFG_CTRL);
28cc219afaSLaurent Pinchart 	if (phy->features->bist_ctrl)
299960aa7cSTomi Valkeinen 		DUMPPHY(HDMI_TXPHY_BIST_CONTROL);
309960aa7cSTomi Valkeinen }
319960aa7cSTomi Valkeinen 
hdmi_phy_parse_lanes(struct hdmi_phy_data * phy,const u32 * lanes)329960aa7cSTomi Valkeinen int hdmi_phy_parse_lanes(struct hdmi_phy_data *phy, const u32 *lanes)
339960aa7cSTomi Valkeinen {
349960aa7cSTomi Valkeinen 	int i;
359960aa7cSTomi Valkeinen 
369960aa7cSTomi Valkeinen 	for (i = 0; i < 8; i += 2) {
379960aa7cSTomi Valkeinen 		u8 lane, pol;
389960aa7cSTomi Valkeinen 		int dx, dy;
399960aa7cSTomi Valkeinen 
409960aa7cSTomi Valkeinen 		dx = lanes[i];
419960aa7cSTomi Valkeinen 		dy = lanes[i + 1];
429960aa7cSTomi Valkeinen 
439960aa7cSTomi Valkeinen 		if (dx < 0 || dx >= 8)
449960aa7cSTomi Valkeinen 			return -EINVAL;
459960aa7cSTomi Valkeinen 
469960aa7cSTomi Valkeinen 		if (dy < 0 || dy >= 8)
479960aa7cSTomi Valkeinen 			return -EINVAL;
489960aa7cSTomi Valkeinen 
499960aa7cSTomi Valkeinen 		if (dx & 1) {
509960aa7cSTomi Valkeinen 			if (dy != dx - 1)
519960aa7cSTomi Valkeinen 				return -EINVAL;
529960aa7cSTomi Valkeinen 			pol = 1;
539960aa7cSTomi Valkeinen 		} else {
549960aa7cSTomi Valkeinen 			if (dy != dx + 1)
559960aa7cSTomi Valkeinen 				return -EINVAL;
569960aa7cSTomi Valkeinen 			pol = 0;
579960aa7cSTomi Valkeinen 		}
589960aa7cSTomi Valkeinen 
599960aa7cSTomi Valkeinen 		lane = dx / 2;
609960aa7cSTomi Valkeinen 
619960aa7cSTomi Valkeinen 		phy->lane_function[lane] = i / 2;
629960aa7cSTomi Valkeinen 		phy->lane_polarity[lane] = pol;
639960aa7cSTomi Valkeinen 	}
649960aa7cSTomi Valkeinen 
659960aa7cSTomi Valkeinen 	return 0;
669960aa7cSTomi Valkeinen }
679960aa7cSTomi Valkeinen 
hdmi_phy_configure_lanes(struct hdmi_phy_data * phy)689960aa7cSTomi Valkeinen static void hdmi_phy_configure_lanes(struct hdmi_phy_data *phy)
699960aa7cSTomi Valkeinen {
709960aa7cSTomi Valkeinen 	static const u16 pad_cfg_list[] = {
719960aa7cSTomi Valkeinen 		0x0123,
729960aa7cSTomi Valkeinen 		0x0132,
739960aa7cSTomi Valkeinen 		0x0312,
749960aa7cSTomi Valkeinen 		0x0321,
759960aa7cSTomi Valkeinen 		0x0231,
769960aa7cSTomi Valkeinen 		0x0213,
779960aa7cSTomi Valkeinen 		0x1023,
789960aa7cSTomi Valkeinen 		0x1032,
799960aa7cSTomi Valkeinen 		0x3012,
809960aa7cSTomi Valkeinen 		0x3021,
819960aa7cSTomi Valkeinen 		0x2031,
829960aa7cSTomi Valkeinen 		0x2013,
839960aa7cSTomi Valkeinen 		0x1203,
849960aa7cSTomi Valkeinen 		0x1302,
859960aa7cSTomi Valkeinen 		0x3102,
869960aa7cSTomi Valkeinen 		0x3201,
879960aa7cSTomi Valkeinen 		0x2301,
889960aa7cSTomi Valkeinen 		0x2103,
899960aa7cSTomi Valkeinen 		0x1230,
909960aa7cSTomi Valkeinen 		0x1320,
919960aa7cSTomi Valkeinen 		0x3120,
929960aa7cSTomi Valkeinen 		0x3210,
939960aa7cSTomi Valkeinen 		0x2310,
949960aa7cSTomi Valkeinen 		0x2130,
959960aa7cSTomi Valkeinen 	};
969960aa7cSTomi Valkeinen 
979960aa7cSTomi Valkeinen 	u16 lane_cfg = 0;
989960aa7cSTomi Valkeinen 	int i;
99d11e5c82SLaurent Pinchart 	unsigned int lane_cfg_val;
1009960aa7cSTomi Valkeinen 	u16 pol_val = 0;
1019960aa7cSTomi Valkeinen 
1029960aa7cSTomi Valkeinen 	for (i = 0; i < 4; ++i)
1039960aa7cSTomi Valkeinen 		lane_cfg |= phy->lane_function[i] << ((3 - i) * 4);
1049960aa7cSTomi Valkeinen 
1059960aa7cSTomi Valkeinen 	pol_val |= phy->lane_polarity[0] << 0;
1069960aa7cSTomi Valkeinen 	pol_val |= phy->lane_polarity[1] << 3;
1079960aa7cSTomi Valkeinen 	pol_val |= phy->lane_polarity[2] << 2;
1089960aa7cSTomi Valkeinen 	pol_val |= phy->lane_polarity[3] << 1;
1099960aa7cSTomi Valkeinen 
1109960aa7cSTomi Valkeinen 	for (i = 0; i < ARRAY_SIZE(pad_cfg_list); ++i)
1119960aa7cSTomi Valkeinen 		if (pad_cfg_list[i] == lane_cfg)
1129960aa7cSTomi Valkeinen 			break;
1139960aa7cSTomi Valkeinen 
1149960aa7cSTomi Valkeinen 	if (WARN_ON(i == ARRAY_SIZE(pad_cfg_list)))
1159960aa7cSTomi Valkeinen 		i = 0;
1169960aa7cSTomi Valkeinen 
1179960aa7cSTomi Valkeinen 	lane_cfg_val = i;
1189960aa7cSTomi Valkeinen 
1199960aa7cSTomi Valkeinen 	REG_FLD_MOD(phy->base, HDMI_TXPHY_PAD_CFG_CTRL, lane_cfg_val, 26, 22);
1209960aa7cSTomi Valkeinen 	REG_FLD_MOD(phy->base, HDMI_TXPHY_PAD_CFG_CTRL, pol_val, 30, 27);
1219960aa7cSTomi Valkeinen }
1229960aa7cSTomi Valkeinen 
hdmi_phy_configure(struct hdmi_phy_data * phy,unsigned long hfbitclk,unsigned long lfbitclk)1239960aa7cSTomi Valkeinen int hdmi_phy_configure(struct hdmi_phy_data *phy, unsigned long hfbitclk,
1249960aa7cSTomi Valkeinen 	unsigned long lfbitclk)
1259960aa7cSTomi Valkeinen {
1269960aa7cSTomi Valkeinen 	u8 freqout;
1279960aa7cSTomi Valkeinen 
1289960aa7cSTomi Valkeinen 	/*
1299960aa7cSTomi Valkeinen 	 * Read address 0 in order to get the SCP reset done completed
1309960aa7cSTomi Valkeinen 	 * Dummy access performed to make sure reset is done
1319960aa7cSTomi Valkeinen 	 */
1329960aa7cSTomi Valkeinen 	hdmi_read_reg(phy->base, HDMI_TXPHY_TX_CTRL);
1339960aa7cSTomi Valkeinen 
1349960aa7cSTomi Valkeinen 	/*
1359960aa7cSTomi Valkeinen 	 * In OMAP5+, the HFBITCLK must be divided by 2 before issuing the
1369960aa7cSTomi Valkeinen 	 * HDMI_PHYPWRCMD_LDOON command.
1379960aa7cSTomi Valkeinen 	*/
138cc219afaSLaurent Pinchart 	if (phy->features->bist_ctrl)
1399960aa7cSTomi Valkeinen 		REG_FLD_MOD(phy->base, HDMI_TXPHY_BIST_CONTROL, 1, 11, 11);
1409960aa7cSTomi Valkeinen 
1419960aa7cSTomi Valkeinen 	/*
1429960aa7cSTomi Valkeinen 	 * If the hfbitclk != lfbitclk, it means the lfbitclk was configured
1439960aa7cSTomi Valkeinen 	 * to be used for TMDS.
1449960aa7cSTomi Valkeinen 	 */
1459960aa7cSTomi Valkeinen 	if (hfbitclk != lfbitclk)
1469960aa7cSTomi Valkeinen 		freqout = 0;
147cc219afaSLaurent Pinchart 	else if (hfbitclk / 10 < phy->features->max_phy)
1489960aa7cSTomi Valkeinen 		freqout = 1;
1499960aa7cSTomi Valkeinen 	else
1509960aa7cSTomi Valkeinen 		freqout = 2;
1519960aa7cSTomi Valkeinen 
1529960aa7cSTomi Valkeinen 	/*
1539960aa7cSTomi Valkeinen 	 * Write to phy address 0 to configure the clock
1549960aa7cSTomi Valkeinen 	 * use HFBITCLK write HDMI_TXPHY_TX_CONTROL_FREQOUT field
1559960aa7cSTomi Valkeinen 	 */
1569960aa7cSTomi Valkeinen 	REG_FLD_MOD(phy->base, HDMI_TXPHY_TX_CTRL, freqout, 31, 30);
1579960aa7cSTomi Valkeinen 
1589960aa7cSTomi Valkeinen 	/* Write to phy address 1 to start HDMI line (TXVALID and TMDSCLKEN) */
1599960aa7cSTomi Valkeinen 	hdmi_write_reg(phy->base, HDMI_TXPHY_DIGITAL_CTRL, 0xF0000000);
1609960aa7cSTomi Valkeinen 
1619960aa7cSTomi Valkeinen 	/* Setup max LDO voltage */
162cc219afaSLaurent Pinchart 	if (phy->features->ldo_voltage)
1639960aa7cSTomi Valkeinen 		REG_FLD_MOD(phy->base, HDMI_TXPHY_POWER_CTRL, 0xB, 3, 0);
1649960aa7cSTomi Valkeinen 
1659960aa7cSTomi Valkeinen 	hdmi_phy_configure_lanes(phy);
1669960aa7cSTomi Valkeinen 
1679960aa7cSTomi Valkeinen 	return 0;
1689960aa7cSTomi Valkeinen }
1699960aa7cSTomi Valkeinen 
1709960aa7cSTomi Valkeinen static const struct hdmi_phy_features omap44xx_phy_feats = {
1719960aa7cSTomi Valkeinen 	.bist_ctrl	=	false,
1729960aa7cSTomi Valkeinen 	.ldo_voltage	=	true,
1739960aa7cSTomi Valkeinen 	.max_phy	=	185675000,
1749960aa7cSTomi Valkeinen };
1759960aa7cSTomi Valkeinen 
1769960aa7cSTomi Valkeinen static const struct hdmi_phy_features omap54xx_phy_feats = {
1779960aa7cSTomi Valkeinen 	.bist_ctrl	=	true,
1789960aa7cSTomi Valkeinen 	.ldo_voltage	=	false,
1799960aa7cSTomi Valkeinen 	.max_phy	=	186000000,
1809960aa7cSTomi Valkeinen };
1819960aa7cSTomi Valkeinen 
hdmi_phy_init(struct platform_device * pdev,struct hdmi_phy_data * phy,unsigned int version)18237ea27b9SLaurent Pinchart int hdmi_phy_init(struct platform_device *pdev, struct hdmi_phy_data *phy,
18337ea27b9SLaurent Pinchart 		  unsigned int version)
1849960aa7cSTomi Valkeinen {
185863f9cdeSLaurent Pinchart 	if (version == 4)
186863f9cdeSLaurent Pinchart 		phy->features = &omap44xx_phy_feats;
187863f9cdeSLaurent Pinchart 	else
188863f9cdeSLaurent Pinchart 		phy->features = &omap54xx_phy_feats;
1899960aa7cSTomi Valkeinen 
190*ed8414abSCai Huoqing 	phy->base = devm_platform_ioremap_resource_byname(pdev, "phy");
191b22622f0SLaurent Pinchart 	if (IS_ERR(phy->base))
1929960aa7cSTomi Valkeinen 		return PTR_ERR(phy->base);
1939960aa7cSTomi Valkeinen 
1949960aa7cSTomi Valkeinen 	return 0;
1959960aa7cSTomi Valkeinen }
196