Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix composite output video norms #4241

Merged
merged 6 commits into from
Jul 15, 2021
Merged

Conversation

kFYatek
Copy link

@kFYatek kFYatek commented Mar 28, 2021

This PR makes the Full-KMS video output over VEC use the same timings as the firmware (with one difference regarding 625-line modes, see below), fixes PAL modes, and adds support for additional TV standards.

Some remarks:

  • I feel like my changes in vc4_crtc.c are somewhat messy, but I made them to align the PixelValve settings (the HORZA/HORZB/VERTA/VERTB registers in particular) perfectly with what the firmware does, while keeping the modeline truthful. For example, I needed that vtotal_fixup to make the total number of lines per field accurate (263/313 lines) while keeping vtotal in mode definition at 525/625 lines. I could set vtotal in mode definition to 526/626, but that's not the actual number of lines, plus it messes up the refresh rates in xrandr and such.
  • In particular, I have very little idea what's going on with that if (vec_interlaced) ++field_delay; thing, but that makes the image cleaner. Compare: before vs. after. Tweaking this value indeed moves the fields relative to each other, but why htotal / 2 + 1 in particular is the proper value rather than htotal / 2, I have no idea.
  • I have moved horizontal sync pulse in the 625-line (PAL/SECAM) mode to 7 pixels earlier than the firmware or the original driver configures it. This is because in that mode, only about 704-706 pixels of the image are output anyway. This "masking" seems to be done by the VEC independently of PixelValve timings; it can be controlled in a very limited way through the VEC_CONFIG3 register - setting the HORIZ_LEN_MPEG1_SIF makes it even narrower, while SHAPE_NON_LINEAR changes the gradient at the edges a little bit. Anyway, positioning the sync pulse 7 pixels earlier than the default centers the image relative to this mask. Compare: before vs. after.
  • I'm not sure if the vc4_vec_get_current_mode() belongs in the source like I put it, but I wrote it so that the sdtv_mode setting in config.txt is respected to some extent by the Full-KMS driver as the default mode at boot time. If there is a better way to do it, please tell me.
  • The NTSC-443, PAL-M, PAL60 and SECAM modes are configured in the same way as in TweakVec. The settings have been verified to work for other users in Support for progressive PAL60 in config.txt/sdtv_mode firmware#811
  • Switching TV norm to one that's not compatible with the mode set at boot (e.g. entering PAL mode when sdtv_mode=0; see below for how to do that) causes the ability to switch virtual terminals (Ctrl+Alt+F1) to be lost, unless you switch the TV norm back first. This is how this driver has behaved before my changes. I don't know if this is how it should behave - perhaps it should switch back to e.g. NTSC (or whatever the last 525-line norm was) when doing so? I wrote some (non-public for now) code that does just that, so I can post a supplementary PR or update this one if that's the requested behaviour.

I tested norm switching under X with the following one-liner:

xrandr --output Composite-1 --off && xrandr --output Composite-1 --set mode {NTSC|NTSC-J|NTSC-443|PAL|PAL-M|PAL-N|PAL60|SECAM} && xrandr --output Composite-1 --mode {720x480|720x576}

720x480 works for NTSC, NTSC-J, NTSC-443, PAL-M and PAL60. 720x576 works for PAL, PAL-N and SECAM.

To switch to a mode incompatible with the one set at boot time, it has to be defined first using:

xrandr --newmode 720x480 13.5 720 734 798 858 480 487 493 525 Interlace

or

xrandr --newmode 720x576 13.5 720 733 797 864 576 580 586 625 Interlace

Modelines that are not exactly one of the two above, are intentionally rejected, at least for now, and this behaviour is carried over from the existing code. I will probably post another PR later that relaxes this requirement a bit, and adds support for progressive (240p/288p) modes.

@6by9
Copy link
Contributor

6by9 commented Apr 1, 2021

Thanks for the patches. I need to spend a little time looking into them and testing.

There will be a request for all the commit texts to be updated as they should start drm/vc4:, reference the VEC in the subject line, and have some more detail in the description.

It's also a little confusing that you add all these *_mode_set functions for the new standards, then rip them all out again and move the config that they add into struct vc4_vec_tv_mode. It would be nicer to make that move first, and then add the extra modes.

With regard vc4_vec_get_current_mode, the firmware should insert a video=Composite-1:... line into the kernel command line, so that should give you the right mode. The parsing of that isn't perfect though.

@kFYatek
Copy link
Author

kFYatek commented Apr 12, 2021

NOTE: There are no changes in the force-push above yet. I just rebased everything onto the current main branch. I will now proceed with addressing your comments.

@kFYatek
Copy link
Author

kFYatek commented Apr 12, 2021

With regard vc4_vec_get_current_mode, the firmware should insert a video=Composite-1:... line into the kernel command line, so that should give you the right mode. The parsing of that isn't perfect though.

Well, that line is just e.g. video=Composite-1:720x576@50i. It's not enough to differentiate between PAL and PAL-N (and SECAM, if that's ever added to firmware), or NTSC (US) vs. NTSC-J vs. PAL-M.

@kFYatek
Copy link
Author

kFYatek commented Apr 12, 2021

NOTE: As far as code is concerned, the only changes compared to the previous version are slight wording improvements in some comments. No actual code has been changed, this is all about reorganizing the commits.

There will be a request for all the commit texts to be updated as they should start drm/vc4:, reference the VEC in the subject line, and have some more detail in the description.

