[PATCH 10/10] drm: xlnx: zynqmp: Add debugfs

Hyun Kwon hyunk at xilinx.com
Thu Jan 11 02:05:31 UTC 2018


Hi Daniel,

> -----Original Message-----
> From: Daniel Vetter [mailto:daniel.vetter at ffwll.ch] On Behalf Of Daniel
> Vetter
> Sent: Tuesday, January 09, 2018 1:54 AM
> To: Hyun Kwon <hyunk at xilinx.com>
> Cc: dri-devel at lists.freedesktop.org; devicetree at vger.kernel.org; Tejas
> Upadhyay <TEJASU at xilinx.com>; Michal Simek <michal.simek at xilinx.com>
> Subject: Re: [PATCH 10/10] drm: xlnx: zynqmp: Add debugfs
> 
> On Thu, Jan 04, 2018 at 06:05:59PM -0800, Hyun Kwon wrote:
> > Debugfs can be used to exploit some specific setting. Main purpose
> > is for testing and debug diagnostic.
> >
> > Signed-off-by: Tejas Upadhyay <tejasu at xilinx.com>
> > Signed-off-by: Hyun Kwon <hyun.kwon at xilinx.com>
> 
> Hm, not sure what's the use, it seems to just be for setting/getting
> your driver-private properties. Usually people use modetest and similar
> tools, but if there's demand for setting properties in debugfs (we already
> have all the infrastructure for dumping the entire kms state, see the
> various atomic_print_state callbacks) I think that should be done with
> generic drm code.
> 
> I'd drop this patch for now (if there's no other reason for it).

Thanks for the input. It'd be helpful, for my own understanding, if this can be elaborated a little more. We are using standard tools as well as custom script/tool, but some specific properties / controls are hard to be executed with modetest only unless we change the entire set up / design between each run. The debugfs is used to run all (or as much as possible) properties in a single run, and from what I understand, that doesn't violate intended debugfs usage as long as it's not treated as a stable ABI. The intention is to help isolate issues and enhance the diagnostics. I agree, in the long term, this kind of stuff should be handled in generic way, but would it be still reasonable to keep it driver specific in the meantime?

Thanks,
-hyun

