[RESOLVED] Even lower screen brightness?

@shazow in sway you can turn the display off with swaymsg "output * power off. Other Wayland compositors should have similar functionality and XOrg has the same functionality with xrandr --output <panel name here> --off (you should be able to see a list of panels with just xrandr). You’ll just have to write a bash script etc that wraps how you change brightness. To turn them back on, just swap off to on.

On the Framework 16 using Arch Linux (also verified on Manjaro), the brightness does not go anywhere near dim enough for use in low-light environments (and is substantially brighter than my thinkpad e595). I wrote the following script to semi-workaround this.

This supports shifting either the brightness or the color temperature of the display by the specified value using redshift. Displays a re-usable notification with a progress bar to indicate the current level (tested using libnotify version 0.8.3).

This also works around the inability to detect the current brightness/color by saving the last set value and making the next change based on that.

Example usage (particularly useful if bound to hotkeys):

displayshift.sh light -25 #decreases brightness by 0.25
displayshift.sh light 10 #increases brightness by 0.1
displayshift.sh temp -525 #decreases color temperature by 525
displayshift.sh temp 200 #increases color temperature by 200

displayshift.sh:

#!/bin/bash

#usage: displayshift.sh [light|temp] [0-9]+
#light range: 1-100
#color range: 1000-25000


#--settings--

#notification icons (defaults are in papirus-icon-theme)
icolight="notification-display-brightness-medium"
icotemp="colortone"
icotemp_default="emblem-default" #shown when color temperature is exactly 6500 (default)

#cache location / files
cachefol="$HOME/.cache/displayshift"
cachelight="$cachefol/backlight.dat"
cachetemp="$cachefol/colortemp.dat"
cachenotid="$cachefol/notify.dat"


#--functions--

to_int(){ [[ "$1" =~ ^[\-]?[0-9]+$ ]] && echo "$1" || echo 0; }
msg_err(){ notify-send -i dialog-error "displayshift" "$@" ; }
msg_light(){ notify-send -i $icolight -h int:value:$newlight -r "$notid" -p " " ; }

msg_temp(){
  tempico="$icotemp"; [[ "$newtemp" = "6500" ]] && tempico="$icotemp_default"
  notify-send -i $tempico -h int:value:$((newtemp*4/1000)) -r "$notid" -p " "
}


#--main--

[[ -d "$cachefol" ]] || mkdir "$cachefol" || (msg_err "err: failed to create cache folder"; exit)


#read params
case "$1" in
  light) chnglight="$2"; chngtemp=0 ;;
  temp) chnglight=0; chngtemp="$2" ;;
  *) msg_err "err: invalid parameter"; exit ;;
esac
chnglight="$(to_int "$chnglight")"
chngtemp="$(to_int "$chngtemp")"


#read cache data
if [[ -f "$cachenotid" ]]; then
  notid="$(cat "$cachenotid")"
else notid=0; fi
notid="$(to_int "$notid")"

if [[ -f "$cachelight" ]]; then
  curlight="$(cat "$cachelight")"
  [[ "${curlight:0:1}" = "0" ]] && curlight="${curlight:1}"
else curlight=100; fi
curlight="$(to_int "$curlight")"

if [[ -f "$cachetemp" ]]; then
  curtemp="$(cat "$cachetemp")"
else curtemp=6500; fi
curtemp="$(to_int "$curtemp")"


#calculate new, ensure within valid range
newlight="$((curlight+chnglight))"
[[ "$newlight" -lt 1 ]] && newlight=1
[[ "$newlight" -gt 100 ]] && newlight=100
[[ "$newlight" -lt 100 ]] && newlight="0$newlight"
[[ "$curlight" -lt 100 ]] && curlight="0$curlight"

newtemp="$((curtemp+chngtemp))"
[[ "$newtemp" -lt 1000 ]] && newtemp=1000
[[ "$newtemp" -gt 25000 ]] && newtemp=25000