Sure, done.

It's also a little confusing that you add all these *_mode_set functions for the new standards, then rip them all out again and move the config that they add into struct vc4_vec_tv_mode. It would be nicer to make that move first, and then add the extra modes.

Sure, done as well. The original PR contained the commits "raw", mostly how I actually developed these changes. I don't know much about the etiquette of the kernel repo, so sorry about that. I now rewrote the history so that the commits make more sense on their own, and are more concise as well.

Copy link
Contributor

@mripard mripard left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure we need to read-out the hardware state. The command line parser explicitly supports named modes, so the firmware could just set video=TV-1:NTSC-J or whatever and the initial framebuffer will use that mode.

Switching back and forth between modes is more problematic though. The userspace is free to build whatever mode it wants, and / or the framebuffer emulation (and thus the initial setup) can be disabled.

In such a case, you don't really have a way to differentiate between variants that use the same mode (like between NSTC-J and PAL-M). Once again, you can only hope that the name will be filled and compare the name to the modes you support, and fallback on PAL or NTSC

Copy link
Contributor

@6by9 6by9 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry it's taken me a while to get around to looking at this.

Generally looks fine, but the PV setup changes in vc4_crtc.c have me a little concerned.

Seeing as I don't believe it holds any secrets, the firmware config code for the PV when driving the VEC is:

   if (use_625_line)
   {
       vec_state.pixelvalve_setup.horizontal_sync_width_in_pixels = 64;
       vec_state.pixelvalve_setup.horizontal_frontporch_in_pixels = 20;
       vec_state.pixelvalve_setup.horizontal_backporch_in_pixels  = 60;
       vec_state.pixelvalve_setup.horizontal_active_in_pixels     = 720;
       vec_state.pixelvalve_setup.vertical_sync_width_in_lines    = 3;
       vec_state.pixelvalve_setup.vertical_frontporch_in_lines    = 2;
       vec_state.pixelvalve_setup.vertical_backporch_in_lines     = use_progressive ? 19 : 20;
       vec_state.pixelvalve_setup.vertical_active_in_lines        = 576/2;
       vec_state.pixelvalve_setup.vertical_even_sync_width_in_lines = vec_state.pixelvalve_setup.vertical_sync_width_in_lines;
       vec_state.pixelvalve_setup.vertical_even_frontporch_in_lines = vec_state.pixelvalve_setup.vertical_frontporch_in_lines;
       vec_state.pixelvalve_setup.vertical_even_backporch_in_lines  = vec_state.pixelvalve_setup.vertical_backporch_in_lines - 1;
       vec_state.pixelvalve_setup.vertical_even_active_in_lines     = vec_state.pixelvalve_setup.vertical_active_in_lines;
       vec_state.pixelvalve_setup.first_field_odd = 0;
   }
   else
   {
       vec_state.pixelvalve_setup.horizontal_sync_width_in_pixels = 64;
       vec_state.pixelvalve_setup.horizontal_frontporch_in_pixels = 14;
       vec_state.pixelvalve_setup.horizontal_backporch_in_pixels  = 60;
       vec_state.pixelvalve_setup.horizontal_active_in_pixels     = 720;
       vec_state.pixelvalve_setup.vertical_sync_width_in_lines    = 3;
       vec_state.pixelvalve_setup.vertical_frontporch_in_lines    = 3;
       vec_state.pixelvalve_setup.vertical_backporch_in_lines     = 16;
       vec_state.pixelvalve_setup.vertical_active_in_lines        = 480/2;
       vec_state.pixelvalve_setup.vertical_even_sync_width_in_lines = vec_state.pixelvalve_setup.vertical_sync_width_in_lines;
       vec_state.pixelvalve_setup.vertical_even_frontporch_in_lines = vec_state.pixelvalve_setup.vertical_frontporch_in_lines + 1;
       vec_state.pixelvalve_setup.vertical_even_backporch_in_lines  = vec_state.pixelvalve_setup.vertical_backporch_in_lines;
       vec_state.pixelvalve_setup.vertical_even_active_in_lines     = vec_state.pixelvalve_setup.vertical_active_in_lines;
       vec_state.pixelvalve_setup.first_field_odd = 1;
   }
   uint32_t half_line = (vec_state.pixelvalve_setup.horizontal_sync_width_in_pixels +
                         vec_state.pixelvalve_setup.horizontal_frontporch_in_pixels +
                         vec_state.pixelvalve_setup.horizontal_backporch_in_pixels +
                         vec_state.pixelvalve_setup.horizontal_active_in_pixels) >> 1;
   vec_state.pixelvalve_setup.second_field_offset = half_line + 1;
   if (pal_mode)
      vec_state.pixelvalve_setup.mode = PIXELVALVE_MODE_VEC_PAL;
   else
      vec_state.pixelvalve_setup.mode = PIXELVALVE_MODE_VEC_NTSC;

The values are written without modification into the relevant PV registers.

Note that all the vertical timings have already been divided by 2 due to interlacing. That happens in the magic of drm_mode_set_crtcinfo for DRM, but that does have the potential for rounding errors on odd values.

