summaryrefslogtreecommitdiffstats
path: root/platforms/chibios/drivers/wear_leveling/wear_leveling_efl.c
blob: cdd1e26a7d945f1a09598ca65743eeffacc1e4b9 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
// Copyright 2022 Nick Brassel (@tzarc)
// SPDX-License-Identifier: GPL-2.0-or-later
#include <stdbool.h>
#include <hal.h>
#include "timer.h"
#include "wear_leveling.h"
#include "wear_leveling_internal.h"

static flash_offset_t base_offset = UINT32_MAX;

#if defined(WEAR_LEVELING_EFL_FIRST_SECTOR)
static flash_sector_t first_sector = WEAR_LEVELING_EFL_FIRST_SECTOR;
#else  // defined(WEAR_LEVELING_EFL_FIRST_SECTOR)
static flash_sector_t first_sector = UINT16_MAX;
#endif // defined(WEAR_LEVELING_EFL_FIRST_SECTOR)

static flash_sector_t sector_count = UINT16_MAX;
static BaseFlash *    flash;

#ifndef WEAR_LEVELING_EFL_FIRST_SECTOR
// "Automatic" detection of the flash size -- ideally ChibiOS would have this already, but alas, it doesn't.
static inline uint32_t detect_flash_size(void) {
#    if defined(WEAR_LEVELING_EFL_FLASH_SIZE)
    return WEAR_LEVELING_EFL_FLASH_SIZE;
#    elif defined(FLASH_BANK_SIZE)
    return FLASH_BANK_SIZE;
#    elif defined(FLASH_SIZE)
    return FLASH_SIZE;
#    elif defined(FLASHSIZE_BASE)
#        if defined(QMK_MCU_SERIES_STM32F0XX) || defined(QMK_MCU_SERIES_STM32F1XX) || defined(QMK_MCU_SERIES_STM32F3XX) || defined(QMK_MCU_SERIES_STM32F4XX) || defined(QMK_MCU_SERIES_STM32G4XX) || defined(QMK_MCU_SERIES_STM32L0XX) || defined(QMK_MCU_SERIES_STM32L4XX) || defined(QMK_MCU_SERIES_GD32VF103)
    return ((*(uint32_t *)FLASHSIZE_BASE) & 0xFFFFU) << 10U; // this register has the flash size in kB, so we convert it to bytes
#        elif defined(QMK_MCU_SERIES_STM32L1XX)
#            error This MCU family has an uncommon flash size register definition and has not been implemented. Perhaps try using the true EEPROM on the MCU instead?
#        endif
#    else
#        error Unknown flash size definition.
    return 0;
#    endif
}
#endif // WEAR_LEVELING_EFL_FIRST_SECTOR

bool backing_store_init(void) {
    bs_dprintf("Init\n");
    flash = (BaseFlash *)&EFLD1;

    // Need to re-lock the EFL, as if we've just had the bootloader executing it'll already be unlocked.
    backing_store_lock();

    const flash_descriptor_t *desc    = flashGetDescriptor(flash);
    uint32_t                  counter = 0;

#if defined(WEAR_LEVELING_EFL_FIRST_SECTOR)

    // Work out how many sectors we want to use, working forwards from the first sector specified
    for (flash_sector_t i = 0; i < desc->sectors_count - first_sector; ++i) {
        counter += flashGetSectorSize(flash, first_sector + i);
        if (counter >= (WEAR_LEVELING_BACKING_SIZE)) {
            sector_count = i + 1;
            base_offset  = flashGetSectorOffset(flash, first_sector);
            break;
        }
    }
    if (sector_count == UINT16_MAX || base_offset >= flash_size) {
        // We didn't get the required number of sectors. Can't do anything here. Fault.
        chSysHalt("Invalid sector count intended to be used with wear_leveling");
    }

#else // defined(WEAR_LEVELING_EFL_FIRST_SECTOR)

    // Work out how many sectors we want to use, working backwards from the end of the flash
    uint32_t       flash_size  = detect_flash_size();
    flash_sector_t last_sector = desc->sectors_count;
    for (flash_sector_t i = 0; i < desc->sectors_count; ++i) {
        first_sector = desc->sectors_count - i - 1;
        if (flashGetSectorOffset(flash, first_sector) >= flash_size) {
            last_sector = first_sector;
            continue;
        }
        counter += flashGetSectorSize(flash, first_sector);
        if (counter >= (WEAR_LEVELING_BACKING_SIZE)) {
            sector_count = last_sector - first_sector;
            base_offset  = flashGetSectorOffset(flash, first_sector);
            break;
        }
    }

#endif // defined(WEAR_LEVELING_EFL_FIRST_SECTOR)

    return true;
}

bool backing_store_unlock(void) {
    bs_dprintf("Unlock\n");
    return eflStart(&EFLD1, NULL) == HAL_RET_SUCCESS;
}

bool backing_store_erase(void) {
#ifdef WEAR_LEVELING_DEBUG_OUTPUT
    uint32_t start = timer_read32();
#endif

    bool          ret = true;
    flash_error_t status;
    for (int i = 0; i < sector_count; ++i) {
        // Kick off the sector erase
        status = flashStartEraseSector(flash, first_sector + i);
        if (status != FLASH_NO_ERROR && status != FLASH_BUSY_ERASING) {
            ret = false;
        }

        // Wait for the erase to complete
        status = flashWaitErase(flash);
        if (status != FLASH_NO_ERROR && status != FLASH_BUSY_ERASING) {
            ret = false;
        }
    }

    bs_dprintf("Backing store erase took %ldms to complete\n", ((long)(timer_read32() - start)));
    return ret;
}

bool backing_store_write(uint32_t address, backing_store_int_t value) {
    uint32_t offset = (base_offset + address);
    bs_dprintf("Write ");
    wl_dump(offset, &value, sizeof(value));
    value = ~value;
    return flashProgram(flash, offset, sizeof(value), (const uint8_t *)&value) == FLASH_NO_ERROR;
}

bool backing_store_lock(void) {
    bs_dprintf("Lock  \n");
    eflStop(&EFLD1);
    return true;
}

bool backing_store_read(uint32_t address, backing_store_int_t *value) {
    uint32_t             offset = (base_offset + address);
    backing_store_int_t *loc    = (backing_store_int_t *)flashGetOffsetAddress(flash, offset);
    *value                      = ~(*loc);
    bs_dprintf("Read  ");
    wl_dump(offset, value, sizeof(backing_store_int_t));
    return true;
}