New Rx8025 RTC driver

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



This is a driver patch for Epson RX8025 SA/NB RTC module.
It based on Uwe's patch http://lists.lm-sensors.org/pipermail/lm-sensors/2006-February/015172.html,and used new rtc class,It has tested on kernel 2.6.17.11.


Index: linux/drivers/rtc/rtc-rx8025.c
===================================================================
--- linux/drivers/rtc/rtc-rx8025.c 
+++ linux/drivers/rtc/rtc-rx8025.c 
@@ -0,0 +1,560 @@
+/*
+ * drivers/rtc/rtc-rx8025.c
+ *
+ * Driver for Epson's RTC module RX-8025 SA/NB
+ *
+ * Copyright (C) 2005 by Digi International Inc.
+ * All rights reserved.
+ *
+ * Modify by fengjh at rising.com.cn
+ * 2006.11
+ *
+ * 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 struct i2c_driver rx8025_driver = {
+ .driver = {
+  .name = "rx8025",
+  .owner = THIS_MODULE,
+ },
+ .attach_adapter = &rx8025_attach_adapter,
+ .detach_client = &rx8025_detach_client,
+};
+
+static struct i2c_client *rx8025_rtcclient = NULL;
+static int rx8025_get_rtctime(struct device *dev,struct rtc_time *dt);
+static int rx8025_set_rtctime(struct device *dev,struct rtc_time *dt);
+static struct rtc_class_ops rx8025_rtc_ops = {
+ .read_time   = rx8025_get_rtctime,
+ .set_time   = rx8025_set_rtctime,
+};
+struct rx8025_data {
+ struct i2c_client client;
+ struct list_head list;
+ struct rtc_device *rtc;
+};
+
+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 device *dev,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 device *dev,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_err(&adapter->dev, "doesn't support full I2C\n");
+  goto errout;
+ }
+
+ if (!(data = kzalloc(sizeof(*data), GFP_KERNEL))) {
+  dev_err(&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;
+  data->rtc = rtc_device_register(client->name, &client->dev,
+    &rx8025_rtc_ops, THIS_MODULE);
+  if (IS_ERR(data->rtc)) {
+   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_err(&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) {
+   rtc_device_unregister(data->rtc);
+   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 __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> and modify by fengjh at rising.com.cn");
+MODULE_DESCRIPTION("RX-8025 SA/NB RTC driver");
+MODULE_LICENSE("GPL");
+
+module_init(rx8025_init);
+module_exit(rx8025_exit);

Best regards

Paul Von
2006.12.1


[Index of Archives]     [Linux Kernel]     [Linux Hardware Monitoring]     [Linux USB Devel]     [Linux Audio Users]     [Linux Kernel]     [Linux SCSI]     [Yosemite Backpacking]

  Powered by Linux