| /**************************************************************************** |
| * |
| * The MIT License (MIT) |
| * |
| * Copyright (c) 2014 - 2018 Vivante Corporation |
| * |
| * 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. |
| * |
| * 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. 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. |
| * |
| ***************************************************************************** |
| * |
| * The GPL License (GPL) |
| * |
| * Copyright (C) 2014 - 2018 Vivante Corporation |
| * |
| * This program is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU General Public License |
| * as published by the Free Software Foundation; either version 2 |
| * of the License, or (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program; if not, write to the Free Software Foundation, |
| * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
| * |
| ***************************************************************************** |
| * |
| * Note: This software is released under dual MIT and GPL licenses. A |
| * recipient may use this file under the terms of either the MIT license or |
| * GPL License. If you wish to use only one license not the other, you can |
| * indicate your decision by deleting one of the above license notices in your |
| * version of this file. |
| * |
| *****************************************************************************/ |
| |
| |
| #include <linux/device.h> |
| #include <linux/slab.h> |
| #include <linux/miscdevice.h> |
| #include <linux/uaccess.h> |
| |
| #include "gc_hal_kernel_linux.h" |
| #include "gc_hal_driver.h" |
| |
| #include <linux/platform_device.h> |
| |
| /* Zone used for header/footer. */ |
| #define _GC_OBJ_ZONE gcvZONE_DRIVER |
| |
| MODULE_DESCRIPTION("Vivante Graphics Driver"); |
| MODULE_LICENSE("Dual MIT/GPL"); |
| |
| /* Disable MSI for internal FPGA build except PPC */ |
| #if gcdFPGA_BUILD && !defined(CONFIG_PPC) |
| #define USE_MSI 0 |
| #else |
| #define USE_MSI 1 |
| #endif |
| |
| static struct class* gpuClass; |
| |
| static gcsPLATFORM *platform; |
| |
| static gckGALDEVICE galDevice; |
| |
| static uint major = 199; |
| module_param(major, uint, 0644); |
| MODULE_PARM_DESC(major, "major device number for GC device"); |
| |
| static int irqLine = -1; |
| module_param(irqLine, int, 0644); |
| MODULE_PARM_DESC(irqLine, "IRQ number of GC core"); |
| |
| static ulong registerMemBase = 0x80000000; |
| module_param(registerMemBase, ulong, 0644); |
| MODULE_PARM_DESC(registerMemBase, "Base of bus address of GC core AHB register"); |
| |
| static ulong registerMemSize = 2 << 10; |
| module_param(registerMemSize, ulong, 0644); |
| MODULE_PARM_DESC(registerMemSize, "Size of bus address range of GC core AHB register"); |
| |
| static int irqLine2D = -1; |
| module_param(irqLine2D, int, 0644); |
| MODULE_PARM_DESC(irqLine2D, "IRQ number of G2D core if irqLine is used for a G3D core"); |
| |
| static ulong registerMemBase2D = 0x00000000; |
| module_param(registerMemBase2D, ulong, 0644); |
| MODULE_PARM_DESC(registerMemBase2D, "Base of bus address of G2D core if registerMemBase2D is used for a G3D core"); |
| |
| static ulong registerMemSize2D = 2 << 10; |
| module_param(registerMemSize2D, ulong, 0644); |
| MODULE_PARM_DESC(registerMemSize2D, "Size of bus address range of G2D core if registerMemSize is used for a G3D core"); |
| |
| static int irqLineVG = -1; |
| module_param(irqLineVG, int, 0644); |
| MODULE_PARM_DESC(irqLineVG, "IRQ number of VG core"); |
| |
| static ulong registerMemBaseVG = 0x00000000; |
| module_param(registerMemBaseVG, ulong, 0644); |
| MODULE_PARM_DESC(registerMemBaseVG, "Base of bus address of VG core"); |
| |
| static ulong registerMemSizeVG = 2 << 10; |
| module_param(registerMemSizeVG, ulong, 0644); |
| MODULE_PARM_DESC(registerMemSizeVG, "Size of bus address range of VG core"); |
| |
| #if gcdDEC_ENABLE_AHB |
| static ulong registerMemBaseDEC300 = 0x00000000; |
| module_param(registerMemBaseDEC300, ulong, 0644); |
| |
| static ulong registerMemSizeDEC300 = 2 << 10; |
| module_param(registerMemSizeDEC300, ulong, 0644); |
| #endif |
| |
| #ifndef gcdDEFAULT_CONTIGUOUS_SIZE |
| #define gcdDEFAULT_CONTIGUOUS_SIZE (4 << 20) |
| #endif |
| static ulong contiguousSize = gcdDEFAULT_CONTIGUOUS_SIZE; |
| module_param(contiguousSize, ulong, 0644); |
| MODULE_PARM_DESC(contiguousSize, "Size of memory reserved for GC"); |
| |
| static ulong contiguousBase = 0; |
| module_param(contiguousBase, ulong, 0644); |
| MODULE_PARM_DESC(contiguousBase, "Base address of memory reserved for GC, if it is 0, GC driver will try to allocate a buffer whose size defined by contiguousSize"); |
| |
| static ulong externalSize = 0; |
| module_param(externalSize, ulong, 0644); |
| MODULE_PARM_DESC(externalSize, "Size of external memory, if it is 0, means there is no external pool"); |
| |
| static ulong externalBase = 0; |
| module_param(externalBase, ulong, 0644); |
| MODULE_PARM_DESC(externalBase, "Base address of external memory"); |
| |
| static int fastClear = -1; |
| module_param(fastClear, int, 0644); |
| MODULE_PARM_DESC(fastClear, "Disable fast clear if set it to 0, enabled by default"); |
| |
| static int compression = -1; |
| module_param(compression, int, 0644); |
| MODULE_PARM_DESC(compression, "Disable compression if set it to 0, enabled by default"); |
| |
| static int powerManagement = 1; |
| module_param(powerManagement, int, 0644); |
| MODULE_PARM_DESC(powerManagement, "Disable auto power saving if set it to 1, enabled by default"); |
| |
| static int gpuProfiler = 0; |
| module_param(gpuProfiler, int, 0644); |
| MODULE_PARM_DESC(gpuProfiler, "Enable profiling support, disabled by default"); |
| |
| static ulong baseAddress = 0; |
| module_param(baseAddress, ulong, 0644); |
| MODULE_PARM_DESC(baseAddress, "Only used for old MMU, set it to 0 if memory which can be accessed by GPU falls into 0 - 2G, otherwise set it to 0x80000000"); |
| |
| static ulong physSize = 0; |
| module_param(physSize, ulong, 0644); |
| MODULE_PARM_DESC(physSize, "Obsolete"); |
| |
| static uint logFileSize = 0; |
| module_param(logFileSize,uint, 0644); |
| MODULE_PARM_DESC(logFileSize, "Size of buffer to store GC driver output messsage, if it is not 0, message is read from /sys/kernel/debug/gc/galcore_trace, default value is 0"); |
| |
| static uint recovery = 0; |
| module_param(recovery, uint, 0644); |
| MODULE_PARM_DESC(recovery, "Recover GPU from stuck (1: Enable, 0: Disable)"); |
| |
| /* Middle needs about 40KB buffer, Maximal may need more than 200KB buffer. */ |
| static uint stuckDump = 0; |
| module_param(stuckDump, uint, 0644); |
| MODULE_PARM_DESC(stuckDump, "Level of stuck dump content (1: Minimal, 2: Middle, 3: Maximal)"); |
| |
| static int showArgs = 0; |
| module_param(showArgs, int, 0644); |
| MODULE_PARM_DESC(showArgs, "Display parameters value when driver loaded"); |
| |
| static int mmu = 1; |
| module_param(mmu, int, 0644); |
| MODULE_PARM_DESC(mmu, "Disable MMU if set it to 0, enabled by default"); |
| |
| static int irqs[gcvCORE_COUNT] = {[0 ... gcvCORE_COUNT - 1] = -1}; |
| module_param_array(irqs, int, NULL, 0644); |
| MODULE_PARM_DESC(irqs, "Array of IRQ numbers of multi-GPU"); |
| |
| static uint registerBases[gcvCORE_COUNT]; |
| module_param_array(registerBases, uint, NULL, 0644); |
| MODULE_PARM_DESC(registerBases, "Array of bases of bus address of register of multi-GPU"); |
| |
| static uint registerSizes[gcvCORE_COUNT] = {[0 ... gcvCORE_COUNT - 1] = 2 << 10}; |
| module_param_array(registerSizes, uint, NULL, 0644); |
| MODULE_PARM_DESC(registerSizes, "Array of sizes of bus address range of register of multi-GPU"); |
| |
| static uint chipIDs[gcvCORE_COUNT] = {[0 ... gcvCORE_COUNT - 1] = gcvCHIP_ID_DEFAULT}; |
| module_param_array(chipIDs, uint, NULL, 0644); |
| MODULE_PARM_DESC(chipIDs, "Array of chipIDs of multi-GPU"); |
| |
| static uint type = 0; |
| module_param(type, uint, 0664); |
| MODULE_PARM_DESC(type, "0 - Char Driver (Default), 1 - Misc Driver"); |
| |
| static int gpu3DMinClock = 1; |
| |
| static int contiguousRequested = 0; |
| |
| static gctBOOL registerMemMapped = gcvFALSE; |
| static gctPOINTER registerMemAddress = gcvNULL; |
| static ulong bankSize = 0; |
| static int signal = 48; |
| |
| void |
| _UpdateModuleParam( |
| gcsMODULE_PARAMETERS *Param |
| ) |
| { |
| irqLine = Param->irqLine ; |
| registerMemBase = Param->registerMemBase; |
| registerMemSize = Param->registerMemSize; |
| irqLine2D = Param->irqLine2D ; |
| registerMemBase2D = Param->registerMemBase2D; |
| registerMemSize2D = Param->registerMemSize2D; |
| #if gcdENABLE_VG |
| irqLineVG = Param->irqLineVG; |
| registerMemBaseVG = Param->registerMemBaseVG; |
| registerMemSizeVG = Param->registerMemSizeVG; |
| #endif |
| contiguousSize = Param->contiguousSize; |
| contiguousBase = Param->contiguousBase; |
| externalSize = Param->externalSize; |
| externalBase = Param->externalBase; |
| bankSize = Param->bankSize; |
| fastClear = Param->fastClear; |
| compression = (gctINT)Param->compression; |
| powerManagement = Param->powerManagement; |
| gpuProfiler = Param->gpuProfiler; |
| signal = Param->signal; |
| baseAddress = Param->baseAddress; |
| physSize = Param->physSize; |
| logFileSize = Param->logFileSize; |
| recovery = Param->recovery; |
| stuckDump = Param->stuckDump; |
| showArgs = Param->showArgs; |
| contiguousRequested = Param->contiguousRequested; |
| gpu3DMinClock = Param->gpu3DMinClock; |
| registerMemMapped = Param->registerMemMapped; |
| registerMemAddress = Param->registerMemAddress; |
| |
| memcpy(irqs, Param->irqs, gcmSIZEOF(gctINT) * gcvCORE_COUNT); |
| memcpy(registerBases, Param->registerBases, gcmSIZEOF(gctUINT) * gcvCORE_COUNT); |
| memcpy(registerSizes, Param->registerSizes, gcmSIZEOF(gctUINT) * gcvCORE_COUNT); |
| memcpy(chipIDs, Param->chipIDs, gcmSIZEOF(gctUINT) * gcvCORE_COUNT); |
| } |
| |
| void |
| gckOS_DumpParam( |
| void |
| ) |
| { |
| gctINT i; |
| |
| printk("Galcore options:\n"); |
| if (irqLine != -1) |
| { |
| printk(" irqLine = %d\n", irqLine); |
| printk(" registerMemBase = 0x%08lX\n", registerMemBase); |
| printk(" registerMemSize = 0x%08lX\n", registerMemSize); |
| } |
| |
| if (irqLine2D != -1) |
| { |
| printk(" irqLine2D = %d\n", irqLine2D); |
| printk(" registerMemBase2D = 0x%08lX\n", registerMemBase2D); |
| printk(" registerMemSize2D = 0x%08lX\n", registerMemSize2D); |
| } |
| |
| if (irqLineVG != -1) |
| { |
| printk(" irqLineVG = %d\n", irqLineVG); |
| printk(" registerMemBaseVG = 0x%08lX\n", registerMemBaseVG); |
| printk(" registerMemSizeVG = 0x%08lX\n", registerMemSizeVG); |
| } |
| |
| #if gcdDEC_ENABLE_AHB |
| printk(" registerMemBaseDEC300 = 0x%08lX\n", registerMemBaseDEC300); |
| printk(" registerMemSizeDEC300 = 0x%08lX\n", registerMemSizeDEC300); |
| #endif |
| |
| printk(" contiguousSize = 0x%08lX\n", contiguousSize); |
| printk(" contiguousBase = 0x%08lX\n", contiguousBase); |
| printk(" externalSize = 0x%08lX\n", externalSize); |
| printk(" externalBase = 0x%08lX\n", externalBase); |
| printk(" bankSize = 0x%08lX\n", bankSize); |
| printk(" fastClear = %d\n", fastClear); |
| printk(" compression = %d\n", compression); |
| printk(" signal = %d\n", signal); |
| printk(" powerManagement = %d\n", powerManagement); |
| printk(" baseAddress = 0x%08lX\n", baseAddress); |
| printk(" physSize = 0x%08lX\n", physSize); |
| printk(" logFileSize = %d KB \n", logFileSize); |
| printk(" recovery = %d\n", recovery); |
| printk(" stuckDump = %d\n", stuckDump); |
| printk(" gpuProfiler = %d\n", gpuProfiler); |
| |
| printk(" irqs = "); |
| for (i = 0; i < gcvCORE_COUNT; i++) |
| { |
| printk("%d, ", irqs[i]); |
| } |
| printk("\n"); |
| |
| printk(" registerBases = "); |
| for (i = 0; i < gcvCORE_COUNT; i++) |
| { |
| printk("0x%08X, ", registerBases[i]); |
| } |
| printk("\n"); |
| |
| printk(" registerSizes = "); |
| for (i = 0; i < gcvCORE_COUNT; i++) |
| { |
| printk("0x%08X, ", registerSizes[i]); |
| } |
| printk("\n"); |
| |
| printk(" chipIDs = "); |
| for (i = 0; i < gcvCORE_COUNT; i++) |
| { |
| printk("0x%08X, ", chipIDs[i]); |
| } |
| printk("\n"); |
| |
| printk("Build options:\n"); |
| printk(" gcdGPU_TIMEOUT = %d\n", gcdGPU_TIMEOUT); |
| printk(" gcdGPU_2D_TIMEOUT = %d\n", gcdGPU_2D_TIMEOUT); |
| printk(" gcdINTERRUPT_STATISTIC = %d\n", gcdINTERRUPT_STATISTIC); |
| } |
| |
| static int drv_open( |
| struct inode* inode, |
| struct file* filp |
| ) |
| { |
| gceSTATUS status; |
| gctBOOL attached = gcvFALSE; |
| gcsHAL_PRIVATE_DATA_PTR data = gcvNULL; |
| gctINT i; |
| |
| gcmkHEADER_ARG("inode=0x%08X filp=0x%08X", inode, filp); |
| |
| if (filp == gcvNULL) |
| { |
| gcmkTRACE_ZONE( |
| gcvLEVEL_ERROR, gcvZONE_DRIVER, |
| "%s(%d): filp is NULL\n", |
| __FUNCTION__, __LINE__ |
| ); |
| |
| gcmkONERROR(gcvSTATUS_INVALID_ARGUMENT); |
| } |
| |
| data = kmalloc(sizeof(gcsHAL_PRIVATE_DATA), GFP_KERNEL | __GFP_NOWARN); |
| |
| if (data == gcvNULL) |
| { |
| gcmkTRACE_ZONE( |
| gcvLEVEL_ERROR, gcvZONE_DRIVER, |
| "%s(%d): private_data is NULL\n", |
| __FUNCTION__, __LINE__ |
| ); |
| |
| gcmkONERROR(gcvSTATUS_OUT_OF_MEMORY); |
| } |
| |
| data->device = galDevice; |
| data->pidOpen = _GetProcessID(); |
| |
| /* Attached the process. */ |
| for (i = 0; i < gcdMAX_GPU_COUNT; i++) |
| { |
| if (galDevice->kernels[i] != gcvNULL) |
| { |
| gcmkONERROR(gckKERNEL_AttachProcess(galDevice->kernels[i], gcvTRUE)); |
| } |
| } |
| attached = gcvTRUE; |
| |
| filp->private_data = data; |
| |
| /* Success. */ |
| gcmkFOOTER_NO(); |
| return 0; |
| |
| OnError: |
| if (data != gcvNULL) |
| { |
| kfree(data); |
| } |
| |
| if (attached) |
| { |
| for (i = 0; i < gcdMAX_GPU_COUNT; i++) |
| { |
| if (galDevice->kernels[i] != gcvNULL) |
| { |
| gcmkVERIFY_OK(gckKERNEL_AttachProcess(galDevice->kernels[i], gcvFALSE)); |
| } |
| } |
| } |
| |
| gcmkFOOTER(); |
| return -ENOTTY; |
| } |
| |
| static int drv_release( |
| struct inode* inode, |
| struct file* filp |
| ) |
| { |
| gceSTATUS status; |
| gcsHAL_PRIVATE_DATA_PTR data; |
| gckGALDEVICE device; |
| gctINT i; |
| |
| gcmkHEADER_ARG("inode=0x%08X filp=0x%08X", inode, filp); |
| |
| if (filp == gcvNULL) |
| { |
| gcmkTRACE_ZONE( |
| gcvLEVEL_ERROR, gcvZONE_DRIVER, |
| "%s(%d): filp is NULL\n", |
| __FUNCTION__, __LINE__ |
| ); |
| |
| gcmkONERROR(gcvSTATUS_INVALID_ARGUMENT); |
| } |
| |
| data = filp->private_data; |
| |
| if (data == gcvNULL) |
| { |
| gcmkTRACE_ZONE( |
| gcvLEVEL_ERROR, gcvZONE_DRIVER, |
| "%s(%d): private_data is NULL\n", |
| __FUNCTION__, __LINE__ |
| ); |
| |
| gcmkONERROR(gcvSTATUS_INVALID_ARGUMENT); |
| } |
| |
| device = data->device; |
| |
| if (device == gcvNULL) |
| { |
| gcmkTRACE_ZONE( |
| gcvLEVEL_ERROR, gcvZONE_DRIVER, |
| "%s(%d): device is NULL\n", |
| __FUNCTION__, __LINE__ |
| ); |
| |
| gcmkONERROR(gcvSTATUS_INVALID_ARGUMENT); |
| } |
| |
| /* A process gets detached. */ |
| for (i = 0; i < gcdMAX_GPU_COUNT; i++) |
| { |
| if (galDevice->kernels[i] != gcvNULL) |
| { |
| gcmkONERROR(gckKERNEL_AttachProcessEx(galDevice->kernels[i], gcvFALSE, data->pidOpen)); |
| } |
| } |
| |
| kfree(data); |
| filp->private_data = NULL; |
| |
| /* Success. */ |
| gcmkFOOTER_NO(); |
| return 0; |
| |
| OnError: |
| gcmkFOOTER(); |
| return -ENOTTY; |
| } |
| |
| static long drv_ioctl( |
| struct file* filp, |
| unsigned int ioctlCode, |
| unsigned long arg |
| ) |
| { |
| gceSTATUS status; |
| gcsHAL_INTERFACE iface; |
| gctUINT32 copyLen; |
| DRIVER_ARGS drvArgs; |
| gckGALDEVICE device; |
| gcsHAL_PRIVATE_DATA_PTR data; |
| |
| gcmkHEADER_ARG( |
| "filp=0x%08X ioctlCode=0x%08X arg=0x%08X", |
| filp, ioctlCode, arg |
| ); |
| |
| if (filp == gcvNULL) |
| { |
| gcmkTRACE_ZONE( |
| gcvLEVEL_ERROR, gcvZONE_DRIVER, |
| "%s(%d): filp is NULL\n", |
| __FUNCTION__, __LINE__ |
| ); |
| |
| gcmkONERROR(gcvSTATUS_INVALID_ARGUMENT); |
| } |
| |
| data = filp->private_data; |
| |
| if (data == gcvNULL) |
| { |
| gcmkTRACE_ZONE( |
| gcvLEVEL_ERROR, gcvZONE_DRIVER, |
| "%s(%d): private_data is NULL\n", |
| __FUNCTION__, __LINE__ |
| ); |
| |
| gcmkONERROR(gcvSTATUS_INVALID_ARGUMENT); |
| } |
| |
| device = data->device; |
| |
| if (device == gcvNULL) |
| { |
| gcmkTRACE_ZONE( |
| gcvLEVEL_ERROR, gcvZONE_DRIVER, |
| "%s(%d): device is NULL\n", |
| __FUNCTION__, __LINE__ |
| ); |
| |
| gcmkONERROR(gcvSTATUS_INVALID_ARGUMENT); |
| } |
| |
| if ((ioctlCode != IOCTL_GCHAL_INTERFACE) |
| && (ioctlCode != IOCTL_GCHAL_KERNEL_INTERFACE) |
| ) |
| { |
| gcmkTRACE_ZONE( |
| gcvLEVEL_ERROR, gcvZONE_DRIVER, |
| "%s(%d): unknown command %d\n", |
| __FUNCTION__, __LINE__, |
| ioctlCode |
| ); |
| |
| gcmkONERROR(gcvSTATUS_INVALID_ARGUMENT); |
| } |
| |
| /* Get the drvArgs. */ |
| copyLen = copy_from_user( |
| &drvArgs, (void *) arg, sizeof(DRIVER_ARGS) |
| ); |
| |
| if (copyLen != 0) |
| { |
| gcmkTRACE_ZONE( |
| gcvLEVEL_ERROR, gcvZONE_DRIVER, |
| "%s(%d): error copying of the input arguments.\n", |
| __FUNCTION__, __LINE__ |
| ); |
| |
| gcmkONERROR(gcvSTATUS_INVALID_ARGUMENT); |
| } |
| |
| /* Now bring in the gcsHAL_INTERFACE structure. */ |
| if ((drvArgs.InputBufferSize != sizeof(gcsHAL_INTERFACE)) |
| || (drvArgs.OutputBufferSize != sizeof(gcsHAL_INTERFACE)) |
| ) |
| { |
| gcmkTRACE_ZONE( |
| gcvLEVEL_ERROR, gcvZONE_DRIVER, |
| "%s(%d): input or/and output structures are invalid.\n", |
| __FUNCTION__, __LINE__ |
| ); |
| |
| gcmkONERROR(gcvSTATUS_INVALID_ARGUMENT); |
| } |
| |
| copyLen = copy_from_user( |
| &iface, gcmUINT64_TO_PTR(drvArgs.InputBuffer), sizeof(gcsHAL_INTERFACE) |
| ); |
| |
| if (copyLen != 0) |
| { |
| gcmkTRACE_ZONE( |
| gcvLEVEL_ERROR, gcvZONE_DRIVER, |
| "%s(%d): error copying of input HAL interface.\n", |
| __FUNCTION__, __LINE__ |
| ); |
| |
| gcmkONERROR(gcvSTATUS_INVALID_ARGUMENT); |
| } |
| |
| status = gckDEVICE_Dispatch(device->device, &iface); |
| |
| /* Redo system call after pending signal is handled. */ |
| if (status == gcvSTATUS_INTERRUPTED) |
| { |
| gcmkFOOTER(); |
| return -ERESTARTSYS; |
| } |
| |
| /* Copy data back to the user. */ |
| copyLen = copy_to_user( |
| gcmUINT64_TO_PTR(drvArgs.OutputBuffer), &iface, sizeof(gcsHAL_INTERFACE) |
| ); |
| |
| if (copyLen != 0) |
| { |
| gcmkTRACE_ZONE( |
| gcvLEVEL_ERROR, gcvZONE_DRIVER, |
| "%s(%d): error copying of output HAL interface.\n", |
| __FUNCTION__, __LINE__ |
| ); |
| |
| gcmkONERROR(gcvSTATUS_INVALID_ARGUMENT); |
| } |
| |
| /* Success. */ |
| gcmkFOOTER_NO(); |
| return 0; |
| |
| OnError: |
| gcmkFOOTER(); |
| return -ENOTTY; |
| } |
| |
| static struct file_operations driver_fops = |
| { |
| .owner = THIS_MODULE, |
| .open = drv_open, |
| .release = drv_release, |
| .unlocked_ioctl = drv_ioctl, |
| #ifdef HAVE_COMPAT_IOCTL |
| .compat_ioctl = drv_ioctl, |
| #endif |
| }; |
| |
| static struct miscdevice gal_device = { |
| .minor = MISC_DYNAMIC_MINOR, |
| .name = DEVICE_NAME, |
| .fops = &driver_fops, |
| }; |
| |
| static int drv_init(void) |
| { |
| int ret; |
| int result = -EINVAL; |
| gceSTATUS status; |
| gckGALDEVICE device = gcvNULL; |
| struct class* device_class = gcvNULL; |
| |
| gcsDEVICE_CONSTRUCT_ARGS args = { |
| .recovery = recovery, |
| .stuckDump = stuckDump, |
| .gpu3DMinClock = gpu3DMinClock, |
| .contiguousRequested = contiguousRequested, |
| .platform = platform, |
| .mmu = mmu, |
| .registerMemMapped = registerMemMapped, |
| .registerMemAddress = registerMemAddress, |
| #if gcdDEC_ENABLE_AHB |
| .registerMemBaseDEC300 = registerMemBaseDEC300, |
| .registerMemSizeDEC300 = registerMemSizeDEC300, |
| #endif |
| }; |
| |
| gcmkHEADER(); |
| |
| memcpy(args.irqs, irqs, gcmSIZEOF(gctINT) * gcvCORE_COUNT); |
| memcpy(args.registerBases, registerBases, gcmSIZEOF(gctUINT) * gcvCORE_COUNT); |
| memcpy(args.registerSizes, registerSizes, gcmSIZEOF(gctUINT) * gcvCORE_COUNT); |
| memcpy(args.chipIDs, chipIDs, gcmSIZEOF(gctUINT) * gcvCORE_COUNT); |
| |
| printk(KERN_INFO "Galcore version %d.%d.%d.%d\n", |
| gcvVERSION_MAJOR, gcvVERSION_MINOR, gcvVERSION_PATCH, gcvVERSION_BUILD); |
| |
| args.powerManagement = powerManagement; |
| args.gpuProfiler = gpuProfiler; |
| |
| if (showArgs) |
| { |
| gckOS_DumpParam(); |
| } |
| |
| if (logFileSize != 0) |
| { |
| gckDEBUGFS_Initialize(); |
| } |
| |
| /* Create the GAL device. */ |
| status = gckGALDEVICE_Construct( |
| irqLine, |
| registerMemBase, registerMemSize, |
| irqLine2D, |
| registerMemBase2D, registerMemSize2D, |
| irqLineVG, |
| registerMemBaseVG, registerMemSizeVG, |
| contiguousBase, contiguousSize, |
| externalBase, externalSize, |
| bankSize, fastClear, compression, baseAddress, physSize, signal, |
| logFileSize, |
| powerManagement, |
| gpuProfiler, |
| &args, |
| &device |
| ); |
| |
| if (gcmIS_ERROR(status)) |
| { |
| gcmkTRACE_ZONE(gcvLEVEL_ERROR, gcvZONE_DRIVER, |
| "%s(%d): Failed to create the GAL device: status=%d\n", |
| __FUNCTION__, __LINE__, status); |
| |
| goto OnError; |
| } |
| |
| /* Start the GAL device. */ |
| gcmkONERROR(gckGALDEVICE_Start(device)); |
| |
| if ((physSize != 0) |
| && (device->kernels[gcvCORE_MAJOR] != gcvNULL) |
| && (device->kernels[gcvCORE_MAJOR]->hardware->mmuVersion != 0)) |
| { |
| /* Reset the base address */ |
| device->baseAddress = 0; |
| } |
| |
| /* Set global galDevice pointer. */ |
| galDevice = device; |
| |
| if (type == 1) |
| { |
| /* Register as misc driver. */ |
| ret = misc_register(&gal_device); |
| |
| if (ret < 0) |
| { |
| gcmkTRACE_ZONE( |
| gcvLEVEL_ERROR, gcvZONE_DRIVER, |
| "%s(%d): misc_register fails.\n", |
| __FUNCTION__, __LINE__ |
| ); |
| |
| gcmkONERROR(gcvSTATUS_OUT_OF_MEMORY); |
| } |
| } |
| else |
| { |
| /* Register the character device. */ |
| ret = register_chrdev(major, DEVICE_NAME, &driver_fops); |
| |
| if (ret < 0) |
| { |
| gcmkTRACE_ZONE( |
| gcvLEVEL_ERROR, gcvZONE_DRIVER, |
| "%s(%d): Could not allocate major number for mmap.\n", |
| __FUNCTION__, __LINE__ |
| ); |
| |
| gcmkONERROR(gcvSTATUS_OUT_OF_MEMORY); |
| } |
| |
| if (major == 0) |
| { |
| major = ret; |
| } |
| |
| /* Create the device class. */ |
| device_class = class_create(THIS_MODULE, CLASS_NAME); |
| |
| if (IS_ERR(device_class)) |
| { |
| gcmkTRACE_ZONE( |
| gcvLEVEL_ERROR, gcvZONE_DRIVER, |
| "%s(%d): Failed to create the class.\n", |
| __FUNCTION__, __LINE__ |
| ); |
| |
| gcmkONERROR(gcvSTATUS_OUT_OF_RESOURCES); |
| } |
| |
| #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,27) |
| device_create(device_class, NULL, MKDEV(major, 0), NULL, DEVICE_NAME); |
| #else |
| device_create(device_class, NULL, MKDEV(major, 0), DEVICE_NAME); |
| #endif |
| |
| gpuClass = device_class; |
| } |
| |
| gcmkTRACE_ZONE( |
| gcvLEVEL_INFO, gcvZONE_DRIVER, |
| "%s(%d): irqLine=%d, contiguousSize=%lu, memBase=0x%lX\n", |
| __FUNCTION__, __LINE__, |
| irqLine, contiguousSize, registerMemBase |
| ); |
| |
| /* Success. */ |
| gcmkFOOTER_NO(); |
| return 0; |
| |
| OnError: |
| /* Roll back. */ |
| if (device_class != gcvNULL) |
| { |
| device_destroy(device_class, MKDEV(major, 0)); |
| class_destroy(device_class); |
| } |
| |
| if (device != gcvNULL) |
| { |
| gcmkVERIFY_OK(gckGALDEVICE_Stop(device)); |
| gcmkVERIFY_OK(gckGALDEVICE_Destroy(device)); |
| } |
| |
| gcmkFOOTER(); |
| return result; |
| } |
| |
| static void drv_exit(void) |
| { |
| gcmkHEADER(); |
| |
| if (type == 1) |
| { |
| misc_deregister(&gal_device); |
| } |
| else |
| { |
| gcmkASSERT(gpuClass != gcvNULL); |
| device_destroy(gpuClass, MKDEV(major, 0)); |
| class_destroy(gpuClass); |
| |
| unregister_chrdev(major, DEVICE_NAME); |
| } |
| |
| gcmkVERIFY_OK(gckGALDEVICE_Stop(galDevice)); |
| gcmkVERIFY_OK(gckGALDEVICE_Destroy(galDevice)); |
| |
| if(gckDEBUGFS_IsEnabled()) |
| { |
| gckDEBUGFS_Terminate(); |
| } |
| |
| gcmkFOOTER_NO(); |
| } |
| |
| #if gcdENABLE_DRM |
| int viv_drm_probe(struct device *dev); |
| int viv_drm_remove(struct device *dev); |
| #endif |
| |
| #if USE_LINUX_PCIE |
| static int gpu_probe(struct pci_dev *pdev, const struct pci_device_id *ent) |
| #else /* USE_LINUX_PCIE */ |
| #if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 8, 0) |
| static int gpu_probe(struct platform_device *pdev) |
| #else |
| static int __devinit gpu_probe(struct platform_device *pdev) |
| #endif |
| #endif /* USE_LINUX_PCIE */ |
| { |
| int ret = -ENODEV; |
| gcsMODULE_PARAMETERS moduleParam = { |
| .irqLine = irqLine, |
| .registerMemBase = registerMemBase, |
| .registerMemSize = registerMemSize, |
| .irqLine2D = irqLine2D, |
| .registerMemBase2D = registerMemBase2D, |
| .registerMemSize2D = registerMemSize2D, |
| .irqLineVG = irqLineVG, |
| .registerMemBaseVG = registerMemBaseVG, |
| .registerMemSizeVG = registerMemSizeVG, |
| .contiguousSize = contiguousSize, |
| .contiguousBase = contiguousBase, |
| .externalSize = externalSize, |
| .externalBase = externalBase, |
| .bankSize = bankSize, |
| .fastClear = fastClear, |
| .powerManagement = powerManagement, |
| .gpuProfiler = gpuProfiler, |
| .signal = signal, |
| .baseAddress = baseAddress, |
| .physSize = physSize, |
| .logFileSize = logFileSize, |
| .recovery = recovery, |
| .stuckDump = stuckDump, |
| .showArgs = showArgs, |
| .gpu3DMinClock = gpu3DMinClock, |
| .registerMemMapped = registerMemMapped, |
| }; |
| |
| gcmkHEADER(); |
| |
| memcpy(moduleParam.irqs, irqs, gcmSIZEOF(gctINT) * gcvCORE_COUNT); |
| memcpy(moduleParam.registerBases, registerBases, gcmSIZEOF(gctUINT) * gcvCORE_COUNT); |
| memcpy(moduleParam.registerSizes, registerSizes, gcmSIZEOF(gctUINT) * gcvCORE_COUNT); |
| memcpy(moduleParam.chipIDs, chipIDs, gcmSIZEOF(gctUINT) * gcvCORE_COUNT); |
| moduleParam.compression = compression; |
| platform->device = pdev; |
| #if USE_LINUX_PCIE |
| if (pci_enable_device(pdev)) { |
| printk(KERN_ERR "galcore: pci_enable_device() failed.\n"); |
| } |
| |
| if (pci_set_dma_mask(pdev, DMA_BIT_MASK(32))) { |
| printk(KERN_ERR "galcore: Failed to set DMA mask.\n"); |
| } |
| |
| pci_set_master(pdev); |
| |
| if (pci_request_regions(pdev, "galcore")) { |
| printk(KERN_ERR "galcore: Failed to get ownership of BAR region.\n"); |
| } |
| |
| #if USE_MSI |
| if (pci_enable_msi(pdev)) { |
| printk(KERN_ERR "galcore: Failed to enable MSI.\n"); |
| } |
| #endif |
| #endif |
| |
| if (platform->ops->getPower) |
| { |
| if (gcmIS_ERROR(platform->ops->getPower(platform))) |
| { |
| gcmkFOOTER_NO(); |
| return ret; |
| } |
| } |
| |
| if (platform->ops->adjustParam) |
| { |
| /* Override default module param. */ |
| platform->ops->adjustParam(platform, &moduleParam); |
| |
| /* Update module param because drv_init() uses them directly. */ |
| _UpdateModuleParam(&moduleParam); |
| } |
| |
| ret = drv_init(); |
| |
| if (!ret) |
| { |
| #if USE_LINUX_PCIE |
| pci_set_drvdata(pdev, galDevice); |
| #else |
| platform_set_drvdata(pdev, galDevice); |
| #endif |
| |
| #if gcdENABLE_DRM |
| ret = viv_drm_probe(&pdev->dev); |
| #endif |
| } |
| |
| if (ret < 0) |
| { |
| gcmkFOOTER_ARG(KERN_INFO "Failed to register gpu driver: %d\n", ret); |
| } |
| else |
| { |
| gcmkFOOTER_NO(); |
| } |
| return ret; |
| } |
| |
| #if USE_LINUX_PCIE |
| static void gpu_remove(struct pci_dev *pdev) |
| #else /* USE_LINUX_PCIE */ |
| #if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 8, 0) |
| static int gpu_remove(struct platform_device *pdev) |
| #else |
| static int __devexit gpu_remove(struct platform_device *pdev) |
| #endif |
| #endif /* USE_LINUX_PCIE */ |
| { |
| gcmkHEADER(); |
| |
| #if gcdENABLE_DRM |
| viv_drm_remove(&pdev->dev); |
| #endif |
| |
| drv_exit(); |
| |
| if (platform->ops->putPower) |
| { |
| platform->ops->putPower(platform); |
| } |
| |
| #if USE_LINUX_PCIE |
| pci_set_drvdata(pdev, NULL); |
| #if USE_MSI |
| pci_disable_msi(pdev); |
| #endif |
| pci_clear_master(pdev); |
| pci_release_regions(pdev); |
| pci_disable_device(pdev); |
| gcmkFOOTER_NO(); |
| return; |
| #else |
| gcmkFOOTER_NO(); |
| return 0; |
| #endif |
| } |
| |
| static int gpu_suspend(struct platform_device *dev, pm_message_t state) |
| { |
| gceSTATUS status; |
| gckGALDEVICE device; |
| gctINT i; |
| |
| device = platform_get_drvdata(dev); |
| |
| if (!device) |
| { |
| return -1; |
| } |
| |
| for (i = 0; i < gcdMAX_GPU_COUNT; i++) |
| { |
| if (device->kernels[i] != gcvNULL) |
| { |
| /* Store states. */ |
| #if gcdENABLE_VG |
| if (i == gcvCORE_VG) |
| { |
| status = gckVGHARDWARE_QueryPowerManagementState(device->kernels[i]->vg->hardware, &device->statesStored[i]); |
| } |
| else |
| #endif |
| { |
| status = gckHARDWARE_QueryPowerManagementState(device->kernels[i]->hardware, &device->statesStored[i]); |
| } |
| |
| if (gcmIS_ERROR(status)) |
| { |
| return -1; |
| } |
| |
| #if gcdENABLE_VG |
| if (i == gcvCORE_VG) |
| { |
| status = gckVGHARDWARE_SetPowerManagementState(device->kernels[i]->vg->hardware, gcvPOWER_OFF); |
| } |
| else |
| #endif |
| { |
| status = gckHARDWARE_SetPowerManagementState(device->kernels[i]->hardware, gcvPOWER_OFF); |
| } |
| |
| if (gcmIS_ERROR(status)) |
| { |
| return -1; |
| } |
| |
| } |
| } |
| |
| return 0; |
| } |
| |
| static int gpu_resume(struct platform_device *dev) |
| { |
| gceSTATUS status; |
| gckGALDEVICE device; |
| gctINT i; |
| gceCHIPPOWERSTATE statesStored; |
| |
| device = platform_get_drvdata(dev); |
| |
| if (!device) |
| { |
| return -1; |
| } |
| |
| for (i = 0; i < gcdMAX_GPU_COUNT; i++) |
| { |
| if (device->kernels[i] != gcvNULL) |
| { |
| #if gcdENABLE_VG |
| if (i == gcvCORE_VG) |
| { |
| status = gckVGHARDWARE_SetPowerManagementState(device->kernels[i]->vg->hardware, gcvPOWER_ON); |
| } |
| else |
| #endif |
| { |
| status = gckHARDWARE_SetPowerManagementState(device->kernels[i]->hardware, gcvPOWER_ON); |
| } |
| |
| if (gcmIS_ERROR(status)) |
| { |
| return -1; |
| } |
| |
| /* Convert global state to crossponding internal state. */ |
| switch(device->statesStored[i]) |
| { |
| case gcvPOWER_OFF: |
| statesStored = gcvPOWER_OFF_BROADCAST; |
| break; |
| case gcvPOWER_IDLE: |
| statesStored = gcvPOWER_IDLE_BROADCAST; |
| break; |
| case gcvPOWER_SUSPEND: |
| statesStored = gcvPOWER_SUSPEND_BROADCAST; |
| break; |
| case gcvPOWER_ON: |
| statesStored = gcvPOWER_ON_AUTO; |
| break; |
| default: |
| statesStored = device->statesStored[i]; |
| break; |
| } |
| |
| /* Restore states. */ |
| #if gcdENABLE_VG |
| if (i == gcvCORE_VG) |
| { |
| status = gckVGHARDWARE_SetPowerManagementState(device->kernels[i]->vg->hardware, statesStored); |
| } |
| else |
| #endif |
| { |
| status = gckHARDWARE_SetPowerManagementState(device->kernels[i]->hardware, statesStored); |
| } |
| |
| if (gcmIS_ERROR(status)) |
| { |
| return -1; |
| } |
| } |
| } |
| |
| return 0; |
| } |
| |
| #if defined(CONFIG_PM) && LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 30) |
| #ifdef CONFIG_PM_SLEEP |
| static int gpu_system_suspend(struct device *dev) |
| { |
| pm_message_t state={0}; |
| return gpu_suspend(to_platform_device(dev), state); |
| } |
| |
| static int gpu_system_resume(struct device *dev) |
| { |
| return gpu_resume(to_platform_device(dev)); |
| } |
| #endif |
| |
| static const struct dev_pm_ops gpu_pm_ops = { |
| SET_SYSTEM_SLEEP_PM_OPS(gpu_system_suspend, gpu_system_resume) |
| }; |
| #endif |
| |
| #if USE_LINUX_PCIE |
| static const struct pci_device_id vivpci_ids[] = { |
| { |
| .class = 0x000000, |
| .class_mask = 0x000000, |
| .vendor = 0x10ee, |
| .device = 0x7012, |
| .subvendor = PCI_ANY_ID, |
| .subdevice = PCI_ANY_ID, |
| .driver_data = 0 |
| }, { /* End: all zeroes */ } |
| }; |
| |
| MODULE_DEVICE_TABLE(pci, vivpci_ids); |
| |
| static struct pci_driver gpu_driver = { |
| .name = DEVICE_NAME, |
| .id_table = vivpci_ids, |
| .probe = gpu_probe, |
| .remove = gpu_remove |
| }; |
| |
| #else /* USE_LINUX_PCIE */ |
| |
| static struct platform_driver gpu_driver = { |
| .probe = gpu_probe, |
| #if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 8, 0) |
| .remove = gpu_remove, |
| #else |
| .remove = __devexit_p(gpu_remove), |
| #endif |
| |
| .suspend = gpu_suspend, |
| .resume = gpu_resume, |
| |
| .driver = { |
| .owner = THIS_MODULE, |
| .name = DEVICE_NAME, |
| #if defined(CONFIG_PM) && LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 30) |
| .pm = &gpu_pm_ops, |
| #endif |
| } |
| }; |
| #endif /* USE_LINUX_PCIE */ |
| |
| static int __init gpu_init(void) |
| { |
| int ret = 0; |
| |
| ret = soc_platform_init(&gpu_driver, &platform); |
| |
| if (ret || !platform) |
| { |
| printk(KERN_ERR "galcore: Soc platform init failed.\n"); |
| return -ENODEV; |
| } |
| |
| #if USE_LINUX_PCIE |
| ret = pci_register_driver(&gpu_driver); |
| #else /* USE_LINUX_PCIE */ |
| ret = platform_driver_register(&gpu_driver); |
| #endif /* USE_LINUX_PCIE */ |
| |
| if (ret) |
| { |
| printk(KERN_ERR "galcore: gpu_init() failed to register driver!\n"); |
| soc_platform_terminate(platform); |
| platform = NULL; |
| return ret; |
| } |
| |
| platform->driver = &gpu_driver; |
| |
| return 0; |
| } |
| |
| static void __exit gpu_exit(void) |
| { |
| #if USE_LINUX_PCIE |
| pci_unregister_driver(&gpu_driver); |
| #else |
| platform_driver_unregister(&gpu_driver); |
| #endif /* USE_LINUX_PCIE */ |
| |
| soc_platform_terminate(platform); |
| platform = NULL; |
| } |
| |
| module_init(gpu_init); |
| module_exit(gpu_exit); |