| /*************************************************************************/ /*! |
| @File |
| @Copyright Copyright (c) Imagination Technologies Ltd. All Rights Reserved |
| @License MIT |
| |
| The contents of this file are subject to the MIT license as set out below. |
| |
| Permission is hereby granted, free of charge, to any person obtaining a copy |
| of this software and associated documentation files (the "Software"), to deal |
| in the Software without restriction, including without limitation the rights |
| to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
| copies of the Software, and to permit persons to whom the Software is |
| furnished to do so, subject to the following conditions: |
| |
| The above copyright notice and this permission notice shall be included in |
| all copies or substantial portions of the Software. |
| |
| This License is also included in this distribution in the file called |
| "MIT-COPYING". |
| |
| EXCEPT AS OTHERWISE STATED IN A NEGOTIATED AGREEMENT: (A) THE SOFTWARE IS |
| PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING |
| BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR |
| PURPOSE AND NONINFRINGEMENT; AND (B) IN NO EVENT SHALL THE AUTHORS OR |
| COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER |
| IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN |
| CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
| */ /**************************************************************************/ |
| |
| #include <linux/console.h> |
| #include <linux/module.h> |
| #include <linux/fb.h> |
| |
| #include "kerneldisplay.h" |
| #include "powervr/imgpixfmts.h" |
| #include "pvrmodule.h" /* for MODULE_LICENSE() */ |
| |
| #if !defined(CONFIG_FB) |
| #error dc_fbdev needs Linux framebuffer support. Enable it in your kernel. |
| #endif |
| |
| #define DRVNAME "dc_fbdev" |
| #define DC_PHYS_HEAP_ID 0 |
| #define MAX_COMMANDS_IN_FLIGHT 2 |
| |
| #if defined(DC_FBDEV_NUM_PREFERRED_BUFFERS) |
| #define NUM_PREFERRED_BUFFERS DC_FBDEV_NUM_PREFERRED_BUFFERS |
| #else |
| #define NUM_PREFERRED_BUFFERS 2 |
| #endif |
| |
| #define FALLBACK_REFRESH_RATE 60 |
| #define FALLBACK_DPI 160 |
| |
| typedef struct |
| { |
| IMG_HANDLE hSrvHandle; |
| IMG_UINT32 ePixFormat; |
| struct fb_info *psLINFBInfo; |
| bool bCanFlip; |
| } |
| DC_FBDEV_DEVICE; |
| |
| typedef struct |
| { |
| DC_FBDEV_DEVICE *psDeviceData; |
| IMG_HANDLE hLastConfigData; |
| IMG_UINT32 ui32AllocUseMask; |
| } |
| DC_FBDEV_CONTEXT; |
| |
| typedef struct |
| { |
| DC_FBDEV_CONTEXT *psDeviceContext; |
| IMG_UINT32 ui32Width; |
| IMG_UINT32 ui32Height; |
| IMG_UINT32 ui32ByteStride; |
| IMG_UINT32 ui32BufferID; |
| } |
| DC_FBDEV_BUFFER; |
| |
| static DC_FBDEV_DEVICE *gpsDeviceData; |
| |
| |
| static |
| void DC_FBDEV_GetInfo(IMG_HANDLE hDeviceData, |
| DC_DISPLAY_INFO *psDisplayInfo) |
| { |
| PVR_UNREFERENCED_PARAMETER(hDeviceData); |
| |
| strncpy(psDisplayInfo->szDisplayName, DRVNAME " 1", DC_NAME_SIZE); |
| |
| psDisplayInfo->ui32MinDisplayPeriod = 0; |
| psDisplayInfo->ui32MaxDisplayPeriod = 1; |
| psDisplayInfo->ui32MaxPipes = 1; |
| psDisplayInfo->bUnlatchedSupported = IMG_FALSE; |
| } |
| |
| static |
| PVRSRV_ERROR DC_FBDEV_PanelQueryCount(IMG_HANDLE hDeviceData, |
| IMG_UINT32 *pui32NumPanels) |
| { |
| PVR_UNREFERENCED_PARAMETER(hDeviceData); |
| *pui32NumPanels = 1; |
| return PVRSRV_OK; |
| } |
| |
| static |
| PVRSRV_ERROR DC_FBDEV_PanelQuery(IMG_HANDLE hDeviceData, |
| IMG_UINT32 ui32PanelsArraySize, |
| IMG_UINT32 *pui32NumPanels, |
| PVRSRV_PANEL_INFO *psPanelInfo) |
| { |
| DC_FBDEV_DEVICE *psDeviceData = hDeviceData; |
| struct fb_var_screeninfo *psVar = &psDeviceData->psLINFBInfo->var; |
| struct fb_var_screeninfo sVar = { .pixclock = 0 }; |
| |
| if(!lock_fb_info(psDeviceData->psLINFBInfo)) |
| return PVRSRV_ERROR_RETRY; |
| |
| *pui32NumPanels = 1; |
| |
| psPanelInfo[0].sSurfaceInfo.sFormat.ePixFormat = psDeviceData->ePixFormat; |
| psPanelInfo[0].sSurfaceInfo.sDims.ui32Width = psVar->xres; |
| psPanelInfo[0].sSurfaceInfo.sDims.ui32Height = psVar->yres; |
| psPanelInfo[0].sSurfaceInfo.sFormat.eMemLayout = PVRSRV_SURFACE_MEMLAYOUT_STRIDED; |
| psPanelInfo[0].sSurfaceInfo.sFormat.u.sFBCLayout.eFBCompressionMode = IMG_FB_COMPRESSION_NONE; |
| |
| /* Conformant fbdev drivers should have `var' and mode in sync by now, |
| * but some don't (like drmfb), so try a couple of different ways to |
| * get the info before falling back to the default. |
| */ |
| if(psVar->xres > 0 && psVar->yres > 0 && psVar->pixclock > 0) |
| sVar = *psVar; |
| else if(psDeviceData->psLINFBInfo->mode) |
| fb_videomode_to_var(&sVar, psDeviceData->psLINFBInfo->mode); |
| |
| /* Override the refresh rate when defined. */ |
| #ifdef DC_FBDEV_REFRESH |
| psPanelInfo[0].ui32RefreshRate = DC_FBDEV_REFRESH; |
| #else |
| if(sVar.xres > 0 && sVar.yres > 0 && sVar.pixclock > 0) |
| { |
| psPanelInfo[0].ui32RefreshRate = 1000000000LU / |
| ((sVar.upper_margin + sVar.lower_margin + |
| sVar.yres + sVar.vsync_len) * |
| (sVar.left_margin + sVar.right_margin + |
| sVar.xres + sVar.hsync_len) * |
| (sVar.pixclock / 1000)); |
| } |
| else |
| psPanelInfo[0].ui32RefreshRate = FALLBACK_REFRESH_RATE; |
| #endif |
| |
| psPanelInfo[0].ui32XDpi = |
| ((int)sVar.width > 0) ? (254000 / sVar.width * psVar->xres / 10000) : FALLBACK_DPI; |
| |
| psPanelInfo[0].ui32YDpi = |
| ((int)sVar.height > 0) ? 254000 / sVar.height * psVar->yres / 10000 : FALLBACK_DPI; |
| |
| unlock_fb_info(psDeviceData->psLINFBInfo); |
| return PVRSRV_OK; |
| } |
| |
| static |
| PVRSRV_ERROR DC_FBDEV_FormatQuery(IMG_HANDLE hDeviceData, |
| IMG_UINT32 ui32NumFormats, |
| PVRSRV_SURFACE_FORMAT *pasFormat, |
| IMG_UINT32 *pui32Supported) |
| { |
| DC_FBDEV_DEVICE *psDeviceData = hDeviceData; |
| int i; |
| |
| for(i = 0; i < ui32NumFormats; i++) |
| { |
| pui32Supported[i] = 0; |
| |
| if(pasFormat[i].ePixFormat == psDeviceData->ePixFormat) |
| pui32Supported[i]++; |
| } |
| |
| return PVRSRV_OK; |
| } |
| |
| static |
| PVRSRV_ERROR DC_FBDEV_DimQuery(IMG_HANDLE hDeviceData, |
| IMG_UINT32 ui32NumDims, |
| PVRSRV_SURFACE_DIMS *psDim, |
| IMG_UINT32 *pui32Supported) |
| { |
| DC_FBDEV_DEVICE *psDeviceData = hDeviceData; |
| struct fb_var_screeninfo *psVar = &psDeviceData->psLINFBInfo->var; |
| int i; |
| |
| if(!lock_fb_info(psDeviceData->psLINFBInfo)) |
| return PVRSRV_ERROR_RETRY; |
| |
| for(i = 0; i < ui32NumDims; i++) |
| { |
| pui32Supported[i] = 0; |
| |
| if(psDim[i].ui32Width == psVar->xres && |
| psDim[i].ui32Height == psVar->yres) |
| pui32Supported[i]++; |
| } |
| |
| unlock_fb_info(psDeviceData->psLINFBInfo); |
| return PVRSRV_OK; |
| } |
| |
| static |
| PVRSRV_ERROR DC_FBDEV_ContextCreate(IMG_HANDLE hDeviceData, |
| IMG_HANDLE *hDisplayContext) |
| { |
| DC_FBDEV_CONTEXT *psDeviceContext; |
| PVRSRV_ERROR eError = PVRSRV_OK; |
| |
| psDeviceContext = kzalloc(sizeof(DC_FBDEV_CONTEXT), GFP_KERNEL); |
| if(!psDeviceContext) |
| { |
| eError = PVRSRV_ERROR_OUT_OF_MEMORY; |
| goto err_out; |
| } |
| |
| psDeviceContext->psDeviceData = hDeviceData; |
| *hDisplayContext = psDeviceContext; |
| |
| err_out: |
| return eError; |
| } |
| |
| static PVRSRV_ERROR |
| DC_FBDEV_ContextConfigureCheck(IMG_HANDLE hDisplayContext, |
| IMG_UINT32 ui32PipeCount, |
| PVRSRV_SURFACE_CONFIG_INFO *pasSurfAttrib, |
| IMG_HANDLE *ahBuffers) |
| { |
| DC_FBDEV_CONTEXT *psDeviceContext = hDisplayContext; |
| DC_FBDEV_DEVICE *psDeviceData = psDeviceContext->psDeviceData; |
| struct fb_var_screeninfo *psVar = &psDeviceData->psLINFBInfo->var; |
| DC_FBDEV_BUFFER *psBuffer; |
| PVRSRV_ERROR eError; |
| |
| if(ui32PipeCount != 1) |
| { |
| eError = PVRSRV_ERROR_DC_TOO_MANY_PIPES; |
| goto err_out; |
| } |
| |
| if(!ahBuffers) |
| { |
| eError = PVRSRV_ERROR_DC_INVALID_CONFIG; |
| goto err_out; |
| } |
| |
| if(!lock_fb_info(psDeviceData->psLINFBInfo)) |
| { |
| eError = PVRSRV_ERROR_RETRY; |
| goto err_out; |
| } |
| |
| psBuffer = ahBuffers[0]; |
| |
| if(pasSurfAttrib[0].sCrop.sDims.ui32Width != psVar->xres || |
| pasSurfAttrib[0].sCrop.sDims.ui32Height != psVar->yres || |
| pasSurfAttrib[0].sCrop.i32XOffset != 0 || |
| pasSurfAttrib[0].sCrop.i32YOffset != 0) |
| { |
| eError = PVRSRV_ERROR_DC_INVALID_CROP_RECT; |
| goto err_unlock; |
| } |
| |
| if(pasSurfAttrib[0].sDisplay.sDims.ui32Width != |
| pasSurfAttrib[0].sCrop.sDims.ui32Width || |
| pasSurfAttrib[0].sDisplay.sDims.ui32Height != |
| pasSurfAttrib[0].sCrop.sDims.ui32Height || |
| pasSurfAttrib[0].sDisplay.i32XOffset != |
| pasSurfAttrib[0].sCrop.i32XOffset || |
| pasSurfAttrib[0].sDisplay.i32YOffset != |
| pasSurfAttrib[0].sCrop.i32YOffset) |
| { |
| eError = PVRSRV_ERROR_DC_INVALID_DISPLAY_RECT; |
| goto err_unlock; |
| } |
| |
| if(psBuffer->ui32Width != psVar->xres && |
| psBuffer->ui32Height != psVar->yres) |
| { |
| eError = PVRSRV_ERROR_DC_INVALID_BUFFER_DIMS; |
| goto err_unlock; |
| } |
| |
| eError = PVRSRV_OK; |
| err_unlock: |
| unlock_fb_info(psDeviceData->psLINFBInfo); |
| err_out: |
| return eError; |
| } |
| |
| static |
| void DC_FBDEV_ContextConfigure(IMG_HANDLE hDisplayContext, |
| IMG_UINT32 ui32PipeCount, |
| PVRSRV_SURFACE_CONFIG_INFO *pasSurfAttrib, |
| IMG_HANDLE *ahBuffers, |
| IMG_UINT32 ui32DisplayPeriod, |
| IMG_HANDLE hConfigData) |
| { |
| DC_FBDEV_CONTEXT *psDeviceContext = hDisplayContext; |
| DC_FBDEV_DEVICE *psDeviceData = psDeviceContext->psDeviceData; |
| struct fb_var_screeninfo sVar = psDeviceData->psLINFBInfo->var; |
| int err; |
| |
| PVR_UNREFERENCED_PARAMETER(ui32PipeCount); |
| PVR_UNREFERENCED_PARAMETER(pasSurfAttrib); |
| PVR_UNREFERENCED_PARAMETER(ui32DisplayPeriod); |
| |
| if(psDeviceContext->hLastConfigData) |
| DCDisplayConfigurationRetired(psDeviceContext->hLastConfigData); |
| |
| sVar.yoffset = 0; |
| |
| if(ui32PipeCount == 0) |
| { |
| /* If the pipe count is zero, we're tearing down. Don't record |
| * any new configurations, but still allow the display to pan |
| * back to buffer 0. |
| */ |
| psDeviceContext->hLastConfigData = NULL; |
| |
| /* |
| We still need to "retire" this NULL flip as that signals back to |
| the DC core that we've finished doing what we need to do |
| and it can destroy the display context |
| */ |
| DCDisplayConfigurationRetired(hConfigData); |
| } |
| else |
| { |
| BUG_ON(ahBuffers == NULL); |
| |
| if(psDeviceData->bCanFlip) |
| { |
| DC_FBDEV_BUFFER *psBuffer = ahBuffers[0]; |
| sVar.yoffset = sVar.yres * psBuffer->ui32BufferID; |
| } |
| |
| psDeviceContext->hLastConfigData = hConfigData; |
| } |
| |
| if(lock_fb_info(psDeviceData->psLINFBInfo)) |
| { |
| console_lock(); |
| |
| /* If we're supposed to be able to flip, but the yres_virtual |
| * has been changed to an unsupported (smaller) value, we need |
| * to change it back (this is a workaround for some Linux fbdev |
| * drivers that seem to lose any modifications to yres_virtual |
| * after a blank.) |
| */ |
| if(psDeviceData->bCanFlip && |
| sVar.yres_virtual < sVar.yres * NUM_PREFERRED_BUFFERS) |
| { |
| sVar.activate = FB_ACTIVATE_NOW; |
| sVar.yres_virtual = sVar.yres * NUM_PREFERRED_BUFFERS; |
| |
| err = fb_set_var(psDeviceData->psLINFBInfo, &sVar); |
| if(err) |
| pr_err("fb_set_var failed (err=%d)\n", err); |
| } |
| else |
| { |
| err = fb_pan_display(psDeviceData->psLINFBInfo, &sVar); |
| if(err) |
| pr_err("fb_pan_display failed (err=%d)\n", err); |
| } |
| |
| console_unlock(); |
| unlock_fb_info(psDeviceData->psLINFBInfo); |
| } |
| } |
| |
| static |
| void DC_FBDEV_ContextDestroy(IMG_HANDLE hDisplayContext) |
| { |
| DC_FBDEV_CONTEXT *psDeviceContext = hDisplayContext; |
| |
| BUG_ON(psDeviceContext->hLastConfigData != NULL); |
| kfree(psDeviceContext); |
| } |
| |
| static |
| IMG_BOOL DC_FBDEV_GetBufferID(DC_FBDEV_CONTEXT *psDeviceContext, IMG_UINT32 *pui32BufferID) |
| { |
| IMG_UINT32 ui32BufferID; |
| |
| /* If we don't support flipping, allow this code to give every |
| * allocated buffer the same ID. This means that the display |
| * won't be panned, and the same page list will be used for |
| * every allocation. |
| */ |
| if (!psDeviceContext->psDeviceData->bCanFlip) |
| { |
| *pui32BufferID = 0; |
| return IMG_TRUE; |
| } |
| |
| for (ui32BufferID = 0; ui32BufferID < NUM_PREFERRED_BUFFERS; ++ui32BufferID) |
| { |
| if ((psDeviceContext->ui32AllocUseMask & (1UL << ui32BufferID)) == 0) |
| { |
| psDeviceContext->ui32AllocUseMask |= (1UL << ui32BufferID); |
| |
| *pui32BufferID = ui32BufferID; |
| |
| return IMG_TRUE; |
| } |
| } |
| return IMG_FALSE; |
| } |
| |
| static |
| void DC_FBDEV_PutBufferID(DC_FBDEV_CONTEXT *psDeviceContext, IMG_UINT32 ui32BufferID) |
| { |
| psDeviceContext->ui32AllocUseMask &= ~(1UL << ui32BufferID); |
| } |
| |
| #define BYTE_TO_PAGES(range) (((range) + (PAGE_SIZE - 1)) >> PAGE_SHIFT) |
| |
| static |
| PVRSRV_ERROR DC_FBDEV_BufferAlloc(IMG_HANDLE hDisplayContext, |
| DC_BUFFER_CREATE_INFO *psCreateInfo, |
| IMG_DEVMEM_LOG2ALIGN_T *puiLog2PageSize, |
| IMG_UINT32 *pui32PageCount, |
| IMG_UINT32 *pui32PhysHeapID, |
| IMG_UINT32 *pui32ByteStride, |
| IMG_HANDLE *phBuffer) |
| { |
| DC_FBDEV_CONTEXT *psDeviceContext = hDisplayContext; |
| DC_FBDEV_DEVICE *psDeviceData = psDeviceContext->psDeviceData; |
| PVRSRV_SURFACE_INFO *psSurfInfo = &psCreateInfo->sSurface; |
| PVRSRV_ERROR eError; |
| DC_FBDEV_BUFFER *psBuffer; |
| IMG_UINT32 ui32ByteSize; |
| |
| if (psSurfInfo->sFormat.ePixFormat != psDeviceData->ePixFormat) |
| { |
| eError = PVRSRV_ERROR_UNSUPPORTED_PIXEL_FORMAT; |
| goto err_out; |
| } |
| |
| psBuffer = kmalloc(sizeof(DC_FBDEV_BUFFER), GFP_KERNEL); |
| if (!psBuffer) |
| { |
| eError = PVRSRV_ERROR_OUT_OF_MEMORY; |
| goto err_out; |
| } |
| |
| psBuffer->psDeviceContext = psDeviceContext; |
| psBuffer->ui32ByteStride = |
| psSurfInfo->sDims.ui32Width * psCreateInfo->ui32BPP; |
| |
| psBuffer->ui32Width = psSurfInfo->sDims.ui32Width; |
| psBuffer->ui32Height = psSurfInfo->sDims.ui32Height; |
| |
| if (!DC_FBDEV_GetBufferID(psDeviceContext, &psBuffer->ui32BufferID)) |
| { |
| eError = PVRSRV_ERROR_OUT_OF_MEMORY; |
| goto err_free; |
| } |
| |
| ui32ByteSize = psBuffer->ui32ByteStride * psBuffer->ui32Height; |
| |
| *puiLog2PageSize = PAGE_SHIFT; |
| *pui32PageCount = BYTE_TO_PAGES(ui32ByteSize); |
| *pui32PhysHeapID = DC_PHYS_HEAP_ID; |
| *pui32ByteStride = psBuffer->ui32ByteStride; |
| *phBuffer = psBuffer; |
| |
| return PVRSRV_OK; |
| |
| err_free: |
| kfree(psBuffer); |
| |
| err_out: |
| return eError; |
| } |
| |
| static |
| PVRSRV_ERROR DC_FBDEV_BufferAcquire(IMG_HANDLE hBuffer, |
| IMG_DEV_PHYADDR *pasDevPAddr, |
| void **ppvLinAddr) |
| { |
| DC_FBDEV_BUFFER *psBuffer = hBuffer; |
| DC_FBDEV_DEVICE *psDeviceData = psBuffer->psDeviceContext->psDeviceData; |
| IMG_UINT32 ui32ByteSize = psBuffer->ui32ByteStride * psBuffer->ui32Height; |
| uintptr_t uiStartAddr; |
| IMG_UINT32 i, ui32MaxLen; |
| |
| #if defined(DC_FBDEV_USE_SCREEN_BASE) |
| uiStartAddr = (uintptr_t)psDeviceData->psLINFBInfo->screen_base; |
| #else |
| uiStartAddr = psDeviceData->psLINFBInfo->fix.smem_start; |
| #endif |
| |
| uiStartAddr += psBuffer->ui32BufferID * ui32ByteSize; |
| ui32MaxLen = psDeviceData->psLINFBInfo->fix.smem_len - |
| psBuffer->ui32BufferID * ui32ByteSize; |
| |
| for (i = 0; i < BYTE_TO_PAGES(ui32ByteSize); i++) |
| { |
| BUG_ON(i * PAGE_SIZE >= ui32MaxLen); |
| pasDevPAddr[i].uiAddr = uiStartAddr + (i * PAGE_SIZE); |
| #if defined(DC_FBDEV_USE_SCREEN_BASE) |
| pasDevPAddr[i].uiAddr = |
| page_to_phys(vmalloc_to_page((void *)(uintptr_t)pasDevPAddr[i].uiAddr)); |
| #endif |
| } |
| |
| /* We're UMA, so services will do the right thing and make |
| * its own CPU virtual address mapping for the buffer. |
| */ |
| *ppvLinAddr = NULL; |
| |
| return PVRSRV_OK; |
| } |
| |
| static void DC_FBDEV_BufferRelease(IMG_HANDLE hBuffer) |
| { |
| PVR_UNREFERENCED_PARAMETER(hBuffer); |
| } |
| |
| static void DC_FBDEV_BufferFree(IMG_HANDLE hBuffer) |
| { |
| DC_FBDEV_BUFFER *psBuffer = hBuffer; |
| |
| DC_FBDEV_PutBufferID(psBuffer->psDeviceContext, psBuffer->ui32BufferID); |
| |
| kfree(psBuffer); |
| } |
| |
| /* If we can flip, we need to make sure we have the memory to do so. |
| * |
| * We'll assume that the fbdev device provides extra space in |
| * yres_virtual for panning; xres_virtual is theoretically supported, |
| * but it involves more work. |
| * |
| * If the fbdev device doesn't have yres_virtual > yres, we'll try |
| * requesting it before bailing. Userspace applications commonly do |
| * this with an FBIOPUT_VSCREENINFO ioctl(). |
| * |
| * Another problem is with a limitation in the services DC -- it |
| * needs framebuffers to be page aligned (this is a SW limitation, |
| * the HW can support non-page-aligned buffers). So we have to |
| * check that stride * height for a single buffer is page aligned. |
| */ |
| |
| static bool DC_FBDEV_FlipPossible(struct fb_info *psLINFBInfo) |
| { |
| struct fb_var_screeninfo sVar = psLINFBInfo->var; |
| int err; |
| |
| if(!psLINFBInfo->fix.xpanstep && !psLINFBInfo->fix.ypanstep && |
| !psLINFBInfo->fix.ywrapstep) |
| { |
| pr_err("The fbdev device detected does not support ypan/ywrap. " |
| "Flipping disabled.\n"); |
| return false; |
| } |
| |
| if((psLINFBInfo->fix.line_length * sVar.yres) % PAGE_SIZE != 0) |
| { |
| pr_err("Line length (in bytes) x yres is not a multiple of " |
| "page size. Flipping disabled.\n"); |
| return false; |
| } |
| |
| /* We might already have enough space */ |
| if(sVar.yres * NUM_PREFERRED_BUFFERS <= sVar.yres_virtual) |
| return true; |
| |
| pr_err("No buffer space for flipping; asking for more.\n"); |
| |
| sVar.activate = FB_ACTIVATE_NOW; |
| sVar.yres_virtual = sVar.yres * NUM_PREFERRED_BUFFERS; |
| |
| err = fb_set_var(psLINFBInfo, &sVar); |
| if(err) |
| { |
| pr_err("fb_set_var failed (err=%d). Flipping disabled.\n", err); |
| return false; |
| } |
| |
| if(sVar.yres * NUM_PREFERRED_BUFFERS > sVar.yres_virtual) |
| { |
| pr_err("Failed to obtain additional buffer space. " |
| "Flipping disabled.\n"); |
| return false; |
| } |
| |
| /* Some fbdev drivers allow the yres_virtual modification through, |
| * but don't actually update the fix. We need the fix to be updated |
| * and more memory allocated, so we can actually take advantage of |
| * the increased yres_virtual. |
| */ |
| if(psLINFBInfo->fix.smem_len < psLINFBInfo->fix.line_length * sVar.yres_virtual) |
| { |
| pr_err("'fix' not re-allocated with sufficient buffer space. " |
| "Flipping disabled.\n"); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| static int __init DC_FBDEV_init(void) |
| { |
| static DC_DEVICE_FUNCTIONS sDCFunctions = |
| { |
| .pfnGetInfo = DC_FBDEV_GetInfo, |
| .pfnPanelQueryCount = DC_FBDEV_PanelQueryCount, |
| .pfnPanelQuery = DC_FBDEV_PanelQuery, |
| .pfnFormatQuery = DC_FBDEV_FormatQuery, |
| .pfnDimQuery = DC_FBDEV_DimQuery, |
| .pfnSetBlank = NULL, |
| .pfnSetVSyncReporting = NULL, |
| .pfnLastVSyncQuery = NULL, |
| .pfnContextCreate = DC_FBDEV_ContextCreate, |
| .pfnContextDestroy = DC_FBDEV_ContextDestroy, |
| .pfnContextConfigure = DC_FBDEV_ContextConfigure, |
| .pfnContextConfigureCheck = DC_FBDEV_ContextConfigureCheck, |
| .pfnBufferAlloc = DC_FBDEV_BufferAlloc, |
| .pfnBufferAcquire = DC_FBDEV_BufferAcquire, |
| .pfnBufferRelease = DC_FBDEV_BufferRelease, |
| .pfnBufferFree = DC_FBDEV_BufferFree, |
| }; |
| |
| struct fb_info *psLINFBInfo; |
| IMG_PIXFMT ePixFormat; |
| int err = -ENODEV; |
| |
| psLINFBInfo = registered_fb[0]; |
| if(!psLINFBInfo) |
| { |
| pr_err("No Linux framebuffer (fbdev) device is registered!\n" |
| "Check you have a framebuffer driver compiled into your " |
| "kernel\nand that it is enabled on the cmdline.\n"); |
| goto err_out; |
| } |
| |
| if(!lock_fb_info(psLINFBInfo)) |
| goto err_out; |
| |
| console_lock(); |
| |
| /* Filter out broken FB devices */ |
| if(!psLINFBInfo->fix.smem_len || !psLINFBInfo->fix.line_length) |
| { |
| pr_err("The fbdev device detected had a zero smem_len or " |
| "line_length,\nwhich suggests it is a broken driver.\n"); |
| goto err_unlock; |
| } |
| |
| if(psLINFBInfo->fix.type != FB_TYPE_PACKED_PIXELS || |
| psLINFBInfo->fix.visual != FB_VISUAL_TRUECOLOR) |
| { |
| pr_err("The fbdev device detected is not truecolor with packed " |
| "pixels.\n"); |
| goto err_unlock; |
| } |
| |
| if(psLINFBInfo->var.bits_per_pixel == 32) |
| { |
| if(psLINFBInfo->var.red.length != 8 || |
| psLINFBInfo->var.green.length != 8 || |
| psLINFBInfo->var.blue.length != 8 || |
| psLINFBInfo->var.red.offset != 16 || |
| psLINFBInfo->var.green.offset != 8 || |
| psLINFBInfo->var.blue.offset != 0) |
| { |
| pr_err("The fbdev device detected uses an unrecognized " |
| "32bit pixel format (%u/%u/%u, %u/%u/%u)\n", |
| psLINFBInfo->var.red.length, |
| psLINFBInfo->var.green.length, |
| psLINFBInfo->var.blue.length, |
| psLINFBInfo->var.red.offset, |
| psLINFBInfo->var.green.offset, |
| psLINFBInfo->var.blue.offset); |
| goto err_unlock; |
| } |
| #if defined(DC_FBDEV_FORCE_XRGB8888) |
| ePixFormat = IMG_PIXFMT_B8G8R8X8_UNORM; |
| #else |
| ePixFormat = IMG_PIXFMT_B8G8R8A8_UNORM; |
| #endif |
| } |
| else if(psLINFBInfo->var.bits_per_pixel == 16) |
| { |
| if(psLINFBInfo->var.red.length != 5 || |
| psLINFBInfo->var.green.length != 6 || |
| psLINFBInfo->var.blue.length != 5 || |
| psLINFBInfo->var.red.offset != 11 || |
| psLINFBInfo->var.green.offset != 5 || |
| psLINFBInfo->var.blue.offset != 0) |
| { |
| pr_err("The fbdev device detected uses an unrecognized " |
| "16bit pixel format (%u/%u/%u, %u/%u/%u)\n", |
| psLINFBInfo->var.red.length, |
| psLINFBInfo->var.green.length, |
| psLINFBInfo->var.blue.length, |
| psLINFBInfo->var.red.offset, |
| psLINFBInfo->var.green.offset, |
| psLINFBInfo->var.blue.offset); |
| goto err_unlock; |
| } |
| ePixFormat = IMG_PIXFMT_B5G6R5_UNORM; |
| } |
| else |
| { |
| pr_err("The fbdev device detected uses an unsupported " |
| "bpp (%u).\n", psLINFBInfo->var.bits_per_pixel); |
| goto err_unlock; |
| } |
| |
| if(!try_module_get(psLINFBInfo->fbops->owner)) |
| { |
| pr_err("try_module_get() failed"); |
| goto err_unlock; |
| } |
| |
| if(psLINFBInfo->fbops->fb_open && |
| psLINFBInfo->fbops->fb_open(psLINFBInfo, 0) != 0) |
| { |
| pr_err("fb_open() failed"); |
| goto err_module_put; |
| } |
| |
| gpsDeviceData = kmalloc(sizeof(DC_FBDEV_DEVICE), GFP_KERNEL); |
| if(!gpsDeviceData) |
| goto err_module_put; |
| |
| gpsDeviceData->psLINFBInfo = psLINFBInfo; |
| gpsDeviceData->ePixFormat = ePixFormat; |
| |
| if(DCRegisterDevice(&sDCFunctions, |
| MAX_COMMANDS_IN_FLIGHT, |
| gpsDeviceData, |
| &gpsDeviceData->hSrvHandle) != PVRSRV_OK) |
| goto err_kfree; |
| |
| gpsDeviceData->bCanFlip = DC_FBDEV_FlipPossible(psLINFBInfo); |
| |
| pr_info("Found usable fbdev device (%s):\n" |
| "range (physical) = 0x%lx-0x%lx\n" |
| "size (bytes) = 0x%x\n" |
| "xres x yres = %ux%u\n" |
| "xres x yres (v) = %ux%u\n" |
| "img pix fmt = %u\n" |
| "flipping? = %d\n", |
| psLINFBInfo->fix.id, |
| psLINFBInfo->fix.smem_start, |
| psLINFBInfo->fix.smem_start + psLINFBInfo->fix.smem_len, |
| psLINFBInfo->fix.smem_len, |
| psLINFBInfo->var.xres, psLINFBInfo->var.yres, |
| psLINFBInfo->var.xres_virtual, psLINFBInfo->var.yres_virtual, |
| ePixFormat, gpsDeviceData->bCanFlip); |
| err = 0; |
| err_unlock: |
| console_unlock(); |
| unlock_fb_info(psLINFBInfo); |
| err_out: |
| return err; |
| err_kfree: |
| kfree(gpsDeviceData); |
| err_module_put: |
| module_put(psLINFBInfo->fbops->owner); |
| goto err_unlock; |
| } |
| |
| static void __exit DC_FBDEV_exit(void) |
| { |
| DC_FBDEV_DEVICE *psDeviceData = gpsDeviceData; |
| struct fb_info *psLINFBInfo = psDeviceData->psLINFBInfo; |
| |
| lock_fb_info(psLINFBInfo); |
| console_lock(); |
| |
| if(psLINFBInfo->fbops->fb_release) |
| psLINFBInfo->fbops->fb_release(psLINFBInfo, 0); |
| |
| module_put(psLINFBInfo->fbops->owner); |
| |
| console_unlock(); |
| unlock_fb_info(psLINFBInfo); |
| |
| DCUnregisterDevice(psDeviceData->hSrvHandle); |
| kfree(psDeviceData); |
| } |
| |
| module_init(DC_FBDEV_init); |
| module_exit(DC_FBDEV_exit); |