Patchwork rtc: Add support for ST M41ST85W RTC chip

login
register
mail settings
Submitter Artem Kukhta
Date Dec. 11, 2010, 10:34 a.m.
Message ID <AANLkTin0RkyLKBTKP3zxUD8aVRM=H0jqbO_ac22Qiqdg@mail.gmail.com>
Download mbox | patch
Permalink /patch/75206/
State New
Headers show

Comments

Artem Kukhta - Dec. 11, 2010, 10:34 a.m.
rtc: Add support for ST M41ST85W RTC chip

This driver based on a driver from xscaleiop project at sourceforge.net.
The existing driver was based on Linux kernel 2.6.24, and used old-style
I2C calls. The code was ported and refactored a bit.

Signed-off-by: Artem Kukhta <artem.kukhta@gmail.com>
---
Hello,

This driver was ported from Linux kernel 2.6.24 to 2.6.36.

I've tested it on a real device, Sabio (IOP321 on IQ31244 platform),
and it seems to work. Only basic RTC functions implemented (no RTC
alarms yet, sorry).

Also, I have a question: what should I put in MODULE_AUTHOR() macros
(and others)?

Thanks.

---
Alessandro Zummo - Dec. 12, 2010, 1:11 a.m.
On Sat, 11 Dec 2010 12:34:13 +0200
Artem Kukhta <artem.kukhta@gmail.com> wrote:

> Also, I have a question: what should I put in MODULE_AUTHOR() macros
> (and others)?

 the author(s) name AND email

 if you are maintaining it, add your name and email,
 don't be shy ;)

Patch

diff -uNr a/arch/arm/mach-iop32x/iq31244.c b/arch/arm/mach-iop32x/iq31244.c
--- a/arch/arm/mach-iop32x/iq31244.c	2010-10-20 23:30:22.000000000 +0300
+++ b/arch/arm/mach-iop32x/iq31244.c	2010-11-04 12:35:48.000000000 +0200
@@ -26,6 +26,7 @@ 
 #include <linux/mtd/physmap.h>
 #include <linux/platform_device.h>
 #include <linux/io.h>
+#include <linux/i2c.h>
 #include <mach/hardware.h>
 #include <asm/cputype.h>
 #include <asm/irq.h>
@@ -287,6 +288,12 @@ 
 		;
 }

