blob: 7439b46d24b9f06646e8caf8000970e5ad2fa94e [file] [log] [blame]
/****************************************************************************
* arch/arm/src/lc823450/lc823450_dvfs2.c
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership. The
* ASF licenses this file to you 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.
*
****************************************************************************/
/****************************************************************************
* Included Files
****************************************************************************/
#include <nuttx/config.h>
#include <nuttx/arch.h>
#include <nuttx/spinlock.h>
#include <nuttx/clock.h>
#include <arch/board/board.h>
#include <string.h>
#include "arm_internal.h"
#include "lc823450_clockconfig.h"
#include "lc823450_syscontrol.h"
#include "lc823450_intc.h"
#include "lc823450_sdc.h"
#include "lc823450_gpio.h"
#include "lc823450_timer.h"
#include "lc823450_dvfs2.h"
/****************************************************************************
* Pre-processor Definitions
****************************************************************************/
#define FREQ_160 0
#define FREQ_080 1
#define FREQ_040 2
#define FREQ_KP 2
#define FREQ_UP 1
#define FREQ_DN 0
#define UP_THRESHOLD 20
#define DN_THRESHOLD 60
#ifdef CONFIG_DVFS_CHANGE_VOLTAGE
# define CORE12V_PIN (GPIO_PORT2 | GPIO_PIN1)
#endif
#ifndef CONFIG_RTC_HIRES
# error "Should be enabled to get high-accuracy for CPU idle time"
#endif
/****************************************************************************
* Private Data
****************************************************************************/
typedef struct freq_entry
{
uint16_t freq;
uint16_t pll1;
uint16_t mdiv;
uint16_t hdiv;
} t_freq_entry;
static struct freq_entry _dvfs_act_tbl[3] =
{
{ 160, OSCCNT_MCSEL, OSCCNT_MAINDIV_1, 3}, /* PLL1 */
{ 80, OSCCNT_MCSEL, OSCCNT_MAINDIV_2, 1}, /* PLL1 */
{ 40, OSCCNT_MCSEL, OSCCNT_MAINDIV_4, 0}, /* PLL1 */
};
static struct freq_entry _dvfs_idl_tbl[3] =
{
{ 24, 0, OSCCNT_MAINDIV_1, 0}, /* XT1 */
{ 12, 0, OSCCNT_MAINDIV_2, 0}, /* XT1 */
{ 6, 0, OSCCNT_MAINDIV_4, 0}, /* XT1 */
};
static uint16_t _dvfs_cur_idx = 0; /* current speed index */
static uint16_t _dvfs_cur_hdiv = 3;
static uint16_t _dvfs_cur_mdiv = OSCCNT_MAINDIV_1;
static uint16_t _dvfs_core12v = 0;
static uint8_t _dvfs_cpu_is_active[CONFIG_SMP_NCPUS];
static void lc823450_dvfs_set_div(int idx, int tbl);
static uint64_t g_idle_starttime[CONFIG_SMP_NCPUS];
static uint64_t g_idle_totaltime[CONFIG_SMP_NCPUS];
static uint64_t g_idle_totaltime0[CONFIG_SMP_NCPUS];
/****************************************************************************
* Public Data
****************************************************************************/
int8_t g_dvfs_enabled = 0;
int8_t g_dvfs_auto = 0;
uint16_t g_dvfs_cur_freq = 160;
uint32_t g_dvfs_freq_stat[3] =
{
0, 0, 0
};
/****************************************************************************
* Private Functions
****************************************************************************/
/****************************************************************************
* Name: _get_current_time64()
****************************************************************************/
static uint64_t _get_current_time64(void)
{
struct timespec ts;
clock_systime_timespec(&ts);
return (uint64_t)ts.tv_sec * NSEC_PER_SEC + (uint64_t)ts.tv_nsec;
}
/****************************************************************************
* Name: lc823450_dvfs_update_lpm
****************************************************************************/
static void lc823450_dvfs_update_lpm(int freq)
{
/* TODO */
}
#if CONFIG_SMP_NCPUS == 2
static int _dvfs_another_cpu_state(int me)
{
if (0 == me)
{
return _dvfs_cpu_is_active[1];
}
else
{
return _dvfs_cpu_is_active[0];
}
}
#endif
/****************************************************************************
* Name: lc823450_dvfs_oneshot
* Callback for 1 shot timer
****************************************************************************/
int lc823450_dvfs_oneshot(int irq, uint32_t *regs, void *arg)
{
/* voltage has reached at 1.2V */
_dvfs_core12v = 1;
lc823450_mtm_stop_oneshot();
/* go to max freq */
lc823450_dvfs_set_div(FREQ_160, 0);
return 0;
}
/****************************************************************************
* Name: lc832450_set_core_voltage
* high=true (1.2V), high=false (1.0V)
****************************************************************************/
#ifdef CONFIG_DVFS_CHANGE_VOLTAGE
static void lc832450_set_core_voltage(bool high)
{
if (false == high)
{
_dvfs_core12v = 0;
}
lc823450_gpio_write(CORE12V_PIN, (int)high);
if (high)
{
/* start 3ms timer to change internal dividers */
lc823450_mtm_start_oneshot(3);
}
}
#endif
/****************************************************************************
* Name: lc823450_dvfs_set_div
* Set dividers in the OSC block
* target tbl: 0=active, 1=idle
****************************************************************************/
static void lc823450_dvfs_set_div(int idx, int tbl)
{
uint32_t target;
uint32_t t_hdiv;
uint32_t t_mdiv;
if (0 == tbl)
{
target = _dvfs_act_tbl[idx].freq;
t_hdiv = _dvfs_act_tbl[idx].hdiv;
t_mdiv = _dvfs_act_tbl[idx].mdiv;
}
else
{
target = _dvfs_idl_tbl[idx].freq;
t_hdiv = _dvfs_idl_tbl[idx].hdiv;
t_mdiv = _dvfs_idl_tbl[idx].mdiv;
}
#ifdef CONFIG_DVFS_CHANGE_VOLTAGE
if (100 < target && !_dvfs_core12v)
{
/* Set high voltage (i.e. 1.2v) */
lc832450_set_core_voltage(true);
return;
}
#endif
if (100 < target)
{
/* Set ROM wait cycle (CPU=1wait) */
modifyreg32(MEMEN4, 0, MEMEN4_HWAIT);
}
/* adjust AHB */
if (t_hdiv > _dvfs_cur_hdiv)
{
uint32_t pclkdiv = t_hdiv;
#ifdef CONFIG_LC823450_SDRAM
pclkdiv += (t_hdiv << 16);
#endif
putreg32(pclkdiv, PERICLKDIV);
}
uint32_t regval = getreg32(OSCCNT);
/* NOTE: In LC823450, MCSEL is reflected first then MAINDIV
* To avoid spec violation, 2-step clock change is needed
*/
/* step 1 : change MAINDIV if needed */
if (t_mdiv > _dvfs_cur_mdiv)
{
regval &= ~OSCCNT_MAINDIV_MASK;
regval |= t_mdiv;
/* change the MAINDIV first */
putreg32(regval, OSCCNT);
}
/* step 2 : change MCSEL and MAINDIV */
regval = getreg32(OSCCNT);
regval &= ~(OSCCNT_MCSEL | OSCCNT_MAINDIV_MASK);
if (0 == tbl)
{
regval |= _dvfs_act_tbl[idx].pll1;
}
else
{
regval |= _dvfs_idl_tbl[idx].pll1;
}
regval |= t_mdiv;
/* set MCSEL and MAINDIV again */
putreg32(regval, OSCCNT);
/* update loops_per_msec for up_udelay(), up_mdelay() */
if (0 == tbl)
{
lc823450_dvfs_update_lpm(_dvfs_act_tbl[idx].freq);
}
else
{
lc823450_dvfs_update_lpm(_dvfs_idl_tbl[idx].freq);
}
/* adjust AHB */
if (t_hdiv < _dvfs_cur_hdiv)
{
uint32_t pclkdiv = t_hdiv;
#ifdef CONFIG_LC823450_SDRAM
pclkdiv += (t_hdiv << 16);
#endif
putreg32(pclkdiv, PERICLKDIV);
}
_dvfs_cur_idx = idx;
_dvfs_cur_hdiv = t_hdiv;
_dvfs_cur_mdiv = t_mdiv;
g_dvfs_cur_freq = target;
#ifdef CONFIG_DVFS_CHANGE_VOLTAGE
/* NOTE: check the index instead of the target freq */
if (_dvfs_core12v && _dvfs_cur_idx != FREQ_160)
{
/* set to lower voltage (i.e. 1.0v) */
lc832450_set_core_voltage(false);
}
#endif
if (100 > target)
{
/* Clear ROM wait cycle (CPU=0wait) */
modifyreg32(MEMEN4, MEMEN4_HWAIT, 0);
}
}
/****************************************************************************
* Name: lc823450_dvfs_change_idx
* up: 1=clock up, 0=clock down
* called in autonomous mode from interrupt context
****************************************************************************/
static void lc823450_dvfs_change_idx(int up)
{
uint16_t idx = _dvfs_cur_idx;
/* NOTE: clock index is in reverse order to the speed */
/* clock up */
if (up && (FREQ_160 < idx))
{
idx--;
}
/* clock down */
if (!up && (FREQ_040 > idx))
{
idx++;
}
#ifdef CONFIG_DVFS_EXCLUDE_40M
if (idx == FREQ_040)
{
return;
}
#endif
if (idx != _dvfs_cur_idx)
{
lc823450_dvfs_set_div(idx, 0);
}
}
/****************************************************************************
* Name: lc823450_dvfs_do_auto
****************************************************************************/
static void lc823450_dvfs_do_auto(uint32_t idle[])
{
uint32_t state[CONFIG_SMP_NCPUS];
int i;
for (i = 0; i < CONFIG_SMP_NCPUS; i++)
{
if (UP_THRESHOLD >= idle[i])
{
state[i] = FREQ_UP;
}
else if (DN_THRESHOLD <= idle[i])
{
state[i] = FREQ_DN;
}
else
{
state[i] = FREQ_KP;
}
}
#if CONFIG_SMP_NCPUS == 1
if (FREQ_UP == state[0])
#elif CONFIG_SMP_NCPUS == 2
if (FREQ_UP == state[0] || FREQ_UP == state[1])
#endif
{
lc823450_dvfs_change_idx(FREQ_UP);
}
#if CONFIG_SMP_NCPUS == 1
if (FREQ_DN == state[0])
#elif CONFIG_SMP_NCPUS == 2
if (FREQ_DN == state[0] && FREQ_DN == state[1])
#endif
{
lc823450_dvfs_change_idx(FREQ_DN);
}
}
/****************************************************************************
* Public Functions
****************************************************************************/
/****************************************************************************
* Name: lc823450_dvfs_get_idletime
****************************************************************************/
void lc823450_dvfs_get_idletime(uint64_t idletime[])
{
irqstate_t flags = spin_lock_irqsave(NULL);
/* First, copy g_idle_totaltime to the caller */
memcpy(idletime, g_idle_totaltime, sizeof(g_idle_totaltime));
#if CONFIG_SMP_NCPUS == 2
int me = up_cpu_index();
if (0 == _dvfs_another_cpu_state(me))
{
/* Another CPU is in idle, so consider this situation */
int cpu = (me == 0) ? 1 : 0;
idletime[cpu] += (_get_current_time64() - g_idle_starttime[cpu]);
/* NOTE: g_idletotaltime[cpu] must not be updated */
}
#endif
spin_unlock_irqrestore(NULL, flags);
}
/****************************************************************************
* Name: lc823450_get_apb
* Assumption: CPU=APB
****************************************************************************/
uint32_t lc823450_get_apb(void)
{
return g_dvfs_cur_freq * 1000000;
}
/****************************************************************************
* Name: lc823450_dvfs_tick_callback
* This callback is called in the timer interrupt on CPU0
****************************************************************************/
void lc823450_dvfs_tick_callback(void)
{
uint64_t tmp_idle_total[CONFIG_SMP_NCPUS];
uint32_t idle[CONFIG_SMP_NCPUS];
int i;
if (g_dvfs_enabled && g_dvfs_auto)
{
lc823450_dvfs_get_idletime(tmp_idle_total);
/* Calculate idle ratio for each CPU */
for (i = 0; i < CONFIG_SMP_NCPUS; i++)
{
idle[i] = 100 *
(tmp_idle_total[i] - g_idle_totaltime0[i]) / NSEC_PER_TICK;
}
/* Update g_idle_totaltime0 */
memcpy(g_idle_totaltime0, tmp_idle_total, sizeof(tmp_idle_total));
/* Do autonomous mode */
lc823450_dvfs_do_auto(idle);
}
/* Update frequency statistics */
g_dvfs_freq_stat[_dvfs_cur_idx]++;
}
/****************************************************************************
* Name: lc823450_dvfs_enter_idle
****************************************************************************/
void lc823450_dvfs_enter_idle(void)
{
irqstate_t flags = spin_lock_irqsave(NULL);
int me = up_cpu_index();
/* Update my state first : 0 (idle) */
_dvfs_cpu_is_active[me] = 0;
/* Update my idle start time */
g_idle_starttime[me] = _get_current_time64();
if (0 == g_dvfs_enabled)
{
goto exit_with_error;
}
#if CONFIG_SMP_NCPUS == 2
/* check if another core is still active */
if (_dvfs_another_cpu_state(me))
{
/* do not change to idle clock */
goto exit_with_error;
}
#endif
#ifdef CONFIG_DVFS_CHECK_SDC
if (lc823450_sdc_locked())
{
goto exit_with_error;
}
#endif
/* NOTE: set idle freq : idx=same, change:tbl */
lc823450_dvfs_set_div(_dvfs_cur_idx, 1);
exit_with_error:
spin_unlock_irqrestore(NULL, flags);
}
/****************************************************************************
* Name: lc823450_dvfs_exit_idle
* This API is called in arm_ack_irq() (i.e. interrupt context)
****************************************************************************/
void lc823450_dvfs_exit_idle(int irq)
{
irqstate_t flags = spin_lock_irqsave(NULL);
int me = up_cpu_index();
uint64_t d;
uint64_t now;
if (0 == g_dvfs_enabled)
{
goto exit_with_error;
}
#if CONFIG_SMP_NCPUS == 2
/* Check if another core is already active */
if (_dvfs_another_cpu_state(me))
{
/* do nothing */
goto exit_with_error;
}
#endif
/* NOTE: set active freq : idx=same, change:tbl */
lc823450_dvfs_set_div(_dvfs_cur_idx, 0);
exit_with_error:
if (0 == _dvfs_cpu_is_active[me])
{
/* In case of idle to active transition
* Accumulate idle total time on this CPU
*/
now = _get_current_time64();
d = now - g_idle_starttime[me];
g_idle_totaltime[me] += d;
}
/* Finally update my state : 1 (active) */
_dvfs_cpu_is_active[me] = 1;
spin_unlock_irqrestore(NULL, flags);
}
/****************************************************************************
* Name: lc823450_dvfs_boost
* boost the system clock to MAX (i.e. 160M)
* timeout in msec
****************************************************************************/
int lc823450_dvfs_boost(int timeout)
{
/* TODO */
return 0;
}
/****************************************************************************
* Name: lc823450_dvfs_set_freq
* NOTE: should be called from dvfs command only
****************************************************************************/
int lc823450_dvfs_set_freq(int freq)
{
int ret = 0;
int idx;
irqstate_t flags;
if (0 == g_dvfs_enabled)
{
return -1;
}
flags = spin_lock_irqsave(NULL);
switch (freq)
{
case 160:
idx = FREQ_160;
break;
case 80:
idx = FREQ_080;
break;
#ifndef CONFIG_DVFS_EXCLUDE_40M
case 40:
idx = FREQ_040;
break;
#endif
default:
ret = -1;
break;
}
if (0 == ret)
{
lc823450_dvfs_set_div(idx, 0);
}
spin_unlock_irqrestore(NULL, flags);
return ret;
}