• Hello Guest! We're hosting a challenge to welcome vintage Intel macs to the MLA during the month of July! See this thread for more information.
  • We've made some quality of life improvements to the Trading Post. More info here.

PowerBook reverse engineering for fun and no profit

(Drafts don't always save on here...I don't know if there's a maximum size or what, but I guess now this post is extra-refined.)

With Omaha2 complete, I moved on to capturing signals from GSC, starting in passive-matrix mode because I can use my singular display panel at the same time I'm running the logic analyzer. I made a few test programs that call SetDepth (to simulate choosing a color depth in the Control Strip) and write to the framebuffer in various repeating patterns. For anyone doing this in the future, make sure you have the system set to 32-bit addressing, or the OS will step in and rewrite the framebuffer with parts of windows while your program is running. The first, most notable thing about GSC is that it formats its output quite differently from passive-matrix mode Omaha2 (this is in 4-grey mode):
1781659454279.png
Instead of a continuous stream of byte clocks (CL2) with line clocks (CL1) at intervals, bursts of CL2 pulses are evenly spaced and ended with CL1 pulses, with just...nothing between them. FLM_F is also much longer, starting a few cycles into a block of CL2 pulses and lasting until the end of that block, and M_F is always high, instead of alternating.

I noticed that the first bits on D7-D4 of the line synchronous with FLM repeat every 4 lines. There are still 32000 CL2 clocks per frame, and 160 per line, they're just spaced differently. In hindsight, the explanation for this should have been obvious, but I didn't put the pieces together. In fact, I got so stumped that I went and Googled the screens.

If you Google for the Sharp LM64P51 or LM64P58, you get absolutely nothing except vintage Apple threads. If you Google for the Hitachi LMG5200, you get one result. From this result I found some leads that took me to lots of other Hitachi and Sharp monochrome passive matrix displays, all 320x240 or 640x480. The most enlightening of these was the Sharp LM64P839, whose datasheet I'm attaching here for the curious. From this datasheet we glean a lot of information: not only was I correct about the data format, but the dual nibble bus is actually a de facto standard! These are dual-scan passive-matrix displays, which means that two parts of the display are scanned at one time, just as I discovered. Here's the block diagram from the datasheet:

1781659961665.png
The data bits are called DU0-DU3 and DL0-DL3, for "display upper" and "display lower". The M signal is also internally generated (which explains why GSC doesn't send it), and is apparently used to prevent DC voltage from building up inside the liquid crystal and causing damage. Sharp calls the row drivers "COM" and the column drivers "SEG", which explains why the silkscreen on my LM64P58 says "STAB" and "CTAB" (though I don't know what "TAB" is for). Presumably GSC does send M when it's in active-matrix mode? We'll find out.

We also learn that FLM, which Sharp calls "S" but Hitachi calls "FLM", stands for "first line marker". The waveform recommended is:

1781660295902.png
and this exactly matches what we see. This waveform specification is identical between different manufacturers and different models, which explains why all the display revisions are electrically compatible.

At this point I realized what I was seeing on the logic analyzer. GSC is dithering the display by alternating pixel states between frames, so that the state of each greyscale pixel averages out to somewhere between on and off. We can confirm this by looking at some pixel data from a program called GreyScreen4GreyOneColor, which sets the whole screen to the byte 0x5f = 0b01_01_11_11, or two pixels light grey and then two black. With this, we see these values sent for the first 4 pixels of four consecutive frames:
D71000
D60010
D51111
D41111

This is obviously a dithered signal. For dark grey (0b10), two out of four frames are 1 and the others are zero. Clearly GSC's dithering algorithm has some complexity, because not only are adjacent pixels' dithering offset from one another, even when they're the same color, but it seems to intentionally not use some on-off combinations (3/4 frames set to 1 just isn't used, in this case).

GSC expands this for 16-grey mode by dithering across a 16-frame interval. It behaves the same otherwise. In each of the three color depths GSC supports, it changes the frequency and timing of the signals it generates. 4-color mode and 16-color mode behave the same as 4-grey and 16-grey mode, respectively. The display datasheets I found recommend certain frame clock frequencies for ideal monochrome results (minimal flicker, highest dynamic range, etc), so presumably GSC is using what Apple determined as the best timing for each mode. Here's the specs, measured on my oscilloscope:

Black and white: 61.0094 Hz FLM, 12.2019 kHz CL1
4 grey: 87.0400 Hz FLM, 17.4080 kHz CL1
16 grey: 119.415 Hz FLM, 23.8829 kHz CL1

