From patchwork Thu Mar 25 09:09:15 2010 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Gregory Hermant X-Patchwork-Id: 48505 Return-Path: <3QCirSwAACV8OQ9-IFKRUDLLDIBDOLRMP.9LJ@groups.bounces.google.com> X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Received: from mail-yw0-f166.google.com (mail-yw0-f166.google.com [209.85.211.166]) by ozlabs.org (Postfix) with ESMTP id 2AE44B7C7E for ; Thu, 25 Mar 2010 20:09:22 +1100 (EST) Received: by ywh38 with SMTP id 38sf14034604ywh.29 for ; Thu, 25 Mar 2010 02:09:20 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=googlegroups.com; s=beta; h=domainkey-signature:received:mime-version:x-beenthere:received :received:received:received:received-spf:x-rzg-auth:x-rzg-class-id :received:from:to:subject:date:message-id:x-mailer :x-original-authentication-results:x-original-sender:reply-to :precedence:mailing-list:list-id:list-post:list-help:list-archive :x-thread-url:x-message-url:sender:list-subscribe:list-unsubscribe :content-type:content-transfer-encoding; bh=zIk4v8e3MfR/8Qya6DUq0FCNKySKYQslONMD1QODpyk=; b=PNuKeG+GjJQvyN08hbzjzJypw3EAK9KV5rPDcFCDvKa1NCXuzsTT+4KKIEv6L1nVt8 yMYa4s+50K2Bn8c661pUa9oOWhNTQWmPKKNZAXfhxQdVxwY5lRsy3Xbiw4ZY1GaJHVvK ZgCs47TT/UICRT5kWpVu1+ppT6b0iuFvKYNTA= DomainKey-Signature: a=rsa-sha1; c=nofws; d=googlegroups.com; s=beta; h=mime-version:x-beenthere:received-spf:x-rzg-auth:x-rzg-class-id :from:to:subject:date:message-id:x-mailer :x-original-authentication-results:x-original-sender:reply-to :precedence:mailing-list:list-id:list-post:list-help:list-archive :x-thread-url:x-message-url:sender:list-subscribe:list-unsubscribe :content-type:content-transfer-encoding; b=PLy4SjwvQapXoYjs5tycwNos+pTywfweOqDhhKW0Xkn44Qhdu1MmAbNW30NULNtwC4 nweCB+aIiCp3PEFSsV0G4JQzYR61ywfJN2xwsn+Qy2uyp2j7yKn8dJGDwELfHzyJTi2A s20M1BuRfJMhz2imWwouZOlUJ7GPBD2GDTni4= Received: by 10.91.218.7 with SMTP id v7mr814911agq.20.1269508160377; Thu, 25 Mar 2010 02:09:20 -0700 (PDT) MIME-Version: 1.0 X-BeenThere: rtc-linux@googlegroups.com Received: by 10.213.2.70 with SMTP id 6ls874357ebi.2.p; Thu, 25 Mar 2010 02:09:18 -0700 (PDT) Received: by 10.213.39.132 with SMTP id g4mr1134595ebe.5.1269508158404; Thu, 25 Mar 2010 02:09:18 -0700 (PDT) Received: by 10.213.39.132 with SMTP id g4mr1134594ebe.5.1269508158359; Thu, 25 Mar 2010 02:09:18 -0700 (PDT) Received: from mo-p00-ob.rzone.de (mo-p00-ob.rzone.de [81.169.146.160]) by gmr-mx.google.com with ESMTP id 11si59542ewy.13.2010.03.25.02.09.16; Thu, 25 Mar 2010 02:09:17 -0700 (PDT) Received-SPF: neutral (google.com: 81.169.146.160 is neither permitted nor denied by best guess record for domain of gregory.hermant@calao-systems.com) client-ip=81.169.146.160; X-RZG-AUTH: :K34Bck+6YrEgjuBfzmrkukPJhhbHEm1G+2SCnvvjhur6rsGj/jgixsrTkwdMQP1Z/nY9Ces+2B2lLRPiIw== X-RZG-CLASS-ID: mo00 Received: from localhost.localdomain (ALyon-754-1-8-79.w90-15.abo.wanadoo.fr [90.15.171.79]) by post.strato.de (mrclete mo32) (RZmta 23.0) with ESMTP id V018ddm2P8mLjV for ; Thu, 25 Mar 2010 10:09:16 +0100 (MET) From: Gregory Hermant To: rtc-linux@googlegroups.com Subject: [rtc-linux] [PATCH] Add rv3029c2 RTC support Date: Thu, 25 Mar 2010 10:09:15 +0100 Message-Id: <1269508155-9432-1-git-send-email-gregory.hermant@calao-systems.com> X-Mailer: git-send-email 1.5.6.3 X-Original-Authentication-Results: gmr-mx.google.com; spf=neutral (google.com: 81.169.146.160 is neither permitted nor denied by best guess record for domain of gregory.hermant@calao-systems.com) smtp.mail=gregory.hermant@calao-systems.com; dkim=pass (test mode) header.i=@calao-systems.com X-Original-Sender: gregory.hermant@calao-systems.com Reply-To: rtc-linux@googlegroups.com Precedence: list Mailing-list: list rtc-linux@googlegroups.com; contact rtc-linux+owners@googlegroups.com List-ID: List-Post: , List-Help: , List-Archive: X-Thread-Url: http://groups.google.com/group/rtc-linux/t/9f2bb09c5eda7318 X-Message-Url: http://groups.google.com/group/rtc-linux/msg/2d7a42c667911c6f Sender: rtc-linux@googlegroups.com List-Subscribe: , List-Unsubscribe: , Signed-off-by: Gregory Hermant --- drivers/rtc/Kconfig | 9 + drivers/rtc/Makefile | 1 + drivers/rtc/rtc-rv3029c2.c | 380 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 390 insertions(+), 0 deletions(-) create mode 100644 drivers/rtc/rtc-rv3029c2.c diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig index 6a13037..9eb256a 100644 --- a/drivers/rtc/Kconfig +++ b/drivers/rtc/Kconfig @@ -324,6 +324,15 @@ config RTC_DRV_RX8025 This driver can also be built as a module. If so, the module will be called rtc-rx8025. +config RTC_DRV_RV3029C2 + tristate "Micro-crytal RTC" + help + If you say yes here you get support for the Micro-crystal + RV3029-C2 RTC chips. + + This driver can also be built as a module. If so, the module + will be called rtc-rv3029c2. + endif # I2C comment "SPI RTC drivers" diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile index 44ef194..2f6a729 100644 --- a/drivers/rtc/Makefile +++ b/drivers/rtc/Makefile @@ -74,6 +74,7 @@ obj-$(CONFIG_RTC_DRV_RP5C01) += rtc-rp5c01.o obj-$(CONFIG_RTC_DRV_RS5C313) += rtc-rs5c313.o obj-$(CONFIG_RTC_DRV_RS5C348) += rtc-rs5c348.o obj-$(CONFIG_RTC_DRV_RS5C372) += rtc-rs5c372.o +obj-$(CONFIG_RTC_DRV_RV3029C2) += rtc-rv3029c2.o obj-$(CONFIG_RTC_DRV_RX8025) += rtc-rx8025.o obj-$(CONFIG_RTC_DRV_RX8581) += rtc-rx8581.o obj-$(CONFIG_RTC_DRV_S35390A) += rtc-s35390a.o diff --git a/drivers/rtc/rtc-rv3029c2.c b/drivers/rtc/rtc-rv3029c2.c new file mode 100644 index 0000000..3e61530 --- /dev/null +++ b/drivers/rtc/rtc-rv3029c2.c @@ -0,0 +1,380 @@ +/* + * Mcrystal RV-3029C2 rtc class driver + * + * Author: Gregory Hermant + * + * based on previously existing rtc class drivers + * + * 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. + * + * NOTE: Currently this driver only supports the bare minimum for read + * and write the RTC. The extra features provided by this chip + * (alarms, trickle charger, eeprom, T° compensation) are unavailable. + */ + +#include +#include +#include +#include + +#define DRV_VERSION "0.1" + +/* Register map */ +/* control section */ +#define RV3029C2_ONOFF_CTRL 0x00 +#define RV3029C2_IRQ_CTRL 0x01 +#define RV3029C2_IRQ_FLAGS 0x02 +#define RV3029C2_STATUS 0x03 +#define RV3029C2_STATUS_VLOW1 (1 << 2) +#define RV3029C2_STATUS_VLOW2 (1 << 3) +#define RV3029C2_STATUS_SR (1 << 4) +#define RV3029C2_STATUS_EEBUSY (1 << 7) +#define RV3029C2_RST_CTRL 0x04 +#define RV3029C2_CONTROL_SECTION_LEN 0x05 + +/* watch section */ +#define RV3029C2_W_SECONDS 0x08 +#define RV3029C2_W_MINUTES 0x09 +#define RV3029C2_W_HOURS 0x0A +#define RV3029C2_REG_HR_12_24 (1<<6) /* 24h/12h mode */ +#define RV3029C2_REG_HR_PM (1<<5) /* PM/AM bit in 12h mode */ +#define RV3029C2_W_DATE 0x0B +#define RV3029C2_W_DAYS 0x0C +#define RV3029C2_W_MONTHS 0x0D +#define RV3029C2_W_YEARS 0x0E +#define RV3029C2_WATCH_SECTION_LEN 0x07 + +/* alarm section */ +#define RV3029C2_A_SC 0x10 +#define RV3029C2_A_MN 0x11 +#define RV3029C2_A_HR 0x12 +#define RV3029C2_A_DT 0x13 +#define RV3029C2_A_DW 0x14 +#define RV3029C2_A_MO 0x15 +#define RV3029C2_A_YR 0x16 +#define RV3029C2_ALARM_SECTION_LEN 0x07 + +/* timer section */ +#define RV3029C2_TIMER_LOW 0x18 +#define RV3029C2_TIMER_HIGH 0x19 + +/* temperature section */ +#define RV3029C2_TEMP_PAGE 0x20 + +/* eeprom data section */ +#define RV3029C2_E2P_EEDATA1 0x28 +#define RV3029C2_E2P_EEDATA2 0x29 + +/* eeprom control section */ +#define RV3029C2_CONTROL_E2P_EECTRL 0x30 +#define RV3029C2_TRICKLE_1K (1<<0) /* 1K resistance */ +#define RV3029C2_TRICKLE_5K (1<<1) /* 5K resistance */ +#define RV3029C2_TRICKLE_20K (1<<2) /* 20K resistance */ +#define RV3029C2_TRICKLE_80K (1<<3) /* 80K resistance */ +#define RV3029C2_CONTROL_E2P_XTALOFFSET 0x31 +#define RV3029C2_CONTROL_E2P_QCOEF 0x32 +#define RV3029C2_CONTROL_E2P_TURNOVER 0x33 + +/* user ram section */ +#define RV3029C2_USR1_RAM_PAGE 0x38 +#define RV3029C2_USR1_SECTION_LEN 0x04 +#define RV3029C2_USR2_RAM_PAGE 0x3C +#define RV3029C2_USR2_SECTION_LEN 0x04 + +static struct i2c_driver rv3029c2_driver; + +static int +rv3029c2_i2c_read_regs(struct i2c_client *client, u8 reg, u8 *buf, + unsigned len) +{ + u8 reg_addr[1] = { reg } ; + struct i2c_msg msgs[2] = { + {client->addr, 0, sizeof(reg_addr), reg_addr} + , + {client->addr, I2C_M_RD, len, buf} + }; + int ret; + + BUG_ON(reg > RV3029C2_USR1_RAM_PAGE + 7); + BUG_ON(reg + len > RV3029C2_USR1_RAM_PAGE + 8); + + ret = i2c_transfer(client->adapter, msgs, 2); + if (ret > 0) + ret = 0; + return ret; +} + +static int +rv3029c2_i2c_write_regs(struct i2c_client *client, u8 reg, u8 const buf[], + unsigned len) +{ + u8 i2c_buf[8]; + struct i2c_msg msgs[1] = { + {client->addr, 0, len + 1, i2c_buf} + }; + int ret; + + BUG_ON(reg > RV3029C2_USR1_RAM_PAGE + 7); + BUG_ON(reg + len > RV3029C2_USR1_RAM_PAGE + 8); + + i2c_buf[0] = reg; + memcpy(&i2c_buf[1], &buf[0], len); + ret = i2c_transfer(client->adapter, msgs, 1); + if (ret > 0) + ret = 0; + return ret; +} + +/* simple check to see if we have a rv3029c2 */ +static int +rv3029c2_i2c_validate_client(struct i2c_client *client) +{ + u8 regs[RV3029C2_WATCH_SECTION_LEN] = { 0, }; + u8 zero_mask[RV3029C2_WATCH_SECTION_LEN] = { + 0x80, 0x80, 0x80, 0xc0, 0xf8, 0xe0, 0x80 + }; + int i; + int ret; + + ret = rv3029c2_i2c_read_regs(client, RV3029C2_W_SECONDS, regs, + RV3029C2_WATCH_SECTION_LEN); + if (ret < 0) + return ret; + + for (i = 0; i < RV3029C2_WATCH_SECTION_LEN; ++i) { + if (regs[i] & zero_mask[i]) /* check if bits are cleared */ + return -ENODEV; + } + + return 0; +} + +static int +rv3029c2_i2c_get_sr(struct i2c_client *client) +{ + u8 buf[1] = { 0, }; + int sr = rv3029c2_i2c_read_regs(client, RV3029C2_STATUS, buf, 1); + printk(KERN_INFO "status = 0x%.2x (%d)\n", buf[0], buf[0]); + if (sr < 0) + return -EIO; + return sr; +} + +static int +rv3029c2_i2c_read_time(struct i2c_client *client, struct rtc_time *tm) +{ + int sr; + u8 regs[RV3029C2_WATCH_SECTION_LEN] = { 0, }; + + sr = rv3029c2_i2c_get_sr(client); + if (sr < 0) { + dev_err(&client->dev, "%s: reading SR failed\n", __func__); + return -EIO; + } + + sr = rv3029c2_i2c_read_regs(client, RV3029C2_W_SECONDS , regs, + RV3029C2_WATCH_SECTION_LEN); + if (sr < 0) { + dev_err(&client->dev, "%s: reading RTC section failed\n", + __func__); + return sr; + } + + tm->tm_sec = bcd2bin(regs[RV3029C2_W_SECONDS-RV3029C2_W_SECONDS]); + tm->tm_min = bcd2bin(regs[RV3029C2_W_MINUTES-RV3029C2_W_SECONDS]); + + /* HR field has a more complex interpretation */ + { + const u8 _hr = regs[RV3029C2_W_HOURS-RV3029C2_W_SECONDS]; + if (_hr & RV3029C2_REG_HR_12_24) /* 24h format */ + tm->tm_hour = bcd2bin(_hr & 0x3f); + else { + /* 12h format */ + tm->tm_hour = bcd2bin(_hr & 0x1f); + if (_hr & RV3029C2_REG_HR_PM) /* PM flag set */ + tm->tm_hour += 12; + } + } + + tm->tm_mday = bcd2bin(regs[RV3029C2_W_DATE-RV3029C2_W_SECONDS]); + tm->tm_mon = bcd2bin(regs[RV3029C2_W_MONTHS-RV3029C2_W_SECONDS])-1 ; + tm->tm_year = bcd2bin(regs[RV3029C2_W_YEARS-RV3029C2_W_SECONDS])+100; + tm->tm_wday = bcd2bin(regs[RV3029C2_W_DAYS-RV3029C2_W_SECONDS])-1; + + return 0; +} + +static int rv3029c2_rtc_read_time(struct device *dev, struct rtc_time *tm) +{ + return rv3029c2_i2c_read_time(to_i2c_client(dev), tm); +} + +static int +rv3029c2_i2c_read_alarm(struct i2c_client *client, struct rtc_wkalrm *alarm) +{ + struct rtc_time *const tm = &alarm->time; + int sr; + u8 regs[8]; + + sr = rv3029c2_i2c_get_sr(client); + if (sr < 0) { + dev_err(&client->dev, "%s: reading SR failed\n", __func__); + return -EIO; + } + + sr = rv3029c2_i2c_read_regs(client, RV3029C2_A_SC, regs, + RV3029C2_ALARM_SECTION_LEN); + + if (sr < 0) { + dev_err(&client->dev, "%s: reading alarm section failed\n", + __func__); + return sr; + } + + tm->tm_sec = bcd2bin(regs[RV3029C2_A_SC-RV3029C2_A_SC] & 0x7f); + tm->tm_min = bcd2bin(regs[RV3029C2_A_MN-RV3029C2_A_SC] & 0x7f); + tm->tm_hour = bcd2bin(regs[RV3029C2_A_HR-RV3029C2_A_SC] & 0x3f); + tm->tm_mday = bcd2bin(regs[RV3029C2_A_DT-RV3029C2_A_SC] & 0x3f); + tm->tm_mon = bcd2bin(regs[RV3029C2_A_MO-RV3029C2_A_SC] & 0x1f) - 1; + tm->tm_year = bcd2bin(regs[RV3029C2_A_YR-RV3029C2_A_SC] & 0x7f) + 100; + tm->tm_wday = bcd2bin(regs[RV3029C2_A_DW-RV3029C2_A_SC] & 0x07) - 1; + + return 0; +} + +static int +rv3029c2_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alarm) +{ + return rv3029c2_i2c_read_alarm(to_i2c_client(dev), alarm); +} + +static int +rv3029c2_i2c_set_time(struct i2c_client *client, struct rtc_time const *tm) +{ + u8 regs[8]; + int sr; + + /* The clock has an 8 bit wide bcd-coded register (they never learn) + * for the year. tm_year is an offset from 1900 and we are interested + * in the 2000-2099 range, so any value less than 100 is invalid. + */ + if (tm->tm_year < 100) + return -EINVAL; + + regs[RV3029C2_W_SECONDS-RV3029C2_W_SECONDS] = bin2bcd(tm->tm_sec); + regs[RV3029C2_W_MINUTES-RV3029C2_W_SECONDS] = bin2bcd(tm->tm_min); + regs[RV3029C2_W_HOURS-RV3029C2_W_SECONDS] = + bin2bcd(tm->tm_hour) | RV3029C2_REG_HR_12_24; + regs[RV3029C2_W_DATE-RV3029C2_W_SECONDS] = bin2bcd(tm->tm_mday); + regs[RV3029C2_W_MONTHS-RV3029C2_W_SECONDS] = bin2bcd(tm->tm_mon+1); + regs[RV3029C2_W_DAYS-RV3029C2_W_SECONDS] = bin2bcd((tm->tm_wday & 7)+1); + regs[RV3029C2_W_YEARS-RV3029C2_W_SECONDS] = bin2bcd(tm->tm_year - 100); + + sr = rv3029c2_i2c_get_sr(client); + if (sr < 0) { + dev_err(&client->dev, "%s: reading SR failed\n", __func__); + return sr; + } + + sr = rv3029c2_i2c_write_regs(client, RV3029C2_W_SECONDS, regs, + RV3029C2_WATCH_SECTION_LEN); + if (sr < 0) + return sr; + + return 0; +} + +static int rv3029c2_rtc_set_time(struct device *dev, struct rtc_time *tm) +{ + return rv3029c2_i2c_set_time(to_i2c_client(dev), tm); +} + +static const struct rtc_class_ops rv3029c2_rtc_ops = { + .read_time = rv3029c2_rtc_read_time, + .set_time = rv3029c2_rtc_set_time, + .read_alarm = rv3029c2_rtc_read_alarm, + /*.set_alarm = rv3029c2_rtc_set_alarm, */ +}; + +static int +rv3029c2_probe(struct i2c_client *client, const struct i2c_device_id *id) +{ + struct rtc_device *rtc; + int rc = 0; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) + return -ENODEV; + + if (rv3029c2_i2c_validate_client(client) < 0) + return -ENODEV; + + dev_info(&client->dev, + "chip found, driver version " DRV_VERSION "\n"); + + rtc = rtc_device_register(rv3029c2_driver.driver.name, + &client->dev, &rv3029c2_rtc_ops, + THIS_MODULE); + + if (IS_ERR(rtc)) + return PTR_ERR(rtc); + + i2c_set_clientdata(client, rtc); + + rc = rv3029c2_i2c_get_sr(client); + if (rc < 0) { + dev_err(&client->dev, "reading status failed\n"); + goto exit_unregister; + } + + return 0; + +exit_unregister: + rtc_device_unregister(rtc); + + return rc; +} + +static int rv3029c2_remove(struct i2c_client *client) +{ + struct rtc_device *rtc = i2c_get_clientdata(client); + + if (rtc) + rtc_device_unregister(rtc); + + return 0; +} + +static struct i2c_device_id rv3029c2_id[] = { + { "rv3029c2", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, rv3029c2_id); + +static struct i2c_driver rv3029c2_driver = { + .driver = { + .name = "rtc-rv3029c2", + }, + .probe = rv3029c2_probe, + .remove = rv3029c2_remove, + .id_table = rv3029c2_id, +}; + +static int __init rv3029c2_init(void) +{ + return i2c_add_driver(&rv3029c2_driver); +} + +static void __exit rv3029c2_exit(void) +{ + i2c_del_driver(&rv3029c2_driver); +} + +MODULE_AUTHOR("Gregory Hermant "); +MODULE_DESCRIPTION("Micro crystal RV3029C2 RTC driver"); +MODULE_LICENSE("GPL"); +MODULE_VERSION(DRV_VERSION); + +module_init(rv3029c2_init); +module_exit(rv3029c2_exit);