summaryrefslogblamecommitdiffstats
path: root/drivers/staging/me4000/me4000.c
blob: 01017b731d0b028c8f6586e5eac6e339aca045f1 (plain) (tree)
5575
5576
5577
5578
5579
5580
5581
5582
5583
5584
5585
5586
5587
5588
5589
5590
5591
5592
5593
5594
5595
5596
5597
5598
5599
5600
5601
5602
5603
5604
5605
5606
5607
5608
5609
5610
5611
5612
5613
5614
5615
5616
5617
5618
5619
5620
5621
5622
5623
5624
5625
5626
5627
5628
5629
5630
5631
5632
5633
5634
5635
5636
5637
5638
5639
5640
5641
5642
5643
5644
5645
5646
5647
5648
5649
5650
5651
5652
5653
5654
5655
5656
5657
5658
5659
5660
5661
5662
5663
5664
5665
5666
5667
5668
5669
5670
5671
5672
5673
5674
5675
5676
5677
5678
5679
5680
5681
5682
5683
5684
5685
5686
5687
5688
5689
5690
5691
5692
5693
5694
5695
5696
5697
5698
5699
5700
5701
5702
5703
5704
5705
5706
5707
5708
5709
5710
5711
5712
5713
5714
5715
5716
5717
5718
5719
5720
5721
5722
5723
5724
5725
5726
5727
5728
5729
5730
5731
5732
5733
5734
5735
5736
5737
5738
5739
5740
5741
5742
5743
5744
5745
5746
5747
5748
5749
5750
5751
5752
5753
5754
5755
5756
5757
5758
5759
5760
5761
5762
5763
5764
5765
5766
5767
5768
5769
5770
5771
5772
5773
5774
5775
5776
5777
5778
5779
5780
5781
5782
5783
5784
5785
5786
5787
5788
5789
5790
5791
5792
5793
5794
5795
5796
5797
5798
5799
5800
5801
5802
5803
5804
5805
5806
5807
5808
5809
5810
5811
5812
5813
5814
5815
5816
5817
5818
5819
5820
5821
5822
5823
5824
5825
5826
5827
5828
5829
5830
5831
5832
5833
5834
5835
5836
5837
5838
5839
5840
5841
5842
5843
5844
5845
5846
5847
5848
5849
5850
5851
5852
5853
5854
5855
5856
5857
5858
5859
5860
5861
5862
5863
5864
5865
5866
5867
5868
5869
5870
5871
5872
5873
5874
5875
5876
5877
5878
5879
5880
5881
5882
5883
5884
5885
5886
5887
5888
5889
5890
5891
5892
5893
5894
5895
5896
5897
5898
5899
5900
5901
5902
5903
5904
5905
5906
5907
5908
5909
5910
5911
5912
5913
5914
5915
5916
5917


























                                                                        

                        



                          
                        

                          
                       
                        

                          
                       













                                                                       
                                         

                                                                           




                                          






















                                                                               


                                                                               

                                                        

                                        
                                                                





                                                                               

                                                                





















                                                                               
























                                                                                

                                                        
                                                                           
                                                        
                                                                           



                                                                               

                                                            











                                                                            


















                                                                               
                                                         
                                                                            



                                                                               



                                                                     





                                                                               






                                                             





                                                                               




                                                                              





                                                                               

                                                                   
                                                   
                                                                                











                                                                               
 
                                                                        
 
                                                  

 
                                                                        
 
                                                        

 
                                                                            







                                          
                                                                           








                                          
                                                                       




                                                              
                                                                       




                                                               
                                                          






                                                               
                                                          






                                                              
                                          




                                     
                                                           




                                    

  
                                                           




                                    

  
                                                           






                                    

  
                                                           



                                    

  
                                                              






                                  

  
                                                              





                                   

  
                                                                    





                                   

  
                                                                       





                                   

  
                                                       



                                

  
                                                       



                                

  
                                                           




                                      

  
                                                               





                                   

  
                                                               









                                                               

  
                                          
 
                   


























































































                                                                                           
             

                                                                               
             

                                                                       
             

                                                                       
             

                                                                     
             

                                                                     
             


                                              
             






                                       

                                                               

                                 
                                                                        
                                                      

                                                                     

                                                              
                                                        
                                                    



                                                
                                                            












                                                      

                                                             
                                                           
                                            



                                  
                                                                             


























































                                                                                        
                                                          





                                          
                                                         




                                                                      
                                             





                                                                         

                                                         



                                                    
                    


                                                 
                                                                               

























































                                                                                
                                                      


                
                                             

                                                       

                                                                            





                                                                                        









                                                             
                                                                       





                                                                                          


















































































                                                                           

                                                                                












                                                                         
                                                               
 
                                                               

                                              

                                                             
                                                      
                                                
                                            



                                  
                                                     
 
                                             

                                      

                                                                            




                                                                                              








































                                                                             
                                                      
 
                                               

                                       

                                                                            




                                                                                                
























                                                                  
                                                      
 
                                               

                                       

                                                                            




                                                                                                


















                                                                   
                                                          
 
                                                       


                                       
                                                                               




                                                                                                    




















                                                                            
                                       



                                                    
                                                                     





                                                                             






                                                                  
                                                                       





                                        
                                                                           





                                                       
                                                                             





                                                  
                                                                         





                                                    
                                                                          





                                                
                                                                     





                                                       
                                                                              





                                              
                                                                             





                                               
                                                                              





                                               
                                                                              






                                                   
                                                                         




                                   
              

                                       
              

                                       
              

                                      
              

                                        
              

                                 
              


                                    
              


                      
                                                           




























                                                                            
                                                            
                                                                    



















                                                                            
                                                                                












                                                                             
                                                                     
                               
                                                                       










                                                 
                                                       







































                                                                                 
                                                                           
                                                                             









                                                                  





                                                              












                                                                             

                                                             

                                       
                            
                 
                                                                       








                                                                                    

                                                                  

                                      
                            
                 
                                                                             

























































                                                                                            

                                                             

                                       
                            
                 
                                                                       















































                                                                                    
                                                                                



                                                             
                                                                   

























                                                                                    
                                                    









                                                                        
                                                                                



                                                             
                                                                   


































                                                                                    
                                                                                



                                                             
                                                                   

















































                                                                                           




                                                       










































                                                                              












                                                                                              
                                                                  













































































                                                                              
                                                                



















                                                                       



                                                               































                                                                        



                                                               





































                                                                         
                                                             
 
                                                                   






                                                            
                                                              




                                      
                                                                    








                                                                         
                                                             
 
                                                                   










                                                                     
                                                                               




                                           
                                                                       













                                                  
                                                                            












                                                                                
                                                             
 
                                                                   









































































































































































                                                                                                                        
                                               












































                                                                                        
                                        
                                                      



                       
                                                                              
 
                                             



















                                                                              
                                             








                                                                        


                                                                                  

                                            
                                                                      




                                              

                                                                        









                                                                           
                                             





















                                                                          
                                                                           








                                                                    
                                                  
                                                                     
                                            
















                                                                           
                                             



























                                                                            
                                                                           





















                                                                           
                                             

































                                                                            
                                                                           















                                                                      

                                                                


































                                                                                                                        


                                                                                 












                                                                                             
                                                               

























                                                                                                  

                                                     



                 
                                                                         
























                                                                                                            

                                                                                    



                 

                                                                            






































                                                                                               
                                                                           

















































                                                                                                
                                                                         






















                                                                                                 
                                                                          






















                                                                                                   
                                                                               
















                                                                                                



                                                  





                                                           
                                                                               






                                                                      



                                                 





                                                           
                                                                          






                                                                 



                                                  





                                                           
                                                                  




                                                         
                                                                         






















                                                                             
                                                                         
                                                                                

                                            
                                                                            
                                                                                








                                                           

                                                                              



                
                                               



                                                                     

                                                               






                                                                                      
                                                                        




                                                                                    






















                                                                                                          



                                                         







                                                           
                                                                              















                                                                            



                                               
















                                                                            
                                                                         














                                                                                



                                                  
















                                                                            
                                                                              




                                                          
                                                                          








                                                                           
                                                                  






                                                        
                                                                          




























                                                                                                                                  


                                                                                 
























                                                                                                                                  
                                                                    





























                                                                                     
                                                                     




























                                                                                   
                                                                              





















                                                                                
                                                                                
 
                                                                  









































                                                                                      
                                                               





                                                                                         








                                               
                                                                































































                                                                                                   
                                             
















                                                                               

                                                                            




                                                             
                                                                               

                                                           
                                                                           









                                                                    

                                                                            
                                    

                                                                            







                                                                          

                                                                 
 
                                    









                                                        
                                                                         








































































                                                                                                                 

                                                                                                























                                                                                             
                                                                       











                                                                               
                                             
















                                                                             

                                                                            
                              

                                                                            








                                                                   
                                                                           

                                                                    

                                                                            
                                    

                                                                            




                                                                       
                                                                            







                                                                          
                                             
















                                                                              

                                                                            
                              

                                                                            










                                                                            
                                                                               



                                                                   
                                                                           





                                                                       
                                                                             






                                                                  
                                             






                                                                      

                                                                 
 
                                    




















                                                                                
                                                                         






































































































































































































                                                                                                                                         
                                                    
                                                                   
















                                                                
              



















                                                                                
                    




                   
                                                                





























































                                                                                     
                                                                















                                                       
                                                                   






















                                                                            
                                                                                   





                                                                                                                   

                                                                                           





                                                                                        
                                                                                   











                                                                                                                   
                                                               



































                                                                                                         
                                                                         



































                                                                                                         
                                                                         














                                                                
                                                                          














                                                                 

                                                                        
 
                                     






                                                               
                                                                          















































                                                                                                 

                                                                   
 
                                




                                                          
                                                                     












                                                                                   
                                           
 
                                                                   



















































































































                                                                                                           
                                        
                                                    




                                                    
                                                                         
 
                                             

















                                                                               
                                                                        











                                                               
                                                                         











                                                                
                                                                           











                                                                  
                                                                            











                                                                   
                                                                              

















                                                                               
                                                                           








                                                                           
                                                                  







                                                                               
                                                                                    




















                                                                             
                                                       
                                                          
                    
                                                           





























                                                                           
                                                                           























                                                                                
                                                       
                                                          
                    
                                                           
 
















































                                                                               

                                                                    

                
                                   































































































































































































                                                                                            

                                                                   


                          
                                   






















































































                                                                                                 
                                               















                                                                               
                                                                         

                                                      
                                                                         

                                                        
                                                                         











                                                                               

                                                                    
 
                                     





                                                         
                                                                          





































































































































































































































                                                                                                                                

                                                                      
 
                                   




                                                           
                                                                        























































                                                                                              

                                                                      
 
                                   




                                                           
                                                                        





























                                                                                    
                                                                      








                                                                                  
                                                                   



















                                                                                           
                                               















                                                                               
                                                                              
                              
                                                                               
                               
                                                                         











                                                                               

                                                                    
 
                                     






                                                         
                                                                          
























































                                                                                  

                                                                  
 
                              





                                                       
                                                                   

































                                                                                
                                                                 








                                                                              

                                                                   
 
                              





                                                        
                                                                   



































                                                                                 
                                                                   

























                                                                                                      
                                                       































                                                                                   
                                                                                











                                                             
                                                                                 












                                                              
                                                                               









                                                                                           

                                                               
 
                                          




























                                                                             
                                                                           









                                                                                       
                                                       















                                                                            
                                             


                   


















































                                                                               














                                                                               






























                                                                                             



                                                               















                                                         
                                             






















































































































































































































































































































































                                                                                                           
                                                       


































                                                                                
                                           
 
                                       

















                                                                               
                                                                        












                                                                                
                                       






                                                                         
                                                                        







                                                                     

                                                                         
                      

                                                                       
                      

                                                                            
























































































                                                                                        

                                 




                   
/* Device driver for Meilhaus ME-4000 board family.
 * ================================================
 *
 *  Copyright (C) 2003 Meilhaus Electronic GmbH (support@meilhaus.de)
 *
 *  This file is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 *  Author:	Guenter Gebhardt	<g.gebhardt@meilhaus.de>
 */

#include <linux/module.h>
#include <linux/fs.h>
#include <linux/sched.h>
#include <linux/interrupt.h>
#include <linux/pci.h>
#include <linux/errno.h>
#include <linux/delay.h>
#include <linux/mm.h>
#include <linux/unistd.h>
#include <linux/list.h>
#include <linux/proc_fs.h>
#include <linux/types.h>
#include <linux/poll.h>
#include <linux/vmalloc.h>
#include <linux/slab.h>
#include <asm/pgtable.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <asm/system.h>

/* Include-File for the Meilhaus ME-4000 I/O board */
#include "me4000.h"
#include "me4000_firmware.h"
#include "me4610_firmware.h"

/* Administrative stuff for modinfo */
MODULE_AUTHOR("Guenter Gebhardt <g.gebhardt@meilhaus.de>");
MODULE_DESCRIPTION
    ("Device Driver Module for Meilhaus ME-4000 boards version 1.0.5");
MODULE_SUPPORTED_DEVICE("Meilhaus ME-4000 Multi I/O boards");
MODULE_LICENSE("GPL");

/* Board specific data are kept in a global list */
static LIST_HEAD(me4000_board_info_list);

/* Major Device Numbers. 0 means to get it automatically from the System */
static int me4000_ao_major_driver_no;
static int me4000_ai_major_driver_no;
static int me4000_dio_major_driver_no;
static int me4000_cnt_major_driver_no;
static int me4000_ext_int_major_driver_no;

/* Let the user specify a custom major driver number */
module_param(me4000_ao_major_driver_no, int, 0);
MODULE_PARM_DESC(me4000_ao_major_driver_no,
		 "Major driver number for analog output (default 0)");

module_param(me4000_ai_major_driver_no, int, 0);
MODULE_PARM_DESC(me4000_ai_major_driver_no,
		 "Major driver number for analog input (default 0)");

module_param(me4000_dio_major_driver_no, int, 0);
MODULE_PARM_DESC(me4000_dio_major_driver_no,
		 "Major driver number digital I/O (default 0)");

module_param(me4000_cnt_major_driver_no, int, 0);
MODULE_PARM_DESC(me4000_cnt_major_driver_no,
		 "Major driver number for counter (default 0)");

module_param(me4000_ext_int_major_driver_no, int, 0);
MODULE_PARM_DESC(me4000_ext_int_major_driver_no,
		 "Major driver number for external interrupt (default 0)");

/*-----------------------------------------------------------------------------
  Board detection and initialization
  ---------------------------------------------------------------------------*/
static int me4000_probe(struct pci_dev *dev, const struct pci_device_id *id);
static int me4000_xilinx_download(struct me4000_info *);
static int me4000_reset_board(struct me4000_info *);

static void clear_board_info_list(void);
static void release_ao_contexts(struct me4000_info *board_info);
/*-----------------------------------------------------------------------------
  Stuff used by all device parts
  ---------------------------------------------------------------------------*/
static int me4000_open(struct inode *, struct file *);
static int me4000_release(struct inode *, struct file *);

static int me4000_get_user_info(struct me4000_user_info *,
				struct me4000_info *board_info);
static int me4000_read_procmem(char *, char **, off_t, int, int *, void *);

/*-----------------------------------------------------------------------------
  Analog output stuff
  ---------------------------------------------------------------------------*/
static ssize_t me4000_ao_write_sing(struct file *, const char *, size_t,
				    loff_t *);
static ssize_t me4000_ao_write_wrap(struct file *, const char *, size_t,
				    loff_t *);
static ssize_t me4000_ao_write_cont(struct file *, const char *, size_t,
				    loff_t *);

static int me4000_ao_ioctl_sing(struct inode *, struct file *, unsigned int,
				unsigned long);
static int me4000_ao_ioctl_wrap(struct inode *, struct file *, unsigned int,
				unsigned long);
static int me4000_ao_ioctl_cont(struct inode *, struct file *, unsigned int,
				unsigned long);

static unsigned int me4000_ao_poll_cont(struct file *, poll_table *);
static int me4000_ao_fsync_cont(struct file *, struct dentry *, int);

static int me4000_ao_start(unsigned long *, struct me4000_ao_context *);
static int me4000_ao_stop(struct me4000_ao_context *);
static int me4000_ao_immediate_stop(struct me4000_ao_context *);
static int me4000_ao_timer_set_divisor(u32 *, struct me4000_ao_context *);
static int me4000_ao_preload(struct me4000_ao_context *);
static int me4000_ao_preload_update(struct me4000_ao_context *);
static int me4000_ao_ex_trig_set_edge(int *, struct me4000_ao_context *);
static int me4000_ao_ex_trig_enable(struct me4000_ao_context *);
static int me4000_ao_ex_trig_disable(struct me4000_ao_context *);
static int me4000_ao_prepare(struct me4000_ao_context *ao_info);
static int me4000_ao_reset(struct me4000_ao_context *ao_info);
static int me4000_ao_enable_do(struct me4000_ao_context *);
static int me4000_ao_disable_do(struct me4000_ao_context *);
static int me4000_ao_fsm_state(int *, struct me4000_ao_context *);

static int me4000_ao_simultaneous_ex_trig(struct me4000_ao_context *ao_context);
static int me4000_ao_simultaneous_sw(struct me4000_ao_context *ao_context);
static int me4000_ao_simultaneous_disable(struct me4000_ao_context *ao_context);
static int me4000_ao_simultaneous_update(
					struct me4000_ao_channel_list *channels,
					struct me4000_ao_context *ao_context);

static int me4000_ao_synchronous_ex_trig(struct me4000_ao_context *ao_context);
static int me4000_ao_synchronous_sw(struct me4000_ao_context *ao_context);
static int me4000_ao_synchronous_disable(struct me4000_ao_context *ao_context);

static int me4000_ao_ex_trig_timeout(unsigned long *arg,
				     struct me4000_ao_context *ao_context);
static int me4000_ao_get_free_buffer(unsigned long *arg,
				     struct me4000_ao_context *ao_context);

/*-----------------------------------------------------------------------------
  Analog input stuff
  ---------------------------------------------------------------------------*/
static int me4000_ai_single(struct me4000_ai_single *,
				struct me4000_ai_context *);
static int me4000_ai_ioctl_sing(struct inode *, struct file *, unsigned int,
				unsigned long);

static ssize_t me4000_ai_read(struct file *, char *, size_t, loff_t *);
static int me4000_ai_ioctl_sw(struct inode *, struct file *, unsigned int,
			      unsigned long);
static unsigned int me4000_ai_poll(struct file *, poll_table *);
static int me4000_ai_fasync(int fd, struct file *file_p, int mode);

static int me4000_ai_ioctl_ext(struct inode *, struct file *, unsigned int,
			       unsigned long);

static int me4000_ai_prepare(struct me4000_ai_context *ai_context);
static int me4000_ai_reset(struct me4000_ai_context *ai_context);
static int me4000_ai_config(struct me4000_ai_config *,
				struct me4000_ai_context *);
static int me4000_ai_start(struct me4000_ai_context *);
static int me4000_ai_start_ex(unsigned long *, struct me4000_ai_context *);
static int me4000_ai_stop(struct me4000_ai_context *);
static int me4000_ai_immediate_stop(struct me4000_ai_context *);
static int me4000_ai_ex_trig_enable(struct me4000_ai_context *);
static int me4000_ai_ex_trig_disable(struct me4000_ai_context *);
static int me4000_ai_ex_trig_setup(struct me4000_ai_trigger *,
				   struct me4000_ai_context *);
static int me4000_ai_sc_setup(struct me4000_ai_sc *arg,
			      struct me4000_ai_context *ai_context);
static int me4000_ai_offset_enable(struct me4000_ai_context *ai_context);
static int me4000_ai_offset_disable(struct me4000_ai_context *ai_context);
static int me4000_ai_fullscale_enable(struct me4000_ai_context *ai_context);
static int me4000_ai_fullscale_disable(struct me4000_ai_context *ai_context);
static int me4000_ai_fsm_state(int *arg, struct me4000_ai_context *ai_context);
static int me4000_ai_get_count_buffer(unsigned long *arg,
				      struct me4000_ai_context *ai_context);

/*-----------------------------------------------------------------------------
  EEPROM stuff
  ---------------------------------------------------------------------------*/
static int me4000_eeprom_read(struct me4000_eeprom *arg,
			      struct me4000_ai_context *ai_context);
static int me4000_eeprom_write(struct me4000_eeprom *arg,
			       struct me4000_ai_context *ai_context);

/*-----------------------------------------------------------------------------
  Digital I/O stuff
  ---------------------------------------------------------------------------*/
static int me4000_dio_ioctl(struct inode *, struct file *, unsigned int,
			    unsigned long);
static int me4000_dio_config(struct me4000_dio_config *,
				struct me4000_dio_context *);
static int me4000_dio_get_byte(struct me4000_dio_byte *,
				struct me4000_dio_context *);
static int me4000_dio_set_byte(struct me4000_dio_byte *,
				struct me4000_dio_context *);
static int me4000_dio_reset(struct me4000_dio_context *);

/*-----------------------------------------------------------------------------
  Counter stuff
  ---------------------------------------------------------------------------*/
static int me4000_cnt_ioctl(struct inode *, struct file *, unsigned int,
			    unsigned long);
static int me4000_cnt_config(struct me4000_cnt_config *,
				struct me4000_cnt_context *);
static int me4000_cnt_read(struct me4000_cnt *, struct me4000_cnt_context *);
static int me4000_cnt_write(struct me4000_cnt *, struct me4000_cnt_context *);
static int me4000_cnt_reset(struct me4000_cnt_context *);

/*-----------------------------------------------------------------------------
  External interrupt routines
  ---------------------------------------------------------------------------*/
static int me4000_ext_int_ioctl(struct inode *, struct file *, unsigned int,
				unsigned long);
static int me4000_ext_int_enable(struct me4000_ext_int_context *);
static int me4000_ext_int_disable(struct me4000_ext_int_context *);
static int me4000_ext_int_count(unsigned long *arg,
				struct me4000_ext_int_context *ext_int_context);
static int me4000_ext_int_fasync(int fd, struct file *file_ptr, int mode);

/*-----------------------------------------------------------------------------
  The interrupt service routines
  ---------------------------------------------------------------------------*/
static irqreturn_t me4000_ao_isr(int, void *);
static irqreturn_t me4000_ai_isr(int, void *);
static irqreturn_t me4000_ext_int_isr(int, void *);

/*-----------------------------------------------------------------------------
  Inline functions
  ---------------------------------------------------------------------------*/

static inline int me4000_buf_count(struct me4000_circ_buf buf, int size)
{
	return (buf.head - buf.tail) & (size - 1);
}

static inline int me4000_buf_space(struct me4000_circ_buf buf, int size)
{
	return (buf.tail - (buf.head + 1)) & (size - 1);
}

static inline int me4000_values_to_end(struct me4000_circ_buf buf, int size)
{
	int end;
	int n;
	end = size - buf.tail;
	n = (buf.head + end) & (size - 1);
	return (n < end) ? n : end;
}

static inline int me4000_space_to_end(struct me4000_circ_buf buf, int size)
{
	int end;
	int n;

	end = size - 1 - buf.head;
	n = (end + buf.tail) & (size - 1);
	return (n <= end) ? n : (end + 1);
}

static inline void me4000_outb(unsigned char value, unsigned long port)
{
	PORT_PDEBUG("--> 0x%02X port 0x%04lX\n", value, port);
	outb(value, port);
}

static inline void me4000_outl(unsigned long value, unsigned long port)
{
	PORT_PDEBUG("--> 0x%08lX port 0x%04lX\n", value, port);
	outl(value, port);
}

static inline unsigned long me4000_inl(unsigned long port)
{
	unsigned long value;
	value = inl(port);
	PORT_PDEBUG("<-- 0x%08lX port 0x%04lX\n", value, port);
	return value;
}

static inline unsigned char me4000_inb(unsigned long port)
{
	unsigned char value;
	value = inb(port);
	PORT_PDEBUG("<-- 0x%08X port 0x%04lX\n", value, port);
	return value;
}

static struct pci_driver me4000_driver = {
	.name = ME4000_NAME,
	.id_table = me4000_pci_table,
	.probe = me4000_probe
};

static const struct file_operations me4000_ao_fops_sing = {
      .owner = THIS_MODULE,
      .write = me4000_ao_write_sing,
      .ioctl = me4000_ao_ioctl_sing,
      .open = me4000_open,
      .release = me4000_release,
};

static const struct file_operations me4000_ao_fops_wrap = {
      .owner = THIS_MODULE,
      .write = me4000_ao_write_wrap,
      .ioctl = me4000_ao_ioctl_wrap,
      .open = me4000_open,
      .release = me4000_release,
};

static const struct file_operations me4000_ao_fops_cont = {
      .owner = THIS_MODULE,
      .write = me4000_ao_write_cont,
      .poll = me4000_ao_poll_cont,
      .ioctl = me4000_ao_ioctl_cont,
      .open = me4000_open,
      .release = me4000_release,
      .fsync = me4000_ao_fsync_cont,
};

static const struct file_operations me4000_ai_fops_sing = {
      .owner = THIS_MODULE,
      .ioctl = me4000_ai_ioctl_sing,
      .open = me4000_open,
      .release = me4000_release,
};

static const struct file_operations me4000_ai_fops_cont_sw = {
      .owner = THIS_MODULE,
      .read = me4000_ai_read,
      .poll = me4000_ai_poll,
      .ioctl = me4000_ai_ioctl_sw,
      .open = me4000_open,
      .release = me4000_release,
      .fasync = me4000_ai_fasync,
};

static const struct file_operations me4000_ai_fops_cont_et = {
      .owner = THIS_MODULE,
      .read = me4000_ai_read,
      .poll = me4000_ai_poll,
      .ioctl = me4000_ai_ioctl_ext,
      .open = me4000_open,
      .release = me4000_release,
};

static const struct file_operations me4000_ai_fops_cont_et_value = {
      .owner = THIS_MODULE,
      .read = me4000_ai_read,
      .poll = me4000_ai_poll,
      .ioctl = me4000_ai_ioctl_ext,
      .open = me4000_open,
      .release = me4000_release,
};

static const struct file_operations me4000_ai_fops_cont_et_chanlist = {
      .owner = THIS_MODULE,
      .read = me4000_ai_read,
      .poll = me4000_ai_poll,
      .ioctl = me4000_ai_ioctl_ext,
      .open = me4000_open,
      .release = me4000_release,
};

static const struct file_operations me4000_dio_fops = {
      .owner = THIS_MODULE,
      .ioctl = me4000_dio_ioctl,
      .open = me4000_open,
      .release = me4000_release,
};

static const struct file_operations me4000_cnt_fops = {
      .owner = THIS_MODULE,
      .ioctl = me4000_cnt_ioctl,
      .open = me4000_open,
      .release = me4000_release,
};

static const struct file_operations me4000_ext_int_fops = {
      .owner = THIS_MODULE,
      .ioctl = me4000_ext_int_ioctl,
      .open = me4000_open,
      .release = me4000_release,
      .fasync = me4000_ext_int_fasync,
};

static const struct file_operations *me4000_ao_fops_array[] = {
	/* single operations */
	&me4000_ao_fops_sing,
	/* wraparound operations */
	&me4000_ao_fops_wrap,
	/* continuous operations */
	&me4000_ao_fops_cont,
};

static const struct file_operations *me4000_ai_fops_array[] = {
	/* single operations */
	&me4000_ai_fops_sing,
	/* continuous operations with software start */
	&me4000_ai_fops_cont_sw,
	/* continuous operations with external trigger */
	&me4000_ai_fops_cont_et,
	/* sample values by external trigger */
	&me4000_ai_fops_cont_et_value,
	/* work through one channel list by external trigger */
	&me4000_ai_fops_cont_et_chanlist,
};

static int __init me4000_init_module(void)
{
	int result;

	CALL_PDEBUG("init_module() is executed\n");

	/* Register driver capabilities */
	result = pci_register_driver(&me4000_driver);
	PDEBUG("init_module():%d devices detected\n", result);
	if (result < 0) {
		printk(KERN_ERR "ME4000:init_module():Can't register driver\n");
		goto INIT_ERROR_1;
	}

	/* Allocate major number for analog output */
	result =
	    register_chrdev(me4000_ao_major_driver_no, ME4000_AO_NAME,
			    &me4000_ao_fops_sing);
	if (result < 0) {
		printk(KERN_ERR "ME4000:init_module():Can't get AO major no\n");
		goto INIT_ERROR_2;
	} else {
		me4000_ao_major_driver_no = result;
	}
	PDEBUG("init_module():Major driver number for AO = %ld\n",
	       me4000_ao_major_driver_no);

	/* Allocate major number for analog input  */
	result =
	    register_chrdev(me4000_ai_major_driver_no, ME4000_AI_NAME,
			    &me4000_ai_fops_sing);
	if (result < 0) {
		printk(KERN_ERR "ME4000:init_module():Can't get AI major no\n");
		goto INIT_ERROR_3;
	} else {
		me4000_ai_major_driver_no = result;
	}
	PDEBUG("init_module():Major driver number for AI = %ld\n",
	       me4000_ai_major_driver_no);

	/* Allocate major number for digital I/O */
	result =
	    register_chrdev(me4000_dio_major_driver_no, ME4000_DIO_NAME,
			    &me4000_dio_fops);
	if (result < 0) {
		printk(KERN_ERR
		       "ME4000:init_module():Can't get DIO major no\n");
		goto INIT_ERROR_4;
	} else {
		me4000_dio_major_driver_no = result;
	}
	PDEBUG("init_module():Major driver number for DIO = %ld\n",
	       me4000_dio_major_driver_no);

	/* Allocate major number for counter */
	result =
	    register_chrdev(me4000_cnt_major_driver_no, ME4000_CNT_NAME,
			    &me4000_cnt_fops);
	if (result < 0) {
		printk(KERN_ERR
		       "ME4000:init_module():Can't get CNT major no\n");
		goto INIT_ERROR_5;
	} else {
		me4000_cnt_major_driver_no = result;
	}
	PDEBUG("init_module():Major driver number for CNT = %ld\n",
	       me4000_cnt_major_driver_no);

	/* Allocate major number for external interrupt */
	result =
	    register_chrdev(me4000_ext_int_major_driver_no, ME4000_EXT_INT_NAME,
			    &me4000_ext_int_fops);
	if (result < 0) {
		printk(KERN_ERR
		       "ME4000:init_module():Can't get major no for external interrupt\n");
		goto INIT_ERROR_6;
	} else {
		me4000_ext_int_major_driver_no = result;
	}
	PDEBUG
	    ("init_module():Major driver number for external interrupt = %ld\n",
	     me4000_ext_int_major_driver_no);

	/* Create the /proc/me4000 entry */
	if (!create_proc_read_entry
	    ("me4000", 0, NULL, me4000_read_procmem, NULL)) {
		result = -ENODEV;
		printk(KERN_ERR
		       "ME4000:init_module():Can't create proc entry\n");
		goto INIT_ERROR_7;
	}

	return 0;

INIT_ERROR_7:
	unregister_chrdev(me4000_ext_int_major_driver_no, ME4000_EXT_INT_NAME);

INIT_ERROR_6:
	unregister_chrdev(me4000_cnt_major_driver_no, ME4000_CNT_NAME);

INIT_ERROR_5:
	unregister_chrdev(me4000_dio_major_driver_no, ME4000_DIO_NAME);

INIT_ERROR_4:
	unregister_chrdev(me4000_ai_major_driver_no, ME4000_AI_NAME);

INIT_ERROR_3:
	unregister_chrdev(me4000_ao_major_driver_no, ME4000_AO_NAME);

INIT_ERROR_2:
	pci_unregister_driver(&me4000_driver);
	clear_board_info_list();

INIT_ERROR_1:
	return result;
}

