diff options
Diffstat (limited to 'drivers/gpu/drm/tegra/hdmi.c')
| -rw-r--r-- | drivers/gpu/drm/tegra/hdmi.c | 222 | 
1 files changed, 68 insertions, 154 deletions
| diff --git a/drivers/gpu/drm/tegra/hdmi.c b/drivers/gpu/drm/tegra/hdmi.c index 0082468f703c..47c55974756d 100644 --- a/drivers/gpu/drm/tegra/hdmi.c +++ b/drivers/gpu/drm/tegra/hdmi.c @@ -11,6 +11,7 @@  #include <linux/debugfs.h>  #include <linux/gpio.h>  #include <linux/hdmi.h> +#include <linux/math64.h>  #include <linux/of_device.h>  #include <linux/pm_runtime.h>  #include <linux/regulator/consumer.h> @@ -18,12 +19,9 @@  #include <drm/drm_atomic_helper.h>  #include <drm/drm_crtc.h> -#include <drm/drm_crtc_helper.h> - -#include <sound/hda_verbs.h> - -#include <media/cec-notifier.h> +#include <drm/drm_probe_helper.h> +#include "hda.h"  #include "hdmi.h"  #include "drm.h"  #include "dc.h" @@ -71,8 +69,7 @@ struct tegra_hdmi {  	const struct tegra_hdmi_config *config;  	unsigned int audio_source; -	unsigned int audio_sample_rate; -	unsigned int audio_channels; +	struct tegra_hda_format format;  	unsigned int pixel_clock;  	bool stereo; @@ -119,68 +116,11 @@ static inline void tegra_hdmi_writel(struct tegra_hdmi *hdmi, u32 value,  }  struct tegra_hdmi_audio_config { -	unsigned int pclk;  	unsigned int n;  	unsigned int cts;  	unsigned int aval;  }; -static const struct tegra_hdmi_audio_config tegra_hdmi_audio_32k[] = { -	{  25200000, 4096,  25200, 24000 }, -	{  27000000, 4096,  27000, 24000 }, -	{  74250000, 4096,  74250, 24000 }, -	{ 148500000, 4096, 148500, 24000 }, -	{         0,    0,      0,     0 }, -}; - -static const struct tegra_hdmi_audio_config tegra_hdmi_audio_44_1k[] = { -	{  25200000, 5880,  26250, 25000 }, -	{  27000000, 5880,  28125, 25000 }, -	{  74250000, 4704,  61875, 20000 }, -	{ 148500000, 4704, 123750, 20000 }, -	{         0,    0,      0,     0 }, -}; - -static const struct tegra_hdmi_audio_config tegra_hdmi_audio_48k[] = { -	{  25200000, 6144,  25200, 24000 }, -	{  27000000, 6144,  27000, 24000 }, -	{  74250000, 6144,  74250, 24000 }, -	{ 148500000, 6144, 148500, 24000 }, -	{         0,    0,      0,     0 }, -}; - -static const struct tegra_hdmi_audio_config tegra_hdmi_audio_88_2k[] = { -	{  25200000, 11760,  26250, 25000 }, -	{  27000000, 11760,  28125, 25000 }, -	{  74250000,  9408,  61875, 20000 }, -	{ 148500000,  9408, 123750, 20000 }, -	{         0,     0,      0,     0 }, -}; - -static const struct tegra_hdmi_audio_config tegra_hdmi_audio_96k[] = { -	{  25200000, 12288,  25200, 24000 }, -	{  27000000, 12288,  27000, 24000 }, -	{  74250000, 12288,  74250, 24000 }, -	{ 148500000, 12288, 148500, 24000 }, -	{         0,     0,      0,     0 }, -}; - -static const struct tegra_hdmi_audio_config tegra_hdmi_audio_176_4k[] = { -	{  25200000, 23520,  26250, 25000 }, -	{  27000000, 23520,  28125, 25000 }, -	{  74250000, 18816,  61875, 20000 }, -	{ 148500000, 18816, 123750, 20000 }, -	{         0,     0,      0,     0 }, -}; - -static const struct tegra_hdmi_audio_config tegra_hdmi_audio_192k[] = { -	{  25200000, 24576,  25200, 24000 }, -	{  27000000, 24576,  27000, 24000 }, -	{  74250000, 24576,  74250, 24000 }, -	{ 148500000, 24576, 148500, 24000 }, -	{         0,     0,      0,     0 }, -}; -  static const struct tmds_config tegra20_tmds_config[] = {  	{ /* slow pixel clock modes */  		.pclk = 27000000, @@ -418,52 +358,53 @@ static const struct tmds_config tegra124_tmds_config[] = {  	},  }; -static const struct tegra_hdmi_audio_config * -tegra_hdmi_get_audio_config(unsigned int sample_rate, unsigned int pclk) +static int +tegra_hdmi_get_audio_config(unsigned int audio_freq, unsigned int pix_clock, +			    struct tegra_hdmi_audio_config *config)  { -	const struct tegra_hdmi_audio_config *table; - -	switch (sample_rate) { -	case 32000: -		table = tegra_hdmi_audio_32k; -		break; - -	case 44100: -		table = tegra_hdmi_audio_44_1k; -		break; - -	case 48000: -		table = tegra_hdmi_audio_48k; -		break; - -	case 88200: -		table = tegra_hdmi_audio_88_2k; -		break; - -	case 96000: -		table = tegra_hdmi_audio_96k; -		break; - -	case 176400: -		table = tegra_hdmi_audio_176_4k; -		break; - -	case 192000: -		table = tegra_hdmi_audio_192k; -		break; - -	default: -		return NULL; -	} - -	while (table->pclk) { -		if (table->pclk == pclk) -			return table; - -		table++; +	const unsigned int afreq = 128 * audio_freq; +	const unsigned int min_n = afreq / 1500; +	const unsigned int max_n = afreq / 300; +	const unsigned int ideal_n = afreq / 1000; +	int64_t min_err = (uint64_t)-1 >> 1; +	unsigned int min_delta = -1; +	int n; + +	memset(config, 0, sizeof(*config)); +	config->n = -1; + +	for (n = min_n; n <= max_n; n++) { +		uint64_t cts_f, aval_f; +		unsigned int delta; +		int64_t cts, err; + +		/* compute aval in 48.16 fixed point */ +		aval_f = ((int64_t)24000000 << 16) * n; +		do_div(aval_f, afreq); +		/* It should round without any rest */ +		if (aval_f & 0xFFFF) +			continue; + +		/* Compute cts in 48.16 fixed point */ +		cts_f = ((int64_t)pix_clock << 16) * n; +		do_div(cts_f, afreq); +		/* Round it to the nearest integer */ +		cts = (cts_f & ~0xFFFF) + ((cts_f & BIT(15)) << 1); + +		delta = abs(n - ideal_n); + +		/* Compute the absolute error */ +		err = abs((int64_t)cts_f - cts); +		if (err < min_err || (err == min_err && delta < min_delta)) { +			config->n = n; +			config->cts = cts >> 16; +			config->aval = aval_f >> 16; +			min_delta = delta; +			min_err = err; +		}  	} -	return NULL; +	return config->n != -1 ? 0 : -EINVAL;  }  static void tegra_hdmi_setup_audio_fs_tables(struct tegra_hdmi *hdmi) @@ -510,7 +451,7 @@ static void tegra_hdmi_write_aval(struct tegra_hdmi *hdmi, u32 value)  	unsigned int i;  	for (i = 0; i < ARRAY_SIZE(regs); i++) { -		if (regs[i].sample_rate == hdmi->audio_sample_rate) { +		if (regs[i].sample_rate == hdmi->format.sample_rate) {  			tegra_hdmi_writel(hdmi, value, regs[i].offset);  			break;  		} @@ -519,8 +460,9 @@ static void tegra_hdmi_write_aval(struct tegra_hdmi *hdmi, u32 value)  static int tegra_hdmi_setup_audio(struct tegra_hdmi *hdmi)  { -	const struct tegra_hdmi_audio_config *config; +	struct tegra_hdmi_audio_config config;  	u32 source, value; +	int err;  	switch (hdmi->audio_source) {  	case HDA: @@ -564,7 +506,7 @@ static int tegra_hdmi_setup_audio(struct tegra_hdmi *hdmi)  		 * play back system startup sounds early. It is possibly not  		 * needed on Linux at all.  		 */ -		if (hdmi->audio_channels == 2) +		if (hdmi->format.channels == 2)  			value = SOR_AUDIO_CNTRL0_INJECT_NULLSMPL;  		else  			value = 0; @@ -595,25 +537,28 @@ static int tegra_hdmi_setup_audio(struct tegra_hdmi *hdmi)  		tegra_hdmi_writel(hdmi, value, HDMI_NV_PDISP_SOR_AUDIO_SPARE0);  	} -	config = tegra_hdmi_get_audio_config(hdmi->audio_sample_rate, -					     hdmi->pixel_clock); -	if (!config) { +	err = tegra_hdmi_get_audio_config(hdmi->format.sample_rate, +					  hdmi->pixel_clock, &config); +	if (err < 0) {  		dev_err(hdmi->dev,  			"cannot set audio to %u Hz at %u Hz pixel clock\n", -			hdmi->audio_sample_rate, hdmi->pixel_clock); -		return -EINVAL; +			hdmi->format.sample_rate, hdmi->pixel_clock); +		return err;  	} +	dev_dbg(hdmi->dev, "audio: pixclk=%u, n=%u, cts=%u, aval=%u\n", +		hdmi->pixel_clock, config.n, config.cts, config.aval); +  	tegra_hdmi_writel(hdmi, 0, HDMI_NV_PDISP_HDMI_ACR_CTRL);  	value = AUDIO_N_RESETF | AUDIO_N_GENERATE_ALTERNATE | -		AUDIO_N_VALUE(config->n - 1); +		AUDIO_N_VALUE(config.n - 1);  	tegra_hdmi_writel(hdmi, value, HDMI_NV_PDISP_AUDIO_N); -	tegra_hdmi_writel(hdmi, ACR_SUBPACK_N(config->n) | ACR_ENABLE, +	tegra_hdmi_writel(hdmi, ACR_SUBPACK_N(config.n) | ACR_ENABLE,  			  HDMI_NV_PDISP_HDMI_ACR_0441_SUBPACK_HIGH); -	tegra_hdmi_writel(hdmi, ACR_SUBPACK_CTS(config->cts), +	tegra_hdmi_writel(hdmi, ACR_SUBPACK_CTS(config.cts),  			  HDMI_NV_PDISP_HDMI_ACR_0441_SUBPACK_LOW);  	value = SPARE_HW_CTS | SPARE_FORCE_SW_CTS | SPARE_CTS_RESET_VAL(1); @@ -624,7 +569,7 @@ static int tegra_hdmi_setup_audio(struct tegra_hdmi *hdmi)  	tegra_hdmi_writel(hdmi, value, HDMI_NV_PDISP_AUDIO_N);  	if (hdmi->config->has_hda) -		tegra_hdmi_write_aval(hdmi, config->aval); +		tegra_hdmi_write_aval(hdmi, config.aval);  	tegra_hdmi_setup_audio_fs_tables(hdmi); @@ -741,7 +686,8 @@ static void tegra_hdmi_setup_avi_infoframe(struct tegra_hdmi *hdmi,  	u8 buffer[17];  	ssize_t err; -	err = drm_hdmi_avi_infoframe_from_display_mode(&frame, mode, false); +	err = drm_hdmi_avi_infoframe_from_display_mode(&frame, +						       &hdmi->output.connector, mode);  	if (err < 0) {  		dev_err(hdmi->dev, "failed to setup AVI infoframe: %zd\n", err);  		return; @@ -787,7 +733,7 @@ static void tegra_hdmi_setup_audio_infoframe(struct tegra_hdmi *hdmi)  		return;  	} -	frame.channels = hdmi->audio_channels; +	frame.channels = hdmi->format.channels;  	err = hdmi_audio_infoframe_pack(&frame, buffer, sizeof(buffer));  	if (err < 0) { @@ -1589,24 +1535,6 @@ static const struct of_device_id tegra_hdmi_of_match[] = {  };  MODULE_DEVICE_TABLE(of, tegra_hdmi_of_match); -static void hda_format_parse(unsigned int format, unsigned int *rate, -			     unsigned int *channels) -{ -	unsigned int mul, div; - -	if (format & AC_FMT_BASE_44K) -		*rate = 44100; -	else -		*rate = 48000; - -	mul = (format & AC_FMT_MULT_MASK) >> AC_FMT_MULT_SHIFT; -	div = (format & AC_FMT_DIV_MASK) >> AC_FMT_DIV_SHIFT; - -	*rate = *rate * (mul + 1) / (div + 1); - -	*channels = (format & AC_FMT_CHAN_MASK) >> AC_FMT_CHAN_SHIFT; -} -  static irqreturn_t tegra_hdmi_irq(int irq, void *data)  {  	struct tegra_hdmi *hdmi = data; @@ -1623,14 +1551,9 @@ static irqreturn_t tegra_hdmi_irq(int irq, void *data)  		value = tegra_hdmi_readl(hdmi, HDMI_NV_PDISP_SOR_AUDIO_HDA_CODEC_SCRATCH0);  		if (value & SOR_AUDIO_HDA_CODEC_SCRATCH0_VALID) { -			unsigned int sample_rate, channels; -  			format = value & SOR_AUDIO_HDA_CODEC_SCRATCH0_FMT_MASK; -			hda_format_parse(format, &sample_rate, &channels); - -			hdmi->audio_sample_rate = sample_rate; -			hdmi->audio_channels = channels; +			tegra_hda_parse_format(format, &hdmi->format);  			err = tegra_hdmi_setup_audio(hdmi);  			if (err < 0) { @@ -1664,8 +1587,6 @@ static int tegra_hdmi_probe(struct platform_device *pdev)  	hdmi->dev = &pdev->dev;  	hdmi->audio_source = AUTO; -	hdmi->audio_sample_rate = 48000; -	hdmi->audio_channels = 2;  	hdmi->stereo = false;  	hdmi->dvi = false; @@ -1709,10 +1630,6 @@ static int tegra_hdmi_probe(struct platform_device *pdev)  		return PTR_ERR(hdmi->vdd);  	} -	hdmi->output.notifier = cec_notifier_get(&pdev->dev); -	if (hdmi->output.notifier == NULL) -		return -ENOMEM; -  	hdmi->output.dev = &pdev->dev;  	err = tegra_output_probe(&hdmi->output); @@ -1771,9 +1688,6 @@ static int tegra_hdmi_remove(struct platform_device *pdev)  	tegra_output_remove(&hdmi->output); -	if (hdmi->output.notifier) -		cec_notifier_put(hdmi->output.notifier); -  	return 0;  } | 
