Wednesday, April 08, 2015

Porting Rockbox to the RCA RC3000A digital boombox

This is an old post that I didn't get around to completing, and kept as a draft. It talks about how I ported Rockbox to the RCA RC3000A digital boombox. Code is available on GitHub.

Summary

First I opened it up and noted the chips inside. Some already have Rockbox drivers. Then, I figured out how to use USB boot mode to run code using tcctool with slight modifications. I used code to dump the firmware, outputting data via a GPIO pin and reading it via the sound card. Then I did some reverse engineering, figured out various functionality, got a Rockbox bootloader running, got Rockbox running and finally made playback work.

The LCD

First, I wanted a better form of feedback than GPIO pin toggling, so I figured out the LCD. The LCD command and data writing routines can be reached by following subroutine calls from a function that displays strings on the LCD. Then, other functions can be identified which use these functions to do stuff with the LCD. The initialization sequence was interesting. Instead of writing register indexes and then writing data to those registers, some commands have a command number in the high bits and data in the lower bits. I identified the controller by searching through Rockbox LCD driver files for | (which is bitwise or in C). The lcd-archos-bitmap.c driver for the graphical LCD on old Archos devices seemed like a match, so the LCD controller is probably a SSD1815. However, the initialization routine uses different values because the LCD panel is different, and it's important to use those values.

The call to the LCD initialization routine was surrounded by other interesting and relevant code. Just before the initialization, a call to a long function set the TCC760 chip select register for access to the LCD. Instead of trying to understand the function, I simply executed it in ARMSim to get the value. There was also a nearby GPIO call which controls the backlight.

Now it was possible to execute some code and write to the LCD. I thought building even a Rockbox bootloader would be difficult, so I just used a modified lcd-archos-bitmap.c file. This worked, but there was nothing interesting to display. I also verified that the nearby GPIO controlled the backlight. This was tricky, because although the CPU and LCD can run from USB power only, the backlight requires external power.

Building a Rockbox bootloader

This success made me want to try running more of Rockbox. I first decided to build a bootloader, because it uses only selected components of Rockbox. The Rockbox Wiki provides some basic information about how to create a new target. I started off by copying files from the Sansa C100, which uses the related Telechips TCC770 CPU. There were plenty of things to edit and rename, but it was all fairly straightforward, and error messages can serve as a guide showing what needs to be done.

Uploading code to SDRAM

The resulting bootloader was too big for loading in TCC760 internal SRAM, so I needed to use DRAM. Unlike with later chips, USB boot mode doesn't provide a way to set the SDCFG register. So, I used a small bit of assembler code to set SDCFG and then jump back to 0 to restart USB boot mode and enable uploading of a longer program to SDRAM. It was easy to get the bootloader running, but timer interrupts took a bit more work. The TCC760 is similar to TCC770, but there are some key differences with some peripheral register bits.

Figuring out the buttons

The bootloader allowed viewing of GPIO and ADC values which helped me figure out the buttons and some other values. The power/play/pause button is connected to its own GPIO pin, but all the other buttons are connected to one pin, with each connecting it to ground via a different value resistor. This means the ADC must be used to read these buttons. I ended up finding the original firmware routine which reads buttons and using its thresholds between different buttons. I suppose that's better than using measured values, because the measured values are affected by resistor tolerances.

SD card access

The RC3000A has 512MB of internal NAND flash storage, and an SD card. I chose to first use the SD card, because that seems safer. When I viewed GPIO values, I found pins for SD card write protect and SD card detection. I used these to find the SD card code. ARM code typically loads a base address into a register and then accesses a memory mapped peripheral register using that register plus an offset encoded into an instruction. In this case, 0x80000000 is always loaded into the register, so the offsets are predictable and they can be used to search disassembled code for GPIO access with few false matches. There is one unfortunate complication though. All the code I've seen makes the GPIO port obvious, but some uses a value loaded from RAM to define the bit.

