fb: auto-configure with EDID

Use EDID when available to configure the frame buffer driver with
good settings for the attached display.

Change-Id: I69a78155a93e55ffa1ca3ff6621a879a56cdbceb
This commit is contained in:
Thomas Cort 2013-07-29 13:01:38 -04:00 committed by Gerrit Code Review
parent bdf33c702a
commit 26f14d6b5d
9 changed files with 467 additions and 26 deletions

View file

@ -3,7 +3,14 @@ PROG= fb
.include "arch/${MACHINE_ARCH}/Makefile.inc"
SRCS+= fb.c
SRCS+= fb_edid.c fb.c
# re-use EDID parsing/validation code from NetBSD.
.PATH: ${NETBSDSRCDIR}/sys/dev/videomode
SRCS+= edid.c pickmode.c videomode.c vesagtf.c
# Put this dir and the EDID headers (dev/videomode/*.h) in the search path.
CPPFLAGS+= -I${.CURDIR} -I${NETBSDSRCDIR}/sys
DPADD+= ${LIBCHARDRIVER} ${LIBSYS}
LDADD+= -lchardriver -lsys

24
drivers/fb/README.txt Normal file
View file

@ -0,0 +1,24 @@
Frame Buffer Driver
===================
Overview
--------
This is the driver for the frame buffer. Currently it only supports the
DM37XX (BeagleBoard-xM).
Testing the Code
----------------
Starting up an instance:
service up /usr/sbin/fb -dev /dev/fb0 -args edid.0=cat24c256.3.50
The arguments take the following form:
edid.X=L where X is the frame buffer device (usually 0) and L is
the service label of the service to perform the EDID reading. In
the example above, it's the EEPROM with slave address 0x50 on
the 3rd I2C bus. If you want to use the defaults and skip EDID
reading, you may omit the arguments.

View file

