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的驱动程序。
imx283开发板有16个LRADC物理通道,同时还有8个虚拟通道,这8个虚拟通道可以同时使用,且每一个通道都可以映射到16个物理信道。但是只有通道0~6可以用于一般用途,它们内部都有硬件除2电路可供配置,虚拟通道7专门测量电池电压,其内部有一个硬件除4电路。
LRADC主要特点如下:
LRADC配置流程如下:
ADC复位——配置时钟——配置采样率、采集次数、数据长度等参数——配置中断、清除相关标志位——启动ADC——等待转换完成——读取数据——清除标志位——开启下一次转换
LRADC的寄存器地址如下:
注意: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:
这里通道7电压为2.09V是因为这里计算是按照*2计算的,实际上通道7内部是硬件除4电路,所以这里应该是*4,也就是2.09*2=4.18V。
上面讲到了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
位bit | 说明 |
15 | HSADC时钟门控 清0使能HSADC时钟 |
14 | 标志位,每次时钟配置生效,该位会自动翻转 |
13-8 | 分频系数fhsadcrac HSADC主时钟=480*(18/分频系数) 系数范围:18-35 |
2.HSADC 时钟分频器
位bit | 说明 |
30 | 分频器使能位 置1 分频器正常工作 |
29-28 | 分频系数 9/18/36/72可选 |
4.HW_HSADC_CTRL1是配置中断使能和中断标志位。
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;
}