/* RetroArch - A frontend for libretro. * Copyright (C) 2010-2014 - Hans-Kristian Arntzen * Copyright (C) 2011-2017 - Daniel De Matteis * Copyright (C) 2019-2020 - James Leaver * Copyright (C) 2022-2022 - Jahed Ahmed * * RetroArch 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 Found- * ation, either version 3 of the License, or (at your option) any later version. * * RetroArch 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 RetroArch. * If not, see . */ #include #include #include #if defined(RS90) #include #endif #include #include "dingux_utils.h" #define DINGUX_ALLOW_DOWNSCALING_FILE "/sys/devices/platform/jz-lcd.0/allow_downscaling" #define DINGUX_KEEP_ASPECT_RATIO_FILE "/sys/devices/platform/jz-lcd.0/keep_aspect_ratio" #define DINGUX_INTEGER_SCALING_FILE "/sys/devices/platform/jz-lcd.0/integer_scaling" #define DINGUX_SHARPNESS_UPSCALING_FILE "/sys/devices/platform/jz-lcd.0/sharpness_upscaling" #define DINGUX_SHARPNESS_DOWNSCALING_FILE "/sys/devices/platform/jz-lcd.0/sharpness_downscaling" #define DINGUX_BATTERY_CAPACITY_FILE "/sys/class/power_supply/battery/capacity" /* Base path defines */ #define DINGUX_HOME_ENVAR "HOME" #define DINGUX_BASE_DIR "retroarch" #define DINGUX_BASE_DIR_HIDDEN ".retroarch" #define DINGUX_RS90_MEDIA_PATH "/media" #define DINGUX_RS90_DEFAULT_SD_PATH "/media/mmcblk0p1" #define DINGUX_RS90_DATA_PATH "/media/data" /* OpenDingux Beta defines */ #define DINGUX_BATTERY_VOLTAGE_MIN "/sys/class/power_supply/jz-battery/voltage_min_design" #define DINGUX_BATTERY_VOLTAGE_MAX "/sys/class/power_supply/jz-battery/voltage_max_design" #define DINGUX_BATTERY_VOLTAGE_NOW "/sys/class/power_supply/jz-battery/voltage_now" #define DINGUX_SCALING_MODE_ENVAR "SDL_VIDEO_KMSDRM_SCALING_MODE" #define DINGUX_SCALING_SHARPNESS_ENVAR "SDL_VIDEO_KMSDRM_SCALING_SHARPNESS" #define DINGUX_VIDEO_REFRESHRATE_ENVAR "SDL_VIDEO_REFRESHRATE" /* Miyoo defines */ #define MIYOO_BATTERY_VOLTAGE_NOW_FILE "/sys/class/power_supply/miyoo-battery/voltage_now" /* RetroFW */ #define RETROFW_BATTERY_VOLTAGE_NOW_FILE "/proc/jz/battery" /* Enables/disables downscaling when using * the IPU hardware scaler */ bool dingux_ipu_set_downscaling_enable(bool enable) { #if !defined(DINGUX_BETA) const char *path = DINGUX_ALLOW_DOWNSCALING_FILE; const char *enable_str = enable ? "1" : "0"; /* Check whether file exists */ if (!path_is_valid(path)) return false; /* Write enable state to file */ if (!filestream_write_file( path, enable_str, 1)) return false; #endif return true; } /* Sets the video scaling mode when using the * IPU hardware scaler * - keep_aspect: When 'true', aspect ratio correction * (1:1 PAR) is applied. When 'false', image is * stretched to full screen dimensions * - integer_scale: When 'true', enables integer * scaling. This implicitly sets keep_aspect to * 'true' (since integer scaling is by definition * 1:1 PAR) * Note: OpenDingux stock firmware allows keep_aspect * and integer_scale to be set independently, hence * separate boolean values. OpenDingux beta properly * groups the settings into a single scaling type * parameter. When supporting both firmwares, it would * be cleaner to refactor this function to accept one * enum rather than 2 booleans - but this would break * users' existing configs, so we maintain the old * format... */ bool dingux_ipu_set_scaling_mode(bool keep_aspect, bool integer_scale) { #if defined(DINGUX_BETA) const char *scaling_str = "0"; /* integer_scale takes priority */ if (integer_scale) scaling_str = "2"; else if (keep_aspect) scaling_str = "1"; return (setenv(DINGUX_SCALING_MODE_ENVAR, scaling_str, 1) == 0); #else const char *keep_aspect_path = DINGUX_KEEP_ASPECT_RATIO_FILE; const char *keep_aspect_str = keep_aspect ? "1" : "0"; bool keep_aspect_success = false; const char *integer_scale_path = DINGUX_INTEGER_SCALING_FILE; const char *integer_scale_str = integer_scale ? "1" : "0"; bool integer_scale_success = false; /* Set keep_aspect */ if (path_is_valid(keep_aspect_path)) keep_aspect_success = filestream_write_file( keep_aspect_path, keep_aspect_str, 1); /* Set integer_scale */ if (path_is_valid(integer_scale_path)) integer_scale_success = filestream_write_file( integer_scale_path, integer_scale_str, 1); return (keep_aspect_success && integer_scale_success); #endif } /* Sets the image filtering method when * using the IPU hardware scaler */ bool dingux_ipu_set_filter_type(enum dingux_ipu_filter_type filter_type) { /* Sharpness settings range is [0,32] * - 0: nearest-neighbour * - 1: bilinear * - 2...32: bicubic (translating to a sharpness * factor of -0.25..-4.0 internally) * Default bicubic sharpness factor is * (-0.125 * 8) = -1.0 */ #if !defined(DINGUX_BETA) const char *upscaling_path = DINGUX_SHARPNESS_UPSCALING_FILE; const char *downscaling_path = DINGUX_SHARPNESS_DOWNSCALING_FILE; bool upscaling_success = false; bool downscaling_success = false; #endif const char *sharpness_str = "8"; /* Check filter type */ switch (filter_type) { case DINGUX_IPU_FILTER_BILINEAR: sharpness_str = "1"; break; case DINGUX_IPU_FILTER_NEAREST: sharpness_str = "0"; break; default: /* sharpness_str is already set to 8 * by default */ break; } #if defined(DINGUX_BETA) return (setenv(DINGUX_SCALING_SHARPNESS_ENVAR, sharpness_str, 1) == 0); #else /* Set upscaling sharpness */ if (path_is_valid(upscaling_path)) upscaling_success = filestream_write_file( upscaling_path, sharpness_str, 1); /* Set downscaling sharpness */ if (path_is_valid(downscaling_path)) downscaling_success = filestream_write_file( downscaling_path, sharpness_str, 1); return (upscaling_success && downscaling_success); #endif } #if defined(DINGUX_BETA) /* Sets the refresh rate of the integral LCD panel. * If specified value is invalid, will set refresh * rate to 60 Hz. * Returns a floating point representation of the * resultant hardware refresh rate. In the event * that a refresh rate cannot be set (i.e. hardware * error), returns 0.0 */ float dingux_set_video_refresh_rate(enum dingux_refresh_rate refresh_rate) { float refresh_rate_float = 60.0f; const char *refresh_rate_str = "60"; /* Check filter type */ if (refresh_rate == DINGUX_REFRESH_RATE_50HZ) { refresh_rate_float = 50.0f; refresh_rate_str = "50"; } if (setenv(DINGUX_VIDEO_REFRESHRATE_ENVAR, refresh_rate_str, 1) != 0) return 0.0f; return refresh_rate_float; } /* Gets the currently set refresh rate of the * integral LCD panel. */ bool dingux_get_video_refresh_rate(enum dingux_refresh_rate *refresh_rate) { const char *refresh_rate_str = getenv(DINGUX_VIDEO_REFRESHRATE_ENVAR); /* If environment variable is unset, refresh * rate defaults to 60 Hz */ if (!refresh_rate_str) *refresh_rate = DINGUX_REFRESH_RATE_60HZ; else if (string_is_equal(refresh_rate_str, "60")) *refresh_rate = DINGUX_REFRESH_RATE_60HZ; else if (string_is_equal(refresh_rate_str, "50")) *refresh_rate = DINGUX_REFRESH_RATE_50HZ; else return false; return true; } #endif /* Resets the IPU hardware scaler to the * default configuration */ bool dingux_ipu_reset(void) { #if defined(DINGUX_BETA) unsetenv(DINGUX_SCALING_MODE_ENVAR); unsetenv(DINGUX_SCALING_SHARPNESS_ENVAR); unsetenv(DINGUX_VIDEO_REFRESHRATE_ENVAR); #else if (!dingux_ipu_set_scaling_mode(true, false)) return false; if (!dingux_ipu_set_filter_type(DINGUX_IPU_FILTER_BICUBIC)) return false; #endif return true; } #if defined(RETROFW) static uint64_t read_battery_ignore_size(const char *path) { int64_t file_len = 0; char file_buf[20]; int sys_file_value = 0; RFILE *file; /* Check whether file exists */ if (!path_is_valid(path)) return -1; memset(file_buf, 0, sizeof(file_buf)); if (!(file = filestream_open(path, RETRO_VFS_FILE_ACCESS_READ, RETRO_VFS_FILE_ACCESS_HINT_NONE))) return -1; file_len = filestream_read(file, file_buf, sizeof(file_buf) - 1); if (filestream_close(file) != 0) if (file) free(file); if (file_len <= 0) return -1; return strtoul(file_buf, NULL, 10); } int retrofw_get_battery_level(enum frontend_powerstate *state) { /* retrofw battery only provides "voltage_now". Values are based on gmenu2x with some interpolation */ uint32_t rawval = read_battery_ignore_size(RETROFW_BATTERY_VOLTAGE_NOW_FILE); int voltage_now = rawval & 0x7fffffff; if (voltage_now > 10000) { *state = FRONTEND_POWERSTATE_NONE; return -1; } if (rawval & 0x80000000) { *state = FRONTEND_POWERSTATE_CHARGING; if (voltage_now > 4000) *state = FRONTEND_POWERSTATE_CHARGED; } else *state = FRONTEND_POWERSTATE_ON_POWER_SOURCE; if (voltage_now < 0) return -1; /* voltage_now not available */ if (voltage_now > 4000) return 100; if (voltage_now > 3700) return 40 + (voltage_now - 3700) / 5; if (voltage_now > 3520) return 20 + (voltage_now - 3520) / 9; if (voltage_now > 3330) return 1 + (voltage_now - 3330) * 10; return 0; } #else static int dingux_read_battery_sys_file(const char *path) { int64_t file_len = 0; char *file_buf = NULL; int sys_file_value = 0; /* Check whether file exists */ if (!path_is_valid(path)) return -1; /* Read file */ if (!filestream_read_file(path, (void**)&file_buf, &file_len) || (file_len == 0) || !file_buf) { if (file_buf) { free(file_buf); file_buf = NULL; } return -1; } /* Convert to integer */ sys_file_value = atoi(file_buf); free(file_buf); file_buf = NULL; return sys_file_value; } /* Fetches internal battery level */ int dingux_get_battery_level(void) { #if defined(DINGUX_BETA) /* Taken from https://github.com/OpenDingux/gmenu2x/blob/master/src/battery.cpp * No 'capacity' file in sysfs - Do a dumb approximation of the capacity * using the current voltage reported and the min/max voltages of the * battery */ int voltage_min = 0; int voltage_max = 0; int voltage_now = 0; voltage_min = dingux_read_battery_sys_file(DINGUX_BATTERY_VOLTAGE_MIN); if (voltage_min < 0) return -1; voltage_max = dingux_read_battery_sys_file(DINGUX_BATTERY_VOLTAGE_MAX); if (voltage_max < 0) return -1; voltage_now = dingux_read_battery_sys_file(DINGUX_BATTERY_VOLTAGE_NOW); if (voltage_now < 0) return -1; if ( (voltage_max <= voltage_min) || (voltage_now < voltage_min)) return -1; return (int)(((voltage_now - voltage_min) * 100) / (voltage_max - voltage_min)); #elif defined(MIYOO) /* miyoo-battery only provides "voltage_now". Results are based on * value distribution while running a game at max load. */ int voltage_now = dingux_read_battery_sys_file(MIYOO_BATTERY_VOLTAGE_NOW_FILE); if (voltage_now < 0) return -1; /* voltage_now not available */ if (voltage_now > 4300) return 100; /* 4320 */ if (voltage_now > 4200) return 90; /* 4230 */ if (voltage_now > 4100) return 80; /* 4140 */ if (voltage_now > 4000) return 70; /* 4050 */ if (voltage_now > 3900) return 60; /* 3960 */ if (voltage_now > 3800) return 50; /* 3870 */ if (voltage_now > 3700) return 40; /* 3780 */ if (voltage_now > 3600) return 30; /* 3690 */ if (voltage_now > 3550) return 20; /* 3600 */ if (voltage_now > 3500) return 10; /* 3510 */ if (voltage_now > 3400) return 5; /* 3420 */ if (voltage_now > 3300) return 1; /* 3330 */ return 0; /* 3240 */ #else return dingux_read_battery_sys_file(DINGUX_BATTERY_CAPACITY_FILE); #endif } #endif /* Fetches the path of the base 'retroarch' * directory */ void dingux_get_base_path(char *path, size_t len) { const char *home = NULL; #if defined(RS90) struct string_list *dir_list = NULL; #endif if (!path || (len < 1)) return; #if defined(RS90) /* The RS-90 home directory is located on the * device's internal storage. This has limited * space (a total of only 256MB), such that it * is impractical to store cores and user files * here. We therefore attempt to use a base * path on the external microsd card */ /* Get list of directories in /media */ if ((dir_list = dir_list_new(DINGUX_RS90_MEDIA_PATH, NULL, true, true, false, false))) { size_t i; bool path_found = false; for (i = 0; i < dir_list->size; i++) { const char *dir_path = dir_list->elems[i].data; int dir_type = dir_list->elems[i].attr.i; /* Skip files and invalid entries */ if ( (dir_type != RARCH_DIRECTORY) || string_is_empty(dir_path) || string_is_equal(dir_path, DINGUX_RS90_DATA_PATH)) continue; /* Build 'retroarch' subdirectory path */ snprintf(path, len, "%s%c%s", dir_path, PATH_DEFAULT_SLASH_C(), DINGUX_BASE_DIR); /* We can use this subdirectory path if: * - Directory corresponds to an unlabelled * microsd card * - Subdirectory already exists */ if ( string_is_equal(dir_path, DINGUX_RS90_DEFAULT_SD_PATH) || path_is_directory(path)) { path_found = true; break; } } dir_list_free(dir_list); if (path_found) return; } #endif /* Get home directory * * If a home directory is found (which should * always be the case), base path is "$HOME/.retroarch" * > If home path is unset, use existing UNIX frontend * driver default of "retroarch" (this will ultimately * fail, but there is nothing else we can do...) */ if ((home = getenv(DINGUX_HOME_ENVAR))) snprintf(path, len, "%s%c%s", home, PATH_DEFAULT_SLASH_C(), DINGUX_BASE_DIR_HIDDEN); else strlcpy(path, DINGUX_BASE_DIR, len); }