I actually think that black and white mode looks a little flickery, but that might just be me. The CL1 frequency determines the amount of dead time between blocks of CL2 pulses, and in 16-grey mode there's almost no time between them. The period of a CL2 pulse is independent of the selected pixel depth, and one block of CL2 pulses is always 40.80 us long.

GSC also has registers, located at 0x50020000 in 32-bit mode, which is new since Omaha and Omaha2 were fully autonomous. These can be read by the CPU and mirror every 32 bytes. One byte near the end appears to be some kind of counter, and a few others change depending on the display mode selected. Other than that I have no idea what they do.
 

Attachments

At this point I realized what I was seeing on the logic analyzer. GSC is dithering the display by alternating pixel states between frames, so that the state of each greyscale pixel averages out to somewhere between on and off.
Makes sense - so greyscale is not really a ‘feature’ of these panels, it’s just how it’s being driven by GSC.

Does the active matrix panel also use dithering to generate greys? I’m guessing not, but…
 
GSC is a relabeled Chips & Technologies 65210, incidentally. Unfortunately no datasheets seem to exist online, although the System 7.1 source tree at least has register descriptions.
 
Is that a custom design to Apple spec, or off the shelf?
Based on the fact that I've never seen it appear in a PC laptop, I'm inclined to think it's custom. C&T's normal lineup followed the name scheme of 655xx (65520, 65525, 65530, 65535, 65540, 65545, 65546, 65548, etc)
 
It's listed as a standard part in one of their databooks, but I've only seen Apple use it.
Interesting. If it was used in any other laptops, then I haven't seen one yet while making my database which currently has specs for 2500 models. Probably because a basic framebuffer chip that only supported grayscale displays was rather primitive by 1992 PC laptop standards? I wouldn't be surprised if it showed up in embedded/industrial applications though.

For reference, the typical C&T chip found in PC laptops released in 1992 was the 65530.
 
GSC in active-matrix mode is where everything comes together. To start with, GSC in active-matrix mode uses a similar waveform to GSC in passive-matrix mode:
1781897657425.png

The difference is that GSC in active-matrix mode does not change the shape or timing of its output signals depending on the color depth. In addition, it always sends 320 bytes per line, for 400 lines between FLM pulses. 400*320 = 128000 bytes, aka the size of the framebuffer when GSC is in 16-grey mode.

From the assumption that the data format is the same---8 bits map linearly to pixels, no dual scan---I first tested a screen in black and white. GreyScreenBWVertStripes sets the system to black & white mode, then sets 3 bytes black and 1 byte white, then repeats across the whole screen. With this, we see 12 CL2 cycles (bytes in active-matrix mode) high on the data bus, then 4 low. On Omaha2, this would equate to 12*8 = 96 pixels black and 4*8 = 32 pixels white, so obviously something is different. Since 320 bytes is 4 times 80 bytes, and 12*8/4 = 24 pixels, and 4*8/4 = 8 pixels, I figured I could assume that GSC sends each pixel as 4 consecutive bits instead of dithering 1-bit screens as it does in passive-matrix mode, and that the display understands greyscale video.

This turns out to be correct, but there is some complexity. The next program, GreyScreen4GreyVertStripes, sets the first 4 pixels to zero, the next 4 to 0b01, the 4 after that to 0b10, and the final four to 0b11, then repeats. With this, we see data that reconstructs to (8 bits at a time, line breaks every 16 bits):
0000000000000000
0100010001000100
1000100010001000
1110111011101110

Clearly GSC is sending data as 4 bits per pixel, which the display presumably understands as a greyscale framebuffer. However, GSC is also clearly adding some dithering or some variety of noise to the signal, since these don't map 1:1 to the gradient programmed to display on the screen. We'd like to see "01010101 01010101" in the second row, "10101010 10101010" in the third, and "11111111 11111111" in the fourth.

The final test is in 16-grey mode. GreyScreen16GreyVertStripes makes vertical columns from white to black at all 16 grey levels before repeating, just like 4GreyVertStripes. With this, we see that D7 is low for 8 CL2 cycles, then high for 8, D6 is low for 4 and high for 4, D5 is low for 2 and high for 2, and D4 is always low. If that's not almost a binary counter, I don't know what is. The fact that D4 is always low, however, means that GSC is purposefully not using the full dynamic range of the display as offered, which is reflected in the fact that sometimes D7 is low for 7 cycles and then high for 9, sometimes 8, D6 is low for 3 and then high for 4, sometimes 5, and D5 is low for 1 and high for 2, sometimes 3. The data at the start of the FLM line exhibits the same kind of repetition we saw in passive-matrix mode. This means that instead of sending straight greyscale data to the active-matrix panel, GSC is using a selected amount of the panel's available 16-bit range and dithering to get the rest of the way. Like in passive-matrix mode, I don't know what the dithering algorithm is.

