Last edited by KermMartian on 18 May 2012 03:39:26 pm; edited 2 times in total

As many of you may know, I recently purchased a Beaglebone, having gotten fed up waiting for the Raspberry Pi to become a reality. The Beaglebone is an embedded development board showcasing Texas Instruments' AM335x line of System-on-a-Chip (SoC) MPUs. For the $90 board, you get a USB slave and host, a 32-bit ARM processor, 256 MB of off-chip DDR RAM, about 60 3.3v GPIO pins, an Ethernet port, and all sorts of other fun things. I have eventual goals of building a complete device around the processor, so the Beaglebone is a great way to prototype as I go. Of course, almost any device I'd want to build needs a screen of some sort, so my first major challenge was interfacing an LCD. I chose a 320x240 pixel touchscreen with an SSD1289 controller, which I purchased for less than $20. I read enough to know that the AM335x has an on-board LCD controller, or LCDC, but the easy stuff ended there.

There's a lot of documentation on the internet about TI's LCDC Raster Engine, but very little documentation or code for the LCDC LIDD Engine, used to operate more intelligent LCD panels, so I hope to document here a lot of the lessons that I learned trying (painfully) to work with it. Since my current distro of choice for my Beaglebone is Angstrom, I'll also be discussing my lessons compiling kernel modules and patches for Angstrom. At the end of this article, you can download a patch to add SSD1289 support for 240x320 LCDs to your Beaglebone (or, with some work, another platform).

Building Angstrom Images
Angstrom Linux uses the OpenEmbedded build system, built around Bitbake. It makes it (supposedly) easy to use an abstraction called recipes to built different sorts of kernels and root file systems for embedded systems, simply by specifying the machine and type of image you want and waiting a bit. It will figure out and generate all of the cross-compilation tools that you need, but I found that if you want to customize the kernel yourself, it is very challenging. If you want to modify your kernel or add your own kernel modules, you should know several things:

To perform a menuconfig to reconfigure your kernel, you must run a series of three commands: Code:

If everything is working properly, you'll get the standard console menuconfig to set kernel options. When you have set everything to your preference, exit and save, and run "MACHINE=<system_type> ./oebb.sh bitbake virtual/kernel -c compile -f". Do not run the config (not to be confused with menuconfig) or update steps again, as those seem to wipe out everything you may have changed or configured.

It's hard to find the actual kernel sources that are being built. For the Beaglebone (for which <system_type> above should be beaglebone), it can be found in ./build/tmp-angstrom_vYYYY_MM-eglibc/work/beaglebone-angstrom-linux-gnueabi/linux-ti33x-psp-X.X-rXXX-gitreNNN....NN/git/, where YYYY and MM are a datestamp, X.X rXXX is a Linux revision number, and NNN....NN is a long cryptographic hash. The exact path will be different depending on which kernel version and Angstrom version you're compiling against.

While we're circling vaguely around the issue of Git, guides that suggest you use the Angstrom website to grab the setup-scripts/OEBB build system are wrong. The correct source is GitHub, so the git clone command is

Code:

git clone git://github.com/Angstrom-distribution/setup-scripts.git

Building anything other than a console-image is shaky at best. You can specify several different sorts of images; the three I have been experimenting with are these:

When you're ready to deploy your built images, there's two places you'll want to look. If you want the full root filesystem plus the bootloader files, look in ./build/tmp-angstrom_vYYYY_MM-eglibc/beaglebone-angstrom-linux-gnueabi/deploy/images/beaglebone/. If all you're doing is repeatedly building a kernel with "MACHINE=<system_type> ./oebb.sh bitbake virtual/kernel -c compile -f", you can get the new uImage in the exhaustingly-long path ./build/tmp-angstrom_vYYYY_MM-eglibc/work/beaglebone-angstrom-linux-gnueabi/linux-ti33x-psp-X.X-rXXX-gitreNNN....NN/git/arch/arm/boot/. Of course, if you're not building an ARM Beaglebone image, the directory may be a bit different.

Getting a system built is just the first part of the puzzle if you, like me, want to create a kernel module of your own or modify the existing kernel. Modifying the kernel, given the instructions here, becomes a lot easier, but there's still plenty of things of which you should be aware.

