/*********************************************************/
/****s3c2440 ADC adjustable resistor driver development source code (miscellaneous device driver framework)****/
/********************************************************/
#include
#include
#include
#define DEVICE_NAME "adc_driver" /*Device name*/
static void __iomem *adc_base; /*Defines a memory address used to save virtual mapping*/
static struct clk *adc_clk; /*Save the ADC clock obtained from the platform clock queue*/
DECLARE_MUTEX(ADC_LOCK); /*Declare and initialize a semaphore ADC_LOCK for mutually exclusive access to ADC resources*/
static DECLARE_WAIT_QUEUE_HEAD(adc_waitq); /*Define and initialize a waiting queue adc_waitq for blocking access to ADC resources*/
static volatile int ev_adc = 0; ///*Used to identify whether the data after AD conversion can be read, 0 means it cannot be read*/
static int adc_data; /*Used to save the read AD converted value, which is read in the ADC interrupt*/
/*ADC interrupt service program, this service program mainly reads the AD converted value from the ADC data register*/
static irqreturn_t adc_irq(int irq, void *dev_id)
{
/*Ensures that the application reads the AD converted value once here,
avoiding multiple interruptions and multiple readings of the AD converted value after the application reads once*/
if(!ev_adc)
{
/*The read AD converted value is saved in the global variable adc_data. S3C2410_ADCDAT0 is defined in regs-adc.h.
Why is it necessary to AND the previous 0x3ff here? It's very simple, because the AD converted data is saved in bits 0-9 of ADCDAT0,
so after ANDing with 0x3ff (i.e.: 1111111111), the data of bits 0-9 is obtained, and the remaining bits are all 0*/
adc_data = readl(adc_base + S3C2410_ADCDAT0) & 0x3ff;
ev_adc = 1; //Set the readable flag to 1 and wake up the waiting queue
wake_up_interruptible(&adc_waitq);
}
return IRQ_HANDLED;
}
/*ADC device driver open interface function*/
static int adc_open(struct inode *inode, struct file *file)
{
int ret;
/*Request ADC interrupt service. Here we use the shared interrupt: IRQF_SHARED. Why do we use the shared interrupt? Because
this interrupt number is also used in the touch screen driver. The interrupt service program is: adc_irq is implemented below. IRQ_ADC is the interrupt number of ADC. Note:
The last parameter of the interrupt request function must not be NULL, otherwise the interrupt request will fail. If this parameter is not used in the interrupt service program
, just give a random value. I will give 1 here*/
ret = request_irq(IRQ_ADC, adc_irq, IRQF_SHARED, DEVICE_NAME, 1);
if (ret)
{
/*Error handling*/
printk(KERN_ERR "IRQ%d error %dn", IRQ_ADC, ret);
return -EINVAL;
}
return 0;
}
/*Set ADC control register and start AD conversion*/
static void start_adc(void)
{
unsigned int tmp;
tmp = (1 << 14) | (255 << 6) | (0 << 3);/* 0 1 00000011 000 0 0 0 */
writel(tmp, adc_base + S3C2410_ADCCON); /*AD prescaler enabled, analog input channel set to AIN0*/
tmp = readl(adc_base + S3C2410_ADCCON);
tmp = tmp | (1 << 0); /* 0 1 00000011 000 0 0 1 */
writel(tmp, adc_base + S3C2410_ADCCON); /*AD conversion starts*/
}
/*ADC device driver read interface function*/
static ssize_t adc_read(struct file *filp, char *buffer, size_t count, loff_t *ppos)
{
/*Try to get the semaphore (ie: lock)*/
if (down_trylock(&ADC_LOCK))
{
return -EBUSY;
}
if(!ev_adc)/*Indicates that there is no AD converted data yet, and it cannot be read*/
{
if(filp->f_flags & O_NONBLOCK) //If the application uses non-blocking reading, an error will be returned
{
return -EAGAIN;
}
else/*Read in blocking mode*/
{
start_adc(); /*Set the ADC control register and start AD conversion*/
wait_event_interruptible(adc_waitq, ev_adc); /*Put the waiting queue to sleep*/
}
}
/*Getting here means that there is AD converted data, so the flag is cleared to 0 for the next reading*/
ev_adc = 0;
/*Send the read AD converted value to the upper application*/
copy_to_user(buffer, (char *)&adc_data, sizeof(adc_data));
up(&ADC_LOCK); /*Release the acquired semaphore (ie: unlock)*/
return sizeof(adc_data);
}
/*ADC device driver shutdown interface function*/
static int adc_release(struct inode *inode, struct file *filp)
{
return 0;
}
/*Related operations of character devices*/
static struct file_operations adc_fops =
{
.owner = THIS_MODULE,
.open = adc_open,
.read = adc_read,
.release = adc_release,
};
/*misc device structure implementation*/
static struct miscdevice adc_miscdev =
{
.minor = MISC_DYNAMIC_MINOR, /*minor device number, defined in miscdevice.h, 255*/
.name = DEVICE_NAME, /* device name*/
.fops = &adc_fops, /* ADC device file operation*/
};
static int __init adc_init(void)
{
int ret;
/*Get the ADC clock from the platform clock queue. Why do we need to get this clock here? Because the conversion frequency of the ADC is related to the clock.
Some system clocks are defined in arch/arm/plat-s3c24xx /s3c2410-clock.c*/
adc_clk = clk_get(NULL, "adc");
if (!adc_clk)
{
/*Error handling*/
printk(KERN_ERR "failed to find adc clock sourcen");
return -ENOENT;
}
/*After the clock is acquired, it must be enabled before it can be used. clk_enable is defined in arch/arm/plat-s3c/clock.c*/
clk_enable(adc_clk);
/*Map the IO space occupied by the ADC IO port to the virtual address of the memory. ioremap is defined in io.h.
Note: IO space can only be used after mapping. The operation of virtual address in the future is the operation of IO space. S3C2410_PA_ADC
is the base address of ADC controller, defined in mach-s3c2410/include/mach/map.h. 0x20 is the length of virtual address*/
adc_base = ioremap(S3C2410_PA_ADC, 0x20);
if (adc_base == NULL)
{
printk(KERN_ERR "Failed to remap register blockn"); /*Error handling*/
ret = -EINVAL;
goto err_noclk;
}
/*Register ADC as misc device, misc_register is defined in miscdevice.h. The adc_miscdev structure definition
and internal interface functions are described in step ②. MISC_DYNAMIC_MINOR is the minor device number, defined in miscdevice.h*/
ret = misc_register(&adc_miscdev);
if (ret)
{
/*Error handling*/
printk(KERN_ERR "cannot register miscdev on minor=%d (%d)n",
MISC_DYNAMIC_MINOR, ret);
goto err_nomap;
}
printk(DEVICE_NAME " initialized!n");
return 0;
//The following is the jump point of the error handling above
err_noclk:
clk_disable(adc_clk);
clk_put(adc_clk);
err_nomap:
iounmap(adc_base);
return ret;
}
static void __exit adc_exit(void)
{
free_irq(IRQ_ADC, 1);/*Release interrupt*/
iounmap(adc_base);/*Release virtual address mapping space*/
if (adc_clk)/*Shield and destroy clock*/
{
clk_disable(adc_clk);
clk_put(adc_clk);
adc_clk = NULL;
}
misc_deregister(&adc_miscdev);/*Cancel misc device*/
}
/*The exported semaphore ADC_LOCK is used in the touch screen driver, because the touch screen driver and ADC driver share
93. Related registers. In order to avoid resource competition, semaphores are used to ensure mutual exclusive access to resources*/
//EXPORT_SYMBOL(ADC_LOCK);
module_init(adc_init);
module_exit(adc_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("youshaohui 2012.10.31 in ruanjianyuan");
MODULE_DESCRIPTION("s3c2440 ADC Driver");
//=======================================================================
The application test code is developed as follows:
//=======================================================================
#include
#define MYADC "/dev/adc_driver"
void Delay_MS( unsigned int time) //50 ns
{
unsigned int i,j;
for ( i=0; i
{
for(j=0;j<30000;j++)
{
}
}
}
//adc可调电阻
int main(void)
{
int fd;
int i=0;
unsigned int value = -1;
char buf[30]={0};
fd = open(MYADC,O_RDWR,0666);
if (fd < 0)
{
perror("open device adc_driver errorn");
exit(1);
}
printf("open /dev/adc_driver success!n");
while(1)
{
read(fd,&value,4);
printf("result value=%dn",value);
Delay_MS(1000);
}
if(close(fd)<0)
{
perror("close errorn");
exit(1);
}
return 0;
}
Previous article:Summary and analysis of IIC timing problems in mini2440 debugging
Next article:3 ways to compile the driver into the kernel (using mini2440 key as an example)
Recommended ReadingLatest update time:2024-11-23 04:47
- Naxin Micro and Xinxian jointly launched the NS800RT series of real-time control MCUs
- How to learn embedded systems based on ARM platform
- Summary of jffs2_scan_eraseblock issues
- Application of SPCOMM Control in Serial Communication of Delphi7.0
- Using TComm component to realize serial communication in Delphi environment
- Bar chart code for embedded development practices
- Embedded Development Learning (10)
- Embedded Development Learning (8)
- Embedded Development Learning (6)
Professor at Beihang University, dedicated to promoting microcontrollers and embedded systems for over 20 years.
- Intel promotes AI with multi-dimensional efforts in technology, application, and ecology
- ChinaJoy Qualcomm Snapdragon Theme Pavilion takes you to experience the new changes in digital entertainment in the 5G era
- Infineon's latest generation IGBT technology platform enables precise control of speed and position
- Two test methods for LED lighting life
- Don't Let Lightning Induced Surges Scare You
- Application of brushless motor controller ML4425/4426
- Easy identification of LED power supply quality
- World's first integrated photovoltaic solar system completed in Israel
- Sliding window mean filter for avr microcontroller AD conversion
- What does call mean in the detailed explanation of ABB robot programming instructions?
- STMicroelectronics discloses its 2027-2028 financial model and path to achieve its 2030 goals
- 2024 China Automotive Charging and Battery Swapping Ecosystem Conference held in Taiyuan
- State-owned enterprises team up to invest in solid-state battery giant
- The evolution of electronic and electrical architecture is accelerating
- The first! National Automotive Chip Quality Inspection Center established
- BYD releases self-developed automotive chip using 4nm process, with a running score of up to 1.15 million
- GEODNET launches GEO-PULSE, a car GPS navigation device
- Should Chinese car companies develop their own high-computing chips?
- Infineon and Siemens combine embedded automotive software platform with microcontrollers to provide the necessary functions for next-generation SDVs
- Continental launches invisible biometric sensor display to monitor passengers' vital signs
- How to Choose an Embedded Software Development Platform
- Device thermal coupling and heat dissipation solutions on PCB
- TI's new optical stage lighting DLP chipset is said to provide up to 15,000 lumens of monochrome light
- Memory alignment processing for ARM processors
- Comparison of Two Control Methods and Zero-Crossing Output Characteristics of Unipolar SPWM
- CC4001 Quad 2-input NOR gate
- Analysis of the top ten popular RF and microwave devices
- Share: [Skills Competition Notes 01] Zigbee point-to-point button control program development
- Has anyone used STM32 to control a digital potentiometer with SPI?
- Microbit replaces the sensor with ST's LSM303