Makes sense - so greyscale is not really a ‘feature’ of these panels, it’s just how it’s being driven by GSC.

Does the active matrix panel also use dithering to generate greys? I’m guessing not, but…
Evidently kind of. I wonder if C&T or Apple decided that the panel's greyscale wasn't good enough, or something, and decided to implement dithering in silicon in parallel. (How good is the greyscale on the active-matrix displays compared to passive-matrix? On my passive-matrix, 16GreyVertStripes looks more like vertical noise than a pleasant gradient---the white end has almost the same color from pixels 0 to 7, and the last two near-black and black columns look the same. I also noticed in passive-matrix mode that alternating columns of some colors were different brightnesses when the screen was displaying gradient rows, with nothing to cause horizontal/vertical shadowing.)

Interestingly, GSC cranks its clock speeds way up in active-matrix mode:
CL2: 8952.71 kHz
CL1: 27.9772 kHz
FLM: 87.013 Hz

CL2 was fast enough that my pitiful little 24 MHz Saelae clone couldn't keep up, and I was seeing about 170 cycles per line, which definitely doesn't make any sense. I used a Pi Pico 2 with sigrok-pico, which is fine except that its sample buffer is too small to capture a whole frame.

On a related note, Sharp recommends 85 Hz for best results on the passive-matrix LM64P839, and in 16-grey mode, GSC is pushing the screen near its limits:
1781912299403.png
I do think that 4-grey mode on GSC with a passive-matrix mode looks the best, and that runs at 87 Hz, so maybe they tuned for that. I have no idea about the active-matrix display, but I'm sure that was tuned as well.
 
GSC is a relabeled Chips & Technologies 65210, incidentally. Unfortunately no datasheets seem to exist online, although the System 7.1 source tree at least has register descriptions.
How do we know this? Is it labeled under the thermal pads that I'm too scared to take off?

It took me some searching, but Drivers/DeclData/DeclData.r in the System 7.1 source says that MSC is "the LCD display control for DB-Lite machines", presumably Omaha2, and that GSC is "the LCD grayscale controller for DB-Lites and Dartanians". I don't know what DB-Lite refers to (the Wikipedia codename list doesn't mention it), but "Dartanian" is the 180, so DB-Lite probably includes the 160. The DB-Lite folder in DeclData has DBLiteDriver.a, the full driver.

The code doesn't sit down and tell you what each register is, but there appears to be a "refresh" register, a "skew" register, and a "m&n polyadj" register. "Refresh" looks like it's just a mode config, and the chip knows what to do with it:
1781920960370.png

"Skew" sets the frame clocks:
1781921039879.png

"m&n polyadj" is only defined for the STN display, and since it has m and n in the name, I assume it's some kind of number of lines configuration. I don't know what 99 and 130 mean.1781921001346.png

This is all from DBLiteSetDepth, and I can see that it's loading the GSCPanelID register (not defined, sadly) from GSC and comparing it to determine what kind of display is connected, so STN_MODE is still connected to GSC like it is to Omaha and Omaha2.

The driver references 3 bytes of 32, and I'm inclined to think that the OS simply doesn't need to touch the other registers. I do see that I missed an active-matrix mode, since the skew register data shows that the TFT can be refreshed at 68Hz and 70Hz depending on the mode (see below). The three refresh rates are for 1-bit, 2-bit, and 4-bit respectively. I have some logic analyzer records of a full boot with STN_MODE low, and the screen is refreshed at 68Hz on startup for a little bit before something in ROM (it's before the OS is even discovered) sets it to 87Hz. That might just be the startup mode of the chip. This matches a 4-bit TFT connected to GSC, set to 1-bit mode, so either the chip initializes itself to that, or the ROM is doing it on startup.

Digging through the source code, I realized I forgot about the Duos, because there's also references to the "color-screen controller", CSC, used in the 270c, 280/280c, and a computer codenamed BlackBird not on the Wikipedia list. (Edit: Blackbird is the PowerBook 520/540 according to this other codename list and the 520/540 dev note.) Reading through the dev notes I see that the 210, 230, and 250 also use GSC. Annoyingly, the 210 dev note explains the concept of dual-scan, which would have been nice to know about sooner.
 
How do we know this? Is it labeled under the thermal pads that I'm too scared to take off?

It took me some searching, but Drivers/DeclData/DeclData.r in the System 7.1 source says that MSC is "the LCD display control for DB-Lite machines", presumably Omaha2, and that GSC is "the LCD grayscale controller for DB-Lites and Dartanians". I don't know what DB-Lite refers to (the Wikipedia codename list doesn't mention it), but "Dartanian" is the 180, so DB-Lite probably includes the 160. The DB-Lite folder in DeclData has DBLiteDriver.a, the full driver.

