Support for the ISL12024 RTC, EEPROM and unique ID. Signed-off-by: Jeremy Laine Index: linux-2.6.31/drivers/misc/eeprom/at24.c =================================================================== --- linux-2.6.31.orig/drivers/misc/eeprom/at24.c 2009-11-10 17:20:53.000000000 +0100 +++ linux-2.6.31/drivers/misc/eeprom/at24.c 2009-11-10 17:54:17.000000000 +0100 @@ -115,6 +115,8 @@ { "spd", AT24_DEVICE_MAGIC(2048 / 8, AT24_FLAG_READONLY | AT24_FLAG_IRUGO) }, { "24c04", AT24_DEVICE_MAGIC(4096 / 8, 0) }, + /* Intersil isl12024 eeprom */ + { "isl12024-eeprom", AT24_DEVICE_MAGIC(4096 / 8, AT24_FLAG_ADDR16) }, /* 24rf08 quirk is handled at i2c-core */ { "24c08", AT24_DEVICE_MAGIC(8192 / 8, 0) }, { "24c16", AT24_DEVICE_MAGIC(16384 / 8, 0) }, Index: linux-2.6.31/drivers/rtc/Kconfig =================================================================== --- linux-2.6.31.orig/drivers/rtc/Kconfig 2009-11-10 17:20:53.000000000 +0100 +++ linux-2.6.31/drivers/rtc/Kconfig 2009-11-10 17:54:17.000000000 +0100 @@ -193,6 +193,15 @@ This driver can also be built as a module. If so, the module will be called rtc-isl1208. +config RTC_DRV_ISL12024 + tristate "Intersil ISL12024" + help + If you say yes here you get support for the Intersil ISL12024 + RTC chip. This driver also exposes the chip's unique ID. + + This driver can also be built as a module. If so, the module + will be called rtc-isl12024. + config RTC_DRV_X1205 tristate "Xicor/Intersil X1205" help Index: linux-2.6.31/drivers/rtc/Makefile =================================================================== --- linux-2.6.31.orig/drivers/rtc/Makefile 2009-11-10 17:20:53.000000000 +0100 +++ linux-2.6.31/drivers/rtc/Makefile 2009-11-10 17:54:17.000000000 +0100 @@ -40,6 +40,7 @@ obj-$(CONFIG_RTC_DRV_EP93XX) += rtc-ep93xx.o obj-$(CONFIG_RTC_DRV_FM3130) += rtc-fm3130.o obj-$(CONFIG_RTC_DRV_ISL1208) += rtc-isl1208.o +obj-$(CONFIG_RTC_DRV_ISL12024) += rtc-isl12024.o obj-$(CONFIG_RTC_DRV_M41T80) += rtc-m41t80.o obj-$(CONFIG_RTC_DRV_M41T94) += rtc-m41t94.o obj-$(CONFIG_RTC_DRV_M48T35) += rtc-m48t35.o Index: linux-2.6.31/drivers/rtc/rtc-isl12024.c =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ linux-2.6.31/drivers/rtc/rtc-isl12024.c 2009-11-10 17:54:31.000000000 +0100 @@ -0,0 +1,455 @@ +/* + * Intersil ISL12024 rtc class driver + * + * Copyright (C) 2007, CenoSYS (www.cenosys.com). + * Copyright (C) 2009, Bollore telecom (www.bolloretelecom.eu). + * + * Guillaume Ligneul + * Sylvain Giroudon + * Jeremy Laine + * + * Based on rtc-x1205. + * + * 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 + +#define DRV_VERSION "0.3" + +/* offsets into CCR area */ + +#define CCR_SEC 0 +#define CCR_MIN 1 +#define CCR_HOUR 2 +#define CCR_MDAY 3 +#define CCR_MONTH 4 +#define CCR_YEAR 5 +#define CCR_WDAY 6 +#define CCR_Y2K 7 + +/* alarm0 section */ +#define ISL12024_REG_ALARM0 0x08 /* Base address of ALARM0 */ + +/* device id section */ +#define ISL12024_REG_ID 0x20 +#define ISL12024_ID_SECTION_LEN 8 + +/* rtc section */ +#define ISL12024_REG_RTC 0x30 /* Base address of RTC */ +#define ISL12024_REG_HR_MIL (1<<7) /* 24h/12h mode */ +#define ISL12024_REG_HR_PM (1<<5) /* AM/PM bit in 12h mode */ +#define ISL12024_RTC_SECTION_LEN 8 + +/* status section */ +#define ISL12024_REG_SR 0x3F +#define ISL12024_REG_SR_BAT (1<<7) /* battery */ +#define ISL12024_REG_SR_AL1 (1<<6) /* alarm 0 */ +#define ISL12024_REG_SR_AL0 (1<<5) /* alarm 1 */ +#define ISL12024_REG_SR_OSCF (1<<4) /* oscillator fail */ +#define ISL12024_REG_SR_RWEL (1<<2) /* register write enable latch */ +#define ISL12024_REG_SR_WEL (1<<1) /* write enable latch */ +#define ISL12024_REG_SR_RTCF (1<<0) /* rtc fail */ + +/* control section */ +#define ISL12024_REG_INT 0x11 +#define ISL12024_INT_AL0E (1<<5) /* Alarm 0 enable */ + +static struct i2c_driver isl12024_driver; + +static int +isl12024_i2c_read_regs(struct i2c_client *client, u8 reg, u8 buf[], + unsigned len) +{ + int ret; + u8 dt_addr[2]; + + struct i2c_msg msgs[2] = { + { + .addr = client->addr, + .flags = 0, + .len = 2, + .buf = dt_addr, + }, + { + .addr = client->addr, + .flags = I2C_M_RD, + .len = len, + .buf = buf, + }, + }; + + dt_addr[0] = 0; + dt_addr[1] = reg; + + ret = i2c_transfer(client->adapter, msgs, 2); + if (ret != 2) { + dev_err(&client->dev, "read error (%i)\n", ret); + return (ret < 0) ? ret : -EIO; + } + return 0; +} + +static int +isl12024_i2c_set_regs(struct i2c_client *client, u8 reg, u8 const buf[], + unsigned len) +{ + int ret; + u8 i2c_buf[10]; + + i2c_buf[0] = 0; + i2c_buf[1] = reg; + memcpy(&i2c_buf[2], &buf[0], len); + + ret = i2c_master_send(client, i2c_buf, len + 2); + if (ret != len + 2) { + dev_err(&client->dev, "write error (%d)\n", ret); + return (ret < 0) ? ret : -EIO; + } + return 0; +} + +static int isl12024_read_time(struct i2c_client *client, + struct rtc_time *tm, u8 reg_base) +{ + unsigned char sr; + int err; + u8 regs[ISL12024_RTC_SECTION_LEN] = { 0, }; + + if (isl12024_i2c_read_regs(client, ISL12024_REG_SR, &sr, + sizeof(sr)) < 0) { + dev_err(&client->dev, "reading SR failed\n"); + return -EIO; + } + + err = isl12024_i2c_read_regs(client, reg_base, regs, + ISL12024_RTC_SECTION_LEN); + + if (err < 0) { + dev_err(&client->dev, "reading RTC section failed\n"); + return err; + } + + tm->tm_sec = bcd2bin(regs[CCR_SEC]); + tm->tm_min = bcd2bin(regs[CCR_MIN]); + + /* HR field has a more complex interpretation */ + if (regs[CCR_HOUR] & ISL12024_REG_HR_MIL) { + /* 24h format */ + tm->tm_hour = bcd2bin(regs[CCR_HOUR] & 0x3f); + } else { + /* 12h format */ + tm->tm_hour = bcd2bin(regs[CCR_HOUR] & 0x1f); + if (regs[CCR_HOUR] & ISL12024_REG_HR_PM) /* PM flag set */ + tm->tm_hour += 12; + } + + tm->tm_mday = bcd2bin(regs[CCR_MDAY]); + tm->tm_mon = bcd2bin(regs[CCR_MONTH]) - 1; + tm->tm_year = bcd2bin(regs[CCR_YEAR]) + + (bcd2bin(regs[CCR_Y2K]) * 100) - 1900; + tm->tm_wday = bcd2bin(regs[CCR_WDAY]); + + return rtc_valid_tm(tm); +} + +static int isl12024_set_datetime(struct i2c_client *client, struct rtc_time *tm, + int datetoo, u8 reg_base, + unsigned char alm_enable) +{ + int i, xfer, nbytes; + unsigned char buf[8]; + + static const unsigned char wel[3] = { 0, ISL12024_REG_SR, + ISL12024_REG_SR_WEL }; + + static const unsigned char rwel[3] = { 0, ISL12024_REG_SR, + ISL12024_REG_SR_WEL | ISL12024_REG_SR_RWEL }; + + static const unsigned char diswe[3] = { 0, ISL12024_REG_SR, 0 }; + + dev_dbg(&client->dev, + "%s: secs=%d, mins=%d, hours=%d\n", + __func__, + tm->tm_sec, tm->tm_min, tm->tm_hour); + + buf[CCR_SEC] = bin2bcd(tm->tm_sec); + buf[CCR_MIN] = bin2bcd(tm->tm_min); + + /* set hour and 24hr bit */ + buf[CCR_HOUR] = bin2bcd(tm->tm_hour) | ISL12024_REG_HR_MIL; + + /* should we also set the date? */ + if (datetoo) { + dev_dbg(&client->dev, + "%s: mday=%d, mon=%d, year=%d, wday=%d\n", + __func__, + tm->tm_mday, tm->tm_mon, tm->tm_year, tm->tm_wday); + + buf[CCR_MDAY] = bin2bcd(tm->tm_mday); + + /* month, 1 - 12 */ + buf[CCR_MONTH] = bin2bcd(tm->tm_mon + 1); + + /* year, since the rtc epoch*/ + buf[CCR_YEAR] = bin2bcd(tm->tm_year % 100); + buf[CCR_WDAY] = tm->tm_wday & 0x07; + buf[CCR_Y2K] = bin2bcd(19 + tm->tm_year / 100); + } + + /* If writing alarm registers, set compare bits on registers 0-4 */ + if (reg_base == ISL12024_REG_ALARM0) + for (i = 0; i <= 4; i++) + buf[i] |= 0x80; + + /* this sequence is required to unlock the chip */ + xfer = i2c_master_send(client, wel, 3); + if (xfer != 3) { + dev_err(&client->dev, "%s: wel - %d\n", __func__, xfer); + return -EIO; + } + + xfer = i2c_master_send(client, rwel, 3); + if (xfer != 3) { + dev_err(&client->dev, "%s: rwel - %d\n", __func__, xfer); + return -EIO; + } + + /* write register's data */ + if (datetoo) + nbytes = 8; + else + nbytes = 3; + xfer = isl12024_i2c_set_regs(client, reg_base, buf, nbytes); + if (xfer < 0) + return xfer; + + /* If we wrote to the nonvolatile region, wait 10msec for write cycle*/ + if (reg_base == ISL12024_REG_ALARM0) { + unsigned char al0e[3] = { 0, ISL12024_REG_INT, 0 }; + + msleep(10); + + /* ...and set or clear the AL0E bit in the INT register */ + + /* Need to set RWEL again as the write has cleared it */ + xfer = i2c_master_send(client, rwel, 3); + if (xfer != 3) { + dev_err(&client->dev, "%s: al0e rwel - %d\n", + __func__, xfer); + return -EIO; + } + + if (alm_enable) + al0e[2] = ISL12024_INT_AL0E; + + xfer = i2c_master_send(client, al0e, 3); + if (xfer != 3) { + dev_err(&client->dev, "%s: al0e - %d\n", + __func__, xfer); + return -EIO; + } + + /* and wait 10msec again for this write to complete */ + msleep(10); + } + + /* disable further writes */ + xfer = i2c_master_send(client, diswe, 3); + if (xfer != 3) { + dev_err(&client->dev, "%s: diswe - %d\n", __func__, xfer); + return -EIO; + } + + return 0; +} + +static int isl12024_fix_osc(struct i2c_client *client) +{ + int err; + struct rtc_time tm; + + tm.tm_hour = tm.tm_min = tm.tm_sec = 0; + + err = isl12024_set_datetime(client, &tm, 0, ISL12024_REG_RTC, 0); + if (err < 0) + dev_err(&client->dev, + "unable to restart the oscillator (%d)\n", err); + + return err; +} + +static int isl12024_rtc_read_time(struct device *dev, struct rtc_time *tm) +{ + return isl12024_read_time(to_i2c_client(dev), tm, + ISL12024_REG_RTC); +} + +static int isl12024_rtc_set_time(struct device *dev, struct rtc_time *tm) +{ + return isl12024_set_datetime(to_i2c_client(dev), tm, 1, + ISL12024_REG_RTC, 0); +} + +static int isl12024_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alrm) +{ + unsigned char intr; + int err; + struct i2c_client *client = to_i2c_client(dev); + + err = isl12024_i2c_read_regs(client, ISL12024_REG_INT, &intr, + sizeof(intr)); + if (err < 0) + return err; + alrm->enabled = (intr & ISL12024_INT_AL0E) ? 1 : 0; + + return isl12024_read_time(client, &alrm->time, ISL12024_REG_ALARM0); +} + +static int isl12024_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alrm) +{ + return isl12024_set_datetime(to_i2c_client(dev), &alrm->time, 1, + ISL12024_REG_ALARM0, alrm->enabled); +} + +static int +isl12024_rtc_proc(struct device *dev, struct seq_file *seq) +{ + /* Nothing to do */ + return 0; +} + +static const struct rtc_class_ops isl12024_rtc_ops = { + .proc = isl12024_rtc_proc, + .read_time = isl12024_rtc_read_time, + .set_time = isl12024_rtc_set_time, + .read_alarm = isl12024_rtc_read_alarm, + .set_alarm = isl12024_rtc_set_alarm, +}; + +static ssize_t isl12024_show_id(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct i2c_client *client = to_i2c_client(dev); + int err; + ssize_t len = 0; + int i; + u8 id_buffer[ISL12024_ID_SECTION_LEN]; + + /* Read unique id from eeprom */ + err = isl12024_i2c_read_regs(client, ISL12024_REG_ID, id_buffer, + sizeof(id_buffer)); + if (err < 0) { + dev_err(&client->dev, "reading ID section failed\n"); + return err; + } + + /* Print hexadecimal */ + for (i = 0; i < sizeof(id_buffer); i++) + len += sprintf(buf + len, "%02X", id_buffer[i]); + len += sprintf(buf + len, "\n"); + return len; +} + +static DEVICE_ATTR(id, S_IRUGO, isl12024_show_id, NULL); + +static int +isl12024_probe(struct i2c_client *client, const struct i2c_device_id *id) +{ + int rc = 0; + unsigned char sr; + struct rtc_device *rtc = NULL; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) + return -ENODEV; + + dev_info(&client->dev, + "chip found, driver version " DRV_VERSION "\n"); + + rtc = rtc_device_register(isl12024_driver.driver.name, + &client->dev, &isl12024_rtc_ops, + THIS_MODULE); + if (IS_ERR(rtc)) + return PTR_ERR(rtc); + + i2c_set_clientdata(client, rtc); + + rc = isl12024_i2c_read_regs(client, ISL12024_REG_SR, &sr, sizeof(sr)); + if (rc < 0) { + dev_err(&client->dev, "reading status failed\n"); + goto exit_unregister; + } + + /* Check for power failures and enable the osc */ + if (sr & ISL12024_REG_SR_RTCF) { + dev_warn(&client->dev, "rtc power failure detected, " + "please set clock.\n"); + udelay(50); + isl12024_fix_osc(client); + } + + /* Register sysfs hooks */ + rc = device_create_file(&client->dev, &dev_attr_id); + if (rc < 0) + goto exit_unregister; + + return 0; + +exit_unregister: + rtc_device_unregister(rtc); + + return rc; +} + +static int +isl12024_remove(struct i2c_client *client) +{ + struct rtc_device *rtc = i2c_get_clientdata(client); + + rtc_device_unregister(rtc); + device_remove_file(&client->dev, &dev_attr_id); + + return 0; +} + +static const struct i2c_device_id isl12024_id[] = { + { "isl12024", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, isl12024_id); + +static struct i2c_driver isl12024_driver = { + .driver = { + .name = "rtc-isl12024", + }, + .probe = isl12024_probe, + .remove = isl12024_remove, + .id_table = isl12024_id, +}; + +/* module init/exit */ + +static int __init isl12024_init(void) +{ + return i2c_add_driver(&isl12024_driver); +} + +static void __exit isl12024_exit(void) +{ + i2c_del_driver(&isl12024_driver); +} + +MODULE_AUTHOR("Guillaume Ligneul "); +MODULE_DESCRIPTION("Intersil ISL12024 driver"); +MODULE_LICENSE("GPL"); +MODULE_VERSION(DRV_VERSION); + +module_init(isl12024_init); +module_exit(isl12024_exit);