ppsspp/Core/HLE/scePower.cpp
Unknown W. Brackets bc4a203fcf Power: Correct hz on update and improve resched.
It only reschedules when the PLL changes, which changes in steps.  This
also reads back much more accurate Mhz for each of PLL, CPU, and bus.
2019-04-14 14:51:35 -07:00

621 lines
23 KiB
C++

// Copyright (c) 2012- PPSSPP Project.
// 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, version 2.0 or later versions.
// 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 2.0 for more details.
// A copy of the GPL 2.0 should have been included with the program.
// If not, see http://www.gnu.org/licenses/
// Official git repository and contact information can be found at
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
#include <map>
#include <vector>
#include "Common/ChunkFile.h"
#include "Core/HLE/HLE.h"
#include "Core/HLE/FunctionWrappers.h"
#include "Core/CoreTiming.h"
#include "Core/MemMap.h"
#include "Core/Reporting.h"
#include "Core/Config.h"
#include "Core/HLE/scePower.h"
#include "Core/HLE/sceKernelThread.h"
#include "Core/HLE/sceKernelInterrupt.h"
struct VolatileWaitingThread {
SceUID threadID;
u32 addrPtr;
u32 sizePtr;
};
const int PSP_POWER_ERROR_TAKEN_SLOT = 0x80000020;
const int PSP_POWER_ERROR_SLOTS_FULL = 0x80000022;
const int PSP_POWER_ERROR_EMPTY_SLOT = 0x80000025;
const int PSP_POWER_ERROR_INVALID_CB = 0x80000100;
const int PSP_POWER_ERROR_INVALID_SLOT = 0x80000102;
const int PSP_POWER_CB_AC_POWER = 0x00001000;
const int PSP_POWER_CB_BATTERY_EXIST = 0x00000080;
const int PSP_POWER_CB_BATTERY_FULL = 0x00000064;
const int POWER_CB_AUTO = -1;
// These are the callback slots for user mode applications.
const int numberOfCBPowerSlots = 16;
// These are the callback slots for kernel mode applications.
const int numberOfCBPowerSlotsPrivate = 32;
static bool volatileMemLocked;
static int powerCbSlots[numberOfCBPowerSlots];
static std::vector<VolatileWaitingThread> volatileWaitingThreads;
// Should this belong here, or in CoreTiming?
static int RealpllFreq = 222000000;
static int RealbusFreq = 111000000;
static int pllFreq = 222000000;
static int busFreq = 111000000;
// The CPU mhz can only be a multiple of the PLL divided by 511.
int PowerCpuMhzToHz(int desired, int pllHz) {
double maxfreq = desired * 1000000.0;
double step = (double)pllHz / 511.0;
// These values seem to be locked.
if (pllHz >= 333000000 && desired == 333) {
return 333000000;
} else if (pllHz >= 222000000 && desired == 222) {
return 222000000;
}
double freq = 0;
while (freq + step < maxfreq) {
freq += step;
}
// We match the PSP's HLE funcs better when we have the same float error, it seems.
return (float)(freq / 1000000.0f) * 1000000;
}
int PowerPllMhzToHz(int mhz) {
// These seem to be the only steps it has.
if (mhz <= 190)
return 190285721;
if (mhz <= 222)
return 222000000;
if (mhz <= 266)
return 266399994;
if (mhz <= 333)
return 333000000;
return mhz * 1000000;
}
int PowerBusMhzToHz(int mhz) {
// These seem to be the only steps it has.
if (mhz <= 95)
return 95142860;
if (mhz <= 111)
return 111000000;
if (mhz <= 133)
return 133199997;
if (mhz <= 166)
return 166500000;
return mhz * 1000000;
}
void __PowerInit() {
memset(powerCbSlots, 0, sizeof(powerCbSlots));
volatileMemLocked = false;
volatileWaitingThreads.clear();
if (g_Config.iLockedCPUSpeed > 0) {
pllFreq = PowerPllMhzToHz(g_Config.iLockedCPUSpeed);
busFreq = PowerBusMhzToHz(pllFreq / 2000000);
CoreTiming::SetClockFrequencyHz(PowerCpuMhzToHz(g_Config.iLockedCPUSpeed, pllFreq));
} else {
pllFreq = PowerPllMhzToHz(222);
busFreq = PowerBusMhzToHz(111);
}
RealpllFreq = PowerPllMhzToHz(222);
RealbusFreq = PowerBusMhzToHz(111);
}
void __PowerDoState(PointerWrap &p) {
auto s = p.Section("scePower",1,2);
if (!s)
return;
if (s >= 2) {
p.Do(RealpllFreq);
p.Do(RealbusFreq);
if (RealpllFreq < 1000000)
RealpllFreq = PowerPllMhzToHz(RealpllFreq);
if (RealbusFreq < 1000000)
RealbusFreq = PowerBusMhzToHz(RealbusFreq);
} else {
RealpllFreq = PowerPllMhzToHz(222);
RealbusFreq = PowerBusMhzToHz(111);
}
if (g_Config.iLockedCPUSpeed > 0) {
pllFreq = PowerPllMhzToHz(g_Config.iLockedCPUSpeed);
busFreq = PowerBusMhzToHz(pllFreq / 2000000);
CoreTiming::SetClockFrequencyHz(PowerCpuMhzToHz(g_Config.iLockedCPUSpeed, pllFreq));
} else {
pllFreq = RealpllFreq;
busFreq = RealbusFreq;
}
p.DoArray(powerCbSlots, ARRAY_SIZE(powerCbSlots));
p.Do(volatileMemLocked);
p.Do(volatileWaitingThreads);
}
static int scePowerGetBatteryLifePercent() {
DEBUG_LOG(HLE, "100=scePowerGetBatteryLifePercent");
return 100;
}
static int scePowerGetBatteryLifeTime() {
DEBUG_LOG(HLE, "0=scePowerGetBatteryLifeTime()");
// 0 means we're on AC power.
return 0;
}
static int scePowerGetBatteryTemp() {
DEBUG_LOG(HLE, "0=scePowerGetBatteryTemp()");
// 0 means celsius temperature of the battery
return 0;
}
static int scePowerIsPowerOnline() {
DEBUG_LOG(HLE, "1=scePowerIsPowerOnline");
return 1;
}
static int scePowerIsBatteryExist() {
DEBUG_LOG(HLE, "1=scePowerIsBatteryExist");
return 1;
}
static int scePowerIsBatteryCharging() {
DEBUG_LOG(HLE, "0=scePowerIsBatteryCharging");
return 0;
}
static int scePowerGetBatteryChargingStatus() {
DEBUG_LOG(HLE, "0=scePowerGetBatteryChargingStatus");
return 0;
}
static int scePowerIsLowBattery() {
DEBUG_LOG(HLE, "0=scePowerIsLowBattery");
return 0;
}
static int scePowerRegisterCallback(int slot, int cbId) {
DEBUG_LOG(HLE, "0=scePowerRegisterCallback(%i, %i)", slot, cbId);
if (slot < -1 || slot >= numberOfCBPowerSlotsPrivate) {
return PSP_POWER_ERROR_INVALID_SLOT;
}
if (slot >= numberOfCBPowerSlots) {
return SCE_KERNEL_ERROR_PRIV_REQUIRED;
}
// TODO: If cbId is invalid return PSP_POWER_ERROR_INVALID_CB.
if (cbId == 0) {
return PSP_POWER_ERROR_INVALID_CB;
}
int retval = -1;
if (slot == POWER_CB_AUTO) { // -1 signifies auto select of bank
for (int i=0; i < numberOfCBPowerSlots; i++) {
if (powerCbSlots[i] == 0 && retval == -1) { // found an empty slot
powerCbSlots[i] = cbId;
retval = i;
}
}
if (retval == -1) {
return PSP_POWER_ERROR_SLOTS_FULL;
}
} else {
if (powerCbSlots[slot] == 0) {
powerCbSlots[slot] = cbId;
retval = 0;
} else {
return PSP_POWER_ERROR_TAKEN_SLOT;
}
}
if (retval >= 0) {
int arg = PSP_POWER_CB_AC_POWER | PSP_POWER_CB_BATTERY_EXIST | PSP_POWER_CB_BATTERY_FULL;
__KernelNotifyCallback(cbId, arg);
}
return retval;
}
static int scePowerUnregisterCallback(int slotId) {
DEBUG_LOG(HLE, "0=scePowerUnregisterCallback(%i)", slotId);
if (slotId < 0 || slotId >= numberOfCBPowerSlotsPrivate) {
return PSP_POWER_ERROR_INVALID_SLOT;
}
if (slotId >= numberOfCBPowerSlots) {
return SCE_KERNEL_ERROR_PRIV_REQUIRED;
}
if (powerCbSlots[slotId] != 0) {
int cbId = powerCbSlots[slotId];
DEBUG_LOG(HLE, "0=scePowerUnregisterCallback(%i) (cbid = %i)", slotId, cbId);
powerCbSlots[slotId] = 0;
} else {
return PSP_POWER_ERROR_EMPTY_SLOT;
}
return 0;
}
static int sceKernelPowerLock(int lockType) {
DEBUG_LOG(HLE, "0=sceKernelPowerLock(%i)", lockType);
if (lockType == 0) {
return 0;
} else {
return SCE_KERNEL_ERROR_INVALID_MODE;
}
}
static int sceKernelPowerUnlock(int lockType) {
DEBUG_LOG(HLE, "0=sceKernelPowerUnlock(%i)", lockType);
if (lockType == 0) {
return 0;
} else {
return SCE_KERNEL_ERROR_INVALID_MODE;
}
}
static int sceKernelPowerTick(int flag) {
DEBUG_LOG(HLE, "UNIMPL 0=sceKernelPowerTick(%i)", flag);
return 0;
}
static int __KernelVolatileMemLock(int type, u32 paddr, u32 psize) {
if (type != 0) {
return SCE_KERNEL_ERROR_INVALID_MODE;
}
if (volatileMemLocked) {
return SCE_KERNEL_ERROR_POWER_VMEM_IN_USE;
}
// Volatile RAM is always at 0x08400000 and is of size 0x00400000.
// It's always available in the emu.
// TODO: Should really reserve this properly!
if (Memory::IsValidAddress(paddr)) {
Memory::Write_U32(0x08400000, paddr);
}
if (Memory::IsValidAddress(psize)) {
Memory::Write_U32(0x00400000, psize);
}
volatileMemLocked = true;
return 0;
}
static int sceKernelVolatileMemTryLock(int type, u32 paddr, u32 psize) {
u32 error = __KernelVolatileMemLock(type, paddr, psize);
switch (error) {
case 0:
// HACK: This fixes Crash Tag Team Racing.
// Should only wait 1200 cycles though according to Unknown's testing,
// and with that it's still broken. So it's not this, unfortunately.
// Leaving it in for the 0.9.8 release anyway.
hleEatCycles(500000);
DEBUG_LOG(HLE, "sceKernelVolatileMemTryLock(%i, %08x, %08x) - success", type, paddr, psize);
break;
case SCE_KERNEL_ERROR_POWER_VMEM_IN_USE:
ERROR_LOG(HLE, "sceKernelVolatileMemTryLock(%i, %08x, %08x) - already locked!", type, paddr, psize);
break;
default:
ERROR_LOG_REPORT(HLE, "%08x=sceKernelVolatileMemTryLock(%i, %08x, %08x) - error", type, paddr, psize, error);
break;
}
return error;
}
static int sceKernelVolatileMemUnlock(int type) {
if (type != 0) {
ERROR_LOG_REPORT(HLE, "sceKernelVolatileMemUnlock(%i) - invalid mode", type);
return SCE_KERNEL_ERROR_INVALID_MODE;
}
if (volatileMemLocked) {
volatileMemLocked = false;
// Wake someone, always fifo.
bool wokeThreads = false;
u32 error;
while (!volatileWaitingThreads.empty() && !volatileMemLocked) {
VolatileWaitingThread waitInfo = volatileWaitingThreads.front();
volatileWaitingThreads.erase(volatileWaitingThreads.begin());
int waitID = __KernelGetWaitID(waitInfo.threadID, WAITTYPE_VMEM, error);
// If they were force-released, just skip.
if (waitID == 1 && __KernelVolatileMemLock(0, waitInfo.addrPtr, waitInfo.sizePtr) == 0) {
__KernelResumeThreadFromWait(waitInfo.threadID, 0);
wokeThreads = true;
}
}
if (wokeThreads) {
INFO_LOG(HLE, "sceKernelVolatileMemUnlock(%i) handed over to another thread", type);
hleReSchedule("volatile mem unlocked");
} else {
DEBUG_LOG(HLE, "sceKernelVolatileMemUnlock(%i)", type);
}
} else {
ERROR_LOG_REPORT(HLE, "sceKernelVolatileMemUnlock(%i) FAILED - not locked", type);
// I guess it must use a sema.
return SCE_KERNEL_ERROR_SEMA_OVF;
}
return 0;
}
static int sceKernelVolatileMemLock(int type, u32 paddr, u32 psize) {
u32 error = 0;
// If dispatch is disabled or in an interrupt, don't check, just return an error.
// But still write the addr and size (some games require this to work, and it's testably true.)
if (!__KernelIsDispatchEnabled()) {
error = SCE_KERNEL_ERROR_CAN_NOT_WAIT;
} else if (__IsInInterrupt()) {
error = SCE_KERNEL_ERROR_ILLEGAL_CONTEXT;
} else {
error = __KernelVolatileMemLock(type, paddr, psize);
}
switch (error) {
case 0:
// Should only wait 1200 cycles though according to Unknown's testing,
hleEatCycles(1200);
DEBUG_LOG(HLE, "sceKernelVolatileMemLock(%i, %08x, %08x) - success", type, paddr, psize);
break;
case SCE_KERNEL_ERROR_POWER_VMEM_IN_USE:
{
WARN_LOG(HLE, "sceKernelVolatileMemLock(%i, %08x, %08x) - already locked, waiting", type, paddr, psize);
const VolatileWaitingThread waitInfo = { __KernelGetCurThread(), paddr, psize };
volatileWaitingThreads.push_back(waitInfo);
__KernelWaitCurThread(WAITTYPE_VMEM, 1, 0, 0, false, "volatile mem waited");
}
break;
case SCE_KERNEL_ERROR_CAN_NOT_WAIT:
{
WARN_LOG(HLE, "sceKernelVolatileMemLock(%i, %08x, %08x): dispatch disabled", type, paddr, psize);
Memory::Write_U32(0x08400000, paddr);
Memory::Write_U32(0x00400000, psize);
}
break;
case SCE_KERNEL_ERROR_ILLEGAL_CONTEXT:
{
WARN_LOG(HLE, "sceKernelVolatileMemLock(%i, %08x, %08x): in interrupt", type, paddr, psize);
Memory::Write_U32(0x08400000, paddr);
Memory::Write_U32(0x00400000, psize);
}
break;
default:
ERROR_LOG_REPORT(HLE, "%08x=sceKernelVolatileMemLock(%i, %08x, %08x) - error", type, paddr, psize, error);
break;
}
return error;
}
static u32 scePowerSetClockFrequency(u32 pllfreq, u32 cpufreq, u32 busfreq) {
// 190 might (probably) be a typo for 19, but it's what the actual PSP validates against.
if (pllfreq < 19 || pllfreq < cpufreq || pllfreq > 333) {
return hleLogWarning(SCEMISC, SCE_KERNEL_ERROR_INVALID_VALUE, "invalid pll frequency");
}
if (cpufreq == 0 || cpufreq > 333) {
return hleLogWarning(SCEMISC, SCE_KERNEL_ERROR_INVALID_VALUE, "invalid cpu frequency");
}
if (busfreq == 0 || busfreq > 166) {
return hleLogWarning(SCEMISC, SCE_KERNEL_ERROR_INVALID_VALUE, "invalid bus frequency");
}
// TODO: More restrictions.
if (g_Config.iLockedCPUSpeed > 0) {
INFO_LOG(HLE, "scePowerSetClockFrequency(%i,%i,%i): locked by user config at %i, %i, %i", pllfreq, cpufreq, busfreq, g_Config.iLockedCPUSpeed, g_Config.iLockedCPUSpeed, busFreq);
} else {
INFO_LOG(HLE, "scePowerSetClockFrequency(%i,%i,%i)", pllfreq, cpufreq, busfreq);
}
// Only reschedules when the stepped PLL frequency changes.
// It seems like the busfreq parameter has no effect (but can cause errors.)
if (RealpllFreq != PowerPllMhzToHz(pllfreq)) {
RealpllFreq = PowerPllMhzToHz(pllfreq);
RealbusFreq = PowerBusMhzToHz(RealpllFreq / 2000000);
if (g_Config.iLockedCPUSpeed <= 0) {
pllFreq = RealpllFreq;
busFreq = RealbusFreq;
CoreTiming::SetClockFrequencyHz(PowerCpuMhzToHz(cpufreq, pllFreq));
}
return hleDelayResult(0, "scepower set clockFrequency", 150000);
}
if (g_Config.iLockedCPUSpeed <= 0)
CoreTiming::SetClockFrequencyHz(PowerCpuMhzToHz(cpufreq, pllFreq));
return 0;
}
static u32 scePowerSetCpuClockFrequency(u32 cpufreq) {
if (cpufreq == 0 || cpufreq > 333) {
return hleLogWarning(SCEMISC, SCE_KERNEL_ERROR_INVALID_VALUE, "invalid frequency");
}
if (g_Config.iLockedCPUSpeed > 0) {
return hleLogDebug(SCEMISC, 0, "locked by user config at %i", g_Config.iLockedCPUSpeed);
}
CoreTiming::SetClockFrequencyHz(PowerCpuMhzToHz(cpufreq, pllFreq));
return hleLogSuccessI(SCEMISC, 0);
}
static u32 scePowerSetBusClockFrequency(u32 busfreq) {
if (busfreq == 0 || busfreq > 111) {
return hleLogWarning(SCEMISC, SCE_KERNEL_ERROR_INVALID_VALUE, "invalid frequency");
}
if (g_Config.iLockedCPUSpeed > 0) {
return hleLogDebug(SCEMISC, 0, "locked by user config at %i", g_Config.iLockedCPUSpeed / 2);
}
// The value passed is validated, but then doesn't seem to matter for the result.
// However, this sets a different hz than scePowerSetClockFrequency would have.
if (pllFreq <= 190)
busFreq = 94956673;
else if (pllFreq <= 222)
busFreq = 111000000;
else if (pllFreq <= 266)
busFreq = 132939331;
else if (pllFreq <= 333)
busFreq = 165848343;
else
busFreq = pllFreq / 2;
return hleLogSuccessI(SCEMISC, 0);
}
static u32 scePowerGetCpuClockFrequencyInt() {
int cpuFreq = CoreTiming::GetClockFrequencyHz() / 1000000;
return hleLogSuccessI(SCEMISC, cpuFreq);
}
static u32 scePowerGetPllClockFrequencyInt() {
return hleLogSuccessInfoI(SCEMISC, pllFreq / 1000000);
}
static u32 scePowerGetBusClockFrequencyInt() {
return hleLogSuccessInfoI(SCEMISC, busFreq / 1000000);
}
static float scePowerGetCpuClockFrequencyFloat() {
float cpuFreq = CoreTiming::GetClockFrequencyHz() / 1000000.0f;
DEBUG_LOG(SCEMISC, "%f=scePowerGetCpuClockFrequencyFloat()", (float)cpuFreq);
return cpuFreq;
}
static float scePowerGetPllClockFrequencyFloat() {
INFO_LOG(SCEMISC, "%f=scePowerGetPllClockFrequencyFloat()", (float)pllFreq / 1000000.0f);
return (float) pllFreq / 1000000.0f;
}
static float scePowerGetBusClockFrequencyFloat() {
INFO_LOG(SCEMISC, "%f=scePowerGetBusClockFrequencyFloat()", (float)busFreq / 1000000.0f);
return (float) busFreq / 1000000.0f;
}
static int scePowerTick() {
DEBUG_LOG(SCEMISC, "scePowerTick()");
// Don't think we need to do anything.
return 0;
}
static u32 IsPSPNonFat() {
DEBUG_LOG(SCEMISC, "%d=scePower_a85880d0_IsPSPNonFat()", g_Config.iPSPModel);
return g_Config.iPSPModel;
}
static const HLEFunction scePower[] = {
{0X04B7766E, &WrapI_II<scePowerRegisterCallback>, "scePowerRegisterCallback", 'i', "ii" },
{0X2B51FE2F, nullptr, "scePower_2B51FE2F", '?', "" },
{0X442BFBAC, nullptr, "scePowerGetBacklightMaximum", '?', "" },
{0XEFD3C963, &WrapI_V<scePowerTick>, "scePowerTick", 'i', "" },
{0XEDC13FE5, nullptr, "scePowerGetIdleTimer", '?', "" },
{0X7F30B3B1, nullptr, "scePowerIdleTimerEnable", '?', "" },
{0X972CE941, nullptr, "scePowerIdleTimerDisable", '?', "" },
{0X27F3292C, nullptr, "scePowerBatteryUpdateInfo", '?', "" },
{0XE8E4E204, nullptr, "scePower_E8E4E204", '?', "" },
{0XB999184C, nullptr, "scePowerGetLowBatteryCapacity", '?', "" },
{0X87440F5E, &WrapI_V<scePowerIsPowerOnline>, "scePowerIsPowerOnline", 'i', "" },
{0X0AFD0D8B, &WrapI_V<scePowerIsBatteryExist>, "scePowerIsBatteryExist", 'i', "" },
{0X1E490401, &WrapI_V<scePowerIsBatteryCharging>, "scePowerIsBatteryCharging", 'i', "" },
{0XB4432BC8, &WrapI_V<scePowerGetBatteryChargingStatus>, "scePowerGetBatteryChargingStatus", 'i', "" },
{0XD3075926, &WrapI_V<scePowerIsLowBattery>, "scePowerIsLowBattery", 'i', "" },
{0X78A1A796, nullptr, "scePowerIsSuspendRequired", '?', "" },
{0X94F5A53F, nullptr, "scePowerGetBatteryRemainCapacity", '?', "" },
{0XFD18A0FF, nullptr, "scePowerGetBatteryFullCapacity", '?', "" },
{0X2085D15D, &WrapI_V<scePowerGetBatteryLifePercent>, "scePowerGetBatteryLifePercent", 'i', "" },
{0X8EFB3FA2, &WrapI_V<scePowerGetBatteryLifeTime>, "scePowerGetBatteryLifeTime", 'i', "" },
{0X28E12023, &WrapI_V<scePowerGetBatteryTemp>, "scePowerGetBatteryTemp", 'i', "" },
{0X862AE1A6, nullptr, "scePowerGetBatteryElec", '?', "" },
{0X483CE86B, nullptr, "scePowerGetBatteryVolt", '?', "" },
{0XCB49F5CE, nullptr, "scePowerGetBatteryChargeCycle", '?', "" },
{0X23436A4A, nullptr, "scePowerGetInnerTemp", '?', "" },
{0X0CD21B1F, nullptr, "scePowerSetPowerSwMode", '?', "" },
{0X165CE085, nullptr, "scePowerGetPowerSwMode", '?', "" },
{0XD6D016EF, nullptr, "scePowerLock", '?', "" },
{0XCA3D34C1, nullptr, "scePowerUnlock", '?', "" },
{0XDB62C9CF, nullptr, "scePowerCancelRequest", '?', "" },
{0X7FA406DD, nullptr, "scePowerIsRequest", '?', "" },
{0X2B7C7CF4, nullptr, "scePowerRequestStandby", '?', "" },
{0XAC32C9CC, nullptr, "scePowerRequestSuspend", '?', "" },
{0X2875994B, nullptr, "scePower_2875994B", '?', "" },
{0X0074EF9B, nullptr, "scePowerGetResumeCount", '?', "" },
{0XDFA8BAF8, &WrapI_I<scePowerUnregisterCallback>, "scePowerUnregisterCallback", 'i', "i" },
{0XDB9D28DD, &WrapI_I<scePowerUnregisterCallback>, "scePowerUnregitserCallback", 'i', "i" },
{0X843FBF43, &WrapU_U<scePowerSetCpuClockFrequency>, "scePowerSetCpuClockFrequency", 'x', "x" },
{0XB8D7B3FB, &WrapU_U<scePowerSetBusClockFrequency>, "scePowerSetBusClockFrequency", 'x', "x" },
{0XFEE03A2F, &WrapU_V<scePowerGetCpuClockFrequencyInt>, "scePowerGetCpuClockFrequency", 'x', "" },
{0X478FE6F5, &WrapU_V<scePowerGetBusClockFrequencyInt>, "scePowerGetBusClockFrequency", 'x', "" },
{0XFDB5BFE9, &WrapU_V<scePowerGetCpuClockFrequencyInt>, "scePowerGetCpuClockFrequencyInt", 'x', "" },
{0XBD681969, &WrapU_V<scePowerGetBusClockFrequencyInt>, "scePowerGetBusClockFrequencyInt", 'x', "" },
{0XB1A52C83, &WrapF_V<scePowerGetCpuClockFrequencyFloat>, "scePowerGetCpuClockFrequencyFloat", 'f', "" },
{0X9BADB3EB, &WrapF_V<scePowerGetBusClockFrequencyFloat>, "scePowerGetBusClockFrequencyFloat", 'f', "" },
{0X737486F2, &WrapU_UUU<scePowerSetClockFrequency>, "scePowerSetClockFrequency", 'x', "xxx"},
{0X34F9C463, &WrapU_V<scePowerGetPllClockFrequencyInt>, "scePowerGetPllClockFrequencyInt", 'x', "" },
{0XEA382A27, &WrapF_V<scePowerGetPllClockFrequencyFloat>, "scePowerGetPllClockFrequencyFloat", 'f', "" },
{0XEBD177D6, &WrapU_UUU<scePowerSetClockFrequency>, "scePower_EBD177D6", 'x', "xxx"}, // This is also the same as SetClockFrequency
{0X469989AD, &WrapU_UUU<scePowerSetClockFrequency>, "scePower_469989ad", 'x', "xxx"}, // This is also the same as SetClockFrequency
{0X545A7F3C, nullptr, "scePower_545A7F3C", '?', "" }, // TODO: Supposedly the same as SetClockFrequency also?
{0XA4E93389, nullptr, "scePower_A4E93389", '?', "" }, // TODO: Supposedly the same as SetClockFrequency also?
{0XA85880D0, &WrapU_V<IsPSPNonFat>, "scePower_a85880d0_IsPSPNonFat", 'x', "" },
{0X3951AF53, nullptr, "scePowerWaitRequestCompletion", '?', "" },
{0X0442D852, nullptr, "scePowerRequestColdReset", '?', "" },
{0XBAFA3DF0, nullptr, "scePowerGetCallbackMode", '?', "" },
{0XA9D22232, nullptr, "scePowerSetCallbackMode", '?', "" },
// These seem to be aliases.
{0X23C31FFE, &WrapI_IUU<sceKernelVolatileMemLock>, "scePowerVolatileMemLock", 'i', "ixx"},
{0XFA97A599, &WrapI_IUU<sceKernelVolatileMemTryLock>, "scePowerVolatileMemTryLock", 'i', "ixx"},
{0XB3EDD801, &WrapI_I<sceKernelVolatileMemUnlock>, "scePowerVolatileMemUnlock", 'i', "i" },
};
//890129c in tyshooter looks bogus
const HLEFunction sceSuspendForUser[] = {
{0XEADB1BD7, &WrapI_I<sceKernelPowerLock>, "sceKernelPowerLock", 'i', "i" }, //(int param) set param to 0
{0X3AEE7261, &WrapI_I<sceKernelPowerUnlock>, "sceKernelPowerUnlock", 'i', "i" }, //(int param) set param to 0
{0X090CCB3F, &WrapI_I<sceKernelPowerTick>, "sceKernelPowerTick", 'i', "i" },
// There's an extra 4MB that can be allocated, which seems to be "volatile". These functions
// let you grab it.
{0XA14F40B2, &WrapI_IUU<sceKernelVolatileMemTryLock>, "sceKernelVolatileMemTryLock", 'i', "ixx"},
{0XA569E425, &WrapI_I<sceKernelVolatileMemUnlock>, "sceKernelVolatileMemUnlock", 'i', "i" },
{0X3E0271D3, &WrapI_IUU<sceKernelVolatileMemLock>, "sceKernelVolatileMemLock", 'i', "ixx"},
};
void Register_scePower() {
RegisterModule("scePower",ARRAY_SIZE(scePower),scePower);
}
void Register_sceSuspendForUser() {
RegisterModule("sceSuspendForUser", ARRAY_SIZE(sceSuspendForUser), sceSuspendForUser);
}