mirror of
git://projects.qi-hardware.com/openwrt-xburst.git
synced 2024-12-01 22:35:20 +02:00
603 lines
15 KiB
C
603 lines
15 KiB
C
|
/*
|
||
|
* linux/arch/mips/jz4740/cpufreq.c
|
||
|
*
|
||
|
* cpufreq driver for JZ4740
|
||
|
*
|
||
|
* Copyright (c) 2006-2007 Ingenic Semiconductor Inc.
|
||
|
* Author: <lhhuang@ingenic.cn>
|
||
|
*
|
||
|
* This program is free software; you can redistribute it and/or modify
|
||
|
* it under the terms of the GNU General Public License version 2 as
|
||
|
* published by the Free Software Foundation.
|
||
|
*/
|
||
|
|
||
|
#include <linux/kernel.h>
|
||
|
#include <linux/module.h>
|
||
|
#include <linux/init.h>
|
||
|
|
||
|
#include <linux/cpufreq.h>
|
||
|
|
||
|
#include <asm/jzsoc.h>
|
||
|
#include <asm/processor.h>
|
||
|
|
||
|
#define dprintk(msg...) cpufreq_debug_printk(CPUFREQ_DEBUG_DRIVER, \
|
||
|
"cpufreq-jz4740", msg)
|
||
|
|
||
|
#undef CHANGE_PLL
|
||
|
|
||
|
#define PLL_UNCHANGED 0
|
||
|
#define PLL_GOES_UP 1
|
||
|
#define PLL_GOES_DOWN 2
|
||
|
|
||
|
#define PLL_WAIT_500NS (500*(__cpm_get_cclk()/1000000000))
|
||
|
|
||
|
/* Saved the boot-time parameters */
|
||
|
static struct {
|
||
|
/* SDRAM parameters */
|
||
|
unsigned int mclk; /* memory clock, KHz */
|
||
|
unsigned int tras; /* RAS pulse width, cycles of mclk */
|
||
|
unsigned int rcd; /* RAS to CAS Delay, cycles of mclk */
|
||
|
unsigned int tpc; /* RAS Precharge time, cycles of mclk */
|
||
|
unsigned int trwl; /* Write Precharge Time, cycles of mclk */
|
||
|
unsigned int trc; /* RAS Cycle Time, cycles of mclk */
|
||
|
unsigned int rtcor; /* Refresh Time Constant */
|
||
|
unsigned int sdram_initialized;
|
||
|
|
||
|
/* LCD parameters */
|
||
|
unsigned int lcd_clk; /* LCD clock, Hz */
|
||
|
unsigned int lcdpix_clk; /* LCD Pixel clock, Hz */
|
||
|
unsigned int lcd_clks_initialized;
|
||
|
} boot_config;
|
||
|
|
||
|
struct jz4740_freq_percpu_info {
|
||
|
struct cpufreq_frequency_table table[7];
|
||
|
};
|
||
|
|
||
|
static struct jz4740_freq_percpu_info jz4740_freq_table;
|
||
|
|
||
|
/*
|
||
|
* This contains the registers value for an operating point.
|
||
|
* If only part of a register needs to change then there is
|
||
|
* a mask value for that register.
|
||
|
* When going to a new operating point the current register
|
||
|
* value is ANDed with the ~mask and ORed with the new value.
|
||
|
*/
|
||
|
struct dpm_regs {
|
||
|
u32 cpccr; /* Clock Freq Control Register */
|
||
|
u32 cpccr_mask; /* Clock Freq Control Register mask */
|
||
|
u32 cppcr; /* PLL1 Control Register */
|
||
|
u32 cppcr_mask; /* PLL1 Control Register mask */
|
||
|
u32 pll_up_flag; /* New PLL freq is higher than current or not */
|
||
|
};
|
||
|
|
||
|
extern jz_clocks_t jz_clocks;
|
||
|
|
||
|
static void jz_update_clocks(void)
|
||
|
{
|
||
|
/* Next clocks must be updated if we have changed
|
||
|
* the PLL or divisors.
|
||
|
*/
|
||
|
jz_clocks.cclk = __cpm_get_cclk();
|
||
|
jz_clocks.hclk = __cpm_get_hclk();
|
||
|
jz_clocks.mclk = __cpm_get_mclk();
|
||
|
jz_clocks.pclk = __cpm_get_pclk();
|
||
|
jz_clocks.lcdclk = __cpm_get_lcdclk();
|
||
|
jz_clocks.pixclk = __cpm_get_pixclk();
|
||
|
jz_clocks.i2sclk = __cpm_get_i2sclk();
|
||
|
jz_clocks.usbclk = __cpm_get_usbclk();
|
||
|
jz_clocks.mscclk = __cpm_get_mscclk();
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
jz_init_boot_config(void)
|
||
|
{
|
||
|
if (!boot_config.lcd_clks_initialized) {
|
||
|
/* the first time to scale pll */
|
||
|
boot_config.lcd_clk = __cpm_get_lcdclk();
|
||
|
boot_config.lcdpix_clk = __cpm_get_pixclk();
|
||
|
boot_config.lcd_clks_initialized = 1;
|
||
|
}
|
||
|
|
||
|
if (!boot_config.sdram_initialized) {
|
||
|
/* the first time to scale frequencies */
|
||
|
unsigned int dmcr, rtcor;
|
||
|
unsigned int tras, rcd, tpc, trwl, trc;
|
||
|
|
||
|
dmcr = REG_EMC_DMCR;
|
||
|
rtcor = REG_EMC_RTCOR;
|
||
|
|
||
|
tras = (dmcr >> 13) & 0x7;
|
||
|
rcd = (dmcr >> 11) & 0x3;
|
||
|
tpc = (dmcr >> 8) & 0x7;
|
||
|
trwl = (dmcr >> 5) & 0x3;
|
||
|
trc = (dmcr >> 2) & 0x7;
|
||
|
|
||
|
boot_config.mclk = __cpm_get_mclk() / 1000;
|
||
|
boot_config.tras = tras + 4;
|
||
|
boot_config.rcd = rcd + 1;
|
||
|
boot_config.tpc = tpc + 1;
|
||
|
boot_config.trwl = trwl + 1;
|
||
|
boot_config.trc = trc * 2 + 1;
|
||
|
boot_config.rtcor = rtcor;
|
||
|
|
||
|
boot_config.sdram_initialized = 1;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static void jz_update_dram_rtcor(unsigned int new_mclk)
|
||
|
{
|
||
|
unsigned int rtcor;
|
||
|
|
||
|
new_mclk /= 1000;
|
||
|
rtcor = boot_config.rtcor * new_mclk / boot_config.mclk;
|
||
|
rtcor--;
|
||
|
|
||
|
if (rtcor < 1) rtcor = 1;
|
||
|
if (rtcor > 255) rtcor = 255;
|
||
|
|
||
|
REG_EMC_RTCOR = rtcor;
|
||
|
REG_EMC_RTCNT = rtcor;
|
||
|
}
|
||
|
|
||
|
static void jz_update_dram_dmcr(unsigned int new_mclk)
|
||
|
{
|
||
|
unsigned int dmcr;
|
||
|
unsigned int tras, rcd, tpc, trwl, trc;
|
||
|
unsigned int valid_time, new_time; /* ns */
|
||
|
|
||
|
new_mclk /= 1000;
|
||
|
tras = boot_config.tras * new_mclk / boot_config.mclk;
|
||
|
rcd = boot_config.rcd * new_mclk / boot_config.mclk;
|
||
|
tpc = boot_config.tpc * new_mclk / boot_config.mclk;
|
||
|
trwl = boot_config.trwl * new_mclk / boot_config.mclk;
|
||
|
trc = boot_config.trc * new_mclk / boot_config.mclk;
|
||
|
|
||
|
/* Validation checking */
|
||
|
valid_time = (boot_config.tras * 1000000) / boot_config.mclk;
|
||
|
new_time = (tras * 1000000) / new_mclk;
|
||
|
if (new_time < valid_time) tras += 1;
|
||
|
|
||
|
valid_time = (boot_config.rcd * 1000000) / boot_config.mclk;
|
||
|
new_time = (rcd * 1000000) / new_mclk;
|
||
|
if (new_time < valid_time) rcd += 1;
|
||
|
|
||
|
valid_time = (boot_config.tpc * 1000000) / boot_config.mclk;
|
||
|
new_time = (tpc * 1000000) / new_mclk;
|
||
|
if (new_time < valid_time) tpc += 1;
|
||
|
|
||
|
valid_time = (boot_config.trwl * 1000000) / boot_config.mclk;
|
||
|
new_time = (trwl * 1000000) / new_mclk;
|
||
|
if (new_time < valid_time) trwl += 1;
|
||
|
|
||
|
valid_time = (boot_config.trc * 1000000) / boot_config.mclk;
|
||
|
new_time = (trc * 1000000) / new_mclk;
|
||
|
if (new_time < valid_time) trc += 2;
|
||
|
|
||
|
tras = (tras < 4) ? 4: tras;
|
||
|
tras = (tras > 11) ? 11: tras;
|
||
|
tras -= 4;
|
||
|
|
||
|
rcd = (rcd < 1) ? 1: rcd;
|
||
|
rcd = (rcd > 4) ? 4: rcd;
|
||
|
rcd -= 1;
|
||
|
|
||
|
tpc = (tpc < 1) ? 1: tpc;
|
||
|
tpc = (tpc > 8) ? 8: tpc;
|
||
|
tpc -= 1;
|
||
|
|
||
|
trwl = (trwl < 1) ? 1: trwl;
|
||
|
trwl = (trwl > 4) ? 4: trwl;
|
||
|
trwl -= 1;
|
||
|
|
||
|
trc = (trc < 1) ? 1: trc;
|
||
|
trc = (trc > 15) ? 15: trc;
|
||
|
trc /= 2;
|
||
|
|
||
|
dmcr = REG_EMC_DMCR;
|
||
|
|
||
|
dmcr &= ~(EMC_DMCR_TRAS_MASK | EMC_DMCR_RCD_MASK | EMC_DMCR_TPC_MASK | EMC_DMCR_TRWL_MASK | EMC_DMCR_TRC_MASK);
|
||
|
dmcr |= ((tras << EMC_DMCR_TRAS_BIT) | (rcd << EMC_DMCR_RCD_BIT) | (tpc << EMC_DMCR_TPC_BIT) | (trwl << EMC_DMCR_TRWL_BIT) | (trc << EMC_DMCR_TRC_BIT));
|
||
|
|
||
|
REG_EMC_DMCR = dmcr;
|
||
|
}
|
||
|
|
||
|
static void jz_update_dram_prev(unsigned int cur_mclk, unsigned int new_mclk)
|
||
|
{
|
||
|
/* No risk, no fun: run with interrupts on! */
|
||
|
if (new_mclk > cur_mclk) {
|
||
|
/* We're going FASTER, so first update TRAS, RCD, TPC, TRWL
|
||
|
* and TRC of DMCR before changing the frequency.
|
||
|
*/
|
||
|
jz_update_dram_dmcr(new_mclk);
|
||
|
} else {
|
||
|
/* We're going SLOWER: first update RTCOR value
|
||
|
* before changing the frequency.
|
||
|
*/
|
||
|
jz_update_dram_rtcor(new_mclk);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static void jz_update_dram_post(unsigned int cur_mclk, unsigned int new_mclk)
|
||
|
{
|
||
|
/* No risk, no fun: run with interrupts on! */
|
||
|
if (new_mclk > cur_mclk) {
|
||
|
/* We're going FASTER, so update RTCOR
|
||
|
* after changing the frequency
|
||
|
*/
|
||
|
jz_update_dram_rtcor(new_mclk);
|
||
|
} else {
|
||
|
/* We're going SLOWER: so update TRAS, RCD, TPC, TRWL
|
||
|
* and TRC of DMCR after changing the frequency.
|
||
|
*/
|
||
|
jz_update_dram_dmcr(new_mclk);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static void jz_scale_divisors(struct dpm_regs *regs)
|
||
|
{
|
||
|
unsigned int cpccr;
|
||
|
unsigned int cur_mclk, new_mclk;
|
||
|
int div[] = {1, 2, 3, 4, 6, 8, 12, 16, 24, 32};
|
||
|
unsigned int tmp = 0, wait = PLL_WAIT_500NS;
|
||
|
|
||
|
cpccr = REG_CPM_CPCCR;
|
||
|
cpccr &= ~((unsigned long)regs->cpccr_mask);
|
||
|
cpccr |= regs->cpccr;
|
||
|
cpccr |= CPM_CPCCR_CE; /* update immediately */
|
||
|
|
||
|
cur_mclk = __cpm_get_mclk();
|
||
|
new_mclk = __cpm_get_pllout() / div[(cpccr & CPM_CPCCR_MDIV_MASK) >> CPM_CPCCR_MDIV_BIT];
|
||
|
|
||
|
/* Update some DRAM parameters before changing frequency */
|
||
|
jz_update_dram_prev(cur_mclk, new_mclk);
|
||
|
|
||
|
/* update register to change the clocks.
|
||
|
* align this code to a cache line.
|
||
|
*/
|
||
|
__asm__ __volatile__(
|
||
|
".set noreorder\n\t"
|
||
|
".align 5\n"
|
||
|
"sw %1,0(%0)\n\t"
|
||
|
"li %3,0\n\t"
|
||
|
"1:\n\t"
|
||
|
"bne %3,%2,1b\n\t"
|
||
|
"addi %3, 1\n\t"
|
||
|
"nop\n\t"
|
||
|
"nop\n\t"
|
||
|
"nop\n\t"
|
||
|
"nop\n\t"
|
||
|
".set reorder\n\t"
|
||
|
:
|
||
|
: "r" (CPM_CPCCR), "r" (cpccr), "r" (wait), "r" (tmp));
|
||
|
|
||
|
/* Update some other DRAM parameters after changing frequency */
|
||
|
jz_update_dram_post(cur_mclk, new_mclk);
|
||
|
}
|
||
|
|
||
|
#ifdef CHANGE_PLL
|
||
|
/* Maintain the LCD clock and pixel clock */
|
||
|
static void jz_scale_lcd_divisors(struct dpm_regs *regs)
|
||
|
{
|
||
|
unsigned int new_pll, new_lcd_div, new_lcdpix_div;
|
||
|
unsigned int cpccr;
|
||
|
unsigned int tmp = 0, wait = PLL_WAIT_500NS;
|
||
|
|
||
|
if (!boot_config.lcd_clks_initialized) return;
|
||
|
|
||
|
new_pll = __cpm_get_pllout();
|
||
|
new_lcd_div = new_pll / boot_config.lcd_clk;
|
||
|
new_lcdpix_div = new_pll / boot_config.lcdpix_clk;
|
||
|
|
||
|
if (new_lcd_div < 1)
|
||
|
new_lcd_div = 1;
|
||
|
if (new_lcd_div > 16)
|
||
|
new_lcd_div = 16;
|
||
|
|
||
|
if (new_lcdpix_div < 1)
|
||
|
new_lcdpix_div = 1;
|
||
|
if (new_lcdpix_div > 512)
|
||
|
new_lcdpix_div = 512;
|
||
|
|
||
|
// REG_CPM_CPCCR2 = new_lcdpix_div - 1;
|
||
|
|
||
|
cpccr = REG_CPM_CPCCR;
|
||
|
cpccr &= ~CPM_CPCCR_LDIV_MASK;
|
||
|
cpccr |= ((new_lcd_div - 1) << CPM_CPCCR_LDIV_BIT);
|
||
|
cpccr |= CPM_CPCCR_CE; /* update immediately */
|
||
|
|
||
|
/* update register to change the clocks.
|
||
|
* align this code to a cache line.
|
||
|
*/
|
||
|
__asm__ __volatile__(
|
||
|
".set noreorder\n\t"
|
||
|
".align 5\n"
|
||
|
"sw %1,0(%0)\n\t"
|
||
|
"li %3,0\n\t"
|
||
|
"1:\n\t"
|
||
|
"bne %3,%2,1b\n\t"
|
||
|
"addi %3, 1\n\t"
|
||
|
"nop\n\t"
|
||
|
"nop\n\t"
|
||
|
"nop\n\t"
|
||
|
"nop\n\t"
|
||
|
".set reorder\n\t"
|
||
|
:
|
||
|
: "r" (CPM_CPCCR), "r" (cpccr), "r" (wait), "r" (tmp));
|
||
|
}
|
||
|
|
||
|
static void jz_scale_pll(struct dpm_regs *regs)
|
||
|
{
|
||
|
unsigned int cppcr;
|
||
|
unsigned int cur_mclk, new_mclk, new_pll;
|
||
|
int div[] = {1, 2, 3, 4, 6, 8, 12, 16, 24, 32};
|
||
|
int od[] = {1, 2, 2, 4};
|
||
|
|
||
|
cppcr = REG_CPM_CPPCR;
|
||
|
cppcr &= ~(regs->cppcr_mask | CPM_CPPCR_PLLS | CPM_CPPCR_PLLEN | CPM_CPPCR_PLLST_MASK);
|
||
|
regs->cppcr &= ~CPM_CPPCR_PLLEN;
|
||
|
cppcr |= (regs->cppcr | 0xff);
|
||
|
|
||
|
/* Update some DRAM parameters before changing frequency */
|
||
|
new_pll = JZ_EXTAL * ((cppcr>>23)+2) / ((((cppcr>>18)&0x1f)+2) * od[(cppcr>>16)&0x03]);
|
||
|
cur_mclk = __cpm_get_mclk();
|
||
|
new_mclk = new_pll / div[(REG_CPM_CPCCR>>CPM_CPCCR_MDIV_BIT) & 0xf];
|
||
|
|
||
|
/*
|
||
|
* Update some SDRAM parameters
|
||
|
*/
|
||
|
jz_update_dram_prev(cur_mclk, new_mclk);
|
||
|
|
||
|
/*
|
||
|
* Update PLL, align code to cache line.
|
||
|
*/
|
||
|
cppcr |= CPM_CPPCR_PLLEN;
|
||
|
__asm__ __volatile__(
|
||
|
".set noreorder\n\t"
|
||
|
".align 5\n"
|
||
|
"sw %1,0(%0)\n\t"
|
||
|
"nop\n\t"
|
||
|
"nop\n\t"
|
||
|
"nop\n\t"
|
||
|
"nop\n\t"
|
||
|
"nop\n\t"
|
||
|
"nop\n\t"
|
||
|
"nop\n\t"
|
||
|
".set reorder\n\t"
|
||
|
:
|
||
|
: "r" (CPM_CPPCR), "r" (cppcr));
|
||
|
|
||
|
/* Update some other DRAM parameters after changing frequency */
|
||
|
jz_update_dram_post(cur_mclk, new_mclk);
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
static void jz4740_transition(struct dpm_regs *regs)
|
||
|
{
|
||
|
/*
|
||
|
* Get and save some boot-time conditions.
|
||
|
*/
|
||
|
jz_init_boot_config();
|
||
|
|
||
|
#ifdef CHANGE_PLL
|
||
|
/*
|
||
|
* Disable LCD before scaling pll.
|
||
|
* LCD and LCD pixel clocks should not be changed even if the PLL
|
||
|
* output frequency has been changed.
|
||
|
*/
|
||
|
REG_LCD_CTRL &= ~LCD_CTRL_ENA;
|
||
|
|
||
|
/*
|
||
|
* Stop module clocks before scaling PLL
|
||
|
*/
|
||
|
__cpm_stop_eth();
|
||
|
__cpm_stop_aic(1);
|
||
|
__cpm_stop_aic(2);
|
||
|
#endif
|
||
|
|
||
|
/* ... add more as necessary */
|
||
|
|
||
|
if (regs->pll_up_flag == PLL_GOES_UP) {
|
||
|
/* the pll frequency is going up, so change dividors first */
|
||
|
jz_scale_divisors(regs);
|
||
|
#ifdef CHANGE_PLL
|
||
|
jz_scale_pll(regs);
|
||
|
#endif
|
||
|
}
|
||
|
else if (regs->pll_up_flag == PLL_GOES_DOWN) {
|
||
|
/* the pll frequency is going down, so change pll first */
|
||
|
#ifdef CHANGE_PLL
|
||
|
jz_scale_pll(regs);
|
||
|
#endif
|
||
|
jz_scale_divisors(regs);
|
||
|
}
|
||
|
else {
|
||
|
/* the pll frequency is unchanged, so change divisors only */
|
||
|
jz_scale_divisors(regs);
|
||
|
}
|
||
|
|
||
|
#ifdef CHANGE_PLL
|
||
|
/*
|
||
|
* Restart module clocks before scaling PLL
|
||
|
*/
|
||
|
__cpm_start_eth();
|
||
|
__cpm_start_aic(1);
|
||
|
__cpm_start_aic(2);
|
||
|
|
||
|
/* ... add more as necessary */
|
||
|
|
||
|
/* Scale the LCD divisors after scaling pll */
|
||
|
if (regs->pll_up_flag != PLL_UNCHANGED) {
|
||
|
jz_scale_lcd_divisors(regs);
|
||
|
}
|
||
|
|
||
|
/* Enable LCD controller */
|
||
|
REG_LCD_CTRL &= ~LCD_CTRL_DIS;
|
||
|
REG_LCD_CTRL |= LCD_CTRL_ENA;
|
||
|
#endif
|
||
|
|
||
|
/* Update system clocks */
|
||
|
jz_update_clocks();
|
||
|
}
|
||
|
|
||
|
extern unsigned int idle_times;
|
||
|
static unsigned int jz4740_freq_get(unsigned int cpu)
|
||
|
{
|
||
|
return (__cpm_get_cclk() / 1000);
|
||
|
}
|
||
|
|
||
|
static unsigned int index_to_divisor(unsigned int index, struct dpm_regs *regs)
|
||
|
{
|
||
|
int n2FR[33] = {
|
||
|
0, 0, 1, 2, 3, 0, 4, 0, 5, 0, 0, 0, 6, 0, 0, 0,
|
||
|
7, 0, 0, 0, 0, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0,
|
||
|
9
|
||
|
};
|
||
|
int div[4] = {1, 2, 2, 2}; /* divisors of I:S:P:M */
|
||
|
unsigned int div_of_cclk, new_freq, i;
|
||
|
|
||
|
regs->pll_up_flag = PLL_UNCHANGED;
|
||
|
regs->cpccr_mask = CPM_CPCCR_CDIV_MASK | CPM_CPCCR_HDIV_MASK | CPM_CPCCR_PDIV_MASK | CPM_CPCCR_MDIV_MASK;
|
||
|
|
||
|
new_freq = jz4740_freq_table.table[index].frequency;
|
||
|
|
||
|
do {
|
||
|
div_of_cclk = __cpm_get_pllout() / (1000 * new_freq);
|
||
|
} while (div_of_cclk==0);
|
||
|
|
||
|
if(div_of_cclk == 1 || div_of_cclk == 2 || div_of_cclk == 4) {
|
||
|
for(i = 1; i<4; i++) {
|
||
|
div[i] = 3;
|
||
|
}
|
||
|
} else {
|
||
|
for(i = 1; i<4; i++) {
|
||
|
div[i] = 2;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
for(i = 0; i<4; i++) {
|
||
|
div[i] *= div_of_cclk;
|
||
|
}
|
||
|
|
||
|
dprintk("divisors of I:S:P:M = %d:%d:%d:%d\n", div[0], div[1], div[2], div[3]);
|
||
|
|
||
|
regs->cpccr =
|
||
|
(n2FR[div[0]] << CPM_CPCCR_CDIV_BIT) |
|
||
|
(n2FR[div[1]] << CPM_CPCCR_HDIV_BIT) |
|
||
|
(n2FR[div[2]] << CPM_CPCCR_PDIV_BIT) |
|
||
|
(n2FR[div[3]] << CPM_CPCCR_MDIV_BIT);
|
||
|
|
||
|
return div_of_cclk;
|
||
|
}
|
||
|
|
||
|
static void jz4740_set_cpu_divider_index(unsigned int cpu, unsigned int index)
|
||
|
{
|
||
|
unsigned long divisor, old_divisor;
|
||
|
struct cpufreq_freqs freqs;
|
||
|
struct dpm_regs regs;
|
||
|
|
||
|
old_divisor = __cpm_get_pllout() / __cpm_get_cclk();
|
||
|
divisor = index_to_divisor(index, ®s);
|
||
|
|
||
|
freqs.old = __cpm_get_cclk() / 1000;
|
||
|
freqs.new = __cpm_get_pllout() / (1000 * divisor);
|
||
|
freqs.cpu = cpu;
|
||
|
|
||
|
cpufreq_notify_transition(&freqs, CPUFREQ_PRECHANGE);
|
||
|
|
||
|
if (old_divisor != divisor)
|
||
|
jz4740_transition(®s);
|
||
|
|
||
|
cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE);
|
||
|
}
|
||
|
|
||
|
static int jz4740_freq_target(struct cpufreq_policy *policy,
|
||
|
unsigned int target_freq,
|
||
|
unsigned int relation)
|
||
|
{
|
||
|
unsigned int new_index = 0;
|
||
|
|
||
|
if (cpufreq_frequency_table_target(policy,
|
||
|
&jz4740_freq_table.table[0],
|
||
|
target_freq, relation, &new_index))
|
||
|
return -EINVAL;
|
||
|
|
||
|
jz4740_set_cpu_divider_index(policy->cpu, new_index);
|
||
|
|
||
|
dprintk("new frequency is %d KHz (REG_CPM_CPCCR:0x%x)\n", __cpm_get_cclk() / 1000, REG_CPM_CPCCR);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int jz4740_freq_verify(struct cpufreq_policy *policy)
|
||
|
{
|
||
|
return cpufreq_frequency_table_verify(policy,
|
||
|
&jz4740_freq_table.table[0]);
|
||
|
}
|
||
|
|
||
|
static int __init jz4740_cpufreq_driver_init(struct cpufreq_policy *policy)
|
||
|
{
|
||
|
|
||
|
struct cpufreq_frequency_table *table = &jz4740_freq_table.table[0];
|
||
|
unsigned int MAX_FREQ;
|
||
|
|
||
|
dprintk(KERN_INFO "Jz4740 cpufreq driver\n");
|
||
|
|
||
|
if (policy->cpu != 0)
|
||
|
return -EINVAL;
|
||
|
|
||
|
policy->cur = MAX_FREQ = __cpm_get_cclk() / 1000; /* in kHz. Current and max frequency is determined by u-boot */
|
||
|
policy->governor = CPUFREQ_DEFAULT_GOVERNOR;
|
||
|
|
||
|
policy->cpuinfo.min_freq = MAX_FREQ/8;
|
||
|
policy->cpuinfo.max_freq = MAX_FREQ;
|
||
|
policy->cpuinfo.transition_latency = 100000; /* in 10^(-9) s = nanoseconds */
|
||
|
|
||
|
table[0].index = 0;
|
||
|
table[0].frequency = MAX_FREQ/8;
|
||
|
table[1].index = 1;
|
||
|
table[1].frequency = MAX_FREQ/6;
|
||
|
table[2].index = 2;
|
||
|
table[2].frequency = MAX_FREQ/4;
|
||
|
table[3].index = 3;
|
||
|
table[3].frequency = MAX_FREQ/3;
|
||
|
table[4].index = 4;
|
||
|
table[4].frequency = MAX_FREQ/2;
|
||
|
table[5].index = 5;
|
||
|
table[5].frequency = MAX_FREQ;
|
||
|
table[6].index = 6;
|
||
|
table[6].frequency = CPUFREQ_TABLE_END;
|
||
|
|
||
|
#ifdef CONFIG_CPU_FREQ_STAT_DETAILS
|
||
|
cpufreq_frequency_table_get_attr(table, policy->cpu); /* for showing /sys/devices/system/cpu/cpuX/cpufreq/stats/ */
|
||
|
#endif
|
||
|
|
||
|
return cpufreq_frequency_table_cpuinfo(policy, table);
|
||
|
}
|
||
|
|
||
|
static struct cpufreq_driver cpufreq_jz4740_driver = {
|
||
|
// .flags = CPUFREQ_STICKY,
|
||
|
.init = jz4740_cpufreq_driver_init,
|
||
|
.verify = jz4740_freq_verify,
|
||
|
.target = jz4740_freq_target,
|
||
|
.get = jz4740_freq_get,
|
||
|
.name = "jz4740",
|
||
|
};
|
||
|
|
||
|
static int __init jz4740_cpufreq_init(void)
|
||
|
{
|
||
|
return cpufreq_register_driver(&cpufreq_jz4740_driver);
|
||
|
}
|
||
|
|
||
|
static void __exit jz4740_cpufreq_exit(void)
|
||
|
{
|
||
|
cpufreq_unregister_driver(&cpufreq_jz4740_driver);
|
||
|
}
|
||
|
|
||
|
module_init(jz4740_cpufreq_init);
|
||
|
module_exit(jz4740_cpufreq_exit);
|
||
|
|
||
|
MODULE_AUTHOR("Regen <lhhuang@ingenic.cn>");
|
||
|
MODULE_DESCRIPTION("cpufreq driver for Jz4740");
|
||
|
MODULE_LICENSE("GPL");
|
||
|
|