对Freescale i.mx53 ADC驱动程序的透彻分析
应该说是freescalebsp提供了N多的东东,但是相对于三星提供的来说要复杂一些,感觉三星的简单,飞思将这些东东弄得复杂了。当然,这只是我个人见解,也有可能当初在学校接触的是s3c2410,所以现在才这样说。哈哈。见笑了。
飞思将ADC的驱动程序放在drivers/mxc/adc目录下,这个目录下有以下几个文件:
Imx_adc.c 这个实现的就是ADC的驱动程序
Imx_adc_reg.h 这个主要就是定义了跟ADC相关的一些寄存器
Kconfig 使用make menuconfig配置时必须配置的文件
Makefile 编译文件
首先,咱们来看看Kconfig文件,这个比较简单的:
Menu “i.MX ADC support”
Config IMX_ADC 此处会被定义成宏的!Makefile里就是在之前加CONFIG-
Tristate“i.MX ADC”
Dependson ARCH_MXC
Defaultn
Help
Thisselects the Freescale i.MX on-chip ADC driver.
Endmenu
这个文件应该没有好说的吧。我绍言在这里也不多说了。如果不懂的话,自己去看看我的博客里写的《如何在Linux2.6中增加自己的驱动程序》。
下面来看一下Makefile,如下:
Obj-$(CONFIG_IMX_ADC) += imx_adc.o
这些都是例行公事,最最主要的还是看驱动源码啊。看吧。为了深入进去,这里还是不限篇幅,列出源码,一条一条的分析!
#ifndef __IMX_ADC_H__
#define __IMX_ADC_H__
/* TSC General Config Register */
#define TGCR 0x000
#define TGCR_IPG_CLK_EN (1 << 0)
#define TGCR_TSC_RST (1 << 1)
#define TGCR_FUNC_RST (1 << 2)
#define TGCR_SLPC (1 << 4)
#define TGCR_STLC (1 << 5)
#define TGCR_HSYNC_EN (1 << 6)
#define TGCR_HSYNC_POL (1 << 7)
#define TGCR_POWERMODE_SHIFT 8
#define TGCR_POWER_OFF (0x0 << TGCR_POWERMODE_SHIFT)
#define TGCR_POWER_SAVE (0x1 << TGCR_POWERMODE_SHIFT)
#define TGCR_POWER_ON (0x3 << TGCR_POWERMODE_SHIFT)
#define TGCR_POWER_MASK (0x3 << TGCR_POWERMODE_SHIFT)
#define TGCR_INTREFEN (1 << 10)
#define TGCR_ADCCLKCFG_SHIFT 16
#define TGCR_PD_EN (1 << 23)
#define TGCR_PDB_EN (1 << 24)
#define TGCR_PDBTIME_SHIFT 25
#define TGCR_PDBTIME128 (0x3f << TGCR_PDBTIME_SHIFT)
#define TGCR_PDBTIME_MASK (0x7f << TGCR_PDBTIME_SHIFT)
/* TSC General Status Register */
#define TGSR 0x004
#define TCQ_INT (1 << 0)
#define GCQ_INT (1 << 1)
#define SLP_INT (1 << 2)
#define TCQ_DMA (1 << 16)
#define GCQ_DMA (1 << 17)
/* TSC IDLE Config Register */
#define TICR 0x008
/* TouchScreen Convert Queue FIFO Register */
#define TCQFIFO 0x400
/* TouchScreen Convert Queue Control Register */
#define TCQCR 0x404
#define CQCR_QSM_SHIFT 0
#define CQCR_QSM_STOP (0x0 << CQCR_QSM_SHIFT)
#define CQCR_QSM_PEN (0x1 << CQCR_QSM_SHIFT)
#define CQCR_QSM_FQS (0x2 << CQCR_QSM_SHIFT)
#define CQCR_QSM_FQS_PEN (0x3 << CQCR_QSM_SHIFT)
#define CQCR_QSM_MASK (0x3 << CQCR_QSM_SHIFT)
#define CQCR_FQS (1 << 2)
#define CQCR_RPT (1 << 3)
#define CQCR_LAST_ITEM_ID_SHIFT 4
#define CQCR_LAST_ITEM_ID_MASK (0xf << CQCR_LAST_ITEM_ID_SHIFT)
#define CQCR_FIFOWATERMARK_SHIFT 8
#define CQCR_FIFOWATERMARK_MASK (0xf << CQCR_FIFOWATERMARK_SHIFT)
#define CQCR_REPEATWAIT_SHIFT 12
#define CQCR_REPEATWAIT_MASK (0xf << CQCR_REPEATWAIT_SHIFT)
#define CQCR_QRST (1 << 16)
#define CQCR_FRST (1 << 17)
#define CQCR_PD_MSK (1 << 18)
#define CQCR_PD_CFG (1 << 19)
/* TouchScreen Convert Queue Status Register */
#define TCQSR 0x408
#define CQSR_PD (1 << 0)
#define CQSR_EOQ (1 << 1)
#define CQSR_FOR (1 << 4)
#define CQSR_FUR (1 << 5)
#define CQSR_FER (1 << 6)
#define CQSR_EMPT (1 << 13)
#define CQSR_FULL (1 << 14)
#define CQSR_FDRY (1 << 15)
/* TouchScreen Convert Queue Mask Register */
#define TCQMR 0x40c
#define TCQMR_PD_IRQ_MSK (1 << 0)
#define TCQMR_EOQ_IRQ_MSK (1 << 1)
#define TCQMR_FOR_IRQ_MSK (1 << 4)
#define TCQMR_FUR_IRQ_MSK (1 << 5)
#define TCQMR_FER_IRQ_MSK (1 << 6)
#define TCQMR_PD_DMA_MSK (1 << 16)
#define TCQMR_EOQ_DMA_MSK (1 << 17)
#define TCQMR_FOR_DMA_MSK (1 << 20)
#define TCQMR_FUR_DMA_MSK (1 << 21)
#define TCQMR_FER_DMA_MSK (1 << 22)
#define TCQMR_FDRY_DMA_MSK (1 << 31)
/* TouchScreen Convert Queue ITEM 7~0 */
#define TCQ_ITEM_7_0 0x420
/* TouchScreen Convert Queue ITEM 15~8 */
#define TCQ_ITEM_15_8 0x424
#define TCQ_ITEM7_SHIFT 28
#define TCQ_ITEM6_SHIFT 24
#define TCQ_ITEM5_SHIFT 20
#define TCQ_ITEM4_SHIFT 16
#define TCQ_ITEM3_SHIFT 12
#define TCQ_ITEM2_SHIFT 8
#define TCQ_ITEM1_SHIFT 4
#define TCQ_ITEM0_SHIFT 0
#define TCQ_ITEM_TCC0 0x0
#define TCQ_ITEM_TCC1 0x1
#define TCQ_ITEM_TCC2 0x2
#define TCQ_ITEM_TCC3 0x3
#define TCQ_ITEM_TCC4 0x4
#define TCQ_ITEM_TCC5 0x5
#define TCQ_ITEM_TCC6 0x6
#define TCQ_ITEM_TCC7 0x7
#define TCQ_ITEM_GCC7 0x8
#define TCQ_ITEM_GCC6 0x9
#define TCQ_ITEM_GCC5 0xa
#define TCQ_ITEM_GCC4 0xb
#define TCQ_ITEM_GCC3 0xc
#define TCQ_ITEM_GCC2 0xd
#define TCQ_ITEM_GCC1 0xe
#define TCQ_ITEM_GCC0 0xf
/* TouchScreen Convert Config 0-7 */
#define TCC0 0x440
#define TCC1 0x444
#define TCC2 0x448
#define TCC3 0x44c
#define TCC4 0x450
#define TCC5 0x454
#define TCC6 0x458
#define TCC7 0x45c
#define CC_PEN_IACK (1 << 1)
#define CC_SEL_REFN_SHIFT 2
#define CC_SEL_REFN_YNLR (0x1 << CC_SEL_REFN_SHIFT)
#define CC_SEL_REFN_AGND (0x2 << CC_SEL_REFN_SHIFT)
#define CC_SEL_REFN_MASK (0x3 << CC_SEL_REFN_SHIFT)
#define CC_SELIN_SHIFT 4
#define CC_SELIN_XPUL (0x0 << CC_SELIN_SHIFT)
#define CC_SELIN_YPLL (0x1 << CC_SELIN_SHIFT)
#define CC_SELIN_XNUR (0x2 << CC_SELIN_SHIFT)
#define CC_SELIN_YNLR (0x3 << CC_SELIN_SHIFT)
#define CC_SELIN_WIPER (0x4 << CC_SELIN_SHIFT)
#define CC_SELIN_INAUX0 (0x5 << CC_SELIN_SHIFT)
#define CC_SELIN_INAUX1 (0x6 << CC_SELIN_SHIFT)
#define CC_SELIN_INAUX2 (0x7 << CC_SELIN_SHIFT)
#define CC_SELIN_MASK (0x7 << CC_SELIN_SHIFT)
#define CC_SELREFP_SHIFT 7
#define CC_SELREFP_YPLL (0x0 << CC_SELREFP_SHIFT)
#define CC_SELREFP_XPUL (0x1 << CC_SELREFP_SHIFT)
#define CC_SELREFP_EXT (0x2 << CC_SELREFP_SHIFT)
#define CC_SELREFP_INT (0x3 << CC_SELREFP_SHIFT)
#define CC_SELREFP_MASK (0x3 << CC_SELREFP_SHIFT)
#define CC_XPULSW (1 << 9)
#define CC_XNURSW_SHIFT 10
#define CC_XNURSW_HIGH (0x0 << CC_XNURSW_SHIFT)
#define CC_XNURSW_OFF (0x1 << CC_XNURSW_SHIFT)
#define CC_XNURSW_LOW (0x3 << CC_XNURSW_SHIFT)
#define CC_XNURSW_MASK (0x3 << CC_XNURSW_SHIFT)
#define CC_YPLLSW_SHIFT 12
#define CC_YPLLSW_MASK (0x3 << CC_YPLLSW_SHIFT)
#define CC_YNLRSW (1 << 14)
#define CC_WIPERSW (1 << 15)
#define CC_NOS_SHIFT 16
#define CC_YPLLSW_HIGH (0x0 << CC_NOS_SHIFT)
#define CC_YPLLSW_OFF (0x1 << CC_NOS_SHIFT)
#define CC_YPLLSW_LOW (0x3 << CC_NOS_SHIFT)
#define CC_NOS_MASK (0xf << CC_NOS_SHIFT)
#define CC_IGS (1 << 20)
#define CC_SETTLING_TIME_SHIFT 24
#define CC_SETTLING_TIME_MASK (0xff <<CC_SETTLING_TIME_SHIFT)
#define TSC_4WIRE_PRECHARGE 0x158c
#define TSC_4WIRE_TOUCH_DETECT 0x578e
#define TSC_4WIRE_X_MEASUMENT 0x1c90
#define TSC_4WIRE_Y_MEASUMENT 0x4604
#define TSC_GENERAL_ADC_GCC0 0x17dc
#define TSC_GENERAL_ADC_GCC1 0x17ec
#define TSC_GENERAL_ADC_GCC2 0x17fc
/* GeneralADC Convert Queue FIFO Register */
#define GCQFIFO 0x800
#define GCQFIFO_ADCOUT_SHIFT 4
#define GCQFIFO_ADCOUT_MASK (0xfff << GCQFIFO_ADCOUT_SHIFT)
/* GeneralADC Convert Queue Control Register */
#define GCQCR 0x804
/* GeneralADC Convert Queue Status Register */
#define GCQSR 0x808
/* GeneralADC Convert Queue Mask Register */
#define GCQMR 0x80c
/* GeneralADC Convert Queue ITEM 7~0 */
#define GCQ_ITEM_7_0 0x820
/* GeneralADC Convert Queue ITEM 15~8 */
#define GCQ_ITEM_15_8 0x824
#define GCQ_ITEM7_SHIFT 28
#define GCQ_ITEM6_SHIFT 24
#define GCQ_ITEM5_SHIFT 20
#define GCQ_ITEM4_SHIFT 16
#define GCQ_ITEM3_SHIFT 12
#define GCQ_ITEM2_SHIFT 8
#define GCQ_ITEM1_SHIFT 4
#define GCQ_ITEM0_SHIFT 0
#define GCQ_ITEM_GCC0 0x0
#define GCQ_ITEM_GCC1 0x1
#define GCQ_ITEM_GCC2 0x2
#define GCQ_ITEM_GCC3 0x3
/* GeneralADC Convert Config 0-7 */
#define GCC0 0x840
#define GCC1 0x844
#define GCC2 0x848
#define GCC3 0x84c
#define GCC4 0x850
#define GCC5 0x854
#define GCC6 0x858
#define GCC7 0x85c
/* TSC Test Register R/W */
#define TTR 0xc00
/* TSC Monitor Register 1, 2 */
#define MNT1 0xc04
#define MNT2 0xc04
#define DETECT_ITEM_ID_1 1
#define DETECT_ITEM_ID_2 5
#define TS_X_ITEM_ID 2
#define TS_Y_ITEM_ID 3
#define TSI_DATA 1
#define FQS_DATA 0
#endif /* __IMX_ADC_H__*/
上面的常数及寄存器offset定义都没什么,如果不明白的话,好好看看芯片手册里的寄存器说明就可以了。
下面就是真正的驱动实现文件,精华都在这里面呢!
/*!
* @fileadc/imx_adc.c
* @briefThis is the main file of i.MX ADC driver.
*
* @ingroupIMX_ADC
*/
/*
* Includes
*/
#include <linux/slab.h>
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/platform_device.h>
#include <linux/poll.h>
#include <linux/sched.h>
#include <linux/time.h>
#include <linux/wait.h>
#include <linux/imx_adc.h>
#include "imx_adc_reg.h"
static int imx_adc_major;
/*!
* Number ofusers waiting in suspendq
*/
static int swait;
/*!
* Toindicate whether any of the adc devices are suspending
*/
static int suspend_flag;
/*!
* Thesuspendq is used by blocking application calls
*/
static wait_queue_head_t suspendq;
static wait_queue_head_t tsq;
static bool imx_adc_ready;
static bool ts_data_ready;
static int tsi_data = TSI_DATA;
static unsigned short ts_data_buf[16];
static struct class *imx_adc_class;
static struct imx_adc_data *adc_data;
static DECLARE_MUTEX(general_convert_mutex); 声明2个互斥锁
static DECLARE_MUTEX(ts_convert_mutex);
unsigned long tsc_base;
int is_imx_adc_ready(void)
{
returnimx_adc_ready;
}
EXPORT_SYMBOL(is_imx_adc_ready);
void tsc_clk_enable(void) 使能时钟,说白了就是读写寄存器
{
unsignedlong reg;
clk_enable(adc_data->adc_clk);
reg =__raw_readl(tsc_base + TGCR);
reg |=TGCR_IPG_CLK_EN;
__raw_writel(reg,tsc_base + TGCR);
}
void tsc_clk_disable(void) 禁止时钟,说白了就是读写寄存器
{
unsignedlong reg;
clk_disable(adc_data->adc_clk);
reg =__raw_readl(tsc_base + TGCR);
reg&= ~TGCR_IPG_CLK_EN;
__raw_writel(reg,tsc_base + TGCR);
}
void tsc_self_reset(void)
{
unsignedlong reg;
reg =__raw_readl(tsc_base + TGCR);
reg |=TGCR_TSC_RST;
__raw_writel(reg,tsc_base + TGCR);
while(__raw_readl(tsc_base + TGCR) & TGCR_TSC_RST)
continue;
}
/* Internal reference */
void tsc_intref_enable(void)
{
unsignedlong reg;
reg =__raw_readl(tsc_base + TGCR);
reg |=TGCR_INTREFEN;
__raw_writel(reg,tsc_base + TGCR);
}
/* initialize touchscreen */
void imx_tsc_init(void)
{
unsignedlong reg;
intlastitemid;
/* Levelsense */
reg =__raw_readl(tsc_base + TCQCR);
reg&= ~CQCR_PD_CFG; /* edge sensitive*/
reg |=(0xf << CQCR_FIFOWATERMARK_SHIFT); /* watermark */
__raw_writel(reg,tsc_base + TCQCR);
/*Configure 4-wire */
reg = TSC_4WIRE_PRECHARGE;
reg |=CC_IGS;
__raw_writel(reg,tsc_base + TCC0);
reg =TSC_4WIRE_TOUCH_DETECT;
reg |= 3<< CC_NOS_SHIFT; /* 4 samples */
reg |= 32<< CC_SETTLING_TIME_SHIFT; /* it'simportant! */
__raw_writel(reg,tsc_base + TCC1);
reg = TSC_4WIRE_X_MEASUMENT;
reg |= 3<< CC_NOS_SHIFT; /* 4 samples */
reg |= 16<< CC_SETTLING_TIME_SHIFT; /*settling time */
__raw_writel(reg,tsc_base + TCC2);
reg =TSC_4WIRE_Y_MEASUMENT;
reg |= 3<< CC_NOS_SHIFT; /* 4 samples */
reg |= 16<< CC_SETTLING_TIME_SHIFT; /*settling time */
__raw_writel(reg,tsc_base + TCC3);
reg =(TCQ_ITEM_TCC0 << TCQ_ITEM7_SHIFT) |
(TCQ_ITEM_TCC0 << TCQ_ITEM6_SHIFT)|
(TCQ_ITEM_TCC1 << TCQ_ITEM5_SHIFT)|
(TCQ_ITEM_TCC0 << TCQ_ITEM4_SHIFT)|
(TCQ_ITEM_TCC3 << TCQ_ITEM3_SHIFT)|
(TCQ_ITEM_TCC2 << TCQ_ITEM2_SHIFT)|
(TCQ_ITEM_TCC1 << TCQ_ITEM1_SHIFT)|
(TCQ_ITEM_TCC0 << TCQ_ITEM0_SHIFT);
__raw_writel(reg,tsc_base + TCQ_ITEM_7_0);
lastitemid= 5;
reg =__raw_readl(tsc_base + TCQCR);
reg =(reg & ~CQCR_LAST_ITEM_ID_MASK) |
(lastitemid <<CQCR_LAST_ITEM_ID_SHIFT);
__raw_writel(reg,tsc_base + TCQCR);
/* Configidle for 4-wire */
reg =TSC_4WIRE_PRECHARGE;
__raw_writel(reg,tsc_base + TICR);
reg =TSC_4WIRE_TOUCH_DETECT;
__raw_writel(reg,tsc_base + TICR);
/* pendown mask */
reg =__raw_readl(tsc_base + TCQCR);
reg&= ~CQCR_PD_MSK;
__raw_writel(reg,tsc_base + TCQCR);
reg =__raw_readl(tsc_base + TCQMR);
reg&= ~TCQMR_PD_IRQ_MSK;
__raw_writel(reg,tsc_base + TCQMR);
/*Debounce time = dbtime*8 adc clock cycles */
reg =__raw_readl(tsc_base + TGCR);
reg&= ~TGCR_PDBTIME_MASK;
reg |=TGCR_PDBTIME128 | TGCR_HSYNC_EN;
__raw_writel(reg,tsc_base + TGCR);
/* pendown enable */
reg =__raw_readl(tsc_base + TGCR);
reg |=TGCR_PDB_EN;
__raw_writel(reg,tsc_base + TGCR);
reg |=TGCR_PD_EN;
__raw_writel(reg,tsc_base + TGCR);
}
static irqreturn_t imx_adc_interrupt(int irq, void*dev_id)
{
unsignedlong reg;
if(__raw_readl(tsc_base + TGSR) & 0x4) {
/*deep sleep wakeup interrupt */
/*clear tgsr */
__raw_writel(0, tsc_base + TGSR);
/*clear deep sleep wakeup irq */
reg =__raw_readl(tsc_base + TGCR);
reg&= ~TGCR_SLPC;
__raw_writel(reg,tsc_base + TGCR);
/*un-mask pen down and pen down irq */
reg =__raw_readl(tsc_base + TCQCR);
reg&= ~CQCR_PD_MSK;
__raw_writel(reg,tsc_base + TCQCR);
reg =__raw_readl(tsc_base + TCQMR);
reg&= ~TCQMR_PD_IRQ_MSK;
__raw_writel(reg,tsc_base + TCQMR);
} else if((__raw_readl(tsc_base + TGSR) & 0x1) &&
(__raw_readl(tsc_base + TCQSR) & 0x1)) {
/*mask pen down detect irq */
reg =__raw_readl(tsc_base + TCQMR);
reg |=TCQMR_PD_IRQ_MSK;
__raw_writel(reg,tsc_base + TCQMR);
ts_data_ready= 1;
wake_up_interruptible(&tsq);
}
returnIRQ_HANDLED;
}
enum IMX_ADC_STATUS imx_adc_read_general(unsignedshort *result)
{
unsignedlong reg;
unsignedint data_num = 0;
reg =__raw_readl(tsc_base + GCQCR);
reg |=CQCR_FQS;
__raw_writel(reg,tsc_base + GCQCR);
while(!(__raw_readl(tsc_base + GCQSR) & CQSR_EOQ))
continue;
reg =__raw_readl(tsc_base + GCQCR);
reg&= ~CQCR_FQS;
__raw_writel(reg,tsc_base + GCQCR);
reg =__raw_readl(tsc_base + GCQSR);
reg |=CQSR_EOQ;
__raw_writel(reg,tsc_base + GCQSR);
while(!(__raw_readl(tsc_base + GCQSR) & CQSR_EMPT)) {
result[data_num]= __raw_readl(tsc_base + GCQFIFO) >>
GCQFIFO_ADCOUT_SHIFT;
data_num++;
}
returnIMX_ADC_SUCCESS;
}
/*!
* Thisfunction will get raw (X,Y) value by converting the voltage
*@param touch_sample Pointer totouch sample
*
*return This funciton returns 0 ifsuccessful.
*
*
*/
enum IMX_ADC_STATUS imx_adc_read_ts(structt_touch_screen *touch_sample,
int wait_tsi)
{
unsignedlong reg;
intdata_num = 0;
intdetect_sample1, detect_sample2;
memset(ts_data_buf,0, sizeof ts_data_buf);
touch_sample->valid_flag= 1;
if(wait_tsi) {
/*Config idle for 4-wire */
reg =TSC_4WIRE_TOUCH_DETECT;
__raw_writel(reg,tsc_base + TICR);
/* Peninterrupt starts new conversion queue */
reg =__raw_readl(tsc_base + TCQCR);
reg&= ~CQCR_QSM_MASK;
reg |=CQCR_QSM_PEN;
__raw_writel(reg,tsc_base + TCQCR);
/*unmask pen down detect irq */
reg =__raw_readl(tsc_base + TCQMR);
reg&= ~TCQMR_PD_IRQ_MSK;
__raw_writel(reg,tsc_base + TCQMR);
wait_event_interruptible(tsq,ts_data_ready);
while(!(__raw_readl(tsc_base + TCQSR) & CQSR_EOQ))
continue;
/*stop the conversion */
reg =__raw_readl(tsc_base + TCQCR);
reg&= ~CQCR_QSM_MASK;
__raw_writel(reg,tsc_base + TCQCR);
reg =CQSR_PD | CQSR_EOQ;
__raw_writel(reg,tsc_base + TCQSR);
/*change configuration for FQS mode */
tsi_data= TSI_DATA;
reg =(0x1 << CC_YPLLSW_SHIFT) | (0x1 << CC_XNURSW_SHIFT) |
CC_XPULSW;
__raw_writel(reg,tsc_base + TICR);
} else {
/* FQSsemaphore */
down(&ts_convert_mutex);
reg =(0x1 << CC_YPLLSW_SHIFT) | (0x1 << CC_XNURSW_SHIFT) |
CC_XPULSW;
__raw_writel(reg,tsc_base + TICR);
/* FQS*/
reg =__raw_readl(tsc_base + TCQCR);
reg&= ~CQCR_QSM_MASK;
reg |=CQCR_QSM_FQS;
__raw_writel(reg,tsc_base + TCQCR);
reg =__raw_readl(tsc_base + TCQCR);
reg |=CQCR_FQS;
__raw_writel(reg,tsc_base + TCQCR);
while(!(__raw_readl(tsc_base + TCQSR) & CQSR_EOQ))
continue;
/*stop FQS */
reg =__raw_readl(tsc_base + TCQCR);
reg&= ~CQCR_QSM_MASK;
__raw_writel(reg,tsc_base + TCQCR);
reg =__raw_readl(tsc_base + TCQCR);
reg&= ~CQCR_FQS;
__raw_writel(reg,tsc_base + TCQCR);
/*clear status bit */
reg =__raw_readl(tsc_base + TCQSR);
reg |=CQSR_EOQ;
__raw_writel(reg,tsc_base + TCQSR);
tsi_data= FQS_DATA;
/*Config idle for 4-wire */
reg =TSC_4WIRE_PRECHARGE;
__raw_writel(reg,tsc_base + TICR);
reg =TSC_4WIRE_TOUCH_DETECT;
__raw_writel(reg,tsc_base + TICR);
}
while(!(__raw_readl(tsc_base + TCQSR) & CQSR_EMPT)) {
reg =__raw_readl(tsc_base + TCQFIFO);
ts_data_buf[data_num]= reg;
data_num++;
}
touch_sample->x_position1= ts_data_buf[4] >> 4;
touch_sample->x_position2= ts_data_buf[5] >> 4;
touch_sample->x_position3= ts_data_buf[6] >> 4;
touch_sample->y_position1= ts_data_buf[9] >> 4;
touch_sample->y_position2= ts_data_buf[10] >> 4;
touch_sample->y_position3= ts_data_buf[11] >> 4;
detect_sample1= ts_data_buf[0];
detect_sample2= ts_data_buf[12];
if((detect_sample1 > 0x6000) || (detect_sample2 > 0x6000))
touch_sample->valid_flag= 0;
ts_data_ready= 0;
if(!(touch_sample->x_position1 ||
touch_sample->x_position2 ||touch_sample->x_position3))
touch_sample->contact_resistance= 0;
else
touch_sample->contact_resistance= 1;
if(tsi_data == FQS_DATA)
up(&ts_convert_mutex);
returnIMX_ADC_SUCCESS;
}
/*!
* Thisfunction performs filtering and rejection of excessive noise prone
* sampl.
*
*@param ts_curr Touch screen value
*
*@return This function returns 0 onsuccess, -1 otherwise.
*/
static int imx_adc_filter(struct t_touch_screen*ts_curr)
{
unsignedint ydiff1, ydiff2, ydiff3, xdiff1, xdiff2, xdiff3;
unsignedint sample_sumx, sample_sumy;
staticunsigned int prev_x[FILTLEN], prev_y[FILTLEN];
int index= 0;
unsignedint y_curr, x_curr;
staticint filt_count;
/* Addeda variable filt_type to decide filtering at run-time */
unsignedint filt_type = 0;
/* ignorethe data converted when pen down and up */
if((ts_curr->contact_resistance == 0) || tsi_data == TSI_DATA) {
ts_curr->x_position= 0;
ts_curr->y_position= 0;
filt_count= 0;
return0;
}
/* ignorethe data valid */
if(ts_curr->valid_flag == 0)
return-1;
ydiff1 =abs(ts_curr->y_position1 - ts_curr->y_position2);
ydiff2 =abs(ts_curr->y_position2 - ts_curr->y_position3);
ydiff3 =abs(ts_curr->y_position1 - ts_curr->y_position3);
if((ydiff1 > DELTA_Y_MAX) ||
(ydiff2 > DELTA_Y_MAX) || (ydiff3 >DELTA_Y_MAX)) {
pr_debug("imx_adc_filter:Ret pos 1\n");
return-1;
}
xdiff1 =abs(ts_curr->x_position1 - ts_curr->x_position2);
xdiff2 =abs(ts_curr->x_position2 - ts_curr->x_position3);
xdiff3 =abs(ts_curr->x_position1 - ts_curr->x_position3);
if ((xdiff1> DELTA_X_MAX) ||
(xdiff2 > DELTA_X_MAX) || (xdiff3 >DELTA_X_MAX)) {
pr_debug("imx_adc_filter:Ret pos 2\n");
return-1;
}
/*Compute two closer values among the three available Y readouts */
if(ydiff1 < ydiff2) {
if(ydiff1 < ydiff3) {
/*Sample 0 & 1 closest together */
sample_sumy= ts_curr->y_position1 +
ts_curr->y_position2;
} else{
/*Sample 0 & 2 closest together */
sample_sumy= ts_curr->y_position1 +
ts_curr->y_position3;
}
} else {
if(ydiff2 < ydiff3) {
/*Sample 1 & 2 closest together */
sample_sumy= ts_curr->y_position2 +
ts_curr->y_position3;
} else{
/*Sample 0 & 2 closest together */
sample_sumy= ts_curr->y_position1 +
ts_curr->y_position3;
}
}
/*
* Compute two closer values among the threeavailable X
* readouts
*/
if(xdiff1 < xdiff2) {
if(xdiff1 < xdiff3) {
/*Sample 0 & 1 closest together */
sample_sumx= ts_curr->x_position1 +
ts_curr->x_position2;
} else{
/*Sample 0 & 2 closest together */
sample_sumx= ts_curr->x_position1 +
ts_curr->x_position3;
}
} else {
if(xdiff2 < xdiff3) {
/*Sample 1 & 2 closest together */
sample_sumx= ts_curr->x_position2 +
ts_curr->x_position3;
} else{
/*Sample 0 & 2 closest together */
sample_sumx= ts_curr->x_position1 +
ts_curr->x_position3;
}
}
/*
* Wait FILTER_MIN_DELAY number of samples torestart
* filtering
*/
if(filt_count < FILTER_MIN_DELAY) {
/*
* Current output is the average of the twocloser
* values and no filtering is used
*/
y_curr= (sample_sumy / 2);
x_curr= (sample_sumx / 2);
ts_curr->y_position= y_curr;
ts_curr->x_position= x_curr;
filt_count++;
} else {
if(abs(sample_sumx - (prev_x[0] + prev_x[1])) >
(DELTA_X_MAX * 16)) {
pr_debug("imx_adc_filter:: Ret pos 3\n");
return-1;
}
if(abs(sample_sumy - (prev_y[0] + prev_y[1])) >
(DELTA_Y_MAX * 16)) {
pr_debug("imx_adc_filter:: Ret pos 4\n");
return-1;
}
sample_sumy/= 2;
sample_sumx/= 2;
/* Usehard filtering if the sample difference < 10 */
if((abs(sample_sumy - prev_y[0]) > 10) ||
(abs(sample_sumx - prev_x[0]) > 10))
filt_type= 1;
/*
* Current outputs are the average of three previous
* values and the present readout
*/
y_curr= sample_sumy;
for(index = 0; index < FILTLEN; index++) {
if(filt_type == 0)
y_curr= y_curr + (prev_y[index]);
else
y_curr= y_curr + (prev_y[index] / 3);
}
if(filt_type == 0)
y_curr= y_curr >> 2;
else
y_curr= y_curr >> 1;
ts_curr->y_position= y_curr;
x_curr= sample_sumx;
for(index = 0; index < FILTLEN; index++) {
if(filt_type == 0)
x_curr= x_curr + (prev_x[index]);
else
x_curr= x_curr + (prev_x[index] / 3);
}
if(filt_type == 0)
x_curr= x_curr >> 2;
else
x_curr= x_curr >> 1;
ts_curr->x_position= x_curr;
}
/* Updateprevious X and Y values */
for(index = (FILTLEN - 1); index > 0; index--) {
prev_x[index]= prev_x[index - 1];
prev_y[index]= prev_y[index - 1];
}
/*
* Current output will be the most recent pastfor the
* next sample
*/
prev_y[0]= y_curr;
prev_x[0]= x_curr;
return 0;
}
/*!
* Thisfunction retrieves the current touch screen (X,Y) coordinates.
*
*@param touch_sample Pointer totouch sample.
*
*@return This function returnsIMX_ADC_SUCCESS if successful.
*/
enum IMX_ADC_STATUSimx_adc_get_touch_sample(struct t_touch_screen
*touch_sample, int wait_tsi)
{
if (imx_adc_read_ts(touch_sample,wait_tsi))
returnIMX_ADC_ERROR;
if(!imx_adc_filter(touch_sample))
returnIMX_ADC_SUCCESS;
else
returnIMX_ADC_ERROR;
}
EXPORT_SYMBOL(imx_adc_get_touch_sample);
void imx_adc_set_hsync(int on)
{
unsignedlong reg;
if(imx_adc_ready) {
reg =__raw_readl(tsc_base + TGCR);
if(on)
reg|= TGCR_HSYNC_EN;
else
reg&= ~TGCR_HSYNC_EN;
__raw_writel(reg,tsc_base + TGCR);
}
}
EXPORT_SYMBOL(imx_adc_set_hsync);
/*!
* This isthe suspend of power management for the i.MX ADC API.
* Itsupports SAVE and POWER_DOWN state.
*
*@param pdev the device
*@param state the state
*
*@return This function returns 0 ifsuccessful.
*/
static int imx_adc_suspend(struct platform_device*pdev, pm_message_t state)
{
unsignedlong reg;
/* Configidle for 4-wire */
reg =TSC_4WIRE_PRECHARGE;
__raw_writel(reg,tsc_base + TICR);
reg =TSC_4WIRE_TOUCH_DETECT;
__raw_writel(reg,tsc_base + TICR);
/* enabledeep sleep wake up */
reg =__raw_readl(tsc_base + TGCR);
reg |=TGCR_SLPC;
__raw_writel(reg,tsc_base + TGCR);
/* maskpen down and pen down irq */
reg =__raw_readl(tsc_base + TCQCR);
reg |=CQCR_PD_MSK;
__raw_writel(reg,tsc_base + TCQCR);
reg =__raw_readl(tsc_base + TCQMR);
reg |=TCQMR_PD_IRQ_MSK;
__raw_writel(reg,tsc_base + TCQMR);
/* Setpower mode to off */
reg =__raw_readl(tsc_base + TGCR) & ~TGCR_POWER_MASK;
reg |=TGCR_POWER_OFF;
__raw_writel(reg,tsc_base + TGCR);
if(device_may_wakeup(&pdev->dev)) {
enable_irq_wake(adc_data->irq);
} else {
suspend_flag= 1;
tsc_clk_disable();
}
return 0;
};
/*!
* This isthe resume of power management for the i.MX adc API.
* Itsupports RESTORE state.
*
*@param pdev the device
*
* @return This function returns 0 if successful.
*/
static int imx_adc_resume(struct platform_device*pdev) 从休眠状态唤醒
{
unsignedlong reg;
if(device_may_wakeup(&pdev->dev)) {
disable_irq_wake(adc_data->irq);
} else {
suspend_flag= 0;
tsc_clk_enable();
while(swait > 0) {
swait--;
wake_up_interruptible(&suspendq);
}
}
/*recover power mode */
reg =__raw_readl(tsc_base + TGCR) & ~TGCR_POWER_MASK;
reg |=TGCR_POWER_SAVE;
__raw_writel(reg,tsc_base + TGCR);
return 0;
}
/*!
* Thisfunction implements the open method on an i.MX ADC device.
*
*@param inode pointer on the node
*@param file pointer on the file
*@return This function returns 0.
*/
static int imx_adc_open(struct inode *inode, structfile *file)
{
while(suspend_flag) {
swait++;
/*Block if the device is suspended */
if(wait_event_interruptible(suspendq, !suspend_flag))
return-ERESTARTSYS;
}
pr_debug("imx_adc: imx_adc_open()\n");
return 0;
}
/*!
* Thisfunction implements the release method on an i.MX ADC device.
*
*@param inode pointer on the node
*@param file pointer on the file
*@return This function returns 0.
*/
static int imx_adc_free(struct inode *inode,struct file *file)
{
pr_debug("imx_adc: imx_adc_free()\n");
return 0;
}
/*!
* Thisfunction initializes all ADC registers with default values. This
* functionalso registers the interrupt events.
*
*@return This function returnsIMX_ADC_SUCCESS if successful.
*/
int imx_adc_init(void)
{
unsignedlong reg;
pr_debug("imx_adc_init()\n");
if(suspend_flag)
return-EBUSY;
tsc_clk_enable();
/* Reset*/
tsc_self_reset();
/*Internal reference */
tsc_intref_enable();
/* Setpower mode */
reg = __raw_readl(tsc_base+ TGCR) & ~TGCR_POWER_MASK;这里又是操作寄存器,读取->修改->写回
reg |=TGCR_POWER_SAVE;
__raw_writel(reg,tsc_base + TGCR);
imx_tsc_init();
returnIMX_ADC_SUCCESS;
}
EXPORT_SYMBOL(imx_adc_init);
/*!
* Thisfunction disables the ADC, de-registers the interrupt events.
*
*@return This function returnsIMX_ADC_SUCCESS if successful.
*/
enum IMX_ADC_STATUS imx_adc_deinit(void)
{
pr_debug("imx_adc_deinit()\n");
returnIMX_ADC_SUCCESS;
}
EXPORT_SYMBOL(imx_adc_deinit);
/*!
* Thisfunction triggers a conversion and returns one sampling result of one
* channel.
*
*@param channel The channel to be sampled
*@param result The pointer to the conversion result. Thememory
* should be allocated bythe caller of this function.
*
*@return This function returnsIMX_ADC_SUCCESS if successful.
*/
这个函数就是AD采样函数,说白了就是操作ADC的寄存器。
enum IMX_ADC_STATUS imx_adc_convert(enum t_channelchannel,
unsigned short *result)
{
unsignedlong reg;
int lastitemid;
structt_touch_screen touch_sample;
switch(channel) {
case TS_X_POS:
imx_adc_get_touch_sample(&touch_sample,0);
result[0]= touch_sample.x_position;
/* ifno pen down ,recover the register configuration */
if(touch_sample.contact_resistance == 0) {
reg= __raw_readl(tsc_base + TCQCR);
reg&= ~CQCR_QSM_MASK;
reg|= CQCR_QSM_PEN;
__raw_writel(reg,tsc_base + TCQCR);
reg= __raw_readl(tsc_base + TCQMR); 这里对寄存器的操作:读取->修改->写回
reg&= ~TCQMR_PD_IRQ_MSK;
__raw_writel(reg,tsc_base + TCQMR);
}
break;
case TS_Y_POS:
imx_adc_get_touch_sample(&touch_sample,0);
result[1]= touch_sample.y_position;
/* ifno pen down ,recover the register configuration */
if(touch_sample.contact_resistance == 0) {
reg= __raw_readl(tsc_base + TCQCR);
reg&= ~CQCR_QSM_MASK;
reg|= CQCR_QSM_PEN;
__raw_writel(reg,tsc_base + TCQCR);
reg= __raw_readl(tsc_base + TCQMR);
reg&= ~TCQMR_PD_IRQ_MSK;
__raw_writel(reg, tsc_base + TCQMR);
}
break;
case GER_PURPOSE_ADC0:
down(&general_convert_mutex);加锁
lastitemid= 0;
reg =(0xf << CQCR_FIFOWATERMARK_SHIFT) |
(lastitemid <<CQCR_LAST_ITEM_ID_SHIFT) | CQCR_QSM_FQS;
__raw_writel(reg,tsc_base + GCQCR);
reg =TSC_GENERAL_ADC_GCC0;
reg |=(3 << CC_NOS_SHIFT) | (16 << CC_SETTLING_TIME_SHIFT);
__raw_writel(reg,tsc_base + GCC0);写寄存器
imx_adc_read_general(result);读取结果
up(&general_convert_mutex);解锁
break;
case GER_PURPOSE_ADC1:
down(&general_convert_mutex);
lastitemid= 0;
reg =(0xf << CQCR_FIFOWATERMARK_SHIFT) |
(lastitemid <<CQCR_LAST_ITEM_ID_SHIFT) | CQCR_QSM_FQS;
__raw_writel(reg,tsc_base + GCQCR);
reg =TSC_GENERAL_ADC_GCC1;
reg |=(3 << CC_NOS_SHIFT) | (16 << CC_SETTLING_TIME_SHIFT);
__raw_writel(reg,tsc_base + GCC0);
imx_adc_read_general(result);
up(&general_convert_mutex);
break;
case GER_PURPOSE_ADC2:
down(&general_convert_mutex);
lastitemid= 0;
reg =(0xf << CQCR_FIFOWATERMARK_SHIFT) |
(lastitemid <<CQCR_LAST_ITEM_ID_SHIFT) | CQCR_QSM_FQS;
__raw_writel(reg,tsc_base + GCQCR);
reg =TSC_GENERAL_ADC_GCC2;
reg |=(3 << CC_NOS_SHIFT) | (16 << CC_SETTLING_TIME_SHIFT);
__raw_writel(reg,tsc_base + GCC0);
imx_adc_read_general(result);
up(&general_convert_mutex);
break;
caseGER_PURPOSE_MULTICHNNEL:
down(&general_convert_mutex);
reg =TSC_GENERAL_ADC_GCC0;
reg |=(3 << CC_NOS_SHIFT) | (16 << CC_SETTLING_TIME_SHIFT);
__raw_writel(reg,tsc_base + GCC0);
reg =TSC_GENERAL_ADC_GCC1;
reg |=(3 << CC_NOS_SHIFT) | (16 << CC_SETTLING_TIME_SHIFT);
__raw_writel(reg,tsc_base + GCC1);
reg =TSC_GENERAL_ADC_GCC2;
reg |=(3 << CC_NOS_SHIFT) | (16 << CC_SETTLING_TIME_SHIFT);
__raw_writel(reg,tsc_base + GCC2);
reg =(GCQ_ITEM_GCC2 << GCQ_ITEM2_SHIFT) |
(GCQ_ITEM_GCC1 << GCQ_ITEM1_SHIFT)|
(GCQ_ITEM_GCC0 << GCQ_ITEM0_SHIFT);
__raw_writel(reg,tsc_base + GCQ_ITEM_7_0);
lastitemid= 2;
reg =(0xf << CQCR_FIFOWATERMARK_SHIFT) |
(lastitemid <<CQCR_LAST_ITEM_ID_SHIFT) | CQCR_QSM_FQS;
__raw_writel(reg,tsc_base + GCQCR);
imx_adc_read_general(result);
up(&general_convert_mutex);
break;
default:
pr_debug("%s:bad channel number\n", __func__);
returnIMX_ADC_ERROR;
}
returnIMX_ADC_SUCCESS;
}
EXPORT_SYMBOL(imx_adc_convert);
/*!
* Thisfunction triggers a conversion and returns sampling results of each
* specifiedchannel.
*
*@param channels This input parameter is bitmap to specifychannels
* to be sampled.
*@param result The pointer to array to store samplingresults.
* The memory should beallocated by the caller of this
* function.
*
*@return This function returnsIMX_ADC_SUCCESS if successful.
*/
这可是枚举类型啊!C也支持?
enumIMX_ADC_STATUS imx_adc_convert_multichnnel(enum t_channel channels,
unsignedshort *result)
{
imx_adc_convert(GER_PURPOSE_MULTICHNNEL,result);
returnIMX_ADC_SUCCESS;
}
EXPORT_SYMBOL(imx_adc_convert_multichnnel);
/*!
* Thisfunction implements IOCTL controls on an i.MX ADC device.
*
*@param inode pointer on the node
*@param file pointer on the file
*@param cmd the command 这里是命令,记得好像得用幻数或魔数来定义,这样防止与其他设备的命令冲突,发出错误的命令。
*@param arg the parameter 这里是命令的参数
*@return This function returns 0 ifsuccessful.
*/
static int imx_adc_ioctl(struct inode *inode,struct file *file,
unsigned int cmd,unsigned long arg)
{
structt_adc_convert_param *convert_param;
if((_IOC_TYPE(cmd) != 'p') && (_IOC_TYPE(cmd) != 'D'))
return-ENOTTY;
while(suspend_flag) {
swait++;
/*Block if the device is suspended */
if(wait_event_interruptible(suspendq, !suspend_flag))
return-ERESTARTSYS;
}
switch(cmd) {
case IMX_ADC_INIT:
pr_debug("initadc\n");
CHECK_ERROR(imx_adc_init());
break;
case IMX_ADC_DEINIT:
pr_debug("deinitadc\n");
CHECK_ERROR(imx_adc_deinit());
break;
case IMX_ADC_CONVERT:
在内核空间中申请一块内存
convert_param= kmalloc(sizeof(*convert_param), GFP_KERNEL);
if(convert_param == NULL)
return-ENOMEM;
将用户空间请求结构体拷贝到内核空间的结构体中
if(copy_from_user(convert_param,
(struct t_adc_convert_param *)arg,
sizeof(*convert_param))) {
kfree(convert_param);
return-EFAULT;
}
按照用户空间传来的结构体中指定的通道进行AD转换并将结果保存在内核空间的结构体中
CHECK_ERROR_KFREE(imx_adc_convert(convert_param->channel,
convert_param->result),
(kfree(convert_param)));
再将内核空间的结构体数据拷贝到用户空间
if(copy_to_user((struct t_adc_convert_param *)arg,
convert_param, sizeof(*convert_param))) {
kfree(convert_param);
return-EFAULT;
}
kfree(convert_param);
break;
case IMX_ADC_CONVERT_MULTICHANNEL:多通道转换跟单通道采样差不多,唯一不同的是底层调用了多通路采样函数
convert_param= kmalloc(sizeof(*convert_param), GFP_KERNEL);
if(convert_param == NULL)
return-ENOMEM;
if(copy_from_user(convert_param,
(struct t_adc_convert_param *)arg,
sizeof(*convert_param))) {
kfree(convert_param);
return-EFAULT;
}
CHECK_ERROR_KFREE(imx_adc_convert_multichnnel
(convert_param->channel,
convert_param->result),
(kfree(convert_param)));
if(copy_to_user((struct t_adc_convert_param *)arg,
convert_param, sizeof(*convert_param))) {
kfree(convert_param);
return-EFAULT;
}
kfree(convert_param);
break;
default:
pr_debug("imx_adc_ioctl:unsupported ioctl command 0x%x\n",
cmd);
return-EINVAL;
}
return 0;
}
为了配合register_chrdev()所以这里定义了文件操作结构
static struct file_operations imx_adc_fops = {
.owner =THIS_MODULE,
.ioctl =imx_adc_ioctl,
.open =imx_adc_open,
.release= imx_adc_free,
};
static int imx_adc_module_probe(structplatform_device *pdev)
{
int ret =0;
intretval;
structdevice *temp_class;
structresource *res;
void __iomem*base;
/*ioremap the base address */
res =platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (res== NULL) {
dev_err(&pdev->dev,"No TSC base address provided\n");
gotoerr_out0;
}
物理地址到虚拟地址的映射,驱动程序不能直接访问物理地址,必须映射到驱动程序空间中的虚拟地址才能进行操作。
base =ioremap(res->start, res->end - res->start);
if (base== NULL) {
dev_err(&pdev->dev,"failed to rebase TSC base address\n");
gotoerr_out0;
}
tsc_base= (unsigned long)base;
注册字符型设备,设备号是内态动态分配的
/* createthe chrdev */
imx_adc_major= register_chrdev(0, "imx_adc", &imx_adc_fops);
if(imx_adc_major < 0) {
dev_err(&pdev->dev,"Unable to get a major for imx_adc\n");
returnimx_adc_major;
}
init_waitqueue_head(&suspendq);
init_waitqueue_head(&tsq);
动态创建设备文件,这里会自动创建/dev/imx_adc,udev机制的好处是不需要手工mknod
imx_adc_class = class_create(THIS_MODULE,"imx_adc");
if(IS_ERR(imx_adc_class)) {
dev_err(&pdev->dev,"Error creating imx_adc class.\n");
ret =PTR_ERR(imx_adc_class);
gotoerr_out1;
}
temp_class= device_create(imx_adc_class, NULL,
MKDEV(imx_adc_major, 0), NULL,"imx_adc");
if(IS_ERR(temp_class)) {
dev_err(&pdev->dev,"Error creating imx_adc class device.\n");
ret =PTR_ERR(temp_class);
gotoerr_out2;
}
动态分配内存
adc_data= kmalloc(sizeof(struct imx_adc_data), GFP_KERNEL);
if(adc_data == NULL)
return-ENOMEM;
adc_data->irq= platform_get_irq(pdev, 0);获取中断资源号
向内核请求中断并传给中断处理函数地址
retval =request_irq(adc_data->irq, imx_adc_interrupt,
0, MOD_NAME, MOD_NAME);
if(retval) {
returnretval;
}
adc_data->adc_clk= clk_get(&pdev->dev, "tchscrn_clk");
ret =imx_adc_init();
if (ret!= IMX_ADC_SUCCESS) {
dev_err(&pdev->dev,"Error in imx_adc_init.\n");
gotoerr_out4;
}
imx_adc_ready= 1;
/* Bydefault, devices should wakeup if they can */
/* SoTouchScreen is set as "should wakeup" as it can */
device_init_wakeup(&pdev->dev,1);
pr_info("i.MXADC at 0x%x irq %d\n", (unsigned int)res->start,
adc_data->irq);
returnret;
err_out4:
device_destroy(imx_adc_class,MKDEV(imx_adc_major, 0));
err_out2:
class_destroy(imx_adc_class);
err_out1:
unregister_chrdev(imx_adc_major,"imx_adc");
err_out0:
returnret;
}
static int imx_adc_module_remove(structplatform_device *pdev)
{
imx_adc_ready= 0;
imx_adc_deinit();
使用udev机制动态删除设备文件节点
device_destroy(imx_adc_class,MKDEV(imx_adc_major, 0));
class_destroy(imx_adc_class);
注销字符设备
unregister_chrdev(imx_adc_major,"imx_adc");
释放中断资源
free_irq(adc_data->irq,MOD_NAME);
kfree(adc_data);跟kmalloc相对应,动态释放内存
pr_debug("i.MXADC successfully removed\n");
return 0;
}
定义平台驱动platform_driver结构体,作为平台设备驱动必须要定义这个!
static struct platform_driver imx_adc_driver = {
.driver ={
.name = "imx_adc",此处的名字必须要跟平台设备的名字一样,否则匹配不上!
},
.suspend= imx_adc_suspend,
.resume =imx_adc_resume,
.probe =imx_adc_module_probe, 相当于初始化的时候会自动调用这个函数
.remove =imx_adc_module_remove,
};
/*
*Initialization and Exit
*/
static int __init imx_adc_module_init(void)
{
pr_debug("i.MXADC driver loading...\n");
returnplatform_driver_register(&imx_adc_driver);注册平台驱动
}
static void __exit imx_adc_module_exit(void)
{
platform_driver_unregister(&imx_adc_driver);注销平台驱动
pr_debug("i.MXADC driver successfully unloaded\n");
}
/*
* Moduleentry points
*/
这里好奇怪噢。我记得2.6内核好像已经不再这样定义了???
module_init(imx_adc_module_init);
module_exit(imx_adc_module_exit);
MODULE_DESCRIPTION("i.MX ADC devicedriver");
MODULE_AUTHOR("Freescale Semiconductor,Inc.");
MODULE_LICENSE("GPL");
驱动中老爱调用一些函数,其实这些函数的实现也是比较简单的,为什么要这样做呢?搞不明白。这样使人脑袋都大了。唉。下面就是我列举的一些,大伙看看吧:
#define __raw_readl(p) (*(unsigned long *)(p))
#define __raw_writel(v,p) (*(unsigned long *)(p) = (v))
我觉得加上volatile会更好一些,可以引用的不是这个文件吧。当然,别的文件也有实现,说真的,我也不知道驱动引用的是哪个文件的。
define __raw_writel(v,a) (__chk_io_ptr(a),*(volatile unsigned int __force *)(a) = (v))
为了使驱动程序加进内核,还得考虑该文件夹上一目录的Kconfig和Makefile,因为内核是一级一级的找文件的。
# drivers/video/mxc/Kconfig
if ARCH_MXC
menu "MXC support drivers"
config MXC_IPU
bool"Image Processing Unit Driver"
dependson !ARCH_MX21
dependson !ARCH_MX27
dependson !ARCH_MX25
selectMXC_IPU_V1 if !ARCH_MX37 && !ARCH_MX5
selectMXC_IPU_V3 if ARCH_MX37 || ARCH_MX5
selectMXC_IPU_V3D if ARCH_MX37
selectMXC_IPU_V3EX if ARCH_MX5
help
If you plan to use the Image Processing unit,say
Y here. IPU is needed by Framebuffer and V4L2drivers.
source "drivers/mxc/ipu/Kconfig"
source "drivers/mxc/ipu3/Kconfig"
source "drivers/mxc/ssi/Kconfig"
source "drivers/mxc/dam/Kconfig"
source "drivers/mxc/pmic/Kconfig"
source "drivers/mxc/mcu_pmic/Kconfig"
source "drivers/mxc/security/Kconfig"
source "drivers/mxc/hmp4e/Kconfig"
source "drivers/mxc/hw_event/Kconfig"
source "drivers/mxc/vpu/Kconfig"
source "drivers/mxc/asrc/Kconfig"
source "drivers/mxc/bt/Kconfig"
source "drivers/mxc/gps_ioctrl/Kconfig"
source "drivers/mxc/mlb/Kconfig"
source"drivers/mxc/adc/Kconfig" 我们的ADC驱动在此
source "drivers/mxc/amd-gpu/Kconfig"
endmenu
endif
在看一下Makefile
obj-$(CONFIG_MXC_IPU_V1) += ipu/
obj-$(CONFIG_MXC_IPU_V3) += ipu3/
obj-$(CONFIG_MXC_SSI) +=ssi/
obj-$(CONFIG_MXC_DAM) +=dam/
obj-$(CONFIG_MXC_PMIC_MC9SDZ60) += mcu_pmic/
obj-$(CONFIG_MXC_PMIC) += pmic/
obj-$(CONFIG_MXC_HMP4E) +=hmp4e/
obj-y += security/
obj-$(CONFIG_MXC_VPU) += vpu/
obj-$(CONFIG_MXC_HWEVENT) += hw_event/
obj-$(CONFIG_MXC_ASRC) += asrc/
obj-$(CONFIG_MXC_BLUETOOTH) += bt/
obj-$(CONFIG_GPS_IOCTRL) += gps_ioctrl/
obj-$(CONFIG_MXC_MLB) += mlb/
obj-$(CONFIG_IMX_ADC) += adc/ 我们的ADC驱动在此
obj-$(CONFIG_MXC_AMD_GPU) +=amd-gpu/
以后我们自己增加驱动程序到内核的时候,完全可以参考这些做法。