Modifying the Angstrom Kernel
If you want to modify your kernel by adding extra drivers or modules, there are a few things that you need to do. First, you'll need the .c and possibly .h file(s) for your particular modification. Some of these will be stand-alone, and some will be additions to existing files. For the Beaglebone SSD1289 driver, I added git/drivers/video/ssd1289.c and modified git/arch/arm/mach-omap2/board-am335xevm.c, devices.c, and clock33xx_data.c. You also need to add the switches in the Kconfig relevant to your driver (for me, git/drivers/video/Kconfig) so that your module can be enabled and disabled. You must modify the requisite Makefile to be aware of your module; for me, this was git/drivers/video/Makefile. Finally, there may be additional data files you need to add; for the SSD1289 driver, I overwrote the custom giant Beaglebone drivers/logo/logo_linux_clut224.ppm with the original 80x80-pixel Tux image.

As you work, keep copies of each file that you modify, and ideally, also keep the originals. You'll eventually need to do a special sort of diff in order to create what's known as a patch, a semi-version-agnostic set of changes that can be applied to a kernel to add your changes. If you forget to keep an original of any file, don't worry; you'll be able to pull a clean version of the Angstrom build system once you finish your driver and diff against that. After each set of changes, you'll need to rebuild the kernel with the "-c compile -f" command I listed in the previous section. The updated uImage will show up in the folder discussed above (.../arch/arm/boot) if the build succeeds. To test out each new kernel to see if it would boot and properly load my driver, I copy the uImage file to the boot partition of the Beaglebone's microSD card as well as to the /boot directory of the Angstrom partition. I suspect that these aren't both necessary, but I did both for the sake of sanity. I set up a tiny bash script to copy the uImage to both partitions and eject both partitions, as I have probably moved the microSD card back and forth between the Beaglebone and my computer about 400 times since I started developing this driver. Make sure that your Beaglebone is powered off when you remove and insert the microSD card, and that you properly eject (umount) the microSD card from your computer where you're building the kernel before removing it! If you don't take such precautions, you're likely to damage or at least corrupt your microSD card.

Creating Angstrom Kernel Patches
Once you have a working driver, you'll need to create an Angstrom patch. Save the git directory, and either restore the original files, or re-create the original git directory so you have a fresh repository version, patched with all the existing Angstrom/Beaglebone patches. Then, you'll use the unified diff tool (via diff -uNr) to create your patch. Something like "diff -uNr old-git-dir/ new-git-dir/ >mypatch.patch" should do the trick. Once you are satisfied with your patch, and have checked it over to make sure it contains everything it should (and nothing extra), you'll have to insert it into the Angstrom recipes directories. At the time of writing, this would be: Code:

sources/meta-ti/recipes-kernel/linux/linux-ti33x-psp-3.2/beaglebone/

There will be a set of numbered patches in this directory; make your patch the next one. For example, I have up to 0030-..., so I named mine "0031-beaglebone-add-ssd1289-support.patch". You'll also have to update the corresponding .bb (bitbake) file to be aware of this new patch. For my example, this file is sources/meta-ti/recipes-kernel/linux/linux-ti33x-psp-3.2.bb. I simply modified the end of the file to: Code:

file://beaglebone/0031-beaglebone-add-ss1289-support.patch \

Thus far I've given you the very basics of setting up a build environment, but there's more documentation out there on customizing kernels than the frustratingly-poorly-documented LCDC LIDD mode, so without further ado, let's move on to that.

The SSD1289 LCD and the AM335x's LCDC
The TI AM335x has an LCDC (LCD Controller) IP which has been used in one form or another in many of their previous SoCs and MPUs. It can support several different types of "dumb" Raster and "smart" LIDD displays. Raster displays must be used with Direct Memory Access, or DMA, meaning that you set up pointers and modes for a chunk of memory, and the AM335x takes care of repeatedly copying that memory to the screen. It worries about the LCD's timing needs without taking up actual CPU time. If you're using an LIDD display, where you speak to the display in address and data commands instead of a continuous stream of raw data, you have two options. You can write directly to memory-mapped I/O (MMIO) addresses, which the AM335x converts into the proper waveforms for sending and receiving addresses and data to and from the LCD's internal driver (such as the HX8347, ILI9325, and SSD1289). However, you still need to spend a lot of CPU time copying width*height*color_depth bits over to the LCD each time the screen image is updated. Once again, DMA can come to your rescue. In the next two sections, I'll discuss the DMA and non-DMA modes of the LCDC's LIDD functionality. For now, let me talk about the basics of interfacing an SSD1289-based LCD, and general lessons that I learned during development that I didn't really find documented anywhere else.

First, you need to have the physical connections in place. The Beaglebone has 3.3V I/O (and the AM335x can be permanently damaged if you accidentally try to interface 5V I/O without level-shifting), and luckily the SSD1289 is happy with a 3.3V supply and 3.3V I/O. Any 8080-style LCD driver (such as the SSD1289) should be interfaced to the Beaglebone/AM335x with the following wiring:

