www.pudn.com > at91rm9200-rtc8025.rar > nx8025.c


/*
 * nx8025.c
 *
 * Device driver for EPSON's Real Time Controller NX-8025.
 *
 * Copyright (C) 2002 Fudan Software Inc.
 *
 * This program 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.
 *
 */

#include 

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#include "nx8025.h"

#ifdef CONFIG_PM
#include 
#include 
static struct pm_dev *pmdev;
#endif

#define DEBUG 0

#if DEBUG
static unsigned int rtc_debug = DEBUG;
#else
#define rtc_debug 0	/* gcc will remove all the debug code for us */
#endif

static unsigned short slave_address = NX8025_I2C_SLAVE_ADDR;

struct i2c_driver nx8025_driver;
struct i2c_client *nx8025_i2c_client = 0;

static unsigned short ignore[] = { I2C_CLIENT_END };
static unsigned short normal_addr[] = { NX8025_I2C_SLAVE_ADDR, I2C_CLIENT_END };

static struct i2c_client_address_data addr_data = {
	normal_i2c:		normal_addr,
	probe:			ignore,
	ignore: 		ignore,
};

static int nx8025_rtc_ioctl( struct inode *, struct file *, unsigned int, unsigned long);
static int nx8025_rtc_open(struct inode *inode, struct file *file);
static int nx8025_rtc_release(struct inode *inode, struct file *file);

static struct file_operations rtc_fops = {
	owner:		THIS_MODULE,
	ioctl:		nx8025_rtc_ioctl,
	open:		nx8025_rtc_open,
	release:	nx8025_rtc_release,
};

static struct miscdevice nx8025_rtc_miscdev = {
	EFI_RTC_MINOR,
	"rtc",
	&rtc_fops
};

/*
 * Client data (each client gets its own)
 */
struct nx8025_data {
	struct i2c_client client;
       // u16 data;
};

static int nx8025_probe(struct i2c_adapter *adap);
static int nx8025_detach(struct i2c_client *client);
static int nx8025_command(struct i2c_client *client, unsigned int cmd, void *arg);

struct i2c_driver nx8025_driver = {
        .driver={
           .name= "NX8025",
	},
	.attach_adapter= nx8025_probe,
	.detach_client=	nx8025_detach,
	.command=	nx8025_command,
};

static spinlock_t nx8025_rtc_lock = SPIN_LOCK_UNLOCKED;

#define DAT(x) ((unsigned int)((x)->data)) /* keep the control register info */

static int
nx8025_readram( char *buf, int len)
{
	unsigned long	flags;
	unsigned char ad[1] = { 0 };
	int ret;
	struct i2c_msg msgs[2] = {
		{ nx8025_i2c_client->addr  , 0,        1, ad  },
		{ nx8025_i2c_client->addr  , I2C_M_RD, len, buf } };

	spin_lock_irqsave(&nx8025_rtc_lock, flags);
	ret = i2c_transfer(nx8025_i2c_client->adapter, msgs, 2);
	spin_unlock_irqrestore(&nx8025_rtc_lock,flags);

	return ret;
}


static int
nx8025_attach(struct i2c_adapter *adapter, int address, int kind)
{
        struct i2c_client *new_client;
	struct nx8025_data *data;
	int err = 0;
	const char *name = "";

	if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA |
				     I2C_FUNC_I2C))
		return err;

	if (!(data = kzalloc(sizeof(struct nx8025_data), GFP_KERNEL))) {
		err = -ENOMEM;
		return err;
	}

	/* The common I2C client data is placed right before the
	 * nx8025-specific data. 
	 */
	new_client = &data->client;
	i2c_set_clientdata(new_client, data);
	new_client->addr = address;
	new_client->adapter = adapter;
	new_client->driver = &nx8025_driver;
	new_client->flags = 0;


//	ret = i2c_transfer(c->adapter, msgs, 2);

//	if ( ret == 2 )
//	{
//		DAT(c) = buf[0];
//	}
//	else
//		printk ("nx8025_attach(): i2c_transfer() returned %d.\n",ret);

	nx8025_i2c_client = new_client;

	return i2c_attach_client(new_client);
}

static int
nx8025_probe(struct i2c_adapter *adap)
{
	return i2c_probe(adap, &addr_data, nx8025_attach);
}

static int
nx8025_detach(struct i2c_client *client)
{
	i2c_detach_client(client);

	return 0;
}

