Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 49d57d9b authored by Linux Build Service Account's avatar Linux Build Service Account Committed by Gerrit - the friendly Code Review server
Browse files

Merge "drm/msm: add hdmi audio support for sde kms"

parents 690cee75 51766baf
Loading
Loading
Loading
Loading
+3 −1
Original line number Diff line number Diff line
@@ -98,7 +98,9 @@ msm_drm-$(CONFIG_DRM_MSM_DSI_STAGING) += dsi-staging/dsi_phy.o \
				dsi-staging/dsi_display_test.o

msm_drm-$(CONFIG_DRM_SDE_HDMI) += \
	hdmi-staging/sde_hdmi.o
	hdmi-staging/sde_hdmi.o \
	hdmi-staging/sde_hdmi_bridge.o \
	hdmi-staging/sde_hdmi_audio.o

msm_drm-$(CONFIG_DRM_MSM_DSI_PLL) += dsi/pll/dsi_pll.o \
				dsi/pll/dsi_pll_28nm.o
+30 −4
Original line number Diff line number Diff line
@@ -325,9 +325,9 @@ static int _sde_hdmi_hpd_enable(struct sde_hdmi *sde_hdmi)
		}
	}

	hdmi_set_mode(hdmi, false);
	sde_hdmi_set_mode(hdmi, false);
	_sde_hdmi_phy_reset(hdmi);
	hdmi_set_mode(hdmi, true);
	sde_hdmi_set_mode(hdmi, true);

	hdmi_write(hdmi, REG_HDMI_USEC_REFTIMER, 0x0001001b);

@@ -367,7 +367,7 @@ static void _sde_hdmi_hdp_disable(struct sde_hdmi *sde_hdmi)
	/* Disable HPD interrupt */
	hdmi_write(hdmi, REG_HDMI_HPD_INT_CTRL, 0);

	hdmi_set_mode(hdmi, false);
	sde_hdmi_set_mode(hdmi, false);

	for (i = 0; i < config->hpd_clk_cnt; i++)
		clk_disable_unprepare(hdmi->hpd_clks[i]);
@@ -460,6 +460,32 @@ static irqreturn_t _sde_hdmi_irq(int irq, void *dev_id)
	return IRQ_HANDLED;
}

void sde_hdmi_set_mode(struct hdmi *hdmi, bool power_on)
{
	uint32_t ctrl = 0;
	unsigned long flags;

	spin_lock_irqsave(&hdmi->reg_lock, flags);
	ctrl = hdmi_read(hdmi, REG_HDMI_CTRL);
	if (power_on) {
		ctrl |= HDMI_CTRL_ENABLE;
		if (!hdmi->hdmi_mode) {
			ctrl |= HDMI_CTRL_HDMI;
			hdmi_write(hdmi, REG_HDMI_CTRL, ctrl);
			ctrl &= ~HDMI_CTRL_HDMI;
		} else {
			ctrl |= HDMI_CTRL_HDMI;
		}
	} else {
		ctrl &= ~HDMI_CTRL_HDMI;
	}

	hdmi_write(hdmi, REG_HDMI_CTRL, ctrl);
	spin_unlock_irqrestore(&hdmi->reg_lock, flags);
	DRM_DEBUG("HDMI Core: %s, HDMI_CTRL=0x%08x\n",
			power_on ? "Enable" : "Disable", ctrl);
}

