Re: [PATCH v5 00/10] drm: zynqmp_dp: IRQ cleanups and debugfs support

2024-06-17 Thread Sean Anderson
On 6/17/24 03:47, Tomi Valkeinen wrote:
> Hi Sean,
> 
> On 03/05/2024 22:29, Sean Anderson wrote:
>> This series cleans up the zyqnmp_dp IRQ and locking situation. Once
>> that's done, it adds debugfs support. The intent is to enable compliance
>> testing or to help debug signal-integrity issues.
>>
>> Last time I discussed converting the HPD work(s) to a threaded IRQ. I
>> did not end up doing that for this series since the steps would be
>>
>> - Add locking
>> - Move link retraining to a work function
>> - Harden the IRQ
>> - Merge the works into a threaded IRQ (omitted)
>>
>> Which with the exception of the final step is the same as leaving those
>> works as-is. Conversion to a threaded IRQ can be done as a follow-up.
> 
> I tested this, and the "drm: zynqmp_dp: Convert to a hard IRQ" causes a hang 
> for me when unloading the drivers. Unfortunately I'm not in the condition to 
> debug it at the moment.
> 
> I have picked the first three patches into drm-misc-next, though, to decrease 
> the number of patches in the series a bit. They looked independent and safe 
> enough to apply.

Are you running into [1]?

--Sean

[1] 
https://lore.kernel.org/dri-devel/4d8f4c9b-2efb-4774-9a37-2f257f79b...@linux.dev/



Re: [PATCH] drm: zynqmp_dpsub: Fix an error handling path in zynqmp_dpsub_probe()

2024-06-13 Thread Sean Anderson
On 5/20/24 11:05, Sean Anderson wrote:
> On 5/20/24 05:40, Christophe JAILLET wrote:
>> If zynqmp_dpsub_drm_init() fails, we must undo the previous
>> drm_bridge_add() call.
>> 
>> Fixes: be3f3042391d ("drm: zynqmp_dpsub: Always register bridge")
>> Signed-off-by: Christophe JAILLET 
>> ---
>> Compile tested only
>> ---
>>  drivers/gpu/drm/xlnx/zynqmp_dpsub.c | 1 +
>>  1 file changed, 1 insertion(+)
>> 
>> diff --git a/drivers/gpu/drm/xlnx/zynqmp_dpsub.c 
>> b/drivers/gpu/drm/xlnx/zynqmp_dpsub.c
>> index face8d6b2a6f..f5781939de9c 100644
>> --- a/drivers/gpu/drm/xlnx/zynqmp_dpsub.c
>> +++ b/drivers/gpu/drm/xlnx/zynqmp_dpsub.c
>> @@ -269,6 +269,7 @@ static int zynqmp_dpsub_probe(struct platform_device 
>> *pdev)
>>  return 0;
>>  
>>  err_disp:
>> +drm_bridge_remove(dpsub->bridge);
>>  zynqmp_disp_remove(dpsub);
>>  err_dp:
>>  zynqmp_dp_remove(dpsub);
> 
> Reviewed-by: Sean Anderson 

Will this be applied soon? The patch it fixes has made its way into the stable 
tree already.

--Sean



Re: [PATCH v5 00/10] drm: zynqmp_dp: IRQ cleanups and debugfs support

2024-05-31 Thread Sean Anderson
On 5/3/24 15:29, Sean Anderson wrote:
> This series cleans up the zyqnmp_dp IRQ and locking situation. Once
> that's done, it adds debugfs support. The intent is to enable compliance
> testing or to help debug signal-integrity issues.
> 
> Last time I discussed converting the HPD work(s) to a threaded IRQ. I
> did not end up doing that for this series since the steps would be
> 
> - Add locking
> - Move link retraining to a work function
> - Harden the IRQ
> - Merge the works into a threaded IRQ (omitted)
> 
> Which with the exception of the final step is the same as leaving those
> works as-is. Conversion to a threaded IRQ can be done as a follow-up.
> 
> Changes in v5:
> - Fix AUX bus not getting unregistered
> - Rebase onto drm-misc/drm-misc-next
> 
> Changes in v4:
> - Rebase onto drm/drm-next
> 
> Changes in v3:
> - Don't delay work
> - Convert to a hard IRQ
> - Use AUX IRQs instead of polling
> - Take dp->lock in zynqmp_dp_hpd_work_func
> 
> Changes in v2:
> - Rearrange zynqmp_dp for better padding
> - Split off the HPD IRQ work into another commit
> - Expand the commit message
> - Document hpd_irq_work
> - Document debugfs files
> - Add ignore_aux_errors and ignore_hpd debugfs files to replace earlier
>   implicit functionality
> - Attempt to fix unreproducable, spurious build warning
> - Drop "Optionally ignore DPCD errors" in favor of a debugfs file
>   directly affecting zynqmp_dp_aux_transfer.
> 
> Sean Anderson (10):
>   drm: zynqmp_kms: Fix AUX bus not getting unregistered
>   drm: zynqmp_dp: Rearrange zynqmp_dp for better padding
>   drm: zynqmp_dp: Don't delay work
>   drm: zynqmp_dp: Add locking
>   drm: zynqmp_dp: Don't retrain the link in our IRQ
>   drm: zynqmp_dp: Convert to a hard IRQ
>   drm: zynqmp_dp: Use AUX IRQs instead of polling
>   drm: zynqmp_dp: Split off several helper functions
>   drm: zynqmp_dp: Take dp->lock in zynqmp_dp_hpd_work_func
>   drm: zynqmp_dp: Add debugfs interface for compliance testing
> 
>  Documentation/gpu/drivers.rst |   1 +
>  Documentation/gpu/zynqmp.rst  | 149 +
>  MAINTAINERS   |   1 +
>  drivers/gpu/drm/xlnx/zynqmp_dp.c  | 883 +++---
>  drivers/gpu/drm/xlnx/zynqmp_kms.c |  12 +-
>  5 files changed, 977 insertions(+), 69 deletions(-)
>  create mode 100644 Documentation/gpu/zynqmp.rst
> 

ping

--Sean


Re: [PATCH] drm: zynqmp_dpsub: Fix an error handling path in zynqmp_dpsub_probe()

2024-05-20 Thread Sean Anderson
On 5/20/24 05:40, Christophe JAILLET wrote:
> If zynqmp_dpsub_drm_init() fails, we must undo the previous
> drm_bridge_add() call.
> 
> Fixes: be3f3042391d ("drm: zynqmp_dpsub: Always register bridge")
> Signed-off-by: Christophe JAILLET 
> ---
> Compile tested only
> ---
>  drivers/gpu/drm/xlnx/zynqmp_dpsub.c | 1 +
>  1 file changed, 1 insertion(+)
> 
> diff --git a/drivers/gpu/drm/xlnx/zynqmp_dpsub.c 
> b/drivers/gpu/drm/xlnx/zynqmp_dpsub.c
> index face8d6b2a6f..f5781939de9c 100644
> --- a/drivers/gpu/drm/xlnx/zynqmp_dpsub.c
> +++ b/drivers/gpu/drm/xlnx/zynqmp_dpsub.c
> @@ -269,6 +269,7 @@ static int zynqmp_dpsub_probe(struct platform_device 
> *pdev)
>   return 0;
>  
>  err_disp:
> + drm_bridge_remove(dpsub->bridge);
>   zynqmp_disp_remove(dpsub);
>  err_dp:
>   zynqmp_dp_remove(dpsub);

Reviewed-by: Sean Anderson 


Re: [BUG] drm: zynqmp_dp: Lockup in zynqmp_dp_bridge_detect when device is unbound

2024-05-06 Thread Sean Anderson
On 5/6/24 03:35, Laurent Pinchart wrote:
> On Mon, May 06, 2024 at 09:29:36AM +0200, Maxime Ripard wrote:
>> Hi Laurent, Sean,
>> 
>> On Sat, May 04, 2024 at 03:21:18PM GMT, Laurent Pinchart wrote:
>> > On Fri, May 03, 2024 at 05:54:32PM -0400, Sean Anderson wrote:
>> > > I have discovered a bug in the displayport driver on drm-misc-next. To
>> > > trigger it, run
>> > > 
>> > > echo fd4a.display > /sys/bus/platform/drivers/zynqmp-dpsub/unbind
>> > > 
>> > > The system will become unresponsive and (after a bit) splat with a hard
>> > > LOCKUP. One core will be unresponsive at the first zynqmp_dp_read in
>> > > zynqmp_dp_bridge_detect.
>> > > 
>> > > I believe the issue is due the registers being unmapped and the block
>> > > put into reset in zynqmp_dp_remove instead of zynqmp_dpsub_release.
>> > 
>> > That is on purpose. Drivers are not allowed to access the device at all
>> > after .remove() returns.
>> 
>> It's not "on purpose" no. Drivers indeed are not allowed to access the
>> device after remove, but the kernel shouldn't crash. This is exactly
>> why we have drm_dev_enter / drm_dev_exit.
> 
> I didn't mean the crash was on purpose :-) It's the registers being
> unmapped that is, as nothing should touch those registers after
> .remove() returns.

OK, so then we need to have some kind of flag in the driver or in the drm
subsystem so we know not to access those registers.

--Sean



Re: [BUG] drm: zynqmp_dp: Lockup in zynqmp_dp_bridge_detect when device is unbound

2024-05-06 Thread Sean Anderson
On 5/6/24 07:16, Tomi Valkeinen wrote:
> Hi,
> 
> On 04/05/2024 00:54, Sean Anderson wrote:
>> Hi,
>>
>> I have discovered a bug in the displayport driver on drm-misc-next. To
>> trigger it, run
>>
>> echo fd4a.display > /sys/bus/platform/drivers/zynqmp-dpsub/unbind
>>
>> The system will become unresponsive and (after a bit) splat with a hard
>> LOCKUP. One core will be unresponsive at the first zynqmp_dp_read in
>> zynqmp_dp_bridge_detect.
>>
>> I believe the issue is due the registers being unmapped and the block
>> put into reset in zynqmp_dp_remove instead of zynqmp_dpsub_release. This
>> could be resolved by deferring things until zynqmp_dpsub_release
>> (requiring us to skip devm_*), or by adding a flag to struct zynqmp_dp
>> and checking it before each callback. A subsystem-level implementation
>> might be better for the latter.
>>
>> For a better traceback, try applying the below patch and running the
>> following commands before triggering the lockup:
>>
>> echo 4 > /sys/module/drm/parameters/debug
>> echo 8 > /proc/sys/kernel/printk
> 
> I wasn't able to reproduce. Where does the detect call come in your case? 
> Looking at the code, afaics, it shouldn't happen.

# echo fd4a.display > /sys/bus/platform/drivers/zynqmp-dpsub/unbind
[234105.917005] Console: switching to colour dummy device 80x25
[234105.962474] zynqmp-dpsub fd4a.display: [drm:drm_client_release] fbdev
[234105.970397] zynqmp-dpsub fd4a.display: [drm:drm_sysfs_connector_remove] 
[CONNECTOR:41:DP-1] removing connector from sysfs
[234105.991669] zynqmp-dpsub fd4a.display: 
[drm:drm_helper_probe_single_connector_modes] [CONNECTOR:41:DP-1]
[234106.001833] [ cut here ]
[234106.006570] WARNING: CPU: 2 PID: 527 at 
drivers/gpu/drm/xlnx/zynqmp_dp.c:1537 zynqmp_dp_bridge_detect 
(drivers/gpu/drm/xlnx/zynqmp_dp.c:1537 (discriminator 1)) 
[234106.016960] Modules linked in:
[234106.021306] CPU: 2 PID: 527 Comm: Xorg Not tainted 6.9.0-rc6+ #34
[234106.027858] Hardware name: xlnx,zynqmp (DT)
[234106.032146] pstate: a005 (NzCv daif -PAN -UAO -TCO -DIT -SSBS BTYPE=--)
[234106.039219] pc : zynqmp_dp_bridge_detect 
(drivers/gpu/drm/xlnx/zynqmp_dp.c:1537 (discriminator 1)) 
[234106.044297] lr : drm_bridge_connector_detect 
(drivers/gpu/drm/drm_bridge_connector.c:176) 
[234106.049548] sp : ffc0895bb980
[234106.052968] x29: ffc0895bb980 x28: ff8805fe1000 x27: 
ffc0818ff000
[234106.060251] x26: 0001 x25: 0050 x24: 
1000
[234106.067534] x23: 1000 x22: ff8805fe0030 x21: 
ff8805fe1000
[234106.074816] x20: ffc0895bbae8 x19: ff8805fe1000 x18: 
5550
[234106.082099] x17: 6e6e6f635f656c67 x16: 6e69735f65626f72 x15: 
705f7265706c6568
[234106.089382] x14: 5f6d72643a6d7264 x13:  x12: 

[234106.096664] x11: 02c5 x10: 02c5 x9 : 
000402c5
[234106.103947] x8 : 95d72cee x7 : f1c4be6a x6 : 
ffc082707b78
[234106.111229] x5 :  x4 : ff8801f2 x3 : 

[234106.118512] x2 : ffc080a5a2bc x1 : 0123456789abcdef x0 : 
deadbeefdeadbeef
[234106.125795] Call trace:
[234106.128347] zynqmp_dp_bridge_detect (drivers/gpu/drm/xlnx/zynqmp_dp.c:1537 
(discriminator 1)) 
[234106.133077] drm_bridge_connector_detect 
(drivers/gpu/drm/drm_bridge_connector.c:176) 
[234106.137981] drm_helper_probe_detect 
(drivers/gpu/drm/drm_probe_helper.c:407) 
[234106.142538] drm_helper_probe_single_connector_modes 
(drivers/gpu/drm/drm_probe_helper.c:596) 
[234106.148658] drm_mode_getconnector (drivers/gpu/drm/drm_connector.c:2947) 
[234106.153215] drm_ioctl_kernel (drivers/gpu/drm/drm_ioctl.c:744 
(discriminator 1)) 
[234106.157251] drm_ioctl (drivers/gpu/drm/drm_ioctl.c:841) 
[234106.160767] __arm64_sys_ioctl (fs/ioctl.c:52 fs/ioctl.c:904 fs/ioctl.c:890 
fs/ioctl.c:890) 
[234106.164803] invoke_syscall (arch/arm64/include/asm/current.h:19 
arch/arm64/kernel/syscall.c:53) 
[234106.168665] el0_svc_common.constprop.0 (arch/arm64/kernel/syscall.c:140) 
[234106.173483] do_el0_svc (arch/arm64/kernel/syscall.c:153) 
[234106.176911] el0_svc (arch/arm64/include/asm/irqflags.h:83 
arch/arm64/include/asm/irqflags.h:124 arch/arm64/include/asm/irqflags.h:137 
arch/arm64/kernel/entry-common.c:165 arch/arm64/kernel/entry-common.c:178 
arch/arm64/kernel/entry-common.c:713) 
[234106.180166] el0t_64_sync_handler (arch/arm64/kernel/entry-common.c:731) 
[234106.184637] el0t_64_sync (arch/arm64/kernel/entry.S:598) 
[234106.188413] irq event stamp: 348036
[234106.192006] hardirqs last enabled at (348035): console_unlock 
(arch/arm64/include/asm/irqflags.h:83 arch/arm64/include/asm/irqflags.h:124 
arch/arm64/include/asm/irqflags.h:137 kernel/printk/printk.c:341 
kernel/printk/printk.c:2731 kernel/printk/pr

[BUG] drm: zynqmp_dp: Lockup in zynqmp_dp_bridge_detect when device is unbound

2024-05-03 Thread Sean Anderson
Hi,

I have discovered a bug in the displayport driver on drm-misc-next. To
trigger it, run

echo fd4a.display > /sys/bus/platform/drivers/zynqmp-dpsub/unbind

The system will become unresponsive and (after a bit) splat with a hard
LOCKUP. One core will be unresponsive at the first zynqmp_dp_read in
zynqmp_dp_bridge_detect.

I believe the issue is due the registers being unmapped and the block
put into reset in zynqmp_dp_remove instead of zynqmp_dpsub_release. This
could be resolved by deferring things until zynqmp_dpsub_release
(requiring us to skip devm_*), or by adding a flag to struct zynqmp_dp
and checking it before each callback. A subsystem-level implementation
might be better for the latter.

For a better traceback, try applying the below patch and running the
following commands before triggering the lockup:

echo 4 > /sys/module/drm/parameters/debug
echo 8 > /proc/sys/kernel/printk

diff --git a/drivers/gpu/drm/xlnx/zynqmp_dp.c b/drivers/gpu/drm/xlnx/zynqmp_dp.c
index 9df068a413f3..17b477b14ab5 100644
--- a/drivers/gpu/drm/xlnx/zynqmp_dp.c
+++ b/drivers/gpu/drm/xlnx/zynqmp_dp.c
@@ -296,6 +296,7 @@ struct zynqmp_dp_config {
  * @train_set: set of training data
  */
 struct zynqmp_dp {
+   unsigned long long magic;
struct device *dev;
struct zynqmp_dpsub *dpsub;
void __iomem *iomem;
@@ -1533,6 +1534,8 @@ static enum drm_connector_status 
zynqmp_dp_bridge_detect(struct drm_bridge *brid
u32 state, i;
int ret;
 
+   WARN_ON(dp->magic != 0x0123456789abcdefULL);
+
/*
 * This is from heuristic. It takes some delay (ex, 100 ~ 500 msec) to
 * get the HPD signal with some monitors.
@@ -1723,6 +1726,7 @@ int zynqmp_dp_probe(struct zynqmp_dpsub *dpsub)
if (!dp)
return -ENOMEM;
 
+   dp->magic = 0x0123456789abcdefULL;
dp->dev = >dev;
dp->dpsub = dpsub;
dp->status = connector_status_disconnected;
@@ -1839,4 +1843,5 @@ void zynqmp_dp_remove(struct zynqmp_dpsub *dpsub)
 
zynqmp_dp_phy_exit(dp);
zynqmp_dp_reset(dp, true);
+   dp->magic = 0xdeadbeefdeadbeefULL;
 }


[PATCH v5 10/10] drm: zynqmp_dp: Add debugfs interface for compliance testing

2024-05-03 Thread Sean Anderson
Add a debugfs interface for exercising the various test modes supported
by the DisplayPort controller. This allows performing compliance
testing, or performing signal integrity measurements on a failing link.
At the moment, we do not support sink-driven link quality testing,
although such support would be fairly easy to add.

Additionally, add some debugfs files for ignoring AUX errors and HPD
events, as this can allow testing with equipment that cannot emulate a
DPRX.

Signed-off-by: Sean Anderson 
---

(no changes since v2)

Changes in v2:
- Document debugfs files
- Add ignore_aux_errors and ignore_hpd debugfs files to replace earlier
  implicit functionality
- Attempt to fix unreproducable, spurious build warning

 Documentation/gpu/drivers.rst|   1 +
 Documentation/gpu/zynqmp.rst | 149 +++
 MAINTAINERS  |   1 +
 drivers/gpu/drm/xlnx/zynqmp_dp.c | 682 ++-
 4 files changed, 830 insertions(+), 3 deletions(-)
 create mode 100644 Documentation/gpu/zynqmp.rst

diff --git a/Documentation/gpu/drivers.rst b/Documentation/gpu/drivers.rst
index b899cbc5c2b4..187201aedbe5 100644
--- a/Documentation/gpu/drivers.rst
+++ b/Documentation/gpu/drivers.rst
@@ -22,6 +22,7 @@ GPU Driver Documentation
afbc
komeda-kms
panfrost
+   zynqmp
 
 .. only::  subproject and html
 
diff --git a/Documentation/gpu/zynqmp.rst b/Documentation/gpu/zynqmp.rst
new file mode 100644
index ..f57bfa0ad6ec
--- /dev/null
+++ b/Documentation/gpu/zynqmp.rst
@@ -0,0 +1,149 @@
+.. SPDX-License-Identifier: GPL-2.0+
+
+===
+Xilinx ZynqMP Ultrascale+ DisplayPort Subsystem
+===
+
+This subsystem handles DisplayPort video and audio output on the ZynqMP. It
+supports in-memory framebuffers with the DisplayPort DMA controller
+(xilinx-dpdma), as well as "live" video and audio from the programmable logic
+(PL). This subsystem can perform several transformations, including color space
+conversion, alpha blending, and audio mixing, although not all features are
+currently supported.
+
+debugfs
+---
+
+To support debugging and compliance testing, several test modes can be enabled
+though debugfs. The following files in /sys/kernel/debug/dri/X/DP-1/test/
+control the DisplayPort test modes:
+
+active:
+Writing a 1 to this file will activate test mode, and writing a 0 will
+deactivate test mode. Writing a 1 or 0 when the test mode is already
+active/inactive will re-activate/re-deactivate test mode. When test
+mode is inactive, changes made to other files will have no (immediate)
+effect, although the settings will be saved for when test mode is
+activated. When test mode is active, changes made to other files will
+apply immediately.
+
+custom:
+Custom test pattern value
+
+downspread:
+Enable/disable clock downspreading (spread-spectrum clocking) by
+writing 1/0
+
+enhanced:
+Enable/disable enhanced framing
+
+ignore_aux_errors:
+Ignore AUX errors when set to 1. Writes to this file take effect
+immediately (regardless of whether test mode is active) and affect all
+AUX transfers.
+
+ignore_hpd:
+Ignore hotplug events (such as cable removals or monitor link
+retraining requests) when set to 1. Writes to this file take effect
+immediately (regardless of whether test mode is active).
+
+laneX_preemphasis:
+Preemphasis from 0 (lowest) to 2 (highest) for lane X
+
+laneX_swing:
+Voltage swing from 0 (lowest) to 3 (highest) for lane X
+
+lanes:
+Number of lanes to use (1, 2, or 4)
+
+pattern:
+Test pattern. May be one of:
+
+video
+Use regular video input
+
+symbol-error
+Symbol error measurement pattern
+
+prbs7
+Output of the PRBS7 (x^7 + x^6 + 1) polynomial
+
+80bit-custom
+A custom 80-bit pattern
+
+cp2520
+HBR2 compliance eye pattern
+
+tps1
+Link training symbol pattern TPS1 (/D10.2/)
+
+tps2
+Link training symbol pattern TPS2
+
+tps3
+Link training symbol pattern TPS3 (for HBR2)
+
+rate:
+Rate in hertz. One of
+
+* 54 (HBR2)
+* 27 (HBR)
+* 162000 (RBR)
+
+You can dump the displayport test settings with the following command::
+
+for prop in /sys/kernel/debug/dri/1/DP-1/test/*; do
+printf '%-17s ' ${prop##*/}
+if [ ${prop##*/} = custom ]; then
+hexdump -C $prop | head -1
+else
+cat $prop
+fi
+  

[PATCH v5 09/10] drm: zynqmp_dp: Take dp->lock in zynqmp_dp_hpd_work_func

2024-05-03 Thread Sean Anderson
Add a non-locking version of zynqmp_dp_bridge_detect and use it in
zynqmp_dp_hpd_work_func so we can take the lock explicitly. This will
make it easier to check for hpd_ignore when we add debugfs support.

Signed-off-by: Sean Anderson 
---

(no changes since v3)

Changes in v3:
- New

 drivers/gpu/drm/xlnx/zynqmp_dp.c | 24 ++--
 1 file changed, 18 insertions(+), 6 deletions(-)

diff --git a/drivers/gpu/drm/xlnx/zynqmp_dp.c b/drivers/gpu/drm/xlnx/zynqmp_dp.c
index 91767ddbe1ce..6f3792dcac28 100644
--- a/drivers/gpu/drm/xlnx/zynqmp_dp.c
+++ b/drivers/gpu/drm/xlnx/zynqmp_dp.c
@@ -1567,14 +1567,13 @@ static int zynqmp_dp_bridge_atomic_check(struct 
drm_bridge *bridge,
return 0;
 }
 
-static enum drm_connector_status zynqmp_dp_bridge_detect(struct drm_bridge 
*bridge)
+static enum drm_connector_status __zynqmp_dp_bridge_detect(struct zynqmp_dp 
*dp)
 {
-   struct zynqmp_dp *dp = bridge_to_dp(bridge);
struct zynqmp_dp_link_config *link_config = >link_config;
u32 state, i;
int ret;
 
-   mutex_lock(>lock);
+   lockdep_assert_held(>lock);
 
/*
 * This is from heuristic. It takes some delay (ex, 100 ~ 500 msec) to
@@ -1603,16 +1602,26 @@ static enum drm_connector_status 
zynqmp_dp_bridge_detect(struct drm_bridge *brid
   dp->num_lanes);
 
dp->status = connector_status_connected;
-   mutex_unlock(>lock);
return connector_status_connected;
}
 
 disconnected:
dp->status = connector_status_disconnected;
-   mutex_unlock(>lock);
return connector_status_disconnected;
 }
 
+static enum drm_connector_status zynqmp_dp_bridge_detect(struct drm_bridge 
*bridge)
+{
+   struct zynqmp_dp *dp = bridge_to_dp(bridge);
+   enum drm_connector_status ret;
+
+   mutex_lock(>lock);
+   ret = __zynqmp_dp_bridge_detect(dp);
+   mutex_unlock(>lock);
+
+   return ret;
+}
+
 static const struct drm_edid *zynqmp_dp_bridge_edid_read(struct drm_bridge 
*bridge,
 struct drm_connector 
*connector)
 {
@@ -1696,7 +1705,10 @@ static void zynqmp_dp_hpd_work_func(struct work_struct 
*work)
struct zynqmp_dp *dp = container_of(work, struct zynqmp_dp, hpd_work);
enum drm_connector_status status;
 
-   status = zynqmp_dp_bridge_detect(>bridge);
+   mutex_lock(>lock);
+   status = __zynqmp_dp_bridge_detect(dp);
+   mutex_unlock(>lock);
+
drm_bridge_hpd_notify(>bridge, status);
 }
 
-- 
2.35.1.1320.gc452695387.dirty



[PATCH v5 08/10] drm: zynqmp_dp: Split off several helper functions

2024-05-03 Thread Sean Anderson
In preparation for supporting compliance testing, split off several
helper functions. No functional change intended.

Signed-off-by: Sean Anderson 
Reviewed-by: Laurent Pinchart 
Reviewed-by: Tomi Valkeinen 
---

(no changes since v1)

 drivers/gpu/drm/xlnx/zynqmp_dp.c | 49 ++--
 1 file changed, 34 insertions(+), 15 deletions(-)

diff --git a/drivers/gpu/drm/xlnx/zynqmp_dp.c b/drivers/gpu/drm/xlnx/zynqmp_dp.c
index 38610a5b932a..91767ddbe1ce 100644
--- a/drivers/gpu/drm/xlnx/zynqmp_dp.c
+++ b/drivers/gpu/drm/xlnx/zynqmp_dp.c
@@ -636,6 +636,7 @@ static void zynqmp_dp_adjust_train(struct zynqmp_dp *dp,
 /**
  * zynqmp_dp_update_vs_emph - Update the training values
  * @dp: DisplayPort IP core structure
+ * @train_set: A set of training values
  *
  * Update the training values based on the request from sink. The mapped values
  * are predefined, and values(vs, pe, pc) are from the device manual.
@@ -643,12 +644,12 @@ static void zynqmp_dp_adjust_train(struct zynqmp_dp *dp,
  * Return: 0 if vs and emph are updated successfully, or the error code 
returned
  * by drm_dp_dpcd_write().
  */
-static int zynqmp_dp_update_vs_emph(struct zynqmp_dp *dp)
+static int zynqmp_dp_update_vs_emph(struct zynqmp_dp *dp, u8 *train_set)
 {
unsigned int i;
int ret;
 
-   ret = drm_dp_dpcd_write(>aux, DP_TRAINING_LANE0_SET, dp->train_set,
+   ret = drm_dp_dpcd_write(>aux, DP_TRAINING_LANE0_SET, train_set,
dp->mode.lane_cnt);
if (ret < 0)
return ret;
@@ -656,7 +657,7 @@ static int zynqmp_dp_update_vs_emph(struct zynqmp_dp *dp)
for (i = 0; i < dp->mode.lane_cnt; i++) {
u32 reg = ZYNQMP_DP_SUB_TX_PHY_PRECURSOR_LANE_0 + i * 4;
union phy_configure_opts opts = { 0 };
-   u8 train = dp->train_set[i];
+   u8 train = train_set[i];
 
opts.dp.voltage[0] = (train & DP_TRAIN_VOLTAGE_SWING_MASK)
   >> DP_TRAIN_VOLTAGE_SWING_SHIFT;
@@ -700,7 +701,7 @@ static int zynqmp_dp_link_train_cr(struct zynqmp_dp *dp)
 * So, This loop should exit before 512 iterations
 */
for (max_tries = 0; max_tries < 512; max_tries++) {
-   ret = zynqmp_dp_update_vs_emph(dp);
+   ret = zynqmp_dp_update_vs_emph(dp, dp->train_set);
if (ret)
return ret;
 
@@ -765,7 +766,7 @@ static int zynqmp_dp_link_train_ce(struct zynqmp_dp *dp)
return ret;
 
for (tries = 0; tries < DP_MAX_TRAINING_TRIES; tries++) {
-   ret = zynqmp_dp_update_vs_emph(dp);
+   ret = zynqmp_dp_update_vs_emph(dp, dp->train_set);
if (ret)
return ret;
 
@@ -788,28 +789,29 @@ static int zynqmp_dp_link_train_ce(struct zynqmp_dp *dp)
 }
 
 /**
- * zynqmp_dp_train - Train the link
+ * zynqmp_dp_setup() - Set up major link parameters
  * @dp: DisplayPort IP core structure
+ * @bw_code: The link bandwidth as a multiple of 270 MHz
+ * @lane_cnt: The number of lanes to use
+ * @enhanced: Use enhanced framing
+ * @downspread: Enable spread-spectrum clocking
  *
- * Return: 0 if all trains are done successfully, or corresponding error code.
+ * Return: 0 on success, or -errno on failure
  */
-static int zynqmp_dp_train(struct zynqmp_dp *dp)
+static int zynqmp_dp_setup(struct zynqmp_dp *dp, u8 bw_code, u8 lane_cnt,
+  bool enhanced, bool downspread)
 {
u32 reg;
-   u8 bw_code = dp->mode.bw_code;
-   u8 lane_cnt = dp->mode.lane_cnt;
u8 aux_lane_cnt = lane_cnt;
-   bool enhanced;
int ret;
 
zynqmp_dp_write(dp, ZYNQMP_DP_LANE_COUNT_SET, lane_cnt);
-   enhanced = drm_dp_enhanced_frame_cap(dp->dpcd);
if (enhanced) {
zynqmp_dp_write(dp, ZYNQMP_DP_ENHANCED_FRAME_EN, 1);
aux_lane_cnt |= DP_LANE_COUNT_ENHANCED_FRAME_EN;
}
 
-   if (dp->dpcd[3] & 0x1) {
+   if (downspread) {
zynqmp_dp_write(dp, ZYNQMP_DP_DOWNSPREAD_CTL, 1);
drm_dp_dpcd_writeb(>aux, DP_DOWNSPREAD_CTRL,
   DP_SPREAD_AMP_0_5);
@@ -852,8 +854,25 @@ static int zynqmp_dp_train(struct zynqmp_dp *dp)
}
 
zynqmp_dp_write(dp, ZYNQMP_DP_PHY_CLOCK_SELECT, reg);
-   ret = zynqmp_dp_phy_ready(dp);
-   if (ret < 0)
+   return zynqmp_dp_phy_ready(dp);
+}
+
+
+/**
+ * zynqmp_dp_train - Train the link
+ * @dp: DisplayPort IP core structure
+ *
+ * Return: 0 if all trains are done successfully, or corresponding error code.
+ */
+static int zynqmp_dp_train(struct zynqmp_dp *dp)
+{
+   int ret;
+
+   ret = zynqmp_dp_setup(dp, dp->mode.bw_code, dp->mode.lane_cnt,
+ drm_dp_enhanced_frame_cap(dp->dpcd),
+ dp->dpcd[DP_MAX_DOW

[PATCH v5 07/10] drm: zynqmp_dp: Use AUX IRQs instead of polling

2024-05-03 Thread Sean Anderson
Instead of polling the status register for the AUX status, just enable
the IRQs and signal a completion.

Signed-off-by: Sean Anderson 
---

(no changes since v3)

Changes in v3:
- New

 drivers/gpu/drm/xlnx/zynqmp_dp.c | 35 +++-
 1 file changed, 25 insertions(+), 10 deletions(-)

diff --git a/drivers/gpu/drm/xlnx/zynqmp_dp.c b/drivers/gpu/drm/xlnx/zynqmp_dp.c
index c9cdfb56f23e..38610a5b932a 100644
--- a/drivers/gpu/drm/xlnx/zynqmp_dp.c
+++ b/drivers/gpu/drm/xlnx/zynqmp_dp.c
@@ -286,6 +286,7 @@ struct zynqmp_dp_config {
  * @next_bridge: The downstream bridge
  * @config: IP core configuration from DTS
  * @aux: aux channel
+ * @aux_done: Completed when we get an AUX reply or timeout
  * @phy: PHY handles for DP lanes
  * @num_lanes: number of enabled phy lanes
  * @hpd_work: hot plug detection worker
@@ -306,6 +307,7 @@ struct zynqmp_dp {
struct drm_bridge bridge;
struct work_struct hpd_work;
struct work_struct hpd_irq_work;
+   struct completion aux_done;
struct mutex lock;
 
struct drm_bridge *next_bridge;
@@ -942,12 +944,15 @@ static int zynqmp_dp_aux_cmd_submit(struct zynqmp_dp *dp, 
u32 cmd, u16 addr,
u8 *buf, u8 bytes, u8 *reply)
 {
bool is_read = (cmd & AUX_READ_BIT) ? true : false;
+   unsigned long time_left;
u32 reg, i;
 
reg = zynqmp_dp_read(dp, ZYNQMP_DP_INTERRUPT_SIGNAL_STATE);
if (reg & ZYNQMP_DP_INTERRUPT_SIGNAL_STATE_REQUEST)
return -EBUSY;
 
+   reinit_completion(>aux_done);
+
zynqmp_dp_write(dp, ZYNQMP_DP_AUX_ADDRESS, addr);
if (!is_read)
for (i = 0; i < bytes; i++)
@@ -962,17 +967,14 @@ static int zynqmp_dp_aux_cmd_submit(struct zynqmp_dp *dp, 
u32 cmd, u16 addr,
zynqmp_dp_write(dp, ZYNQMP_DP_AUX_COMMAND, reg);
 
/* Wait for reply to be delivered upto 2ms */
-   for (i = 0; ; i++) {
-   reg = zynqmp_dp_read(dp, ZYNQMP_DP_INTERRUPT_SIGNAL_STATE);
-   if (reg & ZYNQMP_DP_INTERRUPT_SIGNAL_STATE_REPLY)
-   break;
+   time_left = wait_for_completion_timeout(>aux_done,
+   msecs_to_jiffies(2));
+   if (!time_left)
+   return -ETIMEDOUT;
 
-   if (reg & ZYNQMP_DP_INTERRUPT_SIGNAL_STATE_REPLY_TIMEOUT ||
-   i == 2)
-   return -ETIMEDOUT;
-
-   usleep_range(1000, 1100);
-   }
+   reg = zynqmp_dp_read(dp, ZYNQMP_DP_INTERRUPT_SIGNAL_STATE);
+   if (reg & ZYNQMP_DP_INTERRUPT_SIGNAL_STATE_REPLY_TIMEOUT)
+   return -ETIMEDOUT;
 
reg = zynqmp_dp_read(dp, ZYNQMP_DP_AUX_REPLY_CODE);
if (reply)
@@ -1056,6 +1058,9 @@ static int zynqmp_dp_aux_init(struct zynqmp_dp *dp)
(w << ZYNQMP_DP_AUX_CLK_DIVIDER_AUX_FILTER_SHIFT) |
(rate / (1000 * 1000)));
 
+   zynqmp_dp_write(dp, ZYNQMP_DP_INT_EN, ZYNQMP_DP_INT_REPLY_RECEIVED |
+ ZYNQMP_DP_INT_REPLY_TIMEOUT);
+
dp->aux.name = "ZynqMP DP AUX";
dp->aux.dev = dp->dev;
dp->aux.drm_dev = dp->bridge.dev;
@@ -1073,6 +1078,9 @@ static int zynqmp_dp_aux_init(struct zynqmp_dp *dp)
 static void zynqmp_dp_aux_cleanup(struct zynqmp_dp *dp)
 {
drm_dp_aux_unregister(>aux);
+
+   zynqmp_dp_write(dp, ZYNQMP_DP_INT_DS, ZYNQMP_DP_INT_REPLY_RECEIVED |
+ ZYNQMP_DP_INT_REPLY_TIMEOUT);
 }
 
 /* 
-
@@ -1730,6 +1738,12 @@ static irqreturn_t zynqmp_dp_irq_handler(int irq, void 
*data)
if (status & ZYNQMP_DP_INT_HPD_IRQ)
schedule_work(>hpd_irq_work);
 
+   if (status & ZYNQMP_DP_INTERRUPT_SIGNAL_STATE_REPLY)
+   complete(>aux_done);
+
+   if (status & ZYNQMP_DP_INTERRUPT_SIGNAL_STATE_REPLY_TIMEOUT)
+   complete(>aux_done);
+
return IRQ_HANDLED;
 }
 
@@ -1753,6 +1767,7 @@ int zynqmp_dp_probe(struct zynqmp_dpsub *dpsub)
dp->dpsub = dpsub;
dp->status = connector_status_disconnected;
mutex_init(>lock);
+   init_completion(>aux_done);
 
INIT_WORK(>hpd_work, zynqmp_dp_hpd_work_func);
INIT_WORK(>hpd_irq_work, zynqmp_dp_hpd_irq_work_func);
-- 
2.35.1.1320.gc452695387.dirty



[PATCH v5 06/10] drm: zynqmp_dp: Convert to a hard IRQ

2024-05-03 Thread Sean Anderson
Now that all of the sleeping work is done outside of the IRQ, we can
convert it to a hard IRQ.

Signed-off-by: Sean Anderson 
---

(no changes since v3)

Changes in v3:
- New

 drivers/gpu/drm/xlnx/zynqmp_dp.c | 5 ++---
 1 file changed, 2 insertions(+), 3 deletions(-)

diff --git a/drivers/gpu/drm/xlnx/zynqmp_dp.c b/drivers/gpu/drm/xlnx/zynqmp_dp.c
index cec5711c7026..c9cdfb56f23e 100644
--- a/drivers/gpu/drm/xlnx/zynqmp_dp.c
+++ b/drivers/gpu/drm/xlnx/zynqmp_dp.c
@@ -1831,9 +1831,8 @@ int zynqmp_dp_probe(struct zynqmp_dpsub *dpsub)
 * Now that the hardware is initialized and won't generate spurious
 * interrupts, request the IRQ.
 */
-   ret = devm_request_threaded_irq(dp->dev, dp->irq, NULL,
-   zynqmp_dp_irq_handler, IRQF_ONESHOT,
-   dev_name(dp->dev), dp);
+   ret = devm_request_irq(dp->dev, dp->irq, zynqmp_dp_irq_handler,
+  IRQF_SHARED, dev_name(dp->dev), dp);
if (ret < 0)
goto err_phy_exit;
 
-- 
2.35.1.1320.gc452695387.dirty



[PATCH v5 05/10] drm: zynqmp_dp: Don't retrain the link in our IRQ

2024-05-03 Thread Sean Anderson
Retraining the link can take a while, and might involve waiting for
DPCD reads/writes to complete. In preparation for unthreading the IRQ
handler, move this into its own work function.

Signed-off-by: Sean Anderson 
---

(no changes since v2)

Changes in v2:
- Document hpd_irq_work
- Split this off from the locking changes

 drivers/gpu/drm/xlnx/zynqmp_dp.c | 45 
 1 file changed, 29 insertions(+), 16 deletions(-)

diff --git a/drivers/gpu/drm/xlnx/zynqmp_dp.c b/drivers/gpu/drm/xlnx/zynqmp_dp.c
index abfccd8bb5a7..cec5711c7026 100644
--- a/drivers/gpu/drm/xlnx/zynqmp_dp.c
+++ b/drivers/gpu/drm/xlnx/zynqmp_dp.c
@@ -289,6 +289,7 @@ struct zynqmp_dp_config {
  * @phy: PHY handles for DP lanes
  * @num_lanes: number of enabled phy lanes
  * @hpd_work: hot plug detection worker
+ * @hpd_irq_work: hot plug detection IRQ worker
  * @status: connection status
  * @enabled: flag to indicate if the device is enabled
  * @dpcd: DP configuration data from currently connected sink device
@@ -304,6 +305,7 @@ struct zynqmp_dp {
struct drm_dp_aux aux;
struct drm_bridge bridge;
struct work_struct hpd_work;
+   struct work_struct hpd_irq_work;
struct mutex lock;
 
struct drm_bridge *next_bridge;
@@ -1671,6 +1673,29 @@ static void zynqmp_dp_hpd_work_func(struct work_struct 
*work)
drm_bridge_hpd_notify(>bridge, status);
 }
 
+static void zynqmp_dp_hpd_irq_work_func(struct work_struct *work)
+{
+   struct zynqmp_dp *dp = container_of(work, struct zynqmp_dp,
+   hpd_irq_work);
+   u8 status[DP_LINK_STATUS_SIZE + 2];
+   int err;
+
+   mutex_lock(>lock);
+   err = drm_dp_dpcd_read(>aux, DP_SINK_COUNT, status,
+  DP_LINK_STATUS_SIZE + 2);
+   if (err < 0) {
+   dev_dbg_ratelimited(dp->dev,
+   "could not read sink status: %d\n", err);
+   } else {
+   if (status[4] & DP_LINK_STATUS_UPDATED ||
+   !drm_dp_clock_recovery_ok([2], dp->mode.lane_cnt) ||
+   !drm_dp_channel_eq_ok([2], dp->mode.lane_cnt)) {
+   zynqmp_dp_train_loop(dp);
+   }
+   }
+   mutex_unlock(>lock);
+}
+
 static irqreturn_t zynqmp_dp_irq_handler(int irq, void *data)
 {
struct zynqmp_dp *dp = (struct zynqmp_dp *)data;
@@ -1702,23 +1727,9 @@ static irqreturn_t zynqmp_dp_irq_handler(int irq, void 
*data)
if (status & ZYNQMP_DP_INT_HPD_EVENT)
schedule_work(>hpd_work);
 
-   if (status & ZYNQMP_DP_INT_HPD_IRQ) {
-   int ret;
-   u8 status[DP_LINK_STATUS_SIZE + 2];
+   if (status & ZYNQMP_DP_INT_HPD_IRQ)
+   schedule_work(>hpd_irq_work);
 
-   ret = drm_dp_dpcd_read(>aux, DP_SINK_COUNT, status,
-  DP_LINK_STATUS_SIZE + 2);
-   if (ret < 0)
-   goto handled;
-
-   if (status[4] & DP_LINK_STATUS_UPDATED ||
-   !drm_dp_clock_recovery_ok([2], dp->mode.lane_cnt) ||
-   !drm_dp_channel_eq_ok([2], dp->mode.lane_cnt)) {
-   zynqmp_dp_train_loop(dp);
-   }
-   }
-
-handled:
return IRQ_HANDLED;
 }
 
@@ -1744,6 +1755,7 @@ int zynqmp_dp_probe(struct zynqmp_dpsub *dpsub)
mutex_init(>lock);
 
INIT_WORK(>hpd_work, zynqmp_dp_hpd_work_func);
+   INIT_WORK(>hpd_irq_work, zynqmp_dp_hpd_irq_work_func);
 
/* Acquire all resources (IOMEM, IRQ and PHYs). */
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "dp");
@@ -1848,6 +1860,7 @@ void zynqmp_dp_remove(struct zynqmp_dpsub *dpsub)
zynqmp_dp_write(dp, ZYNQMP_DP_INT_DS, ZYNQMP_DP_INT_ALL);
disable_irq(dp->irq);
 
+   cancel_work_sync(>hpd_irq_work);
cancel_work_sync(>hpd_work);
 
zynqmp_dp_write(dp, ZYNQMP_DP_TRANSMITTER_ENABLE, 0);
-- 
2.35.1.1320.gc452695387.dirty



[PATCH v5 04/10] drm: zynqmp_dp: Add locking

2024-05-03 Thread Sean Anderson
Add some locking to prevent the IRQ/workers/bridge API calls from stepping
on each other's toes. This lock protects:

- Non-atomic registers configuring the link. That is, everything but the
  IRQ registers (since these are accessed in an atomic fashion), and the DP
  AUX registers (since these don't affect the link). We also access AUX
  while holding this lock, so it would be very tricky to support.
- Link configuration. This is effectively everything in zynqmp_dp which
  isn't read-only after probe time. So from next_bridge onward.

This lock is designed to protect configuration changes so we don't have to
do anything tricky. Configuration should never be in the hot path, so I'm
not worried about performance.

Signed-off-by: Sean Anderson 
---

(no changes since v2)

Changes in v2:
- Split off the HPD IRQ work into another commit
- Expand the commit message

 drivers/gpu/drm/xlnx/zynqmp_dp.c | 18 ++
 1 file changed, 18 insertions(+)

diff --git a/drivers/gpu/drm/xlnx/zynqmp_dp.c b/drivers/gpu/drm/xlnx/zynqmp_dp.c
index 129beac4c073..abfccd8bb5a7 100644
--- a/drivers/gpu/drm/xlnx/zynqmp_dp.c
+++ b/drivers/gpu/drm/xlnx/zynqmp_dp.c
@@ -280,6 +280,7 @@ struct zynqmp_dp_config {
  * @dpsub: Display subsystem
  * @iomem: device I/O memory for register access
  * @reset: reset controller
+ * @lock: Mutex protecting this struct and register access (but not AUX)
  * @irq: irq
  * @bridge: DRM bridge for the DP encoder
  * @next_bridge: The downstream bridge
@@ -294,11 +295,16 @@ struct zynqmp_dp_config {
  * @link_config: common link configuration between IP core and sink device
  * @mode: current mode between IP core and sink device
  * @train_set: set of training data
+ *
+ * @lock covers the link configuration in this struct and the device's
+ * registers. It does not cover @aux. It is not strictly required for any of
+ * the members which are only modified at probe/remove time (e.g. @dev).
  */
 struct zynqmp_dp {
struct drm_dp_aux aux;
struct drm_bridge bridge;
struct work_struct hpd_work;
+   struct mutex lock;
 
struct drm_bridge *next_bridge;
struct device *dev;
@@ -1386,8 +1392,10 @@ zynqmp_dp_bridge_mode_valid(struct drm_bridge *bridge,
}
 
/* Check with link rate and lane count */
+   mutex_lock(>lock);
rate = zynqmp_dp_max_rate(dp->link_config.max_rate,
  dp->link_config.max_lanes, dp->config.bpp);
+   mutex_unlock(>lock);
if (mode->clock > rate) {
dev_dbg(dp->dev, "filtered mode %s for high pixel rate\n",
mode->name);
@@ -1414,6 +1422,7 @@ static void zynqmp_dp_bridge_atomic_enable(struct 
drm_bridge *bridge,
 
pm_runtime_get_sync(dp->dev);
 
+   mutex_lock(>lock);
zynqmp_dp_disp_enable(dp, old_bridge_state);
 
/*
@@ -1474,6 +1483,7 @@ static void zynqmp_dp_bridge_atomic_enable(struct 
drm_bridge *bridge,
zynqmp_dp_write(dp, ZYNQMP_DP_SOFTWARE_RESET,
ZYNQMP_DP_SOFTWARE_RESET_ALL);
zynqmp_dp_write(dp, ZYNQMP_DP_MAIN_STREAM_ENABLE, 1);
+   mutex_unlock(>lock);
 }
 
 static void zynqmp_dp_bridge_atomic_disable(struct drm_bridge *bridge,
@@ -1481,6 +1491,7 @@ static void zynqmp_dp_bridge_atomic_disable(struct 
drm_bridge *bridge,
 {
struct zynqmp_dp *dp = bridge_to_dp(bridge);
 
+   mutex_lock(>lock);
dp->enabled = false;
cancel_work(>hpd_work);
zynqmp_dp_write(dp, ZYNQMP_DP_MAIN_STREAM_ENABLE, 0);
@@ -1491,6 +1502,7 @@ static void zynqmp_dp_bridge_atomic_disable(struct 
drm_bridge *bridge,
zynqmp_dp_write(dp, ZYNQMP_DP_TX_AUDIO_CONTROL, 0);
 
zynqmp_dp_disp_disable(dp, old_bridge_state);
+   mutex_unlock(>lock);
 
pm_runtime_put_sync(dp->dev);
 }
@@ -1533,6 +1545,8 @@ static enum drm_connector_status 
zynqmp_dp_bridge_detect(struct drm_bridge *brid
u32 state, i;
int ret;
 
+   mutex_lock(>lock);
+
/*
 * This is from heuristic. It takes some delay (ex, 100 ~ 500 msec) to
 * get the HPD signal with some monitors.
@@ -1560,11 +1574,13 @@ static enum drm_connector_status 
zynqmp_dp_bridge_detect(struct drm_bridge *brid
   dp->num_lanes);
 
dp->status = connector_status_connected;
+   mutex_unlock(>lock);
return connector_status_connected;
}
 
 disconnected:
dp->status = connector_status_disconnected;
+   mutex_unlock(>lock);
return connector_status_disconnected;
 }
 
@@ -1725,6 +1741,7 @@ int zynqmp_dp_probe(struct zynqmp_dpsub *dpsub)
dp->dev = >dev;
dp->dpsub = dpsub;
dp->status = connector_status_disconnected;
+   mutex_init(>lock);
 
INIT_WORK(>hpd_work, zynqmp_dp_hpd_work_func);
 
@@ -18

[PATCH v5 03/10] drm: zynqmp_dp: Don't delay work

2024-05-03 Thread Sean Anderson
We always call scheduled_delayed_work with no delay, so just use a
non-delayed work_struct instead.

Signed-off-by: Sean Anderson 
Reviewed-by: Tomi Valkeinen 
---

(no changes since v3)

Changes in v3:
- New

 drivers/gpu/drm/xlnx/zynqmp_dp.c | 13 ++---
 1 file changed, 6 insertions(+), 7 deletions(-)

diff --git a/drivers/gpu/drm/xlnx/zynqmp_dp.c b/drivers/gpu/drm/xlnx/zynqmp_dp.c
index 12a8248ed125..129beac4c073 100644
--- a/drivers/gpu/drm/xlnx/zynqmp_dp.c
+++ b/drivers/gpu/drm/xlnx/zynqmp_dp.c
@@ -298,7 +298,7 @@ struct zynqmp_dp_config {
 struct zynqmp_dp {
struct drm_dp_aux aux;
struct drm_bridge bridge;
-   struct delayed_work hpd_work;
+   struct work_struct hpd_work;
 
struct drm_bridge *next_bridge;
struct device *dev;
@@ -1482,7 +1482,7 @@ static void zynqmp_dp_bridge_atomic_disable(struct 
drm_bridge *bridge,
struct zynqmp_dp *dp = bridge_to_dp(bridge);
 
dp->enabled = false;
-   cancel_delayed_work(>hpd_work);
+   cancel_work(>hpd_work);
zynqmp_dp_write(dp, ZYNQMP_DP_MAIN_STREAM_ENABLE, 0);
drm_dp_dpcd_writeb(>aux, DP_SET_POWER, DP_SET_POWER_D3);
zynqmp_dp_write(dp, ZYNQMP_DP_TX_PHY_POWER_DOWN,
@@ -1648,8 +1648,7 @@ void zynqmp_dp_disable_vblank(struct zynqmp_dp *dp)
 
 static void zynqmp_dp_hpd_work_func(struct work_struct *work)
 {
-   struct zynqmp_dp *dp = container_of(work, struct zynqmp_dp,
-   hpd_work.work);
+   struct zynqmp_dp *dp = container_of(work, struct zynqmp_dp, hpd_work);
enum drm_connector_status status;
 
status = zynqmp_dp_bridge_detect(>bridge);
@@ -1685,7 +1684,7 @@ static irqreturn_t zynqmp_dp_irq_handler(int irq, void 
*data)
zynqmp_dpsub_drm_handle_vblank(dp->dpsub);
 
if (status & ZYNQMP_DP_INT_HPD_EVENT)
-   schedule_delayed_work(>hpd_work, 0);
+   schedule_work(>hpd_work);
 
if (status & ZYNQMP_DP_INT_HPD_IRQ) {
int ret;
@@ -1727,7 +1726,7 @@ int zynqmp_dp_probe(struct zynqmp_dpsub *dpsub)
dp->dpsub = dpsub;
dp->status = connector_status_disconnected;
 
-   INIT_DELAYED_WORK(>hpd_work, zynqmp_dp_hpd_work_func);
+   INIT_WORK(>hpd_work, zynqmp_dp_hpd_work_func);
 
/* Acquire all resources (IOMEM, IRQ and PHYs). */
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "dp");
@@ -1832,7 +1831,7 @@ void zynqmp_dp_remove(struct zynqmp_dpsub *dpsub)
zynqmp_dp_write(dp, ZYNQMP_DP_INT_DS, ZYNQMP_DP_INT_ALL);
disable_irq(dp->irq);
 
-   cancel_delayed_work_sync(>hpd_work);
+   cancel_work_sync(>hpd_work);
 
zynqmp_dp_write(dp, ZYNQMP_DP_TRANSMITTER_ENABLE, 0);
zynqmp_dp_write(dp, ZYNQMP_DP_INT_DS, 0x);
-- 
2.35.1.1320.gc452695387.dirty



[PATCH v5 02/10] drm: zynqmp_dp: Rearrange zynqmp_dp for better padding

2024-05-03 Thread Sean Anderson
Sort the members of struct zynqmp_dp to reduce padding necessary for
alignment.

Signed-off-by: Sean Anderson 
---

(no changes since v2)

Changes in v2:
- New

 drivers/gpu/drm/xlnx/zynqmp_dp.c | 28 ++--
 1 file changed, 14 insertions(+), 14 deletions(-)

diff --git a/drivers/gpu/drm/xlnx/zynqmp_dp.c b/drivers/gpu/drm/xlnx/zynqmp_dp.c
index 9df068a413f3..12a8248ed125 100644
--- a/drivers/gpu/drm/xlnx/zynqmp_dp.c
+++ b/drivers/gpu/drm/xlnx/zynqmp_dp.c
@@ -256,10 +256,10 @@ struct zynqmp_dp_link_config {
  * @fmt: format identifier string
  */
 struct zynqmp_dp_mode {
-   u8 bw_code;
-   u8 lane_cnt;
-   int pclock;
const char *fmt;
+   int pclock;
+   u8 bw_code;
+   u8 lane_cnt;
 };
 
 /**
@@ -296,27 +296,27 @@ struct zynqmp_dp_config {
  * @train_set: set of training data
  */
 struct zynqmp_dp {
+   struct drm_dp_aux aux;
+   struct drm_bridge bridge;
+   struct delayed_work hpd_work;
+
+   struct drm_bridge *next_bridge;
struct device *dev;
struct zynqmp_dpsub *dpsub;
void __iomem *iomem;
struct reset_control *reset;
-   int irq;
-
-   struct drm_bridge bridge;
-   struct drm_bridge *next_bridge;
-
-   struct zynqmp_dp_config config;
-   struct drm_dp_aux aux;
struct phy *phy[ZYNQMP_DP_MAX_LANES];
-   u8 num_lanes;
-   struct delayed_work hpd_work;
+
enum drm_connector_status status;
+   int irq;
bool enabled;
 
-   u8 dpcd[DP_RECEIVER_CAP_SIZE];
-   struct zynqmp_dp_link_config link_config;
struct zynqmp_dp_mode mode;
+   struct zynqmp_dp_link_config link_config;
+   struct zynqmp_dp_config config;
+   u8 dpcd[DP_RECEIVER_CAP_SIZE];
u8 train_set[ZYNQMP_DP_MAX_LANES];
+   u8 num_lanes;
 };
 
 static inline struct zynqmp_dp *bridge_to_dp(struct drm_bridge *bridge)
-- 
2.35.1.1320.gc452695387.dirty



[PATCH v5 00/10] drm: zynqmp_dp: IRQ cleanups and debugfs support

2024-05-03 Thread Sean Anderson
This series cleans up the zyqnmp_dp IRQ and locking situation. Once
that's done, it adds debugfs support. The intent is to enable compliance
testing or to help debug signal-integrity issues.

Last time I discussed converting the HPD work(s) to a threaded IRQ. I
did not end up doing that for this series since the steps would be

- Add locking
- Move link retraining to a work function
- Harden the IRQ
- Merge the works into a threaded IRQ (omitted)

Which with the exception of the final step is the same as leaving those
works as-is. Conversion to a threaded IRQ can be done as a follow-up.

Changes in v5:
- Fix AUX bus not getting unregistered
- Rebase onto drm-misc/drm-misc-next

Changes in v4:
- Rebase onto drm/drm-next

Changes in v3:
- Don't delay work
- Convert to a hard IRQ
- Use AUX IRQs instead of polling
- Take dp->lock in zynqmp_dp_hpd_work_func

Changes in v2:
- Rearrange zynqmp_dp for better padding
- Split off the HPD IRQ work into another commit
- Expand the commit message
- Document hpd_irq_work
- Document debugfs files
- Add ignore_aux_errors and ignore_hpd debugfs files to replace earlier
  implicit functionality
- Attempt to fix unreproducable, spurious build warning
- Drop "Optionally ignore DPCD errors" in favor of a debugfs file
  directly affecting zynqmp_dp_aux_transfer.

Sean Anderson (10):
  drm: zynqmp_kms: Fix AUX bus not getting unregistered
  drm: zynqmp_dp: Rearrange zynqmp_dp for better padding
  drm: zynqmp_dp: Don't delay work
  drm: zynqmp_dp: Add locking
  drm: zynqmp_dp: Don't retrain the link in our IRQ
  drm: zynqmp_dp: Convert to a hard IRQ
  drm: zynqmp_dp: Use AUX IRQs instead of polling
  drm: zynqmp_dp: Split off several helper functions
  drm: zynqmp_dp: Take dp->lock in zynqmp_dp_hpd_work_func
  drm: zynqmp_dp: Add debugfs interface for compliance testing

 Documentation/gpu/drivers.rst |   1 +
 Documentation/gpu/zynqmp.rst  | 149 +
 MAINTAINERS   |   1 +
 drivers/gpu/drm/xlnx/zynqmp_dp.c  | 883 +++---
 drivers/gpu/drm/xlnx/zynqmp_kms.c |  12 +-
 5 files changed, 977 insertions(+), 69 deletions(-)
 create mode 100644 Documentation/gpu/zynqmp.rst

-- 
2.35.1.1320.gc452695387.dirty



[PATCH v5 01/10] drm: zynqmp_kms: Fix AUX bus not getting unregistered

2024-05-03 Thread Sean Anderson
drm_encoder_cleanup is responsible for calling drm_bridge_detach for
each bridge attached to the encoder. zynqmp_dp_bridge_detach is in turn
responsible for unregistering the AUX bus. However, we never ended up
calling drm_encoder_cleanup in the remove or error paths, so the AUX bus
would stick around after the rest of the driver had been removed.

I don't really understand why drm_mode_config_cleanup doesn't call
drm_encoder_cleanup for us. It will call destroy (which for
simple_encoder is drm_encoder_cleanup) on encoders in the mode_config's
encoder_list.

Should drm_encoder_cleanup get called before or after
drm_atomic_helper_shutdown?

Fixes: 2dfd045c8435 ("drm: xlnx: zynqmp_dpsub: Register AUX bus at bridge 
attach time")
Signed-off-by: Sean Anderson 
---

Changes in v5:
- New

 drivers/gpu/drm/xlnx/zynqmp_kms.c | 12 +---
 1 file changed, 9 insertions(+), 3 deletions(-)

diff --git a/drivers/gpu/drm/xlnx/zynqmp_kms.c 
b/drivers/gpu/drm/xlnx/zynqmp_kms.c
index 43bf416b33d5..f25583ce92e6 100644
--- a/drivers/gpu/drm/xlnx/zynqmp_kms.c
+++ b/drivers/gpu/drm/xlnx/zynqmp_kms.c
@@ -433,23 +433,28 @@ static int zynqmp_dpsub_kms_init(struct zynqmp_dpsub 
*dpsub)
DRM_BRIDGE_ATTACH_NO_CONNECTOR);
if (ret) {
dev_err(dpsub->dev, "failed to attach bridge to encoder\n");
-   return ret;
+   goto err_encoder;
}
 
/* Create the connector for the chain of bridges. */
connector = drm_bridge_connector_init(>drm->dev, encoder);
if (IS_ERR(connector)) {
dev_err(dpsub->dev, "failed to created connector\n");
-   return PTR_ERR(connector);
+   ret = PTR_ERR(connector);
+   goto err_encoder;
}
 
ret = drm_connector_attach_encoder(connector, encoder);
if (ret < 0) {
dev_err(dpsub->dev, "failed to attach connector to encoder\n");
-   return ret;
+   goto err_encoder;
}
 
return 0;
+
+err_encoder:
+   drm_encoder_cleanup(encoder);
+   return ret;
 }
 
 static void zynqmp_dpsub_drm_release(struct drm_device *drm, void *res)
@@ -529,5 +534,6 @@ void zynqmp_dpsub_drm_cleanup(struct zynqmp_dpsub *dpsub)
 
drm_dev_unregister(drm);
drm_atomic_helper_shutdown(drm);
+   drm_encoder_cleanup(>drm->encoder);
drm_kms_helper_poll_fini(drm);
 }
-- 
2.35.1.1320.gc452695387.dirty



Re: [PATCH v4 00/13] drm: zynqmp_dp: IRQ cleanups and debugfs support

2024-04-25 Thread Sean Anderson
On 4/24/24 14:54, Tomi Valkeinen wrote:
> Hi Sean,
> 
> On 23/04/2024 20:18, Sean Anderson wrote:
>> This series cleans up the zyqnmp_dp IRQ and locking situation. Once
>> that's done, it adds debugfs support. The intent is to enable compliance
>> testing or to help debug signal-integrity issues.
>>
>> Last time I discussed converting the HPD work(s) to a threaded IRQ. I
>> did not end up doing that for this series since the steps would be
>>
>> - Add locking
>> - Move link retraining to a work function
>> - Harden the IRQ
>> - Merge the works into a threaded IRQ (omitted)
>>
>> Which with the exception of the final step is the same as leaving those
>> works as-is. Conversion to a threaded IRQ can be done as a follow-up.
> 
> If it's ok to you, I'd like to pick the first four patches to drm-misc 
> already.

Fine by me.

--Sean



Re: [PATCH] drm: zynqmp_dpsub: Always register bridge

2024-04-23 Thread Sean Anderson
Hi,

On 3/22/24 02:01, Tomi Valkeinen wrote:
> Hi,
> 
> On 08/03/2024 22:47, Sean Anderson wrote:
>> We must always register the DRM bridge, since zynqmp_dp_hpd_work_func
>> calls drm_bridge_hpd_notify, which in turn expects hpd_mutex to be
>> initialized. We do this before zynqmp_dpsub_drm_init since that calls
>> drm_bridge_attach. This fixes the following lockdep warning:
>>
>> [   19.217084] [ cut here ]
>> [   19.227530] DEBUG_LOCKS_WARN_ON(lock->magic != lock)
>> [   19.227768] WARNING: CPU: 0 PID: 140 at kernel/locking/mutex.c:582 
>> __mutex_lock+0x4bc/0x550
>> [   19.241696] Modules linked in:
>> [   19.244937] CPU: 0 PID: 140 Comm: kworker/0:4 Not tainted 6.6.20+ #96
>> [   19.252046] Hardware name: xlnx,zynqmp (DT)
>> [   19.256421] Workqueue: events zynqmp_dp_hpd_work_func
>> [   19.261795] pstate: 6005 (nZCv daif -PAN -UAO -TCO -DIT -SSBS 
>> BTYPE=--)
>> [   19.269104] pc : __mutex_lock+0x4bc/0x550
>> [   19.273364] lr : __mutex_lock+0x4bc/0x550
>> [   19.277592] sp : ffc085c5bbe0
>> [   19.281066] x29: ffc085c5bbe0 x28:  x27: 
>> ff88009417f8
>> [   19.288624] x26: ff8800941788 x25: ff8800020008 x24: 
>> ffc082aa3000
>> [   19.296227] x23: ffc080d90e3c x22: 0002 x21: 
>> 
>> [   19.303744] x20:  x19: ff88002f5210 x18: 
>> 
>> [   19.311295] x17: 6c707369642e3030 x16: 3030613464662072 x15: 
>> 0720072007200720
>> [   19.318922] x14:  x13: 284e4f5f4e524157 x12: 
>> 0001
>> [   19.326442] x11: 0001ffc085c5b940 x10: 0001ff88003f388b x9 : 
>> 0001ff88003f3888
>> [   19.334003] x8 : 0001ff88003f3888 x7 :  x6 : 
>> 
>> [   19.341537] x5 :  x4 : 1668 x3 : 
>> 
>> [   19.349054] x2 :  x1 :  x0 : 
>> ff88003f3880
>> [   19.356581] Call trace:
>> [   19.359160]  __mutex_lock+0x4bc/0x550
>> [   19.363032]  mutex_lock_nested+0x24/0x30
>> [   19.367187]  drm_bridge_hpd_notify+0x2c/0x6c
>> [   19.371698]  zynqmp_dp_hpd_work_func+0x44/0x54
>> [   19.376364]  process_one_work+0x3ac/0x988
>> [   19.380660]  worker_thread+0x398/0x694
>> [   19.384736]  kthread+0x1bc/0x1c0
>> [   19.388241]  ret_from_fork+0x10/0x20
>> [   19.392031] irq event stamp: 183
>> [   19.395450] hardirqs last  enabled at (183): [] 
>> finish_task_switch.isra.0+0xa8/0x2d4
>> [   19.405140] hardirqs last disabled at (182): [] 
>> __schedule+0x714/0xd04
>> [   19.413612] softirqs last  enabled at (114): [] 
>> srcu_invoke_callbacks+0x158/0x23c
>> [   19.423128] softirqs last disabled at (110): [] 
>> srcu_invoke_callbacks+0x158/0x23c
>> [   19.432614] ---[ end trace  ]---
>>
>> Fixes: eb2d64bfcc17 ("drm: xlnx: zynqmp_dpsub: Report HPD through the 
>> bridge")
>> Signed-off-by: Sean Anderson 
>> ---
>>
>>   drivers/gpu/drm/xlnx/zynqmp_dpsub.c | 6 ++
>>   1 file changed, 2 insertions(+), 4 deletions(-)
>>
>> diff --git a/drivers/gpu/drm/xlnx/zynqmp_dpsub.c 
>> b/drivers/gpu/drm/xlnx/zynqmp_dpsub.c
>> index 88eb33acd5f0..639fff2c693f 100644
>> --- a/drivers/gpu/drm/xlnx/zynqmp_dpsub.c
>> +++ b/drivers/gpu/drm/xlnx/zynqmp_dpsub.c
>> @@ -256,12 +256,11 @@ static int zynqmp_dpsub_probe(struct platform_device 
>> *pdev)
>>   if (ret)
>>   goto err_dp;
>>   +    drm_bridge_add(dpsub->bridge);
>>   if (dpsub->dma_enabled) {
>>   ret = zynqmp_dpsub_drm_init(dpsub);
>>   if (ret)
>>   goto err_disp;
>> -    } else {
>> -    drm_bridge_add(dpsub->bridge);
>>   }
>>     dev_info(>dev, "ZynqMP DisplayPort Subsystem driver probed");
>> @@ -288,9 +287,8 @@ static void zynqmp_dpsub_remove(struct platform_device 
>> *pdev)
>>     if (dpsub->drm)
>>   zynqmp_dpsub_drm_cleanup(dpsub);
>> -    else
>> -    drm_bridge_remove(dpsub->bridge);
>>   +    drm_bridge_remove(dpsub->bridge);
>>   zynqmp_disp_remove(dpsub);
>>   zynqmp_dp_remove(dpsub);
>>   
> 
> I sent a similar patch:
> 
> https://lore.kernel.org/all/20240312-xilinx-dp-lock-fix-v1-1-1698f9f03...@ideasonboard.com/
> 
> I have the drm_bridge_add() call in zynqmp_dp_probe(), as that's where the 
> bridge is set up, so it felt like a logical place. You add it later, just 
> before the bridge is used the first time.
> 
> I like mine a bit more as it has all the bridge code in the same place, but I 
> also wonder if there might be some risks in adding the bridge early (before 
> zynqmp_disp_probe()), although I can't see any issue right away...
> 
> In any case, as this works for me too:
> 
> Reviewed-by: Tomi Valkeinen 
> 
>  Tomi
> 
Can someone pick up this fix before the release? I am still running into this 
bug on linux-next/master.

--Sean


[PATCH v4 12/13] drm: zynqmp_dp: Take dp->lock in zynqmp_dp_hpd_work_func

2024-04-23 Thread Sean Anderson
Add a non-locking version of zynqmp_dp_bridge_detect and use it in
zynqmp_dp_hpd_work_func so we can take the lock explicitly. This will
make it easier to check for hpd_ignore when we add debugfs support.

Signed-off-by: Sean Anderson 
---

(no changes since v3)

Changes in v3:
- New

 drivers/gpu/drm/xlnx/zynqmp_dp.c | 24 ++--
 1 file changed, 18 insertions(+), 6 deletions(-)

diff --git a/drivers/gpu/drm/xlnx/zynqmp_dp.c b/drivers/gpu/drm/xlnx/zynqmp_dp.c
index d5ee165046f8..fe002fef4662 100644
--- a/drivers/gpu/drm/xlnx/zynqmp_dp.c
+++ b/drivers/gpu/drm/xlnx/zynqmp_dp.c
@@ -1552,14 +1552,13 @@ static int zynqmp_dp_bridge_atomic_check(struct 
drm_bridge *bridge,
return 0;
 }
 
-static enum drm_connector_status zynqmp_dp_bridge_detect(struct drm_bridge 
*bridge)
+static enum drm_connector_status __zynqmp_dp_bridge_detect(struct zynqmp_dp 
*dp)
 {
-   struct zynqmp_dp *dp = bridge_to_dp(bridge);
struct zynqmp_dp_link_config *link_config = >link_config;
u32 state, i;
int ret;
 
-   mutex_lock(>lock);
+   lockdep_assert_held(>lock);
 
/*
 * This is from heuristic. It takes some delay (ex, 100 ~ 500 msec) to
@@ -1588,16 +1587,26 @@ static enum drm_connector_status 
zynqmp_dp_bridge_detect(struct drm_bridge *brid
   dp->num_lanes);
 
dp->status = connector_status_connected;
-   mutex_unlock(>lock);
return connector_status_connected;
}
 
 disconnected:
dp->status = connector_status_disconnected;
-   mutex_unlock(>lock);
return connector_status_disconnected;
 }
 
+static enum drm_connector_status zynqmp_dp_bridge_detect(struct drm_bridge 
*bridge)
+{
+   struct zynqmp_dp *dp = bridge_to_dp(bridge);
+   enum drm_connector_status ret;
+
+   mutex_lock(>lock);
+   ret = __zynqmp_dp_bridge_detect(dp);
+   mutex_unlock(>lock);
+
+   return ret;
+}
+
 static const struct drm_edid *zynqmp_dp_bridge_edid_read(struct drm_bridge 
*bridge,
 struct drm_connector 
*connector)
 {
@@ -1651,7 +1660,10 @@ static void zynqmp_dp_hpd_work_func(struct work_struct 
*work)
struct zynqmp_dp *dp = container_of(work, struct zynqmp_dp, hpd_work);
enum drm_connector_status status;
 
-   status = zynqmp_dp_bridge_detect(>bridge);
+   mutex_lock(>lock);
+   status = __zynqmp_dp_bridge_detect(dp);
+   mutex_unlock(>lock);
+
drm_bridge_hpd_notify(>bridge, status);
 }
 
-- 
2.35.1.1320.gc452695387.dirty



[PATCH v4 13/13] drm: zynqmp_dp: Add debugfs interface for compliance testing

2024-04-23 Thread Sean Anderson
Add a debugfs interface for exercising the various test modes supported
by the DisplayPort controller. This allows performing compliance
testing, or performing signal integrity measurements on a failing link.
At the moment, we do not support sink-driven link quality testing,
although such support would be fairly easy to add.

Additionally, add some debugfs files for ignoring AUX errors and HPD
events, as this can allow testing with equipment that cannot emulate a
DPRX.

Signed-off-by: Sean Anderson 
---

(no changes since v2)

Changes in v2:
- Document debugfs files
- Add ignore_aux_errors and ignore_hpd debugfs files to replace earlier
  implicit functionality
- Attempt to fix unreproducable, spurious build warning

 Documentation/gpu/drivers.rst|   1 +
 Documentation/gpu/zynqmp.rst | 149 +++
 MAINTAINERS  |   1 +
 drivers/gpu/drm/xlnx/zynqmp_dp.c | 682 ++-
 4 files changed, 830 insertions(+), 3 deletions(-)
 create mode 100644 Documentation/gpu/zynqmp.rst

diff --git a/Documentation/gpu/drivers.rst b/Documentation/gpu/drivers.rst
index b899cbc5c2b4..187201aedbe5 100644
--- a/Documentation/gpu/drivers.rst
+++ b/Documentation/gpu/drivers.rst
@@ -22,6 +22,7 @@ GPU Driver Documentation
afbc
komeda-kms
panfrost
+   zynqmp
 
 .. only::  subproject and html
 
diff --git a/Documentation/gpu/zynqmp.rst b/Documentation/gpu/zynqmp.rst
new file mode 100644
index ..f57bfa0ad6ec
--- /dev/null
+++ b/Documentation/gpu/zynqmp.rst
@@ -0,0 +1,149 @@
+.. SPDX-License-Identifier: GPL-2.0+
+
+===
+Xilinx ZynqMP Ultrascale+ DisplayPort Subsystem
+===
+
+This subsystem handles DisplayPort video and audio output on the ZynqMP. It
+supports in-memory framebuffers with the DisplayPort DMA controller
+(xilinx-dpdma), as well as "live" video and audio from the programmable logic
+(PL). This subsystem can perform several transformations, including color space
+conversion, alpha blending, and audio mixing, although not all features are
+currently supported.
+
+debugfs
+---
+
+To support debugging and compliance testing, several test modes can be enabled
+though debugfs. The following files in /sys/kernel/debug/dri/X/DP-1/test/
+control the DisplayPort test modes:
+
+active:
+Writing a 1 to this file will activate test mode, and writing a 0 will
+deactivate test mode. Writing a 1 or 0 when the test mode is already
+active/inactive will re-activate/re-deactivate test mode. When test
+mode is inactive, changes made to other files will have no (immediate)
+effect, although the settings will be saved for when test mode is
+activated. When test mode is active, changes made to other files will
+apply immediately.
+
+custom:
+Custom test pattern value
+
+downspread:
+Enable/disable clock downspreading (spread-spectrum clocking) by
+writing 1/0
+
+enhanced:
+Enable/disable enhanced framing
+
+ignore_aux_errors:
+Ignore AUX errors when set to 1. Writes to this file take effect
+immediately (regardless of whether test mode is active) and affect all
+AUX transfers.
+
+ignore_hpd:
+Ignore hotplug events (such as cable removals or monitor link
+retraining requests) when set to 1. Writes to this file take effect
+immediately (regardless of whether test mode is active).
+
+laneX_preemphasis:
+Preemphasis from 0 (lowest) to 2 (highest) for lane X
+
+laneX_swing:
+Voltage swing from 0 (lowest) to 3 (highest) for lane X
+
+lanes:
+Number of lanes to use (1, 2, or 4)
+
+pattern:
+Test pattern. May be one of:
+
+video
+Use regular video input
+
+symbol-error
+Symbol error measurement pattern
+
+prbs7
+Output of the PRBS7 (x^7 + x^6 + 1) polynomial
+
+80bit-custom
+A custom 80-bit pattern
+
+cp2520
+HBR2 compliance eye pattern
+
+tps1
+Link training symbol pattern TPS1 (/D10.2/)
+
+tps2
+Link training symbol pattern TPS2
+
+tps3
+Link training symbol pattern TPS3 (for HBR2)
+
+rate:
+Rate in hertz. One of
+
+* 54 (HBR2)
+* 27 (HBR)
+* 162000 (RBR)
+
+You can dump the displayport test settings with the following command::
+
+for prop in /sys/kernel/debug/dri/1/DP-1/test/*; do
+printf '%-17s ' ${prop##*/}
+if [ ${prop##*/} = custom ]; then
+hexdump -C $prop | head -1
+else
+cat $prop
+fi
+  

[PATCH v4 05/13] drm: zynqmp_dp: Rearrange zynqmp_dp for better padding

2024-04-23 Thread Sean Anderson
Sort the members of struct zynqmp_dp to reduce padding necessary for
alignment.

Signed-off-by: Sean Anderson 
---

(no changes since v2)

Changes in v2:
- New

 drivers/gpu/drm/xlnx/zynqmp_dp.c | 28 ++--
 1 file changed, 14 insertions(+), 14 deletions(-)

diff --git a/drivers/gpu/drm/xlnx/zynqmp_dp.c b/drivers/gpu/drm/xlnx/zynqmp_dp.c
index 79afe4358d06..6e8478d58b02 100644
--- a/drivers/gpu/drm/xlnx/zynqmp_dp.c
+++ b/drivers/gpu/drm/xlnx/zynqmp_dp.c
@@ -255,10 +255,10 @@ struct zynqmp_dp_link_config {
  * @fmt: format identifier string
  */
 struct zynqmp_dp_mode {
-   u8 bw_code;
-   u8 lane_cnt;
-   int pclock;
const char *fmt;
+   int pclock;
+   u8 bw_code;
+   u8 lane_cnt;
 };
 
 /**
@@ -295,27 +295,27 @@ struct zynqmp_dp_config {
  * @train_set: set of training data
  */
 struct zynqmp_dp {
+   struct drm_dp_aux aux;
+   struct drm_bridge bridge;
+   struct delayed_work hpd_work;
+
+   struct drm_bridge *next_bridge;
struct device *dev;
struct zynqmp_dpsub *dpsub;
void __iomem *iomem;
struct reset_control *reset;
-   int irq;
-
-   struct drm_bridge bridge;
-   struct drm_bridge *next_bridge;
-
-   struct zynqmp_dp_config config;
-   struct drm_dp_aux aux;
struct phy *phy[ZYNQMP_DP_MAX_LANES];
-   u8 num_lanes;
-   struct delayed_work hpd_work;
+
enum drm_connector_status status;
+   int irq;
bool enabled;
 
-   u8 dpcd[DP_RECEIVER_CAP_SIZE];
-   struct zynqmp_dp_link_config link_config;
struct zynqmp_dp_mode mode;
+   struct zynqmp_dp_link_config link_config;
+   struct zynqmp_dp_config config;
+   u8 dpcd[DP_RECEIVER_CAP_SIZE];
u8 train_set[ZYNQMP_DP_MAX_LANES];
+   u8 num_lanes;
 };
 
 static inline struct zynqmp_dp *bridge_to_dp(struct drm_bridge *bridge)
-- 
2.35.1.1320.gc452695387.dirty



[PATCH v4 00/13] drm: zynqmp_dp: IRQ cleanups and debugfs support

2024-04-23 Thread Sean Anderson
This series cleans up the zyqnmp_dp IRQ and locking situation. Once
that's done, it adds debugfs support. The intent is to enable compliance
testing or to help debug signal-integrity issues.

Last time I discussed converting the HPD work(s) to a threaded IRQ. I
did not end up doing that for this series since the steps would be

- Add locking
- Move link retraining to a work function
- Harden the IRQ
- Merge the works into a threaded IRQ (omitted)

Which with the exception of the final step is the same as leaving those
works as-is. Conversion to a threaded IRQ can be done as a follow-up.

Changes in v4:
- Rebase onto drm/drm-next

Changes in v3:
- Store base pointers in zynqmp_disp directly
- Don't delay work
- Convert to a hard IRQ
- Use AUX IRQs instead of polling
- Take dp->lock in zynqmp_dp_hpd_work_func

Changes in v2:
- Fix kerneldoc
- Rearrange zynqmp_dp for better padding
- Split off the HPD IRQ work into another commit
- Expand the commit message
- Document hpd_irq_work
- Document debugfs files
- Add ignore_aux_errors and ignore_hpd debugfs files to replace earlier
  implicit functionality
- Attempt to fix unreproducable, spurious build warning
- Drop "Optionally ignore DPCD errors" in favor of a debugfs file
  directly affecting zynqmp_dp_aux_transfer.

Sean Anderson (13):
  drm: xlnx: Store base pointers in zynqmp_disp directly
  drm: xlnx: Fix kerneldoc
  drm: zynqmp_dp: Downgrade log level for aux retries message
  drm: zynqmp_dp: Adjust training values per-lane
  drm: zynqmp_dp: Rearrange zynqmp_dp for better padding
  drm: zynqmp_dp: Don't delay work
  drm: zynqmp_dp: Add locking
  drm: zynqmp_dp: Don't retrain the link in our IRQ
  drm: zynqmp_dp: Convert to a hard IRQ
  drm: zynqmp_dp: Use AUX IRQs instead of polling
  drm: zynqmp_dp: Split off several helper functions
  drm: zynqmp_dp: Take dp->lock in zynqmp_dp_hpd_work_func
  drm: zynqmp_dp: Add debugfs interface for compliance testing

 Documentation/gpu/drivers.rst   |   1 +
 Documentation/gpu/zynqmp.rst| 149 +
 MAINTAINERS |   1 +
 drivers/gpu/drm/xlnx/zynqmp_disp.c  |  44 +-
 drivers/gpu/drm/xlnx/zynqmp_dp.c| 908 +---
 drivers/gpu/drm/xlnx/zynqmp_dpsub.h |   1 +
 drivers/gpu/drm/xlnx/zynqmp_kms.h   |   4 +-
 7 files changed, 999 insertions(+), 109 deletions(-)
 create mode 100644 Documentation/gpu/zynqmp.rst

-- 
2.35.1.1320.gc452695387.dirty



[PATCH v4 09/13] drm: zynqmp_dp: Convert to a hard IRQ

2024-04-23 Thread Sean Anderson
Now that all of the sleeping work is done outside of the IRQ, we can
convert it to a hard IRQ.

Signed-off-by: Sean Anderson 
---

(no changes since v3)

Changes in v3:
- New

 drivers/gpu/drm/xlnx/zynqmp_dp.c | 5 ++---
 1 file changed, 2 insertions(+), 3 deletions(-)

diff --git a/drivers/gpu/drm/xlnx/zynqmp_dp.c b/drivers/gpu/drm/xlnx/zynqmp_dp.c
index 26034672964a..9d61b6b8f2d4 100644
--- a/drivers/gpu/drm/xlnx/zynqmp_dp.c
+++ b/drivers/gpu/drm/xlnx/zynqmp_dp.c
@@ -1786,9 +1786,8 @@ int zynqmp_dp_probe(struct zynqmp_dpsub *dpsub)
 * Now that the hardware is initialized and won't generate spurious
 * interrupts, request the IRQ.
 */
-   ret = devm_request_threaded_irq(dp->dev, dp->irq, NULL,
-   zynqmp_dp_irq_handler, IRQF_ONESHOT,
-   dev_name(dp->dev), dp);
+   ret = devm_request_irq(dp->dev, dp->irq, zynqmp_dp_irq_handler,
+  IRQF_SHARED, dev_name(dp->dev), dp);
if (ret < 0)
goto err_phy_exit;
 
-- 
2.35.1.1320.gc452695387.dirty



[PATCH v4 10/13] drm: zynqmp_dp: Use AUX IRQs instead of polling

2024-04-23 Thread Sean Anderson
Instead of polling the status register for the AUX status, just enable
the IRQs and signal a completion.

Signed-off-by: Sean Anderson 
---

(no changes since v3)

Changes in v3:
- New

 drivers/gpu/drm/xlnx/zynqmp_dp.c | 35 +++-
 1 file changed, 25 insertions(+), 10 deletions(-)

diff --git a/drivers/gpu/drm/xlnx/zynqmp_dp.c b/drivers/gpu/drm/xlnx/zynqmp_dp.c
index 9d61b6b8f2d4..863668642190 100644
--- a/drivers/gpu/drm/xlnx/zynqmp_dp.c
+++ b/drivers/gpu/drm/xlnx/zynqmp_dp.c
@@ -285,6 +285,7 @@ struct zynqmp_dp_config {
  * @next_bridge: The downstream bridge
  * @config: IP core configuration from DTS
  * @aux: aux channel
+ * @aux_done: Completed when we get an AUX reply or timeout
  * @phy: PHY handles for DP lanes
  * @num_lanes: number of enabled phy lanes
  * @hpd_work: hot plug detection worker
@@ -305,6 +306,7 @@ struct zynqmp_dp {
struct drm_bridge bridge;
struct work_struct hpd_work;
struct work_struct hpd_irq_work;
+   struct completion aux_done;
struct mutex lock;
 
struct drm_bridge *next_bridge;
@@ -941,12 +943,15 @@ static int zynqmp_dp_aux_cmd_submit(struct zynqmp_dp *dp, 
u32 cmd, u16 addr,
u8 *buf, u8 bytes, u8 *reply)
 {
bool is_read = (cmd & AUX_READ_BIT) ? true : false;
+   unsigned long time_left;
u32 reg, i;
 
reg = zynqmp_dp_read(dp, ZYNQMP_DP_INTERRUPT_SIGNAL_STATE);
if (reg & ZYNQMP_DP_INTERRUPT_SIGNAL_STATE_REQUEST)
return -EBUSY;
 
+   reinit_completion(>aux_done);
+
zynqmp_dp_write(dp, ZYNQMP_DP_AUX_ADDRESS, addr);
if (!is_read)
for (i = 0; i < bytes; i++)
@@ -961,17 +966,14 @@ static int zynqmp_dp_aux_cmd_submit(struct zynqmp_dp *dp, 
u32 cmd, u16 addr,
zynqmp_dp_write(dp, ZYNQMP_DP_AUX_COMMAND, reg);
 
/* Wait for reply to be delivered upto 2ms */
-   for (i = 0; ; i++) {
-   reg = zynqmp_dp_read(dp, ZYNQMP_DP_INTERRUPT_SIGNAL_STATE);
-   if (reg & ZYNQMP_DP_INTERRUPT_SIGNAL_STATE_REPLY)
-   break;
+   time_left = wait_for_completion_timeout(>aux_done,
+   msecs_to_jiffies(2));
+   if (!time_left)
+   return -ETIMEDOUT;
 
-   if (reg & ZYNQMP_DP_INTERRUPT_SIGNAL_STATE_REPLY_TIMEOUT ||
-   i == 2)
-   return -ETIMEDOUT;
-
-   usleep_range(1000, 1100);
-   }
+   reg = zynqmp_dp_read(dp, ZYNQMP_DP_INTERRUPT_SIGNAL_STATE);
+   if (reg & ZYNQMP_DP_INTERRUPT_SIGNAL_STATE_REPLY_TIMEOUT)
+   return -ETIMEDOUT;
 
reg = zynqmp_dp_read(dp, ZYNQMP_DP_AUX_REPLY_CODE);
if (reply)
@@ -1055,6 +1057,9 @@ static int zynqmp_dp_aux_init(struct zynqmp_dp *dp)
(w << ZYNQMP_DP_AUX_CLK_DIVIDER_AUX_FILTER_SHIFT) |
(rate / (1000 * 1000)));
 
+   zynqmp_dp_write(dp, ZYNQMP_DP_INT_EN, ZYNQMP_DP_INT_REPLY_RECEIVED |
+ ZYNQMP_DP_INT_REPLY_TIMEOUT);
+
dp->aux.name = "ZynqMP DP AUX";
dp->aux.dev = dp->dev;
dp->aux.drm_dev = dp->bridge.dev;
@@ -1072,6 +1077,9 @@ static int zynqmp_dp_aux_init(struct zynqmp_dp *dp)
 static void zynqmp_dp_aux_cleanup(struct zynqmp_dp *dp)
 {
drm_dp_aux_unregister(>aux);
+
+   zynqmp_dp_write(dp, ZYNQMP_DP_INT_DS, ZYNQMP_DP_INT_REPLY_RECEIVED |
+ ZYNQMP_DP_INT_REPLY_TIMEOUT);
 }
 
 /* 
-
@@ -1685,6 +1693,12 @@ static irqreturn_t zynqmp_dp_irq_handler(int irq, void 
*data)
if (status & ZYNQMP_DP_INT_HPD_IRQ)
schedule_work(>hpd_irq_work);
 
+   if (status & ZYNQMP_DP_INTERRUPT_SIGNAL_STATE_REPLY)
+   complete(>aux_done);
+
+   if (status & ZYNQMP_DP_INTERRUPT_SIGNAL_STATE_REPLY_TIMEOUT)
+   complete(>aux_done);
+
return IRQ_HANDLED;
 }
 
@@ -1708,6 +1722,7 @@ int zynqmp_dp_probe(struct zynqmp_dpsub *dpsub)
dp->dpsub = dpsub;
dp->status = connector_status_disconnected;
mutex_init(>lock);
+   init_completion(>aux_done);
 
INIT_WORK(>hpd_work, zynqmp_dp_hpd_work_func);
INIT_WORK(>hpd_irq_work, zynqmp_dp_hpd_irq_work_func);
-- 
2.35.1.1320.gc452695387.dirty



[PATCH v4 08/13] drm: zynqmp_dp: Don't retrain the link in our IRQ

2024-04-23 Thread Sean Anderson
Retraining the link can take a while, and might involve waiting for
DPCD reads/writes to complete. In preparation for unthreading the IRQ
handler, move this into its own work function.

Signed-off-by: Sean Anderson 
---

(no changes since v2)

Changes in v2:
- Document hpd_irq_work
- Split this off from the locking changes

 drivers/gpu/drm/xlnx/zynqmp_dp.c | 45 
 1 file changed, 29 insertions(+), 16 deletions(-)

diff --git a/drivers/gpu/drm/xlnx/zynqmp_dp.c b/drivers/gpu/drm/xlnx/zynqmp_dp.c
index d0168004dc22..26034672964a 100644
--- a/drivers/gpu/drm/xlnx/zynqmp_dp.c
+++ b/drivers/gpu/drm/xlnx/zynqmp_dp.c
@@ -288,6 +288,7 @@ struct zynqmp_dp_config {
  * @phy: PHY handles for DP lanes
  * @num_lanes: number of enabled phy lanes
  * @hpd_work: hot plug detection worker
+ * @hpd_irq_work: hot plug detection IRQ worker
  * @status: connection status
  * @enabled: flag to indicate if the device is enabled
  * @dpcd: DP configuration data from currently connected sink device
@@ -303,6 +304,7 @@ struct zynqmp_dp {
struct drm_dp_aux aux;
struct drm_bridge bridge;
struct work_struct hpd_work;
+   struct work_struct hpd_irq_work;
struct mutex lock;
 
struct drm_bridge *next_bridge;
@@ -1626,6 +1628,29 @@ static void zynqmp_dp_hpd_work_func(struct work_struct 
*work)
drm_bridge_hpd_notify(>bridge, status);
 }
 
+static void zynqmp_dp_hpd_irq_work_func(struct work_struct *work)
+{
+   struct zynqmp_dp *dp = container_of(work, struct zynqmp_dp,
+   hpd_irq_work);
+   u8 status[DP_LINK_STATUS_SIZE + 2];
+   int err;
+
+   mutex_lock(>lock);
+   err = drm_dp_dpcd_read(>aux, DP_SINK_COUNT, status,
+  DP_LINK_STATUS_SIZE + 2);
+   if (err < 0) {
+   dev_dbg_ratelimited(dp->dev,
+   "could not read sink status: %d\n", err);
+   } else {
+   if (status[4] & DP_LINK_STATUS_UPDATED ||
+   !drm_dp_clock_recovery_ok([2], dp->mode.lane_cnt) ||
+   !drm_dp_channel_eq_ok([2], dp->mode.lane_cnt)) {
+   zynqmp_dp_train_loop(dp);
+   }
+   }
+   mutex_unlock(>lock);
+}
+
 static irqreturn_t zynqmp_dp_irq_handler(int irq, void *data)
 {
struct zynqmp_dp *dp = (struct zynqmp_dp *)data;
@@ -1657,23 +1682,9 @@ static irqreturn_t zynqmp_dp_irq_handler(int irq, void 
*data)
if (status & ZYNQMP_DP_INT_HPD_EVENT)
schedule_work(>hpd_work);
 
-   if (status & ZYNQMP_DP_INT_HPD_IRQ) {
-   int ret;
-   u8 status[DP_LINK_STATUS_SIZE + 2];
+   if (status & ZYNQMP_DP_INT_HPD_IRQ)
+   schedule_work(>hpd_irq_work);
 
-   ret = drm_dp_dpcd_read(>aux, DP_SINK_COUNT, status,
-  DP_LINK_STATUS_SIZE + 2);
-   if (ret < 0)
-   goto handled;
-
-   if (status[4] & DP_LINK_STATUS_UPDATED ||
-   !drm_dp_clock_recovery_ok([2], dp->mode.lane_cnt) ||
-   !drm_dp_channel_eq_ok([2], dp->mode.lane_cnt)) {
-   zynqmp_dp_train_loop(dp);
-   }
-   }
-
-handled:
return IRQ_HANDLED;
 }
 
@@ -1699,6 +1710,7 @@ int zynqmp_dp_probe(struct zynqmp_dpsub *dpsub)
mutex_init(>lock);
 
INIT_WORK(>hpd_work, zynqmp_dp_hpd_work_func);
+   INIT_WORK(>hpd_irq_work, zynqmp_dp_hpd_irq_work_func);
 
/* Acquire all resources (IOMEM, IRQ and PHYs). */
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "dp");
@@ -1803,6 +1815,7 @@ void zynqmp_dp_remove(struct zynqmp_dpsub *dpsub)
zynqmp_dp_write(dp, ZYNQMP_DP_INT_DS, ZYNQMP_DP_INT_ALL);
disable_irq(dp->irq);
 
+   cancel_work_sync(>hpd_irq_work);
cancel_work_sync(>hpd_work);
 
zynqmp_dp_write(dp, ZYNQMP_DP_TRANSMITTER_ENABLE, 0);
-- 
2.35.1.1320.gc452695387.dirty



[PATCH v4 02/13] drm: xlnx: Fix kerneldoc

2024-04-23 Thread Sean Anderson
Fix a few errors in the kerneldoc. Mostly this addresses missing/renamed
members.

Signed-off-by: Sean Anderson 
Reviewed-by: Tomi Valkeinen 
---

(no changes since v3)

Changes in v3:
- Split off documentation for base pointers to previous commit

Changes in v2:
- New

 drivers/gpu/drm/xlnx/zynqmp_dpsub.h | 1 +
 drivers/gpu/drm/xlnx/zynqmp_kms.h   | 4 ++--
 2 files changed, 3 insertions(+), 2 deletions(-)

diff --git a/drivers/gpu/drm/xlnx/zynqmp_dpsub.h 
b/drivers/gpu/drm/xlnx/zynqmp_dpsub.h
index 09ea01878f2a..b18554467e9c 100644
--- a/drivers/gpu/drm/xlnx/zynqmp_dpsub.h
+++ b/drivers/gpu/drm/xlnx/zynqmp_dpsub.h
@@ -53,6 +53,7 @@ enum zynqmp_dpsub_format {
  * @drm: The DRM/KMS device data
  * @bridge: The DP encoder bridge
  * @disp: The display controller
+ * @layers: Video and graphics layers
  * @dp: The DisplayPort controller
  * @dma_align: DMA alignment constraint (must be a power of 2)
  */
diff --git a/drivers/gpu/drm/xlnx/zynqmp_kms.h 
b/drivers/gpu/drm/xlnx/zynqmp_kms.h
index 01be96b00e3f..cb13c6b8008e 100644
--- a/drivers/gpu/drm/xlnx/zynqmp_kms.h
+++ b/drivers/gpu/drm/xlnx/zynqmp_kms.h
@@ -22,9 +22,9 @@
 struct zynqmp_dpsub;
 
 /**
- * struct zynqmp_dpsub - ZynqMP DisplayPort Subsystem DRM/KMS data
+ * struct zynqmp_dpsub_drm - ZynqMP DisplayPort Subsystem DRM/KMS data
  * @dpsub: Backpointer to the DisplayPort subsystem
- * @drm: The DRM/KMS device
+ * @dev: The DRM/KMS device
  * @planes: The DRM planes
  * @crtc: The DRM CRTC
  * @encoder: The dummy DRM encoder
-- 
2.35.1.1320.gc452695387.dirty



[PATCH v4 11/13] drm: zynqmp_dp: Split off several helper functions

2024-04-23 Thread Sean Anderson
In preparation for supporting compliance testing, split off several
helper functions. No functional change intended.

Signed-off-by: Sean Anderson 
Reviewed-by: Laurent Pinchart 
Reviewed-by: Tomi Valkeinen 
---

(no changes since v1)

 drivers/gpu/drm/xlnx/zynqmp_dp.c | 49 ++--
 1 file changed, 34 insertions(+), 15 deletions(-)

diff --git a/drivers/gpu/drm/xlnx/zynqmp_dp.c b/drivers/gpu/drm/xlnx/zynqmp_dp.c
index 863668642190..d5ee165046f8 100644
--- a/drivers/gpu/drm/xlnx/zynqmp_dp.c
+++ b/drivers/gpu/drm/xlnx/zynqmp_dp.c
@@ -635,6 +635,7 @@ static void zynqmp_dp_adjust_train(struct zynqmp_dp *dp,
 /**
  * zynqmp_dp_update_vs_emph - Update the training values
  * @dp: DisplayPort IP core structure
+ * @train_set: A set of training values
  *
  * Update the training values based on the request from sink. The mapped values
  * are predefined, and values(vs, pe, pc) are from the device manual.
@@ -642,12 +643,12 @@ static void zynqmp_dp_adjust_train(struct zynqmp_dp *dp,
  * Return: 0 if vs and emph are updated successfully, or the error code 
returned
  * by drm_dp_dpcd_write().
  */
-static int zynqmp_dp_update_vs_emph(struct zynqmp_dp *dp)
+static int zynqmp_dp_update_vs_emph(struct zynqmp_dp *dp, u8 *train_set)
 {
unsigned int i;
int ret;
 
-   ret = drm_dp_dpcd_write(>aux, DP_TRAINING_LANE0_SET, dp->train_set,
+   ret = drm_dp_dpcd_write(>aux, DP_TRAINING_LANE0_SET, train_set,
dp->mode.lane_cnt);
if (ret < 0)
return ret;
@@ -655,7 +656,7 @@ static int zynqmp_dp_update_vs_emph(struct zynqmp_dp *dp)
for (i = 0; i < dp->mode.lane_cnt; i++) {
u32 reg = ZYNQMP_DP_SUB_TX_PHY_PRECURSOR_LANE_0 + i * 4;
union phy_configure_opts opts = { 0 };
-   u8 train = dp->train_set[i];
+   u8 train = train_set[i];
 
opts.dp.voltage[0] = (train & DP_TRAIN_VOLTAGE_SWING_MASK)
   >> DP_TRAIN_VOLTAGE_SWING_SHIFT;
@@ -699,7 +700,7 @@ static int zynqmp_dp_link_train_cr(struct zynqmp_dp *dp)
 * So, This loop should exit before 512 iterations
 */
for (max_tries = 0; max_tries < 512; max_tries++) {
-   ret = zynqmp_dp_update_vs_emph(dp);
+   ret = zynqmp_dp_update_vs_emph(dp, dp->train_set);
if (ret)
return ret;
 
@@ -764,7 +765,7 @@ static int zynqmp_dp_link_train_ce(struct zynqmp_dp *dp)
return ret;
 
for (tries = 0; tries < DP_MAX_TRAINING_TRIES; tries++) {
-   ret = zynqmp_dp_update_vs_emph(dp);
+   ret = zynqmp_dp_update_vs_emph(dp, dp->train_set);
if (ret)
return ret;
 
@@ -787,28 +788,29 @@ static int zynqmp_dp_link_train_ce(struct zynqmp_dp *dp)
 }
 
 /**
- * zynqmp_dp_train - Train the link
+ * zynqmp_dp_setup() - Set up major link parameters
  * @dp: DisplayPort IP core structure
+ * @bw_code: The link bandwidth as a multiple of 270 MHz
+ * @lane_cnt: The number of lanes to use
+ * @enhanced: Use enhanced framing
+ * @downspread: Enable spread-spectrum clocking
  *
- * Return: 0 if all trains are done successfully, or corresponding error code.
+ * Return: 0 on success, or -errno on failure
  */
-static int zynqmp_dp_train(struct zynqmp_dp *dp)
+static int zynqmp_dp_setup(struct zynqmp_dp *dp, u8 bw_code, u8 lane_cnt,
+  bool enhanced, bool downspread)
 {
u32 reg;
-   u8 bw_code = dp->mode.bw_code;
-   u8 lane_cnt = dp->mode.lane_cnt;
u8 aux_lane_cnt = lane_cnt;
-   bool enhanced;
int ret;
 
zynqmp_dp_write(dp, ZYNQMP_DP_LANE_COUNT_SET, lane_cnt);
-   enhanced = drm_dp_enhanced_frame_cap(dp->dpcd);
if (enhanced) {
zynqmp_dp_write(dp, ZYNQMP_DP_ENHANCED_FRAME_EN, 1);
aux_lane_cnt |= DP_LANE_COUNT_ENHANCED_FRAME_EN;
}
 
-   if (dp->dpcd[3] & 0x1) {
+   if (downspread) {
zynqmp_dp_write(dp, ZYNQMP_DP_DOWNSPREAD_CTL, 1);
drm_dp_dpcd_writeb(>aux, DP_DOWNSPREAD_CTRL,
   DP_SPREAD_AMP_0_5);
@@ -851,8 +853,25 @@ static int zynqmp_dp_train(struct zynqmp_dp *dp)
}
 
zynqmp_dp_write(dp, ZYNQMP_DP_PHY_CLOCK_SELECT, reg);
-   ret = zynqmp_dp_phy_ready(dp);
-   if (ret < 0)
+   return zynqmp_dp_phy_ready(dp);
+}
+
+
+/**
+ * zynqmp_dp_train - Train the link
+ * @dp: DisplayPort IP core structure
+ *
+ * Return: 0 if all trains are done successfully, or corresponding error code.
+ */
+static int zynqmp_dp_train(struct zynqmp_dp *dp)
+{
+   int ret;
+
+   ret = zynqmp_dp_setup(dp, dp->mode.bw_code, dp->mode.lane_cnt,
+ drm_dp_enhanced_frame_cap(dp->dpcd),
+ dp->dpcd[DP_MAX_DOW

[PATCH v4 07/13] drm: zynqmp_dp: Add locking

2024-04-23 Thread Sean Anderson
Add some locking to prevent the IRQ/workers/bridge API calls from stepping
on each other's toes. This lock protects:

- Non-atomic registers configuring the link. That is, everything but the
  IRQ registers (since these are accessed in an atomic fashion), and the DP
  AUX registers (since these don't affect the link). We also access AUX
  while holding this lock, so it would be very tricky to support.
- Link configuration. This is effectively everything in zynqmp_dp which
  isn't read-only after probe time. So from next_bridge onward.

This lock is designed to protect configuration changes so we don't have to
do anything tricky. Configuration should never be in the hot path, so I'm
not worried about performance.

Signed-off-by: Sean Anderson 
---

(no changes since v2)

Changes in v2:
- Split off the HPD IRQ work into another commit
- Expand the commit message

 drivers/gpu/drm/xlnx/zynqmp_dp.c | 18 ++
 1 file changed, 18 insertions(+)

diff --git a/drivers/gpu/drm/xlnx/zynqmp_dp.c b/drivers/gpu/drm/xlnx/zynqmp_dp.c
index 677db546169f..d0168004dc22 100644
--- a/drivers/gpu/drm/xlnx/zynqmp_dp.c
+++ b/drivers/gpu/drm/xlnx/zynqmp_dp.c
@@ -279,6 +279,7 @@ struct zynqmp_dp_config {
  * @dpsub: Display subsystem
  * @iomem: device I/O memory for register access
  * @reset: reset controller
+ * @lock: Mutex protecting this struct and register access (but not AUX)
  * @irq: irq
  * @bridge: DRM bridge for the DP encoder
  * @next_bridge: The downstream bridge
@@ -293,11 +294,16 @@ struct zynqmp_dp_config {
  * @link_config: common link configuration between IP core and sink device
  * @mode: current mode between IP core and sink device
  * @train_set: set of training data
+ *
+ * @lock covers the link configuration in this struct and the device's
+ * registers. It does not cover @aux. It is not strictly required for any of
+ * the members which are only modified at probe/remove time (e.g. @dev).
  */
 struct zynqmp_dp {
struct drm_dp_aux aux;
struct drm_bridge bridge;
struct work_struct hpd_work;
+   struct mutex lock;
 
struct drm_bridge *next_bridge;
struct device *dev;
@@ -1371,8 +1377,10 @@ zynqmp_dp_bridge_mode_valid(struct drm_bridge *bridge,
}
 
/* Check with link rate and lane count */
+   mutex_lock(>lock);
rate = zynqmp_dp_max_rate(dp->link_config.max_rate,
  dp->link_config.max_lanes, dp->config.bpp);
+   mutex_unlock(>lock);
if (mode->clock > rate) {
dev_dbg(dp->dev, "filtered mode %s for high pixel rate\n",
mode->name);
@@ -1399,6 +1407,7 @@ static void zynqmp_dp_bridge_atomic_enable(struct 
drm_bridge *bridge,
 
pm_runtime_get_sync(dp->dev);
 
+   mutex_lock(>lock);
zynqmp_dp_disp_enable(dp, old_bridge_state);
 
/*
@@ -1459,6 +1468,7 @@ static void zynqmp_dp_bridge_atomic_enable(struct 
drm_bridge *bridge,
zynqmp_dp_write(dp, ZYNQMP_DP_SOFTWARE_RESET,
ZYNQMP_DP_SOFTWARE_RESET_ALL);
zynqmp_dp_write(dp, ZYNQMP_DP_MAIN_STREAM_ENABLE, 1);
+   mutex_unlock(>lock);
 }
 
 static void zynqmp_dp_bridge_atomic_disable(struct drm_bridge *bridge,
@@ -1466,6 +1476,7 @@ static void zynqmp_dp_bridge_atomic_disable(struct 
drm_bridge *bridge,
 {
struct zynqmp_dp *dp = bridge_to_dp(bridge);
 
+   mutex_lock(>lock);
dp->enabled = false;
cancel_work(>hpd_work);
zynqmp_dp_write(dp, ZYNQMP_DP_MAIN_STREAM_ENABLE, 0);
@@ -1476,6 +1487,7 @@ static void zynqmp_dp_bridge_atomic_disable(struct 
drm_bridge *bridge,
zynqmp_dp_write(dp, ZYNQMP_DP_TX_AUDIO_CONTROL, 0);
 
zynqmp_dp_disp_disable(dp, old_bridge_state);
+   mutex_unlock(>lock);
 
pm_runtime_put_sync(dp->dev);
 }
@@ -1518,6 +1530,8 @@ static enum drm_connector_status 
zynqmp_dp_bridge_detect(struct drm_bridge *brid
u32 state, i;
int ret;
 
+   mutex_lock(>lock);
+
/*
 * This is from heuristic. It takes some delay (ex, 100 ~ 500 msec) to
 * get the HPD signal with some monitors.
@@ -1545,11 +1559,13 @@ static enum drm_connector_status 
zynqmp_dp_bridge_detect(struct drm_bridge *brid
   dp->num_lanes);
 
dp->status = connector_status_connected;
+   mutex_unlock(>lock);
return connector_status_connected;
}
 
 disconnected:
dp->status = connector_status_disconnected;
+   mutex_unlock(>lock);
return connector_status_disconnected;
 }
 
@@ -1680,6 +1696,7 @@ int zynqmp_dp_probe(struct zynqmp_dpsub *dpsub)
dp->dev = >dev;
dp->dpsub = dpsub;
dp->status = connector_status_disconnected;
+   mutex_init(>lock);
 
INIT_WORK(>hpd_work, zynqmp_dp_hpd_work_func);
 
@@ -17

[PATCH v4 06/13] drm: zynqmp_dp: Don't delay work

2024-04-23 Thread Sean Anderson
We always call scheduled_delayed_work with no delay, so just use a
non-delayed work_struct instead.

Signed-off-by: Sean Anderson 
Reviewed-by: Tomi Valkeinen 
---

(no changes since v3)

Changes in v3:
- New

 drivers/gpu/drm/xlnx/zynqmp_dp.c | 13 ++---
 1 file changed, 6 insertions(+), 7 deletions(-)

diff --git a/drivers/gpu/drm/xlnx/zynqmp_dp.c b/drivers/gpu/drm/xlnx/zynqmp_dp.c
index 6e8478d58b02..677db546169f 100644
--- a/drivers/gpu/drm/xlnx/zynqmp_dp.c
+++ b/drivers/gpu/drm/xlnx/zynqmp_dp.c
@@ -297,7 +297,7 @@ struct zynqmp_dp_config {
 struct zynqmp_dp {
struct drm_dp_aux aux;
struct drm_bridge bridge;
-   struct delayed_work hpd_work;
+   struct work_struct hpd_work;
 
struct drm_bridge *next_bridge;
struct device *dev;
@@ -1467,7 +1467,7 @@ static void zynqmp_dp_bridge_atomic_disable(struct 
drm_bridge *bridge,
struct zynqmp_dp *dp = bridge_to_dp(bridge);
 
dp->enabled = false;
-   cancel_delayed_work(>hpd_work);
+   cancel_work(>hpd_work);
zynqmp_dp_write(dp, ZYNQMP_DP_MAIN_STREAM_ENABLE, 0);
drm_dp_dpcd_writeb(>aux, DP_SET_POWER, DP_SET_POWER_D3);
zynqmp_dp_write(dp, ZYNQMP_DP_TX_PHY_POWER_DOWN,
@@ -1603,8 +1603,7 @@ void zynqmp_dp_disable_vblank(struct zynqmp_dp *dp)
 
 static void zynqmp_dp_hpd_work_func(struct work_struct *work)
 {
-   struct zynqmp_dp *dp = container_of(work, struct zynqmp_dp,
-   hpd_work.work);
+   struct zynqmp_dp *dp = container_of(work, struct zynqmp_dp, hpd_work);
enum drm_connector_status status;
 
status = zynqmp_dp_bridge_detect(>bridge);
@@ -1640,7 +1639,7 @@ static irqreturn_t zynqmp_dp_irq_handler(int irq, void 
*data)
zynqmp_dpsub_drm_handle_vblank(dp->dpsub);
 
if (status & ZYNQMP_DP_INT_HPD_EVENT)
-   schedule_delayed_work(>hpd_work, 0);
+   schedule_work(>hpd_work);
 
if (status & ZYNQMP_DP_INT_HPD_IRQ) {
int ret;
@@ -1682,7 +1681,7 @@ int zynqmp_dp_probe(struct zynqmp_dpsub *dpsub)
dp->dpsub = dpsub;
dp->status = connector_status_disconnected;
 
-   INIT_DELAYED_WORK(>hpd_work, zynqmp_dp_hpd_work_func);
+   INIT_WORK(>hpd_work, zynqmp_dp_hpd_work_func);
 
/* Acquire all resources (IOMEM, IRQ and PHYs). */
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "dp");
@@ -1787,7 +1786,7 @@ void zynqmp_dp_remove(struct zynqmp_dpsub *dpsub)
zynqmp_dp_write(dp, ZYNQMP_DP_INT_DS, ZYNQMP_DP_INT_ALL);
disable_irq(dp->irq);
 
-   cancel_delayed_work_sync(>hpd_work);
+   cancel_work_sync(>hpd_work);
 
zynqmp_dp_write(dp, ZYNQMP_DP_TRANSMITTER_ENABLE, 0);
zynqmp_dp_write(dp, ZYNQMP_DP_INT_DS, 0x);
-- 
2.35.1.1320.gc452695387.dirty



[PATCH v4 04/13] drm: zynqmp_dp: Adjust training values per-lane

2024-04-23 Thread Sean Anderson
The feedback we get from the DPRX is per-lane. Make changes using this
information, instead of picking the maximum values from all lanes. This
results in more-consistent training on marginal links.

Signed-off-by: Sean Anderson 
Reviewed-by: Tomi Valkeinen 
---

(no changes since v1)

 drivers/gpu/drm/xlnx/zynqmp_dp.c | 23 ---
 1 file changed, 8 insertions(+), 15 deletions(-)

diff --git a/drivers/gpu/drm/xlnx/zynqmp_dp.c b/drivers/gpu/drm/xlnx/zynqmp_dp.c
index fdea1a9710de..79afe4358d06 100644
--- a/drivers/gpu/drm/xlnx/zynqmp_dp.c
+++ b/drivers/gpu/drm/xlnx/zynqmp_dp.c
@@ -605,28 +605,21 @@ static void zynqmp_dp_adjust_train(struct zynqmp_dp *dp,
   u8 link_status[DP_LINK_STATUS_SIZE])
 {
u8 *train_set = dp->train_set;
-   u8 voltage = 0, preemphasis = 0;
u8 i;
 
for (i = 0; i < dp->mode.lane_cnt; i++) {
-   u8 v = drm_dp_get_adjust_request_voltage(link_status, i);
-   u8 p = drm_dp_get_adjust_request_pre_emphasis(link_status, i);
+   u8 voltage = drm_dp_get_adjust_request_voltage(link_status, i);
+   u8 preemphasis =
+   drm_dp_get_adjust_request_pre_emphasis(link_status, i);
 
-   if (v > voltage)
-   voltage = v;
+   if (voltage >= DP_TRAIN_VOLTAGE_SWING_LEVEL_3)
+   voltage |= DP_TRAIN_MAX_SWING_REACHED;
 
-   if (p > preemphasis)
-   preemphasis = p;
-   }
+   if (preemphasis >= DP_TRAIN_PRE_EMPH_LEVEL_2)
+   preemphasis |= DP_TRAIN_MAX_PRE_EMPHASIS_REACHED;
 
-   if (voltage >= DP_TRAIN_VOLTAGE_SWING_LEVEL_3)
-   voltage |= DP_TRAIN_MAX_SWING_REACHED;
-
-   if (preemphasis >= DP_TRAIN_PRE_EMPH_LEVEL_2)
-   preemphasis |= DP_TRAIN_MAX_PRE_EMPHASIS_REACHED;
-
-   for (i = 0; i < dp->mode.lane_cnt; i++)
train_set[i] = voltage | preemphasis;
+   }
 }
 
 /**
-- 
2.35.1.1320.gc452695387.dirty



[PATCH v4 01/13] drm: xlnx: Store base pointers in zynqmp_disp directly

2024-04-23 Thread Sean Anderson
The blend, avbuf, and audio members of zynqmp_disp are anonymous structs
with only one member each. This is rather pointless, so move the members
up a level.

Signed-off-by: Sean Anderson 
Reviewed-by: Tomi Valkeinen 
---

(no changes since v3)

Changes in v3:
- New

 drivers/gpu/drm/xlnx/zynqmp_disp.c | 44 +-
 1 file changed, 19 insertions(+), 25 deletions(-)

diff --git a/drivers/gpu/drm/xlnx/zynqmp_disp.c 
b/drivers/gpu/drm/xlnx/zynqmp_disp.c
index 8a39b3accce5..ca164a750ee9 100644
--- a/drivers/gpu/drm/xlnx/zynqmp_disp.c
+++ b/drivers/gpu/drm/xlnx/zynqmp_disp.c
@@ -128,24 +128,18 @@ struct zynqmp_disp_layer {
  * struct zynqmp_disp - Display controller
  * @dev: Device structure
  * @dpsub: Display subsystem
- * @blend.base: Register I/O base address for the blender
- * @avbuf.base: Register I/O base address for the audio/video buffer manager
- * @audio.base: Registers I/O base address for the audio mixer
+ * @blend: Register I/O base address for the blender
+ * @avbuf: Register I/O base address for the audio/video buffer manager
+ * @audio: Registers I/O base address for the audio mixer
  * @layers: Layers (planes)
  */
 struct zynqmp_disp {
struct device *dev;
struct zynqmp_dpsub *dpsub;
 
-   struct {
-   void __iomem *base;
-   } blend;
-   struct {
-   void __iomem *base;
-   } avbuf;
-   struct {
-   void __iomem *base;
-   } audio;
+   void __iomem *blend;
+   void __iomem *avbuf;
+   void __iomem *audio;
 
struct zynqmp_disp_layer layers[ZYNQMP_DPSUB_NUM_LAYERS];
 };
@@ -356,12 +350,12 @@ static const struct zynqmp_disp_format avbuf_gfx_fmts[] = 
{
 
 static u32 zynqmp_disp_avbuf_read(struct zynqmp_disp *disp, int reg)
 {
-   return readl(disp->avbuf.base + reg);
+   return readl(disp->avbuf + reg);
 }
 
 static void zynqmp_disp_avbuf_write(struct zynqmp_disp *disp, int reg, u32 val)
 {
-   writel(val, disp->avbuf.base + reg);
+   writel(val, disp->avbuf + reg);
 }
 
 static bool zynqmp_disp_layer_is_video(const struct zynqmp_disp_layer *layer)
@@ -587,7 +581,7 @@ static void zynqmp_disp_avbuf_disable(struct zynqmp_disp 
*disp)
 
 static void zynqmp_disp_blend_write(struct zynqmp_disp *disp, int reg, u32 val)
 {
-   writel(val, disp->blend.base + reg);
+   writel(val, disp->blend + reg);
 }
 
 /*
@@ -813,7 +807,7 @@ static void zynqmp_disp_blend_layer_disable(struct 
zynqmp_disp *disp,
 
 static void zynqmp_disp_audio_write(struct zynqmp_disp *disp, int reg, u32 val)
 {
-   writel(val, disp->audio.base + reg);
+   writel(val, disp->audio + reg);
 }
 
 /**
@@ -1237,21 +1231,21 @@ int zynqmp_disp_probe(struct zynqmp_dpsub *dpsub)
disp->dev = >dev;
disp->dpsub = dpsub;
 
-   disp->blend.base = devm_platform_ioremap_resource_byname(pdev, "blend");
-   if (IS_ERR(disp->blend.base)) {
-   ret = PTR_ERR(disp->blend.base);
+   disp->blend = devm_platform_ioremap_resource_byname(pdev, "blend");
+   if (IS_ERR(disp->blend)) {
+   ret = PTR_ERR(disp->blend);
goto error;
}
 
-   disp->avbuf.base = devm_platform_ioremap_resource_byname(pdev, 
"av_buf");
-   if (IS_ERR(disp->avbuf.base)) {
-   ret = PTR_ERR(disp->avbuf.base);
+   disp->avbuf = devm_platform_ioremap_resource_byname(pdev, "av_buf");
+   if (IS_ERR(disp->avbuf)) {
+   ret = PTR_ERR(disp->avbuf);
goto error;
}
 
-   disp->audio.base = devm_platform_ioremap_resource_byname(pdev, "aud");
-   if (IS_ERR(disp->audio.base)) {
-   ret = PTR_ERR(disp->audio.base);
+   disp->audio = devm_platform_ioremap_resource_byname(pdev, "aud");
+   if (IS_ERR(disp->audio)) {
+   ret = PTR_ERR(disp->audio);
goto error;
}
 
-- 
2.35.1.1320.gc452695387.dirty



[PATCH v4 03/13] drm: zynqmp_dp: Downgrade log level for aux retries message

2024-04-23 Thread Sean Anderson
Enable this message for verbose debugging only as it is otherwise
printed after every AUX message, quickly filling the log buffer.

Signed-off-by: Sean Anderson 
Reviewed-by: Laurent Pinchart 
Reviewed-by: Tomi Valkeinen 
---

(no changes since v1)

 drivers/gpu/drm/xlnx/zynqmp_dp.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/gpu/drm/xlnx/zynqmp_dp.c b/drivers/gpu/drm/xlnx/zynqmp_dp.c
index 8a15d18a65a6..fdea1a9710de 100644
--- a/drivers/gpu/drm/xlnx/zynqmp_dp.c
+++ b/drivers/gpu/drm/xlnx/zynqmp_dp.c
@@ -1006,7 +1006,7 @@ zynqmp_dp_aux_transfer(struct drm_dp_aux *aux, struct 
drm_dp_aux_msg *msg)
   msg->buffer, msg->size,
   >reply);
if (!ret) {
-   dev_dbg(dp->dev, "aux %d retries\n", i);
+   dev_vdbg(dp->dev, "aux %d retries\n", i);
return msg->size;
}
 
-- 
2.35.1.1320.gc452695387.dirty



Re: [PATCH v3 00/13] drm: zynqmp_dp: IRQ cleanups and debugfs support

2024-04-23 Thread Sean Anderson
On 4/23/24 11:30, Tomi Valkeinen wrote:
> On 23/04/2024 17:59, Sean Anderson wrote:
>> On 4/23/24 09:33, Tomi Valkeinen wrote:
>>> Hi Sean,
>>>
>>> On 22/04/2024 21:45, Sean Anderson wrote:
>>>> This series cleans up the zyqnmp_dp IRQ and locking situation. Once
>>>> that's done, it adds debugfs support. The intent is to enable compliance
>>>> testing or to help debug signal-integrity issues.
>>>>
>>>> Last time I discussed converting the HPD work(s) to a threaded IRQ. I
>>>> did not end up doing that for this series since the steps would be
>>>>
>>>> - Add locking
>>>> - Move link retraining to a work function
>>>> - Harden the IRQ
>>>> - Merge the works into a threaded IRQ (omitted)
>>>>
>>>> Which with the exception of the final step is the same as leaving those
>>>> works as-is. Conversion to a threaded IRQ can be done as a follow-up.
>>>
>>> What is the base for this series? I'm having trouble applying it.
>>>
>>> I managed to mostly apply it, but I see the board hang when I unload the 
>>> modules. I didn't debug it as it might as well be caused by my conflict 
>>> resolution.
>>
>> The base is v6.8-rc1, but it should probably be v6.9. I can rebase and 
>> resend.
> 
> Did you have something extra in your branch before the series? I got "error: 
> sha1 information is lacking or useless".

Nope.

--Sean


Re: [PATCH v3 00/13] drm: zynqmp_dp: IRQ cleanups and debugfs support

2024-04-23 Thread Sean Anderson
On 4/23/24 09:33, Tomi Valkeinen wrote:
> Hi Sean,
> 
> On 22/04/2024 21:45, Sean Anderson wrote:
>> This series cleans up the zyqnmp_dp IRQ and locking situation. Once
>> that's done, it adds debugfs support. The intent is to enable compliance
>> testing or to help debug signal-integrity issues.
>>
>> Last time I discussed converting the HPD work(s) to a threaded IRQ. I
>> did not end up doing that for this series since the steps would be
>>
>> - Add locking
>> - Move link retraining to a work function
>> - Harden the IRQ
>> - Merge the works into a threaded IRQ (omitted)
>>
>> Which with the exception of the final step is the same as leaving those
>> works as-is. Conversion to a threaded IRQ can be done as a follow-up.
> 
> What is the base for this series? I'm having trouble applying it.
> 
> I managed to mostly apply it, but I see the board hang when I unload the 
> modules. I didn't debug it as it might as well be caused by my conflict 
> resolution.

The base is v6.8-rc1, but it should probably be v6.9. I can rebase and resend.

--Sean

>> Changes in v3:
>> - Store base pointers in zynqmp_disp directly
>> - Don't delay work
>> - Convert to a hard IRQ
>> - Use AUX IRQs instead of polling
>> - Take dp->lock in zynqmp_dp_hpd_work_func
>>
>> Changes in v2:
>> - Fix kerneldoc
>> - Rearrange zynqmp_dp for better padding
>> - Split off the HPD IRQ work into another commit
>> - Expand the commit message
>> - Document hpd_irq_work
>> - Document debugfs files
>> - Add ignore_aux_errors and ignore_hpd debugfs files to replace earlier
>>    implicit functionality
>> - Attempt to fix unreproducable, spurious build warning
>> - Drop "Optionally ignore DPCD errors" in favor of a debugfs file
>>    directly affecting zynqmp_dp_aux_transfer.
>>
>> Sean Anderson (13):
>>    drm: xlnx: Store base pointers in zynqmp_disp directly
>>    drm: xlnx: Fix kerneldoc
>>    drm: zynqmp_dp: Downgrade log level for aux retries message
>>    drm: zynqmp_dp: Adjust training values per-lane
>>    drm: zynqmp_dp: Rearrange zynqmp_dp for better padding
>>    drm: zynqmp_dp: Don't delay work
>>    drm: zynqmp_dp: Add locking
>>    drm: zynqmp_dp: Don't retrain the link in our IRQ
>>    drm: zynqmp_dp: Convert to a hard IRQ
>>    drm: zynqmp_dp: Use AUX IRQs instead of polling
>>    drm: zynqmp_dp: Split off several helper functions
>>    drm: zynqmp_dp: Take dp->lock in zynqmp_dp_hpd_work_func
>>    drm: zynqmp_dp: Add debugfs interface for compliance testing
>>
>>   Documentation/gpu/drivers.rst   |   1 +
>>   Documentation/gpu/zynqmp.rst    | 149 +
>>   MAINTAINERS |   1 +
>>   drivers/gpu/drm/xlnx/zynqmp_disp.c  |  44 +-
>>   drivers/gpu/drm/xlnx/zynqmp_dp.c    | 909 +---
>>   drivers/gpu/drm/xlnx/zynqmp_dpsub.h |   1 +
>>   drivers/gpu/drm/xlnx/zynqmp_kms.h   |   4 +-
>>   7 files changed, 1000 insertions(+), 109 deletions(-)
>>   create mode 100644 Documentation/gpu/zynqmp.rst
>>
> 



Re: [PATCH v3 00/13] drm: zynqmp_dp: IRQ cleanups and debugfs support

2024-04-22 Thread Sean Anderson
On 4/22/24 14:45, Sean Anderson wrote:
> This series cleans up the zyqnmp_dp IRQ and locking situation. Once
> that's done, it adds debugfs support. The intent is to enable compliance
> testing or to help debug signal-integrity issues.
> 
> Last time I discussed converting the HPD work(s) to a threaded IRQ. I
> did not end up doing that for this series since the steps would be
> 
> - Add locking
> - Move link retraining to a work function
> - Harden the IRQ
> - Merge the works into a threaded IRQ (omitted)
> 
> Which with the exception of the final step is the same as leaving those
> works as-is. Conversion to a threaded IRQ can be done as a follow-up.
> 
> Changes in v3:
> - Store base pointers in zynqmp_disp directly
> - Don't delay work
> - Convert to a hard IRQ
> - Use AUX IRQs instead of polling
> - Take dp->lock in zynqmp_dp_hpd_work_func
> 
> Changes in v2:
> - Fix kerneldoc
> - Rearrange zynqmp_dp for better padding
> - Split off the HPD IRQ work into another commit
> - Expand the commit message
> - Document hpd_irq_work
> - Document debugfs files
> - Add ignore_aux_errors and ignore_hpd debugfs files to replace earlier
>   implicit functionality
> - Attempt to fix unreproducable, spurious build warning
> - Drop "Optionally ignore DPCD errors" in favor of a debugfs file
>   directly affecting zynqmp_dp_aux_transfer.
> 
> Sean Anderson (13):
>   drm: xlnx: Store base pointers in zynqmp_disp directly
>   drm: xlnx: Fix kerneldoc
>   drm: zynqmp_dp: Downgrade log level for aux retries message
>   drm: zynqmp_dp: Adjust training values per-lane
>   drm: zynqmp_dp: Rearrange zynqmp_dp for better padding
>   drm: zynqmp_dp: Don't delay work
>   drm: zynqmp_dp: Add locking
>   drm: zynqmp_dp: Don't retrain the link in our IRQ
>   drm: zynqmp_dp: Convert to a hard IRQ
>   drm: zynqmp_dp: Use AUX IRQs instead of polling
>   drm: zynqmp_dp: Split off several helper functions
>   drm: zynqmp_dp: Take dp->lock in zynqmp_dp_hpd_work_func
>   drm: zynqmp_dp: Add debugfs interface for compliance testing
> 
>  Documentation/gpu/drivers.rst   |   1 +
>  Documentation/gpu/zynqmp.rst| 149 +
>  MAINTAINERS |   1 +
>  drivers/gpu/drm/xlnx/zynqmp_disp.c  |  44 +-
>  drivers/gpu/drm/xlnx/zynqmp_dp.c| 909 +---
>  drivers/gpu/drm/xlnx/zynqmp_dpsub.h |   1 +
>  drivers/gpu/drm/xlnx/zynqmp_kms.h   |   4 +-
>  7 files changed, 1000 insertions(+), 109 deletions(-)
>  create mode 100644 Documentation/gpu/zynqmp.rst
> 

+CC Tomi, who I forgot to add initially...


[PATCH v3 12/13] drm: zynqmp_dp: Take dp->lock in zynqmp_dp_hpd_work_func

2024-04-22 Thread Sean Anderson
Add a non-locking version of zynqmp_dp_bridge_detect and use it in
zynqmp_dp_hpd_work_func so we can take the lock explicitly. This will
make it easier to check for hpd_ignore when we add debugfs support.

Signed-off-by: Sean Anderson 
---

Changes in v3:
- New

 drivers/gpu/drm/xlnx/zynqmp_dp.c | 25 +++--
 1 file changed, 19 insertions(+), 6 deletions(-)

diff --git a/drivers/gpu/drm/xlnx/zynqmp_dp.c b/drivers/gpu/drm/xlnx/zynqmp_dp.c
index 364caaf26f49..dea4b8a819fd 100644
--- a/drivers/gpu/drm/xlnx/zynqmp_dp.c
+++ b/drivers/gpu/drm/xlnx/zynqmp_dp.c
@@ -1552,14 +1552,13 @@ static int zynqmp_dp_bridge_atomic_check(struct 
drm_bridge *bridge,
return 0;
 }
 
-static enum drm_connector_status zynqmp_dp_bridge_detect(struct drm_bridge 
*bridge)
+static enum drm_connector_status __zynqmp_dp_bridge_detect(struct zynqmp_dp 
*dp)
 {
-   struct zynqmp_dp *dp = bridge_to_dp(bridge);
struct zynqmp_dp_link_config *link_config = >link_config;
u32 state, i;
int ret;
 
-   mutex_lock(>lock);
+   lockdep_assert_held(>lock);
 
/*
 * This is from heuristic. It takes some delay (ex, 100 ~ 500 msec) to
@@ -1588,16 +1587,27 @@ static enum drm_connector_status 
zynqmp_dp_bridge_detect(struct drm_bridge *brid
   dp->num_lanes);
 
dp->status = connector_status_connected;
-   mutex_unlock(>lock);
return connector_status_connected;
}
 
 disconnected:
dp->status = connector_status_disconnected;
-   mutex_unlock(>lock);
return connector_status_disconnected;
 }
 
+
+static enum drm_connector_status zynqmp_dp_bridge_detect(struct drm_bridge 
*bridge)
+{
+   struct zynqmp_dp *dp = bridge_to_dp(bridge);
+   enum drm_connector_status ret;
+
+   mutex_lock(>lock);
+   ret = __zynqmp_dp_bridge_detect(dp);
+   mutex_unlock(>lock);
+
+   return ret;
+}
+
 static struct edid *zynqmp_dp_bridge_get_edid(struct drm_bridge *bridge,
  struct drm_connector *connector)
 {
@@ -1651,7 +1661,10 @@ static void zynqmp_dp_hpd_work_func(struct work_struct 
*work)
struct zynqmp_dp *dp = container_of(work, struct zynqmp_dp, hpd_work);
enum drm_connector_status status;
 
-   status = zynqmp_dp_bridge_detect(>bridge);
+   mutex_lock(>lock);
+   status = __zynqmp_dp_bridge_detect(dp);
+   mutex_unlock(>lock);
+
drm_bridge_hpd_notify(>bridge, status);
 }
 
-- 
2.35.1.1320.gc452695387.dirty



[PATCH v3 06/13] drm: zynqmp_dp: Don't delay work

2024-04-22 Thread Sean Anderson
We always call scheduled_delayed_work with no delay, so just use a
non-delayed work_struct instead.

Signed-off-by: Sean Anderson 
---

Changes in v3:
- New

 drivers/gpu/drm/xlnx/zynqmp_dp.c | 13 ++---
 1 file changed, 6 insertions(+), 7 deletions(-)

diff --git a/drivers/gpu/drm/xlnx/zynqmp_dp.c b/drivers/gpu/drm/xlnx/zynqmp_dp.c
index f1834c8e3c02..59fed00a8f89 100644
--- a/drivers/gpu/drm/xlnx/zynqmp_dp.c
+++ b/drivers/gpu/drm/xlnx/zynqmp_dp.c
@@ -297,7 +297,7 @@ struct zynqmp_dp_config {
 struct zynqmp_dp {
struct drm_dp_aux aux;
struct drm_bridge bridge;
-   struct delayed_work hpd_work;
+   struct work_struct hpd_work;
 
struct drm_bridge *next_bridge;
struct device *dev;
@@ -1467,7 +1467,7 @@ static void zynqmp_dp_bridge_atomic_disable(struct 
drm_bridge *bridge,
struct zynqmp_dp *dp = bridge_to_dp(bridge);
 
dp->enabled = false;
-   cancel_delayed_work(>hpd_work);
+   cancel_work(>hpd_work);
zynqmp_dp_write(dp, ZYNQMP_DP_MAIN_STREAM_ENABLE, 0);
drm_dp_dpcd_writeb(>aux, DP_SET_POWER, DP_SET_POWER_D3);
zynqmp_dp_write(dp, ZYNQMP_DP_TX_PHY_POWER_DOWN,
@@ -1603,8 +1603,7 @@ void zynqmp_dp_disable_vblank(struct zynqmp_dp *dp)
 
 static void zynqmp_dp_hpd_work_func(struct work_struct *work)
 {
-   struct zynqmp_dp *dp = container_of(work, struct zynqmp_dp,
-   hpd_work.work);
+   struct zynqmp_dp *dp = container_of(work, struct zynqmp_dp, hpd_work);
enum drm_connector_status status;
 
status = zynqmp_dp_bridge_detect(>bridge);
@@ -1633,7 +1632,7 @@ static irqreturn_t zynqmp_dp_irq_handler(int irq, void 
*data)
zynqmp_dpsub_drm_handle_vblank(dp->dpsub);
 
if (status & ZYNQMP_DP_INT_HPD_EVENT)
-   schedule_delayed_work(>hpd_work, 0);
+   schedule_work(>hpd_work);
 
if (status & ZYNQMP_DP_INT_HPD_IRQ) {
int ret;
@@ -1675,7 +1674,7 @@ int zynqmp_dp_probe(struct zynqmp_dpsub *dpsub)
dp->dpsub = dpsub;
dp->status = connector_status_disconnected;
 
-   INIT_DELAYED_WORK(>hpd_work, zynqmp_dp_hpd_work_func);
+   INIT_WORK(>hpd_work, zynqmp_dp_hpd_work_func);
 
/* Acquire all resources (IOMEM, IRQ and PHYs). */
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "dp");
@@ -1775,7 +1774,7 @@ void zynqmp_dp_remove(struct zynqmp_dpsub *dpsub)
zynqmp_dp_write(dp, ZYNQMP_DP_INT_DS, ZYNQMP_DP_INT_ALL);
disable_irq(dp->irq);
 
-   cancel_delayed_work_sync(>hpd_work);
+   cancel_work_sync(>hpd_work);
 
zynqmp_dp_write(dp, ZYNQMP_DP_TRANSMITTER_ENABLE, 0);
zynqmp_dp_write(dp, ZYNQMP_DP_INT_DS, 0x);
-- 
2.35.1.1320.gc452695387.dirty



[PATCH v3 10/13] drm: zynqmp_dp: Use AUX IRQs instead of polling

2024-04-22 Thread Sean Anderson
Instead of polling the status register for the AUX status, just enable
the IRQs and signal a completion.

Signed-off-by: Sean Anderson 
---

Changes in v3:
- New

 drivers/gpu/drm/xlnx/zynqmp_dp.c | 35 +++-
 1 file changed, 25 insertions(+), 10 deletions(-)

diff --git a/drivers/gpu/drm/xlnx/zynqmp_dp.c b/drivers/gpu/drm/xlnx/zynqmp_dp.c
index 3ecfebf35a72..3d90533aefd7 100644
--- a/drivers/gpu/drm/xlnx/zynqmp_dp.c
+++ b/drivers/gpu/drm/xlnx/zynqmp_dp.c
@@ -285,6 +285,7 @@ struct zynqmp_dp_config {
  * @next_bridge: The downstream bridge
  * @config: IP core configuration from DTS
  * @aux: aux channel
+ * @aux_done: Completed when we get an AUX reply or timeout
  * @phy: PHY handles for DP lanes
  * @num_lanes: number of enabled phy lanes
  * @hpd_work: hot plug detection worker
@@ -305,6 +306,7 @@ struct zynqmp_dp {
struct drm_bridge bridge;
struct work_struct hpd_work;
struct work_struct hpd_irq_work;
+   struct completion aux_done;
struct mutex lock;
 
struct drm_bridge *next_bridge;
@@ -941,12 +943,15 @@ static int zynqmp_dp_aux_cmd_submit(struct zynqmp_dp *dp, 
u32 cmd, u16 addr,
u8 *buf, u8 bytes, u8 *reply)
 {
bool is_read = (cmd & AUX_READ_BIT) ? true : false;
+   unsigned long time_left;
u32 reg, i;
 
reg = zynqmp_dp_read(dp, ZYNQMP_DP_INTERRUPT_SIGNAL_STATE);
if (reg & ZYNQMP_DP_INTERRUPT_SIGNAL_STATE_REQUEST)
return -EBUSY;
 
+   reinit_completion(>aux_done);
+
zynqmp_dp_write(dp, ZYNQMP_DP_AUX_ADDRESS, addr);
if (!is_read)
for (i = 0; i < bytes; i++)
@@ -961,17 +966,14 @@ static int zynqmp_dp_aux_cmd_submit(struct zynqmp_dp *dp, 
u32 cmd, u16 addr,
zynqmp_dp_write(dp, ZYNQMP_DP_AUX_COMMAND, reg);
 
/* Wait for reply to be delivered upto 2ms */
-   for (i = 0; ; i++) {
-   reg = zynqmp_dp_read(dp, ZYNQMP_DP_INTERRUPT_SIGNAL_STATE);
-   if (reg & ZYNQMP_DP_INTERRUPT_SIGNAL_STATE_REPLY)
-   break;
+   time_left = wait_for_completion_timeout(>aux_done,
+   msecs_to_jiffies(2));
+   if (!time_left)
+   return -ETIMEDOUT;
 
-   if (reg & ZYNQMP_DP_INTERRUPT_SIGNAL_STATE_REPLY_TIMEOUT ||
-   i == 2)
-   return -ETIMEDOUT;
-
-   usleep_range(1000, 1100);
-   }
+   reg = zynqmp_dp_read(dp, ZYNQMP_DP_INTERRUPT_SIGNAL_STATE);
+   if (reg & ZYNQMP_DP_INTERRUPT_SIGNAL_STATE_REPLY_TIMEOUT)
+   return -ETIMEDOUT;
 
reg = zynqmp_dp_read(dp, ZYNQMP_DP_AUX_REPLY_CODE);
if (reply)
@@ -1055,6 +1057,9 @@ static int zynqmp_dp_aux_init(struct zynqmp_dp *dp)
(w << ZYNQMP_DP_AUX_CLK_DIVIDER_AUX_FILTER_SHIFT) |
(rate / (1000 * 1000)));
 
+   zynqmp_dp_write(dp, ZYNQMP_DP_INT_EN, ZYNQMP_DP_INT_REPLY_RECEIVED |
+ ZYNQMP_DP_INT_REPLY_TIMEOUT);
+
dp->aux.name = "ZynqMP DP AUX";
dp->aux.dev = dp->dev;
dp->aux.drm_dev = dp->bridge.dev;
@@ -1072,6 +1077,9 @@ static int zynqmp_dp_aux_init(struct zynqmp_dp *dp)
 static void zynqmp_dp_aux_cleanup(struct zynqmp_dp *dp)
 {
drm_dp_aux_unregister(>aux);
+
+   zynqmp_dp_write(dp, ZYNQMP_DP_INT_DS, ZYNQMP_DP_INT_REPLY_RECEIVED |
+ ZYNQMP_DP_INT_REPLY_TIMEOUT);
 }
 
 /* 
-
@@ -1678,6 +1686,12 @@ static irqreturn_t zynqmp_dp_irq_handler(int irq, void 
*data)
if (status & ZYNQMP_DP_INT_HPD_IRQ)
schedule_work(>hpd_irq_work);
 
+   if (status & ZYNQMP_DP_INTERRUPT_SIGNAL_STATE_REPLY)
+   complete(>aux_done);
+
+   if (status & ZYNQMP_DP_INTERRUPT_SIGNAL_STATE_REPLY_TIMEOUT)
+   complete(>aux_done);
+
return IRQ_HANDLED;
 }
 
@@ -1701,6 +1715,7 @@ int zynqmp_dp_probe(struct zynqmp_dpsub *dpsub)
dp->dpsub = dpsub;
dp->status = connector_status_disconnected;
mutex_init(>lock);
+   init_completion(>aux_done);
 
INIT_WORK(>hpd_work, zynqmp_dp_hpd_work_func);
INIT_WORK(>hpd_irq_work, zynqmp_dp_hpd_irq_work_func);
-- 
2.35.1.1320.gc452695387.dirty



[PATCH v3 13/13] drm: zynqmp_dp: Add debugfs interface for compliance testing

2024-04-22 Thread Sean Anderson
Add a debugfs interface for exercising the various test modes supported
by the DisplayPort controller. This allows performing compliance
testing, or performing signal integrity measurements on a failing link.
At the moment, we do not support sink-driven link quality testing,
although such support would be fairly easy to add.

Additionally, add some debugfs files for ignoring AUX errors and HPD
events, as this can allow testing with equipment that cannot emulate a
DPRX.

Signed-off-by: Sean Anderson 
---

(no changes since v2)

Changes in v2:
- Document debugfs files
- Add ignore_aux_errors and ignore_hpd debugfs files to replace earlier
  implicit functionality
- Attempt to fix unreproducable, spurious build warning

 Documentation/gpu/drivers.rst|   1 +
 Documentation/gpu/zynqmp.rst | 149 +++
 MAINTAINERS  |   1 +
 drivers/gpu/drm/xlnx/zynqmp_dp.c | 682 ++-
 4 files changed, 830 insertions(+), 3 deletions(-)
 create mode 100644 Documentation/gpu/zynqmp.rst

diff --git a/Documentation/gpu/drivers.rst b/Documentation/gpu/drivers.rst
index b899cbc5c2b4..187201aedbe5 100644
--- a/Documentation/gpu/drivers.rst
+++ b/Documentation/gpu/drivers.rst
@@ -22,6 +22,7 @@ GPU Driver Documentation
afbc
komeda-kms
panfrost
+   zynqmp
 
 .. only::  subproject and html
 
diff --git a/Documentation/gpu/zynqmp.rst b/Documentation/gpu/zynqmp.rst
new file mode 100644
index ..f57bfa0ad6ec
--- /dev/null
+++ b/Documentation/gpu/zynqmp.rst
@@ -0,0 +1,149 @@
+.. SPDX-License-Identifier: GPL-2.0+
+
+===
+Xilinx ZynqMP Ultrascale+ DisplayPort Subsystem
+===
+
+This subsystem handles DisplayPort video and audio output on the ZynqMP. It
+supports in-memory framebuffers with the DisplayPort DMA controller
+(xilinx-dpdma), as well as "live" video and audio from the programmable logic
+(PL). This subsystem can perform several transformations, including color space
+conversion, alpha blending, and audio mixing, although not all features are
+currently supported.
+
+debugfs
+---
+
+To support debugging and compliance testing, several test modes can be enabled
+though debugfs. The following files in /sys/kernel/debug/dri/X/DP-1/test/
+control the DisplayPort test modes:
+
+active:
+Writing a 1 to this file will activate test mode, and writing a 0 will
+deactivate test mode. Writing a 1 or 0 when the test mode is already
+active/inactive will re-activate/re-deactivate test mode. When test
+mode is inactive, changes made to other files will have no (immediate)
+effect, although the settings will be saved for when test mode is
+activated. When test mode is active, changes made to other files will
+apply immediately.
+
+custom:
+Custom test pattern value
+
+downspread:
+Enable/disable clock downspreading (spread-spectrum clocking) by
+writing 1/0
+
+enhanced:
+Enable/disable enhanced framing
+
+ignore_aux_errors:
+Ignore AUX errors when set to 1. Writes to this file take effect
+immediately (regardless of whether test mode is active) and affect all
+AUX transfers.
+
+ignore_hpd:
+Ignore hotplug events (such as cable removals or monitor link
+retraining requests) when set to 1. Writes to this file take effect
+immediately (regardless of whether test mode is active).
+
+laneX_preemphasis:
+Preemphasis from 0 (lowest) to 2 (highest) for lane X
+
+laneX_swing:
+Voltage swing from 0 (lowest) to 3 (highest) for lane X
+
+lanes:
+Number of lanes to use (1, 2, or 4)
+
+pattern:
+Test pattern. May be one of:
+
+video
+Use regular video input
+
+symbol-error
+Symbol error measurement pattern
+
+prbs7
+Output of the PRBS7 (x^7 + x^6 + 1) polynomial
+
+80bit-custom
+A custom 80-bit pattern
+
+cp2520
+HBR2 compliance eye pattern
+
+tps1
+Link training symbol pattern TPS1 (/D10.2/)
+
+tps2
+Link training symbol pattern TPS2
+
+tps3
+Link training symbol pattern TPS3 (for HBR2)
+
+rate:
+Rate in hertz. One of
+
+* 54 (HBR2)
+* 27 (HBR)
+* 162000 (RBR)
+
+You can dump the displayport test settings with the following command::
+
+for prop in /sys/kernel/debug/dri/1/DP-1/test/*; do
+printf '%-17s ' ${prop##*/}
+if [ ${prop##*/} = custom ]; then
+hexdump -C $prop | head -1
+else
+cat $prop
+fi
+  

[PATCH v3 11/13] drm: zynqmp_dp: Split off several helper functions

2024-04-22 Thread Sean Anderson
In preparation for supporting compliance testing, split off several
helper functions. No functional change intended.

Signed-off-by: Sean Anderson 
Reviewed-by: Laurent Pinchart 
Reviewed-by: Tomi Valkeinen 
---

(no changes since v1)

 drivers/gpu/drm/xlnx/zynqmp_dp.c | 49 ++--
 1 file changed, 34 insertions(+), 15 deletions(-)

diff --git a/drivers/gpu/drm/xlnx/zynqmp_dp.c b/drivers/gpu/drm/xlnx/zynqmp_dp.c
index 3d90533aefd7..364caaf26f49 100644
--- a/drivers/gpu/drm/xlnx/zynqmp_dp.c
+++ b/drivers/gpu/drm/xlnx/zynqmp_dp.c
@@ -635,6 +635,7 @@ static void zynqmp_dp_adjust_train(struct zynqmp_dp *dp,
 /**
  * zynqmp_dp_update_vs_emph - Update the training values
  * @dp: DisplayPort IP core structure
+ * @train_set: A set of training values
  *
  * Update the training values based on the request from sink. The mapped values
  * are predefined, and values(vs, pe, pc) are from the device manual.
@@ -642,12 +643,12 @@ static void zynqmp_dp_adjust_train(struct zynqmp_dp *dp,
  * Return: 0 if vs and emph are updated successfully, or the error code 
returned
  * by drm_dp_dpcd_write().
  */
-static int zynqmp_dp_update_vs_emph(struct zynqmp_dp *dp)
+static int zynqmp_dp_update_vs_emph(struct zynqmp_dp *dp, u8 *train_set)
 {
unsigned int i;
int ret;
 
-   ret = drm_dp_dpcd_write(>aux, DP_TRAINING_LANE0_SET, dp->train_set,
+   ret = drm_dp_dpcd_write(>aux, DP_TRAINING_LANE0_SET, train_set,
dp->mode.lane_cnt);
if (ret < 0)
return ret;
@@ -655,7 +656,7 @@ static int zynqmp_dp_update_vs_emph(struct zynqmp_dp *dp)
for (i = 0; i < dp->mode.lane_cnt; i++) {
u32 reg = ZYNQMP_DP_SUB_TX_PHY_PRECURSOR_LANE_0 + i * 4;
union phy_configure_opts opts = { 0 };
-   u8 train = dp->train_set[i];
+   u8 train = train_set[i];
 
opts.dp.voltage[0] = (train & DP_TRAIN_VOLTAGE_SWING_MASK)
   >> DP_TRAIN_VOLTAGE_SWING_SHIFT;
@@ -699,7 +700,7 @@ static int zynqmp_dp_link_train_cr(struct zynqmp_dp *dp)
 * So, This loop should exit before 512 iterations
 */
for (max_tries = 0; max_tries < 512; max_tries++) {
-   ret = zynqmp_dp_update_vs_emph(dp);
+   ret = zynqmp_dp_update_vs_emph(dp, dp->train_set);
if (ret)
return ret;
 
@@ -764,7 +765,7 @@ static int zynqmp_dp_link_train_ce(struct zynqmp_dp *dp)
return ret;
 
for (tries = 0; tries < DP_MAX_TRAINING_TRIES; tries++) {
-   ret = zynqmp_dp_update_vs_emph(dp);
+   ret = zynqmp_dp_update_vs_emph(dp, dp->train_set);
if (ret)
return ret;
 
@@ -787,28 +788,29 @@ static int zynqmp_dp_link_train_ce(struct zynqmp_dp *dp)
 }
 
 /**
- * zynqmp_dp_train - Train the link
+ * zynqmp_dp_setup() - Set up major link parameters
  * @dp: DisplayPort IP core structure
+ * @bw_code: The link bandwidth as a multiple of 270 MHz
+ * @lane_cnt: The number of lanes to use
+ * @enhanced: Use enhanced framing
+ * @downspread: Enable spread-spectrum clocking
  *
- * Return: 0 if all trains are done successfully, or corresponding error code.
+ * Return: 0 on success, or -errno on failure
  */
-static int zynqmp_dp_train(struct zynqmp_dp *dp)
+static int zynqmp_dp_setup(struct zynqmp_dp *dp, u8 bw_code, u8 lane_cnt,
+  bool enhanced, bool downspread)
 {
u32 reg;
-   u8 bw_code = dp->mode.bw_code;
-   u8 lane_cnt = dp->mode.lane_cnt;
u8 aux_lane_cnt = lane_cnt;
-   bool enhanced;
int ret;
 
zynqmp_dp_write(dp, ZYNQMP_DP_LANE_COUNT_SET, lane_cnt);
-   enhanced = drm_dp_enhanced_frame_cap(dp->dpcd);
if (enhanced) {
zynqmp_dp_write(dp, ZYNQMP_DP_ENHANCED_FRAME_EN, 1);
aux_lane_cnt |= DP_LANE_COUNT_ENHANCED_FRAME_EN;
}
 
-   if (dp->dpcd[3] & 0x1) {
+   if (downspread) {
zynqmp_dp_write(dp, ZYNQMP_DP_DOWNSPREAD_CTL, 1);
drm_dp_dpcd_writeb(>aux, DP_DOWNSPREAD_CTRL,
   DP_SPREAD_AMP_0_5);
@@ -851,8 +853,25 @@ static int zynqmp_dp_train(struct zynqmp_dp *dp)
}
 
zynqmp_dp_write(dp, ZYNQMP_DP_PHY_CLOCK_SELECT, reg);
-   ret = zynqmp_dp_phy_ready(dp);
-   if (ret < 0)
+   return zynqmp_dp_phy_ready(dp);
+}
+
+
+/**
+ * zynqmp_dp_train - Train the link
+ * @dp: DisplayPort IP core structure
+ *
+ * Return: 0 if all trains are done successfully, or corresponding error code.
+ */
+static int zynqmp_dp_train(struct zynqmp_dp *dp)
+{
+   int ret;
+
+   ret = zynqmp_dp_setup(dp, dp->mode.bw_code, dp->mode.lane_cnt,
+ drm_dp_enhanced_frame_cap(dp->dpcd),
+ dp->dpcd[DP_MAX_DOW

[PATCH v3 07/13] drm: zynqmp_dp: Add locking

2024-04-22 Thread Sean Anderson
Add some locking to prevent the IRQ/workers/bridge API calls from stepping
on each other's toes. This lock protects:

- Non-atomic registers configuring the link. That is, everything but the
  IRQ registers (since these are accessed in an atomic fashion), and the DP
  AUX registers (since these don't affect the link). We also access AUX
  while holding this lock, so it would be very tricky to support.
- Link configuration. This is effectively everything in zynqmp_dp which
  isn't read-only after probe time. So from next_bridge onward.

This lock is designed to protect configuration changes so we don't have to
do anything tricky. Configuration should never be in the hot path, so I'm
not worried about performance.

Signed-off-by: Sean Anderson 
---

(no changes since v2)

Changes in v2:
- Split off the HPD IRQ work into another commit
- Expand the commit message

 drivers/gpu/drm/xlnx/zynqmp_dp.c | 18 ++
 1 file changed, 18 insertions(+)

diff --git a/drivers/gpu/drm/xlnx/zynqmp_dp.c b/drivers/gpu/drm/xlnx/zynqmp_dp.c
index 59fed00a8f89..5eb926f050de 100644
--- a/drivers/gpu/drm/xlnx/zynqmp_dp.c
+++ b/drivers/gpu/drm/xlnx/zynqmp_dp.c
@@ -279,6 +279,7 @@ struct zynqmp_dp_config {
  * @dpsub: Display subsystem
  * @iomem: device I/O memory for register access
  * @reset: reset controller
+ * @lock: Mutex protecting this struct and register access (but not AUX)
  * @irq: irq
  * @bridge: DRM bridge for the DP encoder
  * @next_bridge: The downstream bridge
@@ -293,11 +294,16 @@ struct zynqmp_dp_config {
  * @link_config: common link configuration between IP core and sink device
  * @mode: current mode between IP core and sink device
  * @train_set: set of training data
+ *
+ * @lock covers the link configuration in this struct and the device's
+ * registers. It does not cover @aux. It is not strictly required for any of
+ * the members which are only modified at probe/remove time (e.g. @dev).
  */
 struct zynqmp_dp {
struct drm_dp_aux aux;
struct drm_bridge bridge;
struct work_struct hpd_work;
+   struct mutex lock;
 
struct drm_bridge *next_bridge;
struct device *dev;
@@ -1371,8 +1377,10 @@ zynqmp_dp_bridge_mode_valid(struct drm_bridge *bridge,
}
 
/* Check with link rate and lane count */
+   mutex_lock(>lock);
rate = zynqmp_dp_max_rate(dp->link_config.max_rate,
  dp->link_config.max_lanes, dp->config.bpp);
+   mutex_unlock(>lock);
if (mode->clock > rate) {
dev_dbg(dp->dev, "filtered mode %s for high pixel rate\n",
mode->name);
@@ -1399,6 +1407,7 @@ static void zynqmp_dp_bridge_atomic_enable(struct 
drm_bridge *bridge,
 
pm_runtime_get_sync(dp->dev);
 
+   mutex_lock(>lock);
zynqmp_dp_disp_enable(dp, old_bridge_state);
 
/*
@@ -1459,6 +1468,7 @@ static void zynqmp_dp_bridge_atomic_enable(struct 
drm_bridge *bridge,
zynqmp_dp_write(dp, ZYNQMP_DP_SOFTWARE_RESET,
ZYNQMP_DP_SOFTWARE_RESET_ALL);
zynqmp_dp_write(dp, ZYNQMP_DP_MAIN_STREAM_ENABLE, 1);
+   mutex_unlock(>lock);
 }
 
 static void zynqmp_dp_bridge_atomic_disable(struct drm_bridge *bridge,
@@ -1466,6 +1476,7 @@ static void zynqmp_dp_bridge_atomic_disable(struct 
drm_bridge *bridge,
 {
struct zynqmp_dp *dp = bridge_to_dp(bridge);
 
+   mutex_lock(>lock);
dp->enabled = false;
cancel_work(>hpd_work);
zynqmp_dp_write(dp, ZYNQMP_DP_MAIN_STREAM_ENABLE, 0);
@@ -1476,6 +1487,7 @@ static void zynqmp_dp_bridge_atomic_disable(struct 
drm_bridge *bridge,
zynqmp_dp_write(dp, ZYNQMP_DP_TX_AUDIO_CONTROL, 0);
 
zynqmp_dp_disp_disable(dp, old_bridge_state);
+   mutex_unlock(>lock);
 
pm_runtime_put_sync(dp->dev);
 }
@@ -1518,6 +1530,8 @@ static enum drm_connector_status 
zynqmp_dp_bridge_detect(struct drm_bridge *brid
u32 state, i;
int ret;
 
+   mutex_lock(>lock);
+
/*
 * This is from heuristic. It takes some delay (ex, 100 ~ 500 msec) to
 * get the HPD signal with some monitors.
@@ -1545,11 +1559,13 @@ static enum drm_connector_status 
zynqmp_dp_bridge_detect(struct drm_bridge *brid
   dp->num_lanes);
 
dp->status = connector_status_connected;
+   mutex_unlock(>lock);
return connector_status_connected;
}
 
 disconnected:
dp->status = connector_status_disconnected;
+   mutex_unlock(>lock);
return connector_status_disconnected;
 }
 
@@ -1673,6 +1689,7 @@ int zynqmp_dp_probe(struct zynqmp_dpsub *dpsub)
dp->dev = >dev;
dp->dpsub = dpsub;
dp->status = connector_status_disconnected;
+   mutex_init(>lock);
 
INIT_WORK(>hpd_work, zynqmp_dp_hpd_work_func);
 
@@ -17

[PATCH v3 05/13] drm: zynqmp_dp: Rearrange zynqmp_dp for better padding

2024-04-22 Thread Sean Anderson
Sort the members of struct zynqmp_dp to reduce padding necessary for
alignment.

Signed-off-by: Sean Anderson 
---

(no changes since v2)

Changes in v2:
- New

 drivers/gpu/drm/xlnx/zynqmp_dp.c | 28 ++--
 1 file changed, 14 insertions(+), 14 deletions(-)

diff --git a/drivers/gpu/drm/xlnx/zynqmp_dp.c b/drivers/gpu/drm/xlnx/zynqmp_dp.c
index 8635b5673386..f1834c8e3c02 100644
--- a/drivers/gpu/drm/xlnx/zynqmp_dp.c
+++ b/drivers/gpu/drm/xlnx/zynqmp_dp.c
@@ -255,10 +255,10 @@ struct zynqmp_dp_link_config {
  * @fmt: format identifier string
  */
 struct zynqmp_dp_mode {
-   u8 bw_code;
-   u8 lane_cnt;
-   int pclock;
const char *fmt;
+   int pclock;
+   u8 bw_code;
+   u8 lane_cnt;
 };
 
 /**
@@ -295,27 +295,27 @@ struct zynqmp_dp_config {
  * @train_set: set of training data
  */
 struct zynqmp_dp {
+   struct drm_dp_aux aux;
+   struct drm_bridge bridge;
+   struct delayed_work hpd_work;
+
+   struct drm_bridge *next_bridge;
struct device *dev;
struct zynqmp_dpsub *dpsub;
void __iomem *iomem;
struct reset_control *reset;
-   int irq;
-
-   struct drm_bridge bridge;
-   struct drm_bridge *next_bridge;
-
-   struct zynqmp_dp_config config;
-   struct drm_dp_aux aux;
struct phy *phy[ZYNQMP_DP_MAX_LANES];
-   u8 num_lanes;
-   struct delayed_work hpd_work;
+
enum drm_connector_status status;
+   int irq;
bool enabled;
 
-   u8 dpcd[DP_RECEIVER_CAP_SIZE];
-   struct zynqmp_dp_link_config link_config;
struct zynqmp_dp_mode mode;
+   struct zynqmp_dp_link_config link_config;
+   struct zynqmp_dp_config config;
+   u8 dpcd[DP_RECEIVER_CAP_SIZE];
u8 train_set[ZYNQMP_DP_MAX_LANES];
+   u8 num_lanes;
 };
 
 static inline struct zynqmp_dp *bridge_to_dp(struct drm_bridge *bridge)
-- 
2.35.1.1320.gc452695387.dirty



[PATCH v3 09/13] drm: zynqmp_dp: Convert to a hard IRQ

2024-04-22 Thread Sean Anderson
Now that all of the sleeping work is done outside of the IRQ, we can
convert it to a hard IRQ.

Signed-off-by: Sean Anderson 
---

Changes in v3:
- New

 drivers/gpu/drm/xlnx/zynqmp_dp.c | 5 ++---
 1 file changed, 2 insertions(+), 3 deletions(-)

diff --git a/drivers/gpu/drm/xlnx/zynqmp_dp.c b/drivers/gpu/drm/xlnx/zynqmp_dp.c
index 8ad8db01f136..3ecfebf35a72 100644
--- a/drivers/gpu/drm/xlnx/zynqmp_dp.c
+++ b/drivers/gpu/drm/xlnx/zynqmp_dp.c
@@ -1774,9 +1774,8 @@ int zynqmp_dp_probe(struct zynqmp_dpsub *dpsub)
 * Now that the hardware is initialized and won't generate spurious
 * interrupts, request the IRQ.
 */
-   ret = devm_request_threaded_irq(dp->dev, dp->irq, NULL,
-   zynqmp_dp_irq_handler, IRQF_ONESHOT,
-   dev_name(dp->dev), dp);
+   ret = devm_request_irq(dp->dev, dp->irq, zynqmp_dp_irq_handler,
+  IRQF_SHARED, dev_name(dp->dev), dp);
if (ret < 0)
goto err_phy_exit;
 
-- 
2.35.1.1320.gc452695387.dirty



[PATCH v3 04/13] drm: zynqmp_dp: Adjust training values per-lane

2024-04-22 Thread Sean Anderson
The feedback we get from the DPRX is per-lane. Make changes using this
information, instead of picking the maximum values from all lanes. This
results in more-consistent training on marginal links.

Signed-off-by: Sean Anderson 
Reviewed-by: Tomi Valkeinen 
---

(no changes since v1)

 drivers/gpu/drm/xlnx/zynqmp_dp.c | 23 ---
 1 file changed, 8 insertions(+), 15 deletions(-)

diff --git a/drivers/gpu/drm/xlnx/zynqmp_dp.c b/drivers/gpu/drm/xlnx/zynqmp_dp.c
index 98a32e6a0459..8635b5673386 100644
--- a/drivers/gpu/drm/xlnx/zynqmp_dp.c
+++ b/drivers/gpu/drm/xlnx/zynqmp_dp.c
@@ -605,28 +605,21 @@ static void zynqmp_dp_adjust_train(struct zynqmp_dp *dp,
   u8 link_status[DP_LINK_STATUS_SIZE])
 {
u8 *train_set = dp->train_set;
-   u8 voltage = 0, preemphasis = 0;
u8 i;
 
for (i = 0; i < dp->mode.lane_cnt; i++) {
-   u8 v = drm_dp_get_adjust_request_voltage(link_status, i);
-   u8 p = drm_dp_get_adjust_request_pre_emphasis(link_status, i);
+   u8 voltage = drm_dp_get_adjust_request_voltage(link_status, i);
+   u8 preemphasis =
+   drm_dp_get_adjust_request_pre_emphasis(link_status, i);
 
-   if (v > voltage)
-   voltage = v;
+   if (voltage >= DP_TRAIN_VOLTAGE_SWING_LEVEL_3)
+   voltage |= DP_TRAIN_MAX_SWING_REACHED;
 
-   if (p > preemphasis)
-   preemphasis = p;
-   }
+   if (preemphasis >= DP_TRAIN_PRE_EMPH_LEVEL_2)
+   preemphasis |= DP_TRAIN_MAX_PRE_EMPHASIS_REACHED;
 
-   if (voltage >= DP_TRAIN_VOLTAGE_SWING_LEVEL_3)
-   voltage |= DP_TRAIN_MAX_SWING_REACHED;
-
-   if (preemphasis >= DP_TRAIN_PRE_EMPH_LEVEL_2)
-   preemphasis |= DP_TRAIN_MAX_PRE_EMPHASIS_REACHED;
-
-   for (i = 0; i < dp->mode.lane_cnt; i++)
train_set[i] = voltage | preemphasis;
+   }
 }
 
 /**
-- 
2.35.1.1320.gc452695387.dirty



[PATCH v3 08/13] drm: zynqmp_dp: Don't retrain the link in our IRQ

2024-04-22 Thread Sean Anderson
Retraining the link can take a while, and might involve waiting for
DPCD reads/writes to complete. In preparation for unthreading the IRQ
handler, move this into its own work function.

Signed-off-by: Sean Anderson 
---

(no changes since v2)

Changes in v2:
- Document hpd_irq_work
- Split this off from the locking changes

 drivers/gpu/drm/xlnx/zynqmp_dp.c | 45 
 1 file changed, 29 insertions(+), 16 deletions(-)

diff --git a/drivers/gpu/drm/xlnx/zynqmp_dp.c b/drivers/gpu/drm/xlnx/zynqmp_dp.c
index 5eb926f050de..8ad8db01f136 100644
--- a/drivers/gpu/drm/xlnx/zynqmp_dp.c
+++ b/drivers/gpu/drm/xlnx/zynqmp_dp.c
@@ -288,6 +288,7 @@ struct zynqmp_dp_config {
  * @phy: PHY handles for DP lanes
  * @num_lanes: number of enabled phy lanes
  * @hpd_work: hot plug detection worker
+ * @hpd_irq_work: hot plug detection IRQ worker
  * @status: connection status
  * @enabled: flag to indicate if the device is enabled
  * @dpcd: DP configuration data from currently connected sink device
@@ -303,6 +304,7 @@ struct zynqmp_dp {
struct drm_dp_aux aux;
struct drm_bridge bridge;
struct work_struct hpd_work;
+   struct work_struct hpd_irq_work;
struct mutex lock;
 
struct drm_bridge *next_bridge;
@@ -1626,6 +1628,29 @@ static void zynqmp_dp_hpd_work_func(struct work_struct 
*work)
drm_bridge_hpd_notify(>bridge, status);
 }
 
+static void zynqmp_dp_hpd_irq_work_func(struct work_struct *work)
+{
+   struct zynqmp_dp *dp = container_of(work, struct zynqmp_dp,
+   hpd_irq_work);
+   u8 status[DP_LINK_STATUS_SIZE + 2];
+   int err;
+
+   mutex_lock(>lock);
+   err = drm_dp_dpcd_read(>aux, DP_SINK_COUNT, status,
+  DP_LINK_STATUS_SIZE + 2);
+   if (err < 0) {
+   dev_dbg_ratelimited(dp->dev,
+   "could not read sink status: %d\n", err);
+   } else {
+   if (status[4] & DP_LINK_STATUS_UPDATED ||
+   !drm_dp_clock_recovery_ok([2], dp->mode.lane_cnt) ||
+   !drm_dp_channel_eq_ok([2], dp->mode.lane_cnt)) {
+   zynqmp_dp_train_loop(dp);
+   }
+   }
+   mutex_unlock(>lock);
+}
+
 static irqreturn_t zynqmp_dp_irq_handler(int irq, void *data)
 {
struct zynqmp_dp *dp = (struct zynqmp_dp *)data;
@@ -1650,23 +1675,9 @@ static irqreturn_t zynqmp_dp_irq_handler(int irq, void 
*data)
if (status & ZYNQMP_DP_INT_HPD_EVENT)
schedule_work(>hpd_work);
 
-   if (status & ZYNQMP_DP_INT_HPD_IRQ) {
-   int ret;
-   u8 status[DP_LINK_STATUS_SIZE + 2];
+   if (status & ZYNQMP_DP_INT_HPD_IRQ)
+   schedule_work(>hpd_irq_work);
 
-   ret = drm_dp_dpcd_read(>aux, DP_SINK_COUNT, status,
-  DP_LINK_STATUS_SIZE + 2);
-   if (ret < 0)
-   goto handled;
-
-   if (status[4] & DP_LINK_STATUS_UPDATED ||
-   !drm_dp_clock_recovery_ok([2], dp->mode.lane_cnt) ||
-   !drm_dp_channel_eq_ok([2], dp->mode.lane_cnt)) {
-   zynqmp_dp_train_loop(dp);
-   }
-   }
-
-handled:
return IRQ_HANDLED;
 }
 
@@ -1692,6 +1703,7 @@ int zynqmp_dp_probe(struct zynqmp_dpsub *dpsub)
mutex_init(>lock);
 
INIT_WORK(>hpd_work, zynqmp_dp_hpd_work_func);
+   INIT_WORK(>hpd_irq_work, zynqmp_dp_hpd_irq_work_func);
 
/* Acquire all resources (IOMEM, IRQ and PHYs). */
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "dp");
@@ -1791,6 +1803,7 @@ void zynqmp_dp_remove(struct zynqmp_dpsub *dpsub)
zynqmp_dp_write(dp, ZYNQMP_DP_INT_DS, ZYNQMP_DP_INT_ALL);
disable_irq(dp->irq);
 
+   cancel_work_sync(>hpd_irq_work);
cancel_work_sync(>hpd_work);
 
zynqmp_dp_write(dp, ZYNQMP_DP_TRANSMITTER_ENABLE, 0);
-- 
2.35.1.1320.gc452695387.dirty



[PATCH v3 03/13] drm: zynqmp_dp: Downgrade log level for aux retries message

2024-04-22 Thread Sean Anderson
Enable this message for verbose debugging only as it is otherwise
printed after every AUX message, quickly filling the log buffer.

Signed-off-by: Sean Anderson 
Reviewed-by: Laurent Pinchart 
Reviewed-by: Tomi Valkeinen 
---

(no changes since v1)

 drivers/gpu/drm/xlnx/zynqmp_dp.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/gpu/drm/xlnx/zynqmp_dp.c b/drivers/gpu/drm/xlnx/zynqmp_dp.c
index a0606fab0e22..98a32e6a0459 100644
--- a/drivers/gpu/drm/xlnx/zynqmp_dp.c
+++ b/drivers/gpu/drm/xlnx/zynqmp_dp.c
@@ -1006,7 +1006,7 @@ zynqmp_dp_aux_transfer(struct drm_dp_aux *aux, struct 
drm_dp_aux_msg *msg)
   msg->buffer, msg->size,
   >reply);
if (!ret) {
-   dev_dbg(dp->dev, "aux %d retries\n", i);
+   dev_vdbg(dp->dev, "aux %d retries\n", i);
return msg->size;
}
 
-- 
2.35.1.1320.gc452695387.dirty



[PATCH v3 00/13] drm: zynqmp_dp: IRQ cleanups and debugfs support

2024-04-22 Thread Sean Anderson
This series cleans up the zyqnmp_dp IRQ and locking situation. Once
that's done, it adds debugfs support. The intent is to enable compliance
testing or to help debug signal-integrity issues.

Last time I discussed converting the HPD work(s) to a threaded IRQ. I
did not end up doing that for this series since the steps would be

- Add locking
- Move link retraining to a work function
- Harden the IRQ
- Merge the works into a threaded IRQ (omitted)

Which with the exception of the final step is the same as leaving those
works as-is. Conversion to a threaded IRQ can be done as a follow-up.

Changes in v3:
- Store base pointers in zynqmp_disp directly
- Don't delay work
- Convert to a hard IRQ
- Use AUX IRQs instead of polling
- Take dp->lock in zynqmp_dp_hpd_work_func

Changes in v2:
- Fix kerneldoc
- Rearrange zynqmp_dp for better padding
- Split off the HPD IRQ work into another commit
- Expand the commit message
- Document hpd_irq_work
- Document debugfs files
- Add ignore_aux_errors and ignore_hpd debugfs files to replace earlier
  implicit functionality
- Attempt to fix unreproducable, spurious build warning
- Drop "Optionally ignore DPCD errors" in favor of a debugfs file
  directly affecting zynqmp_dp_aux_transfer.

Sean Anderson (13):
  drm: xlnx: Store base pointers in zynqmp_disp directly
  drm: xlnx: Fix kerneldoc
  drm: zynqmp_dp: Downgrade log level for aux retries message
  drm: zynqmp_dp: Adjust training values per-lane
  drm: zynqmp_dp: Rearrange zynqmp_dp for better padding
  drm: zynqmp_dp: Don't delay work
  drm: zynqmp_dp: Add locking
  drm: zynqmp_dp: Don't retrain the link in our IRQ
  drm: zynqmp_dp: Convert to a hard IRQ
  drm: zynqmp_dp: Use AUX IRQs instead of polling
  drm: zynqmp_dp: Split off several helper functions
  drm: zynqmp_dp: Take dp->lock in zynqmp_dp_hpd_work_func
  drm: zynqmp_dp: Add debugfs interface for compliance testing

 Documentation/gpu/drivers.rst   |   1 +
 Documentation/gpu/zynqmp.rst| 149 +
 MAINTAINERS |   1 +
 drivers/gpu/drm/xlnx/zynqmp_disp.c  |  44 +-
 drivers/gpu/drm/xlnx/zynqmp_dp.c| 909 +---
 drivers/gpu/drm/xlnx/zynqmp_dpsub.h |   1 +
 drivers/gpu/drm/xlnx/zynqmp_kms.h   |   4 +-
 7 files changed, 1000 insertions(+), 109 deletions(-)
 create mode 100644 Documentation/gpu/zynqmp.rst

-- 
2.35.1.1320.gc452695387.dirty



[PATCH v3 02/13] drm: xlnx: Fix kerneldoc

2024-04-22 Thread Sean Anderson
Fix a few errors in the kerneldoc. Mostly this addresses missing/renamed
members.

Signed-off-by: Sean Anderson 
---

Changes in v3:
- Split off documentation for base pointers to previous commit

Changes in v2:
- New

 drivers/gpu/drm/xlnx/zynqmp_dpsub.h | 1 +
 drivers/gpu/drm/xlnx/zynqmp_kms.h   | 4 ++--
 2 files changed, 3 insertions(+), 2 deletions(-)

diff --git a/drivers/gpu/drm/xlnx/zynqmp_dpsub.h 
b/drivers/gpu/drm/xlnx/zynqmp_dpsub.h
index 09ea01878f2a..b18554467e9c 100644
--- a/drivers/gpu/drm/xlnx/zynqmp_dpsub.h
+++ b/drivers/gpu/drm/xlnx/zynqmp_dpsub.h
@@ -53,6 +53,7 @@ enum zynqmp_dpsub_format {
  * @drm: The DRM/KMS device data
  * @bridge: The DP encoder bridge
  * @disp: The display controller
+ * @layers: Video and graphics layers
  * @dp: The DisplayPort controller
  * @dma_align: DMA alignment constraint (must be a power of 2)
  */
diff --git a/drivers/gpu/drm/xlnx/zynqmp_kms.h 
b/drivers/gpu/drm/xlnx/zynqmp_kms.h
index 01be96b00e3f..cb13c6b8008e 100644
--- a/drivers/gpu/drm/xlnx/zynqmp_kms.h
+++ b/drivers/gpu/drm/xlnx/zynqmp_kms.h
@@ -22,9 +22,9 @@
 struct zynqmp_dpsub;
 
 /**
- * struct zynqmp_dpsub - ZynqMP DisplayPort Subsystem DRM/KMS data
+ * struct zynqmp_dpsub_drm - ZynqMP DisplayPort Subsystem DRM/KMS data
  * @dpsub: Backpointer to the DisplayPort subsystem
- * @drm: The DRM/KMS device
+ * @dev: The DRM/KMS device
  * @planes: The DRM planes
  * @crtc: The DRM CRTC
  * @encoder: The dummy DRM encoder
-- 
2.35.1.1320.gc452695387.dirty



[PATCH v3 01/13] drm: xlnx: Store base pointers in zynqmp_disp directly

2024-04-22 Thread Sean Anderson
The blend, avbuf, and audio members of zynqmp_disp are anonymous structs
with only one member each. This is rather pointless, so move the members
up a level.

Signed-off-by: Sean Anderson 
---

Changes in v3:
- New

 drivers/gpu/drm/xlnx/zynqmp_disp.c | 44 +-
 1 file changed, 19 insertions(+), 25 deletions(-)

diff --git a/drivers/gpu/drm/xlnx/zynqmp_disp.c 
b/drivers/gpu/drm/xlnx/zynqmp_disp.c
index 407bc07cec69..94a3ac046373 100644
--- a/drivers/gpu/drm/xlnx/zynqmp_disp.c
+++ b/drivers/gpu/drm/xlnx/zynqmp_disp.c
@@ -128,24 +128,18 @@ struct zynqmp_disp_layer {
  * struct zynqmp_disp - Display controller
  * @dev: Device structure
  * @dpsub: Display subsystem
- * @blend.base: Register I/O base address for the blender
- * @avbuf.base: Register I/O base address for the audio/video buffer manager
- * @audio.base: Registers I/O base address for the audio mixer
+ * @blend: Register I/O base address for the blender
+ * @avbuf: Register I/O base address for the audio/video buffer manager
+ * @audio: Registers I/O base address for the audio mixer
  * @layers: Layers (planes)
  */
 struct zynqmp_disp {
struct device *dev;
struct zynqmp_dpsub *dpsub;
 
-   struct {
-   void __iomem *base;
-   } blend;
-   struct {
-   void __iomem *base;
-   } avbuf;
-   struct {
-   void __iomem *base;
-   } audio;
+   void __iomem *blend;
+   void __iomem *avbuf;
+   void __iomem *audio;
 
struct zynqmp_disp_layer layers[ZYNQMP_DPSUB_NUM_LAYERS];
 };
@@ -356,12 +350,12 @@ static const struct zynqmp_disp_format avbuf_gfx_fmts[] = 
{
 
 static u32 zynqmp_disp_avbuf_read(struct zynqmp_disp *disp, int reg)
 {
-   return readl(disp->avbuf.base + reg);
+   return readl(disp->avbuf + reg);
 }
 
 static void zynqmp_disp_avbuf_write(struct zynqmp_disp *disp, int reg, u32 val)
 {
-   writel(val, disp->avbuf.base + reg);
+   writel(val, disp->avbuf + reg);
 }
 
 static bool zynqmp_disp_layer_is_video(const struct zynqmp_disp_layer *layer)
@@ -587,7 +581,7 @@ static void zynqmp_disp_avbuf_disable(struct zynqmp_disp 
*disp)
 
 static void zynqmp_disp_blend_write(struct zynqmp_disp *disp, int reg, u32 val)
 {
-   writel(val, disp->blend.base + reg);
+   writel(val, disp->blend + reg);
 }
 
 /*
@@ -813,7 +807,7 @@ static void zynqmp_disp_blend_layer_disable(struct 
zynqmp_disp *disp,
 
 static void zynqmp_disp_audio_write(struct zynqmp_disp *disp, int reg, u32 val)
 {
-   writel(val, disp->audio.base + reg);
+   writel(val, disp->audio + reg);
 }
 
 /**
@@ -1237,21 +1231,21 @@ int zynqmp_disp_probe(struct zynqmp_dpsub *dpsub)
disp->dev = >dev;
disp->dpsub = dpsub;
 
-   disp->blend.base = devm_platform_ioremap_resource_byname(pdev, "blend");
-   if (IS_ERR(disp->blend.base)) {
-   ret = PTR_ERR(disp->blend.base);
+   disp->blend = devm_platform_ioremap_resource_byname(pdev, "blend");
+   if (IS_ERR(disp->blend)) {
+   ret = PTR_ERR(disp->blend);
goto error;
}
 
-   disp->avbuf.base = devm_platform_ioremap_resource_byname(pdev, 
"av_buf");
-   if (IS_ERR(disp->avbuf.base)) {
-   ret = PTR_ERR(disp->avbuf.base);
+   disp->avbuf = devm_platform_ioremap_resource_byname(pdev, "av_buf");
+   if (IS_ERR(disp->avbuf)) {
+   ret = PTR_ERR(disp->avbuf);
goto error;
}
 
-   disp->audio.base = devm_platform_ioremap_resource_byname(pdev, "aud");
-   if (IS_ERR(disp->audio.base)) {
-   ret = PTR_ERR(disp->audio.base);
+   disp->audio = devm_platform_ioremap_resource_byname(pdev, "aud");
+   if (IS_ERR(disp->audio)) {
+   ret = PTR_ERR(disp->audio);
goto error;
}
 
-- 
2.35.1.1320.gc452695387.dirty



Re: [PATCH v2 5/8] drm: zynqmp_dp: Don't retrain the link in our IRQ

2024-03-22 Thread Sean Anderson
On 3/22/24 14:09, Tomi Valkeinen wrote:
> On 22/03/2024 18:18, Sean Anderson wrote:
>> On 3/22/24 01:32, Tomi Valkeinen wrote:
>>> On 21/03/2024 21:17, Sean Anderson wrote:
>>>> On 3/21/24 15:08, Tomi Valkeinen wrote:
>>>>> On 21/03/2024 20:01, Sean Anderson wrote:
>>>>>> On 3/21/24 13:25, Tomi Valkeinen wrote:
>>>>>>> On 21/03/2024 17:52, Sean Anderson wrote:
>>>>>>>> On 3/20/24 02:53, Tomi Valkeinen wrote:
>>>>>>>>> On 20/03/2024 00:51, Sean Anderson wrote:
>>>>>>>>> Do we need to handle interrupts while either delayed work is being 
>>>>>>>>> done?
>>>>>>>>
>>>>>>>> Probably not.
>>>>>>>>
>>>>>>>>> If we do need a delayed work, would just one work be enough which
>>>>>>>>> handles both HPD_EVENT and HPD_IRQ, instead of two?
>>>>>>>>
>>>>>>>> Maybe, but then we need to determine which pending events we need to
>>>>>>>> handle. I think since we have only two events it will be easier to just
>>>>>>>> have separate workqueues.
>>>>>>>
>>>>>>> The less concurrency, the better...Which is why it would be nice to do 
>>>>>>> it all in the threaded irq.
>>>>>>
>>>>>> Yeah, but we can use a mutex for this which means there is not too much
>>>>>> interesting going on.
>>>>>
>>>>> Ok. Yep, if we get (hopefully) a single mutex with clearly defined fields 
>>>>> that it protects, I'm ok with workqueues.
>>>>>
>>>>> I'd still prefer just one workqueue, though...
>>>>
>>>> Yeah, but then we need a spinlock or something to tell the workqueue what 
>>>> it should do.
>>>
>>> Yep. We could also always look at the HPD (if we drop the big sleeps) in 
>>> the wq, and have a flag for the HPD IRQ, which would reduce the state to a 
>>> single bit.
>>
>> How about something like
>>
>> zynqmp_dp_irq_handler(...)
>> {
>> /* Read status and handle underflow/overflow/vblank */
>>
>> status &= ZYNQMP_DP_INT_HPD_EVENT | ZYNQMP_DP_INT_HPD_IRQ;
>> if (status) {
>> atomic_or(status, >status);
>> return IRQ_WAKE_THREAD;
>> }
>>
>> return IRQ_HANDLED;
>> }
>>
>> zynqmp_dp_thread_handler(...)
>> {
>> status = atomic_xchg(>status, 0);
>> /* process HPD stuff */
>> }
>>
>> which gets rid of the workqueue too.
> 
> I like it. We can't use IRQF_ONESHOT, as that would keep the irq masked while 
> the threaded handler is being ran. I don't think that's a problem, but just 
> something to keep in mind that both handlers can run concurrently.

Actually, I'm not sure we can do it like this. Imagine we have something
like

CPU 0  CPU 1
zynqmp_dp_thread_handler()
  atomic_xchg()  
   __handle_irq_event_percpu 
 zynqmp_dp_irq_handler()
   atomic_or()
   return IRQ_WAIT_THREAD
 __irq_wake_thread()
   test_and_set_bit(IRQTF_RUNTHREAD, ...)
   return
  return IRQ_HANDLED

and whoops we now have bits set in dp->status but the thread isn't
running. I don't think there's a way to fix this without locking (or two
works). TBH I am leaning towards just having two works; it is a clean
implementation. We can also convert to use work_struct instead of
delayed_work, since we never set a delay.

--Sean


Re: [PATCH v2 5/8] drm: zynqmp_dp: Don't retrain the link in our IRQ

2024-03-22 Thread Sean Anderson
On 3/22/24 01:32, Tomi Valkeinen wrote:
> On 21/03/2024 21:17, Sean Anderson wrote:
>> On 3/21/24 15:08, Tomi Valkeinen wrote:
>>> On 21/03/2024 20:01, Sean Anderson wrote:
>>>> On 3/21/24 13:25, Tomi Valkeinen wrote:
>>>>> On 21/03/2024 17:52, Sean Anderson wrote:
>>>>>> On 3/20/24 02:53, Tomi Valkeinen wrote:
>>>>>>> On 20/03/2024 00:51, Sean Anderson wrote:
>>>>>>> Do we need to handle interrupts while either delayed work is being done?
>>>>>>
>>>>>> Probably not.
>>>>>>
>>>>>>> If we do need a delayed work, would just one work be enough which
>>>>>>> handles both HPD_EVENT and HPD_IRQ, instead of two?
>>>>>>
>>>>>> Maybe, but then we need to determine which pending events we need to
>>>>>> handle. I think since we have only two events it will be easier to just
>>>>>> have separate workqueues.
>>>>>
>>>>> The less concurrency, the better...Which is why it would be nice to do it 
>>>>> all in the threaded irq.
>>>>
>>>> Yeah, but we can use a mutex for this which means there is not too much
>>>> interesting going on.
>>>
>>> Ok. Yep, if we get (hopefully) a single mutex with clearly defined fields 
>>> that it protects, I'm ok with workqueues.
>>>
>>> I'd still prefer just one workqueue, though...
>>
>> Yeah, but then we need a spinlock or something to tell the workqueue what it 
>> should do.
> 
> Yep. We could also always look at the HPD (if we drop the big sleeps) in the 
> wq, and have a flag for the HPD IRQ, which would reduce the state to a single 
> bit.

How about something like

zynqmp_dp_irq_handler(...)
{
/* Read status and handle underflow/overflow/vblank */

status &= ZYNQMP_DP_INT_HPD_EVENT | ZYNQMP_DP_INT_HPD_IRQ;
if (status) {
atomic_or(status, >status);
return IRQ_WAKE_THREAD;
}

return IRQ_HANDLED;
}

zynqmp_dp_thread_handler(...)
{
status = atomic_xchg(>status, 0);
/* process HPD stuff */
}

which gets rid of the workqueue too.

--Sean


Re: [PATCH v2 1/8] drm: xlnx: Fix kerneldoc

2024-03-22 Thread Sean Anderson
On 3/22/24 01:50, Tomi Valkeinen wrote:
> On 21/03/2024 17:33, Sean Anderson wrote:
>> On 3/20/24 02:05, Randy Dunlap wrote:
>>>
>>>
>>> On 3/19/24 22:42, Tomi Valkeinen wrote:
>>>> On 20/03/2024 00:51, Sean Anderson wrote:
>>>>> Fix a few errors in the kerneldoc. Mostly this addresses missing/renamed
>>>>> members.
>>>>>
>>>>> Signed-off-by: Sean Anderson 
>>>>> ---
>>>>>
>>>>> Changes in v2:
>>>>> - New
>>>>>
>>>>>drivers/gpu/drm/xlnx/zynqmp_disp.c  | 6 +++---
>>>>>drivers/gpu/drm/xlnx/zynqmp_dpsub.h | 1 +
>>>>>drivers/gpu/drm/xlnx/zynqmp_kms.h   | 4 ++--
>>>>>3 files changed, 6 insertions(+), 5 deletions(-)
>>>>>
>>>>> diff --git a/drivers/gpu/drm/xlnx/zynqmp_disp.c 
>>>>> b/drivers/gpu/drm/xlnx/zynqmp_disp.c
>>>>> index 407bc07cec69..f79bf3fb8110 100644
>>>>> --- a/drivers/gpu/drm/xlnx/zynqmp_disp.c
>>>>> +++ b/drivers/gpu/drm/xlnx/zynqmp_disp.c
>>>>> @@ -128,9 +128,9 @@ struct zynqmp_disp_layer {
>>>>> * struct zynqmp_disp - Display controller
>>>>> * @dev: Device structure
>>>>> * @dpsub: Display subsystem
>>>>> - * @blend.base: Register I/O base address for the blender
>>>>> - * @avbuf.base: Register I/O base address for the audio/video buffer 
>>>>> manager
>>>>> - * @audio.base: Registers I/O base address for the audio mixer
>>>>> + * @blend: Register I/O base address for the blender
>>>>> + * @avbuf: Register I/O base address for the audio/video buffer manager
>>>>> + * @audio: Registers I/O base address for the audio mixer
>>>>
>>>> Afaics, the kernel doc guide:
>>>>
>>>> https://docs.kernel.org/doc-guide/kernel-doc.html#nested-structs-unions
>>>>
>>>> says that the current version is correct. Or is the issue that while, say, 
>>>> 'base' is documented, 'blend' was not?
>>>
>>> Hi,
>>>
>>> I would do it more like so:
>>>
>>> ---
>>>   drivers/gpu/drm/xlnx/zynqmp_disp.c |3 +++
>>>   1 file changed, 3 insertions(+)
>>>
>>> diff -- a/drivers/gpu/drm/xlnx/zynqmp_disp.c 
>>> b/drivers/gpu/drm/xlnx/zynqmp_disp.c
>>> --- a/drivers/gpu/drm/xlnx/zynqmp_disp.c
>>> +++ b/drivers/gpu/drm/xlnx/zynqmp_disp.c
>>> @@ -128,8 +128,11 @@ struct zynqmp_disp_layer {
>>>* struct zynqmp_disp - Display controller
>>>* @dev: Device structure
>>>* @dpsub: Display subsystem
>>> + * @blend: blender iomem info
>>>* @blend.base: Register I/O base address for the blender
>>> + * @avbuf: audio/video buffer iomem info
>>>* @avbuf.base: Register I/O base address for the audio/video buffer 
>>> manager
>>> + * @audio: audio mixer iomem info
>>>* @audio.base: Registers I/O base address for the audio mixer
>>>* @layers: Layers (planes)
>>>*/
>>>
>>>
>>> but in my testing, Sean's way or my way result in no warning/errors.
>>>
>>
>> The specific errors are:
>>
>> ../drivers/gpu/drm/xlnx/zynqmp_disp.c:151: warning: Function parameter or 
>> struct member 'blend' not described in 'zynqmp_disp'
>> ../drivers/gpu/drm/xlnx/zynqmp_disp.c:151: warning: Function parameter or 
>> struct member 'avbuf' not described in 'zynqmp_disp'
>> ../drivers/gpu/drm/xlnx/zynqmp_disp.c:151: warning: Function parameter or 
>> struct member 'audio' not described in 'zynqmp_disp'
>>
>> I don't see the need to document a single-member struct twice. Actually,
> 
> But if only the struct is documented, then we're documenting the wrong thing. 
> A tool showing to the user what blend.base is would miss that documentation.

Are there any such tools? kerneldoc e.g. just prints the definition and
then a list of members with documentation. So from the user's
perspective the only thing which changes is the name.

--Sean

>> maybe it would be better to just lift the .base member to live in
>> zynqmp_disp. But I think that would be better in another series.
> 
> Yes, there's not much point with the structs.
> 
>  Tomi
> 


Re: [PATCH v2 5/8] drm: zynqmp_dp: Don't retrain the link in our IRQ

2024-03-21 Thread Sean Anderson
On 3/21/24 15:08, Tomi Valkeinen wrote:
> On 21/03/2024 20:01, Sean Anderson wrote:
>> On 3/21/24 13:25, Tomi Valkeinen wrote:
>>> On 21/03/2024 17:52, Sean Anderson wrote:
>>>> On 3/20/24 02:53, Tomi Valkeinen wrote:
>>>>> On 20/03/2024 00:51, Sean Anderson wrote:
>>>>>> Retraining the link can take a while, and might involve waiting for
>>>>>> DPCD reads/writes to complete. This is inappropriate for an IRQ handler.
>>>>>> Just schedule this work for later completion. This is racy, but will be
>>>>>> fixed in the next commit.
>>>>>
>>>>> You should add the locks first, and use them here, rather than first
>>>>> adding a buggy commit and fixing it in the next one.
>>>>
>>>> I didn't think I could add the locks first since I only noticed the IRQ
>>>> was threaded right before sending out this series. So yeah, we could add
>>>> locking, add the workqueue, and then unthread the IRQ.
>>>>
>>>>>> Signed-off-by: Sean Anderson 
>>>>>> ---
>>>>>> Actually, on second look this IRQ is threaded. So why do we have a
>>>>>> workqueue for HPD events? Maybe we should make it unthreaded?
>>>>>
>>>>> Indeed, there's not much work being done in the IRQ handler. I don't know 
>>>>> why it's threaded.
>>>>>
>>>>> We could move the queued work to be inside the threaded irq handler,
>>>>> but with a quick look, the HPD work has lines like "msleep(100)" (and
>>>>> that's inside a for loop...), which is probably not a good thing to do
>>>>> even in threaded irq handler.
>>>>>
>>>>> Although I'm not sure if that code is good to have anywhere. Why do we
>>>>> even have such code in the HPD work path... We already got the HPD
>>>>> interrupt. What does "It takes some delay (ex, 100 ~ 500 msec) to get
>>>>> the HPD signal with some monitors" even mean...
>>>>
>>>> The documentation for this bit is
>>>>
>>>> | HPD_STATE    0    ro    0x0    Contains the raw state of the HPD pin on 
>>>> the DisplayPort connector.
>>>>
>>>> So I think the idea is to perform some debouncing.
>>>
>>> Hmm, it just looks a bit odd to me. It can sleep for a second. And the 
>>> wording "It takes some delay (ex, 100 ~ 500 msec) to get the HPD signal 
>>> with some monitors" makes it sound like some kind of a hack...
>>>
>>> The docs mention debounce once:
>>>
>>> https://docs.amd.com/r/en-US/pg299-v-dp-txss1/Hot-Plug-Detection
>>
>> Are you sure this is the right document? This seems to be documentation for 
>> [1]. Is that instantiated as a hard block on the ZynqMP?
>>
>> [1] 
>> https://www.xilinx.com/products/intellectual-property/ef-di-displayport.html
> 
> You're right, wrong document. The registers and bitfield names I looked at 
> just matched, so I didn't think it through...
> 
> The right doc says even less:
> 
> https://docs.amd.com/r/en-US/ug1085-zynq-ultrascale-trm/Upon-HPD-Assertion
> 
>>> But it's not immediately obvious what the SW must do and what's done by the 
>>> HW. Debounce is not mentioned later, e.g. in the HPD Event Handling. But if 
>>> debounce is needed, wouldn't it be perhaps in a few milliseconds, instead 
>>> of hundreds of milliseconds...
>>
>> Well, the DP spec says
>>
>> | If the HPD is the result of a new device being connected, either
>> | directly to the Source device (signaled by a long HPD), –or– downstream
>> | of a Branch device (indicated by incrementing the DFP_COUNT field value
>> | in the DOWN_STREAM_PORT_COUNT register (DPCD 7h[3:0]) and signaled
>> | by an IRQ_HPD pulse), the Source device shall read the new DisplayID or
>> | legacy EDID that has been made available to it to ensure that content
>> | being transmitted over the link is able to be properly received and
>> | rendered.
>> |
>> | Informative Note: If the HPD signal toggling (or bouncing) is the
>> |   result of the Hot Unplug followed by Hot Plug of a
>> |   cable-connector assembly, the HPD signal is likely
>> |   to remain unstable during the de-bouncing period,
>> |   which is in the order of tens of milliseconds. The
>> |   Source device may either check the HPD signal’s
>> |   s

Re: [PATCH v2 5/8] drm: zynqmp_dp: Don't retrain the link in our IRQ

2024-03-21 Thread Sean Anderson
On 3/21/24 13:25, Tomi Valkeinen wrote:
> On 21/03/2024 17:52, Sean Anderson wrote:
>> On 3/20/24 02:53, Tomi Valkeinen wrote:
>>> On 20/03/2024 00:51, Sean Anderson wrote:
>>>> Retraining the link can take a while, and might involve waiting for
>>>> DPCD reads/writes to complete. This is inappropriate for an IRQ handler.
>>>> Just schedule this work for later completion. This is racy, but will be
>>>> fixed in the next commit.
>>>
>>> You should add the locks first, and use them here, rather than first
>>> adding a buggy commit and fixing it in the next one.
>>
>> I didn't think I could add the locks first since I only noticed the IRQ
>> was threaded right before sending out this series. So yeah, we could add
>> locking, add the workqueue, and then unthread the IRQ.
>>
>>>> Signed-off-by: Sean Anderson 
>>>> ---
>>>> Actually, on second look this IRQ is threaded. So why do we have a
>>>> workqueue for HPD events? Maybe we should make it unthreaded?
>>>
>>> Indeed, there's not much work being done in the IRQ handler. I don't know 
>>> why it's threaded.
>>>
>>> We could move the queued work to be inside the threaded irq handler,
>>> but with a quick look, the HPD work has lines like "msleep(100)" (and
>>> that's inside a for loop...), which is probably not a good thing to do
>>> even in threaded irq handler.
>>>
>>> Although I'm not sure if that code is good to have anywhere. Why do we
>>> even have such code in the HPD work path... We already got the HPD
>>> interrupt. What does "It takes some delay (ex, 100 ~ 500 msec) to get
>>> the HPD signal with some monitors" even mean...
>>
>> The documentation for this bit is
>>
>> | HPD_STATE0ro0x0Contains the raw state of the HPD pin on 
>> the DisplayPort connector.
>>
>> So I think the idea is to perform some debouncing.
> 
> Hmm, it just looks a bit odd to me. It can sleep for a second. And the 
> wording "It takes some delay (ex, 100 ~ 500 msec) to get the HPD signal with 
> some monitors" makes it sound like some kind of a hack...
> 
> The docs mention debounce once:
> 
> https://docs.amd.com/r/en-US/pg299-v-dp-txss1/Hot-Plug-Detection

Are you sure this is the right document? This seems to be documentation for 
[1]. Is that instantiated as a hard block on the ZynqMP?

[1] https://www.xilinx.com/products/intellectual-property/ef-di-displayport.html

> But it's not immediately obvious what the SW must do and what's done by the 
> HW. Debounce is not mentioned later, e.g. in the HPD Event Handling. But if 
> debounce is needed, wouldn't it be perhaps in a few milliseconds, instead of 
> hundreds of milliseconds...

Well, the DP spec says

| If the HPD is the result of a new device being connected, either
| directly to the Source device (signaled by a long HPD), –or– downstream
| of a Branch device (indicated by incrementing the DFP_COUNT field value
| in the DOWN_STREAM_PORT_COUNT register (DPCD 7h[3:0]) and signaled
| by an IRQ_HPD pulse), the Source device shall read the new DisplayID or
| legacy EDID that has been made available to it to ensure that content
| being transmitted over the link is able to be properly received and
| rendered.
|
| Informative Note: If the HPD signal toggling (or bouncing) is the
|   result of the Hot Unplug followed by Hot Plug of a
|   cable-connector assembly, the HPD signal is likely
|   to remain unstable during the de-bouncing period,
|   which is in the order of tens of milliseconds. The
|   Source device may either check the HPD signal’s
|   stability before initiating an AUX read transaction,
|   –or– immediately initiate the AUX read transaction
|   after each HPD rising edge.

So a 100 ms delay seems plausible for some monitors.

That said, maybe we can just skip this and always read the DPCD.

> zynqmp_dp_bridge_detect() is used for drm_bridge_funcs.detect(), and if the 
> cable is not connected, it'll sleep for 1 second (probably more) until 
> returning not connected. It just doesn't sound correct to me.
> 
> Well, it's not part of this patch as such, but related to the amount of time 
> we spend in the interrupt handler (and also the detect()).
> 
>>> Would it be possible to clean up the work funcs a bit (I haven't
>>> looked a the new work func yet), to remove the worst extra sleeps, and
>>> just do all that inside the threaded irq handler?
>>
>> Probably not, since a HPD IRQ results in link retraining, which can

Re: [PATCH v2 8/8] drm: zynqmp_dp: Add debugfs interface for compliance testing

2024-03-21 Thread Sean Anderson
On 3/21/24 12:31, Tomi Valkeinen wrote:
> On 21/03/2024 18:08, Sean Anderson wrote:
>> On 3/20/24 03:49, Tomi Valkeinen wrote:
>>> On 20/03/2024 00:51, Sean Anderson wrote:
>>>
>>>> +/**
>>>> + * enum test_pattern - Test patterns for test testing
>>>
>>> "for test testing"? =)
>>>
>>>> @@ -1655,6 +2321,9 @@ static void zynqmp_dp_hpd_irq_work_func(struct 
>>>> work_struct *work)
>>>>    u8 status[DP_LINK_STATUS_SIZE + 2];
>>>>    int err;
>>>>    +    if (READ_ONCE(dp->ignore_hpd))
>>>> +    return;
>>>> +
>>>>    mutex_lock(>lock);
>>>>    err = drm_dp_dpcd_read(>aux, DP_SINK_COUNT, status,
>>>>   DP_LINK_STATUS_SIZE + 2);
>>>
>>> Why do you need READ/WRITE_ONCE() for ignore_hpd?
>>
>> It's not protected by dp->lock so we don't have to take it for
>> zynqmp_dp_hpd_work_func. Although maybe we should make a version of
>> zynqmp_dp_bridge_detect which assumes we already hold the lock.
> 
> Does using the macros solve some potential issue, or is it just for 
> documenting that this variable is accessed without lock?

Without this the compiler is free to issue multiple loads for this
variable, which could be incorrect.

--Sean



Re: [PATCH v2 8/8] drm: zynqmp_dp: Add debugfs interface for compliance testing

2024-03-21 Thread Sean Anderson
On 3/20/24 03:49, Tomi Valkeinen wrote:
> On 20/03/2024 00:51, Sean Anderson wrote:
> 
>> +/**
>> + * enum test_pattern - Test patterns for test testing
> 
> "for test testing"? =)
> 
>> @@ -1655,6 +2321,9 @@ static void zynqmp_dp_hpd_irq_work_func(struct 
>> work_struct *work)
>>   u8 status[DP_LINK_STATUS_SIZE + 2];
>>   int err;
>>   +if (READ_ONCE(dp->ignore_hpd))
>> +return;
>> +
>>   mutex_lock(>lock);
>>   err = drm_dp_dpcd_read(>aux, DP_SINK_COUNT, status,
>>  DP_LINK_STATUS_SIZE + 2);
> 
> Why do you need READ/WRITE_ONCE() for ignore_hpd?

It's not protected by dp->lock so we don't have to take it for
zynqmp_dp_hpd_work_func. Although maybe we should make a version of
zynqmp_dp_bridge_detect which assumes we already hold the lock.

--Sean


Re: [PATCH v2 5/8] drm: zynqmp_dp: Don't retrain the link in our IRQ

2024-03-21 Thread Sean Anderson
On 3/20/24 02:53, Tomi Valkeinen wrote:
> On 20/03/2024 00:51, Sean Anderson wrote:
>> Retraining the link can take a while, and might involve waiting for
>> DPCD reads/writes to complete. This is inappropriate for an IRQ handler.
>> Just schedule this work for later completion. This is racy, but will be
>> fixed in the next commit.
> 
> You should add the locks first, and use them here, rather than first
> adding a buggy commit and fixing it in the next one.

I didn't think I could add the locks first since I only noticed the IRQ
was threaded right before sending out this series. So yeah, we could add
locking, add the workqueue, and then unthread the IRQ.

>> Signed-off-by: Sean Anderson 
>> ---
>> Actually, on second look this IRQ is threaded. So why do we have a
>> workqueue for HPD events? Maybe we should make it unthreaded?
> 
> Indeed, there's not much work being done in the IRQ handler. I don't know why 
> it's threaded.
> 
> We could move the queued work to be inside the threaded irq handler,
> but with a quick look, the HPD work has lines like "msleep(100)" (and
> that's inside a for loop...), which is probably not a good thing to do
> even in threaded irq handler.
> 
> Although I'm not sure if that code is good to have anywhere. Why do we
> even have such code in the HPD work path... We already got the HPD
> interrupt. What does "It takes some delay (ex, 100 ~ 500 msec) to get
> the HPD signal with some monitors" even mean...

The documentation for this bit is

| HPD_STATE 0   ro  0x0 Contains the raw state of the HPD pin 
on the DisplayPort connector.

So I think the idea is to perform some debouncing.

> Would it be possible to clean up the work funcs a bit (I haven't
> looked a the new work func yet), to remove the worst extra sleeps, and
> just do all that inside the threaded irq handler?

Probably not, since a HPD IRQ results in link retraining, which can take a 
while.

> Do we need to handle interrupts while either delayed work is being done?

Probably not.

> If we do need a delayed work, would just one work be enough which
> handles both HPD_EVENT and HPD_IRQ, instead of two?

Maybe, but then we need to determine which pending events we need to
handle. I think since we have only two events it will be easier to just
have separate workqueues.

--Sean


Re: [PATCH v2 4/8] drm: zynqmp_dp: Rearrange zynqmp_dp for better padding

2024-03-21 Thread Sean Anderson
On 3/20/24 02:14, Tomi Valkeinen wrote:
> On 20/03/2024 00:51, Sean Anderson wrote:
>> Sort the members of struct zynqmp_dp to reduce padding necessary for
>> alignment.
>>
>> Signed-off-by: Sean Anderson 
>> ---
>>
>> Changes in v2:
>> - New
>>
>>   drivers/gpu/drm/xlnx/zynqmp_dp.c | 28 ++--
>>   1 file changed, 14 insertions(+), 14 deletions(-)
>>
>> diff --git a/drivers/gpu/drm/xlnx/zynqmp_dp.c 
>> b/drivers/gpu/drm/xlnx/zynqmp_dp.c
>> index 8635b5673386..f1834c8e3c02 100644
>> --- a/drivers/gpu/drm/xlnx/zynqmp_dp.c
>> +++ b/drivers/gpu/drm/xlnx/zynqmp_dp.c
>> @@ -255,10 +255,10 @@ struct zynqmp_dp_link_config {
>>* @fmt: format identifier string
>>*/
>>   struct zynqmp_dp_mode {
>> -u8 bw_code;
>> -u8 lane_cnt;
>> -int pclock;
>>   const char *fmt;
>> +int pclock;
>> +u8 bw_code;
>> +u8 lane_cnt;
>>   };
>> /**
>> @@ -295,27 +295,27 @@ struct zynqmp_dp_config {
>>* @train_set: set of training data
>>*/
>>   struct zynqmp_dp {
>> +struct drm_dp_aux aux;
>> +struct drm_bridge bridge;
>> +struct delayed_work hpd_work;
>> +
>> +struct drm_bridge *next_bridge;
>>   struct device *dev;
>>   struct zynqmp_dpsub *dpsub;
>>   void __iomem *iomem;
>>   struct reset_control *reset;
>> -int irq;
>> -
>> -struct drm_bridge bridge;
>> -struct drm_bridge *next_bridge;
>> -
>> -struct zynqmp_dp_config config;
>> -struct drm_dp_aux aux;
>>   struct phy *phy[ZYNQMP_DP_MAX_LANES];
>> -u8 num_lanes;
>> -struct delayed_work hpd_work;
>> +
>>   enum drm_connector_status status;
>> +int irq;
>>   bool enabled;
>>   -u8 dpcd[DP_RECEIVER_CAP_SIZE];
>> -struct zynqmp_dp_link_config link_config;
>>   struct zynqmp_dp_mode mode;
>> +struct zynqmp_dp_link_config link_config;
>> +struct zynqmp_dp_config config;
>> +u8 dpcd[DP_RECEIVER_CAP_SIZE];
>>   u8 train_set[ZYNQMP_DP_MAX_LANES];
>> +u8 num_lanes;
>>   };
>> static inline struct zynqmp_dp *bridge_to_dp(struct drm_bridge *bridge)
> 
> If you change the order of the fields, you should change the order in the 
> kernel doc accordingly.

The kernel doc is documentation, so it should continue to group similar
functionality together.

> To be honest, I'm not sure if I like this patch. We have usually one instance 
> of these structs allocated. How many bytes do we save?

Actually, the main reason is to make it easy to determine where to
insert new members. Stick the pointers with the pointers, u8s with u8s,
etc.

> I'm fine with getting easy savings by changing the field order in some cases, 
> but I think the "human" side of the order is important too: usually the 
> fields are grouped in some way, and ordered so that the more base or generic 
> ones are first, and fields for some specific feature are later. And fields 
> protected by a lock should be grouped together, with their lock being 
> first/last in that group.
> 
> Looking at the zynqmp_dp struct with this patch, I get an urge to start 
> moving things around: dev, dpsub, iomem, etc first, hpd somewhere later. Base 
> config fields like config, num_lanes, irq would be grouped together. Etc.

--Sean


Re: [PATCH v2 3/8] drm: zynqmp_dp: Adjust training values per-lane

2024-03-21 Thread Sean Anderson
On 3/20/24 01:57, Tomi Valkeinen wrote:
> On 20/03/2024 00:51, Sean Anderson wrote:
>> The feedback we get from the DPRX is per-lane. Make changes using this
>> information, instead of picking the maximum values from all lanes. This
>> results in more-consistent training on marginal links.
>>
>> Signed-off-by: Sean Anderson 
>> ---
>>
>> (no changes since v1)
>>
>>   drivers/gpu/drm/xlnx/zynqmp_dp.c | 23 ---
>>   1 file changed, 8 insertions(+), 15 deletions(-)
>>
>> diff --git a/drivers/gpu/drm/xlnx/zynqmp_dp.c 
>> b/drivers/gpu/drm/xlnx/zynqmp_dp.c
>> index 98a32e6a0459..8635b5673386 100644
>> --- a/drivers/gpu/drm/xlnx/zynqmp_dp.c
>> +++ b/drivers/gpu/drm/xlnx/zynqmp_dp.c
>> @@ -605,28 +605,21 @@ static void zynqmp_dp_adjust_train(struct zynqmp_dp 
>> *dp,
>>  u8 link_status[DP_LINK_STATUS_SIZE])
>>   {
>>   u8 *train_set = dp->train_set;
>> -u8 voltage = 0, preemphasis = 0;
>>   u8 i;
>> for (i = 0; i < dp->mode.lane_cnt; i++) {
>> -u8 v = drm_dp_get_adjust_request_voltage(link_status, i);
>> -u8 p = drm_dp_get_adjust_request_pre_emphasis(link_status, i);
>> +u8 voltage = drm_dp_get_adjust_request_voltage(link_status, i);
>> +u8 preemphasis =
>> +drm_dp_get_adjust_request_pre_emphasis(link_status, i);
>>   -if (v > voltage)
>> -voltage = v;
>> +if (voltage >= DP_TRAIN_VOLTAGE_SWING_LEVEL_3)
>> +voltage |= DP_TRAIN_MAX_SWING_REACHED;
>>   -if (p > preemphasis)
>> -preemphasis = p;
>> -}
>> +if (preemphasis >= DP_TRAIN_PRE_EMPH_LEVEL_2)
>> +preemphasis |= DP_TRAIN_MAX_PRE_EMPHASIS_REACHED;
>>   -if (voltage >= DP_TRAIN_VOLTAGE_SWING_LEVEL_3)
>> -voltage |= DP_TRAIN_MAX_SWING_REACHED;
>> -
>> -if (preemphasis >= DP_TRAIN_PRE_EMPH_LEVEL_2)
>> -preemphasis |= DP_TRAIN_MAX_PRE_EMPHASIS_REACHED;
>> -
>> -for (i = 0; i < dp->mode.lane_cnt; i++)
>>   train_set[i] = voltage | preemphasis;
>> +}
>>   }
>> /**
> 
> Looks fine to me, but a few cosmetic suggestions, feel free to ignore if not 
> to your liking:
> 
> 1)
> 
> u8 voltage, preemphasis;
> 
> voltage = drm_dp_get_adjust_request_voltage(link_status, i);
> preemphasis = drm_dp_get_adjust_request_pre_emphasis(link_status, i);

If the comment here is about the line break, I agree that this looks
better but the second line is over 80 characters.

> 2)
> 
> for (unsigned int i = 0; i < dp->mode.lane_cnt; i++)

Is this allowed now?

> 3)
> 
> dp->train_set[i] = voltage | preemphasis;

This would be undone in patch 7/8.

--Sean

> Reviewed-by: Tomi Valkeinen 
> 
>  Tomi
> 


Re: [PATCH v2 1/8] drm: xlnx: Fix kerneldoc

2024-03-21 Thread Sean Anderson
On 3/20/24 02:05, Randy Dunlap wrote:
> 
> 
> On 3/19/24 22:42, Tomi Valkeinen wrote:
>> On 20/03/2024 00:51, Sean Anderson wrote:
>>> Fix a few errors in the kerneldoc. Mostly this addresses missing/renamed
>>> members.
>>>
>>> Signed-off-by: Sean Anderson 
>>> ---
>>>
>>> Changes in v2:
>>> - New
>>>
>>>   drivers/gpu/drm/xlnx/zynqmp_disp.c  | 6 +++---
>>>   drivers/gpu/drm/xlnx/zynqmp_dpsub.h | 1 +
>>>   drivers/gpu/drm/xlnx/zynqmp_kms.h   | 4 ++--
>>>   3 files changed, 6 insertions(+), 5 deletions(-)
>>>
>>> diff --git a/drivers/gpu/drm/xlnx/zynqmp_disp.c 
>>> b/drivers/gpu/drm/xlnx/zynqmp_disp.c
>>> index 407bc07cec69..f79bf3fb8110 100644
>>> --- a/drivers/gpu/drm/xlnx/zynqmp_disp.c
>>> +++ b/drivers/gpu/drm/xlnx/zynqmp_disp.c
>>> @@ -128,9 +128,9 @@ struct zynqmp_disp_layer {
>>>* struct zynqmp_disp - Display controller
>>>* @dev: Device structure
>>>* @dpsub: Display subsystem
>>> - * @blend.base: Register I/O base address for the blender
>>> - * @avbuf.base: Register I/O base address for the audio/video buffer 
>>> manager
>>> - * @audio.base: Registers I/O base address for the audio mixer
>>> + * @blend: Register I/O base address for the blender
>>> + * @avbuf: Register I/O base address for the audio/video buffer manager
>>> + * @audio: Registers I/O base address for the audio mixer
>> 
>> Afaics, the kernel doc guide:
>> 
>> https://docs.kernel.org/doc-guide/kernel-doc.html#nested-structs-unions
>> 
>> says that the current version is correct. Or is the issue that while, say, 
>> 'base' is documented, 'blend' was not?
> 
> Hi,
> 
> I would do it more like so:
> 
> ---
>  drivers/gpu/drm/xlnx/zynqmp_disp.c |3 +++
>  1 file changed, 3 insertions(+)
> 
> diff -- a/drivers/gpu/drm/xlnx/zynqmp_disp.c 
> b/drivers/gpu/drm/xlnx/zynqmp_disp.c
> --- a/drivers/gpu/drm/xlnx/zynqmp_disp.c
> +++ b/drivers/gpu/drm/xlnx/zynqmp_disp.c
> @@ -128,8 +128,11 @@ struct zynqmp_disp_layer {
>   * struct zynqmp_disp - Display controller
>   * @dev: Device structure
>   * @dpsub: Display subsystem
> + * @blend: blender iomem info
>   * @blend.base: Register I/O base address for the blender
> + * @avbuf: audio/video buffer iomem info
>   * @avbuf.base: Register I/O base address for the audio/video buffer manager
> + * @audio: audio mixer iomem info
>   * @audio.base: Registers I/O base address for the audio mixer
>   * @layers: Layers (planes)
>   */
> 
> 
> but in my testing, Sean's way or my way result in no warning/errors.
> 

The specific errors are:

../drivers/gpu/drm/xlnx/zynqmp_disp.c:151: warning: Function parameter or 
struct member 'blend' not described in 'zynqmp_disp'
../drivers/gpu/drm/xlnx/zynqmp_disp.c:151: warning: Function parameter or 
struct member 'avbuf' not described in 'zynqmp_disp'
../drivers/gpu/drm/xlnx/zynqmp_disp.c:151: warning: Function parameter or 
struct member 'audio' not described in 'zynqmp_disp'

I don't see the need to document a single-member struct twice. Actually,
maybe it would be better to just lift the .base member to live in
zynqmp_disp. But I think that would be better in another series.

--Sean


Re: [PATCH v2 0/8] drm: zynqmp_dp: Misc. patches and debugfs support

2024-03-19 Thread Sean Anderson
On 3/19/24 18:51, Sean Anderson wrote:
> This series adds debugfs support for the zynqmp_dp driver. The intent is
> to enable compliance testing or to help debug signal-integrity issues.
> 
> The first four patches are general improvements (and could be applied
> independently), while the last four add debugfs support.
> 
> Changes in v2:
> - Fix kerneldoc
> - Rearrange zynqmp_dp for better padding
> - Document hpd_irq_work
> - Split off the HPD IRQ work into another commit
> - Expand the commit message
> - Document debugfs files
> - Add ignore_aux_errors and ignore_hpd debugfs files to replace earlier
>   implicit functionality
> - Attempt to fix unreproducable, spurious build warning
> - Drop "Optionally ignore DPCD errors" in favor of a debugfs file
>   directly affecting zynqmp_dp_aux_transfer.
> 
> Sean Anderson (8):
>   drm: xlnx: Fix kerneldoc
>   drm: zynqmp_dp: Downgrade log level for aux retries message
>   drm: zynqmp_dp: Adjust training values per-lane
>   drm: zynqmp_dp: Rearrange zynqmp_dp for better padding
>   drm: zynqmp_dp: Don't retrain the link in our IRQ
>   drm: zynqmp_dp: Add locking
>   drm: zynqmp_dp: Split off several helper functions
>   drm: zynqmp_dp: Add debugfs interface for compliance testing
> 
>  Documentation/gpu/drivers.rst   |   1 +
>  Documentation/gpu/zynqmp.rst| 149 +
>  MAINTAINERS |   1 +
>  drivers/gpu/drm/xlnx/zynqmp_disp.c  |   6 +-
>  drivers/gpu/drm/xlnx/zynqmp_dp.c| 836 +---
>  drivers/gpu/drm/xlnx/zynqmp_dpsub.h |   1 +
>  drivers/gpu/drm/xlnx/zynqmp_kms.h   |   4 +-
>  7 files changed, 931 insertions(+), 67 deletions(-)
>  create mode 100644 Documentation/gpu/zynqmp.rst
> 

+CC Dmitry, Tomi, and Anatoliy who I forgot to CC

Let me know if you want to be taken off CC for future revisions

--Sean


[PATCH v2 8/8] drm: zynqmp_dp: Add debugfs interface for compliance testing

2024-03-19 Thread Sean Anderson
Add a debugfs interface for exercising the various test modes supported
by the DisplayPort controller. This allows performing compliance
testing, or performing signal integrity measurements on a failing link.
At the moment, we do not support sink-driven link quality testing,
although such support would be fairly easy to add.

Additionally, add some debugfs files for ignoring AUX errors and HPD
events, as this can allow testing with equipment that cannot emulate a
DPRX.

Signed-off-by: Sean Anderson 
---

Changes in v2:
- Document debugfs files
- Add ignore_aux_errors and ignore_hpd debugfs files to replace earlier
  implicit functionality
- Attempt to fix unreproducable, spurious build warning

 Documentation/gpu/drivers.rst|   1 +
 Documentation/gpu/zynqmp.rst | 149 +++
 MAINTAINERS  |   1 +
 drivers/gpu/drm/xlnx/zynqmp_dp.c | 675 ++-
 4 files changed, 823 insertions(+), 3 deletions(-)
 create mode 100644 Documentation/gpu/zynqmp.rst

diff --git a/Documentation/gpu/drivers.rst b/Documentation/gpu/drivers.rst
index b899cbc5c2b4..187201aedbe5 100644
--- a/Documentation/gpu/drivers.rst
+++ b/Documentation/gpu/drivers.rst
@@ -22,6 +22,7 @@ GPU Driver Documentation
afbc
komeda-kms
panfrost
+   zynqmp
 
 .. only::  subproject and html
 
diff --git a/Documentation/gpu/zynqmp.rst b/Documentation/gpu/zynqmp.rst
new file mode 100644
index ..4173a79972a2
--- /dev/null
+++ b/Documentation/gpu/zynqmp.rst
@@ -0,0 +1,149 @@
+.. SPDX-License-Identifier: GPL-2.0+
+
+===
+Xilinx ZynqMP Ultrascale+ DisplayPort Subsystem
+===
+
+This subsystem handles DisplayPort video and audio output on the ZynqMP. It
+supports in-memory framebuffers with the DisplayPort DMA controller
+(xilinx-dpdma), as well as "live" video and audio from the programmable logic
+(PL). This subsystem can perform several transformations, including color space
+conversion, alpha blending, and audio mixing, although not all features are
+currently supported.
+
+debugfs
+---
+
+To support debugging and compliance testing, several test modes can be enabled
+though debugfs. The following files in /sys/kernel/debug/dri/X/DP-1/test/
+control the DisplayPort test modes:
+
+active:
+Writing a 1 to this file will activate test mode, and writing a 0 will
+deactivate test mode. Writing a 1 or 0 when the test mode is already
+active/inactive will re-activate/re-deactivate test mode. When test
+mode is inactive, changes made to other files will have no (immediate)
+effect, although the settings will be saved for when test mode is
+activated. When test mode is active, changes made to other files will
+apply immediately.
+
+custom:
+Custom test pattern value
+
+downspread:
+Enable/disable clock downspreading (spread-spectrum clocking) by
+writing 1/0
+
+enhanced:
+Enable/disable enhanced framing
+
+ignore_aux_errors:
+Ignore AUX errors when set to 1. Writes to this file take effect
+immediately (regardless of whether test mode is active) and affect all
+AUX transfers.
+
+ignore_hpd:
+Ignore hotplug events (such as cable removals or monitor link
+retraining requests) when set to 1. Writes to this file take effect
+immediately (regardless of whether test mode is active).
+
+laneX_preemphasis:
+Preemphasis from 0 (lowest) to 2 (most) for lane X
+
+laneX_swing:
+Voltage swing from 0 (lowest) to 3 (most) for lane X
+
+lanes:
+Number of lanes to use (1, 2, or 4)
+
+pattern:
+Test pattern. May be one of:
+
+video
+Use regular video input
+
+symbol-error
+Symbol error measurement pattern
+
+prbs7
+Output of the PRBS7 (x^7 + x^6 + 1) polynomial
+
+80bit-custom
+A custom 80-bit pattern
+
+cp2520
+HBR2 compliance eye pattern
+
+tps1
+Link training symbol pattern TPS1 (/D10.2/)
+
+tps2
+Link training symbol pattern TPS2
+
+tps3
+Link training symbol pattern TPS3 (for HBR2)
+
+rate:
+Rate in hertz. One of
+
+* 54 (HBR2)
+* 27 (HBR)
+* 162000 (RBR)
+
+You can dump the displayport test settings with the following command::
+
+for prop in /sys/kernel/debug/dri/1/DP-1/test/*; do
+printf '%-17s ' ${prop##*/}
+if [ ${prop##*/} = custom ]; then
+hexdump -C $prop | head -1
+else
+cat $prop
+fi
+done
+
+The output could look some

[PATCH v2 6/8] drm: zynqmp_dp: Add locking

2024-03-19 Thread Sean Anderson
Add some locking to prevent the IRQ/workers/bridge API calls from stepping
on each other's toes. This lock protects:

- Non-atomic registers configuring the link. That is, everything but the
  IRQ registers (since these are accessed in an atomic fashion), and the DP
  AUX registers (since these don't affect the link). We also access AUX
  while holding this lock, so it would be very tricky to support.
- Link configuration. This is effectively everything in zynqmp_dp which
  isn't read-only after probe time. So from next_bridge onward.

This lock is designed to protect configuration changes so we don't have to
do anything tricky. Configuration should never be in the hot path, so I'm
not worried about performance.

Signed-off-by: Sean Anderson 
---

Changes in v2:
- Split off the HPD IRQ work into another commit
- Expand the commit message

 drivers/gpu/drm/xlnx/zynqmp_dp.c | 20 
 1 file changed, 20 insertions(+)

diff --git a/drivers/gpu/drm/xlnx/zynqmp_dp.c b/drivers/gpu/drm/xlnx/zynqmp_dp.c
index f3fcdbf662fa..a90ab5c0f5cf 100644
--- a/drivers/gpu/drm/xlnx/zynqmp_dp.c
+++ b/drivers/gpu/drm/xlnx/zynqmp_dp.c
@@ -279,6 +279,7 @@ struct zynqmp_dp_config {
  * @dpsub: Display subsystem
  * @iomem: device I/O memory for register access
  * @reset: reset controller
+ * @lock: Mutex protecting this struct and register access (but not AUX)
  * @irq: irq
  * @bridge: DRM bridge for the DP encoder
  * @next_bridge: The downstream bridge
@@ -294,12 +295,17 @@ struct zynqmp_dp_config {
  * @link_config: common link configuration between IP core and sink device
  * @mode: current mode between IP core and sink device
  * @train_set: set of training data
+ *
+ * @lock covers the link configuration in this struct and the device's
+ * registers. It does not cover @aux. It is not strictly required for any of
+ * the members which are only modified at probe/remove time (e.g. @dev).
  */
 struct zynqmp_dp {
struct drm_dp_aux aux;
struct drm_bridge bridge;
struct delayed_work hpd_work;
struct delayed_work hpd_irq_work;
+   struct mutex lock;
 
struct drm_bridge *next_bridge;
struct device *dev;
@@ -1373,8 +1379,10 @@ zynqmp_dp_bridge_mode_valid(struct drm_bridge *bridge,
}
 
/* Check with link rate and lane count */
+   mutex_lock(>lock);
rate = zynqmp_dp_max_rate(dp->link_config.max_rate,
  dp->link_config.max_lanes, dp->config.bpp);
+   mutex_unlock(>lock);
if (mode->clock > rate) {
dev_dbg(dp->dev, "filtered mode %s for high pixel rate\n",
mode->name);
@@ -1401,6 +1409,7 @@ static void zynqmp_dp_bridge_atomic_enable(struct 
drm_bridge *bridge,
 
pm_runtime_get_sync(dp->dev);
 
+   mutex_lock(>lock);
zynqmp_dp_disp_enable(dp, old_bridge_state);
 
/*
@@ -1461,6 +1470,7 @@ static void zynqmp_dp_bridge_atomic_enable(struct 
drm_bridge *bridge,
zynqmp_dp_write(dp, ZYNQMP_DP_SOFTWARE_RESET,
ZYNQMP_DP_SOFTWARE_RESET_ALL);
zynqmp_dp_write(dp, ZYNQMP_DP_MAIN_STREAM_ENABLE, 1);
+   mutex_unlock(>lock);
 }
 
 static void zynqmp_dp_bridge_atomic_disable(struct drm_bridge *bridge,
@@ -1468,6 +1478,7 @@ static void zynqmp_dp_bridge_atomic_disable(struct 
drm_bridge *bridge,
 {
struct zynqmp_dp *dp = bridge_to_dp(bridge);
 
+   mutex_lock(>lock);
dp->enabled = false;
cancel_delayed_work(>hpd_work);
zynqmp_dp_write(dp, ZYNQMP_DP_MAIN_STREAM_ENABLE, 0);
@@ -1478,6 +1489,7 @@ static void zynqmp_dp_bridge_atomic_disable(struct 
drm_bridge *bridge,
zynqmp_dp_write(dp, ZYNQMP_DP_TX_AUDIO_CONTROL, 0);
 
zynqmp_dp_disp_disable(dp, old_bridge_state);
+   mutex_unlock(>lock);
 
pm_runtime_put_sync(dp->dev);
 }
@@ -1520,6 +1532,8 @@ static enum drm_connector_status 
zynqmp_dp_bridge_detect(struct drm_bridge *brid
u32 state, i;
int ret;
 
+   mutex_lock(>lock);
+
/*
 * This is from heuristic. It takes some delay (ex, 100 ~ 500 msec) to
 * get the HPD signal with some monitors.
@@ -1547,11 +1561,13 @@ static enum drm_connector_status 
zynqmp_dp_bridge_detect(struct drm_bridge *brid
   dp->num_lanes);
 
dp->status = connector_status_connected;
+   mutex_unlock(>lock);
return connector_status_connected;
}
 
 disconnected:
dp->status = connector_status_disconnected;
+   mutex_unlock(>lock);
return connector_status_disconnected;
 }
 
@@ -1620,6 +1636,7 @@ static void zynqmp_dp_hpd_irq_work_func(struct 
work_struct *work)
u8 status[DP_LINK_STATUS_SIZE + 2];
int err;
 
+   mutex_lock(>lock);
err = drm_dp_dpcd_read(>aux, DP_SINK_COUNT, status,
   

[PATCH v2 5/8] drm: zynqmp_dp: Don't retrain the link in our IRQ

2024-03-19 Thread Sean Anderson
Retraining the link can take a while, and might involve waiting for
DPCD reads/writes to complete. This is inappropriate for an IRQ handler.
Just schedule this work for later completion. This is racy, but will be
fixed in the next commit.

Signed-off-by: Sean Anderson 
---
Actually, on second look this IRQ is threaded. So why do we have a
workqueue for HPD events? Maybe we should make it unthreaded?

Changes in v2:
- Document hpd_irq_work
- Split this off from the lcoking changes

 drivers/gpu/drm/xlnx/zynqmp_dp.c | 43 
 1 file changed, 27 insertions(+), 16 deletions(-)

diff --git a/drivers/gpu/drm/xlnx/zynqmp_dp.c b/drivers/gpu/drm/xlnx/zynqmp_dp.c
index f1834c8e3c02..f3fcdbf662fa 100644
--- a/drivers/gpu/drm/xlnx/zynqmp_dp.c
+++ b/drivers/gpu/drm/xlnx/zynqmp_dp.c
@@ -287,6 +287,7 @@ struct zynqmp_dp_config {
  * @phy: PHY handles for DP lanes
  * @num_lanes: number of enabled phy lanes
  * @hpd_work: hot plug detection worker
+ * @hpd_irq_work: hot plug detection IRQ worker
  * @status: connection status
  * @enabled: flag to indicate if the device is enabled
  * @dpcd: DP configuration data from currently connected sink device
@@ -298,6 +299,7 @@ struct zynqmp_dp {
struct drm_dp_aux aux;
struct drm_bridge bridge;
struct delayed_work hpd_work;
+   struct delayed_work hpd_irq_work;
 
struct drm_bridge *next_bridge;
struct device *dev;
@@ -1611,6 +1613,27 @@ static void zynqmp_dp_hpd_work_func(struct work_struct 
*work)
drm_bridge_hpd_notify(>bridge, status);
 }
 
+static void zynqmp_dp_hpd_irq_work_func(struct work_struct *work)
+{
+   struct zynqmp_dp *dp = container_of(work, struct zynqmp_dp,
+   hpd_irq_work.work);
+   u8 status[DP_LINK_STATUS_SIZE + 2];
+   int err;
+
+   err = drm_dp_dpcd_read(>aux, DP_SINK_COUNT, status,
+  DP_LINK_STATUS_SIZE + 2);
+   if (err < 0) {
+   dev_dbg_ratelimited(dp->dev,
+   "could not read sink status: %d\n", err);
+   } else {
+   if (status[4] & DP_LINK_STATUS_UPDATED ||
+   !drm_dp_clock_recovery_ok([2], dp->mode.lane_cnt) ||
+   !drm_dp_channel_eq_ok([2], dp->mode.lane_cnt)) {
+   zynqmp_dp_train_loop(dp);
+   }
+   }
+}
+
 static irqreturn_t zynqmp_dp_irq_handler(int irq, void *data)
 {
struct zynqmp_dp *dp = (struct zynqmp_dp *)data;
@@ -1635,23 +1658,9 @@ static irqreturn_t zynqmp_dp_irq_handler(int irq, void 
*data)
if (status & ZYNQMP_DP_INT_HPD_EVENT)
schedule_delayed_work(>hpd_work, 0);
 
-   if (status & ZYNQMP_DP_INT_HPD_IRQ) {
-   int ret;
-   u8 status[DP_LINK_STATUS_SIZE + 2];
+   if (status & ZYNQMP_DP_INT_HPD_IRQ)
+   schedule_delayed_work(>hpd_irq_work, 0);
 
-   ret = drm_dp_dpcd_read(>aux, DP_SINK_COUNT, status,
-  DP_LINK_STATUS_SIZE + 2);
-   if (ret < 0)
-   goto handled;
-
-   if (status[4] & DP_LINK_STATUS_UPDATED ||
-   !drm_dp_clock_recovery_ok([2], dp->mode.lane_cnt) ||
-   !drm_dp_channel_eq_ok([2], dp->mode.lane_cnt)) {
-   zynqmp_dp_train_loop(dp);
-   }
-   }
-
-handled:
return IRQ_HANDLED;
 }
 
@@ -1676,6 +1685,7 @@ int zynqmp_dp_probe(struct zynqmp_dpsub *dpsub)
dp->status = connector_status_disconnected;
 
INIT_DELAYED_WORK(>hpd_work, zynqmp_dp_hpd_work_func);
+   INIT_DELAYED_WORK(>hpd_irq_work, zynqmp_dp_hpd_irq_work_func);
 
/* Acquire all resources (IOMEM, IRQ and PHYs). */
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "dp");
@@ -1775,6 +1785,7 @@ void zynqmp_dp_remove(struct zynqmp_dpsub *dpsub)
zynqmp_dp_write(dp, ZYNQMP_DP_INT_DS, ZYNQMP_DP_INT_ALL);
disable_irq(dp->irq);
 
+   cancel_delayed_work_sync(>hpd_irq_work);
cancel_delayed_work_sync(>hpd_work);
 
zynqmp_dp_write(dp, ZYNQMP_DP_TRANSMITTER_ENABLE, 0);
-- 
2.35.1.1320.gc452695387.dirty



[PATCH v2 4/8] drm: zynqmp_dp: Rearrange zynqmp_dp for better padding

2024-03-19 Thread Sean Anderson
Sort the members of struct zynqmp_dp to reduce padding necessary for
alignment.

Signed-off-by: Sean Anderson 
---

Changes in v2:
- New

 drivers/gpu/drm/xlnx/zynqmp_dp.c | 28 ++--
 1 file changed, 14 insertions(+), 14 deletions(-)

diff --git a/drivers/gpu/drm/xlnx/zynqmp_dp.c b/drivers/gpu/drm/xlnx/zynqmp_dp.c
index 8635b5673386..f1834c8e3c02 100644
--- a/drivers/gpu/drm/xlnx/zynqmp_dp.c
+++ b/drivers/gpu/drm/xlnx/zynqmp_dp.c
@@ -255,10 +255,10 @@ struct zynqmp_dp_link_config {
  * @fmt: format identifier string
  */
 struct zynqmp_dp_mode {
-   u8 bw_code;
-   u8 lane_cnt;
-   int pclock;
const char *fmt;
+   int pclock;
+   u8 bw_code;
+   u8 lane_cnt;
 };
 
 /**
@@ -295,27 +295,27 @@ struct zynqmp_dp_config {
  * @train_set: set of training data
  */
 struct zynqmp_dp {
+   struct drm_dp_aux aux;
+   struct drm_bridge bridge;
+   struct delayed_work hpd_work;
+
+   struct drm_bridge *next_bridge;
struct device *dev;
struct zynqmp_dpsub *dpsub;
void __iomem *iomem;
struct reset_control *reset;
-   int irq;
-
-   struct drm_bridge bridge;
-   struct drm_bridge *next_bridge;
-
-   struct zynqmp_dp_config config;
-   struct drm_dp_aux aux;
struct phy *phy[ZYNQMP_DP_MAX_LANES];
-   u8 num_lanes;
-   struct delayed_work hpd_work;
+
enum drm_connector_status status;
+   int irq;
bool enabled;
 
-   u8 dpcd[DP_RECEIVER_CAP_SIZE];
-   struct zynqmp_dp_link_config link_config;
struct zynqmp_dp_mode mode;
+   struct zynqmp_dp_link_config link_config;
+   struct zynqmp_dp_config config;
+   u8 dpcd[DP_RECEIVER_CAP_SIZE];
u8 train_set[ZYNQMP_DP_MAX_LANES];
+   u8 num_lanes;
 };
 
 static inline struct zynqmp_dp *bridge_to_dp(struct drm_bridge *bridge)
-- 
2.35.1.1320.gc452695387.dirty



[PATCH v2 7/8] drm: zynqmp_dp: Split off several helper functions

2024-03-19 Thread Sean Anderson
In preparation for supporting compliance testing, split off several
helper functions. No functional change intended.

Signed-off-by: Sean Anderson 
Reviewed-by: Laurent Pinchart 
---

(no changes since v1)

 drivers/gpu/drm/xlnx/zynqmp_dp.c | 49 ++--
 1 file changed, 34 insertions(+), 15 deletions(-)

diff --git a/drivers/gpu/drm/xlnx/zynqmp_dp.c b/drivers/gpu/drm/xlnx/zynqmp_dp.c
index a90ab5c0f5cf..6e04dcf61e19 100644
--- a/drivers/gpu/drm/xlnx/zynqmp_dp.c
+++ b/drivers/gpu/drm/xlnx/zynqmp_dp.c
@@ -633,6 +633,7 @@ static void zynqmp_dp_adjust_train(struct zynqmp_dp *dp,
 /**
  * zynqmp_dp_update_vs_emph - Update the training values
  * @dp: DisplayPort IP core structure
+ * @train_set: A set of training values
  *
  * Update the training values based on the request from sink. The mapped values
  * are predefined, and values(vs, pe, pc) are from the device manual.
@@ -640,12 +641,12 @@ static void zynqmp_dp_adjust_train(struct zynqmp_dp *dp,
  * Return: 0 if vs and emph are updated successfully, or the error code 
returned
  * by drm_dp_dpcd_write().
  */
-static int zynqmp_dp_update_vs_emph(struct zynqmp_dp *dp)
+static int zynqmp_dp_update_vs_emph(struct zynqmp_dp *dp, u8 *train_set)
 {
unsigned int i;
int ret;
 
-   ret = drm_dp_dpcd_write(>aux, DP_TRAINING_LANE0_SET, dp->train_set,
+   ret = drm_dp_dpcd_write(>aux, DP_TRAINING_LANE0_SET, train_set,
dp->mode.lane_cnt);
if (ret < 0)
return ret;
@@ -653,7 +654,7 @@ static int zynqmp_dp_update_vs_emph(struct zynqmp_dp *dp)
for (i = 0; i < dp->mode.lane_cnt; i++) {
u32 reg = ZYNQMP_DP_SUB_TX_PHY_PRECURSOR_LANE_0 + i * 4;
union phy_configure_opts opts = { 0 };
-   u8 train = dp->train_set[i];
+   u8 train = train_set[i];
 
opts.dp.voltage[0] = (train & DP_TRAIN_VOLTAGE_SWING_MASK)
   >> DP_TRAIN_VOLTAGE_SWING_SHIFT;
@@ -697,7 +698,7 @@ static int zynqmp_dp_link_train_cr(struct zynqmp_dp *dp)
 * So, This loop should exit before 512 iterations
 */
for (max_tries = 0; max_tries < 512; max_tries++) {
-   ret = zynqmp_dp_update_vs_emph(dp);
+   ret = zynqmp_dp_update_vs_emph(dp, dp->train_set);
if (ret)
return ret;
 
@@ -762,7 +763,7 @@ static int zynqmp_dp_link_train_ce(struct zynqmp_dp *dp)
return ret;
 
for (tries = 0; tries < DP_MAX_TRAINING_TRIES; tries++) {
-   ret = zynqmp_dp_update_vs_emph(dp);
+   ret = zynqmp_dp_update_vs_emph(dp, dp->train_set);
if (ret)
return ret;
 
@@ -785,28 +786,29 @@ static int zynqmp_dp_link_train_ce(struct zynqmp_dp *dp)
 }
 
 /**
- * zynqmp_dp_train - Train the link
+ * zynqmp_dp_setup() - Set up major link parameters
  * @dp: DisplayPort IP core structure
+ * @bw_code: The link bandwidth as a multiple of 270 MHz
+ * @lane_cnt: The number of lanes to use
+ * @enhanced: Use enhanced framing
+ * @downspread: Enable spread-spectrum clocking
  *
- * Return: 0 if all trains are done successfully, or corresponding error code.
+ * Return: 0 on success, or -errno on failure
  */
-static int zynqmp_dp_train(struct zynqmp_dp *dp)
+static int zynqmp_dp_setup(struct zynqmp_dp *dp, u8 bw_code, u8 lane_cnt,
+  bool enhanced, bool downspread)
 {
u32 reg;
-   u8 bw_code = dp->mode.bw_code;
-   u8 lane_cnt = dp->mode.lane_cnt;
u8 aux_lane_cnt = lane_cnt;
-   bool enhanced;
int ret;
 
zynqmp_dp_write(dp, ZYNQMP_DP_LANE_COUNT_SET, lane_cnt);
-   enhanced = drm_dp_enhanced_frame_cap(dp->dpcd);
if (enhanced) {
zynqmp_dp_write(dp, ZYNQMP_DP_ENHANCED_FRAME_EN, 1);
aux_lane_cnt |= DP_LANE_COUNT_ENHANCED_FRAME_EN;
}
 
-   if (dp->dpcd[3] & 0x1) {
+   if (downspread) {
zynqmp_dp_write(dp, ZYNQMP_DP_DOWNSPREAD_CTL, 1);
drm_dp_dpcd_writeb(>aux, DP_DOWNSPREAD_CTRL,
   DP_SPREAD_AMP_0_5);
@@ -849,8 +851,25 @@ static int zynqmp_dp_train(struct zynqmp_dp *dp)
}
 
zynqmp_dp_write(dp, ZYNQMP_DP_PHY_CLOCK_SELECT, reg);
-   ret = zynqmp_dp_phy_ready(dp);
-   if (ret < 0)
+   return zynqmp_dp_phy_ready(dp);
+}
+
+
+/**
+ * zynqmp_dp_train - Train the link
+ * @dp: DisplayPort IP core structure
+ *
+ * Return: 0 if all trains are done successfully, or corresponding error code.
+ */
+static int zynqmp_dp_train(struct zynqmp_dp *dp)
+{
+   int ret;
+
+   ret = zynqmp_dp_setup(dp, dp->mode.bw_code, dp->mode.lane_cnt,
+ drm_dp_enhanced_frame_cap(dp->dpcd),
+ dp->dpcd[DP_MAX_DOWNSPREAD] &
+

[PATCH v2 0/8] drm: zynqmp_dp: Misc. patches and debugfs support

2024-03-19 Thread Sean Anderson
This series adds debugfs support for the zynqmp_dp driver. The intent is
to enable compliance testing or to help debug signal-integrity issues.

The first four patches are general improvements (and could be applied
independently), while the last four add debugfs support.

Changes in v2:
- Fix kerneldoc
- Rearrange zynqmp_dp for better padding
- Document hpd_irq_work
- Split off the HPD IRQ work into another commit
- Expand the commit message
- Document debugfs files
- Add ignore_aux_errors and ignore_hpd debugfs files to replace earlier
  implicit functionality
- Attempt to fix unreproducable, spurious build warning
- Drop "Optionally ignore DPCD errors" in favor of a debugfs file
  directly affecting zynqmp_dp_aux_transfer.

Sean Anderson (8):
  drm: xlnx: Fix kerneldoc
  drm: zynqmp_dp: Downgrade log level for aux retries message
  drm: zynqmp_dp: Adjust training values per-lane
  drm: zynqmp_dp: Rearrange zynqmp_dp for better padding
  drm: zynqmp_dp: Don't retrain the link in our IRQ
  drm: zynqmp_dp: Add locking
  drm: zynqmp_dp: Split off several helper functions
  drm: zynqmp_dp: Add debugfs interface for compliance testing

 Documentation/gpu/drivers.rst   |   1 +
 Documentation/gpu/zynqmp.rst| 149 +
 MAINTAINERS |   1 +
 drivers/gpu/drm/xlnx/zynqmp_disp.c  |   6 +-
 drivers/gpu/drm/xlnx/zynqmp_dp.c| 836 +---
 drivers/gpu/drm/xlnx/zynqmp_dpsub.h |   1 +
 drivers/gpu/drm/xlnx/zynqmp_kms.h   |   4 +-
 7 files changed, 931 insertions(+), 67 deletions(-)
 create mode 100644 Documentation/gpu/zynqmp.rst

-- 
2.35.1.1320.gc452695387.dirty



[PATCH v2 3/8] drm: zynqmp_dp: Adjust training values per-lane

2024-03-19 Thread Sean Anderson
The feedback we get from the DPRX is per-lane. Make changes using this
information, instead of picking the maximum values from all lanes. This
results in more-consistent training on marginal links.

Signed-off-by: Sean Anderson 
---

(no changes since v1)

 drivers/gpu/drm/xlnx/zynqmp_dp.c | 23 ---
 1 file changed, 8 insertions(+), 15 deletions(-)

diff --git a/drivers/gpu/drm/xlnx/zynqmp_dp.c b/drivers/gpu/drm/xlnx/zynqmp_dp.c
index 98a32e6a0459..8635b5673386 100644
--- a/drivers/gpu/drm/xlnx/zynqmp_dp.c
+++ b/drivers/gpu/drm/xlnx/zynqmp_dp.c
@@ -605,28 +605,21 @@ static void zynqmp_dp_adjust_train(struct zynqmp_dp *dp,
   u8 link_status[DP_LINK_STATUS_SIZE])
 {
u8 *train_set = dp->train_set;
-   u8 voltage = 0, preemphasis = 0;
u8 i;
 
for (i = 0; i < dp->mode.lane_cnt; i++) {
-   u8 v = drm_dp_get_adjust_request_voltage(link_status, i);
-   u8 p = drm_dp_get_adjust_request_pre_emphasis(link_status, i);
+   u8 voltage = drm_dp_get_adjust_request_voltage(link_status, i);
+   u8 preemphasis =
+   drm_dp_get_adjust_request_pre_emphasis(link_status, i);
 
-   if (v > voltage)
-   voltage = v;
+   if (voltage >= DP_TRAIN_VOLTAGE_SWING_LEVEL_3)
+   voltage |= DP_TRAIN_MAX_SWING_REACHED;
 
-   if (p > preemphasis)
-   preemphasis = p;
-   }
+   if (preemphasis >= DP_TRAIN_PRE_EMPH_LEVEL_2)
+   preemphasis |= DP_TRAIN_MAX_PRE_EMPHASIS_REACHED;
 
-   if (voltage >= DP_TRAIN_VOLTAGE_SWING_LEVEL_3)
-   voltage |= DP_TRAIN_MAX_SWING_REACHED;
-
-   if (preemphasis >= DP_TRAIN_PRE_EMPH_LEVEL_2)
-   preemphasis |= DP_TRAIN_MAX_PRE_EMPHASIS_REACHED;
-
-   for (i = 0; i < dp->mode.lane_cnt; i++)
train_set[i] = voltage | preemphasis;
+   }
 }
 
 /**
-- 
2.35.1.1320.gc452695387.dirty



[PATCH v2 2/8] drm: zynqmp_dp: Downgrade log level for aux retries message

2024-03-19 Thread Sean Anderson
Enable this message for verbose debugging only as it is otherwise
printed after every AUX message, quickly filling the log buffer.

Signed-off-by: Sean Anderson 
Reviewed-by: Laurent Pinchart 
---

(no changes since v1)

 drivers/gpu/drm/xlnx/zynqmp_dp.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/gpu/drm/xlnx/zynqmp_dp.c b/drivers/gpu/drm/xlnx/zynqmp_dp.c
index a0606fab0e22..98a32e6a0459 100644
--- a/drivers/gpu/drm/xlnx/zynqmp_dp.c
+++ b/drivers/gpu/drm/xlnx/zynqmp_dp.c
@@ -1006,7 +1006,7 @@ zynqmp_dp_aux_transfer(struct drm_dp_aux *aux, struct 
drm_dp_aux_msg *msg)
   msg->buffer, msg->size,
   >reply);
if (!ret) {
-   dev_dbg(dp->dev, "aux %d retries\n", i);
+   dev_vdbg(dp->dev, "aux %d retries\n", i);
return msg->size;
}
 
-- 
2.35.1.1320.gc452695387.dirty



[PATCH v2 1/8] drm: xlnx: Fix kerneldoc

2024-03-19 Thread Sean Anderson
Fix a few errors in the kerneldoc. Mostly this addresses missing/renamed
members.

Signed-off-by: Sean Anderson 
---

Changes in v2:
- New

 drivers/gpu/drm/xlnx/zynqmp_disp.c  | 6 +++---
 drivers/gpu/drm/xlnx/zynqmp_dpsub.h | 1 +
 drivers/gpu/drm/xlnx/zynqmp_kms.h   | 4 ++--
 3 files changed, 6 insertions(+), 5 deletions(-)

diff --git a/drivers/gpu/drm/xlnx/zynqmp_disp.c 
b/drivers/gpu/drm/xlnx/zynqmp_disp.c
index 407bc07cec69..f79bf3fb8110 100644
--- a/drivers/gpu/drm/xlnx/zynqmp_disp.c
+++ b/drivers/gpu/drm/xlnx/zynqmp_disp.c
@@ -128,9 +128,9 @@ struct zynqmp_disp_layer {
  * struct zynqmp_disp - Display controller
  * @dev: Device structure
  * @dpsub: Display subsystem
- * @blend.base: Register I/O base address for the blender
- * @avbuf.base: Register I/O base address for the audio/video buffer manager
- * @audio.base: Registers I/O base address for the audio mixer
+ * @blend: Register I/O base address for the blender
+ * @avbuf: Register I/O base address for the audio/video buffer manager
+ * @audio: Registers I/O base address for the audio mixer
  * @layers: Layers (planes)
  */
 struct zynqmp_disp {
diff --git a/drivers/gpu/drm/xlnx/zynqmp_dpsub.h 
b/drivers/gpu/drm/xlnx/zynqmp_dpsub.h
index 09ea01878f2a..b18554467e9c 100644
--- a/drivers/gpu/drm/xlnx/zynqmp_dpsub.h
+++ b/drivers/gpu/drm/xlnx/zynqmp_dpsub.h
@@ -53,6 +53,7 @@ enum zynqmp_dpsub_format {
  * @drm: The DRM/KMS device data
  * @bridge: The DP encoder bridge
  * @disp: The display controller
+ * @layers: Video and graphics layers
  * @dp: The DisplayPort controller
  * @dma_align: DMA alignment constraint (must be a power of 2)
  */
diff --git a/drivers/gpu/drm/xlnx/zynqmp_kms.h 
b/drivers/gpu/drm/xlnx/zynqmp_kms.h
index 01be96b00e3f..cb13c6b8008e 100644
--- a/drivers/gpu/drm/xlnx/zynqmp_kms.h
+++ b/drivers/gpu/drm/xlnx/zynqmp_kms.h
@@ -22,9 +22,9 @@
 struct zynqmp_dpsub;
 
 /**
- * struct zynqmp_dpsub - ZynqMP DisplayPort Subsystem DRM/KMS data
+ * struct zynqmp_dpsub_drm - ZynqMP DisplayPort Subsystem DRM/KMS data
  * @dpsub: Backpointer to the DisplayPort subsystem
- * @drm: The DRM/KMS device
+ * @dev: The DRM/KMS device
  * @planes: The DRM planes
  * @crtc: The DRM CRTC
  * @encoder: The dummy DRM encoder
-- 
2.35.1.1320.gc452695387.dirty



Re: [PATCH 6/6] drm: zynqmp_dp: Add debugfs interface for compliance testing

2024-03-18 Thread Sean Anderson
On 3/18/24 13:50, Laurent Pinchart wrote:
> On Mon, Mar 18, 2024 at 11:06:40AM -0400, Sean Anderson wrote:
>> On 3/16/24 06:14, kernel test robot wrote:
>> > Hi Sean,
>> > 
>> > kernel test robot noticed the following build warnings:
>> > 
>> > [auto build test WARNING on v6.8]
>> > [cannot apply to drm-misc/drm-misc-next linus/master next-20240315]
>> > [If your patch is applied to the wrong git tree, kindly drop us a note.
>> > And when submitting patch, we suggest to use '--base' as documented in
>> > https://git-scm.com/docs/git-format-patch#_base_tree_information]
>> > 
>> > url:
>> > https://github.com/intel-lab-lkp/linux/commits/Sean-Anderson/drm-zynqmp_dp-Downgrade-log-level-for-aux-retries-message/20240316-071208
>> > base:   v6.8
>> > patch link:
>> > https://lore.kernel.org/r/20240315230916.1759060-7-sean.anderson%40linux.dev
>> > patch subject: [PATCH 6/6] drm: zynqmp_dp: Add debugfs interface for 
>> > compliance testing
>> > config: microblaze-allmodconfig 
>> > (https://download.01.org/0day-ci/archive/20240316/202403161704.achjdsjg-...@intel.com/config)
>> > compiler: microblaze-linux-gcc (GCC) 13.2.0
>> > reproduce (this is a W=1 build): 
>> > (https://download.01.org/0day-ci/archive/20240316/202403161704.achjdsjg-...@intel.com/reproduce)
>> > 
>> > If you fix the issue in a separate patch/commit (i.e. not just a new 
>> > version of
>> > the same patch/commit), kindly add following tags
>> > | Reported-by: kernel test robot 
>> > | Closes: 
>> > https://lore.kernel.org/oe-kbuild-all/202403161704.achjdsjg-...@intel.com/
>> > 
>> > All warnings (new ones prefixed by >>):
>> > 
>> >drivers/gpu/drm/xlnx/zynqmp_dp.c: In function 
>> > 'zynqmp_dp_bridge_debugfs_init':
>> >>> drivers/gpu/drm/xlnx/zynqmp_dp.c:2168:31: warning: 'sprintf' may write a 
>> >>> terminating nul past the end of the destination [-Wformat-overflow=]
>> > 2168 | sprintf(name, fmt, i);
>> >  |   ^~~
>> >drivers/gpu/drm/xlnx/zynqmp_dp.c:2168:17: note: 'sprintf' output 
>> > between 18 and 20 bytes into a destination of size 19
>> > 2168 | sprintf(name, fmt, i);
>> >  | ^
>> 
>> Not a bug, as i will be at most 4, which uses 1 digit.
> 
> The compiler can't know that. Please fix this, there's a zero warning
> policy.

I cannot reproduce this with GCC 13.2.0. So given that this is not a bug and I 
can't reproduce
it, I don't see how I can verify any fix.

--Sean

>> > vim +/sprintf +2168 drivers/gpu/drm/xlnx/zynqmp_dp.c
>> > 
>> >   2136 
>> >   2137 DEFINE_DEBUGFS_ATTRIBUTE(fops_zynqmp_dp_rate, 
>> > zynqmp_dp_rate_get,
>> >   2138  zynqmp_dp_rate_set, "%llu\n");
>> >   2139 
>> >   2140 static void zynqmp_dp_bridge_debugfs_init(struct drm_bridge 
>> > *bridge,
>> >   2141   struct dentry *root)
>> >   2142 {
>> >   2143 struct zynqmp_dp *dp = bridge_to_dp(bridge);
>> >   2144 struct dentry *test;
>> >   2145 int i;
>> >   2146 
>> >   2147 dp->test.bw_code = DP_LINK_BW_5_4;
>> >   2148 dp->test.link_cnt = dp->num_lanes;
>> >   2149 
>> >   2150 test = debugfs_create_dir("test", root);
>> >   2151 #define CREATE_FILE(name) \
>> >   2152 debugfs_create_file(#name, 0600, test, dp, 
>> > _zynqmp_dp_##name)
>> >   2153 CREATE_FILE(pattern);
>> >   2154 CREATE_FILE(enhanced);
>> >   2155 CREATE_FILE(downspread);
>> >   2156 CREATE_FILE(active);
>> >   2157 CREATE_FILE(custom);
>> >   2158 CREATE_FILE(rate);
>> >   2159 CREATE_FILE(lanes);
>> >   2160 
>> >   2161 for (i = 0; i < dp->num_lanes; i++) {
>> >   2162 static const char fmt[] = "lane%d_preemphasis";
>> >   2163 char name[sizeof(fmt)];
>> >   2164 
>> >   2165 dp->debugfs_train_set[i].dp = dp;
>> >   2166 dp->debugfs_train_set[i].lane = i;
>> >   2167 
>> >> 2168  sprintf(name, fmt, i);
>> >   2169 debugfs_create_file(name, 0600, test,
>> >   2170 >debugfs_train_set[i],
>> >   2171 
>> > _zynqmp_dp_preemphasis);
>> >   2172 
>> >   2173 sprintf(name, "lane%d_swing", i);
>> >   2174 debugfs_create_file(name, 0600, test,
>> >   2175 >debugfs_train_set[i],
>> >   2176 _zynqmp_dp_swing);
>> >   2177 }
>> >   2178 }
>> >   2179 
> 



Re: [PATCH 5/6] drm: zynqmp_dp: Optionally ignore DPCD errors

2024-03-18 Thread Sean Anderson
On 3/18/24 13:47, Laurent Pinchart wrote:
> Hi Sean,
> 
> Thank you for the patch.
> 
> On Fri, Mar 15, 2024 at 07:09:15PM -0400, Sean Anderson wrote:
>> When testing, it's convenient to be able to ignore DPCD errors if there
>> is test equipment which can't emulate a DPRX connected to the output.
>> Add some (currently-unused) options to ignore these errors and just
>> reconfigure our internal registers as we usually would.
> 
> This seems to be a problem that is not limited to the ZynqMP DP.
> Wouldn't it be better to solve it in the DRM DP DPCD helpers instead ?
> You could expose a parameter on the AUX bus in debugfs to ignore errors,
> and cause the drm_dp_dpcd_write*() functions to return 0.

I think this is probably the easiest thing. I'll add this for v2.

I think something similar might be nice for HPD events (instead of always
ignoring them in test mode). This would make it easier to add DPRX-driven
testing in the future.

--Sean

>> Signed-off-by: Sean Anderson 
>> ---
>> 
>>  drivers/gpu/drm/xlnx/zynqmp_dp.c | 37 
>>  1 file changed, 23 insertions(+), 14 deletions(-)
>> 
>> diff --git a/drivers/gpu/drm/xlnx/zynqmp_dp.c 
>> b/drivers/gpu/drm/xlnx/zynqmp_dp.c
>> index 24043847dab4..040f7b88ee51 100644
>> --- a/drivers/gpu/drm/xlnx/zynqmp_dp.c
>> +++ b/drivers/gpu/drm/xlnx/zynqmp_dp.c
>> @@ -628,6 +628,7 @@ static void zynqmp_dp_adjust_train(struct zynqmp_dp *dp,
>>   * zynqmp_dp_update_vs_emph - Update the training values
>>   * @dp: DisplayPort IP core structure
>>   * @train_set: A set of training values
>> + * @ignore_dpcd: Ignore DPCD errors
>>   *
>>   * Update the training values based on the request from sink. The mapped 
>> values
>>   * are predefined, and values(vs, pe, pc) are from the device manual.
>> @@ -635,15 +636,19 @@ static void zynqmp_dp_adjust_train(struct zynqmp_dp 
>> *dp,
>>   * Return: 0 if vs and emph are updated successfully, or the error code 
>> returned
>>   * by drm_dp_dpcd_write().
>>   */
>> -static int zynqmp_dp_update_vs_emph(struct zynqmp_dp *dp, u8 *train_set)
>> +static int zynqmp_dp_update_vs_emph(struct zynqmp_dp *dp, u8 *train_set,
>> +bool ignore_dpcd)
>>  {
>>  unsigned int i;
>>  int ret;
>>  
>>  ret = drm_dp_dpcd_write(>aux, DP_TRAINING_LANE0_SET, train_set,
>>  dp->mode.lane_cnt);
>> -if (ret < 0)
>> -return ret;
>> +if (ret < 0) {
>> +if (!ignore_dpcd)
>> +return ret;
>> +dev_warn(dp->dev, "failed to update vs/emph\n");
>> +}
>>  
>>  for (i = 0; i < dp->mode.lane_cnt; i++) {
>>  u32 reg = ZYNQMP_DP_SUB_TX_PHY_PRECURSOR_LANE_0 + i * 4;
>> @@ -692,7 +697,7 @@ static int zynqmp_dp_link_train_cr(struct zynqmp_dp *dp)
>>   * So, This loop should exit before 512 iterations
>>   */
>>  for (max_tries = 0; max_tries < 512; max_tries++) {
>> -ret = zynqmp_dp_update_vs_emph(dp, dp->train_set);
>> +ret = zynqmp_dp_update_vs_emph(dp, dp->train_set, false);
>>  if (ret)
>>  return ret;
>>  
>> @@ -757,7 +762,7 @@ static int zynqmp_dp_link_train_ce(struct zynqmp_dp *dp)
>>  return ret;
>>  
>>  for (tries = 0; tries < DP_MAX_TRAINING_TRIES; tries++) {
>> -ret = zynqmp_dp_update_vs_emph(dp, dp->train_set);
>> +ret = zynqmp_dp_update_vs_emph(dp, dp->train_set, false);
>>  if (ret)
>>  return ret;
>>  
>> @@ -785,11 +790,12 @@ static int zynqmp_dp_link_train_ce(struct zynqmp_dp 
>> *dp)
>>   * @lane_cnt: The number of lanes to use
>>   * @enhanced: Use enhanced framing
>>   * @downspread: Enable spread-spectrum clocking
>> + * @ignore_dpcd: Ignore DPCD errors; useful for testing
>>   *
>>   * Return: 0 on success, or -errno on failure
>>   */
>>  static int zynqmp_dp_setup(struct zynqmp_dp *dp, u8 bw_code, u8 lane_cnt,
>> -   bool enhanced, bool downspread)
>> +   bool enhanced, bool downspread, bool ignore_dpcd)
>>  {
>>  u32 reg;
>>  u8 aux_lane_cnt = lane_cnt;
>> @@ -812,21 +818,24 @@ static int zynqmp_dp_setup(struct zynqmp_dp *dp, u8 
>> bw_code, u8 lane_cnt,
>>  
>>  ret = drm_dp_dpcd_writeb(>aux, DP_LANE_COUNT_SET, aux_lane_cnt);
>>  if (ret < 0) {
>> -dev_er

Re: [PATCH 3/6] drm: zynqmp_dp: Add locking

2024-03-18 Thread Sean Anderson
On 3/18/24 13:59, Laurent Pinchart wrote:
> Hi Sean,
> 
> On Mon, Mar 18, 2024 at 01:29:12PM -0400, Sean Anderson wrote:
>> On 3/18/24 13:16, Laurent Pinchart wrote:
>> > On Fri, Mar 15, 2024 at 07:09:13PM -0400, Sean Anderson wrote:
>> >> Add some locking, since none is provided by the drm subsystem. This will
>> > 
>> > That's not quite right, the DRM core doesn't call bridge operations
>> > concurrently.
>> 
>> I figured something like this was going on.
>> 
>> > We may need locking to protect against race conditions
>> > between bridge operations and interrupts though.
>> 
>> And of course this will only get worse once we let userspace get involved.
>> 
>> >> prevent the IRQ/workers/bridge API calls from stepping on each other's
>> >> toes.
>> >> 
>> >> Signed-off-by: Sean Anderson 
>> >> ---
>> >> 
>> >>  drivers/gpu/drm/xlnx/zynqmp_dp.c | 59 +++-
>> >>  1 file changed, 42 insertions(+), 17 deletions(-)
>> >> 
>> >> diff --git a/drivers/gpu/drm/xlnx/zynqmp_dp.c 
>> >> b/drivers/gpu/drm/xlnx/zynqmp_dp.c
>> >> index 8635b5673386..d2dee58e7bf2 100644
>> >> --- a/drivers/gpu/drm/xlnx/zynqmp_dp.c
>> >> +++ b/drivers/gpu/drm/xlnx/zynqmp_dp.c
>> >> @@ -279,6 +279,7 @@ struct zynqmp_dp_config {
>> >>   * @dpsub: Display subsystem
>> >>   * @iomem: device I/O memory for register access
>> >>   * @reset: reset controller
>> >> + * @lock: Mutex protecting this struct and register access (but not AUX)
>> > 
>> > This patch does two things at once, it defers link training from the IRQ
>> > handler to a work queue, and covers everything with a big lock. The
>> > scope is too large.
>> 
>> OK, I can split this.
>> 
>> > Please restrict the lock scope and document the
>> > individual fields that need to be protected, and explain the locking
>> > design in the commit message (or comments in the code).
>> 
>> As said, this lock protects
>> 
>> - Non-atomic registers configuring the link. That is, everything but the IRQ
>>   registers (since these are accessed in an atomic fashion), and the DP AUX
>>   registers (since these don't affect the link).
>> - Link configuration. This is effectively everything in zynqmp_dp which isn't
>>   read-only after probe time. So from next_bridge onward.
>> 
>> It's designed to protect configuration changes so we don't have to do 
>> anything
>> tricky. Configuration should never be in the hot path, so I'm not worried 
>> about
>> performance.
> 
> If userspace can control all this directly through debugfs, can you
> guarantee that locks will be enough ? The driver doesn't expect direct
> userspace access. I have a feeling this is really quite hacky.

Yes, this is fine. The most userspace can do is force a lot of retraining. But 
we
have timeouts on everything so I'm not really concerned.

--Sean

>> >>   * @irq: irq
>> >>   * @bridge: DRM bridge for the DP encoder
>> >>   * @next_bridge: The downstream bridge
>> >> @@ -299,6 +300,7 @@ struct zynqmp_dp {
>> >>   struct zynqmp_dpsub *dpsub;
>> >>   void __iomem *iomem;
>> >>   struct reset_control *reset;
>> >> + struct mutex lock;
>> >>   int irq;
>> >>  
>> >>   struct drm_bridge bridge;
>> >> @@ -308,7 +310,7 @@ struct zynqmp_dp {
>> >>   struct drm_dp_aux aux;
>> >>   struct phy *phy[ZYNQMP_DP_MAX_LANES];
>> >>   u8 num_lanes;
>> >> - struct delayed_work hpd_work;
>> >> + struct delayed_work hpd_work, hpd_irq_work;
>> > 
>> > One variable per line please.
>> 
>> OK
>> 
>> >>   enum drm_connector_status status;
>> >>   bool enabled;
>> >>  
>> >> @@ -1371,8 +1373,10 @@ zynqmp_dp_bridge_mode_valid(struct drm_bridge 
>> >> *bridge,
>> >>   }
>> >>  
>> >>   /* Check with link rate and lane count */
>> >> + mutex_lock(>lock);
>> >>   rate = zynqmp_dp_max_rate(dp->link_config.max_rate,
>> >> dp->link_config.max_lanes, dp->config.bpp);
>> >> + mutex_unlock(>lock);
>> >>   if (mode->clock > rate) {
>> >>   dev_dbg(dp->dev, "filtered mode %s for high pixel rate\n",
>> >>   mode->name);
>> &

Re: [PATCH 3/6] drm: zynqmp_dp: Add locking

2024-03-18 Thread Sean Anderson
On 3/18/24 13:16, Laurent Pinchart wrote:
> Hi Sean,
> 
> Thank you for the patch.
> 
> On Fri, Mar 15, 2024 at 07:09:13PM -0400, Sean Anderson wrote:
>> Add some locking, since none is provided by the drm subsystem. This will
> 
> That's not quite right, the DRM core doesn't call bridge operations
> concurrently.

I figured something like this was going on.

> We may need locking to protect against race conditions
> between bridge operations and interrupts though.

And of course this will only get worse once we let userspace get involved.

>> prevent the IRQ/workers/bridge API calls from stepping on each other's
>> toes.
>> 
>> Signed-off-by: Sean Anderson 
>> ---
>> 
>>  drivers/gpu/drm/xlnx/zynqmp_dp.c | 59 +++-
>>  1 file changed, 42 insertions(+), 17 deletions(-)
>> 
>> diff --git a/drivers/gpu/drm/xlnx/zynqmp_dp.c 
>> b/drivers/gpu/drm/xlnx/zynqmp_dp.c
>> index 8635b5673386..d2dee58e7bf2 100644
>> --- a/drivers/gpu/drm/xlnx/zynqmp_dp.c
>> +++ b/drivers/gpu/drm/xlnx/zynqmp_dp.c
>> @@ -279,6 +279,7 @@ struct zynqmp_dp_config {
>>   * @dpsub: Display subsystem
>>   * @iomem: device I/O memory for register access
>>   * @reset: reset controller
>> + * @lock: Mutex protecting this struct and register access (but not AUX)
> 
> This patch does two things at once, it defers link training from the IRQ
> handler to a work queue, and covers everything with a big lock. The
> scope is too large.

OK, I can split this.

> Please restrict the lock scope and document the
> individual fields that need to be protected, and explain the locking
> design in the commit message (or comments in the code).

As said, this lock protects

- Non-atomic registers configuring the link. That is, everything but the IRQ
  registers (since these are accessed in an atomic fashion), and the DP AUX
  registers (since these don't affect the link).
- Link configuration. This is effectively everything in zynqmp_dp which isn't
  read-only after probe time. So from next_bridge onward.

It's designed to protect configuration changes so we don't have to do anything
tricky. Configuration should never be in the hot path, so I'm not worried about
performance.

>>   * @irq: irq
>>   * @bridge: DRM bridge for the DP encoder
>>   * @next_bridge: The downstream bridge
>> @@ -299,6 +300,7 @@ struct zynqmp_dp {
>>  struct zynqmp_dpsub *dpsub;
>>  void __iomem *iomem;
>>  struct reset_control *reset;
>> +struct mutex lock;
>>  int irq;
>>  
>>  struct drm_bridge bridge;
>> @@ -308,7 +310,7 @@ struct zynqmp_dp {
>>  struct drm_dp_aux aux;
>>  struct phy *phy[ZYNQMP_DP_MAX_LANES];
>>  u8 num_lanes;
>> -struct delayed_work hpd_work;
>> +struct delayed_work hpd_work, hpd_irq_work;
> 
> One variable per line please.

OK

>>  enum drm_connector_status status;
>>  bool enabled;
>>  
>> @@ -1371,8 +1373,10 @@ zynqmp_dp_bridge_mode_valid(struct drm_bridge *bridge,
>>  }
>>  
>>  /* Check with link rate and lane count */
>> +mutex_lock(>lock);
>>  rate = zynqmp_dp_max_rate(dp->link_config.max_rate,
>>dp->link_config.max_lanes, dp->config.bpp);
>> +mutex_unlock(>lock);
>>  if (mode->clock > rate) {
>>  dev_dbg(dp->dev, "filtered mode %s for high pixel rate\n",
>>  mode->name);
>> @@ -1399,6 +1403,7 @@ static void zynqmp_dp_bridge_atomic_enable(struct 
>> drm_bridge *bridge,
>>  
>>  pm_runtime_get_sync(dp->dev);
>>  
>> +mutex_lock(>lock);
>>  zynqmp_dp_disp_enable(dp, old_bridge_state);
>>  
>>  /*
>> @@ -1459,6 +1464,7 @@ static void zynqmp_dp_bridge_atomic_enable(struct 
>> drm_bridge *bridge,
>>  zynqmp_dp_write(dp, ZYNQMP_DP_SOFTWARE_RESET,
>>  ZYNQMP_DP_SOFTWARE_RESET_ALL);
>>  zynqmp_dp_write(dp, ZYNQMP_DP_MAIN_STREAM_ENABLE, 1);
>> +mutex_unlock(>lock);
>>  }
>>  
>>  static void zynqmp_dp_bridge_atomic_disable(struct drm_bridge *bridge,
>> @@ -1466,6 +1472,7 @@ static void zynqmp_dp_bridge_atomic_disable(struct 
>> drm_bridge *bridge,
>>  {
>>  struct zynqmp_dp *dp = bridge_to_dp(bridge);
>>  
>> +mutex_lock(>lock);
>>  dp->enabled = false;
>>  cancel_delayed_work(>hpd_work);
>>  zynqmp_dp_write(dp, ZYNQMP_DP_MAIN_STREAM_ENABLE, 0);
>> @@ -1476,6 +1483,7 @@ static void zynqmp_dp_bridge_atomic_disable(struct 
>> drm_bridge *bridge

Re: [PATCH 6/6] drm: zynqmp_dp: Add debugfs interface for compliance testing

2024-03-18 Thread Sean Anderson
On 3/16/24 13:56, Dmitry Baryshkov wrote:
> On Sat, 16 Mar 2024 at 01:09, Sean Anderson  wrote:
>>
>> Add a debugfs interface for exercising the various test modes supported
>> by the DisplayPort controller. This allows performing compliance
>> testing, or performing signal integrity measurements on a failing link.
>> At the moment, we do not support sink-driven link quality testing,
>> although such support would be fairly easy to add.
> 
> Could you please point out how this is used for compliance testing? We
> have been using the msm_dp_compliance tool [1].

Here's some quick documentation I wrote up. This probably could be put
under Documentation for v2.

The following files in /sys/kernel/debug/dri/1/DP-1/test/ control the
DisplayPort test modes:

active:
  Writing a 1 to this file will activate test mode, and writing a 0 will
  deactivate test mode. Writing a 1 or 0 when the test mode is already
  active/inactive will reactivate/re-deactivate test mode. When test mode
  is inactive, changes made to other files will have no effect. When
  test mode is active, changes made to other files will apply instantly.
  Additionally, hotplug events (as removing the cable or if the monitor
  requests link retraining) are ignored.
custom:
  Custom test pattern value
downspread:
  Enable/disable clock downspreading (spread-spectrum clocking) by
  writing 1/0
enhanced:
  Enable/disable enhanced framing
lane0_preemphasis:
  Preemphasis from 0 (lowest) to 2 (most) for lane 0
lane0_swing:
  Voltage swing from 0 (lowest) to 3 (most) for lane 0
lane1_preemphasis:
  Preemphasis from 0 (lowest) to 2 (most) for lane 1
lane1_swing:
  Voltage swing from 0 (lowest) to 3 (most) for lane 1
lanes:
  Number of lanes to use (1 or 2)
pattern:
  Test pattern. May be one of:
- video: Use regular video input
- symbol-error: Symbol error measurement pattern
- prbs7: Output of the PRBS7 (x^7 + x^6 + 1) polynomial
- 80bit-custom: A custom 80-bit pattern
- cp2520: HBR2 compliance eye pattern
- tps1: Link training symbol pattern TPS1 (/D10.2/)
- tps2: Link training symbol pattern TPS2
- tps3: Link training symbol pattern TPS3 (for HBR2)
rate:
  Rate in hertz. One of
- 54: HBR2
- 27: HBR
- 162000: RBR

You can dump the displayport test settings with the following command:

for prop in /sys/kernel/debug/dri/1/DP-1/test/*; do
printf '%-20s ' ${prop##*/}
if [ ${prop##*/} = custom ]; then
hexdump -C $prop | head -1
else
cat $prop
fi
done

The output could look something like

active   1
custom     00 00 00 00 00 00 00 00  00 00   
 |..|
downspread   0
enhanced 1
lane0_preemphasis0
lane0_swing  3
lane1_preemphasis0
lane1_swing  3
lanes2
pattern  prbs7
rate 162000

The recommended test procedure is to connect the board to a monitor,
configure test mode, activate test mode, and then disconnect the cable
and connect it to your test equipment of choice. For example, one
sequence of commands could be:

echo 1 > /sys/kernel/debug/dri/1/DP-1/test/enhanced
echo tps1 > /sys/kernel/debug/dri/1/DP-1/test/pattern
echo 162000 > /sys/kernel/debug/dri/1/DP-1/test/rate
echo 1 > /sys/kernel/debug/dri/1/DP-1/test/active

at which point the cable could be disconnected from the monitor. When
the cable is disconnected there will be several errors while changing
the settings. This is expected.

> I think it would be nice to rework our drivers towards a common
> debugfs interface used for DP connectors, maybe defining generic
> internal interface/helpers like Maxime is implementing for HDMI
> connectors.
> 
> [1] 
> https://gitlab.freedesktop.org/drm/igt-gpu-tools/-/blob/master/tools/msm_dp_compliance.c?ref_type=heads

I was definitely inspired by the msm, intel, and amd approaches.
However, those debugfs implementations seem to be oriented towards
DisplayPort text fixtures which emulate DPRXs. In particular, both the
intel and msm debugfs interfaces provide no method for configuring test
parameters in userspace. As test fixtures supporting DPCD can run into
the thousands of dollars, I think it is more economical to support
userspace-driven testing. I was particularly inspired by the AMD
approach:

/* Usage: set DP physical test pattern using debugfs with normal DP
 * panel. Then plug out DP panel and connect a scope to measure
 * For normal video mode and test pattern generated from CRCT,
 * they are visibile to user. So do not disable HPD.
 * Video Mode is also set to clear the test pattern, so enable HPD
 * because it might have been disabled after a test pattern was set.
 * AUX depends on HPD * sequence dependent, do not move!
 */

But 

Re: [PATCH 3/6] drm: zynqmp_dp: Add locking

2024-03-18 Thread Sean Anderson
On 3/16/24 05:52, kernel test robot wrote:
> Hi Sean,
> 
> kernel test robot noticed the following build warnings:
> 
> [auto build test WARNING on v6.8]
> [also build test WARNING on linus/master next-20240315]
> [cannot apply to drm-misc/drm-misc-next]
> [If your patch is applied to the wrong git tree, kindly drop us a note.
> And when submitting patch, we suggest to use '--base' as documented in
> https://git-scm.com/docs/git-format-patch#_base_tree_information]
> 
> url:
> https://github.com/intel-lab-lkp/linux/commits/Sean-Anderson/drm-zynqmp_dp-Downgrade-log-level-for-aux-retries-message/20240316-071208
> base:   v6.8
> patch link:
> https://lore.kernel.org/r/20240315230916.1759060-4-sean.anderson%40linux.dev
> patch subject: [PATCH 3/6] drm: zynqmp_dp: Add locking
> config: alpha-allyesconfig 
> (https://download.01.org/0day-ci/archive/20240316/202403161747.trmfawhm-...@intel.com/config)
> compiler: alpha-linux-gcc (GCC) 13.2.0
> reproduce (this is a W=1 build): 
> (https://download.01.org/0day-ci/archive/20240316/202403161747.trmfawhm-...@intel.com/reproduce)
> 
> If you fix the issue in a separate patch/commit (i.e. not just a new version 
> of
> the same patch/commit), kindly add following tags
> | Reported-by: kernel test robot 
> | Closes: 
> https://lore.kernel.org/oe-kbuild-all/202403161747.trmfawhm-...@intel.com/
> 
> All warnings (new ones prefixed by >>):
> 
>>> drivers/gpu/drm/xlnx/zynqmp_dp.c:321: warning: Function parameter or struct 
>>> member 'hpd_irq_work' not described in 'zynqmp_dp'

Will document.

--Sean

> 
> vim +321 drivers/gpu/drm/xlnx/zynqmp_dp.c
> 
> d76271d22694e8 Hyun Kwon2018-07-07  275  
> d76271d22694e8 Hyun Kwon2018-07-07  276  /**
> d76271d22694e8 Hyun Kwon2018-07-07  277   * struct zynqmp_dp - Xilinx 
> DisplayPort core
> d76271d22694e8 Hyun Kwon2018-07-07  278   * @dev: device structure
> d76271d22694e8 Hyun Kwon2018-07-07  279   * @dpsub: Display subsystem
> d76271d22694e8 Hyun Kwon2018-07-07  280   * @iomem: device I/O memory 
> for register access
> d76271d22694e8 Hyun Kwon2018-07-07  281   * @reset: reset controller
> 8ce380e6568015 Sean Anderson2024-03-15  282   * @lock: Mutex protecting 
> this struct and register access (but not AUX)
> d76271d22694e8 Hyun Kwon2018-07-07  283   * @irq: irq
> 47e801bd0749f0 Laurent Pinchart 2021-08-04  284   * @bridge: DRM bridge for 
> the DP encoder
> bd68b9b3cb2e0d Laurent Pinchart 2021-08-04  285   * @next_bridge: The 
> downstream bridge
> d76271d22694e8 Hyun Kwon2018-07-07  286   * @config: IP core 
> configuration from DTS
> d76271d22694e8 Hyun Kwon2018-07-07  287   * @aux: aux channel
> d76271d22694e8 Hyun Kwon2018-07-07  288   * @phy: PHY handles for DP 
> lanes
> d76271d22694e8 Hyun Kwon2018-07-07  289   * @num_lanes: number of 
> enabled phy lanes
> d76271d22694e8 Hyun Kwon2018-07-07  290   * @hpd_work: hot plug 
> detection worker
> d76271d22694e8 Hyun Kwon2018-07-07  291   * @status: connection status
> d76271d22694e8 Hyun Kwon2018-07-07  292   * @enabled: flag to 
> indicate if the device is enabled
> d76271d22694e8 Hyun Kwon2018-07-07  293   * @dpcd: DP configuration 
> data from currently connected sink device
> d76271d22694e8 Hyun Kwon2018-07-07  294   * @link_config: common link 
> configuration between IP core and sink device
> d76271d22694e8 Hyun Kwon2018-07-07  295   * @mode: current mode 
> between IP core and sink device
> d76271d22694e8 Hyun Kwon2018-07-07  296   * @train_set: set of 
> training data
> d76271d22694e8 Hyun Kwon2018-07-07  297   */
> d76271d22694e8 Hyun Kwon2018-07-07  298  struct zynqmp_dp {
> d76271d22694e8 Hyun Kwon2018-07-07  299   struct device *dev;
> d76271d22694e8 Hyun Kwon    2018-07-07  300   struct zynqmp_dpsub 
> *dpsub;
> d76271d22694e8 Hyun Kwon2018-07-07  301   void __iomem *iomem;
> d76271d22694e8 Hyun Kwon2018-07-07  302   struct reset_control 
> *reset;
> 8ce380e6568015 Sean Anderson2024-03-15  303   struct mutex lock;
> d76271d22694e8 Hyun Kwon2018-07-07  304   int irq;
> d76271d22694e8 Hyun Kwon2018-07-07  305  
> 47e801bd0749f0 Laurent Pinchart 2021-08-04  306   struct drm_bridge 
> bridge;
> bd68b9b3cb2e0d Laurent Pinchart 2021-08-04  307   struct drm_bridge 
> *next_bridge;
> 47e801bd0749f0 Laurent Pinchart 2021-08-04  308  
> d76271d22694e8 Hyun Kwon2018-07-07  309   struct zynqmp_dp_config 
> config;
> d76271d22694e8 Hyun Kwon2018-07-07  310   struct drm_dp_aux aux;
> d76271d22694e8 Hyun 

Re: [PATCH 6/6] drm: zynqmp_dp: Add debugfs interface for compliance testing

2024-03-18 Thread Sean Anderson
On 3/16/24 06:55, kernel test robot wrote:
> Hi Sean,
> 
> kernel test robot noticed the following build errors:
> 
> [auto build test ERROR on v6.8]
> [cannot apply to drm-misc/drm-misc-next linus/master next-20240315]
> [If your patch is applied to the wrong git tree, kindly drop us a note.
> And when submitting patch, we suggest to use '--base' as documented in
> https://git-scm.com/docs/git-format-patch#_base_tree_information]
> 
> url:
> https://github.com/intel-lab-lkp/linux/commits/Sean-Anderson/drm-zynqmp_dp-Downgrade-log-level-for-aux-retries-message/20240316-071208
> base:   v6.8
> patch link:
> https://lore.kernel.org/r/20240315230916.1759060-7-sean.anderson%40linux.dev
> patch subject: [PATCH 6/6] drm: zynqmp_dp: Add debugfs interface for 
> compliance testing
> config: m68k-allyesconfig 
> (https://download.01.org/0day-ci/archive/20240316/202403161837.76okcezn-...@intel.com/config)
> compiler: m68k-linux-gcc (GCC) 13.2.0
> reproduce (this is a W=1 build): 
> (https://download.01.org/0day-ci/archive/20240316/202403161837.76okcezn-...@intel.com/reproduce)
> 
> If you fix the issue in a separate patch/commit (i.e. not just a new version 
> of
> the same patch/commit), kindly add following tags
> | Reported-by: kernel test robot 
> | Closes: 
> https://lore.kernel.org/oe-kbuild-all/202403161837.76okcezn-...@intel.com/
> 
> All errors (new ones prefixed by >>):
> 
>m68k-linux-ld: drivers/gpu/drm/xlnx/zynqmp_dp.o: in function 
> `zynqmp_dp_rate_set':
>>> zynqmp_dp.c:(.text+0x1a7a): undefined reference to `__udivdi3'
> 

Will fix.

--Sean


Re: [PATCH 6/6] drm: zynqmp_dp: Add debugfs interface for compliance testing

2024-03-18 Thread Sean Anderson
On 3/16/24 06:14, kernel test robot wrote:
> Hi Sean,
> 
> kernel test robot noticed the following build warnings:
> 
> [auto build test WARNING on v6.8]
> [cannot apply to drm-misc/drm-misc-next linus/master next-20240315]
> [If your patch is applied to the wrong git tree, kindly drop us a note.
> And when submitting patch, we suggest to use '--base' as documented in
> https://git-scm.com/docs/git-format-patch#_base_tree_information]
> 
> url:
> https://github.com/intel-lab-lkp/linux/commits/Sean-Anderson/drm-zynqmp_dp-Downgrade-log-level-for-aux-retries-message/20240316-071208
> base:   v6.8
> patch link:
> https://lore.kernel.org/r/20240315230916.1759060-7-sean.anderson%40linux.dev
> patch subject: [PATCH 6/6] drm: zynqmp_dp: Add debugfs interface for 
> compliance testing
> config: microblaze-allmodconfig 
> (https://download.01.org/0day-ci/archive/20240316/202403161704.achjdsjg-...@intel.com/config)
> compiler: microblaze-linux-gcc (GCC) 13.2.0
> reproduce (this is a W=1 build): 
> (https://download.01.org/0day-ci/archive/20240316/202403161704.achjdsjg-...@intel.com/reproduce)
> 
> If you fix the issue in a separate patch/commit (i.e. not just a new version 
> of
> the same patch/commit), kindly add following tags
> | Reported-by: kernel test robot 
> | Closes: 
> https://lore.kernel.org/oe-kbuild-all/202403161704.achjdsjg-...@intel.com/
> 
> All warnings (new ones prefixed by >>):
> 
>drivers/gpu/drm/xlnx/zynqmp_dp.c: In function 
> 'zynqmp_dp_bridge_debugfs_init':
>>> drivers/gpu/drm/xlnx/zynqmp_dp.c:2168:31: warning: 'sprintf' may write a 
>>> terminating nul past the end of the destination [-Wformat-overflow=]
> 2168 | sprintf(name, fmt, i);
>  |   ^~~
>drivers/gpu/drm/xlnx/zynqmp_dp.c:2168:17: note: 'sprintf' output between 
> 18 and 20 bytes into a destination of size 19
> 2168 | sprintf(name, fmt, i);
>  | ^

Not a bug, as i will be at most 4, which uses 1 digit.

--Sean

> vim +/sprintf +2168 drivers/gpu/drm/xlnx/zynqmp_dp.c
> 
>   2136
>   2137DEFINE_DEBUGFS_ATTRIBUTE(fops_zynqmp_dp_rate, 
> zynqmp_dp_rate_get,
>   2138 zynqmp_dp_rate_set, "%llu\n");
>   2139
>   2140static void zynqmp_dp_bridge_debugfs_init(struct drm_bridge 
> *bridge,
>   2141  struct dentry *root)
>   2142{
>   2143struct zynqmp_dp *dp = bridge_to_dp(bridge);
>   2144struct dentry *test;
>   2145int i;
>   2146
>   2147dp->test.bw_code = DP_LINK_BW_5_4;
>   2148dp->test.link_cnt = dp->num_lanes;
>   2149
>   2150test = debugfs_create_dir("test", root);
>   2151#define CREATE_FILE(name) \
>   2152debugfs_create_file(#name, 0600, test, dp, 
> _zynqmp_dp_##name)
>   2153CREATE_FILE(pattern);
>   2154CREATE_FILE(enhanced);
>   2155CREATE_FILE(downspread);
>   2156CREATE_FILE(active);
>   2157CREATE_FILE(custom);
>   2158CREATE_FILE(rate);
>   2159CREATE_FILE(lanes);
>   2160
>   2161for (i = 0; i < dp->num_lanes; i++) {
>   2162static const char fmt[] = "lane%d_preemphasis";
>   2163char name[sizeof(fmt)];
>   2164
>   2165dp->debugfs_train_set[i].dp = dp;
>   2166dp->debugfs_train_set[i].lane = i;
>   2167
>> 2168 sprintf(name, fmt, i);
>   2169debugfs_create_file(name, 0600, test,
>   2170>debugfs_train_set[i],
>   2171
> _zynqmp_dp_preemphasis);
>   2172
>   2173sprintf(name, "lane%d_swing", i);
>   2174debugfs_create_file(name, 0600, test,
>   2175>debugfs_train_set[i],
>   2176_zynqmp_dp_swing);
>   2177}
>   2178}
>   2179
> 



[PATCH 5/6] drm: zynqmp_dp: Optionally ignore DPCD errors

2024-03-15 Thread Sean Anderson
When testing, it's convenient to be able to ignore DPCD errors if there
is test equipment which can't emulate a DPRX connected to the output.
Add some (currently-unused) options to ignore these errors and just
reconfigure our internal registers as we usually would.

Signed-off-by: Sean Anderson 
---

 drivers/gpu/drm/xlnx/zynqmp_dp.c | 37 
 1 file changed, 23 insertions(+), 14 deletions(-)

diff --git a/drivers/gpu/drm/xlnx/zynqmp_dp.c b/drivers/gpu/drm/xlnx/zynqmp_dp.c
index 24043847dab4..040f7b88ee51 100644
--- a/drivers/gpu/drm/xlnx/zynqmp_dp.c
+++ b/drivers/gpu/drm/xlnx/zynqmp_dp.c
@@ -628,6 +628,7 @@ static void zynqmp_dp_adjust_train(struct zynqmp_dp *dp,
  * zynqmp_dp_update_vs_emph - Update the training values
  * @dp: DisplayPort IP core structure
  * @train_set: A set of training values
+ * @ignore_dpcd: Ignore DPCD errors
  *
  * Update the training values based on the request from sink. The mapped values
  * are predefined, and values(vs, pe, pc) are from the device manual.
@@ -635,15 +636,19 @@ static void zynqmp_dp_adjust_train(struct zynqmp_dp *dp,
  * Return: 0 if vs and emph are updated successfully, or the error code 
returned
  * by drm_dp_dpcd_write().
  */
-static int zynqmp_dp_update_vs_emph(struct zynqmp_dp *dp, u8 *train_set)
+static int zynqmp_dp_update_vs_emph(struct zynqmp_dp *dp, u8 *train_set,
+   bool ignore_dpcd)
 {
unsigned int i;
int ret;
 
ret = drm_dp_dpcd_write(>aux, DP_TRAINING_LANE0_SET, train_set,
dp->mode.lane_cnt);
-   if (ret < 0)
-   return ret;
+   if (ret < 0) {
+   if (!ignore_dpcd)
+   return ret;
+   dev_warn(dp->dev, "failed to update vs/emph\n");
+   }
 
for (i = 0; i < dp->mode.lane_cnt; i++) {
u32 reg = ZYNQMP_DP_SUB_TX_PHY_PRECURSOR_LANE_0 + i * 4;
@@ -692,7 +697,7 @@ static int zynqmp_dp_link_train_cr(struct zynqmp_dp *dp)
 * So, This loop should exit before 512 iterations
 */
for (max_tries = 0; max_tries < 512; max_tries++) {
-   ret = zynqmp_dp_update_vs_emph(dp, dp->train_set);
+   ret = zynqmp_dp_update_vs_emph(dp, dp->train_set, false);
if (ret)
return ret;
 
@@ -757,7 +762,7 @@ static int zynqmp_dp_link_train_ce(struct zynqmp_dp *dp)
return ret;
 
for (tries = 0; tries < DP_MAX_TRAINING_TRIES; tries++) {
-   ret = zynqmp_dp_update_vs_emph(dp, dp->train_set);
+   ret = zynqmp_dp_update_vs_emph(dp, dp->train_set, false);
if (ret)
return ret;
 
@@ -785,11 +790,12 @@ static int zynqmp_dp_link_train_ce(struct zynqmp_dp *dp)
  * @lane_cnt: The number of lanes to use
  * @enhanced: Use enhanced framing
  * @downspread: Enable spread-spectrum clocking
+ * @ignore_dpcd: Ignore DPCD errors; useful for testing
  *
  * Return: 0 on success, or -errno on failure
  */
 static int zynqmp_dp_setup(struct zynqmp_dp *dp, u8 bw_code, u8 lane_cnt,
-  bool enhanced, bool downspread)
+  bool enhanced, bool downspread, bool ignore_dpcd)
 {
u32 reg;
u8 aux_lane_cnt = lane_cnt;
@@ -812,21 +818,24 @@ static int zynqmp_dp_setup(struct zynqmp_dp *dp, u8 
bw_code, u8 lane_cnt,
 
ret = drm_dp_dpcd_writeb(>aux, DP_LANE_COUNT_SET, aux_lane_cnt);
if (ret < 0) {
-   dev_err(dp->dev, "failed to set lane count\n");
-   return ret;
+   dev_warn(dp->dev, "failed to set lane count\n");
+   if (!ignore_dpcd)
+   return ret;
}
 
ret = drm_dp_dpcd_writeb(>aux, DP_MAIN_LINK_CHANNEL_CODING_SET,
 DP_SET_ANSI_8B10B);
if (ret < 0) {
-   dev_err(dp->dev, "failed to set ANSI 8B/10B encoding\n");
-   return ret;
+   dev_warn(dp->dev, "failed to set ANSI 8B/10B encoding\n");
+   if (!ignore_dpcd)
+   return ret;
}
 
ret = drm_dp_dpcd_writeb(>aux, DP_LINK_BW_SET, bw_code);
if (ret < 0) {
-   dev_err(dp->dev, "failed to set DP bandwidth\n");
-   return ret;
+   dev_warn(dp->dev, "failed to set DP bandwidth\n");
+   if (!ignore_dpcd)
+   return ret;
}
 
zynqmp_dp_write(dp, ZYNQMP_DP_LINK_BW_SET, bw_code);
@@ -860,7 +869,7 @@ static int zynqmp_dp_train(struct zynqmp_dp *dp)
 
ret = zynqmp_dp_setup(dp, dp->mode.bw_code, dp->mode.lane_cnt,
  drm_dp_enhanced_frame_cap(dp->dpcd),
- dp->dpcd[3] & 0x1);
+  

[PATCH 3/6] drm: zynqmp_dp: Add locking

2024-03-15 Thread Sean Anderson
Add some locking, since none is provided by the drm subsystem. This will
prevent the IRQ/workers/bridge API calls from stepping on each other's
toes.

Signed-off-by: Sean Anderson 
---

 drivers/gpu/drm/xlnx/zynqmp_dp.c | 59 +++-
 1 file changed, 42 insertions(+), 17 deletions(-)

diff --git a/drivers/gpu/drm/xlnx/zynqmp_dp.c b/drivers/gpu/drm/xlnx/zynqmp_dp.c
index 8635b5673386..d2dee58e7bf2 100644
--- a/drivers/gpu/drm/xlnx/zynqmp_dp.c
+++ b/drivers/gpu/drm/xlnx/zynqmp_dp.c
@@ -279,6 +279,7 @@ struct zynqmp_dp_config {
  * @dpsub: Display subsystem
  * @iomem: device I/O memory for register access
  * @reset: reset controller
+ * @lock: Mutex protecting this struct and register access (but not AUX)
  * @irq: irq
  * @bridge: DRM bridge for the DP encoder
  * @next_bridge: The downstream bridge
@@ -299,6 +300,7 @@ struct zynqmp_dp {
struct zynqmp_dpsub *dpsub;
void __iomem *iomem;
struct reset_control *reset;
+   struct mutex lock;
int irq;
 
struct drm_bridge bridge;
@@ -308,7 +310,7 @@ struct zynqmp_dp {
struct drm_dp_aux aux;
struct phy *phy[ZYNQMP_DP_MAX_LANES];
u8 num_lanes;
-   struct delayed_work hpd_work;
+   struct delayed_work hpd_work, hpd_irq_work;
enum drm_connector_status status;
bool enabled;
 
@@ -1371,8 +1373,10 @@ zynqmp_dp_bridge_mode_valid(struct drm_bridge *bridge,
}
 
/* Check with link rate and lane count */
+   mutex_lock(>lock);
rate = zynqmp_dp_max_rate(dp->link_config.max_rate,
  dp->link_config.max_lanes, dp->config.bpp);
+   mutex_unlock(>lock);
if (mode->clock > rate) {
dev_dbg(dp->dev, "filtered mode %s for high pixel rate\n",
mode->name);
@@ -1399,6 +1403,7 @@ static void zynqmp_dp_bridge_atomic_enable(struct 
drm_bridge *bridge,
 
pm_runtime_get_sync(dp->dev);
 
+   mutex_lock(>lock);
zynqmp_dp_disp_enable(dp, old_bridge_state);
 
/*
@@ -1459,6 +1464,7 @@ static void zynqmp_dp_bridge_atomic_enable(struct 
drm_bridge *bridge,
zynqmp_dp_write(dp, ZYNQMP_DP_SOFTWARE_RESET,
ZYNQMP_DP_SOFTWARE_RESET_ALL);
zynqmp_dp_write(dp, ZYNQMP_DP_MAIN_STREAM_ENABLE, 1);
+   mutex_unlock(>lock);
 }
 
 static void zynqmp_dp_bridge_atomic_disable(struct drm_bridge *bridge,
@@ -1466,6 +1472,7 @@ static void zynqmp_dp_bridge_atomic_disable(struct 
drm_bridge *bridge,
 {
struct zynqmp_dp *dp = bridge_to_dp(bridge);
 
+   mutex_lock(>lock);
dp->enabled = false;
cancel_delayed_work(>hpd_work);
zynqmp_dp_write(dp, ZYNQMP_DP_MAIN_STREAM_ENABLE, 0);
@@ -1476,6 +1483,7 @@ static void zynqmp_dp_bridge_atomic_disable(struct 
drm_bridge *bridge,
zynqmp_dp_write(dp, ZYNQMP_DP_TX_AUDIO_CONTROL, 0);
 
zynqmp_dp_disp_disable(dp, old_bridge_state);
+   mutex_unlock(>lock);
 
pm_runtime_put_sync(dp->dev);
 }
@@ -1518,6 +1526,8 @@ static enum drm_connector_status 
zynqmp_dp_bridge_detect(struct drm_bridge *brid
u32 state, i;
int ret;
 
+   mutex_lock(>lock);
+
/*
 * This is from heuristic. It takes some delay (ex, 100 ~ 500 msec) to
 * get the HPD signal with some monitors.
@@ -1545,11 +1555,13 @@ static enum drm_connector_status 
zynqmp_dp_bridge_detect(struct drm_bridge *brid
   dp->num_lanes);
 
dp->status = connector_status_connected;
+   mutex_unlock(>lock);
return connector_status_connected;
}
 
 disconnected:
dp->status = connector_status_disconnected;
+   mutex_unlock(>lock);
return connector_status_disconnected;
 }
 
@@ -1611,6 +1623,29 @@ static void zynqmp_dp_hpd_work_func(struct work_struct 
*work)
drm_bridge_hpd_notify(>bridge, status);
 }
 
+static void zynqmp_dp_hpd_irq_work_func(struct work_struct *work)
+{
+   struct zynqmp_dp *dp = container_of(work, struct zynqmp_dp,
+   hpd_irq_work.work);
+   u8 status[DP_LINK_STATUS_SIZE + 2];
+   int err;
+
+   mutex_lock(>lock);
+   err = drm_dp_dpcd_read(>aux, DP_SINK_COUNT, status,
+  DP_LINK_STATUS_SIZE + 2);
+   if (err < 0) {
+   dev_dbg_ratelimited(dp->dev,
+   "could not read sink status: %d\n", err);
+   } else {
+   if (status[4] & DP_LINK_STATUS_UPDATED ||
+   !drm_dp_clock_recovery_ok([2], dp->mode.lane_cnt) ||
+   !drm_dp_channel_eq_ok([2], dp->mode.lane_cnt)) {
+   zynqmp_dp_train_loop(dp);
+   }
+   }
+   mutex_unlock(>lock);
+}
+
 static irqreturn_t zynqmp_dp_irq_h

[PATCH 2/6] drm: zynqmp_dp: Adjust training values per-lane

2024-03-15 Thread Sean Anderson
The feedback we get from the DPRX is per-lane. Make changes using this
information, instead of picking the maximum values from all lanes. This
results in more-consistent training on marginal links.

Signed-off-by: Sean Anderson 
---

 drivers/gpu/drm/xlnx/zynqmp_dp.c | 23 ---
 1 file changed, 8 insertions(+), 15 deletions(-)

diff --git a/drivers/gpu/drm/xlnx/zynqmp_dp.c b/drivers/gpu/drm/xlnx/zynqmp_dp.c
index 98a32e6a0459..8635b5673386 100644
--- a/drivers/gpu/drm/xlnx/zynqmp_dp.c
+++ b/drivers/gpu/drm/xlnx/zynqmp_dp.c
@@ -605,28 +605,21 @@ static void zynqmp_dp_adjust_train(struct zynqmp_dp *dp,
   u8 link_status[DP_LINK_STATUS_SIZE])
 {
u8 *train_set = dp->train_set;
-   u8 voltage = 0, preemphasis = 0;
u8 i;
 
for (i = 0; i < dp->mode.lane_cnt; i++) {
-   u8 v = drm_dp_get_adjust_request_voltage(link_status, i);
-   u8 p = drm_dp_get_adjust_request_pre_emphasis(link_status, i);
+   u8 voltage = drm_dp_get_adjust_request_voltage(link_status, i);
+   u8 preemphasis =
+   drm_dp_get_adjust_request_pre_emphasis(link_status, i);
 
-   if (v > voltage)
-   voltage = v;
+   if (voltage >= DP_TRAIN_VOLTAGE_SWING_LEVEL_3)
+   voltage |= DP_TRAIN_MAX_SWING_REACHED;
 
-   if (p > preemphasis)
-   preemphasis = p;
-   }
+   if (preemphasis >= DP_TRAIN_PRE_EMPH_LEVEL_2)
+   preemphasis |= DP_TRAIN_MAX_PRE_EMPHASIS_REACHED;
 
-   if (voltage >= DP_TRAIN_VOLTAGE_SWING_LEVEL_3)
-   voltage |= DP_TRAIN_MAX_SWING_REACHED;
-
-   if (preemphasis >= DP_TRAIN_PRE_EMPH_LEVEL_2)
-   preemphasis |= DP_TRAIN_MAX_PRE_EMPHASIS_REACHED;
-
-   for (i = 0; i < dp->mode.lane_cnt; i++)
train_set[i] = voltage | preemphasis;
+   }
 }
 
 /**
-- 
2.35.1.1320.gc452695387.dirty



[PATCH 6/6] drm: zynqmp_dp: Add debugfs interface for compliance testing

2024-03-15 Thread Sean Anderson
Add a debugfs interface for exercising the various test modes supported
by the DisplayPort controller. This allows performing compliance
testing, or performing signal integrity measurements on a failing link.
At the moment, we do not support sink-driven link quality testing,
although such support would be fairly easy to add.

Signed-off-by: Sean Anderson 
---

 drivers/gpu/drm/xlnx/zynqmp_dp.c | 591 ++-
 1 file changed, 590 insertions(+), 1 deletion(-)

diff --git a/drivers/gpu/drm/xlnx/zynqmp_dp.c b/drivers/gpu/drm/xlnx/zynqmp_dp.c
index 040f7b88ee51..57032186e1ca 100644
--- a/drivers/gpu/drm/xlnx/zynqmp_dp.c
+++ b/drivers/gpu/drm/xlnx/zynqmp_dp.c
@@ -18,7 +18,9 @@
 #include 
 #include 
 
+#include 
 #include 
+#include 
 #include 
 #include 
 #include 
@@ -50,6 +52,7 @@ MODULE_PARM_DESC(power_on_delay_ms, "DP power on delay in 
msec (default: 4)");
 #define ZYNQMP_DP_LANE_COUNT_SET   0x4
 #define ZYNQMP_DP_ENHANCED_FRAME_EN0x8
 #define ZYNQMP_DP_TRAINING_PATTERN_SET 0xc
+#define ZYNQMP_DP_LINK_QUAL_PATTERN_SET0x10
 #define ZYNQMP_DP_SCRAMBLING_DISABLE   0x14
 #define ZYNQMP_DP_DOWNSPREAD_CTL   0x18
 #define ZYNQMP_DP_SOFTWARE_RESET   0x1c
@@ -63,6 +66,9 @@ MODULE_PARM_DESC(power_on_delay_ms, "DP power on delay in 
msec (default: 4)");
 
ZYNQMP_DP_SOFTWARE_RESET_STREAM3 | \
 
ZYNQMP_DP_SOFTWARE_RESET_STREAM4 | \
 
ZYNQMP_DP_SOFTWARE_RESET_AUX)
+#define ZYNQMP_DP_COMP_PATTERN_80BIT_1 0x20
+#define ZYNQMP_DP_COMP_PATTERN_80BIT_2 0x24
+#define ZYNQMP_DP_COMP_PATTERN_80BIT_3 0x28
 
 /* Core enable registers */
 #define ZYNQMP_DP_TRANSMITTER_ENABLE   0x80
@@ -206,6 +212,7 @@ MODULE_PARM_DESC(power_on_delay_ms, "DP power on delay in 
msec (default: 4)");
 #define ZYNQMP_DP_TX_PHY_POWER_DOWN_LANE_2 BIT(2)
 #define ZYNQMP_DP_TX_PHY_POWER_DOWN_LANE_3 BIT(3)
 #define ZYNQMP_DP_TX_PHY_POWER_DOWN_ALL0xf
+#define ZYNQMP_DP_TRANSMIT_PRBS7   0x230
 #define ZYNQMP_DP_PHY_PRECURSOR_LANE_0 0x23c
 #define ZYNQMP_DP_PHY_PRECURSOR_LANE_1 0x240
 #define ZYNQMP_DP_PHY_PRECURSOR_LANE_2 0x244
@@ -273,6 +280,69 @@ struct zynqmp_dp_config {
u8 bpp;
 };
 
+/**
+ * enum test_pattern - Test patterns for test testing
+ * TEST_VIDEO: Use regular video input
+ * TEST_SYMBOL_ERROR: Symbol error measurement pattern
+ * TEST_PRBS7: Output of the PRBS7 (x^7 + x^6 + 1) polynomial
+ * TEST_80BIT_CUSTOM: A custom 80-bit pattern
+ * TEST_CP2520: HBR2 compliance eye pattern
+ * TEST_TPS1: Link training symbol pattern TPS1 (/D10.2/)
+ * TEST_TPS2: Link training symbol pattern TPS2
+ * TEST_TPS3: Link training symbol pattern TPS3 (for HBR2)
+ */
+enum test_pattern {
+   TEST_VIDEO,
+   TEST_TPS1,
+   TEST_TPS2,
+   TEST_TPS3,
+   TEST_SYMBOL_ERROR,
+   TEST_PRBS7,
+   TEST_80BIT_CUSTOM,
+   TEST_CP2520,
+};
+
+static const char *const test_pattern_str[] = {
+   [TEST_VIDEO] = "video",
+   [TEST_TPS1] = "tps1",
+   [TEST_TPS2] = "tps2",
+   [TEST_TPS3] = "tps3",
+   [TEST_SYMBOL_ERROR] = "symbol-error",
+   [TEST_PRBS7] = "prbs7",
+   [TEST_80BIT_CUSTOM] = "80bit-custom",
+   [TEST_CP2520] = "cp2520",
+};
+
+/**
+ * struct zynqmp_dp_test - Configuration for test mode
+ * @pattern: The test pattern
+ * @enhanced: Use enhanced framing
+ * @downspread: Use SSC
+ * @active: Whether test mode is active
+ * @custom: Custom pattern for %TEST_80BIT_CUSTOM
+ * @train_set: Voltage/preemphasis settings
+ * @bw_code: Bandwidth code for the link
+ * @link_cnt: Number of lanes
+ */
+struct zynqmp_dp_test {
+   enum test_pattern pattern;
+   bool enhanced, downspread, active;
+   u8 custom[10];
+   u8 train_set[ZYNQMP_DP_MAX_LANES];
+   u8 bw_code;
+   u8 link_cnt;
+};
+
+/**
+ * struct zynqmp_dp_train_set_priv - Private data for train_set debugfs files
+ * @dp: DisplayPort IP core structure
+ * @lane: The lane for this file
+ */
+struct zynqmp_dp_train_set_priv {
+   struct zynqmp_dp *dp;
+   int lane;
+};
+
 /**
  * struct zynqmp_dp - Xilinx DisplayPort core
  * @dev: device structure
@@ -283,6 +353,7 @@ struct zynqmp_dp_config {
  * @irq: irq
  * @bridge: DRM bridge for the DP encoder
  * @next_bridge: The downstream bridge
+ * @test: Configuration for test mode
  * @config: IP core configuration from DTS
  * @aux: aux channel
  * @phy: PHY handles for DP lanes
@@ -294,6 +365,7 @@ struct zynqmp_dp_config {
  * @link_config: common link configurati

[PATCH 4/6] drm: zynqmp_dp: Split off several helper functions

2024-03-15 Thread Sean Anderson
In preparation for supporting compliance testing, split off several
helper functions. No functional change intended.

Signed-off-by: Sean Anderson 
---

 drivers/gpu/drm/xlnx/zynqmp_dp.c | 49 +---
 1 file changed, 33 insertions(+), 16 deletions(-)

diff --git a/drivers/gpu/drm/xlnx/zynqmp_dp.c b/drivers/gpu/drm/xlnx/zynqmp_dp.c
index d2dee58e7bf2..24043847dab4 100644
--- a/drivers/gpu/drm/xlnx/zynqmp_dp.c
+++ b/drivers/gpu/drm/xlnx/zynqmp_dp.c
@@ -627,6 +627,7 @@ static void zynqmp_dp_adjust_train(struct zynqmp_dp *dp,
 /**
  * zynqmp_dp_update_vs_emph - Update the training values
  * @dp: DisplayPort IP core structure
+ * @train_set: A set of training values
  *
  * Update the training values based on the request from sink. The mapped values
  * are predefined, and values(vs, pe, pc) are from the device manual.
@@ -634,12 +635,12 @@ static void zynqmp_dp_adjust_train(struct zynqmp_dp *dp,
  * Return: 0 if vs and emph are updated successfully, or the error code 
returned
  * by drm_dp_dpcd_write().
  */
-static int zynqmp_dp_update_vs_emph(struct zynqmp_dp *dp)
+static int zynqmp_dp_update_vs_emph(struct zynqmp_dp *dp, u8 *train_set)
 {
unsigned int i;
int ret;
 
-   ret = drm_dp_dpcd_write(>aux, DP_TRAINING_LANE0_SET, dp->train_set,
+   ret = drm_dp_dpcd_write(>aux, DP_TRAINING_LANE0_SET, train_set,
dp->mode.lane_cnt);
if (ret < 0)
return ret;
@@ -647,7 +648,7 @@ static int zynqmp_dp_update_vs_emph(struct zynqmp_dp *dp)
for (i = 0; i < dp->mode.lane_cnt; i++) {
u32 reg = ZYNQMP_DP_SUB_TX_PHY_PRECURSOR_LANE_0 + i * 4;
union phy_configure_opts opts = { 0 };
-   u8 train = dp->train_set[i];
+   u8 train = train_set[i];
 
opts.dp.voltage[0] = (train & DP_TRAIN_VOLTAGE_SWING_MASK)
   >> DP_TRAIN_VOLTAGE_SWING_SHIFT;
@@ -691,7 +692,7 @@ static int zynqmp_dp_link_train_cr(struct zynqmp_dp *dp)
 * So, This loop should exit before 512 iterations
 */
for (max_tries = 0; max_tries < 512; max_tries++) {
-   ret = zynqmp_dp_update_vs_emph(dp);
+   ret = zynqmp_dp_update_vs_emph(dp, dp->train_set);
if (ret)
return ret;
 
@@ -756,7 +757,7 @@ static int zynqmp_dp_link_train_ce(struct zynqmp_dp *dp)
return ret;
 
for (tries = 0; tries < DP_MAX_TRAINING_TRIES; tries++) {
-   ret = zynqmp_dp_update_vs_emph(dp);
+   ret = zynqmp_dp_update_vs_emph(dp, dp->train_set);
if (ret)
return ret;
 
@@ -779,28 +780,28 @@ static int zynqmp_dp_link_train_ce(struct zynqmp_dp *dp)
 }
 
 /**
- * zynqmp_dp_train - Train the link
- * @dp: DisplayPort IP core structure
+ * zynqmp_dp_setup() - Set up major link parameters
+ * @bw_code: The link bandwidth as a multiple of 270 MHz
+ * @lane_cnt: The number of lanes to use
+ * @enhanced: Use enhanced framing
+ * @downspread: Enable spread-spectrum clocking
  *
- * Return: 0 if all trains are done successfully, or corresponding error code.
+ * Return: 0 on success, or -errno on failure
  */
-static int zynqmp_dp_train(struct zynqmp_dp *dp)
+static int zynqmp_dp_setup(struct zynqmp_dp *dp, u8 bw_code, u8 lane_cnt,
+  bool enhanced, bool downspread)
 {
u32 reg;
-   u8 bw_code = dp->mode.bw_code;
-   u8 lane_cnt = dp->mode.lane_cnt;
u8 aux_lane_cnt = lane_cnt;
-   bool enhanced;
int ret;
 
zynqmp_dp_write(dp, ZYNQMP_DP_LANE_COUNT_SET, lane_cnt);
-   enhanced = drm_dp_enhanced_frame_cap(dp->dpcd);
if (enhanced) {
zynqmp_dp_write(dp, ZYNQMP_DP_ENHANCED_FRAME_EN, 1);
aux_lane_cnt |= DP_LANE_COUNT_ENHANCED_FRAME_EN;
}
 
-   if (dp->dpcd[3] & 0x1) {
+   if (downspread) {
zynqmp_dp_write(dp, ZYNQMP_DP_DOWNSPREAD_CTL, 1);
drm_dp_dpcd_writeb(>aux, DP_DOWNSPREAD_CTRL,
   DP_SPREAD_AMP_0_5);
@@ -843,8 +844,24 @@ static int zynqmp_dp_train(struct zynqmp_dp *dp)
}
 
zynqmp_dp_write(dp, ZYNQMP_DP_PHY_CLOCK_SELECT, reg);
-   ret = zynqmp_dp_phy_ready(dp);
-   if (ret < 0)
+   return zynqmp_dp_phy_ready(dp);
+}
+
+
+/**
+ * zynqmp_dp_train - Train the link
+ * @dp: DisplayPort IP core structure
+ *
+ * Return: 0 if all trains are done successfully, or corresponding error code.
+ */
+static int zynqmp_dp_train(struct zynqmp_dp *dp)
+{
+   int ret;
+
+   ret = zynqmp_dp_setup(dp, dp->mode.bw_code, dp->mode.lane_cnt,
+ drm_dp_enhanced_frame_cap(dp->dpcd),
+ dp->dpcd[3] & 0x1);
+   if (ret)
return ret;
 
zynqmp_dp_write(dp, ZYNQMP_DP_SCRAMBLING_DISABLE, 1);
-- 
2.35.1.1320.gc452695387.dirty



[PATCH 1/6] drm: zynqmp_dp: Downgrade log level for aux retries message

2024-03-15 Thread Sean Anderson
Enable this message for verbose debugging only as it is otherwise
printed after every AUX message, quickly filling the log buffer.

Signed-off-by: Sean Anderson 
---

 drivers/gpu/drm/xlnx/zynqmp_dp.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/gpu/drm/xlnx/zynqmp_dp.c b/drivers/gpu/drm/xlnx/zynqmp_dp.c
index a0606fab0e22..98a32e6a0459 100644
--- a/drivers/gpu/drm/xlnx/zynqmp_dp.c
+++ b/drivers/gpu/drm/xlnx/zynqmp_dp.c
@@ -1006,7 +1006,7 @@ zynqmp_dp_aux_transfer(struct drm_dp_aux *aux, struct 
drm_dp_aux_msg *msg)
   msg->buffer, msg->size,
   >reply);
if (!ret) {
-   dev_dbg(dp->dev, "aux %d retries\n", i);
+   dev_vdbg(dp->dev, "aux %d retries\n", i);
return msg->size;
}
 
-- 
2.35.1.1320.gc452695387.dirty



[PATCH 0/6] drm: zynqmp_dp: Misc. patches and debugfs support

2024-03-15 Thread Sean Anderson
This series adds debugfs support for the zynqmp_dp driver. The intent is
to enable compliance testing or to help debug signal-integrity issues.

The first three patches are general improvements (and could be applied
independently), while the last three add debugfs support.


Sean Anderson (6):
  drm: zynqmp_dp: Downgrade log level for aux retries message
  drm: zynqmp_dp: Adjust training values per-lane
  drm: zynqmp_dp: Add locking
  drm: zynqmp_dp: Split off several helper functions
  drm: zynqmp_dp: Optionally ignore DPCD errors
  drm: zynqmp_dp: Add debugfs interface for compliance testing

 drivers/gpu/drm/xlnx/zynqmp_dp.c | 749 ---
 1 file changed, 691 insertions(+), 58 deletions(-)

-- 
2.35.1.1320.gc452695387.dirty



[PATCH] drm: zynqmp_dpsub: Always register bridge

2024-03-08 Thread Sean Anderson
We must always register the DRM bridge, since zynqmp_dp_hpd_work_func
calls drm_bridge_hpd_notify, which in turn expects hpd_mutex to be
initialized. We do this before zynqmp_dpsub_drm_init since that calls
drm_bridge_attach. This fixes the following lockdep warning:

[   19.217084] [ cut here ]
[   19.227530] DEBUG_LOCKS_WARN_ON(lock->magic != lock)
[   19.227768] WARNING: CPU: 0 PID: 140 at kernel/locking/mutex.c:582 
__mutex_lock+0x4bc/0x550
[   19.241696] Modules linked in:
[   19.244937] CPU: 0 PID: 140 Comm: kworker/0:4 Not tainted 6.6.20+ #96
[   19.252046] Hardware name: xlnx,zynqmp (DT)
[   19.256421] Workqueue: events zynqmp_dp_hpd_work_func
[   19.261795] pstate: 6005 (nZCv daif -PAN -UAO -TCO -DIT -SSBS BTYPE=--)
[   19.269104] pc : __mutex_lock+0x4bc/0x550
[   19.273364] lr : __mutex_lock+0x4bc/0x550
[   19.277592] sp : ffc085c5bbe0
[   19.281066] x29: ffc085c5bbe0 x28:  x27: ff88009417f8
[   19.288624] x26: ff8800941788 x25: ff8800020008 x24: ffc082aa3000
[   19.296227] x23: ffc080d90e3c x22: 0002 x21: 
[   19.303744] x20:  x19: ff88002f5210 x18: 
[   19.311295] x17: 6c707369642e3030 x16: 3030613464662072 x15: 0720072007200720
[   19.318922] x14:  x13: 284e4f5f4e524157 x12: 0001
[   19.326442] x11: 0001ffc085c5b940 x10: 0001ff88003f388b x9 : 0001ff88003f3888
[   19.334003] x8 : 0001ff88003f3888 x7 :  x6 : 
[   19.341537] x5 :  x4 : 1668 x3 : 
[   19.349054] x2 :  x1 :  x0 : ff88003f3880
[   19.356581] Call trace:
[   19.359160]  __mutex_lock+0x4bc/0x550
[   19.363032]  mutex_lock_nested+0x24/0x30
[   19.367187]  drm_bridge_hpd_notify+0x2c/0x6c
[   19.371698]  zynqmp_dp_hpd_work_func+0x44/0x54
[   19.376364]  process_one_work+0x3ac/0x988
[   19.380660]  worker_thread+0x398/0x694
[   19.384736]  kthread+0x1bc/0x1c0
[   19.388241]  ret_from_fork+0x10/0x20
[   19.392031] irq event stamp: 183
[   19.395450] hardirqs last  enabled at (183): [] 
finish_task_switch.isra.0+0xa8/0x2d4
[   19.405140] hardirqs last disabled at (182): [] 
__schedule+0x714/0xd04
[   19.413612] softirqs last  enabled at (114): [] 
srcu_invoke_callbacks+0x158/0x23c
[   19.423128] softirqs last disabled at (110): [] 
srcu_invoke_callbacks+0x158/0x23c
[   19.432614] ---[ end trace  ]---

Fixes: eb2d64bfcc17 ("drm: xlnx: zynqmp_dpsub: Report HPD through the bridge")
Signed-off-by: Sean Anderson 
---

 drivers/gpu/drm/xlnx/zynqmp_dpsub.c | 6 ++
 1 file changed, 2 insertions(+), 4 deletions(-)

diff --git a/drivers/gpu/drm/xlnx/zynqmp_dpsub.c 
b/drivers/gpu/drm/xlnx/zynqmp_dpsub.c
index 88eb33acd5f0..639fff2c693f 100644
--- a/drivers/gpu/drm/xlnx/zynqmp_dpsub.c
+++ b/drivers/gpu/drm/xlnx/zynqmp_dpsub.c
@@ -256,12 +256,11 @@ static int zynqmp_dpsub_probe(struct platform_device 
*pdev)
if (ret)
goto err_dp;
 
+   drm_bridge_add(dpsub->bridge);
if (dpsub->dma_enabled) {
ret = zynqmp_dpsub_drm_init(dpsub);
if (ret)
goto err_disp;
-   } else {
-   drm_bridge_add(dpsub->bridge);
}
 
dev_info(>dev, "ZynqMP DisplayPort Subsystem driver probed");
@@ -288,9 +287,8 @@ static void zynqmp_dpsub_remove(struct platform_device 
*pdev)
 
if (dpsub->drm)
zynqmp_dpsub_drm_cleanup(dpsub);
-   else
-   drm_bridge_remove(dpsub->bridge);
 
+   drm_bridge_remove(dpsub->bridge);
zynqmp_disp_remove(dpsub);
zynqmp_dp_remove(dpsub);
 
-- 
2.35.1.1320.gc452695387.dirty



[PATCH v3 3/3] drm: Convert users of drm_of_component_match_add to component_match_add_of

2023-01-19 Thread Sean Anderson
Every user of this function either uses component_compare_of or
something equivalent. Most of them immediately put the device node as
well. Convert these users to component_match_add_of and remove
drm_of_component_match_add.

Signed-off-by: Sean Anderson 
Acked-by: Jyri Sarha 
Tested-by: Jyri Sarha 
---

(no changes since v1)

 .../gpu/drm/arm/display/komeda/komeda_drv.c   |  6 ++--
 drivers/gpu/drm/arm/hdlcd_drv.c   |  9 +-
 drivers/gpu/drm/arm/malidp_drv.c  | 11 +--
 drivers/gpu/drm/armada/armada_drv.c   | 10 ---
 drivers/gpu/drm/drm_of.c  | 29 +++
 drivers/gpu/drm/etnaviv/etnaviv_drv.c |  4 +--
 .../gpu/drm/hisilicon/kirin/kirin_drm_drv.c   |  3 +-
 drivers/gpu/drm/ingenic/ingenic-drm-drv.c |  3 +-
 drivers/gpu/drm/mediatek/mtk_drm_drv.c|  4 +--
 drivers/gpu/drm/msm/msm_drv.c | 14 -
 drivers/gpu/drm/sti/sti_drv.c |  3 +-
 drivers/gpu/drm/sun4i/sun4i_drv.c |  3 +-
 drivers/gpu/drm/tilcdc/tilcdc_external.c  | 10 ++-
 include/drm/drm_of.h  | 12 
 14 files changed, 33 insertions(+), 88 deletions(-)

diff --git a/drivers/gpu/drm/arm/display/komeda/komeda_drv.c 
b/drivers/gpu/drm/arm/display/komeda/komeda_drv.c
index 3f4e719eebd8..e3bfc72c378f 100644
--- a/drivers/gpu/drm/arm/display/komeda/komeda_drv.c
+++ b/drivers/gpu/drm/arm/display/komeda/komeda_drv.c
@@ -103,10 +103,8 @@ static void komeda_add_slave(struct device *master,
struct device_node *remote;
 
remote = of_graph_get_remote_node(np, port, endpoint);
-   if (remote) {
-   drm_of_component_match_add(master, match, component_compare_of, 
remote);
-   of_node_put(remote);
-   }
+   if (remote)
+   component_match_add_of(master, match, remote);
 }
 
 static int komeda_platform_probe(struct platform_device *pdev)
diff --git a/drivers/gpu/drm/arm/hdlcd_drv.c b/drivers/gpu/drm/arm/hdlcd_drv.c
index e3507dd6f82a..5f760bb66af4 100644
--- a/drivers/gpu/drm/arm/hdlcd_drv.c
+++ b/drivers/gpu/drm/arm/hdlcd_drv.c
@@ -347,11 +347,6 @@ static const struct component_master_ops hdlcd_master_ops 
= {
.unbind = hdlcd_drm_unbind,
 };
 
-static int compare_dev(struct device *dev, void *data)
-{
-   return dev->of_node == data;
-}
-
 static int hdlcd_probe(struct platform_device *pdev)
 {
struct device_node *port;
@@ -362,9 +357,7 @@ static int hdlcd_probe(struct platform_device *pdev)
if (!port)
return -ENODEV;
 
-   drm_of_component_match_add(>dev, , compare_dev, port);
-   of_node_put(port);
-
+   component_match_add_of(>dev, , port);
return component_master_add_with_match(>dev, _master_ops,
   match);
 }
diff --git a/drivers/gpu/drm/arm/malidp_drv.c b/drivers/gpu/drm/arm/malidp_drv.c
index 589c1c66a6dc..3a49c29ba5b8 100644
--- a/drivers/gpu/drm/arm/malidp_drv.c
+++ b/drivers/gpu/drm/arm/malidp_drv.c
@@ -909,13 +909,6 @@ static const struct component_master_ops malidp_master_ops 
= {
.unbind = malidp_unbind,
 };
 
-static int malidp_compare_dev(struct device *dev, void *data)
-{
-   struct device_node *np = data;
-
-   return dev->of_node == np;
-}
-
 static int malidp_platform_probe(struct platform_device *pdev)
 {
struct device_node *port;
@@ -929,9 +922,7 @@ static int malidp_platform_probe(struct platform_device 
*pdev)
if (!port)
return -ENODEV;
 
-   drm_of_component_match_add(>dev, , malidp_compare_dev,
-  port);
-   of_node_put(port);
+   component_match_add_of(>dev, , port);
return component_master_add_with_match(>dev, _master_ops,
   match);
 }
diff --git a/drivers/gpu/drm/armada/armada_drv.c 
b/drivers/gpu/drm/armada/armada_drv.c
index 0643887800b4..c0211ad7a45d 100644
--- a/drivers/gpu/drm/armada/armada_drv.c
+++ b/drivers/gpu/drm/armada/armada_drv.c
@@ -184,10 +184,12 @@ static void armada_add_endpoints(struct device *dev,
 
for_each_endpoint_of_node(dev_node, ep) {
remote = of_graph_get_remote_port_parent(ep);
-   if (remote && of_device_is_available(remote))
-   drm_of_component_match_add(dev, match, 
component_compare_of,
-  remote);
-   of_node_put(remote);
+   if (remote) {
+   if (of_device_is_available(remote))
+   component_match_add_of(dev, match, remote);
+   else
+   of_node_put(remote);
+   }
}
 }
 
diff --git a/drivers/gpu/drm/drm_of.c b/drivers/gpu/drm/drm_of.c
index 7bbcb999bb75..0a474729ddf6 100644
--- a/drivers/gpu/drm/drm_of.c
+++ b/drivers/gpu/drm/drm_

[PATCH v3 1/3] component: Add helper for device nodes

2023-01-19 Thread Sean Anderson
There is a common case where component_patch_add_release is called with
component_release_of/component_compare_of and a device node. Add a
helper.

Signed-off-by: Sean Anderson 
---

(no changes since v2)

Changes in v2:
- Split off conversion from helper addition

 include/linux/component.h | 9 +
 1 file changed, 9 insertions(+)

diff --git a/include/linux/component.h b/include/linux/component.h
index df4aa75c9e7c..fb5d2dbc34d8 100644
--- a/include/linux/component.h
+++ b/include/linux/component.h
@@ -6,6 +6,7 @@
 
 
 struct device;
+struct device_node;
 
 /**
  * struct component_ops - callbacks for component drivers
@@ -128,4 +129,12 @@ static inline void component_match_add(struct device 
*parent,
compare_data);
 }
 
+static inline void component_match_add_of(struct device *parent,
+ struct component_match **matchptr,
+ struct device_node *node)
+{
+   component_match_add_release(parent, matchptr, component_release_of,
+   component_compare_of, node);
+}
+
 #endif
-- 
2.35.1.1320.gc452695387.dirty



[PATCH v3 2/3] iommu/sound: Use component_match_add_of helper

2023-01-19 Thread Sean Anderson
Convert users of component_match_add_release with component_release_of
and component_compare_of to component_match_add_of.

Signed-off-by: Sean Anderson 
Acked-by: Mark Brown 
---

Changes in v3:
- Rebase onto drm/drm-next

Changes in v2:
- Split off from helper addition

 drivers/iommu/mtk_iommu_v1.c | 3 +--
 sound/soc/codecs/wcd938x.c   | 6 ++
 2 files changed, 3 insertions(+), 6 deletions(-)

diff --git a/drivers/iommu/mtk_iommu_v1.c b/drivers/iommu/mtk_iommu_v1.c
index 69682ee068d2..14019ba1e41c 100644
--- a/drivers/iommu/mtk_iommu_v1.c
+++ b/drivers/iommu/mtk_iommu_v1.c
@@ -670,8 +670,7 @@ static int mtk_iommu_v1_probe(struct platform_device *pdev)
}
data->larb_imu[i].dev = >dev;
 
-   component_match_add_release(dev, , component_release_of,
-   component_compare_of, larbnode);
+   component_match_add_of(dev, , larbnode);
}
 
platform_set_drvdata(pdev, data);
diff --git a/sound/soc/codecs/wcd938x.c b/sound/soc/codecs/wcd938x.c
index fcac763b04d1..0663b15fa757 100644
--- a/sound/soc/codecs/wcd938x.c
+++ b/sound/soc/codecs/wcd938x.c
@@ -4474,8 +4474,7 @@ static int wcd938x_add_slave_components(struct 
wcd938x_priv *wcd938x,
}
 
of_node_get(wcd938x->rxnode);
-   component_match_add_release(dev, matchptr, component_release_of,
-   component_compare_of, wcd938x->rxnode);
+   component_match_add_of(dev, matchptr, wcd938x->rxnode);
 
wcd938x->txnode = of_parse_phandle(np, "qcom,tx-device", 0);
if (!wcd938x->txnode) {
@@ -4483,8 +4482,7 @@ static int wcd938x_add_slave_components(struct 
wcd938x_priv *wcd938x,
return -ENODEV;
}
of_node_get(wcd938x->txnode);
-   component_match_add_release(dev, matchptr, component_release_of,
-   component_compare_of, wcd938x->txnode);
+   component_match_add_of(dev, matchptr, wcd938x->txnode);
return 0;
 }
 
-- 
2.35.1.1320.gc452695387.dirty



[PATCH v3 0/3] drm: Add component_match_add_of and convert users of drm_of_component_match_add

2023-01-19 Thread Sean Anderson
This series adds a new function component_match_add_of to simplify the
common case of calling component_match_add_release with
component_release_of and component_compare_of. There is already
drm_of_component_match_add, which allows for a custom compare function.
However, all existing users just use component_compare_of (or an
equivalent).

Changes in v3:
- Rebase onto drm/drm-next

Changes in v2:
- Split off conversion from helper addition
- Rebase onto drm/drm-next

Sean Anderson (3):
  component: Add helper for device nodes
  iommu/sound: Use component_match_add_of helper
  drm: Convert users of drm_of_component_match_add to
component_match_add_of

 .../gpu/drm/arm/display/komeda/komeda_drv.c   |  6 ++--
 drivers/gpu/drm/arm/hdlcd_drv.c   |  9 +-
 drivers/gpu/drm/arm/malidp_drv.c  | 11 +--
 drivers/gpu/drm/armada/armada_drv.c   | 10 ---
 drivers/gpu/drm/drm_of.c  | 29 +++
 drivers/gpu/drm/etnaviv/etnaviv_drv.c |  4 +--
 .../gpu/drm/hisilicon/kirin/kirin_drm_drv.c   |  3 +-
 drivers/gpu/drm/ingenic/ingenic-drm-drv.c |  3 +-
 drivers/gpu/drm/mediatek/mtk_drm_drv.c|  4 +--
 drivers/gpu/drm/msm/msm_drv.c | 14 -
 drivers/gpu/drm/sti/sti_drv.c |  3 +-
 drivers/gpu/drm/sun4i/sun4i_drv.c |  3 +-
 drivers/gpu/drm/tilcdc/tilcdc_external.c  | 10 ++-
 drivers/iommu/mtk_iommu_v1.c  |  3 +-
 include/drm/drm_of.h  | 12 
 include/linux/component.h |  9 ++
 sound/soc/codecs/wcd938x.c|  6 ++--
 17 files changed, 45 insertions(+), 94 deletions(-)

-- 
2.35.1.1320.gc452695387.dirty



[PATCH v3] drm: kirin: Enable COMPILE_TEST

2023-01-19 Thread Sean Anderson
Make various small changes to allow compile-testing on other arches.
This is helpful to allow testing changes to 32-bit arm drivers in the
same build.

The primary changes is to use macros for 64-bit divisions and shifts,
but we also need some other fixes to deal with larger constants and
differences in includes.

Signed-off-by: Sean Anderson 
---

Changes in v3:
- Include io.h for readl/writel

Changes in v2:
- Use BIT_ULL

 drivers/gpu/drm/hisilicon/kirin/Kconfig |  2 +-
 drivers/gpu/drm/hisilicon/kirin/dw_drm_dsi.c| 10 +-
 drivers/gpu/drm/hisilicon/kirin/dw_dsi_reg.h|  2 ++
 drivers/gpu/drm/hisilicon/kirin/kirin_ade_reg.h |  2 +-
 4 files changed, 9 insertions(+), 7 deletions(-)

diff --git a/drivers/gpu/drm/hisilicon/kirin/Kconfig 
b/drivers/gpu/drm/hisilicon/kirin/Kconfig
index c5265675bf0c..0772f79567ef 100644
--- a/drivers/gpu/drm/hisilicon/kirin/Kconfig
+++ b/drivers/gpu/drm/hisilicon/kirin/Kconfig
@@ -1,7 +1,7 @@
 # SPDX-License-Identifier: GPL-2.0-only
 config DRM_HISI_KIRIN
tristate "DRM Support for Hisilicon Kirin series SoCs Platform"
-   depends on DRM && OF && ARM64
+   depends on DRM && OF && (ARM64 || COMPILE_TEST)
select DRM_KMS_HELPER
select DRM_GEM_DMA_HELPER
select DRM_MIPI_DSI
diff --git a/drivers/gpu/drm/hisilicon/kirin/dw_drm_dsi.c 
b/drivers/gpu/drm/hisilicon/kirin/dw_drm_dsi.c
index d9978b79828c..1cfeffefd4b4 100644
--- a/drivers/gpu/drm/hisilicon/kirin/dw_drm_dsi.c
+++ b/drivers/gpu/drm/hisilicon/kirin/dw_drm_dsi.c
@@ -157,8 +157,8 @@ static u32 dsi_calc_phy_rate(u32 req_kHz, struct 
mipi_phy_params *phy)
q_pll = 0x10 >> (7 - phy->hstx_ckg_sel);
 
temp = f_kHz * (u64)q_pll * (u64)ref_clk_ps;
-   m_n_int = temp / (u64)10;
-   m_n = (temp % (u64)10) / (u64)1;
+   m_n_int = div_u64_rem(temp, 10, _n);
+   m_n /= 1;
 
if (m_n_int % 2 == 0) {
if (m_n * 6 >= 50) {
@@ -229,8 +229,8 @@ static u32 dsi_calc_phy_rate(u32 req_kHz, struct 
mipi_phy_params *phy)
phy->pll_fbd_div5f = 1;
}
 
-   f_kHz = (u64)10 * (u64)m_pll /
-   ((u64)ref_clk_ps * (u64)n_pll * (u64)q_pll);
+   f_kHz = div64_u64((u64)10 * (u64)m_pll,
+ (u64)ref_clk_ps * (u64)n_pll * (u64)q_pll);
 
if (f_kHz >= req_kHz)
break;
@@ -490,7 +490,7 @@ static void dsi_set_mode_timing(void __iomem *base,
hsa_time = (hsw * lane_byte_clk_kHz) / pixel_clk_kHz;
hbp_time = (hbp * lane_byte_clk_kHz) / pixel_clk_kHz;
tmp = (u64)htot * (u64)lane_byte_clk_kHz;
-   hline_time = DIV_ROUND_UP(tmp, pixel_clk_kHz);
+   hline_time = DIV64_U64_ROUND_UP(tmp, pixel_clk_kHz);
 
/* all specified in byte-lane clocks */
writel(hsa_time, base + VID_HSA_TIME);
diff --git a/drivers/gpu/drm/hisilicon/kirin/dw_dsi_reg.h 
b/drivers/gpu/drm/hisilicon/kirin/dw_dsi_reg.h
index d79fc031e53d..a87d1135856f 100644
--- a/drivers/gpu/drm/hisilicon/kirin/dw_dsi_reg.h
+++ b/drivers/gpu/drm/hisilicon/kirin/dw_dsi_reg.h
@@ -7,6 +7,8 @@
 #ifndef __DW_DSI_REG_H__
 #define __DW_DSI_REG_H__
 
+#include 
+
 #define MASK(x)(BIT(x) - 1)
 
 /*
diff --git a/drivers/gpu/drm/hisilicon/kirin/kirin_ade_reg.h 
b/drivers/gpu/drm/hisilicon/kirin/kirin_ade_reg.h
index be9e789c2d04..36f923cc7594 100644
--- a/drivers/gpu/drm/hisilicon/kirin/kirin_ade_reg.h
+++ b/drivers/gpu/drm/hisilicon/kirin/kirin_ade_reg.h
@@ -10,7 +10,7 @@
 /*
  * ADE Registers
  */
-#define MASK(x)(BIT(x) - 1)
+#define MASK(x)(BIT_ULL(x) - 1)
 
 #define ADE_CTRL   0x0004
 #define FRM_END_START_OFST 0
-- 
2.35.1.1320.gc452695387.dirty



Re: [PATCH v2 2/3] iommu/sound: Use component_match_add_of helper

2023-01-03 Thread Sean Anderson
On 1/3/23 11:15, Maxime Ripard wrote:
> Hi Robin,
> 
> On Tue, Jan 03, 2023 at 01:01:07PM +, Robin Murphy wrote:
>> Hi Sean,
>> 
>> On 22/12/2022 11:37 pm, Sean Anderson wrote:
>> > Convert users of component_match_add_release with component_release_of
>> > and component_compare_of to component_match_add_of.
>> > 
>> > Signed-off-by: Sean Anderson 
>> > Acked-by: Mark Brown 
>> > ---
>> > 
>> > Changes in v2:
>> > - Split off from helper addition
>> > 
>> >   drivers/iommu/mtk_iommu.c| 3 +--
>> >   drivers/iommu/mtk_iommu_v1.c | 3 +--
>> >   sound/soc/codecs/wcd938x.c   | 6 ++
>> >   3 files changed, 4 insertions(+), 8 deletions(-)
>> > 
>> > diff --git a/drivers/iommu/mtk_iommu.c b/drivers/iommu/mtk_iommu.c
>> > index 2ab2ecfe01f8..483b7a9e4410 100644
>> > --- a/drivers/iommu/mtk_iommu.c
>> > +++ b/drivers/iommu/mtk_iommu.c
>> > @@ -1079,8 +1079,7 @@ static int mtk_iommu_mm_dts_parse(struct device 
>> > *dev, struct component_match **m
>> >}
>> >data->larb_imu[id].dev = >dev;
>> > -  component_match_add_release(dev, match, component_release_of,
>> > -  component_compare_of, larbnode);
>> > +  component_match_add_of(dev, match, larbnode);
>> 
>> I've long since given up trying to make sense of how the DRM tree works, but
>> the conflicting change is definitely already in mainline:
>> 
>> https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit?id=b5765a1b44bea9dfcae69c53ffeb4c689d0922a7
> 
> As far as I can see, that patch doesn't affect DRM at all, and the
> commit you pointed to doesn't either, nor has it been merged through the
> DRM tree.
> 
> Can you expand a bit on how we're involved in this, what we should
> clarify or help with?

Patches 2 and 3 of this series depend on patch 1. Since patch 3 contains
the bulk of the changes, I based this series on the drm-next tree.
However, patch 2 has a conflict elswhere in the tree, which did not
appear until 6.2-rc1. At the time I sent out this series, drm-next did
not contain this commit, and I couldn't find it based on Robin's
description [1], so now we end up with a conflict. As this commit has
now been merged into drm-next, I can rebase and resend if there are no
other comments.

--Sean

[1] 
https://lore.kernel.org/linux-arm-kernel/56310773-a062-0e48-28f7-6d2c5d035...@seco.com/


[PATCH v2] drm: kirin: Enable COMPILE_TEST

2022-12-27 Thread Sean Anderson
Use macros for 64-bit divisions and shifts. This allows compile-testing
this driver on 32-bit arches, so enable it.

Signed-off-by: Sean Anderson 
---

Changes in v2:
- Use BIT_ULL

 drivers/gpu/drm/hisilicon/kirin/Kconfig |  2 +-
 drivers/gpu/drm/hisilicon/kirin/dw_drm_dsi.c| 10 +-
 drivers/gpu/drm/hisilicon/kirin/kirin_ade_reg.h |  2 +-
 3 files changed, 7 insertions(+), 7 deletions(-)

diff --git a/drivers/gpu/drm/hisilicon/kirin/Kconfig 
b/drivers/gpu/drm/hisilicon/kirin/Kconfig
index c5265675bf0c..0772f79567ef 100644
--- a/drivers/gpu/drm/hisilicon/kirin/Kconfig
+++ b/drivers/gpu/drm/hisilicon/kirin/Kconfig
@@ -1,7 +1,7 @@
 # SPDX-License-Identifier: GPL-2.0-only
 config DRM_HISI_KIRIN
tristate "DRM Support for Hisilicon Kirin series SoCs Platform"
-   depends on DRM && OF && ARM64
+   depends on DRM && OF && (ARM64 || COMPILE_TEST)
select DRM_KMS_HELPER
select DRM_GEM_DMA_HELPER
select DRM_MIPI_DSI
diff --git a/drivers/gpu/drm/hisilicon/kirin/dw_drm_dsi.c 
b/drivers/gpu/drm/hisilicon/kirin/dw_drm_dsi.c
index d9978b79828c..1cfeffefd4b4 100644
--- a/drivers/gpu/drm/hisilicon/kirin/dw_drm_dsi.c
+++ b/drivers/gpu/drm/hisilicon/kirin/dw_drm_dsi.c
@@ -157,8 +157,8 @@ static u32 dsi_calc_phy_rate(u32 req_kHz, struct 
mipi_phy_params *phy)
q_pll = 0x10 >> (7 - phy->hstx_ckg_sel);
 
temp = f_kHz * (u64)q_pll * (u64)ref_clk_ps;
-   m_n_int = temp / (u64)10;
-   m_n = (temp % (u64)10) / (u64)1;
+   m_n_int = div_u64_rem(temp, 10, _n);
+   m_n /= 1;
 
if (m_n_int % 2 == 0) {
if (m_n * 6 >= 50) {
@@ -229,8 +229,8 @@ static u32 dsi_calc_phy_rate(u32 req_kHz, struct 
mipi_phy_params *phy)
phy->pll_fbd_div5f = 1;
}
 
-   f_kHz = (u64)10 * (u64)m_pll /
-   ((u64)ref_clk_ps * (u64)n_pll * (u64)q_pll);
+   f_kHz = div64_u64((u64)10 * (u64)m_pll,
+ (u64)ref_clk_ps * (u64)n_pll * (u64)q_pll);
 
if (f_kHz >= req_kHz)
break;
@@ -490,7 +490,7 @@ static void dsi_set_mode_timing(void __iomem *base,
hsa_time = (hsw * lane_byte_clk_kHz) / pixel_clk_kHz;
hbp_time = (hbp * lane_byte_clk_kHz) / pixel_clk_kHz;
tmp = (u64)htot * (u64)lane_byte_clk_kHz;
-   hline_time = DIV_ROUND_UP(tmp, pixel_clk_kHz);
+   hline_time = DIV64_U64_ROUND_UP(tmp, pixel_clk_kHz);
 
/* all specified in byte-lane clocks */
writel(hsa_time, base + VID_HSA_TIME);
diff --git a/drivers/gpu/drm/hisilicon/kirin/kirin_ade_reg.h 
b/drivers/gpu/drm/hisilicon/kirin/kirin_ade_reg.h
index be9e789c2d04..36f923cc7594 100644
--- a/drivers/gpu/drm/hisilicon/kirin/kirin_ade_reg.h
+++ b/drivers/gpu/drm/hisilicon/kirin/kirin_ade_reg.h
@@ -10,7 +10,7 @@
 /*
  * ADE Registers
  */
-#define MASK(x)(BIT(x) - 1)
+#define MASK(x)(BIT_ULL(x) - 1)
 
 #define ADE_CTRL   0x0004
 #define FRM_END_START_OFST 0
-- 
2.35.1.1320.gc452695387.dirty



[PATCH v2 3/3] drm: Convert users of drm_of_component_match_add to component_match_add_of

2022-12-22 Thread Sean Anderson
Every user of this function either uses component_compare_of or
something equivalent. Most of them immediately put the device node as
well. Convert these users to component_match_add_of and remove
drm_of_component_match_add.

Signed-off-by: Sean Anderson 
Acked-by: Jyri Sarha 
Tested-by: Jyri Sarha 
---

(no changes since v1)

 .../gpu/drm/arm/display/komeda/komeda_drv.c   |  6 ++--
 drivers/gpu/drm/arm/hdlcd_drv.c   |  9 +-
 drivers/gpu/drm/arm/malidp_drv.c  | 11 +--
 drivers/gpu/drm/armada/armada_drv.c   | 10 ---
 drivers/gpu/drm/drm_of.c  | 29 +++
 drivers/gpu/drm/etnaviv/etnaviv_drv.c |  4 +--
 .../gpu/drm/hisilicon/kirin/kirin_drm_drv.c   |  3 +-
 drivers/gpu/drm/ingenic/ingenic-drm-drv.c |  3 +-
 drivers/gpu/drm/mediatek/mtk_drm_drv.c|  4 +--
 drivers/gpu/drm/msm/msm_drv.c | 14 -
 drivers/gpu/drm/sti/sti_drv.c |  3 +-
 drivers/gpu/drm/sun4i/sun4i_drv.c |  3 +-
 drivers/gpu/drm/tilcdc/tilcdc_external.c  | 10 ++-
 include/drm/drm_of.h  | 12 
 14 files changed, 33 insertions(+), 88 deletions(-)

diff --git a/drivers/gpu/drm/arm/display/komeda/komeda_drv.c 
b/drivers/gpu/drm/arm/display/komeda/komeda_drv.c
index 3f4e719eebd8..e3bfc72c378f 100644
--- a/drivers/gpu/drm/arm/display/komeda/komeda_drv.c
+++ b/drivers/gpu/drm/arm/display/komeda/komeda_drv.c
@@ -103,10 +103,8 @@ static void komeda_add_slave(struct device *master,
struct device_node *remote;
 
remote = of_graph_get_remote_node(np, port, endpoint);
-   if (remote) {
-   drm_of_component_match_add(master, match, component_compare_of, 
remote);
-   of_node_put(remote);
-   }
+   if (remote)
+   component_match_add_of(master, match, remote);
 }
 
 static int komeda_platform_probe(struct platform_device *pdev)
diff --git a/drivers/gpu/drm/arm/hdlcd_drv.c b/drivers/gpu/drm/arm/hdlcd_drv.c
index 7043d1c9ed8f..fa37ca10c80b 100644
--- a/drivers/gpu/drm/arm/hdlcd_drv.c
+++ b/drivers/gpu/drm/arm/hdlcd_drv.c
@@ -353,11 +353,6 @@ static const struct component_master_ops hdlcd_master_ops 
= {
.unbind = hdlcd_drm_unbind,
 };
 
-static int compare_dev(struct device *dev, void *data)
-{
-   return dev->of_node == data;
-}
-
 static int hdlcd_probe(struct platform_device *pdev)
 {
struct device_node *port;
@@ -368,9 +363,7 @@ static int hdlcd_probe(struct platform_device *pdev)
if (!port)
return -ENODEV;
 
-   drm_of_component_match_add(>dev, , compare_dev, port);
-   of_node_put(port);
-
+   component_match_add_of(>dev, , port);
return component_master_add_with_match(>dev, _master_ops,
   match);
 }
diff --git a/drivers/gpu/drm/arm/malidp_drv.c b/drivers/gpu/drm/arm/malidp_drv.c
index 589c1c66a6dc..3a49c29ba5b8 100644
--- a/drivers/gpu/drm/arm/malidp_drv.c
+++ b/drivers/gpu/drm/arm/malidp_drv.c
@@ -909,13 +909,6 @@ static const struct component_master_ops malidp_master_ops 
= {
.unbind = malidp_unbind,
 };
 
-static int malidp_compare_dev(struct device *dev, void *data)
-{
-   struct device_node *np = data;
-
-   return dev->of_node == np;
-}
-
 static int malidp_platform_probe(struct platform_device *pdev)
 {
struct device_node *port;
@@ -929,9 +922,7 @@ static int malidp_platform_probe(struct platform_device 
*pdev)
if (!port)
return -ENODEV;
 
-   drm_of_component_match_add(>dev, , malidp_compare_dev,
-  port);
-   of_node_put(port);
+   component_match_add_of(>dev, , port);
return component_master_add_with_match(>dev, _master_ops,
   match);
 }
diff --git a/drivers/gpu/drm/armada/armada_drv.c 
b/drivers/gpu/drm/armada/armada_drv.c
index 0643887800b4..c0211ad7a45d 100644
--- a/drivers/gpu/drm/armada/armada_drv.c
+++ b/drivers/gpu/drm/armada/armada_drv.c
@@ -184,10 +184,12 @@ static void armada_add_endpoints(struct device *dev,
 
for_each_endpoint_of_node(dev_node, ep) {
remote = of_graph_get_remote_port_parent(ep);
-   if (remote && of_device_is_available(remote))
-   drm_of_component_match_add(dev, match, 
component_compare_of,
-  remote);
-   of_node_put(remote);
+   if (remote) {
+   if (of_device_is_available(remote))
+   component_match_add_of(dev, match, remote);
+   else
+   of_node_put(remote);
+   }
}
 }
 
diff --git a/drivers/gpu/drm/drm_of.c b/drivers/gpu/drm/drm_of.c
index 7bbcb999bb75..0a474729ddf6 100644
--- a/drivers/gpu/drm/drm_of.c
+++ b/drivers/gpu/drm/drm_

[PATCH v2 2/3] iommu/sound: Use component_match_add_of helper

2022-12-22 Thread Sean Anderson
Convert users of component_match_add_release with component_release_of
and component_compare_of to component_match_add_of.

Signed-off-by: Sean Anderson 
Acked-by: Mark Brown 
---

Changes in v2:
- Split off from helper addition

 drivers/iommu/mtk_iommu.c| 3 +--
 drivers/iommu/mtk_iommu_v1.c | 3 +--
 sound/soc/codecs/wcd938x.c   | 6 ++
 3 files changed, 4 insertions(+), 8 deletions(-)

diff --git a/drivers/iommu/mtk_iommu.c b/drivers/iommu/mtk_iommu.c
index 2ab2ecfe01f8..483b7a9e4410 100644
--- a/drivers/iommu/mtk_iommu.c
+++ b/drivers/iommu/mtk_iommu.c
@@ -1079,8 +1079,7 @@ static int mtk_iommu_mm_dts_parse(struct device *dev, 
struct component_match **m
}
data->larb_imu[id].dev = >dev;
 
-   component_match_add_release(dev, match, component_release_of,
-   component_compare_of, larbnode);
+   component_match_add_of(dev, match, larbnode);
}
 
/* Get smi-(sub)-common dev from the last larb. */
diff --git a/drivers/iommu/mtk_iommu_v1.c b/drivers/iommu/mtk_iommu_v1.c
index 6e0e65831eb7..fb09ed6bf550 100644
--- a/drivers/iommu/mtk_iommu_v1.c
+++ b/drivers/iommu/mtk_iommu_v1.c
@@ -672,8 +672,7 @@ static int mtk_iommu_v1_probe(struct platform_device *pdev)
}
data->larb_imu[i].dev = >dev;
 
-   component_match_add_release(dev, , component_release_of,
-   component_compare_of, larbnode);
+   component_match_add_of(dev, , larbnode);
}
 
platform_set_drvdata(pdev, data);
diff --git a/sound/soc/codecs/wcd938x.c b/sound/soc/codecs/wcd938x.c
index aca06a4026f3..2f8444e54083 100644
--- a/sound/soc/codecs/wcd938x.c
+++ b/sound/soc/codecs/wcd938x.c
@@ -4474,8 +4474,7 @@ static int wcd938x_add_slave_components(struct 
wcd938x_priv *wcd938x,
}
 
of_node_get(wcd938x->rxnode);
-   component_match_add_release(dev, matchptr, component_release_of,
-   component_compare_of, wcd938x->rxnode);
+   component_match_add_of(dev, matchptr, wcd938x->rxnode);
 
wcd938x->txnode = of_parse_phandle(np, "qcom,tx-device", 0);
if (!wcd938x->txnode) {
@@ -4483,8 +4482,7 @@ static int wcd938x_add_slave_components(struct 
wcd938x_priv *wcd938x,
return -ENODEV;
}
of_node_get(wcd938x->txnode);
-   component_match_add_release(dev, matchptr, component_release_of,
-   component_compare_of, wcd938x->txnode);
+   component_match_add_of(dev, matchptr, wcd938x->txnode);
return 0;
 }
 
-- 
2.35.1.1320.gc452695387.dirty



[PATCH v2 1/3] component: Add helper for device nodes

2022-12-22 Thread Sean Anderson
There is a common case where component_patch_add_release is called with
component_release_of/component_compare_of and a device node. Add a
helper.

Signed-off-by: Sean Anderson 
---

Changes in v2:
- Split off conversion from helper addition

 include/linux/component.h | 9 +
 1 file changed, 9 insertions(+)

diff --git a/include/linux/component.h b/include/linux/component.h
index df4aa75c9e7c..fb5d2dbc34d8 100644
--- a/include/linux/component.h
+++ b/include/linux/component.h
@@ -6,6 +6,7 @@
 
 
 struct device;
+struct device_node;
 
 /**
  * struct component_ops - callbacks for component drivers
@@ -128,4 +129,12 @@ static inline void component_match_add(struct device 
*parent,
compare_data);
 }
 
+static inline void component_match_add_of(struct device *parent,
+ struct component_match **matchptr,
+ struct device_node *node)
+{
+   component_match_add_release(parent, matchptr, component_release_of,
+   component_compare_of, node);
+}
+
 #endif
-- 
2.35.1.1320.gc452695387.dirty



[PATCH v2 0/3] drm: Add component_match_add_of and convert users of drm_of_component_match_add

2022-12-22 Thread Sean Anderson
This series adds a new function component_match_add_of to simplify the
common case of calling component_match_add_release with
component_release_of and component_compare_of. There is already
drm_of_component_match_add, which allows for a custom compare function.
However, all existing users just use component_compare_of (or an
equivalent).

Changes in v2:
- Split off conversion from helper addition
- Rebased onto drm/drm-next

Sean Anderson (3):
  component: Add helper for device nodes
  iommu/sound: Use component_match_add_of helper
  drm: Convert users of drm_of_component_match_add to
component_match_add_of

 .../gpu/drm/arm/display/komeda/komeda_drv.c   |  6 ++--
 drivers/gpu/drm/arm/hdlcd_drv.c   |  9 +-
 drivers/gpu/drm/arm/malidp_drv.c  | 11 +--
 drivers/gpu/drm/armada/armada_drv.c   | 10 ---
 drivers/gpu/drm/drm_of.c  | 29 +++
 drivers/gpu/drm/etnaviv/etnaviv_drv.c |  4 +--
 .../gpu/drm/hisilicon/kirin/kirin_drm_drv.c   |  3 +-
 drivers/gpu/drm/ingenic/ingenic-drm-drv.c |  3 +-
 drivers/gpu/drm/mediatek/mtk_drm_drv.c|  4 +--
 drivers/gpu/drm/msm/msm_drv.c | 14 -
 drivers/gpu/drm/sti/sti_drv.c |  3 +-
 drivers/gpu/drm/sun4i/sun4i_drv.c |  3 +-
 drivers/gpu/drm/tilcdc/tilcdc_external.c  | 10 ++-
 drivers/iommu/mtk_iommu.c |  3 +-
 drivers/iommu/mtk_iommu_v1.c  |  3 +-
 include/drm/drm_of.h  | 12 
 include/linux/component.h |  9 ++
 sound/soc/codecs/wcd938x.c|  6 ++--
 18 files changed, 46 insertions(+), 96 deletions(-)

-- 
2.35.1.1320.gc452695387.dirty



[PATCH] drm: kirin: Enable COMPILE_TEST

2022-12-22 Thread Sean Anderson
Use macros for 64-bit operations. This allows compile-testing this
driver on 32-bit arches, so enable it.

Signed-off-by: Sean Anderson 
---

 drivers/gpu/drm/hisilicon/kirin/Kconfig  |  2 +-
 drivers/gpu/drm/hisilicon/kirin/dw_drm_dsi.c | 10 +-
 2 files changed, 6 insertions(+), 6 deletions(-)

diff --git a/drivers/gpu/drm/hisilicon/kirin/Kconfig 
b/drivers/gpu/drm/hisilicon/kirin/Kconfig
index c5265675bf0c..0772f79567ef 100644
--- a/drivers/gpu/drm/hisilicon/kirin/Kconfig
+++ b/drivers/gpu/drm/hisilicon/kirin/Kconfig
@@ -1,7 +1,7 @@
 # SPDX-License-Identifier: GPL-2.0-only
 config DRM_HISI_KIRIN
tristate "DRM Support for Hisilicon Kirin series SoCs Platform"
-   depends on DRM && OF && ARM64
+   depends on DRM && OF && (ARM64 || COMPILE_TEST)
select DRM_KMS_HELPER
select DRM_GEM_DMA_HELPER
select DRM_MIPI_DSI
diff --git a/drivers/gpu/drm/hisilicon/kirin/dw_drm_dsi.c 
b/drivers/gpu/drm/hisilicon/kirin/dw_drm_dsi.c
index d9978b79828c..1cfeffefd4b4 100644
--- a/drivers/gpu/drm/hisilicon/kirin/dw_drm_dsi.c
+++ b/drivers/gpu/drm/hisilicon/kirin/dw_drm_dsi.c
@@ -157,8 +157,8 @@ static u32 dsi_calc_phy_rate(u32 req_kHz, struct 
mipi_phy_params *phy)
q_pll = 0x10 >> (7 - phy->hstx_ckg_sel);
 
temp = f_kHz * (u64)q_pll * (u64)ref_clk_ps;
-   m_n_int = temp / (u64)10;
-   m_n = (temp % (u64)10) / (u64)1;
+   m_n_int = div_u64_rem(temp, 10, _n);
+   m_n /= 1;
 
if (m_n_int % 2 == 0) {
if (m_n * 6 >= 50) {
@@ -229,8 +229,8 @@ static u32 dsi_calc_phy_rate(u32 req_kHz, struct 
mipi_phy_params *phy)
phy->pll_fbd_div5f = 1;
}
 
-   f_kHz = (u64)10 * (u64)m_pll /
-   ((u64)ref_clk_ps * (u64)n_pll * (u64)q_pll);
+   f_kHz = div64_u64((u64)10 * (u64)m_pll,
+ (u64)ref_clk_ps * (u64)n_pll * (u64)q_pll);
 
if (f_kHz >= req_kHz)
break;
@@ -490,7 +490,7 @@ static void dsi_set_mode_timing(void __iomem *base,
hsa_time = (hsw * lane_byte_clk_kHz) / pixel_clk_kHz;
hbp_time = (hbp * lane_byte_clk_kHz) / pixel_clk_kHz;
tmp = (u64)htot * (u64)lane_byte_clk_kHz;
-   hline_time = DIV_ROUND_UP(tmp, pixel_clk_kHz);
+   hline_time = DIV64_U64_ROUND_UP(tmp, pixel_clk_kHz);
 
/* all specified in byte-lane clocks */
writel(hsa_time, base + VID_HSA_TIME);
-- 
2.35.1.1320.gc452695387.dirty



  1   2   >