Skip to content

Commit

Permalink
8227425: Add support for e-paper displays on i.MX6 devices
Browse files Browse the repository at this point in the history
Reviewed-by: jvos, kcr
  • Loading branch information
jgneff authored and kevinrushforth committed Apr 29, 2020
1 parent e30049f commit 66c3b38
Show file tree
Hide file tree
Showing 7 changed files with 236 additions and 45 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,11 @@ class EPDFrameBuffer {
*/
private static final int POWERDOWN_DELAY = 1_000;

/**
* Linux system error: ENOTTY 25 Inappropriate ioctl for device.
*/
private static final int ENOTTY = 25;

private final PlatformLogger logger = Logging.getJavaFXLogger();
private final EPDSettings settings;
private final LinuxSystem system;
Expand Down Expand Up @@ -296,6 +301,16 @@ private MxcfbUpdateData createDefaultUpdate(int width, int height) {
* <li>{@link EPDSystem#WAVEFORM_MODE_A2}</li>
* </ul>
*
* @implNote This method fails on the Kobo Glo HD Model N437 with the error
* ENOTTY (25), "Inappropriate ioctl for device." The driver on that device
* uses an extended structure with four additional integers, changing its
* size and its corresponding request code. This method could use the
* extended structure, but the driver on the Kobo Glo HD ignores it and
* returns immediately, anyway. Furthermore, newer devices support both the
* current structure and the extended one, but define the extra fields in a
* different order. Therefore, simply use the current structure and ignore
* an error of ENOTTY, picking up the default values for any extra fields.
*
* @param init the initialization mode for clearing the screen to all white
* @param du the direct update mode for changing any gray values to either
* all black or all white
Expand All @@ -308,7 +323,7 @@ private void setWaveformModes(int init, int du, int gc4, int gc8, int gc16, int
var modes = new MxcfbWaveformModes();
modes.setModes(modes.p, init, du, gc4, gc8, gc16, gc32);
int rc = system.ioctl(fd, driver.MXCFB_SET_WAVEFORM_MODES, modes.p);
if (rc != 0) {
if (rc != 0 && system.errno() != ENOTTY) {
logger.severe("Failed setting waveform modes: {0} ({1})",
system.getErrorMessage(), system.errno());
}
Expand All @@ -325,7 +340,7 @@ private void setWaveformModes(int init, int du, int gc4, int gc8, int gc16, int
private void setTemperature(int temp) {
int rc = driver.ioctl(fd, driver.MXCFB_SET_TEMPERATURE, temp);
if (rc != 0) {
logger.severe("Failed setting temperature to {2} °C: {0} ({1})",
logger.severe("Failed setting temperature to {2} degrees Celsius: {0} ({1})",
system.getErrorMessage(), system.errno(), temp);
}
}
Expand Down Expand Up @@ -421,7 +436,7 @@ private int sendUpdate(MxcfbUpdateData update, int waveformMode) {
logger.severe("Failed sending update {2}: {0} ({1})",
system.getErrorMessage(), system.errno(), Integer.toUnsignedLong(updateMarker));
} else if (logger.isLoggable(Level.FINER)) {
logger.finer("Sent update: {0} × {1}, waveform {2}, selected {3}, flags 0x{4}, marker {5}",
logger.finer("Sent update: {0} x {1}, waveform {2}, selected {3}, flags 0x{4}, marker {5}",
update.getUpdateRegionWidth(update.p), update.getUpdateRegionHeight(update.p),
waveformMode, update.getWaveformMode(update.p),
Integer.toHexString(update.getFlags(update.p)).toUpperCase(),
Expand Down Expand Up @@ -482,7 +497,7 @@ private int getPowerdownDelay() {
logger.severe("Failed getting power-down delay: {0} ({1})",
system.getErrorMessage(), system.errno());
}
return integer.getInteger(integer.p);
return integer.get(integer.p);
}

/**
Expand Down Expand Up @@ -571,20 +586,57 @@ ByteBuffer getOffscreenBuffer() {
* "QuantumRenderer modifies buffer in use by JavaFX Application Thread"
* <https://bugs.openjdk.java.net/browse/JDK-8201567>.
*/
int size = xresVirtual * yresVirtual * Integer.SIZE;
int size = xresVirtual * yres * Integer.BYTES;
return ByteBuffer.allocateDirect(size);
}

/**
* Creates a new mapping of the Linux frame buffer device into memory.
*
* @implNote The virtual y-resolution reported by the device driver can be
* wrong, as shown by the following example on the Kobo Glo HD Model N437
* which reports 2,304 pixels when the correct value is 1,152 pixels
* (6,782,976 / 5,888). Therefore, this method cannot use the frame buffer
* virtual resolution to calculate its size.
*
* <pre>{@code
* $ sudo fbset -i
*
* mode "1448x1072-46"
* # D: 80.000 MHz, H: 50.188 kHz, V: 46.385 Hz
* geometry 1448 1072 1472 2304 32
* timings 12500 16 102 4 4 28 2
* rgba 8/16,8/8,8/0,8/24
* endmode
*
* Frame buffer device information:
* Name : mxc_epdc_fb
* Address : 0x88000000
* Size : 6782976
* Type : PACKED PIXELS
* Visual : TRUECOLOR
* XPanStep : 1
* YPanStep : 1
* YWrapStep : 0
* LineLength : 5888
* Accelerator : No
* }</pre>
*
* @return a byte buffer containing the mapping of the Linux frame buffer
* device
* device if successful; otherwise {@code null}
*/
ByteBuffer getMappedBuffer() {
int size = xresVirtual * yresVirtual * bytesPerPixel;
ByteBuffer buffer = null;
int size = xresVirtual * yres * bytesPerPixel;
logger.fine("Mapping frame buffer: {0} bytes", size);
long addr = system.mmap(0l, size, LinuxSystem.PROT_WRITE, LinuxSystem.MAP_SHARED, fd, 0);
return addr == LinuxSystem.MAP_FAILED ? null : C.getC().NewDirectByteBuffer(addr, size);
if (addr == LinuxSystem.MAP_FAILED) {
logger.severe("Failed mapping {2} bytes of frame buffer: {0} ({1})",
system.getErrorMessage(), system.errno(), size);
} else {
buffer = C.getC().NewDirectByteBuffer(addr, size);
}
return buffer;
}

/**
Expand All @@ -594,7 +646,13 @@ ByteBuffer getMappedBuffer() {
* buffer device
*/
void releaseMappedBuffer(ByteBuffer buffer) {
system.munmap(C.getC().GetDirectBufferAddress(buffer), buffer.capacity());
int size = buffer.capacity();
logger.fine("Unmapping frame buffer: {0} bytes", size);
int rc = system.munmap(C.getC().GetDirectBufferAddress(buffer), size);
if (rc != 0) {
logger.severe("Failed unmapping {2} bytes of frame buffer: {0} ({1})",
system.getErrorMessage(), system.errno(), size);
}
}

/**
Expand All @@ -614,26 +672,31 @@ long getNativeHandle() {
}

/**
* Gets the virtual horizontal resolution of the frame buffer. See the notes
* for the {@linkplain EPDFrameBuffer#EPDFrameBuffer constructor} above.
* Gets the frame buffer width in pixels. See the notes for the
* {@linkplain EPDFrameBuffer#EPDFrameBuffer constructor} above.
*
* @implNote When using an 8-bit, unrotated, and uninverted frame buffer in
* the Y8 pixel format, the Kobo Clara HD Model N249 works only when this
* method returns the visible x-resolution ({@code xres}) instead of the
* normal virtual x-resolution ({@code xresVirtual}).
*
* @return the virtual width in pixels
* @return the width in pixels
*/
int getWidth() {
return xresVirtual;
return settings.getWidthVisible ? xres : xresVirtual;
}

/**
* Gets the visible vertical resolution of the frame buffer.
* Gets the frame buffer height in pixels.
*
* @return the visible height in pixels
* @return the height in pixels
*/
int getHeight() {
return yres;
}

/**
* Gets the color depth of the frame buffer.
* Gets the frame buffer color depth in bits per pixel.
*
* @return the color depth in bits per pixel
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ class EPDScreen implements NativeScreen {

/**
* The density of this screen in pixels per inch. For now, the value is
* hard-coded to the density of a 6-inch display panel with 800 × 600 px at
* hard-coded to the density of a 6-inch display panel with 800 x 600 px at
* 167 ppi.
*/
private static final int DPI = 167;
Expand Down Expand Up @@ -99,6 +99,8 @@ class EPDScreen implements NativeScreen {
width = fbDevice.getWidth();
height = fbDevice.getHeight();
bitDepth = fbDevice.getBitDepth();
logger.fine("Native screen geometry: {0} px x {1} px x {2} bpp",
width, height, bitDepth);

/*
* If the Linux frame buffer is configured for 32-bit color, compose
Expand All @@ -112,8 +114,12 @@ class EPDScreen implements NativeScreen {
* display, though, allows us to reuse the same frame buffer region
* immediately after sending an update.
*/
ByteBuffer mapping = null;
if (bitDepth == Integer.SIZE) {
fbMapping = fbDevice.getMappedBuffer();
mapping = fbDevice.getMappedBuffer();
}
if (mapping != null) {
fbMapping = mapping;
fbChannel = null;
} else {
Path path = FileSystems.getDefault().getPath(fbPath);
Expand Down
Loading

0 comments on commit 66c3b38

Please sign in to comment.