module_init(me4000_init_module);

static void clear_board_info_list(void)
{
	struct me4000_info *board_info, *board_info_safe;
	struct me4000_ao_context *ao_context, *ao_context_safe;

	/* Clear context lists */
	list_for_each_entry(board_info, &me4000_board_info_list, list) {
		/* Clear analog output context list */
		list_for_each_entry_safe(ao_context, ao_context_safe,
				&board_info->ao_context_list, list) {
			me4000_ao_reset(ao_context);
			free_irq(ao_context->irq, ao_context);
			kfree(ao_context->circ_buf.buf);
			list_del(&ao_context->list);
			kfree(ao_context);
		}

		/* Clear analog input context */
		kfree(board_info->ai_context->circ_buf.buf);
		kfree(board_info->ai_context);

		/* Clear digital I/O context */
		kfree(board_info->dio_context);

		/* Clear counter context */
		kfree(board_info->cnt_context);

		/* Clear external interrupt context */
		kfree(board_info->ext_int_context);
	}

	/* Clear the board info list */
	list_for_each_entry_safe(board_info, board_info_safe,
			&me4000_board_info_list, list) {
		pci_release_regions(board_info->pci_dev_p);
		list_del(&board_info->list);
		kfree(board_info);
	}
}

static int get_registers(struct pci_dev *dev, struct me4000_info *board_info)
{

	/*--------------------------- plx regbase ---------------------------------*/

	board_info->plx_regbase = pci_resource_start(dev, 1);
	if (board_info->plx_regbase == 0) {
		printk(KERN_ERR
		       "ME4000:get_registers():PCI base address 1 is not available\n");
		return -ENODEV;
	}
	board_info->plx_regbase_size = pci_resource_len(dev, 1);

	PDEBUG
	    ("get_registers():PLX configuration registers at address 0x%4lX [0x%4lX]\n",
	     board_info->plx_regbase, board_info->plx_regbase_size);

	/*--------------------------- me4000 regbase ------------------------------*/

	board_info->me4000_regbase = pci_resource_start(dev, 2);
	if (board_info->me4000_regbase == 0) {
		printk(KERN_ERR
		       "ME4000:get_registers():PCI base address 2 is not available\n");
		return -ENODEV;
	}
	board_info->me4000_regbase_size = pci_resource_len(dev, 2);

	PDEBUG("get_registers():ME4000 registers at address 0x%4lX [0x%4lX]\n",
	       board_info->me4000_regbase, board_info->me4000_regbase_size);

	/*--------------------------- timer regbase ------------------------------*/

	board_info->timer_regbase = pci_resource_start(dev, 3);
	if (board_info->timer_regbase == 0) {
		printk(KERN_ERR
		       "ME4000:get_registers():PCI base address 3 is not available\n");
		return -ENODEV;
	}
	board_info->timer_regbase_size = pci_resource_len(dev, 3);

	PDEBUG("get_registers():Timer registers at address 0x%4lX [0x%4lX]\n",
	       board_info->timer_regbase, board_info->timer_regbase_size);

	/*--------------------------- program regbase ------------------------------*/

	board_info->program_regbase = pci_resource_start(dev, 5);
	if (board_info->program_regbase == 0) {
		printk(KERN_ERR
		       "get_registers():ME4000:PCI base address 5 is not available\n");
		return -ENODEV;
	}
	board_info->program_regbase_size = pci_resource_len(dev, 5);

	PDEBUG("get_registers():Program registers at address 0x%4lX [0x%4lX]\n",
	       board_info->program_regbase, board_info->program_regbase_size);

	return 0;
}

static int init_board_info(struct pci_dev *pci_dev_p,
			   struct me4000_info *board_info)
{
	int i;
	int result;
	struct list_head *board_p;
	board_info->pci_dev_p = pci_dev_p;

	for (i = 0; i < ARRAY_SIZE(me4000_boards); i++) {
		if (me4000_boards[i].device_id == pci_dev_p->device) {
			board_info->board_p = &me4000_boards[i];
			break;
		}
	}
	if (i == ARRAY_SIZE(me4000_boards)) {
		printk(KERN_ERR
		       "ME4000:init_board_info():Device ID not valid\n");
		return -ENODEV;
	}

	/* Get the index of the board in the global list */
	i = 0;
	list_for_each(board_p, &me4000_board_info_list) {
		if (board_p == &board_info->list) {
			board_info->board_count = i;
			break;
		}
		i++;
	}
	if (board_p == &me4000_board_info_list) {
		printk(KERN_ERR
		       "ME4000:init_board_info():Cannot get index of board\n");
		return -ENODEV;
	}

	/* Init list head for analog output contexts */
	INIT_LIST_HEAD(&board_info->ao_context_list);

	/* Init spin locks */
	spin_lock_init(&board_info->preload_lock);
	spin_lock_init(&board_info->ai_ctrl_lock);

	/* Get the serial number */
	result = pci_read_config_dword(pci_dev_p, 0x2C, &board_info->serial_no);
	if (result != PCIBIOS_SUCCESSFUL) {
		printk(KERN_WARNING
		       "ME4000:init_board_info: Can't get serial_no\n");
		return result;
	}
	PDEBUG("init_board_info():serial_no = 0x%x\n", board_info->serial_no);

	/* Get the hardware revision */
	result =
	    pci_read_config_byte(pci_dev_p, 0x08, &board_info->hw_revision);
	if (result != PCIBIOS_SUCCESSFUL) {
		printk(KERN_WARNING
		       "ME4000:init_board_info():Can't get hw_revision\n");
		return result;
	}
	PDEBUG("init_board_info():hw_revision = 0x%x\n",
	       board_info->hw_revision);

	/* Get the vendor id */
	board_info->vendor_id = pci_dev_p->vendor;
	PDEBUG("init_board_info():vendor_id = 0x%x\n", board_info->vendor_id);

	/* Get the device id */
	board_info->device_id = pci_dev_p->device;
	PDEBUG("init_board_info():device_id = 0x%x\n", board_info->device_id);

	/* Get the pci device number */
	board_info->pci_dev_no = PCI_FUNC(pci_dev_p->devfn);
	PDEBUG("init_board_info():pci_func_no = 0x%x\n",
	       board_info->pci_func_no);

	/* Get the pci slot number */
	board_info->pci_dev_no = PCI_SLOT(pci_dev_p->devfn);
	PDEBUG("init_board_info():pci_dev_no = 0x%x\n", board_info->pci_dev_no);

	/* Get the pci bus number */
	board_info->pci_bus_no = pci_dev_p->bus->number;
	PDEBUG("init_board_info():pci_bus_no = 0x%x\n", board_info->pci_bus_no);

	/* Get the irq assigned to the board */
	board_info->irq = pci_dev_p->irq;
	PDEBUG("init_board_info():irq = %d\n", board_info->irq);

	return 0;
}

static int alloc_ao_contexts(struct me4000_info *info)
{
	int i;
	int err;
	struct me4000_ao_context *ao_context;

	for (i = 0; i < info->board_p->ao.count; i++) {
		ao_context = kzalloc(sizeof(struct me4000_ao_context),
								GFP_KERNEL);
		if (!ao_context) {
			printk(KERN_ERR
			       "alloc_ao_contexts():Can't get memory for ao context\n");
			release_ao_contexts(info);
			return -ENOMEM;
		}

		spin_lock_init(&ao_context->use_lock);
		spin_lock_init(&ao_context->int_lock);
		ao_context->irq = info->irq;
		init_waitqueue_head(&ao_context->wait_queue);
		ao_context->board_info = info;

		if (info->board_p->ao.fifo_count) {
			/* Allocate circular buffer */
			ao_context->circ_buf.buf =
			    kzalloc(ME4000_AO_BUFFER_SIZE, GFP_KERNEL);
			if (!ao_context->circ_buf.buf) {
				printk(KERN_ERR
				       "alloc_ao_contexts():Can't get circular buffer\n");
				release_ao_contexts(info);
				return -ENOMEM;
			}

			/* Clear the circular buffer */
			ao_context->circ_buf.head = 0;
			ao_context->circ_buf.tail = 0;
		}

		switch (i) {
		case 0:
			ao_context->ctrl_reg =
			    info->me4000_regbase + ME4000_AO_00_CTRL_REG;
			ao_context->status_reg =
			    info->me4000_regbase + ME4000_AO_00_STATUS_REG;
			ao_context->fifo_reg =
			    info->me4000_regbase + ME4000_AO_00_FIFO_REG;
			ao_context->single_reg =
			    info->me4000_regbase + ME4000_AO_00_SINGLE_REG;
			ao_context->timer_reg =
			    info->me4000_regbase + ME4000_AO_00_TIMER_REG;
			ao_context->irq_status_reg =
			    info->me4000_regbase + ME4000_IRQ_STATUS_REG;
			ao_context->preload_reg =
			    info->me4000_regbase + ME4000_AO_LOADSETREG_XX;
			break;
		case 1:
			ao_context->ctrl_reg =
			    info->me4000_regbase + ME4000_AO_01_CTRL_REG;
			ao_context->status_reg =
			    info->me4000_regbase + ME4000_AO_01_STATUS_REG;
			ao_context->fifo_reg =
			    info->me4000_regbase + ME4000_AO_01_FIFO_REG;
			ao_context->single_reg =
			    info->me4000_regbase + ME4000_AO_01_SINGLE_REG;
			ao_context->timer_reg =
			    info->me4000_regbase + ME4000_AO_01_TIMER_REG;
			ao_context->irq_status_reg =
			    info->me4000_regbase + ME4000_IRQ_STATUS_REG;
			ao_context->preload_reg =
			    info->me4000_regbase + ME4000_AO_LOADSETREG_XX;
			break;
		case 2:
			ao_context->ctrl_reg =
			    info->me4000_regbase + ME4000_AO_02_CTRL_REG;
			ao_context->status_reg =
			    info->me4000_regbase + ME4000_AO_02_STATUS_REG;
			ao_context->fifo_reg =
			    info->me4000_regbase + ME4000_AO_02_FIFO_REG;
			ao_context->single_reg =
			    info->me4000_regbase + ME4000_AO_02_SINGLE_REG;
			ao_context->timer_reg =
			    info->me4000_regbase + ME4000_AO_02_TIMER_REG;
			ao_context->irq_status_reg =
			    info->me4000_regbase + ME4000_IRQ_STATUS_REG;
			ao_context->preload_reg =
			    info->me4000_regbase + ME4000_AO_LOADSETREG_XX;
			break;
		case 3:
			ao_context->ctrl_reg =
			    info->me4000_regbase + ME4000_AO_03_CTRL_REG;
			ao_context->status_reg =
			    info->me4000_regbase + ME4000_AO_03_STATUS_REG;
			ao_context->fifo_reg =
			    info->me4000_regbase + ME4000_AO_03_FIFO_REG;
			ao_context->single_reg =
			    info->me4000_regbase + ME4000_AO_03_SINGLE_REG;
			ao_context->timer_reg =
			    info->me4000_regbase + ME4000_AO_03_TIMER_REG;
			ao_context->irq_status_reg =
			    info->me4000_regbase + ME4000_IRQ_STATUS_REG;
			ao_context->preload_reg =
			    info->me4000_regbase + ME4000_AO_LOADSETREG_XX;
			break;
		default:
			break;
		}

		if (info->board_p->ao.fifo_count) {
			/* Request the interrupt line */
			err =
			    request_irq(ao_context->irq, me4000_ao_isr,
					IRQF_DISABLED | IRQF_SHARED,
					ME4000_NAME, ao_context);
			if (err) {
				printk(KERN_ERR
				       "%s:Can't get interrupt line", __func__);
				kfree(ao_context->circ_buf.buf);
				kfree(ao_context);
				release_ao_contexts(info);
				return -ENODEV;
			}
		}

		list_add_tail(&ao_context->list, &info->ao_context_list);
		ao_context->index = i;
	}

	return 0;
}

static void release_ao_contexts(struct me4000_info *board_info)
{
	struct me4000_ao_context *ao_context, *ao_context_safe;

	/* Clear analog output context list */
	list_for_each_entry_safe(ao_context, ao_context_safe,
			&board_info->ao_context_list, list) {
		free_irq(ao_context->irq, ao_context);
		kfree(ao_context->circ_buf.buf);
		list_del(&ao_context->list);
		kfree(ao_context);
	}
}

static int alloc_ai_context(struct me4000_info *info)
{
	struct me4000_ai_context *ai_context;

	if (info->board_p->ai.count) {
		ai_context = kzalloc(sizeof(struct me4000_ai_context),
								GFP_KERNEL);
		if (!ai_context) {
			printk(KERN_ERR
			       "ME4000:alloc_ai_context():Can't get memory for ai context\n");
			return -ENOMEM;
		}

		info->ai_context = ai_context;

		spin_lock_init(&ai_context->use_lock);
		spin_lock_init(&ai_context->int_lock);
		ai_context->number = 0;
		ai_context->irq = info->irq;
		init_waitqueue_head(&ai_context->wait_queue);
		ai_context->board_info = info;

		ai_context->ctrl_reg =
		    info->me4000_regbase + ME4000_AI_CTRL_REG;
		ai_context->status_reg =
		    info->me4000_regbase + ME4000_AI_STATUS_REG;
		ai_context->channel_list_reg =
		    info->me4000_regbase + ME4000_AI_CHANNEL_LIST_REG;
		ai_context->data_reg =
		    info->me4000_regbase + ME4000_AI_DATA_REG;
		ai_context->chan_timer_reg =
		    info->me4000_regbase + ME4000_AI_CHAN_TIMER_REG;
		ai_context->chan_pre_timer_reg =
		    info->me4000_regbase + ME4000_AI_CHAN_PRE_TIMER_REG;
		ai_context->scan_timer_low_reg =
		    info->me4000_regbase + ME4000_AI_SCAN_TIMER_LOW_REG;
		ai_context->scan_timer_high_reg =
		    info->me4000_regbase + ME4000_AI_SCAN_TIMER_HIGH_REG;
		ai_context->scan_pre_timer_low_reg =
		    info->me4000_regbase + ME4000_AI_SCAN_PRE_TIMER_LOW_REG;
		ai_context->scan_pre_timer_high_reg =
		    info->me4000_regbase + ME4000_AI_SCAN_PRE_TIMER_HIGH_REG;
		ai_context->start_reg =
		    info->me4000_regbase + ME4000_AI_START_REG;
		ai_context->irq_status_reg =
		    info->me4000_regbase + ME4000_IRQ_STATUS_REG;
		ai_context->sample_counter_reg =
		    info->me4000_regbase + ME4000_AI_SAMPLE_COUNTER_REG;
	}

	return 0;
}

static int alloc_dio_context(struct me4000_info *info)
{
	struct me4000_dio_context *dio_context;

	if (info->board_p->dio.count) {
		dio_context = kzalloc(sizeof(struct me4000_dio_context),
								GFP_KERNEL);
		if (!dio_context) {
			printk(KERN_ERR
			       "ME4000:alloc_dio_context():Can't get memory for dio context\n");
			return -ENOMEM;
		}

		info->dio_context = dio_context;

		spin_lock_init(&dio_context->use_lock);
		dio_context->board_info = info;

		dio_context->dio_count = info->board_p->dio.count;

		dio_context->dir_reg =
		    info->me4000_regbase + ME4000_DIO_DIR_REG;
		dio_context->ctrl_reg =
		    info->me4000_regbase + ME4000_DIO_CTRL_REG;
		dio_context->port_0_reg =
		    info->me4000_regbase + ME4000_DIO_PORT_0_REG;
		dio_context->port_1_reg =
		    info->me4000_regbase + ME4000_DIO_PORT_1_REG;
		dio_context->port_2_reg =
		    info->me4000_regbase + ME4000_DIO_PORT_2_REG;
		dio_context->port_3_reg =
		    info->me4000_regbase + ME4000_DIO_PORT_3_REG;
	}

	return 0;
}

static int alloc_cnt_context(struct me4000_info *info)
{
	struct me4000_cnt_context *cnt_context;

	if (info->board_p->cnt.count) {
		cnt_context = kzalloc(sizeof(struct me4000_cnt_context),
								GFP_KERNEL);
		if (!cnt_context) {
			printk(KERN_ERR
			       "ME4000:alloc_cnt_context():Can't get memory for cnt context\n");
			return -ENOMEM;
		}

		info->cnt_context = cnt_context;

		spin_lock_init(&cnt_context->use_lock);
		cnt_context->board_info = info;

		cnt_context->ctrl_reg =
		    info->timer_regbase + ME4000_CNT_CTRL_REG;
		cnt_context->counter_0_reg =
		    info->timer_regbase + ME4000_CNT_COUNTER_0_REG;
		cnt_context->counter_1_reg =
		    info->timer_regbase + ME4000_CNT_COUNTER_1_REG;
		cnt_context->counter_2_reg =
		    info->timer_regbase + ME4000_CNT_COUNTER_2_REG;
	}

	return 0;
}

static int alloc_ext_int_context(struct me4000_info *info)
{
	struct me4000_ext_int_context *ext_int_context;

	if (info->board_p->cnt.count) {
		ext_int_context =
		    kzalloc(sizeof(struct me4000_ext_int_context), GFP_KERNEL);
		if (!ext_int_context) {
			printk(KERN_ERR
			       "ME4000:alloc_ext_int_context():Can't get memory for cnt context\n");
			return -ENOMEM;
		}

		info->ext_int_context = ext_int_context;

		spin_lock_init(&ext_int_context->use_lock);
		ext_int_context->board_info = info;

		ext_int_context->fasync_ptr = NULL;
		ext_int_context->irq = info->irq;

		ext_int_context->ctrl_reg =
		    info->me4000_regbase + ME4000_AI_CTRL_REG;
		ext_int_context->irq_status_reg =
		    info->me4000_regbase + ME4000_IRQ_STATUS_REG;
	}

	return 0;
}

static int me4000_probe(struct pci_dev *dev, const struct pci_device_id *id)
{
	int result = 0;
	struct me4000_info *board_info;

	CALL_PDEBUG("me4000_probe() is executed\n");

	/* Allocate structure for board context */
	board_info = kzalloc(sizeof(struct me4000_info), GFP_KERNEL);
	if (!board_info) {
		printk(KERN_ERR
		       "ME4000:Can't get memory for board info structure\n");
		result = -ENOMEM;
		goto PROBE_ERROR_1;
	}

	/* Add to global linked list */
	list_add_tail(&board_info->list, &me4000_board_info_list);

	/* Get the PCI base registers */
	result = get_registers(dev, board_info);
	if (result) {
		printk(KERN_ERR "%s:Cannot get registers\n", __func__);
		goto PROBE_ERROR_2;
	}

	/* Enable the device */
	result = pci_enable_device(dev);
	if (result < 0) {
		printk(KERN_ERR "%s:Cannot enable PCI device\n", __func__);
		goto PROBE_ERROR_2;
	}

	/* Request the PCI register regions */
	result = pci_request_regions(dev, ME4000_NAME);
	if (result < 0) {
		printk(KERN_ERR "%s:Cannot request I/O regions\n", __func__);
		goto PROBE_ERROR_2;
	}

	/* Initialize board info */
	result = init_board_info(dev, board_info);
	if (result) {
		printk(KERN_ERR "%s:Cannot init baord info\n", __func__);
		goto PROBE_ERROR_3;
	}

	/* Download the xilinx firmware */
	result = me4000_xilinx_download(board_info);
	if (result) {
		printk(KERN_ERR "%s:Can't download firmware\n", __func__);
		goto PROBE_ERROR_3;
	}

	/* Make a hardware reset */
	result = me4000_reset_board(board_info);
	if (result) {
		printk(KERN_ERR "%s :Can't reset board\n", __func__);
		goto PROBE_ERROR_3;
	}

	/* Allocate analog output context structures */
	result = alloc_ao_contexts(board_info);
	if (result) {
		printk(KERN_ERR "%s:Cannot allocate ao contexts\n", __func__);
		goto PROBE_ERROR_3;
	}

	/* Allocate analog input context */
	result = alloc_ai_context(board_info);
	if (result) {
		printk(KERN_ERR "%s:Cannot allocate ai context\n", __func__);
		goto PROBE_ERROR_4;
	}

	/* Allocate digital I/O context */
	result = alloc_dio_context(board_info);
	if (result) {
		printk(KERN_ERR "%s:Cannot allocate dio context\n", __func__);
		goto PROBE_ERROR_5;
	}

	/* Allocate counter context */
	result = alloc_cnt_context(board_info);
	if (result) {
		printk(KERN_ERR "%s:Cannot allocate cnt context\n", __func__);
		goto PROBE_ERROR_6;
	}

	/* Allocate external interrupt context */
	result = alloc_ext_int_context(board_info);
	if (result) {
		printk(KERN_ERR
		       "%s:Cannot allocate ext_int context\n", __func__);
		goto PROBE_ERROR_7;
	}

	return 0;

PROBE_ERROR_7:
	kfree(board_info->cnt_context);

PROBE_ERROR_6:
	kfree(board_info->dio_context);

PROBE_ERROR_5:
	kfree(board_info->ai_context);

PROBE_ERROR_4:
	release_ao_contexts(board_info);

PROBE_ERROR_3:
	pci_release_regions(dev);

PROBE_ERROR_2:
	list_del(&board_info->list);
	kfree(board_info);

PROBE_ERROR_1:
	return result;
}

static int me4000_xilinx_download(struct me4000_info *info)
{
	int size = 0;
	u32 value = 0;
	int idx = 0;
	unsigned char *firm;
	wait_queue_head_t queue;

	CALL_PDEBUG("me4000_xilinx_download() is executed\n");

	init_waitqueue_head(&queue);

	firm = (info->device_id == 0x4610) ? xilinx_firm_4610 : xilinx_firm;

	/*
	 * Set PLX local interrupt 2 polarity to high.
	 * Interrupt is thrown by init pin of xilinx.
	 */
	outl(0x10, info->plx_regbase + PLX_INTCSR);

	/* Set /CS and /WRITE of the Xilinx */
	value = inl(info->plx_regbase + PLX_ICR);
	value |= 0x100;
	outl(value, info->plx_regbase + PLX_ICR);

	/* Init Xilinx with CS1 */
	inb(info->program_regbase + 0xC8);

	/* Wait until /INIT pin is set */
	udelay(20);
	if (!(inl(info->plx_regbase + PLX_INTCSR) & 0x20)) {
		printk(KERN_ERR "%s:Can't init Xilinx\n", __func__);
		return -EIO;
	}

	/* Reset /CS and /WRITE of the Xilinx */
	value = inl(info->plx_regbase + PLX_ICR);
	value &= ~0x100;
	outl(value, info->plx_regbase + PLX_ICR);

	/* Download Xilinx firmware */
	size = (firm[0] << 24) + (firm[1] << 16) + (firm[2] << 8) + firm[3];
	udelay(10);

	for (idx = 0; idx < size; idx++) {
		outb(firm[16 + idx], info->program_regbase);

		udelay(10);

		/* Check if BUSY flag is low */
		if (inl(info->plx_regbase + PLX_ICR) & 0x20) {
			printk(KERN_ERR
			       "%s:Xilinx is still busy (idx = %d)\n", __func__,
			       idx);
			return -EIO;
		}
	}

	PDEBUG("me4000_xilinx_download():%d bytes written\n", idx);

	/* If done flag is high download was successful */
	if (inl(info->plx_regbase + PLX_ICR) & 0x4) {
		PDEBUG("me4000_xilinx_download():Done flag is set\n");
		PDEBUG("me4000_xilinx_download():Download was successful\n");
	} else {
		printk(KERN_ERR
		       "ME4000:%s:DONE flag is not set\n", __func__);
		printk(KERN_ERR
		       "ME4000:%s:Download not succesful\n", __func__);
		return -EIO;
	}

	/* Set /CS and /WRITE */
	value = inl(info->plx_regbase + PLX_ICR);
	value |= 0x100;
	outl(value, info->plx_regbase + PLX_ICR);

	return 0;
}

static int me4000_reset_board(struct me4000_info *info)
{
	unsigned long icr;

	CALL_PDEBUG("me4000_reset_board() is executed\n");

	/* Make a hardware reset */
	icr = me4000_inl(info->plx_regbase + PLX_ICR);
	icr |= 0x40000000;
	me4000_outl(icr, info->plx_regbase + PLX_ICR);
	icr &= ~0x40000000;
	me4000_outl(icr, info->plx_regbase + PLX_ICR);

	/* Set both stop bits in the analog input control register */
	me4000_outl(ME4000_AI_CTRL_BIT_IMMEDIATE_STOP | ME4000_AI_CTRL_BIT_STOP,
		    info->me4000_regbase + ME4000_AI_CTRL_REG);

	/* Set both stop bits in the analog output control register */
	me4000_outl(ME4000_AO_CTRL_BIT_IMMEDIATE_STOP | ME4000_AO_CTRL_BIT_STOP,
		    info->me4000_regbase + ME4000_AO_00_CTRL_REG);
	me4000_outl(ME4000_AO_CTRL_BIT_IMMEDIATE_STOP | ME4000_AO_CTRL_BIT_STOP,
		    info->me4000_regbase + ME4000_AO_01_CTRL_REG);
	me4000_outl(ME4000_AO_CTRL_BIT_IMMEDIATE_STOP | ME4000_AO_CTRL_BIT_STOP,
		    info->me4000_regbase + ME4000_AO_02_CTRL_REG);
	me4000_outl(ME4000_AO_CTRL_BIT_IMMEDIATE_STOP | ME4000_AO_CTRL_BIT_STOP,
		    info->me4000_regbase + ME4000_AO_03_CTRL_REG);

	/* 0x8000 to the DACs means an output voltage of 0V */
	me4000_outl(0x8000, info->me4000_regbase + ME4000_AO_00_SINGLE_REG);
	me4000_outl(0x8000, info->me4000_regbase + ME4000_AO_01_SINGLE_REG);
	me4000_outl(0x8000, info->me4000_regbase + ME4000_AO_02_SINGLE_REG);
	me4000_outl(0x8000, info->me4000_regbase + ME4000_AO_03_SINGLE_REG);

	/* Enable interrupts on the PLX */
	me4000_outl(0x43, info->plx_regbase + PLX_INTCSR);

	/* Set the adustment register for AO demux */
	me4000_outl(ME4000_AO_DEMUX_ADJUST_VALUE,
		    info->me4000_regbase + ME4000_AO_DEMUX_ADJUST_REG);

	/* Set digital I/O direction for port 0 to output on isolated versions */
	if (!(me4000_inl(info->me4000_regbase + ME4000_DIO_DIR_REG) & 0x1))
		me4000_outl(0x1, info->me4000_regbase + ME4000_DIO_CTRL_REG);

	return 0;
}

