对于hi3518e 主控芯片内部ADC采样的描述有位牛人描述的很到位。
https://blog.csdn.net/u013738338/article/details/78602986
那个大哥从datasheet出发深入浅出地将hi3518e ADC采样过程描述地很好。
接下来我要用改进过的hisi ADC源码来实现电源电压检测,这份代码读者直接获取、编译就可用。
首先介绍程序的架构。
1.hi_adc.ko提供ADC初始化、开始采样、结束采样、更改采样速度与周期、单次采样、连续采样、挂采样结束中断处理函数等相关函数。
2.power.ko中以定时器软中断的方式从hi_adc.ko提供的接口中定时获取 ADC采样值。
我们先看hi_adc.ko的源码(改进过的默认连续采样)
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "hi_adc.h"
/***************************Define Macro*************************/
#define SUPPORT_CHANNEL 0x4
#define SAR_ADC_BASE 0x200B0000
#define PERI_CRG32_BASE 0x2003007C
#define ADC_CTRL 0x00 //寄存器配置
#define ADC_GLITCH_SAMPEL 0x04 //滤波毛刺配置
#define ADC_TIME_SCAN 0x08
#define ADC_DATA_MASK 0x0c
#define ADC_INT_MASK 0x10 //int enable, 0:disable, 1:enable
#define ADC_INT_STATUS 0x14 //int state
#define ADC_INT_CLR 0x18 //int clr
#define ADC_START 0x1c //start auto scan
#define ADC_STOP 0x20 //stop
#define ADC_IRQ_ENABLE 0x01
#define ADC_IRQ_DISABLE 0x00
#define ADC_CLK_ENABLE (1<<2) //0为使能
#define ADC_POWER_ENABLE 0x00
#define TIME_OUT HZ * 5 // 队列等待时间
//ADC_CTRL
#define ADC_ACTIVE_BIT(x) (x << 24) //precision, 11111100(6 bits),11111000(5 bits)...
#define DATA_DELTA(x) (x << 20) //误差范围,连续扫描模式配置值>2,手册没有说明
#define DEGLITCH_BYPASS (1 << 17) //滤毛刺, 0:enable, 1:bypass
#define ADC_RESET (1 << 15) //reset, 0:quit, 1:enter reset
#define POWER_DOWN_MODE (1 << 14) //power_down, 0:disable, 1:enable
#define MODEL_SEL (1 << 13) //model, 0:single scan, 1:continuous scan
#define CHANNEL_SEL(x) (1 << (x+8)) //channel, 0:A, 1:B, 2:C, 3:D
#define ADC_ZERO_MASK 0xffffff00
//ADC_GLITCH_SAMPEL
#define GLITCH_SAMPEL_VAL 5
//ADC_TIME_SCAN
#define TIME_SCAN_VAL 247525 //500ms default, 2.02us per
#define THRESHOLD 253
//ADC_DATA_MASK
//#define get_adc_data(x) ((SAR_ADC_BASE+ADC_DATA_MASK) >> (x << 3) & 0xFF)
//ADC_INT_STATUS
//#define ADC_AUTO_BUSY ((SAR_ADC_BASE+ADC_INT_STATUS) >> 4 & 0x01)
//#define ADC_INT_FLAG(x) ((SAR_ADC_BASE+ADC_INT_STATUS) >> x & 0x01) //中断标志, 0:A, 1:B, 2:C, 3:D
//ADC_INT_CLR
//#define ADC_INT_CLR(x) ((SAR_ADC_BASE+ADC_INT_CLR) | 0x01 << x) //清除中断标志, 0:A, 1:B, 2:C, 3:D
//#define DEBUG_ADC
#define DELAY_MS 100
/***********************Define Globle Variate********************/
struct his_adc_driver{
struct proc_dir_entry *adc_file;
void __iomem *adc_reg_base;
int flag;
wait_queue_head_t irq_wait;
};
static char *adc_proc_name = "pay_adc";
static struct timer_list g_AdcTimer;
static int g_voltage = 0;
static struct his_adc_driver his_adc;
/****************Driver Information Indicate********************/
MODULE_AUTHOR("jinfa.");
MODULE_DESCRIPTION("hi_adc driver");
MODULE_LICENSE("GPL");
void adc_set_freq_ms(int ms) //ms per
{
unsigned int time_val = TIME_SCAN_VAL * ms / 500;
writel(time_val, his_adc.adc_reg_base + ADC_TIME_SCAN);
}
EXPORT_SYMBOL(adc_set_freq_ms);
static void AdcTimerFunc(unsigned long Data)
{
g_voltage = readl(his_adc.adc_reg_base + ADC_DATA_MASK);
if (g_voltage > THRESHOLD)
return;
}
void adc_start(void)
{
// enable the interrupt
writel(ADC_IRQ_ENABLE, his_adc.adc_reg_base + ADC_INT_MASK);
// start convert
writel(0x01, his_adc.adc_reg_base + ADC_START);
}
EXPORT_SYMBOL(adc_start);
void adc_stop(void)
{
// disable the interrupt
writel(ADC_IRQ_DISABLE, his_adc.adc_reg_base + ADC_INT_MASK);
//stop convert
writel(0x01, his_adc.adc_reg_base + ADC_STOP);
}
EXPORT_SYMBOL(adc_stop);
int adc_read_channel(int channel)
{
unsigned int adc_ctrl_reg = 0, adc_data;
// enable the interrupt
writel(ADC_IRQ_ENABLE, his_adc.adc_reg_base + ADC_INT_MASK);
// choose the channel
adc_ctrl_reg = readl(his_adc.adc_reg_base + ADC_CTRL);
adc_ctrl_reg |= CHANNEL_SEL(channel);
printk("adc_ctrl reg is %u\n", adc_ctrl_reg);
writel(adc_ctrl_reg, his_adc.adc_reg_base + ADC_CTRL);
// start convert
writel(0x01, his_adc.adc_reg_base + ADC_START);
wait_event_timeout(his_adc.irq_wait, his_adc.flag, TIME_OUT);
if (0 == his_adc.flag)
return -EFAULT;
his_adc.flag = 0;
// disable the interrupt
writel(ADC_IRQ_DISABLE, his_adc.adc_reg_base + ADC_INT_MASK);
// get the result
adc_data = readl(his_adc.adc_reg_base + ADC_DATA_MASK);
printk("adc data is %u\n", adc_data);
return (adc_data >> (channel<<3) & 0xff);
}
EXPORT_SYMBOL(adc_read_channel);
static int hisi_adc_proc_read(char *page, char **start,
off_t off, int count, int *eof, void *data)
{
int len = 0;
int i;
for (i = 0; i < SUPPORT_CHANNEL; i++)
{
page[i] = adc_read_channel(i);
len += sizeof(page);
udelay(1);
}
*eof = 1;
return len;
}
#if pay_power
int set_adc_channel(int channel)
{
unsigned int adc_ctrl_reg = 0, adc_data;
// enable the interrupt
writel(ADC_IRQ_ENABLE, his_adc.adc_reg_base + ADC_INT_MASK);
// choose the channel
adc_ctrl_reg = readl(his_adc.adc_reg_base + ADC_CTRL);
adc_ctrl_reg |= CHANNEL_SEL(channel);
printk("adc_ctrl reg is %u\n", adc_ctrl_reg);
writel(adc_ctrl_reg, his_adc.adc_reg_base + ADC_CTRL);
// start convert
writel(0x01, his_adc.adc_reg_base + ADC_START);
}
int get_adc_channel(int channel)
{
// get the result
g_voltage = readl(his_adc.adc_reg_base + ADC_DATA_MASK);
return (g_voltage >> (channel<<3) & 0xff);
}
void init_power_adc()
{
adc_set_freq_ms(2);
set_adc_channel(2);
}
EXPORT_SYMBOL(init_power_adc);
int get_power_vol()
{
g_voltage = get_adc_channel(2); //直接获取通道二的采样结果
return g_voltage;
}
EXPORT_SYMBOL(get_power_vol);
#endif
static irqreturn_t hi_adc_interrupt(int irq, void *id)
{
// clear the interrupt
writel(0x1, his_adc.adc_reg_base + ADC_INT_CLR);
// disable the interrupt
writel(ADC_IRQ_DISABLE, his_adc.adc_reg_base + ADC_INT_MASK);
return 0;
}
/*2018.5.9
*log:分析该init_adc发现这里做了adc的初始化,有效通道为0
*将设置有效通道0代码除去.因为设置有效通道放在set_adc_channel做
*/
int hisi_init_adc(void)
{
int retval = 0;
unsigned int adc_ctrl_reg=0,clk_reg;
init_timer(&g_AdcTimer);
g_AdcTimer.expires = jiffies + msecs_to_jiffies(DELAY_MS);
g_AdcTimer.function = AdcTimerFunc;
his_adc.adc_file = create_proc_read_entry(adc_proc_name, 0, NULL,
hisi_adc_proc_read, NULL);
if (his_adc.adc_file == NULL) {
pr_warning("%s: %s fail!\n", __func__, adc_proc_name);
return -ENOMEM;
}
retval = request_irq(INTNR_ADC, hi_adc_interrupt, 0, "HI_ADC", NULL);
if(0 != retval){
pr_warning("hi3518 ADC: failed to register IRQ(%d)\n", retval);
goto ADC_INIT_FAIL1;
}
his_adc.adc_reg_base = ioremap_nocache((unsigned long)SAR_ADC_BASE, 0x20);
if (NULL == his_adc.adc_reg_base){
pr_warning("function %s line %u failed\n", __FUNCTION__, __LINE__);
retval = -EFAULT;
goto ADC_INIT_FAIL2;
}
adc_ctrl_reg = readl(his_adc.adc_reg_base + ADC_CTRL);
//1.adc reset
adc_ctrl_reg |= ADC_RESET;
writel(adc_ctrl_reg, his_adc.adc_reg_base + ADC_CTRL);
adc_ctrl_reg &= ~ADC_RESET;
writel(adc_ctrl_reg, his_adc.adc_reg_base + ADC_CTRL);
//2.adc config 8-bits
//adc_ctrl_reg |= ADC_ACTIVE_BIT(0xff) & (~MODEL_SEL);
//config contunious sample
adc_ctrl_reg |= ADC_ACTIVE_BIT(0xff) | MODEL_SEL |
POWER_DOWN_MODE | DATA_DELTA(5);
//adc_ctrl_reg = adc_ctrl_reg&ADC_ZERO_MASK + 127;
writel(adc_ctrl_reg, his_adc.adc_reg_base + ADC_CTRL);
//glitch_sample
writel(GLITCH_SAMPEL_VAL, his_adc.adc_reg_base + ADC_GLITCH_SAMPEL);
//time_scan
writel(TIME_SCAN_VAL, his_adc.adc_reg_base + ADC_TIME_SCAN);
//3.CLK and cancell soft reset
clk_reg = readl(IO_ADDRESS(PERI_CRG32_BASE));
clk_reg &= ~ADC_CLK_ENABLE;
writel(clk_reg, IO_ADDRESS(PERI_CRG32_BASE));
his_adc.flag = 0;
init_waitqueue_head(&his_adc.irq_wait);
return 0;
ADC_INIT_FAIL2:
free_irq(INTNR_ADC, NULL);
ADC_INIT_FAIL1:
remove_proc_entry(adc_proc_name, NULL);
return retval;
}
void hisi_exit_adc(void)
{
del_timer(&g_AdcTimer);
free_irq(INTNR_ADC, NULL);
iounmap(his_adc.adc_reg_base);
remove_proc_entry(adc_proc_name, NULL);
}
module_init(hisi_init_adc);
module_exit(hisi_exit_adc);
hisi官方adc源码提供adc_start、adc_stop、adc_read_channel、adc_set_freq_ms设置采样周期并且hi_adc采样结束后会触发中断hi_adc_interrupt。
不过,为了匹配我的power.ko我新增了一些函数:set_adc_channel、get_adc_channel、init_power_adc这些接口做EXPORT_SYMBOL给power.ko用的。
那么,我们现在看看我写的power驱动的部分源码、测试用例。
/*
*power驱动开定时器软中断
*定时获取电压采样值也定时唤醒应用下来的执行序列。
*/
static void Get_PowerTimerFunc(unsigned long Data)
{
static int fre=0;
g_electric = get_power_vol(); //调用hi_adc.ko的接口函数
mod_timer(&g_Power_Timer, jiffies+HZ*3);
g_PowerOccurred=1;
wake_up_interruptible(&power_waitq); //唤醒应用下来的执行序列
}
static int power_read(struct file *file, char *user, size_t size, loff_t *oppos)
{
int ret=0;
ret = wait_event_interruptible(power_waitq, (0 != g_PowerOccurred)); //使应用下来的执行序列阻塞
if (0 != ret)
{
printk("Exit WaitComplete function by signal :%d\n", ret);
return -EBADRQC;
}
copy_to_user(user, (const void *)&g_electric, 4);
return 0;
}
测试用例
read(Power_IndLedFD, &electric, 4);
sleep(1);
printf("electric:%d\n", electric);
代码小解析:估计读者对于get_adc_channel这直接返回存放adc采样值寄存器中的值感到疑惑。。为什么不等中断来了才允许读?这么个读值方法不会导致数据的不新鲜吗?
答案:的确是这样。但是对结果没影响。因为,adc采样时ms级别的而电源电压的变化很小。。所以即使调get_power_vol足够频繁,捞上来的电源电压值不新鲜还是没问题的!