Skip to content
TerminalBytes
Go back

Reverse Engineering the iPod Classic's Undocumented Mikey Chip

On this page

My iPod Classic (7th gen) runs Rockbox, and I love almost everything about that arrangement. But the inline remote on Apple’s wired earbuds (the center play/pause button and the volume clicker) did nothing. Never has, for anyone running Rockbox on this family of iPods.

The reason is sitting right there in the Rockbox source:

/* TODO:
 *  - detect jack accessory
 *  - support for remote buttons
 */

And honestly, fair enough. Rockbox on this iPod exists because volunteers reverse engineered Apple hardware with zero documentation, for free, since January 2011. Music, clickwheel, recording, all of it figured out the hard way. The earbud remote just never made it to the top of anyone’s list: the 2014 commit that added the button-interrupt plumbing called remote events “work in progress”, and the TODO landed in the audio driver in 2017. Nobody picked it up since. So I did.

iPod Classic 7th gen running Rockbox with Apple wired earbuds plugged in

TL;DR

  • The chip that decodes the iPod’s earphone remote (“Mikey”, I2C address 0x72) has no public documentation. At all. Anywhere.
  • The only prior art was a 16-year-old reverse engineering writeup on a blog that no longer exists. A wiki mirror saved the day, straight out of xkcd 979.
  • I swept the chip’s mode register through all 256 values on the device itself, found the one that enables button reporting (0x2f), and decoded the events.
  • The hardware lies: the chip’s event engine falls asleep after ~5 seconds and reports button releases 1.8 seconds late.
  • The result is a driver, under review upstream as Gerrit change 7677. All three buttons work.

What do I do to fix it?

If you’re not here for the reverse engineering story and just want working earbud buttons: I’ve published a prebuilt Rockbox image with the remote driver as ipod6g-mikey-v1. It works on the iPod Classic 6G and 7th gen, and assumes your iPod already runs Rockbox (bootloader installed via the official Rockbox Utility).

  1. Download rockbox.zip from the release
  2. Connect the iPod in disk mode and unzip it onto the drive root (it replaces the .rockbox firmware files; your settings, themes, database, and music are untouched)
  3. Eject and reboot

Play/pause then works on every screen, volume works everywhere including menus (hold to ramp), and the remote keeps working with the hold switch on. This doesn’t touch the bootloader, so worst case you delete .rockbox from disk mode and unzip an official build back. Once the change merges upstream, switch back to the official daily builds.

A chip named Mikey, documented nowhere

Quick cast of characters. The iPod Classic (mine is the 7th gen; Rockbox names the whole family “ipod6g” after the 2007 original) has a dedicated little controller for the headphone jack that Apple’s firmware calls “Mikey”. It sits on I2C bus 0 at address 0x72, has 8 registers, and handles two jobs: powering the headset microphone and decoding the inline remote buttons. Rockbox has always known it exists, but only ever used it for one thing: raising the mic bias voltage so jack recording works.

Before touching a wire, I went looking for anyone who had done this before. The search was thorough and completely empty:

  • freemyipod, the community that reverse engineered this iPod enough to run custom code on it in the first place, has no Mikey driver and no register docs.
  • Rockbox’s Gerrit has zero abandoned attempts. Nobody even tried and gave up publicly.
  • GitHub code search for “mikey” returns the Atari Lynx sound chip and a Blue microphone accessory. Genuinely nothing else.
  • openiBoot, the old iPhone Linux project, never did headset accessories.
  • macOS ships an AppleMikeyDriver.kext, which proves the name is real, but nobody ever documented its internals, and on Macs the equivalent logic lives inside the audio codec anyway.

An undocumented chip, an unfinished driver, and 15 years of nobody picking it up. From an earlier session I already had half a win: raise the mic bias (write 7 to register 0, the same value the recording path uses) and the center button shows up as a nonzero value in register 4. Play/pause worked. Volume was a wall. My working theory was that volume rode some undocumented serial protocol, with an interrupt on GPIO E6 announcing new data.

