RadeonHD … and some dual-head trickery

So for a year now I’ve been using ati-drivers (fireglrx, ccc, catalyst, watever you want to call that garbage) and wishing every other day that I didn’t have (some versions simply doesn’t work, older versions won’t compile with newer kernels – which I sometimes need for other bugfixes wrt wireless and possibly due to external requirements – asterisk’s dahdi driver comes to mind). So finally during the week when the proverbial shit once more hit the fan where I wanted to upgrade my kernel to 2.6.32 for some intel wireless fixes and the newest ati-drivers simply exploded in my face and the version just down didn’t want to work … I decided it’s time. Time to try the OSS drivers that didn’t want to work slightly over a year back again. And this time the story is quite different …

It’s beautiful. DPI gets detected properly. Full RandR support. External monitor detection. Faster login times (No, I kid you not, ssh-agent’s askpass locks the screen, with fglrx this takes up to 20 seconds, with the OSS drivers it’s instantaneous). Heck, I haven’t yet found anything I don’t like. Don’t get me wrong, it wasn’t as easy as I’d like it to be. The radeonhd (xf86-drivers-radeonhd) driver which is what I though I needed works well, permitting I don’t connect up my external HDMI screen (also full HD at 1920×1200). It took me two days to figure out that if I just knock the external screen res down from max it works, and different modeline workarounds posed by some people just failed horribly. Eventually I also realized that the modelines for the other resolutions was wrong too (display offsets and stuff just didn’t look right) so I read some more and realized that there is also an “ati” driver (xf86-drivers-ati for those in the Gentoo world).

Essentially it boils down to this: enable AGP in your kernel, load up drm module, as well as the radeon kernel module, and set the driver in your device section to ati. Yes, it’s that simple. So what should have been a 20 minute configuration exercise took me the better part of the evenings of three days. The applicable snippets from my /etc/X11/xorg.conf (I have a nasty feeling you don’t even need this file, but then some of my other trickery won’t work):

Section "ServerLayout"
       Identifier     "DefaultLayout"
       Screen         "PrimaryScreen" 0 0

Section "Monitor"
        Identifier  "LVDS"
        Option      "Position" "0 0"
        Option      "PreferredMode" "1920x1200"

Section "Device"
        Identifier  "radeon"
        BusID       "PCI:1:0:0"
        Driver      "ati"
        # enable some power saving at
        # the potential cost of 3D accel
        Option "DynamicClocks" "on"

Section "Screen"
        Identifier "PrimaryScreen"
        Device     "radeon"
        Monitor    "LVDS"

        DefaultDepth     24

        SubSection "Display"
                Depth     24
                # Set our virtual size to 2*1920 x 1200
                Virtual   3840  1200

I’ve got other sections too for getting the synaptics touch pad to work “just right”, and for enabling composite on the keyboard and stuff, so I’m not sure that just copying the above file will result in a working X. All of the above is focused purely around the video. In spite of setting a Virtual screen size, this won’t enable panning, and neither will it allow your cursor to run off the screen, the initial desktop size will be 1920×1200 and if an external screen is connected it will likely mirror on startup (at least, that’s what it does when I restart X).

Now you can play a bit with the xrandr command. With no external screen connected I get this:

$ xrandr
Screen 0: minimum 320 x 200, current 1920 x 1200, maximum 3840 x 1200
VGA-0 disconnected (normal left inverted right x axis y axis)
LVDS connected 1920x1200+0+0 (normal left inverted right x axis y axis) 367mm x 229mm
   1920x1200      61.7*+
   1920x1080      60.0  
   1600x1200      59.9  
   1680x1050      60.0  
   1400x1050      60.0  
   1280x1024      59.9  
   1440x900       59.9  
   1280x960       59.9  
   1280x854       59.9  
   1280x800       59.8  
   1280x720       59.9  
   1152x768       59.8  
   1024x768       59.9  
   800x600        59.9  
   640x480        59.4  
HDMI-0 disconnected (normal left inverted right x axis y axis)

Note the first line that basically defines the screen as 1920×1200 with the max we set above. This gives us space to once the external monitor is connected to HDMI-0 either mirror, or put it side-by side with the existing screen, so connect the screen, running xrandr now should change the HDMI-0 output to something like:

