diff mbox series

hwmon: (ucd90320) Add minimum delay between bus accesses

Message ID 20230312160312.2227405-1-lars@metafoo.de (mailing list archive)
State Accepted
Headers show
Series hwmon: (ucd90320) Add minimum delay between bus accesses | expand

Commit Message

Lars-Peter Clausen March 12, 2023, 4:03 p.m. UTC
When probing the ucd90320 access to some of the registers randomly fails.
Sometimes it NACKs a transfer, sometimes it returns just random data and
the PEC check fails.

Experimentation shows that this seems to be triggered by a register access
directly back to back with a previous register write. Experimentation also
shows that inserting a small delay after register writes makes the issue go
away.

Use a similar solution to what the max15301 driver does to solve the same
problem. Create a custom set of bus read and write functions that make sure
that the delay is added.

Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
---
 drivers/hwmon/pmbus/ucd9000.c | 75 +++++++++++++++++++++++++++++++++++
 1 file changed, 75 insertions(+)

Comments

Guenter Roeck March 12, 2023, 6:12 p.m. UTC | #1
On Sun, Mar 12, 2023 at 09:03:12AM -0700, Lars-Peter Clausen wrote:
> When probing the ucd90320 access to some of the registers randomly fails.
> Sometimes it NACKs a transfer, sometimes it returns just random data and
> the PEC check fails.
> 
> Experimentation shows that this seems to be triggered by a register access
> directly back to back with a previous register write. Experimentation also
> shows that inserting a small delay after register writes makes the issue go
> away.
> 
> Use a similar solution to what the max15301 driver does to solve the same
> problem. Create a custom set of bus read and write functions that make sure
> that the delay is added.
> 
> Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>

Applied.

Thanks,
Guenter

