📄 ds1307.c
字号:
/*
* Brian Kuschak <bkuschak@yahoo.com>
*
* I2C driver for Dallas (Maxim) DS1307 Real Time Clock.
* Some code 'borrowed' from the DS1302 driver.
*/
#include <linux/config.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/i2c.h>
#include <linux/i2c-algo-ppc405.h>
#include <linux/i2c-ppc405adap.h>
#include <asm/uaccess.h>
#include <asm/rtc.h>
#include <linux/proc_fs.h>
#define RTC_MAJOR_NR 121 /* local major, change later */
static const char ds1307_name[] = "ds1307";
/* The I2C address we scan looking for DS1307
*/
static unsigned short normal_i2c[] = { 0x68, I2C_CLIENT_END };
static unsigned short normal_i2c_range[] = { 0x68, 0x68, I2C_CLIENT_END };
I2C_CLIENT_INSMOD;
static struct i2c_driver rtc_i2c_driver;
static struct i2c_client client_template;
static struct i2c_client *ds1307_client;
static unsigned char days_in_mo[] =
{0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
static void rtc_i2c_init_client(struct i2c_client *pclient);
static void inc_use(struct i2c_client *client)
{
#ifdef MODULE
MOD_INC_USE_COUNT;
#endif
}
static void dec_use(struct i2c_client *client)
{
#ifdef MODULE
MOD_DEC_USE_COUNT;
#endif
}
/* We get here after scanning the I2C device at one of the DS1307 addresses.
*/
static int rtc_i2c_detect_rtc(struct i2c_adapter *adap, int addr,
unsigned short flags, int kind)
{
int err;
char c = 0;
int found = 0;
struct i2c_client *pclient;
if (!(pclient = (struct i2c_client *)kmalloc(sizeof(struct i2c_client), GFP_KERNEL)))
{
printk("ds1307: Driver failed memory allocation\n");
return -ENOMEM;
}
memcpy(pclient, &client_template, sizeof(struct i2c_client));
pclient->addr = addr;
pclient->adapter = adap;
if(i2c_master_send(pclient, &c, 1) == 1)
{
if(i2c_master_recv(pclient, &c, 1) == 1)
{
printk("ds1307: I2C Real-Time-Clock detected at addr 0x%x\n", pclient->addr);
found = 1;
}
}
if(found)
{
if((err = i2c_attach_client(pclient)))
{
printk("ds1307: failed to attach client\n");
kfree(pclient);
return err;
}
rtc_i2c_init_client(pclient);
}
else
{
kfree(pclient);
}
return 0;
}
static int detach_client(struct i2c_client *client)
{
return 0;
}
static int i2c_command(struct i2c_client *client, unsigned int cmd, void *arg)
{
return -1;
}
static int attach_adapter(struct i2c_adapter *adap)
{
/* Look for the PowerPC adapter */
if(adap->id == (I2C_ALGO_PPC405 | I2C_HW_PPC405))
return i2c_probe(adap, &addr_data, rtc_i2c_detect_rtc);
return 0;
}
static int ds1307_readreg(struct i2c_client *pclient, int reg)
{
unsigned char c = reg;
if(!pclient)
return 0;
/* set pointer register */
if(i2c_master_send(pclient, &c, 1) == 1)
{
/* read data register */
if(i2c_master_recv(pclient, &c, 1) == 1)
{
return c;
}
}
return 0;
}
static int ds1307_writereg(struct i2c_client *pclient, int val, int reg)
{
unsigned char buf[2];
if(!pclient)
return 0;
buf[0] = reg;
buf[1] = val;
if(i2c_master_send(pclient, buf, sizeof(buf)) == sizeof(buf))
{
return val;
}
return 0;
}
static void rtc_i2c_init_client(struct i2c_client *pclient)
{
int val;
/* Datasheet says the oscillator may not be enabled on the first powerup.
* Enable it if necessary.
*/
if((val = ds1307_readreg(pclient, RTC_SECONDS)) & 0x80)
ds1307_writereg(pclient, (val & ~0x80), RTC_SECONDS);
ds1307_client = pclient; /* hack! */
}
void
get_rtc_time(struct rtc_time *rtc_tm)
{
unsigned long flags;
struct i2c_client *pclient = ds1307_client; /* hack! */
save_flags(flags);
cli();
rtc_tm->tm_sec = (ds1307_readreg(pclient, RTC_SECONDS) & 0x7f);
rtc_tm->tm_min = (ds1307_readreg(pclient, RTC_MINUTES) & 0x7f);
rtc_tm->tm_hour = (ds1307_readreg(pclient, RTC_HOURS) & 0x3f);
rtc_tm->tm_mday = (ds1307_readreg(pclient, RTC_DAY_OF_MONTH) & 0x3f);
rtc_tm->tm_mon = (ds1307_readreg(pclient, RTC_MONTH) & 0x1f);
rtc_tm->tm_year = ds1307_readreg(pclient, RTC_YEAR);
restore_flags(flags);
BCD_TO_BIN(rtc_tm->tm_sec);
BCD_TO_BIN(rtc_tm->tm_min);
BCD_TO_BIN(rtc_tm->tm_hour);
BCD_TO_BIN(rtc_tm->tm_mday);
BCD_TO_BIN(rtc_tm->tm_mon);
BCD_TO_BIN(rtc_tm->tm_year);
/*
* Account for differences between how the RTC uses the values
* and how they are defined in a struct rtc_time;
*/
if (rtc_tm->tm_year <= 69)
rtc_tm->tm_year += 100;
rtc_tm->tm_mon--;
}
/* ioctl that supports RTC_RD_TIME and RTC_SET_TIME (read and set time/date) */
static int
rtc_ioctl(struct inode *inode, struct file *file, unsigned int cmd,
unsigned long arg)
{
unsigned long flags;
struct i2c_client *pclient = ds1307_client; /* hack! */
switch(cmd) {
case RTC_RD_TIME: /* Read the time/date from RTC */
{
struct rtc_time rtc_tm;
get_rtc_time(&rtc_tm);
if (copy_to_user((struct rtc_time*)arg, &rtc_tm, sizeof(struct rtc_time)))
return -EFAULT;
return 0;
}
case RTC_SET_TIME: /* Set the RTC */
{
struct rtc_time rtc_tm;
unsigned char mon, day, hrs, min, sec, leap_yr;
unsigned int yrs;
#if 1
if (!suser())
return -EACCES;
#endif
if (copy_from_user(&rtc_tm, (struct rtc_time*)arg, sizeof(struct rtc_time)))
return -EFAULT;
yrs = rtc_tm.tm_year + 1900;
mon = rtc_tm.tm_mon + 1; /* tm_mon starts at zero */
day = rtc_tm.tm_mday;
hrs = rtc_tm.tm_hour;
min = rtc_tm.tm_min;
sec = rtc_tm.tm_sec;
if ((yrs < 1970) || (yrs > 2069))
return -EINVAL;
leap_yr = ((!(yrs % 4) && (yrs % 100)) || !(yrs % 400));
if ((mon > 12) || (day == 0))
return -EINVAL;
if (day > (days_in_mo[mon] + ((mon == 2) && leap_yr)))
return -EINVAL;
if ((hrs >= 24) || (min >= 60) || (sec >= 60))
return -EINVAL;
if (yrs >= 2000)
yrs -= 2000; /* RTC (0, 1, ... 69) */
else
yrs -= 1900; /* RTC (70, 71, ... 99) */
BIN_TO_BCD(sec);
BIN_TO_BCD(min);
BIN_TO_BCD(hrs);
BIN_TO_BCD(day);
BIN_TO_BCD(mon);
BIN_TO_BCD(yrs);
save_flags(flags);
cli();
ds1307_writereg(pclient, yrs, RTC_YEAR);
ds1307_writereg(pclient, mon, RTC_MONTH);
ds1307_writereg(pclient, day, RTC_DAY_OF_MONTH);
ds1307_writereg(pclient, hrs, RTC_HOURS);
ds1307_writereg(pclient, min, RTC_MINUTES);
ds1307_writereg(pclient, sec, RTC_SECONDS);
restore_flags(flags);
/* notice that at this point, the RTC is updated but the kernel
* is still running with the old time. you need to set that
* separately with settimeofday or adjtimex.
*/
return 0;
}
default:
//return -ENOIOCTLCMD;
return -EINVAL;
}
}
/*
* Info exported via "/proc/rtc".
*/
static int proc_read_rtc_status(char *page, char **start, off_t off,
int count, int *eof, void *data)
{
char *out = page;
int len;
struct rtc_time tm;
get_rtc_time(&tm);
/*
* There is no way to tell if the luser has the RTC set for local
* time or for Universal Standard Time (GMT). Probably local though.
*/
out += sprintf(out,
"rtc_time\t: %02d:%02d:%02d\n"
"rtc_date\t: %04d-%02d-%02d\n",
tm.tm_hour, tm.tm_min, tm.tm_sec,
tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday);
len = out - page;
len -= off;
if (len < count) {
*eof = 1;
if (len <= 0)
return 0;
} else
len = count;
*start = page + off;
return len;
}
/*
* The various file operations we support.
*/
static struct file_operations rtc_fops = {
owner: THIS_MODULE,
ioctl: rtc_ioctl,
};
/* just probe for the RTC and register the device to handle the ioctl needed */
static __init int ds1307_init(void)
{
int res;
if ((res = i2c_add_driver(&rtc_i2c_driver)))
{
printk("ds1307: I2C driver registration failed\n");
return res;
}
if (register_chrdev(RTC_MAJOR_NR, ds1307_name, &rtc_fops)) {
printk("unable to get major %d for %s\n", RTC_MAJOR_NR, ds1307_name);
return -1;
}
create_proc_read_entry (ds1307_name, 0, NULL, proc_read_rtc_status, NULL);
return 0;
}
static __exit void ds1307_cleanup(void)
{
int res;
remove_proc_entry(ds1307_name, 0);
if (unregister_chrdev(RTC_MAJOR_NR, ds1307_name)) {
printk("unable to unregister %s\n", ds1307_name);
}
if ((res = i2c_del_driver(&rtc_i2c_driver)))
{
printk("ds1307: I2C driver deregistration failed, "
"module not removed.\n");
}
}
static struct i2c_driver rtc_i2c_driver =
{
name: "I2C driver for DS1307 RTC",
id: I2C_DRIVERID_DS1307,
flags: I2C_DF_NOTIFY,
attach_adapter: attach_adapter,
detach_client: detach_client,
command: i2c_command,
inc_use: inc_use,
dec_use: dec_use
};
static struct i2c_client client_template =
{
name: "DS1307 I2C",
id: I2C_DRIVERID_DS1307,
data: NULL,
flags: 0,
addr: 0,
adapter: NULL,
driver: &rtc_i2c_driver
};
MODULE_DESCRIPTION("I2C driver for DS1307 RTC");
module_init(ds1307_init);
module_exit(ds1307_cleanup);
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -