Porting Code Snippets

From iPodLinux

Jump to: navigation, search


Contents


Introduction

iPodLinux is a modified version of uClinux, which is a modified version of Linux. Thus, many simple console and graphical applications can easily be ported over to iPodLinux with some modifications. This page contains general and iPod-specific code snippets that may help you in your port. For console/text-based applications, a simple cross-compile and input re-mapping should be sufficient. For graphical apps (e.g. emulators), you must also deal with video and possibly volume code.


For an example of a console-based port, see the source code modifications for TinySID here. For examples of graphical ports, see the source code modifications for hWolf3D here, iAtari800 here, or igpSP here (listed in order of increasing complexity). Make sure you are familiar with cross-compiling for iPodLinux, i.e. make sure you have already read the Introduction_to_Compiling_for_iPodLinux tutorial.


General

Features compatibility table

Porting an application requires you to know and keep in mind that each iPod model is different and thus may need different code. Here's the table of features and their compatibilities with different iPod models as far as I am aware (please update accordingly):

Generation Input (console) Input (SDL) Input (hardware) Input (scroll wheel) Sound Backlight Backlight brightness Contrast CPU speed Video (SDL) Video (hotdog) COP
1G iPod Y Tick.png Y Tick.png Y Tick.png N Cross.png Y Tick.png Y Tick.png Y Tick.png1 4 Y Tick.png N Cross.png 1
2G iPod Y Tick.png Y Tick.png Y Tick.png N Cross.png Y Tick.png Y Tick.png Y Tick.png1 4 Y Tick.png N Cross.png 1
3G iPod Y Tick.png Y Tick.png Y Tick.png N Cross.png Y Tick.png Y Tick.png Y Tick.png1 4 Y Tick.png N Cross.png 1
4G iPod Y Tick.png Y Tick.png Y Tick.png 1 Y Tick.png Y Tick.png Y Tick.png1 1 Y Tick.png N Cross.png 1
iPod mini 1G Y Tick.png Y Tick.png Y Tick.png 1 Y Tick.png Y Tick.png Y Tick.png1 1 Y Tick.png N Cross.png 1
iPod mini 2G Y Tick.png Y Tick.png Y Tick.png Y Tick.png Y Tick.png Y Tick.png Y Tick.png1 1 Y Tick.png N Cross.png 1
iPod photo/colour Y Tick.png Y Tick.png Y Tick.png Y Tick.png Y Tick.png Y Tick.png Y Tick.png Y Tick.png Y Tick.png Y Tick.png
iPod nano 1G Y Tick.png Y Tick.png Y Tick.png Y Tick.png Y Tick.png Y Tick.png Y Tick.png Y Tick.png Y Tick.png Y Tick.png Y Tick.png
5G/5.5G iPod video Y Tick.png Y Tick.png Y Tick.png Y Tick.png Y Tick.png Y Tick.png Y Tick.png Y Tick.png Y Tick.png Y Tick.png Y Tick.png
Sansa e200 V1 Y Tick.png Y Tick.png N Cross.png Y Tick.png Y Tick.png Y Tick.png2 N Cross.png3 Y Tick.png Y Tick.png Y Tick.png Y Tick.png
1 - Code exists but unsure if it works
2 - Current kernel code flashes the screen white before turning on (needs fixing)
3 - Needs implementation (see Rockbox's code)
4 - A magic constant isn't set so the frequency doesn't go above 66MHz. A fix for that can be found in Rockbox' source.

As shown above, not all features work on all iPod models. As well, some features require different code for different models. Thus you may or may not want to try supporting every model for simplicity reasons. Since the population of monochrome iPod owners (i.e. iPod mini or older) is small, you should probably just aim for iPod photo/colour, iPod nano and iPod video support (and Sansa e200 support if you have one or are willing to experiment). Regardless, the first thing you will need to do is retrieve the iPod's mode/generation (told by the iPod's hardware version) so you can use it for checks later on.

iPod model via hotdog

If you are using hotdog, you can retrieve the information when you're initializing the video code.

int IPOD_HW_VER, IPOD_LCD_TYPE, IPOD_WIDTH, IPOD_HEIGHT;
 
