[PATCH v8 5/5] hwmon: ina2xx: allow to change the averaging rate at run-time

2015-01-05 Thread Bartosz Golaszewski
The averaging rate of ina226 is hardcoded to 16 in the driver. Make it
modifiable at run-time via a new sysfs attribute.

While we're at it - add an additional variable to ina2xx_data, which holds
the current configuration settings - this way we'll be able to restore the
configuration in case of an unexpected chip reset.

Signed-off-by: Bartosz Golaszewski 
---
 Documentation/hwmon/ina2xx |   3 ++
 drivers/hwmon/ina2xx.c | 129 +++--
 2 files changed, 128 insertions(+), 4 deletions(-)

diff --git a/Documentation/hwmon/ina2xx b/Documentation/hwmon/ina2xx
index 320dd69..a11256d 100644
--- a/Documentation/hwmon/ina2xx
+++ b/Documentation/hwmon/ina2xx
@@ -48,3 +48,6 @@ The shunt value in micro-ohms can be set via platform data or 
device tree at
 compile-time or via the shunt_resistor attribute in sysfs at run-time. Please
 refer to the Documentation/devicetree/bindings/i2c/ina2xx.txt for bindings
 if the device tree is used.
+
+The averaging rate of INA226 and INA230 can be changed via the avg_rate sysfs
+attribute. The available rates are: 1, 4, 16, 64, 128, 256, 512 and 1024.
diff --git a/drivers/hwmon/ina2xx.c b/drivers/hwmon/ina2xx.c
index 49537ea..6f6aebd 100644
--- a/drivers/hwmon/ina2xx.c
+++ b/drivers/hwmon/ina2xx.c
@@ -68,6 +68,15 @@
 
 #define INA2XX_RSHUNT_DEFAULT  1
 
+/* bit masks for the averaging setting in the configuration register */
+#define INA226_AVG_RD_MASK 0x0E00
+#define INA226_AVG_WR_MASK 0xF1FF
+
+#define INA226_READ_AVG(reg) ((reg & INA226_AVG_RD_MASK) >> 9)
+
+/* common attrs, ina226 attrs and NULL */
+#define INA2XX_MAX_ATTRIBUTE_GROUPS3
+
 enum ina2xx_ids { ina219, ina226 };
 
 struct ina2xx_config {
@@ -85,12 +94,14 @@ struct ina2xx_data {
const struct ina2xx_config *config;
 
long rshunt;
+   u16 curr_config;
 
struct mutex update_lock;
bool valid;
unsigned long last_updated;
 
int kind;
+   const struct attribute_group *groups[INA2XX_MAX_ATTRIBUTE_GROUPS];
u16 regs[INA2XX_MAX_REGISTERS];
 };
 
@@ -115,6 +126,39 @@ static const struct ina2xx_config ina2xx_config[] = {
},
 };
 
