/*
** linux/drivers/at91/rtcex/at91_rtcex.c
** DS1307 time clock
**
** Copyright (C) 2004 Hyesco Technology Co.,Ltd
**
** Author: Zheng Geng <
[email protected]>
**
** History:
**
** 2004.10 Zheng Geng <
[email protected]>
** Original version
** 2007.6 Chenggp <
[email protected]>
** rtc driver for 2.6.12
*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/sched.h>
#include <linux/fs.h>
#include <linux/mm.h>
#include <linux/poll.h>
#include <linux/slab.h>
#include <linux/ioport.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/fcntl.h>
#include <linux/rtc.h>
#include <linux/miscdevice.h>
#include <linux/string.h>
#include <linux/proc_fs.h>
#include <asm/bitops.h>
#include <asm/arch/AT91RM9200_SYS.h>
#include <asm/arch/AT91RM9200_TWI.h>
#include <asm/arch/at91rm9200dk.h>
#define AT91C_DS1307_I2C_ADDRESS (0x68<<16)
#define AT91C_DS1307_READ_OK 0
#define AT91C_DS1307_WRITE_OK 0
#define AT91C_TWI_CLOCK 100000
#define BCD2BIN(val) (((val)&15) + ((val)>>4)*10)
#define BIN2BCD(val) ((((val)/10)<<4) + (val)%10)
#define TWELVE_HOUR_MODE(n) (((n)>>6)&1)
#define HOURS_AP(n) (((n)>>5)&1)
#define HOURS_12(n) BIN2BCD((n)&0x1F)
#define HOURS_24(n) BIN2BCD((n)&0x3F)
static int AT91F_TWI_Write(char,char *,char);
static int AT91F_TWI_Read(char,char *,char);
static void AT91F_TWI_Init(void);
static void get_rtc_time(struct rtc_time *rtc_tm);
static int set_rtc_time(struct rtc_time *rtc_tm);
spinlock_t at91_rtc_lock;
char rtc_name[]="AT91_RTCEX";
//static unsigned int major =250;
/*
----------------------------------------------------------------------------
\fn AT91F_TWI_Write
\brief Write n bytes to a slave device
----------------------------------------------------------------------------
*/
int AT91F_TWI_Write(char address, char *data2send, char size)
{
unsigned int status;
AT91PS_TWI twi = (AT91PS_TWI) AT91C_VA_BASE_TWI;
// Set the TWI Master Mode Register
twi->TWI_MMR = ( AT91C_DS1307_I2C_ADDRESS | AT91C_TWI_IADRSZ_1_BYTE ) & ~AT91C_TWI_MREAD;
// Set TWI Internal Address Register
twi->TWI_IADR = address;
status = twi->TWI_SR;
twi->TWI_THR = *(data2send++);
twi->TWI_CR = AT91C_TWI_START;
while (size-- >1){
// Wait THR Holding register to be empty
while (!(twi->TWI_SR & AT91C_TWI_TXRDY));
// Send first byte
twi->TWI_THR = *(data2send++);
}
twi->TWI_CR = AT91C_TWI_STOP;
status = twi->TWI_SR;
// Wait transfer is finished
while (!(twi->TWI_SR & AT91C_TWI_TXCOMP));
return AT91C_DS1307_WRITE_OK;
}
//*----------------------------------------------------------------------------
//* \fn AT91F_TWI_Read
//* \brief Read n bytes from a slave device
//*----------------------------------------------------------------------------
int AT91F_TWI_Read(char address, char *data, char size)
{
unsigned int status;
AT91PS_TWI twi = (AT91PS_TWI) AT91C_VA_BASE_TWI;
// Set the TWI Master Mode Register
twi->TWI_MMR = AT91C_DS1307_I2C_ADDRESS | AT91C_TWI_IADRSZ_1_BYTE | AT91C_TWI_MREAD;
// Set TWI Internal Address Register
twi->TWI_IADR = address;
// Start transfer
twi->TWI_CR = AT91C_TWI_START;
status = twi->TWI_SR;
while (size-- >1){
// Wait RHR Holding register is full
while (!(twi->TWI_SR & AT91C_TWI_RXRDY));
// Read byte
*(data++) = twi->TWI_RHR;
}
twi->TWI_CR = AT91C_TWI_STOP;
status = twi->TWI_SR;
// Wait transfer is finished
while (!(twi->TWI_SR & AT91C_TWI_TXCOMP));
// Read last byte
*data = twi->TWI_RHR;
return AT91C_DS1307_READ_OK;
}
void AT91F_SetTwiClock(AT91PS_TWI pTwi)
{
int sclock;
/* Here, CKDIV = 1 and CHDIV=CLDIV ==> CLDIV = CHDIV = 1/4*((Fmclk/FTWI) -6)*/
sclock = (10*AT91C_MASTER_CLOCK /AT91C_TWI_CLOCK);
if (sclock % 10 >= 5)
sclock = (sclock /10) - 5;
else
sclock = (sclock /10)- 6;
sclock = (sclock + (4 - sclock %4)) >> 2; // div 4
pTwi->TWI_CWGR = 0x00010000 | sclock | (sclock << 8);
}
//*----------------------------------------------------------------------------
//* \fn AT91F_TWI_Init
//* \brief Init TWI Controller
//*----------------------------------------------------------------------------
void AT91F_TWI_Init()
{
AT91PS_TWI twi = (AT91PS_TWI) AT91C_VA_BASE_TWI;
//initiate TWI
AT91_SYS->PIOA_ASR = 0x06000000;//Assigns the I/O line to the Peripheral A function(TWD,TWCK)
AT91_SYS->PIOA_PDR = 0x06000000;//enables peripheral control of the pin
AT91_SYS->PIOA_MDER = 0x06000000;//Enables Multi Drive on the I/O line.(Define TWD and TWCK as open-drain)
AT91_SYS->PMC_PCER = 0x00001000;
twi->TWI_IDR = 0xffffffff;//Disable interrupts
twi->TWI_CR = 0x24;//the master data transfer is enabled
//* Here, CKDIV = 1 and CHDIV=CLDIV ==> CLDIV = CHDIV = 1/4*((Fmclk/FTWI) -6)
AT91F_SetTwiClock((AT91PS_TWI) AT91C_VA_BASE_TWI);
twi->TWI_CWGR = 0x00019595; //Set TWI Clock Waveform Generator Register
}
static int rtc_open(struct inode *inode, struct file *file)
{
return 0;
}
static int rtc_release(struct inode *inode, struct file *file)
{
return 0;
}
static int rtc_ioctl(struct inode *inode, struct file *file,
unsigned int cmd, unsigned long arg)
{
struct rtc_time tm;
int ret = 0;
spin_lock_irq(&at91_rtc_lock);
switch (cmd) {
case RTC_AIE_OFF: /* alarm off */
break;
case RTC_AIE_ON: /* alarm on */
break;
case RTC_UIE_OFF: /* update off */
break;
case RTC_UIE_ON: /* update on */
break;
case RTC_PIE_OFF: /* periodic off */
break;
case RTC_PIE_ON: /* periodic on */
break;
case RTC_ALM_READ: /* read alarm */
break;
case RTC_ALM_SET: /* set alarm */
break;
case RTC_RD_TIME: /* read time */
memset(&tm, 0, sizeof(struct rtc_time));
get_rtc_time(&tm);
ret = copy_to_user((void *) arg, &tm, sizeof(tm)) ? -EFAULT : 0;
break;
case RTC_SET_TIME: /* set time */
if (copy_from_user(&tm, (struct rtc_time *) arg, sizeof(tm)))
ret = -EFAULT;
else
{
if (set_rtc_time(&tm)!=0)
ret = -EFAULT;
}
break;
case RTC_IRQP_READ: /* read periodic alarm frequency */
break;
case RTC_IRQP_SET: /* set periodic alarm frequency */
break;
case RTC_EPOCH_READ: /* read epoch */
break;
default:
ret = -EINVAL;
break;
}
spin_unlock_irq(&at91_rtc_lock);
return ret;
}
static void get_rtc_time(struct rtc_time *rtc_tm)
{
char data[7];
//AT91F_TWI_Write(0x3F,data,1);
AT91F_TWI_Read(0x0,(char*)&data,7);
rtc_tm->tm_year = 2000+BCD2BIN(data[6]);
rtc_tm->tm_mon = BCD2BIN(data[5]);//((data[5]>>4)&0x01)*10+data[5]&15;
rtc_tm->tm_mday =BCD2BIN(data[4]);//((data[4]>>4)&0x03)*10+data[4]&15;
rtc_tm->tm_wday =BCD2BIN(data[3]);//data[3]&0x07;
rtc_tm->tm_hour = BCD2BIN(data[2]);//((data[2]>>4)&0x03)*10+data[2]&15;
/*if ( TWELVE_HOUR_MODE(data[2]) )
{
printk("HOURS_12\n");
rtc_tm->tm_hour = HOURS_12(data[2]);
if (HOURS_AP(data[2])) // PM
{
rtc_tm->tm_hour += 12;
}
}
else //24-hour-mode
{
printk("HOURS_24\n");
rtc_tm->tm_hour = HOURS_24(data[2]);
}*/
rtc_tm->tm_min =BCD2BIN(data[1]);//((data[1]>>4)&0x07)*10+data[1]&15;
rtc_tm->tm_sec = BCD2BIN(data[0]);//((data[0]>>4)&0x07)*10+data[0]&15;
}
static int set_rtc_time(struct rtc_time *rtc_tm)
{
char data[8];
//check value
if ( (rtc_tm->tm_mon<1) || (rtc_tm->tm_mon>12)
|| (rtc_tm->tm_mday<1) || (rtc_tm->tm_mday>31)
|| (rtc_tm->tm_wday<1) || (rtc_tm->tm_wday>7)
|| (rtc_tm->tm_hour<0) || (rtc_tm->tm_hour>23)
|| (rtc_tm->tm_min<0) || (rtc_tm->tm_min>59)
|| (rtc_tm->tm_sec<0) || (rtc_tm->tm_sec>59) )
return -EINVAL;
//control
data[7]=0x10;
data[6]=BIN2BCD(rtc_tm->tm_year -2000);
data[5]=BIN2BCD(rtc_tm->tm_mon);
data[4]=BIN2BCD(rtc_tm->tm_mday);
data[3]=BIN2BCD(rtc_tm->tm_wday);
data[2]=BIN2BCD(rtc_tm->tm_hour);
data[