static int me4000_open(struct inode *inode_p, struct file *file_p)
{
	int board, dev, mode;
	int err = 0;
	int i;
	struct list_head *ptr;
	struct me4000_info *board_info = NULL;
	struct me4000_ao_context *ao_context = NULL;
	struct me4000_ai_context *ai_context = NULL;
	struct me4000_dio_context *dio_context = NULL;
	struct me4000_cnt_context *cnt_context = NULL;
	struct me4000_ext_int_context *ext_int_context = NULL;

	CALL_PDEBUG("me4000_open() is executed\n");

	/* Analog output */
	if (MAJOR(inode_p->i_rdev) == me4000_ao_major_driver_no) {
		board = AO_BOARD(inode_p->i_rdev);
		dev = AO_PORT(inode_p->i_rdev);
		mode = AO_MODE(inode_p->i_rdev);

		PDEBUG("me4000_open():board = %d ao = %d mode = %d\n", board,
		       dev, mode);

		/* Search for the board context */
		i = 0;
		list_for_each(ptr, &me4000_board_info_list) {
			if (i == board)
				break;
			i++;
		}
		board_info = list_entry(ptr, struct me4000_info, list);

		if (ptr == &me4000_board_info_list) {
			printk(KERN_ERR
			       "ME4000:me4000_open():Board %d not in device list\n",
			       board);
			return -ENODEV;
		}

		/* Search for the dac context */
		i = 0;
		list_for_each(ptr, &board_info->ao_context_list) {
			if (i == dev)
				break;
			i++;
		}
		ao_context = list_entry(ptr, struct me4000_ao_context, list);

		if (ptr == &board_info->ao_context_list) {
			printk(KERN_ERR
			       "ME4000:me4000_open():Device %d not in device list\n",
			       dev);
			return -ENODEV;
		}

		/* Check if mode is valid */
		if (mode > 2) {
			printk(KERN_ERR
			       "ME4000:me4000_open():Mode is not valid\n");
			return -ENODEV;
		}

		/* Check if mode is valid for this AO */
		if ((mode != ME4000_AO_CONV_MODE_SINGLE)
		    && (dev >= board_info->board_p->ao.fifo_count)) {
			printk(KERN_ERR
			       "ME4000:me4000_open():AO %d only in single mode available\n",
			       dev);
			return -ENODEV;
		}

		/* Check if already opened */
		spin_lock(&ao_context->use_lock);
		if (ao_context->dac_in_use) {
			printk(KERN_ERR
			       "ME4000:me4000_open():AO %d already in use\n",
			       dev);
			spin_unlock(&ao_context->use_lock);
			return -EBUSY;
		}
		ao_context->dac_in_use = 1;
		spin_unlock(&ao_context->use_lock);

		ao_context->mode = mode;

		/* Hold the context in private data */
		file_p->private_data = ao_context;

		/* Set file operations pointer */
		file_p->f_op = me4000_ao_fops_array[mode];

		err = me4000_ao_prepare(ao_context);
		if (err) {
			ao_context->dac_in_use = 0;
			return 1;
		}
	}
	/* Analog input */
	else if (MAJOR(inode_p->i_rdev) == me4000_ai_major_driver_no) {
		board = AI_BOARD(inode_p->i_rdev);
		mode = AI_MODE(inode_p->i_rdev);

		PDEBUG("me4000_open():ai board = %d mode = %d\n", board, mode);

		/* Search for the board context */
		i = 0;
		list_for_each(ptr, &me4000_board_info_list) {
			if (i == board)
				break;
			i++;
		}
		board_info = list_entry(ptr, struct me4000_info, list);

		if (ptr == &me4000_board_info_list) {
			printk(KERN_ERR
			       "ME4000:me4000_open():Board %d not in device list\n",
			       board);
			return -ENODEV;
		}

		ai_context = board_info->ai_context;

		/* Check if mode is valid */
		if (mode > 5) {
			printk(KERN_ERR
			       "ME4000:me4000_open():Mode is not valid\n");
			return -EINVAL;
		}

		/* Check if already opened */
		spin_lock(&ai_context->use_lock);
		if (ai_context->in_use) {
			printk(KERN_ERR
			       "ME4000:me4000_open():AI already in use\n");
			spin_unlock(&ai_context->use_lock);
			return -EBUSY;
		}
		ai_context->in_use = 1;
		spin_unlock(&ai_context->use_lock);

		ai_context->mode = mode;

		/* Hold the context in private data */
		file_p->private_data = ai_context;

		/* Set file operations pointer */
		file_p->f_op = me4000_ai_fops_array[mode];

		/* Prepare analog input */
		me4000_ai_prepare(ai_context);
	}
	/* Digital I/O */
	else if (MAJOR(inode_p->i_rdev) == me4000_dio_major_driver_no) {
		board = DIO_BOARD(inode_p->i_rdev);
		dev = 0;
		mode = 0;

		PDEBUG("me4000_open():board = %d\n", board);

		/* Search for the board context */
		list_for_each_entry(board_info, &me4000_board_info_list, list) {
			if (board_info->board_count == board)
				break;
		}

		if (&board_info->list == &me4000_board_info_list) {
			printk(KERN_ERR
			       "ME4000:me4000_open():Board %d not in device list\n",
			       board);
			return -ENODEV;
		}

		/* Search for the dio context */
		dio_context = board_info->dio_context;

		/* Check if already opened */
		spin_lock(&dio_context->use_lock);
		if (dio_context->in_use) {
			printk(KERN_ERR
			       "ME4000:me4000_open():DIO already in use\n");
			spin_unlock(&dio_context->use_lock);
			return -EBUSY;
		}
		dio_context->in_use = 1;
		spin_unlock(&dio_context->use_lock);

		/* Hold the context in private data */
		file_p->private_data = dio_context;

		/* Set file operations pointer to single functions */
		file_p->f_op = &me4000_dio_fops;

		/* me4000_dio_reset(dio_context); */
	}
	/* Counters */
	else if (MAJOR(inode_p->i_rdev) == me4000_cnt_major_driver_no) {
		board = CNT_BOARD(inode_p->i_rdev);
		dev = 0;
		mode = 0;

		PDEBUG("me4000_open():board = %d\n", board);

		/* Search for the board context */
		list_for_each_entry(board_info, &me4000_board_info_list, list) {
			if (board_info->board_count == board)
				break;
		}

		if (&board_info->list == &me4000_board_info_list) {
			printk(KERN_ERR
			       "ME4000:me4000_open():Board %d not in device list\n",
			       board);
			return -ENODEV;
		}

		/* Get the cnt context */
		cnt_context = board_info->cnt_context;

		/* Check if already opened */
		spin_lock(&cnt_context->use_lock);
		if (cnt_context->in_use) {
			printk(KERN_ERR
			       "ME4000:me4000_open():CNT already in use\n");
			spin_unlock(&cnt_context->use_lock);
			return -EBUSY;
		}
		cnt_context->in_use = 1;
		spin_unlock(&cnt_context->use_lock);

		/* Hold the context in private data */
		file_p->private_data = cnt_context;

		/* Set file operations pointer to single functions */
		file_p->f_op = &me4000_cnt_fops;
	}
	/* External Interrupt */
	else if (MAJOR(inode_p->i_rdev) == me4000_ext_int_major_driver_no) {
		board = EXT_INT_BOARD(inode_p->i_rdev);
		dev = 0;
		mode = 0;

		PDEBUG("me4000_open():board = %d\n", board);

		/* Search for the board context */
		list_for_each_entry(board_info, &me4000_board_info_list, list) {
			if (board_info->board_count == board)
				break;
		}

		if (&board_info->list == &me4000_board_info_list) {
			printk(KERN_ERR
			       "ME4000:me4000_open():Board %d not in device list\n",
			       board);
			return -ENODEV;
		}

		/* Get the external interrupt context */
		ext_int_context = board_info->ext_int_context;

		/* Check if already opened */
		spin_lock(&cnt_context->use_lock);
		if (ext_int_context->in_use) {
			printk(KERN_ERR
			       "ME4000:me4000_open():External interrupt already in use\n");
			spin_unlock(&ext_int_context->use_lock);
			return -EBUSY;
		}
		ext_int_context->in_use = 1;
		spin_unlock(&ext_int_context->use_lock);

		/* Hold the context in private data */
		file_p->private_data = ext_int_context;

		/* Set file operations pointer to single functions */
		file_p->f_op = &me4000_ext_int_fops;

		/* Request the interrupt line */
		err =
		    request_irq(ext_int_context->irq, me4000_ext_int_isr,
				IRQF_DISABLED | IRQF_SHARED, ME4000_NAME,
				ext_int_context);
		if (err) {
			printk(KERN_ERR
			       "ME4000:me4000_open():Can't get interrupt line");
			ext_int_context->in_use = 0;
			return -ENODEV;
		}

		/* Reset the counter */
		me4000_ext_int_disable(ext_int_context);
	} else {
		printk(KERN_ERR "ME4000:me4000_open():Major number unknown\n");
		return -EINVAL;
	}

	return 0;
}

static int me4000_release(struct inode *inode_p, struct file *file_p)
{
	struct me4000_ao_context *ao_context;
	struct me4000_ai_context *ai_context;
	struct me4000_dio_context *dio_context;
	struct me4000_cnt_context *cnt_context;
	struct me4000_ext_int_context *ext_int_context;

	CALL_PDEBUG("me4000_release() is executed\n");

	if (MAJOR(inode_p->i_rdev) == me4000_ao_major_driver_no) {
		ao_context = file_p->private_data;

		/* Mark DAC as unused */
		ao_context->dac_in_use = 0;
	} else if (MAJOR(inode_p->i_rdev) == me4000_ai_major_driver_no) {
		ai_context = file_p->private_data;

		/* Reset the analog input */
		me4000_ai_reset(ai_context);

		/* Free the interrupt and the circular buffer */
		if (ai_context->mode) {
			free_irq(ai_context->irq, ai_context);
			kfree(ai_context->circ_buf.buf);
			ai_context->circ_buf.buf = NULL;
			ai_context->circ_buf.head = 0;
			ai_context->circ_buf.tail = 0;
		}

		/* Mark AI as unused */
		ai_context->in_use = 0;
	} else if (MAJOR(inode_p->i_rdev) == me4000_dio_major_driver_no) {
		dio_context = file_p->private_data;

		/* Mark digital I/O as unused */
		dio_context->in_use = 0;
	} else if (MAJOR(inode_p->i_rdev) == me4000_cnt_major_driver_no) {
		cnt_context = file_p->private_data;

		/* Mark counters as unused */
		cnt_context->in_use = 0;
	} else if (MAJOR(inode_p->i_rdev) == me4000_ext_int_major_driver_no) {
		ext_int_context = file_p->private_data;

		/* Disable the externel interrupt */
		me4000_ext_int_disable(ext_int_context);

		free_irq(ext_int_context->irq, ext_int_context);

		/* Mark as unused */
		ext_int_context->in_use = 0;
	} else {
		printk(KERN_ERR
		       "ME4000:me4000_release():Major number unknown\n");
		return -EINVAL;
	}

	return 0;
}

/*------------------------------- Analog output stuff --------------------------------------*/

static int me4000_ao_prepare(struct me4000_ao_context *ao_context)
{
	unsigned long flags;

	CALL_PDEBUG("me4000_ao_prepare() is executed\n");

	if (ao_context->mode == ME4000_AO_CONV_MODE_CONTINUOUS) {
		/* Only do anything if not already in the correct mode */
		unsigned long mode = me4000_inl(ao_context->ctrl_reg);
		if ((mode & ME4000_AO_CONV_MODE_CONTINUOUS)
		    && (mode & ME4000_AO_CTRL_BIT_ENABLE_FIFO)) {
			return 0;
		}

		/* Stop any conversion */
		me4000_ao_immediate_stop(ao_context);

		/* Set the control register to default state  */
		spin_lock_irqsave(&ao_context->int_lock, flags);
		me4000_outl(ME4000_AO_CONV_MODE_CONTINUOUS |
			    ME4000_AO_CTRL_BIT_ENABLE_FIFO |
			    ME4000_AO_CTRL_BIT_STOP |
			    ME4000_AO_CTRL_BIT_IMMEDIATE_STOP,
			    ao_context->ctrl_reg);
		spin_unlock_irqrestore(&ao_context->int_lock, flags);

		/* Set to fastest sample rate */
		me4000_outl(65, ao_context->timer_reg);
	} else if (ao_context->mode == ME4000_AO_CONV_MODE_WRAPAROUND) {
		/* Only do anything if not already in the correct mode */
		unsigned long mode = me4000_inl(ao_context->ctrl_reg);
		if ((mode & ME4000_AO_CONV_MODE_WRAPAROUND)
		    && (mode & ME4000_AO_CTRL_BIT_ENABLE_FIFO)) {
			return 0;
		}

		/* Stop any conversion */
		me4000_ao_immediate_stop(ao_context);

		/* Set the control register to default state  */
		spin_lock_irqsave(&ao_context->int_lock, flags);
		me4000_outl(ME4000_AO_CONV_MODE_WRAPAROUND |
			    ME4000_AO_CTRL_BIT_ENABLE_FIFO |
			    ME4000_AO_CTRL_BIT_STOP |
			    ME4000_AO_CTRL_BIT_IMMEDIATE_STOP,
			    ao_context->ctrl_reg);
		spin_unlock_irqrestore(&ao_context->int_lock, flags);

		/* Set to fastest sample rate */
		me4000_outl(65, ao_context->timer_reg);
	} else if (ao_context->mode == ME4000_AO_CONV_MODE_SINGLE) {
		/* Only do anything if not already in the correct mode */
		unsigned long mode = me4000_inl(ao_context->ctrl_reg);
		if (!
		    (mode &
		     (ME4000_AO_CONV_MODE_WRAPAROUND |
		      ME4000_AO_CONV_MODE_CONTINUOUS))) {
			return 0;
		}

		/* Stop any conversion */
		me4000_ao_immediate_stop(ao_context);

		/* Clear the control register */
		spin_lock_irqsave(&ao_context->int_lock, flags);
		me4000_outl(0x0, ao_context->ctrl_reg);
		spin_unlock_irqrestore(&ao_context->int_lock, flags);

		/* Set voltage to 0V */
		me4000_outl(0x8000, ao_context->single_reg);
	} else {
		printk(KERN_ERR
		       "ME4000:me4000_ao_prepare():Invalid mode specified\n");
		return -EINVAL;
	}

	return 0;
}

static int me4000_ao_reset(struct me4000_ao_context *ao_context)
{
	u32 tmp;
	wait_queue_head_t queue;
	unsigned long flags;

	CALL_PDEBUG("me4000_ao_reset() is executed\n");

	init_waitqueue_head(&queue);

	if (ao_context->mode == ME4000_AO_CONV_MODE_WRAPAROUND) {
		/*
		 * First stop conversion of the DAC before reconfigure.
		 * This is essantial, cause of the state machine.
		 * If not stopped before configuring mode, it could
		 * walk in a undefined state.
		 */
		tmp = me4000_inl(ao_context->ctrl_reg);
		tmp |= ME4000_AO_CTRL_BIT_IMMEDIATE_STOP;
		me4000_outl(tmp, ao_context->ctrl_reg);

		wait_event_timeout(queue,
			(inl(ao_context->status_reg) &
				ME4000_AO_STATUS_BIT_FSM) == 0,
			1);

		/* Set to transparent mode */
		me4000_ao_simultaneous_disable(ao_context);

		/* Set to single mode in order to set default voltage */
		me4000_outl(0x0, ao_context->ctrl_reg);

		/* Set voltage to 0V */
		me4000_outl(0x8000, ao_context->single_reg);

		/* Set to fastest sample rate */
		me4000_outl(65, ao_context->timer_reg);

		/* Set the original mode and enable FIFO */
		me4000_outl(ME4000_AO_CONV_MODE_WRAPAROUND |
			    ME4000_AO_CTRL_BIT_ENABLE_FIFO |
			    ME4000_AO_CTRL_BIT_STOP |
			    ME4000_AO_CTRL_BIT_IMMEDIATE_STOP,
			    ao_context->ctrl_reg);
	} else if (ao_context->mode == ME4000_AO_CONV_MODE_CONTINUOUS) {
		/*
		 * First stop conversion of the DAC before reconfigure.
		 * This is essantial, cause of the state machine.
		 * If not stopped before configuring mode, it could
		 * walk in a undefined state.
		 */
		spin_lock_irqsave(&ao_context->int_lock, flags);
		tmp = me4000_inl(ao_context->ctrl_reg);
		tmp |= ME4000_AO_CTRL_BIT_STOP;
		me4000_outl(tmp, ao_context->ctrl_reg);
		spin_unlock_irqrestore(&ao_context->int_lock, flags);

		wait_event_timeout(queue,
			(inl(ao_context->status_reg) &
				ME4000_AO_STATUS_BIT_FSM) == 0,
			1);

		/* Clear the circular buffer */
		ao_context->circ_buf.head = 0;
		ao_context->circ_buf.tail = 0;

		/* Set to transparent mode */
		me4000_ao_simultaneous_disable(ao_context);

		/* Set to single mode in order to set default voltage */
		spin_lock_irqsave(&ao_context->int_lock, flags);
		tmp = me4000_inl(ao_context->ctrl_reg);
		me4000_outl(0x0, ao_context->ctrl_reg);

		/* Set voltage to 0V */
		me4000_outl(0x8000, ao_context->single_reg);

		/* Set to fastest sample rate */
		me4000_outl(65, ao_context->timer_reg);

		/* Set the original mode and enable FIFO */
		me4000_outl(ME4000_AO_CONV_MODE_CONTINUOUS |
			    ME4000_AO_CTRL_BIT_ENABLE_FIFO |
			    ME4000_AO_CTRL_BIT_STOP |
			    ME4000_AO_CTRL_BIT_IMMEDIATE_STOP,
			    ao_context->ctrl_reg);
		spin_unlock_irqrestore(&ao_context->int_lock, flags);
	} else {
		/* Set to transparent mode */
		me4000_ao_simultaneous_disable(ao_context);

		/* Set voltage to 0V */
		me4000_outl(0x8000, ao_context->single_reg);
	}

	return 0;
}

static ssize_t me4000_ao_write_sing(struct file *filep, const char *buff,
				    size_t cnt, loff_t *offp)
{
	struct me4000_ao_context *ao_context = filep->private_data;
	u32 value;
	const u16 *buffer = (const u16 *)buff;

	CALL_PDEBUG("me4000_ao_write_sing() is executed\n");

	if (cnt != 2) {
		printk(KERN_ERR
		       "%s:Write count is not 2\n", __func__);
		return -EINVAL;
	}

	if (get_user(value, buffer)) {
		printk(KERN_ERR
		       "%s:Cannot copy data from user\n", __func__);
		return -EFAULT;
	}

	me4000_outl(value, ao_context->single_reg);

	return 2;
}

static ssize_t me4000_ao_write_wrap(struct file *filep, const char *buff,
				    size_t cnt, loff_t *offp)
{
	struct me4000_ao_context *ao_context = filep->private_data;
	size_t i;
	u32 value;
	u32 tmp;
	const u16 *buffer = (const u16 *)buff;
	size_t count = cnt / 2;

	CALL_PDEBUG("me4000_ao_write_wrap() is executed\n");

	/* Check if a conversion is already running */
	if (inl(ao_context->status_reg) & ME4000_AO_STATUS_BIT_FSM) {
		printk(KERN_ERR
		       "%s:There is already a conversion running\n", __func__);
		return -EBUSY;
	}

	if (count > ME4000_AO_FIFO_COUNT) {
		printk(KERN_ERR
		       "%s:Can't load more than %d values\n", __func__,
		       ME4000_AO_FIFO_COUNT);
		return -ENOSPC;
	}

	/* Reset the FIFO */
	tmp = inl(ao_context->ctrl_reg);
	tmp &= ~ME4000_AO_CTRL_BIT_ENABLE_FIFO;
	outl(tmp, ao_context->ctrl_reg);
	tmp |= ME4000_AO_CTRL_BIT_ENABLE_FIFO;
	outl(tmp, ao_context->ctrl_reg);

	for (i = 0; i < count; i++) {
		if (get_user(value, buffer + i)) {
			printk(KERN_ERR
			       "%s:Cannot copy data from user\n", __func__);
			return -EFAULT;
		}
		if (((ao_context->fifo_reg & 0xFF) == ME4000_AO_01_FIFO_REG)
		    || ((ao_context->fifo_reg & 0xFF) == ME4000_AO_03_FIFO_REG))
			value = value << 16;
		outl(value, ao_context->fifo_reg);
	}
	CALL_PDEBUG("me4000_ao_write_wrap() is leaved with %d\n", i * 2);

	return i * 2;
}

static ssize_t me4000_ao_write_cont(struct file *filep, const char *buff,
				    size_t cnt, loff_t *offp)
{
	struct me4000_ao_context *ao_context = filep->private_data;
	const u16 *buffer = (const u16 *)buff;
	size_t count = cnt / 2;
	unsigned long flags;
	u32 tmp;
	int c = 0;
	int k = 0;
	int ret = 0;
	u16 svalue;
	u32 lvalue;
	int i;
	wait_queue_head_t queue;

	CALL_PDEBUG("me4000_ao_write_cont() is executed\n");

	init_waitqueue_head(&queue);

	/* Check count */
	if (count <= 0) {
		PDEBUG("me4000_ao_write_cont():Count is 0\n");
		return 0;
	}

	if (filep->f_flags & O_APPEND) {
		PDEBUG("me4000_ao_write_cont():Append data to data stream\n");
		while (count > 0) {
			if (filep->f_flags & O_NONBLOCK) {
				if (ao_context->pipe_flag) {
					printk(KERN_ERR
					       "ME4000:me4000_ao_write_cont():Broken pipe in nonblocking write\n");
					return -EPIPE;
				}
				c = me4000_space_to_end(ao_context->circ_buf,
							ME4000_AO_BUFFER_COUNT);
				if (!c) {
					PDEBUG
					    ("me4000_ao_write_cont():Returning from nonblocking write\n");
					break;
				}
			} else {
				wait_event_interruptible(ao_context->wait_queue,
							 (c =
							  me4000_space_to_end
							  (ao_context->circ_buf,
							   ME4000_AO_BUFFER_COUNT)));
				if (ao_context->pipe_flag) {
					printk(KERN_ERR
					       "me4000_ao_write_cont():Broken pipe in blocking write\n");
					return -EPIPE;
				}
				if (signal_pending(current)) {
					printk(KERN_ERR
					       "me4000_ao_write_cont():Wait for free buffer interrupted from signal\n");
					return -EINTR;
				}
			}

			PDEBUG("me4000_ao_write_cont():Space to end = %d\n", c);

			/* Only able to write size of free buffer or size of count */
			if (count < c)
				c = count;

			k = 2 * c;
			k -= copy_from_user(ao_context->circ_buf.buf +
					    ao_context->circ_buf.head, buffer,
					    k);
			c = k / 2;
			PDEBUG
			    ("me4000_ao_write_cont():Copy %d values from user space\n",
			     c);

			if (!c)
				return -EFAULT;

			ao_context->circ_buf.head =
			    (ao_context->circ_buf.head +
			     c) & (ME4000_AO_BUFFER_COUNT - 1);
			buffer += c;
			count -= c;
			ret += c;

			/* Values are now available so enable interrupts */
			spin_lock_irqsave(&ao_context->int_lock, flags);
			if (me4000_buf_count
			    (ao_context->circ_buf, ME4000_AO_BUFFER_COUNT)) {
				tmp = me4000_inl(ao_context->ctrl_reg);
				tmp |= ME4000_AO_CTRL_BIT_ENABLE_IRQ;
				me4000_outl(tmp, ao_context->ctrl_reg);
			}
			spin_unlock_irqrestore(&ao_context->int_lock, flags);
		}

		/* Wait until the state machine is stopped if O_SYNC is set */
		if (filep->f_flags & O_SYNC) {
			while (inl(ao_context->status_reg) &
			       ME4000_AO_STATUS_BIT_FSM) {
				interruptible_sleep_on_timeout(&queue, 1);
				if (ao_context->pipe_flag) {
					PDEBUG
					    ("me4000_ao_write_cont():Broken pipe detected after sync\n");
					return -EPIPE;
				}
				if (signal_pending(current)) {
					printk(KERN_ERR
					       "me4000_ao_write_cont():Wait on state machine after sync interrupted\n");
					return -EINTR;
				}
			}
		}
	} else {
		PDEBUG("me4000_ao_write_cont():Preload DAC FIFO\n");
		if ((me4000_inl(ao_context->status_reg) &
		     ME4000_AO_STATUS_BIT_FSM)) {
			printk(KERN_ERR
			       "me4000_ao_write_cont():Can't Preload DAC FIFO while conversion is running\n");
			return -EBUSY;
		}

		/* Clear the FIFO */
		spin_lock_irqsave(&ao_context->int_lock, flags);
		tmp = me4000_inl(ao_context->ctrl_reg);
		tmp &=
		    ~(ME4000_AO_CTRL_BIT_ENABLE_FIFO |
		      ME4000_AO_CTRL_BIT_ENABLE_IRQ);
		me4000_outl(tmp, ao_context->ctrl_reg);
		tmp |= ME4000_AO_CTRL_BIT_ENABLE_FIFO;
		me4000_outl(tmp, ao_context->ctrl_reg);
		spin_unlock_irqrestore(&ao_context->int_lock, flags);

		/* Clear the circular buffer */
		ao_context->circ_buf.head = 0;
		ao_context->circ_buf.tail = 0;

		/* Reset the broken pipe flag */
		ao_context->pipe_flag = 0;

		/* Only able to write size of fifo or count */
		c = ME4000_AO_FIFO_COUNT;
		if (count < c)
			c = count;

		PDEBUG
		    ("me4000_ao_write_cont():Write %d values to DAC on 0x%lX\n",
		     c, ao_context->fifo_reg);

		/* Write values to the fifo */
		for (i = 0; i < c; i++) {
			if (get_user(svalue, buffer))
				return -EFAULT;

			if (((ao_context->fifo_reg & 0xFF) ==
			     ME4000_AO_01_FIFO_REG)
			    || ((ao_context->fifo_reg & 0xFF) ==
				ME4000_AO_03_FIFO_REG)) {
				lvalue = ((u32) svalue) << 16;
			} else
				lvalue = (u32) svalue;

			outl(lvalue, ao_context->fifo_reg);
			buffer++;
		}
		count -= c;
		ret += c;

		while (1) {
			/* Get free buffer */
			c = me4000_space_to_end(ao_context->circ_buf,
						ME4000_AO_BUFFER_COUNT);

			if (c == 0)
				return 2 * ret;

			/* Only able to write size of free buffer or size of count */
			if (count < c)
				c = count;

			/* If count = 0 return to user */
			if (c <= 0) {
				PDEBUG
				    ("me4000_ao_write_cont():Count reached 0\n");
				break;
			}

			k = 2 * c;
			k -= copy_from_user(ao_context->circ_buf.buf +
					    ao_context->circ_buf.head, buffer,
					    k);
			c = k / 2;
			PDEBUG
			    ("me4000_ao_write_cont():Wrote %d values to buffer\n",
			     c);

			if (!c)
				return -EFAULT;

			ao_context->circ_buf.head =
			    (ao_context->circ_buf.head +
			     c) & (ME4000_AO_BUFFER_COUNT - 1);
			buffer += c;
			count -= c;
			ret += c;

			/* If values in the buffer are available so enable interrupts */
			spin_lock_irqsave(&ao_context->int_lock, flags);
			if (me4000_buf_count
			    (ao_context->circ_buf, ME4000_AO_BUFFER_COUNT)) {
				PDEBUG
				    ("me4000_ao_write_cont():Enable Interrupts\n");
				tmp = me4000_inl(ao_context->ctrl_reg);
				tmp |= ME4000_AO_CTRL_BIT_ENABLE_IRQ;
				me4000_outl(tmp, ao_context->ctrl_reg);
			}
			spin_unlock_irqrestore(&ao_context->int_lock, flags);
		}
	}

	if (filep->f_flags & O_NONBLOCK)
		return (ret == 0) ? -EAGAIN : 2 * ret;

	return 2 * ret;
}

static unsigned int me4000_ao_poll_cont(struct file *file_p, poll_table *wait)
{
	struct me4000_ao_context *ao_context;
	unsigned long mask = 0;

	CALL_PDEBUG("me4000_ao_poll_cont() is executed\n");

	ao_context = file_p->private_data;

	poll_wait(file_p, &ao_context->wait_queue, wait);

	/* Get free buffer */
	if (me4000_space_to_end(ao_context->circ_buf, ME4000_AO_BUFFER_COUNT))
		mask |= POLLOUT | POLLWRNORM;

	CALL_PDEBUG("me4000_ao_poll_cont():Return mask %lX\n", mask);

	return mask;
}

static int me4000_ao_fsync_cont(struct file *file_p, struct dentry *dentry_p,
				int datasync)
{
	struct me4000_ao_context *ao_context;
	wait_queue_head_t queue;

	CALL_PDEBUG("me4000_ao_fsync_cont() is executed\n");

	ao_context = file_p->private_data;
	init_waitqueue_head(&queue);

	while (inl(ao_context->status_reg) & ME4000_AO_STATUS_BIT_FSM) {
		interruptible_sleep_on_timeout(&queue, 1);
			wait_event_interruptible_timeout(queue,
			!(inl(ao_context->status_reg) & ME4000_AO_STATUS_BIT_FSM),
			1);
		if (ao_context->pipe_flag) {
			printk(KERN_ERR
			       "%s:Broken pipe detected\n", __func__);
			return -EPIPE;
		}

		if (signal_pending(current)) {
			printk(KERN_ERR
			       "%s:Wait on state machine interrupted\n",
			       __func__);
			return -EINTR;
		}
	}

	return 0;
}

static int me4000_ao_ioctl_sing(struct inode *inode_p, struct file *file_p,
				unsigned int service, unsigned long arg)
{
	struct me4000_ao_context *ao_context;

	CALL_PDEBUG("me4000_ao_ioctl_sing() is executed\n");

	ao_context = file_p->private_data;

	if (_IOC_TYPE(service) != ME4000_MAGIC) {
		return -ENOTTY;
		PDEBUG("me4000_ao_ioctl_sing():Wrong magic number\n");
	}

	switch (service) {
	case ME4000_AO_EX_TRIG_SETUP:
		return me4000_ao_ex_trig_set_edge((int *)arg, ao_context);
	case ME4000_AO_EX_TRIG_ENABLE:
		return me4000_ao_ex_trig_enable(ao_context);
	case ME4000_AO_EX_TRIG_DISABLE:
		return me4000_ao_ex_trig_disable(ao_context);
	case ME4000_AO_PRELOAD:
		return me4000_ao_preload(ao_context);
	case ME4000_AO_PRELOAD_UPDATE:
		return me4000_ao_preload_update(ao_context);
	case ME4000_GET_USER_INFO:
		return me4000_get_user_info((struct me4000_user_info *)arg,
					    ao_context->board_info);
	case ME4000_AO_SIMULTANEOUS_EX_TRIG:
		return me4000_ao_simultaneous_ex_trig(ao_context);
	case ME4000_AO_SIMULTANEOUS_SW:
		return me4000_ao_simultaneous_sw(ao_context);
	case ME4000_AO_SIMULTANEOUS_DISABLE:
		return me4000_ao_simultaneous_disable(ao_context);
	case ME4000_AO_SIMULTANEOUS_UPDATE:
		return
		    me4000_ao_simultaneous_update(
				(struct me4000_ao_channel_list *)arg,
				ao_context);
	case ME4000_AO_EX_TRIG_TIMEOUT:
		return me4000_ao_ex_trig_timeout((unsigned long *)arg,
						 ao_context);
	case ME4000_AO_DISABLE_DO:
		return me4000_ao_disable_do(ao_context);
	default:
		printk(KERN_ERR
		       "me4000_ao_ioctl_sing():Service number invalid\n");
		return -ENOTTY;
	}

	return 0;
}

