/*
 * Copyright 2024 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "drivers/flash.h"
#include "flash_region/flash_region.h"

#include <stdint.h>

static void prv_erase_optimal_range(uint32_t min_start, uint32_t max_start,
                                    uint32_t min_end, uint32_t max_end) {
  // We want to erase the sector that starts immediately below max_start but after min_start. If no sector
  // boundary exists between the two, we need to start erasing sectors after min_start and backfill with
  // subsector erases.
  int32_t sector_start = (max_start & SECTOR_ADDR_MASK);
  int32_t subsector_start = (max_start & SUBSECTOR_ADDR_MASK);
  if (sector_start < (int32_t) min_start) {
    sector_start += SECTOR_SIZE_BYTES;
  }

  // We want to erase ending after min_end but before max_end. If that ends running past the end of max_end,
  // we need to erase starting with the sector before and fill in with subsector erases.
  int32_t sector_end = ((min_end - 1) & SECTOR_ADDR_MASK) + SECTOR_SIZE_BYTES;
  int32_t subsector_end = ((min_end - 1) & SUBSECTOR_ADDR_MASK) + SUBSECTOR_SIZE_BYTES;
  if (sector_end > (int32_t) max_end) {
    sector_end -= SECTOR_SIZE_BYTES;
  }

  int erase_count = 0;

  // Do the upkeep immediately just in case we've spent awhile running without feeding the
  // watchdog before doing this erase operation.

  if (sector_start < sector_end) {
    // Now erase the leading subsectors...
    for (int32_t i = subsector_start; i < sector_start; i += SUBSECTOR_SIZE_BYTES) {
      flash_erase_subsector_blocking(i);
    }

    // Erase the full sectors...
    for (int32_t i = sector_start; i < sector_end; i += SECTOR_SIZE_BYTES) {
      flash_erase_sector_blocking(i);
    }

    // Erase the trailing subsectors
    for (int32_t i = sector_end; i < subsector_end; i += SUBSECTOR_SIZE_BYTES) {
      flash_erase_subsector_blocking(i);
    }
  } else {
    // Can't erase any full sectors, just erase subsectors the whole way.
    for (int32_t i = subsector_start; i < subsector_end; i += SUBSECTOR_SIZE_BYTES) {
      flash_erase_subsector_blocking(i);
    }
  }
}

void flash_region_erase_optimal_range(uint32_t min_start, uint32_t max_start,
                                      uint32_t min_end, uint32_t max_end) {
  prv_erase_optimal_range(min_start, max_start, min_end, max_end);
}

void flash_region_erase_optimal_range_no_watchdog(uint32_t min_start, uint32_t max_start,
                                                  uint32_t min_end, uint32_t max_end) {
  prv_erase_optimal_range(min_start, max_start, min_end, max_end);
}
