// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2012-2020, The Linux Foundation. All rights reserved.
*/
#define pr_fmt(fmt) "[drm-dp] %s: " fmt, __func__
#include <drm/drm_print.h>
#include "dp_reg.h"
#include "dp_link.h"
#include "dp_panel.h"
#define DP_TEST_REQUEST_MASK 0x7F
enum audio_sample_rate {
AUDIO_SAMPLE_RATE_32_KHZ = 0x00,
AUDIO_SAMPLE_RATE_44_1_KHZ = 0x01,
AUDIO_SAMPLE_RATE_48_KHZ = 0x02,
AUDIO_SAMPLE_RATE_88_2_KHZ = 0x03,
AUDIO_SAMPLE_RATE_96_KHZ = 0x04,
AUDIO_SAMPLE_RATE_176_4_KHZ = 0x05,
AUDIO_SAMPLE_RATE_192_KHZ = 0x06,
};
enum audio_pattern_type {
AUDIO_TEST_PATTERN_OPERATOR_DEFINED = 0x00,
AUDIO_TEST_PATTERN_SAWTOOTH = 0x01,
};
struct dp_link_request {
u32 test_requested;
u32 test_link_rate;
u32 test_lane_count;
};
struct dp_link_private {
u32 prev_sink_count;
struct drm_device *drm_dev;
struct drm_dp_aux *aux;
struct dp_link dp_link;
struct dp_link_request request;
struct mutex psm_mutex;
u8 link_status[DP_LINK_STATUS_SIZE];
};
static int dp_aux_link_power_up(struct drm_dp_aux *aux,
struct dp_link_info *link)
{
u8 value;
ssize_t len;
int i;
if (link->revision < 0x11)
return 0;
len = drm_dp_dpcd_readb(aux, DP_SET_POWER, &value);
if (len < 0)
return len;
value &= ~DP_SET_POWER_MASK;
value |= DP_SET_POWER_D0;
/* retry for 1ms to give the sink time to wake up */
for (i = 0; i < 3; i++) {
len = drm_dp_dpcd_writeb(aux, DP_SET_POWER, value);
usleep_range(1000, 2000);
if (len == 1)
break;
}
return 0;
}
static int dp_aux_link_power_down(struct drm_dp_aux *aux,
struct dp_link_info *link)
{
u8 value;
int err;
if (link->revision < 0x11)
return 0;
err = drm_dp_dpcd_readb(aux, DP_SET_POWER, &value);
if (err < 0)
return err;
value &= ~DP_SET_POWER_MASK;
value |= DP_SET_POWER_D3;
err = drm_dp_dpcd_writeb(aux, DP_SET_POWER, value);
if (err < 0)
return err;
return 0;
}
static int dp_link_get_period(struct dp_link_private *link, int const addr)
{
int ret = 0;
u8 data;
u32 const max_audio_period = 0xA;
/* TEST_AUDIO_PERIOD_CH_XX */
if (drm_dp_dpcd_readb(link->aux, addr, &data) < 0) {
DRM_ERROR("failed to read test_audio_period (0x%x)\n", addr);
ret = -EINVAL;
goto exit;
}
/* Period - Bits 3:0 */
data = data & 0xF;
if ((int)data > max_audio_period) {
DRM_ERROR("invalid test_audio_period_ch_1 = 0x%x\n", data);
ret = -EINVAL;
goto exit;
}
ret = data;
exit:
return ret;
}
static int dp_link_parse_audio_channel_period(struct dp_link_private *link)
{
int ret = 0;
struct dp_link_test_audio *req = &link->dp_link.test_audio;
ret = dp_link_get_period(link, DP_TEST_AUDIO_PERIOD_CH1);
if (ret == -EINVAL)
goto exit;
req->test_audio_period_ch_1 = ret;
drm_dbg_dp(link->drm_dev, "test_audio_period_ch_1 = 0x%x\n", ret);
ret = dp_link_get_period(link, DP_TEST_AUDIO_PERIOD_CH2);
if (ret == -EINVAL)
goto exit;
req->test_audio_period_ch_2 = ret;
drm_dbg_dp(link<