* [lm-sensors] [PATCH] I2C: add new rx8025 driver
@ 2006-02-01 10:23 Uwe Zeisberger
2006-02-01 19:23 ` Jean Delvare
` (5 more replies)
0 siblings, 6 replies; 7+ messages in thread
From: Uwe Zeisberger @ 2006-02-01 10:23 UTC (permalink / raw)
To: lm-sensors
Hello,
below is a patch adding support for the Epson RX8025 SA/NB RTC module.
There are a few things I'm not sure about:
- There exists a RTC driver interface in include/asm-arm/rtc.h by
Russell King. Should I use it even if it's ARM specific?
For now I used the ioctl interface directly.
- Is it correct to name the config symbol SENSORS_RTCRX8025?
I question because in my eyes an RTC is not a sensor. OTOH the driver
for the 8564 chip (and serveral others) uses SENSORS_RTC8564.
I'd be happy to get some feedback.
Best regards
Uwe
--- >8 ---
Adds support for the Epson RX8025 SA/NB RTC module.
Signed-off-by: Uwe Zeisberger <Uwe_Zeisberger at digi.com>
---
drivers/i2c/chips/Kconfig | 10 +
drivers/i2c/chips/Makefile | 1
drivers/i2c/chips/rx8025.c | 585 ++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 596 insertions(+), 0 deletions(-)
create mode 100644 drivers/i2c/chips/rx8025.c
8a23a3543a33d98c0ee7626f081e1c9538310afa
diff --git a/drivers/i2c/chips/Kconfig b/drivers/i2c/chips/Kconfig
index f9fae28..07061c9 100644
--- a/drivers/i2c/chips/Kconfig
+++ b/drivers/i2c/chips/Kconfig
@@ -74,6 +74,16 @@ config SENSORS_RTC8564
This driver can also be built as a module. If so, the module
will be called i2c-rtc8564.
+config SENSORS_RTCRX8025
+ tristate "Epson RX-8025 SA/NB RTC chip"
+ depends on I2C && EXPERIMENTAL
+ help
+ If you say yes here you get support for the
+ Epson RX-8025 SA/NB RTC chip.
+
+ This driver can also be built as a module. If so, the module
+ will be called rx8025.
+
config ISP1301_OMAP
tristate "Philips ISP1301 with OMAP OTG"
depends on I2C && ARCH_OMAP_OTG
diff --git a/drivers/i2c/chips/Makefile b/drivers/i2c/chips/Makefile
index 46178b5..16a5e51 100644
--- a/drivers/i2c/chips/Makefile
+++ b/drivers/i2c/chips/Makefile
@@ -11,6 +11,7 @@ obj-$(CONFIG_SENSORS_PCA9539) += pca9539
obj-$(CONFIG_SENSORS_PCF8574) += pcf8574.o
obj-$(CONFIG_SENSORS_PCF8591) += pcf8591.o
obj-$(CONFIG_SENSORS_RTC8564) += rtc8564.o
+obj-$(CONFIG_SENSORS_RTCRX8025) += rx8025.o
obj-$(CONFIG_ISP1301_OMAP) += isp1301_omap.o
obj-$(CONFIG_TPS65010) += tps65010.o
obj-$(CONFIG_RTC_X1205_I2C) += x1205.o
diff --git a/drivers/i2c/chips/rx8025.c b/drivers/i2c/chips/rx8025.c
new file mode 100644
index 0000000..77356a1
--- /dev/null
+++ b/drivers/i2c/chips/rx8025.c
@@ -0,0 +1,585 @@
+/*
+ * drivers/i2c/chips/rx8025.c
+ *
+ * Driver for Epson's RTC module RX-8025 SA/NB
+ *
+ * Copyright (C) 2005 by Digi International Inc.
+ * All rights reserved.
+ *
+ * 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 <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/bcd.h>
+#include <linux/i2c.h>
+#include <linux/init.h>
+#include <linux/list.h>
+#include <linux/rtc.h>
+#include <linux/spinlock.h>
+#include <linux/fs.h>
+#include <linux/miscdevice.h>
+#include <asm/uaccess.h>
+
+/* registers */
+#define RX8025_REG_SEC 0x00
+#define RX8025_REG_MIN 0x01
+#define RX8025_REG_HOUR 0x02
+#define RX8025_REG_WDAY 0x03
+#define RX8025_REG_MDAY 0x04
+#define RX8025_REG_MONTH 0x05
+#define RX8025_REG_YEAR 0x06
+#define RX8025_REG_DIGOFF 0x07
+#define RX8025_REG_ALWMIN 0x08
+#define RX8025_REG_ALWHOUR 0x09
+#define RX8025_REG_ALWWDAY 0x0a
+#define RX8025_REG_ALDMIN 0x0b
+#define RX8025_REG_ALDHOUR 0x0c
+/* 0x0e is reserved */
+#define RX8025_REG_CTRL1 0x0e
+#define RX8025_REG_CTRL2 0x0f
+
+#define RX8025_BIT_CTRL1_1224 (1 << 5)
+#define RX8025_BIT_CTRL1_DALE (1 << 6)
+#define RX8025_BIT_CTRL1_WALE (1 << 7)
+
+#define RX8025_BIT_CTRL2_PON (1 << 4)
+#define RX8025_BIT_CTRL2_VDET (1 << 6)
+
+static unsigned short normal_i2c[] = { 0x32, I2C_CLIENT_END };
+I2C_CLIENT_INSMOD_1(rx8025);
+
+static int rx8025_attach_adapter(struct i2c_adapter *adapter);
+static int rx8025_detect(struct i2c_adapter *adapter, int address, int kind);
+static int rx8025_init_client(struct i2c_client *client);
+static int rx8025_detach_client(struct i2c_client *client);
+static int rx8025_rtc_ioctl(struct inode *inode, struct file *file,
+ unsigned int cmd, unsigned long arg);
+
+static struct i2c_driver rx8025_driver = {
+ .driver = {
+ .name = "rx8025",
+ },
+ .attach_adapter = &rx8025_attach_adapter,
+ .detach_client = &rx8025_detach_client,
+};
+
+static struct i2c_client *rx8025_rtcclient = NULL;
+static struct file_operations rx8025_rtc_fops = {
+ .ioctl = rx8025_rtc_ioctl,
+};
+
+static struct miscdevice rx8025_rtc_dev = {
+ .minor = RTC_MINOR,
+ .name = "rtc",
+ .fops = &rx8025_rtc_fops,
+};
+
+struct rx8025_data {
+ struct i2c_client client;
+ struct list_head list;
+};
+
+static LIST_HEAD(rx8025_clients);
+static DECLARE_MUTEX(rx8025_lock);
+
+static int rx8025_get_rtctime(struct rtc_time *dt)
+{
+ u8 command = RX8025_REG_CTRL1 << 4 | 0x08;
+ u8 result[9] = { 0, };
+ int i, err;
+
+ struct i2c_msg msg[] = {
+ {
+ .len = 1,
+ .buf = &command,
+ }, {
+ .flags = I2C_M_RD,
+ .len = ARRAY_SIZE(result),
+ .buf = result,
+ }
+ };
+
+ if(down_interruptible(&rx8025_lock))
+ return -ERESTARTSYS;
+
+ if (!rx8025_rtcclient) {
+ err = -EIO;
+ goto errout_unlock;
+ }
+
+ if (!dt) {
+ dev_err(&rx8025_rtcclient->dev,
+ "rx8025_get_rtctime: passed in dt = NULL\n");
+ err = -EINVAL;
+ goto errout_unlock;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(msg); ++i)
+ msg[i].addr = rx8025_rtcclient->addr;
+
+ if ((err = i2c_transfer(rx8025_rtcclient->adapter, msg, 2)) < 2) {
+ err = err >= 0 ? -EIO : err;
+ goto errout_unlock;
+ }
+
+ up(&rx8025_lock);
+
+ dev_dbg(&rx8025_rtcclient->dev,
+ "rx8025_get_rtctime: read 0x%02x 0x%02x 0x%02x 0x%02x"
+ " 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x\n", result[0],
+ result[1], result[2], result[3], result[4], result[5],
+ result[6], result[7], result[8]);
+
+ /* according to the documention read-only-bits read as 0 */
+#define CHECK_ROBITS(reg, mask) \
+ if (unlikely(result[2 + (reg)] & ~(mask))) { \
+ dev_warn(&rx8025_rtcclient->dev, \
+ "value for " #reg " out of spec: %x\n", \
+ result[2 + (reg)]); \
+ result[2 + RX8025_REG_SEC] &= (mask); \
+ }
+ CHECK_ROBITS(RX8025_REG_SEC, 0x7f);
+ CHECK_ROBITS(RX8025_REG_MIN, 0x7f);
+ CHECK_ROBITS(RX8025_REG_HOUR, 0x3f);
+ CHECK_ROBITS(RX8025_REG_WDAY, 0x07);
+ CHECK_ROBITS(RX8025_REG_MDAY, 0x3f);
+ CHECK_ROBITS(RX8025_REG_MONTH, 0x1f);
+
+ /* I don't have to do more checking, don't I? */
+ dt->tm_sec = BCD2BIN(result[2 + RX8025_REG_SEC]);
+ dt->tm_min = BCD2BIN(result[2 + RX8025_REG_MIN]);
+ if (result[0] | RX8025_BIT_CTRL1_1224)
+ dt->tm_hour = BCD2BIN(result[2 + RX8025_REG_HOUR]);
+ else
+ dt->tm_hour = BCD2BIN(result[2 + RX8025_REG_HOUR] & 0x1f) % 12
+ + (result[2 + RX8025_REG_HOUR] & 0x20 ? 12 : 0);
+
+ dt->tm_wday = BCD2BIN(result[2 + RX8025_REG_WDAY]);
+ dt->tm_mday = BCD2BIN(result[2 + RX8025_REG_MDAY]);
+ dt->tm_mon = BCD2BIN(result[2 + RX8025_REG_MONTH]) - 1;
+ dt->tm_year = BCD2BIN(result[2 + RX8025_REG_YEAR]);
+
+ if (dt->tm_year < 70)
+ dt->tm_year += 100;
+
+ dev_dbg(&rx8025_rtcclient->dev,
+ "rx8025_get_rtctime: "
+ "result: %ds %dm %dh %dwd %dmd %dm %dy\n",
+ dt->tm_sec, dt->tm_min, dt->tm_hour, dt->tm_wday,
+ dt->tm_mday, dt->tm_mon, dt->tm_year);
+
+ return 0;
+
+errout_unlock:
+ up(&rx8025_lock);
+ return err;
+}
+
+static int rx8025_set_rtctime(struct rtc_time *dt)
+{
+ u8 command[8] = { RX8025_REG_SEC << 4, };
+ int err;
+ s32 ctrl1;
+
+ struct i2c_msg msg = {
+ .len = 8,
+ .buf = command,
+ };
+
+ if(down_interruptible(&rx8025_lock))
+ return -ERESTARTSYS;
+
+ if (!rx8025_rtcclient) {
+ err = -EIO;
+ goto errout_unlock;
+ }
+
+ if (!dt) {
+ dev_err(&rx8025_rtcclient->dev,
+ "rx8025_set_rtctime: passed in dt = NULL\n");
+ err = -EINVAL;
+ goto errout_unlock;
+ }
+
+ if (dt->tm_sec < 0 || dt->tm_sec >= 60) {
+ dev_err(&rx8025_rtcclient->dev,
+ "rx8025_set_rtctime: seconds out of range: %d\n",
+ dt->tm_sec);
+ err = -EINVAL;
+ goto errout_unlock;
+ }
+
+ if (dt->tm_min < 0 || dt->tm_min >= 60) {
+ dev_err(&rx8025_rtcclient->dev,
+ "rx8025_set_rtctime: minutes out of range: %d\n",
+ dt->tm_min);
+ err = -EINVAL;
+ goto errout_unlock;
+ }
+
+ if (dt->tm_hour < 0 || dt->tm_hour >= 24) {
+ dev_err(&rx8025_rtcclient->dev,
+ "rx8025_set_rtctime: hours out of range: %d\n",
+ dt->tm_hour);
+ err = -EINVAL;
+ goto errout_unlock;
+ }
+
+ if (dt->tm_wday < 0 || dt->tm_wday >= 7) {
+ dev_err(&rx8025_rtcclient->dev,
+ "rx8025_set_rtctime: hours out of range: %d\n",
+ dt->tm_wday);
+ err = -EINVAL;
+ goto errout_unlock;
+ }
+
+ if (dt->tm_mon < 0 || dt->tm_mon >= 12) {
+ dev_err(&rx8025_rtcclient->dev,
+ "rx8025_set_rtctime: month out of range: %d\n",
+ dt->tm_mon);
+ err = -EINVAL;
+ goto errout_unlock;
+ }
+
+ if (dt->tm_year < 70 || dt->tm_mon >= 170) {
+ dev_err(&rx8025_rtcclient->dev,
+ "rx8025_set_rtctime: year out of range: %d\n",
+ dt->tm_year);
+ err = -EINVAL;
+ goto errout_unlock;
+ }
+
+ /*
+ * BUG: The HW assumes every year that is a multiple of 4 to be a leap
+ * year. Next time this is wrong is 2100, which will not be a leap
+ * year.
+ */
+ if (dt->tm_mday > 31 ||
+ ((dt->tm_mon = 3 || dt->tm_mon = 5 ||
+ dt->tm_mon = 8 || dt->tm_mon = 10)
+ && dt->tm_mday > 30) ||
+ (dt->tm_mon = 1 && dt->tm_mday > 28 +
+ (dt->tm_year & 3 ? 0 : 1))) {
+ dev_err(&rx8025_rtcclient->dev,
+ "rx8025_set_rtctime: day out of range (with month=%d, "
+ "year=%d): %d\n", dt->tm_mon, dt->tm_year, dt->tm_mon);
+ err = -EINVAL;
+ goto errout_unlock;
+ }
+
+ if ((ctrl1 = i2c_smbus_read_byte_data(rx8025_rtcclient,
+ RX8025_REG_CTRL1 << 4)) < 0) {
+ dev_err(&rx8025_rtcclient->dev,
+ "rx8025_set_rtctime: failed to read out "
+ "RX8025_REG_CTRL1\n");
+ err = -EINVAL;
+ goto errout_unlock;
+ }
+
+ dev_dbg(&rx8025_rtcclient->dev,
+ "rx8025_set_rtctime: ctrl1=0x%x\n", ctrl1);
+ /*
+ * Here the read-only bits are written as "0". I'm not sure if that
+ * is sound.
+ */
+ command[1 + RX8025_REG_SEC] = BIN2BCD(dt->tm_sec);
+ command[1 + RX8025_REG_MIN] = BIN2BCD(dt->tm_min);
+ if (ctrl1 & RX8025_BIT_CTRL1_1224)
+ command[1 + RX8025_REG_HOUR] = BIN2BCD(dt->tm_hour);
+ else
+ command[1 + RX8025_REG_HOUR] = (dt->tm_hour >= 12 ? 1 << 5 : 0) |
+ BIN2BCD((dt->tm_hour + 11) % 12 + 1);
+
+ command[1 + RX8025_REG_WDAY] = BIN2BCD(dt->tm_wday);
+ command[1 + RX8025_REG_MDAY] = BIN2BCD(dt->tm_mday);
+ command[1 + RX8025_REG_MONTH] = BIN2BCD(dt->tm_mon + 1);
+ command[1 + RX8025_REG_YEAR] = BIN2BCD(dt->tm_year % 100);
+
+ msg.addr = rx8025_rtcclient->addr;
+
+ dev_dbg(&rx8025_rtcclient->dev,
+ "rx8025_set_rtctime: send 0x%02x 0x%02x 0x%02x 0x%02x"
+ " 0x%02x 0x%02x 0x%02x 0x%02x\n", command[0],
+ command[1], command[2], command[3], command[4],
+ command[5], command[6], command[7]);
+
+ if ((err = i2c_transfer(rx8025_rtcclient->adapter, &msg, 1)) < 1) {
+ err = err >= 0 ? -EIO : err;
+ goto errout_unlock;
+ }
+
+ up(&rx8025_lock);
+
+ return 0;
+
+errout_unlock:
+ up(&rx8025_lock);
+ return err;
+}
+
+static int rx8025_attach_adapter(struct i2c_adapter *adapter)
+{
+ return i2c_probe(adapter, &addr_data, &rx8025_detect);
+}
+
+static int rx8025_detect(struct i2c_adapter *adapter, int address, int kind)
+{
+ int err = 0;
+ struct rx8025_data *data;
+ struct i2c_client *new_client;
+
+ if (!i2c_check_functionality(adapter, I2C_FUNC_I2C)) {
+ dev_dbg(&adapter->dev, "doesn't support full I2C\n");
+ goto errout;
+ }
+
+ if (!(data = kzalloc(sizeof(*data), GFP_KERNEL))) {
+ dev_dbg(&adapter->dev, "failed to alloc memory\n");
+ err = -ENOMEM;
+ goto errout;
+ }
+
+ new_client = &data->client;
+ i2c_set_clientdata(new_client, data);
+ new_client->addr = address;
+ new_client->adapter = adapter;
+ new_client->driver = &rx8025_driver;
+ new_client->flags = 0;
+
+ INIT_LIST_HEAD(&data->list);
+
+ if (kind = 0)
+ kind = rx8025;
+
+ if (kind < 0) {
+ u8 command = RX8025_REG_SEC << 4 | 0x08;
+ u8 result[8] = { 0, };
+ int err;
+
+ struct i2c_msg msg[] = {
+ {
+ .addr = address,
+ .len = 1,
+ .buf = &command,
+ }, {
+ .addr = address,
+ .flags = I2C_M_RD,
+ .len = ARRAY_SIZE(result),
+ .buf = result,
+ }
+ };
+
+ if ((err = i2c_transfer(adapter, msg, ARRAY_SIZE(msg)))
+ < ARRAY_SIZE(msg)) {
+ err = err >= 0 ? 0 : err;
+ goto errout_free;
+ }
+
+ /* bits 7 of RX8025_REG_MONTH and RX8025_REG_DIGOFF
+ * "should be set as "0". Their value when read will be "0""
+ *
+ * There are serveral more read-only bits who's "value when
+ * read is always "0"" according to the spec, but setting them
+ * yields a "1" sometimes.
+ */
+ if (((result[RX8025_REG_MONTH] | result[RX8025_REG_DIGOFF])
+ & 0x80)) {
+ dev_dbg(&adapter->dev, "RX8025_REG_MONTH = %x, "
+ "RX8025_REG_DIGOFF = %x\n",
+ result[RX8025_REG_MONTH],
+ result[RX8025_REG_DIGOFF]);
+
+
+ goto errout_free;
+ }
+
+ kind = rx8025;
+ }
+
+ BUG_ON(kind != rx8025);
+
+ strlcpy(new_client->name, "rx8025", I2C_NAME_SIZE);
+
+ if(down_interruptible(&rx8025_lock)) {
+ err = -ERESTARTSYS;
+ goto errout_free;
+ }
+
+ if ((err = i2c_attach_client(new_client)))
+ goto errout_unlock;
+
+ list_add(&data->list, &rx8025_clients);
+
+ if ((err = rx8025_init_client(new_client)))
+ goto errout_detach;
+
+ if (!rx8025_rtcclient) {
+ rx8025_rtcclient = new_client;
+ if ((err = misc_register(&rx8025_rtc_dev))) {
+ rx8025_rtcclient = NULL;
+ printk(KERN_ERR "Failed to register rtc device\n");
+ }
+ }
+ up(&rx8025_lock);
+
+ printk(KERN_INFO "loaded driver for RX-8025 SA/NB on addr %d\n",
+ address);
+
+ return 0;
+
+errout_detach:
+ rx8025_detach_client(new_client);
+
+errout_unlock:
+ up(&rx8025_lock);
+
+errout_free:
+ kfree(data);
+
+errout:
+ dev_dbg(&adapter->dev, "Failed to detect rx8025\n");
+ return err;
+}
+
+static int rx8025_init_client(struct i2c_client *client)
+{
+ u8 command[3] = { RX8025_REG_CTRL1 << 4 | 0x08, };
+ int err;
+
+ struct i2c_msg msg[] = {
+ {
+ .addr = client->addr,
+ .len = 1,
+ .buf = command,
+ }, {
+ .addr = client->addr,
+ .flags = I2C_M_RD,
+ .len = ARRAY_SIZE(command) - 1,
+ .buf = command + 1,
+ }
+ };
+
+ if ((err = i2c_transfer(client->adapter, msg, 2)) < 2) {
+ err = err >= 0 ? -EIO : err;
+ goto errout;
+ }
+
+ /* Assert 24-hour mode */
+ command[1] |= RX8025_BIT_CTRL1_1224;
+
+ /* disable Alarm D and Alarm W */
+ command[1] &= ~(RX8025_BIT_CTRL1_DALE | RX8025_BIT_CTRL1_WALE);
+
+ if (command[2] & RX8025_BIT_CTRL2_PON)
+ dev_warn(&client->dev, "power-on reset was detected, "
+ "you may have to readjust the clock\n");
+
+ if (command[2] & RX8025_BIT_CTRL2_VDET)
+ dev_warn(&client->dev, "a power voltage drop was detected, "
+ "you may have to readjust the clock\n");
+
+ command[2] &= ~(RX8025_BIT_CTRL2_PON | RX8025_BIT_CTRL2_VDET);
+
+ command[0] = RX8025_REG_CTRL1 << 4;
+ msg[0].len = ARRAY_SIZE(command);
+
+ if ((err = i2c_transfer(client->adapter, msg, 1)) < 1) {
+ err = err >= 0 ? -EIO : err;
+ goto errout;
+ }
+
+ return 0;
+
+errout:
+ return err;
+}
+
+static int rx8025_detach_client(struct i2c_client *client)
+{
+ int err;
+ struct rx8025_data *data = i2c_get_clientdata(client);
+
+ if(down_interruptible(&rx8025_lock))
+ return -ERESTARTSYS;
+
+ BUG_ON(list_empty(&rx8025_clients));
+
+ if (rx8025_rtcclient = client) {
+ struct list_head *lh = rx8025_clients.next;
+
+
+ if (list_entry(lh, struct rx8025_data, list) = data)
+ lh = lh->next;
+
+ if (lh = &rx8025_clients) {
+
+ if ((err = misc_deregister(&rx8025_rtc_dev))) {
+ up(&rx8025_lock);
+ return err;
+ }
+
+ rx8025_rtcclient = NULL;
+ } else
+ rx8025_rtcclient = &data->client;
+ }
+
+ if ((err = i2c_detach_client(client))) {
+ up(&rx8025_lock);
+ return err;
+ }
+
+ list_del(&data->list);
+
+ up(&rx8025_lock);
+ kfree(data);
+ return 0;
+}
+
+static int rx8025_rtc_ioctl(struct inode *inode, struct file *file,
+ unsigned int cmd, unsigned long arg)
+{
+ int err;
+ struct rtc_time rtctime;
+
+ dev_dbg(&rx8025_rtcclient->dev, "got command %d\n", cmd);
+ switch(cmd) {
+ case RTC_RD_TIME:
+ memset(&rtctime, 0, sizeof(rtctime));
+ if ((err = rx8025_get_rtctime(&rtctime)))
+ return err;
+
+ return copy_to_user((void __user *)arg, &rtctime,
+ sizeof(rtctime)) ? -EFAULT : 0;
+
+ case RTC_SET_TIME:
+ if (!capable(CAP_SYS_TIME))
+ return -EACCES;
+
+ if (copy_from_user(&rtctime, (const void __user *)arg,
+ sizeof(rtctime)))
+ return -EFAULT;
+
+ return rx8025_set_rtctime(&rtctime);
+ }
+ dev_dbg(&rx8025_rtcclient->dev, "unhandled command %d\n", cmd);
+ return -EINVAL;
+}
+
+static int __init rx8025_init(void)
+{
+ return i2c_add_driver(&rx8025_driver);
+}
+
+static void __exit rx8025_exit(void)
+{
+ i2c_del_driver(&rx8025_driver);
+}
+
+MODULE_AUTHOR("Uwe Zeisberger <Uwe_Zeisberger at digi.com>");
+MODULE_DESCRIPTION("RX-8025 SA/NB RTC driver");
+MODULE_LICENSE("GPL");
+
+module_init(rx8025_init);
+module_exit(rx8025_exit);
--
1.1.6.g8233
--
Uwe Zeisberger
FS Forth-Systeme GmbH, A Digi International Company
Kueferstrasse 8, D-79206 Breisach, Germany
Phone: +49 (7667) 908 0 Fax: +49 (7667) 908 200
Web: www.fsforth.de, www.digi.com
^ permalink raw reply related [flat|nested] 7+ messages in thread* [lm-sensors] [PATCH] I2C: add new rx8025 driver 2006-02-01 10:23 [lm-sensors] [PATCH] I2C: add new rx8025 driver Uwe Zeisberger @ 2006-02-01 19:23 ` Jean Delvare 2006-02-02 8:39 ` Uwe Zeisberger ` (4 subsequent siblings) 5 siblings, 0 replies; 7+ messages in thread From: Jean Delvare @ 2006-02-01 19:23 UTC (permalink / raw) To: lm-sensors Hi Uwe, > below is a patch adding support for the Epson RX8025 SA/NB RTC module. First of all: where is this chip found? Have you verified if this chip isn't compatible with other chips we have drivers for already? > - There exists a RTC driver interface in include/asm-arm/rtc.h by > Russell King. Should I use it even if it's ARM specific? > For now I used the ioctl interface directly. See with Alessandro Zummo, he is working on a unified RTC core. Maybe you can simplify your code by using it. > - Is it correct to name the config symbol SENSORS_RTCRX8025? > I question because in my eyes an RTC is not a sensor. OTOH the driver > for the 8564 chip (and serveral others) uses SENSORS_RTC8564. No, please name it RTC_RX8025 instead. SENSORS_* names for non-sensors drivers is a legacy which we are trying to get rid of. My comments on your code follow. > +#define RX8025_REG_ALDHOUR 0x0c > +/* 0x0e is reserved */ > +#define RX8025_REG_CTRL1 0x0e > +#define RX8025_REG_CTRL2 0x0f Don't you mean 0x0d is reserved? > +static struct i2c_client *rx8025_rtcclient = NULL; Don't initialize a static global to NULL explicitly, the compiler will do it for you. > +static int rx8025_get_rtctime(struct rtc_time *dt) > +{ > + u8 command = RX8025_REG_CTRL1 << 4 | 0x08; I'd suggest parentheses for more clarity. > + u8 result[9] = { 0, }; Doesn't seem to actually require an initialization. > + if(down_interruptible(&rx8025_lock)) > + return -ERESTARTSYS; Coding style: space between "if" and opening parenthesis. Also, what exactly are you protecting with this mutex? > + dev_dbg(&rx8025_rtcclient->dev, > + "rx8025_get_rtctime: read 0x%02x 0x%02x 0x%02x 0x%02x" > + " 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x\n", result[0], > + result[1], result[2], result[3], result[4], result[5], > + result[6], result[7], result[8]); When splitting strings, please have the middle space at the end of the first half rather than the beginning of the second half. It makes it easier to spot missing spaces. > + /* according to the documention read-only-bits read as 0 */ > +#define CHECK_ROBITS(reg, mask) \ > + if (unlikely(result[2 + (reg)] & ~(mask))) { \ > + dev_warn(&rx8025_rtcclient->dev, \ > + "value for " #reg " out of spec: %x\n", \ > + result[2 + (reg)]); \ > + result[2 + RX8025_REG_SEC] &= (mask); \ > + } > + CHECK_ROBITS(RX8025_REG_SEC, 0x7f); > + CHECK_ROBITS(RX8025_REG_MIN, 0x7f); > + CHECK_ROBITS(RX8025_REG_HOUR, 0x3f); > + CHECK_ROBITS(RX8025_REG_WDAY, 0x07); > + CHECK_ROBITS(RX8025_REG_MDAY, 0x3f); > + CHECK_ROBITS(RX8025_REG_MONTH, 0x1f); Since you end up checking all unused bits regardless of what the datasheet says, it would be more efficient - and more readble - to just mask the values you read. + /* I don't have to do more checking, don't I? */ English: "don't I" should be "do I". > +static int rx8025_set_rtctime(struct rtc_time *dt) > +{ > (...) > + if(down_interruptible(&rx8025_lock)) > + return -ERESTARTSYS; Coding style. > + if (dt->tm_wday < 0 || dt->tm_wday >= 7) { > + dev_err(&rx8025_rtcclient->dev, > + "rx8025_set_rtctime: hours out of range: %d\n", > + dt->tm_wday); Typo: "hours" should be "week day". > + if (dt->tm_year < 70 || dt->tm_mon >= 170) { Typo: "dt->tm_mon" should be "dt->tm_year". > + /* > + * BUG: The HW assumes every year that is a multiple of 4 to be a leap > + * year. Next time this is wrong is 2100, which will not be a leap > + * year. > + */ > + if (dt->tm_mday > 31 || > + ((dt->tm_mon = 3 || dt->tm_mon = 5 || > + dt->tm_mon = 8 || dt->tm_mon = 10) > + && dt->tm_mday > 30) || > + (dt->tm_mon = 1 && dt->tm_mday > 28 + > + (dt->tm_year & 3 ? 0 : 1))) { > + dev_err(&rx8025_rtcclient->dev, > + "rx8025_set_rtctime: day out of range (with month=%d, " > + "year=%d): %d\n", dt->tm_mon, dt->tm_year, dt->tm_mon); > + err = -EINVAL; > + goto errout_unlock; > + } Other RTC drivers typically use a 12-element array defining the number of day in each month. It's probably cleaner and more efficient. See i2c-x1205 for an example. Also, last parameter of dev_err() should be dt->tm_mday, not dt->tm_mon. > + if ((ctrl1 = i2c_smbus_read_byte_data(rx8025_rtcclient, > + RX8025_REG_CTRL1 << 4)) < 0) { > + dev_err(&rx8025_rtcclient->dev, > + "rx8025_set_rtctime: failed to read out " > + "RX8025_REG_CTRL1\n"); > + err = -EINVAL; > + goto errout_unlock; > + } -EINVAL doesn't seem to be appropriate. -EIO? > + if ((err = i2c_transfer(rx8025_rtcclient->adapter, &msg, 1)) < 1) { > + err = err >= 0 ? -EIO : err; > + goto errout_unlock; > + } i2c_master_send is a convenient wrapper to i2c_transfer when sending a single message (but see right below.) > +static int rx8025_detect(struct i2c_adapter *adapter, int address, int kind) > +{ > + int err = 0; > + struct rx8025_data *data; > + struct i2c_client *new_client; Use "client" instead of "new_client" please. Another legacy we try to get rid of. > + if (!i2c_check_functionality(adapter, I2C_FUNC_I2C)) { > + dev_dbg(&adapter->dev, "doesn't support full I2C\n"); > + goto errout; > + } Given that all block transfers you do are standard I2C block transfers, you may consider switching to i2c_smbus_{read,write}_i2c_block_data for SMBus compatibility. This would also make your code more simple, I think. > + new_client->flags = 0; Not needed, as you've kzalloc'd the client structure. > + if (kind < 0) { > + u8 command = RX8025_REG_SEC << 4 | 0x08; > + u8 result[8] = { 0, }; Initalization not needed AFAICS. > + /* bits 7 of RX8025_REG_MONTH and RX8025_REG_DIGOFF > + * "should be set as "0". Their value when read will be "0"" > + * > + * There are serveral more read-only bits who's "value when > + * read is always "0"" according to the spec, but setting them > + * yields a "1" sometimes. > + */ > + if (((result[RX8025_REG_MONTH] | result[RX8025_REG_DIGOFF]) > + & 0x80)) { > + dev_dbg(&adapter->dev, "RX8025_REG_MONTH = %x, " > + "RX8025_REG_DIGOFF = %x\n", > + result[RX8025_REG_MONTH], > + result[RX8025_REG_DIGOFF]); > + > + > + goto errout_free; > + } Detection is a bit poor. It could be made much more robust by checking the acual values of the registers. See i2c-x1205. > + if ((err = misc_register(&rx8025_rtc_dev))) { > + rx8025_rtcclient = NULL; > + printk(KERN_ERR "Failed to register rtc device\n"); > + } Please use dev_err(). > + printk(KERN_INFO "loaded driver for RX-8025 SA/NB on addr %d\n", > + address); Misleading message. What you did here is register the device with the driver. The driver could have been loaded without ever registering anything. Also, you should use dev_info. > +static int rx8025_detach_client(struct i2c_client *client) > +{ > (...) > + if (rx8025_rtcclient = client) { > + struct list_head *lh = rx8025_clients.next; > + > + > + if (list_entry(lh, struct rx8025_data, list) = data) > + lh = lh->next; No double blank line please. Additionally, some documentation file in Documentation/i2c/chips would be welcome. And feel free to add yourself in MAINTAINERS as the maintainer of this new driver if you want to take care of it in the future. -- Jean Delvare ^ permalink raw reply [flat|nested] 7+ messages in thread
* [lm-sensors] [PATCH] I2C: add new rx8025 driver 2006-02-01 10:23 [lm-sensors] [PATCH] I2C: add new rx8025 driver Uwe Zeisberger 2006-02-01 19:23 ` Jean Delvare @ 2006-02-02 8:39 ` Uwe Zeisberger 2006-02-03 8:51 ` Uwe Zeisberger ` (3 subsequent siblings) 5 siblings, 0 replies; 7+ messages in thread From: Uwe Zeisberger @ 2006-02-02 8:39 UTC (permalink / raw) To: lm-sensors Hi Jean, thanks for all your suggestions, I'll go through them. I agree to most of your suggestions. I don't comment on those and just prepare a new patch. Some of them are mortifying, sorry for them. (Actually I put in these to check how exact the reviews are. You passed ;-) Jean Delvare wrote: > > below is a patch adding support for the Epson RX8025 SA/NB RTC module. > > First of all: where is this chip found? This is a driver I wrote for a custom SoC. Otherwise Google found one or two mails by people having ported the rtc8564 driver to the rx8025 chip. > Have you verified if this chip isn't compatible with other chips we > have drivers for already? I just compared with the rtc8564 driver as I didn't expect to match that chip. In retrospect this proofs true. (I compared with the kconfig symbols in drivers/i2c/chips that have [Rr][Tt][Cc] in their description. Did I miss anything?) > > - There exists a RTC driver interface in include/asm-arm/rtc.h by > > Russell King. Should I use it even if it's ARM specific? > > For now I used the ioctl interface directly. > > See with Alessandro Zummo, he is working on a unified RTC core. Maybe > you can simplify your code by using it. He answered my mail, too. I'll look into it. > Don't initialize a static global to NULL explicitly, the compiler will > do it for you. I'm not fluent in the different C variants, so I usually prefer to explicitly initialize static global variables with NULL, too. > > + if(down_interruptible(&rx8025_lock)) > > + return -ERESTARTSYS; > [...], what exactly are you protecting with this mutex? rx8025_clients and rx8025_rtcclient. Maybe I can get rid of it when using Alessandro's RTC core. > > + /* according to the documention read-only-bits read as 0 */ > > +#define CHECK_ROBITS(reg, mask) \ > > + if (unlikely(result[2 + (reg)] & ~(mask))) { \ > > + dev_warn(&rx8025_rtcclient->dev, \ > > + "value for " #reg " out of spec: %x\n", \ > > + result[2 + (reg)]); \ > > + result[2 + RX8025_REG_SEC] &= (mask); \ > > + } > > + CHECK_ROBITS(RX8025_REG_SEC, 0x7f); > > + CHECK_ROBITS(RX8025_REG_MIN, 0x7f); > > + CHECK_ROBITS(RX8025_REG_HOUR, 0x3f); > > + CHECK_ROBITS(RX8025_REG_WDAY, 0x07); > > + CHECK_ROBITS(RX8025_REG_MDAY, 0x3f); > > + CHECK_ROBITS(RX8025_REG_MONTH, 0x1f); > > Since you end up checking all unused bits regardless of what the > datasheet says, it would be more efficient - and more readble - to just > mask the values you read. While developping I observed that having some of these bits set results in wrong transitions. So I want to warn about it. I put the masking in the if body to have CHECK_ROBITS expand to one statement (without doing do {...} while(0)). Maybe it's sensible to move that check to the detection phase as you suggested. > Use "client" instead of "new_client" please. Another legacy we try to > get rid of. That was one that astonished me, too. I just did that to be consistent. > > + if (!i2c_check_functionality(adapter, I2C_FUNC_I2C)) { > > + dev_dbg(&adapter->dev, "doesn't support full I2C\n"); > > + goto errout; > > + } > > Given that all block transfers you do are standard I2C block transfers, > you may consider switching to i2c_smbus_{read,write}_i2c_block_data for > SMBus compatibility. This would also make your code more simple, I > think. Well I just found out the SMBus interface may match my needs when I posted the driver. I'm not sure about the transfer in rx8025_get_rtctime. In the spec this is called "Simplified read method" so I have to check if the interface suits. (But OTOH the chips supports the "Standard read method for I2C bus", too, so there should be no problem.) The difference is that I don't have to sent "restart", slave address and read bit as part of the request. > > + new_client->flags = 0; > > Not needed, as you've kzalloc'd the client structure. That's a relict, the driver was initially developped for 2.6.12.5. kzalloc is newer. > And feel free to add yourself in MAINTAINERS as the maintainer of this > new driver if you want to take care of it in the future. Yes, I want to, but I didn't hat the courage to do that when submitting the first (non-trivial) patch. Best regards and many thanks for your detailed review. Uwe -- Uwe Zeisberger FS Forth-Systeme GmbH, A Digi International Company Kueferstrasse 8, D-79206 Breisach, Germany Phone: +49 (7667) 908 0 Fax: +49 (7667) 908 200 Web: www.fsforth.de, www.digi.com ^ permalink raw reply [flat|nested] 7+ messages in thread
* [lm-sensors] [PATCH] I2C: add new rx8025 driver 2006-02-01 10:23 [lm-sensors] [PATCH] I2C: add new rx8025 driver Uwe Zeisberger 2006-02-01 19:23 ` Jean Delvare 2006-02-02 8:39 ` Uwe Zeisberger @ 2006-02-03 8:51 ` Uwe Zeisberger 2006-02-03 9:16 ` Jean Delvare ` (2 subsequent siblings) 5 siblings, 0 replies; 7+ messages in thread From: Uwe Zeisberger @ 2006-02-03 8:51 UTC (permalink / raw) To: lm-sensors Hello, Jean Delvare wrote: > Given that all block transfers you do are standard I2C block transfers, > you may consider switching to i2c_smbus_{read,write}_i2c_block_data for > SMBus compatibility. This would also make your code more simple, I > think. I don't understand how I specify the length of the block to be read for i2c_smbus_read_i2c_block_data. And i2c_smbus_xfer_emulated looks to me as if it were not possible to use it when my adapter doesn't have algo->smbus_xfer. Any hints? Best regards Uwe -- Uwe Zeisberger FS Forth-Systeme GmbH, A Digi International Company Kueferstrasse 8, D-79206 Breisach, Germany Phone: +49 (7667) 908 0 Fax: +49 (7667) 908 200 Web: www.fsforth.de, www.digi.com ^ permalink raw reply [flat|nested] 7+ messages in thread
* [lm-sensors] [PATCH] I2C: add new rx8025 driver 2006-02-01 10:23 [lm-sensors] [PATCH] I2C: add new rx8025 driver Uwe Zeisberger ` (2 preceding siblings ...) 2006-02-03 8:51 ` Uwe Zeisberger @ 2006-02-03 9:16 ` Jean Delvare 2006-02-03 9:43 ` Uwe Zeisberger 2006-02-03 13:43 ` Uwe Zeisberger 5 siblings, 0 replies; 7+ messages in thread From: Jean Delvare @ 2006-02-03 9:16 UTC (permalink / raw) To: lm-sensors Hallo Uwe, > Jean Delvare wrote: > > Given that all block transfers you do are standard I2C block transfers, > > you may consider switching to i2c_smbus_{read,write}_i2c_block_data for > > SMBus compatibility. This would also make your code more simple, I > > think. > > I don't understand how I specify the length of the block to be read for > i2c_smbus_read_i2c_block_data. Oops. This is a bug (or at least lack of fundamental feature) in the current implementation of i2c_smbus_read_i2c_block_data. I want to fix it for some times now but always postponed it due to the compatibility issues it raises :( So there is no way you can use i2c_smbus_read_i2c_block_data in your driver at the moment; stick to i2c_transfer for now. > (...) And i2c_smbus_xfer_emulated looks to me > as if it were not possible to use it when my adapter doesn't have > algo->smbus_xfer. Not correct. The i2c-core is able to emulate algo->smbus_xfer using algo->master_xfer. See i2c_smbus_xfer() in i2c-core.c for the test, and i2c_smbus_xfer_emulated() for the implementation. -- Jean Delvare ^ permalink raw reply [flat|nested] 7+ messages in thread
* [lm-sensors] [PATCH] I2C: add new rx8025 driver 2006-02-01 10:23 [lm-sensors] [PATCH] I2C: add new rx8025 driver Uwe Zeisberger ` (3 preceding siblings ...) 2006-02-03 9:16 ` Jean Delvare @ 2006-02-03 9:43 ` Uwe Zeisberger 2006-02-03 13:43 ` Uwe Zeisberger 5 siblings, 0 replies; 7+ messages in thread From: Uwe Zeisberger @ 2006-02-03 9:43 UTC (permalink / raw) To: lm-sensors Hallo Jean, > > (...) And i2c_smbus_xfer_emulated looks to me > > as if it were not possible to use it when my adapter doesn't have > > algo->smbus_xfer. > > Not correct. The i2c-core is able to emulate algo->smbus_xfer using > algo->master_xfer. See i2c_smbus_xfer() in i2c-core.c for the test, and > i2c_smbus_xfer_emulated() for the implementation. I checked that, but my short-term memory played a joke on me. When calling i2c_smbus_read_i2c_block_data(...), i2c_smbus_xfer_emulated is called with size = I2C_SMBUS_I2C_BLOCK_DATA, not I2C_SMBUS_BLOCK_DATA. If it were I2C_SMBUS_BLOCK_DATA (together with read_write = I2C_SMBUS_READ) it would fail. OK, so you're right once more ... Uwe -- Uwe Zeisberger FS Forth-Systeme GmbH, A Digi International Company Kueferstrasse 8, D-79206 Breisach, Germany Phone: +49 (7667) 908 0 Fax: +49 (7667) 908 200 Web: www.fsforth.de, www.digi.com ^ permalink raw reply [flat|nested] 7+ messages in thread
* [lm-sensors] [PATCH] I2C: add new rx8025 driver 2006-02-01 10:23 [lm-sensors] [PATCH] I2C: add new rx8025 driver Uwe Zeisberger ` (4 preceding siblings ...) 2006-02-03 9:43 ` Uwe Zeisberger @ 2006-02-03 13:43 ` Uwe Zeisberger 5 siblings, 0 replies; 7+ messages in thread From: Uwe Zeisberger @ 2006-02-03 13:43 UTC (permalink / raw) To: lm-sensors Adds support for the Epson RX8025 SA/NB RTC module. Signed-off-by: Uwe Zeisberger <Uwe_Zeisberger at digi.com> --- MAINTAINERS | 5 drivers/i2c/chips/Kconfig | 10 + drivers/i2c/chips/Makefile | 1 drivers/i2c/chips/rx8025.c | 598 ++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 614 insertions(+), 0 deletions(-) create mode 100644 drivers/i2c/chips/rx8025.c 2bb51a91a12c7096c50dcb2feb9601095d67f9ba diff --git a/MAINTAINERS b/MAINTAINERS index 42955fe..b2cc2b6 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -900,6 +900,11 @@ P: Christopher Hoover M: ch at murgatroid.com, ch at hpl.hp.com S: Maintained +EPSON RX-8025 SA/NB RTC DRIVER +P: Uwe Zeisberger +M: Uwe_Zeisberger at digi.com +S: Maintained + ETHEREXPRESS-16 NETWORK DRIVER P: Philip Blundell M: philb at gnu.org diff --git a/drivers/i2c/chips/Kconfig b/drivers/i2c/chips/Kconfig index f9fae28..2c572c7 100644 --- a/drivers/i2c/chips/Kconfig +++ b/drivers/i2c/chips/Kconfig @@ -74,6 +74,16 @@ config SENSORS_RTC8564 This driver can also be built as a module. If so, the module will be called i2c-rtc8564. +config RTC_RX8025 + tristate "Epson RX-8025 SA/NB RTC chip" + depends on I2C && EXPERIMENTAL + help + If you say yes here you get support for the + Epson RX-8025 SA/NB RTC chip. + + This driver can also be built as a module. If so, the module + will be called rx8025. + config ISP1301_OMAP tristate "Philips ISP1301 with OMAP OTG" depends on I2C && ARCH_OMAP_OTG diff --git a/drivers/i2c/chips/Makefile b/drivers/i2c/chips/Makefile index 46178b5..5a67db4 100644 --- a/drivers/i2c/chips/Makefile +++ b/drivers/i2c/chips/Makefile @@ -11,6 +11,7 @@ obj-$(CONFIG_SENSORS_PCA9539) += pca9539 obj-$(CONFIG_SENSORS_PCF8574) += pcf8574.o obj-$(CONFIG_SENSORS_PCF8591) += pcf8591.o obj-$(CONFIG_SENSORS_RTC8564) += rtc8564.o +obj-$(CONFIG_RTC_RX8025) += rx8025.o obj-$(CONFIG_ISP1301_OMAP) += isp1301_omap.o obj-$(CONFIG_TPS65010) += tps65010.o obj-$(CONFIG_RTC_X1205_I2C) += x1205.o diff --git a/drivers/i2c/chips/rx8025.c b/drivers/i2c/chips/rx8025.c new file mode 100644 index 0000000..cf8383e --- /dev/null +++ b/drivers/i2c/chips/rx8025.c @@ -0,0 +1,598 @@ +/* + * drivers/i2c/chips/rx8025.c + * + * Driver for Epson's RTC module RX-8025 SA/NB + * + * Copyright (C) 2005 by Digi International Inc. + * All rights reserved. + * + * 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 <linux/kernel.h> +#include <linux/module.h> +#include <linux/bcd.h> +#include <linux/i2c.h> +#include <linux/init.h> +#include <linux/list.h> +#include <linux/rtc.h> +#include <linux/spinlock.h> +#include <linux/fs.h> +#include <linux/miscdevice.h> +#include <asm/uaccess.h> + +/* registers */ +#define RX8025_REG_SEC 0x00 +#define RX8025_REG_MIN 0x01 +#define RX8025_REG_HOUR 0x02 +#define RX8025_REG_WDAY 0x03 +#define RX8025_REG_MDAY 0x04 +#define RX8025_REG_MONTH 0x05 +#define RX8025_REG_YEAR 0x06 +#define RX8025_REG_DIGOFF 0x07 +#define RX8025_REG_ALWMIN 0x08 +#define RX8025_REG_ALWHOUR 0x09 +#define RX8025_REG_ALWWDAY 0x0a +#define RX8025_REG_ALDMIN 0x0b +#define RX8025_REG_ALDHOUR 0x0c +/* 0x0d is reserved */ +#define RX8025_REG_CTRL1 0x0e +#define RX8025_REG_CTRL2 0x0f + +#define RX8025_BIT_CTRL1_1224 (1 << 5) +#define RX8025_BIT_CTRL1_DALE (1 << 6) +#define RX8025_BIT_CTRL1_WALE (1 << 7) + +#define RX8025_BIT_CTRL2_PON (1 << 4) +#define RX8025_BIT_CTRL2_VDET (1 << 6) + +static unsigned short normal_i2c[] = { 0x32, I2C_CLIENT_END }; +I2C_CLIENT_INSMOD_1(rx8025); + +static int rx8025_attach_adapter(struct i2c_adapter *adapter); +static int rx8025_detect(struct i2c_adapter *adapter, int address, int kind); +static int rx8025_init_client(struct i2c_client *client); +static int rx8025_detach_client(struct i2c_client *client); +static int rx8025_rtc_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg); + +static struct i2c_driver rx8025_driver = { + .driver = { + .name = "rx8025", + }, + .attach_adapter = &rx8025_attach_adapter, + .detach_client = &rx8025_detach_client, +}; + +static struct i2c_client *rx8025_rtcclient = NULL; +static struct file_operations rx8025_rtc_fops = { + .ioctl = rx8025_rtc_ioctl, +}; + +static struct miscdevice rx8025_rtc_dev = { + .minor = RTC_MINOR, + .name = "rtc", + .fops = &rx8025_rtc_fops, +}; + +struct rx8025_data { + struct i2c_client client; + struct list_head list; +}; + +static LIST_HEAD(rx8025_clients); +static DECLARE_MUTEX(rx8025_lock); + +static const unsigned char days_in_mo[] + { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; + +static int rx8025_get_rtctime(struct rtc_time *dt) +{ + u8 command = (RX8025_REG_CTRL1 << 4) | 0x08; + u8 result[9]; + int i, err; + + struct i2c_msg msg[] = { + { + .len = 1, + .buf = &command, + }, { + .flags = I2C_M_RD, + .len = ARRAY_SIZE(result), + .buf = result, + } + }; + + if (down_interruptible(&rx8025_lock)) + return -ERESTARTSYS; + + if (!rx8025_rtcclient) { + err = -EIO; + goto errout_unlock; + } + + if (!dt) { + dev_err(&rx8025_rtcclient->dev, + "rx8025_get_rtctime: passed in dt = NULL\n"); + err = -EINVAL; + goto errout_unlock; + } + + for (i = 0; i < ARRAY_SIZE(msg); ++i) + msg[i].addr = rx8025_rtcclient->addr; + + if ((err = i2c_transfer(rx8025_rtcclient->adapter, msg, 2)) < 2) { + err = err >= 0 ? -EIO : err; + goto errout_unlock; + } + + up(&rx8025_lock); + + dev_dbg(&rx8025_rtcclient->dev, + "rx8025_get_rtctime: read 0x%02x 0x%02x 0x%02x 0x%02x " + "0x%02x 0x%02x 0x%02x 0x%02x 0x%02x\n", result[0], + result[1], result[2], result[3], result[4], result[5], + result[6], result[7], result[8]); + + dt->tm_sec = BCD2BIN(result[2 + RX8025_REG_SEC] & 0x7f); + dt->tm_min = BCD2BIN(result[2 + RX8025_REG_MIN] & 0x7f); + if (result[0] | RX8025_BIT_CTRL1_1224) + dt->tm_hour = BCD2BIN(result[2 + RX8025_REG_HOUR] & 0x3f); + else + dt->tm_hour = BCD2BIN(result[2 + RX8025_REG_HOUR] & 0x1f) % 12 + + (result[2 + RX8025_REG_HOUR] & 0x20 ? 12 : 0); + + dt->tm_wday = BCD2BIN(result[2 + RX8025_REG_WDAY] & 0x07); + dt->tm_mday = BCD2BIN(result[2 + RX8025_REG_MDAY] & 0x3f); + dt->tm_mon = BCD2BIN(result[2 + RX8025_REG_MONTH] & 0x1f) - 1; + dt->tm_year = BCD2BIN(result[2 + RX8025_REG_YEAR]); + + if (dt->tm_year < 70) + dt->tm_year += 100; + + dev_dbg(&rx8025_rtcclient->dev, + "rx8025_get_rtctime: " + "result: %ds %dm %dh %dwd %dmd %dm %dy\n", + dt->tm_sec, dt->tm_min, dt->tm_hour, dt->tm_wday, + dt->tm_mday, dt->tm_mon, dt->tm_year); + + return 0; + +errout_unlock: + up(&rx8025_lock); + return err; +} + +static int rx8025_set_rtctime(struct rtc_time *dt) +{ + u8 command[8] = { RX8025_REG_SEC << 4, }; + int err; + s32 ctrl1; + + if (down_interruptible(&rx8025_lock)) + return -ERESTARTSYS; + + if (!rx8025_rtcclient) { + err = -EIO; + goto errout_unlock; + } + + if (!dt) { + dev_err(&rx8025_rtcclient->dev, + "rx8025_set_rtctime: passed in dt = NULL\n"); + err = -EINVAL; + goto errout_unlock; + } + + if (dt->tm_sec < 0 || dt->tm_sec >= 60) { + dev_err(&rx8025_rtcclient->dev, + "rx8025_set_rtctime: seconds out of range: %d\n", + dt->tm_sec); + err = -EINVAL; + goto errout_unlock; + } + + if (dt->tm_min < 0 || dt->tm_min >= 60) { + dev_err(&rx8025_rtcclient->dev, + "rx8025_set_rtctime: minutes out of range: %d\n", + dt->tm_min); + err = -EINVAL; + goto errout_unlock; + } + + if (dt->tm_hour < 0 || dt->tm_hour >= 24) { + dev_err(&rx8025_rtcclient->dev, + "rx8025_set_rtctime: hours out of range: %d\n", + dt->tm_hour); + err = -EINVAL; + goto errout_unlock; + } + + if (dt->tm_wday < 0 || dt->tm_wday >= 7) { + dev_err(&rx8025_rtcclient->dev, + "rx8025_set_rtctime: week day out of range: %d\n", + dt->tm_wday); + err = -EINVAL; + goto errout_unlock; + } + + if (dt->tm_mon < 0 || dt->tm_mon >= 12) { + dev_err(&rx8025_rtcclient->dev, + "rx8025_set_rtctime: month out of range: %d\n", + dt->tm_mon); + err = -EINVAL; + goto errout_unlock; + } + + if (dt->tm_year < 70 || dt->tm_year >= 170) { + dev_err(&rx8025_rtcclient->dev, + "rx8025_set_rtctime: year out of range: %d\n", + dt->tm_year); + err = -EINVAL; + goto errout_unlock; + } + + /* + * BUG: The HW assumes every year that is a multiple of 4 to be a leap + * year. Next time this is wrong is 2100, which will not be a leap + * year. + */ + if (dt->tm_mday > days_in_mo[dt->tm_mon] + + (!(dt->tm_year & 3) && (dt->tm_mon = 1))) { + dev_err(&rx8025_rtcclient->dev, + "%s: day out of range (with month=%d, year=%d): %d\n", + __FUNCTION__, dt->tm_mon, dt->tm_year, dt->tm_mday); + err = -EINVAL; + goto errout_unlock; + } + + if ((ctrl1 = i2c_smbus_read_byte_data(rx8025_rtcclient, + RX8025_REG_CTRL1 << 4)) < 0) { + dev_err(&rx8025_rtcclient->dev, + "%s: failed to read out RX8025_REG_CTRL1\n", + __FUNCTION__); + err = -EIO; + goto errout_unlock; + } + + dev_dbg(&rx8025_rtcclient->dev, + "%s: ctrl1=0x%x\n", __FUNCTION__, ctrl1); + /* + * Here the read-only bits are written as "0". I'm not sure if that + * is sound. + */ + command[1 + RX8025_REG_SEC] = BIN2BCD(dt->tm_sec); + command[1 + RX8025_REG_MIN] = BIN2BCD(dt->tm_min); + if (ctrl1 & RX8025_BIT_CTRL1_1224) + command[1 + RX8025_REG_HOUR] = BIN2BCD(dt->tm_hour); + else + command[1 + RX8025_REG_HOUR] = (dt->tm_hour >= 12 ? 0x20 : 0) | + BIN2BCD((dt->tm_hour + 11) % 12 + 1); + + command[1 + RX8025_REG_WDAY] = BIN2BCD(dt->tm_wday); + command[1 + RX8025_REG_MDAY] = BIN2BCD(dt->tm_mday); + command[1 + RX8025_REG_MONTH] = BIN2BCD(dt->tm_mon + 1); + command[1 + RX8025_REG_YEAR] = BIN2BCD(dt->tm_year % 100); + + dev_dbg(&rx8025_rtcclient->dev, + "%s: send 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x " + "0x%02x 0x%02x\n", __FUNCTION__, command[0], + command[1], command[2], command[3], command[4], + command[5], command[6], command[7]); + + if ((err = i2c_master_send(rx8025_rtcclient, command, 8) < 8)) { + err = err >= 0 ? -EIO : err; + goto errout_unlock; + } + + up(&rx8025_lock); + + return 0; + +errout_unlock: + up(&rx8025_lock); + return err; +} + +static int rx8025_attach_adapter(struct i2c_adapter *adapter) +{ + return i2c_probe(adapter, &addr_data, &rx8025_detect); +} + +struct rx8025_limits { + u8 reg; + u8 mask; + u8 min; + u8 max; +}; + +static int rx8025_detect(struct i2c_adapter *adapter, int address, int kind) +{ + int err = 0; + struct rx8025_data *data; + struct i2c_client *client; + + if (!i2c_check_functionality(adapter, I2C_FUNC_I2C)) { + dev_dbg(&adapter->dev, "doesn't support full I2C\n"); + goto errout; + } + + if (!(data = kzalloc(sizeof(*data), GFP_KERNEL))) { + dev_dbg(&adapter->dev, "failed to alloc memory\n"); + err = -ENOMEM; + goto errout; + } + + client = &data->client; + i2c_set_clientdata(client, data); + client->addr = address; + client->adapter = adapter; + client->driver = &rx8025_driver; + + INIT_LIST_HEAD(&data->list); + + if (kind = 0) + kind = rx8025; + + if (kind < 0) { + u8 command = 0x08; + u8 res[RX8025_REG_YEAR + 1]; + int i, err; + + struct i2c_msg msg[] = { + { + .addr = address, + .len = 1, + .buf = &command, + }, { + .addr = address, + .flags = I2C_M_RD, + .len = ARRAY_SIZE(res), + .buf = res, + } + }; + + static const struct rx8025_limits limits[] = { + { + .reg = RX8025_REG_SEC, + .mask = 0x7f, + .min = 0, + .max = 59, + }, { + .reg = RX8025_REG_MIN, + .mask = 0x7f, + .min = 0, + .max = 59, + }, { + .reg = RX8025_REG_HOUR, + .mask = 0x3f, + .min = 0, + .max = 32, + }, { + .reg = RX8025_REG_WDAY, + .mask = 0x7f, + .min = 0, + .max = 6, + }, { + .reg = RX8025_REG_MDAY, + .mask = 0x3f, + .min = 1, + .max = 31, + }, { + .reg = RX8025_REG_MONTH, + .mask = 0x1f, + .min = 1, + .max = 12, + }, { + .reg = RX8025_REG_YEAR, + .mask = 0xff, + .min = 0, + .max = 99, + } + }; + + if ((err = i2c_transfer(adapter, msg, ARRAY_SIZE(msg))) + < ARRAY_SIZE(msg)) { + err = err >= 0 ? 0 : err; + goto errout_free; + } + + for (i = 0; i < ARRAY_SIZE(limits); ++i) { + u8 value; + + if ((res[limits[i].reg] & ~limits[i].mask) || + (limits[i].mask > 0x0f && + (res[limits[i].reg] & 0x0f) > 9) || + ((value = BCD2BIN(res[limits[i].reg])) + < limits[i].min) || + value > limits[i].max) { + dev_dbg(&adapter->dev, "%s: register=0x%02x, " + "value=0x%02x\n", __FUNCTION__, + limits[i].reg, + res[limits[i].reg]); + err = -ENODEV; + goto errout_unlock; + } + } + + kind = rx8025; + } + + BUG_ON(kind != rx8025); + + strlcpy(client->name, "rx8025", I2C_NAME_SIZE); + + if (down_interruptible(&rx8025_lock)) { + err = -ERESTARTSYS; + goto errout_free; + } + + if ((err = i2c_attach_client(client))) + goto errout_unlock; + + list_add(&data->list, &rx8025_clients); + + if ((err = rx8025_init_client(client))) + goto errout_detach; + + if (!rx8025_rtcclient) { + rx8025_rtcclient = client; + if ((err = misc_register(&rx8025_rtc_dev))) { + rx8025_rtcclient = NULL; + dev_err(&client->dev, + "Failed to register rtc device\n"); + } + } + up(&rx8025_lock); + + dev_info(&client->dev, "chip found\n"); + + return 0; + +errout_detach: + rx8025_detach_client(client); + +errout_unlock: + up(&rx8025_lock); + +errout_free: + kfree(data); + +errout: + dev_dbg(&adapter->dev, "Failed to detect rx8025\n"); + return err; +} + +static int rx8025_init_client(struct i2c_client *client) +{ + u8 command[2] = { RX8025_REG_CTRL2 << 4 | 0x08, }; + int err; + + struct i2c_msg msg[] = { + { + .addr = client->addr, + .len = 1, + .buf = command, + }, { + .addr = client->addr, + .flags = I2C_M_RD, + .len = ARRAY_SIZE(command) - 1, + .buf = command + 1, + } + }; + + if ((err = i2c_transfer(client->adapter, msg, 2)) < 2) { + err = err >= 0 ? -EIO : err; + goto errout; + } + + if (command[1] & RX8025_BIT_CTRL2_PON) + dev_warn(&client->dev, "power-on reset was detected, " + "you may have to readjust the clock\n"); + + if (command[1] & RX8025_BIT_CTRL2_VDET) + dev_warn(&client->dev, "a power voltage drop was detected, " + "you may have to readjust the clock\n"); + + command[1] &= ~(RX8025_BIT_CTRL2_PON | RX8025_BIT_CTRL2_VDET); + + command[0] = RX8025_REG_CTRL2 << 4; + + if ((err = i2c_master_send(client, command, 2)) < 2) { + err = err >= 0 ? -EIO : err; + goto errout; + } + + return 0; + +errout: + return err; +} + +static int rx8025_detach_client(struct i2c_client *client) +{ + int err; + struct rx8025_data *data = i2c_get_clientdata(client); + + if (down_interruptible(&rx8025_lock)) + return -ERESTARTSYS; + + BUG_ON(list_empty(&rx8025_clients)); + + if (rx8025_rtcclient = client) { + struct list_head *lh = rx8025_clients.next; + + if (list_entry(lh, struct rx8025_data, list) = data) + lh = lh->next; + + if (lh = &rx8025_clients) { + + if ((err = misc_deregister(&rx8025_rtc_dev))) { + up(&rx8025_lock); + return err; + } + + rx8025_rtcclient = NULL; + } else + rx8025_rtcclient = &data->client; + } + + if ((err = i2c_detach_client(client))) { + up(&rx8025_lock); + return err; + } + + list_del(&data->list); + + up(&rx8025_lock); + kfree(data); + return 0; +} + +static int rx8025_rtc_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + int err; + struct rtc_time rtctime; + + dev_dbg(&rx8025_rtcclient->dev, "got command %d\n", cmd); + switch(cmd) { + case RTC_RD_TIME: + memset(&rtctime, 0, sizeof(rtctime)); + if ((err = rx8025_get_rtctime(&rtctime))) + return err; + + return copy_to_user((void __user *)arg, &rtctime, + sizeof(rtctime)) ? -EFAULT : 0; + + case RTC_SET_TIME: + if (!capable(CAP_SYS_TIME)) + return -EACCES; + + if (copy_from_user(&rtctime, (const void __user *)arg, + sizeof(rtctime))) + return -EFAULT; + + return rx8025_set_rtctime(&rtctime); + } + dev_dbg(&rx8025_rtcclient->dev, "unhandled command %d\n", cmd); + return -EINVAL; +} + +static int __init rx8025_init(void) +{ + return i2c_add_driver(&rx8025_driver); +} + +static void __exit rx8025_exit(void) +{ + i2c_del_driver(&rx8025_driver); +} + +MODULE_AUTHOR("Uwe Zeisberger <Uwe_Zeisberger at digi.com>"); +MODULE_DESCRIPTION("RX-8025 SA/NB RTC driver"); +MODULE_LICENSE("GPL"); + +module_init(rx8025_init); +module_exit(rx8025_exit); -- 1.1.6.g5248 ^ permalink raw reply related [flat|nested] 7+ messages in thread
end of thread, other threads:[~2006-02-03 13:43 UTC | newest] Thread overview: 7+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2006-02-01 10:23 [lm-sensors] [PATCH] I2C: add new rx8025 driver Uwe Zeisberger 2006-02-01 19:23 ` Jean Delvare 2006-02-02 8:39 ` Uwe Zeisberger 2006-02-03 8:51 ` Uwe Zeisberger 2006-02-03 9:16 ` Jean Delvare 2006-02-03 9:43 ` Uwe Zeisberger 2006-02-03 13:43 ` Uwe Zeisberger
This is an external index of several public inboxes, see mirroring instructions on how to clone and mirror all data and code used by this external index.