+/*
+ * Available averaging rates for ina226. The indices correspond with
+ * the bit values expected by the chip (according to the ina226 datasheet,
+ * table 3 AVG bit settings, found at
+ * http://www.ti.com/lit/ds/symlink/ina226.pdf.
+ */
+static const int ina226_avg_tab[] = { 1, 4, 16, 64, 128, 256, 512, 1024 };
+
+static int ina226_avg_bits(int avg)
+{
+   int i;
+
+   /* Get the closest average from the tab. */
+   for (i = 0; i < ARRAY_SIZE(ina226_avg_tab) - 1; i++) {
+   if (avg <= (ina226_avg_tab[i] + ina226_avg_tab[i + 1]) / 2)
+   return i;
+   }
+
+   return i; /* Return 0b0111 for other values. */
+}
+
+static int ina226_avg_val(int bits)
+{
+   /*
+* Value read from the config register should be correct, but do check
+* the boundary just in case.
+*/
+   if (bits >= ARRAY_SIZE(ina226_avg_tab))
+   return -ENODEV;
+
+   return ina226_avg_tab[bits];
+}
+
 static int ina2xx_calibrate(struct ina2xx_data *data)
 {
return i2c_smbus_write_word_swapped(data->client, INA2XX_CALIBRATION,
@@ -131,7 +175,7 @@ static int ina2xx_init(struct ina2xx_data *data)
 
/* device configuration */
ret = i2c_smbus_write_word_swapped(client, INA2XX_CONFIG,
-  data->config->config_default);
+  data->curr_config);
if (ret < 0)
return ret;
 
@@ -292,6 +336,62 @@ static ssize_t ina2xx_set_shunt(struct device *dev,
return count;
 }
 
+static ssize_t ina226_show_avg(struct device *dev,
+  struct device_attribute *da, char *buf)
+{
+   struct ina2xx_data *data = ina2xx_update_device(dev);
+   int avg, i;
+
+   if (IS_ERR(data))
+   return PTR_ERR(data);
+
+   avg = ina226_avg_val(INA226_READ_AVG(data->regs[INA2XX_CONFIG]));
+   if (avg < 0)
+   return avg;
+
+   for (i = 0; i < ARRAY_SIZE(ina226_avg_tab); i++) {
+   if (avg == ina226_avg_tab[i])
+   break;
+   }
+
+   return snprintf(buf, PAGE_SIZE, "%d\n", ina226_avg_tab[i]);
+}
+
+static ssize_t ina226_set_avg(struct device *dev,
+ struct device_attribute *da,
+ const char *buf, size_t count)
+{
+   struct ina2xx_data *data = dev_get_drvdata(dev);
+   unsigned long val;
+   int status, avg;
+
+   if (IS_ERR(data))
+   return PTR_ERR(data);
+
+   status = kstrtoul(buf, 10, );
+   if (status < 0)
+   return status;
+
+   if (val > INT_MAX || val == 0)
+   return -EINVAL;
+
+   avg = ina226_avg_bits(val);
+
+   mutex_lock(>update_lock);
+   

[PATCH v8 5/5] hwmon: ina2xx: allow to change the averaging rate at run-time

2015-01-05 Thread Bartosz Golaszewski
The averaging rate of ina226 is hardcoded to 16 in the driver. Make it
modifiable at run-time via a new sysfs attribute.

While we're at it - add an additional variable to ina2xx_data, which holds
the current configuration settings - this way we'll be able to restore the
configuration in case of an unexpected chip reset.

Signed-off-by: Bartosz Golaszewski bgolaszew...@baylibre.com
---
 Documentation/hwmon/ina2xx |   3 ++
 drivers/hwmon/ina2xx.c | 129 +++--
 2 files changed, 128 insertions(+), 4 deletions(-)

diff --git a/Documentation/hwmon/ina2xx b/Documentation/hwmon/ina2xx
index 320dd69..a11256d 100644
--- a/Documentation/hwmon/ina2xx
+++ b/Documentation/hwmon/ina2xx
@@ -48,3 +48,6 @@ The shunt value in micro-ohms can be set via platform data or 
device tree at
 compile-time or via the shunt_resistor attribute in sysfs at run-time. Please
 refer to the Documentation/devicetree/bindings/i2c/ina2xx.txt for bindings
 if the device tree is used.
+
+The averaging rate of INA226 and INA230 can be changed via the avg_rate sysfs
+attribute. The available rates are: 1, 4, 16, 64, 128, 256, 512 and 1024.
diff --git a/drivers/hwmon/ina2xx.c b/drivers/hwmon/ina2xx.c
index 49537ea..6f6aebd 100644
--- a/drivers/hwmon/ina2xx.c
+++ b/drivers/hwmon/ina2xx.c
@@ -68,6 +68,15 @@
 
 #define INA2XX_RSHUNT_DEFAULT  1
 
+/* bit masks for the averaging setting in the configuration register */
+#define INA226_AVG_RD_MASK 0x0E00
+#define INA226_AVG_WR_MASK 0xF1FF
+
+#define INA226_READ_AVG(reg) ((reg  INA226_AVG_RD_MASK)  9)
+
+/* common attrs, ina226 attrs and NULL */
+#define INA2XX_MAX_ATTRIBUTE_GROUPS3
+
 enum ina2xx_ids { ina219, ina226 };
 
 struct ina2xx_config {
@@ -85,12 +94,14 @@ struct ina2xx_data {
const struct ina2xx_config *config;
 
long rshunt;
+   u16 curr_config;
 
struct mutex update_lock;
bool valid;
unsigned long last_updated;
 
int kind;
+   const struct attribute_group *groups[INA2XX_MAX_ATTRIBUTE_GROUPS];
u16 regs[INA2XX_MAX_REGISTERS];
 };
 
@@ -115,6 +126,39 @@ static const struct ina2xx_config ina2xx_config[] = {
},
 };
 
+/*
+ * Available averaging rates for ina226. The indices correspond with
+ * the bit values expected by the chip (according to the ina226 datasheet,
+ * table 3 AVG bit settings, found at
+ * http://www.ti.com/lit/ds/symlink/ina226.pdf.
+ */
+static const int ina226_avg_tab[] = { 1, 4, 16, 64, 128, 256, 512, 1024 };
+
+static int ina226_avg_bits(int avg)
+{
+   int i;
+
+   /* Get the closest average from the tab. */
+   for (i = 0; i  ARRAY_SIZE(ina226_avg_tab) - 1; i++) {
+   if (avg = (ina226_avg_tab[i] + ina226_avg_tab[i + 1]) / 2)
+   return i;
+   }
+
+   return i; /* Return 0b0111 for other values. */
+}
+
+static int ina226_avg_val(int bits)
+{
+   /*
+* Value read from the config register should be correct, but do check
+* the boundary just in case.
+*/
+   if (bits = ARRAY_SIZE(ina226_avg_tab))
+   return -ENODEV;
+
+   return ina226_avg_tab[bits];
+}
+
 static int ina2xx_calibrate(struct ina2xx_data *data)
 {
return i2c_smbus_write_word_swapped(data-client, INA2XX_CALIBRATION,
@@ -131,7 +175,7 @@ static int ina2xx_init(struct ina2xx_data *data)
 
/* device configuration */
ret = i2c_smbus_write_word_swapped(client, INA2XX_CONFIG,
-  data-config-config_default);
+  data-curr_config);
if (ret  0)
return ret;
 
@@ -292,6 +336,62 @@ static ssize_t ina2xx_set_shunt(struct device *dev,
return count;
 }
 
+static ssize_t ina226_show_avg(struct device *dev,
+  struct device_attribute *da, char *buf)
+{
+   struct ina2xx_data *data = ina2xx_update_device(dev);
+   int avg, i;
+
+   if (IS_ERR(data))
+   return PTR_ERR(data);
+
+   avg = ina226_avg_val(INA226_READ_AVG(data-regs[INA2XX_CONFIG]));
+   if (avg  0)
+   return avg;
+
+   for (i = 0; i  ARRAY_SIZE(ina226_avg_tab); i++) {
+   if (avg == ina226_avg_tab[i])
+   break;
+   }
+
+   return snprintf(buf, PAGE_SIZE, %d\n, ina226_avg_tab[i]);
+}
+
+static ssize_t ina226_set_avg(struct device *dev,
+ struct device_attribute *da,
+ const char *buf, size_t count)
+{
+   struct ina2xx_data *data = dev_get_drvdata(dev);
+   unsigned long val;
+   int status, avg;
+
+   if (IS_ERR(data))
+   return PTR_ERR(data);
+
+   status = kstrtoul(buf, 10, val);
+   if (status  0)
+   return status;
+
+   if (val  INT_MAX || val == 0)
+   return -EINVAL;
+
+   avg = ina226_avg_bits(val);
+
+   mutex_lock(data-update_lock);