修复nanopi t2 cpu调频不起作用
nanopi t2 s5p4418超频
Cannot get regulator for DVS supply vdd_arm_1.3V
nanopi cpu为s5p4418,标称速度可以达到1400Mhz,实际使用的时候发现只有400Mhz,sys/devices/system/cpu/cpu0
下面没有cpufreq目录。说明cpu调频驱动有问题。
抓一下log看看
[root@minicoco ~]# dmesg | grep cpu
[ 0.000000] Initializing cgroup subsys cpuset
[ 0.000000] Initializing cgroup subsys cpu
[ 0.000000] PERCPU: Embedded 9 pages/cpu @c136a000 s12352 r8192 d16320 u36864
[ 0.000000] pcpu-alloc: s12352 r8192 d16320 u36864 alloc=9*4096
[ 0.000000] pcpu-alloc: [0] 0 [0] 1 [0] 2 [0] 3
[ 0.048000] Initializing cgroup subsys cpuacct
[ 0.048000] CPU0: thread -1, cpu 0, socket 10, mpidr 80000a00
[ 0.092000] CPU1: thread -1, cpu 1, socket 10, mpidr 80000a01
[ 0.132000] CPU2: thread -1, cpu 2, socket 10, mpidr 80000a02
[ 0.172000] CPU3: thread -1, cpu 3, socket 10, mpidr 80000a03
[ 2.076000] nxp-cpufreq nxp-cpufreq.0: Cannot get regulator for DVS supply vdd_arm_1.3V
[ 2.084000] nxp-cpufreq: probe of nxp-cpufreq.0 failed with error -1
[ 2.092000] cpuidle: using governor ladder
[ 2.096000] cpuidle: using governor menu
问题出在nxp-cpufreq nxp-cpufreq.0: Cannot get regulator for DVS supply vdd_arm_1.3V
这一句
nxp-cpufreq
是调频驱动,去代码里面看看。
driver/cpufreq/nxp-cpufreq.c:
regulator_get
这里调用出错了,regulator_get
是电源管理的一个函数,这里是获取vdd_arm_1.3V
这个电压配置失败。nanopi t2的电源管理芯片是axp-228,再看一下电源管理器日志
[root@minicoco ~]# dmesg | grep axp
[ 0.416000] axp_mfd 3-0034: failed reading at 0x03
[ 0.420000] [AXP22-MFD] try to read chip id failed!
可以看到电源管理驱动加载出问题了,看一下代码。
driver/power/axp-power/axp22-mfd.h:
driver/power/axp-power/axp-rw.h:
i2c_smbus_read_byte_data
出了问题,这个是i2c读取数据的方法。问题可以确定出在i2c通信总线上了。
看一下i2c总线注册有没有问题。
arch/arm/plat-s5p4418/nanopi2/device.c
这里乍一看也没什么问题,但是最好吧i2c注册放到axp驱动注册的前面去。i2c驱动注册的是GPIO模拟方式,通道是3,管脚使用了GPIOE30、GPIOE31,从axp驱动的参数中也能看出vdd_arm_1.3V
这个配置就是在这里注册的。看一下电路图
管脚也正确。尝试打印了几个输出也无用。这里读取的器件地址是0x68,寄存器是0x03,值为芯片ID。看一下axp数据手册。
发现没有0x03这个寄存器,以为是驱动不匹配,注释掉芯片ID的读取直接初始化axp芯片,发现也不能通信,看来问题还在i2c通信上面。
由于管脚都没有露出来,所以没办法用示波器测量i2c通信输出。于是只能自己用GPIO模拟i2c通信试试看,GPIO模拟i2c通信文章传送门
根据手册尝试关闭电源
测试axp22
向0x32寄存器的第7位写入1关闭电源。
#include <stdio.h>
#include <linux/ioctl.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <fcntl.h>
#include <unistd.h>
int main(){
int ret,cmd;
fd = open("/dev/char_test_dev_1",O_RDWR);
ret = fd;
if(ret < 0){
perror("open /dev/char_test_dev_1 error");
return ret;
}
unsigned char data[] = {0x68,0x32,0x80};
cmd = _IOC(_IOC_WRITE,0x00,0x00,0x03);
ret = ioctl(fd,cmd,data);
if(ret < 0){
perror("ioctl error");
return ret;
}
close(ret);
return 0;
}
一开始也是失败,最后吧驱动的速度调到一个很低的值成功关闭电源。看来硬件上没有问题,应该是i2c速度太高。
驱动代码
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <asm/io.h>
#include <linux/gpio.h>
#include <mach/soc.h>
#include <linux/delay.h>
#include <mach/platform.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/cdev.h>
#include <linux/slab.h>
#include <asm/uaccess.h>
MODULE_LICENSE("GPL");
dev_t devid;
struct cdev char_dev;
struct class * char_class;
int buffer_size = 100;
char * char_data;
#pragma pack(4)
static struct GPIO_B{
unsigned int out_put;
unsigned int out_enb;
unsigned int detect_md_0;
unsigned int detect_md_1;
unsigned int int_enb;
unsigned int event_detect;
unsigned int pad;
unsigned int resv;
unsigned int func0;
unsigned int func1;
unsigned int DETMODEEX;
unsigned int DETENB;
unsigned int SLEW;
unsigned int SLEW_DISABLE_DEFAULT;
unsigned int DRV1;
unsigned int DRV1_DISABLE_DEFAULT;
unsigned int DRV0;
unsigned int DRV0_DISABLE_DEFAULT;
unsigned int pull_sell;
unsigned int PULLSEL_DISABLE_DEFAULT;
unsigned int pull_enb;
}* gpio_b;
#pragma pack()
#define SCL_PIN (30)
#define SDA_PIN (31)
#define SCL_H (gpio_b->out_put |= (1 << SCL_PIN))
#define SCL_L (gpio_b->out_put &= ~(1 << SCL_PIN))
#define SDA_H (gpio_b->out_put |= (1 << SDA_PIN))
#define SDA_L (gpio_b->out_put &= ~(1 << SDA_PIN))
#define SDA_OUT (gpio_b->out_enb |= (1 << SDA_PIN))
#define SDA_IN (gpio_b->out_enb &= ~(1 << SDA_PIN))
#define SDA_VAL ((gpio_b->pad >> SDA_PIN) & 0x01)
#define cycle_ns 1000000 //iic周期,单位纳秒
/**********************************************
//IIC Start
**********************************************/
inline void IIC_Start(void)
{
SCL_H;
SDA_H;
ndelay(cycle_ns);
SDA_L;
ndelay(cycle_ns);
SCL_L;
ndelay(cycle_ns/2); //等待电平稳定
}
/**********************************************
//IIC Stop
**********************************************/
inline void IIC_Stop(void)
{
SCL_L;
SDA_L;
ndelay(cycle_ns);
SCL_H;
ndelay(cycle_ns);
SDA_H;
ndelay(400);
}
/**********************************************
// 通过I2C总线写一个字节
**********************************************/
inline int Write_IIC_Byte(unsigned char IIC_Byte)
{
unsigned char i;
int ret;
for(i=0;i<8;i++)
{
if(IIC_Byte & 0x80)
SDA_H;
else
SDA_L;
ndelay(cycle_ns/2); //等待电平稳定
SCL_H;
ndelay(cycle_ns); //维持采样时间
SCL_L;
ndelay(cycle_ns/2); //等待电平稳定
IIC_Byte<<=1;
}
SDA_H;
ndelay(cycle_ns/2); //等待电平稳定
SDA_IN;
SCL_H;
ndelay(cycle_ns/2); //等待电平稳定
if(!SDA_VAL){
ret = 0;
}else{
ret = -1;
}
ndelay(cycle_ns/2); //等待电平稳定
SDA_OUT;
SCL_L;
SDA_L;
ndelay(40); //下次传输间隔ack
return ret;
}
static long ioctl(struct file * fl, unsigned int cmd, unsigned long arg){
unsigned int dir,size,i;
dir = _IOC_DIR(cmd);
size = _IOC_SIZE(cmd);
if(dir == _IOC_WRITE){
i = size;
while(i > 0){
i = copy_from_user(char_data,(unsigned char *)arg,i);
}
IIC_Start();
for (i = 0; i < size; ++i)
{
if(Write_IIC_Byte(char_data[i]) != 0){
printk("iic no ack\n");
break;
}
}
IIC_Stop();
}
return 0;
}
struct file_operations my_opts = {
.owner = THIS_MODULE,
.unlocked_ioctl = ioctl
};
static int __init iic_init(void){
int ret = 0;
devid = MKDEV(241, 1); //换算设备号
ret = register_chrdev_region(devid, 1, "char_test");//注册设备,在/proc/drivers下面可以看到
if (ret < 0)
goto err0;
cdev_init(&char_dev,&my_opts); //绑定opt结构体
char_dev.owner = THIS_MODULE;
ret = cdev_add(&char_dev,devid,1); //注册字符设备驱动
if (ret < 0)
goto err1;
char_class = class_create(THIS_MODULE,"char_test"); //在/sys/class中创建文件夹
device_create(char_class,NULL,devid,NULL,"char_test_dev_%d",1);//在上一步文件夹中创建char_test_dev_1
char_data = kzalloc(buffer_size,GFP_KERNEL);
gpio_b = (struct GPIO_B *)ioremap(0xc001e000,sizeof(struct GPIO_B)); //映射地址
gpio_b->func1 |= (1 << (SDA_PIN % 16) * 2);
gpio_b->func1 |= (1 << (SCL_PIN % 16) * 2);
gpio_b->out_enb |= (1 << SDA_PIN);
gpio_b->out_enb |= (1 << SCL_PIN);
gpio_b->pull_enb |= (1 << SDA_PIN);
gpio_b->pull_sell |= (1 << SDA_PIN);
SDA_H;
SCL_H;
printk("iic init\n");
return 0;
err1:
unregister_chrdev_region(devid, 1);
err0:
return ret;
}
static void __exit iic_exit(void){
SDA_H;
SCL_H;
iounmap(gpio_b);
unregister_chrdev_region(devid, 1);
cdev_del(&char_dev);
device_destroy(char_class,devid);
class_destroy(char_class);
printk("iic exit\n");
}
module_init(iic_init);
module_exit(iic_exit);
调整i2c速度
于是修改内核驱动中i2c的速度
最后成功了,重新开机sys/devices/system/cpu/cpu0
下也有cpufreq目录了。收工!