diff -Nru a/drivers/i2c/chips/Kconfig b/drivers/i2c/chips/Kconfig
--- a/drivers/i2c/chips/Kconfig 2005-01-31 10:33:34 -07:00
+++ b/drivers/i2c/chips/Kconfig 2005-01-31 10:33:34 -07:00
@@ -371,4 +371,13 @@
This driver can also be built as a module. If so, the module
will be called isp1301_omap.
+config SENSORS_M41T00
+ tristate "ST M41T00 RTC chip"
+ depends on I2C && PPC32
+ help
+ If you say yes here you get support for the ST M41T00 RTC chip.
+
+ This driver can also be built as a module. If so, the module
+ will be called i2c-m41t00.
+
endmenu
diff -Nru a/drivers/i2c/chips/Makefile b/drivers/i2c/chips/Makefile
--- a/drivers/i2c/chips/Makefile 2005-01-31 10:33:34 -07:00
+++ b/drivers/i2c/chips/Makefile 2005-01-31 10:33:34 -07:00
@@ -26,6 +26,7 @@
obj-$(CONFIG_SENSORS_LM87) += lm87.o
obj-$(CONFIG_SENSORS_LM90) += lm90.o
obj-$(CONFIG_SENSORS_MAX1619) += max1619.o
+obj-$(CONFIG_SENSORS_M41T00) += m41t00.o
obj-$(CONFIG_SENSORS_PC87360) += pc87360.o
obj-$(CONFIG_SENSORS_PCF8574) += pcf8574.o
obj-$(CONFIG_SENSORS_PCF8591) += pcf8591.o
diff -Nru a/drivers/i2c/chips/m41t00.c b/drivers/i2c/chips/m41t00.c
--- /dev/null Wed Dec 31 16:00:00 196900
+++ b/drivers/i2c/chips/m41t00.c 2005-01-31 10:33:34 -07:00
@@ -0,0 +1,224 @@
+/*
+ * drivers/i2c/chips/m41t00.c
+ *
+ * I2C client/driver for the ST M41T00 Real-Time Clock chip.
+ *
+ * Author: Mark A. Greer <[email protected]>
+ *
+ * 2005 (c) MontaVista Software, Inc. This file is licensed under
+ * the terms of the GNU General Public License version 2. This program
+ * is licensed "as is" without any warranty of any kind, whether express
+ * or implied.
+ */
+/*
+ * 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/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>
+
+static DECLARE_MUTEX(m41t00_mutex);
+
+struct m41t00_data {
+ struct i2c_client client;
+};
+
+static struct i2c_driver m41t00_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,
+ .normal_i2c_range = ignore,
+ .probe = ignore,
+ .probe_range = ignore,
+ .ignore = ignore,
+ .ignore_range = ignore,
+ .force = ignore,
+};
+
+ulong
+m41t00_get_rtc_time(void)
+{
+ s32 sec, min, hour, day, mon, year;
+
+ down(&m41t00_mutex);
+ if (((sec = i2c_smbus_read_byte_data(save_client, 0)) < 0)
+ || ((min = i2c_smbus_read_byte_data(save_client, 1)) < 0)
+ || ((hour = i2c_smbus_read_byte_data(save_client, 2)) < 0)
+ || ((day = i2c_smbus_read_byte_data(save_client, 4)) < 0)
+ || ((mon = i2c_smbus_read_byte_data(save_client, 5)) < 0)
+ || ((year = i2c_smbus_read_byte_data(save_client, 6)) < 0)) {
+
+ dev_warn(&save_client->dev, "m41t00: can't read rtc chip\n");
+ sec = min = hour = day = mon = year = 0;
+ }
+ up(&m41t00_mutex);
+
+ sec &= 0x7f;
+ min &= 0x7f;
+ hour &= 0x3f;
+ day &= 0x3f;
+ mon &= 0x1f;
+ year &= 0xff;
+
+ BCD_TO_BIN(sec);
+ BCD_TO_BIN(min);
+ BCD_TO_BIN(hour);
+ BCD_TO_BIN(day);
+ BCD_TO_BIN(mon);
+ BCD_TO_BIN(year);
+
+ year += 1900;
+ if (year < 1970)
+ year += 100;
+
+ return mktime(year, mon, day, hour, min, sec);
+}
+
+static void
+m41t00_set_tlet(ulong arg)
+{
+ struct rtc_time tm;
+ ulong nowtime = *(ulong *)arg;
+
+ to_tm(nowtime, &tm);
+ tm.tm_year = (tm.tm_year - 1900) % 100;
+
+ BIN_TO_BCD(tm.tm_sec);
+ BIN_TO_BCD(tm.tm_min);
+ BIN_TO_BCD(tm.tm_hour);
+ BIN_TO_BCD(tm.tm_mon);
+ BIN_TO_BCD(tm.tm_mday);
+ BIN_TO_BCD(tm.tm_year);
+
+ down(&m41t00_mutex);
+ if ((i2c_smbus_write_byte_data(save_client, 0, tm.tm_sec & 0x7f) < 0)
+ || (i2c_smbus_write_byte_data(save_client, 1, tm.tm_min & 0x7f)
+ < 0)
+ || (i2c_smbus_write_byte_data(save_client, 2, tm.tm_hour & 0x7f)
+ < 0)
+ || (i2c_smbus_write_byte_data(save_client, 4, tm.tm_mday & 0x7f)
+ < 0)
+ || (i2c_smbus_write_byte_data(save_client, 5, tm.tm_mon & 0x7f)
+ < 0)
+ || (i2c_smbus_write_byte_data(save_client, 6, tm.tm_year & 0x7f)
+ < 0))
+
+ dev_warn(&save_client->dev,"m41t00: can't write to rtc chip\n");
+
+ up(&m41t00_mutex);
+ return;
+}
+
+ulong new_time;
+
+DECLARE_TASKLET_DISABLED(m41t00_tasklet, m41t00_set_tlet, (ulong)&new_time);
+
+int
+m41t00_set_rtc_time(ulong nowtime)
+{
+ new_time = nowtime;
+
+ if (in_interrupt())
+ tasklet_schedule(&m41t00_tasklet);
+ else
+ m41t00_set_tlet((ulong)&new_time);
+
+ return 0;
+}
+
+/*
+ *****************************************************************************
+ *
+ * Driver Interface
+ *
+ *****************************************************************************
+ */
+static int __devinit
+m41t00_probe(struct i2c_adapter *adap, int addr, int kind)
+{
+ struct i2c_client *new_client;
+ struct m41t00_data *drv_data;
+ int rc;
+
+ drv_data = kmalloc(sizeof(struct m41t00_data), GFP_KERNEL);
+ if (!drv_data)
+ return -ENOMEM;
+
+ memset(drv_data, 0, sizeof(struct m41t00_data));
+ new_client = &drv_data->client;
+
+ strncpy(new_client->name, "m41t00", I2C_NAME_SIZE);
+ i2c_set_clientdata(new_client, drv_data);
+ new_client->id = m41t00_driver.id;
+ new_client->flags = I2C_DF_NOTIFY;
+ new_client->addr = addr;
+ new_client->adapter = adap;
+ new_client->driver = &m41t00_driver;
+
+ if ((rc = i2c_attach_client(new_client)) != 0) {
+ kfree(drv_data);
+ return rc;
+ }
+
+ save_client = new_client;
+ return 0;
+}
+
+static int __devinit
+m41t00_attach(struct i2c_adapter *adap)
+{
+ return i2c_probe(adap, &addr_data, m41t00_probe);
+}
+
+static int __devexit
+m41t00_detach(struct i2c_client *client)
+{
+ i2c_detach_client(client);
+ kfree(i2c_get_clientdata(client));
+ tasklet_kill(&m41t00_tasklet);
+ return 0;
+}
+
+static struct i2c_driver m41t00_driver = {
+ .owner = THIS_MODULE,
+ .name = "M41T00",
+ .id = I2C_DRIVERID_STM41T00,
+ .flags = I2C_DF_NOTIFY,
+ .attach_adapter = m41t00_attach,
+ .detach_client = m41t00_detach,
+};
+
+static int __devinit
+m41t00_init(void)
+{
+ return i2c_add_driver(&m41t00_driver);
+}
+
+static void __devexit
+m41t00_exit(void)
+{
+ i2c_del_driver(&m41t00_driver);
+ return;
+}
+
+module_init(m41t00_init);
+module_exit(m41t00_exit);
+
+MODULE_AUTHOR("Mark A. Greer <[email protected]>");
+MODULE_DESCRIPTION("ST Microelectronics M41T00 RTC I2C Client Driver");
+MODULE_LICENSE("GPL");
On ix86 machines, it is appropriate to read the RTC clock
several times in a row until nothing changes. This protects
against getting bad readings when some values wrap (like
seconds). You can't stop the clock when you read it
or you will lose time. I don't see anything like
this in your code. Also, when setting the clock it
is necessary to stop the clock so its settings don't
change while you are writing a new time. I also don't
see anything like this in your code either.
Also ix86 machines have a spin-lock, rtc_lock, so that
other procedures, even interrupts can access its registers.
I see you using a semaphore that can't be used in interrupt
context.
Notice: the RTC clock is used for high-precision timing
via interrupt in some ix86 drivers. If you modify the
RTC code on all platforms as you propose, you cannot
"keep" the RTC all to your self. Its interrupt must
be available to drivers and access to the hardware needs
to be locked with the existing spin-lock.
I note that on 2.6.9 on somebody broke it so that, the interrupt is
used for something only ONCE during startup and ONCE during shut-down.
CPU0
0: 6225448 IO-APIC-edge timer
1: 14002 IO-APIC-edge i8042
8: 1 IO-APIC-edge rtc <--- bingo
9: 0 IO-APIC-level acpi
[snipped...]
I don't have a clue why anybody would grab that interrupt. Fortunately,
the interrupt-grabbing code can be loaded as a module and then
unloaded so that other drivers can use that device and its interrupt.
Any changes to the RTC code need to consider these things.
On Mon, 31 Jan 2005, Mark A. Greer wrote:
> This patch adds support for the ST M41T00 RTC chip.
>
> You will likely notice that it implements a PPC-specific interface
> (/dev/rtc->drivers/char/genrtc.h->include/asm-ppc/rtc.h->this file). This
> was necessary to support a subset of ppc platforms that need to hook up the
> rtc support at runtime. If I implemented /dev/rtc directly or interfaced to
> genrtc.c directly, those platforms couldn't use this driver. Eventually, I
> hope to work on more uniform rtc support across all the processor
> architectures.
>
> Also, on ppc at least, the hw clock can be set from a timer interrupt if
> STA_UNSYNC is not set (e.g., ntpd is running). To handle this, a tasklet is
> used to set the clock if in_interrupt() is true.
>
> I'd appreciate an comments or to have it pushed into the kernel.org tree if
> its acceptable.
>
> Thanks,
>
> Mark
>
> Signed-off-by: Mark A. Greer <[email protected]>
> --
>
>
Cheers,
Dick Johnson
Penguin : Linux version 2.6.9 on an i686 machine (5537.79 BogoMips).
Notice : All mail here is now cached for review by Dictator Bush.
98.36% of all statistics are fiction.
Hi Mark,
> This patch adds support for the ST M41T00 RTC chip.
As for your other driver, I lack the device-specific knowledge to
comment on the functionality, but still have some technical comments on
the code itself:
> + This driver can also be built as a module. If so, the module
> + will be called i2c-m41t00.
It'll actually be called m41t00, according to the Makefile.
> +struct m41t00_data {
> + struct i2c_client client;
> +};
You don't have to do that. Including the i2c_client stucture in the data
structure is a trick which let us get both allocated with a single
kmalloc (and freed with a single kfree) while still respecting the
arch-dependent alignment requirements. If you have no private data to
carry around, you can do the kmalloc on the i2c_client structure
directly, and have client->data point to NULL (which it actually already
does thanks to memset). This will save some code in both the detection
and the detach functions.
However, if you know that, in a future update of this driver, you *will*
have to store client-private data, then I guess you can keep it this way.
> + i2c_detach_client(client);
This one supposedly can fail.
> + .name = "M41T00",
No caps in name please (will be used in sysfs).
> +static int __devinit
> +m41t00_init(void)
> +{
> + return i2c_add_driver(&m41t00_driver);
> +}
>
> +static void __devexit
> +m41t00_exit(void)
> +{
> + i2c_del_driver(&m41t00_driver);
> + return;
> +}
Should be __init and __exit, respectively, unless I am mistaken. And the
last return is usless.
I'm also suspicious about the other __devexit and __devinit you used. No
other i2c chip drivers has them.
Thanks,
--
Jean Delvare
linux-os wrote:
>
> On ix86 machines, it is appropriate to read the RTC clock
> several times in a row until nothing changes. This protects
> against getting bad readings when some values wrap (like
> seconds). You can't stop the clock when you read it
> or you will lose time. I don't see anything like this in your code.
> Also, when setting the clock it
> is necessary to stop the clock so its settings don't
> change while you are writing a new time. I also don't
> see anything like this in your code either.
This particular RTC chip provide no mechanism to manually stop the clock
for reading or writing. However, when you begin reading the clock
registers, it delays updating the externally visible register values for
250ms but contiues to keep time internally so time isn't lost. I can't
find anything in the manual WRT updating the clock.
There is a problem that if all the register reads don't happen within
the 250ms window, it could return a bad value if a register wraps.
Unless someone else points out why this is a bad idea, I'll add a loop
to ensure the same values were read twice in a row.
>
>
> Also ix86 machines have a spin-lock, rtc_lock, so that
> other procedures, even interrupts can access its registers.
> I see you using a semaphore that can't be used in interrupt
> context.
This is an I2C based RTC chip and the I2C subsystem used to access it
assumes (AFAICT) that its not called from an interrupt handler (e.g.,
drivers/i2c/i2c-core.c:i2c_transfer calls() calls down()/up()). So I
need to handle an interrupt handler calling this driver which then calls
into code that assumes its not in an interrupt handler. That's why the
'set' routine schedules a tasklet if its in an interrupt handler. In
ppc, at least, the 'get' routine isn't called from an interrupt so its
not an issue.
>
>
> Notice: the RTC clock is used for high-precision timing
> via interrupt in some ix86 drivers. If you modify the
> RTC code on all platforms as you propose, you cannot
> "keep" the RTC all to your self. Its interrupt must
> be available to drivers and access to the hardware needs
> to be locked with the existing spin-lock.
Hmm, interesting. Note that this RTC doesn't generate an
interrupt/initiate an i2c transaction.
>
>
> I note that on 2.6.9 on somebody broke it so that, the interrupt is
> used for something only ONCE during startup and ONCE during shut-down.
>
> CPU0
> 0: 6225448 IO-APIC-edge timer
> 1: 14002 IO-APIC-edge i8042
> 8: 1 IO-APIC-edge rtc <--- bingo
> 9: 0 IO-APIC-level acpi
> [snipped...]
>
> I don't have a clue why anybody would grab that interrupt. Fortunately,
> the interrupt-grabbing code can be loaded as a module and then
> unloaded so that other drivers can use that device and its interrupt.
> Any changes to the RTC code need to consider these things.
Okay, thanks for the insight.
Mark
diff -Nru a/drivers/i2c/chips/Kconfig b/drivers/i2c/chips/Kconfig
--- a/drivers/i2c/chips/Kconfig 2005-02-01 13:37:08 -07:00
+++ b/drivers/i2c/chips/Kconfig 2005-02-01 13:37:08 -07:00
@@ -371,4 +371,13 @@
This driver can also be built as a module. If so, the module
will be called isp1301_omap.
+config SENSORS_M41T00
+ tristate "ST M41T00 RTC chip"
+ depends on I2C && PPC32
+ help
+ If you say yes here you get support for the ST M41T00 RTC chip.
+
+ This driver can also be built as a module. If so, the module
+ will be called m41t00.
+
endmenu
diff -Nru a/drivers/i2c/chips/Makefile b/drivers/i2c/chips/Makefile
--- a/drivers/i2c/chips/Makefile 2005-02-01 13:37:08 -07:00
+++ b/drivers/i2c/chips/Makefile 2005-02-01 13:37:08 -07:00
@@ -26,6 +26,7 @@
obj-$(CONFIG_SENSORS_LM87) += lm87.o
obj-$(CONFIG_SENSORS_LM90) += lm90.o
obj-$(CONFIG_SENSORS_MAX1619) += max1619.o
+obj-$(CONFIG_SENSORS_M41T00) += m41t00.o
obj-$(CONFIG_SENSORS_PC87360) += pc87360.o
obj-$(CONFIG_SENSORS_PCF8574) += pcf8574.o
obj-$(CONFIG_SENSORS_PCF8591) += pcf8591.o
diff -Nru a/drivers/i2c/chips/m41t00.c b/drivers/i2c/chips/m41t00.c
--- /dev/null Wed Dec 31 16:00:00 196900
+++ b/drivers/i2c/chips/m41t00.c 2005-02-01 13:37:08 -07:00
@@ -0,0 +1,247 @@
+/*
+ * drivers/i2c/chips/m41t00.c
+ *
+ * I2C client/driver for the ST M41T00 Real-Time Clock chip.
+ *
+ * Author: Mark A. Greer <[email protected]>
+ *
+ * 2005 (c) MontaVista Software, Inc. This file is licensed under
+ * the terms of the GNU General Public License version 2. This program
+ * is licensed "as is" without any warranty of any kind, whether express
+ * or implied.
+ */
+/*
+ * 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/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 M41T00_DRV_NAME "m41t00"
+
+static DECLARE_MUTEX(m41t00_mutex);
+
+static struct i2c_driver m41t00_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,
+ .normal_i2c_range = ignore,
+ .probe = ignore,
+ .probe_range = ignore,
+ .ignore = ignore,
+ .ignore_range = ignore,
+ .force = ignore,
+};
+
+ulong
+m41t00_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(&m41t00_mutex);
+ do {
+ if (((sec = i2c_smbus_read_byte_data(save_client, 0)) >= 0)
+ && ((min = i2c_smbus_read_byte_data(save_client, 1))
+ >= 0)
+ && ((hour = i2c_smbus_read_byte_data(save_client, 2))
+ >= 0)
+ && ((day = i2c_smbus_read_byte_data(save_client, 4))
+ >= 0)
+ && ((mon = i2c_smbus_read_byte_data(save_client, 5))
+ >= 0)
+ && ((year = i2c_smbus_read_byte_data(save_client, 6))
+ >= 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(&m41t00_mutex);
+
+ if (limit == 0) {
+ dev_warn(&save_client->dev,
+ "m41t00: can't read rtc chip\n");
+ sec = min = hour = day = mon = year = 0;
+ }
+
+ sec &= 0x7f;
+ min &= 0x7f;
+ hour &= 0x3f;
+ day &= 0x3f;
+ mon &= 0x1f;
+ year &= 0xff;
+
+ BCD_TO_BIN(sec);
+ BCD_TO_BIN(min);
+ BCD_TO_BIN(hour);
+ BCD_TO_BIN(day);
+ BCD_TO_BIN(mon);
+ BCD_TO_BIN(year);
+
+ year += 1900;
+ if (year < 1970)
+ year += 100;
+
+ return mktime(year, mon, day, hour, min, sec);
+}
+
+static void
+m41t00_set_tlet(ulong arg)
+{
+ struct rtc_time tm;
+ ulong nowtime = *(ulong *)arg;
+
+ to_tm(nowtime, &tm);
+ tm.tm_year = (tm.tm_year - 1900) % 100;
+
+ BIN_TO_BCD(tm.tm_sec);
+ BIN_TO_BCD(tm.tm_min);
+ BIN_TO_BCD(tm.tm_hour);
+ BIN_TO_BCD(tm.tm_mon);
+ BIN_TO_BCD(tm.tm_mday);
+ BIN_TO_BCD(tm.tm_year);
+
+ down(&m41t00_mutex);
+ if ((i2c_smbus_write_byte_data(save_client, 0, tm.tm_sec & 0x7f) < 0)
+ || (i2c_smbus_write_byte_data(save_client, 1, tm.tm_min & 0x7f)
+ < 0)
+ || (i2c_smbus_write_byte_data(save_client, 2, tm.tm_hour & 0x7f)
+ < 0)
+ || (i2c_smbus_write_byte_data(save_client, 4, tm.tm_mday & 0x7f)
+ < 0)
+ || (i2c_smbus_write_byte_data(save_client, 5, tm.tm_mon & 0x7f)
+ < 0)
+ || (i2c_smbus_write_byte_data(save_client, 6, tm.tm_year & 0x7f)
+ < 0))
+
+ dev_warn(&save_client->dev,"m41t00: can't write to rtc chip\n");
+
+ up(&m41t00_mutex);
+ return;
+}
+
+ulong new_time;
+
+DECLARE_TASKLET_DISABLED(m41t00_tasklet, m41t00_set_tlet, (ulong)&new_time);
+
+int
+m41t00_set_rtc_time(ulong nowtime)
+{
+ new_time = nowtime;
+
+ if (in_interrupt())
+ tasklet_schedule(&m41t00_tasklet);
+ else
+ m41t00_set_tlet((ulong)&new_time);
+
+ return 0;
+}
+
+/*
+ *****************************************************************************
+ *
+ * Driver Interface
+ *
+ *****************************************************************************
+ */
+static int
+m41t00_probe(struct i2c_adapter *adap, int addr, int kind)
+{
+ struct i2c_client *client;
+ int rc;
+
+ client = kmalloc(sizeof(struct i2c_client), GFP_KERNEL);
+ if (!client)
+ return -ENOMEM;
+
+ memset(client, 0, sizeof(struct i2c_client));
+ strncpy(client->name, M41T00_DRV_NAME, I2C_NAME_SIZE);
+ client->id = m41t00_driver.id;
+ client->flags = I2C_DF_NOTIFY;
+ client->addr = addr;
+ client->adapter = adap;
+ client->driver = &m41t00_driver;
+
+ if ((rc = i2c_attach_client(client)) != 0) {
+ kfree(client);
+ return rc;
+ }
+
+ save_client = client;
+ return 0;
+}
+
+static int
+m41t00_attach(struct i2c_adapter *adap)
+{
+ return i2c_probe(adap, &addr_data, m41t00_probe);
+}
+
+static int
+m41t00_detach(struct i2c_client *client)
+{
+ int rc;
+
+ if ((rc = i2c_detach_client(client)) == 0) {
+ kfree(i2c_get_clientdata(client));
+ tasklet_kill(&m41t00_tasklet);
+ }
+ return rc;
+}
+
+static struct i2c_driver m41t00_driver = {
+ .owner = THIS_MODULE,
+ .name = M41T00_DRV_NAME,
+ .id = I2C_DRIVERID_STM41T00,
+ .flags = I2C_DF_NOTIFY,
+ .attach_adapter = m41t00_attach,
+ .detach_client = m41t00_detach,
+};
+
+static int __init
+m41t00_init(void)
+{
+ return i2c_add_driver(&m41t00_driver);
+}
+
+static void __exit
+m41t00_exit(void)
+{
+ i2c_del_driver(&m41t00_driver);
+ return;
+}
+
+module_init(m41t00_init);
+module_exit(m41t00_exit);
+
+MODULE_AUTHOR("Mark A. Greer <[email protected]>");
+MODULE_DESCRIPTION("ST Microelectronics M41T00 RTC I2C Client Driver");
+MODULE_LICENSE("GPL");
From http://archives.andrew.net.au/lm-sensors/msg29319.html:
Mark A. Greer wrote:
> <snip>
>
> Here is a replacement patch that should address Jean Delvare and Dick
> Johnson's issues. Please let me know if there are any more issues.
>
> Thanks,
>
> Mark
>
> Signed-off-by: Mark A. Greer <[email protected]>
> --
I haven't heard of any more problems so can I get this patch applied?
Thanks,
Mark
On Fri, Feb 04, 2005 at 04:14:51PM -0700, Mark A. Greer wrote:
> From http://archives.andrew.net.au/lm-sensors/msg29319.html:
>
> Mark A. Greer wrote:
>
> ><snip>
> >
> >Here is a replacement patch that should address Jean Delvare and Dick
> >Johnson's issues. Please let me know if there are any more issues.
> >
> >Thanks,
> >
> >Mark
> >
> >Signed-off-by: Mark A. Greer <[email protected]>
> >--
>
>
> I haven't heard of any more problems so can I get this patch applied?
Can you resend it with a proper Changelog description in the top of the
email and the signed-off-by line? thanks,
greg k-h
diff -Nru a/drivers/i2c/chips/Kconfig b/drivers/i2c/chips/Kconfig
--- a/drivers/i2c/chips/Kconfig 2005-02-04 17:00:45 -07:00
+++ b/drivers/i2c/chips/Kconfig 2005-02-04 17:00:45 -07:00
@@ -371,4 +371,13 @@
This driver can also be built as a module. If so, the module
will be called isp1301_omap.
+config SENSORS_M41T00
+ tristate "ST M41T00 RTC chip"
+ depends on I2C && PPC32
+ help
+ If you say yes here you get support for the ST M41T00 RTC chip.
+
+ This driver can also be built as a module. If so, the module
+ will be called m41t00.
+
endmenu
diff -Nru a/drivers/i2c/chips/Makefile b/drivers/i2c/chips/Makefile
--- a/drivers/i2c/chips/Makefile 2005-02-04 17:00:45 -07:00
+++ b/drivers/i2c/chips/Makefile 2005-02-04 17:00:45 -07:00
@@ -26,6 +26,7 @@
obj-$(CONFIG_SENSORS_LM87) += lm87.o
obj-$(CONFIG_SENSORS_LM90) += lm90.o
obj-$(CONFIG_SENSORS_MAX1619) += max1619.o
+obj-$(CONFIG_SENSORS_M41T00) += m41t00.o
obj-$(CONFIG_SENSORS_PC87360) += pc87360.o
obj-$(CONFIG_SENSORS_PCF8574) += pcf8574.o
obj-$(CONFIG_SENSORS_PCF8591) += pcf8591.o
diff -Nru a/drivers/i2c/chips/m41t00.c b/drivers/i2c/chips/m41t00.c
--- /dev/null Wed Dec 31 16:00:00 196900
+++ b/drivers/i2c/chips/m41t00.c 2005-02-04 17:00:45 -07:00
@@ -0,0 +1,247 @@
+/*
+ * drivers/i2c/chips/m41t00.c
+ *
+ * I2C client/driver for the ST M41T00 Real-Time Clock chip.
+ *
+ * Author: Mark A. Greer <[email protected]>
+ *
+ * 2005 (c) MontaVista Software, Inc. This file is licensed under
+ * the terms of the GNU General Public License version 2. This program
+ * is licensed "as is" without any warranty of any kind, whether express
+ * or implied.
+ */
+/*
+ * 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/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 M41T00_DRV_NAME "m41t00"
+
+static DECLARE_MUTEX(m41t00_mutex);
+
+static struct i2c_driver m41t00_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,
+ .normal_i2c_range = ignore,
+ .probe = ignore,
+ .probe_range = ignore,
+ .ignore = ignore,
+ .ignore_range = ignore,
+ .force = ignore,
+};
+
+ulong
+m41t00_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(&m41t00_mutex);
+ do {
+ if (((sec = i2c_smbus_read_byte_data(save_client, 0)) >= 0)
+ && ((min = i2c_smbus_read_byte_data(save_client, 1))
+ >= 0)
+ && ((hour = i2c_smbus_read_byte_data(save_client, 2))
+ >= 0)
+ && ((day = i2c_smbus_read_byte_data(save_client, 4))
+ >= 0)
+ && ((mon = i2c_smbus_read_byte_data(save_client, 5))
+ >= 0)
+ && ((year = i2c_smbus_read_byte_data(save_client, 6))
+ >= 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(&m41t00_mutex);
+
+ if (limit == 0) {
+ dev_warn(&save_client->dev,
+ "m41t00: can't read rtc chip\n");
+ sec = min = hour = day = mon = year = 0;
+ }
+
+ sec &= 0x7f;
+ min &= 0x7f;
+ hour &= 0x3f;
+ day &= 0x3f;
+ mon &= 0x1f;
+ year &= 0xff;
+
+ BCD_TO_BIN(sec);
+ BCD_TO_BIN(min);
+ BCD_TO_BIN(hour);
+ BCD_TO_BIN(day);
+ BCD_TO_BIN(mon);
+ BCD_TO_BIN(year);
+
+ year += 1900;
+ if (year < 1970)
+ year += 100;
+
+ return mktime(year, mon, day, hour, min, sec);
+}
+
+static void
+m41t00_set_tlet(ulong arg)
+{
+ struct rtc_time tm;
+ ulong nowtime = *(ulong *)arg;
+
+ to_tm(nowtime, &tm);
+ tm.tm_year = (tm.tm_year - 1900) % 100;
+
+ BIN_TO_BCD(tm.tm_sec);
+ BIN_TO_BCD(tm.tm_min);
+ BIN_TO_BCD(tm.tm_hour);
+ BIN_TO_BCD(tm.tm_mon);
+ BIN_TO_BCD(tm.tm_mday);
+ BIN_TO_BCD(tm.tm_year);
+
+ down(&m41t00_mutex);
+ if ((i2c_smbus_write_byte_data(save_client, 0, tm.tm_sec & 0x7f) < 0)
+ || (i2c_smbus_write_byte_data(save_client, 1, tm.tm_min & 0x7f)
+ < 0)
+ || (i2c_smbus_write_byte_data(save_client, 2, tm.tm_hour & 0x7f)
+ < 0)
+ || (i2c_smbus_write_byte_data(save_client, 4, tm.tm_mday & 0x7f)
+ < 0)
+ || (i2c_smbus_write_byte_data(save_client, 5, tm.tm_mon & 0x7f)
+ < 0)
+ || (i2c_smbus_write_byte_data(save_client, 6, tm.tm_year & 0x7f)
+ < 0))
+
+ dev_warn(&save_client->dev,"m41t00: can't write to rtc chip\n");
+
+ up(&m41t00_mutex);
+ return;
+}
+
+ulong new_time;
+
+DECLARE_TASKLET_DISABLED(m41t00_tasklet, m41t00_set_tlet, (ulong)&new_time);
+
+int
+m41t00_set_rtc_time(ulong nowtime)
+{
+ new_time = nowtime;
+
+ if (in_interrupt())
+ tasklet_schedule(&m41t00_tasklet);
+ else
+ m41t00_set_tlet((ulong)&new_time);
+
+ return 0;
+}
+
+/*
+ *****************************************************************************
+ *
+ * Driver Interface
+ *
+ *****************************************************************************
+ */
+static int
+m41t00_probe(struct i2c_adapter *adap, int addr, int kind)
+{
+ struct i2c_client *client;
+ int rc;
+
+ client = kmalloc(sizeof(struct i2c_client), GFP_KERNEL);
+ if (!client)
+ return -ENOMEM;
+
+ memset(client, 0, sizeof(struct i2c_client));
+ strncpy(client->name, M41T00_DRV_NAME, I2C_NAME_SIZE);
+ client->id = m41t00_driver.id;
+ client->flags = I2C_DF_NOTIFY;
+ client->addr = addr;
+ client->adapter = adap;
+ client->driver = &m41t00_driver;
+
+ if ((rc = i2c_attach_client(client)) != 0) {
+ kfree(client);
+ return rc;
+ }
+
+ save_client = client;
+ return 0;
+}
+
+static int
+m41t00_attach(struct i2c_adapter *adap)
+{
+ return i2c_probe(adap, &addr_data, m41t00_probe);
+}
+
+static int
+m41t00_detach(struct i2c_client *client)
+{
+ int rc;
+
+ if ((rc = i2c_detach_client(client)) == 0) {
+ kfree(i2c_get_clientdata(client));
+ tasklet_kill(&m41t00_tasklet);
+ }
+ return rc;
+}
+
+static struct i2c_driver m41t00_driver = {
+ .owner = THIS_MODULE,
+ .name = M41T00_DRV_NAME,
+ .id = I2C_DRIVERID_STM41T00,
+ .flags = I2C_DF_NOTIFY,
+ .attach_adapter = m41t00_attach,
+ .detach_client = m41t00_detach,
+};
+
+static int __init
+m41t00_init(void)
+{
+ return i2c_add_driver(&m41t00_driver);
+}
+
+static void __exit
+m41t00_exit(void)
+{
+ i2c_del_driver(&m41t00_driver);
+ return;
+}
+
+module_init(m41t00_init);
+module_exit(m41t00_exit);
+
+MODULE_AUTHOR("Mark A. Greer <[email protected]>");
+MODULE_DESCRIPTION("ST Microelectronics M41T00 RTC I2C Client Driver");
+MODULE_LICENSE("GPL");
On Fri, Feb 04, 2005 at 05:04:09PM -0700, Mark A. Greer wrote:
> Greg KH wrote:
>
> >Can you resend it with a proper Changelog description in the top of the
> >email and the signed-off-by line? thanks,
> >
> >greg k-h
> >
> >
> >
> Certainly.
> --
>
> This patch adds support for the ST M41T00 I2C RTC chip.
>
> This rtc chip has no mechanism to freeze it's registers while being
> read; however, it will delay updating the external values of the
> registers for 250ms after a register is read. To ensure that a sane
> time value is read, the driver verifies that the same registers values
> were read twice before returning.
>
> Also, when setting the rtc from an interrupt handler, a tasklet is used
> to provide the context required by the i2c core code.
>
> Please apply.
Applied, thanks.
greg k-h