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

Commit 8506912b authored by Dave Airlie's avatar Dave Airlie
Browse files

Merge branch 'drm-tda998x-devel' of git://git.armlinux.org.uk/~rmk/linux-arm into drm-next

This adds the ASoC codec interfaces for TDA998x HDMI audio from
Jyri Sarha.

* 'drm-tda998x-devel' of git://git.armlinux.org.uk/~rmk/linux-arm:
  ARM: dts: am335x-boneblack: Add HDMI audio support
  drm/i2c: tda998x: Register ASoC hdmi-codec and add audio DT binding
  drm/i2c: tda998x: Improve tda998x_configure_audio() audio related pdata
parents b4eac546 df0bd1e8
Loading
Loading
Loading
Loading
+18 −0
Original line number Diff line number Diff line
@@ -21,8 +21,19 @@ Optional properties:
  - video-ports: 24 bits value which defines how the video controller
	output is wired to the TDA998x input - default: <0x230145>

  - audio-ports: array of 8-bit values, 2 values per one DAI[1].
	The first value defines the DAI type: TDA998x_SPDIF or TDA998x_I2S[2].
	The second value defines the tda998x AP_ENA reg content when the DAI
	in question is used. The implementation allows one or two DAIs. If two
	DAIs are defined, they must be of different type.

[1] Documentation/sound/alsa/soc/DAI.txt
[2] include/dt-bindings/display/tda998x.h

Example:

#include <dt-bindings/display/tda998x.h>

	tda998x: hdmi-encoder {
		compatible = "nxp,tda998x";
		reg = <0x70>;
@@ -30,4 +41,11 @@ Example:
		interrupts = <27 2>;		/* falling edge */
		pinctrl-0 = <&pmx_camera>;
		pinctrl-names = "default";
		video-ports = <0x230145>;

		#sound-dai-cells = <2>;
			     /*	DAI-format	AP_ENA reg value */
		audio-ports = <	TDA998x_SPDIF	0x04
				TDA998x_I2S	0x03>;

	};
+67 −4
Original line number Diff line number Diff line
@@ -9,6 +9,7 @@

#include "am33xx.dtsi"
#include "am335x-bone-common.dtsi"
#include <dt-bindings/display/tda998x.h>

/ {
	model = "TI AM335x BeagleBone Black";
@@ -75,6 +76,16 @@
			AM33XX_IOPAD(0x9b0, PIN_OUTPUT_PULLDOWN | MUX_MODE3)	/* xdma_event_intr0 */
		>;
	};

	mcasp0_pins: mcasp0_pins {
		pinctrl-single,pins = <
			AM33XX_IOPAD(0x9ac, PIN_INPUT_PULLUP | MUX_MODE0) /* mcasp0_ahcklx.mcasp0_ahclkx */
			AM33XX_IOPAD(0x99c, PIN_OUTPUT_PULLDOWN | MUX_MODE2) /* mcasp0_ahclkr.mcasp0_axr2*/
			AM33XX_IOPAD(0x994, PIN_OUTPUT_PULLUP | MUX_MODE0) /* mcasp0_fsx.mcasp0_fsx */
			AM33XX_IOPAD(0x990, PIN_OUTPUT_PULLDOWN | MUX_MODE0) /* mcasp0_aclkx.mcasp0_aclkx */
			AM33XX_IOPAD(0x86c, PIN_OUTPUT_PULLDOWN | MUX_MODE7) /* gpmc_a11.GPIO1_27 */
		>;
	};
};

&lcdc {
@@ -87,21 +98,73 @@
};

