diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig index ff9e35c..07e5182 100644 --- a/drivers/rtc/Kconfig +++ b/drivers/rtc/Kconfig @@ -206,6 +206,15 @@ config RTC_DRV_PCF8583 This driver can also be built as a module. If so, the module will be called rtc-pcf8583. +config RTC_DRV_M41T11 + tristate "ST M41T11" + depends on RTC_CLASS && I2C + help + If you say yes here you get support for the ST M41T11 RTC chip. + + This driver can also be built as a module. If so, the module + will be called rtc-m41t11. + config RTC_DRV_M41T80 tristate "ST M41T80 series RTC" help diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile index d3a33aa..468aa0c 100644 --- a/drivers/rtc/Makefile +++ b/drivers/rtc/Makefile @@ -28,6 +28,7 @@ obj-$(CONFIG_RTC_DRV_DS1672) += rtc-ds1672.o obj-$(CONFIG_RTC_DRV_DS1742) += rtc-ds1742.o obj-$(CONFIG_RTC_DRV_EP93XX) += rtc-ep93xx.o obj-$(CONFIG_RTC_DRV_ISL1208) += rtc-isl1208.o +obj-$(CONFIG_RTC_DRV_M41T11) += rtc-m41t11.o obj-$(CONFIG_RTC_DRV_M41T80) += rtc-m41t80.o obj-$(CONFIG_RTC_DRV_M48T59) += rtc-m48t59.o obj-$(CONFIG_RTC_DRV_M48T86) += rtc-m48t86.o diff --git a/drivers/rtc/interface.c b/drivers/rtc/interface.c index ad66c6e..189afb4 100644 --- a/drivers/rtc/interface.c +++ b/drivers/rtc/interface.c @@ -272,3 +272,43 @@ int rtc_irq_set_freq(struct rtc_device *rtc, struct rtc_task *task, int freq) return err; } EXPORT_SYMBOL_GPL(rtc_irq_set_freq); + +int rtc_speed_up(struct rtc_device *rtc) +{ + int err = 0; + + err = mutex_lock_interruptible(&rtc->ops_lock); + if (err) + return -EBUSY; + + if (!rtc->ops) + err = -ENODEV; + else if (!rtc->ops->speed_up) + err = -EINVAL; + else + err = rtc->ops->speed_up(rtc->dev.parent); + + mutex_unlock(&rtc->ops_lock); + return err; +} +EXPORT_SYMBOL_GPL(rtc_speed_up); + +int rtc_slow_down(struct rtc_device *rtc) +{ + int err = 0; + + err = mutex_lock_interruptible(&rtc->ops_lock); + if (err) + return -EBUSY; + + if (!rtc->ops) + err = -ENODEV; + else if (!rtc->ops->slow_down) + err = -EINVAL; + else + err = rtc->ops->slow_down(rtc->dev.parent); + + mutex_unlock(&rtc->ops_lock); + return err; +} +EXPORT_SYMBOL_GPL(rtc_slow_down); diff --git a/drivers/rtc/rtc-dev.c b/drivers/rtc/rtc-dev.c index 005fff3..d35f907 100644 --- a/drivers/rtc/rtc-dev.c +++ b/drivers/rtc/rtc-dev.c @@ -223,6 +223,8 @@ static int rtc_dev_ioctl(struct inode *inode, struct file *file, switch (cmd) { case RTC_EPOCH_SET: case RTC_SET_TIME: + case RTC_SPEED_UP: + case RTC_SLOW_DOWN: if (!capable(CAP_SYS_TIME)) return -EACCES; break; @@ -395,6 +397,15 @@ static int rtc_dev_ioctl(struct inode *inode, struct file *file, case RTC_UIE_ON: return set_uie(rtc); #endif + + case RTC_SPEED_UP: + err = rtc_speed_up(rtc); + break; + + case RTC_SLOW_DOWN: + err = rtc_speed_up(rtc); + break; + default: err = -ENOTTY; break; diff --git a/drivers/rtc/rtc-m41t11.c b/drivers/rtc/rtc-m41t11.c new file mode 100644 index 0000000..2d7128f --- /dev/null +++ b/drivers/rtc/rtc-m41t11.c @@ -0,0 +1,319 @@ +/* + * rtc-m41t11.c - RTC driver for some ST M41T11, derived from rtc-ds1307.c + * + * Copyright (C) 2005 James Chapman (ds1337 core) + * Copyright (C) 2006 David Brownell + * Copyright (C) 2007 TANDBERG Telecom AS + * + * 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 + +static unsigned short normal_i2c[] = { 0x68, I2C_CLIENT_END }; + +I2C_CLIENT_INSMOD; + +#define M41T11_REG_SECS 0x00 /* 00-59 */ +# define M41T11_BIT_ST 0x80 +# define M41T11_MASK_SECS 0x7f +#define M41T11_REG_MIN 0x01 /* 00-59 */ +# define M41T11_MASK_MIN 0x7f +#define M41T11_REG_HOUR 0x02 /* 00-23 */ +# define M41T11_BIT_CEB 0x80 +# define M41T11_BIT_CB 0x40 +# define M41T11_MASK_HOUR 0x3f +#define M41T11_REG_WDAY 0x03 /* 01-07 */ +# define M41T11_MASK_WDAY 0x7 +#define M41T11_REG_MDAY 0x04 /* 01-31 */ +# define M41T11_MASK_MDAY 0x3f +#define M41T11_REG_MONTH 0x05 /* 01-12 */ +# define M41T11_MASK_MONTH 0x1f +#define M41T11_REG_YEAR 0x06 /* 00-99 */ +# define M41T11_MASK_YEAR 0xff +#define M41T11_REG_CONTROL 0x07 +# define M41T11_BIT_OUT 0x80 +# define M41T11_BIT_FT 0x40 +# define M41T11_BIT_S 0x20 +# define M41T11_MASK_CAL 0x1f + +struct m41t11 { + struct i2c_client i2c; + struct rtc_device *rtc; +}; + +static int m41t11_read(struct m41t11 *m41t11, u8 *data, int len) +{ + struct i2c_msg msg[2]; + int result; + + /* write address register */ + msg[0].addr = m41t11->i2c.addr; + msg[0].flags = 0; + msg[0].len = 1; + msg[0].buf = data; + + /* read data */ + msg[1].addr = m41t11->i2c.addr; + msg[1].flags = I2C_M_RD; + msg[1].len = len; + msg[1].buf = data + 1; + + result = i2c_transfer(m41t11->i2c.adapter, msg, 2); + if (result != 2) { + dev_err(&m41t11->i2c.dev, "read error %d\n", result); + return result; + } + return 0; +} + +static int m41t11_write(struct m41t11 *m41t11, u8 *data, int len) +{ + struct i2c_msg msg[1]; + int result; + + /* write address register and data */ + msg[0].addr = m41t11->i2c.addr; + msg[0].flags = 0; + msg[0].len = 1 + len; + msg[0].buf = data; + + result = i2c_transfer(m41t11->i2c.adapter, msg, 1); + if (result != 1) { + dev_err(&m41t11->i2c.dev, "write error %d\n", result); + return result; + } + return 0; +} + +static int m41t11_get_time(struct device *dev, struct rtc_time *t) +{ + struct m41t11 *m41t11 = dev_get_drvdata(dev); + u8 regs[8]; + + /* read the RTC registers all at once */ + regs[0] = 0; + if (m41t11_read(m41t11, regs, 7) != 0) + return -EIO; + + dev_info(dev, "read: %02x %02x %02x %02x %02x %02x %02x\n", + regs[1], regs[2], regs[3], regs[4], regs[5], regs[6], regs[7]); + + t->tm_sec = BCD2BIN(regs[1+M41T11_REG_SECS] & M41T11_MASK_SECS); + t->tm_min = BCD2BIN(regs[1+M41T11_REG_MIN] & M41T11_MASK_MIN); + t->tm_hour = BCD2BIN(regs[1+M41T11_REG_HOUR] & M41T11_MASK_HOUR); + t->tm_wday = BCD2BIN(regs[1+M41T11_REG_WDAY] & M41T11_MASK_WDAY) - 1; + t->tm_mday = BCD2BIN(regs[1+M41T11_REG_MDAY] & M41T11_MASK_MDAY); + t->tm_mon = BCD2BIN(regs[1+M41T11_REG_MONTH] & M41T11_MASK_MONTH) - 1; + + t->tm_year = BCD2BIN(regs[1+M41T11_REG_YEAR]); + /* assume 20xx if century bit is set, 19xx otherwise */ + if (regs[1+M41T11_REG_HOUR] & M41T11_BIT_CB) + t->tm_year += 100; + + dev_info(dev, "read secs=%d, mins=%d, " + "hours=%d, mday=%d, mon=%d, year=%d, wday=%d\n", + t->tm_sec, t->tm_min, t->tm_hour, t->tm_mday, + t->tm_mon, t->tm_year, t->tm_wday); + + return 0; +} + +static int m41t11_set_time(struct device *dev, struct rtc_time *t) +{ + struct m41t11 *m41t11 = dev_get_drvdata(dev); + u8 regs[8], val; + + dev_info(dev, "write secs=%d, mins=%d, " + "hours=%d, mday=%d, mon=%d, year=%d, wday=%d\n", + t->tm_sec, t->tm_min, t->tm_hour, t->tm_mday, + t->tm_mon, t->tm_year, t->tm_wday); + + regs[0] = 0; + regs[1+M41T11_REG_SECS] = BIN2BCD(t->tm_sec); + regs[1+M41T11_REG_MIN] = BIN2BCD(t->tm_min); + regs[1+M41T11_REG_HOUR] = BIN2BCD(t->tm_hour); + regs[1+M41T11_REG_WDAY] = BIN2BCD(t->tm_wday + 1); + regs[1+M41T11_REG_MDAY] = BIN2BCD(t->tm_mday); + regs[1+M41T11_REG_MONTH] = BIN2BCD(t->tm_mon + 1); + + val = t->tm_year % 100; + regs[1+M41T11_REG_YEAR] = BIN2BCD(val); + regs[1+M41T11_REG_HOUR] |= M41T11_BIT_CEB; + if ((t->tm_year / 100) % 2) + regs[1+M41T11_REG_HOUR] |= M41T11_BIT_CB; + + dev_info(dev, "write %02x %02x %02x %02x %02x %02x %02x\n", + regs[1], regs[2], regs[3], regs[4], regs[5], regs[6], regs[7]); + + if (m41t11_write(m41t11, regs, 7) != 0) + return -EIO; + + return 0; +} + +static int m41t11_speed_up(struct device *dev) +{ + struct m41t11 *m41t11 = dev_get_drvdata(dev); + u8 regs[2]; + + regs[0] = M41T11_REG_CONTROL; + if (m41t11_read(m41t11, regs, 1) != 0) + return -EIO; + + /* XXX */ + + return -EIO; +} + +static int m41t11_slow_down(struct device *dev) +{ + struct m41t11 *m41t11 = dev_get_drvdata(dev); + u8 regs[2]; + + regs[0] = M41T11_REG_CONTROL; + if (m41t11_read(m41t11, regs, 1) != 0) + return -EIO; + + /* XXX */ + + return -EIO; +} + +static const struct rtc_class_ops ds13xx_rtc_ops = { + .read_time = m41t11_get_time, + .set_time = m41t11_set_time, + .speed_up = m41t11_speed_up, + .slow_down = m41t11_slow_down, +}; + +static struct i2c_driver m41t11_driver; + +static int __devinit +m41t11_detect(struct i2c_adapter *adapter, int address, int kind) +{ + struct m41t11 *m41t11; + u8 regs[8], val; + int err = -ENODEV; + + if (!(m41t11 = kzalloc(sizeof(*m41t11), GFP_KERNEL))) { + err = -ENOMEM; + goto exit; + } + + strlcpy(m41t11->i2c.name, "m41t11", I2C_NAME_SIZE); + m41t11->i2c.addr = address; + m41t11->i2c.adapter = adapter; + m41t11->i2c.driver = &m41t11_driver; + m41t11->i2c.flags = 0; + i2c_set_clientdata(&m41t11->i2c, m41t11); + +read_rtc: + /* read RTC registers */ + regs[0] = 0; + if (m41t11_read(m41t11, regs, 7) != 0) { + err = -EIO; + goto exit_free; + } + + /* minimal sanity checking */ + if (regs[1+M41T11_REG_SECS] & M41T11_BIT_ST) { + /* oscillator was stopped */ + dev_warn(&m41t11->i2c.dev, "oscillator started; SET TIME!\n"); + regs[0] = M41T11_REG_SECS; + regs[1] = 0; + m41t11_write(m41t11, regs, 1); + goto read_rtc; + } + val = BCD2BIN(regs[1+M41T11_REG_SECS] & M41T11_MASK_SECS); + if (val > 59) + goto exit_free; + val = BCD2BIN(regs[1+M41T11_REG_MIN] & M41T11_MASK_MIN); + if (val > 59) + goto exit_free; + val = BCD2BIN(regs[1+M41T11_REG_HOUR] & M41T11_MASK_HOUR); + if (val > 23) + goto exit_free; + val = BCD2BIN(regs[1+M41T11_REG_MDAY] & M41T11_MASK_MDAY); + if (val < 1 || val > 31) + goto exit_free; + val = BCD2BIN(regs[1+M41T11_REG_MONTH] & M41T11_MASK_MONTH); + if (val < 1 || val > 12) + goto exit_free; + + /* Tell the I2C layer a new client has arrived */ + if ((err = i2c_attach_client(&m41t11->i2c)) != 0) + goto exit_free; + + m41t11->rtc = rtc_device_register(m41t11->i2c.name, + &m41t11->i2c.dev, + &ds13xx_rtc_ops, + THIS_MODULE); + if (IS_ERR(m41t11->rtc)) { + err = PTR_ERR(m41t11->rtc); + dev_err(&m41t11->i2c.dev, + "unable to register the class device\n"); + goto exit_detach; + } + + return 0; + +exit_detach: + i2c_detach_client(&m41t11->i2c); +exit_free: + kfree(m41t11); +exit: + return err; +} + +static int __devinit +m41t11_attach_adapter(struct i2c_adapter *adapter) +{ + if (!i2c_check_functionality(adapter, I2C_FUNC_I2C)) + return 0; + return i2c_probe(adapter, &addr_data, m41t11_detect); +} + +static int __devexit m41t11_detach_client(struct i2c_client *client) +{ + int err; + struct m41t11 *m41t11 = i2c_get_clientdata(client); + + rtc_device_unregister(m41t11->rtc); + if ((err = i2c_detach_client(client))) + return err; + kfree(m41t11); + return 0; +} + +static struct i2c_driver m41t11_driver = { + .driver = { + .name = "m41t11", + .owner = THIS_MODULE, + }, + .attach_adapter = m41t11_attach_adapter, + .detach_client = __devexit_p(m41t11_detach_client), +}; + +static int __init m41t11_init(void) +{ + return i2c_add_driver(&m41t11_driver); +} +module_init(m41t11_init); + +static void __exit m41t11_exit(void) +{ + i2c_del_driver(&m41t11_driver); +} +module_exit(m41t11_exit); + +MODULE_DESCRIPTION("RTC driver for ST M41T11"); +MODULE_LICENSE("GPL"); diff --git a/include/linux/rtc.h b/include/linux/rtc.h index 6d5e4a4..53e1980 100644 --- a/include/linux/rtc.h +++ b/include/linux/rtc.h @@ -91,6 +91,9 @@ struct rtc_pll_info { #define RTC_PLL_GET _IOR('p', 0x11, struct rtc_pll_info) /* Get PLL correction */ #define RTC_PLL_SET _IOW('p', 0x12, struct rtc_pll_info) /* Set PLL correction */ +#define RTC_SPEED_UP _IO('p', 0x13) /* speed up a notch */ +#define RTC_SLOW_DOWN _IO('p', 0x14) /* slow down a notch */ + /* interrupt flags */ #define RTC_IRQF 0x80 /* any of the following is active */ #define RTC_PF 0x40 @@ -128,6 +131,8 @@ struct rtc_class_ops { int (*irq_set_state)(struct device *, int enabled); int (*irq_set_freq)(struct device *, int freq); int (*read_callback)(struct device *, int data); + int (*speed_up)(struct device *); + int (*slow_down)(struct device *); }; #define RTC_DEVICE_NAME_SIZE 20 @@ -196,6 +201,8 @@ extern int rtc_irq_set_state(struct rtc_device *rtc, struct rtc_task *task, int enabled); extern int rtc_irq_set_freq(struct rtc_device *rtc, struct rtc_task *task, int freq); +extern int rtc_speed_up(struct rtc_device *rtc); +extern int rtc_slow_down(struct rtc_device *rtc); typedef struct rtc_task { void (*func)(void *private_data);