static int me4000_ao_ioctl_wrap(struct inode *inode_p, struct file *file_p,
				unsigned int service, unsigned long arg)
{
	struct me4000_ao_context *ao_context;

	CALL_PDEBUG("me4000_ao_ioctl_wrap() is executed\n");

	ao_context = file_p->private_data;

	if (_IOC_TYPE(service) != ME4000_MAGIC) {
		return -ENOTTY;
		PDEBUG("me4000_ao_ioctl_wrap():Wrong magic number\n");
	}

	switch (service) {
	case ME4000_AO_START:
		return me4000_ao_start((unsigned long *)arg, ao_context);
	case ME4000_AO_STOP:
		return me4000_ao_stop(ao_context);
	case ME4000_AO_IMMEDIATE_STOP:
		return me4000_ao_immediate_stop(ao_context);
	case ME4000_AO_RESET:
		return me4000_ao_reset(ao_context);
	case ME4000_AO_TIMER_SET_DIVISOR:
		return me4000_ao_timer_set_divisor((u32 *) arg, ao_context);
	case ME4000_AO_EX_TRIG_SETUP:
		return me4000_ao_ex_trig_set_edge((int *)arg, ao_context);
	case ME4000_AO_EX_TRIG_ENABLE:
		return me4000_ao_ex_trig_enable(ao_context);
	case ME4000_AO_EX_TRIG_DISABLE:
		return me4000_ao_ex_trig_disable(ao_context);
	case ME4000_GET_USER_INFO:
		return me4000_get_user_info((struct me4000_user_info *)arg,
					    ao_context->board_info);
	case ME4000_AO_FSM_STATE:
		return me4000_ao_fsm_state((int *)arg, ao_context);
	case ME4000_AO_ENABLE_DO:
		return me4000_ao_enable_do(ao_context);
	case ME4000_AO_DISABLE_DO:
		return me4000_ao_disable_do(ao_context);
	case ME4000_AO_SYNCHRONOUS_EX_TRIG:
		return me4000_ao_synchronous_ex_trig(ao_context);
	case ME4000_AO_SYNCHRONOUS_SW:
		return me4000_ao_synchronous_sw(ao_context);
	case ME4000_AO_SYNCHRONOUS_DISABLE:
		return me4000_ao_synchronous_disable(ao_context);
	default:
		return -ENOTTY;
	}
	return 0;
}

static int me4000_ao_ioctl_cont(struct inode *inode_p, struct file *file_p,
				unsigned int service, unsigned long arg)
{
	struct me4000_ao_context *ao_context;

	CALL_PDEBUG("me4000_ao_ioctl_cont() is executed\n");

	ao_context = file_p->private_data;

	if (_IOC_TYPE(service) != ME4000_MAGIC) {
		return -ENOTTY;
		PDEBUG("me4000_ao_ioctl_cont():Wrong magic number\n");
	}

	switch (service) {
	case ME4000_AO_START:
		return me4000_ao_start((unsigned long *)arg, ao_context);
	case ME4000_AO_STOP:
		return me4000_ao_stop(ao_context);
	case ME4000_AO_IMMEDIATE_STOP:
		return me4000_ao_immediate_stop(ao_context);
	case ME4000_AO_RESET:
		return me4000_ao_reset(ao_context);
	case ME4000_AO_TIMER_SET_DIVISOR:
		return me4000_ao_timer_set_divisor((u32 *) arg, ao_context);
	case ME4000_AO_EX_TRIG_SETUP:
		return me4000_ao_ex_trig_set_edge((int *)arg, ao_context);
	case ME4000_AO_EX_TRIG_ENABLE:
		return me4000_ao_ex_trig_enable(ao_context);
	case ME4000_AO_EX_TRIG_DISABLE:
		return me4000_ao_ex_trig_disable(ao_context);
	case ME4000_AO_ENABLE_DO:
		return me4000_ao_enable_do(ao_context);
	case ME4000_AO_DISABLE_DO:
		return me4000_ao_disable_do(ao_context);
	case ME4000_AO_FSM_STATE:
		return me4000_ao_fsm_state((int *)arg, ao_context);
	case ME4000_GET_USER_INFO:
		return me4000_get_user_info((struct me4000_user_info *)arg,
					    ao_context->board_info);
	case ME4000_AO_SYNCHRONOUS_EX_TRIG:
		return me4000_ao_synchronous_ex_trig(ao_context);
	case ME4000_AO_SYNCHRONOUS_SW:
		return me4000_ao_synchronous_sw(ao_context);
	case ME4000_AO_SYNCHRONOUS_DISABLE:
		return me4000_ao_synchronous_disable(ao_context);
	case ME4000_AO_GET_FREE_BUFFER:
		return me4000_ao_get_free_buffer((unsigned long *)arg,
						 ao_context);
	default:
		return -ENOTTY;
	}
	return 0;
}

static int me4000_ao_start(unsigned long *arg,
			   struct me4000_ao_context *ao_context)
{
	u32 tmp;
	wait_queue_head_t queue;
	unsigned long ref;
	unsigned long timeout;
	unsigned long flags;

	CALL_PDEBUG("me4000_ao_start() is executed\n");

	if (get_user(timeout, arg)) {
		printk(KERN_ERR
		       "me4000_ao_start():Cannot copy data from user\n");
		return -EFAULT;
	}

	init_waitqueue_head(&queue);

	spin_lock_irqsave(&ao_context->int_lock, flags);
	tmp = inl(ao_context->ctrl_reg);
	tmp &= ~(ME4000_AO_CTRL_BIT_STOP | ME4000_AO_CTRL_BIT_IMMEDIATE_STOP);
	me4000_outl(tmp, ao_context->ctrl_reg);
	spin_unlock_irqrestore(&ao_context->int_lock, flags);

	if ((tmp & ME4000_AO_CTRL_BIT_ENABLE_EX_TRIG)) {
		if (timeout) {
			ref = jiffies;
			while (!
			       (inl(ao_context->status_reg) &
				ME4000_AO_STATUS_BIT_FSM)) {
				interruptible_sleep_on_timeout(&queue, 1);
				if (signal_pending(current)) {
					printk(KERN_ERR
					       "ME4000:me4000_ao_start():Wait on start of state machine interrupted\n");
					return -EINTR;
				}
				/* kernel 2.6 has different definitions for HZ
				 * in user and kernel space */
				if ((jiffies - ref) > (timeout * HZ / USER_HZ)) {
					printk(KERN_ERR
					       "ME4000:me4000_ao_start():Timeout reached\n");
					return -EIO;
				}
			}
		}
	} else {
		me4000_outl(0x8000, ao_context->single_reg);
	}

	return 0;
}

static int me4000_ao_stop(struct me4000_ao_context *ao_context)
{
	u32 tmp;
	wait_queue_head_t queue;
	unsigned long flags;

	init_waitqueue_head(&queue);

	CALL_PDEBUG("me4000_ao_stop() is executed\n");

	/* Set the stop bit */
	spin_lock_irqsave(&ao_context->int_lock, flags);
	tmp = inl(ao_context->ctrl_reg);
	tmp |= ME4000_AO_CTRL_BIT_STOP;
	me4000_outl(tmp, ao_context->ctrl_reg);
	spin_unlock_irqrestore(&ao_context->int_lock, flags);

	while (inl(ao_context->status_reg) & ME4000_AO_STATUS_BIT_FSM) {
		interruptible_sleep_on_timeout(&queue, 1);
		if (signal_pending(current)) {
			printk(KERN_ERR
			       "me4000_ao_stop():Wait on state machine after stop interrupted\n");
			return -EINTR;
		}
	}

	/* Clear the stop bit */
	/* tmp &= ~ME4000_AO_CTRL_BIT_STOP; */
	/* me4000_outl(tmp, ao_context->ctrl_reg); */

	return 0;
}

static int me4000_ao_immediate_stop(struct me4000_ao_context *ao_context)
{
	u32 tmp;
	wait_queue_head_t queue;
	unsigned long flags;

	init_waitqueue_head(&queue);

	CALL_PDEBUG("me4000_ao_immediate_stop() is executed\n");

	spin_lock_irqsave(&ao_context->int_lock, flags);
	tmp = inl(ao_context->ctrl_reg);
	tmp |= ME4000_AO_CTRL_BIT_STOP | ME4000_AO_CTRL_BIT_IMMEDIATE_STOP;
	me4000_outl(tmp, ao_context->ctrl_reg);
	spin_unlock_irqrestore(&ao_context->int_lock, flags);

	while (inl(ao_context->status_reg) & ME4000_AO_STATUS_BIT_FSM) {
		interruptible_sleep_on_timeout(&queue, 1);
		if (signal_pending(current)) {
			printk(KERN_ERR
			       "me4000_ao_immediate_stop():Wait on state machine after stop interrupted\n");
			return -EINTR;
		}
	}

	/* Clear the stop bits */
	/* tmp &= ~(ME4000_AO_CTRL_BIT_STOP | ME4000_AO_CTRL_BIT_IMMEDIATE_STOP); */
	/* me4000_outl(tmp, ao_context->ctrl_reg); */

	return 0;
}

static int me4000_ao_timer_set_divisor(u32 *arg,
				       struct me4000_ao_context *ao_context)
{
	u32 divisor;
	u32 tmp;

	CALL_PDEBUG("me4000_ao_timer set_divisor() is executed\n");

	if (get_user(divisor, arg))
		return -EFAULT;

	/* Check if the state machine is stopped */
	tmp = me4000_inl(ao_context->status_reg);
	if (tmp & ME4000_AO_STATUS_BIT_FSM) {
		printk(KERN_ERR
		       "me4000_ao_timer_set_divisor():Can't set timer while DAC is running\n");
		return -EBUSY;
	}

	PDEBUG("me4000_ao_timer set_divisor():Divisor from user = %d\n",
	       divisor);

	/* Check if the divisor is right. ME4000_AO_MIN_TICKS is the lowest */
	if (divisor < ME4000_AO_MIN_TICKS) {
		printk(KERN_ERR
		       "ME4000:me4000_ao_timer set_divisor():Divisor to low\n");
		return -EINVAL;
	}

	/* Fix bug in Firmware */
	divisor -= 2;

	PDEBUG("me4000_ao_timer set_divisor():Divisor to HW = %d\n", divisor);

	/* Write the divisor */
	me4000_outl(divisor, ao_context->timer_reg);

	return 0;
}

static int me4000_ao_ex_trig_set_edge(int *arg,
				      struct me4000_ao_context *ao_context)
{
	int mode;
	u32 tmp;
	unsigned long flags;

	CALL_PDEBUG("me4000_ao_ex_trig_set_edge() is executed\n");

	if (get_user(mode, arg))
		return -EFAULT;

	/* Check if the state machine is stopped */
	tmp = me4000_inl(ao_context->status_reg);
	if (tmp & ME4000_AO_STATUS_BIT_FSM) {
		printk(KERN_ERR
		       "me4000_ao_ex_trig_set_edge():Can't set trigger while DAC is running\n");
		return -EBUSY;
	}

	if (mode == ME4000_AO_TRIGGER_EXT_EDGE_RISING) {
		spin_lock_irqsave(&ao_context->int_lock, flags);
		tmp = me4000_inl(ao_context->ctrl_reg);
		tmp &=
		    ~(ME4000_AO_CTRL_BIT_EX_TRIG_EDGE |
		      ME4000_AO_CTRL_BIT_EX_TRIG_BOTH);
		me4000_outl(tmp, ao_context->ctrl_reg);
		spin_unlock_irqrestore(&ao_context->int_lock, flags);
	} else if (mode == ME4000_AO_TRIGGER_EXT_EDGE_FALLING) {
		spin_lock_irqsave(&ao_context->int_lock, flags);
		tmp = me4000_inl(ao_context->ctrl_reg);
		tmp &= ~ME4000_AO_CTRL_BIT_EX_TRIG_BOTH;
		tmp |= ME4000_AO_CTRL_BIT_EX_TRIG_EDGE;
		me4000_outl(tmp, ao_context->ctrl_reg);
		spin_unlock_irqrestore(&ao_context->int_lock, flags);
	} else if (mode == ME4000_AO_TRIGGER_EXT_EDGE_BOTH) {
		spin_lock_irqsave(&ao_context->int_lock, flags);
		tmp = me4000_inl(ao_context->ctrl_reg);
		tmp |=
		    ME4000_AO_CTRL_BIT_EX_TRIG_EDGE |
		    ME4000_AO_CTRL_BIT_EX_TRIG_BOTH;
		me4000_outl(tmp, ao_context->ctrl_reg);
		spin_unlock_irqrestore(&ao_context->int_lock, flags);
	} else {
		printk(KERN_ERR
		       "me4000_ao_ex_trig_set_edge():Invalid trigger mode\n");
		return -EINVAL;
	}

	return 0;
}

static int me4000_ao_ex_trig_enable(struct me4000_ao_context *ao_context)
{
	u32 tmp;
	unsigned long flags;

	CALL_PDEBUG("me4000_ao_ex_trig_enable() is executed\n");

	/* Check if the state machine is stopped */
	tmp = me4000_inl(ao_context->status_reg);
	if (tmp & ME4000_AO_STATUS_BIT_FSM) {
		printk(KERN_ERR
		       "me4000_ao_ex_trig_enable():Can't enable trigger while DAC is running\n");
		return -EBUSY;
	}

	spin_lock_irqsave(&ao_context->int_lock, flags);
	tmp = me4000_inl(ao_context->ctrl_reg);
	tmp |= ME4000_AO_CTRL_BIT_ENABLE_EX_TRIG;
	me4000_outl(tmp, ao_context->ctrl_reg);
	spin_unlock_irqrestore(&ao_context->int_lock, flags);

	return 0;
}

static int me4000_ao_ex_trig_disable(struct me4000_ao_context *ao_context)
{
	u32 tmp;
	unsigned long flags;

	CALL_PDEBUG("me4000_ao_ex_trig_disable() is executed\n");

	/* Check if the state machine is stopped */
	tmp = me4000_inl(ao_context->status_reg);
	if (tmp & ME4000_AO_STATUS_BIT_FSM) {
		printk(KERN_ERR
		       "me4000_ao_ex_trig_disable():Can't disable trigger while DAC is running\n");
		return -EBUSY;
	}

	spin_lock_irqsave(&ao_context->int_lock, flags);
	tmp = me4000_inl(ao_context->ctrl_reg);
	tmp &= ~ME4000_AO_CTRL_BIT_ENABLE_EX_TRIG;
	me4000_outl(tmp, ao_context->ctrl_reg);
	spin_unlock_irqrestore(&ao_context->int_lock, flags);

	return 0;
}

static int me4000_ao_simultaneous_disable(struct me4000_ao_context *ao_context)
{
	u32 tmp;

	CALL_PDEBUG("me4000_ao_simultaneous_disable() is executed\n");

	/* Check if the state machine is stopped */
	/* Be careful here because this function is called from
	   me4000_ao_synchronous disable */
	tmp = me4000_inl(ao_context->status_reg);
	if (tmp & ME4000_AO_STATUS_BIT_FSM) {
		printk(KERN_ERR
		       "me4000_ao_simultaneous_disable():Can't disable while DAC is running\n");
		return -EBUSY;
	}

	spin_lock(&ao_context->board_info->preload_lock);
	tmp = me4000_inl(ao_context->preload_reg);
	/* Disable preload bit */
	tmp &= ~(0x1 << ao_context->index);
	/* Disable hw simultaneous bit */
	tmp &= ~(0x1 << (ao_context->index + 16));
	me4000_outl(tmp, ao_context->preload_reg);
	spin_unlock(&ao_context->board_info->preload_lock);

	return 0;
}

static int me4000_ao_simultaneous_ex_trig(struct me4000_ao_context *ao_context)
{
	u32 tmp;

	CALL_PDEBUG("me4000_ao_simultaneous_ex_trig() is executed\n");

	spin_lock(&ao_context->board_info->preload_lock);
	tmp = me4000_inl(ao_context->preload_reg);
	/* Enable preload bit */
	tmp |= (0x1 << ao_context->index);
	/* Enable hw simulatenous bit */
	tmp |= (0x1 << (ao_context->index + 16));
	me4000_outl(tmp, ao_context->preload_reg);
	spin_unlock(&ao_context->board_info->preload_lock);

	return 0;
}

static int me4000_ao_simultaneous_sw(struct me4000_ao_context *ao_context)
{
	u32 tmp;

	CALL_PDEBUG("me4000_ao_simultaneous_sw() is executed\n");

	spin_lock(&ao_context->board_info->preload_lock);
	tmp = me4000_inl(ao_context->preload_reg);
	/* Enable preload bit */
	tmp |= (0x1 << ao_context->index);
	/* Enable hw simulatenous bit */
	tmp &= ~(0x1 << (ao_context->index + 16));
	me4000_outl(tmp, ao_context->preload_reg);
	spin_unlock(&ao_context->board_info->preload_lock);

	return 0;
}

static int me4000_ao_preload(struct me4000_ao_context *ao_context)
{
	CALL_PDEBUG("me4000_ao_preload() is executed\n");
	return me4000_ao_simultaneous_sw(ao_context);
}

static int me4000_ao_preload_update(struct me4000_ao_context *ao_context)
{
	u32 tmp;
	u32 ctrl;
	struct list_head *entry;

	CALL_PDEBUG("me4000_ao_preload_update() is executed\n");

	spin_lock(&ao_context->board_info->preload_lock);
	tmp = me4000_inl(ao_context->preload_reg);
	list_for_each(entry, &ao_context->board_info->ao_context_list) {
		/* The channels we update must be in the following state :
		   - Mode A
		   - Hardware trigger is disabled
		   - Corresponding simultaneous bit is reset
		 */
		ctrl = me4000_inl(ao_context->ctrl_reg);
		if (!
		    (ctrl &
		     (ME4000_AO_CTRL_BIT_MODE_0 | ME4000_AO_CTRL_BIT_MODE_1 |
		      ME4000_AO_CTRL_BIT_ENABLE_EX_TRIG))) {
			if (!
			    (tmp &
			     (0x1 <<
			      (((struct me4000_ao_context *)entry)->index
								      + 16)))) {
				tmp &=
				    ~(0x1 <<
				      (((struct me4000_ao_context *)entry)->
									index));
			}
		}
	}
	me4000_outl(tmp, ao_context->preload_reg);
	spin_unlock(&ao_context->board_info->preload_lock);

	return 0;
}

static int me4000_ao_simultaneous_update(struct me4000_ao_channel_list *arg,
					 struct me4000_ao_context *ao_context)
{
	int err;
	int i;
	u32 tmp;
	struct me4000_ao_channel_list channels;

	CALL_PDEBUG("me4000_ao_simultaneous_update() is executed\n");

	/* Copy data from user */
	err = copy_from_user(&channels, arg,
			sizeof(struct me4000_ao_channel_list));
	if (err) {
		printk(KERN_ERR
		       "ME4000:me4000_ao_simultaneous_update():Can't copy command\n");
		return -EFAULT;
	}

	channels.list =
	    kzalloc(sizeof(unsigned long) * channels.count, GFP_KERNEL);
	if (!channels.list) {
		printk(KERN_ERR
		       "ME4000:me4000_ao_simultaneous_update():Can't get buffer\n");
		return -ENOMEM;
	}

	/* Copy channel list from user */
	err =
	    copy_from_user(channels.list, arg->list,
			   sizeof(unsigned long) * channels.count);
	if (err) {
		printk(KERN_ERR
		       "ME4000:me4000_ao_simultaneous_update():Can't copy list\n");
		kfree(channels.list);
		return -EFAULT;
	}

	spin_lock(&ao_context->board_info->preload_lock);
	tmp = me4000_inl(ao_context->preload_reg);
	for (i = 0; i < channels.count; i++) {
		if (channels.list[i] >
		    ao_context->board_info->board_p->ao.count) {
			spin_unlock(&ao_context->board_info->preload_lock);
			kfree(channels.list);
			printk(KERN_ERR
			       "ME4000:me4000_ao_simultaneous_update():Invalid board number specified\n");
			return -EFAULT;
		}
		/* Clear the preload bit */
		tmp &= ~(0x1 << channels.list[i]);
		/* Clear the hw simultaneous bit */
		tmp &= ~(0x1 << (channels.list[i] + 16));
	}
	me4000_outl(tmp, ao_context->preload_reg);
	spin_unlock(&ao_context->board_info->preload_lock);
	kfree(channels.list);

	return 0;
}

static int me4000_ao_synchronous_ex_trig(struct me4000_ao_context *ao_context)
{
	u32 tmp;
	unsigned long flags;

	CALL_PDEBUG("me4000_ao_synchronous_ex_trig() is executed\n");

	/* Check if the state machine is stopped */
	tmp = me4000_inl(ao_context->status_reg);
	if (tmp & ME4000_AO_STATUS_BIT_FSM) {
		printk(KERN_ERR
		       "me4000_ao_synchronous_ex_trig(): DAC is running\n");
		return -EBUSY;
	}

	spin_lock(&ao_context->board_info->preload_lock);
	tmp = me4000_inl(ao_context->preload_reg);
	/* Disable synchronous sw bit */
	tmp &= ~(0x1 << ao_context->index);
	/* Enable synchronous hw bit */
	tmp |= 0x1 << (ao_context->index + 16);
	me4000_outl(tmp, ao_context->preload_reg);
	spin_unlock(&ao_context->board_info->preload_lock);

	/* Make runnable */
	spin_lock_irqsave(&ao_context->int_lock, flags);
	tmp = me4000_inl(ao_context->ctrl_reg);
	if (tmp & (ME4000_AO_CTRL_BIT_MODE_0 | ME4000_AO_CTRL_BIT_MODE_1)) {
		tmp &=
		    ~(ME4000_AO_CTRL_BIT_STOP |
		      ME4000_AO_CTRL_BIT_IMMEDIATE_STOP);
		me4000_outl(tmp, ao_context->ctrl_reg);
	}
	spin_unlock_irqrestore(&ao_context->int_lock, flags);

	return 0;
}

static int me4000_ao_synchronous_sw(struct me4000_ao_context *ao_context)
{
	u32 tmp;
	unsigned long flags;

	CALL_PDEBUG("me4000_ao_synchronous_sw() is executed\n");

	/* Check if the state machine is stopped */
	tmp = me4000_inl(ao_context->status_reg);
	if (tmp & ME4000_AO_STATUS_BIT_FSM) {
		printk(KERN_ERR "me4000_ao_synchronous_sw(): DAC is running\n");
		return -EBUSY;
	}

	spin_lock(&ao_context->board_info->preload_lock);
	tmp = me4000_inl(ao_context->preload_reg);
	/* Enable synchronous sw bit */
	tmp |= 0x1 << ao_context->index;
	/* Disable synchronous hw bit */
	tmp &= ~(0x1 << (ao_context->index + 16));
	me4000_outl(tmp, ao_context->preload_reg);
	spin_unlock(&ao_context->board_info->preload_lock);

	/* Make runnable */
	spin_lock_irqsave(&ao_context->int_lock, flags);
	tmp = me4000_inl(ao_context->ctrl_reg);
	if (tmp & (ME4000_AO_CTRL_BIT_MODE_0 | ME4000_AO_CTRL_BIT_MODE_1)) {
		tmp &=
		    ~(ME4000_AO_CTRL_BIT_STOP |
		      ME4000_AO_CTRL_BIT_IMMEDIATE_STOP);
		me4000_outl(tmp, ao_context->ctrl_reg);
	}
	spin_unlock_irqrestore(&ao_context->int_lock, flags);

	return 0;
}

static int me4000_ao_synchronous_disable(struct me4000_ao_context *ao_context)
{
	return me4000_ao_simultaneous_disable(ao_context);
}

static int me4000_ao_get_free_buffer(unsigned long *arg,
				     struct me4000_ao_context *ao_context)
{
	unsigned long c;
	int err;

	c = me4000_buf_space(ao_context->circ_buf, ME4000_AO_BUFFER_COUNT);

	err = copy_to_user(arg, &c, sizeof(unsigned long));
	if (err) {
		printk(KERN_ERR
		       "%s:Can't copy to user space\n", __func__);
		return -EFAULT;
	}

	return 0;
}

static int me4000_ao_ex_trig_timeout(unsigned long *arg,
				     struct me4000_ao_context *ao_context)
{
	u32 tmp;
	wait_queue_head_t queue;
	unsigned long ref;
	unsigned long timeout;

	CALL_PDEBUG("me4000_ao_ex_trig_timeout() is executed\n");

	if (get_user(timeout, arg)) {
		printk(KERN_ERR
		       "me4000_ao_ex_trig_timeout():Cannot copy data from user\n");
		return -EFAULT;
	}

	init_waitqueue_head(&queue);

	tmp = inl(ao_context->ctrl_reg);

	if ((tmp & ME4000_AO_CTRL_BIT_ENABLE_EX_TRIG)) {
		if (timeout) {
			ref = jiffies;
			while ((inl(ao_context->status_reg) &
				ME4000_AO_STATUS_BIT_FSM)) {
				interruptible_sleep_on_timeout(&queue, 1);
				if (signal_pending(current)) {
					printk(KERN_ERR
					       "ME4000:me4000_ao_ex_trig_timeout():Wait on start of state machine interrupted\n");
					return -EINTR;
				}
				/* kernel 2.6 has different definitions for HZ
				 * in user and kernel space */
				if ((jiffies - ref) > (timeout * HZ / USER_HZ)) {
					printk(KERN_ERR
					       "ME4000:me4000_ao_ex_trig_timeout():Timeout reached\n");
					return -EIO;
				}
			}
		} else {
			while ((inl(ao_context->status_reg) &
				ME4000_AO_STATUS_BIT_FSM)) {
				interruptible_sleep_on_timeout(&queue, 1);
				if (signal_pending(current)) {
					printk(KERN_ERR
					       "ME4000:me4000_ao_ex_trig_timeout():Wait on start of state machine interrupted\n");
					return -EINTR;
				}
			}
		}
	} else {
		printk(KERN_ERR
		       "ME4000:me4000_ao_ex_trig_timeout():External Trigger is not enabled\n");
		return -EINVAL;
	}

	return 0;
}

static int me4000_ao_enable_do(struct me4000_ao_context *ao_context)
{
	u32 tmp;
	unsigned long flags;

	CALL_PDEBUG("me4000_ao_enable_do() is executed\n");

	/* Only available for analog output 3 */
	if (ao_context->index != 3) {
		printk(KERN_ERR
		       "me4000_ao_enable_do():Only available for analog output 3\n");
		return -ENOTTY;
	}

	/* Check if the state machine is stopped */
	tmp = me4000_inl(ao_context->status_reg);
	if (tmp & ME4000_AO_STATUS_BIT_FSM) {
		printk(KERN_ERR "me4000_ao_enable_do(): DAC is running\n");
		return -EBUSY;
	}

	/* Set the stop bit */
	spin_lock_irqsave(&ao_context->int_lock, flags);
	tmp = inl(ao_context->ctrl_reg);
	tmp |= ME4000_AO_CTRL_BIT_ENABLE_DO;
	me4000_outl(tmp, ao_context->ctrl_reg);
	spin_unlock_irqrestore(&ao_context->int_lock, flags);

	return 0;
}

static int me4000_ao_disable_do(struct me4000_ao_context *ao_context)
{
	u32 tmp;
	unsigned long flags;

	CALL_PDEBUG("me4000_ao_disable_do() is executed\n");

	/* Only available for analog output 3 */
	if (ao_context->index != 3) {
		printk(KERN_ERR
		       "me4000_ao_disable():Only available for analog output 3\n");
		return -ENOTTY;
	}

	/* Check if the state machine is stopped */
	tmp = me4000_inl(ao_context->status_reg);
	if (tmp & ME4000_AO_STATUS_BIT_FSM) {
		printk(KERN_ERR "me4000_ao_disable_do(): DAC is running\n");
		return -EBUSY;
	}

	spin_lock_irqsave(&ao_context->int_lock, flags);
	tmp = inl(ao_context->ctrl_reg);
	tmp &= ~(ME4000_AO_CTRL_BIT_ENABLE_DO);
	me4000_outl(tmp, ao_context->ctrl_reg);
	spin_unlock_irqrestore(&ao_context->int_lock, flags);

	return 0;
}

static int me4000_ao_fsm_state(int *arg, struct me4000_ao_context *ao_context)
{
	unsigned long tmp;

	CALL_PDEBUG("me4000_ao_fsm_state() is executed\n");

	tmp =
	    (me4000_inl(ao_context->status_reg) & ME4000_AO_STATUS_BIT_FSM) ? 1
	    : 0;

	if (ao_context->pipe_flag) {
		printk(KERN_ERR "me4000_ao_fsm_state():Broken pipe detected\n");
		return -EPIPE;
	}

	if (put_user(tmp, arg)) {
		printk(KERN_ERR "me4000_ao_fsm_state():Cannot copy to user\n");
		return -EFAULT;
	}

	return 0;
}

/*------------------------- Analog input stuff -------------------------------*/