That theory was wrong in every detail, and the E6 interrupt never fired once during the entire project. Not once.

The DenverCoder9 moment

If you’ve spent any time debugging obscure problems, you know xkcd 979. Ten years of searching, one forum thread with your exact problem, and the only reply is the author saying “never mind, fixed it.”

xkcd 979: Wisdom of the Ancients

xkcd 979 “Wisdom of the Ancients” by Randall Munroe, CC BY-NC 2.5

Searching for the remote’s wire protocol surfaced exactly one thread to pull: a Hackaday post from February 2010 and a matching 16-year-old Reddit thread about David Carne reverse engineering the iPod shuffle 3G’s headphone remote. The same remote hardware my earbuds use.

His site? Dead. david.carne.ca/shuffle_hax doesn’t even load anymore. Of course it’s dead, it’s been 16 years.

Unlike DenverCoder9, though, the wisdom of the ancients turned out to be recoverable. A tinymicros.com wiki mirror preserved the full writeup, and a GitHub repo (reverse-shuffle) still carries an Arduino re-implementation of the accessory handshake. The ancients left notes after all.

What Carne figured out back in 2010

Carne’s data detonated my serial-protocol theory on contact. The remote is much, much dumber than I assumed:

SignalWhat it actually is
Center buttonA dead short of the mic line. That’s it.
Volume upA static resistive load that droops the mic line from ~2.08V to ~1.68V
Volume downA bigger load, drooping it to ~1.52V
The “smart” partA one-time ultrasonic ID chirp 8.1ms after power-up (1.5ms at 280kHz, then 4.6ms at 244kHz), answered by a 4.8ms ACK pulse from the player

The chirp is identification, not DRM. Carne proved it by replicating the volume buttons with a plain resistor circuit of his own, no proprietary chip involved; the original remote just had to be attached at power-up to chirp its ID.

So Mikey is a level detector with a chirp demodulator bolted on. The buttons were analog all along, and the job suddenly got a lot smaller: find whatever register mode makes the chip report those levels.

Sweeping the chip, register by register

You can’t exactly attach a debugger to an iPod’s jack controller, so I built the next best thing: a reverse engineering screen inside Rockbox itself. It lives in the device’s debug menu, polls all 8 Mikey registers every 20ms, shows them live on screen, and appends a timestamped line to /.rockbox/mikey.log every time anything changes. The scroll wheel dials register 0 up and down. This screen stays private to my build (it’s a scaffold, not a product), but the core of it is just this:

/* poll all 8 registers every ~20ms, log any change */
for (int i = 0; i < MIKEY_NREGS; i++)
    cur[i] = mikey_read(i);
int e6 = (PDAT(14) >> 6) & 1;   /* Mikey IRQ line (never fired!) */

if (changed && fd >= 0)
{
    fdprintf(fd, "[%08lu] r0=%02x E6=%d ", USEC_TIMER, reg0, e6);
    for (int i = 0; i < MIKEY_NREGS; i++)
        fdprintf(fd, "%02x ", cur[i]);
    fdprintf(fd, "\n");
}

Two features mattered. An auto-sweep mode steps register 0 through all 256 values, about 200ms each, while you hold an earphone button. And a MARK key stamps the log when I press Menu, so physical button presses can be lined up against register changes afterward. I pressed the buttons, plugged and unplugged the jack, flashed builds, and ran the sweeps, and had Claude diff the logs and propose the next experiment (log analysis across three sweep passes is exactly the kind of tedium a model is good at). Every conclusion still got verified on the device before it counted.

The Mikey reverse engineering debug screen showing live register values on the iPod Classic

Confession time: the first batch of sweep data was garbage, and it was my driver’s fault. The existing play/pause polling thread was quietly rewriting register 0 back to 7 every 20ms, fighting the debug screen’s sweep in the background. Every “result” from before I added a hold-the-polling-thread flag was contaminated, and all of it got thrown out.

