驱动:
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/version.h>
#include <linux/platform_device.h>
#include <asm/io.h>
#include <linux/cdev.h>
#include <linux/slab.h>
#include <asm/uaccess.h>
#include <linux/kobject.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/of.h>
#include <linux/interrupt.h>
#include <linux/highmem.h>
#include <asm/kmap_types.h>
#include <linux/wait.h>
#define HIGH_RSL 4096 // 2^12
#define LOW_RSL 1024 // 2^10
/*DTS
myadc {
compatible = "xadc";
reg = <0x10010118 0x1>, <0x126C0000 0x20>;
interrupt-parent = <&combiner>;
interrupts = <10 3> ;
};
*/
MODULE_LICENSE("Dual BSD/GPL");
MODULE_DESCRIPTION("a simple adc_driver !");
typedef struct {
unsigned int ADCCON; //0
unsigned int NOVAL; //4
unsigned int ADCDLY; //8
unsigned int ADCDAT; //12
unsigned int NOVAL1; //16
unsigned int NOVAL2; //20
unsigned int CLRINTADC; //24
unsigned int ADCMUX; //28
}adc_t;
struct ADC
{
dev_t num;
int val; //readval
int val_ok_flag;
int available; //adc aviliable struct mutex lock;
spinlock_t lock;
int resolution; //ADC resolution
struct cdev xadc_cdev; //for containor_of()
struct resource *res; //create a platform driver
adc_t *adcaddr;
struct class *xadc_class; //support hotplug function
struct device *xadc_device;
struct work_struct *xadc_work; //irq_bottom_section
wait_queue_head_t readqueue; //wait IO
struct kobject *xadc_kobj; //support attr_file op
}adc;
struct attribute xadc_attrs[] = {
[0] = {"xadc3", S_IRUGO |S_IWUSR},
{}
};
ssize_t xadc_kobj_show(struct kobject *kobj, struct attribute *attr, char *buffer)
{
if(0 == strcmp(attr->name, "xadc3"))
{
if(adc.resolution == HIGH_RSL)
printk("adc_resolution: HIGH_RSL(2^12)\n");
else
printk("adc_resolution: LOW_RSL(2^10)\n");
return 0;
}
else
return -1;
}
ssize_t xadc_kobj_store(struct kobject *kobj, struct attribute *attr, const char *buffer, size_t size)
{
iowrite32( ioread32( &(adc.adcaddr->ADCCON) ) & (~(1<<0)) , &(adc.adcaddr->ADCCON) ); ///STOP转换
if(0 == strcmp(attr->name, "xadc3")) {
if(0 == strncmp(buffer, "low",3)) {
adc.resolution = LOW_RSL;
iowrite32( ioread32( &(adc.adcaddr->ADCCON) ) & (~(1 << 16)) ,&(adc.adcaddr->ADCCON) ); //resolution 10bit
printk("low resolution set ok!\n");
}
else if(0 == strncmp(buffer, "high",4)) {
adc.resolution = HIGH_RSL;
iowrite32( ioread32( &(adc.adcaddr->ADCCON) ) | (1 << 16) ,&(adc.adcaddr->ADCCON) ); //resolution 12bit
printk("high resolution set ok!\n");
}
}
return size;
}
void xadc_kobj_release(struct kobject * k)
{
kfree(k);
return ;
}
struct sysfs_ops xadc_sysfs_ops = {
.show = xadc_kobj_show,
.store = xadc_kobj_store
};
struct kobj_type xadc_ktype = {
.release = xadc_kobj_release,
.sysfs_ops = &xadc_sysfs_ops,
};
int xadc_open(struct inode *i, struct file *f)
{
spin_lock(&adc.lock); //lock
if(adc.available == 1) //userful
{
adc.available = 0;
spin_unlock(&adc.lock); //unlock
printk("xadc open\n");
return 0;
}
spin_unlock(&adc.lock); //unlock
return -EBUSY;
}
//read waitqueue
ssize_t xadc_read (struct file *f, char __user *p, size_t n, loff_t *off)
{
iowrite32( ioread32( &(adc.adcaddr->ADCCON) ) | (1<<0) , &(adc.adcaddr->ADCCON) ); ///开始转换
if(adc.val_ok_flag == 0)
{
//let this procss go to sleep
wait_event_interruptible(adc.readqueue, adc.val_ok_flag != 0);
}
if( copy_to_user(p, &(adc.val), sizeof(int)) ) //get val to user
return -EFAULT;
adc.val_ok_flag = 0;
return sizeof(int);
}
ssize_t xadc_write (struct file *f, const char __user *p, size_t n, loff_t *off)
{
//copy_from_user(buf, p, n);
return n;
}
#define LOW 0
#define HIGH 1
long xadc_ioctl(struct file *f, unsigned int resolution, unsigned long arg)
{
iowrite32( ioread32( &(adc.adcaddr->ADCCON) ) & (~(1<<0)) , &(adc.adcaddr->ADCCON) ); ///STOP转换
if(resolution < LOW || resolution > HIGH)
{
printk("error type!\n");
return -1;
}
if(resolution == LOW)
{
adc.resolution = LOW_RSL;
iowrite32( ioread32( &(adc.adcaddr->ADCCON) ) & (~(1 << 16)) ,&(adc.adcaddr->ADCCON) ); //resolution 10bit
printk("low resolution set ok!\n");
}
else if(resolution == HIGH)
{
adc.resolution = HIGH_RSL;
iowrite32( ioread32( &(adc.adcaddr->ADCCON) ) | (1 << 16) ,&(adc.adcaddr->ADCCON) ); //resolution 12bit
printk("high resolution set ok!\n");
}
return 0;
}
int xadc_release(struct inode *inode, struct file *file)
{
printk("xadc closed\n");
iowrite32( ioread32( &(adc.adcaddr->ADCCON) ) & (~(1<<0)) , &(adc.adcaddr->ADCCON) ); ///STOP转换
adc.available = 1; //useful
return 0;
}
struct file_operations xadc_ops = {
.owner = THIS_MODULE,
.open = xadc_open,
.unlocked_ioctl = xadc_ioctl,
.read = xadc_read,
.write = xadc_write ,
.release = xadc_release
};
//
//irq_handler
//deal_thing
void adc_workfunc(struct work_struct *work)
{
adc.val = ioread32( &(adc.adcaddr->ADCDAT) ) & 0xfff ;
adc.val_ok_flag = 1; //ok
//woke up processes
wake_up_interruptible(&adc.readqueue);
return;
}
//irq
irqreturn_t xadc_handler(int n, void *dev)
{
adc.xadc_work = kzalloc(GFP_ATOMIC, sizeof(struct work_struct)); //workstruct 将处理的事务交给内核线程中处理
INIT_WORK(adc.xadc_work, adc_workfunc);
schedule_work(adc.xadc_work);
iowrite32( 0x12 , &(adc.adcaddr->CLRINTADC) ); //clear INT
printk("IRQ getval , adc3_val can be read!\n");
return IRQ_HANDLED;
}
//
//driver
int xadc_probe(struct platform_device *pdev)
{
int ret = 0;
printk("probe the adc_device!\n");
//get adc_addr
adc.res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
printk("adc_baseaddr: %#x\n", adc.res->start);
adc.adcaddr = ioremap(adc.res->start, adc.res->end-adc.res->start);
//get adc_irq
adc.res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
printk("adc_irqnum:: %#x\n", adc.res->start);
ret = request_irq(adc.res->start, xadc_handler, IRQF_DISABLED, "xadc_IRQ", NULL); //申请一个中断服务(需要获取中断号) 最后一个是中断函数的参数pdev
printk("adc_device info get !\n");
//request cdev num
ret = alloc_chrdev_region(&adc.num, 0, 1, "xadc3");
if(ret) {
printk("devnum alloc fail!\n");
return ret;
}
printk("devnum: %d %d \n", MAJOR(adc.num) ,MINOR(adc.num));
//init cdev fops
cdev_init(&adc.xadc_cdev, &xadc_ops);
//register cdev into kernel
ret = cdev_add(&adc.xadc_cdev, adc.num , 1);
if(ret) {
printk("add cdev fail!\n");
goto cdev_add_out;
}
//support hotplug function
adc.xadc_class = class_create(THIS_MODULE, "xadc"); //sys/class/
adc.xadc_device = device_create(adc.xadc_class , NULL, adc.num, NULL, "adc%d", 3); //dev/
//init device adc3
iowrite32( ioread32( &(adc.adcaddr->ADCCON) ) & (~(1 << 16)) ,&(adc.adcaddr->ADCCON) ); //resolution
iowrite32( ioread32( &(adc.adcaddr->ADCCON) ) | (1 << 14) , &(adc.adcaddr->ADCCON) ); //enable prescaler
iowrite32( (ioread32( &(adc.adcaddr->ADCCON) ) & (~(0xFF << 6))) | (19<<6) , &(adc.adcaddr->ADCCON) ); //prescaler
iowrite32( ioread32( &(adc.adcaddr->ADCCON) ) & (~(1 << 2)) , &(adc.adcaddr->ADCCON) ); //normal
iowrite32( ioread32( &(adc.adcaddr->ADCMUX) ) | 0x3 , &(adc.adcaddr->ADCMUX) ); //adc 第三通道
adc.resolution = LOW_RSL; //init resolution
init_waitqueue_head(&adc.readqueue); //init wait queue
adc.available = 1; // spin_lock_init(&adc.lock);
spin_lock_init(&adc.lock);
printk("adc.available!\n");
//attr_file
adc.xadc_kobj = kzalloc(sizeof(struct kobject), GFP_KERNEL);
kobject_init(adc.xadc_kobj, &xadc_ktype);
ret = kobject_add(adc.xadc_kobj, NULL, "xadc3");
ret = sysfs_create_file(adc.xadc_kobj, &xadc_attrs[0]);
//success
return 0;
cdev_add_out:
unregister_chrdev_region(adc.num, 1); //release dev_num
//error
return ret;
}
int xadc_remove(struct platform_device *pdev)
{
//release remap
iounmap(adc.adcaddr);
///释放中断
free_irq(adc.res->start, NULL);
//unregister cdev from kernel
cdev_del(&adc.xadc_cdev);
/*
//release cdev object
kfree(&beep.xadc_cdev);
*/
//release dev num
unregister_chrdev_region(adc.num, 1);
//support hotplug function
//free device
device_del(adc.xadc_device);
//free class
class_destroy(adc.xadc_class);
//free attr_file
sysfs_remove_file(adc.xadc_kobj, &xadc_attrs[0]);
kobject_del(adc.xadc_kobj);
kfree(adc.xadc_kobj);
printk("adc_device remove !\n");
return 0;
}
//multi_device table
struct platform_device_id xadc_ids[] = { //support auto_install the driver
[0] = {
.name = "adc3"
},
[1] = {
//null
}
};
#ifdef CONFIG_OF
//设备树匹配方式
struct of_device_id xadc_table[] = {
{ .compatible = "xadc" }, //match adc3
{ } //NULL
};
#endif
struct platform_driver xadc_driver = {
.probe = xadc_probe,
.remove = xadc_remove,
.id_table = xadc_ids, //multi_device table
.driver = {
.name = "adc3",
.of_match_table = of_match_ptr(xadc_table) //设备树匹配选项
}
};
MODULE_DEVICE_TABLE(platform, xadc_ids); //support auto_install the driver
//driver install and release
static int adcmodule_init(void)
{
printk("xadc driver install\n");
//add into platform bus
platform_driver_register(&xadc_driver);
return 0;
}
static void adcmodule_exit(void)
{
printk("xad cdriver release\n");
//del from platform bus
platform_driver_unregister(&xadc_driver);
return ;
}
module_init(adcmodule_init);
module_exit(adcmodule_exit);
测试:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#define LOW 0
#define HIGH 1
int main(int argc,char **argv)
{
int chose;
int fd;
int val;
int RES;
while(1)
{
printf("1.open adc 2.close adc 3.set high_resolution 4.set low_resolution 5.read adc_val \n");
scanf("%d",&chose);
switch(chose)
{
case 1: printf("open\n");
fd = open("/dev/adc3", O_RDWR);
if(-1 == fd)
{
perror("open fail EBUSY\n");
return -1;
}
break;
case 2: printf("close\n");
close(fd);
break;
case 3: printf("set high_resolution, ");
ioctl(fd, HIGH);
RES = HIGH;
printf("set ok!\n");
break;
case 4: printf("set low_resolution, ");
ioctl(fd, LOW);
RES = LOW;
printf("set ok!\n");
break;
case 5: read(fd,&val,sizeof(int));
if(RES == LOW)
val = 1000 * (1.8/1024 * val);
else
val = 1000 * (1.8/4096 * val);
printf("readval: %d mV \n",val);
break;
default: printf("err type\n");
}
}
close(fd);
return 0;
}