mode->crtc_vsync_end - 1,
VC4_SET_FIELD(mode->crtc_vtotal + vtotal_fixup -
mode->crtc_vsync_end -
(ntsc ? 0 : 1),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This one feels odd.

vtotal_fixup = (vec_interlaced && !ntsc) ? 1 : 0
but here we're doing - (ntsc ? 0 : 1), and we're in a condition where we're interlaced, so effectively ```(interlaced && ntsc) ? 0 : 1).
Doesn't that end up with the two operations cancelling each other out?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not exactly.

  • Progressive or HDMI interlace: vtotal_fixup == 0, ntsc == 0, total fixup here: -1
  • VEC 525-line ("NTSC"): vtotal_fixup == 0, ntsc == 1, total fixup here: 0
  • VEC 625-line ("PAL"): vtotal_fixup == 1, ntsc == 0, total fixup here: 0

I guess this vtotal_fixup may be confusing after all, and using vec_interlaced and ntsc directly at those two places these are used will be cleaner.

@@ -343,6 +343,10 @@ static void vc4_crtc_config_pv(struct drm_crtc *crtc)
bool is_dsi1 = vc4_encoder->type == VC4_ENCODER_TYPE_DSI1;
u32 format = is_dsi1 ? PV_CONTROL_FORMAT_DSIV_24 : PV_CONTROL_FORMAT_24;
u8 ppc = pv_data->pixels_per_clock;
bool vec_interlaced = (vc4_encoder->type == VC4_ENCODER_TYPE_VEC &&
interlace);
bool ntsc = (vec_interlaced && mode->htotal == 858);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This way of detecting NTSC does feel a little fragile, but I'm not sure that there is a better solution.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I now realize that this may even be confusing, as this actually detects 525/625-line modes, not NTSC per se (i.e. PAL-M is "NTSC" in this sense and that's intended), so renaming these variables is probably the right thing to do.

@@ -283,8 +283,8 @@ static void vc4_vec_pal_m_mode_set(struct vc4_vec *vec)

static const struct drm_display_mode pal_mode = {
DRM_MODE("720x576", DRM_MODE_TYPE_DRIVER, 13500,
720, 720 + 20, 720 + 20 + 64, 720 + 20 + 64 + 60, 0,
576, 576 + 2, 576 + 2 + 3, 576 + 2 + 3 + 20, 0,
720, 720 + 13, 720 + 13 + 64, 720 + 13 + 64 + 67, 0,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Has this been tested on a Pi4? Some of the PVs on 2711 operate at 2 pixels/clock, which means that odd values in horizontal timings cause issues as it can't signal the sync event between the two pixels.

I'm not convinced by the centering anyway as it will cause different behaviour compared to the firmware, and it also should be a separate patch to that correcting the timings.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair enough, keeping the timing consistent with the firmware is a fair argument.

But odd values for front and back porch lengths do indeed work on Pi4.

PV_VERTB_VFP) |
VC4_SET_FIELD(mode->crtc_vdisplay, PV_VERTB_VACTIVE));
VC4_SET_FIELD(mode->crtc_vdisplay,
PV_VERTB_VACTIVE));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All of these fixups become quite hard to read and understand what exactly is going on.

My gut feeling is that it'd be clearer to add local variables at least for the vertical active, fp, sw, and bp values, and then have explicit clauses for NTSC and PAL when assigning to the registers. At least that way the fixups are obvious.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, I'll try to simplify things a bit.

@kFYatek
Copy link
Author

kFYatek commented Jun 20, 2021

I'm not sure we need to read-out the hardware state. The command line parser explicitly supports named modes, so the firmware could just set video=TV-1:NTSC-J or whatever and the initial framebuffer will use that mode.

Switching back and forth between modes is more problematic though. The userspace is free to build whatever mode it wants, and / or the framebuffer emulation (and thus the initial setup) can be disabled.

In such a case, you don't really have a way to differentiate between variants that use the same mode (like between NSTC-J and PAL-M). Once again, you can only hope that the name will be filled and compare the name to the modes you support, and fallback on PAL or NTSC

Well, unfortunately the current versions of the firmware only pass information such as video=Composite-1:720x480@60i or video=Composite-1:720x576@50i. There is no way to differentiate between NTSC vs. NTSC-J vs. PAL-M or PAL vs. PAL-N, even though all of these are accessible via sdtv_mode.

I agree that what I wrote is a bit of a kludge, and I agree that if the firmware could pass the colour system settings on the kernel's command line, that would be much better. I'm happy to update my code once it does, but for now, reading the VEC register state found at boot is the only way to get this information.

@kFYatek
Copy link
Author

kFYatek commented Jun 20, 2021

Note; the force-push above is just a rebase, with no changes to the actual code. I'll amend the diff with the next update.

@6by9
Copy link
Contributor

6by9 commented Jun 20, 2021

Well, unfortunately the current versions of the firmware only pass information such as video=Composite-1:720x480@60i or video=Composite-1:720x576@50i. There is no way to differentiate between NTSC vs. NTSC-J vs. PAL-M or PAL vs. PAL-N, even though all of these are accessible via sdtv_mode.

I agree that what I wrote is a bit of a kludge, and I agree that if the firmware could pass the colour system settings on the kernel's command line, that would be much better. I'm happy to update my code once it does, but for now, reading the VEC register state found at boot is the only way to get this information.

It looks like the firmware has a struct SDTV_DISPLAY_STATE_T which includes the mode from enum SDTV_MODE_T. That should be fairly simple to map into an appropriate string on the kernel command line.

I'm currently failing to find the kernel side of the parsing for video=TV-1:NTSC-J, nor documentation for that option. Most of the docs are in https://github.com/torvalds/linux/blob/master/Documentation/fb/modedb.rst, but not this option.
drm_mode_parse_command_line_for_connector would appear to have a mode whitelist for NTSC and PAL, but not the variants.

