aboutsummaryrefslogblamecommitdiffstats
path: root/drivers/net/wireless/atmel.c
blob: 6c87a823f5a9d4c6ec228651687b14c7fb545c8e (plain) (tree)
1
2
3
4
5
6
7
8
9
10



                                                                               

                                              
 


                                                                    






















                                                                             
                                                                       






                                                                               
                       
                            

                         




                         
                          
                   










                              




                              
                          
                            


                      
                       





                                                                                 
                                             




                                        
               



                                








                                                                        
  















                                                



                                     
                          

               

                                                                 

                 



                                            



                                            
















                                                                                 
 



                                                          




                                    


                











                                  

















                                       



                                 
 


                             
 




                               
                            



                               

  












                                       


                     





                                    




                                    







                                                                        
                                    


































                                                



















                                                      







                                            
                                         





                                                           

                         



                                  


                             
 


                     
 







                                                                                                  
 
                                 










                                      
                              







                                            
                                         






                                          
                               





                               


                             
 


                  

                                            
                                            



                                                                            









































                                                                                                       


                      
                                                                   








                                                                         
                                                       
                                                        

                                         
                                    
                                






                                                                       
 
                                        

                          


                                                                         
                                             


                                                                     
                                 














                                                                       
 
                                  

                                

                                  
 





                                     
              








                                             
 











                                                                  
                                                                              



                                                                                                    
                                     
                            
                                  


                                                          
 











                                          
                               




                                                            

  
                                                          



                       
                   



                                                            
                                                              





                                                                          
                                                                
                                                                  

                                                                           





                                                                           
                                                              
                                                                

                                                           




                                                                           
                                                                         


                                                                        







                                                                          




                                                                      
                                                    












                                                                         
                                                                            



                                                                                      
                                                                            

























                                                                                      
                                      




                                                                             
                                      




                                                                   
                                      




                                                                              
                                      








                                                     
                   


                                                                                                      












                                                                                                                  
 
                                                                             
                                              

                                               
 

                                                        
                                                              
                            
                                                             








                                                                            
                                                               
                         
 

                                                                        
 



                                                     
 


                 

                                                                          











                                                                                                        

                                                                 












                                                                                       

                                                                 











                                                                                    
 
                                                                                                 
                                         







                                                                                                   
                                                                     




                                       

 
                                                                        
 
                                                                                 
                                                      
                                    

                                                                               

                                                   
                                                     
                                       
                                   
                                    
         
 
                                                         
                                       
                                   
                                    
         
 
                                                    
                                       
                                        
                                                 
                                                                    
 
                                                                               



                                                                            
                                                     
                                        


                                                              
                                      
         
 
                                         
                               
                            
                            
                                                      
                                                    
                                                                 


                                                        
                                                 

                                                             
                                                                 
         
 


                                                       
                                                      




                                                                                            
 

                                                                                             
                                   
 


                                                      
 
                            

 
                                                                       
                                                                         


                                                                   


                                                    






                                                                                            

                                                    
                                                      


                                                                     

                             
                            
 


                                                                            
 



                                             
 
                                                     
                                              





                                                                                
 




                                                                                                  
                                                         



                                           
 
                                                                 
                                                                       


                                                                        
 
                                                       
                                       
                      

                                                    


                                                                         

                                                                               








                                                                               
 








                                                      
                                                    
                                                      

                                                                               
 
                   


                            
                                                                       

                                                 

                                                 
                                              
 







                                                                      
                                    
                                                     
 


                                           
                                                     

                                                       
 






                                                                                                     
                                                                 


                                                                   
 


                                                               

                                                                                 


                                                             

                                                                          


                                                                                                     
                                                                 



                                                                                
 





                                                                          
                                                              

                                                    
                                                                         

                                                            
                                                                               
                                                               
                                              

                                                                                 

                         


                                                
 


                                                   
                                    

                   


                                                                                                                 
 


                                                                                                         
 



                                                                          
                                                             




                                                                                                                
 
                                     
                                                     

                                  
 
                                                          
                                                                                  

                                                              
 


                                                                             
                                      
                                                                                          











                                                                                                      
 
                                                      
                                                                                                       



                                                                                 
                                                                                 


                                                                                         

                                                                         





                                                                                                     
 
                                                                                 

                                                                                                                     
 

                                                                                            
 




                                                                                                 
                                                                         





                                                                                                                           
                 
 
     
                                        

                                                                                                                   
                                                                             
                                             


                                               
 
 
                                                           




                                                              
                                       








                                     
 
                                                   





                                                                             
                                                                   


                                                                    
 







                                                                                     
 

                                                                                 
 



                                                                              
 
                                                                           
 
                                                           

                                               
 




                                                                                     
 



                                                                                 
 
                                       


                                                                    









                                                                         
                                          
                              
 
                                    
                                          
                              
 



                                                                                            

                                          



                                                
                                                                                          








                                                                                   
         

 
                                                                             


                                                      
                                                                        

                                         
 





















                                                                         

                             



                                                                



                                               




                                                                 
 
                                                             




                               
                                      

                                                      
                            


                                                                                 
 


















                                                                        


                                    





                                                                                              
                                                               

                                                                            
                                                     

                                                                                                              
                 
         
 


                                                                    

                                             




                                                                    
                                              

                                                      
 










                                                                       
                                                    

                                              










                                                                          
                                                       














                                                                      



                                                     
                                                        

                                                                     


                                                            

                                                        
                                                     


                                                              
                    



                                                                 










                                              

                 
                                
                                                               

                                                                            
 

                                                               
                                                           



                                                              
 


























                                          
         
 
                                                     
                       




                                                               
                                          
                                                 








                             

 






                                                        


                                                    



                                                                           
 
                                   




                                               
                                            
                 
                            
 
                                                 
                                                               






















                                                                       





                                                                               
 


































                                                                                   
 

                                                    

                              
 
                                     
 
                                                                                           
                                                                                                 


                                  
                                               
                                                                                             

                                 
 

                                 
 
                                     


                                       
 
                               
 


                                                                                      
 

                                                                        
 
                   

            
                                           
            
                                
             





                               
                                            

                                                      
 
                                  
                                              

                                                
 



                                                
                              
                                           












                                                        
                               




                                                                
 
                                                  
                                                   
                                       

                                       
 

                                                            














                                                                   
                                                   

                                                           
                                               
         
 



















































                                                                          
                                                       







                                                                           
                                             



                                                           
                                                                                   

                                                           
                                                                                  





                                                                     
                                              


                                                                    
                                                            
                                               

                            
                                               
                                    
                                           










                                                                           
                                               
                                              
                                         
                                              
 


                                                                 






                                                         
 

                                                 





                                                           






                                                           
                                 



                                                                   
 


                 







                                                                  
                                                      



                                                  
                                       




                                        

                                                 
 
                                                        
                                        

                                                   
 

























                                                                                   

                                       




















                                                                  
                                       






                                        
 




























































































                                                                            














                                                       
 




                                       
 
                                           
                                                              
                                                   
                                                    


                                                













                                                  

















                                                             
                            







                                                       
 















                                                      












                                               































                                                        

                                                                
                                                
                                                       
                                                      





                                                        
                                    
         
 











                                                        


                                                               



                                                
                                                          
                                                      












                                                      
                           
                            
                                          


                                   
 








                                                                 
 














                                                       
                           
                            
                                            



                                                                         
 
















                                                                 






                                                                 
 
                                                           
                           
                                         
 

                                             
                                                         

                                       
                                              






                                                                 
                 

















                                                       
                                                










                                                                         
 



                                                      
                                                             





                                                               
 

                                                 
 



                                                          
 











                                                       
 

                                                             

                                                      


                                                                         


                                                                           





                                                               


                                                                               
 

                                                      


                                                                           
 


                                                        


                                                                           
 




                                                         


                                                                           

 





                                                                               


                                                                           



                                            

                        









                                                           
                    

                                               
                                                  


                                 
                                                       




                                                                                              
                                                                                       
                                                              




                                                                            


                                         
 


























                                                        
 


                                       
 

















                                                      

                                                                       



                                            
 

                                             





                                                              

                                                      













                                                                                    
 

                       
 







                                                                          
                                         

                                                                   
                                                                 





                                                                 

                                                                 












                                                                  
                                                                   



                                                                  

                                                                  







                                                                 

                                                                  





                                                                   








                                                                      

  
                                                 





                                                             

                                   

                   



                                       


                                                         

















                                                                      

  
                                                        


                                                            
                                                       
                                                               

                                                                     








                                                                         
                                     

                      
                        
                                                
                      
 





















                                                                      
                                      
 










                                                                        
 






                                              
                                                                 













                                                                                      
 


                                                                          
 


                                 
 



                  


                         


                           
  



                                                                        
 

                                   
 
                                        
 



















                                                                     


                                        


                             
 












                                                              
 
                        
 

                                                        

                                             
                                         
                                                                          
                                                           
 













                                                                               
                               










                                                      
                                                              

 




                                                       
                            










                                                      
                                                               

 

                                                                           

                       
                                                               






                                                                                   
 




                                                                                 
 


                                                                
 

                                                            
                    
                                                    
         

 

                                                                               
 
                                    
                              
 
                                                                                        
                                                 
                            


                                                     

                                                                          
                                                                     
                                                                               

                                       


                                                                            
                                                                                         
                                                   
 













                                                                                               
                                    
                               

                                       







                                                                      
 
                                                                 
                                                                                        
                                                 
                            
 
                                                    
                                                     
                                                    
 
                                                           
                            
                                                                        
                                             
                                                                               

                                                                                        
 

                                                        
                                                       





                                                   
 
                                     

                                                           
                                                             





                                                                                
                                                                
                                                                  
 
                                                                       









                                                                         
 

                                        
 
                                       



                                                                             
                                      
                                                              
                                                                               
                                                                                




                                                                               


                                 

                                                      












                                                                                      
                                                      
                                                                        

                                                                               
 
                                                                                   
                     


                                                                

                                  
                                                                  

















                                                                    
                                                                             


                                                          
                                              
                                                             
                                                  
                                                             
 
                                                                                     







                                                                   
                                            
 
                                                                








                                                                             
                 
         
 
                                                               
                                         


                                                                           
 
                                               


                                                     
                                                            
                                                     
                                                                




                                                                                                                  
                 
 
                                       
                                                           






                                                                                     
                         
                 

         
                                                           


                                                                           
                                               
                                                                      


                                                                                         

                                                         



                                                                                   

                                                      
 
                                                                                
 



                                                                      

                 
 







                                                                             


                                  



                                                             

                                                   
                                                   

                                                                    

                              

                                      
 

                                                          


                                                               




                                                                                

                                                  



                                                                              

                                                  



                                                                             
                 
 


                                                             







                                                                           

                       
 


                                                       





                                                                           
 


                                                       





                                                                           
 

                                                           

                                       

                                                                        

                                                           
                                                        


         
                                                                     








                                                                       
 






                                                                   



                                                                       
         
 
                                            
                                            
                                                 
 

                                               

                                                                       
         
 




                                                                  
 






                                                                  
 

                                                    
            


                                            


                                                      
 



                                                                        

                                                           


                                                        

         






                                                                                




                                                                    


                                     

                                                               
            
                                                           





                                                            
                                                                   

                                      

                                                                    






                                                                                                              
                                            
                                                              
                                                                
                                                          

                    
 
                                                                            
                          

                                        
 



                                                                               


                                                  









                                                                         
 






                                                                            
                                                                           






                                                                           
 

                                                                         
                                                                              












                                                                                                                     





                                                                              
                                                                                  

                      
 
                                  


                                                                        
 
                      
 

                                          

                                                                       

                                                                       
 

                      
                                      

                                                            


                                                              
 


                                                                        
 

                      
                                    






                                                                        
 






                                            


                                                         
 



                                                       
 


                                                 
 






                                                                                        
                                                  


                                                                                   
                                                                         
                                                            
                                                                         
           
                











                                                                                   
                


















                                                                                    
 
 




                                                                                 
                              

                                        


                                             
                          





                                                                                          
                 
                      
 


                                            
 



                                                           
                                                     

                                                                
                                                                           




                                                             
                                                         

                                                                        




                                                                                         

                      
 

                                    
 

                                                  
 

                                                                
                                                                     


                                                                                 










                                                                                           
                                                          

                                                                                      
 

                                                                                           
                                                                                 
                                                                    
                                                                                 


                               
 
                                    










                                                            
 


                                                
 



                                                   
                                            

                                                   

                                            







                                                                                
                            
         
 

                                                                              
                               
         





                                                                             
                                                                                           

                                            

                                                   

                                            




                                                      
 
                     

                                                                    
                            
         
 



                                                                                      
                            



                                                                                      
                            

         
                                                             
                                                                 
 














                                                                 
                 






                                                      
 
                          
                                              
                                                
 

                                        
 
                                          
                                                                     







                                                                           
                                                  







                                                                                                     
                                                              






                                                                
                                            






                                                                  
                                                       
                                                                                       
 

                                                                             
                                                              




                                                                
 

                                               


                                                                  


                                                                                                             
         
 


                  


                                                               
                                                     

                                                                        
                             


                                          
 

                                       
 
                                                     

                                                  







                                                             
                                                 





                                                            

                                                 
                                                               
 




                                                                          
                                                           



                                                                                          
                             




                                          
 

                                       
 

                                 
 




                                                            
 

                                                                                      
 
                              


                                                                               
                                                            
 






                                                                                                                    
                                                                                                                                 


                                                                                               
                                                                                                       
                                                                                                                              






                                                                                   
 
         
 

                                                                          

                                                   



                                                                             
                                                                   




                                                                                

                                                                   
                                                                
                                                                              


                                                      
                                            
                    
 

                                                               
 




                                            
 
                          
                                              
                                                      
 

                                              
 


                                                       
                                        







                                                                                                               
                                                                                                   


                                                                                        

                                                                                                    

                                                                                                
                                                                             
                                                   








                                                                                                    
 













                                                                                                                         

                                                                                             

                                                                             
                                                       

                                 
 


                                             
 
                                    



                                                                  
                                   










                                                                                         


                                          







                                                                     
 
                                    
                                                                       
 





                                                                                


                                                           

                                                         




                                                                                  



                                                                       


                                                                                  
                                                                       
 
                                     
                                                                              
                                                    
                                                                           
                                          
                                    

                 
 







                                                                                              
                                                                            









                                                                                                           
 
                                               








                                                                       
                 

 

                                                                       

                
                                                                                          
                                                  
 


                                                                             


                                                                           

                      
 
                                                         
 

                                                                                    
                                                



                                                     
 


                                                                                   
                


                                                     
 
























                                                                                                

                                                                          










                                                                                 

                                                                        







                                                                                  
 



                                                                                        

                                                                        




                             
 

                                                                                  
 
                                                                                        
                                           







                                                                                                    
                                                                    


                                                
                                                                
                                                                 















                                                       

                                                                           


































                                                                                  



                                         


                                                                               

                                                 

                           
 




                                                                       
                                      






































































                                                                                    
                                                                            
                                                       
 




                                                                                  
                                




                     
                       
             
               
           
               
              
               

                
               
           
               
           




                                                                  
 



                                                                           
                          





                             
 































                                                         
 














                                                                         
          


                           
            
                  
        



























                                                                   
                            




                                   
         




                                

                                 




                                 
         







                                 
 










                                                                              
                                       


                                                                                         
        




















                                                                  
        












                                                                        
        






                                                                                                                                                                          
        



                                                           
        













                                                                                                                
/*** -*- linux-c -*- **********************************************************

     Driver for Atmel at76c502 at76c504 and at76c506 wireless cards.

	Copyright 2000-2001 ATMEL Corporation.
	Copyright 2003-2004 Simon Kelley.

    This code was developed from version 2.1.1 of the Atmel drivers,
    released by Atmel corp. under the GPL in December 2002. It also
    includes code from the Linux aironet drivers (C) Benjamin Reed,
    and the Linux PCMCIA package, (C) David Hinds and the Linux wireless
    extensions, (C) Jean Tourrilhes.

    The firmware module for reading the MAC address of the card comes from
    net.russotto.AtmelMACFW, written by Matthew T. Russotto and copyright
    by him. net.russotto.AtmelMACFW is used under the GPL license version 2.
    This file contains the module in binary form and, under the terms
    of the GPL, in source form. The source is located at the end of the file.

    This program 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 software 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 Atmel wireless lan drivers; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

    For all queries about this code, please contact the current author,
    Simon Kelley <simon@thekelleys.org.uk> and not Atmel Corporation.

    Credit is due to HP UK and Cambridge Online Systems Ltd for supplying
    hardware used during development of this driver.

******************************************************************************/

#include <linux/init.h>
#include <linux/interrupt.h>

#include <linux/kernel.h>
#include <linux/ptrace.h>
#include <linux/slab.h>
#include <linux/string.h>
#include <linux/ctype.h>
#include <linux/timer.h>
#include <asm/byteorder.h>
#include <asm/io.h>
#include <asm/uaccess.h>
#include <linux/module.h>
#include <linux/netdevice.h>
#include <linux/etherdevice.h>
#include <linux/skbuff.h>
#include <linux/if_arp.h>
#include <linux/ioport.h>
#include <linux/fcntl.h>
#include <linux/delay.h>
#include <linux/wireless.h>
#include <net/iw_handler.h>
#include <linux/crc32.h>
#include <linux/proc_fs.h>
#include <linux/device.h>
#include <linux/moduleparam.h>
#include <linux/firmware.h>
#include <linux/jiffies.h>
#include <linux/ieee80211.h>
#include "atmel.h"

#define DRIVER_MAJOR 0
#define DRIVER_MINOR 98

MODULE_AUTHOR("Simon Kelley");
MODULE_DESCRIPTION("Support for Atmel at76c50x 802.11 wireless ethernet cards.");
MODULE_LICENSE("GPL");
MODULE_SUPPORTED_DEVICE("Atmel at76c50x wireless cards");

/* The name of the firmware file to be loaded
   over-rides any automatic selection */
static char *firmware = NULL;
module_param(firmware, charp, 0);

/* table of firmware file names */
static struct {
	AtmelFWType fw_type;
	const char *fw_file;
	const char *fw_file_ext;
} fw_table[] = {
	{ ATMEL_FW_TYPE_502,		"atmel_at76c502",	"bin" },
	{ ATMEL_FW_TYPE_502D,		"atmel_at76c502d",	"bin" },
	{ ATMEL_FW_TYPE_502E,		"atmel_at76c502e",	"bin" },
	{ ATMEL_FW_TYPE_502_3COM,	"atmel_at76c502_3com",	"bin" },
	{ ATMEL_FW_TYPE_504,		"atmel_at76c504",	"bin" },
	{ ATMEL_FW_TYPE_504_2958,	"atmel_at76c504_2958",	"bin" },
	{ ATMEL_FW_TYPE_504A_2958,	"atmel_at76c504a_2958",	"bin" },
	{ ATMEL_FW_TYPE_506,		"atmel_at76c506",	"bin" },
	{ ATMEL_FW_TYPE_NONE,		NULL,			NULL }
};
MODULE_FIRMWARE("atmel_at76c502-wpa.bin");
MODULE_FIRMWARE("atmel_at76c502.bin");
MODULE_FIRMWARE("atmel_at76c502d-wpa.bin");
MODULE_FIRMWARE("atmel_at76c502d.bin");
MODULE_FIRMWARE("atmel_at76c502e-wpa.bin");
MODULE_FIRMWARE("atmel_at76c502e.bin");
MODULE_FIRMWARE("atmel_at76c502_3com-wpa.bin");
MODULE_FIRMWARE("atmel_at76c502_3com.bin");
MODULE_FIRMWARE("atmel_at76c504-wpa.bin");
MODULE_FIRMWARE("atmel_at76c504.bin");
MODULE_FIRMWARE("atmel_at76c504_2958-wpa.bin");
MODULE_FIRMWARE("atmel_at76c504_2958.bin");
MODULE_FIRMWARE("atmel_at76c504a_2958-wpa.bin");
MODULE_FIRMWARE("atmel_at76c504a_2958.bin");
MODULE_FIRMWARE("atmel_at76c506-wpa.bin");
MODULE_FIRMWARE("atmel_at76c506.bin");

#define MAX_SSID_LENGTH 32
#define MGMT_JIFFIES (256 * HZ / 100)

#define MAX_BSS_ENTRIES	64

/* registers */
#define GCR  0x00    /* (SIR0)  General Configuration Register */
#define BSR  0x02    /* (SIR1)  Bank Switching Select Register */
#define AR   0x04
#define DR   0x08
#define MR1  0x12    /* Mirror Register 1 */
#define MR2  0x14    /* Mirror Register 2 */
#define MR3  0x16    /* Mirror Register 3 */
#define MR4  0x18    /* Mirror Register 4 */

#define GPR1                            0x0c
#define GPR2                            0x0e
#define GPR3                            0x10
/*
 * Constants for the GCR register.
 */
#define GCR_REMAP     0x0400          /* Remap internal SRAM to 0 */
#define GCR_SWRES     0x0080          /* BIU reset (ARM and PAI are NOT reset) */
#define GCR_CORES     0x0060          /* Core Reset (ARM and PAI are reset) */
#define GCR_ENINT     0x0002          /* Enable Interrupts */
#define GCR_ACKINT    0x0008          /* Acknowledge Interrupts */

#define BSS_SRAM      0x0200          /* AMBA module selection --> SRAM */
#define BSS_IRAM      0x0100          /* AMBA module selection --> IRAM */
/*
 *Constants for the MR registers.
 */
#define MAC_INIT_COMPLETE       0x0001        /* MAC init has been completed */
#define MAC_BOOT_COMPLETE       0x0010        /* MAC boot has been completed */
#define MAC_INIT_OK             0x0002        /* MAC boot has been completed */

#define MIB_MAX_DATA_BYTES    212
#define MIB_HEADER_SIZE       4    /* first four fields */

struct get_set_mib {
	u8 type;
	u8 size;
	u8 index;
	u8 reserved;
	u8 data[MIB_MAX_DATA_BYTES];
};

struct rx_desc {
	u32          Next;
	u16          MsduPos;
	u16          MsduSize;

	u8           State;
	u8           Status;
	u8           Rate;
	u8           Rssi;
	u8           LinkQuality;
	u8           PreambleType;
	u16          Duration;
	u32          RxTime;
};

#define RX_DESC_FLAG_VALID       0x80
#define RX_DESC_FLAG_CONSUMED    0x40
#define RX_DESC_FLAG_IDLE        0x00

#define RX_STATUS_SUCCESS        0x00

#define RX_DESC_MSDU_POS_OFFSET      4
#define RX_DESC_MSDU_SIZE_OFFSET     6
#define RX_DESC_FLAGS_OFFSET         8
#define RX_DESC_STATUS_OFFSET        9
#define RX_DESC_RSSI_OFFSET          11
#define RX_DESC_LINK_QUALITY_OFFSET  12
#define RX_DESC_PREAMBLE_TYPE_OFFSET 13
#define RX_DESC_DURATION_OFFSET      14
#define RX_DESC_RX_TIME_OFFSET       16

struct tx_desc {
	u32       NextDescriptor;
	u16       TxStartOfFrame;
	u16       TxLength;

	u8        TxState;
	u8        TxStatus;
	u8        RetryCount;

	u8        TxRate;

	u8        KeyIndex;
	u8        ChiperType;
	u8        ChipreLength;
	u8        Reserved1;

	u8        Reserved;
	u8        PacketType;
	u16       HostTxLength;
};

#define TX_DESC_NEXT_OFFSET          0
#define TX_DESC_POS_OFFSET           4
#define TX_DESC_SIZE_OFFSET          6
#define TX_DESC_FLAGS_OFFSET         8
#define TX_DESC_STATUS_OFFSET        9
#define TX_DESC_RETRY_OFFSET         10
#define TX_DESC_RATE_OFFSET          11
#define TX_DESC_KEY_INDEX_OFFSET     12
#define TX_DESC_CIPHER_TYPE_OFFSET   13
#define TX_DESC_CIPHER_LENGTH_OFFSET 14
#define TX_DESC_PACKET_TYPE_OFFSET   17
#define TX_DESC_HOST_LENGTH_OFFSET   18

/*
 * Host-MAC interface
 */

#define TX_STATUS_SUCCESS       0x00

#define TX_FIRM_OWN             0x80
#define TX_DONE                 0x40

#define TX_ERROR                0x01