static void
nx8025_convert_to_time( struct rtc_time *dt, char *buf)
{
	if(rtc_debug)
		printk("buf: %x, %x, %x, %x, %x, %x\n", buf[0], buf[1], buf[2], buf[4], buf[5], buf[6]);
	dt->tm_sec = BCD_TO_BIN(buf[0]);
	dt->tm_min = BCD_TO_BIN(buf[1]);
        
        //12-hour-mode
	
	if (HOURS_AP(buf[2])) /* PM */
	 {
		dt->tm_hour = HOURS_12(buf[2]);
                if(dt->tm_hour<12)
                  	dt->tm_hour += 12; //PM11 -> AM12
	 
	 }
	else /* AM */
	{
		dt->tm_hour = HOURS_12(buf[2]);
		if(dt->tm_hour==12)
                	dt->tm_hour=0; //AM 11 -> PM 12
	}

	dt->tm_mday = BCD_TO_BIN(buf[4]);
	/* dt->tm_mon is one-based */
	dt->tm_mon = BCD_TO_BIN(buf[5]);
	/* year is 2000 + dt->tm_year */
	dt->tm_year = BCD_TO_BIN(buf[6]) + 2000;

	if( rtc_debug > 2)
	{
		printk("nx8025_get_datetime: year = %d\n", dt->tm_year);
		printk("nx8025_get_datetime: mon  = %d\n", dt->tm_mon);
		printk("nx8025_get_datetime: mday = %d\n", dt->tm_mday);
		printk("nx8025_get_datetime: hour = %d\n", dt->tm_hour);
		printk("nx8025_get_datetime: min  = %d\n", dt->tm_min);
		printk("nx8025_get_datetime: sec  = %d\n", dt->tm_sec);
	}
}

static int
nx8025_get_datetime(struct i2c_client *client, struct rtc_time *dt)
{
	unsigned char buf[7], addr[1] = { 0 };
	struct i2c_msg msgs[2] = {
		{ client->addr, 0,	  1, addr },
		{ client->addr, I2C_M_RD, 7, buf  }
	};
	int ret = -EIO;

	memset(buf, 0, sizeof(buf));

	ret = i2c_transfer(client->adapter, msgs, 2);

	if (ret == 2) {
		nx8025_convert_to_time( dt, buf);
		dt->tm_year -=1900;
		ret = 0;
	}
	else
		printk("nx8025_get_datetime(), i2c_transfer() returned %d\n",ret);

	return ret;
}

static int
nx8025_set_datetime(struct i2c_client *client, struct rtc_time *dt, int datetoo)
{
	unsigned char buf[8];
	int ret, len = 4;

	if( rtc_debug > 2)
	{
		printk("nx8025_set_datetime: tm_year = %d\n", dt->tm_year);
		printk("nx8025_set_datetime: tm_mon  = %d\n", dt->tm_mon);
		printk("nx8025_set_datetime: tm_mday = %d\n", dt->tm_mday);
		printk("nx8025_set_datetime: tm_hour = %d\n", dt->tm_hour);
		printk("nx8025_set_datetime: tm_min  = %d\n", dt->tm_min);
		printk("nx8025_set_datetime: tm_sec  = %d\n", dt->tm_sec);
	}

	buf[0] = 0;	/* register address on nx8025 */
	buf[1] = (BIN_TO_BCD(dt->tm_sec));
	buf[2] = (BIN_TO_BCD(dt->tm_min));
        //12-hour-mode
        if(dt->tm_hour<12 && dt->tm_hour>0)
	   buf[3] = (BIN_TO_BCD(dt->tm_hour));
        else if(dt->tm_hour==0)
           buf[3] = (BIN_TO_BCD(12));
        else if(dt->tm_hour>12 && dt->tm_hour<=23)
           buf[3] = (BIN_TO_BCD(dt->tm_hour + 8));
	else if(dt->tm_hour == 12)
		buf[3] = (BIN_TO_BCD(32));
        else if(dt->tm_hour<0)
           return -1;
	if (datetoo) {
		len = 8;
		/* we skip buf[4] as we don't use day-of-week. */
		buf[5] = (BIN_TO_BCD(dt->tm_mday));
		buf[6] = (BIN_TO_BCD(dt->tm_mon ));
		/* The year only ranges from 0-99, we are being passed an offset from 1900,
		 * and the chip calulates leap years based on 2000, thus we adjust by 100.
		 */
		buf[7] = (BIN_TO_BCD(dt->tm_year - 2000));
	}
	ret = i2c_master_send(client, (char *)buf, len);
	if (ret == len)
		ret = 0;
	else
		printk("nx8025_set_datetime(), i2c_master_send() returned %d\n",ret);


	return ret;
}

static int
nx8025_get_ctrl(struct i2c_client *client, unsigned char *ctrl)
{
	//*ctrl = DAT(client);

	return 0;
}