@mripard Are you believing that the parsing for this is already present and will set the tv_mode_property, or is this a further change that is needed?

@kFYatek kFYatek force-pushed the fix-composite branch 2 times, most recently from 5050b04 to 1fd7b63 Compare June 20, 2021 15:19
@kFYatek
Copy link
Author

kFYatek commented Jun 20, 2021

Note: the update from 7 minutes ago updates the first commit in this PR according to the review notes.

The second update from just now contains no code changes, it just updates the commit message of that commit to remove information about the shift by 7 pixels, which has been removed. I forgot to update the commit message the first time.

@kFYatek
Copy link
Author

kFYatek commented Jun 21, 2021

@6by9 I tried to check how other drivers handle this issue and it seems that there are exactly two that support composite output and support setting its mode at boot - and both ch7006 and nouveau introduce a module parameter called tv_norm. Maybe we should follow suit?

@kFYatek
Copy link
Author

kFYatek commented Jun 21, 2021

I updated the PR with the new logic. The new vc4.tv_norm= kernel command line parameter is added, so that one can add e.g. vc4.tv_norm=NTSC-J or vc4.tv_norm=SECAM. Maybe in the future that could be set by the firmware, but for now there's always /boot/cmdline.txt.

I added additional logic so if that option is absent, but the video= modeline looks like a PAL mode, PAL is used. This at least makes sdtv_mode=2 work.

One important note is that if the video= argument does not match vc4.tv_norm, the fbcon terminal will be inaccessible. This may happen if e.g. sdtv_mode is set to 0, but vc4.tv_norm=PAL is also specified on the command line. fbcon always prefers the cmdline mode, but that mode does not pass validation in vc4_vec_encoder_atomic_check(), and the result is that you can't do Ctrl+Alt+F1 anymore. X works. I don't know if we should address this issue in any way.

@kFYatek
Copy link
Author

kFYatek commented Jun 23, 2021

The above force-push rebased this PR onto current origin/rpi-5.10.y.

The conflict resolution was ("old" is the previously pushed revision, "new" is the current one):