> -Daniel
> 
> > ---
> >  drivers/gpu/drm/xlnx/Kconfig       |  21 +++
> >  drivers/gpu/drm/xlnx/zynqmp_disp.c | 326
> +++++++++++++++++++++++++++++++++++++
> >  drivers/gpu/drm/xlnx/zynqmp_dp.c   | 304
> ++++++++++++++++++++++++++++++++++
> >  3 files changed, 651 insertions(+)
> >
> > diff --git a/drivers/gpu/drm/xlnx/Kconfig b/drivers/gpu/drm/xlnx/Kconfig
> > index 7c5529c..befce0f 100644
> > --- a/drivers/gpu/drm/xlnx/Kconfig
> > +++ b/drivers/gpu/drm/xlnx/Kconfig
> > @@ -21,3 +21,24 @@ config DRM_ZYNQMP_DPSUB
> >  	  this option if you have a Xilinx ZynqMP SoC with DisplayPort
> >  	  subsystem. The driver provides the kernel mode setting
> >  	  functionlaities for ZynqMP DP subsystem.
> > +
> > +config DRM_ZYNQMP_DISP_DEBUG_FS
> > +	bool "ZynqMP Display debugfs"
> > +	depends on DEBUG_FS && DRM_ZYNQMP_DPSUB
> > +	select DRM_ZYNQMP_DP_DEBUG_FS
> > +	help
> > +	  Enable the debugfs code for DP Sub driver. The debugfs code
> > +	  enables debugging or testing related features. It exposes some
> > +	  low level controls to the user space to help testing automation,
> > +	  as well as can enable additional diagnostic or statistical
> > +	  information.
> > +
> > +config DRM_ZYNQMP_DP_DEBUG_FS
> > +	bool "ZynqMP DP debugfs"
> > +	depends on DEBUG_FS && DRM_ZYNQMP_DPSUB
> > +	help
> > +	  Enable the debugfs code for DP driver. The debugfs code
> > +	  enables debugging or testing related features. It exposes some
> > +	  low level controls to the user space to help testing automation,
> > +	  as well as can enable additional diagnostic or statistical
> > +	  information.
> > diff --git a/drivers/gpu/drm/xlnx/zynqmp_disp.c
> b/drivers/gpu/drm/xlnx/zynqmp_disp.c
> > index 68f829c..9fe6d49 100644
> > --- a/drivers/gpu/drm/xlnx/zynqmp_disp.c
> > +++ b/drivers/gpu/drm/xlnx/zynqmp_disp.c
> > @@ -17,6 +17,7 @@
> >  #include <drm/drm_plane_helper.h>
> >
> >  #include <linux/clk.h>
> > +#include <linux/debugfs.h>
> >  #include <linux/device.h>
> >  #include <linux/dmaengine.h>
> >  #include <linux/interrupt.h>
> > @@ -508,6 +509,325 @@ static void zynqmp_disp_set(void __iomem
> *base, int offset, u32 set)
> >  	zynqmp_disp_write(base, offset, zynqmp_disp_read(base, offset) |
> set);
> >  }
> >
> > +#ifdef CONFIG_DRM_ZYNQMP_DISP_DEBUG_FS
> > +
> > +#define ZYNQMP_DISP_DEBUGFS_READ_MAX_SIZE	32UL
> > +#define ZYNQMP_DISP_DEBUGFS_MAX_BG_COLOR_VAL	0xFFF
> > +#define IN_RANGE(x, min, max) ({		\
> > +		typeof(x) _x = (x);		\
> > +		_x >= (min) && _x <= (max); })
> > +
> > +/* Match xilinx_dp_testcases vs dp_debugfs_reqs[] entry */
> > +enum zynqmp_disp_testcases {
> > +	DP_SUB_TC_BG_COLOR,
> > +	DP_SUB_TC_OUTPUT_FMT,
> > +	DP_SUB_TC_NONE
> > +};
> > +
> > +struct zynqmp_disp_debugfs {
> > +	enum zynqmp_disp_testcases testcase;
> > +	u16 r_value;
> > +	u16 g_value;
> > +	u16 b_value;
> > +	u32 output_fmt;
> > +	struct zynqmp_disp *zynqmp_disp;
> > +};
> > +
> > +static struct dentry *zynqmp_disp_debugfs_dir;
> > +struct zynqmp_disp_debugfs disp_debugfs;
> > +struct zynqmp_disp_debugfs_request {
> > +	const char *req;
> > +	enum zynqmp_disp_testcases tc;
> > +	ssize_t (*read_handler)(char **kern_buff);
> > +	ssize_t (*write_handler)(char **cmd);
> > +};
> > +
> > +static void
> > +zynqmp_disp_set_output_fmt(struct zynqmp_disp *disp, unsigned int
> id);
> > +static s64 zynqmp_disp_debugfs_argument_value(char *arg)
> > +{
> > +	s64 value;
> > +
> > +	if (!arg)
> > +		return -1;
> > +
> > +	if (!kstrtos64(arg, 0, &value))
> > +		return value;
> > +
> > +	return -1;
> > +}
> > +
> > +static ssize_t
> > +zynqmp_disp_debugfs_background_color_write(char **disp_test_arg)
> > +{
> > +	char *r_color, *g_color, *b_color;
> > +	s64 r_val, g_val, b_val;
> > +
> > +	r_color = strsep(disp_test_arg, " ");
> > +	g_color = strsep(disp_test_arg, " ");
> > +	b_color = strsep(disp_test_arg, " ");
> > +
> > +	/* char * to int conversion */
> > +	r_val = zynqmp_disp_debugfs_argument_value(r_color);
> > +	g_val = zynqmp_disp_debugfs_argument_value(g_color);
> > +	b_val = zynqmp_disp_debugfs_argument_value(b_color);
> > +
> > +	if (!(IN_RANGE(r_val, 0,
> ZYNQMP_DISP_DEBUGFS_MAX_BG_COLOR_VAL) &&
> > +	      IN_RANGE(g_val, 0,
> ZYNQMP_DISP_DEBUGFS_MAX_BG_COLOR_VAL) &&
> > +	      IN_RANGE(b_val, 0,
> ZYNQMP_DISP_DEBUGFS_MAX_BG_COLOR_VAL)))
> > +		return -EINVAL;
> > +
> > +	disp_debugfs.r_value = r_val;
> > +	disp_debugfs.g_value = g_val;
> > +	disp_debugfs.b_value = b_val;
> > +
> > +	disp_debugfs.testcase = DP_SUB_TC_BG_COLOR;
> > +
> > +	return 0;
> > +}
> > +
> > +static ssize_t
> > +zynqmp_disp_debugfs_output_display_format_write(char
> **disp_test_arg)
> > +{
> > +	char *output_format;
> > +	struct zynqmp_disp *disp = disp_debugfs.zynqmp_disp;
> > +
> > +	/* Read the value from a user value */
> > +	output_format = strsep(disp_test_arg, " ");
> > +	if (strncmp(output_format, "rgb", 3) == 0) {
> > +		disp_debugfs.output_fmt =
> > +			ZYNQMP_DISP_V_BLEND_OUTPUT_VID_FMT_RGB;
> > +	} else if (strncmp(output_format, "ycbcr444", 8) == 0) {
> > +		disp_debugfs.output_fmt =
> > +
> 	ZYNQMP_DISP_V_BLEND_OUTPUT_VID_FMT_YCBCR444;
> > +	} else if (strncmp(output_format, "ycbcr422", 8) == 0) {
> > +		disp_debugfs.output_fmt =
> > +
> 	ZYNQMP_DISP_V_BLEND_OUTPUT_VID_FMT_YCBCR422;
> > +	} else if (strncmp(output_format, "yonly", 5) == 0) {
> > +		disp_debugfs.output_fmt =
> > +
> 	ZYNQMP_DISP_V_BLEND_OUTPUT_VID_FMT_YONLY;
> > +	} else {
> > +		dev_err(disp->dev, "Invalid output format\n");
> > +		return -EINVAL;
> > +	}
> > +
> > +	disp_debugfs.testcase = DP_SUB_TC_OUTPUT_FMT;
> > +
> > +	return 0;
> > +}
> > +
> > +static ssize_t
> > +zynqmp_disp_debugfs_output_display_format_read(char **kern_buff)
> > +{
> > +	size_t out_str_len;
> > +
> > +	disp_debugfs.testcase = DP_SUB_TC_NONE;
> > +	disp_debugfs.output_fmt = 0;
> > +
> > +	out_str_len = strlen("Success");
> > +	out_str_len = min(ZYNQMP_DISP_DEBUGFS_READ_MAX_SIZE,
> out_str_len);
> > +	snprintf(*kern_buff, out_str_len, "%s", "Success");
> > +
> > +	return 0;
> > +}
> > +
> > +static ssize_t
> > +zynqmp_disp_debugfs_background_color_read(char **kern_buff)
> > +{
> > +	size_t out_str_len;
> > +
> > +	disp_debugfs.testcase = DP_SUB_TC_NONE;
> > +	disp_debugfs.r_value = 0;
> > +	disp_debugfs.g_value = 0;
> > +	disp_debugfs.b_value = 0;
> > +
> > +	out_str_len = strlen("Success");
> > +	out_str_len = min(ZYNQMP_DISP_DEBUGFS_READ_MAX_SIZE,
> out_str_len);
> > +	snprintf(*kern_buff, out_str_len, "%s", "Success");
> > +
> > +	return 0;
> > +}
> > +
> > +/* Match xilinx_dp_testcases vs dp_debugfs_reqs[] entry */
> > +struct zynqmp_disp_debugfs_request disp_debugfs_reqs[] = {
> > +	{"BACKGROUND_COLOR", DP_SUB_TC_BG_COLOR,
> > +		zynqmp_disp_debugfs_background_color_read,
> > +		zynqmp_disp_debugfs_background_color_write},
> > +	{"OUTPUT_DISPLAY_FORMAT", DP_SUB_TC_OUTPUT_FMT,
> > +		zynqmp_disp_debugfs_output_display_format_read,
> > +		zynqmp_disp_debugfs_output_display_format_write},
> > +};
> > +
> > +static ssize_t
> > +zynqmp_disp_debugfs_write(struct file *f, const char __user *buf,
> > +			  size_t size, loff_t *pos)
> > +{
> > +	char *kern_buff, *disp_test_req, *kern_buff_start;
> > +	int ret;
> > +	unsigned int i;
> > +
> > +	if (*pos != 0 || size <= 0)
> > +		return -EINVAL;
> > +
> > +	if (disp_debugfs.testcase != DP_SUB_TC_NONE)
> > +		return -EBUSY;
> > +
> > +	kern_buff = kzalloc(size, GFP_KERNEL);
> > +	if (!kern_buff)
> > +		return -ENOMEM;
> > +	kern_buff_start = kern_buff;
> > +
> > +	ret = strncpy_from_user(kern_buff, buf, size);
> > +	if (ret < 0) {
> > +		kfree(kern_buff_start);
> > +		return ret;
> > +	}
> > +
> > +	/* Read the testcase name and argument from a user request */
> > +	disp_test_req = strsep(&kern_buff, " ");
> > +
> > +	for (i = 0; i < ARRAY_SIZE(disp_debugfs_reqs); i++) {
> > +		if (!strcasecmp(disp_test_req, disp_debugfs_reqs[i].req))
> > +			if (!disp_debugfs_reqs[i].write_handler(&kern_buff))
> {
> > +				kfree(kern_buff_start);
> > +				return size;
> > +			}
> > +	}
> > +	kfree(kern_buff_start);
> > +	return -EINVAL;
> > +}
> > +
> > +static ssize_t zynqmp_disp_debugfs_read(struct file *f, char __user *buf,
> > +					size_t size, loff_t *pos)
> > +{
> > +	char *kern_buff = NULL;
> > +	size_t kern_buff_len, out_str_len;
> > +	enum zynqmp_disp_testcases tc;
> > +	int ret;
> > +
> > +	if (size <= 0)
> > +		return -EINVAL;
> > +
> > +	if (*pos != 0)
> > +		return 0;
> > +
> > +	kern_buff = kzalloc(ZYNQMP_DISP_DEBUGFS_READ_MAX_SIZE,
> GFP_KERNEL);
> > +	if (!kern_buff) {
> > +		disp_debugfs.testcase = DP_SUB_TC_NONE;
> > +		return -ENOMEM;
> > +	}
> > +
> > +	tc = disp_debugfs.testcase;
> > +	if (tc == DP_SUB_TC_NONE) {
> > +		out_str_len = strlen("No testcase executed");
> > +		out_str_len =
> min(ZYNQMP_DISP_DEBUGFS_READ_MAX_SIZE,
> > +				  out_str_len);
> > +		snprintf(kern_buff, out_str_len, "%s", "No testcase
> executed");
> > +	} else {
> > +		ret = disp_debugfs_reqs[tc].read_handler(&kern_buff);
> > +		if (ret) {
> > +			kfree(kern_buff);
> > +			return ret;
> > +		}
> > +	}
> > +
> > +	kern_buff_len = strlen(kern_buff);
> > +	size = min(size, kern_buff_len);
> > +
> > +	ret = copy_to_user(buf, kern_buff, size);
> > +
> > +	kfree(kern_buff);
> > +	if (ret)
> > +		return ret;
> > +
> > +	*pos = size + 1;
> > +	return size;
> > +}
> > +
> > +static const struct file_operations fops_zynqmp_disp_dbgfs = {
> > +	.owner = THIS_MODULE,
> > +	.read = zynqmp_disp_debugfs_read,
> > +	.write = zynqmp_disp_debugfs_write,
> > +};
> > +
> > +static int zynqmp_disp_debugfs_init(struct zynqmp_disp *disp)
> > +{
> > +	int err;
> > +	struct dentry *zynqmp_disp_debugfs_file;
> > +
> > +	disp_debugfs.testcase = DP_SUB_TC_NONE;
> > +	disp_debugfs.zynqmp_disp = disp;
> > +
> > +	zynqmp_disp_debugfs_dir = debugfs_create_dir("disp", NULL);
> > +	if (!zynqmp_disp_debugfs_dir) {
> > +		dev_err(disp->dev, "debugfs_create_dir failed\n");
> > +		return -ENODEV;
> > +	}
> > +
> > +	zynqmp_disp_debugfs_file =
> > +		debugfs_create_file("testcase", 0444,
> > +				    zynqmp_disp_debugfs_dir, NULL,
> > +				    &fops_zynqmp_disp_dbgfs);
> > +	if (!zynqmp_disp_debugfs_file) {
> > +		dev_err(disp->dev, "debugfs_create_file testcase failed\n");
> > +		err = -ENODEV;
> > +		goto err_dbgfs;
> > +	}
> > +	return 0;
> > +
> > +err_dbgfs:
> > +	debugfs_remove_recursive(zynqmp_disp_debugfs_dir);
> > +	zynqmp_disp_debugfs_dir = NULL;
> > +	return err;
> > +}
> > +
> > +static void zynqmp_disp_debugfs_exit(struct zynqmp_disp *disp)
> > +{
> > +	debugfs_remove_recursive(zynqmp_disp_debugfs_dir);
> > +	zynqmp_disp_debugfs_dir = NULL;
> > +}
> > +
> > +static void zynqmp_disp_debugfs_bg_color(struct zynqmp_disp *disp)
> > +{
> > +	if (disp_debugfs.testcase == DP_SUB_TC_BG_COLOR) {
> > +		zynqmp_disp_write(disp->blend.base,
> > +				  ZYNQMP_DISP_V_BLEND_BG_CLR_0,
> > +				  disp_debugfs.r_value);
> > +		zynqmp_disp_write(disp->blend.base,
> > +				  ZYNQMP_DISP_V_BLEND_BG_CLR_1,
> > +				  disp_debugfs.g_value);
> > +		zynqmp_disp_write(disp->blend.base,
> > +				  ZYNQMP_DISP_V_BLEND_BG_CLR_2,
> > +				  disp_debugfs.b_value);
> > +	}
> > +}
> > +
> > +static void
> > +zynqmp_disp_set_debugfs_output_fmt(struct zynqmp_disp *disp)
> > +{
> > +	if (disp_debugfs.testcase == DP_SUB_TC_OUTPUT_FMT)
> > +		zynqmp_disp_set_output_fmt(disp,
> disp_debugfs.output_fmt);
> > +}
> > +#else
> > +static void zynqmp_disp_debugfs_exit(struct zynqmp_disp *disp)
> > +{
> > +}
> > +
> > +static int zynqmp_disp_debugfs_init(struct zynqmp_disp *disp)
> > +{
> > +	return 0;
> > +}
> > +
> > +static void zynqmp_disp_debugfs_bg_color(struct zynqmp_disp *disp)
> > +{
> > +}
> > +
> > +static void
> > +zynqmp_disp_set_debugfs_output_fmt(struct zynqmp_disp *disp)
> > +{
> > +}
> > +#endif /* CONFIG_DP_DEBUG_FS */
> > +
> >  /*
> >   * Clock functions
> >   */
> > @@ -597,6 +917,8 @@ zynqmp_disp_blend_set_output_fmt(struct
> zynqmp_disp_blend *blend, u32 fmt)
> >  	u32 *offsets;
> >  	u32 offset, i;
> >
> > +	if (fmt == ZYNQMP_DISP_V_BLEND_OUTPUT_VID_FMT_YCBCR422)
> > +		fmt |=
> ZYNQMP_DISP_V_BLEND_OUTPUT_EN_DOWNSAMPLE;
> >  	zynqmp_disp_write(blend->base,
> ZYNQMP_DISP_V_BLEND_OUTPUT_VID_FMT, fmt);
> >  	if (fmt == ZYNQMP_DISP_V_BLEND_OUTPUT_VID_FMT_RGB) {
> >  		coeffs = reset_coeffs;
> > @@ -1941,6 +2263,7 @@ static void zynqmp_disp_set_bg_color(struct
> zynqmp_disp *disp,
> >  				     u32 c0, u32 c1, u32 c2)
> >  {
> >  	zynqmp_disp_blend_set_bg_color(&disp->blend, c0, c1, c2);
> > +	zynqmp_disp_debugfs_bg_color(disp);
> >  }
> >
> >  /**
> > @@ -2572,6 +2895,7 @@ zynqmp_disp_crtc_atomic_enable(struct
> drm_crtc *crtc,
> >  		return;
> >  	}
> >  	zynqmp_disp_set_output_fmt(disp, disp->color);
> > +	zynqmp_disp_set_debugfs_output_fmt(disp);
> >  	zynqmp_disp_set_bg_color(disp, disp->bg_c0, disp->bg_c1, disp-
> >bg_c2);
> >  	zynqmp_disp_enable(disp);
> >  	/* Delay of 3 vblank intervals for timing gen to be stable */
> > @@ -2911,6 +3235,7 @@ int zynqmp_disp_probe(struct
> platform_device *pdev)
> >  	ret = zynqmp_disp_layer_create(disp);
> >  	if (ret)
> >  		goto error_aclk;
> > +	zynqmp_disp_debugfs_init(disp);
> >
> >  	return 0;
> >
> > @@ -2924,6 +3249,7 @@ int zynqmp_disp_remove(struct
> platform_device *pdev)
> >  	struct zynqmp_dpsub *dpsub = platform_get_drvdata(pdev);
> >  	struct zynqmp_disp *disp = dpsub->disp;
> >
> > +	zynqmp_disp_debugfs_exit(disp);
> >  	zynqmp_disp_layer_destroy(disp);
> >  	if (disp->audclk)
> >  		zynqmp_disp_clk_disable(disp->audclk, &disp->audclk_en);
> > diff --git a/drivers/gpu/drm/xlnx/zynqmp_dp.c
> b/drivers/gpu/drm/xlnx/zynqmp_dp.c
> > index ce3c7c5..66fbad0 100644
> > --- a/drivers/gpu/drm/xlnx/zynqmp_dp.c
> > +++ b/drivers/gpu/drm/xlnx/zynqmp_dp.c
> > @@ -16,6 +16,7 @@
> >  #include <drm/drm_dp_helper.h>
> >  #include <drm/drm_of.h>
> >
> > +#include <linux/debugfs.h>
> >  #include <linux/delay.h>
> >  #include <linux/device.h>
> >  #include <linux/module.h>
> > @@ -371,6 +372,306 @@ static void zynqmp_dp_set(void __iomem
> *base, int offset, u32 set)
> >  }
> >
> >  /*
> > + * Debugfs functions
> > + */
> > +
> > +#ifdef CONFIG_DRM_ZYNQMP_DP_DEBUG_FS
> > +
> > +#define ZYNQMP_DP_DEBUGFS_READ_MAX_SIZE	32UL
> > +#define ZYNQMP_DP_DEBUGFS_UINT8_MAX_STR	"255"
> > +#define IN_RANGE(x, min, max) ({		\
> > +		typeof(x) _x = (x);		\
> > +		_x >= (min) && _x <= (max); })
> > +
> > +/* Match zynqmp_dp_testcases vs debugfs_reqs[] entry */
> > +enum zynqmp_dp_testcases {
> > +	DP_TC_LINK_RATE,
> > +	DP_TC_LANE_COUNT,
> > +	DP_TC_OUTPUT_FMT,
> > +	DP_TC_NONE
> > +};
> > +
> > +struct zynqmp_dp_debugfs {
> > +	enum zynqmp_dp_testcases testcase;
> > +	u8 link_rate;
> > +	u8 lane_cnt;
> > +	u8 old_output_fmt;
> > +	struct zynqmp_dp *dp;
> > +};
> > +
> > +static struct dentry *zynqmp_dp_debugfs_dir;
> > +static struct zynqmp_dp_debugfs dp_debugfs;
> > +struct zynqmp_dp_debugfs_request {
> > +	const char *req;
> > +	enum zynqmp_dp_testcases tc;
> > +	ssize_t (*read_handler)(char **kern_buff);
> > +	ssize_t (*write_handler)(char **cmd);
> > +};
> > +
> > +static s64 zynqmp_dp_debugfs_argument_value(char *arg)
> > +{
> > +	s64 value;
> > +
> > +	if (!arg)
> > +		return -1;
> > +
> > +	if (!kstrtos64(arg, 0, &value))
> > +		return value;
> > +
> > +	return -1;
> > +}
> > +
> > +static ssize_t zynqmp_dp_debugfs_max_linkrate_write(char
> **dp_test_arg)
> > +{
> > +	char *link_rate_arg;
> > +	s64 link_rate;
> > +
> > +	link_rate_arg = strsep(dp_test_arg, " ");
> > +	link_rate = zynqmp_dp_debugfs_argument_value(link_rate_arg);
> > +	if (link_rate < 0 || (link_rate != DP_HIGH_BIT_RATE2 &&
> > +			      link_rate != DP_HIGH_BIT_RATE &&
> > +			      link_rate != DP_REDUCED_BIT_RATE))
> > +		return -EINVAL;
> > +
> > +	dp_debugfs.link_rate = drm_dp_link_rate_to_bw_code(link_rate);
> > +	dp_debugfs.testcase = DP_TC_LINK_RATE;
> > +
> > +	return 0;
> > +}
> > +
> > +static ssize_t zynqmp_dp_debugfs_max_lanecnt_write(char
> **dp_test_arg)
> > +{
> > +	char *lane_cnt_arg;
> > +	s64 lane_count;
> > +
> > +	lane_cnt_arg = strsep(dp_test_arg, " ");
> > +	lane_count = zynqmp_dp_debugfs_argument_value(lane_cnt_arg);
> > +	if (lane_count < 0 || !IN_RANGE(lane_count, 1,
> > +					ZYNQMP_DP_MAX_LANES))
> > +		return -EINVAL;
> > +
> > +	dp_debugfs.lane_cnt = lane_count;
> > +	dp_debugfs.testcase = DP_TC_LANE_COUNT;
> > +
> > +	return 0;
> > +}
> > +
> > +static ssize_t zynqmp_dp_debugfs_max_linkrate_read(char **kern_buff)
> > +{
> > +	struct zynqmp_dp *dp = dp_debugfs.dp;
> > +	size_t output_str_len;
> > +	u8 dpcd_link_bw;
> > +	int ret;
> > +
> > +	dp_debugfs.testcase = DP_TC_NONE;
> > +	dp_debugfs.link_rate = 0;
> > +
> > +	/* Getting Sink Side Link Rate */
> > +	ret = drm_dp_dpcd_readb(&dp->aux, DP_LINK_BW_SET,
> &dpcd_link_bw);
> > +	if (ret < 0) {
> > +		dev_err(dp->dev, "Failed to read link rate via AUX.\n");
> > +		kfree(*kern_buff);
> > +		return ret;
> > +	}
> > +
> > +	output_str_len = strlen(ZYNQMP_DP_DEBUGFS_UINT8_MAX_STR);
> > +	output_str_len = min(ZYNQMP_DP_DEBUGFS_READ_MAX_SIZE,
> output_str_len);
> > +	snprintf(*kern_buff, output_str_len, "%u", dpcd_link_bw);
> > +
> > +	return 0;
> > +}
> > +
> > +static ssize_t zynqmp_dp_debugfs_max_lanecnt_read(char **kern_buff)
> > +{
> > +	struct zynqmp_dp *dp = dp_debugfs.dp;
> > +	size_t output_str_len;
> > +	u8 dpcd_lane_cnt;
> > +	int ret;
> > +
> > +	dp_debugfs.testcase = DP_TC_NONE;
> > +	dp_debugfs.lane_cnt = 0;
> > +
> > +	/* Getting Sink Side Lane Count */
> > +	ret = drm_dp_dpcd_readb(&dp->aux, DP_LANE_COUNT_SET,
> &dpcd_lane_cnt);
> > +	if (ret < 0) {
> > +		dev_err(dp->dev, "Failed to read link rate via AUX.\n");
> > +		kfree(*kern_buff);
> > +		return ret;
> > +	}
> > +
> > +	dpcd_lane_cnt &= DP_LANE_COUNT_MASK;
> > +	output_str_len = strlen(ZYNQMP_DP_DEBUGFS_UINT8_MAX_STR);
> > +	output_str_len = min(ZYNQMP_DP_DEBUGFS_READ_MAX_SIZE,
> output_str_len);
> > +	snprintf(*kern_buff, output_str_len, "%u", dpcd_lane_cnt);
> > +
> > +	return 0;
> > +}
> > +
> > +/* Match zynqmp_dp_testcases vs dp_debugfs_reqs[] entry */
> > +static struct zynqmp_dp_debugfs_request debugfs_reqs[] = {
> > +	{"LINK_RATE", DP_TC_LINK_RATE,
> > +			zynqmp_dp_debugfs_max_linkrate_read,
> > +			zynqmp_dp_debugfs_max_linkrate_write},
> > +	{"LANE_COUNT", DP_TC_LANE_COUNT,
> > +			zynqmp_dp_debugfs_max_lanecnt_read,
> > +			zynqmp_dp_debugfs_max_lanecnt_write},
> > +};
> > +
> > +static ssize_t zynqmp_dp_debugfs_read(struct file *f, char __user *buf,
> > +				      size_t size, loff_t *pos)
> > +{
> > +	char *kern_buff = NULL;
> > +	size_t kern_buff_len, out_str_len;
> > +	enum zynqmp_dp_testcases tc;
> > +	int ret;
> > +
> > +	if (size <= 0)
> > +		return -EINVAL;
> > +
> > +	if (*pos != 0)
> > +		return 0;
> > +
> > +	kern_buff = kzalloc(ZYNQMP_DP_DEBUGFS_READ_MAX_SIZE,
> GFP_KERNEL);
> > +	if (!kern_buff) {
> > +		dp_debugfs.testcase = DP_TC_NONE;
> > +		return -ENOMEM;
> > +	}
> > +
> > +	tc = dp_debugfs.testcase;
> > +	if (tc == DP_TC_NONE) {
> > +		out_str_len = strlen("No testcase executed");
> > +		out_str_len = min(ZYNQMP_DP_DEBUGFS_READ_MAX_SIZE,
> out_str_len);
> > +		snprintf(kern_buff, out_str_len, "%s", "No testcase
> executed");
> > +	} else {
> > +		ret = debugfs_reqs[tc].read_handler(&kern_buff);
> > +		if (ret) {
> > +			kfree(kern_buff);
> > +			return ret;
> > +		}
> > +	}
> > +
> > +	kern_buff_len = strlen(kern_buff);
> > +	size = min(size, kern_buff_len);
> > +
> > +	ret = copy_to_user(buf, kern_buff, size);
> > +
> > +	kfree(kern_buff);
> > +	if (ret)
> > +		return ret;
> > +
> > +	*pos = size + 1;
> > +	return size;
> > +}
> > +
> > +static ssize_t
> > +zynqmp_dp_debugfs_write(struct file *f, const char __user *buf,
> > +			size_t size, loff_t *pos)
> > +{
> > +	char *kern_buff, *kern_buff_start;
> > +	char *dp_test_req;
> > +	int ret;
> > +	int i;
> > +
> > +	if (*pos != 0 || size <= 0)
> > +		return -EINVAL;
> > +
> > +	if (dp_debugfs.testcase != DP_TC_NONE)
> > +		return -EBUSY;
> > +
> > +	kern_buff = kzalloc(size, GFP_KERNEL);
> > +	if (!kern_buff)
> > +		return -ENOMEM;
> > +	kern_buff_start = kern_buff;
> > +
> > +	ret = strncpy_from_user(kern_buff, buf, size);
> > +	if (ret < 0) {
> > +		kfree(kern_buff_start);
> > +		return ret;
> > +	}
> > +
> > +	/* Read the testcase name and argument from a user request */
> > +	dp_test_req = strsep(&kern_buff, " ");
> > +
> > +	for (i = 0; i < ARRAY_SIZE(debugfs_reqs); i++) {
> > +		if (!strcasecmp(dp_test_req, debugfs_reqs[i].req))
> > +			if (!debugfs_reqs[i].write_handler(&kern_buff)) {
> > +				kfree(kern_buff_start);
> > +				return size;
> > +			}
> > +	}
> > +
> > +	kfree(kern_buff_start);
> > +	return -EINVAL;
> > +}
> > +
> > +static const struct file_operations fops_zynqmp_dp_dbgfs = {
> > +	.owner = THIS_MODULE,
> > +	.read = zynqmp_dp_debugfs_read,
> > +	.write = zynqmp_dp_debugfs_write,
> > +};
> > +
> > +static int zynqmp_dp_debugfs_init(struct zynqmp_dp *dp)
> > +{
> > +	int err;
> > +	struct dentry *zynqmp_dp_debugfs_file;
> > +
> > +	dp_debugfs.testcase = DP_TC_NONE;
> > +	dp_debugfs.dp = dp;
> > +
> > +	zynqmp_dp_debugfs_dir = debugfs_create_dir("dp", NULL);
> > +	if (!zynqmp_dp_debugfs_dir) {
> > +		dev_err(dp->dev, "debugfs_create_dir failed\n");
> > +		return -ENODEV;
> > +	}
> > +
> > +	zynqmp_dp_debugfs_file =
> > +		debugfs_create_file("testcase", 0444,
> zynqmp_dp_debugfs_dir,
> > +				    NULL, &fops_zynqmp_dp_dbgfs);
> > +	if (!zynqmp_dp_debugfs_file) {
> > +		dev_err(dp->dev, "debugfs_create_file testcase failed\n");
> > +		err = -ENODEV;
> > +		goto err_dbgfs;
> > +	}
> > +	return 0;
> > +
> > +err_dbgfs:
> > +	debugfs_remove_recursive(zynqmp_dp_debugfs_dir);
> > +	zynqmp_dp_debugfs_dir = NULL;
> > +	return err;
> > +}
> > +
> > +static void zynqmp_dp_debugfs_exit(struct zynqmp_dp *dp)
> > +{
> > +	debugfs_remove_recursive(zynqmp_dp_debugfs_dir);
> > +	zynqmp_dp_debugfs_dir = NULL;
> > +}
> > +
> > +static void zynqmp_dp_debugfs_mode_config(struct zynqmp_dp *dp)
> > +{
> > +	dp->mode.bw_code =
> > +		dp_debugfs.link_rate ? dp_debugfs.link_rate : dp-
> >mode.bw_code;
> > +	dp->mode.lane_cnt =
> > +		dp_debugfs.lane_cnt ? dp_debugfs.lane_cnt : dp-
> >mode.lane_cnt;
> > +}
> > +
> > +#else /* DRM_ZYNQMP_DP_DEBUG_FS */
> > +
> > +static int zynqmp_dp_debugfs_init(struct zynqmp_dp *dp)
> > +{
> > +	return 0;
> > +}
> > +
> > +static void zynqmp_dp_debugfs_exit(struct zynqmp_dp *dp)
> > +{
> > +}
> > +
> > +static void zynqmp_dp_debugfs_mode_config(struct zynqmp_dp *dp)
> > +{
> > +}
> > +
> > +#endif /* DRM_ZYNQMP_DP_DEBUG_FS */
> > +
> > +/*
> >   * Internal functions: used by zynqmp_disp.c
> >   */
> >
> > @@ -597,6 +898,7 @@ static int zynqmp_dp_mode_configure(struct
> zynqmp_dp *dp, int pclock,
> >  			dp->mode.bw_code = bws[i];
> >  			dp->mode.lane_cnt = lane_cnt;
> >  			dp->mode.pclock = pclock;
> > +			zynqmp_dp_debugfs_mode_config(dp);
> >  			return dp->mode.bw_code;
> >  		}
> >  	}
> > @@ -1840,6 +2142,7 @@ int zynqmp_dp_probe(struct platform_device
> *pdev)
> >  	dpsub = platform_get_drvdata(pdev);
> >  	dpsub->dp = dp;
> >  	dp->dpsub = dpsub;
> > +	zynqmp_dp_debugfs_init(dp);
> >
> >  	return 0;
> >
> > @@ -1855,6 +2158,7 @@ int zynqmp_dp_remove(struct
> platform_device *pdev)
> >  	struct zynqmp_dpsub *dpsub = platform_get_drvdata(pdev);
> >  	struct zynqmp_dp *dp = dpsub->dp;
> >
> > +	zynqmp_dp_debugfs_exit(dp);
> >  	zynqmp_dp_write(dp->iomem, ZYNQMP_DP_TX_ENABLE, 0);
> >  	drm_dp_aux_unregister(&dp->aux);
> >  	zynqmp_dp_exit_phy(dp);
> > --
> > 2.7.4
> >
> > _______________________________________________
> > dri-devel mailing list
> > dri-devel at lists.freedesktop.org
> > https://lists.freedesktop.org/mailman/listinfo/dri-devel
> 
> --
> Daniel Vetter
> Software Engineer, Intel Corporation
> http://blog.ffwll.ch


More information about the dri-devel mailing list