static int
nx8025_set_ctrl(struct i2c_client *client, unsigned char *cinfo)
{
	unsigned char buf[2];
	int ret;


	buf[0] = 0x0F;	/* control register address on nx8025 */
	buf[1] = *cinfo;
	/* save the control reg info in the client data field so that get_ctrl
	 * function doesn't have to do an I2C transfer to get it.
	 */
	//DAT(client) = buf[1];

	ret = i2c_master_send(client, (char *)buf, 2);

	return ret;
}

static int
nx8025_read_mem(struct i2c_client *client, struct rtc_mem *mem)
{
	unsigned char addr[1];
	struct i2c_msg msgs[2] = {
		{ client->addr, 0,	  1, addr },
		{ client->addr, 0,  mem->nr, mem->data }
	};

	if ( (mem->loc < NX8025_REG_ADDR_START) ||
	     ((mem->loc + mem->nr -1) > NX8025_REG_ADDR_END) )
		return -EINVAL;

	addr[0] = mem->loc;

	return i2c_transfer(client->adapter, msgs, 2) == 2 ? 0 : -EIO;
}

static int
nx8025_write_mem(struct i2c_client *client, struct rtc_mem *mem)
{
	unsigned char addr[1];
	struct i2c_msg msgs[2] = {
		{ client->addr, 0, 1, addr },
		{ client->addr, 0, mem->nr, mem->data }
	};

	if ( (mem->loc < NX8025_REG_ADDR_START) ||
	     ((mem->loc + mem->nr -1) > NX8025_REG_ADDR_END) )
		return -EINVAL;

	addr[0] = mem->loc;

	return i2c_transfer(client->adapter, msgs, 2) == 2 ? 0 : -EIO;
}

static int
nx8025_command(struct i2c_client *client, unsigned int cmd, void *arg)
{
	switch (cmd) {
	case NX8025_GETDATETIME:
		return nx8025_get_datetime(client, arg);

	case NX8025_SETTIME:
		return nx8025_set_datetime(client, arg, 0);

	case NX8025_SETDATETIME:
		return nx8025_set_datetime(client, arg, 1);

	case NX8025_GETCTRL:
		return nx8025_get_ctrl(client, arg);

	case NX8025_SETCTRL:
		return nx8025_set_ctrl(client, arg);

	case NX8025_MEM_READ:
		return nx8025_read_mem(client, arg);

	case NX8025_MEM_WRITE:
		return nx8025_write_mem(client, arg);

	default:
		return -EINVAL;
	}
}

static int
nx8025_rtc_open(struct inode *inode, struct file *file)
{
	return 0;
}

static int
nx8025_rtc_release(struct inode *inode, struct file *file)
{
	return 0;
}

static int
nx8025_rtc_ioctl( struct inode *inode, struct file *file,
		unsigned int cmd, unsigned long arg)
{
	unsigned long	flags;
	struct rtc_time wtime;
	int status = 0;

	switch (cmd) {
		default:
		case RTC_UIE_ON:
		case RTC_UIE_OFF:
		case RTC_PIE_ON:
		case RTC_PIE_OFF:
		case RTC_AIE_ON:
		case RTC_AIE_OFF:
		case RTC_ALM_SET:
		case RTC_ALM_READ:
		case RTC_IRQP_READ:
		case RTC_IRQP_SET:
		case RTC_EPOCH_READ:
		case RTC_EPOCH_SET:
		case RTC_WKALM_SET:
		case RTC_WKALM_RD:
			status = -EINVAL;
			break;

		case RTC_RD_TIME:
			spin_lock_irqsave(&nx8025_rtc_lock, flags);
			nx8025_command( nx8025_i2c_client, NX8025_GETDATETIME, &wtime);
			spin_unlock_irqrestore(&nx8025_rtc_lock,flags);

			if( copy_to_user((void *)arg, &wtime, sizeof (struct rtc_time)))
				status = -EFAULT;
			break;

		case RTC_SET_TIME:
			if (!capable(CAP_SYS_TIME))
			{
				status = -EACCES;
				break;
			}

			if (copy_from_user(&wtime, (struct rtc_time *)arg, sizeof(struct rtc_time)) )
			{
				status = -EFAULT;
				break;
			}

			spin_lock_irqsave(&nx8025_rtc_lock, flags);
			nx8025_command( nx8025_i2c_client, NX8025_SETDATETIME, &wtime);
			spin_unlock_irqrestore(&nx8025_rtc_lock,flags);
			break;
	}

	return status;
}