#make changes (if needed)
if { [[ "$1" = "light" ]] && [[ ! "$newlight" = "$curlight" ]]; } || \
  { [[ "$1" = "temp" ]] && [[ ! "$newtemp" = "$curtemp" ]]; }; then

  if redshift -P -O $newtemp -b ${newlight:0:1}.${newlight:1:1}:${newlight:0:1}.${newlight:1:1}; then
    case $1 in
      light) echo "$newlight">"$cachelight" ;;
      temp) echo "$newtemp">"$cachetemp" ;;
    esac
  else msg_red "err: failed to run redshift with specified value"; exit; fi

fi


#final notification
case $1 in
  light) msg_light>"$cachenotid" ;;
  temp) msg_temp>"$cachenotid" ;;
esac
6 Likes

How is this marked [RESOLVED] when there’s no resolution here? I’m also on an Framework 13 AMD and the lowest brightness level is way too bright.

3 Likes

Have you tried Plasma? Turning to 1 in the default slider completely disables the backlight, if that’s still too bright you can fiddle the nightmode; which in effect is doing what redshift / others have suggested which changes the colour temperatures.

This should be a limitation of your desktop environment/window manager.

Btw, hi Andrey :slight_smile:

I can certainly disable the screen altogether, regardless of desktop environment, but that’s not the same thing as turning the backlight to zero.

Admittedly I am on x11 and not wayland, not sure if there’s a workaround in wayland that works better (perhaps Plasma is doing some magic there?).

In either case, relying on dpms and temperature shifting to approximate functionality is still just workarounds for what appears to be a bug/limitation of the AMD frameworks’ firmware (a limitation that is not present on the intel version, or any other laptop I’ve used). :slight_smile:

Perhaps it’s worth opening a fresh thread?

P.S. Hey fellow Python http alum! :slight_smile:

2 Likes

This was specifically for this sentence:

On my sway (wayland) setup if I do the following:

swaymsg 'output eDP-1 dpms off'; sleep 10; swaymsg 'output eDP-1 dpms on'

Then I can wildly use input devices during those 10 seconds and the screen stays black.

From a kernel perspective the semantics of brightness = 0 are not well-defined.
Different drivers implement different behaviors.

There is ongoing work to get a new backlight API, with a well-defined 0 value.
But that definition is “just enough to be readable”, for “off” use dpms.

FWIW I forced the kernel to override the limits received from ACPI and set the PWM to a hard 0.
But that still didn’t turn off the screen completely.
So it may be in the GPU firmware.

That’s fair. I won’t make an argument that the driver does not technically comply within the boundaries of the the kernel specifications.

I am making the argument that from a Framework customer perspective, particularly people in this thread (and from a consistency perspective of the Intel version of the device, and the vast majority of other consumer laptops like Thinkpads and Macbooks): Framework should consider changing this behaviour on their AMD devices. :slight_smile:

1 Like

The backlight turning off works both on fedora39 and 40 using those respective DE brightness sliders when set to 0 on the AMD FW13. I don’t think this has anything to do with Framework it’s whatever userspace you’re using and/or configuration that’s been changed from the initial state. I don’t know I haven’t used Xorg in years, but I would suggest testing on a fedora40 live distro in and seeing if you have the same issue. You’re the first user i’ve seen who hasn’t been able to get the backlight turned off i’ve come across on the forums.

1 Like

Regarding the lowest brightness (while still being on), I agree this shouldn’t be marked as “resolved”. There is a workaround that helps (redshift or other tools to darken the displayed image), but not enough IMHO.

It really does not go low enough for use in low-light environments. It is still uncomfortable/harsh to look at in the darkest of environments even when using redshift. Other laptop screens can dim the backlight considerably lower than the AMD-powered framework 16.

Case in point:

Left: framework 16
Right: thinkpad e595

Both screens are showing the same completely white image at their lowest brightness (brightnessctl s 0%) (without the use of redshift or similar software).

I should mention: in the picture, the thinkpad has no screen protector, while the framework has this “blue light blocking” one

5 Likes

I too agree that the lowest brightness is much too high. Using night light makes it somewhat bearable, though it remains a workaround.

According to this post, the matte panel simply has a much higher rated minimum brightness than the glossy one (20 nits vs 4 nits, respectively). Matt promised to provide some more info on the subject in that same thread, but hasn’t come back to it yet.

5 Likes