--- a/drivers/gpu/drm/vc4/vc4_crtc.c
+++ b/drivers/gpu/drm/vc4/vc4_crtc.c
@@ -362,7 +349,7 @@ static void vc4_crtc_config_pv(struct drm_crtc *crtc, struct drm_atomic_state *s
 
        if (interlace) {
                bool odd_field_first = false;
-               u32 field_delay = mode->htotal * pixel_rep / 2;
+               u32 field_delay = mode->htotal * pixel_rep / (2 * ppc);
                u16 vert_bp_even = vert_bp;
                u16 vert_fp_even = vert_fp;
 
@@ -372,16 +359,14 @@ static void vc4_crtc_config_pv(struct drm_crtc *crtc, struct drm_atomic_state *s
                        if (mode->htotal == 858) {
                                /* 525-line mode (NTSC or PAL-M) */
                                odd_field_first = true;
-                               ++vert_fp_even;
-                       } else {
-                               /* 625-line mode (PAL or SECAM) */
-                               ++vert_bp;
                        }
-               } else {
-                       /* HDMI */
-                       --vert_bp_even;
                }
 
+               if (odd_field_first)
+                       ++vert_fp_even;
+               else
+                       ++vert_bp;
+
                CRTC_WRITE(PV_VERTA_EVEN,
                           VC4_SET_FIELD(vert_bp_even, PV_VERTA_VBP) |
                           VC4_SET_FIELD(vert_sync, PV_VERTA_VSYNC));

};

enum vc4_vec_tv_mode_id
vc4_vec_get_default_mode(struct drm_connector *connector) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Function start braces must go on separate lines.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

(!connector->cmdline_mode.refresh_specified &&
(connector->cmdline_mode.yres == 288 ||
connector->cmdline_mode.yres == 576)))) {
/* no explicitly specified TV norm; use PAL if a mode that
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Kernel multi-line comments must be of the form:

/*
 * blah blah
 * blah blah blah
 */

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The drm subsystem is happy with

/* blah
 * blah
*/

but the closing */ does have to be on a separate line.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

@pelwell
Copy link
Contributor

pelwell commented Jun 24, 2021

This isn't really my field, so apologies that I can only contribute nit-picky cosmetic feedback, but this looks like an impressive patch set.

Copy link
Contributor

@6by9 6by9 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the patchset.

I need to test it out to satisfy myself that it actually does the right thing, but other than the minor niggles below it looks good.
I have asked mripard over passing the tv standard via the kernel command line video= entry. Seeing as he wrote most of that code he's a pretty good person to ask :-) I don't see any way that it sets the tv_mode property, so the module parameter seems a good option at present.

The only other driver I really see handling tv_mode is i915, and it just always defaults to NTSC.

@@ -68,6 +68,7 @@
#define VEC_CONFIG0_STD_MASK GENMASK(1, 0)
#define VEC_CONFIG0_NTSC_STD 0
#define VEC_CONFIG0_PAL_BDGHI_STD 1
#define VEC_CONFIG0_PAL_M_STD 2
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can I ask where you got that define from? The datasheet I have lists the values as
0 - NTSC
1 - PAL_BDGHI
2 - reserved
3 - PAL_N

The firmware only selects from modes 0, 1, and 3, so I'm wondering where your mapping for 2 has come from.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's been a while since I found this, so my memory is not exactly clear, but AFAIR it was mostly experimentation. I was just messing with the registers and seeing what happens. I set what's defined here as VEC_CONFIG0_STD_MASK to 2 and poof, a standards-compliant PAL-M mode appeared.

I wrote https://github.com/kFYatek/tweakvec some time earlier to mess with these registers from user space. The effects of that have been discussed a bit in raspberrypi/firmware#811, where @Sobakin76 confirmed that this works on, let's call it, "period-accurate" displays. Here's a video they made that shows it: https://youtu.be/299IORo56lM (note, however, that they have their Pi set to 240p progressive mode). PAL-M support is uncommon on devices from my region, so I couldn't do that personally, but I did check it on modern-ish devices (particularly the Fushicai USBTV007 grabber with Windows drivers, and a Samsung UE40E6800S TV - both are curiously compatible with pretty much every TV norm ever).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've now checked the 2711 register definitions, and those values are listed as reserved there too.

I'll see if I can get anyone at Broadcom to confirm your findings.

* PAL (4433618.75 Hz) - 0x2A098ACB
* PAL-M (3575611.[888111] Hz) - 0x21E6EFE3
* PAL-N (3582056.25 Hz) - 0x21F69446
*
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Datasheet says colour subcarrier frequency = 13.5MHz * FREQ / 2^31.

However the firmware code for setting up PAL_M (the only option which uses a custom subcarrier) is 0x223b61d1, which differs from yours. Computing the values, I agree with your numbers though.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

0x223b61d1, which translates to 3610402.169 Hz, is a long-running bug in the firmware. I'm not aware of a TV norm that would use this frequency ever existing. sdtv_mode=3 indeed configures that frequency (with VEC_CONFIG0_STD_MASK set to 0 for NTSC), but that mode is pretty much unusable. Here's how sdtv_mode=3 looks on my Fushicai USBTV007 grabber. See also raspberrypi/firmware#756, there are pictures of @Sobakin76's monitors showing the same thing there.

*
* NOTE: For SECAM, it is used as the Dr center frequency,
* regardless of whether VEC_CONFIG1_CUSTOM_FREQ is enabled or not;
* that is specified as 4406250 Hz, which corresponds to 0x29C71C72.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That 0x29C71C72 corresponds to the default value by the looks of it.

* Db center frequency for SECAM; the clock for this is the same as for
* VEC_FREQ3_2/VEC_FREQ1_0, which is used for Dr center frequency.
*
* This is specified as 4250000 Hz, which corresponds to 0x284BDA13.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

0x284BDA13 is the default value for the register. Probably worth mentioning that as otherwise there is no other reference to it, and VEC_FCW_SECAM_B is never set.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

@@ -45,6 +45,7 @@
#define VEC_CONFIG0_YDEL(x) ((x) << 26)
#define VEC_CONFIG0_CDEL_MASK GENMASK(25, 24)
#define VEC_CONFIG0_CDEL(x) ((x) << 24)
#define VEC_CONFIG0_SECAM_STD BIT(21)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

More magic fields. CONFIG0 bits 23:19 are defined a reserved, write as 0. Then again it also doesn't say how to select SECAM mode!

Copy link
Author

@kFYatek kFYatek Jun 24, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, that's another product of me just messing around with this register. I read this header, saw that VEC_FCW_SECAM_B and VEC_SECAM_GAIN_VAL registers and was intrigued, especially since nothing else referred to SECAM and I don't think support for SECAM on the Pi was ever seen before. I started flipping bits in this register and when I set this one, again, poof, SECAM appeared.

I empirically confirmed that this mode is detected as SECAM and looks right on a couple of modern devices:

  • Fushicai USBTV007 grabber
  • Samsung UE40E6800S
  • Panasonic TX-L42ET5E
  • Samsung UE65MU8002 (through an RF modulator, this TV does not have any baseband analog inputs)

Again, @Sobakin76 also confirmed this working on their more period-accurate Sony PVM-14N5MDE CRT: raspberrypi/firmware#811 (comment)

EDIT: Note that the artifacts on their photo are a known issue with the SECAM system itself; this is basically what happens on it instead of PAL/NTSC-style "dot crawl" when the image has too much sharp edges across the horizontal axis (exactly like the fine text on that picture screenshot).

(!connector->cmdline_mode.refresh_specified &&
(connector->cmdline_mode.yres == 288 ||
connector->cmdline_mode.yres == 576)))) {
/* no explicitly specified TV norm; use PAL if a mode that
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The drm subsystem is happy with

/* blah
 * blah
*/

but the closing */ does have to be on a separate line.

drm_atomic_helper_connector_reset(connector);
/* preserve TV standard */
if (connector->state)
connector->state->tv.mode = vc4_vec_get_default_mode(connector);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want to reset to the default (as defined by the module parameter), or retain the old setting?
If you change the module parameter (it is permissions 0600) then on the next reset call don't we have the same scenario as at boot where the mode may not match the standard?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right about the comment - checkpatch says "WARNING: Block comments use a trailing */ on a separate line".

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

reset is only called once at boot time, it's what creates the first initial commit. Any state after that is the union of the previous state and whatever has been changed in the various commits that occurred.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Uh, this is actually one of the places where I don't exactly know what I'm doing. From what I understand, this function is called when a new connector "state" object is created. By default, it is more or less just zeroed out, which means also setting tv.mode field to 0, which is NTSC in our case. The default value set through drm_object_attach_property() is not respected for some reason, so without this code, we would end up in NTSC mode at boot regardless of what was set there.

Setting this value later in userspace (xrandr --set mode PAL etc.) still works.

Actually the only other driver in the entire kernel codebase that uses the tv.mode field is i915 - which defaults to NTSC anyway, as pointed out in #4241 (review). nouveau and ch7006 just keep track of it in a separate private variable and use that instead.

I don't know, maybe a cleaner solution would be to patch the DRM subsystem itself so that it would pull the default value from whatever has been set through drm_object_attach_property() - because not respecting that even though the tv_mode_property is automatically mapped to that field seems like a bug. Maybe that should be done directly in vc4_vec_connector_reset() or something, although I'm not sure if that's even possible.

@mripard
Copy link
Contributor

mripard commented Jun 24, 2021

I'm not sure we need to read-out the hardware state. The command line parser explicitly supports named modes, so the firmware could just set video=TV-1:NTSC-J or whatever and the initial framebuffer will use that mode.
Switching back and forth between modes is more problematic though. The userspace is free to build whatever mode it wants, and / or the framebuffer emulation (and thus the initial setup) can be disabled.
In such a case, you don't really have a way to differentiate between variants that use the same mode (like between NSTC-J and PAL-M). Once again, you can only hope that the name will be filled and compare the name to the modes you support, and fallback on PAL or NTSC

Well, unfortunately the current versions of the firmware only pass information such as video=Composite-1:720x480@60i or video=Composite-1:720x576@50i. There is no way to differentiate between NTSC vs. NTSC-J vs. PAL-M or PAL vs. PAL-N, even though all of these are accessible via sdtv_mode.

I agree that what I wrote is a bit of a kludge, and I agree that if the firmware could pass the colour system settings on the kernel's command line, that would be much better. I'm happy to update my code once it does, but for now, reading the VEC register state found at boot is the only way to get this information.

My main issue with reading out the hardware state is that it means that users won't be able to change the TV norm without rebooting, and it has weird interactions with other parts of the system, for example:

  • If the user disables the firmware output in the config.txt, they will be forced into NTSC even though the kernel is supposed to be entirely in charge
  • There's a similar issue if we boot with the HDMI output, and then want to use the TV output
  • If the kernel is build without the simplefb driver and the DRM driver is built as a module, chances are the clocks and power domains of the VEC are going to be disabled at some point, and would make it lose the content of its registers.

The tv_mode parameter you introduced addresses most of these concerns, but it's redundant with the same property that is already set. The command line parser can parse arbitrary properties, so it would make more sense to extend this part:
https://github.com/raspberrypi/linux/blob/rpi-5.10.y/drivers/gpu/drm/drm_modes.c#L1638

Adding another switch statement that fills a new field in drm_cmdline_mode that would store the mode, and then init the tv_mode property to that value here:
https://github.com/raspberrypi/linux/blob/rpi-5.10.y/drivers/gpu/drm/drm_atomic_state_helper.c#L439

would work quite nicely without any ad-hoc solution.

@kFYatek
Copy link
Author

kFYatek commented Jun 24, 2021

My main issue with reading out the hardware state is that it means that users won't be able to change the TV norm without rebooting

This is actually not true. xrandr --output Composite-1 --off && xrandr --output Composite-1 --set mode NTSC-J --mode 720x480 does the job, I do this kind of thing all the time when testing this PR. It works both with the current version of this PR and it also worked with the revision that queried hardware state.

Switching between 525-line (NTSC/NTSC-J/NTSC-443/PAL-M/PAL60) and 625-line (PAL/PAL-N/SECAM) modes requires adding the missing mode via --newmode and --addmode, but otherwise also works. I wonder if maybe that means we should return both modes through vc4_vec_connector_get_modes() - but they cannot be mixed up, i.e. e.g. 720x576 does not work (to the point of outputting complete garbage on the cable) when the norm is set to NTSC, so having a mode that cannot be used without specifying --set mode could be confusing.

@6by9
Copy link
Contributor

6by9 commented Jun 24, 2021

None of it feels totally clean.

Adding an extra property to video= will end up requiring something like video=TV-1:NTSC,tv_mode=NTSC-J as the parser currently insists on a width/height first. Mix and match and nasty things will happen.
Do I recall you discussing allowing properties to be set without the mode? It'd be potentially of use for setting HDMI limited/full range overrides too.

It's also slightly ugly that the cmdline parsing appears to be called from drm_connector_init, so the string names for each of the TV modes haven't been added to the connector at that point (they've been created via drm_mode_create_tv_properties but not drm_object_attach_propertyed to the connector), and there are no standardised enums for these values.

@6by9
Copy link
Contributor

6by9 commented Jun 24, 2021

I've grabbed the Kramer VP719xl that I have access to which can supposedly handle NTSC, NTSC4.43, PAL, PAL-N, PAL-M, SECAM. So it nominally misses NTSC-J and PAL-60, but should be able to tell me about the rest.

Now to find my A/V lead to connect it up....

@kFYatek
Copy link
Author

kFYatek commented Jun 24, 2021

@6by9 If it supports NTSC and PAL, it almost certainly supports PAL-60. It will probably just label it as "PAL", while still syncing at 60Hz.

NTSC-J and regular NTSC are also identical except for the black level; i.e., if you set NTSC-J and never go below about 5% gray level, you'll end up with a compliant NTSC(-M) signal.

So I think your display should handle all the modes.

@kFYatek
Copy link
Author

kFYatek commented Jun 24, 2021

The newest update addresses those couple of cosmetic/formatting issues from @pelwell's and @6by9's reviews.

@6by9
Copy link
Contributor

6by9 commented Jul 1, 2021

Tested against my Kramer (it's a presentation switcher/scaler rather than a display).
Whilst the OSD only tells me NTSC, PAL, or SECAM, the controls allow me to explicitly set the standard, and they are all seemingly decoded with the appropriate setting.

I had expected changing mode (at least between those with the same resolution) wouldn't require a manual disable/reconfigure/enable. That should achievable by setting crtc_state->mode_changed = true; from vc4_vec_encoder_atomic_check, but I don't see a way to get to the old drm_connector_state from an encoder atomic_check to check if it changed or not.
The encoder atomic_check is also seemingly not called if you just change the property.
Ah, vc4_vec also has a struct drm_connector_helper_funcs which can implement the connector atomic_check hook. Doing that also correctly stops you setting a mode that doesn't match the standard (I appear to be able to do that with the current patches, although my current hack ends up doing a NULL deref in drm_atomic_get_crtc_state at present).

It may be this setup, but it looks like it's working at a very reduced bit depth on the VEC output. A quick hack to the overlay so that HDMI is active at the same time and the images are significantly different (I'll try and attach a photo in a bit). I'd be interested to know if you see the same. The desktop looks OK, but a JPEG is quantised.

@kFYatek
Copy link
Author

kFYatek commented Jul 5, 2021

I had expected changing mode (at least between those with the same resolution) wouldn't require a manual disable/reconfigure/enable [...]

I refactored the atomic_check logic to happen in the connector object instead of in the encoder object. That way it is called when just changing properties and this works in the newest revision.

However, Xorg does not check the ioctl return value when setting properties, so if you try to set an invalid value (e.g. xrandr --output Composite-1 --set mode PAL when the resolution is 480i), it will do nothing but X will report as if it has been changed. I guess that's a bug in Xorg, though.

It may be this setup, but it looks like it's working at a very reduced bit depth on the VEC output. A quick hack to the overlay so that HDMI is active at the same time and the images are significantly different (I'll try and attach a photo in a bit). I'd be interested to know if you see the same. The desktop looks OK, but a JPEG is quantised.

I haven't noticed anything like that. What JPEG did you use and what program did you use to display it?

In my experience, there is a bit of banding, but nothing too distracting, and importantly it seems to be more or less the same as in the firmware framebuffer. I did some screenshots with my USB grabber, showing the gradient.png file from rtings.com rendered in Chromium in fullscreen mode (F11).

@6by9
Copy link
Contributor

6by9 commented Jul 6, 2021

Thanks for your perseverance on this.

The refactor to use connector_atomic_check is almost exactly what I'd ended up with. Looks fine.
X doing the wrong thing is X's problem, not the kernel's.

I'd just viewed a JPEG (an unfocused capture from an HQ camera module) and there is a significant difference between the HDMI output and VEC. See this image where the front monitor is on HDMI, and the back one is the VEC fed through the Kramer to give VGA out.
DSC_0103
A greyscale ramp may be the best thing to test with to confirm if it's just my setup or something more fundamental.

@kFYatek
Copy link
Author

kFYatek commented Jul 6, 2021

Whoah. That looks bad indeed. But as you can see on my screenshots linked above, I saw nothing like this.

I found some time ago, however, that the Pi (Pi 4 especially) outputs composite signals that are not exactly the standard voltage. When I used an RF modulator (just a cheapo thing from Amazon) to connect the Pi's composite output to my modern Samsung TV that only accepts analog signals via RF, I found that the brightest parts of the image got clipped. I was able to get the picture to look right by putting a potentiometer between the Pi and the modulator and setting it slightly down from "100%". I can make pictures and measure the actual resistance if you want.

I found that modulator is based on the MC44BS373CA chip, with expects 1 V peak-to-peak video input (see page 7 of the datasheet, among others). The Pi's signal is not exactly that, and it has a nonzero DC component (again, this is especially true on Pi4; on Pi4, I could not find any VEC register tweaks that could fix this issue), which confuses that modulator. All other devices I tested employ some kind of AGC and thus have no problems with its output.

My wild guess is that maybe your Kramer also doesn't like the voltages output by the Pi for whatever reason. Although this weird green tint seems to affect neither the highlights or the shadows, but rather the middle of the brightness range, which is particularly odd and not at all consistent with the voltage mismatch theory...

@pelwell
Copy link
Contributor

pelwell commented Jul 13, 2021

Say the word when you think this is ready for merging.

@kFYatek
Copy link
Author

kFYatek commented Jul 13, 2021

@pelwell I'm not sure who you're asking. I considered it ready from the beginning, and updates were just addressing the reviews ;) With the catch that I expanded on ideas from this PR in #4406, which refactors some code refactored here again - so you might want to take a look there as well.