HDMI-0 connected (normal left inverted right x axis y axis)
   1920x1200      60.0 +
   1920x1080      60.0  
   1600x1200      60.0  
   1680x1050      59.9  
   1280x1024      60.0  
   1440x900       59.9  
   1280x960       60.0  
   1280x800       59.9  
   1024x768       60.0  
   800x600        60.3     56.2  
   640x480        59.9

Which is all good and well, to make use of that screen real-estate:

xrandr --output HDMI-0 --mode 1920x1200 --right-of LVDS
xrandr --output HDMI-0 --mode 1920x1200 --same-as LVDS

Depending on whether you want a side-by side or a mirrored setup. You can also do a –left-of but on my laptop at least that doesn’t make sense in my head (primary stuff all goes on the left-most screen and I prefer to use my panel as primary with the additional screen as secondary, but I guess for some people that would be preferred). Note that on my system at least both displays goes black for about 2 seconds during the mode switches and stuff.

Now, the one thing that does bug me is that there is no sensible handing of Fn-F3 (the external screen switch function). Fn-F2 (panel on/off) is handled by the BIOS, properly, but Fn-F3 is not. So what you do is firstly write a script that’ll do modeswitching for you when run from the shell. I have $HOME/bin in my path so I just wrote a script that I store in there called dualmode. It’s a relatively dumb script, but non-trivial none the less:

#! /bin/bash


export DISPLAY=:0

now=$(date +%s)
last=$([ -r $HOME/.dualmode ] && stat --format=%Y $HOME/.dualmode || echo 0)
last_time=$(( $now - $last ))
mode=$([ -r $HOME/.dualmode ] && cat $HOME/.dualmode || echo "same-as")
echo "last run $last_time seconds ago"

state=$(xrandr | awk '$1!="LVDS" && / connected [0-9]+x[0-9]+/ { on=1 } END { if(on) { print "on" } else { print "off" }}')
echo "Dual head is currently $state"

if [[ $state = "on" ]] && [[ $last_time -lt $modetime ]]; then
        echo -n "Changing from mode $mode to "
        [[ $mode = "same-as" ]] && mode="right-of" || mode="same-as"
        echo $mode

xrandr | awk '/^[^ ]/ && $1!="Screen" { conn=$1 } /+$/ { if (conn != "LVDS") { print conn" "$1 }}' | while read OUTP RES; do
        if [[ $state = "on" ]] && [[ $last_time -ge $modetime ]]; then
                echo "Switching off display $OUTP"
                xrandr --output $OUTP --off
                echo "Attempting to set $OUTP to $RES using $mode"
                xrandr --output $OUTP --mode $RES --$mode LVDS

killall -HUP icewm

echo "All done."
echo $mode > $HOME/.dualmode

Right at the top the modetime value is simply a setting that says “if the external screen is on and we haven’t touched ‘the button’ in less than this number of seconds, switch the external display off instead of switching between mirroring and side-by-side mode”. Next up we determine the duration from when the script was last run. mode= determines the last mode (right-of or mirror). The state= line uses xrandr+awk to try and determine whether an external display is active or not (if your primary display is not LVDS then change as required).

So, firstly we look at the state, and if the state is currently on and we have run this script recently, change the mode to be used.

Next up, for each non-LVDS screen, figure out it’s “preferred” resolution (marked by a + in the xrandr output), this has the side effect of giving us a list of connected displays. Once we have this information we can decide whether the display should be switched off (current state is on and active for longer than modetime) or switch it to the appropriate mirror/right-of mode as well as kick it into preferred resolution.

I also send a HUP signal to icewm (my window manager) to get it to reload and realize that the desktop dimensions has changed, and lastly I store the new mode in ~/.dualmode (both so I can know what I need to switch to, as well as to be able to measure the time between two runs of the script.

And if you’re wondering, the export DISPLAY line is because we’re going to hook this from ACPI :). I (obviously) use the non-root users and ACPI setup, and then in $HOME/.acpi/video I have this:

#! /bin/bash

case $2 in
        echo $@ >> /tmp/video-event.txt

This just filters out some other video events (brighness comes through on video/LCD whereas the button comes through on video/VGA, so if the second parameter is VGA, run the dualmode script.

So far this works very well.

Comments are closed.