The next step is to set up the multiplexers that choose what internal modules are connected to which external pins. The major file where the Beaglebone sets this up is ./<rest of the path>/git/arch/arm/mach-omap2/board-am335xevm.c. You'll see several examples of other modules setting up pins; I ended up doing it like this:

Notice that lcd_data0 through lcd_data15 are set to mux mode 0, which connects those pins to the LCDC module. lcd_data16 through lcd_data23 are used to create a 24-bit interface for 24-bit LCDs, but since the SSD1289 has a 16-bit data/address bus, I set those to mode7 so that they can be used as regular GPIO pins. The final four pins have different functions depending on whether you're using a Raster or LIDD LCD, but either way, you need to set the mux mode to connect them to the LCDC.

Of course, you also need to run some code to make that array do something useful. The function in question is setup_pin_mux(), and I use it like this:

This is the code to initialize the LCD module, and it's a bit messy, but functional. If you're writing a non-DMA LCD driver, you can omit the am33xx_register_lcdc_lidd_dma() and instead directly call platform_register_device(). It seems that am33x_register_lcdc(), the raster version of the call, does some special device setup for DMA. It wasn't until I spent about 20 hours last weekend painfully debugging my DMA code that I discovered this was necessary. But more on this later.

You may note that the initialization function above references "ssd1289_device". If you're familiar with writing Linux device drivers, you're probably used to setting up the resources needed by each device. If you, like me, had never tried this before, then the following code might be new (but hopefully relatively understandable):

When in doubt, grepping through the source files in the ./.../git/ directory finds a surprising amount of reference information, and Googling for "<functionname> identifier" or <symbolname> identifier" yields lots of cleanly-presented and indexed Linux source using and defining that particular function or symbol.

Setting up the resources for a driver is, again, a relatively well-documented procedure, and if there's anything you don't understand, you (like me) can try to grok some of the extensive body of existing driver code that's out there. Let's look at initializing and using the AM335x LCDC in LIDD mode, with non-DMA commands.

Interfacing the TI's AM335x LCDC LIDD Mode in Non-DMA Mode
In non-DMA mode, the CPU is responsible for copying each frame of data over to the display when said frame is ready. The AM335x communicates with the LCD, generating the necessary waveforms and voltages on the LCD data/address bus pins to communicate with displays, while your driver need only feed in the 16-bit data and address words to be written (or to be read back out). In order to use the non-DMA LIDD mode of the LCDC module, I discovered through relatively painful trial-and-error that you must at least do the following steps:

Fetch the MMIO address range from the device structure with the platform_get_resource() call.

Request exclusive control over the MMIO address range with request_mem_region(); for the AM335x, this is 4KB starting at 0x4830E000. You will only use the lower 0x6C or so bytes.

Map the memory into kernel virtual memory with ioremap().

Enable the LCDC clock, which is actually more akin to flipping a power switch to turn on the LCDC module. You'll need to modify clock33xx_data.c to use <yourmodule>.c instead of da8xx-fb.c for &lcdc_fck, which is referenced on two lines of the file. If you modify clock33xx_data.c, you can use clk_get() to get the clock, and then clk_enable() to enable the clock (turn on the LCDC). Before you turn on the LCDC, if you try to access the MMIO registers, you'll get "Unhandled fault: external abort on non-linefetch (0x1028)".

Now that the LCDC is on, you can check it's version by reading offset register offset 0x00. Value 0x4F200800 or 0x4F201000 means a Version 2 LCDC like the one in the AM335x, which has a more complex set of commands than the previous Version 1.

Before you can send or receive any data or commands to and from the LCD itself, you have to enable a separate set of clocks, and set their multipliers. First turn just the LIDD clock on by writing LCD_V2_LIDD_CLK_EN to LCD_CLKC_ENABLE, offset 0x6C. You then set the desired divisor (I used x4) into LCD_CTRL, along with any other flags you want for that register (refer to the AM335x hardware manual for details). Finally, set the display type into LCD_LIDD_CTRL; for the SSD1289, it's an 8080-type, with no line inversion needed.

At this point, you can properly communicate with the SSD1289 via the LCDC.

