On Tue, Mar 07, 2006 at 10:01:07AM -0700, Mark A. Greer wrote: > Hi Jean. > > On Mon, Mar 06, 2006 at 12:45:14PM +0100, Jean Delvare wrote: > > > > So I'd suggest that you do not rename the driver, it it's not too late. > > Okay, that makes sense. I'll change the name back to m41t00 and submit that > patch in the next day or so. Hi Jean, et. al., It looks like in the 2.6.16-rc6-mm1 tree I should really be putting the m41t00 driver into drivers/rtc but I need to check if it will work okay for ppc. In the meantime, here is a patch that keeps it in drivers/i2c/chips. I hope it is easier to review and is acceptable. Thanks for your time (I know you're busy), Mark -------------------------------------------------------------------------- Replace the old m41t00-specific driver with a more generic driver for the ST family of i2c RTC chips. Currently, the m41t00, m41t81, and m41t85 chips are supported but only the m41t00 and m41t85 have been tested. Signed-off-by: Mark A. Greer <mgreer at mvista.com> --- drivers/i2c/chips/Kconfig | 9 - drivers/i2c/chips/Makefile | 2 drivers/i2c/chips/m41t00.c | 322 +++++++++++++++++++++++++++++++-------------- include/linux/m41t00.h | 47 ++++++ 4 files changed, 276 insertions(+), 104 deletions(-) --- diff -Nurp linux-2.6.16-rc6-mm1/drivers/i2c/chips/Kconfig linux-2.6.16-rc6-mm1-m41t00/drivers/i2c/chips/Kconfig --- linux-2.6.16-rc6-mm1/drivers/i2c/chips/Kconfig 2006-03-15 11:37:51.000000000 -0700 +++ linux-2.6.16-rc6-mm1-m41t00/drivers/i2c/chips/Kconfig 2006-03-15 14:04:39.000000000 -0700 @@ -93,11 +93,12 @@ config TPS65010 This driver can also be built as a module. If so, the module will be called tps65010. -config SENSORS_M41T00 - tristate "ST M41T00 RTC chip" - depends on I2C && PPC32 +config RTC_M41T00_I2C + tristate "ST M41T00 Family of I2C RTC chips" + depends on I2C help - If you say yes here you get support for the ST M41T00 RTC chip. + If you say yes here you get support for the ST M41T00, M41T81, + and M41T85 (and possibly other) I2C RTC chips. This driver can also be built as a module. If so, the module will be called m41t00. diff -Nurp linux-2.6.16-rc6-mm1/drivers/i2c/chips/m41t00.c linux-2.6.16-rc6-mm1-m41t00/drivers/i2c/chips/m41t00.c --- linux-2.6.16-rc6-mm1/drivers/i2c/chips/m41t00.c 2006-03-15 11:37:51.000000000 -0700 +++ linux-2.6.16-rc6-mm1-m41t00/drivers/i2c/chips/m41t00.c 2006-03-16 16:33:20.000000000 -0700 @@ -1,7 +1,5 @@ /* - * drivers/i2c/chips/m41t00.c - * - * I2C client/driver for the ST M41T00 Real-Time Clock chip. + * I2C client/driver for the ST M41T00 family of i2c rtc chips. * * Author: Mark A. Greer <mgreer at mvista.com> * @@ -13,9 +11,6 @@ /* * 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> @@ -23,145 +18,202 @@ #include <linux/interrupt.h> #include <linux/i2c.h> #include <linux/rtc.h> +#include <linux/m41t00.h> #include <linux/bcd.h> -#include <linux/mutex.h> +#include <linux/workqueue.h> +#include <linux/platform_device.h> #include <asm/time.h> #include <asm/rtc.h> -#define M41T00_DRV_NAME "m41t00" - -static DEFINE_MUTEX(m41t00_mutex); +static struct work_struct set_rtc_time_task; 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 unsigned short normal_addr[] = { 0, I2C_CLIENT_END }; static struct i2c_client_address_data addr_data = { - .normal_i2c = normal_addr, - .probe = ignore, - .ignore = ignore, + .normal_i2c = normal_addr, + .probe = ignore, + .ignore = ignore, +}; + +struct m41t00_chip_info { + u16 type; + u16 read_limit; + u8 sec; /* Offsets for chip regs */ + u8 min; + u8 hour; + u8 day; + u8 mon; + u8 year; + u8 alarm_mon; + u8 alarm_hour; + u8 sqw; + u32 sqw_freq; +}; + +static struct m41t00_chip_info m41t00_chip_info_tbl[] = { + { M41T00_TYPE_M41T00, 5, 0, 1, 2, 4, 5, 6, 0, 0, 0, 0 }, + { M41T00_TYPE_M41T81, 1, 1, 2, 3, 5, 6, 7, 0xa, 0xc, 0x13, 0 }, + { M41T00_TYPE_M41T85, 1, 1, 2, 3, 5, 6, 7, 0xa, 0xc, 0x13, 0 }, }; +static struct m41t00_chip_info *m41t00_chip; -ulong +unsigned long m41t00_get_rtc_time(void) { - s32 sec, min, hour, day, mon, year; - s32 sec1, min1, hour1, day1, mon1, year1; - ulong limit = 10; + s32 sec, min, hour, day, mon, year; + s32 sec1, min1, hour1, day1, mon1, year1; + u16 reads = 0; + u8 buf[8], msgbuf[1] = { 0 }; /* offset into rtc's regs */ + struct i2c_msg msgs[] = { + { save_client->addr, 0, 1, msgbuf }, + { save_client->addr, I2C_M_RD, 8, buf } + }; sec = min = hour = day = mon = year = 0; - sec1 = min1 = hour1 = day1 = mon1 = year1 = 0; - mutex_lock(&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))) + if (i2c_transfer(save_client->adapter, msgs, 2) < 0) + goto read_err; - break; - - sec1 = sec; - min1 = min; + sec1 = sec; + min1 = min; hour1 = hour; - day1 = day; - mon1 = mon; + day1 = day; + mon1 = mon; year1 = year; - } while (--limit > 0); - mutex_unlock(&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); + sec = buf[m41t00_chip->sec] & 0x7f; + min = buf[m41t00_chip->min] & 0x7f; + hour = buf[m41t00_chip->hour] & 0x3f; + day = buf[m41t00_chip->day] & 0x3f; + mon = buf[m41t00_chip->mon] & 0x1f; + year = buf[m41t00_chip->year] & 0xff; + } while ((++reads < m41t00_chip->read_limit) && ((sec != sec1) + || (min != min1) || (hour != hour1) || (day != day1) + || (mon != mon1) || (year != year1))); + + if ((m41t00_chip->read_limit > 1) && ((sec != sec1) || (min != min1) + || (hour != hour1) || (day != day1) || (mon != mon1) + || (year != year1))) + goto read_err; + + sec = BCD2BIN(sec); + min = BCD2BIN(min); + hour = BCD2BIN(hour); + day = BCD2BIN(day); + mon = BCD2BIN(mon); + year = BCD2BIN(year); year += 1900; if (year < 1970) year += 100; return mktime(year, mon, day, hour, min, sec); + +read_err: + dev_err(&save_client->dev, "m41t00_get_rtc_time: Read error\n"); + return 0; } static void -m41t00_set_tlet(ulong arg) +m41t00_set(void *arg) { struct rtc_time tm; - ulong nowtime = *(ulong *)arg; + int nowtime = *(int *)arg; + s32 sec, min, hour, day, mon, year; + u8 wbuf[9], *buf = &wbuf[1], msgbuf[1] = { 0 }; + struct i2c_msg msgs[] = { + { save_client->addr, 0, 1, msgbuf }, + { save_client->addr, I2C_M_RD, 8, buf } + }; 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); - - mutex_lock(&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"); + sec = BIN2BCD(tm.tm_sec); + min = BIN2BCD(tm.tm_min); + hour = BIN2BCD(tm.tm_hour); + day = BIN2BCD(tm.tm_mday); + mon = BIN2BCD(tm.tm_mon); + year = BIN2BCD(tm.tm_year); + + /* Read reg values into buf[0..7]/wbuf[1..8] */ + if (i2c_transfer(save_client->adapter, msgs, 2) < 0) { + dev_err(&save_client->dev, "m41t00_set: Read error\n"); + return; + } - mutex_unlock(&m41t00_mutex); - return; -} + wbuf[0] = 0; /* offset into rtc's regs */ + buf[m41t00_chip->sec] = (buf[m41t00_chip->sec] & ~0x7f) | (sec & 0x7f); + buf[m41t00_chip->min] = (buf[m41t00_chip->min] & ~0x7f) | (min & 0x7f); + buf[m41t00_chip->hour] = (buf[m41t00_chip->hour]& ~0x3f) | (hour& 0x3f); + buf[m41t00_chip->day] = (buf[m41t00_chip->day] & ~0x3f) | (day & 0x3f); + buf[m41t00_chip->mon] = (buf[m41t00_chip->mon] & ~0x1f) | (mon & 0x1f); -static ulong new_time; + if (i2c_master_send(save_client, wbuf, 9) < 0) + dev_err(&save_client->dev, "m41t00_set: Write error\n"); +} -DECLARE_TASKLET_DISABLED(m41t00_tasklet, m41t00_set_tlet, (ulong)&new_time); +static u32 new_time; int -m41t00_set_rtc_time(ulong nowtime) +m41t00_set_rtc_time(unsigned long nowtime) { new_time = nowtime; if (in_interrupt()) - tasklet_schedule(&m41t00_tasklet); + schedule_work(&set_rtc_time_task); else - m41t00_set_tlet((ulong)&new_time); + m41t00_set((void *)&new_time); + return 0; +} + +/* + ***************************************************************************** + * + * platform_data Driver Interface + * + ***************************************************************************** + */ +static int __init +m41t00_platform_probe(struct platform_device *pdev) +{ + struct m41t00_platform_data *pdata; + int i; + if (pdev && (pdata = pdev->dev.platform_data)) { + normal_addr[0] = pdata->i2c_addr; + + for (i=0; i<ARRAY_SIZE(m41t00_chip_info_tbl); i++) + if (m41t00_chip_info_tbl[i].type == pdata->type) { + m41t00_chip = &m41t00_chip_info_tbl[i]; + m41t00_chip->sqw_freq = pdata->sqw_freq; + return 0; + } + } + return -ENODEV; +} + +static int __exit +m41t00_platform_remove(struct platform_device *pdev) +{ return 0; } +static struct platform_driver m41t00_platform_driver = { + .probe = m41t00_platform_probe, + .remove = m41t00_platform_remove, + .driver = { + .owner = THIS_MODULE, + .name = M41T00_DRV_NAME, + }, +}; + /* ***************************************************************************** * @@ -174,23 +226,90 @@ m41t00_probe(struct i2c_adapter *adap, i { struct i2c_client *client; int rc; + u8 rbuf[1], wbuf[2], msgbuf[1] = { 0 }; /* offset into rtc's regs */ + struct i2c_msg msgs[] = { + { 0, 0, 1, msgbuf }, + { 0, I2C_M_RD, 1, rbuf } + }; client = kzalloc(sizeof(struct i2c_client), GFP_KERNEL); if (!client) return -ENOMEM; - strncpy(client->name, M41T00_DRV_NAME, I2C_NAME_SIZE); + strlcpy(client->name, m41t00_driver.driver.name, I2C_NAME_SIZE); client->addr = addr; client->adapter = adap; client->driver = &m41t00_driver; - if ((rc = i2c_attach_client(client)) != 0) { - kfree(client); - return rc; + if ((rc = i2c_attach_client(client)) != 0) + goto attach_err; + + msgs[0].addr = addr; + msgs[1].addr = addr; + + if (m41t00_chip->type != M41T00_TYPE_M41T00) { + /* If asked, set SQW frequency & enable */ + if (m41t00_chip->sqw_freq) { + msgbuf[0] = m41t00_chip->alarm_mon; + if ((rc = i2c_transfer(client->adapter, msgs, 2)) < 0) + goto sqw_err; + + wbuf[0] = m41t00_chip->alarm_mon; + wbuf[1] = rbuf[0] & ~0x40; + if ((rc = i2c_master_send(client, wbuf, 2)) < 0) + goto sqw_err; + + wbuf[0] = m41t00_chip->sqw; + wbuf[1] = m41t00_chip->sqw_freq; + if ((rc = i2c_master_send(client, wbuf, 2)) < 0) + goto sqw_err; + + wbuf[0] = m41t00_chip->alarm_mon; + wbuf[1] = rbuf[0] | 0x40; + if ((rc = i2c_master_send(client, wbuf, 2)) < 0) + goto sqw_err; + } + + /* Make sure HT (Halt Update) bit is cleared */ + msgbuf[0] = m41t00_chip->alarm_hour; + if ((rc = i2c_transfer(client->adapter, msgs, 2)) < 0) + goto ht_err; + + if (rbuf[0] & 0x40) { + wbuf[0] = m41t00_chip->alarm_hour; + wbuf[1] = rbuf[0] & ~0x40; + if ((rc = i2c_master_send(client, wbuf, 2)) < 0) + goto ht_err; + } + } + + /* Make sure ST (stop) bit is cleared */ + msgbuf[0] = m41t00_chip->sec; + if ((rc = i2c_transfer(client->adapter, msgs, 2)) < 0) + goto st_err; + + if (rbuf[0] & 0x80) { + wbuf[0] = m41t00_chip->sec; + wbuf[1] = rbuf[0] & ~0x80; + if ((rc = i2c_master_send(client, wbuf, 2)) < 0) + goto st_err; } + INIT_WORK(&set_rtc_time_task, &m41t00_set, &new_time); save_client = client; return 0; + +st_err: + dev_err(&client->dev, "m41t00_probe: Can't clear ST bit\n"); + goto attach_err; +ht_err: + dev_err(&client->dev, "m41t00_probe: Can't clear HT bit\n"); + goto attach_err; +sqw_err: + dev_err(&client->dev, "m41t00_probe: Can't set SQW Frequency\n"); +attach_err: + kfree(client); + return rc; } static int @@ -206,13 +325,14 @@ m41t00_detach(struct i2c_client *client) if ((rc = i2c_detach_client(client)) == 0) { kfree(client); - tasklet_kill(&m41t00_tasklet); + flush_scheduled_work(); } return rc; } static struct i2c_driver m41t00_driver = { .driver = { + .owner = THIS_MODULE, .name = M41T00_DRV_NAME, }, .id = I2C_DRIVERID_STM41T00, @@ -223,14 +343,18 @@ static struct i2c_driver m41t00_driver = static int __init m41t00_init(void) { - return i2c_add_driver(&m41t00_driver); + int rc; + + if (!(rc = platform_driver_register(&m41t00_platform_driver))) + rc = i2c_add_driver(&m41t00_driver); + return rc; } static void __exit m41t00_exit(void) { i2c_del_driver(&m41t00_driver); - return; + platform_driver_unregister(&m41t00_platform_driver); } module_init(m41t00_init); diff -Nurp linux-2.6.16-rc6-mm1/drivers/i2c/chips/Makefile linux-2.6.16-rc6-mm1-m41t00/drivers/i2c/chips/Makefile --- linux-2.6.16-rc6-mm1/drivers/i2c/chips/Makefile 2006-03-15 11:37:51.000000000 -0700 +++ linux-2.6.16-rc6-mm1-m41t00/drivers/i2c/chips/Makefile 2006-03-15 13:57:53.000000000 -0700 @@ -6,12 +6,12 @@ obj-$(CONFIG_SENSORS_DS1337) += ds1337.o obj-$(CONFIG_SENSORS_DS1374) += ds1374.o obj-$(CONFIG_SENSORS_EEPROM) += eeprom.o obj-$(CONFIG_SENSORS_MAX6875) += max6875.o -obj-$(CONFIG_SENSORS_M41T00) += m41t00.o obj-$(CONFIG_SENSORS_PCA9539) += pca9539.o obj-$(CONFIG_SENSORS_PCF8574) += pcf8574.o obj-$(CONFIG_SENSORS_PCF8591) += pcf8591.o obj-$(CONFIG_ISP1301_OMAP) += isp1301_omap.o obj-$(CONFIG_TPS65010) += tps65010.o +obj-$(CONFIG_RTC_M41T00_I2C) += m41t00.o ifeq ($(CONFIG_I2C_DEBUG_CHIP),y) EXTRA_CFLAGS += -DDEBUG diff -Nurp linux-2.6.16-rc6-mm1/include/linux/m41t00.h linux-2.6.16-rc6-mm1-m41t00/include/linux/m41t00.h --- linux-2.6.16-rc6-mm1/include/linux/m41t00.h 1969-12-31 17:00:00.000000000 -0700 +++ linux-2.6.16-rc6-mm1-m41t00/include/linux/m41t00.h 2006-03-16 11:31:53.000000000 -0700 @@ -0,0 +1,47 @@ +/* + * Definitions for the ST M41T00 family of i2c rtc chips. + * + * Author: Mark A. Greer <mgreer at mvista.com> + * + * 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. + */ + +#ifndef _M41T00_H +#define _M41T00_H + +#define M41T00_DRV_NAME "m41t00 rtc" +#define M41T00_I2C_ADDR 0x68 + +#define M41T00_TYPE_M41T00 0 +#define M41T00_TYPE_M41T81 81 +#define M41T00_TYPE_M41T85 85 + +struct m41t00_platform_data { + u16 type; + u16 i2c_addr; + u32 sqw_freq; +}; + +/* SQW output disabled, this is default value by power on*/ +#define SQW_FREQ_DISABLE (0) + +#define SQW_FREQ_32KHZ (1<<4) /* 32.768 KHz */ +#define SQW_FREQ_8KHZ (2<<4) /* 8.192 KHz */ +#define SQW_FREQ_4KHZ (3<<4) /* 4.096 KHz */ +#define SQW_FREQ_2KHZ (4<<4) /* 2.048 KHz */ +#define SQW_FREQ_1KHZ (5<<4) /* 1.024 KHz */ +#define SQW_FREQ_512HZ (6<<4) /* 512 Hz */ +#define SQW_FREQ_256HZ (7<<4) /* 256 Hz */ +#define SQW_FREQ_128HZ (8<<4) /* 128 Hz */ +#define SQW_FREQ_64HZ (9<<4) /* 64 Hz */ +#define SQW_FREQ_32HZ (10<<4) /* 32 Hz */ +#define SQW_FREQ_16HZ (11<<4) /* 16 Hz */ +#define SQW_FREQ_8HZ (12<<4) /* 8 Hz */ +#define SQW_FREQ_4HZ (13<<4) /* 4 Hz */ +#define SQW_FREQ_2HZ (14<<4) /* 2 Hz */ +#define SQW_FREQ_1HZ (15<<4) /* 1 Hz */ + +#endif /* _M41T00_H */