I assumed that the SD card uses bit banging because there is no SD controller and USB access to the card is ridiculously slow. I did find bit banging code, but actually the SD card is hooked up to GSIO0, and there is also code for that. I chose to initially use bit banging code, because it is simpler to be sure I'm doing it correctly. GSIO could have left me wondering if the problem was the way I'm using GSIO or the interface to the SD card.

SD card bit bang SPI interface code can be used to add SD cards to routers, and source is available there. However, I chose to start with code from within Rockbox, from the Archos Ondio MMC card driver. Its structure more closely matches my intended final structure, with GSIO and hopefully also DMA.

After learning about the SD card protocol and commands, I first worked on getting initialize_card() working. At first this seemed futile, but then I added the initial synchronizing code sending 0xFF bytes with chip select not asserted and it worked. Then I just had an endianness issue in card_extract_bits() and after that the function succeeded. Then it was simple to read a sector.

Building Rockbox, adding functionality, and getting sound

(Post was interrupted here and continued much later.)

Once SD card reading was available and mountable, it seemed like a good time to run a full build of Rockbox. Since this includes files and functionality not compiled into the bootloader, some work was needed to get it to compile. Then it was time to add more functionality.

First I added I2C support, testing it using the E8564 RTC chip, which Rockbox already supports. The RC3000A uses purely software based bit-banging I2C, although the TCC760 chip does contain an I2C controller. 

I wanted to first set up the CODEC for FM radio playback, which should be simpler than music playback. At first I thought I could use the CS42L55 code present in Rockbox, but the CS42L51 is significantly different, so I ended up creating a new driver using parts of the Linux driver. I got the radio working using the pre-existing TEA5767 driver. Only the headphone jack was usable, because the amplifier driving the speakers was off.

Then it was time to do various tweaks to CODEC and I2S configuration and make playback work. However, the result was interrupted playback, because the device was too slow. I enabled the CPU caches, and started using GSIO for SD card access, and sped up the SD clock. Finally, I got perfect playback of a FLAC file.

Cleanups, tweaks and optimizations

There was still a lot of optimizing to do. The fast internal RAM in the TCC760 could be used to speed up code. Thumb and INIT_ATTR could free up memory. The SD driver needed more work. DMA could be used for playback.

I spent a lot of time trying to get DMA to work with GSIO for SD card accesses, but it seemed impossible and eventually I gave up. I optimized things the best I could with 16 bit GSIO transfers.

Getting USB to work was interesting, because I had never worked with a USB device before.

There were a lot of features to support. Rockbox includes various LCD settings such as flip and inverse, and a way to display greyscale on an LCD which doesn't officially support it. I also enabled sound bass and treble setting via the codec and backlight PWM fading.

Flashing the bootloader

So far, Rockbox could only be loaded via USB boot. It time to flash a Rockbox bootloader with dual-boot support, allowing for choosing between original firmware and Rockbox. I was a bit anxious because this could brick the device, but there was no real danger because USB boot should allow recovery.

Initially, I was thinking I would create a flash plugin and use it to flash the bootloader. However, I ended up creating a firmware upgrade file and flashing it via the original firmware. My first attempt allowed access to original firmware, but failed to run Rockbox. After adding some short initialization code from the original firmware I could load Rockbox.

The end?

Once I had a working Rockbox port, I stopped working on this. The RCA RC3000A devices are probably quite rare, and I've never encountered anyone else interested in running Rockbox on one. It doesn't even seem like there's any interest in running Rockbox on TCC76X devices. Therefore, I don't think adding this port to Rockbox would benefit anyone.

One thing that still interests me is making use of the OHCI USB host controller on the device. The original firmware supports it but fails badly when accessing a large device because it tries to scan all the files and keep a list in memory. I wonder if there's a good free software embedded USB stack? The SeaBIOS code might be useful, but it is GPL 3 and Rockbox is GPL 2.

I've also thought about adding an ESP8266 module and creating a Rockbox plugin for playing Internet radio. Such a plugin could also be used with other targets with externally accessible serial ports, such as the iPods.

No comments: