i.MX283开发板MISC设备驱动——LRADC

MISC设备:

​​​​​​MISC 设备驱动的主设备号都为 10,不同的设备使用不同的从设备号。MISC 设备会自动创建 cdev,也不需要创建class和device,它实际上也属于字符设备——只不过是简化的字符设备。

下面是misc设备结构体的定义:

struct miscdevice  {
	int minor;
	const char *name;
	const struct file_operations *fops;
	struct list_head list;
	struct device *parent;
	struct device *this_device;
	const char *nodename;
	mode_t mode;
};
/*
minor:次设备号 
name:设备名字
fops:file_operations 结构体
parent:这个指针决定了在/sys文件系统里面,它是创建在哪个目录下。如果为空就在/sys/class根目录下创建,如果不为空都是在/sys/class/misc 文件下面创建的一些属性文件。
this_device:这个就代表当前设备的设备结构体
*/

主要就是minor、name、fops三个参数,其中minor如果定义为MISC_DYNAMIC_MINOR(255),表示由内核动态分配次设备号;name 就是此 MISC 设备名字;fops就是open、read、write这些调用的集合。

所以对于MISC设备,我们只需填充上面三个参数就可以了。

定义并填充好一个miscdevice类型的结构体后,就可以用以下函数注册设备了,它会自动创建设备节点并在/dev 目录下生成一个名为name的设备文件。

int misc_register(struct miscdevice * misc)

 对应的注销函数如下:

int misc_deregister(struct miscdevice *misc)

下面就利用MISC设备驱动框架,编写一个ADC的驱动程序。

LRADC(低精度ADC)

imx283开发板有16个LRADC物理通道,同时还有8个虚拟通道,这8个虚拟通道可以同时使用,且每一个通道都可以映射到16个物理信道。但是只有通道0~6可以用于一般用途,它们内部都有硬件除2电路可供配置,虚拟通道7专门测量电池电压,其内部有一个硬件除4电路。

LRADC主要特点如下:

  • 在未开启除 2 电路时,其量程为 0~1.85V,开启除 2 电路时,量程为 0~3.7V,有寄存器可配置是否开启
  • LRADC内部参考电压 1.85V
  • LRADC 主时钟为24MHz,可以配置4/6/8/12分频,采样率=CLK/16

LRADC配置流程如下:

ADC复位——配置时钟——配置采样率、采集次数、数据长度等参数——配置中断、清除相关标志位——启动ADC——等待转换完成——读取数据——清除标志位——开启下一次转换

LRADC的寄存器地址如下:

i.MX283开发板MISC设备驱动——LRADC_第1张图片

注意:imx28x系列芯片大多数寄存器的置位、复位、翻转操作是写不同的寄存器。比如HW_LRADC_CTRL0

寄存器的地址是0x80050000

HW_LRADC_CTRL0: 0x80050000
HW_LRADC_CTRL0_SET: 0x80050004
HW_LRADC_CTRL0_CLR: 0x80050008
HW_LRADC_CTRL0_TOG: 0x8005000C

它的置位、复位、翻转寄存器依次偏移4字节。

该组寄存器的基地址是0x80050000,若是裸机开发,我们可以直接对这个地址进行读写,但是在Linux上是不行的,Linux是不允许程序直接访问物理地址的,它有一个MMU内存管理单元,MMU的一个作用就是负责内存保护,设置存储器的访问权限。但MMU的另一个功能就是内存映射,就是将一段物理内存映射到一段虚拟内存上,这样实际上就间接的访问了真实的物理内存,这里需要用到两个函数:ioremap 和 iounmap。

#define ioremap(cookie,size)		__arm_ioremap(cookie, size, MT_DEVICE)
void __iomem *__arm_ioremap(unsigned long phys_addr, size_t size,
			    unsigned int mtype)
{
	return (void __iomem *)phys_addr;
}


#define iounmap(cookie)			__iounmap(cookie)
void __iounmap(volatile void __iomem *io_addr)
{
	void *addr = (void *)(PAGE_MASK & (unsigned long)io_addr);
#ifndef CONFIG_SMP
	struct vm_struct **p, *tmp;
	write_lock(&vmlist_lock);
	for (p = &vmlist ; (tmp = *p) ; p = &tmp->next) {
		if ((tmp->flags & VM_IOREMAP) && (tmp->addr == addr)) {
			if (tmp->flags & VM_ARM_SECTION_MAPPING) {
				unmap_area_sections((unsigned long)tmp->addr,
						    tmp->size);
			}
			break;
		}
	}
	write_unlock(&vmlist_lock);
#endif

	vunmap(addr);
}

ioremap 参数cookie就是要映射的起始地址,size就是需要映射空间的大小

iounmap 只有一个参数 addr,此参数就是要取消映射的虚拟地址空间首地址

除此之外,我们还需要内存访问函数来访问映射后的地址,使用 ioremap 函数将寄存器的物理地址映射到虚拟地址以后,我们就可以直接通过指针访问这些地址,但是 Linux 内核不建议这么做,而是推荐使用一组操作函数来对映射后的内存进行读写操作。

static inline void __raw_writeb(u8 b, volatile void __iomem *addr)
{
	*(volatile u8 __force *) addr = b;
}

static inline void __raw_writew(u16 b, volatile void __iomem *addr)
{
	*(volatile u16 __force *) addr = b;
}

static inline void __raw_writel(u32 b, volatile void __iomem *addr)
{
	*(volatile u32 __force *) addr = b;
}

#define writeb __raw_writeb
#define writew(b,addr) __raw_writew(__cpu_to_le16(b),addr)
#define writel(b,addr) __raw_writel(__cpu_to_le32(b),addr)

 writeb、writew 和 writel 这三个函数分别对应 8bit、16bit 和 32bit 写操作,参数 value 是要写入的数值,addr 是要写入的地址。

static inline u8 __raw_readb(const volatile void __iomem *addr)
{
	return *(const volatile u8 __force *) addr;
}

static inline u16 __raw_readw(const volatile void __iomem *addr)
{
	return *(const volatile u16 __force *) addr;
}

static inline u32 __raw_readl(const volatile void __iomem *addr)
{
	return *(const volatile u32 __force *) addr;
}

#define readb __raw_readb
#define readw(addr) __le16_to_cpu(__raw_readw(addr))
#define readl(addr) __le32_to_cpu(__raw_readl(addr))

readb、readw 和 readl 这三个函数分别对应 8bit、16bit 和 32bit 读操作,参数 addr 就是要读取写内存地址,返回值就是读取到的数据。

接下来,就是编写驱动程序了,具体的寄存器配置就不一一介绍了,这里直接给出驱动源码:

#include      /* module */
#include          /* file operation*/
#include       /* get_user()*/
#include  /* miscdevice*/
#include            /* ioctl*/
#include  /* #define*/

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#include "mach/adc_driver.h"

#define DEBUG_T 1  //调试开关
#define DEVICE_NAME	"imx283_adc"//驱动名称
#define LRADC_BASE  0x80050000 //LRADC基地址 实际物理地址
static void __iomem *adc_base = NULL;//映射后LRADC基地址


static int adc_open(struct inode *inode, struct file *fp)
{
   
   /*SFTRST=0 HSADC退出复位进入正常模式*/
   writel(1<<31,adc_base+0x08);//HW_HSADC_CTRL0_CLR
   
   /*CLKGATE=0 进入正常模式*/
   writel(1<<30,adc_base+0x08);//HW_HSADC_CTRL0_CLR
   
   /*失能ADC通道0/1/6中断和按键中断*/
   writel((1<<28)|(1<<27)|(1<<22)|(1<<17)|(1<<16),adc_base+HW_LRADC_CTRL1_CLR);

   /*清除ADC0/1/6 标志位*/
   writel((1<<6)|(1<<1)|(1<<0),adc_base+HW_LRADC_CTRL1_CLR);
   
   /*使能0/1/2/6通道硬件/2电路      ADC量程为0~3.7V*/ 
   writel((1<<30)|(1<<25)|(1<<24), adc_base + HW_LRADC_CTRL2_SET);
   
   /*丢弃初次ADC转换的前3次采样值           */
   writel(3<<24,adc_base+HW_LRADC_CTRL3_SET);

   /*配置  ADC时钟为4M 采样率=4M/16*/ 
   writel(1<<9,adc_base+HW_LRADC_CTRL3_CLR);
   writel(1<<8,adc_base+HW_LRADC_CTRL3_SET);
   
   /*指定转换ADC通道0~7对应的实际通道*/
   writel(0xFFFFFFFF,adc_base+HW_LRADC_CTRL4_CLR);
   writel(0x76543210,adc_base+HW_LRADC_CTRL4_SET);
   
#if (DEBUG_T)	   
   printk("HW_LRADC_CTRL0=%08X\n",readl(adc_base+HW_LRADC_CTRL0));
   printk("HW_LRADC_CTRL1=%08X\n",readl(adc_base+HW_LRADC_CTRL1));
   printk("HW_LRADC_CTRL2=%08X\n",readl(adc_base+HW_LRADC_CTRL2));
   printk("HW_LRADC_CTRL3=%08X\n",readl(adc_base+HW_LRADC_CTRL3));
   printk("HW_LRADC_CTRL4=%08X\n",readl(adc_base+HW_LRADC_CTRL4));
   printk("HW_LRADC_VERSION=%08X\n",readl(adc_base+HW_LRADC_VERSION));
#endif   

   return 0;
   
}


static int adc_release (struct inode *inode, struct file *fp)
{
     /*失能ADC,让ADC进入复位模式*/
	writel((1<<31)|(1<<30), adc_base + HW_LRADC_CTRL0_SET);	  
	return 0;
}