> ---
>  drivers/hwmon/pmbus/ucd9000.c | 75 +++++++++++++++++++++++++++++++++++
>  1 file changed, 75 insertions(+)
> 
> diff --git a/drivers/hwmon/pmbus/ucd9000.c b/drivers/hwmon/pmbus/ucd9000.c
> index 75fc770c9e40..3daaf2237832 100644
> --- a/drivers/hwmon/pmbus/ucd9000.c
> +++ b/drivers/hwmon/pmbus/ucd9000.c
> @@ -7,6 +7,7 @@
>   */
>  
>  #include <linux/debugfs.h>
> +#include <linux/delay.h>
>  #include <linux/kernel.h>
>  #include <linux/module.h>
>  #include <linux/of_device.h>
> @@ -16,6 +17,7 @@
>  #include <linux/i2c.h>
>  #include <linux/pmbus.h>
>  #include <linux/gpio/driver.h>
> +#include <linux/timekeeping.h>
>  #include "pmbus.h"
>  
>  enum chips { ucd9000, ucd90120, ucd90124, ucd90160, ucd90320, ucd9090,
> @@ -65,6 +67,7 @@ struct ucd9000_data {
>  	struct gpio_chip gpio;
>  #endif
>  	struct dentry *debugfs;
> +	ktime_t write_time;
>  };
>  #define to_ucd9000_data(_info) container_of(_info, struct ucd9000_data, info)
>  
> @@ -73,6 +76,73 @@ struct ucd9000_debugfs_entry {
>  	u8 index;
>  };
>  
> +/*
> + * It has been observed that the UCD90320 randomly fails register access when
> + * doing another access right on the back of a register write. To mitigate this
> + * make sure that there is a minimum delay between a write access and the
> + * following access. The 250us is based on experimental data. At a delay of
> + * 200us the issue seems to go away. Add a bit of extra margin to allow for
> + * system to system differences.
> + */
> +#define UCD90320_WAIT_DELAY_US 250
> +
> +static inline void ucd90320_wait(const struct ucd9000_data *data)
> +{
> +	s64 delta = ktime_us_delta(ktime_get(), data->write_time);
> +
> +	if (delta < UCD90320_WAIT_DELAY_US)
> +		udelay(UCD90320_WAIT_DELAY_US - delta);
> +}
> +
> +static int ucd90320_read_word_data(struct i2c_client *client, int page,
> +				   int phase, int reg)
> +{
> +	const struct pmbus_driver_info *info = pmbus_get_driver_info(client);
> +	struct ucd9000_data *data = to_ucd9000_data(info);
> +
> +	if (reg >= PMBUS_VIRT_BASE)
> +		return -ENXIO;
> +
> +	ucd90320_wait(data);
> +	return pmbus_read_word_data(client, page, phase, reg);
> +}
> +
> +static int ucd90320_read_byte_data(struct i2c_client *client, int page, int reg)
> +{
> +	const struct pmbus_driver_info *info = pmbus_get_driver_info(client);
> +	struct ucd9000_data *data = to_ucd9000_data(info);
> +
> +	ucd90320_wait(data);
> +	return pmbus_read_byte_data(client, page, reg);
> +}
> +
> +static int ucd90320_write_word_data(struct i2c_client *client, int page,
> +				    int reg, u16 word)
> +{
> +	const struct pmbus_driver_info *info = pmbus_get_driver_info(client);
> +	struct ucd9000_data *data = to_ucd9000_data(info);
> +	int ret;
> +
> +	ucd90320_wait(data);
> +	ret = pmbus_write_word_data(client, page, reg, word);
> +	data->write_time = ktime_get();
> +
> +	return ret;
> +}
> +
> +static int ucd90320_write_byte(struct i2c_client *client, int page, u8 value)
> +{
> +	const struct pmbus_driver_info *info = pmbus_get_driver_info(client);
> +	struct ucd9000_data *data = to_ucd9000_data(info);
> +	int ret;
> +
> +	ucd90320_wait(data);
> +	ret = pmbus_write_byte(client, page, value);
> +	data->write_time = ktime_get();
> +
> +	return ret;
> +}
> +
>  static int ucd9000_get_fan_config(struct i2c_client *client, int fan)
>  {
>  	int fan_config = 0;
> @@ -598,6 +668,11 @@ static int ucd9000_probe(struct i2c_client *client)
>  		info->read_byte_data = ucd9000_read_byte_data;
>  		info->func[0] |= PMBUS_HAVE_FAN12 | PMBUS_HAVE_STATUS_FAN12
>  		  | PMBUS_HAVE_FAN34 | PMBUS_HAVE_STATUS_FAN34;
> +	} else if (mid->driver_data == ucd90320) {
> +		info->read_byte_data = ucd90320_read_byte_data;
> +		info->read_word_data = ucd90320_read_word_data;
> +		info->write_byte = ucd90320_write_byte;
> +		info->write_word_data = ucd90320_write_word_data;
>  	}
>  
>  	ucd9000_probe_gpio(client, mid, data);
diff mbox series

Patch

diff --git a/drivers/hwmon/pmbus/ucd9000.c b/drivers/hwmon/pmbus/ucd9000.c
index 75fc770c9e40..3daaf2237832 100644
--- a/drivers/hwmon/pmbus/ucd9000.c
+++ b/drivers/hwmon/pmbus/ucd9000.c
@@ -7,6 +7,7 @@ 
  */
 
 #include <linux/debugfs.h>
+#include <linux/delay.h>
 #include <linux/kernel.h>
 #include <linux/module.h>
 #include <linux/of_device.h>
@@ -16,6 +17,7 @@ 
 #include <linux/i2c.h>
 #include <linux/pmbus.h>
 #include <linux/gpio/driver.h>
+#include <linux/timekeeping.h>
 #include "pmbus.h"
 
 enum chips { ucd9000, ucd90120, ucd90124, ucd90160, ucd90320, ucd9090,
@@ -65,6 +67,7 @@  struct ucd9000_data {
 	struct gpio_chip gpio;
 #endif
 	struct dentry *debugfs;
+	ktime_t write_time;
 };
 #define to_ucd9000_data(_info) container_of(_info, struct ucd9000_data, info)
 
@@ -73,6 +76,73 @@  struct ucd9000_debugfs_entry {
 	u8 index;
 };
 
+/*
+ * It has been observed that the UCD90320 randomly fails register access when
+ * doing another access right on the back of a register write. To mitigate this
+ * make sure that there is a minimum delay between a write access and the
+ * following access. The 250us is based on experimental data. At a delay of
+ * 200us the issue seems to go away. Add a bit of extra margin to allow for
+ * system to system differences.
+ */
+#define UCD90320_WAIT_DELAY_US 250
+
+static inline void ucd90320_wait(const struct ucd9000_data *data)
+{
+	s64 delta = ktime_us_delta(ktime_get(), data->write_time);
+
+	if (delta < UCD90320_WAIT_DELAY_US)
+		udelay(UCD90320_WAIT_DELAY_US - delta);
+}
+
+static int ucd90320_read_word_data(struct i2c_client *client, int page,
+				   int phase, int reg)
+{
+	const struct pmbus_driver_info *info = pmbus_get_driver_info(client);
+	struct ucd9000_data *data = to_ucd9000_data(info);
+
+	if (reg >= PMBUS_VIRT_BASE)
+		return -ENXIO;
+
+	ucd90320_wait(data);
+	return pmbus_read_word_data(client, page, phase, reg);
+}
+
+static int ucd90320_read_byte_data(struct i2c_client *client, int page, int reg)
+{
+	const struct pmbus_driver_info *info = pmbus_get_driver_info(client);
+	struct ucd9000_data *data = to_ucd9000_data(info);
+
+	ucd90320_wait(data);
+	return pmbus_read_byte_data(client, page, reg);
+}
+
+static int ucd90320_write_word_data(struct i2c_client *client, int page,
+				    int reg, u16 word)
+{
+	const struct pmbus_driver_info *info = pmbus_get_driver_info(client);
+	struct ucd9000_data *data = to_ucd9000_data(info);
+	int ret;
+
+	ucd90320_wait(data);
+	ret = pmbus_write_word_data(client, page, reg, word);
+	data->write_time = ktime_get();
+
+	return ret;
+}
+
+static int ucd90320_write_byte(struct i2c_client *client, int page, u8 value)
+{
+	const struct pmbus_driver_info *info = pmbus_get_driver_info(client);
+	struct ucd9000_data *data = to_ucd9000_data(info);
+	int ret;
+
+	ucd90320_wait(data);
+	ret = pmbus_write_byte(client, page, value);
+	data->write_time = ktime_get();
+
+	return ret;
+}
+
 static int ucd9000_get_fan_config(struct i2c_client *client, int fan)
 {
 	int fan_config = 0;
@@ -598,6 +668,11 @@  static int ucd9000_probe(struct i2c_client *client)
 		info->read_byte_data = ucd9000_read_byte_data;
 		info->func[0] |= PMBUS_HAVE_FAN12 | PMBUS_HAVE_STATUS_FAN12
 		  | PMBUS_HAVE_FAN34 | PMBUS_HAVE_STATUS_FAN34;
+	} else if (mid->driver_data == ucd90320) {
+		info->read_byte_data = ucd90320_read_byte_data;
+		info->read_word_data = ucd90320_read_word_data;
+		info->write_byte = ucd90320_write_byte;
+		info->write_word_data = ucd90320_write_word_data;
 	}
 
 	ucd9000_probe_gpio(client, mid, data);