With clean data, three full sweeps (touch nothing, hold volume up, hold volume down) and a log diff found an active window: register 0 values 0x24-0x27, 0x2c-0x2f, 0x34-0x37 and 0x3c-0x3f, mirrored at +0x80 because the chip doesn’t store bits 7 and 3. Inside that window, registers 5 and 6 come alive. Everywhere else, flatline. A focused pass over those 16 values with MARK bracketing landed on the answer: at register 0 = 0x2f, the volume buttons produce clean, repeatable events.

The full decode:

SignalMeaning
reg0 = 0x2fRemote-reporting mode. Low 3 bits are mic bias (same 7 as recording), 0x28 arms the detector
reg5 = 0x04 / 0x08Volume up press / release (edge events, readable for ~100ms)
reg5 = 0x01 / 0x02Volume down press / release
reg5 = 0x30Accessory-ID event at mode entry (ignore)
reg4 & 0x05Center button, a level on a 0x40 base
UnplugChip fully resets, all registers read zero, mode must be re-armed on insertion

A 5-second hold test confirmed the volume events are true edges: exactly one press event and one release event, 6.3 seconds apart, nothing in between.

One protocol test produced my favorite dumb moment of the project. My own test notes said “click the center button 3 times”, and mid-session I clicked the center of the iPod’s clickwheel three times instead of the earbud button. The log dutifully recorded three debug-screen resets and zero remote events. Turns out “center button” is a dangerous thing to write in your notes when there are two devices on the desk with center buttons.

Ship it upstream

Driver version 2: arm mode 0x2f, poll at 20ms, latch the volume state between press and release edges (Rockbox’s normal button-repeat machinery then gives hold-to-ramp volume for free), read the center button from the register 4 level. All three buttons worked on the device.

Volume and play/pause from the earbud remote, on hardware from 2009. Sped up 1.8x.

Now, upstreaming. Rockbox doesn’t take GitHub pull requests; the GitHub repo is a mirror. Real review happens on their own self-hosted Gerrit with a real-names policy. The setup is old-school but painless: OpenID login, SSH key, a commit-msg hook that stamps a Change-Id into your commit, then push to refs/for/master.

Then came the cleanup for submission: different build configurations, edge cases around jack removal, what happens when recording wants the chip. That pass surfaced three bugs that daily use on my own iPod was never going to catch:

  • Dev builds wouldn’t compile. Both remote_control_rx() and the BUTTON_RC_* codes only exist when the iAP accessory protocol is compiled in, and logf builds disable it. The fix was a proper HAVE_MIKEY_REMOTE capability macro instead of hardcoding the target name in shared code three times.
  • mikey_read() returned uninitialized stack garbage whenever the chip NAKed an I2C transfer, which it does every single time the jack gets wiggled. One glitch could latch a phantom stuck volume button.
  • The polling thread silently fought the recording path for the mode register. Fixed with an explicit handover: the remote pauses while the jack mic records.

A word on the developer experience, because it surprised me: Rockbox ships a script (rockboxdev.sh) that builds you the pinned cross-compiler, arm-elf-eabi-gcc 9.5.0, and from there it’s just make. Testing a change meant dropping the fresh rockbox.ipod into /.rockbox/ over disk mode and rebooting. For a volunteer project that’s been going since 2001 and targets dozens of different players, the tooling is in better shape than some products I’ve been paid to work on.

Submitted as Gerrit change 7677, “ipod6g: Add inline earphone remote support”. One clean commit, 8 files, 318 insertions.

The hardware lies

Then I actually lived with it for a day, and the best act of the whole project started: single clicks on the center button would sometimes toggle play/pause twice.

My first fix was a textbook jumped-the-gun mistake. I assumed register 4 was a live level with occasional dropouts, so I debounced the release side. It made everything worse. Clicks started reading as long-presses, and in Rockbox’s keymap a long-press of play means “stop”, which dumps you out to the main menu. Even better: the “working” behavior I’d been enjoying before turned out to be an accident. The flicker my debounce removed had been accidentally masking a much bigger problem.

