455 lines
14 KiB
Python
Executable File
455 lines
14 KiB
Python
Executable File
#!/usr/bin/env python2
|
|
# Copyright 2015 The Chromium OS Authors. All rights reserved.
|
|
# Use of this source code is governed by a BSD-style license that can be
|
|
# found in the LICENSE file.
|
|
|
|
import sys
|
|
|
|
# Things seem to magically change in the tables at these TMDS rates.
|
|
# Specifically looking at NO pixel repetition in the table:
|
|
#
|
|
# 0 - 44.9 - output divider is 0b11
|
|
# 49.5 - 90.0 - output divider is 0b10
|
|
# 94.5 - 182.75 - output divider is 0b01
|
|
# 185.625 - - output divider is 0b00
|
|
#
|
|
# You can also notice that MPLL charge pump settings change at similar times.
|
|
|
|
RATE1 = 46000000
|
|
RATE2 = 92000000
|
|
RATE3 = 184000000
|
|
|
|
|
|
def make_mpll(rate, depth, pixel_rep=0):
|
|
assert pixel_rep == 0, "Untested with non-zero pixel rep and probably wrong"
|
|
|
|
tmds = (rate * depth) / 8. * (pixel_rep + 1)
|
|
|
|
if depth == 8:
|
|
prep_div = 0
|
|
elif depth == 10:
|
|
prep_div = 1
|
|
elif depth == 12:
|
|
prep_div = 2
|
|
elif depth == 16:
|
|
prep_div = 3
|
|
|
|
# Rates higher than 340MHz are HDMI 2.0
|
|
# From tables, tmdsmhl_cntrl is 100% correlated with HDMI 1.4 vs 2.0
|
|
if tmds <= 340000000:
|
|
opmode = 0 # HDMI 1.4
|
|
tmdsmhl_cntrl = 0x0
|
|
else:
|
|
opmode = 1 # HDMI 2.0
|
|
tmdsmhl_cntrl = 0x3
|
|
|
|
# Keep the rate within the proper range with the output divider control
|
|
if tmds <= RATE1:
|
|
n_cntrl = 0x3 # output divider: 0b11
|
|
elif tmds <= RATE2:
|
|
n_cntrl = 0x2 # output divider: 0b10
|
|
elif tmds <= RATE3:
|
|
n_cntrl = 0x1 # output divider: 0b01
|
|
else:
|
|
n_cntrl = 0x0 # output divider: 0b00
|
|
|
|
# Need to make the dividers work out
|
|
#
|
|
# This could be done algorithmically, but let's not for now. We show the
|
|
# math to make this work out below as an assert.
|
|
if n_cntrl == 0x3:
|
|
if depth == 8:
|
|
fbdiv2_cntrl = 0x2 # feedback div1: / 2 (no +1)
|
|
fbdiv1_cntrl = 0x3 # feedback div2: / 4
|
|
ref_cntrl = 0x0 # input divider: / 1
|
|
elif depth == 10:
|
|
fbdiv2_cntrl = 0x5 # feedback div1: / 5 (no +1)
|
|
fbdiv1_cntrl = 0x1 # feedback div2: / 2
|
|
ref_cntrl = 0x0 # input divider: / 1
|
|
elif depth == 12:
|
|
fbdiv2_cntrl = 0x3 # feedback div1: / 3 (no +1)
|
|
fbdiv1_cntrl = 0x3 # feedback div2: / 4
|
|
ref_cntrl = 0x0 # input divider: / 1
|
|
elif depth == 16:
|
|
# Guess:
|
|
fbdiv2_cntrl = 0x4 # feedback div1: / 4 (no +1)
|
|
fbdiv1_cntrl = 0x3 # feedback div2: / 4
|
|
ref_cntrl = 0x0 # input divider: / 1
|
|
|
|
elif n_cntrl == 0x2:
|
|
if depth == 8:
|
|
fbdiv2_cntrl = 0x1 # feedback div1: / 1 (no +1)
|
|
fbdiv1_cntrl = 0x3 # feedback div2: / 4
|
|
ref_cntrl = 0x0 # input divider: / 1
|
|
elif depth == 10:
|
|
fbdiv2_cntrl = 0x5 # feedback div1: / 5 (no +1)
|
|
fbdiv1_cntrl = 0x0 # feedback div2: / 1
|
|
ref_cntrl = 0x0 # input divider: / 1
|
|
elif depth == 12:
|
|
fbdiv2_cntrl = 0x2 # feedback div1: / 2 (no +1)
|
|
fbdiv1_cntrl = 0x2 # feedback div2: / 3
|
|
ref_cntrl = 0x0 # input divider: / 1
|
|
elif depth == 16:
|
|
fbdiv2_cntrl = 0x2 # feedback div1: / 2 (no +1)
|
|
fbdiv1_cntrl = 0x3 # feedback div2: / 4
|
|
ref_cntrl = 0x0 # input divider: / 1
|
|
|
|
elif n_cntrl == 0x1:
|
|
if depth == 8:
|
|
fbdiv2_cntrl = 0x1 # feedback div1: / 1 (no +1)
|
|
fbdiv1_cntrl = 0x1 # feedback div2: / 2
|
|
ref_cntrl = 0x0 # input divider: / 1
|
|
elif depth == 10:
|
|
fbdiv2_cntrl = 0x5 # feedback div1: / 5 (no +1)
|
|
fbdiv1_cntrl = 0x0 # feedback div2: / 1
|
|
ref_cntrl = 0x1 # input divider: / 2
|
|
elif depth == 12:
|
|
fbdiv2_cntrl = 0x1 # feedback div1: / 1 (no +1)
|
|
fbdiv1_cntrl = 0x2 # feedback div2: / 3
|
|
ref_cntrl = 0x0 # input divider: / 1
|
|
elif depth == 16:
|
|
fbdiv2_cntrl = 0x1 # feedback div1: / 1 (no +1)
|
|
fbdiv1_cntrl = 0x3 # feedback div2: / 4
|
|
ref_cntrl = 0x0 # input divider: / 1
|
|
|
|
elif n_cntrl == 0x0:
|
|
if depth == 8:
|
|
fbdiv2_cntrl = 0x1 # feedback div1: / 1 (no +1)
|
|
fbdiv1_cntrl = 0x0 # feedback div2: / 1
|
|
ref_cntrl = 0x0 # input divider: / 1
|
|
elif depth == 10:
|
|
fbdiv2_cntrl = 0x5 # feedback div1: / 5 (no +1)
|
|
fbdiv1_cntrl = 0x0 # feedback div2: / 1
|
|
ref_cntrl = 0x3 # input divider: / 4
|
|
elif depth == 12:
|
|
fbdiv2_cntrl = 0x1 # feedback div1: / 1 (no +1)
|
|
fbdiv1_cntrl = 0x2 # feedback div2: / 3
|
|
ref_cntrl = 0x1 # input divider: / 2
|
|
elif depth == 16:
|
|
fbdiv2_cntrl = 0x1 # feedback div1: / 1 (no +1)
|
|
fbdiv1_cntrl = 0x1 # feedback div2: / 2
|
|
ref_cntrl = 0x0 # input divider: / 1
|
|
|
|
# Double check with math; this formula derived from the table.
|
|
total_div = (fbdiv2_cntrl * (fbdiv1_cntrl + 1) * (1 << (3 - n_cntrl)) /
|
|
(ref_cntrl + 1))
|
|
assert depth == total_div, \
|
|
"Error with rate=%d, tmds=%d, depth=%d, n_cntrl=%d, pixel_rep=%d" % (
|
|
rate, tmds, depth, n_cntrl, pixel_rep)
|
|
|
|
# Could be done by math, but this makes it more obvious I think...
|
|
if n_cntrl == 3:
|
|
gmp_cntrl = 0
|
|
elif n_cntrl == 2:
|
|
gmp_cntrl = 1
|
|
elif n_cntrl == 1:
|
|
gmp_cntrl = 2
|
|
elif n_cntrl == 0:
|
|
gmp_cntrl = 3
|
|
|
|
return ((n_cntrl << 0) |
|
|
(ref_cntrl << 2) |
|
|
(fbdiv1_cntrl << 4) |
|
|
(fbdiv2_cntrl << 6) |
|
|
(opmode << 9) |
|
|
(tmdsmhl_cntrl << 11) |
|
|
(prep_div << 13),
|
|
gmp_cntrl)
|
|
|
|
def do_mpll_loop():
|
|
mpll_cfg_table = {}
|
|
last_mpll_cfg = None
|
|
last_rate = None
|
|
|
|
for rate in xrange(13500000, 600001000, 1000):
|
|
for8bpp = make_mpll(rate, 8)
|
|
for10bpp = make_mpll(rate, 10)
|
|
for12bpp = make_mpll(rate, 12)
|
|
|
|
mpll_cfg = (for8bpp, for10bpp, for12bpp)
|
|
if (mpll_cfg != last_mpll_cfg) and (last_rate is not None):
|
|
mpll_cfg_table[last_rate] = last_mpll_cfg
|
|
|
|
last_rate = rate
|
|
last_mpll_cfg = mpll_cfg
|
|
|
|
mpll_cfg_table[last_rate] = last_mpll_cfg
|
|
|
|
print "\t",
|
|
for rate in sorted(mpll_cfg_table.keys()):
|
|
print ("{\n"
|
|
"\t\t%d, {\n"
|
|
"\t\t\t{ %#06x, %#06x },\n"
|
|
"\t\t\t{ %#06x, %#06x },\n"
|
|
"\t\t\t{ %#06x, %#06x },\n"
|
|
"\t\t},\n"
|
|
"\t}, ") % (
|
|
rate,
|
|
mpll_cfg_table[rate][0][0], mpll_cfg_table[rate][0][1],
|
|
mpll_cfg_table[rate][1][0], mpll_cfg_table[rate][1][1],
|
|
mpll_cfg_table[rate][2][0], mpll_cfg_table[rate][2][1]),
|
|
print
|
|
|
|
def CLK_SLOP(clk): return ((clk) / 1000)
|
|
def CLK_PLUS_SLOP(clk): return ((clk) + CLK_SLOP(clk))
|
|
def CLK_MINUS_SLOP(clk): return ((clk) - CLK_SLOP(clk))
|
|
|
|
def make_cur_ctr(rate, depth, pixel_rep=0):
|
|
assert pixel_rep == 0, "Untested with non-zero pixel rep and probably wrong"
|
|
|
|
tmds = (rate * depth) / 8. * (pixel_rep + 1)
|
|
|
|
adjust_for_jittery_pll = True
|
|
|
|
# If the PIXEL clock (not the TMDS rate) is using the special 594 PLL
|
|
# and is slow enough, we can use normal rates...
|
|
if ((CLK_MINUS_SLOP(74250000) <= rate <= CLK_PLUS_SLOP(74250000)) or
|
|
(CLK_MINUS_SLOP(148500000) <= rate <= CLK_PLUS_SLOP(148500000))):
|
|
adjust_for_jittery_pll = False
|
|
|
|
# If rate is slow enough then our jitter isn't a huge issue.
|
|
# ...allowable clock jitter is 362.3 or higher and we're OK there w/ plenty of
|
|
# margin as long as we're careful about our PLL settings.
|
|
if rate <= 79000000:
|
|
adjust_for_jittery_pll = False
|
|
|
|
if not adjust_for_jittery_pll:
|
|
# This is as documented
|
|
if tmds <= RATE1: # 46000000
|
|
return 0x18
|
|
elif tmds <= RATE2: # 92000000
|
|
return 0x28
|
|
|
|
# I have no idea why the below is true, but it is the simplest rule I could
|
|
# come up with that matched the tables...
|
|
if depth == 8:
|
|
if tmds <= 340000000:
|
|
# HDMI 1.4
|
|
return 0x38
|
|
# HDMI 2.0
|
|
return 0x18
|
|
elif depth == 16:
|
|
if tmds < 576000000:
|
|
return 0x38
|
|
return 0x28
|
|
else:
|
|
return 0x38
|
|
|
|
# The output of rk3288 PLL is the source of the HDMI's MPLL. Apparently
|
|
# the rk3288 PLL is too jittery. We can lower the PLL bandwidth of MPLL
|
|
# to compensate.
|
|
#
|
|
# Where possible, we try to use the MPLL bandwidth suggested by Synopsis
|
|
# and we just use lower bandwidth when testing has shown that it's needed.
|
|
# We try to stick to 0x28 and 0x18 since those numbers are present in
|
|
# Synopsis tables. We go down to 0x08 if needed and finally to 0x00.
|
|
|
|
if rate <= 79000000:
|
|
# Supposed to be 0x28 here, but we'll do 0x18 to reduce jitter
|
|
return 0x18
|
|
elif rate <= 118000000:
|
|
# Supposed to be 0x28/0x38 here, but we'll do 0x08 to reduce jitter
|
|
return 0x08
|
|
# Any higher clock rates go to bandwidth = 0
|
|
return 0
|
|
|
|
def do_curr_ctrl_loop():
|
|
cur_ctrl_table = {}
|
|
last_cur_ctrl = None
|
|
last_rate = None
|
|
|
|
for rate in xrange(13500000, 600001000, 1000):
|
|
for8bpp = make_cur_ctr(rate, 8)
|
|
for10bpp = make_cur_ctr(rate, 10)
|
|
for12bpp = make_cur_ctr(rate, 12)
|
|
|
|
cur_ctrl = (for8bpp, for10bpp, for12bpp)
|
|
if (cur_ctrl != last_cur_ctrl) and (last_rate is not None):
|
|
cur_ctrl_table[last_rate] = last_cur_ctrl
|
|
|
|
last_rate = rate
|
|
last_cur_ctrl = cur_ctrl
|
|
|
|
cur_ctrl_table[last_rate] = last_cur_ctrl
|
|
|
|
print "\t",
|
|
for rate in sorted(cur_ctrl_table.keys()):
|
|
print ("{\n"
|
|
"\t\t%d, { %#06x, %#06x, %#06x },\n"
|
|
"\t}, ") % (
|
|
rate,
|
|
cur_ctrl_table[rate][0],
|
|
cur_ctrl_table[rate][1],
|
|
cur_ctrl_table[rate][2]),
|
|
print
|
|
|
|
|
|
|
|
# From HDMI spec
|
|
VPH_RXTERM = 3.3
|
|
RXTERM = 50
|
|
|
|
def get_phy_preemphasis(symon, traon, trbon):
|
|
if (symon, traon, trbon) == (0, 0, 0):
|
|
assert False, "Not valid?"
|
|
elif (symon, traon, trbon) == (1, 0, 0):
|
|
preemph = 0.00
|
|
elif (symon, traon, trbon) == (1, 0, 1):
|
|
# Numbers match examples better if I assume .25 / 3 rather than .08
|
|
preemph = 0.25 / 3
|
|
elif (symon, traon, trbon) == (1, 1, 0):
|
|
# Numbers match examples better if I assume .50 / 3 rather than .17
|
|
preemph = 0.50 / 3
|
|
elif (symon, traon, trbon) == (1, 1, 1):
|
|
preemph = 0.25
|
|
else:
|
|
assert False, "Not valid"
|
|
|
|
return preemph
|
|
|
|
def phy_lvl_to_voltages(lvl, preemph, rterm):
|
|
v_lo = VPH_RXTERM - (.772 - 0.01405 * lvl)
|
|
v_swing = ((VPH_RXTERM - v_lo) * (1 - preemph) /
|
|
(1 + (RXTERM * (1 + preemph)) / (2 * rterm)))
|
|
v_hi = v_lo + v_swing
|
|
|
|
return v_lo, v_swing, v_hi
|
|
|
|
def print_phy_config(symbol, term, vlev):
|
|
ck_symon = bool(symbol & (1 << 0))
|
|
tx_trbon = bool(symbol & (1 << 1))
|
|
tx_traon = bool(symbol & (1 << 2))
|
|
tx_symon = bool(symbol & (1 << 3))
|
|
|
|
slopeboost = {
|
|
0: "no slope boost",
|
|
1: " 5-10% decrease on TMDS rise/fall times",
|
|
2: "10-20% decrease on TMDS rise/fall times",
|
|
3: "20-35% decrease on TMDS rise/fall times",
|
|
}[(symbol >> 4) & 0x3]
|
|
|
|
override = bool(symbol & (1 << 15))
|
|
|
|
rterm = (50, 57.14, 66.67, 80, 100, 133, 200)[term]
|
|
|
|
sup_ck_lvl = (vlev >> 0) & 0x1f
|
|
sup_tx_lvl = (vlev >> 5) & 0x1f
|
|
|
|
preemph = get_phy_preemphasis(tx_symon, tx_traon, tx_trbon)
|
|
|
|
print "symbol=%#06x, term=%#06x, vlev=%#06x" % (symbol, term, vlev)
|
|
for name, lvl in [("ck", sup_ck_lvl), ("tx", sup_tx_lvl)]:
|
|
v_lo, v_swing, v_hi = phy_lvl_to_voltages(lvl, preemph, rterm)
|
|
print " %s: lvl = %2d, term=%3d, vlo = %.2f, vhi=%.2f, vswing = %.2f, %s" % (
|
|
name, lvl, rterm, v_lo, v_hi, v_swing, slopeboost)
|
|
|
|
#def calc_ideal_phy_lvl(swing_mv, preemph, rterm):
|
|
#"""Get the ideal "lvl" for the given swing, preemph, and termination.
|
|
|
|
#This might not be integral, but and might not fit the 0-31 range.
|
|
#"""
|
|
#v_lo = (VPH_RXTERM -
|
|
#v_swing / (1 - preemph) -
|
|
#(v_swing * RXTERM * (1 + preemph)) / (2 * rterm * (1 - preemph)))
|
|
#lvl = (.772 - (VPH_RXTERM - v_lo)) / 0.01405
|
|
|
|
#return lvl
|
|
|
|
def do_phy_config_list(rate=16500000):
|
|
# From HDMI spec
|
|
VPH_RXTERM = 3.3
|
|
RXTERM = 50
|
|
|
|
# Set to True to print even things that don't meet requirements.
|
|
print_invalid = False
|
|
|
|
# Totally a guess based on what's in IMX6DQRM
|
|
if rate <= 165000000:
|
|
symon = 1 # tx_symon
|
|
traon = 0 # tx_traon
|
|
trbon = 0 # tx_trbon
|
|
else:
|
|
symon = 1 # tx_symon
|
|
traon = 0 # tx_traon
|
|
trbon = 1 # tx_trbon
|
|
|
|
print "Guessing symon, traon, trbon based on rate: (%d, %d, %d)" % (
|
|
symon, traon, trbon)
|
|
|
|
preemph = get_phy_preemphasis(symon, traon, trbon)
|
|
|
|
# Notes:
|
|
# - swing needs to be between .4 and .6
|
|
# - If <= 165MHz, vhi is (VPH_RXTERM + .01) thru (VPH_RXTERM - .01)
|
|
# - If > 165MHz, vhi is (VPH_RXTERM + .01) thru (VPH_RXTERM - .2)
|
|
# - If <= 165MHz, vlo is (VPH_RXTERM - .4) thru (VPH_RXTERM - .6)
|
|
# - If > 165MHz, vlo is (VPH_RXTERM - .4) thru (VPH_RXTERM - .7)
|
|
#
|
|
# TODO: I'm not sure we can actually reach vhi of 3.3 +/- .01
|
|
# TODO: How do we pick amongst all of these?
|
|
|
|
if rate <= 165000000:
|
|
v_hi_min = 3.19 # Should be (VPH_RXTERM - .01), but not possible?
|
|
v_hi_max = (VPH_RXTERM + .01)
|
|
v_lo_min = (VPH_RXTERM - .6)
|
|
v_lo_max = (VPH_RXTERM - .4)
|
|
else:
|
|
v_hi_min = (VPH_RXTERM - .2)
|
|
v_hi_max = (VPH_RXTERM + .01)
|
|
v_lo_min = (VPH_RXTERM - .7)
|
|
v_lo_max = (VPH_RXTERM - .4)
|
|
|
|
for lvl in xrange(0, 31):
|
|
for rterm in (50, 57.14, 66.67, 80, 100, 133, 200):
|
|
v_lo, v_swing, v_hi = phy_lvl_to_voltages(lvl, preemph, rterm)
|
|
|
|
if (print_invalid or
|
|
((.4 <= v_swing <= .6) and
|
|
(v_hi_min <= v_hi <= v_hi_max) and
|
|
(v_lo_min <= v_lo <= v_lo_max))):
|
|
print "lvl = %2d, term=%3d, vlo = %.2f, vhi=%.2f, vswing = %.2f" % (
|
|
lvl, rterm, v_lo, v_hi, v_swing)
|
|
|
|
# Examples:
|
|
#
|
|
# $ ./rk3288_hdmitables.py mpll
|
|
# $ ./rk3288_hdmitables.py curr
|
|
# $ ./rk3288_hdmitables.py phy_list 165000000
|
|
# $ ./rk3288_hdmitables.py phy_list 600000000
|
|
# $ ./rk3288_hdmitables.py phy_print 0x8009, 0x0005, 0x01ad
|
|
|
|
def main(todo, *args):
|
|
if todo == "mpll":
|
|
do_mpll_loop()
|
|
elif todo == "curr":
|
|
do_curr_ctrl_loop()
|
|
elif todo == "phy_list":
|
|
(rate,) = args
|
|
rate = int(rate, 0)
|
|
do_phy_config_list(rate)
|
|
elif todo == "phy_print":
|
|
(symbol, term, vlev) = args
|
|
symbol = int(symbol.rstrip(","), 0)
|
|
term = int(term.rstrip(","), 0)
|
|
vlev = int(vlev.rstrip(","), 0)
|
|
|
|
print_phy_config(symbol, term, vlev)
|
|
|
|
# These ought to match the tables in the docs. They are close, but not
|
|
# perfect. ...but my math is definitely right since v_lo doesn't match
|
|
# and v_lo should be very simple. Even if we try to find more significant
|
|
# digits for 0.772 and 0.01405 we still can't make it match, so I'm assuming
|
|
# that they rounded somewhere in their math...
|
|
|
|
#print "%.3f, %.3f, %.3f" % phy_lvl_to_voltages(19, get_phy_preemphasis(1, 0, 0), 100)
|
|
#print "%.3f, %.3f, %.3f" % phy_lvl_to_voltages(10, get_phy_preemphasis(1, 0, 0), 100)
|
|
#print "%.3f, %.3f, %.3f" % phy_lvl_to_voltages( 6, get_phy_preemphasis(1, 0, 1), 100)
|
|
|
|
#print "%.3f, %.3f, %.3f" % phy_lvl_to_voltages(21, get_phy_preemphasis(1, 0, 0), 133)
|
|
#print "%.3f, %.3f, %.3f" % phy_lvl_to_voltages(13, get_phy_preemphasis(1, 0, 0), 133)
|
|
#print "%.3f, %.3f, %.3f" % phy_lvl_to_voltages( 8, get_phy_preemphasis(1, 0, 1), 133)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main(*sys.argv[1:])
|