/********************************************************
***
*** JK Microsystems   http://www.jkmicro.com
*** NJU6350 Clock Calendar Driver
*** Adam Yergovich 
*** 9-9-2005
*** 
*** A module that updates the system time as stored by the
*** battery backed calandar.  It should not disturb the 
*** HGPIO pin out in user space.  I don't claim to be a 
*** master of program writing by any means, but this seems 
*** to do the trick.  There's probably a more elegant way, 
*** but this method seems to make sense.  Feel free to change
*** it as you see fit for the JK Microsystems OmniFlashlite
*** SBC.
***
*** A few general notes about implementation:
*** 1)  The clock calandar has a range of 100 years.  This means
*** that when 2100 rolls around this driver will have to be 
*** modified if you wish to continue to use it. This modification
*** should be pretty straightforward, just change the way the dates
*** are interpretted by adding on a different "magic number" to the 
*** value you send off to the OS.
*** 2)  I didn't use the "Day of the week" field in the clock/
*** calandar chip as it was not necessary.  Feel free to use it.
*** 3)  I do some seemingly crazy manipulation of the data below.
*** This is not due to drug use (at least on my part).  The data 
*** in the clock calandar chip is stored in a very, very warped way.
*** this is best illustrated with the following example.  The value 
*** of October, the tenth month is stored as 0x10, not decimal 10.
*** I do not know why it was done this way, but this is why there are 
*** some extra hoops to jump through below.
*** 4)  I wrote this driver late in 2005.  Any date before 2004 will 
*** not work.  I don't know why you'd want to lie to the computer 
*** about the date (or at least the RTC), so this is not supported.
*** 5)  I don't do any error checking to see if anyone else has messed 
*** with the part of the sys call table that we're using.  Since nothing
*** could really be done about it anyway, i've left it out.
*** 6)  The clock calandar only has a resolution of seconds.  This means 
*** it is possible to lose a fraction of a second when this is module is 
*** loaded.  You could probably do some crazy reading and writing to synch
*** things to that extent, but it would be much more than most users need.
***
*** Compiled with the command: 
*** ./armie -c -O2 -DMODULE -D__KERNEL__ -isystem /root/linux-2/linux-2.4.21/include gpio_int_2.c
*** where "./armie" is a link to the gcc cross compiler
*** 
*** Much of this was derived from the wonderful example 
*** by Peter Salzman found here: 
*** http://www.faqs.org/docs/kernel/index.html
***
********************************************************/


/* The necessary header files */
/* Standard in kernel modules */

/* Make certain its pointing to the right files!!!!*/

#include "/root/linux-2/linux-2.4.21/include/linux/kernel.h"               
#include "/root/linux-2/linux-2.4.21/include/linux/module.h"               
#include "/root/linux-2/linux-2.4.21/include/linux/tty.h"      
#include "/root/linux-2/linux-2.4.21/include/linux/sched.h"

/* We want an interrupt */
#include "/root/linux-2/linux-2.4.21/include/linux/interrupt.h"
#include "/root/linux-2/linux-2.4.21/include/asm/io.h"

/* System call stuff */

#include "/root/linux-2/linux-2.4.21/include/linux/sched.h"
#include "/root/linux-2/linux-2.4.21/include/asm/uaccess.h"
#include "/root/linux-2/linux-2.4.21/include/asm/unistd.h"
//#include <time.h>

#ifndef KERNEL_VERSION
#define KERNEL_VERSION(a,b,c) ((a)*65536+(b)*256+(c))
#endif

#define DRIVER_AUTHOR "Adam Yergovich www.jkmicro.com"
#define DRIVER_DESC "NJU6350 Clock Calandar Driver"

MODULE_LICENSE("GPL");
MODULE_AUTHOR("DRIVER_AUTHOR");
MODULE_DESCRIPTION("DRIVER_DESC");


/* Global variables we will need */

static struct file_operations fops;
static int Major;
int status = 0;
void *statusAdr, *map_base, *virt_addr_dir, *virt_addr_inten, *virt_addr_data;
extern void *sys_call_table[];
struct timeval *tvs;
struct timezone *tzs;




asmlinkage int (*original_call)(const struct timeval* tv, const struct timezone* tz);	//store the original function call


/*
 Print String function from Peter Salzman's tutorial.  Allows for printing to console no matter its location.
*/


