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);