没有用到子系统,简单示例。
这两天参考网上的资料,自己写了个SPI的驱动,并实际测试通过。
硬件平台:mini2440 用的是S3C2440 的SPI1(共有2个SPI模块)
操作系统:linux-2.6.32.2
测试方法:将SPI的MISO与MOSI管脚短路,这样读数据的时候第一个发出的dummy字节即为收到的字节。
下面是驱动的源代码(mini2440_spi.c):
/***************************************************/ |
#include <linux/irq.h> |
#include <linux/miscdevice.h> |
#include <linux/delay.h> |
#include <asm/irq.h> |
#include <linux/interrupt.h> |
#include <mach/regs-gpio.h> |
#include <mach/hardware.h> |
#include <linux/kernel.h> |
#include <linux/module.h> |
#include <linux/init.h> |
#include <linux/mm.h> |
#include <linux/fs.h> |
#include <linux/types.h> |
#include <linux/delay.h> |
#include <linux/moduleparam.h> |
#include <linux/slab.h> |
#include <linux/errno.h> |
#include <linux/ioctl.h> |
#include <linux/cdev.h> |
#include <linux/string.h> |
#include <linux/list.h> |
#include <linux/pci.h> |
#include <linux/gpio.h> |
#include <asm/uaccess.h> |
#include <asm/atomic.h> |
#include <asm/unistd.h> |
#include <linux/spinlock.h> |
#include <asm/system.h> |
#include <asm/uaccess.h> |
int loopChar=0x88; |
module_param(loopChar, int ,S_IRUGO); |
|
static int spi_major = 55; |
#define spi_name "mini2440_spi" |
struct cdev spiCdev; |
/*****************************************************/ |
static int spi_open( struct inode *, struct file *); |
static int spi_release( struct inode *, struct file *); |
static ssize_t spi_write( struct file *filp, const char *buf, size_t count,loff_t *f_ops); |
static ssize_t spi_read( struct file *filp, char *buf, size_t count,loff_t *f_ops); |
static ssize_t spi_ioctl( struct inode *inode, struct file *filp,unsigned int cmd,unsigned long data); |
volatile int *spi_gpfcon=NULL; //GPF Part define |
volatile int *spi_gpfdat=NULL; |
volatile int *spi_gpfup=NULL; |
volatile int *spi_gpgcon=NULL; //GPG Part define |
volatile int *spi_gpgdat=NULL; |
volatile int *spi_gpgup=NULL; |
volatile int *s3c2440_clkcon; |
volatile int *spi_spcon1; //SPI Part define |
volatile int *spi_spsta1; |
volatile int *spi_sppin1; |
volatile int *spi_sppre1; |
volatile int *spi_sptdat1; |
volatile int *spi_sprdat1; |
#define SPI_TXRX_READY (((*spi_spsta1)&0x1) == 0x1) |
/**********************************************************/ |
static const struct file_operations spi_fops = |
{ |
.owner=THIS_MODULE, |
.open=spi_open, |
.read=spi_read, |
.ioctl=spi_ioctl, |
.release=spi_release, |
.write=spi_write, |
}; |
/********************************************************/ |
static int spi_open( struct inode *inode, struct file *filp) |
{ |
filp->private_data =&spiCdev; |
/*********************************************** |
*PCLK |
************************************************/ |
/*control PCLK into spi block*/ |
*s3c2440_clkcon |=0x40000; |
printk( "s3c2440_clkcon=%08X\n" ,*s3c2440_clkcon); |
/*********************************************** |
*GPG PORTS |
************************************************/ |
/*config SCK1,MOSI1,MISO1 = 11*/ |
*spi_gpgcon |=0x0000FC00; |
/*poll up MISO1 MOSI1,SCK1*/ |
*spi_gpgup &=0xFF1F; |
*spi_gpgup |=0x0060; |
/*********************************************** |
*GPF PORTS |
************************************************/ |
*spi_gpfcon &= 0xFCF3; |
*spi_gpfcon |= 0x0108; |
*spi_gpfup &= 0xED; |
*spi_gpfdat |= 0x10; |
/*********************************************** |
*SPI REGS |
************************************************/ |
//SPI Baud Rate Prescaler Register,Baud Rate=PCLK/2/(Prescaler value+1) |
*spi_sppre1=0x18; //freq = 1M |
printk( "spi_sppre1=%02X\n" ,*spi_sppre1); |
//polling,en-sck,master,low,format A,nomal = 0 | TAGD = 1 |
*spi_spcon1=(0<<6)|(0<<5)|(1<<4)|(1<<3)|(0<<2)|(0<<1)|(0<<0); |
printk( "spi_spcon1=%02X\n" ,*spi_spcon1); |
//Multi Master error detect disable,reserved,rech=*spi_sprdat0;lease |
*spi_sppin1=(0<<2)|(0<<0); |
printk( "spi_sppin1=%02X\n" ,*spi_sppin1); |
return 0; |
} |
static int spi_release( struct inode *inode, struct file *filp) |
{ |
//free irq |
free_irq(IRQ_EINT1, NULL); |
printk( "<1>release\n" ); |
return 0; |
} |
static void writeByte( const char c) |
{ |
int j = 0; |
*spi_sptdat1 = c; |
for (j=0;j<0xFF;j++); |
while (!SPI_TXRX_READY) |
for (j=0;j<0xFF;j++); |
} |
static char readByte( void ) |
{ |
int j = 0; |
char ch = 0; |
*spi_sptdat1 = ( char )loopChar; |
for (j=0;j<0xFF;j++); |
while (!SPI_TXRX_READY) |
for (j=0;j<0xFF;j++); |
ch=*spi_sprdat1; |
return ch; |
} |
static ssize_t spi_read( struct file *filp, char __user *buf, size_t count,loff_t *f_ops) |
{ |
//int len=0; |
char ch; |
printk( "<1>spi read!\n" ); |
ch=readByte(); |
copy_to_user(buf,&ch,1); |
return 1; |
} |
static ssize_t spi_write( struct file *filp, const char __user *buf, size_t count,loff_t *f_ops) |
{ |
int i; |
char *kbuf; |
printk( "<1>spi write!,count=%d\n" ,count); |
kbuf=kmalloc(count,GFP_KERNEL); |
if (copy_from_user(kbuf,buf,count)) |
{ |
printk( "no enough memory!\n" ); |
return -1; |
} |
|
for (i=0;i<count;i++) |
{ |
writeByte(*kbuf); |
printk( "write 0x%02X!\n" ,*kbuf); |
kbuf++; |
} |
return count; |
} |
static ssize_t spi_ioctl( struct inode *inode, struct file *filp,unsigned int cmd,unsigned long data) |
{ |
return 0; |
} |
static int __init spi_init( void ) |
{ |
int result; |
dev_t devno = MKDEV(spi_major, 0); |
/**/ |
if (spi_major) |
result = register_chrdev_region(devno, 1, spi_name); |
else /**/ |
{ |
result = alloc_chrdev_region(&devno, 0, 1, spi_name); |
spi_major = MAJOR(devno); |
} |
if (result < 0) |
return result; |
|
cdev_init(&spiCdev, &spi_fops); |
spiCdev.owner = THIS_MODULE; |
spiCdev.ops = &spi_fops; |
if (cdev_add(&spiCdev, devno, 1)) |
printk(KERN_NOTICE "Error adding spi %d" , 0); |
s3c2440_clkcon = ( int *)ioremap(0x4C00000c,3); |
spi_gpgcon = ( int *)ioremap (0x56000060,4); |
spi_gpgdat = ( int *)ioremap (0x56000064,2); |
spi_gpgup = ( int *)ioremap (0x56000068,2); |
spi_gpfcon = ( int *)ioremap (0x56000050,2); |
spi_gpfdat = ( int *)ioremap (0x56000054,1); |
spi_gpfup = ( int *)ioremap (0x56000058,1); |
spi_spcon1 = ( int *)ioremap(0x59000020,1); |
spi_spsta1 = ( int *)ioremap(0x59000024,1); |
spi_sppin1 = ( int *)ioremap(0x59000028,1); |
spi_sppre1 = ( int *)ioremap(0x5900002c,1); |
spi_sptdat1 = ( int *)ioremap(0x59000030,1); |
spi_sprdat1 = ( int *)ioremap(0x59000034,1); |
printk( "Init spi success!\n" ); |
return result; |
} |
static void __exit spi_exit( void ) |
{ |
cdev_del(&spiCdev); |
unregister_chrdev_region(MKDEV(spi_major, 0), 1); |
printk( "<1>spi_exit!\n" ); |
} |
module_init(spi_init); |
module_exit(spi_exit); |
MODULE_LICENSE( "GPL" ); |
MODULE_AUTHOR( "nkzc" ); |
MODULE_DESCRIPTION( "SPI driver for S3C2440" ); |
几点需要注意的地方:
1.一开始在spi_exit()函数中使用了void unregister_chrdev(unsigned int major, const char *name)函数来注销设备,但再次insmod驱动的时候提示"Device or resource busy",改为unregister_chrdev_region()后一切正常,说明即使只注册了一个设 备,register_chrdev_region()和unregister_chrdev_region()也要配套使用。
2.定义spi_spcon1等寄存器变量时前面要加上volatile关键字,这样每次访问该变量时cpu会从实际内存中读取该值而不是使用寄存 器中的值。尤其是spi_spsta1变量,它的最低位代表了spi发送接收是否ready,如果没有volatile,可能会在readByte()或 writeByte()函数中导致死循环。
3.使用了module_param()宏向驱动传递参数,这里定义了一个int型的loopChar参数,加载模块时使用insmod mini2440_spi.ko loopChar=123 来设置loopChar的值。
测试程序:spi_test.c
#include <stdio.h> |
#include <stdlib.h> |
#include <unistd.h> |
#include <sys/ioctl.h> |
#include<sys/types.h> |
#include<sys/stat.h> |
#include<fcntl.h> |
int main( int argc, char **argv) |
{ |
int fd; |
int count=0; |
char buf[]={0x11,0x22,0x33,0x44,0x55}; |
fd = open( "/dev/mini2440_spi" , O_RDWR); |
if (fd < 0) { |
perror ( "open device spi" ); |
exit (1); |
} |
count=write(fd,buf, sizeof (buf)/ sizeof (buf[0])); |
read(fd,buf,1); |
printf ( "read byte is: 0x%02X\n" ,buf[0]); |
close(fd); |
return 0; |
} |
很简单的一个程序,分别调用了open,write,read,close函数,可以观察输出结果,验证驱动程序是否正确,read的输出即为loopChar的值。
注意:open的时候要注意第二个参数flag,只有当flag为O_RDWR时,驱动中的相应的spi_read,spi_write函数才会被调用。