/***********
*** Peter Salzman's handy print_string function.
***********

void print_string(char *str)
{
   struct tty_struct *my_tty;
   my_tty = current->tty;           // The tty for the current task

   if (my_tty != NULL) { 

      
      (*(my_tty->driver).write)(
         my_tty,                 // The tty itself
         0,                      // We don't take the string from user space
         str,                    // String
         strlen(str));           // Length

     
      (*(my_tty->driver).write)(my_tty, 0, "\015\012", 2);
   }
}
*/




/************
*** Used to tick the clock/calandar clk pin (hgpio4)
************/

void rtctick(void){
unsigned long temp;

temp = *((unsigned long *) virt_addr_data);
temp |= 0x8;
*((unsigned long *) virt_addr_data) = temp;
__udelay(2000);
temp &= 0xF7;
*((unsigned long *) virt_addr_data) = temp;
__udelay(2000);

}






/************
*** Our tick has to be a bit different for reading sometimes.
************/


void readtick(void){
unsigned long temp;

temp = *((unsigned long *) virt_addr_data);
temp |= 0x8;
*((unsigned long *) virt_addr_data) = temp;
__udelay(2000);
temp &= 0xF7;
*((unsigned long *) virt_addr_data) = temp;
__udelay(200);
*((unsigned long *) virt_addr_dir) = 0x0F;
__udelay(1800);
}





/********************
*** Set the data bit (HGPIO3)
********************/

void set_data(void){
unsigned long temp;
temp = *((unsigned long *) virt_addr_data);
temp |= 0x10;
*((unsigned long *) virt_addr_data) = temp;

}




/*****************
*** Clear the data bit (HGPIO3)
*****************/

void clear_data(void){
unsigned long temp;
temp = *((unsigned long *) virt_addr_data);
temp &= 0xEF;
*((unsigned long *) virt_addr_data) = temp;

}





/*************
*** Setup the HGPIO port with the silly-walk described in the manual. Blame Cirrus.  
*************/

void rtc_prep(void){
unsigned long check;
unsigned long temp;


map_base = __ioremap(0x809300c0,32,0);			//map physical address
   *((unsigned long *) map_base) = 0xAA; 		//set to edge triggered

   map_base = __ioremap(0x80930080,32,0);
   check = *((unsigned long *) map_base);
   check |= 0x8000800;					//set bits 27 and 11 for the silly walk
   *((unsigned long *) map_base) = check;		
   
   virt_addr_dir  = __ioremap(0x80840044,32,0);
   temp = *((unsigned long *) virt_addr_dir);
   temp |= 0x1C;   	
   *((unsigned long *) virt_addr_dir) = temp;
   virt_addr_data = __ioremap(0x80840040,32,0);		//direction and data registers
   

}






/**********************
*** Takes a location to read from the chip (described on the datasheet). Returns its value.
**********************/

int rtc_read(int location){

   int checker[8];
   int mastah = 0;
   int i = 0;
   unsigned long temp;
   disable_irq();
   temp = *((unsigned long *) virt_addr_data);
   temp &= 0xFFFFFFE7;
   temp |= 0x4;
   *((unsigned long *) virt_addr_data) = temp;		//t0 setup
   clear_data();					//read operation
   __udelay(1000);					//setup time
   rtctick();						//read RW bit
   if(location & 0x1){				
   set_data();						
   __udelay(100);
   }
   else{
   clear_data();
   __udelay(100);
   }
   rtctick();						//read 1st bit
   if(location & 0x2){					
   set_data();
   __udelay(100);
   }
   else{
   clear_data();
   __udelay(100);
   }
   rtctick();						//read 2nd bit
   if(location & 0x4){
   set_data();
   __udelay(100);
   } 
   else{
   clear_data();
   __udelay(100);
   }
   readtick();						//read 3d bit (last control bit) and switch control mode
   checker[0] = *((unsigned long *) virt_addr_data);
   rtctick();
   checker[1] = *((unsigned long *) virt_addr_data);
   rtctick();
   checker[2] = *((unsigned long *) virt_addr_data); 
   rtctick();
   checker[3] = *((unsigned long *) virt_addr_data);
   rtctick();
   checker[4] = *((unsigned long *) virt_addr_data);
   rtctick();
   checker[5] = *((unsigned long *) virt_addr_data);
   rtctick();
   checker[6] = *((unsigned long *) virt_addr_data);
   rtctick();
   checker[7] = *((unsigned long *) virt_addr_data);
   __udelay(200);
   temp = *((unsigned long *) virt_addr_data);
   temp &= 0xFFFFFFE3;
   *((unsigned long *) virt_addr_data) = temp;
   enable_irq();
   __udelay(3000);
   for(i=0; i<8; i++){
      checker[i] &= 0x10;
      mastah >>= 1;
      if(checker[i])
        mastah |= 0x80;
   }
return mastah;
}