void ipod_init_video()
{
    HD_LCD_Init();
    HD_LCD_GetInfo(&IPOD_HW_VER, &IPOD_WIDTH, &IPOD_HEIGHT, &IPOD_LCD_TYPE);
    // Continue on with rest of code - see ==Video== section
    ...

iPod model via code

If you are not using hotdog, you will have to get the iPod hardware version manually

int IPOD_HW_VER;
 
static long ipod_get_generation() 
{
    static long gen = 0;
    if (gen == 0) {
        int i;
        char cpuinfo[256];
        char *ptr;
        FILE *file;
 
        if ((file = fopen("/proc/cpuinfo", "r")) != NULL) {
            while (fgets(cpuinfo, sizeof(cpuinfo), file) != NULL)
                if (strncmp(cpuinfo, "Revision", 8) == 0)
                    break;
            fclose(file);
        }
        for (i = 0; !isspace(cpuinfo[i]); i++);
        for (; isspace(cpuinfo[i]); i++);
        ptr = cpuinfo + i + 2;
 
        gen = strtol (ptr, NULL, 16);
    }
    return gen;
}
 
void ipod_init()
{
    IPOD_HW_VER = ipod_get_generation() >> 16;
    // Continue on with rest of initiation code
    ...

Hardware versions

The corresponding values of IPOD_HW_VER for each iPod model is as follows:

#define IPOD_SANSA      0x0
#define IPOD_VIDEO      0xB
#define IPOD_NANO       0xC
#define IPOD_PHOTO      0x6
#define IPOD_MINI_2G    0x7
#define IPOD_MINI_1G    0x4
#define IPOD_MONO_4G    0x5
#define IPOD_MONO_3G    0x3
#define IPOD_MONO_2G    0x2
#define IPOD_MONO_1G    0x1

Note that to differentiate between an iPod photo and iPod colour, use the following code:

switch (IPOD_HW_VER) {
    IPOD_PHOTO: // photo, color
        if (ipod_get_generation() == 0x60000) {
            //iPod photo
        } else {
            //iPod colour
        }
        break;
    // Other iPod models
}

Keep in mind that you will most likely not need to differentiate between the two models as the kernel already handles most discrepancies.

Header imports

The following code snippets use various header files (some Linux-specific, some from the C library) and rather than keeping track of which headers are needed for which code snippets, I've lumped them together here. Feel free to stick this all into a ipod_common.h file or whatnot.

#include <ctype.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <termios.h>
#include <linux/kd.h>
#include <sys/ioctl.h>
#include <sys/soundcard.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/types.h>
#include "hotdog.h" // If you're using hotdog
#include "SDL.h" // If you're using SDL (you'll probably need to include a lot more than this)

Input

There are two sources of input for the iPod: the scroll wheel and the buttons. For 4G iPods and newer, both are available via direct hardware access; for 3Gs and older, the scroll wheel cannot detect touches so only button presses can be used. The Sansa e200 does not have a touch-sensitive scroll wheel but does have two extra buttons (power and record). Which code snippets you decide to use depends on the complexity of your port. Some emulators or applications keep track of buttons presses or button holding states in their own format. For these, you will want to use SDL's or hardware access and for each case you may be returning another constant representing a key value (e.g. return BUTTON_B) or you may be toggling a bit (e.g. pressed_buttons |= BUTTON_B; for press, pressed_buttons &=~BUTTON_B; for release).

Button presses via console reading

The simplest method of getting input is directly reading the characters that are usually printed out to the console. This method should only be used if input is only needed occasionally and for minor things (i.e. volume changing, press-to-confirm, etc.). If you're application will constantly require interaction, do not use this.

#define KEY_MENU    'm'  // Up
#define KEY_PLAY    'd'  // Down
#define KEY_REWIND  'w'  // Left
#define KEY_FORWARD 'f'  // Right
#define KEY_ACTION  '\r' // Select
#define KEY_HOLD    'h'  // Hold
#define KEY_REC     'c'  // Record (Sansa e200 only)
#define KEY_POWER   'p'  // Power (Sansa e200 only)
#define SCROLL_L    'l'  // Counter-clockwise
#define SCROLL_R    'r'  // Clockwise
 
#define SCROLL_MOD_NUM    13   // 100 / 8 = 12.5 (experiment and change this value to whatever you want)
 
#define SCROLL_MOD(n) \
    ({ \
        static int scroll_count = 0; \
        int use = 0; \
        if (++scroll_count >= n) { \
            scroll_count -= n; \
            use = 1; \
        } \
        (use == 1); \
    })
 
static struct termios stored_settings; 
 
static void ipod_init_input()
{
    struct termios new_settings;
    tcgetattr(0,&stored_settings);
    new_settings = stored_settings;
    new_settings.c_lflag &= ~(ICANON | ECHO | ISIG);
    new_settings.c_iflag &= ~(ISTRIP | IGNCR | ICRNL | INLCR | IXOFF | IXON);
    new_settings.c_cc[VTIME] = 0;
    tcgetattr(0,&stored_settings);
    new_settings.c_cc[VMIN] = 1;
    tcsetattr(0,TCSANOW,&new_settings);
}
 
static void ipod_exit_input()
{
    tcsetattr(0,TCSANOW,&stored_settings);
}
 
static char ipod_get_keypress()
{
    fd_set rd;
    struct timeval tv;
    int n;
    char ch;
 
    FD_ZERO(&rd);
    FD_SET(0, &rd);
    tv.tv_sec = 0;
    tv.tv_usec = 100;
    n = select(0+1, &rd, NULL, NULL, &tv);
    if (!FD_ISSET(0, &rd) || (n <= 0))
        break;
    read(0, &ch, 1);
    return ch;
}
 
void ipod_parse_input()
{
    char input;
    input = ipod_get_keypress();
    switch(input) {
        case KEY_MENU:
            // Usually up
            break;
        case KEY_PLAY:
            // Usually down
            break;
        case KEY_REWIND:
            // Usually left
            break;
        case KEY_FORWARD:
            // Usually right
            break;
        case KEY_ACTION:
            // Usually enter/confirm
            break;
        case KEY_HOLD:
            // Usually enter menu
            break;
        case KEY_REC:
            // Usually option/select
            break;
        case KEY_POWER:
            // Usually start/menu
            break;
        case SCROLL_L:
            if (SCROLL_MOD(SCROLL_MOD_NUM)) {
                // Strafe left
            }
            break;
        case SCROLL_R:
            if (SCROLL_MOD(SCROLL_MOD_NUM)) {
                // Strafe right
            }
            break;
        default:
            break;
}

Button presses via SDL

If you're porting a graphical application that already uses SDL, you're in luck. The iPodLinux SDL port already has code for detecting the button presses and state of the presses as SDL_Events. You can get precompiled libraries here in each "pre-built" folder.

#define KEY_MENU     SDLK_m        // Up
#define KEY_PLAY     SDLK_d        // Down
#define KEY_REWIND   SDLK_w        // Left
#define KEY_FORWARD  SDLK_f        // Right
#define KEY_ACTION   SDLK_RETURN   // Select
#define KEY_HOLD     SDLK_h        // Hold
#define KEY_REC      SDLK_c        // Record (Sansa e200 only)
#define KEY_POWER    SDLK_p        // Power (Sansa e200 only)
#define SCROLL_L     SDLK_l        // Counter-clockwise
#define SCROLL_R     SDLK_r        // Clockwise
 
#define SCROLL_MOD_NUM    13   // 100 / 8 = 12.5 (experiment and change this value to whatever you want)
 
#define SCROLL_MOD(n) \
    ({ \
        static int scroll_count = 0; \
        int use = 0; \
        if (++scroll_count >= n) { \
            scroll_count -= n; \
            use = 1; \
        } \
        (use == 1); \
    })
 
void ipod_parse_input()
{
    static int input, pressed;
    SDL_Event event;
    if (SDL_PollEvent(&event)) {
        switch (event.type) {
            case SDL_KEYDOWN:
                input = event.key.keysym.sym;
                pressed = 1;
                break;
            case SDL_KEYUP:
                input = event.key.keysym.sym;
                pressed = 0;
                break;
            default:
                return;
        }
    }
    if (!pressed) { // Key lifted
        switch(input) {
            case KEY_MENU:
                // Usually up
                break;
            case KEY_PLAY:
                // Usually down
                break;
            case KEY_REWIND:
                // Usually left
                break;
            case KEY_FORWARD:
                // Usually right
                break;
            case KEY_ACTION:
                // Usually enter/confirm
                break;
            case KEY_HOLD:
                // Usually enter menu
                break;
            case KEY_REC:
                // Usually option/select
                break;
            case KEY_POWER:
                // Usually start/menu
                break;
            case SCROLL_L:
                if (SCROLL_MOD(SCROLL_MOD_NUM)) {
                    // Strafe left
                }
                break;
            case SCROLL_R:
                if (SCROLL_MOD(SCROLL_MOD_NUM)) {
                    // Strafe right
                }
                break;
            default:
                break;
    } else { // Key pressed
        // Same as above but different actions
        // Usually stop the action/movement
    }
}

Button presses via scan codes and hardware

This is for real ports that require lots of input but does not conveniently use SDL, or you decided to do a proper port of a graphical application using hotdog. The advantage of this method is that you can easily control what happens in response to what actions occur (i.e. button is pressed down, button is released). As well, it allows for multiple buttons to be pressed and held down unlike SDL.

#define KEY_MENU     50 // Up
#define KEY_PLAY     32 // Down
#define KEY_REWIND   17 // Left
#define KEY_FORWARD  33 // Right
#define KEY_ACTION   28 // Select
#define KEY_HOLD     35 // Hold
#define KEY_REC      46 // Record (Sansa e200 only)
#define KEY_POWER    25 // Power (Sansa e200 only)
#define SCROLL_L     38 // Counter-clockwise
#define SCROLL_R     19 // Clockwise
 
#define KEY_NULL     -1 // No key event
#define KEYCODE(a)   (a & 0x7f) // Use to get keycode of scan code.
#define KEYSTATE(a)  (a & 0x80) // Check if key is pressed or lifted
 
#define SCROLL_MOD_NUM    13   // 100 / 8 = 12.5 (experiment and change this value to whatever you want)
 
#define SCROLL_MOD(n) \
    ({ \
        static int scroll_count = 0; \
        int use = 0; \
        if (++scroll_count >= n) { \
            scroll_count -= n; \
            use = 1; \
        } \
        (use == 1); \
    })
 
static int console;
static struct termios stored_settings;
 
static int ipod_get_keypress()
{
    int press = 0;
    if (read(console, &press, 1) != 1)
        return KEY_NULL;
    return press;
}
 
void ipod_parse_input()
{
    int input;
    input = ipod_get_keypress();
    while (input != KEY_NULL) {
        if (KEYSTATE(input)) { // Key up/lifted
            switch(input) {
                case KEY_MENU:
                    // Usually up
                    break;
                case KEY_PLAY:
                    // Usually down
                    break;
                case KEY_REWIND:
                    // Usually left
                    break;
                case KEY_FORWARD:
                    // Usually right
                    break;
                case KEY_ACTION:
                    // Usually enter/confirm
                    break;
                case KEY_HOLD:
                    // Usually enter menu
                    break;
                case KEY_REC:
                    // Usually option/select
                    break;
                case KEY_POWER:
                    // Usually start/menu
                    break;
                case SCROLL_L:
                    if (SCROLL_MOD(SCROLL_MOD_NUM)) {
                        // Strafe left
                    }
                    break;
                case SCROLL_R:
                    if (SCROLL_MOD(SCROLL_MOD_NUM)) {
                        // Strafe right
                    }
                    break;
                default:
                    break;
        } else { // Key pressed
            // Same as above but different actions
            // Usually stop the action/movement
        }
        input = ipod_get_keypress();
    }
}

Scroll wheel touch input

This is really nifty for applications that require multiple inputs, such as emulators. Sansa e200s do not have touch support for their scroll wheels, but they do luckily have two extra buttons that can be mapped. 3Gs and older also do not have touch input but as not many users own them anyway (and they are slower and not supported by hotdog), it would be better to not waste your time trying to support them for your port (if it is such a complex application). Note that unlike key presses, there is no lifting detection, just whether or not that part of the wheel has pressure on it or not. Thus you may want to create a static variable that represents the last state, create a new variable each time with its bits toggled, then compare the two.

#define TOUCH_U     0 // North
#define TOUCH_UR    1 // North-East
#define TOUCH_R     2 // East
#define TOUCH_DR    3 // South-East
#define TOUCH_D     4 // South
#define TOUCH_DL    5 // South-West
#define TOUCH_L     6 // West
#define TOUCH_UL    7 // North-West
#define TOUCH_NULL -1 // No wheel touch event
 
static int ipod_get_keytouch()
{
    int touch;
 
    touch = 0xff;
    if (IPOD_HW_VER != 0x4 && IPOD_HW_VER != 0x3) { // Not mini 1G or 3G
        int in, st;
        in = inl(0x7000C140);
        st = ((in & 0xff000000) >> 24);
 
        touch = 0xff;
        if (st == 0xc0)
            touch = (in & 0x007F0000 ) >> 16;
    }
 
    // See http://ipodlinux.org/wiki/Key_Chart
    // The +6 is for rounding
    if (touch != 0xff) {
        touch += 6;
        touch /= 12;
        if(touch > 7)
            touch = 0;
        return touch;
    } else {
        return TOUCH_NULL;
    }
}
 
void ipod_parse_input()
{
    int input;
    input = ipod_get_keytouch();
    switch(input) {
        case TOUCH_NULL:
            break;
        case TOUCH_U:
            // Up
            break;
        case TOUCH_UR:
            // Up right
            break;
        case TOUCH_R:
            // Right
            break;
        case TOUCH_DR:
            // Down right
            break;
        case TOUCH_D:
            // Down
            break;
        case TOUCH_DL:
            // Down left
            break;
        case TOUCH_L:
            // Left
            break;
        case TOUCH_UL:
            // Up left
            break;
        default:
            break;
    }
}

Sound

Pretty much ordinary Linux code, nothing special. Note that for simplicity, the default volume is set at the beginning of the application and kept track of throughout. If you really want to be fancy, you can use SOUND_MIXER_READ_PCM but it might not be worth the effort (plus I have not tried it so I do not have any code snippet ready - feel free to add to this section if you do write one).

int ipod_volume;
static int ipod_fd, ipod_mixer;
static int channels, frequency, volume_current;
 
void ipod_update_volume()
{
    if (volume_current != ipod_volume) {
        volume_current = ipod_volume;
        int vol;
        vol = volume_current << 8 | volume_current;
        ioctl(ipod_mixer, SOUND_MIXER_WRITE_PCM, &vol);
    }
}
 
void ipod_increase_volume()
{
    ++ipod_volume;
    if (ipod_volume > 70)
        ipod_volume = 70; // To be safe - 70 is VERY loud
        ipod_update_volume();
    }
}
 
void ipod_decrease_volume()
{
    --ipod_volume;
    if (ipod_volume < 0)
        ipod_volume = 0;
        ipod_update_volume();
    }
} 
 
void ipod_init_sound()
{
    ipod_fd = open("/dev/dsp", O_WRONLY);
    ipod_mixer = open("/dev/mixer", O_RDWR);
    ipod_volume = 50; // Reasonable default
 
    channels = 1; // Mono is usually better and much faster than stereo
    frequency = 44100;
    ioctl(ipod_fd, SNDCTL_DSP_CHANNELS, &channels);
    ioctl(ipod_fd, SNDCTL_DSP_SPEED, &frequency);
    ipod_update_volume();
}
 
void ipod_exit_sound()
{
    close(ipod_fd);
    close(ipod_mixer);
}

Hardware

Backlight

If you're porting a graphical application, you probably want backlight control as an added feature for saving battery life. If you're porting a non-interactive console based application (e.g. a console-based music player), you probably want to give the feature of turning off the backlight. Note that the ipod_get_backlight() function does not seem to work properly so you might want to just keep track of the backlight state.

#define BACKLIGHT_OFF   0
#define BACKLIGHT_ON    1
 
#define FBIOGET_BACKLIGHT    _IOR('F', 0x24, int)
#define FBIOSET_BACKLIGHT    _IOW('F', 0x25, int)
 
int ipod_backlight;
static int backlight_current;
 
static int ipod_ioctl(int request, int *arg)
{
    int fd;
    fd = open("/dev/fb0", O_NONBLOCK);
    if (fd < 0) fd = open("/dev/fb/0", O_NONBLOCK);
    if (fd < 0) return -1;
    if (ioctl(fd, request, arg) < 0) {
        close(fd);
        return -1;
    }
    close(fd);
    return 0;
}
 
// Doesn't seem to work on my nano so no idea if it actually works
/*
static int ipod_get_backlight()
{
    int backlight = 0;
    if (ipod_ioctl(FBIOGET_BACKLIGHT, &backlight) < 0)
        return -1;
    return backlight;
}
*/
 
static void ipod_set_backlight(int backlight)
{
    ipod_ioctl(FBIOSET_BACKLIGHT, (int *)(long)backlight);
}
 
void ipod_update_backlight()
{
    if (backlight_current != ipod_backlight) {
        backlight_current = ipod_backlight;
        if (backlight_current == 0) {
            ipod_set_backlight(BACKLIGHT_OFF);
        } else {
            ipod_set_backlight(BACKLIGHT_ON);
            // Turning on backlight sets brightness level to default, thus need to re-sync
            brightness_current = BRIGHTNESS_DEFAULT;
            ipod_update_brightness();
        }
    }
}

Backlight brightness

Backlight brightness control only works on iPod nanos and iPod videos with the code being borrowed from Rockbox (and may be implemented as an ioctrl call with the kernel in the future).

// Note the default value. In the above ipod_update_backlight() code,
// the brightness level is set to to this default and brightness level readjusted
#define BRIGHTNESS_DEFAULT 16
 
#define GPIOD_OUTPUT_VAL \
    (*(volatile unsigned long *)(0x6000d02c))
 
#define GPIO_SET_BITWISE(port, mask) \
    do { *(&port + (0x800/sizeof(long))) = (mask << 8) | mask; } while(0)
 
#define GPIO_CLEAR_BITWISE(port, mask) \
    do { *(&port + (0x800/sizeof(long))) = mask << 8; } while(0)
 
#define IRQ_STATUS    0x80
 
static inline int disable_interrupt_save(int mask)
{
    /* Set I and/or F disable bit and return old cpsr value */
    int cpsr, tmp;
    asm volatile (
        "mrs     %1, cpsr   \n"
        "orr     %0, %1, %2 \n"
        "msr     cpsr_c, %0 \n"
        : "=&r"(tmp), "=&r"(cpsr)
        : "r"(mask)
        );
    return cpsr;
}
 
static inline void restore_interrupt(int cpsr)
{
    /* Set cpsr_c from value returned by disable_interrupt_save or set_interrupt_status */
    asm volatile ("msr cpsr_c, %0" : : "r"(cpsr));
}
 
#define disable_irq_save() \
    disable_interrupt_save(IRQ_STATUS)
 
#define restore_irq(cpsr) \
    restore_interrupt(cpsr)
 
#define USEC_TIMER \
    (*(volatile unsigned long *)(0x60005010))
 
#define TIME_AFTER(a,b) \
    ((long)(b) - (long)(a) < 0)
 
#define TIME_BEFORE(a,b) \
    TIME_AFTER(b,a)
 
static inline void udelay(unsigned usecs)
{
    unsigned stop = USEC_TIMER + usecs;
    while (TIME_BEFORE(USEC_TIMER, stop));
}
 
int ipod_brightness;
static int brightness_current;
 
static void ipod_set_brightness(int brightness)
{
    int oldlevel;
    if (brightness_current < brightness) {
        do {
            oldlevel = disable_irq_save();
            GPIO_CLEAR_BITWISE(GPIOD_OUTPUT_VAL, 0x80);
            udelay(10);
            GPIO_SET_BITWISE(GPIOD_OUTPUT_VAL, 0x80);
            restore_irq(oldlevel);
            udelay(10);
        } while (++brightness_current < brightness);
    } else if (brightness_current > brightness) {
        do {
            oldlevel = disable_irq_save();
            GPIO_CLEAR_BITWISE(GPIOD_OUTPUT_VAL, 0x80);
            udelay(200);
            GPIO_SET_BITWISE(GPIOD_OUTPUT_VAL, 0x80);
            restore_irq(oldlevel);
            udelay(10);
        } while (--brightness_current > brightness);
    }
}
 
void ipod_update_brightness()
{
    if (IPOD_HW_VER == IPOD_NANO || IPOD_HW_VER == IPOD_VIDEO) { // iPod nano or video
        if (brightness_current != ipod_brightness) {
            ipod_set_brightness(ipod_brightness);
        }
    }
}

Contrast

Since I do not own a monochrome iPod, I do not know if the contrast changing code works. Either way, here it is.

#define FBIOGET_CONTRAST    _IOR('F', 0x22, int)
#define FBIOSET_CONTRAST    _IOW('F', 0x23, int)
 
int ipod_contrast;
static int contrast_current;
 
static int ipod_get_contrast()
{
    int contrast;
    ipod_ioctl(FBIOGET_CONTRAST, &contrast);
    return contrast;
}
 
static void ipod_set_contrast(int contrast)
{
    ipod_ioctl(FBIOSET_CONTRAST, (int*)(long)contrast);
}
 
void ipod_update_contrast()
{
    if (IPOD_LCD_TYPE == 2 || IPOD_LCD_TYPE == 3) { // monochromes (1-4G & minis)
        if (contrast_current != ipod_contrast) {
            contrast_current = ipod_contrast;
            ipod_set_contrast(contrast_current);
        }
    }
}

CPU speed

While not recommended, it is possible to explicitly change the iPod's CPU speed. This can be useful if the emulator is running slow and you are unable to optimize it any further. It is NOT if you are are too lazy to optimize and just want things running faster, however, as increasing the CPU speed too high (i.e. overclocking) for long periods WILL cause your iPod to overheat, crash and experience hardware damages. Note that this changes both the main CPU (core processing unit) and COP (co-processor). Like the brightness control, this will probably be implemented as a kernel ioctrl call in the future.

// Postscalar values: CPU speed = (24 / 8) * postscalar
// 81MHz is the MAXIMUM the iPod will go without screwing up 
// 75MHz is iPodLinux's default, 66MHz is Apple OS's default
#define CPU_33MHz    11
#define CPU_45MHz    15
#define CPU_66MHz    22 // Underclock
#define CPU_75MHz    25 // Normal
#define CPU_78MHz    26 // Overclocked
#define CPU_81MHz    27 // Max Overclock - Unstable!
 
#define CLOCK_SCALER    0x60006034
#define CLOCK_POLICY    0x60006020
#define RUN_CLK(x) (0x20000000 | ((x) <<  4))
#define RUN_GET(x) ((inl(CLOCK_POLICY) & 0x0fffff8f) | RUN_CLK(x))
#define RUN_SET(x) outl(RUN_GET(x), CLOCK_POLICY)
 
int ipod_cpu_speed;
static int cpu_speed_current;
 
static void ipod_set_cpu_speed(int postscalar)
{
    outl(inl(0x70000020) | (1 << 30), 0x70000020);
    RUN_SET(0x2);
    outl(0xaa020008 | (postscalar << 8), CLOCK_SCALER);
    int x;
    for (x = 0; x < 10000; x++);
    RUN_SET(0x7);
}
 
// Assumes you just use 4 different pre-set speeds
void ipod_update_cpu_speed()
{
    if (cpu_speed_current != ipod_cpu_speed) {
        cpu_speed_current = ipod_cpu_speed;
        switch (cpu_speed_current) {
            case 0: // Underclock
                ipod_set_cpu_speed(CPU_66MHz);
                break;
            case 1: // Normal
                ipod_set_cpu_speed(CPU_75MHz);
                break;
            case 2: // Overclock
                ipod_set_cpu_speed(CPU_78MHz);
                break;
            case 3: // Max overclock - Unstable!
                ipod_set_cpu_speed(CPU_81MHz);
                break;
        }
    }
}

Video

Graphical output should be either handled by one of two libraries: SDL or hotdog. SDL (SimpleDirectLayer) is a common graphics library that has been ported to many platforms. Many applications and emulators use SDL for graphical output and since SDL has been ported to iPodLinux, you will usually only need to make a few minor changes to make things work, particularly set the screen dimensions via SDL_ListModes:

SDL_Rect **modes;
modes = SDL_ListModes(NULL, 0);
SDL_SetVideoMode(modes[0]->w, modes[0]->h, 16, SDL_FULLSCREEN); // 16bpp = colour iPods, use 2 for mono iPods

If you're porting an application that doesn't use SDL, however, you are better off using iPodLinux's own "hotdog" graphics library. Hotdog is much faster and more optimized for iPods than SDL and I recommend you port use hotdog (even if there is an SDL port). Note however that hotdog does not currently work on monochrome iPods (i.e. iPod minis or older). The older generations are rarer though, so it is a small sacrifice to make (see the source code for iBoy if you are interested in supporting older monochrome iPods).

Simple scaling and blitting

Here's the basic code for taking a one-dimensional array of pixels and blitting it to the screen. In most cases the pixel data is an unsigned short int (uint16) in RGB format and thus easily transferable to the scaled array that will be blitted. In the case that the data is in a different format, you will have to do some manipulation first and rebuild an uint16 pixel from the extracted RGB values. Again note that since I do not own a monochrome iPod (and hotdog does not work on monochrome iPods), I do not know if the code works with monochrome iPods.

#define get_R_from_RGB565(p) \
    (((p) & 0xf800) >> 8)
 
#define get_G_from_RGB565(p) \
    (((p) & 0x07e0) >> 3)
 
#define get_B_from_RGB565(p) \
    (((p) & 0x001f) << 3)
 
#define RGB565(r, g, b) \
    ((r >> 3) << 11) | ((g >> 2) << 5) | ((b >> 3) << 0)
 
// Y = 0.299R + 0.587G + 0.114B
// About the same as (1R + 2G + 1B) / 4
//#define Y(r, g, b) \
    //(((r) + (g << 1) + (b)) >> 2)
 
 
// Get Y (luminosity) value of YUV format from RGB565 format for monochrome iPods
/*
#define convert_pixel_RGB565_to_Y(p) \
    Y( \
        get_R_from_RGB565(p), \
        get_G_from_RGB565(p), \
        get_B_from_RGB565(p) \
    )
*/
 
typedef unsigned short uint16;
 
// Normal screen dimensions used by application/emulator
extern int WIDTH, HEIGHT;
 
// Normal pixel array handled by original application/emulator
extern uint16 *screen;
 
// iPod's screen dimensions, etc.
int IPOD_HW_VER, IPOD_LCD_TYPE, IPOD_WIDTH, IPOD_HEIGHT;
 
// New, scaled pixel array that will be blitted
static uint16 *ipod_screen; // RGB565
//static uint8 *ipod_screen_mono; //Y'UV - Y only
//static int monochrome = -1;
static int scale_x[320], scale_y[240]; // Scaling factors, 320x240 is max iPod screen dimensions (iPod video)
 
static void ipod_update_screen_default()
{
    int x, y, p_ipod, p_src;
    for (y = 0; y < IPOD_HEIGHT; y++) {
        for (x = 0; x < IPOD_WIDTH; x++) {
            p_ipod = x + y * IPOD_WIDTH;
            p_src = scale_x[x] + scale_y[y];
            ipod_screen[p_ipod] = screen[p_src];
        }
    }
}
 
#define CLEAR    0x000000    // Black pixel
void ipod_clear_screen()
{
    int i, max;
    max = IPOD_WIDTH * IPOD_HEIGHT;
    for (i = 0; i < max; i++) { // Linear - don't worry about co-ordinates
        ipod_screen[i] = CLEAR;
    }
}
 
// hotdog doesn't work on monochrome iPods so this probably won't work
/*
static void ipod_update_screen_convert_to_mono()
{
    int i, max;
    max = IPOD_WIDTH * IPOD_HEIGHT;
    for (i = 0; i < max; i++) { // Linear - don't worry about co-ordinates
        ipod_screen_mono[i] = convert_pixel_RGB565_to_Y(ipod_screen[i]);
    }
}
*/
 
// Call this when appropriate in the emulator/application's main source code
void ipod_update_screen()
{
    ipod_update_screen_default();
    //if (monochrome) {
        //ipod_update_screen_convert_to_mono();
        //HD_LCD_Update(ipod_screen_mono, 0, 0, IPOD_WIDTH, IPOD_HEIGHT);
    //} else {
        HD_LCD_Update(ipod_screen, 0, 0, IPOD_WIDTH, IPOD_HEIGHT);
    //}
}
 
// Called at the beginning of the emulator/application
void ipod_init_video()
{
    HD_LCD_Init();
    HD_LCD_GetInfo(&IPOD_HW_VER, &IPOD_WIDTH, &IPOD_HEIGHT, &IPOD_LCD_TYPE);
 
    ipod_screen = malloc(IPOD_WIDTH * IPOD_HEIGHT * 2);
    /*
    if (IPOD_LCD_TYPE == 2 || IPOD_LCD_TYPE == 3) { // monochromes (1-4G & minis)
        monochrome = 1;
        ipod_screen_mono = malloc(IPOD_WIDTH * IPOD_HEIGHT * 2);
    } else {
        monochrome = 0;
        ipod_screen_mono = NULL;
    }
    */
 
    for (i = 0; i < IPOD_WIDTH; i++)
        scale_x[i] = i * WIDTH / IPOD_WIDTH;
    for (i = 0; i < IPOD_HEIGHT; i++)
        scale_y[i] = (i * HEIGHT / IPOD_HEIGHT) * WIDTH;
}
 
void ipod_exit_video()
{
    free(screen);
    free(ipod_screen);
    HD_LCD_Quit();
}

Scaling types

Sometimes the native screen dimensions of the application is not your ordinary 4:3 ratio (in fact, most of the iPod screens are not perfect 4:3 ratios). Instead of stretching the screen as full screen, here's a few code snippets that will allow you to display the screen differently

typedef void (*ipod_update_screen_type)();
static ipod_update_screen_type ipod_update_screen_funct = 0;
 
int ipod_scale_type;
static int scale_type_current = -1;
static int scale_x_offset, scale_y_offset;
static int scale_x_max, scale_y_max;
 
// For 5G and photo (not sure if works on photo though)
static void ipod_update_screen_unscaled_big()
{
    int x, y, sx, sy, p_ipod, p_src;
    for (y = scale_y_offset, sy = 0; y <= scale_y_max; y++, sy++) {
        for (x = scale_x_offset, sx = 0; x <= scale_x_max; x++, sx++) {
            p_ipod = x + y * IPOD_WIDTH;
            p_src = scale_x[sx] + scale_y[sy];
            ipod_screen[p_ipod] = screen[p_src];
        }
    }
}
 
// Doesn't fill full screen
static void ipod_update_screen_width()
{
    int x, y, sy, p_ipod, p_src;
    for (y = scale_y_offset, sy = 0; y <= scale_y_max; y++, sy++) {
        for (x = 0; x < IPOD_WIDTH; x++) {
            p_ipod = x + y * IPOD_WIDTH;
            p_src = scale_x[x] + scale_y[sy];
            ipod_screen[p_ipod] = screen[p_src];
        }
    }
}
 
void ipod_update_scale_type()
{
    if (scale_type_current != ipod_scale_type) {
        scale_type_current = ipod_scale_type;
        int i;
        switch (scale_type_current) {
            case 0: // Unscaled
                scale_x_offset = (IPOD_WIDTH - WIDTH) / 2;
                scale_y_offset = (IPOD_HEIGHT - HEIGHT) / 2;
 
                if (IPOD_HEIGHT > HEIGHT || IPOD_WIDTH > WIDTH) { // iPod video or photo
                    scale_x_max = IPOD_WIDTH - scale_x_offset;
                    scale_y_max = IPOD_HEIGHT - scale_y_offset;
 
                    for (i = 0; i < IPOD_WIDTH; i++)
                        scale_x[i] = i;
                    for (i = 0; i < IPOD_HEIGHT; i++)
                        scale_y[i] = i * WIDTH;
 
                    ipod_update_screen_funct = ipod_update_screen_unscaled_big;
                } else {
                    scale_x_max = IGNORE;
                    scale_y_max = IGNORE;
 
                    for (i = 0; i < IPOD_WIDTH; i++)
                        scale_x[i] = i - scale_x_offset; // - negative = positive
                    for (i = 0; i < IPOD_HEIGHT; i++)
                        scale_y[i] = (i - scale_y_offset) * WIDTH;
 
                    ipod_update_screen_funct = ipod_update_screen_default;
                }
                break;
            case 1: // Full-screen
                scale_x_offset = IGNORE;
                scale_y_offset = IGNORE;
                scale_x_max = IGNORE;
                scale_y_max = IGNORE;
 
                for (i = 0; i < IPOD_WIDTH; i++)
                    scale_x[i] = i * WIDTH / IPOD_WIDTH;
                for (i = 0; i < IPOD_HEIGHT; i++)
                    scale_y[i] = (i * HEIGHT / IPOD_HEIGHT) * WIDTH;
 
                ipod_update_screen_funct = ipod_update_screen_default;
                break;
            case 2: // Scale to height - width gets stretched
                scale_x_offset = (IPOD_WIDTH - WIDTH * IPOD_HEIGHT / HEIGHT) / 2;
                scale_y_offset = IGNORE;
                scale_x_max = IGNORE;
                scale_y_max = IGNORE;
 
                for (i = 0; i < IPOD_WIDTH; i++)
                    scale_x[i] = (i - scale_x_offset) * HEIGHT / IPOD_HEIGHT;
                for (i = 0; i < IPOD_HEIGHT; i++)
                    scale_y[i] = (i * HEIGHT / IPOD_HEIGHT) * WIDTH;
 
                ipod_update_screen_funct = ipod_update_screen_default;
                break;
            case 3: // Scale to width - height gets shortened
                scale_x_offset = IGNORE;
                scale_y_offset = (IPOD_HEIGHT - HEIGHT * IPOD_WIDTH / WIDTH) / 2;
                scale_x_max = IGNORE;
                scale_y_max = IPOD_HEIGHT - scale_y_offset - 2;
 
                for (i = 0; i < IPOD_WIDTH; i++)
                    scale_x[i] = i * WIDTH / IPOD_WIDTH;
                for (i = 0; i < IPOD_HEIGHT; i++)
                    scale_y[i] = (i * WIDTH / IPOD_WIDTH) * WIDTH;
 
                ipod_update_screen_funct = ipod_update_screen_width;
                break;
        }
        // Note that this sometimes screws up due to syncing issues
        // Screwy pixels get cleared when the menu is exited though
        ipod_clear_screen(CLEAR);
    }
}
 
void ipod_update_screen()
{
    ipod_update_screen_funct();
    //if (monochrome) {
        //ipod_update_screen_convert_to_mono();
        //HD_LCD_Update(ipod_screen_mono, 0, 0, IPOD_WIDTH, IPOD_HEIGHT);
    //} else {
        HD_LCD_Update(ipod_screen, 0, 0, IPOD_WIDTH, IPOD_HEIGHT);
    //}
}
 
void ipod_init_video()
{
    HD_LCD_Init();
    HD_LCD_GetInfo(&IPOD_HW_VER, &IPOD_WIDTH, &IPOD_HEIGHT, &IPOD_LCD_TYPE);
 
    ipod_screen = malloc(IPOD_WIDTH * IPOD_HEIGHT * 2);
    /*
    if (IPOD_LCD_TYPE == 2 || IPOD_LCD_TYPE == 3) { // monochromes (1-4G & minis)
        monochrome = 1;
        ipod_screen_mono = malloc(IPOD_WIDTH * IPOD_HEIGHT * 2);
    } else {
        monochrome = 0;
        ipod_screen_mono = NULL;
    }
    */
 
    ipod_update_scale_type();
}

Pixel blending

On small screens, down-scaling the image will usually make things look very poor and pixellated. To compensate for the pixellation, you can use pixel sampling and blend/blur surrounding pixels, making a "smoother" output. While this does slow down blitting slightly, users usually prefer a smoother looking screen.

// Blend four pixel values - p1 gets weighted two times
#define get_avg_R_from_4_RGB565(p1, p2, p3) \
    (((get_R_from_RGB565(p1) << 1) + get_R_from_RGB565(p2) + get_R_from_RGB565(p3)) >> 2)
 
#define get_avg_G_from_4_RGB565(p1, p2, p3) \
    (((get_G_from_RGB565(p1) << 1) + get_G_from_RGB565(p2) + get_G_from_RGB565(p3)) >> 2)
 
#define get_avg_B_from_4_RGB565(p1, p2, p3) \
    (((get_B_from_RGB565(p1) << 1) + get_B_from_RGB565(p2) + get_B_from_RGB565(p3)) >> 2)
 
#define blend_pixels_4_RGB565(p1, p2, p3) \
    RGB565( \
        get_avg_R_from_4_RGB565(p1, p2, p3), \
        get_avg_G_from_4_RGB565(p1, p2, p3), \
        get_avg_B_from_4_RGB565(p1, p2, p3) \
    )
 
 
// Blend eight pixel values - p1 gets weighted four times
#define get_avg_R_from_8_RGB565(p1, p2, p3, p4, p5) \
    (((get_R_from_RGB565(p1) << 2) + \
        get_R_from_RGB565(p2) + get_R_from_RGB565(p3) + get_R_from_RGB565(p4) + get_R_from_RGB565(p5) \
    ) >> 3)
 
#define get_avg_G_from_8_RGB565(p1, p2, p3, p4, p5) \
    (((get_G_from_RGB565(p1) << 2) + \
        get_G_from_RGB565(p2) + get_G_from_RGB565(p3) + get_G_from_RGB565(p4) + get_G_from_RGB565(p5) \
    ) >> 3)
 
#define get_avg_B_from_8_RGB565(p1, p2, p3, p4, p5) \
    (((get_B_from_RGB565(p1) << 2) + \
        get_B_from_RGB565(p2) + get_B_from_RGB565(p3) + get_B_from_RGB565(p4) + get_B_from_RGB565(p5) \
    ) >> 3)
 
#define blend_pixels_8_RGB565(p1, p2, p3, p4, p5) \
    RGB565( \
        get_avg_R_from_8_RGB565(p1, p2, p3, p4, p5), \
        get_avg_G_from_8_RGB565(p1, p2, p3, p4, p5), \
        get_avg_B_from_8_RGB565(p1, p2, p3, p4, p5) \
    )
 
int ipod_smooth_type;
static int scale_type_current = -1;
 
// Four pixel blending
static void ipod_update_screen_default_smooth_1()
{
    int x, y, p_ipod, p_src;
    uint16 p1, p2, p3;
    for (y = 0; y < IPOD_HEIGHT; y++) {
        for (x = 0; x < IPOD_WIDTH; x++) {
            p_ipod = x + y * IPOD_WIDTH;
            p_src = scale_x[x] + scale_y[y];
            p1 = screen[p_src]; // Pixel
            p2 = screen[p_src + 1]; // Pixel to the right
            p3 = screen[p_src + WIDTH]; // Pixel underneath
            ipod_screen[p_ipod] = blend_pixels_4_RGB565(p1, p2, p3);
        }
    }
}
 
// Eight pixel blending
static void ipod_update_screen_default_smooth_2()
{
    int x, y, p_ipod, p_src;
    uint16 p1, p2, p3, p4, p5;
    // To stay in bounds
    for (y = 0; y < 2; y++) {
        for (x = 0; x < IPOD_WIDTH * 2; x++) {
            p_ipod = x + y * IPOD_WIDTH;
            p_src = scale_x[x] + scale_y[y];
            p1 = screen[p_src]; // Pixel
            p2 = screen[p_src + 1]; // Pixel to the right
            p3 = screen[p_src + WIDTH]; // Pixel underneath
            ipod_screen[p_ipod] = blend_pixels_4_RGB565(p1, p2, p3);
        }
    }
    for (y = 2; y < IPOD_HEIGHT; y++) {
        for (x = 0; x < IPOD_WIDTH; x++) {
            p_ipod = x + y * IPOD_WIDTH;
            p_src = scale_x[x] + scale_y[y];
            p1 = screen[p_src]; // Pixel
            p2 = screen[p_src + 1]; // Pixel to the right
            p3 = screen[p_src - 1]; // Pixel to the right
            p4 = screen[p_src + WIDTH]; // Pixel underneath
            p5 = screen[p_src - WIDTH]; // Pixel above
            ipod_screen[p_ipod] = blend_pixels_8_RGB565(p1, p2, p3, p4, p5);
        }
    }
}
 
// Four pixel blending
static void ipod_update_screen_unscaled_big_smooth_1()
{
    int x, y, sx, sy, p_ipod, p_src;
    uint16 p1, p2, p3;
    for (y = scale_y_offset, sy = 0; y <= scale_y_max; y++, sy++) {
        for (x = scale_x_offset, sx = 0; x <= scale_x_max; x++, sx++) {
            p_ipod = x + y * IPOD_WIDTH;
            p_src = scale_x[sx] + scale_y[sy];
            p1 = screen[p_src]; // Pixel
            p2 = screen[p_src + 1]; // Pixel to the right
            p3 = screen[p_src + WIDTH]; // Pixel underneath
            ipod_screen[p_ipod] = blend_pixels_4_RGB565(p1, p2, p3);
        }
    }
}
 
// Eight pixel blending
static void ipod_update_screen_unscaled_big_smooth_2()
{
    int x, y, sx, sy, p_ipod, p_src;
    uint16 p1, p2, p3, p4, p5;
    // To stay in bounds
    for (y = scale_y_offset, sy = 0; y < scale_y_offset + 2; y++, sy++) {
        for (x = scale_x_offset, sx = 0; x <= scale_x_max; x++, sx++) {
            p_ipod = x + y * IPOD_WIDTH;
            p_src = scale_x[sx] + scale_y[sy];
            p1 = screen[p_src]; // Pixel
            p2 = screen[p_src + 1]; // Pixel to the right
            p4 = screen[p_src + WIDTH]; // Pixel underneath
            ipod_screen[p_ipod] = blend_pixels_4_RGB565(p1, p2, p3);
        }
    }
    for (y = scale_y_offset + 2, sy = 2; y <= scale_y_max; y++, sy++) {
        for (x = scale_x_offset, sx = 0; x <= scale_x_max; x++, sx++) {
            p_ipod = x + y * IPOD_WIDTH;
            p_src = scale_x[sx] + scale_y[sy];
            p1 = screen[p_src]; // Pixel
            p2 = screen[p_src + 1]; // Pixel to the right
            p3 = screen[p_src - 1]; // Pixel to the right
            p4 = screen[p_src + WIDTH]; // Pixel underneath
            p5 = screen[p_src - WIDTH]; // Pixel above
            ipod_screen[p_ipod] = blend_pixels_8_RGB565(p1, p2, p3, p4, p5);
        }
    }
}
 
// Four pixel blending
static void ipod_update_screen_width_smooth_1()
{
    int x, y, sy, p_ipod, p_src;
    uint16 p1, p2, p3;
    for (y = scale_y_offset, sy = 0; y <= scale_y_max; y++, sy++) {
        for (x = 0; x < IPOD_WIDTH; x++) {
            p_ipod = x + y * IPOD_WIDTH;
            p_src = scale_x[x] + scale_y[sy];
            p1 = screen[p_src]; // Pixel
            p2 = screen[p_src + 1]; // Pixel to the right
            p3 = screen[p_src + WIDTH]; // Pixel underneath
            ipod_screen[p_ipod] = blend_pixels_4_RGB565(p1, p2, p3);
        }
    }
}
 
// Eight pixel blending
static void ipod_update_screen_width_smooth_2()
{
    int x, y, sy, p_ipod, p_src;
    uint16 p1, p2, p3, p4, p5;
    // To stay in bounds
    for (y = scale_y_offset, sy = 0; y <= scale_y_offset + 2; y++, sy++) {
        for (x = 0; x < IPOD_WIDTH; x++) {
            p_ipod = x + y * IPOD_WIDTH;
            p_src = scale_x[x] + scale_y[sy];
            p1 = screen[p_src]; // Pixel
            p2 = screen[p_src + 1]; // Pixel to the right
            p3 = screen[p_src + WIDTH]; // Pixel underneath
            ipod_screen[p_ipod] = blend_pixels_4_RGB565(p1, p2, p3);
        }
    }
    for (y = scale_y_offset + 2, sy = 2; y <= scale_y_max; y++, sy++) {
        for (x = 0; x < IPOD_WIDTH; x++) {
            p_ipod = x + y * IPOD_WIDTH;
            p_src = scale_x[x] + scale_y[sy];
            p1 = screen[p_src]; // Pixel
            p2 = screen[p_src + 1]; // Pixel to the right
            p3 = screen[p_src - 1]; // Pixel to the right
            p4 = screen[p_src + WIDTH]; // Pixel underneath
            p5 = screen[p_src - WIDTH]; // Pixel above
            ipod_screen[p_ipod] = blend_pixels_8_RGB565(p1, p2, p3, p4, p5);
        }
    }
}
 
void ipod_update_scale_type()
{
    if (scale_type_current != ipod_scale_type ||
        smooth_type_current != ipod_smooth_type) {
        scale_type_current = ipod_scale_type;
        smooth_type_current = ipod_smooth_type;
        int i;
        switch (scale_type_current) {
            case 0: // Unscaled
                scale_x_offset = (IPOD_WIDTH - WIDTH) / 2;
                scale_y_offset = (IPOD_HEIGHT - HEIGHT) / 2;
 
                if (IPOD_HEIGHT > HEIGHT || IPOD_WIDTH > WIDTH) { // iPod video or photo
                    scale_x_max = IPOD_WIDTH - scale_x_offset;
                    scale_y_max = IPOD_HEIGHT - scale_y_offset;
 
                    for (i = 0; i < IPOD_WIDTH; i++)
                        scale_x[i] = i;
                    for (i = 0; i < IPOD_HEIGHT; i++)
                        scale_y[i] = i * WIDTH;
 
                    switch(ipod_smooth_type) {
                        case 0:
                            ipod_update_screen_funct = ipod_update_screen_unscaled_big;
                            break;
                        case 1:
                            ipod_update_screen_funct = ipod_update_screen_unscaled_big_smooth_1;
                            break;
                        case 2:
                            ipod_update_screen_funct = ipod_update_screen_unscaled_big_smooth_2;
                            break;
                    }
                } else {
                    scale_x_max = IGNORE;
                    scale_y_max = IGNORE;
 
                    for (i = 0; i < IPOD_WIDTH; i++)
                        scale_x[i] = i - scale_x_offset; // - negative = positive
                    for (i = 0; i < IPOD_HEIGHT; i++)
                        scale_y[i] = (i - scale_y_offset) * WIDTH;
 
                    switch(ipod_smooth_type) {
                        case 0:
                            ipod_update_screen_funct = ipod_update_screen_default;
                            break;
                        case 1:
                            ipod_update_screen_funct = ipod_update_screen_default_smooth_1;
                            break;
                        case 2:
                            ipod_update_screen_funct = ipod_update_screen_default_smooth_2;
                            break;
                    }
                }
                break;
            case 1: // Full-screen
                scale_x_offset = IGNORE;
                scale_y_offset = IGNORE;
                scale_x_max = IGNORE;
                scale_y_max = IGNORE;
 
                for (i = 0; i < IPOD_WIDTH; i++)
                    scale_x[i] = i * WIDTH / IPOD_WIDTH;
                for (i = 0; i < IPOD_HEIGHT; i++)
                    scale_y[i] = (i * HEIGHT / IPOD_HEIGHT) * WIDTH;
 
                switch(ipod_smooth_type) {
                    case 0:
                        ipod_update_screen_funct = ipod_update_screen_default;
                        break;
                    case 1:
                        ipod_update_screen_funct = ipod_update_screen_default_smooth_1;
                        break;
                    case 2:
                        ipod_update_screen_funct = ipod_update_screen_default_smooth_2;
                        break;
                }
                break;
            case 2: // Scale to height - width gets stretched
                scale_x_offset = (IPOD_WIDTH - WIDTH * IPOD_HEIGHT / HEIGHT) / 2;
                scale_y_offset = IGNORE;
                scale_x_max = IGNORE;
                scale_y_max = IGNORE;
 
                for (i = 0; i < IPOD_WIDTH; i++)
                    scale_x[i] = (i - scale_x_offset) * HEIGHT / IPOD_HEIGHT;
                for (i = 0; i < IPOD_HEIGHT; i++)
                    scale_y[i] = (i * HEIGHT / IPOD_HEIGHT) * WIDTH;
 
                switch(ipod_smooth_type) {
                    case 0:
                        ipod_update_screen_funct = ipod_update_screen_default;
                        break;
                    case 1:
                        ipod_update_screen_funct = ipod_update_screen_default_smooth_1;
                        break;
                    case 2:
                        ipod_update_screen_funct = ipod_update_screen_default_smooth_2;
                        break;
                }
                break;
            case 3: // Scale to width - height gets shortened
                scale_x_offset = IGNORE;
                scale_y_offset = (IPOD_HEIGHT - HEIGHT * IPOD_WIDTH / WIDTH) / 2;
                scale_x_max = IGNORE;
                scale_y_max = IPOD_HEIGHT - scale_y_offset - 2;
 
                for (i = 0; i < IPOD_WIDTH; i++)
                    scale_x[i] = i * WIDTH / IPOD_WIDTH;
                for (i = 0; i < IPOD_HEIGHT; i++)
                    scale_y[i] = (i * WIDTH / IPOD_WIDTH) * WIDTH;
 
                switch(ipod_smooth_type) {
                    case 0:
                        ipod_update_screen_funct = ipod_update_screen_width;
                        break;
                    case 1:
                        ipod_update_screen_funct = ipod_update_screen_width_smooth_1;
                        break;
                    case 2:
                        ipod_update_screen_funct = ipod_update_screen_width_smooth_2;
                        break;
                }
                break;
        }
        // Note that this sometimes screws up due to syncing issues
        // Screwy pixels get cleared when the menu is exited though
        ipod_clear_screen(CLEAR);
    }
}

Co-processor

This is the section I know and understand least about. The code was borrowed from iBoy's incomplete COP implementation and so I do not clearly know what each step does or how to best use this. Regardless, it is very useful in combination with video blitting for slow emulators. in this code snippet, instead of calling ipod_update_video directly, it tells the COP to call it and do the pixel calculations. Due to the asynchronous nature of the two CPU cores, The COP will only blit to the screen once its done doing all the calculations but does so independently of the main CPU. Thus, the main CPU does not have to wait until the COP is done the blitting (which usually takes a large portion of time) and can continue on with emulation. This sometimes leads to minor graphical glitches/artifacts but drastically improves speed overall. For example, adding COP code to hWolf3D (which is almost full speed) creates numerous graphical problems and does not offer noticeable speedups. On the other hand, igpSP runs painfully slow without COP code added. Only add in the COP code if it is clear that the application/emulator is not running at full speed and users are willing to sacrifice minor graphical glitches for speed.

#define COP_HANDLER            0x4001501C
#define COP_STATUS            0x40015020
#define COP_CONTROL            0x60007004
 
#define COP_RUNNING            (1 << 0)
#define COP_LINE_REQ        (1 << 1)
#define COP_LINE_REQ_CLEAR    (2 << 1)
 
#define ipod_cop_clear_frameready() \
    outl(inl(COP_STATUS) &~ COP_LINE_REQ_CLEAR, COP_STATUS)
 
// Waits for main update_screen to call "ipod_cop_update_screen();"
static void ipod_cop_sync_screen()
{
    while (inl(COP_STATUS) & COP_RUNNING) // COP is active
    {
        // Get stuck in loop until told to update screen
        while ((inl(COP_STATUS) & COP_LINE_REQ) == 0);
        ipod_update_screen();
        outl(inl(COP_STATUS) &~ COP_LINE_REQ, COP_STATUS);
    }    
}
 
// Call this now instead of ipod_update_screen()
void ipod_cop_update_screen()
{
    // Envokes ipod_cop_sync_screen() to synchronize
    outl(inl(COP_STATUS) | COP_LINE_REQ, COP_STATUS);
}
 
void ipod_cop_execute(void (*function)())
{
    outl((unsigned int)function, COP_HANDLER);
    outl(0x0, COP_CONTROL);
}
 
// Call before ipod_init_video()
void ipod_init_cop()
{
    ipod_cop_clear_frameready();
    ipod_cop_execute(ipod_cop_sync_screen);    
    outl(COP_RUNNING, COP_STATUS); // Start COP?
}
 
// Doesn't seem to work so I don't actually call this function
void ipod_exit_cop()
{
    // Causes iPod to freeze sometimes ?
    //outl(inl(COP_STATUS) &~ COP_RUNNING, COP_STATUS); // Stop COP
    //outl(inl(COP_STATUS) | COP_LINE_REQ, COP_STATUS);
}

Source Code References

Source code snippets were gathered from various sources, including, but not limited to:

This page was written by Keripo. Please update, fix, and add useful code snippets here.

~Keripo