static int me4000_ai_prepare(struct me4000_ai_context *ai_context)
{
	wait_queue_head_t queue;
	int err;

	CALL_PDEBUG("me4000_ai_prepare() is executed\n");

	init_waitqueue_head(&queue);

	/* Set the new mode and stop bits */
	me4000_outl(ai_context->
		    mode | ME4000_AI_CTRL_BIT_STOP |
		    ME4000_AI_CTRL_BIT_IMMEDIATE_STOP, ai_context->ctrl_reg);

	/* Set the timer registers */
	ai_context->chan_timer = 66;
	ai_context->chan_pre_timer = 66;
	ai_context->scan_timer_low = 0;
	ai_context->scan_timer_high = 0;

	me4000_outl(65, ai_context->chan_timer_reg);
	me4000_outl(65, ai_context->chan_pre_timer_reg);
	me4000_outl(0, ai_context->scan_timer_low_reg);
	me4000_outl(0, ai_context->scan_timer_high_reg);
	me4000_outl(0, ai_context->scan_pre_timer_low_reg);
	me4000_outl(0, ai_context->scan_pre_timer_high_reg);

	ai_context->channel_list_count = 0;

	if (ai_context->mode) {
		/* Request the interrupt line */
		err =
		    request_irq(ai_context->irq, me4000_ai_isr,
				IRQF_DISABLED | IRQF_SHARED, ME4000_NAME,
				ai_context);
		if (err) {
			printk(KERN_ERR
			       "ME4000:me4000_ai_prepare():Can't get interrupt line");
			return -ENODEV;
		}

		/* Allocate circular buffer */
		ai_context->circ_buf.buf =
		    kzalloc(ME4000_AI_BUFFER_SIZE, GFP_KERNEL);
		if (!ai_context->circ_buf.buf) {
			printk(KERN_ERR
			       "ME4000:me4000_ai_prepare():Can't get circular buffer\n");
			free_irq(ai_context->irq, ai_context);
			return -ENOMEM;
		}

		/* Clear the circular buffer */
		ai_context->circ_buf.head = 0;
		ai_context->circ_buf.tail = 0;
	}

	return 0;
}

static int me4000_ai_reset(struct me4000_ai_context *ai_context)
{
	wait_queue_head_t queue;
	u32 tmp;
	unsigned long flags;

	CALL_PDEBUG("me4000_ai_reset() is executed\n");

	init_waitqueue_head(&queue);

	/*
	 * First stop conversion of the state machine before reconfigure.
	 * If not stopped before configuring mode, it could
	 * walk in a undefined state.
	 */
	spin_lock_irqsave(&ai_context->int_lock, flags);
	tmp = me4000_inl(ai_context->ctrl_reg);
	tmp |= ME4000_AI_CTRL_BIT_IMMEDIATE_STOP;
	me4000_outl(tmp, ai_context->ctrl_reg);
	spin_unlock_irqrestore(&ai_context->int_lock, flags);

	while (inl(ai_context->status_reg) & ME4000_AI_STATUS_BIT_FSM) {
		interruptible_sleep_on_timeout(&queue, 1);
		if (signal_pending(current)) {
			printk(KERN_ERR
			       "me4000_ai_reset():Wait on state machine after stop interrupted\n");
			return -EINTR;
		}
	}

	/* Clear the control register and set the stop bits */
	spin_lock_irqsave(&ai_context->int_lock, flags);
	tmp = me4000_inl(ai_context->ctrl_reg);
	me4000_outl(ME4000_AI_CTRL_BIT_IMMEDIATE_STOP | ME4000_AI_CTRL_BIT_STOP,
		    ai_context->ctrl_reg);
	spin_unlock_irqrestore(&ai_context->int_lock, flags);

	/* Reset timer registers */
	ai_context->chan_timer = 66;
	ai_context->chan_pre_timer = 66;
	ai_context->scan_timer_low = 0;
	ai_context->scan_timer_high = 0;
	ai_context->sample_counter = 0;
	ai_context->sample_counter_reload = 0;

	me4000_outl(65, ai_context->chan_timer_reg);
	me4000_outl(65, ai_context->chan_pre_timer_reg);
	me4000_outl(0, ai_context->scan_timer_low_reg);
	me4000_outl(0, ai_context->scan_timer_high_reg);
	me4000_outl(0, ai_context->scan_pre_timer_low_reg);
	me4000_outl(0, ai_context->scan_pre_timer_high_reg);
	me4000_outl(0, ai_context->sample_counter_reg);

	ai_context->channel_list_count = 0;

	/* Clear the circular buffer */
	ai_context->circ_buf.head = 0;
	ai_context->circ_buf.tail = 0;

	return 0;
}

static int me4000_ai_ioctl_sing(struct inode *inode_p, struct file *file_p,
				unsigned int service, unsigned long arg)
{
	struct me4000_ai_context *ai_context;

	CALL_PDEBUG("me4000_ai_ioctl_sing() is executed\n");

	ai_context = file_p->private_data;

	if (_IOC_TYPE(service) != ME4000_MAGIC) {
		printk(KERN_ERR "me4000_ai_ioctl_sing():Wrong magic number\n");
		return -ENOTTY;
	}
	if (_IOC_NR(service) > ME4000_IOCTL_MAXNR) {
		printk(KERN_ERR
		       "me4000_ai_ioctl_sing():Service number to high\n");
		return -ENOTTY;
	}

	switch (service) {
	case ME4000_AI_SINGLE:
		return me4000_ai_single((struct me4000_ai_single *)arg,
								ai_context);
	case ME4000_AI_EX_TRIG_ENABLE:
		return me4000_ai_ex_trig_enable(ai_context);
	case ME4000_AI_EX_TRIG_DISABLE:
		return me4000_ai_ex_trig_disable(ai_context);
	case ME4000_AI_EX_TRIG_SETUP:
		return me4000_ai_ex_trig_setup((struct me4000_ai_trigger *)arg,
					       ai_context);
	case ME4000_GET_USER_INFO:
		return me4000_get_user_info((struct me4000_user_info *)arg,
					    ai_context->board_info);
	case ME4000_AI_OFFSET_ENABLE:
		return me4000_ai_offset_enable(ai_context);
	case ME4000_AI_OFFSET_DISABLE:
		return me4000_ai_offset_disable(ai_context);
	case ME4000_AI_FULLSCALE_ENABLE:
		return me4000_ai_fullscale_enable(ai_context);
	case ME4000_AI_FULLSCALE_DISABLE:
		return me4000_ai_fullscale_disable(ai_context);
	case ME4000_AI_EEPROM_READ:
		return me4000_eeprom_read((struct me4000_eeprom *)arg,
								ai_context);
	case ME4000_AI_EEPROM_WRITE:
		return me4000_eeprom_write((struct me4000_eeprom *)arg,
								ai_context);
	default:
		printk(KERN_ERR
		       "me4000_ai_ioctl_sing():Invalid service number\n");
		return -ENOTTY;
	}
	return 0;
}

static int me4000_ai_single(struct me4000_ai_single *arg,
			    struct me4000_ai_context *ai_context)
{
	struct me4000_ai_single cmd;
	int err;
	u32 tmp;
	wait_queue_head_t queue;
	unsigned long jiffy;

	CALL_PDEBUG("me4000_ai_single() is executed\n");

	init_waitqueue_head(&queue);

	/* Copy data from user */
	err = copy_from_user(&cmd, arg, sizeof(struct me4000_ai_single));
	if (err) {
		printk(KERN_ERR
		       "ME4000:me4000_ai_single():Can't copy from user space\n");
		return -EFAULT;
	}

	/* Check range parameter */
	switch (cmd.range) {
	case ME4000_AI_LIST_RANGE_BIPOLAR_10:
	case ME4000_AI_LIST_RANGE_BIPOLAR_2_5:
	case ME4000_AI_LIST_RANGE_UNIPOLAR_10:
	case ME4000_AI_LIST_RANGE_UNIPOLAR_2_5:
		break;
	default:
		printk(KERN_ERR
		       "ME4000:me4000_ai_single():Invalid range specified\n");
		return -EINVAL;
	}

	/* Check mode and channel number */
	switch (cmd.mode) {
	case ME4000_AI_LIST_INPUT_SINGLE_ENDED:
		if (cmd.channel >= ai_context->board_info->board_p->ai.count) {
			printk(KERN_ERR
			       "ME4000:me4000_ai_single():Analog input is not available\n");
			return -EINVAL;
		}
		break;
	case ME4000_AI_LIST_INPUT_DIFFERENTIAL:
		if (cmd.channel >=
		    ai_context->board_info->board_p->ai.diff_count) {
			printk(KERN_ERR
			       "ME4000:me4000_ai_single():Analog input is not available in differential mode\n");
			return -EINVAL;
		}
		break;
	default:
		printk(KERN_ERR
		       "ME4000:me4000_ai_single():Invalid mode specified\n");
		return -EINVAL;
	}

	/* Clear channel list, data fifo and both stop bits */
	tmp = me4000_inl(ai_context->ctrl_reg);
	tmp &=
	    ~(ME4000_AI_CTRL_BIT_CHANNEL_FIFO | ME4000_AI_CTRL_BIT_DATA_FIFO |
	      ME4000_AI_CTRL_BIT_STOP | ME4000_AI_CTRL_BIT_IMMEDIATE_STOP);
	me4000_outl(tmp, ai_context->ctrl_reg);

	/* Enable channel list and data fifo */
	tmp |= ME4000_AI_CTRL_BIT_CHANNEL_FIFO | ME4000_AI_CTRL_BIT_DATA_FIFO;
	me4000_outl(tmp, ai_context->ctrl_reg);

	/* Generate channel list entry */
	me4000_outl(cmd.channel | cmd.range | cmd.
		    mode | ME4000_AI_LIST_LAST_ENTRY,
		    ai_context->channel_list_reg);

	/* Set the timer to maximum */
	me4000_outl(66, ai_context->chan_timer_reg);
	me4000_outl(66, ai_context->chan_pre_timer_reg);

	if (tmp & ME4000_AI_CTRL_BIT_EX_TRIG) {
		jiffy = jiffies;
		while (!
		       (me4000_inl(ai_context->status_reg) &
			ME4000_AI_STATUS_BIT_EF_DATA)) {
			interruptible_sleep_on_timeout(&queue, 1);
			if (signal_pending(current)) {
				printk(KERN_ERR
				       "ME4000:me4000_ai_single():Wait on start of state machine interrupted\n");
				return -EINTR;
			}
			/* 2.6 has different definitions for HZ in user and kernel space */
			if (((jiffies - jiffy) > (cmd.timeout * HZ / USER_HZ)) && cmd.timeout) {
				printk(KERN_ERR
				       "ME4000:me4000_ai_single():Timeout reached\n");
				return -EIO;
			}
		}
	} else {
		/* Start conversion */
		me4000_inl(ai_context->start_reg);

		/* Wait until ready */
		udelay(10);
		if (!
		    (me4000_inl(ai_context->status_reg) &
		     ME4000_AI_STATUS_BIT_EF_DATA)) {
			printk(KERN_ERR
			       "ME4000:me4000_ai_single():Value not available after wait\n");
			return -EIO;
		}
	}

	/* Read value from data fifo */
	cmd.value = me4000_inl(ai_context->data_reg) & 0xFFFF;

	/* Copy result back to user */
	err = copy_to_user(arg, &cmd, sizeof(struct me4000_ai_single));
	if (err) {
		printk(KERN_ERR
		       "ME4000:me4000_ai_single():Can't copy to user space\n");
		return -EFAULT;
	}

	return 0;
}

static int me4000_ai_ioctl_sw(struct inode *inode_p, struct file *file_p,
			      unsigned int service, unsigned long arg)
{
	struct me4000_ai_context *ai_context;

	CALL_PDEBUG("me4000_ai_ioctl_sw() is executed\n");

	ai_context = file_p->private_data;

	if (_IOC_TYPE(service) != ME4000_MAGIC) {
		printk(KERN_ERR "me4000_ai_ioctl_sw():Wrong magic number\n");
		return -ENOTTY;
	}
	if (_IOC_NR(service) > ME4000_IOCTL_MAXNR) {
		printk(KERN_ERR
		       "me4000_ai_ioctl_sw():Service number to high\n");
		return -ENOTTY;
	}

	switch (service) {
	case ME4000_AI_SC_SETUP:
		return me4000_ai_sc_setup((struct me4000_ai_sc *)arg,
								ai_context);
	case ME4000_AI_CONFIG:
		return me4000_ai_config((struct me4000_ai_config *)arg,
								ai_context);
	case ME4000_AI_START:
		return me4000_ai_start(ai_context);
	case ME4000_AI_STOP:
		return me4000_ai_stop(ai_context);
	case ME4000_AI_IMMEDIATE_STOP:
		return me4000_ai_immediate_stop(ai_context);
	case ME4000_AI_FSM_STATE:
		return me4000_ai_fsm_state((int *)arg, ai_context);
	case ME4000_GET_USER_INFO:
		return me4000_get_user_info((struct me4000_user_info *)arg,
					    ai_context->board_info);
	case ME4000_AI_EEPROM_READ:
		return me4000_eeprom_read((struct me4000_eeprom *)arg,
								ai_context);
	case ME4000_AI_EEPROM_WRITE:
		return me4000_eeprom_write((struct me4000_eeprom *)arg,
								ai_context);
	case ME4000_AI_GET_COUNT_BUFFER:
		return me4000_ai_get_count_buffer((unsigned long *)arg,
						  ai_context);
	default:
		printk(KERN_ERR
		       "%s:Invalid service number %d\n", __func__, service);
		return -ENOTTY;
	}
	return 0;
}

static int me4000_ai_ioctl_ext(struct inode *inode_p, struct file *file_p,
			       unsigned int service, unsigned long arg)
{
	struct me4000_ai_context *ai_context;

	CALL_PDEBUG("me4000_ai_ioctl_ext() is executed\n");

	ai_context = file_p->private_data;

	if (_IOC_TYPE(service) != ME4000_MAGIC) {
		printk(KERN_ERR "me4000_ai_ioctl_ext():Wrong magic number\n");
		return -ENOTTY;
	}
	if (_IOC_NR(service) > ME4000_IOCTL_MAXNR) {
		printk(KERN_ERR
		       "me4000_ai_ioctl_ext():Service number to high\n");
		return -ENOTTY;
	}

	switch (service) {
	case ME4000_AI_SC_SETUP:
		return me4000_ai_sc_setup((struct me4000_ai_sc *)arg,
								ai_context);
	case ME4000_AI_CONFIG:
		return me4000_ai_config((struct me4000_ai_config *)arg,
								ai_context);
	case ME4000_AI_START:
		return me4000_ai_start_ex((unsigned long *)arg, ai_context);
	case ME4000_AI_STOP:
		return me4000_ai_stop(ai_context);
	case ME4000_AI_IMMEDIATE_STOP:
		return me4000_ai_immediate_stop(ai_context);
	case ME4000_AI_EX_TRIG_ENABLE:
		return me4000_ai_ex_trig_enable(ai_context);
	case ME4000_AI_EX_TRIG_DISABLE:
		return me4000_ai_ex_trig_disable(ai_context);
	case ME4000_AI_EX_TRIG_SETUP:
		return me4000_ai_ex_trig_setup((struct me4000_ai_trigger *)arg,
					       ai_context);
	case ME4000_AI_FSM_STATE:
		return me4000_ai_fsm_state((int *)arg, ai_context);
	case ME4000_GET_USER_INFO:
		return me4000_get_user_info((struct me4000_user_info *)arg,
					    ai_context->board_info);
	case ME4000_AI_GET_COUNT_BUFFER:
		return me4000_ai_get_count_buffer((unsigned long *)arg,
						  ai_context);
	default:
		printk(KERN_ERR
		       "%s:Invalid service number %d\n", __func__ , service);
		return -ENOTTY;
	}
	return 0;
}

static int me4000_ai_fasync(int fd, struct file *file_p, int mode)
{
	struct me4000_ai_context *ai_context;

	CALL_PDEBUG("me4000_ao_fasync_cont() is executed\n");

	ai_context = file_p->private_data;
	return fasync_helper(fd, file_p, mode, &ai_context->fasync_p);
}

static int me4000_ai_config(struct me4000_ai_config *arg,
			    struct me4000_ai_context *ai_context)
{
	struct me4000_ai_config cmd;
	u32 *list = NULL;
	u32 mode;
	int i;
	int err;
	wait_queue_head_t queue;
	u64 scan;
	u32 tmp;

	CALL_PDEBUG("me4000_ai_config() is executed\n");

	init_waitqueue_head(&queue);

	/* Check if conversion is stopped */
	if (inl(ai_context->ctrl_reg) & ME4000_AI_STATUS_BIT_FSM) {
		printk(KERN_ERR
		       "ME4000:me4000_ai_config():Conversion is not stopped\n");
		err = -EBUSY;
		goto AI_CONFIG_ERR;
	}

	/* Copy data from user */
	err = copy_from_user(&cmd, arg, sizeof(struct me4000_ai_config));
	if (err) {
		printk(KERN_ERR
		       "ME4000:me4000_ai_config():Can't copy from user space\n");
		err = -EFAULT;
		goto AI_CONFIG_ERR;
	}

	PDEBUG
	    ("me4000_ai_config():chan = %ld, pre_chan = %ld, scan_low = %ld, scan_high = %ld, count = %ld\n",
	     cmd.timer.chan, cmd.timer.pre_chan, cmd.timer.scan_low,
	     cmd.timer.scan_high, cmd.channel_list.count);

	/* Check whether sample and hold is available for this board */
	if (cmd.sh) {
		if (!ai_context->board_info->board_p->ai.sh_count) {
			printk(KERN_ERR
			       "ME4000:me4000_ai_config():Sample and Hold is not available for this board\n");
			err = -ENODEV;
			goto AI_CONFIG_ERR;
		}
	}

	/* Check the channel list size */
	if (cmd.channel_list.count > ME4000_AI_CHANNEL_LIST_COUNT) {
		printk(KERN_ERR
		       "me4000_ai_config():Channel list is to large\n");
		err = -EINVAL;
		goto AI_CONFIG_ERR;
	}

	/* Copy channel list from user */
	list = kmalloc(sizeof(u32) * cmd.channel_list.count, GFP_KERNEL);
	if (!list) {
		printk(KERN_ERR
		       "ME4000:me4000_ai_config():Can't get memory for channel list\n");
		err = -ENOMEM;
		goto AI_CONFIG_ERR;
	}
	err =
	    copy_from_user(list, cmd.channel_list.list,
			   sizeof(u32) * cmd.channel_list.count);
	if (err) {
		printk(KERN_ERR
		       "ME4000:me4000_ai_config():Can't copy from user space\n");
		err = -EFAULT;
		goto AI_CONFIG_ERR;
	}

	/* Check if last entry bit is set */
	if (!(list[cmd.channel_list.count - 1] & ME4000_AI_LIST_LAST_ENTRY)) {
		printk(KERN_WARNING
		       "me4000_ai_config():Last entry bit is not set\n");
		list[cmd.channel_list.count - 1] |= ME4000_AI_LIST_LAST_ENTRY;
	}

	/* Check whether mode is equal for all entries */
	mode = list[0] & 0x20;
	for (i = 0; i < cmd.channel_list.count; i++) {
		if ((list[i] & 0x20) != mode) {
			printk(KERN_ERR
			       "ME4000:me4000_ai_config():Mode is not equal for all entries\n");
			err = -EINVAL;
			goto AI_CONFIG_ERR;
		}
	}

	/* Check whether channels are available for this mode */
	if (mode == ME4000_AI_LIST_INPUT_SINGLE_ENDED) {
		for (i = 0; i < cmd.channel_list.count; i++) {
			if ((list[i] & 0x1F) >=
			    ai_context->board_info->board_p->ai.count) {
				printk(KERN_ERR
				       "ME4000:me4000_ai_config():Channel is not available for single ended\n");
				err = -EINVAL;
				goto AI_CONFIG_ERR;
			}
		}
	} else if (mode == ME4000_AI_LIST_INPUT_DIFFERENTIAL) {
		for (i = 0; i < cmd.channel_list.count; i++) {
			if ((list[i] & 0x1F) >=
			    ai_context->board_info->board_p->ai.diff_count) {
				printk(KERN_ERR
				       "ME4000:me4000_ai_config():Channel is not available for differential\n");
				err = -EINVAL;
				goto AI_CONFIG_ERR;
			}
		}
	}

	/* Check if bipolar is set for all entries when in differential mode */
	if (mode == ME4000_AI_LIST_INPUT_DIFFERENTIAL) {
		for (i = 0; i < cmd.channel_list.count; i++) {
			if ((list[i] & 0xC0) != ME4000_AI_LIST_RANGE_BIPOLAR_10
			    && (list[i] & 0xC0) !=
			    ME4000_AI_LIST_RANGE_BIPOLAR_2_5) {
				printk(KERN_ERR
				       "ME4000:me4000_ai_config():Bipolar is not selected in differential mode\n");
				err = -EINVAL;
				goto AI_CONFIG_ERR;
			}
		}
	}

	if (ai_context->mode != ME4000_AI_ACQ_MODE_EXT_SINGLE_VALUE) {
		/* Check for minimum channel divisor */
		if (cmd.timer.chan < ME4000_AI_MIN_TICKS) {
			printk(KERN_ERR
			       "ME4000:me4000_ai_config():Channel timer divisor is to low\n");
			err = -EINVAL;
			goto AI_CONFIG_ERR;
		}

		/* Check if minimum channel divisor is adjusted when sample and hold is activated */
		if ((cmd.sh) && (cmd.timer.chan != ME4000_AI_MIN_TICKS)) {
			printk(KERN_ERR
			       "ME4000:me4000_ai_config():Channel timer divisor must be at minimum when sample and hold is activated\n");
			err = -EINVAL;
			goto AI_CONFIG_ERR;
		}

		/* Check for minimum channel pre divisor */
		if (cmd.timer.pre_chan < ME4000_AI_MIN_TICKS) {
			printk(KERN_ERR
			       "ME4000:me4000_ai_config():Channel pre timer divisor is to low\n");
			err = -EINVAL;
			goto AI_CONFIG_ERR;
		}

		/* Write the channel timers */
		me4000_outl(cmd.timer.chan - 1, ai_context->chan_timer_reg);
		me4000_outl(cmd.timer.pre_chan - 1,
			    ai_context->chan_pre_timer_reg);

		/* Save the timer values in the board context */
		ai_context->chan_timer = cmd.timer.chan;
		ai_context->chan_pre_timer = cmd.timer.pre_chan;

		if (ai_context->mode != ME4000_AI_ACQ_MODE_EXT_SINGLE_CHANLIST) {
			/* Check for scan timer divisor */
			scan =
			    (u64) cmd.timer.scan_low | ((u64) cmd.timer.
							scan_high << 32);
			if (scan != 0) {
				if (scan <
				    cmd.channel_list.count * cmd.timer.chan +
				    1) {
					printk(KERN_ERR
					       "ME4000:me4000_ai_config():Scan timer divisor is to low\n");
					err = -EINVAL;
					goto AI_CONFIG_ERR;
				}
			}

			/* Write the scan timers */
			if (scan != 0) {
				scan--;
				tmp = (u32) (scan & 0xFFFFFFFF);
				me4000_outl(tmp,
					    ai_context->scan_timer_low_reg);
				tmp = (u32) ((scan >> 32) & 0xFFFFFFFF);
				me4000_outl(tmp,
					    ai_context->scan_timer_high_reg);

				scan =
				    scan - (cmd.timer.chan - 1) +
				    (cmd.timer.pre_chan - 1);
				tmp = (u32) (scan & 0xFFFFFFFF);
				me4000_outl(tmp,
					    ai_context->scan_pre_timer_low_reg);
				tmp = (u32) ((scan >> 32) & 0xFFFFFFFF);
				me4000_outl(tmp,
					    ai_context->
					    scan_pre_timer_high_reg);
			} else {
				me4000_outl(0x0,
					    ai_context->scan_timer_low_reg);
				me4000_outl(0x0,
					    ai_context->scan_timer_high_reg);

				me4000_outl(0x0,
					    ai_context->scan_pre_timer_low_reg);
				me4000_outl(0x0,
					    ai_context->
					    scan_pre_timer_high_reg);
			}

			ai_context->scan_timer_low = cmd.timer.scan_low;
			ai_context->scan_timer_high = cmd.timer.scan_high;
		}
	}

	/* Clear the channel list */
	tmp = me4000_inl(ai_context->ctrl_reg);
	tmp &= ~ME4000_AI_CTRL_BIT_CHANNEL_FIFO;
	me4000_outl(tmp, ai_context->ctrl_reg);
	tmp |= ME4000_AI_CTRL_BIT_CHANNEL_FIFO;
	me4000_outl(tmp, ai_context->ctrl_reg);

	/* Write the channel list */
	for (i = 0; i < cmd.channel_list.count; i++)
		me4000_outl(list[i], ai_context->channel_list_reg);

	/* Setup sample and hold */
	if (cmd.sh) {
		tmp |= ME4000_AI_CTRL_BIT_SAMPLE_HOLD;
		me4000_outl(tmp, ai_context->ctrl_reg);
	} else {
		tmp &= ~ME4000_AI_CTRL_BIT_SAMPLE_HOLD;
		me4000_outl(tmp, ai_context->ctrl_reg);
	}

	/* Save the channel list size in the board context */
	ai_context->channel_list_count = cmd.channel_list.count;

	kfree(list);

	return 0;

AI_CONFIG_ERR:

	/* Reset the timers */
	ai_context->chan_timer = 66;
	ai_context->chan_pre_timer = 66;
	ai_context->scan_timer_low = 0;
	ai_context->scan_timer_high = 0;

	me4000_outl(65, ai_context->chan_timer_reg);
	me4000_outl(65, ai_context->chan_pre_timer_reg);
	me4000_outl(0, ai_context->scan_timer_high_reg);
	me4000_outl(0, ai_context->scan_timer_low_reg);
	me4000_outl(0, ai_context->scan_pre_timer_high_reg);
	me4000_outl(0, ai_context->scan_pre_timer_low_reg);

	ai_context->channel_list_count = 0;

	tmp = me4000_inl(ai_context->ctrl_reg);
	tmp &=
	    ~(ME4000_AI_CTRL_BIT_CHANNEL_FIFO | ME4000_AI_CTRL_BIT_SAMPLE_HOLD);

	kfree(list);

	return err;

}

static int ai_common_start(struct me4000_ai_context *ai_context)
{
	u32 tmp;
	CALL_PDEBUG("ai_common_start() is executed\n");

	tmp = me4000_inl(ai_context->ctrl_reg);

	/* Check if conversion is stopped */
	if (tmp & ME4000_AI_STATUS_BIT_FSM) {
		printk(KERN_ERR
		       "ME4000:ai_common_start():Conversion is not stopped\n");
		return -EBUSY;
	}

	/* Clear data fifo, disable all interrupts, clear sample counter reload */
	tmp &= ~(ME4000_AI_CTRL_BIT_DATA_FIFO | ME4000_AI_CTRL_BIT_LE_IRQ |
		 ME4000_AI_CTRL_BIT_HF_IRQ | ME4000_AI_CTRL_BIT_SC_IRQ |
		 ME4000_AI_CTRL_BIT_SC_RELOAD);

	me4000_outl(tmp, ai_context->ctrl_reg);

	/* Clear circular buffer */
	ai_context->circ_buf.head = 0;
	ai_context->circ_buf.tail = 0;

	/* Enable data fifo */
	tmp |= ME4000_AI_CTRL_BIT_DATA_FIFO;

	/* Determine interrupt setup */
	if (ai_context->sample_counter && !ai_context->sample_counter_reload) {
		/* Enable Half Full Interrupt and Sample Counter Interrupt */
		tmp |= ME4000_AI_CTRL_BIT_SC_IRQ | ME4000_AI_CTRL_BIT_HF_IRQ;
	} else if (ai_context->sample_counter
		   && ai_context->sample_counter_reload) {
		if (ai_context->sample_counter <= ME4000_AI_FIFO_COUNT / 2) {
			/* Enable only Sample Counter Interrupt */
			tmp |=
			    ME4000_AI_CTRL_BIT_SC_IRQ |
			    ME4000_AI_CTRL_BIT_SC_RELOAD;
		} else {
			/* Enable Half Full Interrupt and Sample Counter Interrupt */
			tmp |=
			    ME4000_AI_CTRL_BIT_SC_IRQ |
			    ME4000_AI_CTRL_BIT_HF_IRQ |
			    ME4000_AI_CTRL_BIT_SC_RELOAD;
		}
	} else {
		/* Enable only Half Full Interrupt */
		tmp |= ME4000_AI_CTRL_BIT_HF_IRQ;
	}

	/* Clear the stop bits */
	tmp &= ~(ME4000_AI_CTRL_BIT_STOP | ME4000_AI_CTRL_BIT_IMMEDIATE_STOP);

	/* Write setup to hardware */
	me4000_outl(tmp, ai_context->ctrl_reg);

	/* Write sample counter */
	me4000_outl(ai_context->sample_counter, ai_context->sample_counter_reg);

	return 0;
}

static int me4000_ai_start(struct me4000_ai_context *ai_context)
{
	int err;
	CALL_PDEBUG("me4000_ai_start() is executed\n");

	/* Prepare Hardware */
	err = ai_common_start(ai_context);
	if (err)
		return err;

	/* Start conversion by dummy read */
	me4000_inl(ai_context->start_reg);

	return 0;
}

static int me4000_ai_start_ex(unsigned long *arg,
			      struct me4000_ai_context *ai_context)
{
	int err;
	wait_queue_head_t queue;
	unsigned long ref;
	unsigned long timeout;

	CALL_PDEBUG("me4000_ai_start_ex() is executed\n");

	if (get_user(timeout, arg)) {
		printk(KERN_ERR
		       "me4000_ai_start_ex():Cannot copy data from user\n");
		return -EFAULT;
	}

	init_waitqueue_head(&queue);

	/* Prepare Hardware */
	err = ai_common_start(ai_context);
	if (err)
		return err;

	if (timeout) {
		ref = jiffies;
		while (!(inl(ai_context->status_reg) & ME4000_AI_STATUS_BIT_FSM)) {
			interruptible_sleep_on_timeout(&queue, 1);
			if (signal_pending(current)) {
				printk(KERN_ERR
				       "ME4000:me4000_ai_start_ex():Wait on start of state machine interrupted\n");
				return -EINTR;
			}
			/* 2.6 has different definitions for HZ in user and kernel space */
			if ((jiffies - ref) > (timeout * HZ / USER_HZ)) {
				printk(KERN_ERR
				       "ME4000:me4000_ai_start_ex():Timeout reached\n");
				return -EIO;
			}
		}
	} else {
		while (!(inl(ai_context->status_reg) & ME4000_AI_STATUS_BIT_FSM)) {
			interruptible_sleep_on_timeout(&queue, 1);
			if (signal_pending(current)) {
				printk(KERN_ERR
				       "ME4000:me4000_ai_start_ex():Wait on start of state machine interrupted\n");
				return -EINTR;
			}
		}
	}