/*************************
*** Writes a date value to a location.  Note it must be in the silly hex value its looking for.
*************************/

void rtc_write(int location, unsigned long dat){

   int checker[8];
   int mastah = 0;
   int i = 0;
   unsigned long temp;
   disable_irq();
   //__udelay(5000);
   temp = *((unsigned long *) virt_addr_data);
   temp &= 0xFFFFFFE7;
   temp |= 0x4;
   *((unsigned long *) virt_addr_data) = temp;		//t0 setup
   set_data();						//write operation
   __udelay(1000);					//setup time
   rtctick();						//read RW bit
   if(location & 0x1){				
   set_data();						
   __udelay(100);
   }
   else{
   clear_data();
   __udelay(100);
   }
   rtctick();						//read 1st bit
   if(location & 0x2){					
   set_data();
   __udelay(100);
   }
   else{
   clear_data();
   __udelay(100);
   }
   rtctick();						//read 2nd bit
   if(location & 0x4){
   set_data();
   __udelay(100);
   } 
   else{
   clear_data();
   __udelay(100);
   }
   rtctick();						//read 3d bit (last control bit) and switch control mode

   if(dat & 0x1)
     set_data();
   else clear_data();
   __udelay(100);
   rtctick();						//Transmit 0 data bit

   if(dat & 0x2)
     set_data();
   else clear_data();
   __udelay(100);
   rtctick();						//Transmit 1 data bit

   if(dat & 0x4)
     set_data();
   else clear_data();
   __udelay(100);
   rtctick();						//Transmit 2 data bit

   if(dat & 0x8)
     set_data();
   else clear_data();
   __udelay(100);
   rtctick();						//Transmit 3 data bit

   if(dat & 0x10)
     set_data();
   else clear_data();
   __udelay(100);
   rtctick();						//Transmit 4 data bit

   if(dat & 0x20)
     set_data();
   else clear_data();
   __udelay(100);
   rtctick();						//Transmit 5 data bit

   if(dat & 0x40)
     set_data();
   else clear_data();
   __udelay(100);
   rtctick();						//Transmit 6 data bit

   if(dat & 0x80)
     set_data();
   else clear_data();
   __udelay(100);
   rtctick();						//Transmit 7 data bit

   
   __udelay(200);
   temp = *((unsigned long *) virt_addr_data);
   temp &= 0xFFFFFFE3;
   *((unsigned long *) virt_addr_data) = temp;
   enable_irq();
   __udelay(3000);
}





/***********************
*** Takes a hex value date stored in the chip and converts it to an int value needed for calculation
***********************/

int cuf(int change){
int temp = change;
change &= 0xF0;
if(change == 0x10){
   return ((temp & 0xF)+10);
}
else if(change == 0x20){
   return ((temp & 0xF)+20);
}
else if(change == 0x30){
   return ((temp & 0xF)+30);
}
else if(change == 0x40){
   return ((temp & 0xF)+40);
}
else if(change == 0x50){
   return ((temp & 0xF)+50);
}
else if(change == 0x60){
   return ((temp & 0xF) +60);
}
else if(change == 0x70){
   return ((temp & 0xF) +70);
}
else if(change == 0x80){
   return ((temp & 0xF) +80);
}
else if(change == 0x90){
   return ((temp & 0xF) +90);
}
else
   return temp;

}




/*********************
*** Inverse of cuf.  Takes the integer value and turns it into the silly hex value
*********************/

int fuc(int change){
if(change<10) 
  return change;
else if(change<20){
   change = change -10;
   change |= 0x10;
   return change;
}

else if(change<30){
   change = change -20;
   change |= 0x20;
   return change;
}

else if (change<40){
   change = change - 30;
   change |= 0x30;
   return change;
}
else if( change<50){
   change = change - 40;
   change |=0x40;
   return change;
}
else if(change<60){
   change = change - 50;
   change |=0x50;
   return change;

}
else if(change<70){
   change = change -60;
   change |= 0x60;
   return change;
}
else if(change<80){
   change = change -70;
   change |= 0x70;
   return change;
}
else if(change<90){
   change = change -80;
   change |= 0x80;
   return change;
}
else{
   change = change -90;
   change |= 0x90;
   return change;

}

}




