media: i2c: max9286: Introduce a debugfs layer

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()
diff --git a/drivers/media/i2c/max9286.c b/drivers/media/i2c/max9286.c
index dc5176a..7c83825 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>
@@ -143,6 +144,7 @@ struct max9286_device {
 	struct v4l2_subdev sd;
 	struct media_pad pads[MAX9286_N_PADS];
 	struct regulator *regulator;
+	struct dentry *dbgroot;
 	bool poc_enabled;
 	int streaming;
 
@@ -216,6 +218,123 @@ static int max9286_write(struct max9286_device *dev, u8 reg, u8 val)
 }
 
 /* -----------------------------------------------------------------------------
+ * 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_device *dev,
+				       struct seq_file *s)
+{
+	int reg_49 = max9286_read(dev, 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_device *dev, struct seq_file *s)
+{
+	int l, m, h;
+	unsigned int frame_length;
+
+	l = max9286_read(dev, 0x5b);
+	m = max9286_read(dev, 0x5c);
+	h = max9286_read(dev, 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_device *dev, struct seq_file *s)
+{
+	int reg_71 = max9286_read(dev, 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_device *dev = s->private;
+
+	max9286_config_video_detect(dev, s);
+	max9286_vs_period(dev, s);
+	max9286_master_link(dev, s);
+
+	return 0;
+}
+
+DEBUGFS_RO_ATTR(max9286_debugfs_info);
+
+static int max9286_debugfs_init(struct max9286_device *dev)
+{
+	char name[32];
+
+	snprintf(name, sizeof(name), "max9286-%s", dev_name(&dev->client->dev));
+
+	dev->dbgroot = debugfs_create_dir(name, NULL);
+	if (!dev->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, dev->dbgroot, dev,
+			    &max9286_debugfs_info_fops);
+
+	return 0;
+}
+
+static void max9286_debugfs_remove(struct max9286_device *dev)
+{
+	debugfs_remove_recursive(dev->dbgroot);
+}
+
+/* -----------------------------------------------------------------------------
  * I2C Multiplexer
  */
 
@@ -1126,6 +1245,9 @@ static int max9286_probe(struct i2c_client *client,
 	 */
 	max9286_configure_i2c(dev, false);
 
+	/* Add any userspace support before we return early. */
+	max9286_debugfs_init(dev);
+
 	ret = device_for_each_child(client->dev.parent, &client->dev,
 				    max9286_is_bound);
 	if (ret)
@@ -1147,6 +1269,7 @@ static int max9286_probe(struct i2c_client *client,
 	regulator_put(dev->regulator);
 	max9286_i2c_mux_close(dev);
 	max9286_configure_i2c(dev, false);
+	max9286_debugfs_remove(dev);
 err_free:
 	max9286_cleanup_dt(dev);
 	kfree(dev);
@@ -1158,6 +1281,9 @@ static int max9286_remove(struct i2c_client *client)
 {
 	struct max9286_device *dev = i2c_get_clientdata(client);
 
+	/* Remove all Debugfs / sysfs entries */
+	max9286_debugfs_remove(dev);
+
 	i2c_mux_del_adapters(dev->mux);
 
 	fwnode_handle_put(dev->sd.fwnode);