#define TX_PACKET_TYPE_DATA     0x01
#define TX_PACKET_TYPE_MGMT     0x02

#define ISR_EMPTY               0x00        /* no bits set in ISR */
#define ISR_TxCOMPLETE          0x01        /* packet transmitted */
#define ISR_RxCOMPLETE          0x02        /* packet received */
#define ISR_RxFRAMELOST         0x04        /* Rx Frame lost */
#define ISR_FATAL_ERROR         0x08        /* Fatal error */
#define ISR_COMMAND_COMPLETE    0x10        /* command completed */
#define ISR_OUT_OF_RANGE        0x20        /* command completed */
#define ISR_IBSS_MERGE          0x40        /* (4.1.2.30): IBSS merge */
#define ISR_GENERIC_IRQ         0x80

#define Local_Mib_Type          0x01
#define Mac_Address_Mib_Type    0x02
#define Mac_Mib_Type            0x03
#define Statistics_Mib_Type     0x04
#define Mac_Mgmt_Mib_Type       0x05
#define Mac_Wep_Mib_Type        0x06
#define Phy_Mib_Type            0x07
#define Multi_Domain_MIB        0x08

#define MAC_MGMT_MIB_CUR_BSSID_POS            14
#define MAC_MIB_FRAG_THRESHOLD_POS            8
#define MAC_MIB_RTS_THRESHOLD_POS             10
#define MAC_MIB_SHORT_RETRY_POS               16
#define MAC_MIB_LONG_RETRY_POS                17
#define MAC_MIB_SHORT_RETRY_LIMIT_POS         16
#define MAC_MGMT_MIB_BEACON_PER_POS           0
#define MAC_MGMT_MIB_STATION_ID_POS           6
#define MAC_MGMT_MIB_CUR_PRIVACY_POS          11
#define MAC_MGMT_MIB_CUR_BSSID_POS            14
#define MAC_MGMT_MIB_PS_MODE_POS              53
#define MAC_MGMT_MIB_LISTEN_INTERVAL_POS      54
#define MAC_MGMT_MIB_MULTI_DOMAIN_IMPLEMENTED 56
#define MAC_MGMT_MIB_MULTI_DOMAIN_ENABLED     57
#define PHY_MIB_CHANNEL_POS                   14
#define PHY_MIB_RATE_SET_POS                  20
#define PHY_MIB_REG_DOMAIN_POS                26
#define LOCAL_MIB_AUTO_TX_RATE_POS            3
#define LOCAL_MIB_SSID_SIZE                   5
#define LOCAL_MIB_TX_PROMISCUOUS_POS          6
#define LOCAL_MIB_TX_MGMT_RATE_POS            7
#define LOCAL_MIB_TX_CONTROL_RATE_POS         8
#define LOCAL_MIB_PREAMBLE_TYPE               9
#define MAC_ADDR_MIB_MAC_ADDR_POS             0

#define         CMD_Set_MIB_Vars              0x01
#define         CMD_Get_MIB_Vars              0x02
#define         CMD_Scan                      0x03
#define         CMD_Join                      0x04
#define         CMD_Start                     0x05
#define         CMD_EnableRadio               0x06
#define         CMD_DisableRadio              0x07
#define         CMD_SiteSurvey                0x0B

#define         CMD_STATUS_IDLE                   0x00
#define         CMD_STATUS_COMPLETE               0x01
#define         CMD_STATUS_UNKNOWN                0x02
#define         CMD_STATUS_INVALID_PARAMETER      0x03
#define         CMD_STATUS_FUNCTION_NOT_SUPPORTED 0x04
#define         CMD_STATUS_TIME_OUT               0x07
#define         CMD_STATUS_IN_PROGRESS            0x08
#define         CMD_STATUS_REJECTED_RADIO_OFF     0x09
#define         CMD_STATUS_HOST_ERROR             0xFF
#define         CMD_STATUS_BUSY                   0xFE

#define CMD_BLOCK_COMMAND_OFFSET        0
#define CMD_BLOCK_STATUS_OFFSET         1
#define CMD_BLOCK_PARAMETERS_OFFSET     4

#define SCAN_OPTIONS_SITE_SURVEY        0x80

#define MGMT_FRAME_BODY_OFFSET		24
#define MAX_AUTHENTICATION_RETRIES	3
#define MAX_ASSOCIATION_RETRIES		3

#define AUTHENTICATION_RESPONSE_TIME_OUT  1000

#define MAX_WIRELESS_BODY  2316 /* mtu is 2312, CRC is 4 */
#define LOOP_RETRY_LIMIT   500000

#define ACTIVE_MODE	1
#define PS_MODE		2

#define MAX_ENCRYPTION_KEYS 4
#define MAX_ENCRYPTION_KEY_SIZE 40

/*
 * 802.11 related definitions
 */

/*
 * Regulatory Domains
 */

#define REG_DOMAIN_FCC		0x10	/* Channels	1-11	USA				*/
#define REG_DOMAIN_DOC		0x20	/* Channel	1-11	Canada				*/
#define REG_DOMAIN_ETSI		0x30	/* Channel	1-13	Europe (ex Spain/France)	*/
#define REG_DOMAIN_SPAIN	0x31	/* Channel	10-11	Spain				*/
#define REG_DOMAIN_FRANCE	0x32	/* Channel	10-13	France				*/
#define REG_DOMAIN_MKK		0x40	/* Channel	14	Japan				*/
#define REG_DOMAIN_MKK1		0x41	/* Channel	1-14	Japan(MKK1)			*/
#define REG_DOMAIN_ISRAEL	0x50	/* Channel	3-9	ISRAEL				*/

#define BSS_TYPE_AD_HOC		1
#define BSS_TYPE_INFRASTRUCTURE 2

#define SCAN_TYPE_ACTIVE	0
#define SCAN_TYPE_PASSIVE	1

#define LONG_PREAMBLE		0
#define SHORT_PREAMBLE		1
#define AUTO_PREAMBLE		2

#define DATA_FRAME_WS_HEADER_SIZE   30

/* promiscuous mode control */
#define PROM_MODE_OFF			0x0
#define PROM_MODE_UNKNOWN		0x1
#define PROM_MODE_CRC_FAILED		0x2
#define PROM_MODE_DUPLICATED		0x4
#define PROM_MODE_MGMT			0x8
#define PROM_MODE_CTRL			0x10
#define PROM_MODE_BAD_PROTOCOL		0x20

#define IFACE_INT_STATUS_OFFSET		0
#define IFACE_INT_MASK_OFFSET		1
#define IFACE_LOCKOUT_HOST_OFFSET	2
#define IFACE_LOCKOUT_MAC_OFFSET	3
#define IFACE_FUNC_CTRL_OFFSET		28
#define IFACE_MAC_STAT_OFFSET		30
#define IFACE_GENERIC_INT_TYPE_OFFSET	32

#define CIPHER_SUITE_NONE     0
#define CIPHER_SUITE_WEP_64   1
#define CIPHER_SUITE_TKIP     2
#define CIPHER_SUITE_AES      3
#define CIPHER_SUITE_CCX      4
#define CIPHER_SUITE_WEP_128  5

/*
 * IFACE MACROS & definitions
 */

/*
 * FuncCtrl field:
 */
#define FUNC_CTRL_TxENABLE		0x10
#define FUNC_CTRL_RxENABLE		0x20
#define FUNC_CTRL_INIT_COMPLETE		0x01

/* A stub firmware image which reads the MAC address from NVRAM on the card.
   For copyright information and source see the end of this file. */
static u8 mac_reader[] = {
	0x06, 0x00, 0x00, 0xea, 0x04, 0x00, 0x00, 0xea, 0x03, 0x00, 0x00, 0xea, 0x02, 0x00, 0x00, 0xea,
	0x01, 0x00, 0x00, 0xea, 0x00, 0x00, 0x00, 0xea, 0xff, 0xff, 0xff, 0xea, 0xfe, 0xff, 0xff, 0xea,
	0xd3, 0x00, 0xa0, 0xe3, 0x00, 0xf0, 0x21, 0xe1, 0x0e, 0x04, 0xa0, 0xe3, 0x00, 0x10, 0xa0, 0xe3,
	0x81, 0x11, 0xa0, 0xe1, 0x00, 0x10, 0x81, 0xe3, 0x00, 0x10, 0x80, 0xe5, 0x1c, 0x10, 0x90, 0xe5,
	0x10, 0x10, 0xc1, 0xe3, 0x1c, 0x10, 0x80, 0xe5, 0x01, 0x10, 0xa0, 0xe3, 0x08, 0x10, 0x80, 0xe5,
	0x02, 0x03, 0xa0, 0xe3, 0x00, 0x10, 0xa0, 0xe3, 0xb0, 0x10, 0xc0, 0xe1, 0xb4, 0x10, 0xc0, 0xe1,
	0xb8, 0x10, 0xc0, 0xe1, 0xbc, 0x10, 0xc0, 0xe1, 0x56, 0xdc, 0xa0, 0xe3, 0x21, 0x00, 0x00, 0xeb,
	0x0a, 0x00, 0xa0, 0xe3, 0x1a, 0x00, 0x00, 0xeb, 0x10, 0x00, 0x00, 0xeb, 0x07, 0x00, 0x00, 0xeb,
	0x02, 0x03, 0xa0, 0xe3, 0x02, 0x14, 0xa0, 0xe3, 0xb4, 0x10, 0xc0, 0xe1, 0x4c, 0x10, 0x9f, 0xe5,
	0xbc, 0x10, 0xc0, 0xe1, 0x10, 0x10, 0xa0, 0xe3, 0xb8, 0x10, 0xc0, 0xe1, 0xfe, 0xff, 0xff, 0xea,
	0x00, 0x40, 0x2d, 0xe9, 0x00, 0x20, 0xa0, 0xe3, 0x02, 0x3c, 0xa0, 0xe3, 0x00, 0x10, 0xa0, 0xe3,
	0x28, 0x00, 0x9f, 0xe5, 0x37, 0x00, 0x00, 0xeb, 0x00, 0x40, 0xbd, 0xe8, 0x1e, 0xff, 0x2f, 0xe1,
	0x00, 0x40, 0x2d, 0xe9, 0x12, 0x2e, 0xa0, 0xe3, 0x06, 0x30, 0xa0, 0xe3, 0x00, 0x10, 0xa0, 0xe3,
	0x02, 0x04, 0xa0, 0xe3, 0x2f, 0x00, 0x00, 0xeb, 0x00, 0x40, 0xbd, 0xe8, 0x1e, 0xff, 0x2f, 0xe1,
	0x00, 0x02, 0x00, 0x02, 0x80, 0x01, 0x90, 0xe0, 0x01, 0x00, 0x00, 0x0a, 0x01, 0x00, 0x50, 0xe2,
	0xfc, 0xff, 0xff, 0xea, 0x1e, 0xff, 0x2f, 0xe1, 0x80, 0x10, 0xa0, 0xe3, 0xf3, 0x06, 0xa0, 0xe3,
	0x00, 0x10, 0x80, 0xe5, 0x00, 0x10, 0xa0, 0xe3, 0x00, 0x10, 0x80, 0xe5, 0x01, 0x10, 0xa0, 0xe3,
	0x04, 0x10, 0x80, 0xe5, 0x00, 0x10, 0x80, 0xe5, 0x0e, 0x34, 0xa0, 0xe3, 0x1c, 0x10, 0x93, 0xe5,
	0x02, 0x1a, 0x81, 0xe3, 0x1c, 0x10, 0x83, 0xe5, 0x58, 0x11, 0x9f, 0xe5, 0x30, 0x10, 0x80, 0xe5,
	0x54, 0x11, 0x9f, 0xe5, 0x34, 0x10, 0x80, 0xe5, 0x38, 0x10, 0x80, 0xe5, 0x3c, 0x10, 0x80, 0xe5,
	0x10, 0x10, 0x90, 0xe5, 0x08, 0x00, 0x90, 0xe5, 0x1e, 0xff, 0x2f, 0xe1, 0xf3, 0x16, 0xa0, 0xe3,
	0x08, 0x00, 0x91, 0xe5, 0x05, 0x00, 0xa0, 0xe3, 0x0c, 0x00, 0x81, 0xe5, 0x10, 0x00, 0x91, 0xe5,
	0x02, 0x00, 0x10, 0xe3, 0xfc, 0xff, 0xff, 0x0a, 0xff, 0x00, 0xa0, 0xe3, 0x0c, 0x00, 0x81, 0xe5,
	0x10, 0x00, 0x91, 0xe5, 0x02, 0x00, 0x10, 0xe3, 0xfc, 0xff, 0xff, 0x0a, 0x08, 0x00, 0x91, 0xe5,
	0x10, 0x00, 0x91, 0xe5, 0x01, 0x00, 0x10, 0xe3, 0xfc, 0xff, 0xff, 0x0a, 0x08, 0x00, 0x91, 0xe5,
	0xff, 0x00, 0x00, 0xe2, 0x1e, 0xff, 0x2f, 0xe1, 0x30, 0x40, 0x2d, 0xe9, 0x00, 0x50, 0xa0, 0xe1,
	0x03, 0x40, 0xa0, 0xe1, 0xa2, 0x02, 0xa0, 0xe1, 0x08, 0x00, 0x00, 0xe2, 0x03, 0x00, 0x80, 0xe2,
	0xd8, 0x10, 0x9f, 0xe5, 0x00, 0x00, 0xc1, 0xe5, 0x01, 0x20, 0xc1, 0xe5, 0xe2, 0xff, 0xff, 0xeb,
	0x01, 0x00, 0x10, 0xe3, 0xfc, 0xff, 0xff, 0x1a, 0x14, 0x00, 0xa0, 0xe3, 0xc4, 0xff, 0xff, 0xeb,
	0x04, 0x20, 0xa0, 0xe1, 0x05, 0x10, 0xa0, 0xe1, 0x02, 0x00, 0xa0, 0xe3, 0x01, 0x00, 0x00, 0xeb,
	0x30, 0x40, 0xbd, 0xe8, 0x1e, 0xff, 0x2f, 0xe1, 0x70, 0x40, 0x2d, 0xe9, 0xf3, 0x46, 0xa0, 0xe3,
	0x00, 0x30, 0xa0, 0xe3, 0x00, 0x00, 0x50, 0xe3, 0x08, 0x00, 0x00, 0x9a, 0x8c, 0x50, 0x9f, 0xe5,
	0x03, 0x60, 0xd5, 0xe7, 0x0c, 0x60, 0x84, 0xe5, 0x10, 0x60, 0x94, 0xe5, 0x02, 0x00, 0x16, 0xe3,
	0xfc, 0xff, 0xff, 0x0a, 0x01, 0x30, 0x83, 0xe2, 0x00, 0x00, 0x53, 0xe1, 0xf7, 0xff, 0xff, 0x3a,
	0xff, 0x30, 0xa0, 0xe3, 0x0c, 0x30, 0x84, 0xe5, 0x08, 0x00, 0x94, 0xe5, 0x10, 0x00, 0x94, 0xe5,
	0x01, 0x00, 0x10, 0xe3, 0xfc, 0xff, 0xff, 0x0a, 0x08, 0x00, 0x94, 0xe5, 0x00, 0x00, 0xa0, 0xe3,
	0x00, 0x00, 0x52, 0xe3, 0x0b, 0x00, 0x00, 0x9a, 0x10, 0x50, 0x94, 0xe5, 0x02, 0x00, 0x15, 0xe3,
	0xfc, 0xff, 0xff, 0x0a, 0x0c, 0x30, 0x84, 0xe5, 0x10, 0x50, 0x94, 0xe5, 0x01, 0x00, 0x15, 0xe3,
	0xfc, 0xff, 0xff, 0x0a, 0x08, 0x50, 0x94, 0xe5, 0x01, 0x50, 0xc1, 0xe4, 0x01, 0x00, 0x80, 0xe2,
	0x02, 0x00, 0x50, 0xe1, 0xf3, 0xff, 0xff, 0x3a, 0xc8, 0x00, 0xa0, 0xe3, 0x98, 0xff, 0xff, 0xeb,
	0x70, 0x40, 0xbd, 0xe8, 0x1e, 0xff, 0x2f, 0xe1, 0x01, 0x0c, 0x00, 0x02, 0x01, 0x02, 0x00, 0x02,
	0x00, 0x01, 0x00, 0x02
};

struct atmel_private {
	void *card; /* Bus dependent structure varies for PCcard */
	int (*present_callback)(void *); /* And callback which uses it */
	char firmware_id[32];
	AtmelFWType firmware_type;
	u8 *firmware;
	int firmware_length;
	struct timer_list management_timer;
	struct net_device *dev;
	struct device *sys_dev;
	struct iw_statistics wstats;
	spinlock_t irqlock, timerlock;	/* spinlocks */
	enum { BUS_TYPE_PCCARD, BUS_TYPE_PCI } bus_type;
	enum {
		CARD_TYPE_PARALLEL_FLASH,
		CARD_TYPE_SPI_FLASH,
		CARD_TYPE_EEPROM
	} card_type;
	int do_rx_crc; /* If we need to CRC incoming packets */
	int probe_crc; /* set if we don't yet know */
	int crc_ok_cnt, crc_ko_cnt; /* counters for probing */
	u16 rx_desc_head;
	u16 tx_desc_free, tx_desc_head, tx_desc_tail, tx_desc_previous;
	u16 tx_free_mem, tx_buff_head, tx_buff_tail;

	u16 frag_seq, frag_len, frag_no;
	u8 frag_source[6];

	u8 wep_is_on, default_key, exclude_unencrypted, encryption_level;
	u8 group_cipher_suite, pairwise_cipher_suite;
	u8 wep_keys[MAX_ENCRYPTION_KEYS][MAX_ENCRYPTION_KEY_SIZE];
	int wep_key_len[MAX_ENCRYPTION_KEYS];
	int use_wpa, radio_on_broken; /* firmware dependent stuff. */

	u16 host_info_base;
	struct host_info_struct {
		/* NB this is matched to the hardware, don't change. */
		u8 volatile int_status;
		u8 volatile int_mask;
		u8 volatile lockout_host;
		u8 volatile lockout_mac;

		u16 tx_buff_pos;
		u16 tx_buff_size;
		u16 tx_desc_pos;
		u16 tx_desc_count;

		u16 rx_buff_pos;
		u16 rx_buff_size;
		u16 rx_desc_pos;
		u16 rx_desc_count;

		u16 build_version;
		u16 command_pos;

		u16 major_version;
		u16 minor_version;

		u16 func_ctrl;
		u16 mac_status;
		u16 generic_IRQ_type;
		u8  reserved[2];
	} host_info;

	enum {
		STATION_STATE_SCANNING,
		STATION_STATE_JOINNING,
		STATION_STATE_AUTHENTICATING,
		STATION_STATE_ASSOCIATING,
		STATION_STATE_READY,
		STATION_STATE_REASSOCIATING,
		STATION_STATE_DOWN,
		STATION_STATE_MGMT_ERROR
	} station_state;

	int operating_mode, power_mode;
	time_t last_qual;
	int beacons_this_sec;
	int channel;
	int reg_domain, config_reg_domain;
	int tx_rate;
	int auto_tx_rate;
	int rts_threshold;
	int frag_threshold;
	int long_retry, short_retry;
	int preamble;
	int default_beacon_period, beacon_period, listen_interval;
	int CurrentAuthentTransactionSeqNum, ExpectedAuthentTransactionSeqNum;
	int AuthenticationRequestRetryCnt, AssociationRequestRetryCnt, ReAssociationRequestRetryCnt;
	enum {
		SITE_SURVEY_IDLE,
		SITE_SURVEY_IN_PROGRESS,
		SITE_SURVEY_COMPLETED
	} site_survey_state;
	unsigned long last_survey;

	int station_was_associated, station_is_associated;
	int fast_scan;

	struct bss_info {
		int channel;
		int SSIDsize;
		int RSSI;
		int UsingWEP;
		int preamble;
		int beacon_period;
		int BSStype;
		u8 BSSID[6];
		u8 SSID[MAX_SSID_LENGTH];
	} BSSinfo[MAX_BSS_ENTRIES];
	int BSS_list_entries, current_BSS;
	int connect_to_any_BSS;
	int SSID_size, new_SSID_size;
	u8 CurrentBSSID[6], BSSID[6];
	u8 SSID[MAX_SSID_LENGTH], new_SSID[MAX_SSID_LENGTH];
	u64 last_beacon_timestamp;
	u8 rx_buf[MAX_WIRELESS_BODY];
};

static u8 atmel_basic_rates[4] = {0x82, 0x84, 0x0b, 0x16};

static const struct {
	int reg_domain;
	int min, max;
	char *name;
} channel_table[] = { { REG_DOMAIN_FCC, 1, 11, "USA" },
		      { REG_DOMAIN_DOC, 1, 11, "Canada" },
		      { REG_DOMAIN_ETSI, 1, 13, "Europe" },
		      { REG_DOMAIN_SPAIN, 10, 11, "Spain" },
		      { REG_DOMAIN_FRANCE, 10, 13, "France" },
		      { REG_DOMAIN_MKK, 14, 14, "MKK" },
		      { REG_DOMAIN_MKK1, 1, 14, "MKK1" },
		      { REG_DOMAIN_ISRAEL, 3, 9, "Israel"} };

static void build_wpa_mib(struct atmel_private *priv);
static int atmel_ioctl(struct net_device *dev, struct ifreq *rq, int cmd);
static void atmel_copy_to_card(struct net_device *dev, u16 dest,
			       const unsigned char *src, u16 len);
static void atmel_copy_to_host(struct net_device *dev, unsigned char *dest,
			       u16 src, u16 len);
static void atmel_set_gcr(struct net_device *dev, u16 mask);
static void atmel_clear_gcr(struct net_device *dev, u16 mask);
static int atmel_lock_mac(struct atmel_private *priv);
static void atmel_wmem32(struct atmel_private *priv, u16 pos, u32 data);
static void atmel_command_irq(struct atmel_private *priv);
static int atmel_validate_channel(struct atmel_private *priv, int channel);
static void atmel_management_frame(struct atmel_private *priv,
				   struct ieee80211_hdr *header,
				   u16 frame_len, u8 rssi);
static void atmel_management_timer(u_long a);
static void atmel_send_command(struct atmel_private *priv, int command,
			       void *cmd, int cmd_size);
static int atmel_send_command_wait(struct atmel_private *priv, int command,
				   void *cmd, int cmd_size);
static void atmel_transmit_management_frame(struct atmel_private *priv,
					    struct ieee80211_hdr *header,
					    u8 *body, int body_len);

static u8 atmel_get_mib8(struct atmel_private *priv, u8 type, u8 index);
static void atmel_set_mib8(struct atmel_private *priv, u8 type, u8 index,
			   u8 data);
static void atmel_set_mib16(struct atmel_private *priv, u8 type, u8 index,
			    u16 data);
static void atmel_set_mib(struct atmel_private *priv, u8 type, u8 index,
			  u8 *data, int data_len);
static void atmel_get_mib(struct atmel_private *priv, u8 type, u8 index,
			  u8 *data, int data_len);
static void atmel_scan(struct atmel_private *priv, int specific_ssid);
static void atmel_join_bss(struct atmel_private *priv, int bss_index);
static void atmel_smooth_qual(struct atmel_private *priv);
static void atmel_writeAR(struct net_device *dev, u16 data);
static int probe_atmel_card(struct net_device *dev);
static int reset_atmel_card(struct net_device *dev);
static void atmel_enter_state(struct atmel_private *priv, int new_state);
int atmel_open (struct net_device *dev);

static inline u16 atmel_hi(struct atmel_private *priv, u16 offset)
{
	return priv->host_info_base + offset;
}

static inline u16 atmel_co(struct atmel_private *priv, u16 offset)
{
	return priv->host_info.command_pos + offset;
}

static inline u16 atmel_rx(struct atmel_private *priv, u16 offset, u16 desc)
{
	return priv->host_info.rx_desc_pos + (sizeof(struct rx_desc) * desc) + offset;
}

static inline u16 atmel_tx(struct atmel_private *priv, u16 offset, u16 desc)
{
	return priv->host_info.tx_desc_pos + (sizeof(struct tx_desc) * desc) + offset;
}

static inline u8 atmel_read8(struct net_device *dev, u16 offset)
{
	return inb(dev->base_addr + offset);
}

static inline void atmel_write8(struct net_device *dev, u16 offset, u8 data)
{
	outb(data, dev->base_addr + offset);
}