	return 0;
}

static int me4000_ai_stop(struct me4000_ai_context *ai_context)
{
	wait_queue_head_t queue;
	u32 tmp;
	unsigned long flags;

	CALL_PDEBUG("me4000_ai_stop() is executed\n");

	init_waitqueue_head(&queue);

	/* Disable irqs and clear data fifo */
	spin_lock_irqsave(&ai_context->int_lock, flags);
	tmp = me4000_inl(ai_context->ctrl_reg);
	tmp &=
	    ~(ME4000_AI_CTRL_BIT_HF_IRQ | ME4000_AI_CTRL_BIT_SC_IRQ |
	      ME4000_AI_CTRL_BIT_DATA_FIFO);
	/* Stop conversion of the state machine */
	tmp |= ME4000_AI_CTRL_BIT_STOP;
	me4000_outl(tmp, ai_context->ctrl_reg);
	spin_unlock_irqrestore(&ai_context->int_lock, flags);

	/* Clear circular buffer */
	ai_context->circ_buf.head = 0;
	ai_context->circ_buf.tail = 0;

	while (inl(ai_context->status_reg) & ME4000_AI_STATUS_BIT_FSM) {
		interruptible_sleep_on_timeout(&queue, 1);
		if (signal_pending(current)) {
			printk(KERN_ERR
			       "ME4000:me4000_ai_stop():Wait on state machine after stop interrupted\n");
			return -EINTR;
		}
	}

	return 0;
}

static int me4000_ai_immediate_stop(struct me4000_ai_context *ai_context)
{
	wait_queue_head_t queue;
	u32 tmp;
	unsigned long flags;

	CALL_PDEBUG("me4000_ai_stop() is executed\n");

	init_waitqueue_head(&queue);

	/* Disable irqs and clear data fifo */
	spin_lock_irqsave(&ai_context->int_lock, flags);
	tmp = me4000_inl(ai_context->ctrl_reg);
	tmp &=
	    ~(ME4000_AI_CTRL_BIT_HF_IRQ | ME4000_AI_CTRL_BIT_SC_IRQ |
	      ME4000_AI_CTRL_BIT_DATA_FIFO);
	/* Stop conversion of the state machine */
	tmp |= ME4000_AI_CTRL_BIT_IMMEDIATE_STOP;
	me4000_outl(tmp, ai_context->ctrl_reg);
	spin_unlock_irqrestore(&ai_context->int_lock, flags);

	/* Clear circular buffer */
	ai_context->circ_buf.head = 0;
	ai_context->circ_buf.tail = 0;

	while (inl(ai_context->status_reg) & ME4000_AI_STATUS_BIT_FSM) {
		interruptible_sleep_on_timeout(&queue, 1);
		if (signal_pending(current)) {
			printk(KERN_ERR
			       "ME4000:me4000_ai_stop():Wait on state machine after stop interrupted\n");
			return -EINTR;
		}
	}

	return 0;
}

static int me4000_ai_ex_trig_enable(struct me4000_ai_context *ai_context)
{
	u32 tmp;
	unsigned long flags;

	CALL_PDEBUG("me4000_ai_ex_trig_enable() is executed\n");

	spin_lock_irqsave(&ai_context->int_lock, flags);
	tmp = me4000_inl(ai_context->ctrl_reg);
	tmp |= ME4000_AI_CTRL_BIT_EX_TRIG;
	me4000_outl(tmp, ai_context->ctrl_reg);
	spin_unlock_irqrestore(&ai_context->int_lock, flags);

	return 0;
}

static int me4000_ai_ex_trig_disable(struct me4000_ai_context *ai_context)
{
	u32 tmp;
	unsigned long flags;

	CALL_PDEBUG("me4000_ai_ex_trig_disable() is executed\n");

	spin_lock_irqsave(&ai_context->int_lock, flags);
	tmp = me4000_inl(ai_context->ctrl_reg);
	tmp &= ~ME4000_AI_CTRL_BIT_EX_TRIG;
	me4000_outl(tmp, ai_context->ctrl_reg);
	spin_unlock_irqrestore(&ai_context->int_lock, flags);

	return 0;
}

static int me4000_ai_ex_trig_setup(struct me4000_ai_trigger *arg,
				   struct me4000_ai_context *ai_context)
{
	struct me4000_ai_trigger cmd;
	int err;
	u32 tmp;
	unsigned long flags;

	CALL_PDEBUG("me4000_ai_ex_trig_setup() is executed\n");

	/* Copy data from user */
	err = copy_from_user(&cmd, arg, sizeof(struct me4000_ai_trigger));
	if (err) {
		printk(KERN_ERR
		       "ME4000:me4000_ai_ex_trig_setup():Can't copy from user space\n");
		return -EFAULT;
	}

	spin_lock_irqsave(&ai_context->int_lock, flags);
	tmp = me4000_inl(ai_context->ctrl_reg);

	if (cmd.mode == ME4000_AI_TRIGGER_EXT_DIGITAL) {
		tmp &= ~ME4000_AI_CTRL_BIT_EX_TRIG_ANALOG;
	} else if (cmd.mode == ME4000_AI_TRIGGER_EXT_ANALOG) {
		if (!ai_context->board_info->board_p->ai.ex_trig_analog) {
			printk(KERN_ERR
			       "ME4000:me4000_ai_ex_trig_setup():No analog trigger available\n");
			return -EINVAL;
		}
		tmp |= ME4000_AI_CTRL_BIT_EX_TRIG_ANALOG;
	} else {
		spin_unlock_irqrestore(&ai_context->int_lock, flags);
		printk(KERN_ERR
		       "ME4000:me4000_ai_ex_trig_setup():Invalid trigger mode specified\n");
		return -EINVAL;
	}

	if (cmd.edge == ME4000_AI_TRIGGER_EXT_EDGE_RISING) {
		tmp &=
		    ~(ME4000_AI_CTRL_BIT_EX_TRIG_BOTH |
		      ME4000_AI_CTRL_BIT_EX_TRIG_FALLING);
	} else if (cmd.edge == ME4000_AI_TRIGGER_EXT_EDGE_FALLING) {
		tmp |= ME4000_AI_CTRL_BIT_EX_TRIG_FALLING;
		tmp &= ~ME4000_AI_CTRL_BIT_EX_TRIG_BOTH;
	} else if (cmd.edge == ME4000_AI_TRIGGER_EXT_EDGE_BOTH) {
		tmp |=
		    ME4000_AI_CTRL_BIT_EX_TRIG_BOTH |
		    ME4000_AI_CTRL_BIT_EX_TRIG_FALLING;
	} else {
		spin_unlock_irqrestore(&ai_context->int_lock, flags);
		printk(KERN_ERR
		       "ME4000:me4000_ai_ex_trig_setup():Invalid trigger edge specified\n");
		return -EINVAL;
	}

	me4000_outl(tmp, ai_context->ctrl_reg);
	spin_unlock_irqrestore(&ai_context->int_lock, flags);
	return 0;
}

static int me4000_ai_sc_setup(struct me4000_ai_sc *arg,
			      struct me4000_ai_context *ai_context)
{
	struct me4000_ai_sc cmd;
	int err;

	CALL_PDEBUG("me4000_ai_sc_setup() is executed\n");

	/* Copy data from user */
	err = copy_from_user(&cmd, arg, sizeof(struct me4000_ai_sc));
	if (err) {
		printk(KERN_ERR
		       "ME4000:me4000_ai_sc_setup():Can't copy from user space\n");
		return -EFAULT;
	}

	ai_context->sample_counter = cmd.value;
	ai_context->sample_counter_reload = cmd.reload;

	return 0;
}

static ssize_t me4000_ai_read(struct file *filep, char *buff, size_t cnt,
			      loff_t *offp)
{
	struct me4000_ai_context *ai_context = filep->private_data;
	s16 *buffer = (s16 *) buff;
	size_t count = cnt / 2;
	unsigned long flags;
	int tmp;
	int c = 0;
	int k = 0;
	int ret = 0;
	wait_queue_t wait;

	CALL_PDEBUG("me4000_ai_read() is executed\n");

	init_waitqueue_entry(&wait, current);

	/* Check count */
	if (count <= 0) {
		PDEBUG("me4000_ai_read():Count is 0\n");
		return 0;
	}

	while (count > 0) {
		if (filep->f_flags & O_NONBLOCK) {
			c = me4000_values_to_end(ai_context->circ_buf,
						 ME4000_AI_BUFFER_COUNT);
			if (!c) {
				PDEBUG
				    ("me4000_ai_read():Returning from nonblocking read\n");
				break;
			}
		} else {
			/* Check if conversion is still running */
			if (!
			    (me4000_inl(ai_context->status_reg) &
			     ME4000_AI_STATUS_BIT_FSM)) {
				printk(KERN_ERR
				       "ME4000:me4000_ai_read():Conversion interrupted\n");
				return -EPIPE;
			}

			wait_event_interruptible(ai_context->wait_queue,
						 (me4000_values_to_end
						  (ai_context->circ_buf,
						   ME4000_AI_BUFFER_COUNT)));
			if (signal_pending(current)) {
				printk(KERN_ERR
				       "ME4000:me4000_ai_read():Wait on values interrupted from signal\n");
				return -EINTR;
			}
		}

		/* Only read count values or as much as available */
		c = me4000_values_to_end(ai_context->circ_buf,
					 ME4000_AI_BUFFER_COUNT);
		PDEBUG("me4000_ai_read():%d values to end\n", c);
		if (count < c)
			c = count;

		PDEBUG("me4000_ai_read():Copy %d values to user space\n", c);
		k = 2 * c;
		k -= copy_to_user(buffer,
				  ai_context->circ_buf.buf +
				  ai_context->circ_buf.tail, k);
		c = k / 2;
		if (!c) {
			printk(KERN_ERR
			       "ME4000:me4000_ai_read():Cannot copy new values to user\n");
			return -EFAULT;
		}

		ai_context->circ_buf.tail =
		    (ai_context->circ_buf.tail + c) & (ME4000_AI_BUFFER_COUNT -
						       1);
		buffer += c;
		count -= c;
		ret += c;

		spin_lock_irqsave(&ai_context->int_lock, flags);
		if (me4000_buf_space
		    (ai_context->circ_buf, ME4000_AI_BUFFER_COUNT)) {
			tmp = me4000_inl(ai_context->ctrl_reg);

			/* Determine interrupt setup */
			if (ai_context->sample_counter
			    && !ai_context->sample_counter_reload) {
				/* Enable Half Full Interrupt and Sample Counter Interrupt */
				tmp |=
				    ME4000_AI_CTRL_BIT_SC_IRQ |
				    ME4000_AI_CTRL_BIT_HF_IRQ;
			} else if (ai_context->sample_counter
				   && ai_context->sample_counter_reload) {
				if (ai_context->sample_counter <
				    ME4000_AI_FIFO_COUNT / 2) {
					/* Enable only Sample Counter Interrupt */
					tmp |= ME4000_AI_CTRL_BIT_SC_IRQ;
				} else {
					/* Enable Half Full Interrupt and Sample Counter Interrupt */
					tmp |=
					    ME4000_AI_CTRL_BIT_SC_IRQ |
					    ME4000_AI_CTRL_BIT_HF_IRQ;
				}
			} else {
				/* Enable only Half Full Interrupt */
				tmp |= ME4000_AI_CTRL_BIT_HF_IRQ;
			}

			me4000_outl(tmp, ai_context->ctrl_reg);
		}
		spin_unlock_irqrestore(&ai_context->int_lock, flags);
	}

	/* Check if conversion is still running */
	if (!(me4000_inl(ai_context->status_reg) & ME4000_AI_STATUS_BIT_FSM)) {
		printk(KERN_ERR
		       "ME4000:me4000_ai_read():Conversion not running after complete read\n");
		return -EPIPE;
	}

	if (filep->f_flags & O_NONBLOCK)
		return (k == 0) ? -EAGAIN : 2 * ret;

	CALL_PDEBUG("me4000_ai_read() is leaved\n");
	return ret * 2;
}

static unsigned int me4000_ai_poll(struct file *file_p, poll_table *wait)
{
	struct me4000_ai_context *ai_context;
	unsigned long mask = 0;

	CALL_PDEBUG("me4000_ai_poll() is executed\n");

	ai_context = file_p->private_data;

	/* Register wait queue */
	poll_wait(file_p, &ai_context->wait_queue, wait);

	/* Get available values */
	if (me4000_values_to_end(ai_context->circ_buf, ME4000_AI_BUFFER_COUNT))
		mask |= POLLIN | POLLRDNORM;

	PDEBUG("me4000_ai_poll():Return mask %lX\n", mask);

	return mask;
}

static int me4000_ai_offset_enable(struct me4000_ai_context *ai_context)
{
	unsigned long tmp;

	CALL_PDEBUG("me4000_ai_offset_enable() is executed\n");

	tmp = me4000_inl(ai_context->ctrl_reg);
	tmp |= ME4000_AI_CTRL_BIT_OFFSET;
	me4000_outl(tmp, ai_context->ctrl_reg);

	return 0;
}

static int me4000_ai_offset_disable(struct me4000_ai_context *ai_context)
{
	unsigned long tmp;

	CALL_PDEBUG("me4000_ai_offset_disable() is executed\n");

	tmp = me4000_inl(ai_context->ctrl_reg);
	tmp &= ~ME4000_AI_CTRL_BIT_OFFSET;
	me4000_outl(tmp, ai_context->ctrl_reg);

	return 0;
}

static int me4000_ai_fullscale_enable(struct me4000_ai_context *ai_context)
{
	unsigned long tmp;

	CALL_PDEBUG("me4000_ai_fullscale_enable() is executed\n");

	tmp = me4000_inl(ai_context->ctrl_reg);
	tmp |= ME4000_AI_CTRL_BIT_FULLSCALE;
	me4000_outl(tmp, ai_context->ctrl_reg);

	return 0;
}

static int me4000_ai_fullscale_disable(struct me4000_ai_context *ai_context)
{
	unsigned long tmp;

	CALL_PDEBUG("me4000_ai_fullscale_disable() is executed\n");

	tmp = me4000_inl(ai_context->ctrl_reg);
	tmp &= ~ME4000_AI_CTRL_BIT_FULLSCALE;
	me4000_outl(tmp, ai_context->ctrl_reg);

	return 0;
}

static int me4000_ai_fsm_state(int *arg, struct me4000_ai_context *ai_context)
{
	unsigned long tmp;

	CALL_PDEBUG("me4000_ai_fsm_state() is executed\n");

	tmp =
	    (me4000_inl(ai_context->status_reg) & ME4000_AI_STATUS_BIT_FSM) ? 1
	    : 0;

	if (put_user(tmp, arg)) {
		printk(KERN_ERR "me4000_ai_fsm_state():Cannot copy to user\n");
		return -EFAULT;
	}

	return 0;
}

static int me4000_ai_get_count_buffer(unsigned long *arg,
				      struct me4000_ai_context *ai_context)
{
	unsigned long c;
	int err;

	c = me4000_buf_count(ai_context->circ_buf, ME4000_AI_BUFFER_COUNT);

	err = copy_to_user(arg, &c, sizeof(unsigned long));
	if (err) {
		printk(KERN_ERR
		       "%s:Can't copy to user space\n", __func__);
		return -EFAULT;
	}

	return 0;
}

/*---------------------------------- EEPROM stuff ---------------------------*/

static int eeprom_write_cmd(struct me4000_ai_context *ai_context, unsigned long cmd,
			    int length)
{
	int i;
	unsigned long value;

	CALL_PDEBUG("eeprom_write_cmd() is executed\n");

	PDEBUG("eeprom_write_cmd():Write command 0x%08lX with length = %d\n",
	       cmd, length);

	/* Get the ICR register and clear the related bits */
	value = me4000_inl(ai_context->board_info->plx_regbase + PLX_ICR);
	value &= ~(PLX_ICR_MASK_EEPROM);
	me4000_outl(value, ai_context->board_info->plx_regbase + PLX_ICR);

	/* Raise the chip select */
	value |= PLX_ICR_BIT_EEPROM_CHIP_SELECT;
	me4000_outl(value, ai_context->board_info->plx_regbase + PLX_ICR);
	udelay(EEPROM_DELAY);

	for (i = 0; i < length; i++) {
		if (cmd & ((0x1 << (length - 1)) >> i))
			value |= PLX_ICR_BIT_EEPROM_WRITE;
		else
			value &= ~PLX_ICR_BIT_EEPROM_WRITE;

		/* Write to EEPROM */
		me4000_outl(value,
			    ai_context->board_info->plx_regbase + PLX_ICR);
		udelay(EEPROM_DELAY);

		/* Raising edge of the clock */
		value |= PLX_ICR_BIT_EEPROM_CLOCK_SET;
		me4000_outl(value,
			    ai_context->board_info->plx_regbase + PLX_ICR);
		udelay(EEPROM_DELAY);

		/* Falling edge of the clock */
		value &= ~PLX_ICR_BIT_EEPROM_CLOCK_SET;
		me4000_outl(value,
			    ai_context->board_info->plx_regbase + PLX_ICR);
		udelay(EEPROM_DELAY);
	}

	/* Clear the chip select */
	value &= ~PLX_ICR_BIT_EEPROM_CHIP_SELECT;
	me4000_outl(value, ai_context->board_info->plx_regbase + PLX_ICR);
	udelay(EEPROM_DELAY);

	/* Wait until hardware is ready for sure */
	mdelay(10);

	return 0;
}

static unsigned short eeprom_read_cmd(struct me4000_ai_context *ai_context,
				      unsigned long cmd, int length)
{
	int i;
	unsigned long value;
	unsigned short id = 0;

	CALL_PDEBUG("eeprom_read_cmd() is executed\n");

	PDEBUG("eeprom_read_cmd():Read command 0x%08lX with length = %d\n", cmd,
	       length);

	/* Get the ICR register and clear the related bits */
	value = me4000_inl(ai_context->board_info->plx_regbase + PLX_ICR);
	value &= ~(PLX_ICR_MASK_EEPROM);

	me4000_outl(value, ai_context->board_info->plx_regbase + PLX_ICR);

	/* Raise the chip select */
	value |= PLX_ICR_BIT_EEPROM_CHIP_SELECT;
	me4000_outl(value, ai_context->board_info->plx_regbase + PLX_ICR);
	udelay(EEPROM_DELAY);

	/* Write the read command to the eeprom */
	for (i = 0; i < length; i++) {
		if (cmd & ((0x1 << (length - 1)) >> i))
			value |= PLX_ICR_BIT_EEPROM_WRITE;
		else
			value &= ~PLX_ICR_BIT_EEPROM_WRITE;

		me4000_outl(value,
			    ai_context->board_info->plx_regbase + PLX_ICR);
		udelay(EEPROM_DELAY);

		/* Raising edge of the clock */
		value |= PLX_ICR_BIT_EEPROM_CLOCK_SET;
		me4000_outl(value,
			    ai_context->board_info->plx_regbase + PLX_ICR);
		udelay(EEPROM_DELAY);

		/* Falling edge of the clock */
		value &= ~PLX_ICR_BIT_EEPROM_CLOCK_SET;
		me4000_outl(value,
			    ai_context->board_info->plx_regbase + PLX_ICR);
		udelay(EEPROM_DELAY);
	}

	/* Read the value from the eeprom */
	for (i = 0; i < 16; i++) {
		/* Raising edge of the clock */
		value |= PLX_ICR_BIT_EEPROM_CLOCK_SET;
		me4000_outl(value,
			    ai_context->board_info->plx_regbase + PLX_ICR);
		udelay(EEPROM_DELAY);

		if (me4000_inl(ai_context->board_info->plx_regbase + PLX_ICR) &
		    PLX_ICR_BIT_EEPROM_READ) {
			id |= (0x8000 >> i);
			PDEBUG("eeprom_read_cmd():OR with 0x%04X\n",
			       (0x8000 >> i));
		} else {
			PDEBUG("eeprom_read_cmd():Dont't OR\n");
		}

		/* Falling edge of the clock */
		value &= ~PLX_ICR_BIT_EEPROM_CLOCK_SET;
		me4000_outl(value,
			    ai_context->board_info->plx_regbase + PLX_ICR);
		udelay(EEPROM_DELAY);
	}

	/* Clear the chip select */
	value &= ~PLX_ICR_BIT_EEPROM_CHIP_SELECT;
	me4000_outl(value, ai_context->board_info->plx_regbase + PLX_ICR);
	udelay(EEPROM_DELAY);

	return id;
}

static int me4000_eeprom_write(struct me4000_eeprom *arg,
			       struct me4000_ai_context *ai_context)
{
	int err;
	struct me4000_eeprom setup;
	unsigned long cmd;
	unsigned long date_high;
	unsigned long date_low;

	CALL_PDEBUG("me4000_eeprom_write() is executed\n");

	err = copy_from_user(&setup, arg, sizeof(setup));
	if (err) {
		printk(KERN_ERR
		       "ME4000:me4000_eeprom_write():Cannot copy from user\n");
		return err;
	}

	/* Enable writing */
	eeprom_write_cmd(ai_context, ME4000_EEPROM_CMD_WRITE_ENABLE,
			 ME4000_EEPROM_CMD_LENGTH_WRITE_ENABLE);

	/* Command for date */
	date_high = (setup.date & 0xFFFF0000) >> 16;
	date_low = (setup.date & 0x0000FFFF);

	cmd =
	    ME4000_EEPROM_CMD_WRITE | (ME4000_EEPROM_ADR_DATE_HIGH <<
				       ME4000_EEPROM_DATA_LENGTH) | (0xFFFF &
								     (unsigned
								      long)
								     date_high);
	err = eeprom_write_cmd(ai_context, cmd, ME4000_EEPROM_CMD_LENGTH_WRITE);
	if (err)
		return err;

	cmd =
	    ME4000_EEPROM_CMD_WRITE | (ME4000_EEPROM_ADR_DATE_LOW <<
				       ME4000_EEPROM_DATA_LENGTH) | (0xFFFF &
								     (unsigned
								      long)
								     date_low);
	err = eeprom_write_cmd(ai_context, cmd, ME4000_EEPROM_CMD_LENGTH_WRITE);
	if (err)
		return err;

	/* Command for unipolar 10V offset */
	cmd =
	    ME4000_EEPROM_CMD_WRITE | (ME4000_EEPROM_ADR_GAIN_1_UNI_OFFSET <<
				       ME4000_EEPROM_DATA_LENGTH) | (0xFFFF &
								     (unsigned
								      long)
								     setup.
								     uni_10_offset);
	err = eeprom_write_cmd(ai_context, cmd, ME4000_EEPROM_CMD_LENGTH_WRITE);
	if (err)
		return err;

	/* Command for unipolar 10V fullscale */
	cmd =
	    ME4000_EEPROM_CMD_WRITE | (ME4000_EEPROM_ADR_GAIN_1_UNI_FULLSCALE <<
				       ME4000_EEPROM_DATA_LENGTH) | (0xFFFF &
								     (unsigned
								      long)
								     setup.
								     uni_10_fullscale);
	err = eeprom_write_cmd(ai_context, cmd, ME4000_EEPROM_CMD_LENGTH_WRITE);
	if (err)
		return err;

	/* Command for unipolar 2,5V offset */
	cmd =
	    ME4000_EEPROM_CMD_WRITE | (ME4000_EEPROM_ADR_GAIN_4_UNI_OFFSET <<
				       ME4000_EEPROM_DATA_LENGTH) | (0xFFFF &
								     (unsigned
								      long)
								     setup.
								     uni_2_5_offset);
	err = eeprom_write_cmd(ai_context, cmd, ME4000_EEPROM_CMD_LENGTH_WRITE);
	if (err)
		return err;

	/* Command for unipolar 2,5V fullscale */
	cmd =
	    ME4000_EEPROM_CMD_WRITE | (ME4000_EEPROM_ADR_GAIN_4_UNI_FULLSCALE <<
				       ME4000_EEPROM_DATA_LENGTH) | (0xFFFF &
								     (unsigned
								      long)
								     setup.
								     uni_2_5_fullscale);
	err = eeprom_write_cmd(ai_context, cmd, ME4000_EEPROM_CMD_LENGTH_WRITE);
	if (err)
		return err;

	/* Command for bipolar 10V offset */
	cmd =
	    ME4000_EEPROM_CMD_WRITE | (ME4000_EEPROM_ADR_GAIN_1_BI_OFFSET <<
				       ME4000_EEPROM_DATA_LENGTH) | (0xFFFF &
								     (unsigned
								      long)
								     setup.
								     bi_10_offset);
	err = eeprom_write_cmd(ai_context, cmd, ME4000_EEPROM_CMD_LENGTH_WRITE);
	if (err)
		return err;

	/* Command for bipolar 10V fullscale */
	cmd =
	    ME4000_EEPROM_CMD_WRITE | (ME4000_EEPROM_ADR_GAIN_1_BI_FULLSCALE <<
				       ME4000_EEPROM_DATA_LENGTH) | (0xFFFF &
								     (unsigned
								      long)
								     setup.
								     bi_10_fullscale);
	err = eeprom_write_cmd(ai_context, cmd, ME4000_EEPROM_CMD_LENGTH_WRITE);
	if (err)
		return err;

	/* Command for bipolar 2,5V offset */
	cmd =
	    ME4000_EEPROM_CMD_WRITE | (ME4000_EEPROM_ADR_GAIN_4_BI_OFFSET <<
				       ME4000_EEPROM_DATA_LENGTH) | (0xFFFF &
								     (unsigned
								      long)
								     setup.
								     bi_2_5_offset);
	err = eeprom_write_cmd(ai_context, cmd, ME4000_EEPROM_CMD_LENGTH_WRITE);
	if (err)
		return err;

	/* Command for bipolar 2,5V fullscale */
	cmd =
	    ME4000_EEPROM_CMD_WRITE | (ME4000_EEPROM_ADR_GAIN_4_BI_FULLSCALE <<
				       ME4000_EEPROM_DATA_LENGTH) | (0xFFFF &
								     (unsigned
								      long)
								     setup.
								     bi_2_5_fullscale);
	err = eeprom_write_cmd(ai_context, cmd, ME4000_EEPROM_CMD_LENGTH_WRITE);
	if (err)
		return err;

	/* Command for differential 10V offset */
	cmd =
	    ME4000_EEPROM_CMD_WRITE | (ME4000_EEPROM_ADR_GAIN_1_DIFF_OFFSET <<
				       ME4000_EEPROM_DATA_LENGTH) | (0xFFFF &
								     (unsigned
								      long)
								     setup.
								     diff_10_offset);
	err = eeprom_write_cmd(ai_context, cmd, ME4000_EEPROM_CMD_LENGTH_WRITE);
	if (err)
		return err;

	/* Command for differential 10V fullscale */
	cmd =
	    ME4000_EEPROM_CMD_WRITE | (ME4000_EEPROM_ADR_GAIN_1_DIFF_FULLSCALE
				       << ME4000_EEPROM_DATA_LENGTH) | (0xFFFF &
									(unsigned
									 long)
									setup.
									diff_10_fullscale);
	err = eeprom_write_cmd(ai_context, cmd, ME4000_EEPROM_CMD_LENGTH_WRITE);
	if (err)
		return err;

	/* Command for differential 2,5V offset */
	cmd =
	    ME4000_EEPROM_CMD_WRITE | (ME4000_EEPROM_ADR_GAIN_4_DIFF_OFFSET <<
				       ME4000_EEPROM_DATA_LENGTH) | (0xFFFF &
								     (unsigned
								      long)
								     setup.
								     diff_2_5_offset);
	err = eeprom_write_cmd(ai_context, cmd, ME4000_EEPROM_CMD_LENGTH_WRITE);
	if (err)
		return err;

	/* Command for differential 2,5V fullscale */
	cmd =
	    ME4000_EEPROM_CMD_WRITE | (ME4000_EEPROM_ADR_GAIN_4_DIFF_FULLSCALE
				       << ME4000_EEPROM_DATA_LENGTH) | (0xFFFF &
									(unsigned
									 long)
									setup.
									diff_2_5_fullscale);
	err = eeprom_write_cmd(ai_context, cmd, ME4000_EEPROM_CMD_LENGTH_WRITE);
	if (err)
		return err;

	/* Disable writing */
	eeprom_write_cmd(ai_context, ME4000_EEPROM_CMD_WRITE_DISABLE,
			 ME4000_EEPROM_CMD_LENGTH_WRITE_DISABLE);

	return 0;
}

static int me4000_eeprom_read(struct me4000_eeprom *arg,
			      struct me4000_ai_context *ai_context)
{
	int err;
	unsigned long cmd;
	struct me4000_eeprom setup;

	CALL_PDEBUG("me4000_eeprom_read() is executed\n");

	/* Command for date */
	cmd = ME4000_EEPROM_CMD_READ | ME4000_EEPROM_ADR_DATE_HIGH;
	setup.date =
	    eeprom_read_cmd(ai_context, cmd, ME4000_EEPROM_CMD_LENGTH_READ);
	setup.date <<= 16;
	cmd = ME4000_EEPROM_CMD_READ | ME4000_EEPROM_ADR_DATE_LOW;
	setup.date |=
	    eeprom_read_cmd(ai_context, cmd, ME4000_EEPROM_CMD_LENGTH_READ);

	/* Command for unipolar 10V offset */
	cmd = ME4000_EEPROM_CMD_READ | ME4000_EEPROM_ADR_GAIN_1_UNI_OFFSET;
	setup.uni_10_offset =
	    eeprom_read_cmd(ai_context, cmd, ME4000_EEPROM_CMD_LENGTH_READ);

