[PATCH 1/1] Added support of ST m41t85 rtc chip

From: Andrey Volkov
Date: Mon Nov 14 2005 - 08:57:31 EST


Hello Jean,

Possible too late to include in 2.6.15,
but better later then never :).

Comments?

--
Regards
Andrey Volkov

P.S. Please CC to me directly, I'm not in lm-sensors@xxxxxxxxxxxxxx list.
Added support of ST M41T85 RTC

Signed-off-By: Andrey Volkov <avolkov@xxxxxxxxxxxx>
---

drivers/i2c/chips/Kconfig | 88 ++++++++++++++
drivers/i2c/chips/Makefile | 1
drivers/i2c/chips/m41t85.c | 285 ++++++++++++++++++++++++++++++++++++++++++++
include/linux/i2c-id.h | 1
4 files changed, 375 insertions(+), 0 deletions(-)

diff --git a/drivers/i2c/chips/Kconfig b/drivers/i2c/chips/Kconfig
index f9fae28..6593fc5 100644
--- a/drivers/i2c/chips/Kconfig
+++ b/drivers/i2c/chips/Kconfig
@@ -111,6 +111,94 @@ config SENSORS_M41T00
This driver can also be built as a module. If so, the module
will be called m41t00.

+config SENSORS_M41T85
+ tristate "ST M41T85 RTC chip"
+ depends on I2C
+ help
+ If you say yes here you get support for the ST M41T85 RTC chip.
+
+ This driver can also be built as a module. If so, the module
+ will be called m41t85.
+
+config SENSORS_M41T85_SQW_FRQ_ENABLE
+ depends on SENSORS_M41T85
+ bool "Square Wave Output"
+ help
+ If you say yes here, then m41t85 will generate clocks on it
+ SQW pin at frequency which you are select below.
+
+choice
+ prompt "SQW frequincy"
+ depends on SENSORS_M41T85_SQW_FRQ_ENABLE
+ default SENSORS_M41T85_SQW_FRQ_32KHZ
+
+config SENSORS_M41T85_SQW_FRQ_32KHZ
+ bool "32.768 kHz"
+
+config SENSORS_M41T85_SQW_FRQ_8KHZ
+ bool "8 kHz"
+
+config SENSORS_M41T85_SQW_FRQ_4KHZ
+ bool "4 kHz"
+
+config SENSORS_M41T85_SQW_FRQ_2KHZ
+ bool "2 kHz"
+
+config SENSORS_M41T85_SQW_FRQ_1KHZ
+ bool "1 kHz"
+
+config SENSORS_M41T85_SQW_FRQ_512HZ
+ bool "512 Hz"
+
+config SENSORS_M41T85_SQW_FRQ_256HZ
+ bool "256 Hz"
+
+config SENSORS_M41T85_SQW_FRQ_128HZ
+ bool "128 Hz"
+
+config SENSORS_M41T85_SQW_FRQ_64HZ
+ bool "64 Hz"
+
+config SENSORS_M41T85_SQW_FRQ_32HZ
+ bool "32 Hz"
+
+config SENSORS_M41T85_SQW_FRQ_16HZ
+ bool "16 Hz"
+
+config SENSORS_M41T85_SQW_FRQ_8HZ
+ bool "8 Hz"
+
+config SENSORS_M41T85_SQW_FRQ_4HZ
+ bool "4 Hz"
+
+config SENSORS_M41T85_SQW_FRQ_2HZ
+ bool "2 Hz"
+
+config SENSORS_M41T85_SQW_FRQ_1HZ
+ bool "1 Hz"
+
+endchoice
+
+config SENSORS_M41T85_SQW_FRQ
+ int
+ depends on SENSORS_M41T85_SQW_FRQ_ENABLE
+ default "1" if SENSORS_M41T85_SQW_FRQ_32KHZ
+ default "2" if SENSORS_M41T85_SQW_FRQ_8KHZ
+ default "3" if SENSORS_M41T85_SQW_FRQ_4KHZ
+ default "4" if SENSORS_M41T85_SQW_FRQ_2KHZ
+ default "5" if SENSORS_M41T85_SQW_FRQ_1KHZ
+ default "6" if SENSORS_M41T85_SQW_FRQ_512HZ
+ default "7" if SENSORS_M41T85_SQW_FRQ_256HZ
+ default "8" if SENSORS_M41T85_SQW_FRQ_128HZ
+ default "9" if SENSORS_M41T85_SQW_FRQ_64HZ
+ default "10" if SENSORS_M41T85_SQW_FRQ_32HZ
+ default "11" if SENSORS_M41T85_SQW_FRQ_16HZ
+ default "12" if SENSORS_M41T85_SQW_FRQ_8HZ
+ default "13" if SENSORS_M41T85_SQW_FRQ_4HZ
+ default "14" if SENSORS_M41T85_SQW_FRQ_2HZ
+ default "15" if SENSORS_M41T85_SQW_FRQ_1HZ
+
+
config SENSORS_MAX6875
tristate "Maxim MAX6875 Power supply supervisor"
depends on I2C && EXPERIMENTAL
diff --git a/drivers/i2c/chips/Makefile b/drivers/i2c/chips/Makefile
index 46178b5..5637881 100644
--- a/drivers/i2c/chips/Makefile
+++ b/drivers/i2c/chips/Makefile
@@ -14,6 +14,7 @@ obj-$(CONFIG_SENSORS_RTC8564) += rtc8564
obj-$(CONFIG_ISP1301_OMAP) += isp1301_omap.o
obj-$(CONFIG_TPS65010) += tps65010.o
obj-$(CONFIG_RTC_X1205_I2C) += x1205.o
+obj-$(CONFIG_SENSORS_M41T85) += m41t85.o