static inline u16 atmel_read16(struct net_device *dev, u16 offset)
{
	return inw(dev->base_addr + offset);
}

static inline void atmel_write16(struct net_device *dev, u16 offset, u16 data)
{
	outw(data, dev->base_addr + offset);
}

static inline u8 atmel_rmem8(struct atmel_private *priv, u16 pos)
{
	atmel_writeAR(priv->dev, pos);
	return atmel_read8(priv->dev, DR);
}

static inline void atmel_wmem8(struct atmel_private *priv, u16 pos, u16 data)
{
	atmel_writeAR(priv->dev, pos);
	atmel_write8(priv->dev, DR, data);
}

static inline u16 atmel_rmem16(struct atmel_private *priv, u16 pos)
{
	atmel_writeAR(priv->dev, pos);
	return atmel_read16(priv->dev, DR);
}

static inline void atmel_wmem16(struct atmel_private *priv, u16 pos, u16 data)
{
	atmel_writeAR(priv->dev, pos);
	atmel_write16(priv->dev, DR, data);
}

static const struct iw_handler_def atmel_handler_def;

static void tx_done_irq(struct atmel_private *priv)
{
	int i;

	for (i = 0;
	     atmel_rmem8(priv, atmel_tx(priv, TX_DESC_FLAGS_OFFSET, priv->tx_desc_head)) == TX_DONE &&
		     i < priv->host_info.tx_desc_count;
	     i++) {
		u8 status = atmel_rmem8(priv, atmel_tx(priv, TX_DESC_STATUS_OFFSET, priv->tx_desc_head));
		u16 msdu_size = atmel_rmem16(priv, atmel_tx(priv, TX_DESC_SIZE_OFFSET, priv->tx_desc_head));
		u8 type = atmel_rmem8(priv, atmel_tx(priv, TX_DESC_PACKET_TYPE_OFFSET, priv->tx_desc_head));

		atmel_wmem8(priv, atmel_tx(priv, TX_DESC_FLAGS_OFFSET, priv->tx_desc_head), 0);

		priv->tx_free_mem += msdu_size;
		priv->tx_desc_free++;

		if (priv->tx_buff_head + msdu_size > (priv->host_info.tx_buff_pos + priv->host_info.tx_buff_size))
			priv->tx_buff_head = 0;
		else
			priv->tx_buff_head += msdu_size;

		if (priv->tx_desc_head < (priv->host_info.tx_desc_count - 1))
			priv->tx_desc_head++ ;
		else
			priv->tx_desc_head = 0;

		if (type == TX_PACKET_TYPE_DATA) {
			if (status == TX_STATUS_SUCCESS)
				priv->dev->stats.tx_packets++;
			else
				priv->dev->stats.tx_errors++;
			netif_wake_queue(priv->dev);
		}
	}
}

static u16 find_tx_buff(struct atmel_private *priv, u16 len)
{
	u16 bottom_free = priv->host_info.tx_buff_size - priv->tx_buff_tail;

	if (priv->tx_desc_free == 3 || priv->tx_free_mem < len)
		return 0;

	if (bottom_free >= len)
		return priv->host_info.tx_buff_pos + priv->tx_buff_tail;

	if (priv->tx_free_mem - bottom_free >= len) {
		priv->tx_buff_tail = 0;
		return priv->host_info.tx_buff_pos;
	}

	return 0;
}

static void tx_update_descriptor(struct atmel_private *priv, int is_bcast,
				 u16 len, u16 buff, u8 type)
{
	atmel_wmem16(priv, atmel_tx(priv, TX_DESC_POS_OFFSET, priv->tx_desc_tail), buff);
	atmel_wmem16(priv, atmel_tx(priv, TX_DESC_SIZE_OFFSET, priv->tx_desc_tail), len);
	if (!priv->use_wpa)
		atmel_wmem16(priv, atmel_tx(priv, TX_DESC_HOST_LENGTH_OFFSET, priv->tx_desc_tail), len);
	atmel_wmem8(priv, atmel_tx(priv, TX_DESC_PACKET_TYPE_OFFSET, priv->tx_desc_tail), type);
	atmel_wmem8(priv, atmel_tx(priv, TX_DESC_RATE_OFFSET, priv->tx_desc_tail), priv->tx_rate);
	atmel_wmem8(priv, atmel_tx(priv, TX_DESC_RETRY_OFFSET, priv->tx_desc_tail), 0);
	if (priv->use_wpa) {
		int cipher_type, cipher_length;
		if (is_bcast) {
			cipher_type = priv->group_cipher_suite;
			if (cipher_type == CIPHER_SUITE_WEP_64 ||
			    cipher_type == CIPHER_SUITE_WEP_128)
				cipher_length = 8;
			else if (cipher_type == CIPHER_SUITE_TKIP)
				cipher_length = 12;
			else if (priv->pairwise_cipher_suite == CIPHER_SUITE_WEP_64 ||
				 priv->pairwise_cipher_suite == CIPHER_SUITE_WEP_128) {
				cipher_type = priv->pairwise_cipher_suite;
				cipher_length = 8;
			} else {
				cipher_type = CIPHER_SUITE_NONE;
				cipher_length = 0;
			}
		} else {
			cipher_type = priv->pairwise_cipher_suite;
			if (cipher_type == CIPHER_SUITE_WEP_64 ||
			    cipher_type == CIPHER_SUITE_WEP_128)
				cipher_length = 8;
			else if (cipher_type == CIPHER_SUITE_TKIP)
				cipher_length = 12;
			else if (priv->group_cipher_suite == CIPHER_SUITE_WEP_64 ||
				 priv->group_cipher_suite == CIPHER_SUITE_WEP_128) {
				cipher_type = priv->group_cipher_suite;
				cipher_length = 8;
			} else {
				cipher_type = CIPHER_SUITE_NONE;
				cipher_length = 0;
			}
		}

		atmel_wmem8(priv, atmel_tx(priv, TX_DESC_CIPHER_TYPE_OFFSET, priv->tx_desc_tail),
			    cipher_type);
		atmel_wmem8(priv, atmel_tx(priv, TX_DESC_CIPHER_LENGTH_OFFSET, priv->tx_desc_tail),
			    cipher_length);
	}
	atmel_wmem32(priv, atmel_tx(priv, TX_DESC_NEXT_OFFSET, priv->tx_desc_tail), 0x80000000L);
	atmel_wmem8(priv, atmel_tx(priv, TX_DESC_FLAGS_OFFSET, priv->tx_desc_tail), TX_FIRM_OWN);
	if (priv->tx_desc_previous != priv->tx_desc_tail)
		atmel_wmem32(priv, atmel_tx(priv, TX_DESC_NEXT_OFFSET, priv->tx_desc_previous), 0);
	priv->tx_desc_previous = priv->tx_desc_tail;
	if (priv->tx_desc_tail < (priv->host_info.tx_desc_count - 1))
		priv->tx_desc_tail++;
	else
		priv->tx_desc_tail = 0;
	priv->tx_desc_free--;
	priv->tx_free_mem -= len;
}

static netdev_tx_t start_tx(struct sk_buff *skb, struct net_device *dev)
{
	static const u8 SNAP_RFC1024[6] = { 0xaa, 0xaa, 0x03, 0x00, 0x00, 0x00 };
	struct atmel_private *priv = netdev_priv(dev);
	struct ieee80211_hdr header;
	unsigned long flags;
	u16 buff, frame_ctl, len = (ETH_ZLEN < skb->len) ? skb->len : ETH_ZLEN;

	if (priv->card && priv->present_callback &&
	    !(*priv->present_callback)(priv->card)) {
		dev->stats.tx_errors++;
		dev_kfree_skb(skb);
		return NETDEV_TX_OK;
	}

	if (priv->station_state != STATION_STATE_READY) {
		dev->stats.tx_errors++;
		dev_kfree_skb(skb);
		return NETDEV_TX_OK;
	}

	/* first ensure the timer func cannot run */
	spin_lock_bh(&priv->timerlock);
	/* then stop the hardware ISR */
	spin_lock_irqsave(&priv->irqlock, flags);
	/* nb doing the above in the opposite order will deadlock */

	/* The Wireless Header is 30 bytes. In the Ethernet packet we "cut" the
	   12 first bytes (containing DA/SA) and put them in the appropriate
	   fields of the Wireless Header. Thus the packet length is then the
	   initial + 18 (+30-12) */

	if (!(buff = find_tx_buff(priv, len + 18))) {
		dev->stats.tx_dropped++;
		spin_unlock_irqrestore(&priv->irqlock, flags);
		spin_unlock_bh(&priv->timerlock);
		netif_stop_queue(dev);
		return NETDEV_TX_BUSY;
	}

	frame_ctl = IEEE80211_FTYPE_DATA;
	header.duration_id = 0;
	header.seq_ctrl = 0;
	if (priv->wep_is_on)
		frame_ctl |= IEEE80211_FCTL_PROTECTED;
	if (priv->operating_mode == IW_MODE_ADHOC) {
		skb_copy_from_linear_data(skb, &header.addr1, 6);
		memcpy(&header.addr2, dev->dev_addr, 6);
		memcpy(&header.addr3, priv->BSSID, 6);
	} else {
		frame_ctl |= IEEE80211_FCTL_TODS;
		memcpy(&header.addr1, priv->CurrentBSSID, 6);
		memcpy(&header.addr2, dev->dev_addr, 6);
		skb_copy_from_linear_data(skb, &header.addr3, 6);
	}

	if (priv->use_wpa)
		memcpy(&header.addr4, SNAP_RFC1024, 6);

	header.frame_control = cpu_to_le16(frame_ctl);
	/* Copy the wireless header into the card */
	atmel_copy_to_card(dev, buff, (unsigned char *)&header, DATA_FRAME_WS_HEADER_SIZE);
	/* Copy the packet sans its 802.3 header addresses which have been replaced */
	atmel_copy_to_card(dev, buff + DATA_FRAME_WS_HEADER_SIZE, skb->data + 12, len - 12);
	priv->tx_buff_tail += len - 12 + DATA_FRAME_WS_HEADER_SIZE;

	/* low bit of first byte of destination tells us if broadcast */
	tx_update_descriptor(priv, *(skb->data) & 0x01, len + 18, buff, TX_PACKET_TYPE_DATA);
	dev->stats.tx_bytes += len;

	spin_unlock_irqrestore(&priv->irqlock, flags);
	spin_unlock_bh(&priv->timerlock);
	dev_kfree_skb(skb);

	return NETDEV_TX_OK;
}

static void atmel_transmit_management_frame(struct atmel_private *priv,
					    struct ieee80211_hdr *header,
					    u8 *body, int body_len)
{
	u16 buff;
	int len = MGMT_FRAME_BODY_OFFSET + body_len;

	if (!(buff = find_tx_buff(priv, len)))
		return;

	atmel_copy_to_card(priv->dev, buff, (u8 *)header, MGMT_FRAME_BODY_OFFSET);
	atmel_copy_to_card(priv->dev, buff + MGMT_FRAME_BODY_OFFSET, body, body_len);
	priv->tx_buff_tail += len;
	tx_update_descriptor(priv, header->addr1[0] & 0x01, len, buff, TX_PACKET_TYPE_MGMT);
}

static void fast_rx_path(struct atmel_private *priv,
			 struct ieee80211_hdr *header,
			 u16 msdu_size, u16 rx_packet_loc, u32 crc)
{
	/* fast path: unfragmented packet copy directly into skbuf */
	u8 mac4[6];
	struct sk_buff	*skb;
	unsigned char *skbp;

	/* get the final, mac 4 header field, this tells us encapsulation */
	atmel_copy_to_host(priv->dev, mac4, rx_packet_loc + 24, 6);
	msdu_size -= 6;

	if (priv->do_rx_crc) {
		crc = crc32_le(crc, mac4, 6);
		msdu_size -= 4;
	}

	if (!(skb = dev_alloc_skb(msdu_size + 14))) {
		priv->dev->stats.rx_dropped++;
		return;
	}

	skb_reserve(skb, 2);
	skbp = skb_put(skb, msdu_size + 12);
	atmel_copy_to_host(priv->dev, skbp + 12, rx_packet_loc + 30, msdu_size);

	if (priv->do_rx_crc) {
		u32 netcrc;
		crc = crc32_le(crc, skbp + 12, msdu_size);
		atmel_copy_to_host(priv->dev, (void *)&netcrc, rx_packet_loc + 30 + msdu_size, 4);
		if ((crc ^ 0xffffffff) != netcrc) {
			priv->dev->stats.rx_crc_errors++;
			dev_kfree_skb(skb);
			return;
		}
	}

	memcpy(skbp, header->addr1, 6); /* destination address */
	if (le16_to_cpu(header->frame_control) & IEEE80211_FCTL_FROMDS)
		memcpy(&skbp[6], header->addr3, 6);
	else
		memcpy(&skbp[6], header->addr2, 6); /* source address */

	skb->protocol = eth_type_trans(skb, priv->dev);
	skb->ip_summed = CHECKSUM_NONE;
	netif_rx(skb);
	priv->dev->stats.rx_bytes += 12 + msdu_size;
	priv->dev->stats.rx_packets++;
}

/* Test to see if the packet in card memory at packet_loc has a valid CRC
   It doesn't matter that this is slow: it is only used to proble the first few
   packets. */
static int probe_crc(struct atmel_private *priv, u16 packet_loc, u16 msdu_size)
{
	int i = msdu_size - 4;
	u32 netcrc, crc = 0xffffffff;

	if (msdu_size < 4)
		return 0;

	atmel_copy_to_host(priv->dev, (void *)&netcrc, packet_loc + i, 4);

	atmel_writeAR(priv->dev, packet_loc);
	while (i--) {
		u8 octet = atmel_read8(priv->dev, DR);
		crc = crc32_le(crc, &octet, 1);
	}

	return (crc ^ 0xffffffff) == netcrc;
}

static void frag_rx_path(struct atmel_private *priv,
			 struct ieee80211_hdr *header,
			 u16 msdu_size, u16 rx_packet_loc, u32 crc, u16 seq_no,
			 u8 frag_no, int more_frags)
{
	u8 mac4[6];
	u8 source[6];
	struct sk_buff *skb;

	if (le16_to_cpu(header->frame_control) & IEEE80211_FCTL_FROMDS)
		memcpy(source, header->addr3, 6);
	else
		memcpy(source, header->addr2, 6);

	rx_packet_loc += 24; /* skip header */

	if (priv->do_rx_crc)
		msdu_size -= 4;

	if (frag_no == 0) { /* first fragment */
		atmel_copy_to_host(priv->dev, mac4, rx_packet_loc, 6);
		msdu_size -= 6;
		rx_packet_loc += 6;

		if (priv->do_rx_crc)
			crc = crc32_le(crc, mac4, 6);

		priv->frag_seq = seq_no;
		priv->frag_no = 1;
		priv->frag_len = msdu_size;
		memcpy(priv->frag_source, source, 6);
		memcpy(&priv->rx_buf[6], source, 6);
		memcpy(priv->rx_buf, header->addr1, 6);

		atmel_copy_to_host(priv->dev, &priv->rx_buf[12], rx_packet_loc, msdu_size);

		if (priv->do_rx_crc) {
			u32 netcrc;
			crc = crc32_le(crc, &priv->rx_buf[12], msdu_size);
			atmel_copy_to_host(priv->dev, (void *)&netcrc, rx_packet_loc + msdu_size, 4);
			if ((crc ^ 0xffffffff) != netcrc) {
				priv->dev->stats.rx_crc_errors++;
				memset(priv->frag_source, 0xff, 6);
			}
		}

	} else if (priv->frag_no == frag_no &&
		   priv->frag_seq == seq_no &&
		   memcmp(priv->frag_source, source, 6) == 0) {

		atmel_copy_to_host(priv->dev, &priv->rx_buf[12 + priv->frag_len],
				   rx_packet_loc, msdu_size);
		if (priv->do_rx_crc) {
			u32 netcrc;
			crc = crc32_le(crc,
				       &priv->rx_buf[12 + priv->frag_len],
				       msdu_size);
			atmel_copy_to_host(priv->dev, (void *)&netcrc, rx_packet_loc + msdu_size, 4);
			if ((crc ^ 0xffffffff) != netcrc) {
				priv->dev->stats.rx_crc_errors++;
				memset(priv->frag_source, 0xff, 6);
				more_frags = 1; /* don't send broken assembly */
			}
		}

		priv->frag_len += msdu_size;
		priv->frag_no++;

		if (!more_frags) { /* last one */
			memset(priv->frag_source, 0xff, 6);
			if (!(skb = dev_alloc_skb(priv->frag_len + 14))) {
				priv->dev->stats.rx_dropped++;
			} else {
				skb_reserve(skb, 2);
				memcpy(skb_put(skb, priv->frag_len + 12),
				       priv->rx_buf,
				       priv->frag_len + 12);
				skb->protocol = eth_type_trans(skb, priv->dev);
				skb->ip_summed = CHECKSUM_NONE;
				netif_rx(skb);
				priv->dev->stats.rx_bytes += priv->frag_len + 12;
				priv->dev->stats.rx_packets++;
			}
		}
	} else
		priv->wstats.discard.fragment++;
}

static void rx_done_irq(struct atmel_private *priv)
{
	int i;
	struct ieee80211_hdr header;

	for (i = 0;
	     atmel_rmem8(priv, atmel_rx(priv, RX_DESC_FLAGS_OFFSET, priv->rx_desc_head)) == RX_DESC_FLAG_VALID &&
		     i < priv->host_info.rx_desc_count;
	     i++) {

		u16 msdu_size, rx_packet_loc, frame_ctl, seq_control;
		u8 status = atmel_rmem8(priv, atmel_rx(priv, RX_DESC_STATUS_OFFSET, priv->rx_desc_head));
		u32 crc = 0xffffffff;

		if (status != RX_STATUS_SUCCESS) {
			if (status == 0xc1) /* determined by experiment */
				priv->wstats.discard.nwid++;
			else
				priv->dev->stats.rx_errors++;
			goto next;
		}

		msdu_size = atmel_rmem16(priv, atmel_rx(priv, RX_DESC_MSDU_SIZE_OFFSET, priv->rx_desc_head));
		rx_packet_loc = atmel_rmem16(priv, atmel_rx(priv, RX_DESC_MSDU_POS_OFFSET, priv->rx_desc_head));

		if (msdu_size < 30) {
			priv->dev->stats.rx_errors++;
			goto next;
		}

		/* Get header as far as end of seq_ctrl */
		atmel_copy_to_host(priv->dev, (char *)&header, rx_packet_loc, 24);
		frame_ctl = le16_to_cpu(header.frame_control);
		seq_control = le16_to_cpu(header.seq_ctrl);

		/* probe for CRC use here if needed  once five packets have
		   arrived with the same crc status, we assume we know what's
		   happening and stop probing */
		if (priv->probe_crc) {
			if (!priv->wep_is_on || !(frame_ctl & IEEE80211_FCTL_PROTECTED)) {
				priv->do_rx_crc = probe_crc(priv, rx_packet_loc, msdu_size);
			} else {
				priv->do_rx_crc = probe_crc(priv, rx_packet_loc + 24, msdu_size - 24);
			}
			if (priv->do_rx_crc) {
				if (priv->crc_ok_cnt++ > 5)
					priv->probe_crc = 0;
			} else {
				if (priv->crc_ko_cnt++ > 5)
					priv->probe_crc = 0;
			}
		}

		/* don't CRC header when WEP in use */
		if (priv->do_rx_crc && (!priv->wep_is_on || !(frame_ctl & IEEE80211_FCTL_PROTECTED))) {
			crc = crc32_le(0xffffffff, (unsigned char *)&header, 24);
		}
		msdu_size -= 24; /* header */

		if ((frame_ctl & IEEE80211_FCTL_FTYPE) == IEEE80211_FTYPE_DATA) {
			int more_fragments = frame_ctl & IEEE80211_FCTL_MOREFRAGS;
			u8 packet_fragment_no = seq_control & IEEE80211_SCTL_FRAG;
			u16 packet_sequence_no = (seq_control & IEEE80211_SCTL_SEQ) >> 4;

			if (!more_fragments && packet_fragment_no == 0) {
				fast_rx_path(priv, &header, msdu_size, rx_packet_loc, crc);
			} else {
				frag_rx_path(priv, &header, msdu_size, rx_packet_loc, crc,
					     packet_sequence_no, packet_fragment_no, more_fragments);
			}
		}

		if ((frame_ctl & IEEE80211_FCTL_FTYPE) == IEEE80211_FTYPE_MGMT) {
			/* copy rest of packet into buffer */
			atmel_copy_to_host(priv->dev, (unsigned char *)&priv->rx_buf, rx_packet_loc + 24, msdu_size);

			/* we use the same buffer for frag reassembly and control packets */
			memset(priv->frag_source, 0xff, 6);

			if (priv->do_rx_crc) {
				/* last 4 octets is crc */
				msdu_size -= 4;
				crc = crc32_le(crc, (unsigned char *)&priv->rx_buf, msdu_size);
				if ((crc ^ 0xffffffff) != (*((u32 *)&priv->rx_buf[msdu_size]))) {
					priv->dev->stats.rx_crc_errors++;
					goto next;
				}
			}

			atmel_management_frame(priv, &header, msdu_size,
					       atmel_rmem8(priv, atmel_rx(priv, RX_DESC_RSSI_OFFSET, priv->rx_desc_head)));
		}

next:
		/* release descriptor */
		atmel_wmem8(priv, atmel_rx(priv, RX_DESC_FLAGS_OFFSET, priv->rx_desc_head), RX_DESC_FLAG_CONSUMED);

		if (priv->rx_desc_head < (priv->host_info.rx_desc_count - 1))
			priv->rx_desc_head++;
		else
			priv->rx_desc_head = 0;
	}
}

static irqreturn_t service_interrupt(int irq, void *dev_id)
{
	struct net_device *dev = (struct net_device *) dev_id;
	struct atmel_private *priv = netdev_priv(dev);
	u8 isr;
	int i = -1;
	static const u8 irq_order[] = {
		ISR_OUT_OF_RANGE,
		ISR_RxCOMPLETE,
		ISR_TxCOMPLETE,
		ISR_RxFRAMELOST,
		ISR_FATAL_ERROR,
		ISR_COMMAND_COMPLETE,
		ISR_IBSS_MERGE,
		ISR_GENERIC_IRQ
	};

	if (priv->card && priv->present_callback &&
	    !(*priv->present_callback)(priv->card))
		return IRQ_HANDLED;

	/* In this state upper-level code assumes it can mess with
	   the card unhampered by interrupts which may change register state.
	   Note that even though the card shouldn't generate interrupts
	   the inturrupt line may be shared. This allows card setup
	   to go on without disabling interrupts for a long time. */
	if (priv->station_state == STATION_STATE_DOWN)
		return IRQ_NONE;

	atmel_clear_gcr(dev, GCR_ENINT); /* disable interrupts */

	while (1) {
		if (!atmel_lock_mac(priv)) {
			/* failed to contact card */
			printk(KERN_ALERT "%s: failed to contact MAC.\n", dev->name);
			return IRQ_HANDLED;
		}

		isr = atmel_rmem8(priv, atmel_hi(priv, IFACE_INT_STATUS_OFFSET));
		atmel_wmem8(priv, atmel_hi(priv, IFACE_LOCKOUT_MAC_OFFSET), 0);

		if (!isr) {
			atmel_set_gcr(dev, GCR_ENINT); /* enable interrupts */
			return i == -1 ? IRQ_NONE : IRQ_HANDLED;
		}

		atmel_set_gcr(dev, GCR_ACKINT); /* acknowledge interrupt */

		for (i = 0; i < ARRAY_SIZE(irq_order); i++)
			if (isr & irq_order[i])
				break;

		if (!atmel_lock_mac(priv)) {
			/* failed to contact card */
			printk(KERN_ALERT "%s: failed to contact MAC.\n", dev->name);
			return IRQ_HANDLED;
		}

		isr = atmel_rmem8(priv, atmel_hi(priv, IFACE_INT_STATUS_OFFSET));
		isr ^= irq_order[i];
		atmel_wmem8(priv, atmel_hi(priv, IFACE_INT_STATUS_OFFSET), isr);
		atmel_wmem8(priv, atmel_hi(priv, IFACE_LOCKOUT_MAC_OFFSET), 0);

		switch (irq_order[i]) {

		case ISR_OUT_OF_RANGE:
			if (priv->operating_mode == IW_MODE_INFRA &&
			    priv->station_state == STATION_STATE_READY) {
				priv->station_is_associated = 0;
				atmel_scan(priv, 1);
			}
			break;

		case ISR_RxFRAMELOST:
			priv->wstats.discard.misc++;
			/* fall through */
		case ISR_RxCOMPLETE:
			rx_done_irq(priv);
			break;

		case ISR_TxCOMPLETE:
			tx_done_irq(priv);
			break;

		case ISR_FATAL_ERROR:
			printk(KERN_ALERT "%s: *** FATAL error interrupt ***\n", dev->name);
			atmel_enter_state(priv, STATION_STATE_MGMT_ERROR);
			break;

		case ISR_COMMAND_COMPLETE:
			atmel_command_irq(priv);
			break;

		case ISR_IBSS_MERGE:
			atmel_get_mib(priv, Mac_Mgmt_Mib_Type, MAC_MGMT_MIB_CUR_BSSID_POS,
				      priv->CurrentBSSID, 6);
			/* The WPA stuff cares about the current AP address */
			if (priv->use_wpa)
				build_wpa_mib(priv);
			break;
		case ISR_GENERIC_IRQ:
			printk(KERN_INFO "%s: Generic_irq received.\n", dev->name);
			break;
		}
	}
}