int sde_hdmi_get_info(struct msm_display_info *info,
				void *display)
{
@@ -1087,7 +1113,7 @@ int sde_hdmi_drm_init(struct sde_hdmi *display, struct drm_encoder *enc)

	hdmi_audio_infoframe_init(&hdmi->audio.infoframe);

	hdmi->bridge = hdmi_bridge_init(hdmi);
	hdmi->bridge = sde_hdmi_bridge_init(hdmi);
	if (IS_ERR(hdmi->bridge)) {
		rc = PTR_ERR(hdmi->bridge);
		SDE_ERROR("failed to create HDMI bridge: %d\n", rc);
+44 −0
Original line number Diff line number Diff line
@@ -19,6 +19,7 @@
#include <linux/bitops.h>
#include <linux/debugfs.h>
#include <linux/of_device.h>
#include <linux/msm_ext_display.h>

#include <drm/drmP.h>
#include <drm/drm_crtc.h>
@@ -224,6 +225,49 @@ int sde_hdmi_drm_deinit(struct sde_hdmi *display);
int sde_hdmi_get_info(struct msm_display_info *info,
				void *display);

/**
 * sde_hdmi_bridge_init() - init sde hdmi bridge
 * @hdmi:          Handle to the hdmi.
 *
 * Return: struct drm_bridge *.
 */
struct drm_bridge *sde_hdmi_bridge_init(struct hdmi *hdmi);

/**
 * sde_hdmi_set_mode() - Set HDMI mode API.
 * @hdmi:          Handle to the hdmi.
 * @power_on:      Power on/off request.
 *
 * Return: void.
 */
void sde_hdmi_set_mode(struct hdmi *hdmi, bool power_on);

/**
 * sde_hdmi_audio_on() - enable hdmi audio.
 * @hdmi:          Handle to the hdmi.
 * @params:        audio setup parameters from codec.
 *
 * Return: error code.
 */
int sde_hdmi_audio_on(struct hdmi *hdmi,
	struct msm_ext_disp_audio_setup_params *params);

/**
 * sde_hdmi_audio_off() - disable hdmi audio.
 * @hdmi:          Handle to the hdmi.
 *
 * Return: void.
 */
void sde_hdmi_audio_off(struct hdmi *hdmi);

/**
 * sde_hdmi_config_avmute() - mute hdmi.
 * @hdmi:          Handle to the hdmi.
 * @set:           enable/disable avmute.
 *
 * Return: error code.
 */
int sde_hdmi_config_avmute(struct hdmi *hdmi, bool set);
#else /*#ifdef CONFIG_DRM_SDE_HDMI*/

static inline u32 sde_hdmi_get_num_of_displays(void)
+393 −0
Original line number Diff line number Diff line
/* Copyright (c) 2017, The Linux Foundation. All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 and
 * only version 2 as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 */

#include <linux/slab.h>
#include <linux/bitops.h>
#include <linux/delay.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/iopoll.h>
#include <linux/types.h>
#include <linux/switch.h>
#include <linux/gcd.h>

#include "drm_edid.h"
#include "sde_kms.h"
#include "sde_hdmi.h"
#include "sde_hdmi_regs.h"
#include "hdmi.h"

#define HDMI_AUDIO_INFO_FRAME_PACKET_HEADER 0x84
#define HDMI_AUDIO_INFO_FRAME_PACKET_VERSION 0x1
#define HDMI_AUDIO_INFO_FRAME_PACKET_LENGTH 0x0A

#define HDMI_KHZ_TO_HZ 1000
#define HDMI_MHZ_TO_HZ 1000000
#define HDMI_ACR_N_MULTIPLIER 128
#define DEFAULT_AUDIO_SAMPLE_RATE_HZ 48000

/* Supported HDMI Audio channels */
enum hdmi_audio_channels {
	AUDIO_CHANNEL_2 = 2,
	AUDIO_CHANNEL_3,
	AUDIO_CHANNEL_4,
	AUDIO_CHANNEL_5,
	AUDIO_CHANNEL_6,
	AUDIO_CHANNEL_7,
	AUDIO_CHANNEL_8,
};

/* parameters for clock regeneration */
struct hdmi_audio_acr {
	u32 n;
	u32 cts;
};

enum hdmi_audio_sample_rates {
	AUDIO_SAMPLE_RATE_32KHZ,
	AUDIO_SAMPLE_RATE_44_1KHZ,
	AUDIO_SAMPLE_RATE_48KHZ,
	AUDIO_SAMPLE_RATE_88_2KHZ,
	AUDIO_SAMPLE_RATE_96KHZ,
	AUDIO_SAMPLE_RATE_176_4KHZ,
	AUDIO_SAMPLE_RATE_192KHZ,
	AUDIO_SAMPLE_RATE_MAX
};

struct sde_hdmi_audio {
	struct hdmi *hdmi;
	struct msm_ext_disp_audio_setup_params params;
	u32 pclk;
};

static void _sde_hdmi_audio_get_audio_sample_rate(u32 *sample_rate_hz)
{
	u32 rate = *sample_rate_hz;

	switch (rate) {
	case 32000:
		*sample_rate_hz = AUDIO_SAMPLE_RATE_32KHZ;
		break;
	case 44100:
		*sample_rate_hz = AUDIO_SAMPLE_RATE_44_1KHZ;
		break;
	case 48000:
		*sample_rate_hz = AUDIO_SAMPLE_RATE_48KHZ;
		break;
	case 88200:
		*sample_rate_hz = AUDIO_SAMPLE_RATE_88_2KHZ;
		break;
	case 96000:
		*sample_rate_hz = AUDIO_SAMPLE_RATE_96KHZ;
		break;
	case 176400:
		*sample_rate_hz = AUDIO_SAMPLE_RATE_176_4KHZ;
		break;
	case 192000:
		*sample_rate_hz = AUDIO_SAMPLE_RATE_192KHZ;
		break;
	default:
		SDE_ERROR("%d unchanged\n", rate);
		break;
	}
}

static void _sde_hdmi_audio_get_acr_param(u32 pclk, u32 fs,
	struct hdmi_audio_acr *acr)
{
	u32 div, mul;

	if (!acr) {
		SDE_ERROR("invalid data\n");
		return;
	}

	/*
	 * as per HDMI specification, N/CTS = (128*fs)/pclk.
	 * get the ratio using this formula.
	 */
	acr->n = HDMI_ACR_N_MULTIPLIER * fs;
	acr->cts = pclk;

	/* get the greatest common divisor for the ratio */
	div = gcd(acr->n, acr->cts);

	/* get the n and cts values wrt N/CTS formula */
	acr->n /= div;
	acr->cts /= div;

	/*
	 * as per HDMI specification, 300 <= 128*fs/N <= 1500
	 * with a target of 128*fs/N = 1000. To get closest
	 * value without truncating fractional values, find
	 * the corresponding multiplier
	 */
	mul = ((HDMI_ACR_N_MULTIPLIER * fs / HDMI_KHZ_TO_HZ)
		+ (acr->n - 1)) / acr->n;

	acr->n *= mul;
	acr->cts *= mul;
}

static void _sde_hdmi_audio_acr_enable(struct sde_hdmi_audio *audio)
{
	struct hdmi_audio_acr acr;
	struct msm_ext_disp_audio_setup_params *params;
	u32 pclk, layout, multiplier = 1, sample_rate;
	u32 acr_pkt_ctl, aud_pkt_ctl2, acr_reg_cts, acr_reg_n;
	struct hdmi *hdmi;

	hdmi = audio->hdmi;
	params = &audio->params;
	pclk = audio->pclk;
	sample_rate = params->sample_rate_hz;

	_sde_hdmi_audio_get_acr_param(pclk, sample_rate, &acr);
	_sde_hdmi_audio_get_audio_sample_rate(&sample_rate);

	layout = (params->num_of_channels == AUDIO_CHANNEL_2) ? 0 : 1;

	SDE_DEBUG("n=%u, cts=%u, layout=%u\n", acr.n, acr.cts, layout);

	/* AUDIO_PRIORITY | SOURCE */
	acr_pkt_ctl = BIT(31) | BIT(8);

	switch (sample_rate) {
	case AUDIO_SAMPLE_RATE_44_1KHZ:
		acr_pkt_ctl |= 0x2 << 4;
		acr.cts <<= 12;

		acr_reg_cts = HDMI_ACR_44_0;
		acr_reg_n = HDMI_ACR_44_1;
		break;
	case AUDIO_SAMPLE_RATE_48KHZ:
		acr_pkt_ctl |= 0x3 << 4;
		acr.cts <<= 12;

		acr_reg_cts = HDMI_ACR_48_0;
		acr_reg_n = HDMI_ACR_48_1;
		break;
	case AUDIO_SAMPLE_RATE_192KHZ:
		multiplier = 4;
		acr.n >>= 2;

		acr_pkt_ctl |= 0x3 << 4;
		acr.cts <<= 12;

		acr_reg_cts = HDMI_ACR_48_0;
		acr_reg_n = HDMI_ACR_48_1;
		break;
	case AUDIO_SAMPLE_RATE_176_4KHZ:
		multiplier = 4;
		acr.n >>= 2;

		acr_pkt_ctl |= 0x2 << 4;
		acr.cts <<= 12;

		acr_reg_cts = HDMI_ACR_44_0;
		acr_reg_n = HDMI_ACR_44_1;
		break;
	case AUDIO_SAMPLE_RATE_96KHZ:
		multiplier = 2;
		acr.n >>= 1;

		acr_pkt_ctl |= 0x3 << 4;
		acr.cts <<= 12;

		acr_reg_cts = HDMI_ACR_48_0;
		acr_reg_n = HDMI_ACR_48_1;
		break;
	case AUDIO_SAMPLE_RATE_88_2KHZ:
		multiplier = 2;
		acr.n >>= 1;

		acr_pkt_ctl |= 0x2 << 4;
		acr.cts <<= 12;

		acr_reg_cts = HDMI_ACR_44_0;
		acr_reg_n = HDMI_ACR_44_1;
		break;
	default:
		multiplier = 1;

		acr_pkt_ctl |= 0x1 << 4;
		acr.cts <<= 12;

		acr_reg_cts = HDMI_ACR_32_0;
		acr_reg_n = HDMI_ACR_32_1;
		break;
	}

	aud_pkt_ctl2 = BIT(0) | (layout << 1);

	/* N_MULTIPLE(multiplier) */
	acr_pkt_ctl &= ~(7 << 16);
	acr_pkt_ctl |= (multiplier & 0x7) << 16;

	/* SEND | CONT */
	acr_pkt_ctl |= BIT(0) | BIT(1);

	hdmi_write(hdmi, acr_reg_cts, acr.cts);
	hdmi_write(hdmi, acr_reg_n, acr.n);
	hdmi_write(hdmi, HDMI_ACR_PKT_CTRL, acr_pkt_ctl);
	hdmi_write(hdmi, HDMI_AUDIO_PKT_CTRL2, aud_pkt_ctl2);
}

static void _sde_hdmi_audio_acr_setup(struct sde_hdmi_audio *audio, bool on)
{
	if (on)
		_sde_hdmi_audio_acr_enable(audio);
	else
		hdmi_write(audio->hdmi, HDMI_ACR_PKT_CTRL, 0);
}

static void _sde_hdmi_audio_infoframe_setup(struct sde_hdmi_audio *audio,
	bool enabled)
{
	struct hdmi *hdmi = audio->hdmi;
	u32 channels, channel_allocation, level_shift, down_mix, layout;
	u32 hdmi_debug_reg = 0, audio_info_0_reg = 0, audio_info_1_reg = 0;
	u32 audio_info_ctrl_reg, aud_pck_ctrl_2_reg;
	u32 check_sum, sample_present;

	audio_info_ctrl_reg = hdmi_read(hdmi, HDMI_INFOFRAME_CTRL0);
	audio_info_ctrl_reg &= ~0xF0;

	if (!enabled)
		goto end;

	channels           = audio->params.num_of_channels - 1;
	channel_allocation = audio->params.channel_allocation;
	level_shift        = audio->params.level_shift;
	down_mix           = audio->params.down_mix;
	sample_present     = audio->params.sample_present;

	layout = (audio->params.num_of_channels == AUDIO_CHANNEL_2) ? 0 : 1;
	aud_pck_ctrl_2_reg = BIT(0) | (layout << 1);
	hdmi_write(hdmi, HDMI_AUDIO_PKT_CTRL2, aud_pck_ctrl_2_reg);

	audio_info_1_reg |= channel_allocation & 0xFF;
	audio_info_1_reg |= ((level_shift & 0xF) << 11);
	audio_info_1_reg |= ((down_mix & 0x1) << 15);

	check_sum = 0;
	check_sum += HDMI_AUDIO_INFO_FRAME_PACKET_HEADER;
	check_sum += HDMI_AUDIO_INFO_FRAME_PACKET_VERSION;
	check_sum += HDMI_AUDIO_INFO_FRAME_PACKET_LENGTH;
	check_sum += channels;
	check_sum += channel_allocation;
	check_sum += (level_shift & 0xF) << 3 | (down_mix & 0x1) << 7;
	check_sum &= 0xFF;
	check_sum = (u8) (256 - check_sum);

	audio_info_0_reg |= check_sum & 0xFF;
	audio_info_0_reg |= ((channels & 0x7) << 8);

	/* Enable Audio InfoFrame Transmission */
	audio_info_ctrl_reg |= 0xF0;

	if (layout) {
		/* Set the Layout bit */
		hdmi_debug_reg |= BIT(4);

		/* Set the Sample Present bits */
		hdmi_debug_reg |= sample_present & 0xF;
	}
end:
	hdmi_write(hdmi, HDMI_DEBUG, hdmi_debug_reg);
	hdmi_write(hdmi, HDMI_AUDIO_INFO0, audio_info_0_reg);
	hdmi_write(hdmi, HDMI_AUDIO_INFO1, audio_info_1_reg);
	hdmi_write(hdmi, HDMI_INFOFRAME_CTRL0, audio_info_ctrl_reg);
}

int sde_hdmi_audio_on(struct hdmi *hdmi,
	struct msm_ext_disp_audio_setup_params *params)
{
	struct sde_hdmi_audio audio;
	int rc = 0;

	if (!hdmi) {
		SDE_ERROR("invalid HDMI Ctrl\n");
		rc = -ENODEV;
		goto end;
	}

	audio.pclk = hdmi->pixclock;
	audio.params = *params;
	audio.hdmi = hdmi;

	if (!audio.params.num_of_channels) {
		audio.params.sample_rate_hz = DEFAULT_AUDIO_SAMPLE_RATE_HZ;
		audio.params.num_of_channels = AUDIO_CHANNEL_2;
	}

	_sde_hdmi_audio_acr_setup(&audio, true);
	_sde_hdmi_audio_infoframe_setup(&audio, true);

	SDE_DEBUG("HDMI Audio: Enabled\n");
end:
	return rc;
}

void sde_hdmi_audio_off(struct hdmi *hdmi)
{
	struct sde_hdmi_audio audio;
	int rc = 0;

	if (!hdmi) {
		SDE_ERROR("invalid HDMI Ctrl\n");
		rc = -ENODEV;
		return;
	}

	audio.hdmi = hdmi;

	_sde_hdmi_audio_infoframe_setup(&audio, false);
	_sde_hdmi_audio_acr_setup(&audio, false);

	SDE_DEBUG("HDMI Audio: Disabled\n");
}

int sde_hdmi_config_avmute(struct hdmi *hdmi, bool set)
{
	u32 av_mute_status;
	bool av_pkt_en = false;

	if (!hdmi) {
		SDE_ERROR("invalid HDMI Ctrl\n");
		return -ENODEV;
	}

	av_mute_status = hdmi_read(hdmi, HDMI_GC);

	if (set) {
		if (!(av_mute_status & BIT(0))) {
			hdmi_write(hdmi, HDMI_GC, av_mute_status | BIT(0));
			av_pkt_en = true;
		}
	} else {
		if (av_mute_status & BIT(0)) {
			hdmi_write(hdmi, HDMI_GC, av_mute_status & ~BIT(0));
			av_pkt_en = true;
		}
	}

	/* Enable AV Mute tranmission here */
	if (av_pkt_en)
		hdmi_write(hdmi, HDMI_VBI_PKT_CTRL,
			hdmi_read(hdmi, HDMI_VBI_PKT_CTRL) | (BIT(4) & BIT(5)));

	SDE_DEBUG("AVMUTE %s\n", set ? "set" : "cleared");

	return 0;
}
+388 −0
Original line number Diff line number Diff line
/*
 * Copyright (c) 2016-2017, The Linux Foundation. All rights reserved.
 * Copyright (C) 2013 Red Hat
 * Author: Rob Clark <robdclark@gmail.com>
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 as published by
 * the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 * more details.
 *
 * You should have received a copy of the GNU General Public License along with
 * this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "drm_edid.h"
#include "sde_kms.h"
#include "sde_hdmi.h"
#include "hdmi.h"

struct sde_hdmi_bridge {
	struct drm_bridge base;
	struct hdmi *hdmi;
};
#define to_hdmi_bridge(x) container_of(x, struct sde_hdmi_bridge, base)

/* for AVI program */
#define HDMI_AVI_INFOFRAME_BUFFER_SIZE \
	(HDMI_INFOFRAME_HEADER_SIZE + HDMI_AVI_INFOFRAME_SIZE)
#define HDMI_VS_INFOFRAME_BUFFER_SIZE (HDMI_INFOFRAME_HEADER_SIZE + 6)
#define HDMI_SPD_INFOFRAME_BUFFER_SIZE \
	(HDMI_INFOFRAME_HEADER_SIZE + HDMI_SPD_INFOFRAME_SIZE)
#define HDMI_DEFAULT_VENDOR_NAME "unknown"
#define HDMI_DEFAULT_PRODUCT_NAME "msm"
#define LEFT_SHIFT_BYTE(x) ((x) << 8)
#define LEFT_SHIFT_WORD(x) ((x) << 16)
#define LEFT_SHIFT_24BITS(x) ((x) << 24)
#define HDMI_AVI_IFRAME_LINE_NUMBER 1
#define HDMI_VENDOR_IFRAME_LINE_NUMBER 3

void _sde_hdmi_bridge_destroy(struct drm_bridge *bridge)
{
}

static void _sde_hdmi_bridge_power_on(struct drm_bridge *bridge)
{
	struct sde_hdmi_bridge *sde_hdmi_bridge = to_hdmi_bridge(bridge);
	struct hdmi *hdmi = sde_hdmi_bridge->hdmi;
	const struct hdmi_platform_config *config = hdmi->config;
	int i, ret;

	for (i = 0; i < config->pwr_reg_cnt; i++) {
		ret = regulator_enable(hdmi->pwr_regs[i]);
		if (ret) {
			SDE_ERROR("failed to enable pwr regulator: %s (%d)\n",
					config->pwr_reg_names[i], ret);
		}
	}

	if (config->pwr_clk_cnt > 0) {
		DRM_DEBUG("pixclock: %lu", hdmi->pixclock);
		ret = clk_set_rate(hdmi->pwr_clks[0], hdmi->pixclock);
		if (ret) {
			SDE_ERROR("failed to set pixel clk: %s (%d)\n",
					config->pwr_clk_names[0], ret);
		}
	}

	for (i = 0; i < config->pwr_clk_cnt; i++) {
		ret = clk_prepare_enable(hdmi->pwr_clks[i]);
		if (ret) {
			SDE_ERROR("failed to enable pwr clk: %s (%d)\n",
					config->pwr_clk_names[i], ret);
		}
	}
}

static void _sde_hdmi_bridge_power_off(struct drm_bridge *bridge)
{
	struct sde_hdmi_bridge *sde_hdmi_bridge = to_hdmi_bridge(bridge);
	struct hdmi *hdmi = sde_hdmi_bridge->hdmi;
	const struct hdmi_platform_config *config = hdmi->config;
	int i, ret;

	/* Wait for vsync */
	msleep(20);

	for (i = 0; i < config->pwr_clk_cnt; i++)
		clk_disable_unprepare(hdmi->pwr_clks[i]);

	for (i = 0; i < config->pwr_reg_cnt; i++) {
		ret = regulator_disable(hdmi->pwr_regs[i]);
		if (ret) {
			SDE_ERROR("failed to disable pwr regulator: %s (%d)\n",
					config->pwr_reg_names[i], ret);
		}
	}
}

static void _sde_hdmi_bridge_pre_enable(struct drm_bridge *bridge)
{
	struct sde_hdmi_bridge *sde_hdmi_bridge = to_hdmi_bridge(bridge);
	struct hdmi *hdmi = sde_hdmi_bridge->hdmi;
	struct hdmi_phy *phy = hdmi->phy;

	DRM_DEBUG("power up");

	if (!hdmi->power_on) {
		_sde_hdmi_bridge_power_on(bridge);
		hdmi->power_on = true;
		hdmi_audio_update(hdmi);
	}

	if (phy)
		phy->funcs->powerup(phy, hdmi->pixclock);

	sde_hdmi_set_mode(hdmi, true);

	if (hdmi->hdcp_ctrl && hdmi->is_hdcp_supported)
		hdmi_hdcp_ctrl_on(hdmi->hdcp_ctrl);
}

static void _sde_hdmi_bridge_enable(struct drm_bridge *bridge)
{
}

static void _sde_hdmi_bridge_disable(struct drm_bridge *bridge)
{
}

static void _sde_hdmi_bridge_post_disable(struct drm_bridge *bridge)
{
	struct sde_hdmi_bridge *sde_hdmi_bridge = to_hdmi_bridge(bridge);
	struct hdmi *hdmi = sde_hdmi_bridge->hdmi;
	struct hdmi_phy *phy = hdmi->phy;

	if (hdmi->hdcp_ctrl && hdmi->is_hdcp_supported)
		hdmi_hdcp_ctrl_off(hdmi->hdcp_ctrl);

	DRM_DEBUG("power down");
	sde_hdmi_set_mode(hdmi, false);

	if (phy)
		phy->funcs->powerdown(phy);

	if (hdmi->power_on) {
		_sde_hdmi_bridge_power_off(bridge);
		hdmi->power_on = false;
		hdmi_audio_update(hdmi);
	}
}

static void _sde_hdmi_bridge_set_avi_infoframe(struct hdmi *hdmi,
	const struct drm_display_mode *mode)
{
	u8 avi_iframe[HDMI_AVI_INFOFRAME_BUFFER_SIZE] = {0};
	u8 *avi_frame = &avi_iframe[HDMI_INFOFRAME_HEADER_SIZE];
	u8 checksum;
	u32 reg_val;
	struct hdmi_avi_infoframe info;

	drm_hdmi_avi_infoframe_from_display_mode(&info, mode);
	hdmi_avi_infoframe_pack(&info, avi_iframe, sizeof(avi_iframe));
	checksum = avi_iframe[HDMI_INFOFRAME_HEADER_SIZE - 1];

	reg_val = checksum |
		LEFT_SHIFT_BYTE(avi_frame[0]) |
		LEFT_SHIFT_WORD(avi_frame[1]) |
		LEFT_SHIFT_24BITS(avi_frame[2]);
	hdmi_write(hdmi, REG_HDMI_AVI_INFO(0), reg_val);

	reg_val = avi_frame[3] |
		LEFT_SHIFT_BYTE(avi_frame[4]) |
		LEFT_SHIFT_WORD(avi_frame[5]) |
		LEFT_SHIFT_24BITS(avi_frame[6]);
	hdmi_write(hdmi, REG_HDMI_AVI_INFO(1), reg_val);

	reg_val = avi_frame[7] |
		LEFT_SHIFT_BYTE(avi_frame[8]) |
		LEFT_SHIFT_WORD(avi_frame[9]) |
		LEFT_SHIFT_24BITS(avi_frame[10]);
	hdmi_write(hdmi, REG_HDMI_AVI_INFO(2), reg_val);

	reg_val = avi_frame[11] |
		LEFT_SHIFT_BYTE(avi_frame[12]) |
		LEFT_SHIFT_24BITS(avi_iframe[1]);
	hdmi_write(hdmi, REG_HDMI_AVI_INFO(3), reg_val);

	/* AVI InfFrame enable (every frame) */
	hdmi_write(hdmi, REG_HDMI_INFOFRAME_CTRL0,
		hdmi_read(hdmi, REG_HDMI_INFOFRAME_CTRL0) | BIT(1) | BIT(0));

	reg_val = hdmi_read(hdmi, REG_HDMI_INFOFRAME_CTRL1);
	reg_val &= ~0x3F;
	reg_val |= HDMI_AVI_IFRAME_LINE_NUMBER;
	hdmi_write(hdmi, REG_HDMI_INFOFRAME_CTRL1, reg_val);
}

static void _sde_hdmi_bridge_set_vs_infoframe(struct hdmi *hdmi,
	const struct drm_display_mode *mode)
{
	u8 vs_iframe[HDMI_VS_INFOFRAME_BUFFER_SIZE] = {0};
	u32 reg_val;
	struct hdmi_vendor_infoframe info;
	int rc = 0;

	rc = drm_hdmi_vendor_infoframe_from_display_mode(&info, mode);
	if (rc < 0) {
		SDE_DEBUG("don't send vendor infoframe\n");
		return;
	}
	hdmi_vendor_infoframe_pack(&info, vs_iframe, sizeof(vs_iframe));

	reg_val = (info.s3d_struct << 24) | (info.vic << 16) |
			(vs_iframe[3] << 8) | (vs_iframe[7] << 5) |
			vs_iframe[2];
	hdmi_write(hdmi, REG_HDMI_VENSPEC_INFO0, reg_val);

	/* vendor specific info-frame enable (every frame) */
	hdmi_write(hdmi, REG_HDMI_INFOFRAME_CTRL0,
		hdmi_read(hdmi, REG_HDMI_INFOFRAME_CTRL0) | BIT(13) | BIT(12));

	reg_val = hdmi_read(hdmi, REG_HDMI_INFOFRAME_CTRL1);
	reg_val &= ~0x3F000000;
	reg_val |= (HDMI_VENDOR_IFRAME_LINE_NUMBER << 24);
	hdmi_write(hdmi, REG_HDMI_INFOFRAME_CTRL1, reg_val);
}

static void _sde_hdmi_bridge_set_spd_infoframe(struct hdmi *hdmi,
	const struct drm_display_mode *mode)
{
	u8 spd_iframe[HDMI_SPD_INFOFRAME_BUFFER_SIZE] = {0};
	u32 packet_payload, packet_control, packet_header;
	struct hdmi_spd_infoframe info;
	int i;

	/* Need to query vendor and product name from platform setup */
	hdmi_spd_infoframe_init(&info, HDMI_DEFAULT_VENDOR_NAME,
		HDMI_DEFAULT_PRODUCT_NAME);
	hdmi_spd_infoframe_pack(&info, spd_iframe, sizeof(spd_iframe));

	packet_header = spd_iframe[0]
			| LEFT_SHIFT_BYTE(spd_iframe[1] & 0x7f)
			| LEFT_SHIFT_WORD(spd_iframe[2] & 0x7f);
	hdmi_write(hdmi, REG_HDMI_GENERIC1_HDR, packet_header);

	for (i = 0; i < MAX_REG_HDMI_GENERIC1_INDEX; i++) {
		packet_payload = spd_iframe[3 + i * 4]
			| LEFT_SHIFT_BYTE(spd_iframe[4 + i * 4] & 0x7f)
			| LEFT_SHIFT_WORD(spd_iframe[5 + i * 4] & 0x7f)
			| LEFT_SHIFT_24BITS(spd_iframe[6 + i * 4] & 0x7f);
		hdmi_write(hdmi, REG_HDMI_GENERIC1(i), packet_payload);
	}

	packet_payload = (spd_iframe[27] & 0x7f)
			| LEFT_SHIFT_BYTE(spd_iframe[28] & 0x7f);
	hdmi_write(hdmi, REG_HDMI_GENERIC1(MAX_REG_HDMI_GENERIC1_INDEX),
		packet_payload);

	/*
	 * GENERIC1_LINE | GENERIC1_CONT | GENERIC1_SEND
	 * Setup HDMI TX generic packet control
	 * Enable this packet to transmit every frame
	 * Enable HDMI TX engine to transmit Generic packet 1
	 */
	packet_control = hdmi_read(hdmi, REG_HDMI_GEN_PKT_CTRL);
	packet_control |= ((0x1 << 24) | (1 << 5) | (1 << 4));
	hdmi_write(hdmi, REG_HDMI_GEN_PKT_CTRL, packet_control);
}

static void _sde_hdmi_bridge_mode_set(struct drm_bridge *bridge,
		 struct drm_display_mode *mode,
		 struct drm_display_mode *adjusted_mode)
{
	struct sde_hdmi_bridge *sde_hdmi_bridge = to_hdmi_bridge(bridge);
	struct hdmi *hdmi = sde_hdmi_bridge->hdmi;
	int hstart, hend, vstart, vend;
	uint32_t frame_ctrl;

	mode = adjusted_mode;

	hdmi->pixclock = mode->clock * 1000;

	hstart = mode->htotal - mode->hsync_start;
	hend   = mode->htotal - mode->hsync_start + mode->hdisplay;

	vstart = mode->vtotal - mode->vsync_start - 1;
	vend   = mode->vtotal - mode->vsync_start + mode->vdisplay - 1;

	DRM_DEBUG(
		"htotal=%d, vtotal=%d, hstart=%d, hend=%d, vstart=%d, vend=%d",
		mode->htotal, mode->vtotal, hstart, hend, vstart, vend);

	hdmi_write(hdmi, REG_HDMI_TOTAL,
			HDMI_TOTAL_H_TOTAL(mode->htotal - 1) |
			HDMI_TOTAL_V_TOTAL(mode->vtotal - 1));

	hdmi_write(hdmi, REG_HDMI_ACTIVE_HSYNC,
			HDMI_ACTIVE_HSYNC_START(hstart) |
			HDMI_ACTIVE_HSYNC_END(hend));
	hdmi_write(hdmi, REG_HDMI_ACTIVE_VSYNC,
			HDMI_ACTIVE_VSYNC_START(vstart) |
			HDMI_ACTIVE_VSYNC_END(vend));

	if (mode->flags & DRM_MODE_FLAG_INTERLACE) {
		hdmi_write(hdmi, REG_HDMI_VSYNC_TOTAL_F2,
				HDMI_VSYNC_TOTAL_F2_V_TOTAL(mode->vtotal));
		hdmi_write(hdmi, REG_HDMI_VSYNC_ACTIVE_F2,
				HDMI_VSYNC_ACTIVE_F2_START(vstart + 1) |
				HDMI_VSYNC_ACTIVE_F2_END(vend + 1));
	} else {
		hdmi_write(hdmi, REG_HDMI_VSYNC_TOTAL_F2,
				HDMI_VSYNC_TOTAL_F2_V_TOTAL(0));
		hdmi_write(hdmi, REG_HDMI_VSYNC_ACTIVE_F2,
				HDMI_VSYNC_ACTIVE_F2_START(0) |
				HDMI_VSYNC_ACTIVE_F2_END(0));
	}

	frame_ctrl = 0;
	if (mode->flags & DRM_MODE_FLAG_NHSYNC)
		frame_ctrl |= HDMI_FRAME_CTRL_HSYNC_LOW;
	if (mode->flags & DRM_MODE_FLAG_NVSYNC)
		frame_ctrl |= HDMI_FRAME_CTRL_VSYNC_LOW;
	if (mode->flags & DRM_MODE_FLAG_INTERLACE)
		frame_ctrl |= HDMI_FRAME_CTRL_INTERLACED_EN;
	DRM_DEBUG("frame_ctrl=%08x\n", frame_ctrl);
	hdmi_write(hdmi, REG_HDMI_FRAME_CTRL, frame_ctrl);

	/*
	 * Setup info frame
	 * Current drm_edid driver doesn't have all CEA formats defined in
	 * latest CEA-861(CTA-861) spec. So, don't check if mode is CEA mode
	 * in here. Once core framework is updated, the check needs to be
	 * added back.
	 */
	if (hdmi->hdmi_mode) {
		_sde_hdmi_bridge_set_avi_infoframe(hdmi, mode);
		_sde_hdmi_bridge_set_vs_infoframe(hdmi, mode);
		_sde_hdmi_bridge_set_spd_infoframe(hdmi, mode);
		DRM_DEBUG("hdmi setup info frame\n");
	}

	hdmi_audio_update(hdmi);
}

static const struct drm_bridge_funcs _sde_hdmi_bridge_funcs = {
		.pre_enable = _sde_hdmi_bridge_pre_enable,
		.enable = _sde_hdmi_bridge_enable,
		.disable = _sde_hdmi_bridge_disable,
		.post_disable = _sde_hdmi_bridge_post_disable,
		.mode_set = _sde_hdmi_bridge_mode_set,
};


/* initialize bridge */
struct drm_bridge *sde_hdmi_bridge_init(struct hdmi *hdmi)
{
	struct drm_bridge *bridge = NULL;
	struct sde_hdmi_bridge *sde_hdmi_bridge;
	int ret;

	sde_hdmi_bridge = devm_kzalloc(hdmi->dev->dev,
			sizeof(*sde_hdmi_bridge), GFP_KERNEL);
	if (!sde_hdmi_bridge) {
		ret = -ENOMEM;
		goto fail;
	}

	sde_hdmi_bridge->hdmi = hdmi;

	bridge = &sde_hdmi_bridge->base;
	bridge->funcs = &_sde_hdmi_bridge_funcs;

	ret = drm_bridge_attach(hdmi->dev, bridge);
	if (ret)
		goto fail;

	return bridge;

fail:
	if (bridge)
		_sde_hdmi_bridge_destroy(bridge);

	return ERR_PTR(ret);
}
Loading