+static struct i2c_board_info __initdata m41st85w_i2c_device[] = {
+	{
+		I2C_BOARD_INFO("m41st85w", 0x68),
+	},
+};
+
 static void __init iq31244_init_machine(void)
 {
 	platform_device_register(&iop3xx_i2c0_device);
@@ -296,6 +303,9 @@ 
 	platform_device_register(&iop3xx_dma_0_channel);
 	platform_device_register(&iop3xx_dma_1_channel);

+	i2c_register_board_info(0, m41st85w_i2c_device,
+		ARRAY_SIZE(m41st85w_i2c_device));
+
 	if (is_ep80219())
 		pm_power_off = ep80219_power_off;

diff -uNr a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig
--- a/drivers/rtc/Kconfig	2010-10-20 23:30:22.000000000 +0300
+++ b/drivers/rtc/Kconfig	2010-11-15 22:01:07.000000000 +0200
@@ -271,6 +271,14 @@ 
 	  If you say Y here you will get support for the
 	  watchdog timer in the ST M41T60 and M41T80 RTC chips series.

+config RTC_DRV_M41ST85W
+	tristate "ST M41ST85W"
+	help
+	  If you say yes here you get support for ST M41ST85W RTC chip.
+
+	  This driver can also be built as a module.  If so, the module
+	  will be called rtc-m41st85w.
+
 config RTC_DRV_BQ32K
 	tristate "TI BQ32000"
 	help
@@ -952,4 +960,5 @@ 
 	  This driver can also be buillt as a module. If so, the module
 	  will be called rtc-jz4740.

+
 endif # RTC_CLASS
diff -uNr a/drivers/rtc/Makefile b/drivers/rtc/Makefile
--- a/drivers/rtc/Makefile	2010-10-20 23:30:22.000000000 +0300
+++ b/drivers/rtc/Makefile	2010-11-15 21:47:21.000000000 +0200
@@ -53,6 +53,7 @@ 
 obj-$(CONFIG_RTC_DRV_JZ4740)	+= rtc-jz4740.o
 obj-$(CONFIG_RTC_DRV_M41T80)	+= rtc-m41t80.o
 obj-$(CONFIG_RTC_DRV_M41T94)	+= rtc-m41t94.o
+obj-$(CONFIG_RTC_DRV_M41ST85W)	+= rtc-m41st85w.o
 obj-$(CONFIG_RTC_DRV_M48T35)	+= rtc-m48t35.o
 obj-$(CONFIG_RTC_DRV_M48T59)	+= rtc-m48t59.o
 obj-$(CONFIG_RTC_DRV_M48T86)	+= rtc-m48t86.o
diff -uNr a/drivers/rtc/rtc-m41st85w.c b/drivers/rtc/rtc-m41st85w.c
--- a/drivers/rtc/rtc-m41st85w.c	1970-01-01 03:00:00.000000000 +0300
+++ b/drivers/rtc/rtc-m41st85w.c	2010-12-11 11:32:09.000000000 +0200
@@ -0,0 +1,270 @@ 
+/*
+ * rtc-m41st85w.c
+ *
+ * Device driver for ST's Real Time Controller M41ST85W.
+ * Copyright (C) 2002 Intrinsyc Software Inc.
+ * Copyright (C) 2003 Intel Corp.
+ * Copyright (C) 2010 Artem Kukhta
+ *
+ * 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.
+ *
+ * This driver is adapted from the DS1307 driver originally
+ * Kernel time sync borrowed from RMK's acorn RTC driver
+ * Ported to the latest kernel from the M41ST85W driver iop-2.6.24 patches
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/i2c.h>
+#include <linux/rtc.h>
+#include <linux/slab.h>
+#include <linux/bcd.h>
+
+#define M41ST85W_REG_SECONDS	0x01
+#define M41ST85W_REG_MINUTES	0x02
+#define M41ST85W_REG_HOURS	0x03
+#define M41ST85W_REG_DAY	0x04
+#define M41ST85W_REG_DATE	0x05
+#define M41ST85W_REG_MONTH	0x06
+#define M41ST85W_REG_YEAR	0x07
+#define M41ST85W_REG_ALMONTH	0x0A	/* Alarm month */
+#define M41ST85W_REG_ALHOUR	0x0C	/* Alarm month */
+#define M41ST85W_REG_SQW	0x13
+
+#define SQW_ENABLE		0x40	/* Square Wave Enable */
+#define SQW_DISABLE		0x00	/* Square Wave disable */
+
+#define RATE_32768HZ		0x10	/* Rate Select 32.768KHz */
+#define RATE_8192HZ		0x20	/* Rate Select 8.192KHz */
+#define RATE_4096HZ		0x30	/* Rate Select 4.096KHz */
+#define RATE_2048HZ		0x40    /* Rate Select 2.048Khz */
+#define RATE_1024HZ		0x50    /* Rate Select 1.024Khz */
+#define RATE_512HZ		0x60    /* Rate Select 0.512Khz */
+#define RATE_256HZ		0x70    /* Rate Select 0.256Khz */
+#define RATE_128HZ		0x80    /* Rate Select 0.128Khz */
+#define RATE_64HZ		0x90    /* Rate Select 0.064Khz */
+#define RATE_32HZ		0xa0    /* Rate Select 0.032Khz */
+#define RATE_16HZ		0xb0    /* Rate Select 0.016Khz */
+#define RATE_8HZ		0xc0    /* Rate Select 0.008Khz */
+#define RATE_4HZ		0xd0    /* Rate Select 0.004Khz */
+#define RATE_2HZ		0xe0    /* Rate Select 0.0001Khz */
+#define RATE_1HZ		0xf0	/* Rate Select 1Hz */
+#define RATE_0HZ		0x00    /* Rate Select None */
+
+#define CLOCK_STOP		0x80	/* Clock Stop */
+#define CLOCK_HALT		0x40    /* Clock Halt */
+
+
+
+struct m41st85w {
+	struct i2c_client *client;
+	struct rtc_device *rtc;
+};
+
+static struct i2c_driver m41st85w_driver;
+
+static int m41st85w_set_ctrl(struct i2c_client *client, int *cinfo)
+{
+	int ret;
+
+	/* need to set square wave first */
+	ret = i2c_smbus_write_byte_data(client, M41ST85W_REG_SQW, cinfo[1]);
+	if (ret < 0)
+		return ret;
+
+	return i2c_smbus_write_byte_data(client, M41ST85W_REG_ALMONTH, cinfo[0]);
+}
+
+static void m41st85w_enable_clock(struct i2c_client *client, int enable)
+{
+	int buf1, buf2;
+	int ctrl_info[2];
+	int ret;
+
+	if (enable) {
+		ctrl_info[0] = SQW_ENABLE;
+		ctrl_info[1] = RATE_32768HZ;
+	} else {
+		ctrl_info[0] = SQW_DISABLE;
+		ctrl_info[1] = RATE_0HZ;
+	}
+	ret = m41st85w_set_ctrl(client, ctrl_info);
+	if (ret < 0)
+		printk(KERN_ERR "%s %d: m41st85w_set_ctrl failed\n",
+				__func__, __LINE__);
+
+	/* read Clock-Halt bit and second counter */
+	buf1 = i2c_smbus_read_byte_data(client, M41ST85W_REG_ALHOUR);
+	if (buf1 < 0)
+		printk(KERN_ERR "%s %d: i2c_smbus_read_byte_data failed\n",
+				__func__, __LINE__);
+
+	buf2 = i2c_smbus_read_byte_data(client, M41ST85W_REG_SECONDS);
+	if (buf2 < 0)
+		printk(KERN_ERR "%s %d: i2c_smbus_read_byte_data failed\n",
+				__func__, __LINE__);
+
+
+	if (enable) {
+		buf1 &= ~CLOCK_HALT; /* clear Clock-Halt bit */
+		buf2 &= ~CLOCK_STOP;
+	} else {
+		buf1 |= CLOCK_HALT; /* set Clock-Halt bit */
+		buf2 |= CLOCK_STOP;
+	}
+
+	ret = i2c_smbus_write_byte_data(client, M41ST85W_REG_ALHOUR, buf1);
+	if (ret < 0)
+		printk(KERN_ERR "%s %d: i2c_smbus_write_byte_data failed\n",
+				__func__, __LINE__);
+
+	ret = i2c_smbus_write_byte_data(client, M41ST85W_REG_SECONDS, buf2);
+	if (ret < 0)
+		printk(KERN_ERR "%s %d: i2c_smbus_write_byte_data failed\n",
+				__func__, __LINE__);
+}
+
+static int m41st85w_read_time(struct device *dev, struct rtc_time *dt)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	u8 buf[7];
+	int ret;
+	unsigned int year, month, day, week, hour, minute, second;
+
+	ret = i2c_smbus_read_i2c_block_data(client, M41ST85W_REG_SECONDS, 7, buf);
+
+	if (ret < 0)
+		return ret;
+	if (ret < 7)
+		return -EIO;
+
+	second = buf[0];
+	minute = buf[1];
+	hour = buf[2];
+	week = buf[3];
+	day = buf[4];
+	month = buf[5];
+	year = buf[6];
+
+	dt->tm_sec = bcd2bin(second & 0x7f);
+	dt->tm_min = bcd2bin(minute & 0x7f);
+	dt->tm_hour = bcd2bin(hour & 0x3f);
+	dt->tm_wday = bcd2bin(week & 0x07);
+	dt->tm_mday = bcd2bin(day & 0x7f);
+	dt->tm_mon = bcd2bin(month & 0x3f);
+	dt->tm_year = bcd2bin(year);
+
+	/* fix up values */
+	/* dt->tm_mon is zero-based */
+	dt->tm_mon--;
+	/* year is 1900 + dt->tm_year */
+	if (dt->tm_year < 95)
+		dt->tm_year += 100;
+
+	dt->tm_isdst = 0;
+
+	return rtc_valid_tm(dt);
+}
+
+static int
+		m41st85w_set_time(struct device *dev, struct rtc_time *dt)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	u8 buf[7];
+
+	buf[0] = bin2bcd(dt->tm_sec);
+	buf[1] = bin2bcd(dt->tm_min);
+	buf[2] = bin2bcd(dt->tm_hour);
+	buf[3] = bin2bcd(dt->tm_wday);
+	buf[4] = bin2bcd(dt->tm_mday);
+	buf[5] = bin2bcd(dt->tm_mon + 1);
+	/* 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[6] = bin2bcd(dt->tm_year - 100);
+
+	return i2c_smbus_write_i2c_block_data(client,
+				      M41ST85W_REG_SECONDS, 7, buf);
+}
+
+static struct rtc_class_ops m41st85w_rtc_ops = {
+	.read_time = m41st85w_read_time,
+	.set_time = m41st85w_set_time,
+};
+
+static int __devinit m41st85w_probe(struct i2c_client *client,
+		const struct i2c_device_id *id)
+{
+	struct m41st85w *m41st85w;
+	int ret;
+
+	m41st85w = kzalloc(sizeof(struct m41st85w), GFP_KERNEL);
+	if (!m41st85w)
+		return -ENOMEM;
+
+	m41st85w->client = client;
+	i2c_set_clientdata(client, m41st85w);
+
+	m41st85w_enable_clock(client, 1);
+
+	m41st85w->rtc = rtc_device_register(client->name, &client->dev,
+					  &m41st85w_rtc_ops, THIS_MODULE);
+	if (IS_ERR(m41st85w->rtc)) {
+		ret = PTR_ERR(m41st85w->rtc);
+		dev_err(&client->dev, "unable to register the class device\n");
+		goto out_free;
+	}
+
+	return 0;
+
+out_free:
+	kfree(m41st85w);
+	return ret;
+}
+
+static int __devexit m41st85w_remove(struct i2c_client *client)
+{
+	struct m41st85w *m41st85w = i2c_get_clientdata(client);
+
+	m41st85w_enable_clock(client, 0);
+	rtc_device_unregister(m41st85w->rtc);
+	kfree(m41st85w);
+	return 0;
+}
+
+static struct i2c_device_id m41st85w_id[] = {
+	{ "m41st85w", 0 },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, m41st85w_id);
+
+static struct i2c_driver m41st85w_driver = {
+	.driver = {
+		.name = "rtc-m41st85w",
+		.owner = THIS_MODULE,
+	},
+	.probe = m41st85w_probe,
+	.remove = __devexit_p(m41st85w_remove),
+	.id_table = m41st85w_id,
+};
+
+static int __init m41st85w_init(void)
+{
+	return i2c_add_driver(&m41st85w_driver);
+}
+
+static void __exit m41st85w_exit(void)
+{
+	i2c_del_driver(&m41st85w_driver);
+}
+
+module_init(m41st85w_init);
+module_exit(m41st85w_exit);
+
+MODULE_AUTHOR("Intel Corp.");
+MODULE_DESCRIPTION("ST M41ST85W RTC Driver");
+MODULE_LICENSE("GPL");