static struct iw_statistics *atmel_get_wireless_stats(struct net_device *dev)
{
	struct atmel_private *priv = netdev_priv(dev);

	/* update the link quality here in case we are seeing no beacons
	   at all to drive the process */
	atmel_smooth_qual(priv);

	priv->wstats.status = priv->station_state;

	if (priv->operating_mode == IW_MODE_INFRA) {
		if (priv->station_state != STATION_STATE_READY) {
			priv->wstats.qual.qual = 0;
			priv->wstats.qual.level = 0;
			priv->wstats.qual.updated = (IW_QUAL_QUAL_INVALID
					| IW_QUAL_LEVEL_INVALID);
		}
		priv->wstats.qual.noise = 0;
		priv->wstats.qual.updated |= IW_QUAL_NOISE_INVALID;
	} else {
		/* Quality levels cannot be determined in ad-hoc mode,
		   because we can 'hear' more that one remote station. */
		priv->wstats.qual.qual = 0;
		priv->wstats.qual.level	= 0;
		priv->wstats.qual.noise	= 0;
		priv->wstats.qual.updated = IW_QUAL_QUAL_INVALID
					| IW_QUAL_LEVEL_INVALID
					| IW_QUAL_NOISE_INVALID;
		priv->wstats.miss.beacon = 0;
	}

	return &priv->wstats;
}

static int atmel_change_mtu(struct net_device *dev, int new_mtu)
{
	if ((new_mtu < 68) || (new_mtu > 2312))
		return -EINVAL;
	dev->mtu = new_mtu;
	return 0;
}

static int atmel_set_mac_address(struct net_device *dev, void *p)
{
	struct sockaddr *addr = p;

	memcpy (dev->dev_addr, addr->sa_data, dev->addr_len);
	return atmel_open(dev);
}

EXPORT_SYMBOL(atmel_open);

int atmel_open(struct net_device *dev)
{
	struct atmel_private *priv = netdev_priv(dev);
	int i, channel, err;

	/* any scheduled timer is no longer needed and might screw things up.. */
	del_timer_sync(&priv->management_timer);

	/* Interrupts will not touch the card once in this state... */
	priv->station_state = STATION_STATE_DOWN;

	if (priv->new_SSID_size) {
		memcpy(priv->SSID, priv->new_SSID, priv->new_SSID_size);
		priv->SSID_size = priv->new_SSID_size;
		priv->new_SSID_size = 0;
	}
	priv->BSS_list_entries = 0;

	priv->AuthenticationRequestRetryCnt = 0;
	priv->AssociationRequestRetryCnt = 0;
	priv->ReAssociationRequestRetryCnt = 0;
	priv->CurrentAuthentTransactionSeqNum = 0x0001;
	priv->ExpectedAuthentTransactionSeqNum = 0x0002;

	priv->site_survey_state = SITE_SURVEY_IDLE;
	priv->station_is_associated = 0;

	err = reset_atmel_card(dev);
	if (err)
		return err;

	if (priv->config_reg_domain) {
		priv->reg_domain = priv->config_reg_domain;
		atmel_set_mib8(priv, Phy_Mib_Type, PHY_MIB_REG_DOMAIN_POS, priv->reg_domain);
	} else {
		priv->reg_domain = atmel_get_mib8(priv, Phy_Mib_Type, PHY_MIB_REG_DOMAIN_POS);
		for (i = 0; i < ARRAY_SIZE(channel_table); i++)
			if (priv->reg_domain == channel_table[i].reg_domain)
				break;
		if (i == ARRAY_SIZE(channel_table)) {
			priv->reg_domain = REG_DOMAIN_MKK1;
			printk(KERN_ALERT "%s: failed to get regulatory domain: assuming MKK1.\n", dev->name);
		}
	}

	if ((channel = atmel_validate_channel(priv, priv->channel)))
		priv->channel = channel;

	/* this moves station_state on.... */
	atmel_scan(priv, 1);

	atmel_set_gcr(priv->dev, GCR_ENINT); /* enable interrupts */
	return 0;
}

static int atmel_close(struct net_device *dev)
{
	struct atmel_private *priv = netdev_priv(dev);

	/* Send event to userspace that we are disassociating */
	if (priv->station_state == STATION_STATE_READY) {
		union iwreq_data wrqu;

		wrqu.data.length = 0;
		wrqu.data.flags = 0;
		wrqu.ap_addr.sa_family = ARPHRD_ETHER;
		memset(wrqu.ap_addr.sa_data, 0, ETH_ALEN);
		wireless_send_event(priv->dev, SIOCGIWAP, &wrqu, NULL);
	}

	atmel_enter_state(priv, STATION_STATE_DOWN);

	if (priv->bus_type == BUS_TYPE_PCCARD)
		atmel_write16(dev, GCR, 0x0060);
	atmel_write16(dev, GCR, 0x0040);
	return 0;
}

static int atmel_validate_channel(struct atmel_private *priv, int channel)
{
	/* check that channel is OK, if so return zero,
	   else return suitable default channel */
	int i;

	for (i = 0; i < ARRAY_SIZE(channel_table); i++)
		if (priv->reg_domain == channel_table[i].reg_domain) {
			if (channel >= channel_table[i].min &&
			    channel <= channel_table[i].max)
				return 0;
			else
				return channel_table[i].min;
		}
	return 0;
}

static int atmel_proc_output (char *buf, struct atmel_private *priv)
{
	int i;
	char *p = buf;
	char *s, *r, *c;

	p += sprintf(p, "Driver version:\t\t%d.%d\n",
		     DRIVER_MAJOR, DRIVER_MINOR);

	if (priv->station_state != STATION_STATE_DOWN) {
		p += sprintf(p, "Firmware version:\t%d.%d build %d\n"
				"Firmware location:\t",
			     priv->host_info.major_version,
			     priv->host_info.minor_version,
			     priv->host_info.build_version);

		if (priv->card_type != CARD_TYPE_EEPROM)
			p += sprintf(p, "on card\n");
		else if (priv->firmware)
			p += sprintf(p, "%s loaded by host\n",
				     priv->firmware_id);
		else
			p += sprintf(p, "%s loaded by hotplug\n",
				     priv->firmware_id);

		switch (priv->card_type) {
		case CARD_TYPE_PARALLEL_FLASH:
			c = "Parallel flash";
			break;
		case CARD_TYPE_SPI_FLASH:
			c = "SPI flash\n";
			break;
		case CARD_TYPE_EEPROM:
			c = "EEPROM";
			break;
		default:
			c = "<unknown>";
		}

		r = "<unknown>";
		for (i = 0; i < ARRAY_SIZE(channel_table); i++)
			if (priv->reg_domain == channel_table[i].reg_domain)
				r = channel_table[i].name;

		p += sprintf(p, "MAC memory type:\t%s\n", c);
		p += sprintf(p, "Regulatory domain:\t%s\n", r);
		p += sprintf(p, "Host CRC checking:\t%s\n",
			     priv->do_rx_crc ? "On" : "Off");
		p += sprintf(p, "WPA-capable firmware:\t%s\n",
			     priv->use_wpa ? "Yes" : "No");
	}

	switch (priv->station_state) {
	case STATION_STATE_SCANNING:
		s = "Scanning";
		break;
	case STATION_STATE_JOINNING:
		s = "Joining";
		break;
	case STATION_STATE_AUTHENTICATING:
		s = "Authenticating";
		break;
	case STATION_STATE_ASSOCIATING:
		s = "Associating";
		break;
	case STATION_STATE_READY:
		s = "Ready";
		break;
	case STATION_STATE_REASSOCIATING:
		s = "Reassociating";
		break;
	case STATION_STATE_MGMT_ERROR:
		s = "Management error";
		break;
	case STATION_STATE_DOWN:
		s = "Down";
		break;
	default:
		s = "<unknown>";
	}

	p += sprintf(p, "Current state:\t\t%s\n", s);
	return p - buf;
}

static int atmel_read_proc(char *page, char **start, off_t off,
			   int count, int *eof, void *data)
{
	struct atmel_private *priv = data;
	int len = atmel_proc_output (page, priv);
	if (len <= off+count)
		*eof = 1;
	*start = page + off;
	len -= off;
	if (len > count)
		len = count;
	if (len < 0)
		len = 0;
	return len;
}

static const struct net_device_ops atmel_netdev_ops = {
	.ndo_open 		= atmel_open,
	.ndo_stop		= atmel_close,
	.ndo_change_mtu 	= atmel_change_mtu,
	.ndo_set_mac_address 	= atmel_set_mac_address,
	.ndo_start_xmit 	= start_tx,
	.ndo_do_ioctl 		= atmel_ioctl,
	.ndo_validate_addr	= eth_validate_addr,
};

struct net_device *init_atmel_card(unsigned short irq, unsigned long port,
				   const AtmelFWType fw_type,
				   struct device *sys_dev,
				   int (*card_present)(void *), void *card)
{
	struct proc_dir_entry *ent;
	struct net_device *dev;
	struct atmel_private *priv;
	int rc;

	/* Create the network device object. */
	dev = alloc_etherdev(sizeof(*priv));
	if (!dev)
		return NULL;

	if (dev_alloc_name(dev, dev->name) < 0) {
		printk(KERN_ERR "atmel: Couldn't get name!\n");
		goto err_out_free;
	}

	priv = netdev_priv(dev);
	priv->dev = dev;
	priv->sys_dev = sys_dev;
	priv->present_callback = card_present;
	priv->card = card;
	priv->firmware = NULL;
	priv->firmware_id[0] = '\0';
	priv->firmware_type = fw_type;
	if (firmware) /* module parameter */
		strcpy(priv->firmware_id, firmware);
	priv->bus_type = card_present ? BUS_TYPE_PCCARD : BUS_TYPE_PCI;
	priv->station_state = STATION_STATE_DOWN;
	priv->do_rx_crc = 0;
	/* For PCMCIA cards, some chips need CRC, some don't
	   so we have to probe. */
	if (priv->bus_type == BUS_TYPE_PCCARD) {
		priv->probe_crc = 1;
		priv->crc_ok_cnt = priv->crc_ko_cnt = 0;
	} else
		priv->probe_crc = 0;
	priv->last_qual = jiffies;
	priv->last_beacon_timestamp = 0;
	memset(priv->frag_source, 0xff, sizeof(priv->frag_source));
	memset(priv->BSSID, 0, 6);
	priv->CurrentBSSID[0] = 0xFF; /* Initialize to something invalid.... */
	priv->station_was_associated = 0;

	priv->last_survey = jiffies;
	priv->preamble = LONG_PREAMBLE;
	priv->operating_mode = IW_MODE_INFRA;
	priv->connect_to_any_BSS = 0;
	priv->config_reg_domain = 0;
	priv->reg_domain = 0;
	priv->tx_rate = 3;
	priv->auto_tx_rate = 1;
	priv->channel = 4;
	priv->power_mode = 0;
	priv->SSID[0] = '\0';
	priv->SSID_size = 0;
	priv->new_SSID_size = 0;
	priv->frag_threshold = 2346;
	priv->rts_threshold = 2347;
	priv->short_retry = 7;
	priv->long_retry = 4;

	priv->wep_is_on = 0;
	priv->default_key = 0;
	priv->encryption_level = 0;
	priv->exclude_unencrypted = 0;
	priv->group_cipher_suite = priv->pairwise_cipher_suite = CIPHER_SUITE_NONE;
	priv->use_wpa = 0;
	memset(priv->wep_keys, 0, sizeof(priv->wep_keys));
	memset(priv->wep_key_len, 0, sizeof(priv->wep_key_len));

	priv->default_beacon_period = priv->beacon_period = 100;
	priv->listen_interval = 1;

	init_timer(&priv->management_timer);
	spin_lock_init(&priv->irqlock);
	spin_lock_init(&priv->timerlock);
	priv->management_timer.function = atmel_management_timer;
	priv->management_timer.data = (unsigned long) dev;

	dev->netdev_ops = &atmel_netdev_ops;
	dev->wireless_handlers = &atmel_handler_def;
	dev->irq = irq;
	dev->base_addr = port;

	SET_NETDEV_DEV(dev, sys_dev);

	if ((rc = request_irq(dev->irq, service_interrupt, IRQF_SHARED, dev->name, dev))) {
		printk(KERN_ERR "%s: register interrupt %d failed, rc %d\n", dev->name, irq, rc);
		goto err_out_free;
	}

	if (!request_region(dev->base_addr, 32,
			    priv->bus_type == BUS_TYPE_PCCARD ?  "atmel_cs" : "atmel_pci")) {
		goto err_out_irq;
	}

	if (register_netdev(dev))
		goto err_out_res;

	if (!probe_atmel_card(dev)) {
		unregister_netdev(dev);
		goto err_out_res;
	}

	netif_carrier_off(dev);

	ent = create_proc_read_entry ("driver/atmel", 0, NULL, atmel_read_proc, priv);
	if (!ent)
		printk(KERN_WARNING "atmel: unable to create /proc entry.\n");

	printk(KERN_INFO "%s: Atmel at76c50x. Version %d.%d. MAC %pM\n",
	       dev->name, DRIVER_MAJOR, DRIVER_MINOR, dev->dev_addr);

	return dev;

err_out_res:
	release_region(dev->base_addr, 32);
err_out_irq:
	free_irq(dev->irq, dev);
err_out_free:
	free_netdev(dev);
	return NULL;
}

EXPORT_SYMBOL(init_atmel_card);

void stop_atmel_card(struct net_device *dev)
{
	struct atmel_private *priv = netdev_priv(dev);

	/* put a brick on it... */
	if (priv->bus_type == BUS_TYPE_PCCARD)
		atmel_write16(dev, GCR, 0x0060);
	atmel_write16(dev, GCR, 0x0040);

	del_timer_sync(&priv->management_timer);
	unregister_netdev(dev);
	remove_proc_entry("driver/atmel", NULL);
	free_irq(dev->irq, dev);
	kfree(priv->firmware);
	release_region(dev->base_addr, 32);
	free_netdev(dev);
}

EXPORT_SYMBOL(stop_atmel_card);

static int atmel_set_essid(struct net_device *dev,
			   struct iw_request_info *info,
			   struct iw_point *dwrq,
			   char *extra)
{
	struct atmel_private *priv = netdev_priv(dev);

	/* Check if we asked for `any' */
	if (dwrq->flags == 0) {
		priv->connect_to_any_BSS = 1;
	} else {
		int index = (dwrq->flags & IW_ENCODE_INDEX) - 1;

		priv->connect_to_any_BSS = 0;

		/* Check the size of the string */
		if (dwrq->length > MAX_SSID_LENGTH)
			 return -E2BIG;
		if (index != 0)
			return -EINVAL;

		memcpy(priv->new_SSID, extra, dwrq->length);
		priv->new_SSID_size = dwrq->length;
	}

	return -EINPROGRESS;
}

static int atmel_get_essid(struct net_device *dev,
			   struct iw_request_info *info,
			   struct iw_point *dwrq,
			   char *extra)
{
	struct atmel_private *priv = netdev_priv(dev);

	/* Get the current SSID */
	if (priv->new_SSID_size != 0) {
		memcpy(extra, priv->new_SSID, priv->new_SSID_size);
		dwrq->length = priv->new_SSID_size;
	} else {
		memcpy(extra, priv->SSID, priv->SSID_size);
		dwrq->length = priv->SSID_size;
	}

	dwrq->flags = !priv->connect_to_any_BSS; /* active */

	return 0;
}

static int atmel_get_wap(struct net_device *dev,
			 struct iw_request_info *info,
			 struct sockaddr *awrq,
			 char *extra)
{
	struct atmel_private *priv = netdev_priv(dev);
	memcpy(awrq->sa_data, priv->CurrentBSSID, 6);
	awrq->sa_family = ARPHRD_ETHER;

	return 0;
}

static int atmel_set_encode(struct net_device *dev,
			    struct iw_request_info *info,
			    struct iw_point *dwrq,
			    char *extra)
{
	struct atmel_private *priv = netdev_priv(dev);

	/* Basic checking: do we have a key to set ?
	 * Note : with the new API, it's impossible to get a NULL pointer.
	 * Therefore, we need to check a key size == 0 instead.
	 * New version of iwconfig properly set the IW_ENCODE_NOKEY flag
	 * when no key is present (only change flags), but older versions
	 * don't do it. - Jean II */
	if (dwrq->length > 0) {
		int index = (dwrq->flags & IW_ENCODE_INDEX) - 1;
		int current_index = priv->default_key;
		/* Check the size of the key */
		if (dwrq->length > 13) {
			return -EINVAL;
		}
		/* Check the index (none -> use current) */
		if (index < 0 || index >= 4)
			index = current_index;
		else
			priv->default_key = index;
		/* Set the length */
		if (dwrq->length > 5)
			priv->wep_key_len[index] = 13;
		else
			if (dwrq->length > 0)
				priv->wep_key_len[index] = 5;
			else
				/* Disable the key */
				priv->wep_key_len[index] = 0;
		/* Check if the key is not marked as invalid */
		if (!(dwrq->flags & IW_ENCODE_NOKEY)) {
			/* Cleanup */
			memset(priv->wep_keys[index], 0, 13);
			/* Copy the key in the driver */
			memcpy(priv->wep_keys[index], extra, dwrq->length);
		}
		/* WE specify that if a valid key is set, encryption
		 * should be enabled (user may turn it off later)
		 * This is also how "iwconfig ethX key on" works */
		if (index == current_index &&
		    priv->wep_key_len[index] > 0) {
			priv->wep_is_on = 1;
			priv->exclude_unencrypted = 1;
			if (priv->wep_key_len[index] > 5) {
				priv->pairwise_cipher_suite = CIPHER_SUITE_WEP_128;
				priv->encryption_level = 2;
			} else {
				priv->pairwise_cipher_suite = CIPHER_SUITE_WEP_64;
				priv->encryption_level = 1;
			}
		}
	} else {
		/* Do we want to just set the transmit key index ? */
		int index = (dwrq->flags & IW_ENCODE_INDEX) - 1;
		if (index >= 0 && index < 4) {
			priv->default_key = index;
		} else
			/* Don't complain if only change the mode */
			if (!(dwrq->flags & IW_ENCODE_MODE))
				return -EINVAL;
	}
	/* Read the flags */
	if (dwrq->flags & IW_ENCODE_DISABLED) {
		priv->wep_is_on = 0;
		priv->encryption_level = 0;
		priv->pairwise_cipher_suite = CIPHER_SUITE_NONE;
	} else {
		priv->wep_is_on = 1;
		if (priv->wep_key_len[priv->default_key] > 5) {
			priv->pairwise_cipher_suite = CIPHER_SUITE_WEP_128;
			priv->encryption_level = 2;
		} else {
			priv->pairwise_cipher_suite = CIPHER_SUITE_WEP_64;
			priv->encryption_level = 1;
		}
	}
	if (dwrq->flags & IW_ENCODE_RESTRICTED)
		priv->exclude_unencrypted = 1;
	if (dwrq->flags & IW_ENCODE_OPEN)
		priv->exclude_unencrypted = 0;

	return -EINPROGRESS;		/* Call commit handler */
}

static int atmel_get_encode(struct net_device *dev,
			    struct iw_request_info *info,
			    struct iw_point *dwrq,
			    char *extra)
{
	struct atmel_private *priv = netdev_priv(dev);
	int index = (dwrq->flags & IW_ENCODE_INDEX) - 1;

	if (!priv->wep_is_on)
		dwrq->flags = IW_ENCODE_DISABLED;
	else {
		if (priv->exclude_unencrypted)
			dwrq->flags = IW_ENCODE_RESTRICTED;
		else
			dwrq->flags = IW_ENCODE_OPEN;
	}
		/* Which key do we want ? -1 -> tx index */
	if (index < 0 || index >= 4)
		index = priv->default_key;
	dwrq->flags |= index + 1;
	/* Copy the key to the user buffer */
	dwrq->length = priv->wep_key_len[index];
	if (dwrq->length > 16) {
		dwrq->length = 0;
	} else {
		memset(extra, 0, 16);
		memcpy(extra, priv->wep_keys[index], dwrq->length);
	}

	return 0;
}

static int atmel_set_encodeext(struct net_device *dev,
			    struct iw_request_info *info,
			    union iwreq_data *wrqu,
			    char *extra)
{
	struct atmel_private *priv = netdev_priv(dev);
	struct iw_point *encoding = &wrqu->encoding;
	struct iw_encode_ext *ext = (struct iw_encode_ext *)extra;
	int idx, key_len, alg = ext->alg, set_key = 1;

	/* Determine and validate the key index */
	idx = encoding->flags & IW_ENCODE_INDEX;
	if (idx) {
		if (idx < 1 || idx > 4)
			return -EINVAL;
		idx--;
	} else
		idx = priv->default_key;

	if (encoding->flags & IW_ENCODE_DISABLED)
	    alg = IW_ENCODE_ALG_NONE;

	if (ext->ext_flags & IW_ENCODE_EXT_SET_TX_KEY) {
		priv->default_key = idx;
		set_key = ext->key_len > 0 ? 1 : 0;
	}

	if (set_key) {
		/* Set the requested key first */
		switch (alg) {
		case IW_ENCODE_ALG_NONE:
			priv->wep_is_on = 0;
			priv->encryption_level = 0;
			priv->pairwise_cipher_suite = CIPHER_SUITE_NONE;
			break;
		case IW_ENCODE_ALG_WEP:
			if (ext->key_len > 5) {
				priv->wep_key_len[idx] = 13;
				priv->pairwise_cipher_suite = CIPHER_SUITE_WEP_128;
				priv->encryption_level = 2;
			} else if (ext->key_len > 0) {
				priv->wep_key_len[idx] = 5;
				priv->pairwise_cipher_suite = CIPHER_SUITE_WEP_64;
				priv->encryption_level = 1;
			} else {
				return -EINVAL;
			}
			priv->wep_is_on = 1;
			memset(priv->wep_keys[idx], 0, 13);
			key_len = min ((int)ext->key_len, priv->wep_key_len[idx]);
			memcpy(priv->wep_keys[idx], ext->key, key_len);
			break;
		default:
			return -EINVAL;
		}
	}

	return -EINPROGRESS;
}

static int atmel_get_encodeext(struct net_device *dev,
			    struct iw_request_info *info,
			    union iwreq_data *wrqu,
			    char *extra)
{
	struct atmel_private *priv = netdev_priv(dev);
	struct iw_point *encoding = &wrqu->encoding;
	struct iw_encode_ext *ext = (struct iw_encode_ext *)extra;
	int idx, max_key_len;

	max_key_len = encoding->length - sizeof(*ext);
	if (max_key_len < 0)
		return -EINVAL;

	idx = encoding->flags & IW_ENCODE_INDEX;
	if (idx) {
		if (idx < 1 || idx > 4)
			return -EINVAL;
		idx--;
	} else
		idx = priv->default_key;

	encoding->flags = idx + 1;
	memset(ext, 0, sizeof(*ext));

	if (!priv->wep_is_on) {
		ext->alg = IW_ENCODE_ALG_NONE;
		ext->key_len = 0;
		encoding->flags |= IW_ENCODE_DISABLED;
	} else {
		if (priv->encryption_level > 0)
			ext->alg = IW_ENCODE_ALG_WEP;
		else
			return -EINVAL;

		ext->key_len = priv->wep_key_len[idx];
		memcpy(ext->key, priv->wep_keys[idx], ext->key_len);
		encoding->flags |= IW_ENCODE_ENABLED;
	}

	return 0;
}

