blob: 99d0f239e659aeb1c150bda2abea598af78414e2 [file] [log] [blame]
#!/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:])