The code doesn't sit down and tell you what each register is, but there appears to be a "refresh" register, a "skew" register, and a "m&n polyadj" register. "Refresh" looks like it's just a mode config, and the chip knows what to do with it:
View attachment 100375

"Skew" sets the frame clocks:
View attachment 100377

"m&n polyadj" is only defined for the STN display, and since it has m and n in the name, I assume it's some kind of number of lines configuration. I don't know what 99 and 130 mean.View attachment 100376

This is all from DBLiteSetDepth, and I can see that it's loading the GSCPanelID register (not defined, sadly) from GSC and comparing it to determine what kind of display is connected, so STN_MODE is still connected to GSC like it is to Omaha and Omaha2.

The driver references 3 bytes of 32, and I'm inclined to think that the OS simply doesn't need to touch the other registers. I do see that I missed an active-matrix mode, since the skew register data shows that the TFT can be refreshed at 68Hz and 70Hz depending on the mode (see below). The three refresh rates are for 1-bit, 2-bit, and 4-bit respectively. I have some logic analyzer records of a full boot with STN_MODE low, and the screen is refreshed at 68Hz on startup for a little bit before something in ROM (it's before the OS is even discovered) sets it to 87Hz. That might just be the startup mode of the chip. This matches a 4-bit TFT connected to GSC, set to 1-bit mode, so either the chip initializes itself to that, or the ROM is doing it on startup.

Digging through the source code, I realized I forgot about the Duos, because there's also references to the "color-screen controller", CSC, used in the 270c, 280/280c, and a computer codenamed BlackBird not on the Wikipedia list. (Edit: Blackbird is the PowerBook 520/540 according to this other codename list and the 520/540 dev note.) Reading through the dev notes I see that the 210, 230, and 250 also use GSC. Annoyingly, the 210 dev note explains the concept of dual-scan, which would have been nice to know about sooner.

According to my notes, DBLite was the codename for the PowerBook Duo 210/230, not the 160/165. But I think all four share the same driver: '.Display_Video_Apple_DBLite'.

The PowerBook Duo Prototype Schematics have recently been released and they feature the GSC chip, in case that's of interest.
 
Late here but I've got a prototype ESP that converts the video from Omaha running in black and white to MIPI-DSI (so PowerBook 140-145 to a modern screen)
Can confirm the timing findings above. It's basically 2 640x200 screens

Unfortunately the ESP revision I'm using is broken (many DMA issues, scaling issues, broken PARLIO state engine...) so the image isn't stable and at best I get 14fps. The new one aren't out yet so this is postponed for a at least a year because the new revision isn't out yet.

ESP32-P4_v1_Previes.jpeg


If someone needs the (awful) code as a reference, I can post it here.
 
Good find on the Duo 210/230 schematic, whoever dug that up! The details I've checked all look final to me.

As the schematic indicates, panel IDs for GSC and CSC are read from the actual panel and are different per machine, unlike the monitor IDs in desktop Macs. Each machine only recognizes certain IDs and won't init video if it sees one it doesn't like.
 
According to my notes, DBLite was the codename for the PowerBook Duo 210/230, not the 160/165. But I think all four share the same driver: '.Display_Video_Apple_DBLite'.

The PowerBook Duo Prototype Schematics have recently been released and they feature the GSC chip, in case that's of interest.
Nice. Having a symbol for GSC is helpful, and those ID pins are interesting. The DB-Lite driver compares the panel ID to 5 (0b101) for a TFT panel and 1 (0b001) for a FSTN panel, so I suspect they grounded LCDID1 and pulled LCDID0 high, then connected LCDID2 to STN_MODE from the display. All three pins are routed to a connector on the Duo so maybe they wanted to support more than 2 types of display.

Late here but I've got a prototype ESP that converts the video from Omaha running in black and white to MIPI-DSI (so PowerBook 140-145 to a modern screen)
Can confirm the timing findings above. It's basically 2 640x200 screens
Super cool! Unfortunate that they made the chip bugged though.
 
Long-time lurker, first post this thread is what finally pushed me to start reverse-engineering the display on my own PowerBook 180. Huge thanks to liamur first off, your GSC writeups gave me an enormous head start.

Figured I'd throw in some active-matrix numbers from a 180, since most of what's posted so far is off a 145B/160 and a few things come out noticeably different on mine.

Quick setup note in case it's useful to anyone else: I'm probing the 26-pin interconnect directly with an Gusman V6 RP2350 logic analyzer plus a 100MHz 12-bit scope. The LA can hold a full frame at 12.5 MHz sampling, which got me past the buffer-size wall that both the 24 MHz Saleae clone and the Pi Pico 2 hit earlier in this thread.
WhatsApp Image 2026-07-12 at 01.19.59.jpeg
Timing on the 180, GSC, active-matrix, boot screen showing:

CL2 (byte clock) comes out to about 15.7 MHz inside a burst (63.8ns period), but only 13.06 MHz if you average over a whole line including the gap between bursts. Flagging this because it trips people up my scope reads ~13 MHz since it averages across the gaps, but the LA shows the real 15.7 MHz burst rate. If your number doesn't match someone else's in this thread, this is probably why.

CL1 (line clock) is 40.8 kHz, FLM (frame) is 102 Hz.

Both faster than what's posted for the 145B/160 (CL2 ~9MHz, CL1 28kHz, FLM 87Hz) which tracks, the 180's the fastest of the family at 33 MHz. What I didn't expect is that CL2 and CL1 scale up more than FLM does seems like the 180 has proportionally more dead time between CL2 bursts, so the faster pixel clock isn't translating 1:1 into a higher frame rate.

Frame structure: 320 bytes per line, exactly (320 CL2 pulses between CL1 pulses). Frame period divides cleanly into 400 line-slots too, so 400 active lines and no extra vertical blanking lines tucked in there FLM just marks the frame directly. All consistent with 4 bits/pixel, linear, no dual-scan, matches what you found on active-matrix already.

Also, the boot screen turns out to be a decent free test pattern. The grey desktop background is a 1-bit checkerboard, and on the bus it shows up as alternating lines of 0xF1 and 0x1F (320 identical bytes per line). Two things fall out of that: it's 4bpp with two pixels per byte (you get 0xF/0x1 per nibble, not 0xFF/0x00 per byte), and dark pixels come across as 0x1 rather than 0x0, so GSC isn't using the panel's full range here either same thing you noted for the other modes. Still want to run some proper stripe patterns like yours to actually pin down the pixel format instead of just inferring it from a checkerboard.

On the control lines: DISP_BLANK/ (pin 25) sits low at power-on and goes high a second or two later once the display's initialized, then just stays high decent "data valid" marker if anyone needs one. M_F (pin 19) is held static high the whole time I captured in active-matrix mode, so re: your open question about whether GSC drives M in active-matrix, on my timescale it doesn't, it just parks it high. Caveat though, my captures only cover about 1.5 frames, so I can't rule out a slow polarity toggle at frame rate or slower yet. Longer capture is next on my list.

STN_MODE (pin 23) is static low with no activity. One thing worth mentioning if anyone's probing this themselves: if you see ~13 MHz on that line, that's just CL2 crosstalk bleeding in, not a real signal it's a few tens of mV, well below logic threshold, and the LA correctly reads it flat low. I grounded it externally and booted the machine and nothing changed, so on the 180 it really does look like a static type-select tied to the panel's ground plane, no serial handshake happening on that pin.
Bildschirmfoto_20260712_012603.png

On the panel-ID question I've matched all 26 pins on the interconnect against liamur's pinout and there's nothing left over, no mystery lines on the 180. STN_MODE is a static type-select and grounding it boots fine, so no serial ID handshake there. What I can't rule out yet is whether GSC is reading a panel ID off static levels strapped across pins that already have names in the 14x/170 schematic (a few of them differ on the 180). Arbee, since you've dug into the GSCPanelID register and the Duo LCDID pins does GSC on the 180 actually gate video on an ID, or is STN_MODE the only type info it needs? That's the one thing I'd want nailed down before trying to drive GSC with no panel attached.

Happy to share the raw LA captures (VCD/CSV) if anyone wants to poke at them. Thanks again, this is exactly the kind of documentation that makes a project like this feel approachable instead of hopeless.
 
Back
Top