ifeq ($(CONFIG_I2C_DEBUG_CHIP),y)
EXTRA_CFLAGS += -DDEBUG
diff --git a/drivers/i2c/chips/m41t85.c b/drivers/i2c/chips/m41t85.c
new file mode 100644
index 0000000..f26a115
--- /dev/null
+++ b/drivers/i2c/chips/m41t85.c
@@ -0,0 +1,285 @@
+/*
+ * drivers/i2c/chips/m41t85.c
+ *
+ * DESCRIPTION:
+ * I2C client/driver for the ST M41T85 Real-Time Clock chip.
+ *
+ * AUTHOR:
+ * Andrey Volkov <avolkov@xxxxxxxxxxxx>
+ *
+ * COPYRIGHT:
+ * 2005, Varma Electronics Oy
+ *
+ * Based on driver for ST M41T00 by Mark A. Greer <mgreer@xxxxxxxxxx>
+ * (see m41t00.c for copyright)
+ *
+ * LICENCE:
+ * This file is subject to the terms and conditions of the GNU General Public
+ * License version 2. See the file COPYING in the main directory of this archive
+ * for more details.
+ *
+ * HISTORY:
+ * 2005-10-12 Created
+ */
+
+/*
+ * This i2c client/driver wedges between the drivers/char/genrtc.c RTC
+ * interface and the SMBus interface of the i2c subsystem.
+ * It would be more efficient to use i2c msgs/i2c_transfer directly but, as
+ * recommened in .../Documentation/i2c/writing-clients section
+ * "Sending and receiving", using SMBus level communication is preferred.
+ */
+
+#include <linux/config.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/interrupt.h>
+#include <linux/i2c.h>
+#include <linux/rtc.h>
+#include <linux/bcd.h>
+
+#include <asm/time.h>
+#include <asm/rtc.h>
+
+#define M41T85_DRV_NAME "m41t85"
+
+#define RTC_CSEC_ADDR 0x00
+#define RTC_SEC_ADDR 0x01
+#define RTC_MIN_ADDR 0x02
+#define RTC_HOUR_ADDR 0x03
+#define RTC_WDAY_ADDR 0x04
+#define RTC_DAY_ADDR 0x05
+#define RTC_MONTH_ADDR 0x06
+#define RTC_YEARS_ADDR 0x07
+#define RTC_CONTROL_ADDR 0x08
+#define RTC_WATCHDOG_ADDR 0x09
+#define RTC_ALARM_MONTH_ADDR 0x0a
+#define RTC_ALARM_DATE_ADDR 0x0b
+#define RTC_ALARM_HOUR_ADDR 0x0c
+#define RTC_ALARM_MIN_ADDR 0x0d
+#define RTC_ALARM_SEC_ADDR 0x0e
+#define RTC_WDF_ADDR 0x0f
+#define RTC_SQW_ADDR 0x13
+
+
+static DECLARE_MUTEX(m41t85_mutex);
+
+static struct i2c_driver m41t85_driver;
+static struct i2c_client *save_client;
+
+static unsigned short ignore[] = { I2C_CLIENT_END };
+static unsigned short normal_addr[] = { 0x68, I2C_CLIENT_END };
+
+static struct i2c_client_address_data addr_data = {
+ .normal_i2c = normal_addr,
+ .probe = ignore,
+ .ignore = ignore,
+};
+
+ulong
+m41t85_get_rtc_time(void)
+{
+ s32 sec, min, hour, day, mon, year;
+ s32 sec1, min1, hour1, day1, mon1, year1;
+ ulong limit = 10;
+
+ sec = min = hour = day = mon = year = 0;
+ sec1 = min1 = hour1 = day1 = mon1 = year1 = 0;
+
+ down(&m41t85_mutex);
+ do {
+
+ if (((sec = i2c_smbus_read_byte_data(save_client, RTC_SEC_ADDR)) >= 0)
+ && ((min = i2c_smbus_read_byte_data(save_client, RTC_MIN_ADDR))
+ >= 0)
+ && ((hour = i2c_smbus_read_byte_data(save_client, RTC_HOUR_ADDR))
+ >= 0)
+ && ((day = i2c_smbus_read_byte_data(save_client, RTC_DAY_ADDR))
+ >= 0)
+ && ((mon = i2c_smbus_read_byte_data(save_client, RTC_MONTH_ADDR))
+ >= 0)
+ && ((year = i2c_smbus_read_byte_data(save_client, RTC_YEARS_ADDR))
+ >= 0)
+ && ((sec == sec1) && (min == min1) && (hour == hour1)
+ && (day == day1) && (mon == mon1)
+ && (year == year1)))
+
+ break;
+
+ sec1 = sec;
+ min1 = min;
+ hour1 = hour;
+ day1 = day;
+ mon1 = mon;
+ year1 = year;
+ } while (--limit > 0);
+ up(&m41t85_mutex);
+
+ if (limit == 0) {
+ dev_warn(&save_client->dev,
+ "m41t85: can't read rtc chip\n");
+ sec = min = hour = day = mon = year = 0;
+ }
+
+ sec = BCD2BIN(sec & 0x7f);
+ min = BCD2BIN(min & 0x7f);
+ hour = BCD2BIN(hour & 0x3f);
+ day = BCD2BIN(day & 0x3f);
+ mon = BCD2BIN(mon & 0x1f);
+ year = BCD2BIN(year & 0xff);
+
+ year += 1900;
+ if (year < 1970)
+ year += 100;
+
+ return mktime(year, mon, day, hour, min, sec);
+}
+
+static void
+m41t85_set_tlet(ulong arg)
+{
+ struct rtc_time tm;
+ ulong nowtime = *(ulong *)arg;
+
+ to_tm(nowtime, &tm);
+ tm.tm_year = (tm.tm_year - 1900) % 100;
+
+ tm.tm_sec = BIN2BCD(tm.tm_sec);
+ tm.tm_min = BIN2BCD(tm.tm_min);
+ tm.tm_hour = BIN2BCD(tm.tm_hour);
+ tm.tm_mon = BIN2BCD(tm.tm_mon);
+ tm.tm_mday = BIN2BCD(tm.tm_mday);
+ tm.tm_year = BIN2BCD(tm.tm_year);
+
+ down(&m41t85_mutex);
+ if ((i2c_smbus_write_byte_data(save_client, RTC_SEC_ADDR, tm.tm_sec & 0x7f) < 0)
+ || (i2c_smbus_write_byte_data(save_client, RTC_MIN_ADDR, tm.tm_min & 0x7f)
+ < 0)
+ || (i2c_smbus_write_byte_data(save_client, RTC_HOUR_ADDR, tm.tm_hour & 0x7f)
+ < 0)
+ || (i2c_smbus_write_byte_data(save_client, RTC_DAY_ADDR, tm.tm_mday & 0x7f)
+ < 0)
+ || (i2c_smbus_write_byte_data(save_client, RTC_MONTH_ADDR, tm.tm_mon & 0x7f)
+ < 0)
+ || (i2c_smbus_write_byte_data(save_client, RTC_YEARS_ADDR, tm.tm_year & 0x7f)
+ < 0))
+
+ dev_warn(&save_client->dev,"m41t85: can't write to rtc chip\n");
+
+ up(&m41t85_mutex);
+ return;
+}
+
+static ulong new_time;
+
+DECLARE_TASKLET_DISABLED(m41t85_tasklet, m41t85_set_tlet, (ulong)&new_time);
+
+int
+m41t85_set_rtc_time(ulong nowtime)
+{
+ new_time = nowtime;
+
+ if (in_interrupt())
+ tasklet_schedule(&m41t85_tasklet);
+ else
+ m41t85_set_tlet((ulong)&new_time);
+
+ return 0;
+}
+
+/*
+ *****************************************************************************
+ *
+ * Driver Interface
+ *
+ *****************************************************************************
+ */
+static int
+m41t85_probe(struct i2c_adapter *adap, int addr, int kind)
+{
+ struct i2c_client *client;
+ int ret;
+ s32 reg;
+
+ client = kzalloc(sizeof(struct i2c_client), GFP_KERNEL);
+ if (!client)
+ return -ENOMEM;
+
+ strncpy(client->name, M41T85_DRV_NAME, I2C_NAME_SIZE);
+ client->addr = addr;
+ client->adapter = adap;
+ client->driver = &m41t85_driver;
+
+ if ((ret = i2c_attach_client(client)) != 0) {
+ kfree(client);
+ return ret;
+ }
+
+ #if defined (CONFIG_SENSORS_M41T85_SQW_FRQ)
+ reg = i2c_smbus_read_byte_data(client, RTC_ALARM_MONTH_ADDR);
+ if (reg >=0 && (reg & 0x40) == 0) {
+ ret = i2c_smbus_write_byte_data(client, RTC_SQW_ADDR, CONFIG_SENSORS_M41T85_SQW_FRQ);
+ if(ret == 0)
+ i2c_smbus_write_byte_data(client, RTC_ALARM_MONTH_ADDR, reg | 0x40);
+ }
+ #endif
+ /* Check if ST flag set, and if yes, then clear it */
+ reg = i2c_smbus_read_byte_data(client, RTC_SEC_ADDR);
+ if (reg >=0 && (reg & 0x80) != 0)
+ i2c_smbus_write_byte_data(client, RTC_SEC_ADDR, reg & ~0x80);
+
+ /* Check if HT flag set, and if yes, then clear it */
+ reg = i2c_smbus_read_byte_data(client, RTC_ALARM_HOUR_ADDR);
+ if (reg >=0 && (reg & 0x40) != 0)
+ i2c_smbus_write_byte_data(client, RTC_ALARM_HOUR_ADDR, reg & ~0x40);
+
+ save_client = client;
+ return 0;
+}
+
+static int
+m41t85_attach(struct i2c_adapter *adap)
+{
+ return i2c_probe(adap, &addr_data, m41t85_probe);
+}
+
+static int
+m41t85_detach(struct i2c_client *client)
+{
+ int rc;
+
+ if ((rc = i2c_detach_client(client)) == 0) {
+ kfree(client);
+ tasklet_kill(&m41t85_tasklet);
+ }
+ return rc;
+}
+
+static struct i2c_driver m41t85_driver = {
+ .owner = THIS_MODULE,
+ .name = M41T85_DRV_NAME,
+ .id = I2C_DRIVERID_STM41T85,
+ .flags = I2C_DF_NOTIFY,
+ .attach_adapter = m41t85_attach,
+ .detach_client = m41t85_detach,
+};
+
+static int __init
+m41t85_init(void)
+{
+ return i2c_add_driver(&m41t85_driver);
+}
+
+static void __exit
+m41t85_exit(void)
+{
+ i2c_del_driver(&m41t85_driver);
+ return;
+}
+
+module_init(m41t85_init);
+module_exit(m41t85_exit);
+
+MODULE_AUTHOR("Andrey Volkov <avolkov@xxxxxxxxxxxx>");
+MODULE_DESCRIPTION("ST Microelectronics M41T85 RTC I2C Client Driver");
+MODULE_LICENSE("GPL v2");
diff --git a/include/linux/i2c-id.h b/include/linux/i2c-id.h
index 5c05f52..ac8ffcd 100644
--- a/include/linux/i2c-id.h
+++ b/include/linux/i2c-id.h
@@ -108,6 +108,7 @@
#define I2C_DRIVERID_SAA7127 72 /* saa7124 video encoder */
#define I2C_DRIVERID_SAA711X 73 /* saa711x video encoders */
#define I2C_DRIVERID_AKITAIOEXP 74 /* IO Expander on Sharp SL-C1000 */
+#define I2C_DRIVERID_STM41T85 75 /* real time clock */

#define I2C_DRIVERID_EXP0 0xF0 /* experimental use id's */
#define I2C_DRIVERID_EXP1 0xF1