Macbook Pro 5,x Display Switching Linux

#include <stdio.h>
#include <sys/io.h>

#define PORT_SWITCH_DISPLAY 0x710
#define PORT_SWITCH_SELECT 0x728
#define PORT_SWITCH_DDC 0x740
#define PORT_DISCRETE_POWER 0x750

static int gmux_switch_to_igd()
{
outb(1, PORT_SWITCH_SELECT);
outb(2, PORT_SWITCH_DISPLAY);
outb(2, PORT_SWITCH_DDC);
return 0;
    }

    static void mbp_gpu_power(int state)
{
    outb(state, PORT_DISCRETE_POWER);
    }

int main(int argc, char **argv)
{
if (iopl(3) < 0) {
perror ("No IO permissions");
return 1;
}
mbp_gpu_power(0);
gmux_switch_to_igd();
return 0;
}

compile with gcc -O2 igd.c -o igd

  • dgd.c
    based on the source above with the correct gmux values for the dedicated chip
#include <stdio.h>
#include <sys/io.h>

#define PORT_SWITCH_DISPLAY 0x710
#define PORT_SWITCH_SELECT 0x728
#define PORT_SWITCH_DDC 0x740
#define PORT_DISCRETE_POWER 0x750

static int gmux_switch_to_dgd()
{
outb(2, PORT_SWITCH_SELECT);
outb(3, PORT_SWITCH_DISPLAY);
outb(3, PORT_SWITCH_DDC);
return 0;
    }

int main(int argc, char **argv)
{
if (iopl(3) < 0) {
perror ("No IO permissions");
return 1;
}
gmux_switch_to_dgd();
return 0;
}

compile with gcc -O2 dgd.c -o dgd

  • copy them into your /bin directory.

 

Now it gets a bit -not so elegant-. I will have a look for a better solution but since it works for me I share my first try.

  • Create 2 boot options in your bootloader
    one with the parameter gpu=i, and one with gpu=d
    - for example, if you use grub, duplicate the file /etc/grub.d/10_linux to /etc/grub.d/10_linux_2 or something
    - search for “${GRUB_CMDLINE_LINUX} ${GRUB_CMDLINE_LINUX_DEFAULT}”
    - add “gpu=i” in 10_linux inside the string above as a last parameter. do the same with 10_linux_2 but with “gpu=d”
  • create the new grub.cfg (grub2-mkconfig -o path/to/your/grub.cfg)
  • create two xorg.conf files in /etc/X11:
    - use nvidia-xconfig to create an xorg.conf for you
    - delete everything but the Section “Device”
    - edit your xorg.conf and add this Option to your “Device” Section:
    BusID           “PCI:2:0:0″
    - Save the new xorg as xorg.conf_d in the same dir
    - Change the String to:
    BusID           “PCI:3:0:0″
    - And save it as xorg.conf_i
  • Blacklist the nvidia driver
    blacklisting first makes sure the nvidia driver isn’t initialized before we disable the dedicated chip. this helps using opencl applications.
    - you should have blacklisted nouveau before somewhere (/etc/modprobe.d/*).
    - add the line “blacklist nvidia”
  • Create a Shell Script in /bin (like “changegpu.sh”):

#!/bin/sh

gpu=`cat /proc/cmdline|awk '{print match($0,"gpu=i")}'`
if [ $gpu -gt 0 ] ;then
    cp /etc/X11/xorg.conf_i /etc/X11/xorg.conf
    echo "xorg_i"
    /bin/igd
    sleep 1
    /sbin/modprobe nvidia
    echo "igd executed"
fi

gpu=`cat /proc/cmdline|awk '{print match($0,"gpu=d")}'`
if [ $gpu -gt 0 ] ;then
    cp /etc/X11/xorg.conf_d /etc/X11/xorg.conf
    echo "xorg_d"
    /bin/dgd
    sleep 1
    /sbin/modprobe nvidia
    echo "dgd executed"
fi

This captures your bootparameters and searches for your gpu=x string. I don’t know how to create environment variables or something I could work with from the beginning. So it’s unlikely that the string gpu=x will appear somewhere else in the parameters it should do it for now.
The script copies a xorg.conf file as the default one (use symlinks if you prefer) and runs the application that switches the gmux.

  • Make the script executable with “chmod +x /bin/changegpu.sh”

Now we can boot with the GPU we want to be enabled and screened. The last step is to execute the script on resume from sleep/hibernation because if using the integrated chip, the dedicated one will be enabled again when you resume.

  • Create a new file:
[Unit]
Description=Init GPU . . .
After=suspend.target hibernate.target hybrid-sleep.target basic.target

[Service]
User=root
Type=simple
ExecStart=/bin/changegpu.sh

[Install]
WantedBy=suspend.target hibernate.target hybrid-sleep.target basic.target

This Service runs on boot and after every resume event.

  • Save your newly created service to “/etc/systemd/system/setgpu.service” and enable it with “systemctl enable setgpu”
  • Reboot and choose your GPU in your bootloader

There is a way to make this work on with vgaswitcheroo and the nouveau drivers. but the 9400M in this notebook won’t run in accelerated mode so for me it’s useless.

this is tested with arch, fedora and opensuse

with this setup it doesn’t matter which gpu is activated at boot time (os x respectively) because the gmux values are always set at boot time depending on your bootloader choice.

selected and not so regulary updated works