Even if it is a hardware problem it would be great to have a native software solution from Framework. The lowest brightness on the laptop is higher than my monitor at 50% brightness. I avoid using the laptop past 19:00 because of that.

Marking something as solved without a solution is quite upsetting.

2 Likes

I tested this again, and while sending a PWM signal of 0 to the GPU does not completely turn off the display, it is still considerably less bright than by default.

You can test this with the following patch

diff --git a/drivers/gpu/drm/amd/display/dc/link/protocols/link_edp_panel_control.c b/drivers/gpu/drm/amd/display/dc/link/protocols/link_edp_panel_control.c
index ad9aca790dd7..42f1f476ec67 100644
--- a/drivers/gpu/drm/amd/display/dc/link/protocols/link_edp_panel_control.c
+++ b/drivers/gpu/drm/amd/display/dc/link/protocols/link_edp_panel_control.c
@@ -521,12 +521,18 @@ bool edp_set_backlight_level(const struct dc_link *link,
                uint32_t backlight_pwm_u16_16,
                uint32_t frame_ramp)
 {
+       static bool force_real_zero;
        struct dc  *dc = link->ctx->dc;
 
        DC_LOGGER_INIT(link->ctx->logger);
        DC_LOG_BACKLIGHT("New Backlight level: %d (0x%X)\n",
                        backlight_pwm_u16_16, backlight_pwm_u16_16);
 
+       if (force_real_zero && backlight_pwm_u16_16 < 5000)
+               backlight_pwm_u16_16 = 0;
+       force_real_zero = !force_real_zero;
+
+
        if (dc_is_embedded_signal(link->connector_signal)) {
                struct pipe_ctx *pipe_ctx = get_pipe_from_link(link);

And then repeatedly do echo 0 > /sys/class/backlight/amdgpu_bl1/brightness and observe the different brightness levels.

For a “permanent” “fix” use this:

diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_acpi.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_acpi.c
index 7099ff9cf8c5..6e463e5860e5 100644
--- a/drivers/gpu/drm/amd/amdgpu/amdgpu_acpi.c
+++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_acpi.c
@@ -383,6 +383,9 @@ static int amdgpu_atif_query_backlight_caps(struct amdgpu_atif *atif)
                        characteristics.min_input_signal;
        atif->backlight_caps.max_input_signal =
                        characteristics.max_input_signal;
+
+       /* Framework Firmware returns "12" by default. */
+       atif->backlight_caps.min_input_signal = 0;
 out:
        kfree(info);
        return err;

This is just some constant in the Firmware which should be easily changable for Framework.
(I don’t see a way to easily override this as a user)

Only tested on a matte panel.

6 Likes

Can confirm that using that kernel driver patch to force override the minimum value provided by the firmware makes it much better. With that, it’s now much more inline with expectations.

Left: framework 16
Right: thinkpad e595

with patch:

without patch:

4 Likes

I would like to experiment with this patch. Could you or @Thomas_Weissschuh point me in the direction of how to compile (?) the modified amdgpu driver and configure my system to use it? What are the keywords I should search for?

I am running Fedora 39.

@gaben

This should work: Building a Custom Kernel :: Fedora Docs

But further than that I have no clue about Fedora.

I sent a quirk to the kernel: [PATCH] drm/amd: force min_input_signal to 0 on Framework AMD 13/16 - Thomas Weißschuh
Let’s see if it can be made to work out of the box.

5 Likes

Shouldn’t this just be enabled for the default matte panel? And not for the glossy panel and the new, recently launched panel?

Aside from that, I suppose a Framework firmware patch would be the best long-term solution?

Depends on what it does on those panels.
Somebody would need to test it.

Yes.

I did measurements for fw13 old glossy panel and fw16 panel. On each graph there are 2 curves – unpatched (higher) and patched (lower) version. Axis X – value set in /sys/.../amdgpu_bl0/brightness, axis Y – brightness measured by a sensor in some relative units.

Fw13 original glossy (~2022)

Fw16

With the patch above, for both panels min brightness goes 5.5x down.

As I understand it, framework folks aren’t going to patch firmware for 16" model, since current value is already minimal per panel’s datasheet. It works nicely outside of spec, though.

2 Likes