So, properly this time: an instrumented timing session, MARK stamped at the exact physical press and the exact physical release, logs analyzed afterward. The truth is much stranger than a bouncy contact. The chip’s event engine naps. After about 5 seconds without button activity, a press still raises register 4 instantly, but the release (the register 4 fall and the register 6 events) gets reported 1.7 to 1.8 seconds late, batched together when the engine wakes back up. Recent activity keeps it awake, so rapid clicking reads perfectly, but the first click after any pause reads as a 1.8-second hold. The log made it vivid: physical press and release at t=525.73s, and every consequence of it landing at t=527.38s.

There’s no debouncing your way out of hardware that reports the past. So the center button is now click-only: every debounced rise emits a fixed 60ms click pulse, and there are no hold semantics at all, because the chip can’t reliably report press duration.

/* Center-button handling: click-only. reg4's rising edge is always
 * immediate, but after ~5s of inactivity the chip's event engine naps
 * and reports the release ~1.8s late, so press duration is unreliable.
 * Every press is a fixed-length click at the rise; no hold/long-press. */
#define MIKEY_CENTER_PULSE_POLLS  3     /* reported click length, 60ms */
#define MIKEY_CENTER_OFF_POLLS    3     /* release debounce, 60ms */

With the button finally firing reliably, one last surprise surfaced, in software this time. Rockbox’s stock keymap was written for the old dock remotes, and it maps the remote’s play button to Select in menus. So my earbud clicker was selecting menu items. The fix turned out nicer than patching keymaps: Rockbox has carried a BUTTON_MULTIMEDIA_PLAYPAUSE code for years, commented “multimedia keys on keyboards, headsets”, handled globally on every screen, and as far as I can tell this driver is the first native player to actually use it for a headset. The center button now sends that, so play/pause works everywhere, including resuming a stopped playlist. Volume in menus and lists came along for the ride via HAVE_VOLUME_IN_LIST (previously volume only worked on the Now Playing screen). Both went up as patch set 2 on the same Gerrit change.

The boot logo aside

While I had a build pipeline warmed up, my iPod also got a personalized boot screen: a neon “Hemant’s iPod” wordmark, downscaled to the 320x98 BMP that Rockbox compiles into the binary. The stock boot screen is white with the logo pinned near the top; a tiny patch clears to black and centers it:

#ifdef IPOD_6G
    /* the custom logo has a black background */
    lcd_set_background(LCD_BLACK);
    lcd_set_foreground(LCD_RGBPACK(0x60, 0x60, 0x60));
#endif
    /* ... */
    lcd_bmp(&bm_rockboxlogo, (LCD_WIDTH - BMPWIDTH_rockboxlogo) / 2,
            (LCD_HEIGHT - BMPHEIGHT_rockboxlogo) / 2);

That one stays personal, on my device only. Some patches are for upstream and some are just for you.

Where it stands

Change 7677 is under review as I write this, so the ending of this post is “pending”, not “merged”. If review turns up something I missed, that’s the system working.

A caveat before I sign off: all of this was new to me. I’d never touched the Rockbox codebase before this project, and everything I know about how an iPod works on the inside I picked up along the way. So if something here is old news to the embedded crowd, or I’ve proudly reinvented a wheel that has had a proper name since 2005, pardon me.

Still on the someday list: multi-click gestures (double-click for next track is the obvious one) and using the accessory-ID event to only power the mic line when a remote is actually attached, which would shave a little battery with plain headphones.

This is the same itch behind reviving a 10-year-old Kindle, jailbreaking one in a browser, and turning an iPhone 8 into a solar-powered OCR server: hardware this good shouldn’t be limited by abandoned software. A 2009 iPod with working earbud controls in 2026 feels right. And for a previous chapter of this kind of tinkering, there’s the desk toy that spins when Claude is thinking.

The TODO is gone from audio-6g.c, by the way. Three years of Rockbox on this iPod, and the earbud clicker finally clicks. Somebody just had to sweep the registers.

Happy clicking! 🎧

Last updated: July 2026