Porting Code Snippets
From iPodLinux
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 | | | | | | | — | | 4 | | | 1 |
| 2G iPod | | | | | | | — | | 4 | | | 1 |
| 3G iPod | | | | | | | — | | 4 | | | 1 |
| 4G iPod | | | | 1 | | | — | | 1 | | | 1 |
| iPod mini 1G | | | | 1 | | | — | | 1 | | | 1 |
| iPod mini 2G | | | | | | | — | | 1 | | | 1 |
| iPod photo/colour | | | | | | | — | — | | | | |
| iPod nano 1G | | | | | | | | — | | | | |
| 5G/5.5G iPod video | | | | | | | | — | | | | |
| Sansa e200 V1 | | | | | | | | — | | | | |
- 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:
- iBoy: http://sourceforge.net/projects/iboy
- igpSP: (Zaphod's original port)
- hotdog: http://ipodlinux.svn.sourceforge.net/svnroot/ipodlinux/libs/hotdog/
- ithread: http://svn.so2.sytes.net/repos/ipod/ithread/
- libipod: http://ipodlinux.svn.sourceforge.net/svnroot/ipodlinux/libs/libipod/
- Rockbox: svn://svn.rockbox.org/rockbox/trunk/firmware
- http://objectmix.com/dotnet/101346-help-display-16-bit-bitmap.html
- http://www.fourcc.org/fccyvrgb.php
- http://www.gamedev.net/reference/articles/article1369.asp
This page was written by Keripo. Please update, fix, and add useful code snippets here.
~Keripo