本文共 10664 字,大约阅读时间需要 35 分钟。
下面分析一下数字温度计DS1620的驱动程序。先从网上找到其数据手册,看看它的功能,再找到时序图片一下。这样有利于分析驱动,再套用上linux的驱动框架就可以了。
同样,先找驱动的入口和出口,看看他们都做了什么。
module_init(ds1620_init);
module_exit(ds1620_exit);
先来看一些常量的定义吧
/*
* arch/arm/include/asm/therm.h: Definitions for Dallas Semiconductor
* DS1620 thermometer driver (as used in the Rebel.com NetWinder)
*/
#ifndef __ASM_THERM_H
#define __ASM_THERM_H
/* ioctl numbers for /dev/therm */
#define CMD_SET_THERMOSTATE 0x53
#define CMD_GET_THERMOSTATE 0x54
#define CMD_GET_STATUS 0x56
#define CMD_GET_TEMPERATURE 0x57
#define CMD_SET_THERMOSTATE2 0x58
#define CMD_GET_THERMOSTATE2 0x59
#define CMD_GET_TEMPERATURE2 0x5a
#define CMD_GET_FAN 0x5b
#define CMD_SET_FAN 0x5c
#define FAN_OFF 0
#define FAN_ON 1
#define FAN_ALWAYS_ON 2
struct therm {
int hi;
int lo;
};
#endif
定义了一些常量寄存器的值:
/*Definitions for DS1620 chip */
#defineTHERM_START_CONVERT 0xee
#defineTHERM_RESET 0xaf
#defineTHERM_READ_CONFIG 0xac
#defineTHERM_READ_TEMP 0xaa
#defineTHERM_READ_TL 0xa2
#defineTHERM_READ_TH 0xa1
#defineTHERM_WRITE_CONFIG 0x0c
#defineTHERM_WRITE_TL 0x02
#defineTHERM_WRITE_TH 0x01
#defineCFG_CPU 2
#defineCFG_1SHOT 1
staticint__init ds1620_init(void)
{
intret;
structtherm th, th_start;
这个什么东东????
if(!machine_is_netwinder())
return-ENODEV;
这个是设置一些参数?复位,配置,停止转换。
ds1620_out(THERM_RESET,0, 0);
ds1620_out(THERM_WRITE_CONFIG,8, CFG_CPU);
ds1620_out(THERM_START_CONVERT,0, 0);
/*
* Trigger the fan to start by setting
* temperature high point low. This kicks
* the fan into action.
*/
ds1620_read_state(&th);
th_start.lo= 0;
th_start.hi= 1;
ds1620_write_state(&th_start);
msleep(2000);
ds1620_write_state(&th);
这里是注册一个混杂设备(混杂设备就是不知道如何归类的)
ret= misc_register(&ds1620_miscdev);
if(ret < 0)
returnret;
这里创建/proc文件系统节点,方便从/proc中获取信息,这样就不用写标准的测试程序了。
就不用走那套老的程序open(),read(),write(),close().
#ifdefTHERM_USE_PROC
proc_therm_ds1620= create_proc_entry("therm",0, NULL);
if(proc_therm_ds1620)
proc_therm_ds1620->read_proc= proc_therm_ds1620_read;
else
printk(KERN_ERR"therm:unable to register /proc/therm\n");
#endif
读取一下当前状态,输出一下信息。
ds1620_read_state(&th);
ret= cvt_9_to_int(ds1620_in(THERM_READ_TEMP, 9));
printk(KERN_INFO"Thermostat:high %i.%i, low %i.%i, "
"current%i.%i C, fan %s.\n",
th.hi >> 1, th.hi & 1 ? 5 : 0,
th.lo >> 1, th.lo & 1 ? 5 : 0,
ret >> 1, ret & 1 ? 5 : 0,
fan_state[netwinder_get_fan()]);
return0;
}
staticvoid__exit ds1620_exit(void)
{
移除/proc节点
#ifdefTHERM_USE_PROC
remove_proc_entry("therm",NULL);
#endif
注销混杂设备。
misc_deregister(&ds1620_miscdev);
}
再来看一下混杂设备的结构体及接口函数:
structmiscdevice {
intminor;
constchar*name;
conststructfile_operations*fops;
structlist_headlist;
structdevice*parent;
structdevice*this_device;
constchar*nodename;
mode_tmode;
};
externintmisc_register(structmiscdevice* misc);
externintmisc_deregister(structmiscdevice*misc);
这里是真实代码:
staticstructmiscdeviceds1620_miscdev= {
TEMP_MINOR,
"temp",
&ds1620_fops
};
从文件操作指针可以看来,也只支持open,read,ioctl三个函数。
staticconststructfile_operationsds1620_fops = {
.owner =THIS_MODULE,
.open =ds1620_open,
.read =ds1620_read,
.unlocked_ioctl =ds1620_unlocked_ioctl,
.llseek =no_llseek,
};
看一下打开函数,这里调用了一个奇怪的函数,以前没有见到过呀:
立即F3查看一下
好像就是设置一个这个fd,不支持seek的功能。
staticintds1620_open(structinode*inode, structfile*file)
{
returnnonseekable_open(inode, file);
}
/*
*This is used by subsystems that don't want seekable
*file descriptors. The function is not supposed to ever fail, the only
*reason it returns an 'int'and not 'void' is so that it can be plugged
*directly into file_operations structure.
*/
intnonseekable_open(structinode*inode, structfile*filp)
{
filp->f_mode&= ~(FMODE_LSEEK| FMODE_PREAD| FMODE_PWRITE);
return0;
}
EXPORT_SYMBOL(nonseekable_open);
这个不管,直接返回0也行。再来看看read()函数。最为关键是读取数据那部分。
到底是怎么读取回来的。这些只是套用linux的驱动框架而已。
staticssize_t
ds1620_read(structfile*file, char__user *buf, size_tcount, loff_t*ptr)
{
signedintcur_temp;
signedcharcur_temp_degF;
读取值并做转换。
cur_temp= cvt_9_to_int(ds1620_in(THERM_READ_TEMP, 9)) >> 1;
/*convert to Fahrenheit,as per wdt.c */
cur_temp_degF= (cur_temp * 9) / 5 + 32;
拷贝到用户空间。
if(copy_to_user(buf, &cur_temp_degF, 1))
return-EFAULT;
return1;
}
再来看看ioctl()支持的功能,这里使用了互斥锁,有效的防止多线程访问时导致的不可见错误。
staticlong
ds1620_unlocked_ioctl(structfile*file, unsignedintcmd, unsignedlongarg)
{
intret;
mutex_lock(&ds1620_mutex);
ret= ds1620_ioctl(file, cmd, arg);
mutex_unlock(&ds1620_mutex);
returnret;
}
这里的ioctl()支持好几个命令呢。
staticint
ds1620_ioctl(structfile*file, unsignedintcmd, unsignedlongarg)
{
structthermtherm;
union{
structtherm__user *therm;
int__user *i;
}uarg;
inti;
uarg.i= (int__user *)arg;
switch(cmd){
caseCMD_SET_THERMOSTATE:
caseCMD_SET_THERMOSTATE2:
这里检测是否具有能力
if(!capable(CAP_SYS_ADMIN))
return-EPERM;
if(cmd == CMD_SET_THERMOSTATE) {
if(get_user(therm.hi,uarg.i))
return-EFAULT;
therm.lo= therm.hi- 3;
}else{
if(copy_from_user(&therm, uarg.therm,sizeof(therm)))
return-EFAULT;
}
therm.lo<<= 1;
therm.hi<<= 1;
ds1620_write_state(&therm);
break;
caseCMD_GET_THERMOSTATE:
caseCMD_GET_THERMOSTATE2:
ds1620_read_state(&therm);
therm.lo>>= 1;
therm.hi>>= 1;
if(cmd == CMD_GET_THERMOSTATE) {
if(put_user(therm.hi,uarg.i))
return-EFAULT;
}else{
if(copy_to_user(uarg.therm,&therm, sizeof(therm)))
return-EFAULT;
}
break;
caseCMD_GET_TEMPERATURE:
caseCMD_GET_TEMPERATURE2:
i= cvt_9_to_int(ds1620_in(THERM_READ_TEMP, 9));
if(cmd == CMD_GET_TEMPERATURE)
i>>= 1;
returnput_user(i,uarg.i)? -EFAULT : 0;
caseCMD_GET_STATUS:
i= ds1620_in(THERM_READ_CONFIG, 8) & 0xe3;
returnput_user(i,uarg.i)? -EFAULT : 0;
caseCMD_GET_FAN:
i= netwinder_get_fan();
returnput_user(i,uarg.i)? -EFAULT : 0;
caseCMD_SET_FAN:
if(!capable(CAP_SYS_ADMIN))
return-EPERM;
if(get_user(i,uarg.i))
return-EFAULT;
netwinder_set_fan(i);
break;
default:
return-ENOIOCTLCMD;
}
return0;
}
再来看看子函数的实现:
staticvoidds1620_write_state(structtherm*therm)
{
ds1620_out(THERM_WRITE_CONFIG,8, CFG_CPU);
ds1620_out(THERM_WRITE_TL,9, therm->lo);
ds1620_out(THERM_WRITE_TH,9, therm->hi);
ds1620_out(THERM_START_CONVERT,0, 0);
}
staticvoidds1620_read_state(structtherm*therm)
{
therm->lo= cvt_9_to_int(ds1620_in(THERM_READ_TL, 9));
therm->hi= cvt_9_to_int(ds1620_in(THERM_READ_TH, 9));
}
接着调用子函数:
staticvoidds1620_out(intcmd, intbits, intvalue)
{
unsignedlongflags;
netwinder_lock(&flags);
netwinder_ds1620_set_clk(1);
netwinder_ds1620_set_data_dir(0);
netwinder_ds1620_reset();
udelay(1);
ds1620_send_bits(8,cmd);
if(bits)
ds1620_send_bits(bits,value);
udelay(1);
netwinder_ds1620_reset();
netwinder_unlock(&flags);
msleep(20);
}
staticunsignedintds1620_in(intcmd, intbits)
{
unsignedlongflags;
unsignedintvalue;
netwinder_lock(&flags);
netwinder_ds1620_set_clk(1);
netwinder_ds1620_set_data_dir(0);
netwinder_ds1620_reset();
udelay(1);
ds1620_send_bits(8,cmd);
netwinder_ds1620_set_data_dir(1);
value= ds1620_recv_bits(bits);
netwinder_ds1620_reset();
netwinder_unlock(&flags);
returnvalue;
}
接着嵌套调用,唉,调用这么多
看到send()函数这样写法,很明显是使用GPIO来模拟时序的。低-高-低-高。模拟时钟。
staticvoidds1620_send_bits(intnr, intvalue)
{
inti;
for(i = 0; i < nr; i++) {
netwinder_ds1620_set_data(value& 1);
netwinder_ds1620_set_clk(0);
udelay(1);
netwinder_ds1620_set_clk(1);
udelay(1);
value>>= 1;
}
}
这个接收函数也是使用GPIO来模拟时序。
staticunsignedintds1620_recv_bits(intnr)
{
unsignedintvalue = 0, mask = 1;
inti;
netwinder_ds1620_set_data(0);
for(i = 0; i < nr; i++) {
netwinder_ds1620_set_clk(0);
udelay(1);
if(netwinder_ds1620_get_data())
value|= mask;
mask<<= 1;
netwinder_ds1620_set_clk(1);
udelay(1);
}
returnvalue;
}
下面这几个函数就是最终操作的GPIO的高低电平值:
staticinlinevoidnetwinder_ds1620_set_clk(intclk)
{
nw_gpio_modify_op(GPIO_DSCLK,clk ? GPIO_DSCLK: 0);
}
staticinlinevoidnetwinder_ds1620_set_data(intdat)
{
nw_gpio_modify_op(GPIO_DATA,dat ? GPIO_DATA: 0);
}
staticinlineintnetwinder_ds1620_get_data(void)
{
returnnw_gpio_read() & GPIO_DATA;
}
staticinlinevoidnetwinder_ds1620_set_data_dir(intdir)
{
nw_gpio_modify_io(GPIO_DATA,dir ? GPIO_DATA: 0);
}
staticinlinevoidnetwinder_ds1620_reset(void)
{
nw_cpld_modify(CPLD_DS_ENABLE,0);
nw_cpld_modify(CPLD_DS_ENABLE,CPLD_DS_ENABLE);
}
staticinlinevoidnetwinder_lock(unsignedlong*flags)
{
spin_lock_irqsave(&nw_gpio_lock,*flags);
}
staticinlinevoidnetwinder_unlock(unsignedlong*flags)
{
spin_unlock_irqrestore(&nw_gpio_lock,*flags);
}
staticinlinevoidnetwinder_set_fan(inti)
{
unsignedlongflags;
spin_lock_irqsave(&nw_gpio_lock,flags);
nw_gpio_modify_op(GPIO_FAN,i ? GPIO_FAN: 0);
spin_unlock_irqrestore(&nw_gpio_lock,flags);
}
staticinlineintnetwinder_get_fan(void)
{
if((system_rev & 0xf000) == 0x4000)
returnFAN_ALWAYS_ON;
return(nw_gpio_read() & GPIO_FAN)? FAN_ON : FAN_OFF;
}
这里面反复调用了nw_gpio_modify_op这个函数,来看看这个是干什么用的
唉,这驱动谁写的,搞得嵌套这么多函数,头不疼吗。俺好累呀。
这里的outb()就是输出一个字节的意思啦。最终的输出在这里,
肯定是经过映射后的虚拟地址吧
voidnw_gpio_modify_op(unsignedintmask, unsignedintset)
{
unsignedintnew_gpio, changed;
new_gpio= (current_gpio_op & ~mask) | set;
changed= new_gpio ^ current_gpio_op;
current_gpio_op= new_gpio;
if(changed & 0xff)
outb(new_gpio,GP1_IO_BASE);
if(changed & 0xff00)
outb(new_gpio>> 8, GP2_IO_BASE);
}
EXPORT_SYMBOL(nw_gpio_modify_op);
unsignedintnw_gpio_read(void)
{
returninb(GP1_IO_BASE) | inb(GP2_IO_BASE) << 8;
}
EXPORT_SYMBOL(nw_gpio_read);
再来看看混杂设备的设备号:
#ifndef_LINUX_MISCDEVICE_H
#define_LINUX_MISCDEVICE_H
#include<linux/module.h>
#include<linux/major.h>
/*
* Theseallocations are managed by device@lanana.org. If you use an
* entrythat is not in assigned your entry may well be moved and
* reassigned,or set dynamic if a fixed value is not justified.
*/
#definePSMOUSE_MINOR 1
#defineMS_BUSMOUSE_MINOR 2
#defineATIXL_BUSMOUSE_MINOR 3
/*#defineAMIGAMOUSE_MINOR 4 FIXME OBSOLETE */
#defineATARIMOUSE_MINOR 5
#defineSUN_MOUSE_MINOR 6
#defineAPOLLO_MOUSE_MINOR 7
#definePC110PAD_MINOR 9
/*#defineADB_MOUSE_MINOR 10 FIXME OBSOLETE */
#defineWATCHDOG_MINOR 130 /* Watchdog timer */
#defineTEMP_MINOR 131 /* Temperature Sensor */ 这里温度传感器
#defineRTC_MINOR 135 这里是RTC的设备号.
#defineEFI_RTC_MINOR 136 /* EFI Time services */
#defineSUN_OPENPROM_MINOR 139
#defineDMAPI_MINOR 140 /* DMAPI */
#defineNVRAM_MINOR 144
#defineSGI_MMTIMER 153
#defineSTORE_QUEUE_MINOR 155
#defineI2O_MINOR 166
#defineMICROCODE_MINOR 184
#defineTUN_MINOR 200
#defineMWAVE_MINOR 219 /* ACP/Mwave Modem */
#defineMPT_MINOR 220
#defineMPT2SAS_MINOR 221
#defineUINPUT_MINOR 223
#defineHPET_MINOR 228
#defineFUSE_MINOR 229
#defineKVM_MINOR 232
#defineBTRFS_MINOR 234
#defineAUTOFS_MINOR 235
#defineMAPPER_CTRL_MINOR 236
#defineMISC_DYNAMIC_MINOR 255
structdevice;
structmiscdevice {
intminor;
constchar *name;
conststruct file_operations *fops;
structlist_head list;
structdevice *parent;
structdevice *this_device;
constchar *nodename;
mode_tmode;
};
externint misc_register(struct miscdevice * misc);
externint misc_deregister(struct miscdevice *misc);
#defineMODULE_ALIAS_MISCDEV(minor) \
MODULE_ALIAS("char-major-"__stringify(MISC_MAJOR) \
"-"__stringify(minor))
#endif