[v4,1/2] video: hdmi: add helper functions for N and CTS

Doug Anderson dianders at chromium.org
Mon Jun 6 16:34:47 UTC 2016


Hi,

On Thu, Apr 21, 2016 at 8:29 AM, Arnaud Pouliquen <dianders at chromium.org> wrote:
> Add helper functions to compute HDMI CTS and N parameters.
> Implementation is based on HDMI 1.4b specification.

It would be super nice to have this somewhere common.  Any idea who
would land this?


> +static const struct hdmi_audio_acr hdmi_audio_standard_acr[3][13] = {
> +       [HDMI_AUDIO_N_CTS_32KHZ] = {
> +               /* N and CTS values for 32 kHz rate*/
> +               {  25174825, {  4576,  28125, 0 } }, /* 25.20/1.001  MHz */
> +               {  25200000, {  4096,  25200, 0 } }, /* 25.20        MHz */
> +               {  27000000, {  4096,  27000, 0 } }, /* 27.00        MHz */
> +               {  27027000, {  4096,  27027, 0 } }, /* 27.00*1.001  MHz */
> +               {  54000000, {  4096,  54000, 0 } }, /* 54.00        MHz */
> +               {  54054000, {  4096,  54054, 0 } }, /* 54.00*1.001  MHz */
> +               {  74175824, { 11648, 210937, 50 } }, /* 74.25/1.001 MHz */
> +               {  74250000, {  4096,  74250, 0 } }, /* 74.25        MHz */
> +               { 148351648, { 11648, 421875, 0 } }, /* 148.50/1.001 MHz */
> +               { 148500000, {  4096, 148500, 0 } }, /* 148.50       MHz */
> +               { 296703296, {  5824, 421875, 0 } }, /* 297/1.001 MHz (truncated)*/
> +               { 296703297, {  5824, 421875, 0 } }, /* 297/1.001 MHz (rounded)*/
> +               { 297000000, {  3072, 222750, 0 } }, /* 297          MHz */

One thing to note is that for all but the non-integral clock rates and
the rates >= ~297MHz, all of this can be done programmatically.
...the function I came up with to do that is pretty slow, so a table
is still useful in general unless you want to try to optimize things,
but it might be nice to have the function available as a fallback?
Specifically many TVs will allow audio to work with rates other than
the ones in the HDMI spec.

You can see the full implementation we used on some devices I worked
on at <https://chromium.googlesource.com/chromiumos/third_party/kernel/+/chromeos-3.14/drivers/gpu/drm/bridge/dw_hdmi.c>.
Specifically the function for computing N:

static unsigned int hdmi_compute_n(struct dw_hdmi *hdmi,
           unsigned long pixel_clk)
{
  unsigned int freq = hdmi->sample_rate;
  unsigned int min_n = DIV_ROUND_UP((128 * freq), 1500);
  unsigned int max_n = (128 * freq) / 300;
  unsigned int ideal_n = (128 * freq) / 1000;
  unsigned int best_n_distance = ideal_n;
  unsigned int best_n = 0;
  u64 best_diff = U64_MAX;
  int n;
  /* If the ideal N could satisfy the audio math, then just take it */
  if (hdmi_audio_math_diff(freq, ideal_n, pixel_clk) == 0)
    return ideal_n;
  for (n = min_n; n <= max_n; n++) {
    u64 diff = hdmi_audio_math_diff(freq, n, pixel_clk);
    if (diff < best_diff || (diff == best_diff &&
        abs(n - ideal_n) < best_n_distance)) {
      best_n = n;
      best_diff = diff;
      best_n_distance = abs(best_n - ideal_n);
    }
    /*
     * The best N already satisfy the audio math, and also be
     * the closest value to ideal N, so just cut the loop.
     */
    if ((best_diff == 0) && (abs(n - ideal_n) > best_n_distance))
      break;
  }
  return best_n;
}

I believe this function written by Yakir Yang based on a bit of python
I had coded up.  The python has the advantage that it will come up
with the right N/CTS even for fractional clock rates, like
25.20/1.001:

def DIV_ROUND_UP(x, y): return (x + y - 1) / y
def calc(freq, tmds):
  min_n = DIV_ROUND_UP((128 * freq), 1500)
  max_n = (128 * freq) / 300
  ideal_n = (128 * freq) / 1000
  best = 0xffffffffffffffff
  for n in xrange(min_n, max_n + 1):
    cts = int(round((tmds * n / (128. * freq))))
    diff = abs(tmds * n - cts * (128. * freq))
    if (diff < best) or \
       (diff == best and
        abs(n - ideal_n) < abs(best_n - ideal_n)):
      best = diff
      best_n = n

  # Want a number that's close to an integer here
  print tmds, freq, best_n, tmds * (best_n) / (128. * freq)

  n = best_n
  cts = (tmds * n) / (128 * freq)
  print ">>> ((128 * %d) * %d) / %d." % (freq, cts, n)
  print "%f" % (((128 * freq) * cts) / n)
  print

25174825.1748 32000 4576 28125.0
>>> ((128 * 32000) * 28125) / 4576.
25174825.174825

25174825.1748 44100 7007 31250.0
>>> ((128 * 44100) * 31250) / 7007.
25174825.174825

25174825.1748 48000 6864 28125.0
>>> ((128 * 48000) * 28125) / 6864.
25174825.174825


One other thing to note is that if your HDMI block doesn't happen to
make _exactly_ the right clock then these values aren't right.  For
instance, if you end up making 25174825 Hz instead of 25200000 / 1.001
Hz that different N/CTS values are ideal.  The numbers below are the
result of my python but (as you can see) things don't match up
properly.

25174825 32000 4405 27074.0000305
>>> ((128 * 32000) * 27074) / 4405.
25174824.000000

25174825 44100 9073 40464.0000044
>>> ((128 * 44100) * 40464) / 9073.
25174824.000000

25174825 48000 15503 63522.9999959
>>> ((128 * 48000) * 63522) / 15503.
25174428.000000


In my particular case we could make 25,176,471 which we thought was
close enough to the proper clock rate, but still deserves better N/CTS
rates.

  /* 25176471 for 25.175 MHz = 428000000 / 17. */
  { .tmds = 25177000, .n_32k = 4352, .n_44k1 = 14994, .n_48k = 6528, },


> +       /*
> +        * Pre-defined frequency not found. Compute CTS using formula:
> +        * CTS = (Ftdms_clk * N) / (128 * audio_fs)
> +        */
> +       val = (u64)tmds_clk * n_cts->n;
> +       n_cts->cts = div64_u64(val, 128UL * audio_fs);
> +
> +       n_cts->cts_1_ratio = 0;
> +       min = (u64)n_cts->cts * 128UL * audio_fs;
> +       if (min < val) {
> +               /*
> +                * Non-accurate value for CTS
> +                * compute ratio, needed by user to alternate in ACR
> +                * between CTS and CTS + 1 value.
> +                */
> +               n_cts->cts_1_ratio = ((u32)(val - min)) * 100 /
> +                                    (128 * audio_fs);
> +       }

This fallback isn't nearly as nice and will likely lead to audio
reconstitution problems.  IIRC the problem was periodic audio cutouts
of you listened long enough.


-Doug


More information about the dri-devel mailing list