/***********************
*** A function we slip into the sys_call table to update the clock calandar
*** Note that system time is updated before we update the clock/Calandar
***********************/

asmlinkage int our_settime(const struct timeval* tv, const struct timezone* tz)
{
  int sec, minute, hour, day, month, year;
  unsigned long temp, wrap;
  int ily = 0;
  int ly = 0;
  int y = 0;
  int val =0;


  val = original_call(tv, tz);	//Before we get knee-deep in crazy timing, lets update the system clock so its pretty darn accurate.


  temp = tv->tv_sec - 0x41d5e800;

  sec = tv->tv_sec % 60;
  minute = (tv->tv_sec /60) %60;
  hour = (tv->tv_sec/ (60*60)) %24;
  year = 0;
  while(1){			//loop to count leap years that have occured.
     wrap = temp;
     if(ily == 3){
        ly++;
        ily = 0;
        temp = temp - 0x1e28500;
     }
     else{
        ily++;
        y++;
        temp = temp - 0x1e13380;
     }
    if(temp > wrap) break;	//find out when we've counted all the years by the "rollover" 
				//effect of numbers stored in a computer.  This is one of those
				//things us low level programmers do to "cheat".
  }
  if(ily == 0)
  day = (wrap / (60*60*24)) % 366;
  else{
  day = (wrap / (60*60*24)) %365;
  }
  day++;
  year = y + ly;
  /**********
  *** Next bit of code figures out how many days into the (leap) year we are.  
  *** Again, not pretty, but its easy to understand and it works.
  **********/

  if(ily == 0){
     if(day<=31){
        month=0x1;
        day = fuc(day);
     }
     else if(day<=60){ 
        month =0x2;
        day = day-31;
        day = fuc(day);
        }
     else if(day<=91) {
        month=0x3;
        day=day-60;
        day = fuc(day);
        }
     else if(day<=121){
         month=0x4;
         day=day-91;
         day = fuc(day);
     }
     else if(day<=152){
          month=0x5;
          day=day-121;
          day = fuc(day);
     }
     else if(day<=182){
           month=0x6;
           day=day-152;
           day = fuc(day);
     }
     else if(day<=213){
            month=0x7;
            day=day-182;
            day=fuc(day);
     }
     else if(day<=244){
             month=0x8;
             day=day-213;
             day=fuc(day);
     }
     else if(day<=274){
              month=0x9;
              day=day-244;
              day=fuc(day);
     }
     else if(day<=305){
               month=0x10;
               day=day-274;
               day=fuc(day);
     }
     else if(day<=335){
                month=0x11;
                day=day-305;
                day=fuc(day);
     }
     else {
                month=0x12;
                day=day-335;
                day=fuc(day);
     }
                
  }

  if(ily != 0){
     if(day<=31){
        month=0x1;
        day = fuc(day);
     }
     else if(day<=59){ 
        month =0x2;
        day = day - 31;
        day = fuc(day);
        }
     else if(day<=90) {
        month=0x3;
        day=day-59;
        day = fuc(day);
        }
     else if(day<=120){
         month=0x4;
         day=day-90;
         day = fuc(day);
     }
     else if(day<=151){
          month=0x5;
          day=day-120;
          day = fuc(day);
     }
     else if(day<=181){
           month=0x6;
           day=day-151;
           day = fuc(day);
     }
     else if(day<=212){
            month=0x7;
            day=day-181;
            day=fuc(day);
     }
     else if(day<=243){
             month=0x8;
             day=day-212;
             day=fuc(day);
     }
     else if(day<=273){
              month=0x9;
              day=day-243;
              day=fuc(day);
     }
     else if(day<=304){
               month=0x10;
               day=day-273;
               day=fuc(day);
     }
     else if(day<=334){
                month=0x11;
                day=day-304;
                day=fuc(day);
     }
     else {
                month=0x12;
                day=day-334;
                day=fuc(day);
     }
                
  }
  /************************
  *** Convert these things into values the chip likes.
  *** Note that the chip has exceptionally bad taste.
  ************************/
  sec = fuc(sec);
  minute = fuc(minute);
  hour = fuc(hour);
  year = fuc(year);
  //printk("sec %x, minute %x, hour %x, day %x, month %x, year %x\n", sec, minute, hour, day, month, year);

   rtc_prep();

   rtc_write(0x7, sec);				//write seconds

   rtc_prep();
   
   rtc_write(0x6, minute);			//write minutes

   rtc_prep();

   rtc_write(0x5, hour);			//write hours

   rtc_prep();

   rtc_write(0x3, day);				//write date

   rtc_prep();

   rtc_write(0x2, month);			//write month

   rtc_prep();

   rtc_write(0x1, year);			//write year
  //printk("year %d, y %d, ly %d \n", year, y, ly);

  //update the clockcalendar chip
  return val;  //Return that original value from the function call
}








