648 lines
18 KiB
C
648 lines
18 KiB
C
/* drivers/char/s3c-dvfs/s3c-dvfs.c
|
|
*
|
|
* Copyright (c) 2008 Samsung Electronics
|
|
* Kwanghyun.La <nala.la@samsung.com>
|
|
*
|
|
* 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.
|
|
*
|
|
* S3C64xx DVFS interface with LTC3714 DCDC convertor power
|
|
* 2008.02.28. basic scheme update to here
|
|
*/
|
|
#include <linux/init.h>
|
|
#include <linux/module.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/fs.h>
|
|
#include <linux/errno.h>
|
|
#include <linux/types.h>
|
|
#include <linux/fcntl.h>
|
|
|
|
#include <asm/uaccess.h>
|
|
#include <asm/io.h>
|
|
|
|
#include <linux/stat.h>
|
|
#include <linux/proc_fs.h>
|
|
|
|
#include <linux/delay.h> // For loops_per_jiffy
|
|
#include <asm/arch/regs-gpio.h>
|
|
|
|
#include "s3c-dvfs.h"
|
|
|
|
#define DEV_NAME "s3c-dvfs"
|
|
#define DEV_MAJOR 240
|
|
|
|
|
|
|
|
#define XTAL 12*1000*1000 /* Clock source 12Mhz */
|
|
#define MAX_APLL_RATIO 7 /* See S3C6400 user manual 3-7 */
|
|
#define MAX_HCLK2_RATIO 7 /* See S3C6400 user manual 0-1 */
|
|
#define Mhz 1000*1000
|
|
|
|
#define SUPPORT_PROC_FS
|
|
|
|
#ifdef SUPPORT_PROC_FS
|
|
struct proc_dir_entry *proc_root_fp = NULL;
|
|
struct proc_dir_entry *proc_voltage_fp = NULL;
|
|
struct proc_dir_entry *proc_freq_fp = NULL;
|
|
struct proc_dir_entry *proc_step_fp = NULL;
|
|
|
|
char proc_voltage_str[PAGE_SIZE-80] = { 0,};
|
|
char proc_freq_str[PAGE_SIZE-80] = { 0,};
|
|
char proc_step_str[PAGE_SIZE-80] = { 0,};
|
|
#endif
|
|
|
|
|
|
/* it's depend on DCDC regurator specification
|
|
* if change parts, you must be change table and configuration*/
|
|
static const unsigned int voltage_table[32] = {
|
|
1750, 1700, 1650, 1600, 1550, 1500, 1450, 1400,
|
|
1350, 1300, 1250, 1200, 1150, 1100, 1050, 1000,
|
|
975, 950, 925, 900, 875, 850, 825, 800,
|
|
775, 750, 725, 700, 675, 650, 625, 600,
|
|
};
|
|
|
|
#define NUMBER_OF_STEP 4
|
|
|
|
/*Frequency step define section */
|
|
#define MDIV 0
|
|
#define PDIV 1
|
|
#define SDIV 2
|
|
#define ARM_RATIO 3
|
|
#define HCLK_RATIO 4
|
|
#define DVFS MDIV /* for S3C2443,2416 and S3C2450*/
|
|
|
|
#define ARM_VOLT_STEP 0
|
|
#define INT_VOLT_STEP 1
|
|
#define FREQ_STEP 2
|
|
|
|
#if defined(CONFIG_MACH_SMDK6400) || defined(CONFIG_MACH_SMDK6410)
|
|
static const unsigned int pll_mps_table[][5] = {
|
|
{533, 6, 1, 0, 1}, /* step 0 ARM:(533 / 1)=533 MHz ,HCLKx2:266 HCLK: 133*/
|
|
{400, 6, 1, 0, 1}, /* step 1 ARM:(400 / 1)=400 MHz ,HCLKx2:266 HCLK: 133*/
|
|
{400, 6, 1, 1, 1}, /* step 2 ARM:(400 / 2)=200 MHz ,HCLKx2:266 HCLK: 133*/
|
|
{400, 6, 1, 3, 2}, /* step 3 ARM:(400 / 4)=100 MHz ,HCLKx2:133 HCLK: 66 ==> careful linked device*/
|
|
/* {MDIV, PDIV, SDIV, ARM RATIO, HCLKx2 RATIO } */
|
|
};
|
|
|
|
static const unsigned int dvfs_step[NUMBER_OF_STEP][3] = {
|
|
{ 1200 , 1200, 0}, /* ARM 1.2Volt, INT 1.2V, pll mode 0 533-133*/
|
|
{ 1000 , 1000, 1}, /* ARM 1.2Volt, INT 1.2V, pll mode 2 200-133*/
|
|
{ 950 , 1000, 2}, /* ARM 1.2Volt, INT 1.2V, pll mode 2 200-133*/
|
|
{ 900 , 1000, 2}, /* ARM 1.2Volt, INT 1.2V, pll mode 3 100- 66*/
|
|
/* {Voltage, Frequency_STEP} */
|
|
};
|
|
#elif defined(CONFIG_MACH_SMDK2450) ||defined (CONFIG_MACH_SMDK2416) ||defined (CONFIG_MACH_SMDK2443)
|
|
static const unsigned int pll_mps_table[][5] = {
|
|
{0, 0, 0, 0, 1}, /* step 0 DVS OFF, ARM:534MHz ,HCLK:133MHz*/
|
|
{0, 0, 0, 1, 1}, /* step 1 DVS OFF, ARM:266MHz ,HCLK:133MHz*/
|
|
{0, 0, 0, 3, 1}, /* step 2 DVS OFF, ARM:133MHz ,HCLK:133MHz*/
|
|
{1, 0, 0, 3, 1}, /* step 3 DVS ON , ARM:133MHz ,HCLK:133MHz careful the linked devices*/
|
|
/* {DVS_ON, NA, NA, ARM RATIO, HCLK RATIO } */
|
|
/* S3C24xx case , when HCLK is changed , UART signal is fragle, so must need to re setting the uart div
|
|
and the ARM Clock must be over than HCLK or same, it'can not support under HCLK*/
|
|
};
|
|
|
|
static const unsigned int pll_mps_table400[][5] = {
|
|
{0, 0, 0, 1, 1}, /* step 0 DVS OFF, ARM:400MHz ,HCLK:133MHz*/
|
|
{0, 0, 0, 2, 1}, /* step 1 DVS OFF, ARM:266MHz ,HCLK:133MHz*/
|
|
{0, 0, 0, 4, 1}, /* step 2 DVS OFF, ARM:160Hz ,HCLK:133MHz*/
|
|
{1, 0, 0, 4, 1}, /* step 3 DVS ON , ARM:160MHz ,HCLK:133MHz careful the linked devices*/
|
|
/* {DVS_ON, NA, NA, ARM RATIO, HCLK RATIO } */
|
|
/* S3C24xx case , when HCLK is changed , UART signal is fragle, so must need to re setting the uart div
|
|
and the ARM Clock must be over than HCLK or same, it'can not support under HCLK*/
|
|
};
|
|
|
|
static const unsigned int dvfs_step[NUMBER_OF_STEP][3] = {
|
|
{ 1200 , 1200, 0}, /* ARM 1.2Volt, INT 1.2V, pll mode 0 533-133*/
|
|
{ 1100 , 1100, 1}, /* ARM 1.0Volt, INT 1.0V, pll mode 2 200-133*/
|
|
{ 975 , 1100, 2}, /* ARM 1.0Volt, INT 1.0V, pll mode 2 200-133*/
|
|
{ 955 , 1000, 3}, /* ARM 1.0Volt, INT 1.0V, pll mode 3 100- 66*/
|
|
/* {ARM Voltage,INT ARM Voltage, Frequency_STEP} */
|
|
};
|
|
|
|
#endif
|
|
|
|
static int previous_dvfs_step;
|
|
|
|
static s3c_dvfs_info dvfs_info;
|
|
|
|
static void set_dvfs_gpio(void)
|
|
{
|
|
/* We can control the voltage of ARM core and Internal block by setting GPIO */
|
|
/* GPN(11~15). First of all, we should set these gpio to output mode. */
|
|
/* GPIO configuration */
|
|
GPIO_CFG(LTC3714_DATA1, LTC3714_OUTP1);
|
|
GPIO_CFG(LTC3714_DATA2, LTC3714_OUTP2);
|
|
GPIO_CFG(LTC3714_DATA3, LTC3714_OUTP3);
|
|
GPIO_CFG(LTC3714_DATA4, LTC3714_OUTP4);
|
|
GPIO_CFG(LTC3714_DATA5, LTC3714_OUTP5);
|
|
|
|
/* Pull-up/down disable */
|
|
GPIO_PULLUP(LTC3714_DATA1, 0x0);
|
|
GPIO_PULLUP(LTC3714_DATA2, 0x0);
|
|
GPIO_PULLUP(LTC3714_DATA3, 0x0);
|
|
GPIO_PULLUP(LTC3714_DATA4, 0x0);
|
|
GPIO_PULLUP(LTC3714_DATA5, 0x0);
|
|
|
|
|
|
/* Latch control signal*/
|
|
/* CORE_REG_OE: GPL9, ARM_REG_OE: GPL8, INT_REG_LE: GPL10*/
|
|
GPIO_CFG(ARM_LE , ARM_LE_OUTP);
|
|
GPIO_CFG(DVS_OE , DVS_OE_OUTP);
|
|
GPIO_CFG(INT_LE , INT_LE_OUTP);
|
|
|
|
GPIO_PULLUP(ARM_LE , 0x0);
|
|
GPIO_PULLUP(DVS_OE , 0x0);
|
|
GPIO_PULLUP(INT_LE , 0x0);
|
|
|
|
|
|
}
|
|
|
|
/* Set LTC3714 voltage regulator */
|
|
/* Input : pwr : 1(ARM), 2(Internal), 3(Both) */
|
|
/* voltage : 1mV step */
|
|
static int set_ltc3714(unsigned int pwr, unsigned int voltage)
|
|
{
|
|
int position = 0;
|
|
unsigned int val;
|
|
|
|
|
|
if(voltage > voltage_table[0] || voltage < voltage_table[31]) {
|
|
printk("[ERROR]: voltage value over limits!!!");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if(voltage > voltage_table[16]) { // 1750 ~ 1000 mV
|
|
for(position = 15; position >= 0; position --) {
|
|
if(voltage_table[position] == voltage) break;
|
|
}
|
|
|
|
}
|
|
else if(voltage >= voltage_table[31]) { //975 ~ 600 mV
|
|
for(position = 31; position >= 16; position --) {
|
|
if(voltage_table[position] == voltage) break;
|
|
}
|
|
}
|
|
else {
|
|
printk("[error]: Can't find adquate voltage table list value\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
printk("Founded postion :[%d] \n",position);
|
|
|
|
position &=0x1f;
|
|
|
|
#if defined (CONFIG_MACH_SMDK6400) ||defined (CONFIG_MACH_SMDK6410)
|
|
__raw_writel((__raw_readl(S3C_GPNDAT)&~(0x1f<<11))|(position<<11), S3C_GPNDAT);
|
|
#elif defined (CONFIG_MACH_SMDK2450) ||defined (CONFIG_MACH_SMDK2416) ||defined (CONFIG_MACH_SMDK2443)
|
|
/* It's depend on schematic and assigned GPIO pin */
|
|
val = __raw_readl(S3C2410_GPCDAT) &~(0x1<<7|0x1<<6|0x1<<5|0x1<<0);
|
|
val |= ((((position & 0xe)>>1)<<5)|(position & 0x1));
|
|
__raw_writel(val, S3C2410_GPCDAT);
|
|
|
|
val = __raw_readl(S3C2410_GPBDAT) &~(0x1<<2);
|
|
val |= (position & 0x10)<<2;
|
|
__raw_writel(val,S3C2410_GPBDAT);
|
|
#endif
|
|
|
|
if(pwr == ARM_VOLT_STEP) { //ARM Voltage Control => ARM_REG_LE => Output H => Data Changed
|
|
|
|
CONTROl_SET(ARM_LE, 0x1);
|
|
udelay(10);
|
|
CONTROl_SET(ARM_LE, 0x0);
|
|
} else if(pwr == INT_VOLT_STEP) { // INT Voltage Control
|
|
CONTROl_SET(INT_LE, 0x1);
|
|
udelay(10);
|
|
CONTROl_SET(INT_LE, 0x0);
|
|
} else {
|
|
printk("[error]: set_ltc3714, check mode [pwr] value\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
/* Set CLK_DIV0 register with specific APLL divider value*/
|
|
static int set_freq_divider(unsigned int clk_source , unsigned int val)
|
|
{
|
|
unsigned int tmp;
|
|
|
|
if (clk_source == ARM_RATIO){
|
|
if(val > MAX_APLL_RATIO) {
|
|
printk(KERN_ERR "Freq divider value(APLL_RATIO) is out of spec\n");
|
|
printk(KERN_ERR "APLL_RATIO : 0~7\n");
|
|
return -EINVAL;
|
|
}
|
|
dvfs_info.freq_divider = (val + 1);
|
|
tmp = __raw_readl(ARM_CLK_DIV)&~ARM_DIV_MASK;
|
|
tmp |= val<<ARM_DIV_RATIO_BIT;
|
|
__raw_writel(tmp, ARM_CLK_DIV);
|
|
} else if (clk_source == HCLK_RATIO){
|
|
if(val > MAX_HCLK2_RATIO) {
|
|
printk(KERN_ERR "Freq divider value is out of spec\n");
|
|
printk(KERN_ERR "HCLK_PostDivider RATIO : 0,1 \n");
|
|
return -EINVAL;
|
|
}
|
|
/* it's only for setting the HCLK or system Clock */
|
|
tmp = __raw_readl(ARM_CLK_DIV) & ~HCLK_DIV_MASK;
|
|
tmp |= (val<<HCLK_DIV_RATIO_BIT);
|
|
__raw_writel(tmp, ARM_CLK_DIV);
|
|
} else {
|
|
printk(KERN_ERR " It's wrong clock post divider path \n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Set APLL P,M,S value to make specific CPU frequency*/
|
|
/* Only support Async mode */
|
|
static int set_pll(unsigned int freq_level)
|
|
{
|
|
unsigned int val,err;
|
|
|
|
if (freq_level < NUMBER_OF_STEP ){
|
|
|
|
#if defined (CONFIG_MACH_SMDK2450) ||defined (CONFIG_MACH_SMDK2416)
|
|
|
|
val = ((__raw_readl(MPLL_CON)>>14)&0x3ff);
|
|
|
|
if (val == 267){
|
|
err = set_freq_divider(ARM_RATIO, pll_mps_table[freq_level][ARM_RATIO]);
|
|
if(err)
|
|
return err;
|
|
|
|
err = set_freq_divider(HCLK_RATIO, pll_mps_table[freq_level][HCLK_RATIO]);
|
|
if(err)
|
|
return err;
|
|
}
|
|
else{
|
|
err = set_freq_divider(ARM_RATIO, pll_mps_table400[freq_level][ARM_RATIO]);
|
|
if(err)
|
|
return err;
|
|
|
|
err = set_freq_divider(HCLK_RATIO, pll_mps_table400[freq_level][HCLK_RATIO]);
|
|
if(err)
|
|
return err;
|
|
}
|
|
|
|
|
|
#else
|
|
err = set_freq_divider(ARM_RATIO, pll_mps_table[freq_level][ARM_RATIO]);
|
|
if(err)
|
|
return err;
|
|
|
|
err = set_freq_divider(HCLK_RATIO, pll_mps_table[freq_level][HCLK_RATIO]);
|
|
if(err)
|
|
return err;
|
|
#endif
|
|
|
|
/*
|
|
it's only guarantee for chaiging PLL on S3C64xx
|
|
if you want to change dynamic freq. you must check stability of system
|
|
*/
|
|
#if defined (CONFIG_MACH_SMDK6400) ||defined (CONFIG_MACH_SMDK6410)
|
|
val = PLL_CALC_VAL(pll_mps_table[freq_level][MDIV],\
|
|
pll_mps_table[freq_level][PDIV],\
|
|
pll_mps_table[freq_level][SDIV]);
|
|
__raw_writel(val, ARM_PLL_CON);
|
|
/*
|
|
it does not guarantee for chaiging PLL on S3C2443,S3C2450 and S3C2416
|
|
but we guide to set DVFS or divide arm clock output
|
|
*/
|
|
#elif defined (CONFIG_MACH_SMDK2450) ||defined (CONFIG_MACH_SMDK2416) ||defined (CONFIG_MACH_SMDK2443)
|
|
/* set the DVS ON or OFF*/
|
|
val = __raw_readl(ARM_CLK_DIV) & ~(DVFS_MASK);
|
|
val |= (pll_mps_table[freq_level][DVFS] << 13) ;
|
|
/* it's ony for conpensation PCLK of S3C2443,2416and 2450*/
|
|
__raw_writel(val, ARM_CLK_DIV);
|
|
#endif
|
|
|
|
|
|
}
|
|
else{
|
|
printk("It's wrong DFS %d range \n",freq_level);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Other modules can use this function to change power status */
|
|
/* Especially, ARM Dynamic power interface can control DVFS by accessing this */
|
|
int set_dvfs_step(unsigned int step)
|
|
{
|
|
int err;
|
|
|
|
if(step >= NUMBER_OF_STEP) {
|
|
printk(KERN_ERR "DVFS step is out of range(0 ~ %2d)\n",NUMBER_OF_STEP-1);
|
|
return -EINVAL;
|
|
}
|
|
//printk("set_dvfs_step[%d]\n",step);
|
|
|
|
if(previous_dvfs_step < step) { // decreasing CPU freq & Voltage
|
|
/* CPU Frequency control */
|
|
err = set_pll(dvfs_step[step][FREQ_STEP]);
|
|
|
|
/* CPU voltage control */
|
|
dvfs_info.pwr_type = ARM_VOLT_STEP;
|
|
dvfs_info.new_voltage = dvfs_step[step][ARM_VOLT_STEP];
|
|
err = set_ltc3714(dvfs_info.pwr_type, dvfs_info.new_voltage);
|
|
if(err == 0){
|
|
dvfs_info.curr_voltage_arm = dvfs_info.new_voltage;
|
|
}
|
|
|
|
|
|
dvfs_info.pwr_type = INT_VOLT_STEP;
|
|
dvfs_info.new_voltage = dvfs_step[step][INT_VOLT_STEP];
|
|
err = set_ltc3714(dvfs_info.pwr_type, dvfs_info.new_voltage);
|
|
if(err == 0){
|
|
dvfs_info.curr_voltage_internal = dvfs_info.new_voltage;
|
|
}
|
|
|
|
} else if(previous_dvfs_step > step) { // increasing CPU freq & Voltage
|
|
|
|
/* CPU voltage control */
|
|
dvfs_info.pwr_type = ARM_VOLT_STEP;
|
|
dvfs_info.new_voltage = dvfs_step[step][ARM_VOLT_STEP];
|
|
err = set_ltc3714(dvfs_info.pwr_type, dvfs_info.new_voltage);
|
|
if(err == 0){
|
|
dvfs_info.curr_voltage_arm = dvfs_info.new_voltage;
|
|
}
|
|
|
|
dvfs_info.pwr_type = INT_VOLT_STEP;
|
|
dvfs_info.new_voltage = dvfs_step[step][INT_VOLT_STEP];
|
|
err = set_ltc3714(dvfs_info.pwr_type, dvfs_info.new_voltage);
|
|
if(err == 0){
|
|
dvfs_info.curr_voltage_internal = dvfs_info.new_voltage;
|
|
}
|
|
|
|
/* CPU Frequency control */
|
|
err = set_pll(dvfs_step[step][FREQ_STEP]);
|
|
|
|
} else {
|
|
}
|
|
previous_dvfs_step = step;
|
|
|
|
return 0;
|
|
}
|
|
|
|
EXPORT_SYMBOL(set_dvfs_step);
|
|
|
|
int s3c_dvfs_open(struct inode *inode, struct file *filp)
|
|
{
|
|
int num = MINOR(inode->i_rdev);
|
|
|
|
printk("s3c_dvfs_open -> minor : %d\n", num);
|
|
|
|
return 0;
|
|
}
|
|
|
|
ssize_t s3c_dvfs_read(struct file *filp, char *buf, size_t count,
|
|
loff_t *f_pos)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
ssize_t s3c_dvfs_write(struct file *filp, const char *buf, size_t count,
|
|
loff_t *f_pos)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
int s3c_dvfs_ioctl(struct inode *inode, struct file *filp,
|
|
unsigned int cmd, unsigned long arg)
|
|
{
|
|
int err, size;
|
|
// printk("s3c_dvfs_ioctl -> cmd 0x%x, arg : 0x%x\n", cmd, arg);
|
|
|
|
if(_IOC_TYPE(cmd) != DVFS_IOCTL_MAGIC) return -EINVAL;
|
|
if(_IOC_NR(cmd) >= DVFS_MAXNR) return -EINVAL;
|
|
|
|
size = _IOC_SIZE(cmd);
|
|
|
|
switch(cmd) {
|
|
case DVFS_ON:
|
|
break;
|
|
case DVFS_OFF:
|
|
break;
|
|
case DVFS_GET_STATUS:
|
|
err=copy_to_user((void *) arg, (const void *) &dvfs_info, (unsigned long) size);
|
|
break;
|
|
case DVFS_SET_STATUS:
|
|
err=copy_from_user((void *) &dvfs_info, (const void *) arg, size);
|
|
err = set_ltc3714(dvfs_info.pwr_type, dvfs_info.new_voltage);
|
|
|
|
if(err != 0) return err;
|
|
|
|
if(dvfs_info.pwr_type == ARM_VOLT_STEP) {
|
|
dvfs_info.curr_voltage_arm = dvfs_info.new_voltage;
|
|
} else if(dvfs_info.pwr_type == INT_VOLT_STEP) {
|
|
dvfs_info.curr_voltage_internal = dvfs_info.new_voltage;
|
|
} else {
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int s3c_dvfs_release(struct inode *inode, struct file *filp)
|
|
{
|
|
printk("s3c_dvfs_release \n");
|
|
return 0;
|
|
}
|
|
|
|
struct file_operations s3c_dvfs_fops =
|
|
{
|
|
.owner = THIS_MODULE,
|
|
// .llseek = s3c_dvfs_llseek,
|
|
.read = s3c_dvfs_read,
|
|
.write = s3c_dvfs_write,
|
|
.ioctl = s3c_dvfs_ioctl,
|
|
.open = s3c_dvfs_open,
|
|
.release = s3c_dvfs_release,
|
|
};
|
|
|
|
#ifdef SUPPORT_PROC_FS
|
|
int read_proc_voltage(char *page, char **start, off_t off,
|
|
int count, int *eof, void *data_unused)
|
|
{
|
|
char *buf;
|
|
|
|
buf = page;
|
|
buf += sprintf(buf, "ARM = [%d]mV, Internal = [%d]mV\n", dvfs_info.curr_voltage_arm,dvfs_info.curr_voltage_internal);
|
|
|
|
return buf - page;
|
|
}
|
|
|
|
int write_proc_voltage(struct file *file, const char __user *buffer,
|
|
unsigned long count, void *data)
|
|
{
|
|
int len, err;
|
|
char *realdata;
|
|
|
|
realdata = (char *) data;
|
|
|
|
if(copy_from_user(realdata, buffer, count))
|
|
return -EFAULT;
|
|
|
|
realdata[count] = '\0';
|
|
len = strlen(realdata);
|
|
if(realdata[len - 1] == '\n')
|
|
realdata[--len] = 0;
|
|
|
|
dvfs_info.new_voltage = simple_strtoul(realdata, NULL, 10);
|
|
|
|
err = set_ltc3714(dvfs_info.pwr_type, dvfs_info.new_voltage);
|
|
|
|
if(err == 0) {
|
|
if(dvfs_info.pwr_type == ARM_VOLT_STEP) {
|
|
dvfs_info.curr_voltage_arm = dvfs_info.new_voltage;
|
|
} else if(dvfs_info.pwr_type == INT_VOLT_STEP) {
|
|
dvfs_info.curr_voltage_internal = dvfs_info.new_voltage;
|
|
} else {
|
|
}
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
int read_proc_freq(char *page, char **start, off_t off,
|
|
int count, int *eof, void *data_unused)
|
|
{
|
|
char *buf;
|
|
unsigned int arm_freq,tmp;
|
|
|
|
arm_freq = GET_ARM_CLOCK(XTAL);
|
|
dvfs_info.freq_divider = READ_ARM_DIV;
|
|
dvfs_info.curr_freq = arm_freq/dvfs_info.freq_divider;
|
|
|
|
/*it's only supporting DVFS of S3C2443/2450/2416 */
|
|
#if defined (CONFIG_MACH_SMDK2450) ||defined (CONFIG_MACH_SMDK2416) ||defined (CONFIG_MACH_SMDK2443)
|
|
tmp=__raw_readl(ARM_CLK_DIV) ;
|
|
if((tmp&DVFS_MASK)){ /* if DVFS set, HCLK use for ARMCLK*/
|
|
dvfs_info.curr_freq = arm_freq/(((tmp & HCLK_DIV_MASK)+1)<<((tmp&0x8)>>3));
|
|
}
|
|
#endif
|
|
|
|
buf = page;
|
|
buf += sprintf(buf, "Freq = [%d]MHz\n", dvfs_info.curr_freq/1000000);
|
|
|
|
return buf - page;
|
|
}
|
|
|
|
int write_proc_freq(struct file *file, const char __user *buffer,
|
|
unsigned long count, void *data)
|
|
{
|
|
int len, tmp, err;
|
|
char *realdata;
|
|
|
|
realdata = (char *) data;
|
|
|
|
if(copy_from_user(realdata, buffer, count))
|
|
return -EFAULT;
|
|
|
|
realdata[count] = '\0';
|
|
len = strlen(realdata);
|
|
if(realdata[len - 1] == '\n')
|
|
realdata[--len] = 0;
|
|
tmp = simple_strtoul(realdata, NULL, 10);
|
|
|
|
printk("level %d \n",tmp);
|
|
|
|
err = set_freq_divider(ARM_RATIO, tmp);
|
|
if(err){
|
|
printk("level err %d \n",err);
|
|
return err;
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
int write_proc_step(struct file *file, const char __user *buffer, unsigned long count, void *data)
|
|
{
|
|
int len, tmp, err;
|
|
char *realdata;
|
|
|
|
realdata = (char *) data;
|
|
|
|
if(copy_from_user(realdata, buffer, count))
|
|
return -EFAULT;
|
|
realdata[count] = '\0';
|
|
len = strlen(realdata);
|
|
if(realdata[len - 1] == '\n')
|
|
realdata[--len] = 0;
|
|
tmp = simple_strtoul(realdata, NULL, 10);
|
|
|
|
err = set_dvfs_step(tmp);
|
|
|
|
return count;
|
|
}
|
|
|
|
#endif
|
|
|
|
int s3c_dvfs_init(void)
|
|
{
|
|
int result;
|
|
|
|
/* Initialize structure */
|
|
previous_dvfs_step = -1; /* 1st initial operating*/
|
|
set_dvfs_gpio();
|
|
set_dvfs_step(0); // Set Maximum or default initial performance set
|
|
CONTROl_SET(DVS_OE, 0x1); /* Just open target power voltage*/
|
|
|
|
result = register_chrdev(DEV_MAJOR, DEV_NAME, &s3c_dvfs_fops);
|
|
|
|
if(result < 0)
|
|
return result;
|
|
|
|
|
|
|
|
#ifdef SUPPORT_PROC_FS
|
|
proc_root_fp = proc_mkdir("dvfs", 0);
|
|
|
|
proc_voltage_fp = create_proc_entry("voltage", S_IFREG | S_IRWXU, proc_root_fp);
|
|
if(proc_voltage_fp) {
|
|
proc_voltage_fp->data = proc_voltage_str;
|
|
proc_voltage_fp->read_proc = read_proc_voltage;
|
|
proc_voltage_fp->write_proc = write_proc_voltage;
|
|
}
|
|
|
|
proc_freq_fp = create_proc_entry("frequency", S_IFREG | S_IRWXU, proc_root_fp);
|
|
if(proc_freq_fp) {
|
|
proc_freq_fp->data = proc_freq_str;
|
|
proc_freq_fp->read_proc = read_proc_freq;
|
|
proc_freq_fp->write_proc = write_proc_freq;
|
|
}
|
|
|
|
proc_step_fp = create_proc_entry("step", S_IFREG | S_IWUSR, proc_root_fp);
|
|
if(proc_step_fp) {
|
|
proc_step_fp->data = proc_step_str;
|
|
proc_step_fp->read_proc = NULL;
|
|
proc_step_fp->write_proc = write_proc_step;
|
|
}
|
|
#endif
|
|
|
|
return 0;
|
|
}
|
|
|
|
void s3c_dvfs_exit(void)
|
|
{
|
|
unregister_chrdev(DEV_MAJOR, DEV_NAME);
|
|
remove_proc_entry("voltage", proc_root_fp);
|
|
remove_proc_entry("frequency", proc_root_fp);
|
|
remove_proc_entry("step", proc_root_fp);
|
|
remove_proc_entry("dvfs", 0);
|
|
}
|
|
|
|
module_init(s3c_dvfs_init);
|
|
module_exit(s3c_dvfs_exit);
|
|
|
|
MODULE_LICENSE("Dual BSD/GPL");
|
|
MODULE_DESCRIPTION("S3C64xx DVFS Driver");
|
|
MODULE_AUTHOR("KWANGHYUN.LA, <nala.la@samsung.com>");
|