static char *
nx8025_mon2str( unsigned int mon)
{
	char *mon2str[13] = {
	  "NULL","Jan", "Feb", "Mar", "Apr", "May", "Jun",
	  "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
	};
	if( mon > 12) return "error";
	else return mon2str[ mon];
}

static int rtc8025_read_proc(char *page, char **start, off_t off, int count, int *eof, void *data)
{
//#define CHECK(ctrl,bit) ((ctrl & bit) ? "yes" : "no")
	unsigned char ram[NX8025_REG_SIZE];
        int ret;
	char *p = page;

	ret = nx8025_readram( ram,NX8025_REG_SIZE );
	if( ret > 0)
	{
		struct rtc_time dt;
                printk("access nx8025 sucessfully!\n");

		p += sprintf(p, "NX-8025 (Real Time Clock)\n");

		nx8025_convert_to_time( &dt, ram);
		p += sprintf(p, "Date/Time	     : %02d-%s-%04d %02d:%02d:%02d\n",
			dt.tm_mday, nx8025_mon2str(dt.tm_mon), dt.tm_year,
			dt.tm_hour, dt.tm_min, dt.tm_sec);

		//p += sprintf(p, "24h mode	     : %s\n", CHECK(ram[2],0x40));
	}
	else
	{
                printk("access nx8025 failed\n");
		p += sprintf(p, "Failed to read RTC memory!\n");
	}

	
	return p - page;
}

#ifdef CONFIG_PM
static struct timespec nx8025_rtc_delta;

static int nx8025_suspend()
{
	struct rtc_time tm;
	struct timespec time;

	time.tv_nsec = 0;

	/* calculate time delta for suspend */
	
	nx8025_command( nx8025_i2c_client, NX8025_GETDATETIME, &tm);
	
	tm.tm_year -=1;
	rtc_tm_to_time(&tm, &time.tv_sec);
	save_time_delta(&nx8025_rtc_delta, &time);

	if(rtc_debug)
		printk("%s(): %4d-%02d-%02d %02d:%02d:%02d\n", __FUNCTION__,
		1900 + tm.tm_year, tm.tm_mon, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec);

	return 0;
}

static void nx8025_resume()
{
	struct rtc_time tm;
	struct timespec time;

	time.tv_nsec = 0;
	
	nx8025_command( nx8025_i2c_client, NX8025_GETDATETIME, &tm);
	
	tm.tm_mon -=1;
	rtc_tm_to_time(&tm, &time.tv_sec);
	restore_time_delta(&nx8025_rtc_delta, &time);

	if(rtc_debug)
		printk("%s(): %4d-%02d-%02d %02d:%02d:%02d\n", __FUNCTION__,
		1900 + tm.tm_year, tm.tm_mon, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec);
}
static int nx8025_pm_callback(struct pm_dev *dev, pm_request_t request, void *data)
{
       switch(request){
           case PM_SUSPEND:
                break;
           case PM_RESUME:
                nx8025_resume();
                break;
           default:
                break;
       }
       return 0;

}
#else
#define nx8025_resume NULL
#endif
static __init int nx8025_init(void)
{
	int retval=0;

	if( slave_address != 0xffff)
	{
		normal_addr[0] = slave_address;
	}

	if( normal_addr[0] == 0xffff)
	{
		printk(KERN_ERR"I2C: Invalid slave address for NX-8025 RTC (%#x)\n",
			normal_addr[0]);
		return -EINVAL;
	}

	retval = i2c_add_driver(&nx8025_driver);

	if (retval==0)
	{
		misc_register (&nx8025_rtc_miscdev);
		create_proc_read_entry (PROC_NX8025_NAME, 0, 0, rtc8025_read_proc, NULL);
#ifdef CONFIG_PM
       			pmdev=pm_register(PM_SYS_DEV,PM_SYS_UNKNOWN,nx8025_pm_callback);
			if(pmdev==NULL)
			{
				printk("nx8025 pm register error!\n");
				misc_deregister(&nx8025_rtc_miscdev);
				return -1;
			}
		
#endif
		printk("I2C: NX-8025 RTC driver successfully loaded\n");

		//if( rtc_debug) nx8025_dumpram();
	}
	return retval;
}

static __exit void nx8025_exit(void)
{
	remove_proc_entry (PROC_NX8025_NAME, NULL);
	misc_deregister(&nx8025_rtc_miscdev);
#ifdef CONFIG_PM
        pm_unregister(pmdev);
#endif
	i2c_del_driver(&nx8025_driver);
}

module_init(nx8025_init);
module_exit(nx8025_exit);

MODULE_PARM_DESC (slave_address, "I2C slave address for NX-8025 RTC.");

MODULE_AUTHOR ("SACRROT");
MODULE_LICENSE("GPL");