static int atmel_set_auth(struct net_device *dev,
			       struct iw_request_info *info,
			       union iwreq_data *wrqu, char *extra)
{
	struct atmel_private *priv = netdev_priv(dev);
	struct iw_param *param = &wrqu->param;

	switch (param->flags & IW_AUTH_INDEX) {
	case IW_AUTH_WPA_VERSION:
	case IW_AUTH_CIPHER_PAIRWISE:
	case IW_AUTH_CIPHER_GROUP:
	case IW_AUTH_KEY_MGMT:
	case IW_AUTH_RX_UNENCRYPTED_EAPOL:
	case IW_AUTH_PRIVACY_INVOKED:
		/*
		 * atmel does not use these parameters
		 */
		break;

	case IW_AUTH_DROP_UNENCRYPTED:
		priv->exclude_unencrypted = param->value ? 1 : 0;
		break;

	case IW_AUTH_80211_AUTH_ALG: {
			if (param->value & IW_AUTH_ALG_SHARED_KEY) {
				priv->exclude_unencrypted = 1;
			} else if (param->value & IW_AUTH_ALG_OPEN_SYSTEM) {
				priv->exclude_unencrypted = 0;
			} else
				return -EINVAL;
			break;
		}

	case IW_AUTH_WPA_ENABLED:
		/* Silently accept disable of WPA */
		if (param->value > 0)
			return -EOPNOTSUPP;
		break;

	default:
		return -EOPNOTSUPP;
	}
	return -EINPROGRESS;
}

static int atmel_get_auth(struct net_device *dev,
			       struct iw_request_info *info,
			       union iwreq_data *wrqu, char *extra)
{
	struct atmel_private *priv = netdev_priv(dev);
	struct iw_param *param = &wrqu->param;

	switch (param->flags & IW_AUTH_INDEX) {
	case IW_AUTH_DROP_UNENCRYPTED:
		param->value = priv->exclude_unencrypted;
		break;

	case IW_AUTH_80211_AUTH_ALG:
		if (priv->exclude_unencrypted == 1)
			param->value = IW_AUTH_ALG_SHARED_KEY;
		else
			param->value = IW_AUTH_ALG_OPEN_SYSTEM;
		break;

	case IW_AUTH_WPA_ENABLED:
		param->value = 0;
		break;

	default:
		return -EOPNOTSUPP;
	}
	return 0;
}


static int atmel_get_name(struct net_device *dev,
			  struct iw_request_info *info,
			  char *cwrq,
			  char *extra)
{
	strcpy(cwrq, "IEEE 802.11-DS");
	return 0;
}

static int atmel_set_rate(struct net_device *dev,
			  struct iw_request_info *info,
			  struct iw_param *vwrq,
			  char *extra)
{
	struct atmel_private *priv = netdev_priv(dev);

	if (vwrq->fixed == 0) {
		priv->tx_rate = 3;
		priv->auto_tx_rate = 1;
	} else {
		priv->auto_tx_rate = 0;

		/* Which type of value ? */
		if ((vwrq->value < 4) && (vwrq->value >= 0)) {
			/* Setting by rate index */
			priv->tx_rate = vwrq->value;
		} else {
		/* Setting by frequency value */
			switch (vwrq->value) {
			case  1000000:
				priv->tx_rate = 0;
				break;
			case  2000000:
				priv->tx_rate = 1;
				break;
			case  5500000:
				priv->tx_rate = 2;
				break;
			case 11000000:
				priv->tx_rate = 3;
				break;
			default:
				return -EINVAL;
			}
		}
	}

	return -EINPROGRESS;
}

static int atmel_set_mode(struct net_device *dev,
			  struct iw_request_info *info,
			  __u32 *uwrq,
			  char *extra)
{
	struct atmel_private *priv = netdev_priv(dev);

	if (*uwrq != IW_MODE_ADHOC && *uwrq != IW_MODE_INFRA)
		return -EINVAL;

	priv->operating_mode = *uwrq;
	return -EINPROGRESS;
}

static int atmel_get_mode(struct net_device *dev,
			  struct iw_request_info *info,
			  __u32 *uwrq,
			  char *extra)
{
	struct atmel_private *priv = netdev_priv(dev);

	*uwrq = priv->operating_mode;
	return 0;
}

static int atmel_get_rate(struct net_device *dev,
			 struct iw_request_info *info,
			 struct iw_param *vwrq,
			 char *extra)
{
	struct atmel_private *priv = netdev_priv(dev);

	if (priv->auto_tx_rate) {
		vwrq->fixed = 0;
		vwrq->value = 11000000;
	} else {
		vwrq->fixed = 1;
		switch (priv->tx_rate) {
		case 0:
			vwrq->value =  1000000;
			break;
		case 1:
			vwrq->value =  2000000;
			break;
		case 2:
			vwrq->value =  5500000;
			break;
		case 3:
			vwrq->value = 11000000;
			break;
		}
	}
	return 0;
}

static int atmel_set_power(struct net_device *dev,
			   struct iw_request_info *info,
			   struct iw_param *vwrq,
			   char *extra)
{
	struct atmel_private *priv = netdev_priv(dev);
	priv->power_mode = vwrq->disabled ? 0 : 1;
	return -EINPROGRESS;
}

static int atmel_get_power(struct net_device *dev,
			   struct iw_request_info *info,
			   struct iw_param *vwrq,
			   char *extra)
{
	struct atmel_private *priv = netdev_priv(dev);
	vwrq->disabled = priv->power_mode ? 0 : 1;
	vwrq->flags = IW_POWER_ON;
	return 0;
}

static int atmel_set_retry(struct net_device *dev,
			   struct iw_request_info *info,
			   struct iw_param *vwrq,
			   char *extra)
{
	struct atmel_private *priv = netdev_priv(dev);

	if (!vwrq->disabled && (vwrq->flags & IW_RETRY_LIMIT)) {
		if (vwrq->flags & IW_RETRY_LONG)
			priv->long_retry = vwrq->value;
		else if (vwrq->flags & IW_RETRY_SHORT)
			priv->short_retry = vwrq->value;
		else {
			/* No modifier : set both */
			priv->long_retry = vwrq->value;
			priv->short_retry = vwrq->value;
		}
		return -EINPROGRESS;
	}

	return -EINVAL;
}

static int atmel_get_retry(struct net_device *dev,
			   struct iw_request_info *info,
			   struct iw_param *vwrq,
			   char *extra)
{
	struct atmel_private *priv = netdev_priv(dev);

	vwrq->disabled = 0;      /* Can't be disabled */

	/* Note : by default, display the short retry number */
	if (vwrq->flags & IW_RETRY_LONG) {
		vwrq->flags = IW_RETRY_LIMIT | IW_RETRY_LONG;
		vwrq->value = priv->long_retry;
	} else {
		vwrq->flags = IW_RETRY_LIMIT;
		vwrq->value = priv->short_retry;
		if (priv->long_retry != priv->short_retry)
			vwrq->flags |= IW_RETRY_SHORT;
	}

	return 0;
}

static int atmel_set_rts(struct net_device *dev,
			 struct iw_request_info *info,
			 struct iw_param *vwrq,
			 char *extra)
{
	struct atmel_private *priv = netdev_priv(dev);
	int rthr = vwrq->value;

	if (vwrq->disabled)
		rthr = 2347;
	if ((rthr < 0) || (rthr > 2347)) {
		return -EINVAL;
	}
	priv->rts_threshold = rthr;

	return -EINPROGRESS;		/* Call commit handler */
}

static int atmel_get_rts(struct net_device *dev,
			 struct iw_request_info *info,
			 struct iw_param *vwrq,
			 char *extra)
{
	struct atmel_private *priv = netdev_priv(dev);

	vwrq->value = priv->rts_threshold;
	vwrq->disabled = (vwrq->value >= 2347);
	vwrq->fixed = 1;

	return 0;
}

static int atmel_set_frag(struct net_device *dev,
			  struct iw_request_info *info,
			  struct iw_param *vwrq,
			  char *extra)
{
	struct atmel_private *priv = netdev_priv(dev);
	int fthr = vwrq->value;

	if (vwrq->disabled)
		fthr = 2346;
	if ((fthr < 256) || (fthr > 2346)) {
		return -EINVAL;
	}
	fthr &= ~0x1;	/* Get an even value - is it really needed ??? */
	priv->frag_threshold = fthr;

	return -EINPROGRESS;		/* Call commit handler */
}

static int atmel_get_frag(struct net_device *dev,
			  struct iw_request_info *info,
			  struct iw_param *vwrq,
			  char *extra)
{
	struct atmel_private *priv = netdev_priv(dev);

	vwrq->value = priv->frag_threshold;
	vwrq->disabled = (vwrq->value >= 2346);
	vwrq->fixed = 1;

	return 0;
}

static int atmel_set_freq(struct net_device *dev,
			  struct iw_request_info *info,
			  struct iw_freq *fwrq,
			  char *extra)
{
	struct atmel_private *priv = netdev_priv(dev);
	int rc = -EINPROGRESS;		/* Call commit handler */

	/* If setting by frequency, convert to a channel */
	if (fwrq->e == 1) {
		int f = fwrq->m / 100000;

		/* Hack to fall through... */
		fwrq->e = 0;
		fwrq->m = ieee80211_freq_to_dsss_chan(f);
	}
	/* Setting by channel number */
	if ((fwrq->m > 1000) || (fwrq->e > 0))
		rc = -EOPNOTSUPP;
	else {
		int channel = fwrq->m;
		if (atmel_validate_channel(priv, channel) == 0) {
			priv->channel = channel;
		} else {
			rc = -EINVAL;
		}
	}
	return rc;
}

static int atmel_get_freq(struct net_device *dev,
			  struct iw_request_info *info,
			  struct iw_freq *fwrq,
			  char *extra)
{
	struct atmel_private *priv = netdev_priv(dev);

	fwrq->m = priv->channel;
	fwrq->e = 0;
	return 0;
}

static int atmel_set_scan(struct net_device *dev,
			  struct iw_request_info *info,
			  struct iw_point *dwrq,
			  char *extra)
{
	struct atmel_private *priv = netdev_priv(dev);
	unsigned long flags;

	/* Note : you may have realised that, as this is a SET operation,
	 * this is privileged and therefore a normal user can't
	 * perform scanning.
	 * This is not an error, while the device perform scanning,
	 * traffic doesn't flow, so it's a perfect DoS...
	 * Jean II */

	if (priv->station_state == STATION_STATE_DOWN)
		return -EAGAIN;

	/* Timeout old surveys. */
	if (time_after(jiffies, priv->last_survey + 20 * HZ))
		priv->site_survey_state = SITE_SURVEY_IDLE;
	priv->last_survey = jiffies;

	/* Initiate a scan command */
	if (priv->site_survey_state == SITE_SURVEY_IN_PROGRESS)
		return -EBUSY;

	del_timer_sync(&priv->management_timer);
	spin_lock_irqsave(&priv->irqlock, flags);

	priv->site_survey_state = SITE_SURVEY_IN_PROGRESS;
	priv->fast_scan = 0;
	atmel_scan(priv, 0);
	spin_unlock_irqrestore(&priv->irqlock, flags);

	return 0;
}

static int atmel_get_scan(struct net_device *dev,
			  struct iw_request_info *info,
			  struct iw_point *dwrq,
			  char *extra)
{
	struct atmel_private *priv = netdev_priv(dev);
	int i;
	char *current_ev = extra;
	struct iw_event	iwe;

	if (priv->site_survey_state != SITE_SURVEY_COMPLETED)
		return -EAGAIN;

	for (i = 0; i < priv->BSS_list_entries; i++) {
		iwe.cmd = SIOCGIWAP;
		iwe.u.ap_addr.sa_family = ARPHRD_ETHER;
		memcpy(iwe.u.ap_addr.sa_data, priv->BSSinfo[i].BSSID, 6);
		current_ev = iwe_stream_add_event(info, current_ev,
						  extra + IW_SCAN_MAX_DATA,
						  &iwe, IW_EV_ADDR_LEN);

		iwe.u.data.length =  priv->BSSinfo[i].SSIDsize;
		if (iwe.u.data.length > 32)
			iwe.u.data.length = 32;
		iwe.cmd = SIOCGIWESSID;
		iwe.u.data.flags = 1;
		current_ev = iwe_stream_add_point(info, current_ev,
						  extra + IW_SCAN_MAX_DATA,
						  &iwe, priv->BSSinfo[i].SSID);

		iwe.cmd = SIOCGIWMODE;
		iwe.u.mode = priv->BSSinfo[i].BSStype;
		current_ev = iwe_stream_add_event(info, current_ev,
						  extra + IW_SCAN_MAX_DATA,
						  &iwe, IW_EV_UINT_LEN);

		iwe.cmd = SIOCGIWFREQ;
		iwe.u.freq.m = priv->BSSinfo[i].channel;
		iwe.u.freq.e = 0;
		current_ev = iwe_stream_add_event(info, current_ev,
						  extra + IW_SCAN_MAX_DATA,
						  &iwe, IW_EV_FREQ_LEN);

		/* Add quality statistics */
		iwe.cmd = IWEVQUAL;
		iwe.u.qual.level = priv->BSSinfo[i].RSSI;
		iwe.u.qual.qual  = iwe.u.qual.level;
		/* iwe.u.qual.noise  = SOMETHING */
		current_ev = iwe_stream_add_event(info, current_ev,
						  extra + IW_SCAN_MAX_DATA,
						  &iwe, IW_EV_QUAL_LEN);


		iwe.cmd = SIOCGIWENCODE;
		if (priv->BSSinfo[i].UsingWEP)
			iwe.u.data.flags = IW_ENCODE_ENABLED | IW_ENCODE_NOKEY;
		else
			iwe.u.data.flags = IW_ENCODE_DISABLED;
		iwe.u.data.length = 0;
		current_ev = iwe_stream_add_point(info, current_ev,
						  extra + IW_SCAN_MAX_DATA,
						  &iwe, NULL);
	}

	/* Length of data */
	dwrq->length = (current_ev - extra);
	dwrq->flags = 0;

	return 0;
}

static int atmel_get_range(struct net_device *dev,
			   struct iw_request_info *info,
			   struct iw_point *dwrq,
			   char *extra)
{
	struct atmel_private *priv = netdev_priv(dev);
	struct iw_range *range = (struct iw_range *) extra;
	int k, i, j;

	dwrq->length = sizeof(struct iw_range);
	memset(range, 0, sizeof(struct iw_range));
	range->min_nwid = 0x0000;
	range->max_nwid = 0x0000;
	range->num_channels = 0;
	for (j = 0; j < ARRAY_SIZE(channel_table); j++)
		if (priv->reg_domain == channel_table[j].reg_domain) {
			range->num_channels = channel_table[j].max - channel_table[j].min + 1;
			break;
		}
	if (range->num_channels != 0) {
		for (k = 0, i = channel_table[j].min; i <= channel_table[j].max; i++) {
			range->freq[k].i = i; /* List index */

			/* Values in MHz -> * 10^5 * 10 */
			range->freq[k].m = (ieee80211_dsss_chan_to_freq(i) *
					    100000);
			range->freq[k++].e = 1;
		}
		range->num_frequency = k;
	}

	range->max_qual.qual = 100;
	range->max_qual.level = 100;
	range->max_qual.noise = 0;
	range->max_qual.updated = IW_QUAL_NOISE_INVALID;

	range->avg_qual.qual = 50;
	range->avg_qual.level = 50;
	range->avg_qual.noise = 0;
	range->avg_qual.updated = IW_QUAL_NOISE_INVALID;

	range->sensitivity = 0;

	range->bitrate[0] =  1000000;
	range->bitrate[1] =  2000000;
	range->bitrate[2] =  5500000;
	range->bitrate[3] = 11000000;
	range->num_bitrates = 4;

	range->min_rts = 0;
	range->max_rts = 2347;
	range->min_frag = 256;
	range->max_frag = 2346;

	range->encoding_size[0] = 5;
	range->encoding_size[1] = 13;
	range->num_encoding_sizes = 2;
	range->max_encoding_tokens = 4;

	range->pmp_flags = IW_POWER_ON;
	range->pmt_flags = IW_POWER_ON;
	range->pm_capa = 0;

	range->we_version_source = WIRELESS_EXT;
	range->we_version_compiled = WIRELESS_EXT;
	range->retry_capa = IW_RETRY_LIMIT ;
	range->retry_flags = IW_RETRY_LIMIT;
	range->r_time_flags = 0;
	range->min_retry = 1;
	range->max_retry = 65535;

	return 0;
}

static int atmel_set_wap(struct net_device *dev,
			 struct iw_request_info *info,
			 struct sockaddr *awrq,
			 char *extra)
{
	struct atmel_private *priv = netdev_priv(dev);
	int i;
	static const u8 any[] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF };
	static const u8 off[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
	unsigned long flags;

	if (awrq->sa_family != ARPHRD_ETHER)
		return -EINVAL;

	if (!memcmp(any, awrq->sa_data, 6) ||
	    !memcmp(off, awrq->sa_data, 6)) {
		del_timer_sync(&priv->management_timer);
		spin_lock_irqsave(&priv->irqlock, flags);
		atmel_scan(priv, 1);
		spin_unlock_irqrestore(&priv->irqlock, flags);
		return 0;
	}

	for (i = 0; i < priv->BSS_list_entries; i++) {
		if (memcmp(priv->BSSinfo[i].BSSID, awrq->sa_data, 6) == 0) {
			if (!priv->wep_is_on && priv->BSSinfo[i].UsingWEP) {
				return -EINVAL;
			} else if  (priv->wep_is_on && !priv->BSSinfo[i].UsingWEP) {
				return -EINVAL;
			} else {
				del_timer_sync(&priv->management_timer);
				spin_lock_irqsave(&priv->irqlock, flags);
				atmel_join_bss(priv, i);
				spin_unlock_irqrestore(&priv->irqlock, flags);
				return 0;
			}
		}
	}

	return -EINVAL;
}

static int atmel_config_commit(struct net_device *dev,
			       struct iw_request_info *info,	/* NULL */
			       void *zwrq,			/* NULL */
			       char *extra)			/* NULL */
{
	return atmel_open(dev);
}

static const iw_handler atmel_handler[] =
{
	(iw_handler) atmel_config_commit,	/* SIOCSIWCOMMIT */
	(iw_handler) atmel_get_name,		/* SIOCGIWNAME */
	(iw_handler) NULL,			/* SIOCSIWNWID */
	(iw_handler) NULL,			/* SIOCGIWNWID */
	(iw_handler) atmel_set_freq,		/* SIOCSIWFREQ */
	(iw_handler) atmel_get_freq,		/* SIOCGIWFREQ */
	(iw_handler) atmel_set_mode,		/* SIOCSIWMODE */
	(iw_handler) atmel_get_mode,		/* SIOCGIWMODE */
	(iw_handler) NULL,			/* SIOCSIWSENS */
	(iw_handler) NULL,			/* SIOCGIWSENS */
	(iw_handler) NULL,			/* SIOCSIWRANGE */
	(iw_handler) atmel_get_range,           /* SIOCGIWRANGE */
	(iw_handler) NULL,			/* SIOCSIWPRIV */
	(iw_handler) NULL,			/* SIOCGIWPRIV */
	(iw_handler) NULL,			/* SIOCSIWSTATS */
	(iw_handler) NULL,			/* SIOCGIWSTATS */
	(iw_handler) NULL,			/* SIOCSIWSPY */
	(iw_handler) NULL,			/* SIOCGIWSPY */
	(iw_handler) NULL,			/* -- hole -- */
	(iw_handler) NULL,			/* -- hole -- */
	(iw_handler) atmel_set_wap,		/* SIOCSIWAP */
	(iw_handler) atmel_get_wap,		/* SIOCGIWAP */
	(iw_handler) NULL,			/* -- hole -- */
	(iw_handler) NULL,			/* SIOCGIWAPLIST */
	(iw_handler) atmel_set_scan,		/* SIOCSIWSCAN */
	(iw_handler) atmel_get_scan,		/* SIOCGIWSCAN */
	(iw_handler) atmel_set_essid,		/* SIOCSIWESSID */
	(iw_handler) atmel_get_essid,		/* SIOCGIWESSID */
	(iw_handler) NULL,			/* SIOCSIWNICKN */
	(iw_handler) NULL,			/* SIOCGIWNICKN */
	(iw_handler) NULL,			/* -- hole -- */
	(iw_handler) NULL,			/* -- hole -- */
	(iw_handler) atmel_set_rate,		/* SIOCSIWRATE */
	(iw_handler) atmel_get_rate,		/* SIOCGIWRATE */
	(iw_handler) atmel_set_rts,		/* SIOCSIWRTS */
	(iw_handler) atmel_get_rts,		/* SIOCGIWRTS */
	(iw_handler) atmel_set_frag,		/* SIOCSIWFRAG */
	(iw_handler) atmel_get_frag,		/* SIOCGIWFRAG */
	(iw_handler) NULL,			/* SIOCSIWTXPOW */
	(iw_handler) NULL,			/* SIOCGIWTXPOW */
	(iw_handler) atmel_set_retry,		/* SIOCSIWRETRY */
	(iw_handler) atmel_get_retry,		/* SIOCGIWRETRY */
	(iw_handler) atmel_set_encode,		/* SIOCSIWENCODE */
	(iw_handler) atmel_get_encode,		/* SIOCGIWENCODE */
	(iw_handler) atmel_set_power,		/* SIOCSIWPOWER */
	(iw_handler) atmel_get_power,		/* SIOCGIWPOWER */
	(iw_handler) NULL,			/* -- hole -- */
	(iw_handler) NULL,			/* -- hole -- */
	(iw_handler) NULL,			/* SIOCSIWGENIE */
	(iw_handler) NULL,			/* SIOCGIWGENIE */
	(iw_handler) atmel_set_auth,		/* SIOCSIWAUTH */
	(iw_handler) atmel_get_auth,		/* SIOCGIWAUTH */
	(iw_handler) atmel_set_encodeext,	/* SIOCSIWENCODEEXT */
	(iw_handler) atmel_get_encodeext,	/* SIOCGIWENCODEEXT */
	(iw_handler) NULL,			/* SIOCSIWPMKSA */
};

static const iw_handler atmel_private_handler[] =
{
	NULL,				/* SIOCIWFIRSTPRIV */
};

typedef struct atmel_priv_ioctl {
	char id[32];
	unsigned char __user *data;
	unsigned short len;
} atmel_priv_ioctl;

#define ATMELFWL	SIOCIWFIRSTPRIV
#define ATMELIDIFC	ATMELFWL + 1
#define ATMELRD		ATMELFWL + 2
#define ATMELMAGIC 0x51807
#define REGDOMAINSZ 20

static const struct iw_priv_args atmel_private_args[] = {
	{
		.cmd = ATMELFWL,
		.set_args = IW_PRIV_TYPE_BYTE
				| IW_PRIV_SIZE_FIXED
				| sizeof (atmel_priv_ioctl),
		.get_args = IW_PRIV_TYPE_NONE,
		.name = "atmelfwl"
	}, {
		.cmd = ATMELIDIFC,
		.set_args = IW_PRIV_TYPE_NONE,
		.get_args = IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1,
		.name = "atmelidifc"
	}, {
		.cmd = ATMELRD,
		.set_args = IW_PRIV_TYPE_CHAR | REGDOMAINSZ,
		.get_args = IW_PRIV_TYPE_NONE,
		.name = "regdomain"
	},
};

static const struct iw_handler_def atmel_handler_def = {
	.num_standard	= ARRAY_SIZE(atmel_handler),
	.num_private	= ARRAY_SIZE(atmel_private_handler),
	.num_private_args = ARRAY_SIZE(atmel_private_args),
	.standard	= (iw_handler *) atmel_handler,
	.private	= (iw_handler *) atmel_private_handler,
	.private_args	= (struct iw_priv_args *) atmel_private_args,
	.get_wireless_stats = atmel_get_wireless_stats
};