I was under the impression that we're waiting for acceptance from @mripard and, mostly, @6by9.

@pelwell
Copy link
Contributor

pelwell commented Jul 13, 2021

Understood - I was addressing the reviewers as well.

@popcornmix
Copy link
Collaborator

I'm in favour of merging - it sounds like it's had more thorough testing that the current composite kms code.
But an okay from @mripard / @6by9 would be good.

@6by9
Copy link
Contributor

6by9 commented Jul 14, 2021

Sorry, juggling too many things.
I believe I'm good with these. My last few tests weren't conclusive, but that looked to be my setup rather than the code being wrong.

One last checkpatch complaint (sorry) - patch author doesn't match the Signed-Off-By

ERROR: Missing Signed-off-by: line by nominal patch author 'kFYatek <4499762+kFYatek@users.noreply.github.com>'

If you've updated your standard author email address, then git commit --amend --reset-author will up the current patch. Doing it during a rebase -i allows you to update it on each patch (sorry if I'm teaching you to suck eggs).

Mateusz Kwiatkowski added 6 commits July 15, 2021 01:07
This commit fixes vertical timings of the VEC (composite output) modes
to accurately represent the 525-line ("NTSC") and 625-line ("PAL") ITU-R
standards.

Previous timings were actually defined as 502 and 601 lines, resulting
in non-standard 62.69 Hz and 52 Hz signals being generated,
respectively.

