summaryrefslogblamecommitdiffstats
path: root/bsps/arm/atsam/start/power-wait.c
blob: 8d49f6f78b07761786b08da781dd52461dc71b55 (plain) (tree)


































































































































































































                                                                               
/*
 * SPDX-License-Identifier: BSD-2-Clause
 *
 * Copyright (C) 2019 embedded brains GmbH
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include <bsp/power.h>
#include <bsp/linker-symbols.h>

#include <libchip/chip.h>

BSP_FAST_TEXT_SECTION static void
pmc_wait_for_master_clock_ready(volatile Pmc *pmc)
{
  while ((pmc->PMC_SR & PMC_SR_MCKRDY) == 0) {
    /* Wait */
  }
}

BSP_FAST_TEXT_SECTION static void
pmc_set_master_clock_source(volatile Pmc *pmc, uint32_t mckr)
{
  pmc->PMC_MCKR =
    (pmc->PMC_MCKR & ~PMC_MCKR_CSS_Msk) | (mckr & PMC_MCKR_CSS_Msk);
}

BSP_FAST_TEXT_SECTION static void
pmc_set_master_clock_prescaler(volatile Pmc *pmc, uint32_t mckr)
{
  pmc->PMC_MCKR =
    (pmc->PMC_MCKR & ~PMC_MCKR_PRES_Msk) | (mckr & PMC_MCKR_PRES_Msk);
}

BSP_FAST_TEXT_SECTION static void
pmc_set_master_clock_division(volatile Pmc *pmc, uint32_t mckr)
{
  pmc->PMC_MCKR =
    (pmc->PMC_MCKR & ~PMC_MCKR_MDIV_Msk) | (mckr & PMC_MCKR_MDIV_Msk);
}

BSP_FAST_TEXT_SECTION static void
pmc_wait_for_main_rc_osc(volatile Pmc *pmc)
{
  while ((pmc->PMC_SR & PMC_SR_MOSCRCS) == 0) {
    /* Wait */
  }
}

BSP_FAST_TEXT_SECTION static void
pmc_wait_for_main_osc_selection(volatile Pmc *pmc)
{
  while ((pmc->PMC_SR & PMC_SR_MOSCSELS) == 0) {
    /* Wait */
  }
}

BSP_FAST_TEXT_SECTION static void
pmc_use_main_rc_osc_4mhz(volatile Pmc *pmc)
{
  uint32_t ckgr_mor;

  ckgr_mor = pmc->CKGR_MOR;
  ckgr_mor |= CKGR_MOR_KEY_PASSWD;

  /* Enable main RC oscillator */
  ckgr_mor |= CKGR_MOR_MOSCRCEN;
  PMC->CKGR_MOR = ckgr_mor;
  pmc_wait_for_main_rc_osc(pmc);

  /* Set main RC oscillator frequency to 4MHz */
  ckgr_mor &= ~CKGR_MOR_MOSCRCF_Msk;
  ckgr_mor |= CKGR_MOR_MOSCRCF_4_MHz;
  pmc->CKGR_MOR = ckgr_mor;
  pmc_wait_for_main_rc_osc(pmc);

  /* Switch to main RC oscillator */
  ckgr_mor &= ~CKGR_MOR_MOSCSEL;
  pmc->CKGR_MOR = ckgr_mor;
  pmc_wait_for_main_osc_selection(pmc);
  pmc_wait_for_master_clock_ready(pmc);
}

BSP_FAST_TEXT_SECTION static void
pmc_use_main_ext_osc(volatile Pmc *pmc)
{
  uint32_t ckgr_mor;

  ckgr_mor = pmc->CKGR_MOR;
  ckgr_mor |= CKGR_MOR_KEY_PASSWD;

  /* Enable main external oscillator */
  ckgr_mor |= CKGR_MOR_MOSCRCF_12_MHz | CKGR_MOR_MOSCXTEN | CKGR_MOR_MOSCRCEN |
    CKGR_MOR_MOSCXTST(DEFAUTL_MAIN_OSC_COUNT);
  PMC->CKGR_MOR = ckgr_mor;
  pmc_wait_for_main_rc_osc(pmc);
  pmc_wait_for_master_clock_ready(pmc);

  /* Switch to main external oscillator */
  ckgr_mor |= CKGR_MOR_MOSCSEL;
  pmc->CKGR_MOR = ckgr_mor;
  pmc_wait_for_main_osc_selection(pmc);
  pmc_wait_for_master_clock_ready(pmc);
}

BSP_FAST_TEXT_SECTION void
atsam_power_handler_wait_mode(
  const atsam_power_control *control,
  atsam_power_state state
)
{
  rtems_interrupt_level level;
  volatile Pmc *pmc;
  uint32_t mckr;
  uint32_t fmr;
  uint32_t fsmr;

  (void) control;
  pmc = PMC;

  switch (state) {
    case ATSAM_POWER_OFF:
      rtems_interrupt_disable(level);

      mckr = pmc->PMC_MCKR;

      /* Switch main clock to main RC oscillator (4MHz) */
      pmc_use_main_rc_osc_4mhz(pmc);
      pmc_set_master_clock_source(pmc, PMC_MCKR_CSS_MAIN_CLK);
      pmc_wait_for_master_clock_ready(pmc);
      pmc_set_master_clock_prescaler(pmc, PMC_MCKR_PRES_CLK_1);
      pmc_wait_for_master_clock_ready(pmc);
      pmc_set_master_clock_division(pmc, PMC_MCKR_MDIV_EQ_PCK);
      pmc_wait_for_master_clock_ready(pmc);

      /* Set flash wait state to 0 */
      fmr = EFC->EEFC_FMR;
      EFC->EEFC_FMR = EEFC_FMR_FWS(0);

      /* Enter wait mode */
      fsmr = pmc->PMC_FSMR;
      pmc->PMC_FSMR = fsmr | PMC_FSMR_LPM;
      pmc->CKGR_MOR = CKGR_MOR_KEY_PASSWD | CKGR_MOR_WAITMODE;
      pmc_wait_for_master_clock_ready(pmc);
      __asm__ volatile ( "wfe" : : : "memory" );
      pmc->PMC_FSMR = fsmr;

      /* Restore main clock */
      pmc_set_master_clock_prescaler(pmc, mckr);
      pmc_wait_for_master_clock_ready(pmc);
      pmc_set_master_clock_division(pmc, mckr);
      pmc_wait_for_master_clock_ready(pmc);
      pmc_set_master_clock_source(pmc, mckr);
      pmc_wait_for_master_clock_ready(pmc);
      pmc_use_main_ext_osc(pmc);

      /* Restore flash wait state */
      EFC->EEFC_FMR = fmr;

      rtems_interrupt_enable(level);
      break;
    case ATSAM_POWER_INIT:
      rtems_interrupt_disable(level);

      pmc->PMC_FSMR = (pmc->PMC_FSMR & ~PMC_FSMR_FLPM_Msk)
        | PMC_FSMR_FLPM(PMC_FSMR_FLPM_FLASH_DEEP_POWERDOWN);

      /*  No Cortex-M7 Deep Sleep mode (Backup Mode) */
      SCB->SCR &= ~SCB_SCR_SLEEPDEEP_Msk;

      rtems_interrupt_enable(level);
    default:
      break;
  }
}