static int atmel_ioctl(struct net_device *dev, struct ifreq *rq, int cmd)
{
	int i, rc = 0;
	struct atmel_private *priv = netdev_priv(dev);
	atmel_priv_ioctl com;
	struct iwreq *wrq = (struct iwreq *) rq;
	unsigned char *new_firmware;
	char domain[REGDOMAINSZ + 1];

	switch (cmd) {
	case ATMELIDIFC:
		wrq->u.param.value = ATMELMAGIC;
		break;

	case ATMELFWL:
		if (copy_from_user(&com, rq->ifr_data, sizeof(com))) {
			rc = -EFAULT;
			break;
		}

		if (!capable(CAP_NET_ADMIN)) {
			rc = -EPERM;
			break;
		}

		if (!(new_firmware = kmalloc(com.len, GFP_KERNEL))) {
			rc = -ENOMEM;
			break;
		}

		if (copy_from_user(new_firmware, com.data, com.len)) {
			kfree(new_firmware);
			rc = -EFAULT;
			break;
		}

		kfree(priv->firmware);

		priv->firmware = new_firmware;
		priv->firmware_length = com.len;
		strncpy(priv->firmware_id, com.id, 31);
		priv->firmware_id[31] = '\0';
		break;

	case ATMELRD:
		if (copy_from_user(domain, rq->ifr_data, REGDOMAINSZ)) {
			rc = -EFAULT;
			break;
		}

		if (!capable(CAP_NET_ADMIN)) {
			rc = -EPERM;
			break;
		}

		domain[REGDOMAINSZ] = 0;
		rc = -EINVAL;
		for (i = 0; i < ARRAY_SIZE(channel_table); i++) {
			/* strcasecmp doesn't exist in the library */
			char *a = channel_table[i].name;
			char *b = domain;
			while (*a) {
				char c1 = *a++;
				char c2 = *b++;
				if (tolower(c1) != tolower(c2))
					break;
			}
			if (!*a && !*b) {
				priv->config_reg_domain = channel_table[i].reg_domain;
				rc = 0;
			}
		}

		if (rc == 0 &&  priv->station_state != STATION_STATE_DOWN)
			rc = atmel_open(dev);
		break;

	default:
		rc = -EOPNOTSUPP;
	}

	return rc;
}

struct auth_body {
	__le16 alg;
	__le16 trans_seq;
	__le16 status;
	u8 el_id;
	u8 chall_text_len;
	u8 chall_text[253];
};

static void atmel_enter_state(struct atmel_private *priv, int new_state)
{
	int old_state = priv->station_state;

	if (new_state == old_state)
		return;

	priv->station_state = new_state;

	if (new_state == STATION_STATE_READY) {
		netif_start_queue(priv->dev);
		netif_carrier_on(priv->dev);
	}

	if (old_state == STATION_STATE_READY) {
		netif_carrier_off(priv->dev);
		if (netif_running(priv->dev))
			netif_stop_queue(priv->dev);
		priv->last_beacon_timestamp = 0;
	}
}

static void atmel_scan(struct atmel_private *priv, int specific_ssid)
{
	struct {
		u8 BSSID[6];
		u8 SSID[MAX_SSID_LENGTH];
		u8 scan_type;
		u8 channel;
		__le16 BSS_type;
		__le16 min_channel_time;
		__le16 max_channel_time;
		u8 options;
		u8 SSID_size;
	} cmd;

	memset(cmd.BSSID, 0xff, 6);

	if (priv->fast_scan) {
		cmd.SSID_size = priv->SSID_size;
		memcpy(cmd.SSID, priv->SSID, priv->SSID_size);
		cmd.min_channel_time = cpu_to_le16(10);
		cmd.max_channel_time = cpu_to_le16(50);
	} else {
		priv->BSS_list_entries = 0;
		cmd.SSID_size = 0;
		cmd.min_channel_time = cpu_to_le16(10);
		cmd.max_channel_time = cpu_to_le16(120);
	}

	cmd.options = 0;

	if (!specific_ssid)
		cmd.options |= SCAN_OPTIONS_SITE_SURVEY;

	cmd.channel = (priv->channel & 0x7f);
	cmd.scan_type = SCAN_TYPE_ACTIVE;
	cmd.BSS_type = cpu_to_le16(priv->operating_mode == IW_MODE_ADHOC ?
		BSS_TYPE_AD_HOC : BSS_TYPE_INFRASTRUCTURE);

	atmel_send_command(priv, CMD_Scan, &cmd, sizeof(cmd));

	/* This must come after all hardware access to avoid being messed up
	   by stuff happening in interrupt context after we leave STATE_DOWN */
	atmel_enter_state(priv, STATION_STATE_SCANNING);
}

static void join(struct atmel_private *priv, int type)
{
	struct {
		u8 BSSID[6];
		u8 SSID[MAX_SSID_LENGTH];
		u8 BSS_type; /* this is a short in a scan command - weird */
		u8 channel;
		__le16 timeout;
		u8 SSID_size;
		u8 reserved;
	} cmd;

	cmd.SSID_size = priv->SSID_size;
	memcpy(cmd.SSID, priv->SSID, priv->SSID_size);
	memcpy(cmd.BSSID, priv->CurrentBSSID, 6);
	cmd.channel = (priv->channel & 0x7f);
	cmd.BSS_type = type;
	cmd.timeout = cpu_to_le16(2000);

	atmel_send_command(priv, CMD_Join, &cmd, sizeof(cmd));
}

static void start(struct atmel_private *priv, int type)
{
	struct {
		u8 BSSID[6];
		u8 SSID[MAX_SSID_LENGTH];
		u8 BSS_type;
		u8 channel;
		u8 SSID_size;
		u8 reserved[3];
	} cmd;

	cmd.SSID_size = priv->SSID_size;
	memcpy(cmd.SSID, priv->SSID, priv->SSID_size);
	memcpy(cmd.BSSID, priv->BSSID, 6);
	cmd.BSS_type = type;
	cmd.channel = (priv->channel & 0x7f);

	atmel_send_command(priv, CMD_Start, &cmd, sizeof(cmd));
}

static void handle_beacon_probe(struct atmel_private *priv, u16 capability,
				u8 channel)
{
	int rejoin = 0;
	int new = capability & WLAN_CAPABILITY_SHORT_PREAMBLE ?
		SHORT_PREAMBLE : LONG_PREAMBLE;

	if (priv->preamble != new) {
		priv->preamble = new;
		rejoin = 1;
		atmel_set_mib8(priv, Local_Mib_Type, LOCAL_MIB_PREAMBLE_TYPE, new);
	}

	if (priv->channel != channel) {
		priv->channel = channel;
		rejoin = 1;
		atmel_set_mib8(priv, Phy_Mib_Type, PHY_MIB_CHANNEL_POS, channel);
	}

	if (rejoin) {
		priv->station_is_associated = 0;
		atmel_enter_state(priv, STATION_STATE_JOINNING);

		if (priv->operating_mode == IW_MODE_INFRA)
			join(priv, BSS_TYPE_INFRASTRUCTURE);
		else
			join(priv, BSS_TYPE_AD_HOC);
	}
}

static void send_authentication_request(struct atmel_private *priv, u16 system,
					u8 *challenge, int challenge_len)
{
	struct ieee80211_hdr header;
	struct auth_body auth;

	header.frame_control = cpu_to_le16(IEEE80211_FTYPE_MGMT | IEEE80211_STYPE_AUTH);
	header.duration_id = cpu_to_le16(0x8000);
	header.seq_ctrl = 0;
	memcpy(header.addr1, priv->CurrentBSSID, 6);
	memcpy(header.addr2, priv->dev->dev_addr, 6);
	memcpy(header.addr3, priv->CurrentBSSID, 6);

	if (priv->wep_is_on && priv->CurrentAuthentTransactionSeqNum != 1)
		/* no WEP for authentication frames with TrSeqNo 1 */
		header.frame_control |=  cpu_to_le16(IEEE80211_FCTL_PROTECTED);

	auth.alg = cpu_to_le16(system);

	auth.status = 0;
	auth.trans_seq = cpu_to_le16(priv->CurrentAuthentTransactionSeqNum);
	priv->ExpectedAuthentTransactionSeqNum = priv->CurrentAuthentTransactionSeqNum+1;
	priv->CurrentAuthentTransactionSeqNum += 2;

	if (challenge_len != 0)	{
		auth.el_id = 16; /* challenge_text */
		auth.chall_text_len = challenge_len;
		memcpy(auth.chall_text, challenge, challenge_len);
		atmel_transmit_management_frame(priv, &header, (u8 *)&auth, 8 + challenge_len);
	} else {
		atmel_transmit_management_frame(priv, &header, (u8 *)&auth, 6);
	}
}

static void send_association_request(struct atmel_private *priv, int is_reassoc)
{
	u8 *ssid_el_p;
	int bodysize;
	struct ieee80211_hdr header;
	struct ass_req_format {
		__le16 capability;
		__le16 listen_interval;
		u8 ap[6]; /* nothing after here directly accessible */
		u8 ssid_el_id;
		u8 ssid_len;
		u8 ssid[MAX_SSID_LENGTH];
		u8 sup_rates_el_id;
		u8 sup_rates_len;
		u8 rates[4];
	} body;

	header.frame_control = cpu_to_le16(IEEE80211_FTYPE_MGMT |
		(is_reassoc ? IEEE80211_STYPE_REASSOC_REQ : IEEE80211_STYPE_ASSOC_REQ));
	header.duration_id = cpu_to_le16(0x8000);
	header.seq_ctrl = 0;

	memcpy(header.addr1, priv->CurrentBSSID, 6);
	memcpy(header.addr2, priv->dev->dev_addr, 6);
	memcpy(header.addr3, priv->CurrentBSSID, 6);

	body.capability = cpu_to_le16(WLAN_CAPABILITY_ESS);
	if (priv->wep_is_on)
		body.capability |= cpu_to_le16(WLAN_CAPABILITY_PRIVACY);
	if (priv->preamble == SHORT_PREAMBLE)
		body.capability |= cpu_to_le16(WLAN_CAPABILITY_SHORT_PREAMBLE);

	body.listen_interval = cpu_to_le16(priv->listen_interval * priv->beacon_period);

	/* current AP address - only in reassoc frame */
	if (is_reassoc) {
		memcpy(body.ap, priv->CurrentBSSID, 6);
		ssid_el_p = (u8 *)&body.ssid_el_id;
		bodysize = 18 + priv->SSID_size;
	} else {
		ssid_el_p = (u8 *)&body.ap[0];
		bodysize = 12 + priv->SSID_size;
	}

	ssid_el_p[0] = WLAN_EID_SSID;
	ssid_el_p[1] = priv->SSID_size;
	memcpy(ssid_el_p + 2, priv->SSID, priv->SSID_size);
	ssid_el_p[2 + priv->SSID_size] = WLAN_EID_SUPP_RATES;
	ssid_el_p[3 + priv->SSID_size] = 4; /* len of suported rates */
	memcpy(ssid_el_p + 4 + priv->SSID_size, atmel_basic_rates, 4);

	atmel_transmit_management_frame(priv, &header, (void *)&body, bodysize);
}

static int is_frame_from_current_bss(struct atmel_private *priv,
				     struct ieee80211_hdr *header)
{
	if (le16_to_cpu(header->frame_control) & IEEE80211_FCTL_FROMDS)
		return memcmp(header->addr3, priv->CurrentBSSID, 6) == 0;
	else
		return memcmp(header->addr2, priv->CurrentBSSID, 6) == 0;
}

static int retrieve_bss(struct atmel_private *priv)
{
	int i;
	int max_rssi = -128;
	int max_index = -1;

	if (priv->BSS_list_entries == 0)
		return -1;

	if (priv->connect_to_any_BSS) {
		/* Select a BSS with the max-RSSI but of the same type and of
		   the same WEP mode and that it is not marked as 'bad' (i.e.
		   we had previously failed to connect to this BSS with the
		   settings that we currently use) */
		priv->current_BSS = 0;
		for (i = 0; i < priv->BSS_list_entries; i++) {
			if (priv->operating_mode == priv->BSSinfo[i].BSStype &&
			    ((!priv->wep_is_on && !priv->BSSinfo[i].UsingWEP) ||
			     (priv->wep_is_on && priv->BSSinfo[i].UsingWEP)) &&
			    !(priv->BSSinfo[i].channel & 0x80)) {
				max_rssi = priv->BSSinfo[i].RSSI;
				priv->current_BSS = max_index = i;
			}
		}
		return max_index;
	}

	for (i = 0; i < priv->BSS_list_entries; i++) {
		if (priv->SSID_size == priv->BSSinfo[i].SSIDsize &&
		    memcmp(priv->SSID, priv->BSSinfo[i].SSID, priv->SSID_size) == 0 &&
		    priv->operating_mode == priv->BSSinfo[i].BSStype &&
		    atmel_validate_channel(priv, priv->BSSinfo[i].channel) == 0) {
			if (priv->BSSinfo[i].RSSI >= max_rssi) {
				max_rssi = priv->BSSinfo[i].RSSI;
				max_index = i;
			}
		}
	}
	return max_index;
}

static void store_bss_info(struct atmel_private *priv,
			   struct ieee80211_hdr *header, u16 capability,
			   u16 beacon_period, u8 channel, u8 rssi, u8 ssid_len,
			   u8 *ssid, int is_beacon)
{
	u8 *bss = capability & WLAN_CAPABILITY_ESS ? header->addr2 : header->addr3;
	int i, index;

	for (index = -1, i = 0; i < priv->BSS_list_entries; i++)
		if (memcmp(bss, priv->BSSinfo[i].BSSID, 6) == 0)
			index = i;

	/* If we process a probe and an entry from this BSS exists
	   we will update the BSS entry with the info from this BSS.
	   If we process a beacon we will only update RSSI */

	if (index == -1) {
		if (priv->BSS_list_entries == MAX_BSS_ENTRIES)
			return;
		index = priv->BSS_list_entries++;
		memcpy(priv->BSSinfo[index].BSSID, bss, 6);
		priv->BSSinfo[index].RSSI = rssi;
	} else {
		if (rssi > priv->BSSinfo[index].RSSI)
			priv->BSSinfo[index].RSSI = rssi;
		if (is_beacon)
			return;
	}

	priv->BSSinfo[index].channel = channel;
	priv->BSSinfo[index].beacon_period = beacon_period;
	priv->BSSinfo[index].UsingWEP = capability & WLAN_CAPABILITY_PRIVACY;
	memcpy(priv->BSSinfo[index].SSID, ssid, ssid_len);
	priv->BSSinfo[index].SSIDsize = ssid_len;

	if (capability & WLAN_CAPABILITY_IBSS)
		priv->BSSinfo[index].BSStype = IW_MODE_ADHOC;
	else if (capability & WLAN_CAPABILITY_ESS)
		priv->BSSinfo[index].BSStype = IW_MODE_INFRA;

	priv->BSSinfo[index].preamble = capability & WLAN_CAPABILITY_SHORT_PREAMBLE ?
		SHORT_PREAMBLE : LONG_PREAMBLE;
}

static void authenticate(struct atmel_private *priv, u16 frame_len)
{
	struct auth_body *auth = (struct auth_body *)priv->rx_buf;
	u16 status = le16_to_cpu(auth->status);
	u16 trans_seq_no = le16_to_cpu(auth->trans_seq);
	u16 system = le16_to_cpu(auth->alg);

	if (status == WLAN_STATUS_SUCCESS && !priv->wep_is_on) {
		/* no WEP */
		if (priv->station_was_associated) {
			atmel_enter_state(priv, STATION_STATE_REASSOCIATING);
			send_association_request(priv, 1);
			return;
		} else {
			atmel_enter_state(priv, STATION_STATE_ASSOCIATING);
			send_association_request(priv, 0);
			return;
		}
	}

	if (status == WLAN_STATUS_SUCCESS && priv->wep_is_on) {
		int should_associate = 0;
		/* WEP */
		if (trans_seq_no != priv->ExpectedAuthentTransactionSeqNum)
			return;

		if (system == WLAN_AUTH_OPEN) {
			if (trans_seq_no == 0x0002) {
				should_associate = 1;
			}
		} else if (system == WLAN_AUTH_SHARED_KEY) {
			if (trans_seq_no == 0x0002 &&
			    auth->el_id == WLAN_EID_CHALLENGE) {
				send_authentication_request(priv, system, auth->chall_text, auth->chall_text_len);
				return;
			} else if (trans_seq_no == 0x0004) {
				should_associate = 1;
			}
		}

		if (should_associate) {
			if (priv->station_was_associated) {
				atmel_enter_state(priv, STATION_STATE_REASSOCIATING);
				send_association_request(priv, 1);
				return;
			} else {
				atmel_enter_state(priv, STATION_STATE_ASSOCIATING);
				send_association_request(priv, 0);
				return;
			}
		}
	}

	if (status == WLAN_STATUS_NOT_SUPPORTED_AUTH_ALG) {
		/* Flip back and forth between WEP auth modes until the max
		 * authentication tries has been exceeded.
		 */
		if (system == WLAN_AUTH_OPEN) {
			priv->CurrentAuthentTransactionSeqNum = 0x001;
			priv->exclude_unencrypted = 1;
			send_authentication_request(priv, WLAN_AUTH_SHARED_KEY, NULL, 0);
			return;
		} else if (system == WLAN_AUTH_SHARED_KEY
			   && priv->wep_is_on) {
			priv->CurrentAuthentTransactionSeqNum = 0x001;
			priv->exclude_unencrypted = 0;
			send_authentication_request(priv, WLAN_AUTH_OPEN, NULL, 0);
			return;
		} else if (priv->connect_to_any_BSS) {
			int bss_index;

			priv->BSSinfo[(int)(priv->current_BSS)].channel |= 0x80;

			if ((bss_index  = retrieve_bss(priv)) != -1) {
				atmel_join_bss(priv, bss_index);
				return;
			}
		}
	}

	priv->AuthenticationRequestRetryCnt = 0;
	atmel_enter_state(priv,  STATION_STATE_MGMT_ERROR);
	priv->station_is_associated = 0;
}

static void associate(struct atmel_private *priv, u16 frame_len, u16 subtype)
{
	struct ass_resp_format {
		__le16 capability;
		__le16 status;
		__le16 ass_id;
		u8 el_id;
		u8 length;
		u8 rates[4];
	} *ass_resp = (struct ass_resp_format *)priv->rx_buf;

	u16 status = le16_to_cpu(ass_resp->status);
	u16 ass_id = le16_to_cpu(ass_resp->ass_id);
	u16 rates_len = ass_resp->length > 4 ? 4 : ass_resp->length;

	union iwreq_data wrqu;

	if (frame_len < 8 + rates_len)
		return;

	if (status == WLAN_STATUS_SUCCESS) {
		if (subtype == IEEE80211_STYPE_ASSOC_RESP)
			priv->AssociationRequestRetryCnt = 0;
		else
			priv->ReAssociationRequestRetryCnt = 0;

		atmel_set_mib16(priv, Mac_Mgmt_Mib_Type,
				MAC_MGMT_MIB_STATION_ID_POS, ass_id & 0x3fff);
		atmel_set_mib(priv, Phy_Mib_Type,
			      PHY_MIB_RATE_SET_POS, ass_resp->rates, rates_len);
		if (priv->power_mode == 0) {
			priv->listen_interval = 1;
			atmel_set_mib8(priv, Mac_Mgmt_Mib_Type,
				       MAC_MGMT_MIB_PS_MODE_POS, ACTIVE_MODE);
			atmel_set_mib16(priv, Mac_Mgmt_Mib_Type,
					MAC_MGMT_MIB_LISTEN_INTERVAL_POS, 1);
		} else {
			priv->listen_interval = 2;
			atmel_set_mib8(priv, Mac_Mgmt_Mib_Type,
				       MAC_MGMT_MIB_PS_MODE_POS,  PS_MODE);
			atmel_set_mib16(priv, Mac_Mgmt_Mib_Type,
					MAC_MGMT_MIB_LISTEN_INTERVAL_POS, 2);
		}

		priv->station_is_associated = 1;
		priv->station_was_associated = 1;
		atmel_enter_state(priv, STATION_STATE_READY);

		/* Send association event to userspace */
		wrqu.data.length = 0;
		wrqu.data.flags = 0;
		memcpy(wrqu.ap_addr.sa_data, priv->CurrentBSSID, ETH_ALEN);
		wrqu.ap_addr.sa_family = ARPHRD_ETHER;
		wireless_send_event(priv->dev, SIOCGIWAP, &wrqu, NULL);

		return;
	}

	if (subtype == IEEE80211_STYPE_ASSOC_RESP &&
	    status != WLAN_STATUS_ASSOC_DENIED_RATES &&
	    status != WLAN_STATUS_CAPS_UNSUPPORTED &&
	    priv->AssociationRequestRetryCnt < MAX_ASSOCIATION_RETRIES) {
		mod_timer(&priv->management_timer, jiffies + MGMT_JIFFIES);
		priv->AssociationRequestRetryCnt++;
		send_association_request(priv, 0);
		return;
	}

	if (subtype == IEEE80211_STYPE_REASSOC_RESP &&
	    status != WLAN_STATUS_ASSOC_DENIED_RATES &&
	    status != WLAN_STATUS_CAPS_UNSUPPORTED &&
	    priv->AssociationRequestRetryCnt < MAX_ASSOCIATION_RETRIES) {
		mod_timer(&priv->management_timer, jiffies + MGMT_JIFFIES);
		priv->ReAssociationRequestRetryCnt++;
		send_association_request(priv, 1);
		return;
	}

	atmel_enter_state(priv,  STATION_STATE_MGMT_ERROR);
	priv->station_is_associated = 0;

	if (priv->connect_to_any_BSS) {
		int bss_index;
		priv->BSSinfo[(int)(priv->current_BSS)].channel |= 0x80;

		if ((bss_index = retrieve_bss(priv)) != -1)
			atmel_join_bss(priv, bss_index);
	}
}

static void atmel_join_bss(struct atmel_private *priv, int bss_index)
{
	struct bss_info *bss =  &priv->BSSinfo[bss_index];

	memcpy(priv->CurrentBSSID, bss->BSSID, 6);
	memcpy(priv->SSID, bss->SSID, priv->SSID_size = bss->SSIDsize);

	/* The WPA stuff cares about the current AP address */
	if (priv->use_wpa)
		build_wpa_mib(priv);

	/* When switching to AdHoc turn OFF Power Save if needed */

	if (bss->BSStype == IW_MODE_ADHOC &&
	    priv->operating_mode != IW_MODE_ADHOC &&
	    priv->power_mode) {
		priv->power_mode = 0;
		priv->listen_interval = 1;
		atmel_set_mib8(priv, Mac_Mgmt_Mib_Type,
			       MAC_MGMT_MIB_PS_MODE_POS,  ACTIVE_MODE);
		atmel_set_mib16(priv, Mac_Mgmt_Mib_Type,
				MAC_MGMT_MIB_LISTEN_INTERVAL_POS, 1);
	}

	priv->operating_mode = bss->BSStype;
	priv->channel = bss->channel & 0x7f;
	priv->beacon_period = bss->beacon_period;

	if (priv->preamble != bss->preamble) {
		priv->preamble = bss->preamble;
		atmel_set_mib8(priv, Local_Mib_Type,
			       LOCAL_MIB_PREAMBLE_TYPE, bss->preamble);
	}

	if (!priv->wep_is_on && bss->UsingWEP) {
		atmel_enter_state(priv, STATION_STATE_MGMT_ERROR);
		priv->station_is_associated = 0;
		return;
	}

	if (priv->wep_is_on && !bss->UsingWEP) {
		atmel_enter_state(priv, STATION_STATE_MGMT_ERROR);
		priv->station_is_associated = 0;
		return;
	}

	atmel_enter_state(priv, STATION_STATE_JOINNING);

	if (priv->operating_mode == IW_MODE_INFRA)
		join(priv, BSS_TYPE_INFRASTRUCTURE);
	else
		join(priv, BSS_TYPE_AD_HOC);
}

static void restart_search(struct atmel_private *priv)
{
	int bss_index;

	if (!priv->connect_to_any_BSS) {
		atmel_scan(priv, 1);
	} else {
		priv->BSSinfo[(int)(priv->current_BSS)].channel |= 0x80;

		if ((bss_index = retrieve_bss(priv)) != -1)
			atmel_join_bss(priv, bss_index);
		else
			atmel_scan(priv, 0);
	}
}

