Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 0 additions & 44 deletions drivers/gpu/drm/apple/audio-link-test.c
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
struct dcpaud_link_test {
bool link_on;
bool link_live;
bool typec;
/* The error removing the idle transfer returns */
int idle_remove_err;
u64 asked;
Expand Down Expand Up @@ -40,11 +39,6 @@ static bool dcpaud_link_test_live(struct device *dev)
return dcpaud_link_current->link_live;
}

static bool dcpaud_link_test_typec(struct device *dev)
{
return dcpaud_link_current->typec;
}

static int dcpaud_link_test_terminate(struct dma_chan *chan)
{
dcpaud_link_record('X');
Expand Down Expand Up @@ -147,7 +141,6 @@ static struct dcp_audio *dcpaud_link_setup(struct kunit *test, struct platform_d
kunit_activate_static_stub(test, dcp_audiosrv_link_on, dcpaud_link_test_on);
kunit_activate_static_stub(test, dcp_audiosrv_link_live, dcpaud_link_test_live);
kunit_activate_static_stub(test, dcp_audiosrv_link, dcpaud_link_test_call);
kunit_activate_static_stub(test, dcp_audiosrv_typec, dcpaud_link_test_typec);
return dcpaud;
}

Expand Down Expand Up @@ -451,42 +444,6 @@ static void dcpaud_close_keeps_held_silence(struct kunit *test)
pm_runtime_put_noidle(audio->dev);
}

static void dcpaud_link_test_hold_default(void *data)
{
WRITE_ONCE(hold_link, 1);
}

static void dcpaud_typec_stop_ends_stream(struct kunit *test)
{
struct platform_device *pdev;
struct dcp_audio *audio = dcpaud_link_setup(test, &pdev);
struct snd_pcm_substream *sub = dcpaud_link_test_open(test, audio);

KUNIT_ASSERT_EQ(test,
kunit_add_action_or_reset(test, dcpaud_link_test_hold_default, NULL), 0);
sub->runtime->channels = 2;
sub->runtime->dma_area = kunit_kzalloc(test, 64, GFP_KERNEL);
KUNIT_ASSERT_NOT_NULL(test, sub->runtime->dma_area);
sub->runtime->dma_bytes = 64;
dcpaud_link_current->typec = true;

/* USB-C by default: STOP ends the stream's DMA, no link is held */
audio->stream_dma = true;
pm_runtime_get_noresume(audio->dev);
KUNIT_ASSERT_EQ(test, dcp_pcm_native_trigger(sub, SNDRV_PCM_TRIGGER_STOP), 0);
KUNIT_EXPECT_STREQ(test, dcpaud_link_current->sequence, "X");
KUNIT_EXPECT_FALSE(test, audio->dma_cleared);
KUNIT_EXPECT_EQ(test, atomic_read(&audio->dev->power.usage_count), 0);

/* hold_link=2: the DMA plays the cleared buffer on for the held link */
WRITE_ONCE(hold_link, 2);
pm_runtime_get_noresume(audio->dev);
KUNIT_ASSERT_EQ(test, dcp_pcm_native_trigger(sub, SNDRV_PCM_TRIGGER_STOP), 0);
KUNIT_EXPECT_STREQ(test, dcpaud_link_current->sequence, "X");
KUNIT_EXPECT_TRUE(test, audio->dma_cleared);
pm_runtime_put_noidle(audio->dev);
}