static int adc_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)
{

   int ch = 0;
   int adc_res = 0;
   if(_IOC_TYPE(cmd)!=ADC_IOC_MAGIC)
   {
     return -ENOTTY;//命令参数错误
   }
   
  /* switch(cmd)
   	{
      case ADC_CH0:
		break;
	  case ADC_CH1:
	   break;
	  case ADC_CH6:
	  	break;
	  default:
		printk("adc control cmd invalid!!\n");
		return -1;
   }*/

   ch = _IOC_NR(cmd);//获取通道号
   
   printk("ch=%d\n",ch);
   
   /*ADC采集不连续 单次模式*/     	
   writel(1<<29,adc_base+HW_LRADC_CHn_CLR(ch));
   
   /*采集样本数为1*/
   writel(0x1f<<24,adc_base+HW_LRADC_CHn_CLR(ch));

   /*清空ADC转换结果寄存器*/
   writel(0x3FFFF<<0,adc_base+HW_LRADC_CHn_CLR(ch));
   
   /*启动ADC开始转换*/
   writel((1<

adc_driver.h的内容为:

#ifndef __ADC_DRIVER_H
#define __ADC_DRIVER_H

#define ADC_IOC_MAGIC     'j'

#define ADC_CH0     	_IOR(ADC_IOC_MAGIC, 0, int)	/* 通道0			*/
#define ADC_CH1     	_IOR(ADC_IOC_MAGIC, 1, int)	/* 通道1			*/
#define ADC_CH2     	_IOR(ADC_IOC_MAGIC, 2, int)	/* 通道2			*/
#define ADC_CH3   	    _IOR(ADC_IOC_MAGIC, 3, int)	/* 通道3			*/
#define ADC_CH4     	_IOR(ADC_IOC_MAGIC, 4, int)	/* 通道4			*/
#define ADC_CH5     	_IOR(ADC_IOC_MAGIC, 5, int)	/* 通过5			*/
#define ADC_CH6     	_IOR(ADC_IOC_MAGIC, 6, int)	/* 通道6			*/
#define ADC_VBAT     	_IOR(ADC_IOC_MAGIC, 7, int)	/* 测量电池电压			*/

#define ADC_CH0_DIV2     	_IOW(ADC_IOC_MAGIC, 20, int)	/* 通道0,开启除 2		*/
#define ADC_CH1_DIV2     	_IOW(ADC_IOC_MAGIC, 21, int)	/* 通道1,开启除 2		*/
#define ADC_CH2_DIV2     	_IOW(ADC_IOC_MAGIC, 22, int)	/* 通道2,开启除 2		*/
#define ADC_CH3_DIV2   	_IOW(ADC_IOC_MAGIC, 23, int)	/* 通道3,开启除 2		*/
#define ADC_CH4_DIV2     	_IOW(ADC_IOC_MAGIC, 24, int)	/* 通道4,开启除 2		*/
#define ADC_CH5_DIV2     	_IOW(ADC_IOC_MAGIC, 25, int)	/* 通过5,开启除 2		*/
#define ADC_CH6_DIV2     	_IOW(ADC_IOC_MAGIC, 26, int)	/* 通道6,开启除 2		*/
#define ADC_VBAT_DIV4     _IOW(ADC_IOC_MAGIC, 27, int)	/* 测量电池电压,开启除4		*/

#endif

adc_driver.h文件必须和引用的位置保持一致,我这里引用的是#include "mach/adc_driver.h",所以我这个文件放在内核源码

arch/arm/mach-mx28/include/mach下。

接下来我们需要编写一个测试程序,测试程序也需要用到上面的adc_driver.h文件,测试程序如下:

#include	/* using printf()        */
#include      /* using sleep()         */
#include       /* using file operation  */
#include   /* using ioctl()         */
#include 
#include  //sleep  write read close
#include "adc_driver.h"


int main( int argc, const char * argv [] )
{

   int fd,res,ch;
   if(argc !=2)
   {
     printf("parameter nunmber error \n");
	 return 0;
   }
   ch = atoi(argv[1]);
   fd = open("/dev/imx283_adc",O_RDWR);
   if(fd < 0)
   {
     printf("open imx283_adc error %d\n",fd);
	 close(fd);
	 return 0;
   }

   while(1)
   {
 
      switch(ch) 
      {
      	 case 0:ioctl(fd,ADC_CH0,&res);break;
		 case 1:ioctl(fd,ADC_CH1,&res);break;	 
		 case 6:ioctl(fd,ADC_CH6,&res);break;
		 case 7:ioctl(fd,ADC_VBAT,&res);break;
		 default:ioctl(fd,ADC_CH0,&res);break;
      }
	  printf("ADC_CH%d=%0.2f\n",ch,(double)(res)*3.7/4096);
	  sleep(1);
   }
   close(fd);
   return 0;
}

注意这里的测试程序是需要传入参数的,参数个数为1个,多于或少于1个会提示参数错误。参数说明如下:

传入参数 说明
0 ADC通道0 
1 ADC通道1
6 ADC通道6
7 ADC通道7  采集电池电压
其他 ADC通道0 

在开发板上加载驱动:

首先我们可以查看/dev下的设备节点:

可以看到imx283_adc的设备节点已经生成,主设备号10,次设备号是55。接下来运行测试APP:

i.MX283开发板MISC设备驱动——LRADC_第2张图片

i.MX283开发板MISC设备驱动——LRADC_第3张图片

i.MX283开发板MISC设备驱动——LRADC_第4张图片

i.MX283开发板MISC设备驱动——LRADC_第5张图片

这里通道7电压为2.09V是因为这里计算是按照*2计算的,实际上通道7内部是硬件除4电路,所以这里应该是*4,也就是2.09*2=4.18V。


HSADC(High-Speed ADC)

上面讲到了LRADC,这里就顺便讲下imx283开发板的HSADC,即高速ADC,最高采样率可达2Msps,它的精度最高是12bit。

这部分内容主要讲下HSADC的配置,不涉及太多驱动知识。

高速ADC模块设计用于驱动线性图像扫描仪传感器(例如,东芝TCD1304DG线性图像扫描仪传感器)。它还可以支持以高达2 Msps的数据速率采样模拟源然后将样本数据移动到外部存储器的情况。为了提高灵活性,高速ADC块可以与PWM块协同工作。它可以产生外部设备的驱动信号,例如线性图像扫描仪传感器。PWM还可以产生与高速同步的触发信号启动ADC转换的ADC块。一个APBH-DMA信道连接到高速ADC块,用于在高速ADC块到外部存储器。

注意:HSADC最大量程是0~1.85V

HSADC的配置流程如下:

  配置系统时钟——配置HSADC模块时钟——HSADC复位——配置工作模式及相关参数配置——使能HSADC预充电——启动HSADC ——配置中断、清除标志位——触发HSADC进行转换——等待转换完成——读取转换结果——清除标志位——再次触发进行下一次转换。

下面介绍几个重要的配置:

1.系统时钟配置,寄存器:HW_CLKCTRL_FRAC1

i.MX283开发板MISC设备驱动——LRADC_第6张图片

位bit 说明
15 HSADC时钟门控 清0使能HSADC时钟
14 标志位,每次时钟配置生效,该位会自动翻转
13-8 分频系数fhsadcrac   HSADC主时钟=480*(18/分频系数)   系数范围:18-35

2.HSADC 时钟分频器

i.MX283开发板MISC设备驱动——LRADC_第7张图片

 

位bit 说明
30 分频器使能位  置1 分频器正常工作
29-28 分频系数 9/18/36/72可选

 3.HW_HSADC_CTRL2 的bit13和bit0i.MX283开发板MISC设备驱动——LRADC_第8张图片

 

4.HW_HSADC_CTRL1是配置中断使能和中断标志位。

i.MX283开发板MISC设备驱动——LRADC_第9张图片

BIT0是当某个中断发生时会被置位,由于我们是查询方式获取ADC结果,所以我们需要轮询bit0是否被置位,所以这里务必打开至少一个ADC中断,否则这一位永远不会被置位。

其余的配置就没有什么需要特别强调的,按照上面讲的流程,一个个配置即可。下面给出HSADC驱动源码:

#include      /* module */
#include          /* file operation*/
#include       /* get_user()*/
#include  /* miscdevice*/
#include            /* ioctl*/

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#include <../arch/arm/mach-mx28/regs-clkctrl.h>

#define DEBUG_T 0

#define DEVICE_NAME	"imx283_hsadc"//驱动名称
#define HSADC_BASE  0x80002000 //HSADC基地址 实际物理地址
#define HW_CLKCTRL_BASE  0x80040000//时钟控制模块基地址
#define HSADC_CLKCTRL_BASE 0x80040150//HSADC CLK控制寄存器基地址

static void __iomem *hsadc_base = NULL;//映射后HSADC基地址
static void __iomem *hsadc_clk_base = NULL;
static void __iomem *CLKCTRL_FRAC0_BASE=NULL;


static int hsadc_open(struct inode *inode, struct file *fp)
{
    /* 清0分频器配置位  再将       分频值配置为30 主clk=480*18/30=288MHz*/
    writel((0x3F<<8),CLKCTRL_FRAC0_BASE+0x18);//CLR 
    writel((0x1E<<8),CLKCTRL_FRAC0_BASE+0x14);//SET
	
    /*使能HSADC CLOCK*/
	writel(1<<15,CLKCTRL_FRAC0_BASE+0x18);//HW_CLKCTRL_FRAC1_SET

	/*RESETB=1 Divider normal work  FREQDIV=11 Divide by 72*/
    writel((1<<30)|(3<<28),hsadc_clk_base);//hsadc_clk_base

    /*SFTRST=0 HSADC退出复位进入正常模式*/
	writel(1<<31,hsadc_base+0x08);//HW_HSADC_CTRL0_CLR
	
	/*CLKGATE=0 进入正常模式*/
	writel(1<<30,hsadc_base+0x08);//HW_HSADC_CTRL0_CLR

    /*HSADC由软件触发转换*/
	writel(3<<28,hsadc_base+0x08);//HW_HSADC_CTRL0_CLR
    
    /*HSADC忽略初次上电4次采样值 */
    writel(3<<19,hsadc_base+0x04);//HW_HSADC_CTRL0_SET

    /*HSADC输出12bit结果*/
	writel(1<<18, hsadc_base+0x04);//HW_HSADC_CTRL0_SET
	writel(1<<17, hsadc_base+0x08);//HW_HSADC_CTRL0_CLR
	
    /*小端数据模式*/
	writel(1<<16,hsadc_base+0x08);//HW_HSADC_CTRL0_CLR
  
    /*使能ADC_DONE中断*/
    writel((1<<30),hsadc_base+0x14);//HW_HSADC_CTRL1_CLR
	
    /*清除POWER_DOWN位    ADC模块上电*/
	writel(1<<13,hsadc_base+0x28);//HW_HSADC_CTRL2_CLR
	
	/*ADC_PRECHARGE置1 使能HSADC预充电*/
    writel(1<<0,hsadc_base+0x24);//HW_HSADC_CTRL2_SET

	/*选择HSADC0作为ADC输入源*/        
	writel(7<<1,hsadc_base+0x24);//HW_HSADC_CTRL2_SET
	
#if (DEBUG_T)	
	printk("HW_HSADC_CTRL0=%08X\n",readl(hsadc_base+0));
	printk("HW_HSADC_CTRL1=%08X\n",readl(hsadc_base+0X10));
	printk("HW_HSADC_CTRL2=%08X\n",readl(hsadc_base+0x20));
	printk("HW_HSADC_CLK=%08x\n",readl(hsadc_clk_base)); 	  
    printk("HW_HSADC_CLK2=%08x\n",readl(CLKCTRL_FRAC0_BASE+0x10)); 
#endif	
	
	return 0; 
}


static int hsadc_release(struct inode * inode, struct file * file)
{
   //失能HSADC    
   writel((1<<31)|(1<<30),hsadc_base+0x04);//HW_HSADC_CTRL0_SET
   return 0;
}


static int hsadc_read(struct file *filp,  char __user *buf, size_t count,
                loff_t *f_pos)
{
    int ret;
	int hsadc_res;
	unsigned char databuf[2];
    unsigned int timeout = 1000;
	/*清除所有flag*/
	writel(3<<26,hsadc_base+0x14);//HW_HSADC_CTRL1_SET

	/*HSADC_RUN=1  启动HSADC等待触发*/
	writel(1<<0,hsadc_base+0x04);//HW_HSADC_CTRL0_SET

	/*SOFTWARE_TRIGGER=1 软件触发HSADC进行转换*/
    writel(1<<27,hsadc_base+0x04);//HW_HSADC_CTRL0_SET

	while(((readl(hsadc_base+0x10)&0x01)==0)&&(timeout > 0))//HW_HSADC_CTRL1等待HSADC转换完成 
	{
	   timeout--;
	   if(timeout == 0)
	   {    
            printk("timeout! \n");   
			break;
	   }
	   
	   //cpu_relax();
	}
#if (DEBUG_T)	
	printk("HW_HSADC_CTRL0=%08X\n",readl(hsadc_base+0));
	printk("HW_HSADC_CTRL1=%08X\n",readl(hsadc_base+0X10));
	printk("HW_HSADC_CTRL2=%08X\n",readl(hsadc_base+0x20));
#endif	
	/*清除所有flag*/
	writel(3<<26,hsadc_base+0x14);//HW_HSADC_CTRL1_SET 

	/*读取转换结果 12bit*/
	hsadc_res = readl(hsadc_base+0x50) & 0xFFF;//HW_HSADC_FIFO_DATA 读取ADC数据

	printk("hsadc_res= %x\n",hsadc_res);
   	
	databuf[0] = (hsadc_res >> 8) & 0xFF;
	databuf[1] = hsadc_res & 0xFF;
	ret = copy_to_user(buf, databuf, 2);
	if(ret < 0)
	{
      printk("kernel read error \n");
	  return ret;
	}
	return 0;
  
}


static struct file_operations hsadc_fops =
{
  .owner   = THIS_MODULE,
  .open    = hsadc_open,
  .release = hsadc_release,
  .read    = hsadc_read,
};

static struct cdev *hsadc_cdev = NULL;//cdev
static struct class *hsadc_class = NULL;//类
static struct device *hsadc_device = NULL;//设备
static dev_t  dev_id;//设备号
static int major;//主设备号

static int __init hsadc_init(void)
{
  int ret;
  hsadc_clk_base = ioremap(HSADC_CLKCTRL_BASE, 4);
  hsadc_base = ioremap(HSADC_BASE,0xC0*4);//地址映射
  CLKCTRL_FRAC0_BASE = ioremap(HW_CLKCTRL_BASE+HW_CLKCTRL_FRAC0,32);
  
  ret = alloc_chrdev_region(&dev_id, 0, 1, DEVICE_NAME); //申请设备号
  if(ret < 0)
  {
     printk(KERN_ERR "alloc dev_id error %d \n", ret);
	 return ret;
  }
  major = MAJOR(dev_id);//获取主设备号
  hsadc_cdev=cdev_alloc();//申请cdev结构
  if(hsadc_cdev != NULL)
  {
     cdev_init(hsadc_cdev,&hsadc_fops);//初始化hsadc_cdev结构体
	 ret = cdev_add(hsadc_cdev, dev_id, 1);//添加hsadc设备到hsadc_cdev结构体 
	 if(ret != 0)
	 {
       printk("cdev add error %d \n",ret);
	   goto error;
	 }
  }
  else
  {
     printk("cdev_alloc error \n");
     return -1;
  }
  //创建hsadc_class类
  hsadc_class = class_create(THIS_MODULE, "HSADC_CLASS");
  if(hsadc_class !=NULL)
  {
     //在hsadc_class类下面创建1个设备
     hsadc_device=device_create(hsadc_class, NULL, dev_id, NULL, DEVICE_NAME);
  }
	 printk("module init ok\n");
	 return 0;
error:
     cdev_del(hsadc_cdev);//删除hsadc_cdev结构体
     unregister_chrdev_region(dev_id, 1);//释放设备号
     return ret;	 
}


static void __exit hsadc_exit(void)
{
   iounmap(hsadc_base);//取消地址映射
   cdev_del(hsadc_cdev);//删除hsadc_cdev结构体
   unregister_chrdev_region(dev_id, 1);//释放设备号
   device_del(hsadc_device);//删除设备
   class_destroy(hsadc_class);//删除类
   printk("module exit ok\n");
}


module_init(hsadc_init);
module_exit(hsadc_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("xzx2020");

以及测试APP:

#include	/* using printf()        */
#include      /* using sleep()         */
#include       /* using file operation  */
#include   /* using ioctl()         */
#include 
#include  //sleep  write read close

int main(int argc, const char * argv [ ])
{
  int fd;
  int value = 0;
  unsigned char buf[2];
  fd = open("/dev/imx283_hsadc",O_RDWR);
  if(fd < 0)
  {
     printf("open imx283_hsadc error %d\n",fd);
	 return 0;
  }

  while(1)
  {
    read(fd,buf,2);
	value = buf[0]<<8 | buf[1];
    printf("HSADC = %.2f\n",(double)(value)*1.85/4096);
	sleep(1);
  }
  return 0;
}

 

 

 

你可能感兴趣的:(#,EasyARM-imx283)