static void smooth_rssi(struct atmel_private *priv, u8 rssi)
{
	u8 old = priv->wstats.qual.level;
	u8 max_rssi = 42; /* 502-rmfd-revd max by experiment, default for now */

	switch (priv->firmware_type) {
	case ATMEL_FW_TYPE_502E:
		max_rssi = 63; /* 502-rmfd-reve max by experiment */
		break;
	default:
		break;
	}

	rssi = rssi * 100 / max_rssi;
	if ((rssi + old) % 2)
		priv->wstats.qual.level = (rssi + old) / 2 + 1;
	else
		priv->wstats.qual.level = (rssi + old) / 2;
	priv->wstats.qual.updated |= IW_QUAL_LEVEL_UPDATED;
	priv->wstats.qual.updated &= ~IW_QUAL_LEVEL_INVALID;
}

static void atmel_smooth_qual(struct atmel_private *priv)
{
	unsigned long time_diff = (jiffies - priv->last_qual) / HZ;
	while (time_diff--) {
		priv->last_qual += HZ;
		priv->wstats.qual.qual = priv->wstats.qual.qual / 2;
		priv->wstats.qual.qual +=
			priv->beacons_this_sec * priv->beacon_period * (priv->wstats.qual.level + 100) / 4000;
		priv->beacons_this_sec = 0;
	}
	priv->wstats.qual.updated |= IW_QUAL_QUAL_UPDATED;
	priv->wstats.qual.updated &= ~IW_QUAL_QUAL_INVALID;
}

/* deals with incoming management frames. */
static void atmel_management_frame(struct atmel_private *priv,
				   struct ieee80211_hdr *header,
				   u16 frame_len, u8 rssi)
{
	u16 subtype;

	subtype = le16_to_cpu(header->frame_control) & IEEE80211_FCTL_STYPE;
	switch (subtype) {
	case IEEE80211_STYPE_BEACON:
	case IEEE80211_STYPE_PROBE_RESP:

		/* beacon frame has multiple variable-length fields -
		   never let an engineer loose with a data structure design. */
		{
			struct beacon_format {
				__le64 timestamp;
				__le16 interval;
				__le16 capability;
				u8 ssid_el_id;
				u8 ssid_length;
				/* ssid here */
				u8 rates_el_id;
				u8 rates_length;
				/* rates here */
				u8 ds_el_id;
				u8 ds_length;
				/* ds here */
			} *beacon = (struct beacon_format *)priv->rx_buf;

			u8 channel, rates_length, ssid_length;
			u64 timestamp = le64_to_cpu(beacon->timestamp);
			u16 beacon_interval = le16_to_cpu(beacon->interval);
			u16 capability = le16_to_cpu(beacon->capability);
			u8 *beaconp = priv->rx_buf;
			ssid_length = beacon->ssid_length;
			/* this blows chunks. */
			if (frame_len < 14 || frame_len < ssid_length + 15)
				return;
			rates_length = beaconp[beacon->ssid_length + 15];
			if (frame_len < ssid_length + rates_length + 18)
				return;
			if (ssid_length >  MAX_SSID_LENGTH)
				return;
			channel = beaconp[ssid_length + rates_length + 18];

			if (priv->station_state == STATION_STATE_READY) {
				smooth_rssi(priv, rssi);
				if (is_frame_from_current_bss(priv, header)) {
					priv->beacons_this_sec++;
					atmel_smooth_qual(priv);
					if (priv->last_beacon_timestamp) {
						/* Note truncate this to 32 bits - kernel can't divide a long long */
						u32 beacon_delay = timestamp - priv->last_beacon_timestamp;
						int beacons = beacon_delay / (beacon_interval * 1000);
						if (beacons > 1)
							priv->wstats.miss.beacon += beacons - 1;
					}
					priv->last_beacon_timestamp = timestamp;
					handle_beacon_probe(priv, capability, channel);
				}
			}

			if (priv->station_state == STATION_STATE_SCANNING)
				store_bss_info(priv, header, capability,
					       beacon_interval, channel, rssi,
					       ssid_length,
					       &beacon->rates_el_id,
					       subtype == IEEE80211_STYPE_BEACON);
		}
		break;

	case IEEE80211_STYPE_AUTH:

		if (priv->station_state == STATION_STATE_AUTHENTICATING)
			authenticate(priv, frame_len);

		break;

	case IEEE80211_STYPE_ASSOC_RESP:
	case IEEE80211_STYPE_REASSOC_RESP:

		if (priv->station_state == STATION_STATE_ASSOCIATING ||
		    priv->station_state == STATION_STATE_REASSOCIATING)
			associate(priv, frame_len, subtype);

		break;

	case IEEE80211_STYPE_DISASSOC:
		if (priv->station_is_associated &&
		    priv->operating_mode == IW_MODE_INFRA &&
		    is_frame_from_current_bss(priv, header)) {
			priv->station_was_associated = 0;
			priv->station_is_associated = 0;

			atmel_enter_state(priv, STATION_STATE_JOINNING);
			join(priv, BSS_TYPE_INFRASTRUCTURE);
		}

		break;

	case IEEE80211_STYPE_DEAUTH:
		if (priv->operating_mode == IW_MODE_INFRA &&
		    is_frame_from_current_bss(priv, header)) {
			priv->station_was_associated = 0;

			atmel_enter_state(priv, STATION_STATE_JOINNING);
			join(priv, BSS_TYPE_INFRASTRUCTURE);
		}

		break;
	}
}

/* run when timer expires */
static void atmel_management_timer(u_long a)
{
	struct net_device *dev = (struct net_device *) a;
	struct atmel_private *priv = netdev_priv(dev);
	unsigned long flags;

	/* Check if the card has been yanked. */
	if (priv->card && priv->present_callback &&
		!(*priv->present_callback)(priv->card))
		return;

	spin_lock_irqsave(&priv->irqlock, flags);

	switch (priv->station_state) {

	case STATION_STATE_AUTHENTICATING:
		if (priv->AuthenticationRequestRetryCnt >= MAX_AUTHENTICATION_RETRIES) {
			atmel_enter_state(priv, STATION_STATE_MGMT_ERROR);
			priv->station_is_associated = 0;
			priv->AuthenticationRequestRetryCnt = 0;
			restart_search(priv);
		} else {
			int auth = WLAN_AUTH_OPEN;
			priv->AuthenticationRequestRetryCnt++;
			priv->CurrentAuthentTransactionSeqNum = 0x0001;
			mod_timer(&priv->management_timer, jiffies + MGMT_JIFFIES);
			if (priv->wep_is_on && priv->exclude_unencrypted)
				auth = WLAN_AUTH_SHARED_KEY;
			send_authentication_request(priv, auth, NULL, 0);
	  }
	  break;

	case STATION_STATE_ASSOCIATING:
		if (priv->AssociationRequestRetryCnt == MAX_ASSOCIATION_RETRIES) {
			atmel_enter_state(priv, STATION_STATE_MGMT_ERROR);
			priv->station_is_associated = 0;
			priv->AssociationRequestRetryCnt = 0;
			restart_search(priv);
		} else {
			priv->AssociationRequestRetryCnt++;
			mod_timer(&priv->management_timer, jiffies + MGMT_JIFFIES);
			send_association_request(priv, 0);
		}
	  break;

	case STATION_STATE_REASSOCIATING:
		if (priv->ReAssociationRequestRetryCnt == MAX_ASSOCIATION_RETRIES) {
			atmel_enter_state(priv, STATION_STATE_MGMT_ERROR);
			priv->station_is_associated = 0;
			priv->ReAssociationRequestRetryCnt = 0;
			restart_search(priv);
		} else {
			priv->ReAssociationRequestRetryCnt++;
			mod_timer(&priv->management_timer, jiffies + MGMT_JIFFIES);
			send_association_request(priv, 1);
		}
		break;

	default:
		break;
	}

	spin_unlock_irqrestore(&priv->irqlock, flags);
}

static void atmel_command_irq(struct atmel_private *priv)
{
	u8 status = atmel_rmem8(priv, atmel_co(priv, CMD_BLOCK_STATUS_OFFSET));
	u8 command = atmel_rmem8(priv, atmel_co(priv, CMD_BLOCK_COMMAND_OFFSET));
	int fast_scan;
	union iwreq_data wrqu;

	if (status == CMD_STATUS_IDLE ||
	    status == CMD_STATUS_IN_PROGRESS)
		return;

	switch (command) {
	case CMD_Start:
		if (status == CMD_STATUS_COMPLETE) {
			priv->station_was_associated = priv->station_is_associated;
			atmel_get_mib(priv, Mac_Mgmt_Mib_Type, MAC_MGMT_MIB_CUR_BSSID_POS,
				      (u8 *)priv->CurrentBSSID, 6);
			atmel_enter_state(priv, STATION_STATE_READY);
		}
		break;

	case CMD_Scan:
		fast_scan = priv->fast_scan;
		priv->fast_scan = 0;

		if (status != CMD_STATUS_COMPLETE) {
			atmel_scan(priv, 1);
		} else {
			int bss_index = retrieve_bss(priv);
			int notify_scan_complete = 1;
			if (bss_index != -1) {
				atmel_join_bss(priv, bss_index);
			} else if (priv->operating_mode == IW_MODE_ADHOC &&
				   priv->SSID_size != 0) {
				start(priv, BSS_TYPE_AD_HOC);
			} else {
				priv->fast_scan = !fast_scan;
				atmel_scan(priv, 1);
				notify_scan_complete = 0;
			}
			priv->site_survey_state = SITE_SURVEY_COMPLETED;
			if (notify_scan_complete) {
				wrqu.data.length = 0;
				wrqu.data.flags = 0;
				wireless_send_event(priv->dev, SIOCGIWSCAN, &wrqu, NULL);
			}
		}
		break;

	case CMD_SiteSurvey:
		priv->fast_scan = 0;

		if (status != CMD_STATUS_COMPLETE)
			return;

		priv->site_survey_state = SITE_SURVEY_COMPLETED;
		if (priv->station_is_associated) {
			atmel_enter_state(priv, STATION_STATE_READY);
			wrqu.data.length = 0;
			wrqu.data.flags = 0;
			wireless_send_event(priv->dev, SIOCGIWSCAN, &wrqu, NULL);
		} else {
			atmel_scan(priv, 1);
		}
		break;

	case CMD_Join:
		if (status == CMD_STATUS_COMPLETE) {
			if (priv->operating_mode == IW_MODE_ADHOC) {
				priv->station_was_associated = priv->station_is_associated;
				atmel_enter_state(priv, STATION_STATE_READY);
			} else {
				int auth = WLAN_AUTH_OPEN;
				priv->AuthenticationRequestRetryCnt = 0;
				atmel_enter_state(priv, STATION_STATE_AUTHENTICATING);

				mod_timer(&priv->management_timer, jiffies + MGMT_JIFFIES);
				priv->CurrentAuthentTransactionSeqNum = 0x0001;
				if (priv->wep_is_on && priv->exclude_unencrypted)
					auth = WLAN_AUTH_SHARED_KEY;
				send_authentication_request(priv, auth, NULL, 0);
			}
			return;
		}

		atmel_scan(priv, 1);
	}
}

static int atmel_wakeup_firmware(struct atmel_private *priv)
{
	struct host_info_struct *iface = &priv->host_info;
	u16 mr1, mr3;
	int i;

	if (priv->card_type == CARD_TYPE_SPI_FLASH)
		atmel_set_gcr(priv->dev, GCR_REMAP);

	/* wake up on-board processor */
	atmel_clear_gcr(priv->dev, 0x0040);
	atmel_write16(priv->dev, BSR, BSS_SRAM);

	if (priv->card_type == CARD_TYPE_SPI_FLASH)
		mdelay(100);

	/* and wait for it */
	for (i = LOOP_RETRY_LIMIT; i; i--) {
		mr1 = atmel_read16(priv->dev, MR1);
		mr3 = atmel_read16(priv->dev, MR3);

		if (mr3 & MAC_BOOT_COMPLETE)
			break;
		if (mr1 & MAC_BOOT_COMPLETE &&
		    priv->bus_type == BUS_TYPE_PCCARD)
			break;
	}

	if (i == 0) {
		printk(KERN_ALERT "%s: MAC failed to boot.\n", priv->dev->name);
		return -EIO;
	}

	if ((priv->host_info_base = atmel_read16(priv->dev, MR2)) == 0xffff) {
		printk(KERN_ALERT "%s: card missing.\n", priv->dev->name);
		return -ENODEV;
	}

	/* now check for completion of MAC initialization through
	   the FunCtrl field of the IFACE, poll MR1 to detect completion of
	   MAC initialization, check completion status, set interrupt mask,
	   enables interrupts and calls Tx and Rx initialization functions */

	atmel_wmem8(priv, atmel_hi(priv, IFACE_FUNC_CTRL_OFFSET), FUNC_CTRL_INIT_COMPLETE);

	for (i = LOOP_RETRY_LIMIT; i; i--) {
		mr1 = atmel_read16(priv->dev, MR1);
		mr3 = atmel_read16(priv->dev, MR3);

		if (mr3 & MAC_INIT_COMPLETE)
			break;
		if (mr1 & MAC_INIT_COMPLETE &&
		    priv->bus_type == BUS_TYPE_PCCARD)
			break;
	}

	if (i == 0) {
		printk(KERN_ALERT "%s: MAC failed to initialise.\n",
				priv->dev->name);
		return -EIO;
	}

	/* Check for MAC_INIT_OK only on the register that the MAC_INIT_OK was set */
	if ((mr3 & MAC_INIT_COMPLETE) &&
	    !(atmel_read16(priv->dev, MR3) & MAC_INIT_OK)) {
		printk(KERN_ALERT "%s: MAC failed MR3 self-test.\n", priv->dev->name);
		return -EIO;
	}
	if ((mr1 & MAC_INIT_COMPLETE) &&
	    !(atmel_read16(priv->dev, MR1) & MAC_INIT_OK)) {
		printk(KERN_ALERT "%s: MAC failed MR1 self-test.\n", priv->dev->name);
		return -EIO;
	}

	atmel_copy_to_host(priv->dev, (unsigned char *)iface,
			   priv->host_info_base, sizeof(*iface));

	iface->tx_buff_pos = le16_to_cpu(iface->tx_buff_pos);
	iface->tx_buff_size = le16_to_cpu(iface->tx_buff_size);
	iface->tx_desc_pos = le16_to_cpu(iface->tx_desc_pos);
	iface->tx_desc_count = le16_to_cpu(iface->tx_desc_count);
	iface->rx_buff_pos = le16_to_cpu(iface->rx_buff_pos);
	iface->rx_buff_size = le16_to_cpu(iface->rx_buff_size);
	iface->rx_desc_pos = le16_to_cpu(iface->rx_desc_pos);
	iface->rx_desc_count = le16_to_cpu(iface->rx_desc_count);
	iface->build_version = le16_to_cpu(iface->build_version);
	iface->command_pos = le16_to_cpu(iface->command_pos);
	iface->major_version = le16_to_cpu(iface->major_version);
	iface->minor_version = le16_to_cpu(iface->minor_version);
	iface->func_ctrl = le16_to_cpu(iface->func_ctrl);
	iface->mac_status = le16_to_cpu(iface->mac_status);

	return 0;
}

/* determine type of memory and MAC address */
static int probe_atmel_card(struct net_device *dev)
{
	int rc = 0;
	struct atmel_private *priv = netdev_priv(dev);

	/* reset pccard */
	if (priv->bus_type == BUS_TYPE_PCCARD)
		atmel_write16(dev, GCR, 0x0060);

	atmel_write16(dev, GCR, 0x0040);
	mdelay(500);

	if (atmel_read16(dev, MR2) == 0) {
		/* No stored firmware so load a small stub which just
		   tells us the MAC address */
		int i;
		priv->card_type = CARD_TYPE_EEPROM;
		atmel_write16(dev, BSR, BSS_IRAM);
		atmel_copy_to_card(dev, 0, mac_reader, sizeof(mac_reader));
		atmel_set_gcr(dev, GCR_REMAP);
		atmel_clear_gcr(priv->dev, 0x0040);
		atmel_write16(dev, BSR, BSS_SRAM);
		for (i = LOOP_RETRY_LIMIT; i; i--)
			if (atmel_read16(dev, MR3) & MAC_BOOT_COMPLETE)
				break;
		if (i == 0) {
			printk(KERN_ALERT "%s: MAC failed to boot MAC address reader.\n", dev->name);
		} else {
			atmel_copy_to_host(dev, dev->dev_addr, atmel_read16(dev, MR2), 6);
			/* got address, now squash it again until the network
			   interface is opened */
			if (priv->bus_type == BUS_TYPE_PCCARD)
				atmel_write16(dev, GCR, 0x0060);
			atmel_write16(dev, GCR, 0x0040);
			rc = 1;
		}
	} else if (atmel_read16(dev, MR4) == 0) {
		/* Mac address easy in this case. */
		priv->card_type = CARD_TYPE_PARALLEL_FLASH;
		atmel_write16(dev,  BSR, 1);
		atmel_copy_to_host(dev, dev->dev_addr, 0xc000, 6);
		atmel_write16(dev,  BSR, 0x200);
		rc = 1;
	} else {
		/* Standard firmware in flash, boot it up and ask
		   for the Mac Address */
		priv->card_type = CARD_TYPE_SPI_FLASH;
		if (atmel_wakeup_firmware(priv) == 0) {
			atmel_get_mib(priv, Mac_Address_Mib_Type, 0, dev->dev_addr, 6);

			/* got address, now squash it again until the network
			   interface is opened */
			if (priv->bus_type == BUS_TYPE_PCCARD)
				atmel_write16(dev, GCR, 0x0060);
			atmel_write16(dev, GCR, 0x0040);
			rc = 1;
		}
	}

	if (rc) {
		if (dev->dev_addr[0] == 0xFF) {
			static const u8 default_mac[] = {
				0x00, 0x04, 0x25, 0x00, 0x00, 0x00
			};
			printk(KERN_ALERT "%s: *** Invalid MAC address. UPGRADE Firmware ****\n", dev->name);
			memcpy(dev->dev_addr, default_mac, 6);
		}
	}

	return rc;
}

/* Move the encyption information on the MIB structure.
   This routine is for the pre-WPA firmware: later firmware has
   a different format MIB and a different routine. */
static void build_wep_mib(struct atmel_private *priv)
{
	struct { /* NB this is matched to the hardware, don't change. */
		u8 wep_is_on;
		u8 default_key; /* 0..3 */
		u8 reserved;
		u8 exclude_unencrypted;

		u32 WEPICV_error_count;
		u32 WEP_excluded_count;

		u8 wep_keys[MAX_ENCRYPTION_KEYS][13];
		u8 encryption_level; /* 0, 1, 2 */
		u8 reserved2[3];
	} mib;
	int i;

	mib.wep_is_on = priv->wep_is_on;
	if (priv->wep_is_on) {
		if (priv->wep_key_len[priv->default_key] > 5)
			mib.encryption_level = 2;
		else
			mib.encryption_level = 1;
	} else {
		mib.encryption_level = 0;
	}

	mib.default_key = priv->default_key;
	mib.exclude_unencrypted = priv->exclude_unencrypted;

	for (i = 0; i < MAX_ENCRYPTION_KEYS; i++)
		memcpy(mib.wep_keys[i], priv->wep_keys[i], 13);

	atmel_set_mib(priv, Mac_Wep_Mib_Type, 0, (u8 *)&mib, sizeof(mib));
}

static void build_wpa_mib(struct atmel_private *priv)
{
	/* This is for the later (WPA enabled) firmware. */

	struct { /* NB this is matched to the hardware, don't change. */
		u8 cipher_default_key_value[MAX_ENCRYPTION_KEYS][MAX_ENCRYPTION_KEY_SIZE];
		u8 receiver_address[6];
		u8 wep_is_on;
		u8 default_key; /* 0..3 */
		u8 group_key;
		u8 exclude_unencrypted;
		u8 encryption_type;
		u8 reserved;

		u32 WEPICV_error_count;
		u32 WEP_excluded_count;

		u8 key_RSC[4][8];
	} mib;

	int i;

	mib.wep_is_on = priv->wep_is_on;
	mib.exclude_unencrypted = priv->exclude_unencrypted;
	memcpy(mib.receiver_address, priv->CurrentBSSID, 6);

	/* zero all the keys before adding in valid ones. */
	memset(mib.cipher_default_key_value, 0, sizeof(mib.cipher_default_key_value));

	if (priv->wep_is_on) {
		/* There's a comment in the Atmel code to the effect that this
		   is only valid when still using WEP, it may need to be set to
		   something to use WPA */
		memset(mib.key_RSC, 0, sizeof(mib.key_RSC));

		mib.default_key = mib.group_key = 255;
		for (i = 0; i < MAX_ENCRYPTION_KEYS; i++) {
			if (priv->wep_key_len[i] > 0) {
				memcpy(mib.cipher_default_key_value[i], priv->wep_keys[i], MAX_ENCRYPTION_KEY_SIZE);
				if (i == priv->default_key) {
					mib.default_key = i;
					mib.cipher_default_key_value[i][MAX_ENCRYPTION_KEY_SIZE-1] = 7;
					mib.cipher_default_key_value[i][MAX_ENCRYPTION_KEY_SIZE-2] = priv->pairwise_cipher_suite;
				} else {
					mib.group_key = i;
					priv->group_cipher_suite = priv->pairwise_cipher_suite;
					mib.cipher_default_key_value[i][MAX_ENCRYPTION_KEY_SIZE-1] = 1;
					mib.cipher_default_key_value[i][MAX_ENCRYPTION_KEY_SIZE-2] = priv->group_cipher_suite;
				}
			}
		}
		if (mib.default_key == 255)
			mib.default_key = mib.group_key != 255 ? mib.group_key : 0;
		if (mib.group_key == 255)
			mib.group_key = mib.default_key;

	}

	atmel_set_mib(priv, Mac_Wep_Mib_Type, 0, (u8 *)&mib, sizeof(mib));
}