You will have to initialize the display's settings by alternately sending addresses (register offsets in the SSD's command memory) to LCD_LIDD_CS0_ADDR and data values to LCD_LIDD_CS0_DATA. You can see this done in ssd1289_setup() in my patch. When that is complete, you can create video memory for the display (which must be a multiple of the page size for your machine), then register a framebuffer. I was surprised to discover that even though the SSD1289 with a 16-bit display is used with FB_VISUAL_TRUECOLOR mode, you still have to create and reserve a pseudo_palette, otherwise your driver will inexplicably appear to hang the kernel deep within register_framebuffer().

Almost all of this initialization will take place in the ..._probe(struct platform_device *dev) function of your driver. Once you initialize the framebuffer, control will be turned over to the various framebuffer functions, which must at least including rectangle drawing (sys_fillrect), rectangle-copying (sys_copyarea), and 1-bit image blitting (sys_imageblit). The sys_ (instead of cfb_) versions of these functions work with a framebuffer stored in system RAM, rather than DMA'd to a device, so you may have to wrap those three functions in functions that call the system function and then manually copy the buffer to the screen. This is, needless to say, rather slow, which I discovered during my development. There's a method called deferred I/O that performs batching to minimize the overhead of re-copying the screen buffer to the screen itself, but I was unable to get it to work. I knew that there was an even better solution I could use, Direct Memory Access or DMA.

Interfacing the TI's AM335x LCDC LIDD Mode in DMA Mode
DMA mode means that the processor has separate hardware that can copy from RAM out to the display's driver and hence the LCD without the CPU itself having to mediate the copy. DMA is even worse-documented than the non-DMA LIDD mode. Although I already had a mostly-working framework for my driver from successfully writing it without DMA, it still took me two or three 12-hour days of work to get the DMA version functioning. The biggest problem turned out to be that with DMA, it becomes necessary to use omap_device_build() via am33xx_register_lcdc_lidd_dma(), a modified clone of am33xx_register_lcdc() that I wrote, which seems to enable one or more clocks necessary for proper DMA functionality. If you use a modified am33xx_register_lcdc() function that calls omap_device_build() (which will eventually reach your probe() method), then the following steps should be sufficient to get DMA running:

Create an interrupt handler which will disable DMA, send the necessary commands to your video driver by writing to LCD_LIDD_CS0_ADDR and/or LCD_LIDD_CS0_DATA to reset the driver's internal memory cursor to the top-left corner, then re-enable DMA. This interrupt handler will be called each time the DMA module in the MPU completes a pass over the video buffer.

Optionally, create a function that will read the LCD_LIDD_CTRL register in the LCDC, set or reset the DMA bit, and re-write the register. I called my version ssd1289_dma_setstatus().

Perform the same initialization steps in ..._probe() as for non-DMA LIDD drivers. However, you'll need to turn on the DMA_CLK, the LIDD_CLK, and the CORE_CLK. You'll also have to request and register the IRQ (incidentally, it's IRQ #36) associated with the LCDC that will trigger your interrupt handler.

Before you can register your framebuffer, you should set up DMA mode. Start by writing a 0 to LCD_DMA_CTRL_REG to make sure DMA is disabled. Then, configure the DMA burst size (which you might as well max out to 16) and FIFO threshold (0 will probably work). For the LCD_INT_ENABLE_SET_REG register, at least set LCD_V2_DONE_INT_ENA (bit 0). Finally, set LCD_DMA_FRM_BUF_BASE_ADDR_0_REG and LCD_DMA_FRM_BUF_CEILING_ADDR_0_REG.

After that setup, you can request_irq(), enable DMA, and return from the probe function.

The key insight here is that if you don't go through omap_device_build(), then DMA will never truly begin. You'll be able to tell this is the case because about 10 seconds after power-on, you'll see another non-linefetch abort, this one with code 0x1018. You also cannot directly inject any data or address commands to the LCD driver while the DMA engine is active, so you must either put such commands between disabling and enabling DMA in the interrupt handler, or disable DMA and wait for the current frame to complete before trying to communicate directly with the display driver.

Concluding Remarks
I hope that you'll find this guide helpful in saving you from the many hours of frustrating testing, fruitless searching, and head-scratching confusion that I went through trying to write my driver. Needless to say, I've assumed that you've been able to find at least some information elsewhere, especially the TI Sitari AM3358 documentation. If there's anything that I omitted that you think I may be able to answer, feel free to post your question.

Lincoln: Most likely. I am close to having X (X11/the Xwindowing system) running properly on it, so I probably won't even really have to do anything crazy to get Doom running.

AHelper: It's not a project I feel like publicly announcing yet; I can neither confirm nor deny anything (but the fact that I'm working with Octave on it should tell you some things). Yes, it's a 3.2"-diagonal 320x240-pixel display, ironically almost exactly the same as the Nspire's screen except that it's a touchscreen.

