blob: d503c539bff4d96c9aa14ba9d3a1c7b90fcdf406 [file] [log] [blame]
/****************************************************************************
*
* The MIT License (MIT)
*
* Copyright (c) 2014 - 2019 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 - 2019 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 "gc_hal_kernel_linux.h"
#include "gc_hal_kernel_platform.h"
#include "gc_hal_kernel_device.h"
#include "shared/gc_hal_driver.h"
#include <linux/slab.h>
#if defined(CONFIG_PM_OPP)
#include <linux/pm_opp.h>
#endif
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,10,0)
# include <linux/of_platform.h>
# include <linux/of_gpio.h>
# include <linux/of_address.h>
#endif
#if USE_PLATFORM_DRIVER
# include <linux/platform_device.h>
#endif
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4,9,0)
# define IMX_GPU_SUBSYSTEM 1
# include <linux/component.h>
#endif
#if LINUX_VERSION_CODE < KERNEL_VERSION(3,5,0)
# include <mach/viv_gpu.h>
#elif defined (CONFIG_PM)
# include <linux/pm_runtime.h>
#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 10, 0)
# include <mach/busfreq.h>
# elif LINUX_VERSION_CODE < KERNEL_VERSION(3, 14, 29)
# include <linux/busfreq-imx6.h>
# include <linux/reset.h>
# else
# include <linux/busfreq-imx.h>
# include <linux/reset.h>
# endif
#endif
#include <linux/clk.h>
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5,0,0)
#include <linux/firmware/imx/ipc.h>
#include <linux/firmware/imx/svc/misc.h>
#include <dt-bindings/firmware/imx/rsrc.h>
static struct imx_sc_ipc *gpu_ipcHandle;
#elif defined(IMX8_SCU_CONTROL)
#include <soc/imx8/sc/sci.h>
static sc_ipc_t gpu_ipcHandle;
#endif
#if LINUX_VERSION_CODE < KERNEL_VERSION(3,10,0)
# include <mach/hardware.h>
#endif
#include <linux/pm_runtime.h>
#include <linux/regulator/consumer.h>
#ifdef CONFIG_DEVICE_THERMAL
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,10,0)
# include <linux/device_cooling.h>
# define REG_THERMAL_NOTIFIER(a) register_devfreq_cooling_notifier(a);
# define UNREG_THERMAL_NOTIFIER(a) unregister_devfreq_cooling_notifier(a);
# else
extern int register_thermal_notifier(struct notifier_block *nb);
extern int unregister_thermal_notifier(struct notifier_block *nb);
# define REG_THERMAL_NOTIFIER(a) register_thermal_notifier(a);
# define UNREG_THERMAL_NOTIFIER(a) unregister_thermal_notifier(a);
# endif
#endif
#ifndef gcdFSL_CONTIGUOUS_SIZE
# define gcdFSL_CONTIGUOUS_SIZE (4 << 20)
#endif
static int initgpu3DMinClock = 1;
module_param(initgpu3DMinClock, int, 0644);
struct platform_device *pdevice;
#ifdef CONFIG_GPU_LOW_MEMORY_KILLER
# include <linux/kernel.h>
# include <linux/mm.h>
# include <linux/oom.h>
# include <linux/sched.h>
# include <linux/profile.h>
struct task_struct *lowmem_deathpending;
static int
task_notify_func(struct notifier_block *self, unsigned long val, void *data);
static struct notifier_block task_nb = {
.notifier_call = task_notify_func,
};
static int
task_notify_func(struct notifier_block *self, unsigned long val, void *data)
{
struct task_struct *task = data;
if (task == lowmem_deathpending)
lowmem_deathpending = NULL;
return NOTIFY_DONE;
}
extern struct task_struct *lowmem_deathpending;
static unsigned long lowmem_deathpending_timeout;
static int force_contiguous_lowmem_shrink(IN gckKERNEL Kernel)
{
struct task_struct *p;
struct task_struct *selected = NULL;
int tasksize;
int ret = -1;
int min_adj = 0;
int selected_tasksize = 0;
int selected_oom_adj;
/*
* If we already have a death outstanding, then
* bail out right away; indicating to vmscan
* that we have nothing further to offer on
* this pass.
*
*/
if (lowmem_deathpending &&
time_before_eq(jiffies, lowmem_deathpending_timeout))
return 0;
selected_oom_adj = min_adj;
rcu_read_lock();
for_each_process(p) {
struct mm_struct *mm;
struct signal_struct *sig;
gcuDATABASE_INFO info;
int oom_adj;
task_lock(p);
mm = p->mm;
sig = p->signal;
if (!mm || !sig) {
task_unlock(p);
continue;
}
oom_adj = sig->oom_score_adj;
if (oom_adj < min_adj) {
task_unlock(p);
continue;
}
tasksize = 0;
task_unlock(p);
rcu_read_unlock();
if (gckKERNEL_QueryProcessDB(Kernel, p->pid, gcvFALSE, gcvDB_VIDEO_MEMORY, &info) == gcvSTATUS_OK) {
tasksize += info.counters.bytes / PAGE_SIZE;
}
rcu_read_lock();
if (tasksize <= 0)
continue;
printk("<gpu> pid %d (%s), adj %d, size %d \n", p->pid, p->comm, oom_adj, tasksize);
if (selected) {
if (oom_adj < selected_oom_adj)
continue;
if (oom_adj == selected_oom_adj &&
tasksize <= selected_tasksize)
continue;
}
selected = p;
selected_tasksize = tasksize;
selected_oom_adj = oom_adj;
}
if (selected && selected_oom_adj > 0) {
printk("<gpu> send sigkill to %d (%s), adj %d, size %d\n",
selected->pid, selected->comm,
selected_oom_adj, selected_tasksize);
lowmem_deathpending = selected;
lowmem_deathpending_timeout = jiffies + HZ;
force_sig(SIGKILL, selected);
ret = 0;
}
rcu_read_unlock();
return ret;
}
extern gckKERNEL
_GetValidKernel(
gckGALDEVICE Device
);
static gceSTATUS
_ShrinkMemory(
IN gcsPLATFORM * Platform
)
{
struct platform_device *pdev;
gckGALDEVICE galDevice;
gckKERNEL kernel;
gceSTATUS status = gcvSTATUS_OK;
pdev = Platform->device;
galDevice = platform_get_drvdata(pdev);
kernel = _GetValidKernel(galDevice);
if (kernel != gcvNULL)
{
if (force_contiguous_lowmem_shrink(kernel) != 0)
status = gcvSTATUS_OUT_OF_MEMORY;
}
else
{
printk("%s: can't find kernel!\n", __FUNCTION__);
}
return status;
}
#endif
#if gcdENABLE_FSCALE_VAL_ADJUST && (defined(CONFIG_DEVICE_THERMAL) || defined(CONFIG_DEVICE_THERMAL_MODULE))
static int thermal_hot_pm_notify(struct notifier_block *nb, unsigned long event,
void *dummy)
{
static gctUINT orgFscale, minFscale, maxFscale;
static unsigned long prev_event = 0xffffffff;
gckHARDWARE hardware;
gckGALDEVICE galDevice;
if (event == prev_event)
return NOTIFY_OK;
galDevice = platform_get_drvdata(pdevice);
if (!galDevice)
{
/* GPU is not ready, so it is meaningless to change GPU freq. */
return NOTIFY_OK;
}
if (!galDevice->kernels[gcvCORE_MAJOR])
{
return NOTIFY_OK;
}
hardware = galDevice->kernels[gcvCORE_MAJOR]->hardware;
if (!hardware)
{
return NOTIFY_OK;
}
if (prev_event == 0xffffffff) /* get initial value of Fscale */
gckHARDWARE_GetFscaleValue(hardware,&orgFscale,&minFscale, &maxFscale);
prev_event = event;
switch (event) {
case 0:
gckHARDWARE_SetFscaleValue(hardware, orgFscale, ~0U);
dev_info(&pdevice->dev, "Hot alarm is canceled. GPU3D clock will return to %d/64\n", orgFscale);
break;
case 1:
gckHARDWARE_SetFscaleValue(hardware, maxFscale >> 1, ~0U); /* switch to 1/2 of max frequency */
dev_warn(&pdevice->dev, "System is a little hot. GPU3D clock will work at %d/64\n", maxFscale >> 1);
break;
case 2:
gckHARDWARE_SetFscaleValue(hardware, minFscale, ~0U);
dev_warn(&pdevice->dev, "System is too hot. GPU3D will work at %d/64 clock\n", minFscale);
break;
default:
dev_warn(&pdevice->dev, "Unsupported thermal event: %ld\n", event);
break;
}
return NOTIFY_OK;
}
static struct notifier_block thermal_hot_pm_notifier =
{
.notifier_call = thermal_hot_pm_notify,
};
static ssize_t gpu3DMinClock_show(struct device_driver *dev, char *buf)
{
gctUINT currentf = 0, minf = 0, maxf = 0;
gckGALDEVICE galDevice;
galDevice = platform_get_drvdata(pdevice);
if (galDevice->kernels[gcvCORE_MAJOR])
{
gckHARDWARE_GetFscaleValue(galDevice->kernels[gcvCORE_MAJOR]->hardware,
&currentf, &minf, &maxf);
}
snprintf(buf, PAGE_SIZE, "%d\n", minf);
return strlen(buf);
}
static ssize_t gpu3DMinClock_store(struct device_driver *dev, const char *buf, size_t count)
{
gctINT fields;
gctUINT MinFscaleValue;
gckGALDEVICE galDevice;
galDevice = platform_get_drvdata(pdevice);
if (galDevice->kernels[gcvCORE_MAJOR])
{
fields = sscanf(buf, "%d", &MinFscaleValue);
if (fields < 1)
return -EINVAL;
gckHARDWARE_SetMinFscaleValue(galDevice->kernels[gcvCORE_MAJOR]->hardware,MinFscaleValue);
}
return count;
}
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,11,0)
static DRIVER_ATTR_RW(gpu3DMinClock);
#else
static DRIVER_ATTR(gpu3DMinClock, S_IRUGO | S_IWUSR, gpu3DMinClock_show, gpu3DMinClock_store);
#endif
static ssize_t gpu3DClockScale_show(struct device_driver *dev, char *buf)
{
gctUINT currentf = 0, minf = 0, maxf = 0;
gckGALDEVICE galDevice;
galDevice = platform_get_drvdata(pdevice);
if (galDevice->kernels[gcvCORE_MAJOR])
{
gckHARDWARE_GetFscaleValue(galDevice->kernels[gcvCORE_MAJOR]->hardware,
&currentf, &minf, &maxf);
}
snprintf(buf, PAGE_SIZE, "%d\n", currentf);
return strlen(buf);
}
static ssize_t gpu3DClockScale_store(struct device_driver *dev, const char *buf, size_t count)
{
gctINT fields;
gctUINT FscaleValue;
gckGALDEVICE galDevice;
gctUINT core = gcvCORE_MAJOR;
galDevice = platform_get_drvdata(pdevice);
if (!galDevice)
return -EINVAL;
fields = sscanf(buf, "%d", &FscaleValue);
if (fields < 1)
return -EINVAL;
while (galDevice->kernels[core] && core <= gcvCORE_3D_MAX)
{
gckHARDWARE_SetFscaleValue(galDevice->kernels[core++]->hardware,FscaleValue,FscaleValue);
}
return count;
}
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,11,0)
static DRIVER_ATTR_RW(gpu3DClockScale);
#else
static DRIVER_ATTR(gpu3DClockScale, S_IRUGO | S_IWUSR, gpu3DClockScale_show, gpu3DClockScale_store);
#endif
#endif
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,5,0)
static const struct of_device_id mxs_gpu_dt_ids[] = {
#ifdef IMX_GPU_SUBSYSTEM
{ .compatible = "fsl,imx8-gpu-ss", },
#endif
{ .compatible = "fsl,imx6q-gpu", }, /*Backward Compatiblity */
{/* sentinel */}
};
MODULE_DEVICE_TABLE(of, mxs_gpu_dt_ids);
#endif
struct gpu_clk
{
struct clk *clk_core;
struct clk *clk_shader;
struct clk *clk_axi;
struct clk *clk_ahb;
};
#if defined(CONFIG_PM_OPP)
typedef enum _GOVERN_MODE
{
OVERDRIVE,
NOMINAL,
UNDERDRIVE,
GOVERN_COUNT
}
GOVERN_MODE;
static const char *govern_modes[] =
{
"overdrive",
"nominal",
"underdrive"
};
struct gpu_govern
{
unsigned long core_clk_freq[GOVERN_COUNT];
unsigned long shader_clk_freq[GOVERN_COUNT];
struct device* dev;
int num_modes;
int current_mode;
};
#endif
struct imx_priv
{
struct gpu_clk imx_gpu_clks[gcdMAX_GPU_COUNT];
#if LINUX_VERSION_CODE < KERNEL_VERSION(3,14,0)
#if LINUX_VERSION_CODE < KERNEL_VERSION(3,5,0) || LINUX_VERSION_CODE >= KERNEL_VERSION(3,10,0)
/*Power management.*/
struct regulator *gpu_regulator;
# endif
#endif
/*Run time pm*/
struct device *pmdev[gcdMAX_GPU_COUNT];
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,10,0)
struct reset_control *rstc[gcdMAX_GPU_COUNT];
#endif
uint32_t sc_gpu_pid[gcdMAX_GPU_COUNT];
int gpu3dCount;
#if defined(CONFIG_PM_OPP)
struct gpu_govern imx_gpu_govern;
#endif
};
static struct imx_priv imxPriv;
#if defined(CONFIG_PM_OPP)
static ssize_t gpu_govern_show(struct device_driver *dev, char *buf)
{
struct imx_priv *priv = &imxPriv;
int i;
ssize_t len;
int max_modes;
unsigned long core_freq;
unsigned long shader_freq;
if (priv->imx_gpu_govern.num_modes == GOVERN_COUNT)
max_modes = priv->imx_gpu_govern.num_modes - 1;
else
max_modes = priv->imx_gpu_govern.num_modes;
len = sprintf(buf, "GPU support %d modes\n", priv->imx_gpu_govern.num_modes);
for (i = priv->imx_gpu_govern.current_mode; i <= max_modes; i++)
{
core_freq = priv->imx_gpu_govern.core_clk_freq[i];
shader_freq = priv->imx_gpu_govern.shader_clk_freq[i];
len += sprintf(buf + len,
"%s:\tcore_clk frequency: %lu\tshader_clk frequency: %lu\n",
govern_modes[i], core_freq, shader_freq);
}
len += sprintf(buf + len, "Currently GPU runs on mode %s\n",
govern_modes[priv->imx_gpu_govern.current_mode]);
return len;
}
static ssize_t gpu_govern_store(struct device_driver *dev, const char *buf, size_t count)
{
unsigned long core_freq = 0;
unsigned long shader_freq = 0;
struct imx_priv *priv = &imxPriv;
int core = gcvCORE_MAJOR;
int i;
for (i = 0; i < GOVERN_COUNT; i++)
{
if (strstr(buf, govern_modes[i]))
{
break;
}
}
if (i == GOVERN_COUNT)
{
return count;
}
core_freq = priv->imx_gpu_govern.core_clk_freq[i];
shader_freq = priv->imx_gpu_govern.shader_clk_freq[i];
priv->imx_gpu_govern.current_mode = i;
for (core = gcvCORE_MAJOR; core <= gcvCORE_3D_MAX; core++)
{
struct clk* clk_core = priv->imx_gpu_clks[core].clk_core;
struct clk* clk_shader = priv->imx_gpu_clks[core].clk_shader;
if (clk_core != NULL && clk_shader != NULL &&
core_freq != 0 && shader_freq != 0)
{
#ifdef CONFIG_PM
pm_runtime_get_sync(priv->pmdev[core]);
#endif
clk_set_rate(clk_core, core_freq);
clk_set_rate(clk_shader, shader_freq);
#ifdef CONFIG_PM
pm_runtime_put_sync(priv->pmdev[core]);
#endif
}
}
return count;
}
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,11,0)
static DRIVER_ATTR_RW(gpu_govern);
#else
static DRIVER_ATTR(gpu_govern, S_IRUGO | S_IWUSR, gpu_govern_show, gpu_govern_store);
#endif
int init_gpu_opp_table(struct device *dev)
{
const struct property *prop;
const __be32 *val;
int nr;
int ret = 0;
int i, p;
int core = gcvCORE_MAJOR;
struct imx_priv *priv = &imxPriv;
struct clk *clk_core;
struct clk *clk_shader;
unsigned long core_freq, shader_freq;
priv->imx_gpu_govern.num_modes = 0;
prop = of_find_property(dev->of_node, "operating-points", NULL);
if (!prop) {
return 0;
}
if (!prop->value) {
dev_err(dev, "operating-points invalid. Frequency scaling will not work\n");
return -ENODATA;
}
/*
* Each OPP is a set of tuples consisting of frequency and
* voltage like <freq-kHz vol-uV>.
*/
nr = prop->length / sizeof(u32);
if (nr % 2) {
dev_err(dev, "%s: Invalid OPP list\n", __func__);
return -EINVAL;
}
/*
* We handle both cases where UNDERDRIVE is represented by a single tuple
* or when it is represented by two. More than 4 tuples means that we have
* the current mode defaulting to OVERDRIVE, while less than 3 means only
* nominal. Lastly just two tuples means UNDERDRIVE. Note that the tuples
* are divisible by 2 (X Y) hence there's no need to test for odd values.
*/
if (nr < 6)
priv->imx_gpu_govern.current_mode = UNDERDRIVE;
else if (nr == 6 || nr == 8)
priv->imx_gpu_govern.current_mode = NOMINAL;
else
priv->imx_gpu_govern.current_mode = OVERDRIVE;
val = prop->value;
for (p = 0, i = priv->imx_gpu_govern.current_mode; nr > 0 && i < GOVERN_COUNT; nr -= 4)
{
unsigned long core_freq, core_volt, shader_freq, shader_volt;
core_freq = be32_to_cpup(val++) * 1000;
core_volt = be32_to_cpup(val++);
if (nr == 2)
{
shader_freq = core_freq;
shader_volt = core_volt;
}
else
{
shader_freq = be32_to_cpup(val++) * 1000;
shader_volt = be32_to_cpup(val++);
}
/* We only register core_clk frequency */
if (dev_pm_opp_add(dev, core_freq, core_volt))
{
dev_warn(dev, "%s: Failed to add OPP %ld\n",
__func__, core_freq);
continue;
}
priv->imx_gpu_govern.core_clk_freq[i] = core_freq;
priv->imx_gpu_govern.shader_clk_freq[i] = shader_freq;
p++;
i++;
}
priv->imx_gpu_govern.num_modes = p;
priv->imx_gpu_govern.dev = dev;
if (priv->imx_gpu_govern.num_modes > 0)
{
ret = driver_create_file(dev->driver, &driver_attr_gpu_govern);
if (ret) {
dev_err(dev, "create gpu_govern attr failed (%d)\n", ret);
return ret;
}
/*
* This could be redundant, but it is useful for testing DTS with
* different OPPs that have assigned-clock rates different than the
* ones specified in OPP tuple array. Otherwise we will display
* different clock values when the driver is loaded. Further
* modifications of the governor will display correctly but not when
* the driver has been loaded.
*/
core_freq = priv->imx_gpu_govern.core_clk_freq[priv->imx_gpu_govern.current_mode];
shader_freq = priv->imx_gpu_govern.shader_clk_freq[priv->imx_gpu_govern.current_mode];
if (core_freq && shader_freq) {
for (; core <= gcvCORE_3D_MAX; core++) {
clk_core = priv->imx_gpu_clks[core].clk_core;
clk_shader = priv->imx_gpu_clks[core].clk_shader;
if (clk_core != NULL && clk_shader != NULL) {
clk_set_rate(clk_core, core_freq);
clk_set_rate(clk_shader, shader_freq);
}
}
}
}
return ret;
}
int remove_gpu_opp_table(void)
{
struct imx_priv *priv = &imxPriv;
struct device* dev = priv->imx_gpu_govern.dev;
int i = 0;
int max_modes;
if (priv->imx_gpu_govern.num_modes == GOVERN_COUNT)
max_modes = priv->imx_gpu_govern.num_modes - 1;
else
max_modes = priv->imx_gpu_govern.num_modes;
/* if we don't have any modes available we don't have OPP */
if (max_modes == 0)
return 0;
for (i = priv->imx_gpu_govern.current_mode; i <= max_modes; i++)
{
unsigned long core_freq;
core_freq = priv->imx_gpu_govern.core_clk_freq[i];
dev_pm_opp_remove(dev, core_freq);
}
if (i > 0)
{
driver_remove_file(dev->driver, &driver_attr_gpu_govern);
}
return 0;
}
#endif
#ifdef IMX_GPU_SUBSYSTEM
static int use_imx_gpu_subsystem;
/* sub device component ops. */
static int mxc_gpu_sub_bind(struct device *dev,
struct device *master, void *data)
{
return 0;
}
static void mxc_gpu_sub_unbind(struct device *dev, struct device *master, void *data)
{
}
static const struct component_ops mxc_gpu_sub_ops =
{
.bind = mxc_gpu_sub_bind,
.unbind = mxc_gpu_sub_unbind,
};
/* sub device driver. */
static const struct of_device_id mxc_gpu_sub_match[] =
{
{ .compatible = "fsl,imx8-gpu"},
{ /* sentinel */ }
};
static int mxc_gpu_sub_probe(struct platform_device *pdev)
{
return component_add(&pdev->dev, &mxc_gpu_sub_ops);
}
static int mxc_gpu_sub_remove(struct platform_device *pdev)
{
component_del(&pdev->dev, &mxc_gpu_sub_ops);
return 0;
}
struct platform_driver mxc_gpu_sub_driver =
{
.driver = {
.name = "mxc-gpu",
.owner = THIS_MODULE,
.of_match_table = mxc_gpu_sub_match,
},
.probe = mxc_gpu_sub_probe,
.remove = mxc_gpu_sub_remove,
};
static int register_mxc_gpu_sub_driver(void)
{
return use_imx_gpu_subsystem ? platform_driver_register(&mxc_gpu_sub_driver) : 0;
}
static void unregister_mxc_gpu_sub_driver(void)
{
if (use_imx_gpu_subsystem) {
platform_driver_unregister(&mxc_gpu_sub_driver);
}
}
static int patch_param_imx8_subsystem(struct platform_device *pdev,
gcsMODULE_PARAMETERS *args)
{
int i = 0;
struct resource* res;
struct imx_priv *priv = &imxPriv;
struct device_node *node = pdev->dev.of_node;
struct device_node *core_node;
int core = gcvCORE_MAJOR;
while ((core_node = of_parse_phandle(node, "cores", i++)) != NULL &&
core < gcvCORE_COUNT) {
struct platform_device *pdev_gpu;
int irqLine = -1;
if (!of_device_is_available(core_node)) {
of_node_put(core_node);
continue;
}
pdev_gpu = of_find_device_by_node(core_node);
if (!pdev_gpu)
break;
irqLine = platform_get_irq(pdev_gpu, 0);
if (irqLine < 0)
break;
res = platform_get_resource(pdev_gpu, IORESOURCE_MEM, 0);
if (!res)
break;
while(!priv->pmdev[core] && core < (gcvCORE_COUNT-1))
core++;
args->irqs[core] = irqLine;
args->registerBases[core] = res->start;
args->registerSizes[core] = res->end - res->start + 1;
of_node_put(core_node);
++core;
}
if (core_node)
of_node_put(core_node);
return 0;
}
static inline int get_power_imx8_subsystem(struct device *pdev)
{
struct imx_priv *priv = &imxPriv;
struct clk *clk_core = NULL;
struct clk *clk_shader = NULL;
struct clk *clk_axi = NULL;
struct clk *clk_ahb = NULL;
/* Initialize the clock structure */
int i = 0;
struct device_node *node = pdev->of_node;
struct device_node *core_node;
int core = gcvCORE_MAJOR;
while ((core_node = of_parse_phandle(node, "cores", i++)) != NULL) {
struct platform_device *pdev_gpu = NULL;
clk_shader = NULL;
clk_core = NULL;
clk_axi = NULL;
clk_ahb = NULL;
if (!of_device_is_available(core_node)) {
of_node_put(core_node);
continue;
}
pdev_gpu = of_find_device_by_node(core_node);
if (!pdev_gpu)
break;
clk_core = clk_get(&pdev_gpu->dev, "core");
if (IS_ERR(clk_core)) {
printk("galcore: clk_get clk_core failed\n");
break;
}
clk_axi = clk_get(&pdev_gpu->dev, "axi");
if (IS_ERR(clk_axi))
clk_axi = NULL;
clk_ahb = clk_get(&pdev_gpu->dev, "ahb");
if (IS_ERR(clk_ahb))
clk_ahb = NULL;
clk_shader = clk_get(&pdev_gpu->dev, "shader");
if (IS_ERR(clk_shader)) {
priv->gpu3dCount = core;
core = gcvCORE_2D;
clk_shader = NULL;
}
#if defined(CONFIG_ANDROID) && LINUX_VERSION_CODE < KERNEL_VERSION(4,9,0)
/* TODO: freescale BSP issue in some platform like imx8dv. */
clk_prepare(clk_core);
clk_set_rate(clk_core, 800000000);
clk_unprepare(clk_core);
clk_prepare(clk_shader);
clk_set_rate(clk_shader, 800000000);
clk_unprepare(clk_shader);
#endif
priv->imx_gpu_clks[core].clk_shader = clk_shader;
priv->imx_gpu_clks[core].clk_core = clk_core;
priv->imx_gpu_clks[core].clk_axi = clk_axi;
priv->imx_gpu_clks[core].clk_ahb = clk_ahb;
if (of_property_read_u32(core_node, "fsl,sc_gpu_pid", &priv->sc_gpu_pid[core])) {
priv->sc_gpu_pid[core] = 0;
}
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5,0,0)
else if (!gpu_ipcHandle) {
uint32_t ret = imx_scu_get_handle(&gpu_ipcHandle);
if (ret != 0) {
printk("galcore: cannot open MU channel to SCU\n");
return ret;
}
}
#elif defined(IMX8_SCU_CONTROL)
else if (!gpu_ipcHandle) {
sc_err_t sciErr;
uint32_t mu_id;
sciErr = sc_ipc_getMuID(&mu_id);
if (sciErr != SC_ERR_NONE) {
printk("galcore; cannot obtain mu id\n");
return -EINVAL;
}
sciErr = sc_ipc_open(&gpu_ipcHandle, mu_id);
if (sciErr != SC_ERR_NONE) {
printk("galcore: cannot open MU channel to SCU\n");
return -EINVAL;
}
}
#endif
#ifdef CONFIG_PM
pm_runtime_get_noresume(&pdev_gpu->dev);
pm_runtime_set_active(&pdev_gpu->dev);
pm_runtime_enable(&pdev_gpu->dev);
pm_runtime_put_sync(&pdev_gpu->dev);
priv->pmdev[core] = &pdev_gpu->dev;
#endif
of_node_put(core_node);
++core;
}
if (core <= gcvCORE_3D_MAX)
priv->gpu3dCount = core;
if (core_node)
of_node_put(core_node);
return 0;
}
#endif
static int patch_param_imx6(struct platform_device *pdev,
gcsMODULE_PARAMETERS *args)
{
struct resource* res;
res = platform_get_resource_byname(pdev, IORESOURCE_IRQ, "irq_3d");
if (res)
args->irqs[gcvCORE_MAJOR] = res->start;
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "iobase_3d");
if (res) {
args->registerBases[gcvCORE_MAJOR] = res->start;
args->registerSizes[gcvCORE_MAJOR] = res->end - res->start + 1;
}
res = platform_get_resource_byname(pdev, IORESOURCE_IRQ, "irq_2d");
if (res)
args->irqs[gcvCORE_2D] = res->start;
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "iobase_2d");
if (res) {
args->registerBases[gcvCORE_2D] = res->start;
args->registerSizes[gcvCORE_2D] = res->end - res->start + 1;
}
res = platform_get_resource_byname(pdev, IORESOURCE_IRQ, "irq_vg");
if (res)
args->irqs[gcvCORE_VG] = res->start;
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "iobase_vg");
if (res) {
args->registerBases[gcvCORE_VG] = res->start;
args->registerSizes[gcvCORE_VG] = res->end - res->start + 1;
}
return 0;
}
static bool is_layerscape;
static int patch_param_ls(struct platform_device *pdev,
gcsMODULE_PARAMETERS *args)
{
struct device_node *node = pdev->dev.of_node;
struct resource *res;
int core = gcvCORE_MAJOR;
struct platform_device *pdev_gpu;
int irqLine = -1;
pdev_gpu = of_find_device_by_node(node);
if (!pdev_gpu)
return gcvSTATUS_DEVICE;
of_node_put(node);
irqLine = platform_get_irq(pdev_gpu, 0);
if (irqLine < 0)
return gcvSTATUS_NOT_FOUND;
res = platform_get_resource(pdev_gpu, IORESOURCE_MEM, 0);
if (!res)
return gcvSTATUS_NOT_FOUND;
args->irqs[core] = irqLine;
args->registerBases[core] = res->start;
args->registerSizes[core] = res->end - res->start + 1;
return 0;
}
static int patch_param(struct platform_device *pdev,
gcsMODULE_PARAMETERS *args)
{
struct resource* res;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4,1,0)
struct device_node *node;
struct resource res_mem;
#elif LINUX_VERSION_CODE >= KERNEL_VERSION(3,10,0)
#elif LINUX_VERSION_CODE >= KERNEL_VERSION(3,5,0)
struct device_node *dn =pdev->dev.of_node;
const u32 *prop;
#else
struct viv_gpu_platform_data *pdata;
#endif
pdevice = pdev;
if (is_layerscape) {
patch_param_ls(pdev, args);
} else {
#ifdef IMX_GPU_SUBSYSTEM
if (pdev->dev.of_node && use_imx_gpu_subsystem)
patch_param_imx8_subsystem(pdev, args);
else
#endif
patch_param_imx6(pdev, args);
}
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,5,0)
if(args->compression == gcvCOMPRESSION_OPTION_DEFAULT)
{
const u32 *property;
property = of_get_property(pdev->dev.of_node, "depth-compression", NULL);
if (property && *property == 0)
{
args->compression &= ~gcvCOMPRESSION_OPTION_DEPTH;
}
}
#endif
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "phys_baseaddr");
if (res && !args->baseAddress && !args->physSize) {
args->baseAddress = res->start;
args->physSize = res->end - res->start + 1;
}
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4,1,0)
node = of_parse_phandle(pdev->dev.of_node, "memory-region", 0);
if (node && !of_address_to_resource(node, 0, &res_mem))
{
res = &res_mem;
}
else
{
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "contiguous_mem");
}
if (node)
{
of_node_put(node);
}
if (res) {
if (args->contiguousBase == 0)
args->contiguousBase = res->start;
if (args->contiguousSize == ~0U)
args->contiguousSize = res->end - res->start + 1;
}
#elif LINUX_VERSION_CODE >= KERNEL_VERSION(3,10,0)
args->contiguousBase = 0;
#elif LINUX_VERSION_CODE >= KERNEL_VERSION(3,5,0)
prop = of_get_property(dn, "contiguousbase", NULL);
if (prop)
args->contiguousBase = *prop;
of_property_read_u32(dn,"contiguoussize", (u32 *)&contiguousSize);
#else
pdata = pdev->dev.platform_data;
if (pdata) {
args->contiguousBase = pdata->reserved_mem_base;
args->contiguousSize = pdata->reserved_mem_size;
}
#endif
if (args->contiguousSize == ~0U) {
printk("Warning: No contiguous memory is reserverd for gpu.!\n");
printk("Warning: Will use default value(%d) for the reserved memory!\n", gcdFSL_CONTIGUOUS_SIZE);
args->contiguousSize = gcdFSL_CONTIGUOUS_SIZE;
}
args->gpu3DMinClock = initgpu3DMinClock;
if (args->physSize == 0) {
#if defined(IMX8_PHYS_BASE)
args->baseAddress = IMX8_PHYS_BASE;
#endif
#if defined(IMX8_PHYS_SIZE)
args->physSize = IMX8_PHYS_SIZE;
#else
args->physSize = 0x80000000;
#endif
}
return 0;
}
int init_priv(void)
{
memset(&imxPriv, 0, sizeof(imxPriv));
#ifdef CONFIG_GPU_LOW_MEMORY_KILLER
#if LINUX_VERSION_CODE < KERNEL_VERSION(4,1,0)
task_free_register(&task_nb);
# else
task_handoff_register(&task_nb);
# endif
#endif
return 0;
}
void
free_priv(void)
{
#ifdef CONFIG_GPU_LOW_MEMORY_KILLER
#if LINUX_VERSION_CODE < KERNEL_VERSION(4,1,0)
task_free_unregister(&task_nb);
# else
task_handoff_unregister(&task_nb);
# endif
#endif
}
static int set_clock(int gpu, int enable);
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,10,0)
static void imx6sx_optimize_qosc_for_GPU(void)
{
struct device_node *np;
void __iomem *src_base;
np = of_find_compatible_node(NULL, NULL, "fsl,imx6sx-qosc");
if (!np)
return;
src_base = of_iomap(np, 0);
WARN_ON(!src_base);
set_clock(gcvCORE_MAJOR, 1);
writel_relaxed(0, src_base); /* Disable clkgate & soft_rst */
writel_relaxed(0, src_base+0x60); /* Enable all masters */
writel_relaxed(0, src_base+0x1400); /* Disable clkgate & soft_rst for gpu */
writel_relaxed(0x0f000222, src_base+0x1400+0xd0); /* Set Write QoS 2 for gpu */
writel_relaxed(0x0f000822, src_base+0x1400+0xe0); /* Set Read QoS 8 for gpu */
set_clock(gcvCORE_MAJOR, 0);
return;
}
#endif
static inline int get_power_imx6(struct device *pdev)
{
struct imx_priv *priv = &imxPriv;
struct clk *clk_core = NULL;
struct clk *clk_shader = NULL;
struct clk *clk_axi = NULL;
struct clk *clk_ahb = NULL;
#ifdef CONFIG_RESET_CONTROLLER
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,10,0)
struct reset_control *rstc;
rstc = devm_reset_control_get(pdev, "gpu3d");
priv->rstc[gcvCORE_MAJOR] = IS_ERR(rstc) ? NULL : rstc;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4,9,0)
rstc = devm_reset_control_get_shared(pdev, "gpu2d");
priv->rstc[gcvCORE_2D] = IS_ERR(rstc) ? NULL : rstc;
rstc = devm_reset_control_get_shared(pdev, "gpuvg");
# else
rstc = devm_reset_control_get(pdev, "gpu2d");
priv->rstc[gcvCORE_2D] = IS_ERR(rstc) ? NULL : rstc;
rstc = devm_reset_control_get(pdev, "gpuvg");
# endif
priv->rstc[gcvCORE_VG] = IS_ERR(rstc) ? NULL : rstc;
# endif
#endif
#if LINUX_VERSION_CODE < KERNEL_VERSION(3,14,0)
#if LINUX_VERSION_CODE < KERNEL_VERSION(3,5,0)
/* Get gpu regulator */
priv->gpu_regulator = regulator_get(pdev, "cpu_vddgpu");
# elif LINUX_VERSION_CODE >= KERNEL_VERSION(3,10,0)
priv->gpu_regulator = devm_regulator_get(pdev, "pu");
# endif
#if LINUX_VERSION_CODE < KERNEL_VERSION(3,5,0) || LINUX_VERSION_CODE >= KERNEL_VERSION(3,10,0)
if (IS_ERR(priv->gpu_regulator)) {
printk("%s: Failed to get gpu regulator\n", __FUNCTION__);
return -ENXIO;
}
# endif
#endif
clk_core = clk_get(pdev, "gpu3d_clk");
if (!IS_ERR(clk_core)) {
clk_axi = clk_get(pdev, "gpu3d_axi_clk");
clk_shader = clk_get(pdev, "gpu3d_shader_clk");
if (IS_ERR(clk_shader)) {
clk_put(clk_core);
clk_core = NULL;
clk_shader = NULL;
printk("galcore: clk_get gpu3d_shader_clk failed, disable 3d!\n");
}
clk_ahb = clk_get(pdev, "gpu3d_ahb_clk");
if (IS_ERR(clk_ahb)) {
clk_ahb = NULL;
}
} else {
clk_core = NULL;
printk("galcore: clk_get gpu3d_clk failed, disable 3d!\n");
}
priv->imx_gpu_clks[gcvCORE_MAJOR].clk_core = clk_core;
priv->imx_gpu_clks[gcvCORE_MAJOR].clk_shader = clk_shader;
priv->imx_gpu_clks[gcvCORE_MAJOR].clk_axi = clk_axi;
priv->imx_gpu_clks[gcvCORE_MAJOR].clk_ahb = clk_ahb;
clk_core = clk_get(pdev, "gpu2d_clk");
if (IS_ERR(clk_core)) {
clk_core = NULL;
pr_debug("galcore: clk_get 2d core clock failed, disable 2d/vg!\n");
} else {
clk_axi = clk_get(pdev, "gpu2d_axi_clk");
if (IS_ERR(clk_axi)) {
clk_axi = NULL;
printk("galcore: clk_get 2d axi clock failed, disable 2d\n");
}
clk_ahb = clk_get(pdev, "gpu2d_ahb_clk");
if (IS_ERR(clk_ahb)) {
clk_ahb = NULL;
}
priv->imx_gpu_clks[gcvCORE_2D].clk_ahb = clk_ahb;
priv->imx_gpu_clks[gcvCORE_2D].clk_core = clk_core;
priv->imx_gpu_clks[gcvCORE_2D].clk_axi = clk_axi;
clk_axi = clk_get(pdev, "openvg_axi_clk");
if (IS_ERR(clk_axi)) {
clk_axi = NULL;
printk("galcore: clk_get vg clock failed, disable vg!\n");
}
priv->imx_gpu_clks[gcvCORE_VG].clk_core = clk_core;
priv->imx_gpu_clks[gcvCORE_VG].clk_axi = clk_axi;
}
#ifdef CONFIG_PM
pm_runtime_enable(pdev);
priv->pmdev[gcvCORE_MAJOR] = pdev;
priv->pmdev[gcvCORE_2D] = pdev;
priv->pmdev[gcvCORE_VG] = pdev;
#endif
return 0;
}
static inline int get_power(struct device *pdev)
{
int ret;
/*Initialize the clock structure*/
#ifdef IMX_GPU_SUBSYSTEM
if (pdev->of_node && use_imx_gpu_subsystem)
ret = get_power_imx8_subsystem(pdev);
else
#endif
ret = get_power_imx6(pdev);
if (ret)
return ret;
#if gcdENABLE_FSCALE_VAL_ADJUST && (defined(CONFIG_DEVICE_THERMAL) || defined(CONFIG_DEVICE_THERMAL_MODULE))
REG_THERMAL_NOTIFIER(&thermal_hot_pm_notifier);
ret = driver_create_file(pdev->driver, &driver_attr_gpu3DMinClock);
if (ret)
dev_err(pdev, "create gpu3DMinClock attr failed (%d)\n", ret);
ret = driver_create_file(pdev->driver, &driver_attr_gpu3DClockScale);
if (ret)
dev_err(pdev, "create gpu3DClockScale attr failed (%d)\n", ret);
#endif
#if defined(CONFIG_PM_OPP)
ret = init_gpu_opp_table(pdev);
if (ret)
dev_err(pdev, "OPP init failed!\n");
#endif
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,10,0)
imx6sx_optimize_qosc_for_GPU();
#endif
return 0;
}
static inline void put_power(void)
{
int core = 0;
struct gpu_clk *imx_clk = NULL;
struct imx_priv *priv = &imxPriv;
struct device *pmdev_last = NULL;/*legacy gpu device entry for imx6*/
struct clk *clk_core_last = NULL;/*vg has same core clk as 2d */
for (core = 0; core < gcdMAX_GPU_COUNT; core++) {
imx_clk = &priv->imx_gpu_clks[core];
if (imx_clk->clk_core && imx_clk->clk_core != clk_core_last) {
clk_put(imx_clk->clk_core);
clk_core_last = imx_clk->clk_core;
imx_clk->clk_core = NULL;
}
if (imx_clk->clk_shader) {
clk_put(imx_clk->clk_shader);
imx_clk->clk_shader = NULL;
}
if (imx_clk->clk_axi) {
clk_put(imx_clk->clk_axi);
imx_clk->clk_axi = NULL;
}
if (imx_clk->clk_ahb) {
clk_put(imx_clk->clk_ahb);
imx_clk->clk_ahb = NULL;
}
#ifdef CONFIG_PM
if (priv->pmdev[core] && priv->pmdev[core] != pmdev_last){
pm_runtime_disable(priv->pmdev[core]);
pmdev_last = priv->pmdev[core];
}
#endif
}
#if LINUX_VERSION_CODE < KERNEL_VERSION(3,5,0)
if (priv->gpu_regulator) {
regulator_put(priv->gpu_regulator);
priv->gpu_regulator = NULL;
}
#endif
#if gcdENABLE_FSCALE_VAL_ADJUST && (defined(CONFIG_DEVICE_THERMAL) || defined(CONFIG_DEVICE_THERMAL_MODULE))
UNREG_THERMAL_NOTIFIER(&thermal_hot_pm_notifier);
driver_remove_file(pdevice->dev.driver, &driver_attr_gpu3DMinClock);
driver_remove_file(pdevice->dev.driver, &driver_attr_gpu3DClockScale);
#endif
#if defined(CONFIG_PM_OPP)
remove_gpu_opp_table();
#endif
#if (LINUX_VERSION_CODE < KERNEL_VERSION(5,0,0)) && defined(IMX8_SCU_CONTROL)
if (gpu_ipcHandle)
sc_ipc_close(gpu_ipcHandle);
#endif
}
static inline int set_power(int gpu, int enable)
{
#ifdef CONFIG_PM
struct imx_priv* priv = &imxPriv;
#endif
if (enable) {
#if LINUX_VERSION_CODE < KERNEL_VERSION(3,14,0)
#if LINUX_VERSION_CODE < KERNEL_VERSION(3,5,0) || LINUX_VERSION_CODE >= KERNEL_VERSION(3,10,0)
if (!IS_ERR(priv->gpu_regulator)) {
int ret = regulator_enable(priv->gpu_regulator);
if (ret)
printk("%s: fail to enable pu regulator %d!\n", __FUNCTION__, ret);
}
# else
imx_gpc_power_up_pu(true);
# endif
#endif
#ifdef CONFIG_PM
pm_runtime_get_sync(priv->pmdev[gpu]);
#endif
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5,0,0)
if (priv->sc_gpu_pid[gpu]) {
uint32_t ret = imx_sc_misc_set_control(gpu_ipcHandle, priv->sc_gpu_pid[gpu], IMX_SC_C_ID, gpu);
if (ret) {
printk("galcore: failed to set gpu id for 3d_%d\n", gpu);
return ret;
}
if (priv->gpu3dCount > 1) {
ret = imx_sc_misc_set_control(gpu_ipcHandle, priv->sc_gpu_pid[gpu], IMX_SC_C_SINGLE_MODE, 0);
if (ret) {
printk("galcore: failed to set gpu dual mode for 3d_%d\n", gpu);
return ret;
}
} else {
ret = imx_sc_misc_set_control(gpu_ipcHandle, priv->sc_gpu_pid[gpu], IMX_SC_C_SINGLE_MODE, 1);
if (ret) {
printk("galcore: failed to set gpu single mode for 3d_%d\n", gpu);
return ret;
}
}
}
#elif defined(IMX8_SCU_CONTROL)
if (priv->sc_gpu_pid[gpu]) {
sc_err_t sciErr = sc_misc_set_control(gpu_ipcHandle, priv->sc_gpu_pid[gpu], SC_C_ID, gpu);
if (sciErr != SC_ERR_NONE)
printk("galcore: failed to set gpu id for 3d_%d\n", gpu);
if (priv->gpu3dCount > 1) {
sciErr = sc_misc_set_control(gpu_ipcHandle, priv->sc_gpu_pid[gpu], SC_C_SINGLE_MODE, 0);
if (sciErr != SC_ERR_NONE)
printk("galcore: failed to set gpu dual mode for 3d_%d\n", gpu);
} else {
sciErr = sc_misc_set_control(gpu_ipcHandle, priv->sc_gpu_pid[gpu], SC_C_SINGLE_MODE, 1);
if (sciErr != SC_ERR_NONE)
printk("galcore: failed to set gpu single mode for 3d_%d\n", gpu);
}
}
#endif
} else {
#ifdef CONFIG_PM
pm_runtime_put_sync(priv->pmdev[gpu]);
#endif
#if LINUX_VERSION_CODE < KERNEL_VERSION(3,14,0)
#if LINUX_VERSION_CODE < KERNEL_VERSION(3,5,0) || LINUX_VERSION_CODE >= KERNEL_VERSION(3,10,0)
if (!IS_ERR(priv->gpu_regulator))
regulator_disable(priv->gpu_regulator);
# else
imx_gpc_power_up_pu(false);
# endif
#endif
}
return 0;
}
int set_clock(int gpu, int enable)
{
struct imx_priv* priv = &imxPriv;
struct clk *clk_core = priv->imx_gpu_clks[gpu].clk_core;
struct clk *clk_shader = priv->imx_gpu_clks[gpu].clk_shader;
struct clk *clk_axi = priv->imx_gpu_clks[gpu].clk_axi;
struct clk *clk_ahb = priv->imx_gpu_clks[gpu].clk_ahb;
if (enable) {
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,5,0)
if (clk_core)
clk_prepare(clk_core);
if (clk_shader)
clk_prepare(clk_shader);
if (clk_axi)
clk_prepare(clk_axi);
if (clk_ahb)
clk_prepare(clk_ahb);
#endif
if (clk_core)
clk_enable(clk_core);
if (clk_shader)
clk_enable(clk_shader);
if (clk_axi)
clk_enable(clk_axi);
if (clk_ahb)
clk_enable(clk_ahb);
} else {
if (clk_core)
clk_disable(clk_core);
if (clk_shader)
clk_disable(clk_shader);
if (clk_axi)
clk_disable(clk_axi);
if (clk_ahb)
clk_disable(clk_ahb);
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,5,0)
if (clk_core)
clk_unprepare(clk_core);
if (clk_shader)
clk_unprepare(clk_shader);
if (clk_axi)
clk_unprepare(clk_axi);
if (clk_ahb)
clk_unprepare(clk_ahb);
#endif
}
return gcvSTATUS_OK;
}
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,5,0)
#ifdef CONFIG_PM
#ifdef CONFIG_PM_RUNTIME
static int gpu_runtime_suspend(struct device *dev)
{
release_bus_freq(BUS_FREQ_HIGH);
return 0;
}
static int gpu_runtime_resume(struct device *dev)
{
request_bus_freq(BUS_FREQ_HIGH);
return 0;
}
#endif
static struct dev_pm_ops gpu_pm_ops;
#endif
#endif
static int adjust_platform_driver(struct platform_driver *driver)
{
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,5,0)
driver->driver.of_match_table = mxs_gpu_dt_ids;
#endif
#ifdef CONFIG_PM
/* Override PM callbacks to add runtime PM callbacks. */
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,5,0)
/* Fill local structure with original value. */
memcpy(&gpu_pm_ops, driver->driver.pm, sizeof(struct dev_pm_ops));
/* Add runtime PM callback. */
#ifdef CONFIG_PM_RUNTIME
gpu_pm_ops.runtime_suspend = gpu_runtime_suspend;
gpu_pm_ops.runtime_resume = gpu_runtime_resume;
gpu_pm_ops.runtime_idle = NULL;
#endif
/* Replace callbacks. */
driver->driver.pm = &gpu_pm_ops;
#endif
#endif
return 0;
}
#define SRC_SCR_OFFSET 0
#define BP_SRC_SCR_GPU3D_RST 1
#define BP_SRC_SCR_GPU2D_RST 4
static inline int reset_gpu(int gpu)
{
#if LINUX_VERSION_CODE < KERNEL_VERSION(3,5,0)
void __iomem *src_base = IO_ADDRESS(SRC_BASE_ADDR);
uint32_t bit_offset, val;
if (gpu == gcvCORE_MAJOR)
bit_offset = BP_SRC_SCR_GPU3D_RST;
else if ((gpu == gcvCORE_VG) ||(gpu == gcvCORE_2D))
bit_offset = BP_SRC_SCR_GPU2D_RST;
else
return -ENXIO;
val = __raw_readl(src_base + SRC_SCR_OFFSET);
val &= ~(1 << (bit_offset));
val |= (1 << (bit_offset));
__raw_writel(val, src_base + SRC_SCR_OFFSET);
while ((__raw_readl(src_base + SRC_SCR_OFFSET) & (1 << (bit_offset))) != 0);
return -ENODEV;
#elif LINUX_VERSION_CODE >= KERNEL_VERSION(3,10,0)
struct imx_priv* priv = &imxPriv;
struct reset_control *rstc = priv->rstc[gpu];
if (rstc)
reset_control_reset(rstc);
#else
imx_src_reset_gpu((int)gpu);
#endif
return 0;
}
static gceSTATUS
_AdjustParam(
gcsPLATFORM * Platform,
gcsMODULE_PARAMETERS *Args
)
{
patch_param(Platform->device, Args);
if ((of_find_compatible_node(NULL, NULL, "fsl,imx8mq-gpu") ||
of_find_compatible_node(NULL, NULL, "fsl,imx8mm-gpu") ||
of_find_compatible_node(NULL, NULL, "fsl,imx8mn-gpu")) &&
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5,0,0)
((Args->baseAddress + totalram_pages() * PAGE_SIZE) > 0x100000000))
#else
((Args->baseAddress + totalram_pages * PAGE_SIZE) > 0x100000000))
#endif
{
Platform->flagBits |= gcvPLATFORM_FLAG_LIMIT_4G_ADDRESS;
}
if (of_find_compatible_node(NULL, NULL, "fsl,imx8mm-gpu"))
{
Platform->flagBits |= gcvPLATFORM_FLAG_IMX_MM;
}
return gcvSTATUS_OK;
}
static gceSTATUS
_GetPower(
gcsPLATFORM * Platform
)
{
int ret = get_power(&Platform->device->dev);
if (ret)
return gcvSTATUS_GENERIC_IO;
return gcvSTATUS_OK;
}
static gceSTATUS
_PutPower(
gcsPLATFORM * Platform
)
{
put_power();
return gcvSTATUS_OK;
}
static gceSTATUS
_SetPower(
gcsPLATFORM * Platform,
gceCORE GPU,
gctBOOL Enable
)
{
return set_power((int)GPU, Enable) ? gcvSTATUS_GENERIC_IO
: gcvSTATUS_OK;
}
static gceSTATUS
_SetClock(
gcsPLATFORM * Platform,
gceCORE GPU,
gctBOOL Enable
)
{
set_clock((int)GPU, Enable);
return gcvSTATUS_OK;
}
static gceSTATUS
_Reset(
gcsPLATFORM * Platform,
gceCORE GPU
)
{
int ret;
ret = reset_gpu((int)GPU);
if (!ret)
return gcvSTATUS_OK;
else if (ret == -ENODEV)
return gcvSTATUS_NOT_SUPPORTED;
else
return gcvSTATUS_INVALID_CONFIG;
}
struct _gcsPLATFORM_OPERATIONS imx_platform_ops =
{
.adjustParam = _AdjustParam,
.getPower = _GetPower,
.putPower = _PutPower,
.setPower = _SetPower,
.setClock = _SetClock,
.reset = _Reset,
#ifdef CONFIG_GPU_LOW_MEMORY_KILLER
.shrinkMemory = _ShrinkMemory,
#endif
};
static struct _gcsPLATFORM imx_platform =
{
.name = __FILE__,
.ops = &imx_platform_ops,
};
static const struct of_device_id gpu_match[] = {
{ .compatible = "fsl,ls1028a-gpu"},
{ /* sentinel */ }
};
struct _gcsPLATFORM_OPERATIONS ls_platform_ops = {
.adjustParam = _AdjustParam,
};
static struct _gcsPLATFORM ls_platform = {
.name = __FILE__,
.ops = &ls_platform_ops,
};
int gckPLATFORM_Init(struct platform_driver *pdrv,
struct _gcsPLATFORM **platform)
{
#ifdef IMX_GPU_SUBSYSTEM
if (of_find_compatible_node(NULL, NULL, "fsl,imx8-gpu-ss")) {
use_imx_gpu_subsystem = 1;
if (!of_find_compatible_node(NULL, NULL, "fsl,imx8-gpu")) {
printk(KERN_ERR "Incorrect device-tree, please update dtb.");
return -EINVAL;
}
}
#endif
if (of_find_compatible_node(NULL, NULL, "fsl,ls1028a-gpu")) {
is_layerscape = 1;
pdrv->driver.of_match_table = gpu_match;
*platform = &ls_platform;
return 0;
}
adjust_platform_driver(pdrv);
init_priv();
#ifdef IMX_GPU_SUBSYSTEM
register_mxc_gpu_sub_driver();
#endif
*platform = &imx_platform;
return 0;
}
int gckPLATFORM_Terminate(struct _gcsPLATFORM *platform)
{
if (is_layerscape)
return 0;
#ifdef IMX_GPU_SUBSYSTEM
unregister_mxc_gpu_sub_driver();
#endif
free_priv();
return 0;
}