rtc: rv3029: Add thermometer hwmon support
authorMichael Büsch <m@bues.ch>
Thu, 10 Mar 2016 17:34:46 +0000 (18:34 +0100)
committerAlexandre Belloni <alexandre.belloni@free-electrons.com>
Mon, 14 Mar 2016 16:08:40 +0000 (17:08 +0100)
This adds support to
- enable/disable the thermometer
- set the temperature scanning interval
- read the current temperature that is used for temp compensation.
via hwmon interface

Signed-off-by: Michael Buesch <m@bues.ch>
Signed-off-by: Alexandre Belloni <alexandre.belloni@free-electrons.com>
drivers/rtc/Kconfig
drivers/rtc/rtc-rv3029c2.c

index 987c501..d2cd206 100644 (file)
@@ -591,6 +591,15 @@ config RTC_DRV_RV3029C2
          This driver can also be built as a module. If so, the module
          will be called rtc-rv3029c2.
 
+config RTC_DRV_RV3029_HWMON
+       bool "HWMON support for RV3029"
+       depends on RTC_DRV_RV3029C2 && HWMON
+       depends on !(RTC_DRV_RV3029C2=y && HWMON=m)
+       default y
+       help
+         Say Y here if you want to expose temperature sensor data on
+         rtc-rv3029c2.
+
 config RTC_DRV_RV8803
        tristate "Micro Crystal RV8803"
        help
index 3bbb581..d0cbf08 100644 (file)
@@ -18,6 +18,8 @@
 #include <linux/rtc.h>
 #include <linux/delay.h>
 #include <linux/of.h>
+#include <linux/hwmon.h>
+#include <linux/hwmon-sysfs.h>
 
 
 /* Register map */
@@ -637,6 +639,123 @@ static void rv3029_trickle_config(struct i2c_client *client)
        }
 }
 
+#ifdef CONFIG_RTC_DRV_RV3029_HWMON
+
+static int rv3029_read_temp(struct i2c_client *client, int *temp_mC)
+{
+       int ret;
+       u8 temp;
+
+       ret = rv3029_i2c_read_regs(client, RV3029_TEMP_PAGE, &temp, 1);
+       if (ret < 0)
+               return ret;
+
+       *temp_mC = ((int)temp - 60) * 1000;
+
+       return 0;
+}
+
+static ssize_t rv3029_hwmon_show_temp(struct device *dev,
+                                     struct device_attribute *attr,
+                                     char *buf)
+{
+       struct i2c_client *client = dev_get_drvdata(dev);
+       int ret, temp_mC;
+
+       ret = rv3029_read_temp(client, &temp_mC);
+       if (ret < 0)
+               return ret;
+
+       return sprintf(buf, "%d\n", temp_mC);
+}
+
+static ssize_t rv3029_hwmon_set_update_interval(struct device *dev,
+                                               struct device_attribute *attr,
+                                               const char *buf,
+                                               size_t count)
+{
+       struct i2c_client *client = dev_get_drvdata(dev);
+       unsigned long interval_ms;
+       int ret;
+       u8 th_set_bits = 0;
+
+       ret = kstrtoul(buf, 10, &interval_ms);
+       if (ret < 0)
+               return ret;
+
+       if (interval_ms != 0) {
+               th_set_bits |= RV3029_EECTRL_THE;
+               if (interval_ms >= 16000)
+                       th_set_bits |= RV3029_EECTRL_THP;
+       }
+       ret = rv3029_eeprom_update_bits(client, RV3029_CONTROL_E2P_EECTRL,
+                                       RV3029_EECTRL_THE | RV3029_EECTRL_THP,
+                                       th_set_bits);
+       if (ret < 0)
+               return ret;
+
+       return count;
+}
+
+static ssize_t rv3029_hwmon_show_update_interval(struct device *dev,
+                                                struct device_attribute *attr,
+                                                char *buf)
+{
+       struct i2c_client *client = dev_get_drvdata(dev);
+       int ret, interval_ms;
+       u8 eectrl;
+
+       ret = rv3029_eeprom_read(client, RV3029_CONTROL_E2P_EECTRL,
+                                &eectrl, 1);
+       if (ret < 0)
+               return ret;
+
+       if (eectrl & RV3029_EECTRL_THE) {
+               if (eectrl & RV3029_EECTRL_THP)
+                       interval_ms = 16000;
+               else
+                       interval_ms = 1000;
+       } else {
+               interval_ms = 0;
+       }
+
+       return sprintf(buf, "%d\n", interval_ms);
+}
+
+static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, rv3029_hwmon_show_temp,
+                         NULL, 0);
+static SENSOR_DEVICE_ATTR(update_interval, S_IWUSR | S_IRUGO,
+                         rv3029_hwmon_show_update_interval,
+                         rv3029_hwmon_set_update_interval, 0);
+
+static struct attribute *rv3029_hwmon_attrs[] = {
+       &sensor_dev_attr_temp1_input.dev_attr.attr,
+       &sensor_dev_attr_update_interval.dev_attr.attr,
+       NULL,
+};
+ATTRIBUTE_GROUPS(rv3029_hwmon);
+
+static void rv3029_hwmon_register(struct i2c_client *client)
+{
+       struct device *hwmon_dev;
+
+       hwmon_dev = devm_hwmon_device_register_with_groups(
+               &client->dev, client->name, client, rv3029_hwmon_groups);
+       if (IS_ERR(hwmon_dev)) {
+               dev_warn(&client->dev,
+                       "unable to register hwmon device %ld\n",
+                       PTR_ERR(hwmon_dev));
+       }
+}
+
+#else /* CONFIG_RTC_DRV_RV3029_HWMON */
+
+static void rv3029_hwmon_register(struct i2c_client *client)
+{
+}
+
+#endif /* CONFIG_RTC_DRV_RV3029_HWMON */
+
 static const struct rtc_class_ops rv3029_rtc_ops = {
        .read_time      = rv3029_rtc_read_time,
        .set_time       = rv3029_rtc_set_time,
@@ -668,6 +787,7 @@ static int rv3029_probe(struct i2c_client *client,
        }
 
        rv3029_trickle_config(client);
+       rv3029_hwmon_register(client);
 
        rtc = devm_rtc_device_register(&client->dev, client->name,
                                       &rv3029_rtc_ops, THIS_MODULE);