@ -1,23 +1,50 @@
/* Architecture dependent part for the framebuffer on the OMAP3. Since we don't
* have support for EDID (which requires support for i2c, also something we
* don't have, yet), but we do have a screen with 1024*600 resolution for our
* testing purposes, we hardcode that resolution here. There's obvious room for
* improvement. */
/* Architecture dependent part for the framebuffer on the OMAP3.
* There's obvious room for improvement.
*/
#include <minix/chardriver.h>
#include <minix/drivers.h>
#include <minix/fb.h>
#include <minix/type.h>
#include <minix/vm.h>
#include <minix/log.h>
#include <assert.h>
#include <sys/mman.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <dev/videomode/videomode.h>
#include <dev/videomode/edidvar.h>
#include <dev/videomode/edidreg.h>
#include "dss.h"
#include "fb.h"
/* default / fallback resolution if EDID reading fails */
#define SCREEN_WIDTH 1024
#define SCREEN_HEIGHT 600
#define PAGES_NR 2
#define NSUPPORTED_MODES (4)
/* List of valid modes from TRM 7.1
* Other modes might work (like the default 1024x600), but no guarantees.
*/
struct supported_modes {
int hdisplay;
int vdisplay;
} omap_supported_modes[NSUPPORTED_MODES] = {
{ .hdisplay = 1024, .vdisplay = 768 }, /* XGA */
{ .hdisplay = 1280, .vdisplay = 800 }, /* WXGA */
{ .hdisplay = 1400, .vdisplay = 1050 }, /* SXGA+ */
{ .hdisplay = 1280, .vdisplay = 720 } /* HD 720p */
};
/* local function prototypes */
static struct videomode *choose_mode(struct edid_info *info);
static void configure_with_defaults(int minor);
static int configure_with_edid(int minor, struct edid_info *info);
/* globals */
static vir_bytes dss_phys_base; /* Address of dss phys memory map */
static vir_bytes dispc_phys_base; /* Address of dispc phys memory map */
static vir_bytes fb_vir;
@ -50,7 +77,9 @@ static const struct panel_config default_cfg = {
.panel_color = 0xFFFFFF /* WHITE */
};
static const struct fb_fix_screeninfo fbfs = {
static struct panel_config omap_cfg[FB_DEV_NR];
static const struct fb_fix_screeninfo default_fbfs = {
.xpanstep = 0,
.ypanstep = 0,
.ywrapstep = 0,
@ -59,7 +88,9 @@ static const struct fb_fix_screeninfo fbfs = {
.mmio_len = 0 /* these are set to 0 */
};
static struct fb_var_screeninfo fbvs = {
static struct fb_fix_screeninfo omap_fbfs[FB_DEV_NR];
static const struct fb_var_screeninfo default_fbvs = {
.xres = SCREEN_WIDTH,
.yres = SCREEN_HEIGHT,
.xres_virtual = SCREEN_WIDTH,
@ -89,6 +120,15 @@ static struct fb_var_screeninfo fbvs = {
}
};
static struct fb_var_screeninfo omap_fbvs[FB_DEV_NR];
/* logging - use with log_warn(), log_info(), log_debug(), log_trace() */
static struct log log = {
.name = "fb",
.log_level = LEVEL_INFO,
.log_func = default_log
};
static inline u32_t
readw(vir_bytes addr)
{
@ -101,6 +141,97 @@ writew(vir_bytes addr, u32_t val)
*((volatile u32_t *) addr) = val;
}
static struct videomode *
choose_mode(struct edid_info *info)
{
int i, j;
/* choose the highest resolution supported by both the SoC and screen */
for (i = info->edid_nmodes - 1; i >= 0; i--) {
for (j = NSUPPORTED_MODES - 1; j >= 0; j--) {
if (info->edid_modes[i].hdisplay ==
omap_supported_modes[j].hdisplay &&
info->edid_modes[i].vdisplay ==
omap_supported_modes[j].vdisplay) {
return &(info->edid_modes[i]);
}
}
}
return NULL;
}
static int
configure_with_edid(int minor, struct edid_info *info)
{
struct videomode *mode;
if (info == NULL || minor < 0 || minor >= FB_DEV_NR) {
log_warn(&log, "Invalid minor #%d or info == NULL\n", minor);
return -1;
}
/* If debugging or tracing, print the contents of info */
if (log.log_level >= LEVEL_DEBUG) {
log_debug(&log, "--- EDID - START ---\n");
edid_print(info);
log_debug(&log, "--- EDID - END ---\n");
}
/* Choose the preferred mode. */
mode = choose_mode(info);
if (mode == NULL) {
log_warn(&log, "Couldn't find a supported resolution.\n");
return -1;
}
/*
* apply the default settings since we don't overwrite every field
*/
configure_with_defaults(minor);
/*
* apply the settings corresponding to the given EDID
*/
/* panel_config */
omap_cfg[minor].lcd_size = ((mode->vdisplay - 1) << 16 | (mode->hdisplay - 1));
if (EDID_FEATURES_DISP_TYPE(info->edid_features) ==
EDID_FEATURES_DISP_TYPE_MONO) {
omap_cfg[minor].panel_type = 0x00; /* Mono */
} else {
omap_cfg[minor].panel_type = 0x01; /* RGB/Color */
}
/* fb_fix_screeninfo */
omap_fbfs[minor].line_length = mode->hdisplay * 4;
/* fb_var_screeninfo */
omap_fbvs[minor].xres = mode->hdisplay;
omap_fbvs[minor].yres = mode->vdisplay;
omap_fbvs[minor].xres_virtual = mode->hdisplay;
omap_fbvs[minor].yres_virtual = mode->vdisplay*2;
return OK;
}
static void
configure_with_defaults(int minor)
{
if (minor < 0 || minor >= FB_DEV_NR) {
log_warn(&log, "Invalid minor #%d\n", minor);
return;
}
/* copy the default values into this minor's configuration */
memcpy(&omap_cfg[minor], &default_cfg, sizeof(struct panel_config));
memcpy(&omap_fbfs[minor], &default_fbfs, sizeof(struct fb_fix_screeninfo));
memcpy(&omap_fbvs[minor], &default_fbvs, sizeof(struct fb_var_screeninfo));
}
static void
arch_configure_display(int minor)
{
@ -110,7 +241,7 @@ arch_configure_display(int minor)
if (!initialized) return;
if (minor != 0) return;
off = fbvs.yoffset * fbvs.xres_virtual * (fbvs.bits_per_pixel/8);
off = omap_fbvs[minor].yoffset * omap_fbvs[minor].xres_virtual * (omap_fbvs[minor].bits_per_pixel/8);
writew((vir_bytes) OMAP3_DISPC_GFX_BA0(dispc_phys_base),
fb_phys + (phys_bytes) off);
@ -136,7 +267,7 @@ arch_get_varscreeninfo(int minor, struct fb_var_screeninfo *fbvsp)
if (!initialized) return ENXIO;
if (minor != 0) return ENXIO;
*fbvsp = fbvs;
*fbvsp = omap_fbvs[minor];
return OK;
}
@ -151,12 +282,12 @@ arch_put_varscreeninfo(int minor, struct fb_var_screeninfo *fbvsp)
if (minor != 0) return ENXIO;
/* For now we only allow to play with the yoffset setting */
if (fbvsp->yoffset != fbvs.yoffset) {
if (fbvsp->yoffset < 0 || fbvsp->yoffset > fbvs.yres) {
if (fbvsp->yoffset != omap_fbvs[minor].yoffset) {
if (fbvsp->yoffset < 0 || fbvsp->yoffset > omap_fbvs[minor].yres) {
return EINVAL;
}
fbvs.yoffset = fbvsp->yoffset;
omap_fbvs[minor].yoffset = fbvsp->yoffset;
}
/* Now update hardware with new settings */
@ -170,7 +301,7 @@ arch_get_fixscreeninfo(int minor, struct fb_fix_screeninfo *fbfsp)
if (!initialized) return ENXIO;
if (minor != 0) return ENXIO;
*fbfsp = fbfs;
*fbfsp = omap_fbfs[minor];
return OK;
}
@ -181,20 +312,32 @@ arch_pan_display(int minor, struct fb_var_screeninfo *fbvsp)
}
int
arch_fb_init(int minor, struct device *dev)
arch_fb_init(int minor, struct device *dev, struct edid_info *info)
{
int r;
u32_t rdispc;
struct minix_mem_range mr;
const struct panel_config *panel_cfg = &default_cfg;
const struct panel_config *panel_cfg = &omap_cfg[minor];
assert(dev != NULL);
if (minor != 0) return ENXIO; /* We support only one minor */
if (initialized) {
dev->dv_base = fb_vir;
dev->dv_size = fb_size;
return OK;
} else if (info != NULL) {
log_debug(&log, "Configuring Settings based on EDID...\n");
r = configure_with_edid(minor, info);
if (r != OK) {
log_warn(&log, "EDID config failed. Using defaults.\n");
configure_with_defaults(minor);
}
} else {
log_debug(&log, "Loading Default Settings...\n");
configure_with_defaults(minor);
}
initialized = 1;
@ -254,8 +397,8 @@ arch_fb_init(int minor, struct device *dev)
writew(OMAP3_DISPC_GFX_PIXEL_INC(dispc_phys_base), 1);
/* Allocate contiguous physical memory for the display buffer */
fb_size = fbvs.yres_virtual * fbvs.xres_virtual *
(fbvs.bits_per_pixel / 8);
fb_size = omap_fbvs[minor].yres_virtual * omap_fbvs[minor].xres_virtual *
(omap_fbvs[minor].bits_per_pixel / 8);
fb_vir = (vir_bytes) alloc_contig(fb_size, 0, &fb_phys);
if (fb_vir == (vir_bytes) MAP_FAILED) {
panic("Unable to allocate contiguous memory\n");

View file

@ -11,10 +11,15 @@
#include <sys/mman.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <dev/videomode/videomode.h>
#include <dev/videomode/edidvar.h>
#include <dev/videomode/edidreg.h>
#include "logos.h"
#include "fb_edid.h"
#include "fb.h"
#define FB_DEV_NR 1
/*
* Function prototypes for the fb driver.
*/
@ -69,11 +74,19 @@ static int open_counter[FB_DEV_NR]; /* Open count */
static int
fb_open(message *m)
{
int r;
static int initialized = 0;
static struct edid_info info;
static struct edid_info *infop = NULL;
if (m->DEVICE < 0 || m->DEVICE >= FB_DEV_NR) return ENXIO;
if (arch_fb_init(m->DEVICE, &fb_device[m->DEVICE]) == OK) {
if (!initialized) {
r = fb_edid_read(m->DEVICE, &info);
infop = (r == 0) ? &info : NULL;
}
if (arch_fb_init(m->DEVICE, &fb_device[m->DEVICE], infop) == OK) {
open_counter[m->DEVICE]++;
if (!initialized) {
if (has_restarted) {
@ -363,8 +376,11 @@ sef_cb_init(int type, sef_init_info_t *UNUSED(info))
}
int
main(void)
main(int argc, char *argv[])
{
env_setargs(argc, argv);
fb_edid_args_parse();
sef_local_startup();
chardriver_task(&fb_tab, CHARDRIVER_SYNC);
return OK;

View file

@ -1,7 +1,9 @@
#ifndef __FB_H__
#define __FB_H__
int arch_fb_init(int minor, struct device *dev);
#include <minix/fb.h>
int arch_fb_init(int minor, struct device *dev, struct edid_info *info);
int arch_get_device(int minor, struct device *dev);
int arch_get_varscreeninfo(int minor, struct fb_var_screeninfo *fbvsp);
int arch_put_varscreeninfo(int minor, struct fb_var_screeninfo *fbvs_copy);
@ -9,5 +11,6 @@ int arch_get_fixscreeninfo(int minor, struct fb_fix_screeninfo *fbfsp);
int arch_pan_display(int minor, struct fb_var_screeninfo *fbvs_copy);
#define FB_MESSAGE "Hello, world! From framebuffer!\n"
#define FB_DEV_NR 1
#endif /* __FB_H__ */

188
drivers/fb/fb_edid.c Normal file
View file

@ -0,0 +1,188 @@
/*
* Handle reading the EDID information, validating it, and parsing it into
* a struct edid_info. EDID reads are done using the Block Device Protocol
* as it's already supported by the cat24c256 driver and there is no need
* to add yet another message format/type.
*/
#include <minix/fb.h>
#include <minix/chardriver.h>
#include <minix/drivers.h>
#include <minix/ds.h>
#include <minix/rs.h>
#include <minix/log.h>
#include <minix/sysutil.h>
#include <minix/type.h>
#include <minix/vm.h>
#include <sys/ioc_fb.h>
#include <assert.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <errno.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <dev/videomode/videomode.h>
#include <dev/videomode/edidvar.h>
#include <dev/videomode/edidreg.h>
#include "fb_edid.h"
#include "fb.h"
static int do_read(endpoint_t endpt, uint8_t *buf, size_t bufsize);
/* logging - use with log_warn(), log_info(), log_debug(), log_trace() */
static struct log log = {
.name = "edid",
.log_level = LEVEL_INFO,
.log_func = default_log
};
/*
* Labels corresponding to drivers which provide EDID.
*/
static char edid_providers[FB_DEV_NR][RS_MAX_LABEL_LEN+1];
/*
* Populate edid_providers from command line arguments. The service command
* should get EDID providers like this: "-args edid.0=tda19988.1.3470" where
* 0 is the minor number of the frame buffer, tda19988 is the device driver,
* 1 is the i2c bus and 3470 is the slave address (the TDA19988 has 2 slave
* addresses 0x34 and 0x70).
*/
int
fb_edid_args_parse(void)
{
int i;
int r;
char key[32];
for (i = 0; i < FB_DEV_NR; i++) {
memset(key, '\0', 32);
snprintf(key, 32, "edid.%d", i);
memset(edid_providers[i], '\0', RS_MAX_LABEL_LEN);
r = env_get_param(key, edid_providers[i], RS_MAX_LABEL_LEN);
if (r == OK) {
log_debug(&log, "Found key:%s value:%s\n", key, edid_providers[i]);
} else {
/* not an error, user is allowed to omit EDID
* providers in order to skip EDID reading and use
* the default settings.
*/
log_debug(&log, "Couldn't find key:%s\n", key);
}
}
return OK;
}
/*
* Send a read request to the block driver at endpoint endpt.
*/
static int
do_read(endpoint_t driver_endpt, uint8_t *buf, size_t bufsize)
{
int r;
message m;
cp_grant_id_t grant_nr;
/* Open Device - required for drivers using libblockdriver */
memset(&m, '\0', sizeof(message));
m.m_type = BDEV_OPEN;
m.BDEV_ACCESS = R_BIT;
m.BDEV_ID = 0;
m.BDEV_MINOR = 0;
r = sendrec(driver_endpt, &m);
if (r != OK) {
log_debug(&log, "sendrec(BDEV_OPEN) failed (r=%d)\n", r);
return r;
}
grant_nr = cpf_grant_direct(driver_endpt, (vir_bytes) buf,
bufsize, CPF_READ | CPF_WRITE);
/* Perform the read */
memset(&m, '\0', sizeof(message));
m.m_type = BDEV_READ;
m.BDEV_MINOR = 0;
m.BDEV_COUNT = bufsize;
m.BDEV_GRANT = grant_nr;
m.BDEV_FLAGS = BDEV_NOPAGE; /* the EEPROMs used for EDID are pageless */
m.BDEV_ID = 0;
m.BDEV_POS_LO = 0;
m.BDEV_POS_HI = 0;
r = sendrec(driver_endpt, &m);
cpf_revoke(grant_nr);
if (r != OK) {
log_debug(&log, "sendrec(BDEV_READ) failed (r=%d)\n", r);
/* Clean-up: try to close the device */
memset(&m, '\0', sizeof(message));
m.m_type = BDEV_CLOSE;
m.BDEV_MINOR = 0;
m.BDEV_ID = 0;
sendrec(driver_endpt, &m);
return r;
}
/* Close the device */
memset(&m, '\0', sizeof(message));
m.m_type = BDEV_CLOSE;
m.BDEV_MINOR = 0;
m.BDEV_ID = 0;
r = sendrec(driver_endpt, &m);
if (r != OK) {
log_debug(&log, "sendrec(BDEV_CLOSE) failed (r=%d)\n", r);
return r;
}
return bufsize;
}
int
fb_edid_read(int minor, struct edid_info *info)
{
int r;
uint8_t buffer[128];
endpoint_t endpt;
if (info == NULL || minor < 0 || minor >= FB_DEV_NR ||
edid_providers[minor][0] == '\0') {
return EINVAL;
}
log_debug(&log, "Contacting %s to get EDID.\n", edid_providers[minor]);
/* Look up the endpoint that corresponds to the label */
endpt = 0;
r = ds_retrieve_label_endpt(edid_providers[minor], &endpt);
if (r != 0 || endpt == 0) {
log_warn(&log, "Couldn't find endpoint for label '%s'\n", edid_providers[minor]);
return r;
}
/* Perform the request and put the resulting EDID into the buffer. */
memset(buffer, 0x00, 128);
r = do_read(endpt, buffer, 128);
if (r < 0) {
log_debug(&log, "Failed to read EDID\n");
return r;
}
/* parse and validate EDID */
r = edid_parse(buffer, info);
if (r != 0) {
log_warn(&log, "Invalid EDID data in buffer.\n");
return r;
}
log_debug(&log, "EDID Retrieved and Parsed OK\n");
return OK;
}

12
drivers/fb/fb_edid.h Normal file
View file

@ -0,0 +1,12 @@
#ifndef __EDID_H
#define __EDID_H
#include <stdint.h>
#include <dev/videomode/videomode.h>
#include <dev/videomode/edidvar.h>
#include <dev/videomode/edidreg.h>
int fb_edid_args_parse(void);
int fb_edid_read(int minor, struct edid_info *info);
#endif /* __EDID_H */

View file

@ -573,7 +573,7 @@ service fb
PRIVCTL # 4
;
ipc
SYSTEM pm rs ds vm vfs tda19988
SYSTEM pm rs ds vm vfs cat24c256 tda19988
;
};

View file

@ -197,10 +197,14 @@ start)
BOARD_NAME=`eepromread -i | sed -n 's/^BOARD_NAME : \(.*\)$/\1/p'`
case "${BOARD_NAME}" in
A335BONE)
echo "Detected BeagleBone"
echo -n "Starting i2c device drivers: "
test -e /dev/eepromb1s50 || (cd /dev && MAKEDEV eepromb1s50)
# start EEPROM driver for reading board info
test -e /dev/eepromb1s50 || \
(cd /dev && MAKEDEV eepromb1s50)
up cat24c256 -dev /dev/eepromb1s50 \
-label cat24c256.1.50 \
-args 'bus=1 address=0x50'
@ -209,13 +213,35 @@ start)
up tps65217 -label tps65217.1.24 \
-args 'bus=1 address=0x24'
# check for the presence of a display
eepromread -f /dev/i2c-2 -n > /dev/null 2>&1
RESULT=$?
if [ $RESULT -eq 0 ]
then
# start eeprom driver for reading EDID.
test -e /dev/eepromb2s50 || \
(cd /dev && MAKEDEV eepromb2s50)
up cat24c256 -dev /dev/eepromb2s50 \
-label cat24c256.2.50 \
-args 'bus=2 address=0x50'
# start frame buffer
#up fb -dev /dev/fb0 -args edid.0=cat24c256.2.50
# fb hasn't been ported to AM335X yet.
fi
;;
A335BNLT)
echo "Detected BeagleBone Black"
echo -n "Starting i2c device drivers: "
test -e /dev/eepromb1s50 || (cd /dev && MAKEDEV eepromb1s50)
# start EEPROM driver for reading board info
test -e /dev/eepromb1s50 || \
(cd /dev && MAKEDEV eepromb1s50)
up cat24c256 -dev /dev/eepromb1s50 \
-label cat24c256.1.50 -args 'bus=1 address=0x50'
-label cat24c256.1.50 \
-args 'bus=1 address=0x50'
# Start TPS65217 driver for power management.
up tps65217 -label tps65217.1.24 \
@ -224,7 +250,13 @@ start)
# Start TDA19988 driver for reading EDID.
up tda19988 -label tda19988.1.3470 -args \
'cec_bus=1 cec_address=0x34 hdmi_bus=1 hdmi_address=0x70'
# start frame buffer
#up fb -dev /dev/fb0 -args edid.0=tda19988.1.3470
# fb hasn't been ported to AM335X yet.
;;
UNKNOWN)
echo "Unable to detect board -- assuming BeagleBoard-xM"
echo -n "Starting i2c device drivers: "
@ -236,6 +268,22 @@ start)
# Set the system time to the time in the TPS65950's RTC
readclock
# check for the presence of a display
eepromread -f /dev/i2c-3 -n > /dev/null 2>&1
RESULT=$?
if [ $RESULT -eq 0 ]
then
# start eeprom driver for reading edid
test -e /dev/eepromb3s50 || \
(cd /dev && MAKEDEV eepromb3s50)
up cat24c256 -dev /dev/eepromb3s50 \
-label cat24c256.3.50 \
-args 'bus=3 address=0x50'
# start frame buffer
up fb -dev /dev/fb0 -args edid.0=cat24c256.3.50
fi
;;
esac