&i2c0 {
	tda19988 {
	tda19988: tda19988 {
		compatible = "nxp,tda998x";
		reg = <0x70>;

		pinctrl-names = "default", "off";
		pinctrl-0 = <&nxp_hdmi_bonelt_pins>;
		pinctrl-1 = <&nxp_hdmi_bonelt_off_pins>;

		port {
		#sound-dai-cells = <0>;
		audio-ports = <	TDA998x_I2S	0x03>;

		ports {
			port@0 {
				hdmi_0: endpoint@0 {
					remote-endpoint = <&lcdc_0>;
				};
			};
		};
	};
};

&rtc {
	system-power-controller;
};

&mcasp0	{
	#sound-dai-cells = <0>;
	pinctrl-names = "default";
	pinctrl-0 = <&mcasp0_pins>;
	status = "okay";
	op-mode = <0>;	/* MCASP_IIS_MODE */
	tdm-slots = <2>;
	serial-dir = <	/* 0: INACTIVE, 1: TX, 2: RX */
			0 0 1 0
		>;
	tx-num-evt = <32>;
	rx-num-evt = <32>;
};

/ {
	clk_mcasp0_fixed: clk_mcasp0_fixed {
		#clock-cells = <0>;
		compatible = "fixed-clock";
		clock-frequency = <24576000>;
	};

	clk_mcasp0: clk_mcasp0 {
		#clock-cells = <0>;
		compatible = "gpio-gate-clock";
		clocks = <&clk_mcasp0_fixed>;
		enable-gpios = <&gpio1 27 0>; /* BeagleBone Black Clk enable on GPIO1_27 */
	};

	sound {
		compatible = "simple-audio-card";
		simple-audio-card,name = "TI BeagleBone Black";
		simple-audio-card,format = "i2s";
		simple-audio-card,bitclock-master = <&dailink0_master>;
		simple-audio-card,frame-master = <&dailink0_master>;

		dailink0_master: simple-audio-card,cpu {
			sound-dai = <&mcasp0>;
			clocks = <&clk_mcasp0>;
		};

		simple-audio-card,codec {
			sound-dai = <&tda19988>;
		};
	};
};
+1 −0
Original line number Diff line number Diff line
@@ -22,6 +22,7 @@ config DRM_I2C_SIL164
config DRM_I2C_NXP_TDA998X
	tristate "NXP Semiconductors TDA998X HDMI encoder"
	default m if DRM_TILCDC
	select SND_SOC_HDMI_CODEC if SND_SOC
	help
	  Support for NXP Semiconductors TDA998X HDMI encoders.

+256 −41
Original line number Diff line number Diff line
@@ -20,6 +20,7 @@
#include <linux/module.h>
#include <linux/irq.h>
#include <sound/asoundef.h>
#include <sound/hdmi-codec.h>

#include <drm/drmP.h>
#include <drm/drm_atomic_helper.h>
@@ -30,6 +31,11 @@

#define DBG(fmt, ...) DRM_DEBUG(fmt"\n", ##__VA_ARGS__)

struct tda998x_audio_port {
	u8 format;		/* AFMT_xxx */
	u8 config;		/* AP value */
};

struct tda998x_priv {
	struct i2c_client *cec;
	struct i2c_client *hdmi;
@@ -41,7 +47,10 @@ struct tda998x_priv {
	u8 vip_cntrl_0;
	u8 vip_cntrl_1;
	u8 vip_cntrl_2;
	struct tda998x_encoder_params params;
	struct tda998x_audio_params audio_params;

	struct platform_device *audio_pdev;
	struct mutex audio_mutex;

	wait_queue_head_t wq_edid;
	volatile int wq_edid_wait;
@@ -53,6 +62,8 @@ struct tda998x_priv {

	struct drm_encoder encoder;
	struct drm_connector connector;

	struct tda998x_audio_port audio_port[2];
};

#define conn_to_tda998x_priv(x) \
@@ -666,26 +677,16 @@ tda998x_write_if(struct tda998x_priv *priv, u8 bit, u16 addr,
	reg_set(priv, REG_DIP_IF_FLAGS, bit);
}

static void
tda998x_write_aif(struct tda998x_priv *priv, struct tda998x_encoder_params *p)
static int tda998x_write_aif(struct tda998x_priv *priv,
			     struct hdmi_audio_infoframe *cea)
{
	union hdmi_infoframe frame;

	hdmi_audio_infoframe_init(&frame.audio);

	frame.audio.channels = p->audio_frame[1] & 0x07;
	frame.audio.channel_allocation = p->audio_frame[4];
	frame.audio.level_shift_value = (p->audio_frame[5] & 0x78) >> 3;
	frame.audio.downmix_inhibit = (p->audio_frame[5] & 0x80) >> 7;

	/*
	 * L-PCM and IEC61937 compressed audio shall always set sample
	 * frequency to "refer to stream".  For others, see the HDMI
	 * specification.
	 */
	frame.audio.sample_frequency = (p->audio_frame[2] & 0x1c) >> 2;
	frame.audio = *cea;

	tda998x_write_if(priv, DIP_IF_FLAGS_IF4, REG_IF4_HB0, &frame);

	return 0;
}

static void
@@ -710,20 +711,21 @@ static void tda998x_audio_mute(struct tda998x_priv *priv, bool on)
	}
}

static void
static int
tda998x_configure_audio(struct tda998x_priv *priv,
		struct drm_display_mode *mode, struct tda998x_encoder_params *p)
			struct tda998x_audio_params *params,
			unsigned mode_clock)
{
	u8 buf[6], clksel_aip, clksel_fs, cts_n, adiv;
	u32 n;

	/* Enable audio ports */
	reg_write(priv, REG_ENA_AP, p->audio_cfg);
	reg_write(priv, REG_ENA_ACLK, p->audio_clk_cfg);
	reg_write(priv, REG_ENA_AP, params->config);

	/* Set audio input source */
	switch (p->audio_format) {
	switch (params->format) {
	case AFMT_SPDIF:
		reg_write(priv, REG_ENA_ACLK, 0);
		reg_write(priv, REG_MUX_AP, MUX_AP_SELECT_SPDIF);
		clksel_aip = AIP_CLKSEL_AIP_SPDIF;
		clksel_fs = AIP_CLKSEL_FS_FS64SPDIF;
@@ -731,15 +733,29 @@ tda998x_configure_audio(struct tda998x_priv *priv,
		break;

	case AFMT_I2S:
		reg_write(priv, REG_ENA_ACLK, 1);
		reg_write(priv, REG_MUX_AP, MUX_AP_SELECT_I2S);
		clksel_aip = AIP_CLKSEL_AIP_I2S;
		clksel_fs = AIP_CLKSEL_FS_ACLK;
		switch (params->sample_width) {
		case 16:
			cts_n = CTS_N_M(3) | CTS_N_K(1);
			break;
		case 18:
		case 20:
		case 24:
			cts_n = CTS_N_M(3) | CTS_N_K(2);
			break;
		default:
		case 32:
			cts_n = CTS_N_M(3) | CTS_N_K(3);
			break;
		}
		break;

	default:
		BUG();
		return;
		dev_err(&priv->hdmi->dev, "Unsupported I2S format\n");
		return -EINVAL;
	}

	reg_write(priv, REG_AIP_CLKSEL, clksel_aip);
@@ -755,11 +771,11 @@ tda998x_configure_audio(struct tda998x_priv *priv,
	 * assume 100MHz requires larger divider.
	 */
	adiv = AUDIO_DIV_SERCLK_8;
	if (mode->clock > 100000)
	if (mode_clock > 100000)
		adiv++;			/* AUDIO_DIV_SERCLK_16 */

	/* S/PDIF asks for a larger divider */
	if (p->audio_format == AFMT_SPDIF)
	if (params->format == AFMT_SPDIF)
		adiv++;			/* AUDIO_DIV_SERCLK_16 or _32 */

	reg_write(priv, REG_AUDIO_DIV, adiv);
@@ -768,7 +784,7 @@ tda998x_configure_audio(struct tda998x_priv *priv,
	 * This is the approximate value of N, which happens to be
	 * the recommended values for non-coherent clocks.
	 */
	n = 128 * p->audio_sample_rate / 1000;
	n = 128 * params->sample_rate / 1000;

	/* Write the CTS and N values */
	buf[0] = 0x44;
@@ -786,20 +802,21 @@ tda998x_configure_audio(struct tda998x_priv *priv,
	reg_set(priv, REG_AIP_CNTRL_0, AIP_CNTRL_0_RST_CTS);
	reg_clear(priv, REG_AIP_CNTRL_0, AIP_CNTRL_0_RST_CTS);

	/* Write the channel status */
	buf[0] = IEC958_AES0_CON_NOT_COPYRIGHT;
	buf[1] = 0x00;
	buf[2] = IEC958_AES3_CON_FS_NOTID;
	buf[3] = IEC958_AES4_CON_ORIGFS_NOTID |
			IEC958_AES4_CON_MAX_WORDLEN_24;
	/* Write the channel status
	 * The REG_CH_STAT_B-registers skip IEC958 AES2 byte, because
	 * there is a separate register for each I2S wire.
	 */
	buf[0] = params->status[0];
	buf[1] = params->status[1];
	buf[2] = params->status[3];
	buf[3] = params->status[4];
	reg_write_range(priv, REG_CH_STAT_B(0), buf, 4);

	tda998x_audio_mute(priv, true);
	msleep(20);
	tda998x_audio_mute(priv, false);

	/* Write the audio information packet */
	tda998x_write_aif(priv, p);
	return tda998x_write_aif(priv, &params->cea);
}

/* DRM encoder functions */
@@ -820,7 +837,7 @@ static void tda998x_encoder_set_config(struct tda998x_priv *priv,
			    VIP_CNTRL_2_SWAP_F(p->swap_f) |
			    (p->mirr_f ? VIP_CNTRL_2_MIRR_F : 0);

	priv->params = *p;
	priv->audio_params = p->audio_params;
}

static void tda998x_encoder_dpms(struct drm_encoder *encoder, int mode)
@@ -1057,9 +1074,13 @@ tda998x_encoder_mode_set(struct drm_encoder *encoder,

		tda998x_write_avi(priv, adjusted_mode);

		if (priv->params.audio_cfg)
			tda998x_configure_audio(priv, adjusted_mode,
						&priv->params);
		if (priv->audio_params.format != AFMT_UNUSED) {
			mutex_lock(&priv->audio_mutex);
			tda998x_configure_audio(priv,
						&priv->audio_params,
						adjusted_mode->clock);
			mutex_unlock(&priv->audio_mutex);
		}
	}
}

@@ -1159,6 +1180,8 @@ static int tda998x_connector_get_modes(struct drm_connector *connector)
	drm_mode_connector_update_edid_property(connector, edid);
	n = drm_add_edid_modes(connector, edid);
	priv->is_hdmi_sink = drm_detect_hdmi_monitor(edid);
	drm_edid_to_eld(connector, edid);

	kfree(edid);

	return n;
@@ -1180,6 +1203,9 @@ static void tda998x_destroy(struct tda998x_priv *priv)
	cec_write(priv, REG_CEC_RXSHPDINTENA, 0);
	reg_clear(priv, REG_INT_FLAGS_2, INT_FLAGS_2_EDID_BLK_RD);

	if (priv->audio_pdev)
		platform_device_unregister(priv->audio_pdev);

	if (priv->hdmi->irq)
		free_irq(priv->hdmi->irq, priv);

@@ -1189,8 +1215,189 @@ static void tda998x_destroy(struct tda998x_priv *priv)
	i2c_unregister_device(priv->cec);
}

static int tda998x_audio_hw_params(struct device *dev, void *data,
				   struct hdmi_codec_daifmt *daifmt,
				   struct hdmi_codec_params *params)
{
	struct tda998x_priv *priv = dev_get_drvdata(dev);
	int i, ret;
	struct tda998x_audio_params audio = {
		.sample_width = params->sample_width,
		.sample_rate = params->sample_rate,
		.cea = params->cea,
	};

	if (!priv->encoder.crtc)
		return -ENODEV;

	memcpy(audio.status, params->iec.status,
	       min(sizeof(audio.status), sizeof(params->iec.status)));

	switch (daifmt->fmt) {
	case HDMI_I2S:
		if (daifmt->bit_clk_inv || daifmt->frame_clk_inv ||
		    daifmt->bit_clk_master || daifmt->frame_clk_master) {
			dev_err(dev, "%s: Bad flags %d %d %d %d\n", __func__,
				daifmt->bit_clk_inv, daifmt->frame_clk_inv,
				daifmt->bit_clk_master,
				daifmt->frame_clk_master);
			return -EINVAL;
		}
		for (i = 0; i < ARRAY_SIZE(priv->audio_port); i++)
			if (priv->audio_port[i].format == AFMT_I2S)
				audio.config = priv->audio_port[i].config;
		audio.format = AFMT_I2S;
		break;
	case HDMI_SPDIF:
		for (i = 0; i < ARRAY_SIZE(priv->audio_port); i++)
			if (priv->audio_port[i].format == AFMT_SPDIF)
				audio.config = priv->audio_port[i].config;
		audio.format = AFMT_SPDIF;
		break;
	default:
		dev_err(dev, "%s: Invalid format %d\n", __func__, daifmt->fmt);
		return -EINVAL;
	}

	if (audio.config == 0) {
		dev_err(dev, "%s: No audio configutation found\n", __func__);
		return -EINVAL;
	}

	mutex_lock(&priv->audio_mutex);
	ret = tda998x_configure_audio(priv,
				      &audio,
				      priv->encoder.crtc->hwmode.clock);

	if (ret == 0)
		priv->audio_params = audio;
	mutex_unlock(&priv->audio_mutex);

	return ret;
}

static void tda998x_audio_shutdown(struct device *dev, void *data)
{
	struct tda998x_priv *priv = dev_get_drvdata(dev);

	mutex_lock(&priv->audio_mutex);

	reg_write(priv, REG_ENA_AP, 0);

	priv->audio_params.format = AFMT_UNUSED;

	mutex_unlock(&priv->audio_mutex);
}

int tda998x_audio_digital_mute(struct device *dev, void *data, bool enable)
{
	struct tda998x_priv *priv = dev_get_drvdata(dev);

	mutex_lock(&priv->audio_mutex);

	tda998x_audio_mute(priv, enable);

	mutex_unlock(&priv->audio_mutex);
	return 0;
}

static int tda998x_audio_get_eld(struct device *dev, void *data,
				 uint8_t *buf, size_t len)
{
	struct tda998x_priv *priv = dev_get_drvdata(dev);
	struct drm_mode_config *config = &priv->encoder.dev->mode_config;
	struct drm_connector *connector;
	int ret = -ENODEV;

	mutex_lock(&config->mutex);
	list_for_each_entry(connector, &config->connector_list, head) {
		if (&priv->encoder == connector->encoder) {
			memcpy(buf, connector->eld,
			       min(sizeof(connector->eld), len));
			ret = 0;
		}
	}
	mutex_unlock(&config->mutex);

	return ret;
}

static const struct hdmi_codec_ops audio_codec_ops = {
	.hw_params = tda998x_audio_hw_params,
	.audio_shutdown = tda998x_audio_shutdown,
	.digital_mute = tda998x_audio_digital_mute,
	.get_eld = tda998x_audio_get_eld,
};

static int tda998x_audio_codec_init(struct tda998x_priv *priv,
				    struct device *dev)
{
	struct hdmi_codec_pdata codec_data = {
		.ops = &audio_codec_ops,
		.max_i2s_channels = 2,
	};
	int i;

	for (i = 0; i < ARRAY_SIZE(priv->audio_port); i++) {
		if (priv->audio_port[i].format == AFMT_I2S &&
		    priv->audio_port[i].config != 0)
			codec_data.i2s = 1;
		if (priv->audio_port[i].format == AFMT_SPDIF &&
		    priv->audio_port[i].config != 0)
			codec_data.spdif = 1;
	}

	priv->audio_pdev = platform_device_register_data(
		dev, HDMI_CODEC_DRV_NAME, PLATFORM_DEVID_AUTO,
		&codec_data, sizeof(codec_data));

	return PTR_ERR_OR_ZERO(priv->audio_pdev);
}

/* I2C driver functions */

static int tda998x_get_audio_ports(struct tda998x_priv *priv,
				   struct device_node *np)
{
	const u32 *port_data;
	u32 size;
	int i;

	port_data = of_get_property(np, "audio-ports", &size);
	if (!port_data)
		return 0;

	size /= sizeof(u32);
	if (size > 2 * ARRAY_SIZE(priv->audio_port) || size % 2 != 0) {
		dev_err(&priv->hdmi->dev,
			"Bad number of elements in audio-ports dt-property\n");
		return -EINVAL;
	}

	size /= 2;

	for (i = 0; i < size; i++) {
		u8 afmt = be32_to_cpup(&port_data[2*i]);
		u8 ena_ap = be32_to_cpup(&port_data[2*i+1]);

		if (afmt != AFMT_SPDIF && afmt != AFMT_I2S) {
			dev_err(&priv->hdmi->dev,
				"Bad audio format %u\n", afmt);
			return -EINVAL;
		}

		priv->audio_port[i].format = afmt;
		priv->audio_port[i].config = ena_ap;
	}

	if (priv->audio_port[0].format == priv->audio_port[1].format) {
		dev_err(&priv->hdmi->dev,
			"There can only be on I2S port and one SPDIF port\n");
		return -EINVAL;
	}
	return 0;
}

static int tda998x_create(struct i2c_client *client, struct tda998x_priv *priv)
{
	struct device_node *np = client->dev.of_node;
@@ -1304,7 +1511,7 @@ static int tda998x_create(struct i2c_client *client, struct tda998x_priv *priv)
	if (!np)
		return 0;		/* non-DT */

	/* get the optional video properties */
	/* get the device tree parameters */
	ret = of_property_read_u32(np, "video-ports", &video);
	if (ret == 0) {
		priv->vip_cntrl_0 = video >> 16;
@@ -1312,8 +1519,16 @@ static int tda998x_create(struct i2c_client *client, struct tda998x_priv *priv)
		priv->vip_cntrl_2 = video;
	}

	return 0;
	mutex_init(&priv->audio_mutex); /* Protect access from audio thread */

	ret = tda998x_get_audio_ports(priv, np);
	if (ret)
		goto fail;

	if (priv->audio_port[0].format != AFMT_UNUSED)
		tda998x_audio_codec_init(priv, &client->dev);

	return 0;
fail:
	/* if encoder_init fails, the encoder slave is never registered,
	 * so cleanup here:
+19 −10
Original line number Diff line number Diff line
#ifndef __DRM_I2C_TDA998X_H__
#define __DRM_I2C_TDA998X_H__

#include <linux/hdmi.h>
#include <dt-bindings/display/tda998x.h>

enum {
	AFMT_UNUSED =	0,
	AFMT_SPDIF =	TDA998x_SPDIF,
	AFMT_I2S =	TDA998x_I2S,
};

struct tda998x_audio_params {
	u8 config;
	u8 format;
	unsigned sample_width;
	unsigned sample_rate;
	struct hdmi_audio_infoframe cea;
	u8 status[5];
};

struct tda998x_encoder_params {
	u8 swap_b:3;
	u8 mirr_b:1;
@@ -15,16 +33,7 @@ struct tda998x_encoder_params {
	u8 swap_e:3;
	u8 mirr_e:1;

	u8 audio_cfg;
	u8 audio_clk_cfg;
	u8 audio_frame[6];

	enum {
		AFMT_SPDIF,
		AFMT_I2S
	} audio_format;

	unsigned audio_sample_rate;
	struct tda998x_audio_params audio_params;
};

#endif
Loading