From 26f14d6b5df2ddb37311bda69ae4bb8d42051657 Mon Sep 17 00:00:00 2001 From: Thomas Cort Date: Mon, 29 Jul 2013 13:01:38 -0400 Subject: [PATCH] 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 --- drivers/fb/Makefile | 9 +- drivers/fb/README.txt | 24 +++++ drivers/fb/arch/earm/fb_arch.c | 177 ++++++++++++++++++++++++++++--- drivers/fb/fb.c | 22 +++- drivers/fb/fb.h | 5 +- drivers/fb/fb_edid.c | 188 +++++++++++++++++++++++++++++++++ drivers/fb/fb_edid.h | 12 +++ etc/system.conf | 2 +- etc/usr/rc | 54 +++++++++- 9 files changed, 467 insertions(+), 26 deletions(-) create mode 100644 drivers/fb/README.txt create mode 100644 drivers/fb/fb_edid.c create mode 100644 drivers/fb/fb_edid.h diff --git a/drivers/fb/Makefile b/drivers/fb/Makefile index 2744ccfdb..493892fb4 100644 --- a/drivers/fb/Makefile +++ b/drivers/fb/Makefile @@ -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 diff --git a/drivers/fb/README.txt b/drivers/fb/README.txt new file mode 100644 index 000000000..838901b82 --- /dev/null +++ b/drivers/fb/README.txt @@ -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. + diff --git a/drivers/fb/arch/earm/fb_arch.c b/drivers/fb/arch/earm/fb_arch.c index 5dbf7340f..d324ed16a 100644 --- a/drivers/fb/arch/earm/fb_arch.c +++ b/drivers/fb/arch/earm/fb_arch.c @@ -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 #include #include #include #include +#include #include #include #include #include +#include +#include +#include +#include #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"); diff --git a/drivers/fb/fb.c b/drivers/fb/fb.c index b3103de97..52c08fce7 100644 --- a/drivers/fb/fb.c +++ b/drivers/fb/fb.c @@ -11,10 +11,15 @@ #include #include #include +#include +#include +#include +#include + #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; diff --git a/drivers/fb/fb.h b/drivers/fb/fb.h index 2784ef6d5..cf9da112d 100644 --- a/drivers/fb/fb.h +++ b/drivers/fb/fb.h @@ -1,7 +1,9 @@ #ifndef __FB_H__ #define __FB_H__ -int arch_fb_init(int minor, struct device *dev); +#include + +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__ */ diff --git a/drivers/fb/fb_edid.c b/drivers/fb/fb_edid.c new file mode 100644 index 000000000..116b64434 --- /dev/null +++ b/drivers/fb/fb_edid.c @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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; +} + diff --git a/drivers/fb/fb_edid.h b/drivers/fb/fb_edid.h new file mode 100644 index 000000000..2829ba4a3 --- /dev/null +++ b/drivers/fb/fb_edid.h @@ -0,0 +1,12 @@ +#ifndef __EDID_H +#define __EDID_H + +#include +#include +#include +#include + +int fb_edid_args_parse(void); +int fb_edid_read(int minor, struct edid_info *info); + +#endif /* __EDID_H */ diff --git a/etc/system.conf b/etc/system.conf index ff5e595de..47045888f 100644 --- a/etc/system.conf +++ b/etc/system.conf @@ -573,7 +573,7 @@ service fb PRIVCTL # 4 ; ipc - SYSTEM pm rs ds vm vfs tda19988 + SYSTEM pm rs ds vm vfs cat24c256 tda19988 ; }; diff --git a/etc/usr/rc b/etc/usr/rc index 6667e45d4..7dfb42ffd 100644 --- a/etc/usr/rc +++ b/etc/usr/rc @@ -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