diff mbox series

[RFC,v6,10/13] media: i2c: max9286: Introduce a debugfs layer

Message ID 20191216102930.5867-11-kieran.bingham+renesas@ideasonboard.com (mailing list archive)
State Under Review
Delegated to: Kieran Bingham
Headers show
Series GMSL Renesas Platform Support | expand

Commit Message

Kieran Bingham Dec. 16, 2019, 10:29 a.m. UTC
The MAX9286 provides several read-only status registers for observing
the system state of the device.

Provide error statistics and link status through debugfs files.
These files will be free-form and should not be considered as part
of any userspace API

Signed-off-by: Kieran Bingham <kieran.bingham+renesas@ideasonboard.com>

---

Please consider this a starting point for adding useful debug. I don't
necessarily expect this code to make it to the final driver, but it
could be very useful to process and expose this information internally
in the driver.

Alternatively - we could move this all to max9286_debugfs.c or such.

Watch out for a lot of the error counters which reset to 0 when read.
We should handle those by adding the value to a local store on every
read.

v2:
 - Cleanup debugfs in error path of probe()

v6:
 - Fix a bogus (h << 0) check
---
 drivers/media/i2c/max9286.c | 127 ++++++++++++++++++++++++++++++++++++
 1 file changed, 127 insertions(+)
diff mbox series

Patch

diff --git a/drivers/media/i2c/max9286.c b/drivers/media/i2c/max9286.c
index df77871e603c..6e1dfc518e03 100644
--- a/drivers/media/i2c/max9286.c
+++ b/drivers/media/i2c/max9286.c
@@ -10,6 +10,7 @@ 
  * Copyright (C) 2015 Cogent Embedded, Inc.
  */
 
+#include <linux/debugfs.h>
 #include <linux/delay.h>
 #include <linux/device.h>
 #include <linux/fwnode.h>
@@ -148,6 +149,7 @@  struct max9286_priv {
 	struct v4l2_subdev sd;
 	struct media_pad pads[MAX9286_N_PADS];
 	struct regulator *regulator;
+	struct dentry *dbgroot;
 	bool poc_enabled;
 
 	struct gpio_chip gpio;
@@ -225,6 +227,124 @@  static int max9286_write(struct max9286_priv *priv, u8 reg, u8 val)
 	return ret;
 }
 
+/* -----------------------------------------------------------------------------
+ * DebugFS
+ */
+
+#define DEBUGFS_RO_ATTR(name)						\
+static int name##_open(struct inode *inode, struct file *file)		\
+{									\
+	return single_open(file, name, inode->i_private);		\
+}									\
+static const struct file_operations name##_fops = {			\
+	.owner = THIS_MODULE,						\
+	.open = name##_open,						\
+	.llseek = seq_lseek,						\
+	.read = seq_read,						\
+	.release = single_release,					\
+}
+
+static int max9286_config_video_detect(struct max9286_priv *priv,
+				       struct seq_file *s)
+{
+	int reg_49 = max9286_read(priv, 0x49);
+	unsigned int i;
+
+	if (reg_49 < 0)
+		return reg_49;
+
+	seq_puts(s, "                  :  0   1   2   3\n");
+	seq_puts(s, "Configuration Link:");
+	for (i = 0; i < 4; i++) {
+		int link = (reg_49 & BIT(i + 4));
+
+		seq_printf(s, " %3s", link ? " O " : "xxx");
+	}
+	seq_puts(s, "\n");
+
+	seq_puts(s, "Video Link        :");
+	for (i = 0; i < 4; i++) {
+		int link = (reg_49 & BIT(i));
+
+		seq_printf(s, " %3s", link ? " O " : "xxx");
+	}
+	seq_puts(s, "\n");
+
+	return 0;
+}
+
+static int max9286_vs_period(struct max9286_priv *priv, struct seq_file *s)
+{
+	int l, m, h;
+	unsigned int frame_length;
+
+	l = max9286_read(priv, 0x5b);
+	m = max9286_read(priv, 0x5c);
+	h = max9286_read(priv, 0x5d);
+
+	if (l < 0 || m < 0 || h < 0)
+		return -ENODEV;
+
+	frame_length = (h << 16) + (m << 8) + l;
+
+	seq_printf(s, "Calculated VS Period (pxclk) : %u\n", frame_length);
+
+	return 0;
+}
+
+static int max9286_master_link(struct max9286_priv *priv, struct seq_file *s)
+{
+	int reg_71 = max9286_read(priv, 0x71);
+	unsigned int link = (reg_71 >> 4) & 0x03;
+
+	if (reg_71 < 0)
+		return reg_71;
+
+	seq_printf(s, "Master link selected : %u\n", link);
+
+	return 0;
+}
+
+static int max9286_debugfs_info(struct seq_file *s, void *p)
+{
+	struct max9286_priv *priv = s->private;
+
+	max9286_config_video_detect(priv, s);
+	max9286_vs_period(priv, s);
+	max9286_master_link(priv, s);
+
+	return 0;
+}
+
+DEBUGFS_RO_ATTR(max9286_debugfs_info);
+
+static int max9286_debugfs_init(struct max9286_priv *priv)
+{
+	char name[32];
+
+	snprintf(name, sizeof(name), "max9286-%s",
+		 dev_name(&priv->client->dev));
+
+	priv->dbgroot = debugfs_create_dir(name, NULL);
+	if (!priv->dbgroot)
+		return -ENOMEM;
+
+	/*
+	 * dentry pointers are discarded, and remove_recursive is used to
+	 * cleanup the tree. DEBUGFS_RO_ATTR defines the file operations with
+	 * the _fops extension to the function name.
+	 */
+	debugfs_create_file("info", 0444, priv->dbgroot, priv,
+			    &max9286_debugfs_info_fops);
+
+	return 0;
+}
+
+static void max9286_debugfs_remove(struct max9286_priv *priv)
+{
+	debugfs_remove_recursive(priv->dbgroot);
+}
+
 /* -----------------------------------------------------------------------------
  * I2C Multiplexer
  */
@@ -1094,6 +1214,9 @@  static int max9286_probe(struct i2c_client *client)
 	 */
 	max9286_configure_i2c(priv, false);
 
+	/* Add any userspace support before we return early. */
+	max9286_debugfs_init(priv);
+
 	ret = max9286_init(&client->dev);
 	if (ret < 0)
 		goto err_regulator;
@@ -1104,6 +1227,7 @@  static int max9286_probe(struct i2c_client *client)
 	regulator_put(priv->regulator);
 	max9286_i2c_mux_close(priv);
 	max9286_configure_i2c(priv, false);
+	max9286_debugfs_remove(priv);
 err_free:
 	max9286_cleanup_dt(priv);
 	kfree(priv);
@@ -1115,6 +1239,9 @@  static int max9286_remove(struct i2c_client *client)
 {
 	struct max9286_priv *priv = i2c_get_clientdata(client);
 
+	/* Remove all Debugfs / sysfs entries */
+	max9286_debugfs_remove(priv);
+
 	i2c_mux_del_adapters(priv->mux);
 
 	fwnode_handle_put(priv->sd.fwnode);