/* Initialize the module - and slip our function into the system call table. */
int init_module()
{ 
   unsigned long tdisplay = 0;
   unsigned long second, minute, hour, day, month, year;
   int s, mi, h, d, mo, ye;
   unsigned long finder = 0;
   //struct timeval* tvs;
   //struct timezone* tzs;
   struct timeval solid;
   struct timezone zolid;
   int y=0;
   int ly=0;
   int retvalue=0;
   /* Major = register_chrdev(0, "Text_Out_Device", &fops); 

    if (Major == -1) {
        print_string(" Dynamic Major number allocation failed\n");
        return Major;
    }*/
   __udelay(5000);
   rtc_prep();


   /****************
   *** Read in the time
   ****************/

   second = rtc_read(0x7);
   second >>= 1;
   s = cuf((int)second);
   //printk(" \n seconds: %x \n", second);
   

   rtc_prep();


   minute = rtc_read(0x6);
   minute >>= 1;
   //printk(" minutes: %x \n", minute);
   
   rtc_prep();

   hour  = rtc_read(0x5);
   hour >>= 1;
   //printk(" hour: %x \n", hour);
   
   rtc_prep();

   day = rtc_read(0x3);
   day >>= 1;
   //printk(" day: %x \n", day);

   rtc_prep();
   
   month = rtc_read(0x2);
   month >>= 1;
   //printk(" month: %x \n", month);

   rtc_prep();

   year = rtc_read(0x1);
   year >>= 1;
   //printk(" year: %x \n", year);

   /*********************
   *** Convert the time
   *********************/

   second = cuf(second);
   minute = cuf(minute);
   hour = cuf(hour);
   day = cuf(day);
   year = cuf(year);
     
  //printk(" \n seconds: %d \n", second);
  //printk(" \n minute: %d \n", minute);
  //printk(" \n hour: %d \n", hour);
  //printk(" \n day: %d \n", day);
  //printk(" \n year: %d \n", year);

  
  original_call = sys_call_table[25];  //Store the original function call.


  /****************
  *** Next bit we figure out how many seconds have gone by since the epoch
  ****************/

  ly=year/4;
  y=year-ly;
  if(month==0x1){
	day=day;
  }
  else if(month==0x2){
     day = day + 31;
  }
  else if(month==0x3){
     day = day +59;
  }
  else if(month==0x4){
     day = day + 90;              
  }
  else if(month==0x5){
     day = day + 120;
  }
  else if(month==0x6){
     day = day + 151;
  }
  else if(month==0x7){
     day = day + 181;
  }
  else if(month==0x8){
     day = day + 212;
  }
  else if(month==0x9){
     day = day + 243;
  }
  else if(month==0x10){
     day = day + 273;
  }
  else if(month==0x11){
     day = day + 304;
  }
  else if(month==0x12){
     day = day + 334;
  }
  else{
     printk("Error! Unexpected month \n");
  }
  if(year%4 == 0) day++;

  
  tvs=&solid;
  tzs=&zolid;
  
  /************************
  ***  Next we add up the seconds 0x3FF36300. is Jan 01 00:00:00 2004
  ***  The second number is my tweaking value for sending it to set_timeofday.
  ***  I did some experimentation with this until i got a good value.
  *************************/

  tvs->tv_sec = 0x3FF36300 + 1075;
  //printk(" %x \n",tvs->tv_sec);
  tvs->tv_sec = tvs->tv_sec + second + (minute * 60) + (hour *3600) + (day * 0x15180) + (0x1E28500 * ly) + (0x1E13380 * y);
  
  

  //tvs = &solid;
  //printk("tvs:%x \n",tvs->tv_sec);

  do_settimeofday(tvs);			//set the date in the OS
  //printk("%d \n", retvalue);

  sys_call_table[25] = our_settime;	//Slip our function into the system call table.

 
   return(0);
}

/* Cleanup.  Note there is no check to see if anyone else has messed with the functioncall. */
void cleanup_module()
{
   printk("Removing Module \n");
   //unregister_chrdev(Major, "OurDevice");  //make sure to unregister the device
   sys_call_table[25] = original_call;		//make sure to put the original function call.  

}
