对Freescale i.mx53 ADC驱动程序的透彻分析

来源:互联网 发布:百川教育软件 编辑:程序博客网 时间:2024/04/29 12:22

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))

 

为了使驱动程序加进内核,还得考虑该文件夹上一目录的KconfigMakefile,因为内核是一级一级的找文件的。

# 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/

 

以后我们自己增加驱动程序到内核的时候,完全可以参考这些做法。

 

原创粉丝点击