Any plans for improving your driver and attempting to get it mainlined, or is it just a hack to you?

If you don't, I'll probably pick up a similar piece of hardware and do it.

Yeah, I'm planning to keep hacking on it and try to get it in the Angstrom mainline. Before I do that, I need to at least fix the fb_ioctl() issues that I suspect are behind X refusing the launch and get it to understand that it has a landscape mode.

Yup, that has been suggested, and I was planning on it. You need at least 16+4 GPIO to use this (16+2, in a pinch), so it would overwhelm an Arduino. You could use an Arduino Mega, though, which has more GPIO pins.

Yup, that has been suggested, and I was planning on it. You need at least 16+4 GPIO to use this (16+2, in a pinch), so it would overwhelm an Arduino. You could use an Arduino Mega, though, which has more GPIO pins.

I think another approach to this problem is to do what the Adafruit guys did: routing all the communication through I2C using a port expander (like this one.

The one I've pointed to isn't sufficient, however: it only supports setting a pin high or low, and doesn't support PWM output. Of course, there's plenty of more interesting I2C chips out there!

I could go even further and get a separate chip, like a PIC, to handle other aspects like SPI.

Of course, that kind of insanity will be handled over the summer.... Just some thoughts!

Offtopic: I got an invite to buy the Raspberry Pi, but I'm not sure if I should take it. Should I? I would really like to see an onboard Wifi added before I get one, but...

Actually, the SSD1289 supports SPI communication out-of-the-box; it's just that the board this particular LCD is mounted on sets the display to the parallel mode and doesn't expose the SPI pins. It would be unfortunate to need more hardware to switch back to a two-wire protocol, but perhaps necessary. Also, all communication for this LCD is digital, so a non-PWM I2C expander would be fine.

My LCD module came in at the beginning of this week. I haven't actually mucked with the hardware yet, but I took the time to create a part in EAGLE for it.
My board is a bit different in that it lacks the 40-pin header at the top of the LCD, but it does have an SD card socket.
It's in my library now, which can be found on bitbucket.

Very cool, thanks for sharing. That's actually what my LCD was marked as on eBay, the one with the single header and the SD card socket, but when it arrived it turned out it was the one I photographed, with two headers and no SD card socket.

Thanks! Well, the da8xx stuff isn't going to help you much with an SSD1289-related display, since the da8xx driver is built for raster mode, and something like the SSD1289 requires the LIDD mode. When you say that it's unable to find the lcdc nodes in am33xx_register_lcdc_lidd_dma(), do you mean that the ""SSD1289 register: Could not look up LCD0 hwmod\n" message triggers at runtime (or rather, boot time)?

Hi,
Sorry. Forgot to say, that I don't have a SSD1289 display but a HX8238. It uses raster mode. I've been using your patch to see what files I needed to change... and your thread to get a better understanding. So, thanks!

Ahhh, gotcha. If you're positive that you have all of the connections correct, then investigate how (for example) the backlight should be activated for your screen. For mine, it's a PWM signal, so I usually just jack it directly into +3.3V.

The connections should be correct. The backlight is just supplied by 18V and doesn't need to be activated.
I've also checked my interrupt function, and it's called with the LCD_MASKED_STAT_REG bits LCD_END_OF_FRAME0 & 1 alternatingly high, so the DMA should be running.
Can I check somehow that my board is actually trying to use my driver and is filling data in the dma buffers?
Thanks!

Thank you so much for this wonderful documentation. It has been three weeks that I bought my beaglebone and started playing with it. Because of the reason that it is the first time that I am using a linux os, tutorials like this helps me a lot!

Now I have a few questions if you would like to answer: = )

I was not using OE for recompiling the kernel and I thought I was successful. However today, even though I added the generic usb serial support, when I tried:

Because of the reason that I got the error "uknown parameter 'vendor' " I thought I could not add the generic usb serial support. (Please warn me if this error is not about generic usb serial support or if I had to add something more to the kernel.) Is there a command to check if I could successfully added generic usb serial support?

Because I though that the problem was related to generic usb serial support I tried to recompile my kernel by using OE and following your guidline, now how I am going to copy this (only the new compiled kernel) to my microSD card?

Have your own thoughts to add to this or any other topic? Want to ask a question, offer a suggestion, share your own programs and projects, upload a file to the file archives, get help with calculator and computer programming, or simply chat with like-minded coders and tech and calculator enthusiasts via the site-wide AJAX SAX widget? Registration for a free Cemetech account only takes a minute.