mirror of
https://gitee.com/anod/open_agb_firm.git
synced 2025-05-08 23:04:13 +08:00
247 lines
7.6 KiB
C
247 lines
7.6 KiB
C
/*
|
|
* This file is part of libn3ds
|
|
* Copyright (C) 2023 profi200
|
|
*
|
|
* 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 3 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 <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include "arm11/drivers/hw_cal.h"
|
|
#include "fs.h"
|
|
|
|
|
|
alignas(4) CodecCalBase g_cdcCal =
|
|
{
|
|
.driverGainHp = 0, // 0 dB.
|
|
.driverGainSp = 1, // 12 dB.
|
|
.analogVolumeHp = 0, // 0 dB.
|
|
.analogVolumeSp = 7, // -3.5 dB.
|
|
.shutterVolumeI2s1 = -3,
|
|
.shutterVolumeI2s2 = -20,
|
|
.microphoneBias = 3,
|
|
.quickCharge = 2,
|
|
.pgaGain = 0,
|
|
.padding = {0, 0, 0},
|
|
// Note:
|
|
// The sample rate used for all filters
|
|
// is the word clock of the I2S line.
|
|
.filterHp32 = // I2S1.
|
|
{
|
|
{CDC_SWAP_BIQUAD( 32767, 0, 0, 0, 0)},
|
|
{CDC_SWAP_BIQUAD( 32767, 0, 0, 0, 0)},
|
|
{CDC_SWAP_BIQUAD( 32736, -16368, 0, 16352, 0)} // First order 10 Hz high-pass at 32730 Hz.
|
|
},
|
|
.filterHp47 = // I2S2.
|
|
{
|
|
{CDC_SWAP_BIQUAD( 32767, 0, 0, 0, 0)},
|
|
{CDC_SWAP_BIQUAD( 32767, 0, 0, 0, 0)},
|
|
{CDC_SWAP_BIQUAD( 32745, -16372, 0, 16361, 0)} // First order 10 Hz high-pass at 47610 Hz.
|
|
},
|
|
.filterSp32 = // I2S1.
|
|
{
|
|
{CDC_SWAP_BIQUAD( 32767, -27535, 22413, 30870, -29096)}, // Customized peak filter with negative offset?
|
|
{CDC_SWAP_BIQUAD(-14000, 30000, -14000, 0, 0)}, // Customized high shelf filter?
|
|
{CDC_SWAP_BIQUAD( 32736, -16368, 0, 16352, 0)} // First order 10 Hz high-pass at 32730 Hz.
|
|
},
|
|
.filterSp47 = // I2S2.
|
|
{
|
|
{CDC_SWAP_BIQUAD( 32767, -28995, 25277, 31456, -30200)}, // Customized peak filter with negative offset?
|
|
{CDC_SWAP_BIQUAD(-14402, 30000, -14402, 0, 0)}, // Customized high shelf filter?
|
|
{CDC_SWAP_BIQUAD( 32745, -16372, 0, 16361, 0)} // First order 10 Hz high-pass at 47610 Hz.
|
|
},
|
|
.filterMic32 = // I2S1?
|
|
{
|
|
{CDC_SWAP_IIR( 32767, 0, 0)},
|
|
{CDC_SWAP_BIQUAD( 32767, 0, 0, 0, 0)},
|
|
{CDC_SWAP_BIQUAD( 32767, 0, 0, 0, 0)},
|
|
{CDC_SWAP_BIQUAD( 32767, 0, 0, 0, 0)},
|
|
{CDC_SWAP_BIQUAD( 32767, 0, 0, 0, 0)},
|
|
{CDC_SWAP_BIQUAD( 32767, 0, 0, 0, 0)}
|
|
},
|
|
.filterMic47 = // I2S2?
|
|
{
|
|
{CDC_SWAP_IIR( 32767, 0, 0)},
|
|
{CDC_SWAP_BIQUAD( 32767, 0, 0, 0, 0)},
|
|
{CDC_SWAP_BIQUAD( 32767, 0, 0, 0, 0)},
|
|
{CDC_SWAP_BIQUAD( 32767, 0, 0, 0, 0)},
|
|
{CDC_SWAP_BIQUAD( 32767, 0, 0, 0, 0)},
|
|
{CDC_SWAP_BIQUAD( 32767, 0, 0, 0, 0)}
|
|
},
|
|
.filterFree = // All I2S lines.
|
|
{
|
|
{CDC_SWAP_IIR( 32767, 0, 0)},
|
|
{CDC_SWAP_BIQUAD(-12959, -8785, 32767, 8785, 12959)}, // all-pass: Fc 2500 Hz, Q 0.1 at 32730(?) Hz.
|
|
{CDC_SWAP_BIQUAD(-12959, -8785, 32767, 8785, 12959)}, // all-pass: Fc 2500 Hz, Q 0.1 at 32730(?) Hz.
|
|
{CDC_SWAP_BIQUAD(-12959, -8785, 32767, 8785, 12959)}, // all-pass: Fc 2500 Hz, Q 0.1 at 32730(?) Hz.
|
|
{CDC_SWAP_BIQUAD( 32767, 0, 0, 0, 0)},
|
|
{CDC_SWAP_BIQUAD( 32767, 0, 0, 0, 0)}
|
|
},
|
|
.analogInterval = 1,
|
|
.analogStabilize = 9,
|
|
.analogPrecharge = 4,
|
|
.analogSense = 3,
|
|
.analogDebounce = 0,
|
|
.analogXpPullup = 6,
|
|
.ymDriver = 1,
|
|
.reserved = 0
|
|
};
|
|
|
|
// These defaults are identical to launch day o3DS calibration.
|
|
BacklightPwmCalBase g_blPwmCal =
|
|
{
|
|
.coeffs =
|
|
{
|
|
{0.00111639f, 1.41412f, 0.07178809f}, // Bottom LCD.
|
|
{0.000418169f, 0.66567f, 0.06098654f}, // Top LCD in 2D mode.
|
|
{0.00208543f, 1.55639f, 0.0385939f} // Top LCD in 3D mode.
|
|
},
|
|
.numLevels = 5,
|
|
.unknown = 0,
|
|
.lumLevels = {20, 43, 73, 95, 117, 172, 172},
|
|
.hwBrightnessBase = 512,
|
|
.hwBrightnessMin = 13
|
|
};
|
|
|
|
static u32 g_calLoadedMask = 0;
|
|
|
|
|
|
|
|
// Based on code from cfg module plus an optimization idea eleminating 2 table lookups from here:
|
|
// https://github.com/LacobusVentura/MODBUS-CRC16/blob/master/MODBUS_CRC16.c#L131
|
|
static u16 reverseCrc16Modbus(const u16 init, const void *src, u32 size)
|
|
{
|
|
u32 crc = init;
|
|
const u8 *ptr8 = (const u8*)src;
|
|
|
|
static const u16 crc16Table[16] =
|
|
{
|
|
0x0000, 0xCC01, 0xD801, 0x1400,
|
|
0xF001, 0x3C00, 0x2800, 0xE401,
|
|
0xA001, 0x6C00, 0x7800, 0xB401,
|
|
0x5000, 0x9C01, 0x8801, 0x4400
|
|
};
|
|
|
|
do
|
|
{
|
|
const u32 data = *ptr8++;
|
|
crc = (crc>>4) ^ crc16Table[(data ^ crc) & 15u];
|
|
crc = (crc>>4) ^ crc16Table[((data>>4) ^ crc) & 15u];
|
|
} while(--size > 0);
|
|
|
|
return crc;
|
|
}
|
|
|
|
static void endianSwapCodecFilter(u16 *filt, u32 entries)
|
|
{
|
|
do
|
|
{
|
|
const u16 tmp = __builtin_bswap16(*filt);
|
|
*filt++ = tmp;
|
|
} while(--entries > 0);
|
|
}
|
|
|
|
static void updateCals(const Hwcal *const hwcal)
|
|
{
|
|
// Check and update CODEC calibration.
|
|
const u16 agingPassedMask = hwcal->agingPassedMask;
|
|
u32 calLoadedMask = 0;
|
|
if(agingPassedMask & CAL_MASK_CODEC)
|
|
{
|
|
const u16 crc16 = reverseCrc16Modbus(0x55AA, &hwcal->codec, CAL_CRC_OFFSET(hwcal->codec));
|
|
if(hwcal->codec.crc16 == crc16)
|
|
{
|
|
// First copy data raw and then endian swap filters.
|
|
// Stored in little endian in HWCAL but CODEC wants them in big endian.
|
|
memcpy(&g_cdcCal, &hwcal->codec, sizeof(g_cdcCal));
|
|
endianSwapCodecFilter((u16*)&g_cdcCal.filterHp32, sizeof(g_cdcCal.filterHp32) / 2);
|
|
endianSwapCodecFilter((u16*)&g_cdcCal.filterHp47, sizeof(g_cdcCal.filterHp47) / 2);
|
|
endianSwapCodecFilter((u16*)&g_cdcCal.filterSp32, sizeof(g_cdcCal.filterSp32) / 2);
|
|
endianSwapCodecFilter((u16*)&g_cdcCal.filterSp47, sizeof(g_cdcCal.filterSp47) / 2);
|
|
endianSwapCodecFilter((u16*)&g_cdcCal.filterMic32, sizeof(g_cdcCal.filterMic32) / 2);
|
|
endianSwapCodecFilter((u16*)&g_cdcCal.filterMic47, sizeof(g_cdcCal.filterMic47) / 2);
|
|
endianSwapCodecFilter((u16*)&g_cdcCal.filterFree, sizeof(g_cdcCal.filterFree) / 2);
|
|
|
|
calLoadedMask = CAL_MASK_CODEC;
|
|
}
|
|
}
|
|
|
|
// Check and update backlight PWM calibration.
|
|
if(agingPassedMask & CAL_MASK_BACKLIGHT_PWM)
|
|
{
|
|
const u16 crc16 = reverseCrc16Modbus(0x55AA, &hwcal->blPwn, CAL_CRC_OFFSET(hwcal->blPwn));
|
|
if(hwcal->blPwn.crc16 == crc16)
|
|
{
|
|
memcpy(&g_blPwmCal, &hwcal->blPwn, sizeof(g_blPwmCal));
|
|
calLoadedMask |= CAL_MASK_BACKLIGHT_PWM;
|
|
}
|
|
}
|
|
|
|
// Keep track of successfully updated calibrations.
|
|
g_calLoadedMask = calLoadedMask;
|
|
}
|
|
|
|
// TODO: If we dump HWCAL to SD give the file an unique name in case the SD is shared between consoles.
|
|
Result HWCAL_load(void)
|
|
{
|
|
// Try to open the HWCAL file.
|
|
FHandle f;
|
|
Result res = fOpen(&f, "sdmc:/3ds/HWCAL.dat", FA_OPEN_EXISTING | FA_READ);
|
|
if(res != RES_OK) return res;
|
|
|
|
// Allocate memory for the file.
|
|
Hwcal *const hwcal = malloc(sizeof(Hwcal));
|
|
if(hwcal == NULL)
|
|
{
|
|
fClose(f);
|
|
return RES_OUT_OF_MEM;
|
|
}
|
|
|
|
// Load the whole file.
|
|
u32 bytesRead;
|
|
res = fRead(f, hwcal, sizeof(Hwcal), &bytesRead);
|
|
if(res != RES_OK)
|
|
{
|
|
fClose(f);
|
|
free(hwcal);
|
|
return res;
|
|
}
|
|
if(bytesRead != sizeof(Hwcal))
|
|
{
|
|
fClose(f);
|
|
free(hwcal);
|
|
return RES_OUT_OF_RANGE;
|
|
}
|
|
|
|
fClose(f);
|
|
|
|
// Check HWCAL magic.
|
|
if(hwcal->magic != HWCAL_MAGIC)
|
|
{
|
|
free(hwcal);
|
|
return RES_INVALID_ARG;
|
|
}
|
|
|
|
// Check and load all calibration data.
|
|
updateCals(hwcal);
|
|
|
|
free(hwcal);
|
|
|
|
return res;
|
|
}
|
|
|
|
u32 HWCAL_getLoadedMask(void)
|
|
{
|
|
return g_calLoadedMask;
|
|
} |