Changes to vc4_crtc.c have also been made, to make the PixelValve
vertical timings accurately correspond to the DRM modeline in interlaced
modes. The resulting VERTA/VERTB register values have been verified
against the reference values set by the Raspberry Pi firmware.

Signed-off-by: Mateusz Kwiatkowski <kfyatek+publicgit@gmail.com>
Change the mode_set function pointer logic to declarative config0,
config1 and custom_freq fields, to make TV mode setting logic more
concise and uniform.

Additionally, remove the superfluous tv_mode field, which was redundant
with the mode field in struct drm_tv_connector_state.

Signed-off-by: Mateusz Kwiatkowski <kfyatek+publicgit@gmail.com>
PAL-M is a Brazilian analog TV standard that uses a PAL-style chroma
subcarrier at 3.575611[888111] MHz on top of 525-line (480i60) timings.
This commit makes the driver actually use the proper VEC preset for this
mode instead of just changing PAL subcarrier frequency.

DRM mode constant names have also been changed, as they no longer
correspond to the "NTSC" or "PAL" terms.

Signed-off-by: Mateusz Kwiatkowski <kfyatek+publicgit@gmail.com>
Add support for the following composite output modes (all of them are
somewhat more obscure than the previously defined ones):

- NTSC_443 - NTSC-style signal with the chroma subcarrier shifted to
  4.43361875 MHz (the PAL subcarrier frequency). Never used for
  broadcasting, but sometimes used as a hack to play NTSC content in PAL
  regions (e.g. on VCRs).