static struct kunit_case dcpaud_link_cases[] = {
KUNIT_CASE(dcpaud_link_release_instance),
KUNIT_CASE(dcpaud_disconnect_sync_completes_live_owner),
Expand All @@ -499,7 +456,6 @@ static struct kunit_case dcpaud_link_cases[] = {
KUNIT_CASE(dcpaud_start_after_held_link_went),
KUNIT_CASE(dcpaud_failed_start_keeps_held_link),
KUNIT_CASE(dcpaud_close_keeps_held_silence),
KUNIT_CASE(dcpaud_typec_stop_ends_stream),
{}
};

Expand Down
42 changes: 11 additions & 31 deletions drivers/gpu/drm/apple/audio.c
Original file line number Diff line number Diff line change
Expand Up @@ -655,39 +655,19 @@ struct snd_pcm_ops dcp_playback_ops = {
* service itself is open while the PCM is, or while its link is held.
*/
/*
* The sink behind the J514S HDMI port (PS190 converter and TV) mutes the
* audio of a link that stops and starts again with an unchanged format,
* until the DP link trains again: only a different channel layout brings
* it back. So a stereo link, once started, is held between streams, as
* macOS holds it: silence follows the stopped stream's DMA and the link
* keeps running until the format changes or the display goes off
* The sinks behind the J514S HDMI port (PS190 converter and TV) and its
* Type-C ports mute the audio of a link that stops and starts again with an
* unchanged format, until the DP link trains again: only a different channel
* layout brings it back. So a stereo link, once started, is held between
* streams, as macOS holds it: silence follows the stopped stream's DMA and
* the link keeps running until the format changes or the display goes off
* (dcpaud_release_link()).
*
* The link must not run dry either: a gap of 20 ms in its samples mutes the
* sink the same way. The silence is the SIO channel's idle transfer, which
* the channel issues whenever no stream's DMA does, in descriptor order:
* a stream's DMA takes over from it and hands back to it without a gap.
*/
/*
* Read-only at runtime: a stream opened while it was lower keeps periods
* longer than a held link's synchronize allows for.
*/
static int hold_link = 1;
module_param(hold_link, int, 0444);
MODULE_PARM_DESC(hold_link,
"Hold started audio links between streams (silence): 0 never, 1 on the HDMI port (default), 2 also on USB-C");

/*
* The mute was measured behind the HDMI port. USB-C sinks keep one link per
* stream unless hold_link=2, until they have been qualified with this.
*/
static bool dcpaud_holds(struct dcp_audio *dcpaud)
{
int hold = READ_ONCE(hold_link);

return hold >= 2 || (hold == 1 && !dcp_audiosrv_typec(dcpaud->dcp_dev));
}

/*
* A change of sample rate or sample size with an unchanged channel layout
* replaces the link with a new one, which this sink mutes just as it does a
Expand Down Expand Up @@ -879,7 +859,7 @@ static int dcp_pcm_native_open(struct snd_pcm_substream *substream)
}

ret = dcp_pcm_open(substream);
if (!ret && live && !dcpaud->open_unplugged && dcpaud_holds(dcpaud)) {
if (!ret && live && !dcpaud->open_unplugged) {
ret = dcpaud_pin_rate_format(dcpaud, substream->runtime);
if (!ret)
ret = snd_pcm_hw_constraint_minmax(substream->runtime,
Expand Down Expand Up @@ -1043,8 +1023,8 @@ static int dcp_pcm_native_start(struct snd_pcm_substream *substream, int cmd)
/* The first link of the connection waits for the sink to settle. */
scoped_guard(mutex, &dcpaud->data_lock)
up_ms = ktime_ms_delta(ktime_get(), dcpaud->connected_at);
if (dcpaud_holds(dcpaud) && !READ_ONCE(dcpaud->link_started) &&
up_ms >= 0 && up_ms < DCPAUD_SINK_SETTLE_MS) {
if (!READ_ONCE(dcpaud->link_started) && up_ms >= 0 &&
up_ms < DCPAUD_SINK_SETTLE_MS) {
dev_dbg(dcpaud->dev, "sink up %lld ms, link waits\n", up_ms);
msleep(DCPAUD_SINK_SETTLE_MS - up_ms);
}
Expand Down Expand Up @@ -1117,8 +1097,8 @@ static int dcp_pcm_native_trigger(struct snd_pcm_substream *substream, int cmd)
* reference, until sync_stop hands over to the held link's
* silence: the link must not run dry meanwhile.
*/
if (dcpaud_holds(dcpaud) && READ_ONCE(dcpaud->stream_dma) &&
substream->runtime->channels == 2 && substream->runtime->dma_area) {
if (READ_ONCE(dcpaud->stream_dma) && substream->runtime->channels == 2 &&
substream->runtime->dma_area) {
memset(substream->runtime->dma_area, 0, substream->runtime->dma_bytes);
WRITE_ONCE(dcpaud->dma_cleared, true);
return 0;
Expand Down
2 changes: 0 additions & 2 deletions drivers/gpu/drm/apple/audio.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,6 @@ void dcpaud_release_link(struct platform_device *pdev, const u64 *gen);
bool dcp_audiosrv_link_on(struct device *dev, u64 gen);
/* The prepared link still belongs to the client of the published instance. */
bool dcp_audiosrv_link_live(struct device *dev);
/* The display processor drives a Type-C route rather than the HDMI port. */
bool dcp_audiosrv_typec(struct device *dev);
#endif

#endif /* __AUDIO_H__ */
6 changes: 0 additions & 6 deletions drivers/gpu/drm/apple/av.c
Original file line number Diff line number Diff line change
Expand Up @@ -400,12 +400,6 @@ bool dcp_audiosrv_link_live(struct device *dev)
return asrv->link_gen == asrv->open_gen && av_audiosrv_open_published(asrv);
}

bool dcp_audiosrv_typec(struct device *dev)
{
KUNIT_STATIC_STUB_REDIRECT(dcp_audiosrv_typec, dev);
return dcp_is_typec_output(dev_get_drvdata(dev));
}

/*
* The link commands of the 14.7 external role's audio service, the native
* audio driver's: prepare, start, stop and complete all take the selected
Expand Down
12 changes: 12 additions & 0 deletions drivers/gpu/drm/apple/iomfb_v14_7.c
Original file line number Diff line number Diff line change
Expand Up @@ -3271,6 +3271,17 @@ static int dcp_v14_external_modeset(struct apple_dcp *dcp, struct drm_crtc_state
ret = dcp_v14_set_mode(v14, mode.color_mode_id, mode.timing_mode_id);
if (!ret)
ret = dcp_v14_set_power(v14, true);
/*
* The mode was set with the pipe off. A mode that needs DSC is then
* reset again ("pipe not enabled"), and the power-on only configures
* DSC: the firmware starts the timings in a mode set of its own, after
* the hotplug notification it sends itself, and drops a swap that
* reaches it before then without completing it, so A407 times out.
* Setting the mode once more with the pipe on starts the timings
* before the first swap.
*/
if (!ret)
ret = dcp_v14_set_mode(v14, mode.color_mode_id, mode.timing_mode_id);
if (!ret) {
v14->panel_width = mode.mode.hdisplay;
v14->panel_height = mode.mode.vdisplay;
Expand Down Expand Up @@ -3383,6 +3394,7 @@ void iomfb_v14_7_poweroff(struct apple_dcp *dcp)

#if IS_ENABLED(CONFIG_DRM_APPLE_DISPLAY_KUNIT_TEST)
#include "iomfb_v14_7_external_audio_test.c"
#include "iomfb_v14_7_external_modeset_test.c"
#include "iomfb_v14_7_backlight_test.c"
#include "iomfb_v14_7_backlight_lifetime_test.c"
#endif
165 changes: 165 additions & 0 deletions drivers/gpu/drm/apple/iomfb_v14_7_external_modeset_test.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
// SPDX-License-Identifier: GPL-2.0-only OR MIT
/*
* The DCP calls of an external mode set, included at the end of
* iomfb_v14_7.c so that the production dcp_v14_external_modeset() runs.
* Every call is answered as accepted; the audio service is stubbed.
*/
#include <kunit/device.h>
#include <kunit/static_stub.h>
#include <kunit/test.h>

#define V14_EXT_MODESET_MAX_CALLS 8

struct v14_ext_modeset {
struct apple_dcp *dcp;
struct apple_dcp_v14 *v14;
struct dcp_display_mode mode;
u32 tags[V14_EXT_MODESET_MAX_CALLS];
bool power[V14_EXT_MODESET_MAX_CALLS];
unsigned int calls;
};

static struct v14_ext_modeset *v14_ext_modeset_current;

static int v14_ext_modeset_call(struct apple_dcp_v14 *v14, u32 tag, const void *in, u32 in_size,
void *out, u32 out_size, u32 completion, dcp_v14_admit_fn admit,
void *admit_cookie)
{
struct v14_ext_modeset *s = v14_ext_modeset_current;

if (s->calls < V14_EXT_MODESET_MAX_CALLS) {
s->tags[s->calls] = tag;
s->power[s->calls] = tag == A(472) && in_size >= 8 && get_unaligned_le64(in);
}
s->calls++;
if (out)
memset(out, 0, out_size);
return 0;
}

static void v14_ext_modeset_audio(struct apple_dcp *dcp)
{
}

static void v14_ext_modeset_retry(struct work_struct *work)
{
}

static void v14_ext_modeset_stop(void *data)
{
struct apple_dcp *dcp = data;

cancel_delayed_work_sync(&dcp->external_retry_wq);
}

/* A started, described native external session with one mode, its display @powered. */
static struct v14_ext_modeset *v14_ext_modeset_setup(struct kunit *test, bool powered)
{
struct v14_ext_modeset *s = kunit_kzalloc(test, sizeof(*s), GFP_KERNEL);
struct device *dev;

KUNIT_ASSERT_NOT_NULL(test, s);
dev = kunit_device_register(test, "dcp-v14-ext-modeset");
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev);
s->dcp = devm_kzalloc(dev, sizeof(*s->dcp), GFP_KERNEL);
s->v14 = devm_kzalloc(dev, sizeof(*s->v14), GFP_KERNEL);
KUNIT_ASSERT_NOT_NULL(test, s->dcp);
KUNIT_ASSERT_NOT_NULL(test, s->v14);
s->dcp->dev = dev;
s->dcp->fw_compat = DCP_FIRMWARE_V_14_7;
s->dcp->external = true;
s->dcp->external_native = true;
/* Never dereferenced: the audio service is stubbed. */
s->dcp->avep = (struct apple_dcp_afkep *)s;
mutex_init(&s->dcp->modes_lock);
mutex_init(&s->dcp->hpd_mutex);
spin_lock_init(&s->dcp->metadata.lock);
spin_lock_init(&s->dcp->mode_state.lock);
INIT_DELAYED_WORK(&s->dcp->external_retry_wq, v14_ext_modeset_retry);
/* 3840x2160 at 100 Hz, timing 167 and color 219 as a J514S TV reports it. */
s->mode.mode = (struct drm_display_mode){
DRM_MODE("3840x2160", DRM_MODE_TYPE_DRIVER, 1188000, 3840, 4896, 4984, 5280, 0,
2160, 2168, 2178, 2250, 0, DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC)
};
s->mode.timing_mode_id = 167;
s->mode.color_mode_id = 219;
s->dcp->modes = &s->mode;
s->dcp->nr_modes = 1;
s->dcp->modes_admitted = true;
s->dcp->v14 = s->v14;
s->v14->dev = dev;
s->v14->dcp = s->dcp;
s->v14->external = true;
s->v14->started = true;
s->v14->opened = true;
s->v14->powered = powered;
s->v14->described = DCP_V14_DESC_ALL;
mutex_init(&s->v14->lock);
init_waitqueue_head(&s->v14->described_wait);
KUNIT_ASSERT_EQ(test, kunit_add_action_or_reset(test, v14_ext_modeset_stop, s->dcp), 0);
v14_ext_modeset_current = s;
kunit_activate_static_stub(test, dcp_v14_call_guarded, v14_ext_modeset_call);
kunit_activate_static_stub(test, dcp_v14_audio_off, v14_ext_modeset_audio);
kunit_activate_static_stub(test, dcp_v14_audio_on, v14_ext_modeset_audio);
return s;
}

static int v14_ext_modeset_run(struct kunit *test, struct v14_ext_modeset *s)
{
struct drm_crtc_state *state = kunit_kzalloc(test, sizeof(*state), GFP_KERNEL);

KUNIT_ASSERT_NOT_NULL(test, state);
drm_mode_copy(&state->mode, &s->mode.mode);
return dcp_v14_external_modeset(s->dcp, state);
}

/*
* A display that was off, as on attach: the mode is set, the display powered
* on, and the mode set once more with the pipe on, before any swap. A DSC
* mode set with the pipe off only takes effect then.
*/
static void v14_ext_modeset_sets_mode_again_after_power_on(struct kunit *test)
{
struct v14_ext_modeset *s = v14_ext_modeset_setup(test, false);

KUNIT_EXPECT_EQ(test, v14_ext_modeset_run(test, s), 0);
KUNIT_ASSERT_EQ(test, s->calls, 3U);
KUNIT_EXPECT_EQ(test, s->tags[0], A(411));
KUNIT_EXPECT_EQ(test, s->tags[1], A(472));
KUNIT_EXPECT_TRUE(test, s->power[1]);
KUNIT_EXPECT_EQ(test, s->tags[2], A(411));
KUNIT_EXPECT_TRUE(test, s->v14->powered);
KUNIT_EXPECT_TRUE(test, s->v14->mode_live);
KUNIT_EXPECT_EQ(test, s->v14->mode_timing, 167U);
KUNIT_EXPECT_EQ(test, s->v14->mode_color, 219U);
KUNIT_EXPECT_FALSE(test, mutex_is_locked(&s->v14->lock));
}

/* A mode change on a display that is on: power down, set, power up, set again. */
static void v14_ext_modeset_mode_change(struct kunit *test)
{
struct v14_ext_modeset *s = v14_ext_modeset_setup(test, true);

KUNIT_EXPECT_EQ(test, v14_ext_modeset_run(test, s), 0);
KUNIT_ASSERT_EQ(test, s->calls, 4U);
KUNIT_EXPECT_EQ(test, s->tags[0], A(472));
KUNIT_EXPECT_FALSE(test, s->power[0]);
KUNIT_EXPECT_EQ(test, s->tags[1], A(411));
KUNIT_EXPECT_EQ(test, s->tags[2], A(472));
KUNIT_EXPECT_TRUE(test, s->power[2]);
KUNIT_EXPECT_EQ(test, s->tags[3], A(411));
KUNIT_EXPECT_TRUE(test, s->v14->powered);
}

static struct kunit_case v14_ext_modeset_cases[] = {
KUNIT_CASE(v14_ext_modeset_sets_mode_again_after_power_on),
KUNIT_CASE(v14_ext_modeset_mode_change),
{}
};

static struct kunit_suite v14_ext_modeset_suite = {
.name = "dcp_v14_external_modeset",
.test_cases = v14_ext_modeset_cases,
};

kunit_test_suite(v14_ext_modeset_suite);
Loading