	/* Command for unipolar 10V fullscale */
	cmd = ME4000_EEPROM_CMD_READ | ME4000_EEPROM_ADR_GAIN_1_UNI_FULLSCALE;
	setup.uni_10_fullscale =
	    eeprom_read_cmd(ai_context, cmd, ME4000_EEPROM_CMD_LENGTH_READ);

	/* Command for unipolar 2,5V offset */
	cmd = ME4000_EEPROM_CMD_READ | ME4000_EEPROM_ADR_GAIN_4_UNI_OFFSET;
	setup.uni_2_5_offset =
	    eeprom_read_cmd(ai_context, cmd, ME4000_EEPROM_CMD_LENGTH_READ);

	/* Command for unipolar 2,5V fullscale */
	cmd = ME4000_EEPROM_CMD_READ | ME4000_EEPROM_ADR_GAIN_4_UNI_FULLSCALE;
	setup.uni_2_5_fullscale =
	    eeprom_read_cmd(ai_context, cmd, ME4000_EEPROM_CMD_LENGTH_READ);

	/* Command for bipolar 10V offset */
	cmd = ME4000_EEPROM_CMD_READ | ME4000_EEPROM_ADR_GAIN_1_BI_OFFSET;
	setup.bi_10_offset =
	    eeprom_read_cmd(ai_context, cmd, ME4000_EEPROM_CMD_LENGTH_READ);

	/* Command for bipolar 10V fullscale */
	cmd = ME4000_EEPROM_CMD_READ | ME4000_EEPROM_ADR_GAIN_1_BI_FULLSCALE;
	setup.bi_10_fullscale =
	    eeprom_read_cmd(ai_context, cmd, ME4000_EEPROM_CMD_LENGTH_READ);

	/* Command for bipolar 2,5V offset */
	cmd = ME4000_EEPROM_CMD_READ | ME4000_EEPROM_ADR_GAIN_4_BI_OFFSET;
	setup.bi_2_5_offset =
	    eeprom_read_cmd(ai_context, cmd, ME4000_EEPROM_CMD_LENGTH_READ);

	/* Command for bipolar 2,5V fullscale */
	cmd = ME4000_EEPROM_CMD_READ | ME4000_EEPROM_ADR_GAIN_4_BI_FULLSCALE;
	setup.bi_2_5_fullscale =
	    eeprom_read_cmd(ai_context, cmd, ME4000_EEPROM_CMD_LENGTH_READ);

	/* Command for differntial 10V offset */
	cmd = ME4000_EEPROM_CMD_READ | ME4000_EEPROM_ADR_GAIN_1_DIFF_OFFSET;
	setup.diff_10_offset =
	    eeprom_read_cmd(ai_context, cmd, ME4000_EEPROM_CMD_LENGTH_READ);

	/* Command for differential 10V fullscale */
	cmd = ME4000_EEPROM_CMD_READ | ME4000_EEPROM_ADR_GAIN_1_DIFF_FULLSCALE;
	setup.diff_10_fullscale =
	    eeprom_read_cmd(ai_context, cmd, ME4000_EEPROM_CMD_LENGTH_READ);

	/* Command for differntial 2,5V offset */
	cmd = ME4000_EEPROM_CMD_READ | ME4000_EEPROM_ADR_GAIN_4_DIFF_OFFSET;
	setup.diff_2_5_offset =
	    eeprom_read_cmd(ai_context, cmd, ME4000_EEPROM_CMD_LENGTH_READ);

	/* Command for differential 2,5V fullscale */
	cmd = ME4000_EEPROM_CMD_READ | ME4000_EEPROM_ADR_GAIN_4_DIFF_FULLSCALE;
	setup.diff_2_5_fullscale =
	    eeprom_read_cmd(ai_context, cmd, ME4000_EEPROM_CMD_LENGTH_READ);

	err = copy_to_user(arg, &setup, sizeof(setup));
	if (err) {
		printk(KERN_ERR
		       "ME4000:me4000_eeprom_read():Cannot copy to user\n");
		return err;
	}

	return 0;
}

/*------------------------------------ DIO stuff ----------------------------------------------*/

static int me4000_dio_ioctl(struct inode *inode_p, struct file *file_p,
			    unsigned int service, unsigned long arg)
{
	struct me4000_dio_context *dio_context;

	CALL_PDEBUG("me4000_dio_ioctl() is executed\n");

	dio_context = file_p->private_data;

	if (_IOC_TYPE(service) != ME4000_MAGIC) {
		printk(KERN_ERR "me4000_dio_ioctl():Wrong magic number\n");
		return -ENOTTY;
	}
	if (_IOC_NR(service) > ME4000_IOCTL_MAXNR) {
		printk(KERN_ERR "me4000_dio_ioctl():Service number to high\n");
		return -ENOTTY;
	}

	switch (service) {
	case ME4000_DIO_CONFIG:
		return me4000_dio_config((struct me4000_dio_config *)arg,
					 dio_context);
	case ME4000_DIO_SET_BYTE:
		return me4000_dio_set_byte((struct me4000_dio_byte *)arg,
					   dio_context);
	case ME4000_DIO_GET_BYTE:
		return me4000_dio_get_byte((struct me4000_dio_byte *)arg,
					   dio_context);
	case ME4000_DIO_RESET:
		return me4000_dio_reset(dio_context);
	default:
		printk(KERN_ERR
		       "ME4000:me4000_dio_ioctl():Invalid service number %d\n",
		       service);
		return -ENOTTY;
	}
	return 0;
}

static int me4000_dio_config(struct me4000_dio_config *arg,
			     struct me4000_dio_context *dio_context)
{
	struct me4000_dio_config cmd;
	u32 tmp;
	int err;

	CALL_PDEBUG("me4000_dio_config() is executed\n");

	/* Copy data from user */
	err = copy_from_user(&cmd, arg, sizeof(struct me4000_dio_config));
	if (err) {
		printk(KERN_ERR
		       "ME4000:me4000_dio_config():Can't copy from user space\n");
		return -EFAULT;
	}

	/* Check port parameter */
	if (cmd.port >= dio_context->dio_count) {
		printk(KERN_ERR
		       "ME4000:me4000_dio_config():Port %d is not available\n",
		       cmd.port);
		return -EINVAL;
	}

	PDEBUG("me4000_dio_config(): port %d, mode %d, function %d\n", cmd.port,
	       cmd.mode, cmd.function);

	if (cmd.port == ME4000_DIO_PORT_A) {
		if (cmd.mode == ME4000_DIO_PORT_INPUT) {
			/* Check if opto isolated version */
			if (!(me4000_inl(dio_context->dir_reg) & 0x1)) {
				printk(KERN_ERR
				       "ME4000:me4000_dio_config():Cannot set to input on opto isolated versions\n");
				return -EIO;
			}

			tmp = me4000_inl(dio_context->ctrl_reg);
			tmp &=
			    ~(ME4000_DIO_CTRL_BIT_MODE_0 |
			      ME4000_DIO_CTRL_BIT_MODE_1);
			me4000_outl(tmp, dio_context->ctrl_reg);
		} else if (cmd.mode == ME4000_DIO_PORT_OUTPUT) {
			tmp = me4000_inl(dio_context->ctrl_reg);
			tmp &=
			    ~(ME4000_DIO_CTRL_BIT_MODE_0 |
			      ME4000_DIO_CTRL_BIT_MODE_1);
			tmp |= ME4000_DIO_CTRL_BIT_MODE_0;
			me4000_outl(tmp, dio_context->ctrl_reg);
		} else if (cmd.mode == ME4000_DIO_FIFO_LOW) {
			tmp = me4000_inl(dio_context->ctrl_reg);
			tmp &=
			    ~(ME4000_DIO_CTRL_BIT_MODE_0 |
			      ME4000_DIO_CTRL_BIT_MODE_1 |
			      ME4000_DIO_CTRL_BIT_FIFO_HIGH_0);
			tmp |=
			    ME4000_DIO_CTRL_BIT_MODE_0 |
			    ME4000_DIO_CTRL_BIT_MODE_1;
			me4000_outl(tmp, dio_context->ctrl_reg);
		} else if (cmd.mode == ME4000_DIO_FIFO_HIGH) {
			tmp = me4000_inl(dio_context->ctrl_reg);
			tmp |=
			    ME4000_DIO_CTRL_BIT_MODE_0 |
			    ME4000_DIO_CTRL_BIT_MODE_1 |
			    ME4000_DIO_CTRL_BIT_FIFO_HIGH_0;
			me4000_outl(tmp, dio_context->ctrl_reg);
		} else {
			printk(KERN_ERR
			       "ME4000:me4000_dio_config():Mode %d is not available\n",
			       cmd.mode);
			return -EINVAL;
		}
	} else if (cmd.port == ME4000_DIO_PORT_B) {
		if (cmd.mode == ME4000_DIO_PORT_INPUT) {
			/* Only do anything when TTL version is installed */
			if ((me4000_inl(dio_context->dir_reg) & 0x1)) {
				tmp = me4000_inl(dio_context->ctrl_reg);
				tmp &=
				    ~(ME4000_DIO_CTRL_BIT_MODE_2 |
				      ME4000_DIO_CTRL_BIT_MODE_3);
				me4000_outl(tmp, dio_context->ctrl_reg);
			}
		} else if (cmd.mode == ME4000_DIO_PORT_OUTPUT) {
			/* Check if opto isolated version */
			if (!(me4000_inl(dio_context->dir_reg) & 0x1)) {
				printk(KERN_ERR
				       "ME4000:me4000_dio_config():Cannot set to output on opto isolated versions\n");
				return -EIO;
			}

			tmp = me4000_inl(dio_context->ctrl_reg);
			tmp &=
			    ~(ME4000_DIO_CTRL_BIT_MODE_2 |
			      ME4000_DIO_CTRL_BIT_MODE_3);
			tmp |= ME4000_DIO_CTRL_BIT_MODE_2;
			me4000_outl(tmp, dio_context->ctrl_reg);
		} else if (cmd.mode == ME4000_DIO_FIFO_LOW) {
			/* Check if opto isolated version */
			if (!(me4000_inl(dio_context->dir_reg) & 0x1)) {
				printk(KERN_ERR
				       "ME4000:me4000_dio_config():Cannot set to FIFO low output on opto isolated versions\n");
				return -EIO;
			}

			tmp = me4000_inl(dio_context->ctrl_reg);
			tmp &=
			    ~(ME4000_DIO_CTRL_BIT_MODE_2 |
			      ME4000_DIO_CTRL_BIT_MODE_3 |
			      ME4000_DIO_CTRL_BIT_FIFO_HIGH_1);
			tmp |=
			    ME4000_DIO_CTRL_BIT_MODE_2 |
			    ME4000_DIO_CTRL_BIT_MODE_3;
			me4000_outl(tmp, dio_context->ctrl_reg);
		} else if (cmd.mode == ME4000_DIO_FIFO_HIGH) {
			/* Check if opto isolated version */
			if (!(me4000_inl(dio_context->dir_reg) & 0x1)) {
				printk(KERN_ERR
				       "ME4000:me4000_dio_config():Cannot set to FIFO high output on opto isolated versions\n");
				return -EIO;
			}

			tmp = me4000_inl(dio_context->ctrl_reg);
			tmp |=
			    ME4000_DIO_CTRL_BIT_MODE_2 |
			    ME4000_DIO_CTRL_BIT_MODE_3 |
			    ME4000_DIO_CTRL_BIT_FIFO_HIGH_1;
			me4000_outl(tmp, dio_context->ctrl_reg);
		} else {
			printk(KERN_ERR
			       "ME4000:me4000_dio_config():Mode %d is not available\n",
			       cmd.mode);
			return -EINVAL;
		}
	} else if (cmd.port == ME4000_DIO_PORT_C) {
		if (cmd.mode == ME4000_DIO_PORT_INPUT) {
			tmp = me4000_inl(dio_context->ctrl_reg);
			tmp &=
			    ~(ME4000_DIO_CTRL_BIT_MODE_4 |
			      ME4000_DIO_CTRL_BIT_MODE_5);
			me4000_outl(tmp, dio_context->ctrl_reg);
		} else if (cmd.mode == ME4000_DIO_PORT_OUTPUT) {
			tmp = me4000_inl(dio_context->ctrl_reg);
			tmp &=
			    ~(ME4000_DIO_CTRL_BIT_MODE_4 |
			      ME4000_DIO_CTRL_BIT_MODE_5);
			tmp |= ME4000_DIO_CTRL_BIT_MODE_4;
			me4000_outl(tmp, dio_context->ctrl_reg);
		} else if (cmd.mode == ME4000_DIO_FIFO_LOW) {
			tmp = me4000_inl(dio_context->ctrl_reg);
			tmp &=
			    ~(ME4000_DIO_CTRL_BIT_MODE_4 |
			      ME4000_DIO_CTRL_BIT_MODE_5 |
			      ME4000_DIO_CTRL_BIT_FIFO_HIGH_2);
			tmp |=
			    ME4000_DIO_CTRL_BIT_MODE_4 |
			    ME4000_DIO_CTRL_BIT_MODE_5;
			me4000_outl(tmp, dio_context->ctrl_reg);
		} else if (cmd.mode == ME4000_DIO_FIFO_HIGH) {
			tmp = me4000_inl(dio_context->ctrl_reg);
			tmp |=
			    ME4000_DIO_CTRL_BIT_MODE_4 |
			    ME4000_DIO_CTRL_BIT_MODE_5 |
			    ME4000_DIO_CTRL_BIT_FIFO_HIGH_2;
			me4000_outl(tmp, dio_context->ctrl_reg);
		} else {
			printk(KERN_ERR
			       "ME4000:me4000_dio_config():Mode %d is not available\n",
			       cmd.mode);
			return -EINVAL;
		}
	} else if (cmd.port == ME4000_DIO_PORT_D) {
		if (cmd.mode == ME4000_DIO_PORT_INPUT) {
			tmp = me4000_inl(dio_context->ctrl_reg);
			tmp &=
			    ~(ME4000_DIO_CTRL_BIT_MODE_6 |
			      ME4000_DIO_CTRL_BIT_MODE_7);
			me4000_outl(tmp, dio_context->ctrl_reg);
		} else if (cmd.mode == ME4000_DIO_PORT_OUTPUT) {
			tmp = me4000_inl(dio_context->ctrl_reg);
			tmp &=
			    ~(ME4000_DIO_CTRL_BIT_MODE_6 |
			      ME4000_DIO_CTRL_BIT_MODE_7);
			tmp |= ME4000_DIO_CTRL_BIT_MODE_6;
			me4000_outl(tmp, dio_context->ctrl_reg);
		} else if (cmd.mode == ME4000_DIO_FIFO_LOW) {
			tmp = me4000_inl(dio_context->ctrl_reg);
			tmp &=
			    ~(ME4000_DIO_CTRL_BIT_MODE_6 |
			      ME4000_DIO_CTRL_BIT_MODE_7 |
			      ME4000_DIO_CTRL_BIT_FIFO_HIGH_3);
			tmp |=
			    ME4000_DIO_CTRL_BIT_MODE_6 |
			    ME4000_DIO_CTRL_BIT_MODE_7;
			me4000_outl(tmp, dio_context->ctrl_reg);
		} else if (cmd.mode == ME4000_DIO_FIFO_HIGH) {
			tmp = me4000_inl(dio_context->ctrl_reg);
			tmp |=
			    ME4000_DIO_CTRL_BIT_MODE_6 |
			    ME4000_DIO_CTRL_BIT_MODE_7 |
			    ME4000_DIO_CTRL_BIT_FIFO_HIGH_3;
			me4000_outl(tmp, dio_context->ctrl_reg);
		} else {
			printk(KERN_ERR
			       "ME4000:me4000_dio_config():Mode %d is not available\n",
			       cmd.mode);
			return -EINVAL;
		}
	} else {
		printk(KERN_ERR
		       "ME4000:me4000_dio_config():Port %d is not available\n",
		       cmd.port);
		return -EINVAL;
	}

	PDEBUG("me4000_dio_config(): port %d, mode %d, function %d\n", cmd.port,
	       cmd.mode, cmd.function);

	if ((cmd.mode == ME4000_DIO_FIFO_HIGH)
	    || (cmd.mode == ME4000_DIO_FIFO_LOW)) {
		tmp = me4000_inl(dio_context->ctrl_reg);
		tmp &=
		    ~(ME4000_DIO_CTRL_BIT_FUNCTION_0 |
		      ME4000_DIO_CTRL_BIT_FUNCTION_1);
		if (cmd.function == ME4000_DIO_FUNCTION_PATTERN) {
			me4000_outl(tmp, dio_context->ctrl_reg);
		} else if (cmd.function == ME4000_DIO_FUNCTION_DEMUX) {
			tmp |= ME4000_DIO_CTRL_BIT_FUNCTION_0;
			me4000_outl(tmp, dio_context->ctrl_reg);
		} else if (cmd.function == ME4000_DIO_FUNCTION_MUX) {
			tmp |= ME4000_DIO_CTRL_BIT_FUNCTION_1;
			me4000_outl(tmp, dio_context->ctrl_reg);
		} else {
			printk(KERN_ERR
			       "ME4000:me4000_dio_config():Invalid port function specified\n");
			return -EINVAL;
		}
	}

	return 0;
}

static int me4000_dio_set_byte(struct me4000_dio_byte *arg,
			       struct me4000_dio_context *dio_context)
{
	struct me4000_dio_byte cmd;
	int err;

	CALL_PDEBUG("me4000_dio_set_byte() is executed\n");

	/* Copy data from user */
	err = copy_from_user(&cmd, arg, sizeof(struct me4000_dio_byte));
	if (err) {
		printk(KERN_ERR
		       "ME4000:me4000_dio_set_byte():Can't copy from user space\n");
		return -EFAULT;
	}

	/* Check port parameter */
	if (cmd.port >= dio_context->dio_count) {
		printk(KERN_ERR
		       "ME4000:me4000_dio_set_byte():Port %d is not available\n",
		       cmd.port);
		return -EINVAL;
	}

	if (cmd.port == ME4000_DIO_PORT_A) {
		if ((me4000_inl(dio_context->ctrl_reg) & 0x3) != 0x1) {
			printk(KERN_ERR
			       "ME4000:me4000_dio_set_byte():Port %d is not in output mode\n",
			       cmd.port);
			return -EIO;
		}
		me4000_outl(cmd.byte, dio_context->port_0_reg);
	} else if (cmd.port == ME4000_DIO_PORT_B) {
		if ((me4000_inl(dio_context->ctrl_reg) & 0xC) != 0x4) {
			printk(KERN_ERR
			       "ME4000:me4000_dio_set_byte():Port %d is not in output mode\n",
			       cmd.port);
			return -EIO;
		}
		me4000_outl(cmd.byte, dio_context->port_1_reg);
	} else if (cmd.port == ME4000_DIO_PORT_C) {
		if ((me4000_inl(dio_context->ctrl_reg) & 0x30) != 0x10) {
			printk(KERN_ERR
			       "ME4000:me4000_dio_set_byte():Port %d is not in output mode\n",
			       cmd.port);
			return -EIO;
		}
		me4000_outl(cmd.byte, dio_context->port_2_reg);
	} else if (cmd.port == ME4000_DIO_PORT_D) {
		if ((me4000_inl(dio_context->ctrl_reg) & 0xC0) != 0x40) {
			printk(KERN_ERR
			       "ME4000:me4000_dio_set_byte():Port %d is not in output mode\n",
			       cmd.port);
			return -EIO;
		}
		me4000_outl(cmd.byte, dio_context->port_3_reg);
	} else {
		printk(KERN_ERR
		       "ME4000:me4000_dio_set_byte():Port %d is not available\n",
		       cmd.port);
		return -EINVAL;
	}

	return 0;
}

static int me4000_dio_get_byte(struct me4000_dio_byte *arg,
			       struct me4000_dio_context *dio_context)
{
	struct me4000_dio_byte cmd;
	int err;

	CALL_PDEBUG("me4000_dio_get_byte() is executed\n");

	/* Copy data from user */
	err = copy_from_user(&cmd, arg, sizeof(struct me4000_dio_byte));
	if (err) {
		printk(KERN_ERR
		       "ME4000:me4000_dio_get_byte():Can't copy from user space\n");
		return -EFAULT;
	}

	/* Check port parameter */
	if (cmd.port >= dio_context->dio_count) {
		printk(KERN_ERR
		       "ME4000:me4000_dio_get_byte():Port %d is not available\n",
		       cmd.port);
		return -EINVAL;
	}

	if (cmd.port == ME4000_DIO_PORT_A) {
		cmd.byte = me4000_inl(dio_context->port_0_reg) & 0xFF;
	} else if (cmd.port == ME4000_DIO_PORT_B) {
		cmd.byte = me4000_inl(dio_context->port_1_reg) & 0xFF;
	} else if (cmd.port == ME4000_DIO_PORT_C) {
		cmd.byte = me4000_inl(dio_context->port_2_reg) & 0xFF;
	} else if (cmd.port == ME4000_DIO_PORT_D) {
		cmd.byte = me4000_inl(dio_context->port_3_reg) & 0xFF;
	} else {
		printk(KERN_ERR
		       "ME4000:me4000_dio_get_byte():Port %d is not available\n",
		       cmd.port);
		return -EINVAL;
	}

	/* Copy result back to user */
	err = copy_to_user(arg, &cmd, sizeof(struct me4000_dio_byte));
	if (err) {
		printk(KERN_ERR
		       "ME4000:me4000_dio_get_byte():Can't copy to user space\n");
		return -EFAULT;
	}

	return 0;
}

static int me4000_dio_reset(struct me4000_dio_context *dio_context)
{
	CALL_PDEBUG("me4000_dio_reset() is executed\n");

	/* Clear the control register */
	me4000_outl(0, dio_context->ctrl_reg);

	/* Check for opto isolated version */
	if (!(me4000_inl(dio_context->dir_reg) & 0x1)) {
		me4000_outl(0x1, dio_context->ctrl_reg);
		me4000_outl(0x0, dio_context->port_0_reg);
	}

	return 0;
}

/*------------------------------------ COUNTER STUFF ------------------------------------*/

static int me4000_cnt_ioctl(struct inode *inode_p, struct file *file_p,
			    unsigned int service, unsigned long arg)
{
	struct me4000_cnt_context *cnt_context;

	CALL_PDEBUG("me4000_cnt_ioctl() is executed\n");

	cnt_context = file_p->private_data;

	if (_IOC_TYPE(service) != ME4000_MAGIC) {
		printk(KERN_ERR "me4000_dio_ioctl():Wrong magic number\n");
		return -ENOTTY;
	}
	if (_IOC_NR(service) > ME4000_IOCTL_MAXNR) {
		printk(KERN_ERR "me4000_dio_ioctl():Service number to high\n");
		return -ENOTTY;
	}

	switch (service) {
	case ME4000_CNT_READ:
		return me4000_cnt_read((struct me4000_cnt *)arg, cnt_context);
	case ME4000_CNT_WRITE:
		return me4000_cnt_write((struct me4000_cnt *)arg, cnt_context);
	case ME4000_CNT_CONFIG:
		return me4000_cnt_config((struct me4000_cnt_config *)arg,
					 cnt_context);
	case ME4000_CNT_RESET:
		return me4000_cnt_reset(cnt_context);
	default:
		printk(KERN_ERR
		       "ME4000:me4000_dio_ioctl():Invalid service number %d\n",
		       service);
		return -ENOTTY;
	}
	return 0;
}

static int me4000_cnt_config(struct me4000_cnt_config *arg,
			     struct me4000_cnt_context *cnt_context)
{
	struct me4000_cnt_config cmd;
	u8 counter;
	u8 mode;
	int err;

	CALL_PDEBUG("me4000_cnt_config() is executed\n");

	/* Copy data from user */
	err = copy_from_user(&cmd, arg, sizeof(struct me4000_cnt_config));
	if (err) {
		printk(KERN_ERR
		       "ME4000:me4000_cnt_config():Can't copy from user space\n");
		return -EFAULT;
	}

	/* Check counter parameter */
	switch (cmd.counter) {
	case ME4000_CNT_COUNTER_0:
		counter = ME4000_CNT_CTRL_BIT_COUNTER_0;
		break;
	case ME4000_CNT_COUNTER_1:
		counter = ME4000_CNT_CTRL_BIT_COUNTER_1;
		break;
	case ME4000_CNT_COUNTER_2:
		counter = ME4000_CNT_CTRL_BIT_COUNTER_2;
		break;
	default:
		printk(KERN_ERR
		       "ME4000:me4000_cnt_config():Counter %d is not available\n",
		       cmd.counter);
		return -EINVAL;
	}

	/* Check mode parameter */
	switch (cmd.mode) {
	case ME4000_CNT_MODE_0:
		mode = ME4000_CNT_CTRL_BIT_MODE_0;
		break;
	case ME4000_CNT_MODE_1:
		mode = ME4000_CNT_CTRL_BIT_MODE_1;
		break;
	case ME4000_CNT_MODE_2:
		mode = ME4000_CNT_CTRL_BIT_MODE_2;
		break;
	case ME4000_CNT_MODE_3:
		mode = ME4000_CNT_CTRL_BIT_MODE_3;
		break;
	case ME4000_CNT_MODE_4:
		mode = ME4000_CNT_CTRL_BIT_MODE_4;
		break;
	case ME4000_CNT_MODE_5:
		mode = ME4000_CNT_CTRL_BIT_MODE_5;
		break;
	default:
		printk(KERN_ERR
		       "ME4000:me4000_cnt_config():Mode %d is not available\n",
		       cmd.mode);
		return -EINVAL;
	}

	/* Write the control word */
	me4000_outb((counter | mode | 0x30), cnt_context->ctrl_reg);

	return 0;
}

static int me4000_cnt_read(struct me4000_cnt *arg,
			   struct me4000_cnt_context *cnt_context)
{
	struct me4000_cnt cmd;
	u8 tmp;
	int err;

	CALL_PDEBUG("me4000_cnt_read() is executed\n");

	/* Copy data from user */
	err = copy_from_user(&cmd, arg, sizeof(struct me4000_cnt));
	if (err) {
		printk(KERN_ERR
		       "ME4000:me4000_cnt_read():Can't copy from user space\n");
		return -EFAULT;
	}

	/* Read counter */
	switch (cmd.counter) {
	case ME4000_CNT_COUNTER_0:
		tmp = me4000_inb(cnt_context->counter_0_reg);
		cmd.value = tmp;
		tmp = me4000_inb(cnt_context->counter_0_reg);
		cmd.value |= ((u16) tmp) << 8;
		break;
	case ME4000_CNT_COUNTER_1:
		tmp = me4000_inb(cnt_context->counter_1_reg);
		cmd.value = tmp;
		tmp = me4000_inb(cnt_context->counter_1_reg);
		cmd.value |= ((u16) tmp) << 8;
		break;
	case ME4000_CNT_COUNTER_2:
		tmp = me4000_inb(cnt_context->counter_2_reg);
		cmd.value = tmp;
		tmp = me4000_inb(cnt_context->counter_2_reg);
		cmd.value |= ((u16) tmp) << 8;
		break;
	default:
		printk(KERN_ERR
		       "ME4000:me4000_cnt_read():Counter %d is not available\n",
		       cmd.counter);
		return -EINVAL;
	}

	/* Copy result back to user */
	err = copy_to_user(arg, &cmd, sizeof(struct me4000_cnt));
	if (err) {
		printk(KERN_ERR
		       "ME4000:me4000_cnt_read():Can't copy to user space\n");
		return -EFAULT;
	}

	return 0;
}

static int me4000_cnt_write(struct me4000_cnt *arg,
			    struct me4000_cnt_context *cnt_context)
{
	struct me4000_cnt cmd;
	u8 tmp;
	int err;

	CALL_PDEBUG("me4000_cnt_write() is executed\n");

	/* Copy data from user */
	err = copy_from_user(&cmd, arg, sizeof(struct me4000_cnt));
	if (err) {
		printk(KERN_ERR
		       "ME4000:me4000_cnt_write():Can't copy from user space\n");
		return -EFAULT;
	}

	/* Write counter */
	switch (cmd.counter) {
	case ME4000_CNT_COUNTER_0:
		tmp = cmd.value & 0xFF;
		me4000_outb(tmp, cnt_context->counter_0_reg);
		tmp = (cmd.value >> 8) & 0xFF;
		me4000_outb(tmp, cnt_context->counter_0_reg);
		break;
	case ME4000_CNT_COUNTER_1:
		tmp = cmd.value & 0xFF;
		me4000_outb(tmp, cnt_context->counter_1_reg);
		tmp = (cmd.value >> 8) & 0xFF;
		me4000_outb(tmp, cnt_context->counter_1_reg);
		break;
	case ME4000_CNT_COUNTER_2:
		tmp = cmd.value & 0xFF;
		me4000_outb(tmp, cnt_context->counter_2_reg);
		tmp = (cmd.value >> 8) & 0xFF;
		me4000_outb(tmp, cnt_context->counter_2_reg);
		break;
	default:
		printk(KERN_ERR
		       "ME4000:me4000_cnt_write():Counter %d is not available\n",
		       cmd.counter);
		return -EINVAL;
	}

	return 0;
}

static int me4000_cnt_reset(struct me4000_cnt_context *cnt_context)
{
	CALL_PDEBUG("me4000_cnt_reset() is executed\n");

	/* Set the mode and value for counter 0 */
	me4000_outb(0x30, cnt_context->ctrl_reg);
	me4000_outb(0x00, cnt_context->counter_0_reg);
	me4000_outb(0x00, cnt_context->counter_0_reg);

	/* Set the mode and value for counter 1 */
	me4000_outb(0x70, cnt_context->ctrl_reg);
	me4000_outb(0x00, cnt_context->counter_1_reg);
	me4000_outb(0x00, cnt_context->counter_1_reg);

	/* Set the mode and value for counter 2 */
	me4000_outb(0xB0, cnt_context->ctrl_reg);
	me4000_outb(0x00, cnt_context->counter_2_reg);
	me4000_outb(0x00, cnt_context->counter_2_reg);

	return 0;
}

/*------------------------------------ External Interrupt stuff ------------------------------------*/

static int me4000_ext_int_ioctl(struct inode *inode_p, struct file *file_p,
				unsigned int service, unsigned long arg)
{
	struct me4000_ext_int_context *ext_int_context;

	CALL_PDEBUG("me4000_ext_int_ioctl() is executed\n");

	ext_int_context = file_p->private_data;

	if (_IOC_TYPE(service) != ME4000_MAGIC) {
		printk(KERN_ERR "me4000_ext_int_ioctl():Wrong magic number\n");
		return -ENOTTY;
	}
	if (_IOC_NR(service) > ME4000_IOCTL_MAXNR) {
		printk(KERN_ERR
		       "me4000_ext_int_ioctl():Service number to high\n");
		return -ENOTTY;
	}

	switch (service) {
	case ME4000_EXT_INT_ENABLE:
		return me4000_ext_int_enable(ext_int_context);
	case ME4000_EXT_INT_DISABLE:
		return me4000_ext_int_disable(ext_int_context);
	case ME4000_EXT_INT_COUNT:
		return me4000_ext_int_count((unsigned long *)arg,
					    ext_int_context);
	default:
		printk(KERN_ERR
		       "ME4000:me4000_ext_int_ioctl():Invalid service number %d\n",
		       service);
		return -ENOTTY;
	}
	return 0;
}

static int me4000_ext_int_enable(struct me4000_ext_int_context *ext_int_context)
{
	unsigned long tmp;

	CALL_PDEBUG("me4000_ext_int_enable() is executed\n");

	tmp = me4000_inl(ext_int_context->ctrl_reg);
	tmp |= ME4000_AI_CTRL_BIT_EX_IRQ;
	me4000_outl(tmp, ext_int_context->ctrl_reg);

	return 0;
}

static int me4000_ext_int_disable(struct me4000_ext_int_context *ext_int_context)
{
	unsigned long tmp;

	CALL_PDEBUG("me4000_ext_int_disable() is executed\n");

	tmp = me4000_inl(ext_int_context->ctrl_reg);
	tmp &= ~ME4000_AI_CTRL_BIT_EX_IRQ;
	me4000_outl(tmp, ext_int_context->ctrl_reg);

	return 0;
}

static int me4000_ext_int_count(unsigned long *arg,
				struct me4000_ext_int_context *ext_int_context)
{

	CALL_PDEBUG("me4000_ext_int_count() is executed\n");

	put_user(ext_int_context->int_count, arg);
	return 0;
}

/*------------------------------------ General stuff ------------------------------------*/

static int me4000_get_user_info(struct me4000_user_info *arg,
				struct me4000_info *board_info)
{
	struct me4000_user_info user_info;

	CALL_PDEBUG("me4000_get_user_info() is executed\n");

	user_info.board_count = board_info->board_count;
	user_info.plx_regbase = board_info->plx_regbase;
	user_info.plx_regbase_size = board_info->plx_regbase_size;
	user_info.me4000_regbase = board_info->me4000_regbase;
	user_info.me4000_regbase_size = board_info->me4000_regbase_size;
	user_info.serial_no = board_info->serial_no;
	user_info.hw_revision = board_info->hw_revision;
	user_info.vendor_id = board_info->vendor_id;
	user_info.device_id = board_info->device_id;
	user_info.pci_bus_no = board_info->pci_bus_no;
	user_info.pci_dev_no = board_info->pci_dev_no;
	user_info.pci_func_no = board_info->pci_func_no;
	user_info.irq = board_info->irq;
	user_info.irq_count = board_info->irq_count;
	user_info.driver_version = ME4000_DRIVER_VERSION;
	user_info.ao_count = board_info->board_p->ao.count;
	user_info.ao_fifo_count = board_info->board_p->ao.fifo_count;

	user_info.ai_count = board_info->board_p->ai.count;
	user_info.ai_sh_count = board_info->board_p->ai.sh_count;
	user_info.ai_ex_trig_analog = board_info->board_p->ai.ex_trig_analog;

	user_info.dio_count = board_info->board_p->dio.count;

	user_info.cnt_count = board_info->board_p->cnt.count;

	if (copy_to_user(arg, &user_info, sizeof(struct me4000_user_info)))
		return -EFAULT;

	return 0;
}

/*------------------------------------ ISR STUFF ------------------------------------*/

static int me4000_ext_int_fasync(int fd, struct file *file_ptr, int mode)
{
	int result = 0;
	struct me4000_ext_int_context *ext_int_context;

	CALL_PDEBUG("me4000_ext_int_fasync() is executed\n");

	ext_int_context = file_ptr->private_data;

	result =
	    fasync_helper(fd, file_ptr, mode, &ext_int_context->fasync_ptr);

	CALL_PDEBUG("me4000_ext_int_fasync() is leaved\n");
	return result;
}

static irqreturn_t me4000_ao_isr(int irq, void *dev_id)
{
	u32 tmp;
	u32 value;
	struct me4000_ao_context *ao_context;
	int i;
	int c = 0;
	int c1 = 0;

	ISR_PDEBUG("me4000_ao_isr() is executed\n");

	ao_context = dev_id;

	/* Check if irq number is right */
	if (irq != ao_context->irq) {
		ISR_PDEBUG("me4000_ao_isr():incorrect interrupt num: %d\n",
			   irq);
		return IRQ_NONE;
	}

	/* Check if this DAC rised an interrupt */
	if (!
	    ((0x1 << (ao_context->index + 3)) &
	     me4000_inl(ao_context->irq_status_reg))) {
		ISR_PDEBUG("me4000_ao_isr():Not this DAC\n");
		return IRQ_NONE;
	}

	/* Read status register to find out what happened */
	tmp = me4000_inl(ao_context->status_reg);

	if (!(tmp & ME4000_AO_STATUS_BIT_EF) && (tmp & ME4000_AO_STATUS_BIT_HF)
	    && (tmp & ME4000_AO_STATUS_BIT_HF)) {
		c = ME4000_AO_FIFO_COUNT;
		ISR_PDEBUG("me4000_ao_isr():Fifo empty\n");
	} else if ((tmp & ME4000_AO_STATUS_BIT_EF)
		   && (tmp & ME4000_AO_STATUS_BIT_HF)
		   && (tmp & ME4000_AO_STATUS_BIT_HF)) {
		c = ME4000_AO_FIFO_COUNT / 2;
		ISR_PDEBUG("me4000_ao_isr():Fifo under half full\n");
	} else {
		c = 0;
		ISR_PDEBUG("me4000_ao_isr():Fifo full\n");
	}

	ISR_PDEBUG("me4000_ao_isr():Try to write 0x%04X values\n", c);

	while (1) {
		c1 = me4000_values_to_end(ao_context->circ_buf,
					  ME4000_AO_BUFFER_COUNT);
		ISR_PDEBUG("me4000_ao_isr():Values to end = %d\n", c1);
		if (c1 > c)
			c1 = c;

		if (c1 <= 0) {
			ISR_PDEBUG
			    ("me4000_ao_isr():Work done or buffer empty\n");
			break;
		}
		if (((ao_context->fifo_reg & 0xFF) == ME4000_AO_01_FIFO_REG) ||
		    ((ao_context->fifo_reg & 0xFF) == ME4000_AO_03_FIFO_REG)) {
			for (i = 0; i < c1; i++) {
				value =
				    ((u32)
				     (*
				      (ao_context->circ_buf.buf +
				       ao_context->circ_buf.tail + i))) << 16;
				outl(value, ao_context->fifo_reg);
			}
		} else
			outsw(ao_context->fifo_reg,
			      ao_context->circ_buf.buf +
			      ao_context->circ_buf.tail, c1);


		ao_context->circ_buf.tail =
		    (ao_context->circ_buf.tail + c1) & (ME4000_AO_BUFFER_COUNT -
							1);
		ISR_PDEBUG("me4000_ao_isr():%d values wrote to port 0x%04X\n",
			   c1, ao_context->fifo_reg);
		c -= c1;
	}

	/* If there are no values left in the buffer, disable interrupts */
	spin_lock(&ao_context->int_lock);
	if (!me4000_buf_count(ao_context->circ_buf, ME4000_AO_BUFFER_COUNT)) {
		ISR_PDEBUG
		    ("me4000_ao_isr():Disable Interrupt because no values left in buffer\n");
		tmp = me4000_inl(ao_context->ctrl_reg);
		tmp &= ~ME4000_AO_CTRL_BIT_ENABLE_IRQ;
		me4000_outl(tmp, ao_context->ctrl_reg);
	}
	spin_unlock(&ao_context->int_lock);

	/* Reset the interrupt */
	spin_lock(&ao_context->int_lock);
	tmp = me4000_inl(ao_context->ctrl_reg);
	tmp |= ME4000_AO_CTRL_BIT_RESET_IRQ;
	me4000_outl(tmp, ao_context->ctrl_reg);
	tmp &= ~ME4000_AO_CTRL_BIT_RESET_IRQ;
	me4000_outl(tmp, ao_context->ctrl_reg);

	/* If state machine is stopped, flow was interrupted */
	if (!(me4000_inl(ao_context->status_reg) & ME4000_AO_STATUS_BIT_FSM)) {
		printk(KERN_ERR "ME4000:me4000_ao_isr():Broken pipe\n");
		/* Set flag in order to inform write routine */
		ao_context->pipe_flag = 1;
		/* Disable interrupt */
		tmp &= ~ME4000_AO_CTRL_BIT_ENABLE_IRQ;
	}
	me4000_outl(tmp, ao_context->ctrl_reg);
	spin_unlock(&ao_context->int_lock);

	/* Wake up waiting process */
	wake_up_interruptible(&(ao_context->wait_queue));

	/* Count the interrupt */
	ao_context->board_info->irq_count++;

	return IRQ_HANDLED;
}

static irqreturn_t me4000_ai_isr(int irq, void *dev_id)
{
	u32 tmp;
	struct me4000_ai_context *ai_context;
	int i;
	int c = 0;
	int c1 = 0;
#ifdef ME4000_ISR_DEBUG
	unsigned long before;
	unsigned long after;
#endif

	ISR_PDEBUG("me4000_ai_isr() is executed\n");

#ifdef ME4000_ISR_DEBUG
	rdtscl(before);
#endif

	ai_context = dev_id;

	/* Check if irq number is right */
	if (irq != ai_context->irq) {
		ISR_PDEBUG("me4000_ai_isr():incorrect interrupt num: %d\n",
			   irq);
		return IRQ_NONE;
	}

	if (me4000_inl(ai_context->irq_status_reg) &
	    ME4000_IRQ_STATUS_BIT_AI_HF) {
		ISR_PDEBUG
		    ("me4000_ai_isr():Fifo half full interrupt occured\n");

		/* Read status register to find out what happened */
		tmp = me4000_inl(ai_context->ctrl_reg);

		if (!(tmp & ME4000_AI_STATUS_BIT_FF_DATA) &&
		    !(tmp & ME4000_AI_STATUS_BIT_HF_DATA)
		    && (tmp & ME4000_AI_STATUS_BIT_EF_DATA)) {
			ISR_PDEBUG("me4000_ai_isr():Fifo full\n");
			c = ME4000_AI_FIFO_COUNT;

			/* FIFO overflow, so stop conversion and disable all interrupts */
			spin_lock(&ai_context->int_lock);
			tmp = me4000_inl(ai_context->ctrl_reg);
			tmp |= ME4000_AI_CTRL_BIT_IMMEDIATE_STOP;
			tmp &=
			    ~(ME4000_AI_CTRL_BIT_HF_IRQ |
			      ME4000_AI_CTRL_BIT_SC_IRQ);
			outl(tmp, ai_context->ctrl_reg);
			spin_unlock(&ai_context->int_lock);
		} else if ((tmp & ME4000_AI_STATUS_BIT_FF_DATA) &&
			   !(tmp & ME4000_AI_STATUS_BIT_HF_DATA)
			   && (tmp & ME4000_AI_STATUS_BIT_EF_DATA)) {
			ISR_PDEBUG("me4000_ai_isr():Fifo half full\n");
			c = ME4000_AI_FIFO_COUNT / 2;
		} else {
			c = 0;
			ISR_PDEBUG
			    ("me4000_ai_isr():Can't determine state of fifo\n");
		}

		ISR_PDEBUG("me4000_ai_isr():Try to read %d values\n", c);

		while (1) {
			c1 = me4000_space_to_end(ai_context->circ_buf,
						 ME4000_AI_BUFFER_COUNT);
			ISR_PDEBUG("me4000_ai_isr():Space to end = %d\n", c1);
			if (c1 > c)
				c1 = c;

			if (c1 <= 0) {
				ISR_PDEBUG
				    ("me4000_ai_isr():Work done or buffer full\n");
				break;
			}

			insw(ai_context->data_reg,
			     ai_context->circ_buf.buf +
			     ai_context->circ_buf.head, c1);
			ai_context->circ_buf.head =
			    (ai_context->circ_buf.head +
			     c1) & (ME4000_AI_BUFFER_COUNT - 1);
			c -= c1;
		}

		/* Work is done, so reset the interrupt */
		ISR_PDEBUG
		    ("me4000_ai_isr():reset interrupt fifo half full interrupt\n");
		spin_lock(&ai_context->int_lock);
		tmp = me4000_inl(ai_context->ctrl_reg);
		tmp |= ME4000_AI_CTRL_BIT_HF_IRQ_RESET;
		me4000_outl(tmp, ai_context->ctrl_reg);
		tmp &= ~ME4000_AI_CTRL_BIT_HF_IRQ_RESET;
		me4000_outl(tmp, ai_context->ctrl_reg);
		spin_unlock(&ai_context->int_lock);
	}

	if (me4000_inl(ai_context->irq_status_reg) & ME4000_IRQ_STATUS_BIT_SC) {
		ISR_PDEBUG
		    ("me4000_ai_isr():Sample counter interrupt occured\n");

		if (!ai_context->sample_counter_reload) {
			ISR_PDEBUG
			    ("me4000_ai_isr():Single data block available\n");

			/* Poll data until fifo empty */
			for (i = 0;
			     (i < ME4000_AI_FIFO_COUNT / 2)
			     && (inl(ai_context->ctrl_reg) &
				 ME4000_AI_STATUS_BIT_EF_DATA); i++) {
				if (me4000_space_to_end
				    (ai_context->circ_buf,
				     ME4000_AI_BUFFER_COUNT)) {
					*(ai_context->circ_buf.buf +
					  ai_context->circ_buf.head) =
		 inw(ai_context->data_reg);
					ai_context->circ_buf.head =
					    (ai_context->circ_buf.head +
					     1) & (ME4000_AI_BUFFER_COUNT - 1);
				} else
					break;
			}
			ISR_PDEBUG("me4000_ai_isr():%d values read\n", i);
		} else {
			if (ai_context->sample_counter <=
			    ME4000_AI_FIFO_COUNT / 2) {
				ISR_PDEBUG
				    ("me4000_ai_isr():Interrupt from adjustable half full threshold\n");

				/* Read status register to find out what happened */
				tmp = me4000_inl(ai_context->ctrl_reg);

				if (!(tmp & ME4000_AI_STATUS_BIT_FF_DATA) &&
				    !(tmp & ME4000_AI_STATUS_BIT_HF_DATA)
				    && (tmp & ME4000_AI_STATUS_BIT_EF_DATA)) {
					ISR_PDEBUG
					    ("me4000_ai_isr():Fifo full\n");
					c = ME4000_AI_FIFO_COUNT;

					/* FIFO overflow, so stop conversion */
					spin_lock(&ai_context->int_lock);
					tmp = me4000_inl(ai_context->ctrl_reg);
					tmp |=
					    ME4000_AI_CTRL_BIT_IMMEDIATE_STOP;
					outl(tmp, ai_context->ctrl_reg);
					spin_unlock(&ai_context->int_lock);
				} else if ((tmp & ME4000_AI_STATUS_BIT_FF_DATA)
					   && !(tmp &
						ME4000_AI_STATUS_BIT_HF_DATA)
					   && (tmp &
					       ME4000_AI_STATUS_BIT_EF_DATA)) {
					ISR_PDEBUG
					    ("me4000_ai_isr():Fifo half full\n");
					c = ME4000_AI_FIFO_COUNT / 2;
				} else {
					c = ai_context->sample_counter;
					ISR_PDEBUG
					    ("me4000_ai_isr():Sample count values\n");
				}

				ISR_PDEBUG
				    ("me4000_ai_isr():Try to read %d values\n",
				     c);

				while (1) {
					c1 = me4000_space_to_end(ai_context->
								 circ_buf,
								 ME4000_AI_BUFFER_COUNT);
					ISR_PDEBUG
					    ("me4000_ai_isr():Space to end = %d\n",
					     c1);
					if (c1 > c)
						c1 = c;

					if (c1 <= 0) {
						ISR_PDEBUG
						    ("me4000_ai_isr():Work done or buffer full\n");
						break;
					}

					insw(ai_context->data_reg,
					     ai_context->circ_buf.buf +
					     ai_context->circ_buf.head, c1);
					ai_context->circ_buf.head =
					    (ai_context->circ_buf.head +
					     c1) & (ME4000_AI_BUFFER_COUNT - 1);
					c -= c1;
				}
			} else {
				ISR_PDEBUG
				    ("me4000_ai_isr():Multiple data block available\n");

				/* Read status register to find out what happened */
				tmp = me4000_inl(ai_context->ctrl_reg);

				if (!(tmp & ME4000_AI_STATUS_BIT_FF_DATA) &&
				    !(tmp & ME4000_AI_STATUS_BIT_HF_DATA)
				    && (tmp & ME4000_AI_STATUS_BIT_EF_DATA)) {
					ISR_PDEBUG
					    ("me4000_ai_isr():Fifo full\n");
					c = ME4000_AI_FIFO_COUNT;

					/* FIFO overflow, so stop conversion */
					spin_lock(&ai_context->int_lock);
					tmp = me4000_inl(ai_context->ctrl_reg);
					tmp |=
					    ME4000_AI_CTRL_BIT_IMMEDIATE_STOP;
					outl(tmp, ai_context->ctrl_reg);
					spin_unlock(&ai_context->int_lock);

					while (1) {
						c1 = me4000_space_to_end
						    (ai_context->circ_buf,
						     ME4000_AI_BUFFER_COUNT);
						ISR_PDEBUG
						    ("me4000_ai_isr():Space to end = %d\n",
						     c1);
						if (c1 > c)
							c1 = c;

						if (c1 <= 0) {
							ISR_PDEBUG
							    ("me4000_ai_isr():Work done or buffer full\n");
							break;
						}

						insw(ai_context->data_reg,
						     ai_context->circ_buf.buf +
						     ai_context->circ_buf.head,
						     c1);
						ai_context->circ_buf.head =
						    (ai_context->circ_buf.head +
						     c1) &
						    (ME4000_AI_BUFFER_COUNT -
						     1);
						c -= c1;
					}
				} else if ((tmp & ME4000_AI_STATUS_BIT_FF_DATA)
					   && !(tmp &
						ME4000_AI_STATUS_BIT_HF_DATA)
					   && (tmp &
					       ME4000_AI_STATUS_BIT_EF_DATA)) {
					ISR_PDEBUG
					    ("me4000_ai_isr():Fifo half full\n");
					c = ME4000_AI_FIFO_COUNT / 2;

					while (1) {
						c1 = me4000_space_to_end
						    (ai_context->circ_buf,
						     ME4000_AI_BUFFER_COUNT);
						ISR_PDEBUG
						    ("me4000_ai_isr():Space to end = %d\n",
						     c1);
						if (c1 > c)
							c1 = c;

						if (c1 <= 0) {
							ISR_PDEBUG
							    ("me4000_ai_isr():Work done or buffer full\n");
							break;
						}

						insw(ai_context->data_reg,
						     ai_context->circ_buf.buf +
						     ai_context->circ_buf.head,
						     c1);
						ai_context->circ_buf.head =
						    (ai_context->circ_buf.head +
						     c1) &
						    (ME4000_AI_BUFFER_COUNT -
						     1);
						c -= c1;
					}
				} else {
					/* Poll data until fifo empty */
					for (i = 0;
					     (i < ME4000_AI_FIFO_COUNT / 2)
					     && (inl(ai_context->ctrl_reg) &
						 ME4000_AI_STATUS_BIT_EF_DATA);
					     i++) {
						if (me4000_space_to_end
						    (ai_context->circ_buf,
						     ME4000_AI_BUFFER_COUNT)) {
							*(ai_context->circ_buf.
							  buf +
							  ai_context->circ_buf.
							  head) =
				       inw(ai_context->data_reg);
							ai_context->circ_buf.
							    head =
							    (ai_context->
							     circ_buf.head +
							     1) &
							    (ME4000_AI_BUFFER_COUNT
							     - 1);
						} else
							break;
					}
					ISR_PDEBUG
					    ("me4000_ai_isr():%d values read\n",
					     i);
				}
			}
		}

		/* Work is done, so reset the interrupt */
		ISR_PDEBUG
		    ("me4000_ai_isr():reset interrupt from sample counter\n");
		spin_lock(&ai_context->int_lock);
		tmp = me4000_inl(ai_context->ctrl_reg);
		tmp |= ME4000_AI_CTRL_BIT_SC_IRQ_RESET;
		me4000_outl(tmp, ai_context->ctrl_reg);
		tmp &= ~ME4000_AI_CTRL_BIT_SC_IRQ_RESET;
		me4000_outl(tmp, ai_context->ctrl_reg);
		spin_unlock(&ai_context->int_lock);
	}

	/* Values are now available, so wake up waiting process */
	if (me4000_buf_count(ai_context->circ_buf, ME4000_AI_BUFFER_COUNT)) {
		ISR_PDEBUG("me4000_ai_isr():Wake up waiting process\n");
		wake_up_interruptible(&(ai_context->wait_queue));
	}

	/* If there is no space left in the buffer, disable interrupts */
	spin_lock(&ai_context->int_lock);
	if (!me4000_buf_space(ai_context->circ_buf, ME4000_AI_BUFFER_COUNT)) {
		ISR_PDEBUG
		    ("me4000_ai_isr():Disable Interrupt because no space left in buffer\n");
		tmp = me4000_inl(ai_context->ctrl_reg);
		tmp &=
		    ~(ME4000_AI_CTRL_BIT_SC_IRQ | ME4000_AI_CTRL_BIT_HF_IRQ |
		      ME4000_AI_CTRL_BIT_LE_IRQ);
		me4000_outl(tmp, ai_context->ctrl_reg);
	}
	spin_unlock(&ai_context->int_lock);

#ifdef ME4000_ISR_DEBUG
	rdtscl(after);
	printk(KERN_ERR "ME4000:me4000_ai_isr():Time lapse = %lu\n",
	       after - before);
#endif

	return IRQ_HANDLED;
}

static irqreturn_t me4000_ext_int_isr(int irq, void *dev_id)
{
	struct me4000_ext_int_context *ext_int_context;
	unsigned long tmp;

	ISR_PDEBUG("me4000_ext_int_isr() is executed\n");

	ext_int_context = dev_id;

	/* Check if irq number is right */
	if (irq != ext_int_context->irq) {
		ISR_PDEBUG("me4000_ext_int_isr():incorrect interrupt num: %d\n",
			   irq);
		return IRQ_NONE;
	}

	if (me4000_inl(ext_int_context->irq_status_reg) &
	    ME4000_IRQ_STATUS_BIT_EX) {
		ISR_PDEBUG("me4000_ext_int_isr():External interrupt occured\n");
		tmp = me4000_inl(ext_int_context->ctrl_reg);
		tmp |= ME4000_AI_CTRL_BIT_EX_IRQ_RESET;
		me4000_outl(tmp, ext_int_context->ctrl_reg);
		tmp &= ~ME4000_AI_CTRL_BIT_EX_IRQ_RESET;
		me4000_outl(tmp, ext_int_context->ctrl_reg);

		ext_int_context->int_count++;

		if (ext_int_context->fasync_ptr) {
			ISR_PDEBUG
			    ("me2600_ext_int_isr():Send signal to process\n");
			kill_fasync(&ext_int_context->fasync_ptr, SIGIO,
				    POLL_IN);
		}
	}

	return IRQ_HANDLED;
}

static void __exit me4000_module_exit(void)
{
	struct me4000_info *board_info;

	CALL_PDEBUG("cleanup_module() is executed\n");

	unregister_chrdev(me4000_ext_int_major_driver_no, ME4000_EXT_INT_NAME);

	unregister_chrdev(me4000_cnt_major_driver_no, ME4000_CNT_NAME);

	unregister_chrdev(me4000_dio_major_driver_no, ME4000_DIO_NAME);

	unregister_chrdev(me4000_ai_major_driver_no, ME4000_AI_NAME);

	unregister_chrdev(me4000_ao_major_driver_no, ME4000_AO_NAME);

	remove_proc_entry("me4000", NULL);

	pci_unregister_driver(&me4000_driver);

	/* Reset the boards */
	list_for_each_entry(board_info, &me4000_board_info_list, list) {
		me4000_reset_board(board_info);
	}

	clear_board_info_list();
}

module_exit(me4000_module_exit);

static int me4000_read_procmem(char *buf, char **start, off_t offset, int count,
			       int *eof, void *data)
{
	int len = 0;
	int limit = count - 1000;
	struct me4000_info *board_info;

	len += sprintf(buf + len, "\nME4000 DRIVER VERSION %X.%X.%X\n\n",
		       (ME4000_DRIVER_VERSION & 0xFF0000) >> 16,
		       (ME4000_DRIVER_VERSION & 0xFF00) >> 8,
		       (ME4000_DRIVER_VERSION & 0xFF));

	/* Search for the board context */
	list_for_each_entry(board_info, &me4000_board_info_list, list) {
		len +=
		    sprintf(buf + len, "Board number %d:\n",
			    board_info->board_count);
		len += sprintf(buf + len, "---------------\n");
		len +=
		    sprintf(buf + len, "PLX base register = 0x%lX\n",
			    board_info->plx_regbase);
		len +=
		    sprintf(buf + len, "PLX base register size = 0x%X\n",
			    (unsigned int)board_info->plx_regbase_size);
		len +=
		    sprintf(buf + len, "ME4000 base register = 0x%X\n",
			    (unsigned int)board_info->me4000_regbase);
		len +=
		    sprintf(buf + len, "ME4000 base register size = 0x%X\n",
			    (unsigned int)board_info->me4000_regbase_size);
		len +=
		    sprintf(buf + len, "Serial number = 0x%X\n",
			    board_info->serial_no);
		len +=
		    sprintf(buf + len, "Hardware revision = 0x%X\n",
			    board_info->hw_revision);
		len +=
		    sprintf(buf + len, "Vendor id = 0x%X\n",
			    board_info->vendor_id);
		len +=
		    sprintf(buf + len, "Device id = 0x%X\n",
			    board_info->device_id);
		len +=
		    sprintf(buf + len, "PCI bus number = %d\n",
			    board_info->pci_bus_no);
		len +=
		    sprintf(buf + len, "PCI device number = %d\n",
			    board_info->pci_dev_no);
		len +=
		    sprintf(buf + len, "PCI function number = %d\n",
			    board_info->pci_func_no);
		len += sprintf(buf + len, "IRQ = %u\n", board_info->irq);
		len +=
		    sprintf(buf + len,
			    "Count of interrupts since module was loaded = %d\n",
			    board_info->irq_count);

		len +=
		    sprintf(buf + len, "Count of analog outputs = %d\n",
			    board_info->board_p->ao.count);
		len +=
		    sprintf(buf + len, "Count of analog output fifos = %d\n",
			    board_info->board_p->ao.fifo_count);

		len +=
		    sprintf(buf + len, "Count of analog inputs = %d\n",
			    board_info->board_p->ai.count);
		len +=
		    sprintf(buf + len,
			    "Count of sample and hold devices for analog input = %d\n",
			    board_info->board_p->ai.sh_count);
		len +=
		    sprintf(buf + len,
			    "Analog external trigger available for analog input = %d\n",
			    board_info->board_p->ai.ex_trig_analog);

		len +=
		    sprintf(buf + len, "Count of digital ports = %d\n",
			    board_info->board_p->dio.count);

		len +=
		    sprintf(buf + len, "Count of counter devices = %d\n",
			    board_info->board_p->cnt.count);
		len +=
		    sprintf(buf + len, "AI control register = 0x%08X\n",
			    inl(board_info->me4000_regbase +
				ME4000_AI_CTRL_REG));

		len += sprintf(buf + len, "AO 0 control register = 0x%08X\n",
			       inl(board_info->me4000_regbase +
				   ME4000_AO_00_CTRL_REG));
		len +=
		    sprintf(buf + len, "AO 0 status register = 0x%08X\n",
			    inl(board_info->me4000_regbase +
				ME4000_AO_00_STATUS_REG));
		len +=
		    sprintf(buf + len, "AO 1 control register = 0x%08X\n",
			    inl(board_info->me4000_regbase +
				ME4000_AO_01_CTRL_REG));
		len +=
		    sprintf(buf + len, "AO 1 status register = 0x%08X\n",
			    inl(board_info->me4000_regbase +
				ME4000_AO_01_STATUS_REG));
		len +=
		    sprintf(buf + len, "AO 2 control register = 0x%08X\n",
			    inl(board_info->me4000_regbase +
				ME4000_AO_02_CTRL_REG));
		len +=
		    sprintf(buf + len, "AO 2 status register = 0x%08X\n",
			    inl(board_info->me4000_regbase +
				ME4000_AO_02_STATUS_REG));
		len +=
		    sprintf(buf + len, "AO 3 control register = 0x%08X\n",
			    inl(board_info->me4000_regbase +
				ME4000_AO_03_CTRL_REG));
		len +=
		    sprintf(buf + len, "AO 3 status register = 0x%08X\n",
			    inl(board_info->me4000_regbase +
				ME4000_AO_03_STATUS_REG));
		if (len >= limit)
			break;
	}

	*eof = 1;
	return len;
}