static int reset_atmel_card(struct net_device *dev)
{
	/* do everything necessary to wake up the hardware, including
	   waiting for the lightning strike and throwing the knife switch....

	   set all the Mib values which matter in the card to match
	   their settings in the atmel_private structure. Some of these
	   can be altered on the fly, but many (WEP, infrastucture or ad-hoc)
	   can only be changed by tearing down the world and coming back through
	   here.

	   This routine is also responsible for initialising some
	   hardware-specific fields in the atmel_private structure,
	   including a copy of the firmware's hostinfo structure
	   which is the route into the rest of the firmware datastructures. */

	struct atmel_private *priv = netdev_priv(dev);
	u8 configuration;
	int old_state = priv->station_state;
	int err = 0;

	/* data to add to the firmware names, in priority order
	   this implemenents firmware versioning */

	static char *firmware_modifier[] = {
		"-wpa",
		"",
		NULL
	};

	/* reset pccard */
	if (priv->bus_type == BUS_TYPE_PCCARD)
		atmel_write16(priv->dev, GCR, 0x0060);

	/* stop card , disable interrupts */
	atmel_write16(priv->dev, GCR, 0x0040);

	if (priv->card_type == CARD_TYPE_EEPROM) {
		/* copy in firmware if needed */
		const struct firmware *fw_entry = NULL;
		const unsigned char *fw;
		int len = priv->firmware_length;
		if (!(fw = priv->firmware)) {
			if (priv->firmware_type == ATMEL_FW_TYPE_NONE) {
				if (strlen(priv->firmware_id) == 0) {
					printk(KERN_INFO
					       "%s: card type is unknown: assuming at76c502 firmware is OK.\n",
					       dev->name);
					printk(KERN_INFO
					       "%s: if not, use the firmware= module parameter.\n",
					       dev->name);
					strcpy(priv->firmware_id, "atmel_at76c502.bin");
				}
				err = request_firmware(&fw_entry, priv->firmware_id, priv->sys_dev);
				if (err != 0) {
					printk(KERN_ALERT
					       "%s: firmware %s is missing, cannot continue.\n",
					       dev->name, priv->firmware_id);
					return err;
				}
			} else {
				int fw_index = 0;
				int success = 0;

				/* get firmware filename entry based on firmware type ID */
				while (fw_table[fw_index].fw_type != priv->firmware_type
						&& fw_table[fw_index].fw_type != ATMEL_FW_TYPE_NONE)
					fw_index++;

				/* construct the actual firmware file name */
				if (fw_table[fw_index].fw_type != ATMEL_FW_TYPE_NONE) {
					int i;
					for (i = 0; firmware_modifier[i]; i++) {
						snprintf(priv->firmware_id, 32, "%s%s.%s", fw_table[fw_index].fw_file,
							firmware_modifier[i], fw_table[fw_index].fw_file_ext);
						priv->firmware_id[31] = '\0';
						if (request_firmware(&fw_entry, priv->firmware_id, priv->sys_dev) == 0) {
							success = 1;
							break;
						}
					}
				}
				if (!success) {
					printk(KERN_ALERT
					       "%s: firmware %s is missing, cannot start.\n",
					       dev->name, priv->firmware_id);
					priv->firmware_id[0] = '\0';
					return -ENOENT;
				}
			}

			fw = fw_entry->data;
			len = fw_entry->size;
		}

		if (len <= 0x6000) {
			atmel_write16(priv->dev, BSR, BSS_IRAM);
			atmel_copy_to_card(priv->dev, 0, fw, len);
			atmel_set_gcr(priv->dev, GCR_REMAP);
		} else {
			/* Remap */
			atmel_set_gcr(priv->dev, GCR_REMAP);
			atmel_write16(priv->dev, BSR, BSS_IRAM);
			atmel_copy_to_card(priv->dev, 0, fw, 0x6000);
			atmel_write16(priv->dev, BSR, 0x2ff);
			atmel_copy_to_card(priv->dev, 0x8000, &fw[0x6000], len - 0x6000);
		}

		if (fw_entry)
			release_firmware(fw_entry);
	}

	err = atmel_wakeup_firmware(priv);
	if (err != 0)
		return err;

	/* Check the version and set the correct flag for wpa stuff,
	   old and new firmware is incompatible.
	   The pre-wpa 3com firmware reports major version 5,
	   the wpa 3com firmware is major version 4 and doesn't need
	   the 3com broken-ness filter. */
	priv->use_wpa = (priv->host_info.major_version == 4);
	priv->radio_on_broken = (priv->host_info.major_version == 5);

	/* unmask all irq sources */
	atmel_wmem8(priv, atmel_hi(priv, IFACE_INT_MASK_OFFSET), 0xff);

	/* int Tx system and enable Tx */
	atmel_wmem8(priv, atmel_tx(priv, TX_DESC_FLAGS_OFFSET, 0), 0);
	atmel_wmem32(priv, atmel_tx(priv, TX_DESC_NEXT_OFFSET, 0), 0x80000000L);
	atmel_wmem16(priv, atmel_tx(priv, TX_DESC_POS_OFFSET, 0), 0);
	atmel_wmem16(priv, atmel_tx(priv, TX_DESC_SIZE_OFFSET, 0), 0);

	priv->tx_desc_free = priv->host_info.tx_desc_count;
	priv->tx_desc_head = 0;
	priv->tx_desc_tail = 0;
	priv->tx_desc_previous = 0;
	priv->tx_free_mem = priv->host_info.tx_buff_size;
	priv->tx_buff_head = 0;
	priv->tx_buff_tail = 0;

	configuration = atmel_rmem8(priv, atmel_hi(priv, IFACE_FUNC_CTRL_OFFSET));
	atmel_wmem8(priv, atmel_hi(priv, IFACE_FUNC_CTRL_OFFSET),
				   configuration | FUNC_CTRL_TxENABLE);

	/* init Rx system and enable */
	priv->rx_desc_head = 0;

	configuration = atmel_rmem8(priv, atmel_hi(priv, IFACE_FUNC_CTRL_OFFSET));
	atmel_wmem8(priv, atmel_hi(priv, IFACE_FUNC_CTRL_OFFSET),
				   configuration | FUNC_CTRL_RxENABLE);

	if (!priv->radio_on_broken) {
		if (atmel_send_command_wait(priv, CMD_EnableRadio, NULL, 0) ==
		    CMD_STATUS_REJECTED_RADIO_OFF) {
			printk(KERN_INFO "%s: cannot turn the radio on.\n",
			       dev->name);
			return -EIO;
		}
	}

	/* set up enough MIB values to run. */
	atmel_set_mib8(priv, Local_Mib_Type, LOCAL_MIB_AUTO_TX_RATE_POS, priv->auto_tx_rate);
	atmel_set_mib8(priv, Local_Mib_Type,  LOCAL_MIB_TX_PROMISCUOUS_POS,  PROM_MODE_OFF);
	atmel_set_mib16(priv, Mac_Mib_Type, MAC_MIB_RTS_THRESHOLD_POS, priv->rts_threshold);
	atmel_set_mib16(priv, Mac_Mib_Type, MAC_MIB_FRAG_THRESHOLD_POS, priv->frag_threshold);
	atmel_set_mib8(priv, Mac_Mib_Type, MAC_MIB_SHORT_RETRY_POS, priv->short_retry);
	atmel_set_mib8(priv, Mac_Mib_Type, MAC_MIB_LONG_RETRY_POS, priv->long_retry);
	atmel_set_mib8(priv, Local_Mib_Type, LOCAL_MIB_PREAMBLE_TYPE, priv->preamble);
	atmel_set_mib(priv, Mac_Address_Mib_Type, MAC_ADDR_MIB_MAC_ADDR_POS,
		      priv->dev->dev_addr, 6);
	atmel_set_mib8(priv, Mac_Mgmt_Mib_Type, MAC_MGMT_MIB_PS_MODE_POS, ACTIVE_MODE);
	atmel_set_mib16(priv, Mac_Mgmt_Mib_Type, MAC_MGMT_MIB_LISTEN_INTERVAL_POS, 1);
	atmel_set_mib16(priv, Mac_Mgmt_Mib_Type, MAC_MGMT_MIB_BEACON_PER_POS, priv->default_beacon_period);
	atmel_set_mib(priv, Phy_Mib_Type, PHY_MIB_RATE_SET_POS, atmel_basic_rates, 4);
	atmel_set_mib8(priv, Mac_Mgmt_Mib_Type, MAC_MGMT_MIB_CUR_PRIVACY_POS, priv->wep_is_on);
	if (priv->use_wpa)
		build_wpa_mib(priv);
	else
		build_wep_mib(priv);

	if (old_state == STATION_STATE_READY) {
		union iwreq_data wrqu;

		wrqu.data.length = 0;
		wrqu.data.flags = 0;
		wrqu.ap_addr.sa_family = ARPHRD_ETHER;
		memset(wrqu.ap_addr.sa_data, 0, ETH_ALEN);
		wireless_send_event(priv->dev, SIOCGIWAP, &wrqu, NULL);
	}

	return 0;
}

static void atmel_send_command(struct atmel_private *priv, int command,
			       void *cmd, int cmd_size)
{
	if (cmd)
		atmel_copy_to_card(priv->dev, atmel_co(priv, CMD_BLOCK_PARAMETERS_OFFSET),
				   cmd, cmd_size);

	atmel_wmem8(priv, atmel_co(priv, CMD_BLOCK_COMMAND_OFFSET), command);
	atmel_wmem8(priv, atmel_co(priv, CMD_BLOCK_STATUS_OFFSET), 0);
}

static int atmel_send_command_wait(struct atmel_private *priv, int command,
				   void *cmd, int cmd_size)
{
	int i, status;

	atmel_send_command(priv, command, cmd, cmd_size);

	for (i = 5000; i; i--) {
		status = atmel_rmem8(priv, atmel_co(priv, CMD_BLOCK_STATUS_OFFSET));
		if (status != CMD_STATUS_IDLE &&
		    status != CMD_STATUS_IN_PROGRESS)
			break;
		udelay(20);
	}

	if (i == 0) {
		printk(KERN_ALERT "%s: failed to contact MAC.\n", priv->dev->name);
		status =  CMD_STATUS_HOST_ERROR;
	} else {
		if (command != CMD_EnableRadio)
			status = CMD_STATUS_COMPLETE;
	}

	return status;
}

static u8 atmel_get_mib8(struct atmel_private *priv, u8 type, u8 index)
{
	struct get_set_mib m;
	m.type = type;
	m.size = 1;
	m.index = index;

	atmel_send_command_wait(priv, CMD_Get_MIB_Vars, &m, MIB_HEADER_SIZE + 1);
	return atmel_rmem8(priv, atmel_co(priv, CMD_BLOCK_PARAMETERS_OFFSET + MIB_HEADER_SIZE));
}

static void atmel_set_mib8(struct atmel_private *priv, u8 type, u8 index, u8 data)
{
	struct get_set_mib m;
	m.type = type;
	m.size = 1;
	m.index = index;
	m.data[0] = data;

	atmel_send_command_wait(priv, CMD_Set_MIB_Vars, &m, MIB_HEADER_SIZE + 1);
}

static void atmel_set_mib16(struct atmel_private *priv, u8 type, u8 index,
			    u16 data)
{
	struct get_set_mib m;
	m.type = type;
	m.size = 2;
	m.index = index;
	m.data[0] = data;
	m.data[1] = data >> 8;

	atmel_send_command_wait(priv, CMD_Set_MIB_Vars, &m, MIB_HEADER_SIZE + 2);
}

static void atmel_set_mib(struct atmel_private *priv, u8 type, u8 index,
			  u8 *data, int data_len)
{
	struct get_set_mib m;
	m.type = type;
	m.size = data_len;
	m.index = index;

	if (data_len > MIB_MAX_DATA_BYTES)
		printk(KERN_ALERT "%s: MIB buffer too small.\n", priv->dev->name);

	memcpy(m.data, data, data_len);
	atmel_send_command_wait(priv, CMD_Set_MIB_Vars, &m, MIB_HEADER_SIZE + data_len);
}

static void atmel_get_mib(struct atmel_private *priv, u8 type, u8 index,
			  u8 *data, int data_len)
{
	struct get_set_mib m;
	m.type = type;
	m.size = data_len;
	m.index = index;

	if (data_len > MIB_MAX_DATA_BYTES)
		printk(KERN_ALERT "%s: MIB buffer too small.\n", priv->dev->name);

	atmel_send_command_wait(priv, CMD_Get_MIB_Vars, &m, MIB_HEADER_SIZE + data_len);
	atmel_copy_to_host(priv->dev, data,
			   atmel_co(priv, CMD_BLOCK_PARAMETERS_OFFSET + MIB_HEADER_SIZE), data_len);
}

static void atmel_writeAR(struct net_device *dev, u16 data)
{
	int i;
	outw(data, dev->base_addr + AR);
	/* Address register appears to need some convincing..... */
	for (i = 0; data != inw(dev->base_addr + AR) && i < 10; i++)
		outw(data, dev->base_addr + AR);
}

static void atmel_copy_to_card(struct net_device *dev, u16 dest,
			       const unsigned char *src, u16 len)
{
	int i;
	atmel_writeAR(dev, dest);
	if (dest % 2) {
		atmel_write8(dev, DR, *src);
		src++; len--;
	}
	for (i = len; i > 1 ; i -= 2) {
		u8 lb = *src++;
		u8 hb = *src++;
		atmel_write16(dev, DR, lb | (hb << 8));
	}
	if (i)
		atmel_write8(dev, DR, *src);
}

static void atmel_copy_to_host(struct net_device *dev, unsigned char *dest,
			       u16 src, u16 len)
{
	int i;
	atmel_writeAR(dev, src);
	if (src % 2) {
		*dest = atmel_read8(dev, DR);
		dest++; len--;
	}
	for (i = len; i > 1 ; i -= 2) {
		u16 hw = atmel_read16(dev, DR);
		*dest++ = hw;
		*dest++ = hw >> 8;
	}
	if (i)
		*dest = atmel_read8(dev, DR);
}

static void atmel_set_gcr(struct net_device *dev, u16 mask)
{
	outw(inw(dev->base_addr + GCR) | mask, dev->base_addr + GCR);
}

static void atmel_clear_gcr(struct net_device *dev, u16 mask)
{
	outw(inw(dev->base_addr + GCR) & ~mask, dev->base_addr + GCR);
}

static int atmel_lock_mac(struct atmel_private *priv)
{
	int i, j = 20;
 retry:
	for (i = 5000; i; i--) {
		if (!atmel_rmem8(priv, atmel_hi(priv, IFACE_LOCKOUT_HOST_OFFSET)))
			break;
		udelay(20);
	}

	if (!i)
		return 0; /* timed out */

	atmel_wmem8(priv, atmel_hi(priv, IFACE_LOCKOUT_MAC_OFFSET), 1);
	if (atmel_rmem8(priv, atmel_hi(priv, IFACE_LOCKOUT_HOST_OFFSET))) {
		atmel_wmem8(priv, atmel_hi(priv, IFACE_LOCKOUT_MAC_OFFSET), 0);
		if (!j--)
			return 0; /* timed out */
		goto retry;
	}

	return 1;
}

static void atmel_wmem32(struct atmel_private *priv, u16 pos, u32 data)
{
	atmel_writeAR(priv->dev, pos);
	atmel_write16(priv->dev, DR, data); /* card is little-endian */
	atmel_write16(priv->dev, DR, data >> 16);
}

/***************************************************************************/
/* There follows the source form of the MAC address reading firmware       */
/***************************************************************************/
#if 0

/* Copyright 2003 Matthew T. Russotto                                      */
/* But derived from the Atmel 76C502 firmware written by Atmel and         */
/* included in "atmel wireless lan drivers" package                        */
/**
    This file is part of net.russotto.AtmelMACFW, hereto referred to
    as AtmelMACFW

    AtmelMACFW is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License version 2
    as published by the Free Software Foundation.

    AtmelMACFW 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 AtmelMACFW; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

****************************************************************************/
/* This firmware should work on the 76C502 RFMD, RFMD_D, and RFMD_E        */
/* It will probably work on the 76C504 and 76C502 RFMD_3COM                */
/* It only works on SPI EEPROM versions of the card.                       */

/* This firmware initializes the SPI controller and clock, reads the MAC   */
/* address from the EEPROM into SRAM, and puts the SRAM offset of the MAC  */
/* address in MR2, and sets MR3 to 0x10 to indicate it is done             */
/* It also puts a complete copy of the EEPROM in SRAM with the offset in   */
/* MR4, for investigational purposes (maybe we can determine chip type     */
/* from that?)                                                             */

	.org 0
    .set MRBASE, 0x8000000
	.set CPSR_INITIAL, 0xD3 /* IRQ/FIQ disabled, ARM mode, Supervisor state */
	.set CPSR_USER, 0xD1 /* IRQ/FIQ disabled, ARM mode, USER state */
	.set SRAM_BASE,  0x02000000
	.set SP_BASE,    0x0F300000
	.set UNK_BASE,   0x0F000000 /* Some internal device, but which one? */
	.set SPI_CGEN_BASE,  0x0E000000 /* Some internal device, but which one? */
	.set UNK3_BASE,  0x02014000 /* Some internal device, but which one? */
	.set STACK_BASE, 0x5600
	.set SP_SR, 0x10
	.set SP_TDRE, 2 /* status register bit -- TDR empty */
	.set SP_RDRF, 1 /* status register bit -- RDR full */
	.set SP_SWRST, 0x80
	.set SP_SPIEN, 0x1
	.set SP_CR, 0   /* control register */
	.set SP_MR, 4   /* mode register */
	.set SP_RDR, 0x08 /* Read Data Register */
	.set SP_TDR, 0x0C /* Transmit Data Register */
	.set SP_CSR0, 0x30 /* chip select registers */
	.set SP_CSR1, 0x34
	.set SP_CSR2, 0x38
	.set SP_CSR3, 0x3C
	.set NVRAM_CMD_RDSR, 5 /* read status register */
	.set NVRAM_CMD_READ, 3 /* read data */
	.set NVRAM_SR_RDY, 1 /* RDY bit.  This bit is inverted */
	.set SPI_8CLOCKS, 0xFF /* Writing this to the TDR doesn't do anything to the
				  serial output, since SO is normally high.  But it
				  does cause 8 clock cycles and thus 8 bits to be
				  clocked in to the chip.  See Atmel's SPI
				  controller (e.g. AT91M55800) timing and 4K
				  SPI EEPROM manuals */

	.set NVRAM_SCRATCH, 0x02000100  /* arbitrary area for scratchpad memory */
	.set NVRAM_IMAGE, 0x02000200
	.set NVRAM_LENGTH, 0x0200
	.set MAC_ADDRESS_MIB, SRAM_BASE
	.set MAC_ADDRESS_LENGTH, 6
	.set MAC_BOOT_FLAG, 0x10
	.set MR1, 0
	.set MR2, 4
	.set MR3, 8
	.set MR4, 0xC
RESET_VECTOR:
	b RESET_HANDLER
UNDEF_VECTOR:
	b HALT1
SWI_VECTOR:
	b HALT1
IABORT_VECTOR:
	b HALT1
DABORT_VECTOR:
RESERVED_VECTOR:
	b HALT1
IRQ_VECTOR:
	b HALT1
FIQ_VECTOR:
	b HALT1
HALT1:	b HALT1
RESET_HANDLER:
	mov     r0, #CPSR_INITIAL
	msr	CPSR_c, r0	/* This is probably unnecessary */

/* I'm guessing this is initializing clock generator electronics for SPI */
	ldr	r0, =SPI_CGEN_BASE
	mov	r1, #0
	mov	r1, r1, lsl #3
	orr	r1, r1, #0
	str	r1, [r0]
	ldr	r1, [r0, #28]
	bic	r1, r1, #16
	str	r1, [r0, #28]
	mov	r1, #1
	str	r1, [r0, #8]

	ldr	r0, =MRBASE
	mov	r1, #0
	strh	r1, [r0, #MR1]
	strh	r1, [r0, #MR2]
	strh	r1, [r0, #MR3]
	strh	r1, [r0, #MR4]

	mov	sp, #STACK_BASE
	bl	SP_INIT
	mov	r0, #10
	bl	DELAY9
	bl	GET_MAC_ADDR
	bl	GET_WHOLE_NVRAM
	ldr	r0, =MRBASE
	ldr	r1, =MAC_ADDRESS_MIB
	strh	r1, [r0, #MR2]
	ldr	r1, =NVRAM_IMAGE
	strh	r1, [r0, #MR4]
	mov	r1, #MAC_BOOT_FLAG
	strh	r1, [r0, #MR3]
HALT2:	b HALT2
.func Get_Whole_NVRAM, GET_WHOLE_NVRAM
GET_WHOLE_NVRAM:
	stmdb	sp!, {lr}
	mov	r2, #0 /* 0th bytes of NVRAM */
	mov	r3, #NVRAM_LENGTH
	mov	r1, #0		/* not used in routine */
	ldr	r0, =NVRAM_IMAGE
	bl	NVRAM_XFER
	ldmia	sp!, {lr}
	bx	lr
.endfunc

.func Get_MAC_Addr, GET_MAC_ADDR
GET_MAC_ADDR:
	stmdb	sp!, {lr}
	mov	r2, #0x120	/* address of MAC Address within NVRAM */
	mov	r3, #MAC_ADDRESS_LENGTH
	mov	r1, #0		/* not used in routine */
	ldr	r0, =MAC_ADDRESS_MIB
	bl	NVRAM_XFER
	ldmia	sp!, {lr}
	bx	lr
.endfunc
.ltorg
.func Delay9, DELAY9
DELAY9:
	adds	r0, r0, r0, LSL #3   /* r0 = r0 * 9 */
DELAYLOOP:
	beq	DELAY9_done
	subs	r0, r0, #1
	b	DELAYLOOP
DELAY9_done:
	bx	lr
.endfunc

.func SP_Init, SP_INIT
SP_INIT:
	mov	r1, #SP_SWRST
	ldr	r0, =SP_BASE
	str	r1, [r0, #SP_CR] /* reset the SPI */
	mov	r1, #0
	str	r1, [r0, #SP_CR] /* release SPI from reset state */
	mov	r1, #SP_SPIEN
	str	r1, [r0, #SP_MR] /* set the SPI to MASTER mode*/
	str	r1, [r0, #SP_CR] /* enable the SPI */

/*  My guess would be this turns on the SPI clock */
	ldr	r3, =SPI_CGEN_BASE
	ldr	r1, [r3, #28]
	orr	r1, r1, #0x2000
	str	r1, [r3, #28]

	ldr	r1, =0x2000c01
	str	r1, [r0, #SP_CSR0]
	ldr	r1, =0x2000201
	str	r1, [r0, #SP_CSR1]
	str	r1, [r0, #SP_CSR2]
	str	r1, [r0, #SP_CSR3]
	ldr	r1, [r0, #SP_SR]
	ldr	r0, [r0, #SP_RDR]
	bx	lr
.endfunc
.func NVRAM_Init, NVRAM_INIT
NVRAM_INIT:
	ldr	r1, =SP_BASE
	ldr	r0, [r1, #SP_RDR]
	mov	r0, #NVRAM_CMD_RDSR
	str	r0, [r1, #SP_TDR]
SP_loop1:
	ldr	r0, [r1, #SP_SR]
	tst	r0, #SP_TDRE
	beq	SP_loop1

	mov	r0, #SPI_8CLOCKS
	str	r0, [r1, #SP_TDR]
SP_loop2:
	ldr	r0, [r1, #SP_SR]
	tst	r0, #SP_TDRE
	beq	SP_loop2

	ldr	r0, [r1, #SP_RDR]
SP_loop3:
	ldr	r0, [r1, #SP_SR]
	tst	r0, #SP_RDRF
	beq	SP_loop3

	ldr	r0, [r1, #SP_RDR]
	and	r0, r0, #255
	bx	lr
.endfunc

.func NVRAM_Xfer, NVRAM_XFER
	/* r0 = dest address */
	/* r1 = not used */
	/* r2 = src address within NVRAM */
	/* r3 = length */
NVRAM_XFER:
	stmdb	sp!, {r4, r5, lr}
	mov	r5, r0		/* save r0 (dest address) */
	mov	r4, r3		/* save r3 (length) */
	mov	r0, r2, LSR #5 /*  SPI memories put A8 in the command field */
	and	r0, r0, #8
	add	r0, r0, #NVRAM_CMD_READ
	ldr	r1, =NVRAM_SCRATCH
	strb	r0, [r1, #0]	/* save command in NVRAM_SCRATCH[0] */
	strb	r2, [r1, #1]    /* save low byte of source address in NVRAM_SCRATCH[1] */
_local1:
	bl	NVRAM_INIT
	tst	r0, #NVRAM_SR_RDY
	bne	_local1
	mov	r0, #20
	bl	DELAY9
	mov	r2, r4		/* length */
	mov	r1, r5		/* dest address */
	mov	r0, #2		/* bytes to transfer in command */
	bl	NVRAM_XFER2
	ldmia	sp!, {r4, r5, lr}
	bx	lr
.endfunc

.func NVRAM_Xfer2, NVRAM_XFER2
NVRAM_XFER2:
	stmdb	sp!, {r4, r5, r6, lr}
	ldr	r4, =SP_BASE
	mov	r3, #0
	cmp	r0, #0
	bls	_local2
	ldr	r5, =NVRAM_SCRATCH
_local4:
	ldrb	r6, [r5, r3]
	str	r6, [r4, #SP_TDR]
_local3:
	ldr	r6, [r4, #SP_SR]
	tst	r6, #SP_TDRE
	beq	_local3
	add	r3, r3, #1
	cmp	r3, r0 /* r0 is # of bytes to send out (command+addr) */
	blo	_local4
_local2:
	mov	r3, #SPI_8CLOCKS
	str	r3, [r4, #SP_TDR]
	ldr	r0, [r4, #SP_RDR]
_local5:
	ldr	r0, [r4, #SP_SR]
	tst	r0, #SP_RDRF
	beq	_local5
	ldr	r0, [r4, #SP_RDR] /* what's this byte?  It's the byte read while writing the TDR -- nonsense, because the NVRAM doesn't read and write at the same time */
	mov	r0, #0
	cmp	r2, #0  /* r2 is # of bytes to copy in */
	bls	_local6
_local7:
	ldr	r5, [r4, #SP_SR]
	tst	r5, #SP_TDRE
	beq	_local7
	str	r3, [r4, #SP_TDR]  /* r3 has SPI_8CLOCKS */
_local8:
	ldr	r5, [r4, #SP_SR]
	tst	r5, #SP_RDRF
	beq	_local8
	ldr	r5, [r4, #SP_RDR] /* but didn't we read this byte above? */
	strb	r5, [r1], #1 /* postindexed */
	add	r0, r0, #1
	cmp	r0, r2
	blo	_local7 /* since we don't send another address, the NVRAM must be capable of sequential reads */
_local6:
	mov	r0, #200
	bl	DELAY9
	ldmia	sp!, {r4, r5, r6, lr}
	bx	lr
#endif