blob: 027d1042c236eb2d8c72e1b190b5b7aea5de9063 [file] [log] [blame]
/****************************************************************************
* arch/arm/src/am335x/am335x_edid.c
*
* SPDX-License-Identifier: BSD-3-Clause
* SPDX-FileCopyrightText: 2019 Gregory Nutt. All rights reserved.
* SPDX-FileCopyrightText: 2013 Oleksandr Tymoshenko <gonzo@freebsd.org>
* SPDX-FileContributor: Gregory Nutt <gnutt@nuttx.org>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
* 3. Neither the name NuttX nor the names of its contributors may be
* used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
****************************************************************************/
/****************************************************************************
* Included Files
****************************************************************************/
#include <nuttx/config.h>
#include <inttypes.h>
#include <string.h>
#include <assert.h>
#include <errno.h>
#include <debug.h>
#include <nuttx/video/edid.h>
#include "am335x_lcdc.h"
/****************************************************************************
* Pre-processor definitions Functions
****************************************************************************/
#define MODE_HBP(mode) ((mode)->htotal - (mode)->hsync_end)
#define MODE_HFP(mode) ((mode)->hsync_start - (mode)->hdisplay)
#define MODE_HSW(mode) ((mode)->hsync_end - (mode)->hsync_start)
#define MODE_VBP(mode) ((mode)->vtotal - (mode)->vsync_end)
#define MODE_VFP(mode) ((mode)->vsync_start - (mode)->vdisplay)
#define MODE_VSW(mode) ((mode)->vsync_end - (mode)->vsync_start)
#define MAX_PIXEL_CLOCK 126000
#define MAX_BANDWIDTH (1280*1024*60)
/****************************************************************************
* Private Functions
****************************************************************************/
/****************************************************************************
* Name: am335x_lcd_edid
*
* Description:
* Return the vertical refresh rate for this video mode.
*
****************************************************************************/
static uint32_t
am335x_videomode_vrefresh(const struct videomode_s *videomode)
{
uint32_t refresh;
/* Calculate vertical refresh rate */
refresh = (videomode->dotclock * 1000 / videomode->htotal);
refresh = (refresh + videomode->vtotal / 2) / videomode->vtotal;
if (videomode->flags & VID_INTERLACE)
{
refresh *= 2;
}
if (videomode->flags & VID_DBLSCAN)
{
refresh /= 2;
}
return refresh;
}
/****************************************************************************
* Name: am335x_videomode_valid
*
* Description:
* Return true if the provided video mode is valid.
*
****************************************************************************/
static bool
am335x_videomode_valid(const struct videomode_s *videomode)
{
size_t fbstride;
size_t fbsize;
uint32_t hbp;
uint32_t hfp;
uint32_t hsw;
uint32_t vbp;
uint32_t vfp;
uint32_t vsw;
uint32_t vrefresh;
if (videomode->dotclock > MAX_PIXEL_CLOCK)
{
return false;
}
if (videomode->hdisplay & 0xf)
{
return false;
}
if (videomode->vdisplay > 2048)
{
return false;
}
/* Check ranges for timing parameters */
hbp = MODE_HBP(videomode) - 1;
hfp = MODE_HFP(videomode) - 1;
hsw = MODE_HSW(videomode) - 1;
vbp = MODE_VBP(videomode);
vfp = MODE_VFP(videomode);
vsw = MODE_VSW(videomode) - 1;
if (hbp > 0x3ff)
{
return false;
}
if (hfp > 0x3ff)
{
return false;
}
if (hsw > 0x3ff)
{
return false;
}
if (vbp > 0xff)
{
return false;
}
if (vfp > 0xff)
{
return false;
}
if (vsw > 0x3f)
{
return false;
}
vrefresh = am335x_videomode_vrefresh(videomode);
if (videomode->vdisplay * videomode->hdisplay * vrefresh > MAX_BANDWIDTH)
{
return false;
}
/* Finally, make sure that the framebuffer buffer region is large enough
* to support this video mode.
*/
fbstride = (videomode->hdisplay * AM335X_BPP + 7) >> 3;
fbsize = videomode->vdisplay * fbstride;
if (fbsize > AM335X_LCDC_FB_SIZE)
{
return false;
}
return true;
}
/****************************************************************************
* Name: am335x_lcd_pickmode
*
* Description:
* If there is access to Extended Display Identification Data (EDIDI),
* then the board-specific logic may read the EDID data and use this
* function to select an appropriate video mode.
*
* edid_parse() should be used to convert the raw EDID data into the
* digested form of struct edid_info.
*
* The returned video mode may be used to both (1) configure HDMI and (2)
* initialize the AM335x LCD controller.
*
****************************************************************************/
static const struct videomode_s *
am335x_lcd_pickmode(struct edid_info_s *ei)
{
const struct videomode_s *videomode;
int n;
/* Get standard VGA as default */
videomode = NULL;
/* Pick a video mode -- First check if we can support the preferred mode. */
if (ei->edid_preferred_mode != NULL)
{
if (am335x_videomode_valid(ei->edid_preferred_mode))
{
videomode = ei->edid_preferred_mode;
return videomode;
}
}
/* Sort video modes by refresh rate, aspect ratio (*), then resolution.
* Preferred mode or largest mode is first in the list and other modes
* are sorted on closest match to that mode.
*/
sort_videomodes(ei->edid_modes, &ei->edid_preferred_mode, ei->edid_nmodes);
/* Pick the first valid mode in the list */
for (n = 0; n < ei->edid_nmodes; n++)
{
if (am335x_videomode_valid(&ei->edid_modes[n]))
{
videomode = &ei->edid_modes[n];
break;
}
}
return videomode;
}
/****************************************************************************
* Public Functions
****************************************************************************/
/****************************************************************************
* Name: am335x_lcd_videomode
*
* Description:
* If the video mod is known, then the board-specific logic may read the
* use this function to convert the video mode data to an instance of
* struct am335x_panel_info_s which then may be used to initialize the
* the LCD/
*
* Input Parameters:
* videomode - A reference to the desired video mode.
* panel - A user provided location to receive the panel data.
*
* Returned value:
* None. Always succeeds.
*
****************************************************************************/
void am335x_lcd_videomode(const struct videomode_s *videomode,
struct am335x_panel_info_s *panel)
{
lcdinfo("Detected videomode: %dx%d @ %" PRId32 "KHz\n",
videomode->hdisplay, videomode->vdisplay,
am335x_videomode_vrefresh(videomode));
panel->width = videomode->hdisplay;
panel->height = videomode->vdisplay;
panel->hfp = videomode->hsync_start - videomode->hdisplay;
panel->hbp = videomode->htotal - videomode->hsync_end;
panel->hsw = videomode->hsync_end - videomode->hsync_start;
panel->vfp = videomode->vsync_start - videomode->vdisplay;
panel->vbp = videomode->vtotal - videomode->vsync_end;
panel->vsw = videomode->vsync_end - videomode->vsync_start;
panel->pixelclk_active = true;
/* Logic for HSYNC should be reversed */
panel->hsync_active = ((videomode->flags & VID_NHSYNC) != 0);
panel->vsync_active = ((videomode->flags & VID_NVSYNC) == 0);
panel->pixclk = videomode->dotclock * 1000;
/* Set other values to the default */
#ifdef CONFIG_AM335X_LCDC_SYNC_EDGE
panel->sync_edge = true;
#else
panel->sync_edge = false;
#endif
#ifdef CONFIG_AM335X_LCDC_SYNC_CTRL
panel->sync_ctrl = true;
#else
panel->sync_ctrl = false;
#endif
#ifdef CONFIG_AM335X_LCDC_PIXCLK_INVERT
panel->pixelclk_active = true;
#else
panel->pixelclk_active = false;
#endif
panel->acbias = CONFIG_AM335X_LCDC_ACBIAS;
panel->acbias_pint = CONFIG_AM335X_LCDC_ACBIAS_PINT;
panel->dma_burstsz = AM335X_LCD_DMA_BURSTSZ;
panel->bpp = AM335X_BPP; /* REVISIT */
panel->fdd = CONFIG_AM335X_LCDC_FDD;
}
/****************************************************************************
* Name: am335x_lcd_edid
*
* Description:
* If there is access to Extended Display Identification Data (EDID),
* then the board-specific logic may read the EDID data and use this
* function to initialize an instance of struct am335x_panel_info_s.
*
* The returned video mode may optionally be returned to configure HDMI.
*
* Input Parameters:
* edid - A reference to the raw EDID data.
* len - The length of the EDID data in bytes
* panel - A user provided location to receive the panel data.
* selected - A user provided location to receive the selected video mode.
*
* Returned value:
* None. Always succeeds. The logic will fallback to VGA mode if no
* EDID data is provided or if there is no valid video mode in the EDID
* data.
*
****************************************************************************/
void am335x_lcd_edid(const uint8_t *edid, size_t edid_len,
struct am335x_panel_info_s *panel,
const struct videomode_s **selected)
{
const struct videomode_s *videomode = NULL;
struct edid_info_s ei;
/* Do we have EDID data? */
if (edid != NULL && edid_len > 0)
{
/* Parse the EDID data */
if (edid_parse(edid, &ei) == 0)
{
videomode = am335x_lcd_pickmode(&ei);
}
else
{
lcderr("ERROR: Failed to parse EDID\n");
}
}
/* Use standard VGA as fallback */
if (videomode == NULL)
{
videomode = videomode_lookup_by_name("640x480x60");
DEBUGASSERT(videomode != NULL);
}
/* Initialize the LCD using the selected video mode */
am335x_lcd_videomode(videomode, panel);
/* Return the selected video mode */
if (selected != NULL)
{
*selected = videomode;
}
}