- PAL_N - PAL with alternative chroma subcarrier frequency,
  3.58205625 MHz. Used as a broadcast standard in Argentina, Paraguay
  and Uruguay to fit 576i50 with colour in 6 MHz channel raster.
- PAL60 - 480i60 signal with PAL-style color at normal European PAL
  frequency. Another non-standard, non-broadcast mode, used in similar
  contexts as NTSC_443. Some displays support one but not the other.
- SECAM - French frequency-modulated analog color standard; also have
  been broadcast in Eastern Europe and various parts of Africa and Asia.
  Uses the same 576i50 timings as PAL.

Also added some comments explaining color subcarrier frequency
registers.

Signed-off-by: Mateusz Kwiatkowski <kfyatek+publicgit@gmail.com>
Similar to the ch7006 and nouveau drivers, introduce a "tv_mode" module
parameter that allow setting the TV norm by specifying vc4.tv_norm= on
the kernel command line.

If that is not specified, try inferring one of the most popular norms
(PAL or NTSC) from the video mode specified on the command line. On
Raspberry Pis, this causes the most common cases of the sdtv_mode
setting in config.txt to be respected.

Signed-off-by: Mateusz Kwiatkowski <kfyatek+publicgit@gmail.com>
Replace drm_encoder_helper_funcs::atomic_check with
drm_connector_helper_funcs::atomic_check - the former is not called
during drm_mode_obj_set_property_ioctl(). Set crtc_state->mode_changed
if TV norm changes even without explicit mode change. This makes things
like "xrandr --output Composite-1 --set mode PAL-M" work properly.

Signed-off-by: Mateusz Kwiatkowski <kfyatek+publicgit@gmail.com>
@kFYatek
Copy link
Author

kFYatek commented Jul 14, 2021

No problem. I believe it should be OK now.

@6by9
Copy link
Contributor

6by9 commented Jul 15, 2021

Thank you for fixing those up.

Checkpatch --strict still has one final whinge of

CHECK: No space is necessary after a cast
#62: FILE: drivers/gpu/drm/vc4/vc4_vec.c:368:
+				return (enum vc4_vec_tv_mode_id) i;

total: 0 errors, 0 warnings, 1 checks, 115 lines checked

but I'm happy to ignore that one as otherwise the i disappears into the bracket.

LGTM.

@pelwell pelwell merged commit f665786 into raspberrypi:rpi-5.10.y Jul 15, 2021
@kFYatek kFYatek deleted the fix-composite branch July 15, 2021 08:56
popcornmix added a commit to raspberrypi/firmware that referenced this pull request Jul 16, 2021
kernel: Fix composite output video norms
See: raspberrypi/linux#4241
popcornmix added a commit to Hexxeh/rpi-firmware that referenced this pull request Jul 16, 2021
kernel: Fix composite output video norms
See: raspberrypi/linux#4241
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants