/*
 * Copyright (C) 2020-2021 Aloysius Indrayanto
 *
 * This file is part of the eCxx library, see LICENSE file for the license details.
 */


#ifndef INCLUDE_ECXX_PLATFORM_SPECIFIC_AVR_COMMON_HWINFO_CPP
#error "this file cannot be compiled directly"
#endif
#undef INCLUDE_ECXX_PLATFORM_SPECIFIC_AVR_COMMON_HWINFO_CPP


//
// Variables with "C" linkage
//
extern "C" unsigned int  __data_load_start;
extern "C" unsigned int  __data_load_end;

extern "C" unsigned int  __data_start;
extern "C" unsigned int  __data_end;
extern "C" unsigned int  __bss_start;
extern "C" unsigned int  __bss_end;

extern "C" unsigned int* __malloc_heap_start;
extern "C" unsigned int* __brkval;

extern "C" unsigned int  __malloc_margin;


namespace eCxx {


//
// A simple hardware information class
//
PGMCharPointer HWInfo::eCxxVersion() noexcept
{
    static const char PROGMEM eCxxVersionString[] = ECXX_LIBRARY_VERSION_STRING;

    return eCxx::PGM_PTR(eCxxVersionString);
}


PGMCharPointer HWInfo::compilerVersion() noexcept
{
    static const char PROGMEM compilerVersionString[] = ECXX_COMPILER_VERSION_STRING;

    return eCxx::PGM_PTR(compilerVersionString);
}


PGMCharPointer HWInfo::deviceName() noexcept
{
    static const char PROGMEM deviceName[] = STRINGIFY(__AVR_DEVICE_NAME__);

    return eCxx::PGM_PTR(deviceName);
}


uint32_t HWInfo::chipSignature() noexcept
{
    return ( static_cast<uint32_t>(SIGNATURE_2) << 24 ) |
           ( static_cast<uint32_t>(SIGNATURE_1) << 16 ) |
           ( static_cast<uint32_t>(SIGNATURE_0) <<  8 );
}


void HWInfo::chipId(uint8_t (&id)[10]) noexcept
{
#ifndef __AVR_ARCH_X__

    // Not all AVR MCUs have chip identifier data
    #ifdef SIGRD
    for(uint8_t i = 0; i < 10; ++i) id[i] = boot_signature_byte_get(0x0E + i);
    #else
    for(uint8_t i = 0; i < 10; ++i) id[i] = 0;
    #endif

#else

    for(uint8_t i = 0; i < 10; ++i) id[i] = *(&SIGROW.SERNUM0 + i);

#endif
}


uint32_t HWInfo::cpuFrequency() noexcept
{ return Application::getCPUFrequency(); }


uint16_t HWInfo::cpuFrequency_kHz() noexcept
{
    const uint32_t cpuFreq = Application::getCPUFrequency();
    if(cpuFreq > 65535000UL) return 0;

    return static_cast<uint16_t>(cpuFreq / 1000UL);
}


uint8_t HWInfo::cpuFrequency_MHz() noexcept
{
    const uint32_t cpuFreq = Application::getCPUFrequency();
    if(cpuFreq > 255000000UL) return 0;

    return static_cast<uint8_t>(cpuFreq / 1000000UL);
}


uint32_t HWInfo::totalRAMSize() noexcept
{ return RAM_SIZE; }


uint32_t HWInfo::totalFlashSize() noexcept
{
#ifndef __AVR_ARCH_X__
    return static_cast<uint32_t>(FLASHEND) + 1;
#else
    return PROGMEM_SIZE;
#endif
}


uint32_t HWInfo::totalEEPROMSize() noexcept
{
#ifndef __AVR_ARCH_X__
    return E2END + 1;
#else
    return EEPROM_SIZE;
#endif
}


uint32_t HWInfo::rawNVStorageSize() noexcept
{
#ifndef __AVR_ARCH_X__
    return E2END + 1;
#else
    return (EEPROM_SIZE) + Application::UserRowSize;
#endif
}


bool HWInfo::eepromIsEmulated() noexcept
{ return false; }


uint32_t HWInfo::textSectionSize() noexcept
{ return reinterpret_cast<uint32_t>(&__data_load_start); }


uint32_t HWInfo::dataSectionSize() noexcept
{ return reinterpret_cast<uint32_t>(&__data_end) - reinterpret_cast<uint32_t>(&__data_start); }


uint32_t HWInfo::bssSectionSize() noexcept
{ return reinterpret_cast<uint32_t>(&__bss_end) - reinterpret_cast<uint32_t>(&__bss_start); }


uint32_t HWInfo::usedStackSize() noexcept
{
    char m;
    return RAMEND - reinterpret_cast<uint32_t>(&m);
}


uint32_t HWInfo::usedHeapSize() noexcept
{
    return (__brkval == nullptr)
           ? 0
           : reinterpret_cast<uint32_t>(__brkval) - reinterpret_cast<uint32_t>(__malloc_heap_start);
}


uint32_t HWInfo::freeRAMSize() noexcept
{
    char m;
    return reinterpret_cast<uint32_t>(&m) - ( (__brkval == nullptr)
                                              ? reinterpret_cast<uint32_t>(__malloc_heap_start)
                                              : reinterpret_cast<uint32_t>(__brkval           )
                                            );
}


uint32_t HWInfo::mallocMargin() noexcept
{ return __malloc_margin; }


uint8_t HWInfo::numberOfDigitalPins() noexcept
{ return maxDigitalPinNumber + 1; }


uint8_t HWInfo::numberOfPWMPins() noexcept
{
#ifndef __AVR_ARCH_X__

    constexpr uint8_t numSec = eCxx::arraySize(pwm_pins);

    uint8_t cnt = 0;
    for(uint8_t s = 0; s < numSec; ++s) {
        const uint8_t numPin = eCxx::arraySize(pwm_pins[s]);
        for(uint8_t p = 0; p < numPin; ++p) {
            if(pwm_pins[s][p][1] == ECXX_NOT_PWM) continue;
            ++cnt;
        }
    }

#else

    constexpr uint8_t numIdx = eCxx::arraySize(pin_to_pwm_timer);

    uint8_t cnt = 0;
    for(uint8_t i = 0; i < numIdx; ++i) {
        if( ECXX_PGM_READ_BYTE( eCxx::PGM_PTR(pin_to_pwm_timer, i).p ) != ECXX_NOT_PWM ) ++cnt;
    }

#endif

    return cnt;
}


uint8_t HWInfo::numberOfADCPins() noexcept
{
#ifndef __AVR_ARCH_X__

    return maxADCPinNumber - minADCPinNumber + 1;

#else

    constexpr uint8_t numIdx = eCxx::arraySize(pin_to_adc_channel);

    uint8_t cnt = 0;
    for(uint8_t i = 0; i < numIdx; ++i) {
        if( ECXX_PGM_READ_BYTE( eCxx::PGM_PTR(pin_to_adc_channel, i).p ) != ECXX_NOT_ADC ) ++cnt;
    }

    return cnt;

#endif
}


} // namespace eCxx
