/*
* Copyright (c) 2008-2011 WonderMedia Technologies, Inc. All Rights Reserved.
*
* 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, see .
*
* WonderMedia Technologies, Inc.
* 4F, 533, Chung-Cheng Road, Hsin-Tien, Taipei 231, R.O.C
*/
/* Written by Vincent Chen, WonderMedia Technologies, Inc., 2008-2011 */
#include
#include
#include
#include
#include
#include
#include
#include
#include "mali.h"
#include
/* Mali-400 Power Management Kernel Driver */
#define REG_MALI_BADDR (0xD8080000 + WMT_MMAP_OFFSET)
/*
#define MALI_PMU_CONTROL
#define MALI_CLK_CONTROL
#define USE_PMC_POWER_STATUS
*/
static spinlock_t mali_spinlock;
static unsigned int mali_max_freq;
static unsigned int mali_cur_freq;
static int on = 1;
static int off;
static int acpi;
static int mem_size = 0x100000; /* 1 MiB */
static int debug;
#define MALI_POWER_MASK 0xf
#define OPT_EQUAL(opt, name) (!strncmp(opt, name, strlen(name)))
#define OPT_INTVAL(opt, name) kstrtoul(opt + strlen(name) + 1, 0, NULL)
#define OPT_STRVAL(opt, name) (opt + strlen(name))
static inline char *get_opt_string(const char *this_opt, const char *name)
{
const char *p;
int i;
char *ret;
p = OPT_STRVAL(this_opt, name);
i = 0;
while (p[i] && p[i] != ' ' && p[i] != ',')
i++;
ret = kmalloc(i + 1, GFP_KERNEL);
if (ret) {
strncpy(ret, p, i);
ret[i] = '\0';
}
return ret;
}
static inline int get_opt_int(const char *this_opt, const char *name,
int *ret)
{
if (!ret)
return 0;
if (!OPT_EQUAL(this_opt, name))
return 0;
*ret = OPT_INTVAL(this_opt, name);
return 1;
}
static inline int get_opt_bool(const char *this_opt, const char *name,
int *ret)
{
if (!ret)
return 0;
if (OPT_EQUAL(this_opt, name)) {
if (this_opt[strlen(name)] == '=')
*ret = kstrtoul(this_opt + strlen(name) + 1,
0, NULL);
else
*ret = 1;
} else {
if (OPT_EQUAL(this_opt, "no") && OPT_EQUAL(this_opt + 2, name))
*ret = 0;
else
return 0;
}
return 1;
}
static int __init malipm_setup(char *options)
{
char *this_opt;
if (!options || !*options)
return 0;
/* The syntax is:
* malipm=[][,=] ...
* e.g.,
* malipm=on:acpi,mem_size=0x100000
*/
while ((this_opt = strsep(&options, ",")) != NULL) {
if (!*this_opt)
continue;
if (get_opt_bool(this_opt, "on", &on))
;
else if (get_opt_bool(this_opt, "off", &off))
;
else if (get_opt_bool(this_opt, "acpi", &acpi))
;
else if (get_opt_int(this_opt, "mem_size", &mem_size))
;
else if (get_opt_bool(this_opt, "debug", &debug))
;
}
on = !off;
printk(KERN_DEBUG
"malipm: on %d, off %d, acpi %d, debug %d, %d MiB\n",
on, off, acpi, debug, mem_size >> 20);
return 0;
}
__setup("malipm=", malipm_setup);
static int mali_suspend(u32 cores);
static int mali_resume(u32 cores);
static void mali_enable_clock(int enab1le);
static void mali_enable_power(int enable);
static void mali_set_memory_base(unsigned int val);
static void mali_set_memory_size(unsigned int val);
static void mali_set_mem_validation_base(unsigned int val);
static void mali_set_mem_validation_size(unsigned int val);
static void mali_get_memory_base(unsigned int *val);
static void mali_get_memory_size(unsigned int *val);
static void mali_get_mem_validation_base(unsigned int *val);
static void mali_get_mem_validation_size(unsigned int *val);
/* symbols for ARM's Mali.ko */
unsigned int mali_memory_base;
EXPORT_SYMBOL(mali_memory_base);
unsigned int mali_memory_size;
EXPORT_SYMBOL(mali_memory_size);
unsigned int mali_mem_validation_base;
EXPORT_SYMBOL(mali_mem_validation_base);
unsigned int mali_mem_validation_size;
EXPORT_SYMBOL(mali_mem_validation_size);
unsigned int mali_ump_secure_id;
EXPORT_SYMBOL(mali_ump_secure_id);
unsigned int (*mali_get_ump_secure_id)(unsigned int addr, unsigned int size);
EXPORT_SYMBOL(mali_get_ump_secure_id);
void (*mali_put_ump_secure_id)(unsigned int ump_id);
EXPORT_SYMBOL(mali_put_ump_secure_id);
/* PMU */
#define REG_MALI400_BASE (0xd8080000 + WMT_MMAP_OFFSET)
#define REG_MALI400_GP (REG_MALI400_BASE)
#define REG_MALI400_L2 (REG_MALI400_BASE + 0x1000)
#define REG_MALI400_PMU (REG_MALI400_BASE + 0x2000)
#define REG_MALI400_MMU_GP (REG_MALI400_BASE + 0x3000)
#define REG_MALI400_MMU_PP0 (REG_MALI400_BASE + 0x4000)
#define REG_MALI400_MMU_PP1 (REG_MALI400_BASE + 0x5000)
#define REG_MALI400_MMU_PP2 (REG_MALI400_BASE + 0x6000)
#define REG_MALI400_MMU_PP3 (REG_MALI400_BASE + 0x7000)
#define REG_MALI400_PP0 (REG_MALI400_BASE + 0x8000)
#define REG_MALI400_PP1 (REG_MALI400_BASE + 0xa000)
#define REG_MALI400_PP2 (REG_MALI400_BASE + 0xc000)
#define REG_MALI400_PP3 (REG_MALI400_BASE + 0xe000)
#define MMU_DTE_ADDR 0x00
#define MMU_STATUS 0x04
#define MMU_COMMAND 0x08
#define MMU_PAGE_FAULT_ADDR 0x0c
#define MMU_ZAP_ONE_LINE 0x10
#define MMU_INT_RAWSTAT 0x14
#define MMU_INT_CLEAR 0x18
#define MMU_INT_MASK 0x1c
#define MMU_INT_STATUS 0x20
#ifdef USE_PMC_POWER_STATUS
#define REG_PMC_BASE (0xd8130000 + WMT_MMAP_OFFSET)
#define REG_MALI_GP_SHUT_OFF_CONTROL (REG_PMC_BASE + 0x0600)
#define REG_MALI_L2C_SHUT_OFF_CONTROL (REG_PMC_BASE + 0x0620)
#define REG_MALI_PP0_SHUT_OFF_CONTROL (REG_PMC_BASE + 0x0624)
#define REG_MALI_PP1_SHUT_OFF_CONTROL (REG_PMC_BASE + 0x0628)
#define PWR_SEQ_MSK 0x6
#define PWR_STA_MSK 0xf0
#ifndef REG_VAL32
#define REG_VAL32 REG_GET32
#endif
static int wait_powerup(unsigned int reg)
{
int i = 10; /* 10 * 100 us = 1 ms */
unsigned int val;
while (i) {
val = ioread32(reg);
if ((val & PWR_SEQ_MSK) == 0)
break;
udelay(100);
i--;
}
if (i == 0)
printk(KERN_ERR "%s %d: *0x%08x = %08x\n",
__func__, __LINE__, reg, val);
while (i) {
val = ioread32(reg);
if (val & PWR_STA_MSK)
break;
udelay(100);
i--;
}
if (i == 0)
printk(KERN_ERR "%s %d: *0x%08x = %08x\n",
__func__, __LINE__, reg, val);
return i ? 0 : -1;
}
static int wait_powerdown(unsigned int reg)
{
int i = 10; /* 10 * 100 us = 1 ms */
unsigned int val;
while (i) {
val = ioread32(reg);
if ((val & PWR_SEQ_MSK) == 0)
break;
udelay(100);
i--;
}
if (i == 0)
printk(KERN_ERR "%s %d: *0x%08x = %08x\n",
__func__, __LINE__, reg, val);
while (i) {
val = ioread32(reg);
if ((val & PWR_STA_MSK) == 0)
break;
udelay(100);
i--;
}
if (i == 0)
printk(KERN_ERR "%s %d: *0x%08x = %08x\n",
__func__, __LINE__, reg, val);
return i ? 0 : -1;
}
#endif /* USE_PMC_POWER_STATUS */
int mali_platform_wait_powerup(int msk)
{
#ifdef USE_PMC_POWER_STATUS
int err;
if (debug)
printk(KERN_DEBUG "%s\n", __func__);
err = 0;
if (msk & BIT0)
err += wait_powerup(REG_MALI_GP_SHUT_OFF_CONTROL);
if (msk & BIT1)
err += wait_powerup(REG_MALI_L2C_SHUT_OFF_CONTROL);
if (msk & BIT2)
err += wait_powerup(REG_MALI_PP0_SHUT_OFF_CONTROL);
if (msk & BIT3)
err += wait_powerup(REG_MALI_PP1_SHUT_OFF_CONTROL);
if (err)
printk(KERN_ERR "%s error\n", __func__);
return err;
#else
return 0;
#endif /* USE_PMC_POWER_STATUS */
}
EXPORT_SYMBOL(mali_platform_wait_powerup);
int mali_platform_wait_powerdown(int msk)
{
#ifdef USE_PMC_POWER_STATUS
int err;
if (debug)
printk(KERN_DEBUG "%s\n", __func__);
err = 0;
if (msk & BIT0)
err += wait_powerdown(REG_MALI_GP_SHUT_OFF_CONTROL);
if (msk & BIT1)
err += wait_powerdown(REG_MALI_L2C_SHUT_OFF_CONTROL);
if (msk & BIT2)
err += wait_powerdown(REG_MALI_PP0_SHUT_OFF_CONTROL);
if (msk & BIT3)
err += wait_powerdown(REG_MALI_PP1_SHUT_OFF_CONTROL);
if (err)
printk(KERN_ERR "%s error\n", __func__);
return err;
#else
return 0;
#endif /* USE_PMC_POWER_STATUS */
}
EXPORT_SYMBOL(mali_platform_wait_powerdown);
int mali_pmu_power_up(unsigned int msk)
{
int timeout;
unsigned int pmu0c;
unsigned int pmu08;
unsigned int val;
val = ioread32(REG_MALI400_PMU + 8);
if ((val & msk) == 0)
return 0;
pmu08 = ioread32(REG_MALI400_PMU + 8);
pmu0c = ioread32(REG_MALI400_PMU + 0xc);
iowrite32(0, REG_MALI400_PMU + 0xc);
asm volatile("" : : : "memory");
/* PMU_POWER_UP */
iowrite32(msk, REG_MALI400_PMU);
asm volatile("" : : : "memory");
timeout = 10;
do {
val = ioread32(REG_MALI400_PMU + 8);
if ((val & msk) == 0)
break;
msleep_interruptible(1);
timeout--;
} while (timeout > 0);
if (debug) {
val = ioread32(REG_MALI400_PMU + 8);
if (timeout == 0)
printk(KERN_DEBUG "%s: 0x%x, 0x%08x -> 0x%08x: fail\n",
__func__, msk, pmu08, val);
else
printk(KERN_DEBUG "%s: 0x%x, 0x%08x -> 0x%08x: pass\n",
__func__, msk, pmu08, val);
}
iowrite32(pmu0c, REG_MALI400_PMU + 0xc);
msleep_interruptible(10);
if (timeout == 0) {
printk(KERN_DEBUG "mali pmu power up failure\n");
return -1;
}
return 0;
}
int mali_pmu_power_down(unsigned int msk)
{
unsigned int pmu08;
unsigned int pmu0c;
int timeout;
unsigned int val;
val = ioread32(REG_MALI400_PMU + 8);
if ((val & msk) == msk)
return 0;
pmu08 = ioread32(REG_MALI400_PMU + 8);
pmu0c = ioread32(REG_MALI400_PMU + 0xc);
iowrite32(0, REG_MALI400_PMU + 0xc);
asm volatile("" : : : "memory");
/* PMU_POWER_DOWN */
iowrite32(msk, REG_MALI400_PMU + 4);
asm volatile("" : : : "memory");
timeout = 10;
do {
val = ioread32(REG_MALI400_PMU + 8);
if ((val & msk) == msk)
break;
msleep_interruptible(1);
timeout--;
} while (timeout > 0);
if (debug) {
val = ioread32(REG_MALI400_PMU + 8);
if (timeout == 0)
printk(KERN_DEBUG "%s: 0x%x, 0x%08x -> 0x%08x: fail\n",
__func__, msk, pmu08, val);
else
printk(KERN_DEBUG "%s: 0x%x, 0x%08x -> 0x%08x: pass\n",
__func__, msk, pmu08, val);
}
iowrite32(pmu0c, REG_MALI400_PMU + 0xc);
msleep_interruptible(10);
if (timeout == 0) {
printk(KERN_DEBUG "mali pmu power down failure\n");
return -1;
}
return 0;
}
static void mali_show_info(void)
{
/* GP_CONTR_REG_VERSION */
printk(KERN_INFO "maligp: version = 0x%08x\n",
ioread32(REG_MALI400_GP + 0x6c));
/* PP0_VERSION */
printk(KERN_INFO "malipp: version = 0x%08x\n",
ioread32(REG_MALI400_PP0 + 0x1000));
}
struct mali_device *create_mali_device(void)
{
struct mali_device *dev;
dev = kcalloc(1, sizeof(struct mali_device), GFP_KERNEL);
dev->suspend = &mali_suspend;
dev->resume = &mali_resume;
dev->enable_clock = &mali_enable_clock;
dev->enable_power = &mali_enable_power;
dev->set_memory_base = &mali_set_memory_base;
dev->set_memory_size = &mali_set_memory_size;
dev->set_mem_validation_base = &mali_set_mem_validation_base;
dev->set_mem_validation_size = &mali_set_mem_validation_size;
dev->get_memory_base = &mali_get_memory_base;
dev->get_memory_size = &mali_get_memory_size;
dev->get_mem_validation_base = &mali_get_mem_validation_base;
dev->get_mem_validation_size = &mali_get_mem_validation_size;
return dev;
}
EXPORT_SYMBOL(create_mali_device);
void release_mali_device(struct mali_device *dev)
{
kfree(dev);
}
EXPORT_SYMBOL(release_mali_device);
static int mali_suspend(u32 cores)
{
if (debug)
printk(KERN_DEBUG "mali_suspend(%d)\n", cores);
return 0;
}
static int mali_resume(u32 cores)
{
if (debug)
printk(KERN_DEBUG "mali_resume(%d)\n", cores);
return 0;
}
static void mali_enable_clock(int enable)
{
int clk_en;
/*
* if your enable clock with auto_pll_divisor() twice,
* then you have to call it at least twice to disable clock.
* It is really bad.
*/
if (enable) {
auto_pll_divisor(DEV_MALI, CLK_ENABLE, 0, 0);
if (debug)
printk(KERN_DEBUG "Mali clock enabled\n");
} else {
do {
clk_en = auto_pll_divisor(DEV_MALI, CLK_DISABLE, 0, 0);
} while (clk_en);
if (debug)
printk(KERN_DEBUG "Mali clock disabled\n");
}
}
static void mali_enable_power(int enable)
{
/* Mali-400's power was always enabled on WM3481. */
}
static void mali_set_memory_base(unsigned int val)
{
spin_lock(&mali_spinlock);
mali_memory_base = val;
spin_unlock(&mali_spinlock);
}
static void mali_set_memory_size(unsigned int val)
{
spin_lock(&mali_spinlock);
mali_memory_size = val;
spin_unlock(&mali_spinlock);
}
static void mali_set_mem_validation_base(unsigned int val)
{
spin_lock(&mali_spinlock);
mali_mem_validation_base = val;
spin_unlock(&mali_spinlock);
}
static void mali_set_mem_validation_size(unsigned int val)
{
spin_lock(&mali_spinlock);
mali_mem_validation_size = val;
spin_unlock(&mali_spinlock);
}
static void mali_get_memory_base(unsigned int *val)
{
spin_lock(&mali_spinlock);
*val = mali_memory_base;
spin_unlock(&mali_spinlock);
}
static void mali_get_memory_size(unsigned int *val)
{
spin_lock(&mali_spinlock);
*val = mali_memory_size;
spin_unlock(&mali_spinlock);
}
static void mali_get_mem_validation_base(unsigned int *val)
{
spin_lock(&mali_spinlock);
*val = mali_mem_validation_base;
spin_unlock(&mali_spinlock);
}
static void mali_get_mem_validation_size(unsigned int *val)
{
spin_lock(&mali_spinlock);
*val = mali_mem_validation_size;
spin_unlock(&mali_spinlock);
}
/* Export functions */
int mali_platform_init_impl(void *data)
{
if (debug)
printk(KERN_DEBUG "mali_platform_init_impl\n");
return 0;
}
EXPORT_SYMBOL(mali_platform_init_impl);
int mali_platform_deinit_impl(void *data)
{
if (debug)
printk(KERN_DEBUG "mali_platform_deinit_impl\n");
return 0;
}
EXPORT_SYMBOL(mali_platform_deinit_impl);
int mali_platform_powerdown_impl(u32 cores)
{
unsigned int status;
if (debug)
printk(KERN_DEBUG "mali_platform_powerdown_impl(%d)\n", cores);
status = MALI_POWER_MASK;
if (acpi == 0)
return 0;
#ifdef MALI_PMU_CONTROL
status = ioread32(REG_MALI400_PMU + 8);
if ((status & MALI_POWER_MASK) != MALI_POWER_MASK)
mali_pmu_power_down(MALI_POWER_MASK);
#endif
#ifdef MALI_CLK_CONTROL
spin_lock(&mali_spinlock);
mali_enable_clock(0);
mali_enable_power(0);
spin_unlock(&mali_spinlock);
#endif
return 0;
}
EXPORT_SYMBOL(mali_platform_powerdown_impl);
int mali_platform_powerup_impl(u32 cores)
{
unsigned int status;
if (debug)
printk(KERN_DEBUG "mali_platform_powerup_impl(%d)\n", cores);
#ifdef MALI_PMU_CONTROL
status = ioread32(REG_MALI400_PMU + 8);
/* printk("mali pmu: status = 0x08%x\n", status); */
if ((status & MALI_POWER_MASK) != 0)
mali_pmu_power_up(MALI_POWER_MASK);
#else
status = 0;
#endif
#ifdef MALI_CLK_CONTROL
spin_lock(&mali_spinlock);
mali_enable_power(1);
mali_enable_clock(1);
spin_unlock(&mali_spinlock);
#endif
return 0;
}
EXPORT_SYMBOL(mali_platform_powerup_impl);
void mali_gpu_utilization_handler_impl(u32 utilization)
{
unsigned int freq;
unsigned int oldfreq;
int ret;
if (acpi < 2 || mali_max_freq == 0)
return;
utilization = (utilization + 63) & ~63;
oldfreq = mali_cur_freq;
freq = ((utilization * mali_max_freq) >> 8) + 64;
ret = auto_pll_divisor(DEV_MALI, SET_DIV, 2, freq); /* MHz */
if (ret > 0)
mali_cur_freq = (ret >> 20) + 1; /* MHz */
printk(KERN_DEBUG "%s: %d, %d -> %d (%d) MHz\n",
__func__, utilization, oldfreq, mali_cur_freq, freq);
}
EXPORT_SYMBOL(mali_gpu_utilization_handler_impl);
void set_mali_parent_power_domain(struct platform_device *dev)
{
/* No implemented yet */
}
EXPORT_SYMBOL(set_mali_parent_power_domain);
static int __init mali_init(void)
{
unsigned long smem_start;
unsigned long smem_len;
int err = 0;
int ret;
spin_lock_init(&mali_spinlock);
smem_start = num_physpages << PAGE_SHIFT;
smem_len = mem_size;
mali_set_memory_base(smem_start);
mali_set_memory_size(smem_len);
mali_set_mem_validation_base(0);
mali_set_mem_validation_size(0);
mali_ump_secure_id = (unsigned int) -1;
mali_get_ump_secure_id = NULL;
mali_put_ump_secure_id = NULL;
if (off)
return -1;
mali_enable_power(1);
mali_enable_clock(1);
/* Wait for power stable */
msleep_interruptible(1);
/* Verify Mali-400 PMU */
err += mali_pmu_power_down(MALI_POWER_MASK);
if (!err)
err += mali_pmu_power_up(MALI_POWER_MASK);
if (!err)
mali_show_info();
if (acpi)
err += mali_pmu_power_down(MALI_POWER_MASK);
if (acpi > 1) {
ret = auto_pll_divisor(DEV_MALI, GET_FREQ, 0, 0);
if (ret > 0) {
mali_max_freq = (ret >> 20) + 1; /* MHz */
mali_cur_freq = mali_max_freq;
}
} else {
mali_max_freq = mali_cur_freq = 0;
}
/* Power on all Mali core at bootup, otherwise Mali driver will fail
* at driver/src/devicedrv/mali/common/mali_pp.c: mali_pp_reset_wait().
*/
mali_pmu_power_up(MALI_POWER_MASK);
return err;
}
static void __exit mali_exit(void)
{
mali_enable_clock(0);
mali_enable_power(0);
}
module_init(mali_init);
module_exit(mali_exit);
MODULE_AUTHOR("WonderMedia Technologies, Inc.");
MODULE_DESCRIPTION("Mali PM Kernel Driver");
MODULE_LICENSE("GPL");