diff options
author | Shengjiu Wang <shengjiu.wang@freescale.com> | 2014-08-27 14:52:50 +0800 |
---|---|---|
committer | Dong Aisheng <aisheng.dong@nxp.com> | 2019-11-25 15:52:28 +0800 |
commit | ef0a172403b45e9ea223a1ca7336e43ce1777e74 (patch) | |
tree | 7fa673355884ea64eeae2a63f90a02a70288e267 /sound/soc | |
parent | 219d54332a09e8d8741c1e1982f5eae56099de85 (diff) |
MLK-11530-01 ASoC: fsl_hdmi: port hdmi audio driver
cherry-pick below patch from v3.14.y:
ENGR00330403-2: ASoC: fsl_hdmi: port hdmi audio driver from imx_3.10.y
Port HDMI audio driver (CPU driver, machine driver, platform driver) from
imx_3.10.y.
Signed-off-by: Shengjiu Wang <shengjiu.wang@freescale.com>
During 4.14 rebase converted from snd_pcm_ops.copy to copy_user because
copy was removed by upstream
Signed-off-by: Leonard Crestez <leonard.crestez@nxp.com>
[ Aisheng: fix conflicts for a clean base and merge MLK-12244
file onwership change ]
Signed-off-by: Dong Aisheng <aisheng.dong@nxp.com>
Diffstat (limited to 'sound/soc')
-rw-r--r-- | sound/soc/fsl/Kconfig | 19 | ||||
-rw-r--r-- | sound/soc/fsl/Makefile | 7 | ||||
-rw-r--r-- | sound/soc/fsl/fsl_hdmi.c | 721 | ||||
-rw-r--r-- | sound/soc/fsl/hdmi_pcm.S | 246 | ||||
-rw-r--r-- | sound/soc/fsl/imx-hdmi-dma.c | 1169 | ||||
-rw-r--r-- | sound/soc/fsl/imx-hdmi.c | 114 | ||||
-rw-r--r-- | sound/soc/fsl/imx-hdmi.h | 106 |
7 files changed, 2382 insertions, 0 deletions
diff --git a/sound/soc/fsl/Kconfig b/sound/soc/fsl/Kconfig index aa99c008a925..9e410697b6a9 100644 --- a/sound/soc/fsl/Kconfig +++ b/sound/soc/fsl/Kconfig @@ -77,6 +77,9 @@ config SND_SOC_FSL_MICFIL config SND_SOC_FSL_UTILS tristate +config SND_SOC_FSL_HDMI + tristate + config SND_SOC_IMX_PCM_DMA tristate select SND_SOC_GENERIC_DMAENGINE_PCM @@ -201,6 +204,11 @@ config SND_SOC_IMX_SSI tristate select SND_SOC_FSL_UTILS +config SND_SOC_IMX_HDMI_DMA + bool + select SND_SOC_GENERIC_DMAENGINE_PCM + select SND_SOC_IMX_PCM_DMA + comment "SoC Audio support for Freescale i.MX boards:" config SND_MXC_SOC_WM1133_EV1 @@ -314,6 +322,17 @@ config SND_SOC_IMX_AUDMIX Say Y if you want to add support for SoC audio on an i.MX board with an Audio Mixer. +config SND_SOC_IMX_HDMI + tristate "SoC Audio support for i.MX boards with HDMI port" + depends on MFD_MXC_HDMI + select SND_SOC_IMX_HDMI_DMA + select SND_SOC_FSL_HDMI + select SND_SOC_HDMI_CODEC + help + SoC Audio support for i.MX boards with HDMI audio + Say Y if you want to add support for SoC audio on an i.MX board with + IMX HDMI. + endif # SND_IMX_SOC endmenu diff --git a/sound/soc/fsl/Makefile b/sound/soc/fsl/Makefile index c0dd04422fe9..18ac88c56ab0 100644 --- a/sound/soc/fsl/Makefile +++ b/sound/soc/fsl/Makefile @@ -26,6 +26,7 @@ snd-soc-fsl-dma-objs := fsl_dma.o obj-$(CONFIG_SND_SOC_FSL_AUDMIX) += snd-soc-fsl-audmix.o obj-$(CONFIG_SND_SOC_FSL_ASOC_CARD) += snd-soc-fsl-asoc-card.o +snd-soc-fsl-hdmi-objs := fsl_hdmi.o obj-$(CONFIG_SND_SOC_FSL_ASRC) += snd-soc-fsl-asrc.o obj-$(CONFIG_SND_SOC_FSL_SAI) += snd-soc-fsl-sai.o obj-$(CONFIG_SND_SOC_FSL_SSI) += snd-soc-fsl-ssi.o @@ -33,6 +34,7 @@ obj-$(CONFIG_SND_SOC_FSL_SPDIF) += snd-soc-fsl-spdif.o obj-$(CONFIG_SND_SOC_FSL_ESAI) += snd-soc-fsl-esai.o obj-$(CONFIG_SND_SOC_FSL_MICFIL) += snd-soc-fsl-micfil.o obj-$(CONFIG_SND_SOC_FSL_UTILS) += snd-soc-fsl-utils.o +obj-$(CONFIG_SND_SOC_FSL_HDMI) += snd-soc-fsl-hdmi.o obj-$(CONFIG_SND_SOC_POWERPC_DMA) += snd-soc-fsl-dma.o # MPC5200 Platform Support @@ -52,6 +54,7 @@ obj-$(CONFIG_SND_SOC_IMX_AUDMUX) += snd-soc-imx-audmux.o obj-$(CONFIG_SND_SOC_IMX_PCM_FIQ) += imx-pcm-fiq.o obj-$(CONFIG_SND_SOC_IMX_PCM_DMA) += imx-pcm-dma.o +obj-$(CONFIG_SND_SOC_IMX_HDMI_DMA) += imx-hdmi-dma.o hdmi_pcm.o # i.MX Machine Support snd-soc-eukrea-tlv320-objs := eukrea-tlv320.o @@ -63,6 +66,7 @@ snd-soc-imx-sgtl5000-objs := imx-sgtl5000.o snd-soc-imx-spdif-objs := imx-spdif.o snd-soc-imx-mc13783-objs := imx-mc13783.o snd-soc-imx-audmix-objs := imx-audmix.o +snd-soc-imx-hdmi-objs := imx-hdmi.o obj-$(CONFIG_SND_SOC_EUKREA_TLV320) += snd-soc-eukrea-tlv320.o obj-$(CONFIG_SND_SOC_PHYCORE_AC97) += snd-soc-phycore-ac97.o @@ -73,3 +77,6 @@ obj-$(CONFIG_SND_SOC_IMX_SGTL5000) += snd-soc-imx-sgtl5000.o obj-$(CONFIG_SND_SOC_IMX_SPDIF) += snd-soc-imx-spdif.o obj-$(CONFIG_SND_SOC_IMX_MC13783) += snd-soc-imx-mc13783.o obj-$(CONFIG_SND_SOC_IMX_AUDMIX) += snd-soc-imx-audmix.o +obj-$(CONFIG_SND_SOC_IMX_HDMI) += snd-soc-imx-hdmi.o + +AFLAGS_hdmi_pcm.o := -march=armv7-a -mtune=cortex-a9 -mfpu=neon -mfloat-abi=softfp diff --git a/sound/soc/fsl/fsl_hdmi.c b/sound/soc/fsl/fsl_hdmi.c new file mode 100644 index 000000000000..4f2ac274b85c --- /dev/null +++ b/sound/soc/fsl/fsl_hdmi.c @@ -0,0 +1,721 @@ +/* + * ALSA SoC HDMI Audio Layer for Freescale i.MX + * + * Copyright (C) 2011-2014 Freescale Semiconductor, Inc. + * + * Some code from patch_hdmi.c + * Copyright (c) 2008-2010 Intel Corporation. All rights reserved. + * Copyright (c) 2006 ATI Technologies Inc. + * Copyright (c) 2008 NVIDIA Corp. All rights reserved. + * Copyright (c) 2008 Wei Ni <wni@nvidia.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/dma-mapping.h> +#include <linux/slab.h> +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/mfd/mxc-hdmi-core.h> +#include <sound/pcm.h> +#include <sound/soc.h> +#include <sound/asoundef.h> + +#include <video/mxc_hdmi.h> + +#include "imx-hdmi.h" + + +static struct mxc_edid_cfg edid_cfg; + +static u32 playback_rates[HDMI_MAX_RATES]; +static u32 playback_sample_size[HDMI_MAX_SAMPLE_SIZE]; +static u32 playback_channels[HDMI_MAX_CHANNEL_CONSTRAINTS]; + +static struct snd_pcm_hw_constraint_list playback_constraint_rates; +static struct snd_pcm_hw_constraint_list playback_constraint_bits; +static struct snd_pcm_hw_constraint_list playback_constraint_channels; + +#ifdef DEBUG +static void dumpregs(struct snd_soc_dai *dai) +{ + u32 n, cts; + + cts = (hdmi_readb(HDMI_AUD_CTS3) << 16) | + (hdmi_readb(HDMI_AUD_CTS2) << 8) | + hdmi_readb(HDMI_AUD_CTS1); + + n = (hdmi_readb(HDMI_AUD_N3) << 16) | + (hdmi_readb(HDMI_AUD_N2) << 8) | + hdmi_readb(HDMI_AUD_N1); + + dev_debug(dai->dev, "HDMI_PHY_CONF0 0x%02x\n", + hdmi_readb(HDMI_PHY_CONF0)); + dev_debug(dai->dev, "HDMI_MC_CLKDIS 0x%02x\n", + hdmi_readb(HDMI_MC_CLKDIS)); + dev_debug(dai->dev, "HDMI_AUD_N[1-3] 0x%06x (%d)\n", + n, n); + dev_debug(dai->dev, "HDMI_AUD_CTS[1-3] 0x%06x (%d)\n", + cts, cts); + dev_debug(dai->dev, "HDMI_FC_AUDSCONF 0x%02x\n", + hdmi_readb(HDMI_FC_AUDSCONF)); +} +#else +static void dumpregs(struct snd_soc_dai *dai) {} +#endif + +enum cea_speaker_placement { + FL = (1 << 0), /* Front Left */ + FC = (1 << 1), /* Front Center */ + FR = (1 << 2), /* Front Right */ + FLC = (1 << 3), /* Front Left Center */ + FRC = (1 << 4), /* Front Right Center */ + RL = (1 << 5), /* Rear Left */ + RC = (1 << 6), /* Rear Center */ + RR = (1 << 7), /* Rear Right */ + RLC = (1 << 8), /* Rear Left Center */ + RRC = (1 << 9), /* Rear Right Center */ + LFE = (1 << 10), /* Low Frequency Effect */ + FLW = (1 << 11), /* Front Left Wide */ + FRW = (1 << 12), /* Front Right Wide */ + FLH = (1 << 13), /* Front Left High */ + FCH = (1 << 14), /* Front Center High */ + FRH = (1 << 15), /* Front Right High */ + TC = (1 << 16), /* Top Center */ +}; + +/* + * EDID SA bits in the CEA Speaker Allocation data block + */ +static int edid_speaker_allocation_bits[] = { + [0] = FL | FR, + [1] = LFE, + [2] = FC, + [3] = RL | RR, + [4] = RC, + [5] = FLC | FRC, + [6] = RLC | RRC, + [7] = FLW | FRW, + [8] = FLH | FRH, + [9] = TC, + [10] = FCH, +}; + +struct cea_channel_speaker_allocation { + int ca_index; + int speakers[8]; + + /* Derived values, just for convenience */ + int channels; + int spk_mask; +}; + +/* + * This is an ordered list! + * + * The preceding ones have better chances to be selected by + * hdmi_channel_allocation(). + */ +static struct cea_channel_speaker_allocation channel_allocations[] = { + /* channel: 7 6 5 4 3 2 1 0 */ + { .ca_index = 0x00, .speakers = { 0, 0, 0, 0, 0, 0, FR, FL },}, + /* 2.1 */ + { .ca_index = 0x01, .speakers = { 0, 0, 0, 0, 0, LFE, FR, FL },}, + /* Dolby Surround */ + { .ca_index = 0x02, .speakers = { 0, 0, 0, 0, FC, 0, FR, FL },}, + { .ca_index = 0x03, .speakers = { 0, 0, 0, 0, FC, LFE, FR, FL },}, + { .ca_index = 0x04, .speakers = { 0, 0, 0, RC, 0, 0, FR, FL },}, + { .ca_index = 0x05, .speakers = { 0, 0, 0, RC, 0, LFE, FR, FL },}, + { .ca_index = 0x06, .speakers = { 0, 0, 0, RC, FC, 0, FR, FL },}, + { .ca_index = 0x07, .speakers = { 0, 0, 0, RC, FC, LFE, FR, FL },}, + { .ca_index = 0x08, .speakers = { 0, 0, RR, RL, 0, 0, FR, FL },}, + { .ca_index = 0x09, .speakers = { 0, 0, RR, RL, 0, LFE, FR, FL },}, + { .ca_index = 0x0a, .speakers = { 0, 0, RR, RL, FC, 0, FR, FL },}, + /* surround51 */ + { .ca_index = 0x0b, .speakers = { 0, 0, RR, RL, FC, LFE, FR, FL },}, + { .ca_index = 0x0c, .speakers = { 0, RC, RR, RL, 0, 0, FR, FL },}, + { .ca_index = 0x0d, .speakers = { 0, RC, RR, RL, 0, LFE, FR, FL },}, + { .ca_index = 0x0e, .speakers = { 0, RC, RR, RL, FC, 0, FR, FL },}, + /* 6.1 */ + { .ca_index = 0x0f, .speakers = { 0, RC, RR, RL, FC, LFE, FR, FL },}, + { .ca_index = 0x10, .speakers = { RRC, RLC, RR, RL, 0, 0, FR, FL },}, + { .ca_index = 0x11, .speakers = { RRC, RLC, RR, RL, 0, LFE, FR, FL },}, + { .ca_index = 0x12, .speakers = { RRC, RLC, RR, RL, FC, 0, FR, FL },}, + /* surround71 */ + { .ca_index = 0x13, .speakers = { RRC, RLC, RR, RL, FC, LFE, FR, FL },}, + { .ca_index = 0x14, .speakers = { FRC, FLC, 0, 0, 0, 0, FR, FL },}, + { .ca_index = 0x15, .speakers = { FRC, FLC, 0, 0, 0, LFE, FR, FL },}, + { .ca_index = 0x16, .speakers = { FRC, FLC, 0, 0, FC, 0, FR, FL },}, + { .ca_index = 0x17, .speakers = { FRC, FLC, 0, 0, FC, LFE, FR, FL },}, + { .ca_index = 0x18, .speakers = { FRC, FLC, 0, RC, 0, 0, FR, FL },}, + { .ca_index = 0x19, .speakers = { FRC, FLC, 0, RC, 0, LFE, FR, FL },}, + { .ca_index = 0x1a, .speakers = { FRC, FLC, 0, RC, FC, 0, FR, FL },}, + { .ca_index = 0x1b, .speakers = { FRC, FLC, 0, RC, FC, LFE, FR, FL },}, + { .ca_index = 0x1c, .speakers = { FRC, FLC, RR, RL, 0, 0, FR, FL },}, + { .ca_index = 0x1d, .speakers = { FRC, FLC, RR, RL, 0, LFE, FR, FL },}, + { .ca_index = 0x1e, .speakers = { FRC, FLC, RR, RL, FC, 0, FR, FL },}, + { .ca_index = 0x1f, .speakers = { FRC, FLC, RR, RL, FC, LFE, FR, FL },}, + { .ca_index = 0x20, .speakers = { 0, FCH, RR, RL, FC, 0, FR, FL },}, + { .ca_index = 0x21, .speakers = { 0, FCH, RR, RL, FC, LFE, FR, FL },}, + { .ca_index = 0x22, .speakers = { TC, 0, RR, RL, FC, 0, FR, FL },}, + { .ca_index = 0x23, .speakers = { TC, 0, RR, RL, FC, LFE, FR, FL },}, + { .ca_index = 0x24, .speakers = { FRH, FLH, RR, RL, 0, 0, FR, FL },}, + { .ca_index = 0x25, .speakers = { FRH, FLH, RR, RL, 0, LFE, FR, FL },}, + { .ca_index = 0x26, .speakers = { FRW, FLW, RR, RL, 0, 0, FR, FL },}, + { .ca_index = 0x27, .speakers = { FRW, FLW, RR, RL, 0, LFE, FR, FL },}, + { .ca_index = 0x28, .speakers = { TC, RC, RR, RL, FC, 0, FR, FL },}, + { .ca_index = 0x29, .speakers = { TC, RC, RR, RL, FC, LFE, FR, FL },}, + { .ca_index = 0x2a, .speakers = { FCH, RC, RR, RL, FC, 0, FR, FL },}, + { .ca_index = 0x2b, .speakers = { FCH, RC, RR, RL, FC, LFE, FR, FL },}, + { .ca_index = 0x2c, .speakers = { TC, FCH, RR, RL, FC, 0, FR, FL },}, + { .ca_index = 0x2d, .speakers = { TC, FCH, RR, RL, FC, LFE, FR, FL },}, + { .ca_index = 0x2e, .speakers = { FRH, FLH, RR, RL, FC, 0, FR, FL },}, + { .ca_index = 0x2f, .speakers = { FRH, FLH, RR, RL, FC, LFE, FR, FL },}, + { .ca_index = 0x30, .speakers = { FRW, FLW, RR, RL, FC, 0, FR, FL },}, + { .ca_index = 0x31, .speakers = { FRW, FLW, RR, RL, FC, LFE, FR, FL },}, +}; + +/* Compute derived values in channel_allocations[] */ +static void init_channel_allocations(void) +{ + struct cea_channel_speaker_allocation *p; + int i, j; + + for (i = 0; i < ARRAY_SIZE(channel_allocations); i++) { + p = channel_allocations + i; + p->channels = 0; + p->spk_mask = 0; + for (j = 0; j < ARRAY_SIZE(p->speakers); j++) + if (p->speakers[j]) { + p->channels++; + p->spk_mask |= p->speakers[j]; + } + } +} + +/* + * The transformation takes two steps: + * + * speaker_alloc => (edid_speaker_allocation_bits[]) => spk_mask + * spk_mask => (channel_allocations[]) => CA + * + * TODO: it could select the wrong CA from multiple candidates. +*/ +static int hdmi_channel_allocation(int channels) +{ + int spk_mask = 0, ca = 0, i, tmpchn, tmpspk; + + /* CA defaults to 0 for basic stereo audio */ + if (channels <= 2) + return 0; + + /* + * Expand EDID's speaker allocation mask + * + * EDID tells the speaker mask in a compact(paired) form, + * expand EDID's notions to match the ones used by Audio InfoFrame. + */ + for (i = 0; i < ARRAY_SIZE(edid_speaker_allocation_bits); i++) { + if (edid_cfg.speaker_alloc & (1 << i)) + spk_mask |= edid_speaker_allocation_bits[i]; + } + + /* Search for the first working match in the CA table */ + for (i = 0; i < ARRAY_SIZE(channel_allocations); i++) { + tmpchn = channel_allocations[i].channels; + tmpspk = channel_allocations[i].spk_mask; + + if (channels == tmpchn && (spk_mask & tmpspk) == tmpspk) { + ca = channel_allocations[i].ca_index; + break; + } + } + + return ca; +} + +static void hdmi_set_audio_infoframe(unsigned int channels) +{ + u8 audiconf0, audiconf2; + + /* + * From CEA-861-D spec: + * HDMI requires the CT, SS and SF fields to be set to 0 ("Refer + * to Stream Header") as these items are carried in the audio stream. + * + * So we only set the CC and CA fields. + */ + audiconf0 = ((channels - 1) << HDMI_FC_AUDICONF0_CC_OFFSET) & + HDMI_FC_AUDICONF0_CC_MASK; + + audiconf2 = hdmi_channel_allocation(channels); + + hdmi_writeb(audiconf0, HDMI_FC_AUDICONF0); + hdmi_writeb(0, HDMI_FC_AUDICONF1); + hdmi_writeb(audiconf2, HDMI_FC_AUDICONF2); + hdmi_writeb(0, HDMI_FC_AUDICONF3); +} + +static int cea_audio_rates[HDMI_MAX_RATES] = { + 32000, 44100, 48000, 88200, 96000, 176400, 192000, +}; + +static void fsl_hdmi_get_playback_rates(void) +{ + int i, count = 0; + u8 rates; + + /* Always assume basic audio support */ + rates = edid_cfg.sample_rates | 0x7; + + for (i = 0 ; i < HDMI_MAX_RATES ; i++) + if ((rates & (1 << i)) != 0) + playback_rates[count++] = cea_audio_rates[i]; + + playback_constraint_rates.list = playback_rates; + playback_constraint_rates.count = count; + + for (i = 0 ; i < playback_constraint_rates.count ; i++) + pr_debug("%s: constraint = %d Hz\n", __func__, playback_rates[i]); +} + +static void fsl_hdmi_get_playback_sample_size(void) +{ + int i = 0; + + /* Always assume basic audio support */ + playback_sample_size[i++] = 16; + + if (edid_cfg.sample_sizes & 0x4) + playback_sample_size[i++] = 24; + + playback_constraint_bits.list = playback_sample_size; + playback_constraint_bits.count = i; + + for (i = 0 ; i < playback_constraint_bits.count ; i++) + pr_debug("%s: constraint = %d bits\n", __func__, playback_sample_size[i]); +} + +static void fsl_hdmi_get_playback_channels(void) +{ + int channels = 2, i = 0; + + /* Always assume basic audio support */ + playback_channels[i++] = channels; + channels += 2; + + while ((i < HDMI_MAX_CHANNEL_CONSTRAINTS) && + (channels <= edid_cfg.max_channels)) { + playback_channels[i++] = channels; + channels += 2; + } + + playback_constraint_channels.list = playback_channels; + playback_constraint_channels.count = i; + + for (i = 0 ; i < playback_constraint_channels.count ; i++) + pr_debug("%s: constraint = %d channels\n", __func__, playback_channels[i]); +} + +static int fsl_hdmi_update_constraints(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + int ret; + + hdmi_get_edid_cfg(&edid_cfg); + + fsl_hdmi_get_playback_rates(); + ret = snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + &playback_constraint_rates); + if (ret) + return ret; + + fsl_hdmi_get_playback_sample_size(); + ret = snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_SAMPLE_BITS, + &playback_constraint_bits); + if (ret) + return ret; + + fsl_hdmi_get_playback_channels(); + ret = snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, + &playback_constraint_channels); + if (ret) + return ret; + + ret = snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS); + if (ret) + return ret; + + return 0; +} + +static int fsl_hdmi_soc_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct imx_hdmi *hdmi_data = snd_soc_dai_get_drvdata(dai); + int ret; + + clk_prepare_enable(hdmi_data->mipi_core_clk); + clk_prepare_enable(hdmi_data->isfr_clk); + clk_prepare_enable(hdmi_data->iahb_clk); + + dev_dbg(dai->dev, "%s hdmi clks: mipi_core: %d isfr:%d iahb:%d\n", __func__, + (int)clk_get_rate(hdmi_data->mipi_core_clk), + (int)clk_get_rate(hdmi_data->isfr_clk), + (int)clk_get_rate(hdmi_data->iahb_clk)); + + ret = fsl_hdmi_update_constraints(substream); + if (ret < 0) + return ret; + + /* Indicates the subpacket represents a flatline sample */ + hdmi_audio_writeb(FC_AUDSCONF, AUD_PACKET_SAMPFIT, 0x0); + + return 0; +} + +static void fsl_hdmi_soc_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct imx_hdmi *hdmi_data = snd_soc_dai_get_drvdata(dai); + + clk_disable_unprepare(hdmi_data->iahb_clk); + clk_disable_unprepare(hdmi_data->isfr_clk); + clk_disable_unprepare(hdmi_data->mipi_core_clk); +} + +static int fsl_hdmi_soc_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + + hdmi_set_audio_infoframe(runtime->channels); + hdmi_audio_writeb(FC_AUDSCONF, AUD_PACKET_LAYOUT, + (runtime->channels > 2) ? 0x1 : 0x0); + hdmi_set_sample_rate(runtime->rate); + dumpregs(dai); + + return 0; +} + +static struct snd_soc_dai_ops fsl_hdmi_soc_dai_ops = { + .startup = fsl_hdmi_soc_startup, + .shutdown = fsl_hdmi_soc_shutdown, + .prepare = fsl_hdmi_soc_prepare, +}; + +/* IEC60958 status functions */ +static int fsl_hdmi_iec_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958; + uinfo->count = 1; + + return 0; +} + + +static int fsl_hdmi_iec_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *uvalue) +{ + int i; + + for (i = 0 ; i < 4 ; i++) + uvalue->value.iec958.status[i] = iec_header.status[i]; + + return 0; +} + +static int fsl_hdmi_iec_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *uvalue) +{ + int i; + + /* Do not allow professional mode */ + if (uvalue->value.iec958.status[0] & IEC958_AES0_PROFESSIONAL) + return -EPERM; + + for (i = 0 ; i < 4 ; i++) { + iec_header.status[i] = uvalue->value.iec958.status[i]; + pr_debug("%s status[%d]=0x%02x\n", __func__, i, iec_header.status[i]); + } + + return 0; +} + +static int fsl_hdmi_channels_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + hdmi_get_edid_cfg(&edid_cfg); + fsl_hdmi_get_playback_channels(); + + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = playback_constraint_channels.count; + + return 0; +} + + +static int fsl_hdmi_channels_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *uvalue) +{ + int i; + hdmi_get_edid_cfg(&edid_cfg); + fsl_hdmi_get_playback_channels(); + + for (i = 0 ; i < playback_constraint_channels.count ; i++) + uvalue->value.integer.value[i] = playback_channels[i]; + + return 0; +} + +static int fsl_hdmi_rates_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + hdmi_get_edid_cfg(&edid_cfg); + fsl_hdmi_get_playback_rates(); + + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = playback_constraint_rates.count; + + return 0; +} + +static int fsl_hdmi_rates_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *uvalue) +{ + int i; + hdmi_get_edid_cfg(&edid_cfg); + fsl_hdmi_get_playback_rates(); + + for (i = 0 ; i < playback_constraint_rates.count ; i++) + uvalue->value.integer.value[i] = playback_rates[i]; + + return 0; +} + +static int fsl_hdmi_formats_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + hdmi_get_edid_cfg(&edid_cfg); + fsl_hdmi_get_playback_sample_size(); + + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = playback_constraint_bits.count; + + return 0; +} + +static int fsl_hdmi_formats_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *uvalue) +{ + int i; + hdmi_get_edid_cfg(&edid_cfg); + fsl_hdmi_get_playback_sample_size(); + + for (i = 0 ; i < playback_constraint_bits.count ; i++) + uvalue->value.integer.value[i] = playback_sample_size[i]; + + return 0; +} + +static struct snd_kcontrol_new fsl_hdmi_ctrls[] = { + /* Status cchanel controller */ + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, DEFAULT), + .access = SNDRV_CTL_ELEM_ACCESS_READ | + SNDRV_CTL_ELEM_ACCESS_WRITE | + SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = fsl_hdmi_iec_info, + .get = fsl_hdmi_iec_get, + .put = fsl_hdmi_iec_put, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "HDMI Support Channels", + .access = SNDRV_CTL_ELEM_ACCESS_READ | + SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = fsl_hdmi_channels_info, + .get = fsl_hdmi_channels_get, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "HDMI Support Rates", + .access = SNDRV_CTL_ELEM_ACCESS_READ | + SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = fsl_hdmi_rates_info, + .get = fsl_hdmi_rates_get, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "HDMI Support Formats", + .access = SNDRV_CTL_ELEM_ACCESS_READ | + SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = fsl_hdmi_formats_info, + .get = fsl_hdmi_formats_get, + }, +}; + +static int fsl_hdmi_soc_dai_probe(struct snd_soc_dai *dai) +{ + int ret; + + init_channel_allocations(); + + ret = snd_soc_add_dai_controls(dai, fsl_hdmi_ctrls, + ARRAY_SIZE(fsl_hdmi_ctrls)); + if (ret) + dev_warn(dai->dev, "failed to add dai controls\n"); + + return 0; +} + +static struct snd_soc_dai_driver fsl_hdmi_dai = { + .probe = &fsl_hdmi_soc_dai_probe, + .playback = { + .channels_min = 2, + .channels_max = 8, + .rates = MXC_HDMI_RATES_PLAYBACK, + .formats = MXC_HDMI_FORMATS_PLAYBACK, + }, + .ops = &fsl_hdmi_soc_dai_ops, +}; + +static const struct snd_soc_component_driver fsl_hdmi_component = { + .name = "fsl-hdmi", +}; + +static int fsl_hdmi_dai_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct imx_hdmi *hdmi_data; + int ret = 0; + + if (!np) + return -ENODEV; + + if (!hdmi_get_registered()) { + dev_err(&pdev->dev, "failed to probe. Load HDMI-video first.\n"); + return -ENOMEM; + } + + hdmi_data = devm_kzalloc(&pdev->dev, sizeof(*hdmi_data), GFP_KERNEL); + if (!hdmi_data) { + dev_err(&pdev->dev, "failed to alloc hdmi_data\n"); + return -ENOMEM; + } + + hdmi_data->pdev = pdev; + + memcpy(&hdmi_data->cpu_dai_drv, &fsl_hdmi_dai, sizeof(fsl_hdmi_dai)); + hdmi_data->cpu_dai_drv.name = np->name; + + hdmi_data->mipi_core_clk = devm_clk_get(&pdev->dev, "mipi_core"); + if (IS_ERR(hdmi_data->mipi_core_clk)) { + ret = PTR_ERR(hdmi_data->mipi_core_clk); + dev_err(&pdev->dev, "failed to get mipi core clk: %d\n", ret); + return -EINVAL; + } + + hdmi_data->isfr_clk = devm_clk_get(&pdev->dev, "hdmi_isfr"); + if (IS_ERR(hdmi_data->isfr_clk)) { + ret = PTR_ERR(hdmi_data->isfr_clk); + dev_err(&pdev->dev, "failed to get HDMI isfr clk: %d\n", ret); + return -EINVAL; + } + + hdmi_data->iahb_clk = devm_clk_get(&pdev->dev, "hdmi_iahb"); + if (IS_ERR(hdmi_data->iahb_clk)) { + ret = PTR_ERR(hdmi_data->iahb_clk); + dev_err(&pdev->dev, "failed to get HDMI ahb clk: %d\n", ret); + return -EINVAL; + } + + dev_set_drvdata(&pdev->dev, hdmi_data); + ret = snd_soc_register_component(&pdev->dev, &fsl_hdmi_component, + &hdmi_data->cpu_dai_drv, 1); + if (ret) { + dev_err(&pdev->dev, "register DAI failed\n"); + return ret; + } + + hdmi_data->codec_dev = platform_device_register_simple( + "hdmi-audio-codec", -1, NULL, 0); + if (IS_ERR(hdmi_data->codec_dev)) { + dev_err(&pdev->dev, "failed to register HDMI audio codec\n"); + ret = PTR_ERR(hdmi_data->codec_dev); + goto fail; + } + + hdmi_data->dma_dev = platform_device_alloc("imx-hdmi-audio", -1); + if (IS_ERR(hdmi_data->dma_dev)) { + ret = PTR_ERR(hdmi_data->dma_dev); + goto fail_dma; + } + + platform_set_drvdata(hdmi_data->dma_dev, hdmi_data); + + ret = platform_device_add(hdmi_data->dma_dev); + if (ret) { + platform_device_put(hdmi_data->dma_dev); + goto fail_dma; + } + + return 0; + +fail_dma: + platform_device_unregister(hdmi_data->codec_dev); +fail: + snd_soc_unregister_component(&pdev->dev); + + return ret; +} + +static int fsl_hdmi_dai_remove(struct platform_device *pdev) +{ + struct imx_hdmi *hdmi_data = platform_get_drvdata(pdev); + + platform_device_unregister(hdmi_data->dma_dev); + platform_device_unregister(hdmi_data->codec_dev); + snd_soc_unregister_component(&pdev->dev); + + return 0; +} + +static const struct of_device_id fsl_hdmi_dai_dt_ids[] = { + { .compatible = "fsl,imx6dl-hdmi-audio", }, + { .compatible = "fsl,imx6q-hdmi-audio", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, fsl_hdmi_dai_dt_ids); + +static struct platform_driver fsl_hdmi_driver = { + .probe = fsl_hdmi_dai_probe, + .remove = fsl_hdmi_dai_remove, + .driver = { + .name = "fsl-hdmi-dai", + .owner = THIS_MODULE, + .of_match_table = fsl_hdmi_dai_dt_ids, + }, +}; +module_platform_driver(fsl_hdmi_driver); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("IMX HDMI TX DAI"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:fsl-hdmi-dai"); diff --git a/sound/soc/fsl/hdmi_pcm.S b/sound/soc/fsl/hdmi_pcm.S new file mode 100644 index 000000000000..d8d95fd8f42f --- /dev/null +++ b/sound/soc/fsl/hdmi_pcm.S @@ -0,0 +1,246 @@ +/** + * Copyright (C) 2010-2014 Freescale Semiconductor, Inc. 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 as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + + * 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, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +.section .text + +.global hdmi_dma_copy_16_neon_lut +.global hdmi_dma_copy_16_neon_fast +.global hdmi_dma_copy_24_neon_lut +.global hdmi_dma_copy_24_neon_fast + + +/** + * hdmi_dma_copy_16_neon_lut + * Convert pcm sample to iec sample. Pcm sample is 16 bits. + * Frame index's between 0 and 47 inclusively. Channel count can be 1, 2, 4, 8. + * Frame count should be multipliable by 4, and Sample count by 8. + * + * C Prototype + * void hdmi_dma_copy_16_neon_lut(unsigned short *src, unsigned int *dst, + * int samples, unsigned char *lookup_table); + * Return value + * None + * Parameters + * src Source PCM16 samples + * dst Dest buffer to store pcm with header + * samples Contains sample count (=frame_count * channel_count) + * lookup_table Preconstructed header table. Channels interleaved. + */ + +hdmi_dma_copy_16_neon_lut: + mov r12, #1 /* construct vector(1) */ + vdup.8 d6, r12 + +hdmi_dma_copy_16_neon_lut_start: + + /* get 8 samples to q0 */ + vld1.16 {d0, d1}, [r0]! /* TODO: aligned */ + + /* pld [r1, #(64*4)] */ + + /* xor every bit */ + vcnt.8 q1, q0 /* count of 1s */ + vpadd.i8 d2, d2, d3 /* only care about the LST in every element */ + vand d2, d2, d6 /* clear other bits while keep the least bit */ + vshl.u8 d2, d2, #3 /* bit p: d2 = d2 << 3 */ + + /* get packet header */ + vld1.8 {d5}, [r3]! + veor d4, d5, d2 /* xor bit c */ + + /* store: (d4 << 16 | q0) << 8 */ + vmovl.u8 q2, d4 /* expand from char to short */ + vzip.16 q0, q2 + vshl.u32 q0, q0, #8 + vshl.u32 q1, q2, #8 + vst1.32 {d0, d1, d2, d3}, [r1]! + + /* decrease sample count */ + subs r2, r2, #8 + bne hdmi_dma_copy_16_neon_lut_start + + mov pc, lr + +/** + * hdmi_dma_copy_16_neon_fast + * Convert pcm sample to iec sample. Pcm sample is 16 bits. + * Frame index's between 48 and 191 inclusively. + * Channel count can be 1, 2, 4 or 8. + * Frame count should be multipliable by 4, and Sample count by 8. + * + * C Prototype + * void hdmi_dma_copy_16_neon_fast(unsigned short *src, + * unsigned int *dst, int samples); + * Return value + * None + * Parameters + * src Source PCM16 samples + * dst Dest buffer to store pcm with header + * samples Contains sample count (=frame_count * channel_count) + */ + +hdmi_dma_copy_16_neon_fast: + mov r12, #1 /* construct vector(1) */ + vdup.8 d6, r12 + +hdmi_dma_copy_16_neon_fast_start: + /* get 8 samples to q0 */ + vld1.16 {d0, d1}, [r0]! /* TODO: aligned */ + + /* pld [r1, #(64*4)] */ + + /* xor every bit */ + vcnt.8 q1, q0 /* count of 1s */ + vpadd.i8 d2, d2, d3 + vand d2, d2, d6 /* clear other bits while keep the LST */ + /* finally we construct packet header */ + vshl.u8 d4, d2, #3 /* bit p: d2 = d2 << 3 */ + + /* get packet header: always 0 */ + + /* store: (d4 << 16 | q0) << 8 */ + vmovl.u8 q2, d4 /* expand from char to short */ + vzip.16 q0, q2 + vshl.u32 q0, q0, #8 + vshl.u32 q1, q2, #8 + vst1.32 {d0, d1, d2, d3}, [r1]! + + /* decrease sample count */ + subs r2, r2, #8 + bne hdmi_dma_copy_16_neon_fast_start + + mov pc, lr + + + +/** + * hdmi_dma_copy_24_neon_lut + * Convert pcm sample to iec sample. Pcm sample is 24 bits. + * Frame index's between 0 and 47 inclusively. Channel count can be 1, 2, 4, 8. + * Frame count should be multipliable by 4, and Sample count by 8. + * + * C Prototype + * void hdmi_dma_copy_24_neon_lut(unsigned int *src, unsigned int *dst, + * int samples, unsigned char *lookup_table); + * Return value + * None + * Parameters + * src Source PCM24 samples + * dst Dest buffer to store pcm with header + * samples Contains sample count (=frame_count * channel_count) + * lookup_table Preconstructed header table. Channels interleaved. + */ + +hdmi_dma_copy_24_neon_lut: + vpush {d8} + + mov r12, #1 /* construct vector(1) */ + vdup.8 d8, r12 + +hdmi_dma_copy_24_neon_lut_start: + + /* get 8 samples to q0 and q1 */ + vld1.32 {d0, d1, d2, d3}, [r0]! /* TODO: aligned */ + + /* pld [r1, #(64*4)] */ + + /* xor every bit */ + vcnt.8 q2, q0 /* count of 1s */ + vpadd.i8 d4, d4, d5 /* only care about the LSB in every element */ + vcnt.8 q3, q1 + vpadd.i8 d6, d6, d7 + vpadd.i8 d4, d4, d6 /* d4: contains xor result and other dirty bits */ + vand d4, d4, d8 /* clear other bits while keep the least bit */ + vshl.u8 d4, d4, #3 /* bit p: d4 = d4 << 3 */ + + /* get packet header */ + vld1.8 {d5}, [r3]!/* d5: original header */ + veor d5, d5, d4 /* fix bit p */ + + /* store: (d5 << 24 | q0) */ + vmovl.u8 q3, d5 /* expand from char to short */ + vmovl.u16 q2, d6 /* expand from short to int */ + vmovl.u16 q3, d7 + vshl.u32 q2, q2, #24 + vshl.u32 q3, q3, #24 + vorr q0, q0, q2 + vorr q1, q1, q3 + vst1.32 {d0, d1, d2, d3}, [r1]! + + /* decrease sample count */ + subs r2, r2, #8 + bne hdmi_dma_copy_24_neon_lut_start + + vpop {d8} + mov pc, lr + +/** + * hdmi_dma_copy_24_neon_fast + * Convert pcm sample to iec sample. Pcm sample is 24 bits. + * Frame index's between 48 and 191 inclusively. + * Channel count can be 1, 2, 4 or 8. + * Frame count should be multipliable by 4, and Sample count by 8. + * + * C Prototype + * void hdmi_dma_copy_24_neon_fast(unsigned int *src, + * unsigned int *dst, int samples); + * Return value + * None + * Parameters + * src Source PCM24 samples + * dst Dest buffer to store pcm with header + * samples Contains sample count (=frame_count * channel_count) + */ + +hdmi_dma_copy_24_neon_fast: + vpush {d8} + + mov r12, #1 /* construct vector(1) */ + vdup.8 d8, r12 + +hdmi_dma_copy_24_neon_fast_start: + /* get 8 samples to q0 and q1 */ + vld1.32 {d0, d1, d2, d3}, [r0]! /* TODO: aligned */ + + /* pld [r1, #(64*4)] */ + + /* xor every bit */ + vcnt.8 q2, q0 /* count of 1s */ + vpadd.i8 d4, d4, d5 /* only care about the LSB in every element */ + vcnt.8 q3, q1 + vpadd.i8 d6, d6, d7 + vpadd.i8 d4, d4, d6 /* d4: contains xor result and other dirty bits */ + vand d4, d4, d8 /* clear other bits while keep the least bit */ + vshl.u8 d4, d4, #3 /* bit p: d4 = d4 << 3 */ + + /* store: (d4 << 24 | q0) */ + vmovl.u8 q3, d4 /* expand from char to short */ + vmovl.u16 q2, d6 /* expand from short to int */ + vmovl.u16 q3, d7 + vshl.u32 q2, q2, #24 + vshl.u32 q3, q3, #24 + vorr q0, q0, q2 + vorr q1, q1, q3 + vst1.32 {d0, d1, d2, d3}, [r1]! + + /* decrease sample count */ + subs r2, r2, #8 + bne hdmi_dma_copy_24_neon_fast_start + + vpop {d8} + mov pc, lr diff --git a/sound/soc/fsl/imx-hdmi-dma.c b/sound/soc/fsl/imx-hdmi-dma.c new file mode 100644 index 000000000000..0c08e0042d6d --- /dev/null +++ b/sound/soc/fsl/imx-hdmi-dma.c @@ -0,0 +1,1169 @@ +/* + * imx-hdmi-dma.c -- HDMI DMA driver for ALSA Soc Audio Layer + * + * Copyright (C) 2011-2014 Freescale Semiconductor, Inc. + * + * based on imx-pcm-dma-mx2.c + * Copyright 2009 Sascha Hauer <s.hauer@pengutronix.de> + * + * This code is based on code copyrighted by Freescale, + * Liam Girdwood, Javier Martin and probably others. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/dma-mapping.h> +#include <linux/mfd/mxc-hdmi-core.h> +#include <linux/platform_data/dma-imx.h> + +#include <video/mxc_hdmi.h> + +#include "imx-hdmi.h" + +#define HDMI_DMA_BURST_UNSPECIFIED_LEGNTH 0 +#define HDMI_DMA_BURST_INCR4 1 +#define HDMI_DMA_BURST_INCR8 2 +#define HDMI_DMA_BURST_INCR16 3 + +#define HDMI_BASE_ADDR 0x00120000 + +struct hdmi_sdma_script { + int control_reg_addr; + int status_reg_addr; + int dma_start_addr; + u32 buffer[20]; +}; + +struct hdmi_dma_priv { + struct snd_pcm_substream *substream; + struct platform_device *pdev; + + struct snd_dma_buffer hw_buffer; + unsigned long buffer_bytes; + unsigned long appl_bytes; + + int periods; + int period_time; + int period_bytes; + int dma_period_bytes; + int buffer_ratio; + + unsigned long offset; + + snd_pcm_format_t format; + int sample_align; + int sample_bits; + int channels; + int rate; + + int frame_idx; + + bool tx_active; + spinlock_t irq_lock; + + /* SDMA part */ + dma_addr_t phy_hdmi_sdma_t; + struct hdmi_sdma_script *hdmi_sdma_t; + struct dma_chan *dma_channel; + struct dma_async_tx_descriptor *desc; + struct imx_hdmi_sdma_params sdma_params; +}; + +/* bit 0:0:0:b:p(0):c:(u)0:(v)0 */ +/* max 8 channels supported; channels are interleaved */ +static u8 g_packet_head_table[48 * 8]; + +void hdmi_dma_copy_16_neon_lut(unsigned short *src, unsigned int *dst, + int samples, unsigned char *lookup_table); +void hdmi_dma_copy_16_neon_fast(unsigned short *src, unsigned int *dst, + int samples); +void hdmi_dma_copy_24_neon_lut(unsigned int *src, unsigned int *dst, + int samples, unsigned char *lookup_table); +void hdmi_dma_copy_24_neon_fast(unsigned int *src, unsigned int *dst, + int samples); +static void hdmi_dma_irq_enable(struct hdmi_dma_priv *priv); +static void hdmi_dma_irq_disable(struct hdmi_dma_priv *priv); + +union hdmi_audio_header_t iec_header; +EXPORT_SYMBOL(iec_header); + +/* + * Note that the period size for DMA != period size for ALSA because the + * driver adds iec frame info to the audio samples (in hdmi_dma_copy). + * + * Each 4 byte subframe = 1 byte of iec data + 3 byte audio sample. + * + * A 16 bit audio sample becomes 32 bits including the frame info. Ratio=2 + * A 24 bit audio sample becomes 32 bits including the frame info. Ratio=3:4 + * If the 24 bit raw audio is in 32 bit words, the + * + * Original Packed into subframe Ratio of size Format + * sample how many size of DMA buffer + * (bits) bits to ALSA buffer + * -------- ----------- -------- -------------- ------------------------ + * 16 16 32 2 SNDRV_PCM_FORMAT_S16_LE + * 24 24 32 1.33 SNDRV_PCM_FORMAT_S24_3LE* + * 24 32 32 1 SNDRV_PCM_FORMAT_S24_LE + * + * *so SNDRV_PCM_FORMAT_S24_3LE is not supported. + */ + +/* + * The minimum dma period is one IEC audio frame (192 * 4 * channels). + * The maximum dma period for the HDMI DMA is 8K. + * + * channels minimum maximum + * dma period dma period + * -------- ------------------ ---------- + * 2 192 * 4 * 2 = 1536 * 4 = 6144 + * 4 192 * 4 * 4 = 3072 * 2 = 6144 + * 6 192 * 4 * 6 = 4608 * 1 = 4608 + * 8 192 * 4 * 8 = 6144 * 1 = 6144 + * + * Bottom line: + * 1. Must keep the ratio of DMA buffer to ALSA buffer consistent. + * 2. frame_idx is saved in the private data, so even if a frame cannot be + * transmitted in a period, it can be continued in the next period. This + * is necessary for 6 ch. + */ +#define HDMI_DMA_PERIOD_BYTES (12288) +#define HDMI_DMA_BUF_SIZE (128 * 1024) +#define HDMI_PCM_BUF_SIZE (128 * 1024) + +#define hdmi_audio_debug(dev, reg) \ + dev_dbg(dev, #reg ": 0x%02x\n", hdmi_readb(reg)) + +#ifdef DEBUG +static void dumpregs(struct device *dev) +{ + hdmi_audio_debug(dev, HDMI_AHB_DMA_CONF0); + hdmi_audio_debug(dev, HDMI_AHB_DMA_START); + hdmi_audio_debug(dev, HDMI_AHB_DMA_STOP); + hdmi_audio_debug(dev, HDMI_AHB_DMA_THRSLD); + hdmi_audio_debug(dev, HDMI_AHB_DMA_STRADDR0); + hdmi_audio_debug(dev, HDMI_AHB_DMA_STPADDR0); + hdmi_audio_debug(dev, HDMI_AHB_DMA_BSTADDR0); + hdmi_audio_debug(dev, HDMI_AHB_DMA_MBLENGTH0); + hdmi_audio_debug(dev, HDMI_AHB_DMA_MBLENGTH1); + hdmi_audio_debug(dev, HDMI_AHB_DMA_STAT); + hdmi_audio_debug(dev, HDMI_AHB_DMA_INT); + hdmi_audio_debug(dev, HDMI_AHB_DMA_MASK); + hdmi_audio_debug(dev, HDMI_AHB_DMA_POL); + hdmi_audio_debug(dev, HDMI_AHB_DMA_CONF1); + hdmi_audio_debug(dev, HDMI_AHB_DMA_BUFFSTAT); + hdmi_audio_debug(dev, HDMI_AHB_DMA_BUFFINT); + hdmi_audio_debug(dev, HDMI_AHB_DMA_BUFFMASK); + hdmi_audio_debug(dev, HDMI_AHB_DMA_BUFFPOL); + hdmi_audio_debug(dev, HDMI_IH_MUTE_AHBDMAAUD_STAT0); + hdmi_audio_debug(dev, HDMI_IH_AHBDMAAUD_STAT0); + hdmi_audio_debug(dev, HDMI_IH_MUTE); +} + +static void dumppriv(struct device *dev, struct hdmi_dma_priv *priv) +{ + dev_dbg(dev, "channels = %d\n", priv->channels); + dev_dbg(dev, "periods = %d\n", priv->periods); + dev_dbg(dev, "period_bytes = %d\n", priv->period_bytes); + dev_dbg(dev, "dma period_bytes = %d\n", priv->dma_period_bytes); + dev_dbg(dev, "buffer_ratio = %d\n", priv->buffer_ratio); + dev_dbg(dev, "hw dma buffer = 0x%08x\n", (int)priv->hw_buffer.addr); + dev_dbg(dev, "dma buf size = %d\n", (int)priv->buffer_bytes); + dev_dbg(dev, "sample_rate = %d\n", (int)priv->rate); +} +#else +static void dumpregs(struct device *dev) {} +static void dumppriv(struct device *dev, struct hdmi_dma_priv *priv) {} +#endif + +/* + * Conditions for DMA to work: + * ((final_addr - initial_addr)>>2)+1) < 2k. So max period is 8k. + * (inital_addr & 0x3) == 0 + * (final_addr & 0x3) == 0x3 + * + * The DMA Period should be an integer multiple of the IEC 60958 audio + * frame size, which is 768 bytes (192 * 4). + */ +static void hdmi_dma_set_addr(int start_addr, int dma_period_bytes) +{ + int final_addr = start_addr + dma_period_bytes - 1; + + hdmi_write4(start_addr, HDMI_AHB_DMA_STRADDR0); + hdmi_write4(final_addr, HDMI_AHB_DMA_STPADDR0); +} + +static void hdmi_dma_irq_set(bool set) +{ + u8 val = hdmi_readb(HDMI_AHB_DMA_MASK); + + if (set) + val |= HDMI_AHB_DMA_DONE; + else + val &= (u8)~HDMI_AHB_DMA_DONE; + + hdmi_writeb(val, HDMI_AHB_DMA_MASK); +} + +static void hdmi_mask(int mask) +{ + u8 regval = hdmi_readb(HDMI_AHB_DMA_MASK); + + if (mask) + regval |= HDMI_AHB_DMA_ERROR | HDMI_AHB_DMA_FIFO_EMPTY; + else + regval &= (u8)~(HDMI_AHB_DMA_ERROR | HDMI_AHB_DMA_FIFO_EMPTY); + + hdmi_writeb(regval, HDMI_AHB_DMA_MASK); +} + +int odd_ones(unsigned a) +{ + a ^= a >> 8; + a ^= a >> 4; + a ^= a >> 2; + a ^= a >> 1; + + return a & 1; +} + +/* Add frame information for one pcm subframe */ +static u32 hdmi_dma_add_frame_info(struct hdmi_dma_priv *priv, + u32 pcm_data, int subframe_idx) +{ + union hdmi_audio_dma_data_t subframe; + + subframe.U = 0; + iec_header.B.channel = subframe_idx; + + /* fill b (start-of-block) */ + subframe.B.b = (priv->frame_idx == 0) ? 1 : 0; + + /* fill c (channel status) */ + if (priv->frame_idx < 42) + subframe.B.c = (iec_header.U >> priv->frame_idx) & 0x1; + else + subframe.B.c = 0; + + subframe.B.p = odd_ones(pcm_data); + subframe.B.p ^= subframe.B.c; + subframe.B.p ^= subframe.B.u; + subframe.B.p ^= subframe.B.v; + + /* fill data */ + if (priv->sample_bits == 16) + subframe.B.data = pcm_data << 8; + else + subframe.B.data = pcm_data; + + return subframe.U; +} + +static void init_table(int channels) +{ + unsigned char *p = g_packet_head_table; + int i, ch = 0; + + for (i = 0; i < 48; i++) { + int b = 0; + if (i == 0) + b = 1; + + for (ch = 0; ch < channels; ch++) { + int c = 0; + if (i < 42) { + iec_header.B.channel = ch+1; + c = (iec_header.U >> i) & 0x1; + } + /* preset bit p as c */ + *p++ = (b << 4) | (c << 2) | (c << 3); + } + } +} + +/* + * FIXME: Disable NEON Optimization in hdmi, or it will cause crash of + * pulseaudio in the userspace. There is no issue for the Optimization + * implemenation, if only use "VPUSH, VPOP" in the function, the pulseaudio + * will crash also. So for my assumption, we can't use the NEON in the + * interrupt.(tasklet is implemented by softirq.) + * Disable SMP, preempt, change the dma buffer to nocached, add protection of + * Dn register and fpscr, all these operation have no effect to the result. + */ +#define HDMI_DMA_NO_NEON + +#ifdef HDMI_DMA_NO_NEON +/* Optimization for IEC head */ +static void hdmi_dma_copy_16_c_lut(u16 *src, u32 *dst, int samples, + u8 *lookup_table) +{ + u32 sample, head, p; + int i; + + for (i = 0; i < samples; i++) { + /* get source sample */ + sample = *src++; + + /* xor every bit */ + p = sample ^ (sample >> 8); + p ^= (p >> 4); + p ^= (p >> 2); + p ^= (p >> 1); + p &= 1; /* only want last bit */ + p <<= 3; /* bit p */ + + /* get packet header */ + head = *lookup_table++; + + /* fix head */ + head ^= p; + + /* store */ + *dst++ = (head << 24) | (sample << 8); + } +} + +static void hdmi_dma_copy_16_c_fast(u16 *src, u32 *dst, int samples) +{ + u32 sample, p; + int i; + + for (i = 0; i < samples; i++) { + /* get source sample */ + sample = *src++; + + /* xor every bit */ + p = sample ^ (sample >> 8); + p ^= (p >> 4); + p ^= (p >> 2); + p ^= (p >> 1); + p &= 1; /* only want last bit */ + p <<= 3; /* bit p */ + + /* store */ + *dst++ = (p << 24) | (sample << 8); + } +} + +static void hdmi_dma_copy_16(u16 *src, u32 *dst, int framecnt, int channelcnt) +{ + /* split input frames into 192-frame each */ + int count_in_192 = (framecnt + 191) / 192; + int i; + + for (i = 0; i < count_in_192; i++) { + int count, samples; + + /* handles frame index [0, 48) */ + count = (framecnt < 48) ? framecnt : 48; + samples = count * channelcnt; + hdmi_dma_copy_16_c_lut(src, dst, samples, g_packet_head_table); + framecnt -= count; + if (framecnt == 0) + break; + + src += samples; + dst += samples; + + /* handles frame index [48, 192) */ + count = (framecnt < 192 - 48) ? framecnt : 192 - 48; + samples = count * channelcnt; + hdmi_dma_copy_16_c_fast(src, dst, samples); + framecnt -= count; + src += samples; + dst += samples; + } +} +#else +/* NEON optimization for IEC head*/ + +/** + * Convert pcm samples to iec samples suitable for HDMI transfer. + * PCM sample is 16 bits length. + * Frame index always starts from 0. + * Channel count can be 1, 2, 4, 6, or 8 + * Sample count (frame_count * channel_count) is multipliable by 8. + */ +static void hdmi_dma_copy_16(u16 *src, u32 *dst, int framecount, int channelcount) +{ + /* split input frames into 192-frame each */ + int i, count_in_192 = (framecount + 191) / 192; + + for (i = 0; i < count_in_192; i++) { + int count, samples; + + /* handles frame index [0, 48) */ + count = (framecount < 48) ? framecount : 48; + samples = count * channelcount; + hdmi_dma_copy_16_neon_lut(src, dst, samples, g_packet_head_table); + framecount -= count; + if (framecount == 0) + break; + + src += samples; + dst += samples; + + /* handles frame index [48, 192) */ + count = (framecount < 192 - 48) ? framecount : (192 - 48); + samples = count * channelcount; + hdmi_dma_copy_16_neon_fast(src, dst, samples); + framecount -= count; + src += samples; + dst += samples; + } +} +#endif + +static void hdmi_dma_mmap_copy(struct snd_pcm_substream *substream, + int offset, int count) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_pcm_runtime *runtime = substream->runtime; + struct hdmi_dma_priv *priv = runtime->private_data; + struct device *dev = rtd->platform->dev; + u32 framecount, *dst; + u16 *src16; + + framecount = count / (priv->sample_align * priv->channels); + + /* hw_buffer is the destination for pcm data plus frame info. */ + dst = (u32 *)(priv->hw_buffer.area + (offset * priv->buffer_ratio)); + + switch (priv->format) { + case SNDRV_PCM_FORMAT_S16_LE: + /* dma_buffer is the mmapped buffer we are copying pcm from. */ + src16 = (u16 *)(runtime->dma_area + offset); + hdmi_dma_copy_16(src16, dst, framecount, priv->channels); + break; + default: + dev_err(dev, "unsupported sample format %s\n", + snd_pcm_format_name(priv->format)); + return; + } +} + +static void hdmi_dma_data_copy(struct snd_pcm_substream *substream, + struct hdmi_dma_priv *priv, char type) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + unsigned long offset, count, appl_bytes, space_to_end; + + if (runtime->access != SNDRV_PCM_ACCESS_MMAP_INTERLEAVED) + return; + + appl_bytes = frames_to_bytes(runtime, runtime->status->hw_ptr); + if (type == 'p') + appl_bytes += 2 * priv->period_bytes; + offset = appl_bytes % priv->buffer_bytes; + + switch (type) { + case 'p': + count = priv->period_bytes; + space_to_end = priv->period_bytes; + break; + case 'b': + count = priv->buffer_bytes; + space_to_end = priv->buffer_bytes - offset; + + break; + default: + return; + } + + if (count <= space_to_end) { + hdmi_dma_mmap_copy(substream, offset, count); + } else { + hdmi_dma_mmap_copy(substream, offset, space_to_end); + hdmi_dma_mmap_copy(substream, 0, count - space_to_end); + } +} + +static void hdmi_sdma_callback(void *data) +{ + struct hdmi_dma_priv *priv = (struct hdmi_dma_priv *)data; + struct snd_pcm_substream *substream = priv->substream; + struct snd_pcm_runtime *runtime = substream->runtime; + unsigned long flags; + + spin_lock_irqsave(&priv->irq_lock, flags); + + if (runtime && runtime->dma_area && priv->tx_active) { + priv->offset += priv->period_bytes; + priv->offset %= priv->period_bytes * priv->periods; + + /* Copy data by period_bytes */ + hdmi_dma_data_copy(substream, priv, 'p'); + + snd_pcm_period_elapsed(substream); + } + + spin_unlock_irqrestore(&priv->irq_lock, flags); + + return; +} + +static int hdmi_dma_set_thrsld_incrtype(struct device *dev, int channels) +{ + u8 mask = HDMI_AHB_DMA_CONF0_BURST_MODE | HDMI_AHB_DMA_CONF0_INCR_TYPE_MASK; + u8 val = hdmi_readb(HDMI_AHB_DMA_CONF0) & ~mask; + int incr_type, threshold; + + switch (hdmi_readb(HDMI_REVISION_ID)) { + case 0x0a: + incr_type = HDMI_DMA_BURST_INCR4; + if (channels == 2) + threshold = 126; + else + threshold = 124; + break; + case 0x1a: + incr_type = HDMI_DMA_BURST_INCR8; + threshold = 128; + break; + default: + dev_err(dev, "unknown hdmi controller!\n"); + return -ENODEV; + } + + hdmi_writeb(threshold, HDMI_AHB_DMA_THRSLD); + + switch (incr_type) { + case HDMI_DMA_BURST_UNSPECIFIED_LEGNTH: + break; + case HDMI_DMA_BURST_INCR4: + val |= HDMI_AHB_DMA_CONF0_BURST_MODE; + break; + case HDMI_DMA_BURST_INCR8: + val |= HDMI_AHB_DMA_CONF0_BURST_MODE | + HDMI_AHB_DMA_CONF0_INCR8; + break; + case HDMI_DMA_BURST_INCR16: + val |= HDMI_AHB_DMA_CONF0_BURST_MODE | + HDMI_AHB_DMA_CONF0_INCR16; + break; + default: + dev_err(dev, "invalid increment type: %d!", incr_type); + return -EINVAL; + } + + hdmi_writeb(val, HDMI_AHB_DMA_CONF0); + + hdmi_audio_debug(dev, HDMI_AHB_DMA_THRSLD); + + return 0; +} + +static int hdmi_dma_configure_dma(struct device *dev, int channels) +{ + u8 i, val = 0; + int ret; + + if (channels <= 0 || channels > 8 || channels % 2 != 0) { + dev_err(dev, "unsupported channel number: %d\n", channels); + return -EINVAL; + } + + hdmi_audio_writeb(AHB_DMA_CONF0, EN_HLOCK, 0x1); + + ret = hdmi_dma_set_thrsld_incrtype(dev, channels); + if (ret) + return ret; + + for (i = 0; i < channels; i += 2) + val |= 0x3 << i; + + hdmi_writeb(val, HDMI_AHB_DMA_CONF1); + + return 0; +} + +static void hdmi_dma_init_iec_header(void) +{ + iec_header.U = 0; + + iec_header.B.consumer = 0; /* Consumer use */ + iec_header.B.linear_pcm = 0; /* linear pcm audio */ + iec_header.B.copyright = 1; /* no copyright */ + iec_header.B.pre_emphasis = 0; /* 2 channels without pre-emphasis */ + iec_header.B.mode = 0; /* Mode 0 */ + + iec_header.B.category_code = 0; + + iec_header.B.source = 2; /* stereo */ + iec_header.B.channel = 0; + + iec_header.B.sample_freq = 0x02; /* 48 KHz */ + iec_header.B.clock_acc = 0; /* Level II */ + + iec_header.B.word_length = 0x02; /* 16 bits */ + iec_header.B.org_sample_freq = 0x0D; /* 48 KHz */ + + iec_header.B.cgms_a = 0; /* Copying is permitted without restriction */ +} + +static int hdmi_dma_update_iec_header(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_pcm_runtime *runtime = substream->runtime; + struct hdmi_dma_priv *priv = runtime->private_data; + struct device *dev = rtd->platform->dev; + + iec_header.B.source = priv->channels; + + switch (priv->rate) { + case 32000: + iec_header.B.sample_freq = 0x03; + iec_header.B.org_sample_freq = 0x0C; + break; + case 44100: + iec_header.B.sample_freq = 0x00; + iec_header.B.org_sample_freq = 0x0F; + break; + case 48000: + iec_header.B.sample_freq = 0x02; + iec_header.B.org_sample_freq = 0x0D; + break; + case 88200: + iec_header.B.sample_freq = 0x08; + iec_header.B.org_sample_freq = 0x07; + break; + case 96000: + iec_header.B.sample_freq = 0x0A; + iec_header.B.org_sample_freq = 0x05; + break; + case 176400: + iec_header.B.sample_freq = 0x0C; + iec_header.B.org_sample_freq = 0x03; + break; + case 192000: + iec_header.B.sample_freq = 0x0E; + iec_header.B.org_sample_freq = 0x01; + break; + default: + dev_err(dev, "unsupported sample rate\n"); + return -EFAULT; + } + + switch (priv->format) { + case SNDRV_PCM_FORMAT_S16_LE: + iec_header.B.word_length = 0x02; + break; + case SNDRV_PCM_FORMAT_S24_LE: + iec_header.B.word_length = 0x0b; + break; + default: + return -EFAULT; + } + + return 0; +} + +/* + * The HDMI block transmits the audio data without adding any of the audio + * frame bits. So we have to copy the raw dma data from the ALSA buffer + * to the DMA buffer, adding the frame information. + */ +static int hdmi_dma_copy_user(struct snd_pcm_substream *substream, int channel, + unsigned long pos_bytes, void __user *buf, + unsigned long count) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct hdmi_dma_priv *priv = runtime->private_data; + u32 *hw_buf; + int subframe_idx; + u32 pcm_data; + + /* Adding frame info to pcm data from userspace and copy to hw_buffer */ + hw_buf = (u32 *)(priv->hw_buffer.area + (pos_bytes * priv->buffer_ratio)); + + while (count > 0) { + for (subframe_idx = 1 ; subframe_idx <= priv->channels ; subframe_idx++) { + if (copy_from_user(&pcm_data, buf, priv->sample_align)) + return -EFAULT; + + buf += priv->sample_align; + count -= priv->sample_align; + + /* Save the header info to the audio dma buffer */ + *hw_buf++ = hdmi_dma_add_frame_info(priv, pcm_data, subframe_idx); + } + + priv->frame_idx++; + if (priv->frame_idx == 192) + priv->frame_idx = 0; + } + + return 0; +} + +static int hdmi_sdma_initbuf(struct device *dev, struct hdmi_dma_priv *priv) +{ + struct hdmi_sdma_script *hdmi_sdma_t = priv->hdmi_sdma_t; + u32 *head, *tail, i; + + if (!hdmi_sdma_t) { + dev_err(dev, "hdmi private addr invalid!!!\n"); + return -EINVAL; + } + + hdmi_sdma_t->control_reg_addr = HDMI_BASE_ADDR + HDMI_AHB_DMA_START; + hdmi_sdma_t->status_reg_addr = HDMI_BASE_ADDR + HDMI_IH_AHBDMAAUD_STAT0; + hdmi_sdma_t->dma_start_addr = HDMI_BASE_ADDR + HDMI_AHB_DMA_STRADDR0; + + head = &hdmi_sdma_t->buffer[0]; + tail = &hdmi_sdma_t->buffer[1]; + + for (i = 0; i < priv->sdma_params.buffer_num; i++) { + *head = priv->hw_buffer.addr + i * priv->period_bytes * priv->buffer_ratio; + *tail = *head + priv->dma_period_bytes - 1; + head += 2; + tail += 2; + } + + return 0; +} + +static int hdmi_sdma_config(struct snd_pcm_substream *substream, + struct hdmi_dma_priv *priv) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct device *dai_dev = &priv->pdev->dev; + struct device *dev = rtd->platform->dev; + struct dma_slave_config slave_config; + int ret; + + priv->dma_channel = dma_request_slave_channel(dai_dev, "tx"); + if (priv->dma_channel == NULL) { + dev_err(dev, "failed to alloc dma channel\n"); + return -EBUSY; + } + + slave_config.direction = DMA_TRANS_NONE; + slave_config.src_addr = (dma_addr_t)priv->sdma_params.buffer_num; + slave_config.dst_addr = (dma_addr_t)priv->sdma_params.phyaddr; + + ret = dmaengine_slave_config(priv->dma_channel, &slave_config); + if (ret) { + dev_err(dev, "failed to config slave dma\n"); + return -EINVAL; + } + + return 0; +} + +static int hdmi_dma_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct hdmi_dma_priv *priv = runtime->private_data; + + if (priv->dma_channel) { + dma_release_channel(priv->dma_channel); + priv->dma_channel = NULL; + } + + return 0; +} + +static int hdmi_dma_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct hdmi_dma_priv *priv = runtime->private_data; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct device *dev = rtd->platform->dev; + int ret; + + priv->buffer_bytes = params_buffer_bytes(params); + priv->periods = params_periods(params); + priv->period_bytes = params_period_bytes(params); + priv->channels = params_channels(params); + priv->format = params_format(params); + priv->rate = params_rate(params); + + priv->offset = 0; + priv->period_time = HZ / (priv->rate / params_period_size(params)); + + switch (priv->format) { + case SNDRV_PCM_FORMAT_S16_LE: + priv->buffer_ratio = 2; + priv->sample_align = 2; + priv->sample_bits = 16; + break; + case SNDRV_PCM_FORMAT_S24_LE: + /* 24 bit audio in 32 bit word */ + priv->buffer_ratio = 1; + priv->sample_align = 4; + priv->sample_bits = 24; + break; + default: + dev_err(dev, "unsupported sample format: %d\n", priv->format); + return -EINVAL; + } + + priv->dma_period_bytes = priv->period_bytes * priv->buffer_ratio; + priv->sdma_params.buffer_num = priv->periods; + priv->sdma_params.phyaddr = priv->phy_hdmi_sdma_t; + + ret = hdmi_sdma_initbuf(dev, priv); + if (ret) + return ret; + + ret = hdmi_sdma_config(substream, priv); + if (ret) + return ret; + + snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); + + ret = hdmi_dma_configure_dma(dev, priv->channels); + if (ret) + return ret; + + hdmi_dma_set_addr(priv->hw_buffer.addr, priv->dma_period_bytes); + + dumppriv(dev, priv); + + hdmi_dma_update_iec_header(substream); + + /* Init par for mmap optimizate */ + init_table(priv->channels); + + priv->appl_bytes = 0; + + return 0; +} + +static void hdmi_dma_trigger_init(struct snd_pcm_substream *substream, + struct hdmi_dma_priv *priv) +{ + unsigned long status; + + priv->offset = 0; + priv->frame_idx = 0; + + /* Copy data by buffer_bytes */ + hdmi_dma_data_copy(substream, priv, 'b'); + + hdmi_audio_writeb(AHB_DMA_CONF0, SW_FIFO_RST, 0x1); + + /* Delay after reset */ + udelay(1); + + status = hdmi_readb(HDMI_IH_AHBDMAAUD_STAT0); + hdmi_writeb(status, HDMI_IH_AHBDMAAUD_STAT0); +} + +static int hdmi_dma_prepare_and_submit(struct snd_pcm_substream *substream, + struct hdmi_dma_priv *priv) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct device *dev = rtd->platform->dev; + + priv->desc = dmaengine_prep_dma_cyclic(priv->dma_channel, 0, 0, 0, + DMA_TRANS_NONE, 0); + if (!priv->desc) { + dev_err(dev, "failed to prepare slave dma\n"); + return -EINVAL; + } + + priv->desc->callback = hdmi_sdma_callback; + priv->desc->callback_param = (void *)priv; + dmaengine_submit(priv->desc); + + return 0; +} + +static int hdmi_dma_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct hdmi_dma_priv *priv = runtime->private_data; + struct device *dev = rtd->platform->dev; + int ret; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + if (!check_hdmi_state()) + return 0; + hdmi_dma_trigger_init(substream, priv); + + dumpregs(dev); + + priv->tx_active = true; + hdmi_audio_writeb(AHB_DMA_START, START, 0x1); + hdmi_dma_irq_set(false); + hdmi_set_dma_mode(1); + ret = hdmi_dma_prepare_and_submit(substream, priv); + if (ret) + return ret; + dma_async_issue_pending(priv->desc->chan); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + dmaengine_terminate_all(priv->dma_channel); + hdmi_set_dma_mode(0); + hdmi_dma_irq_set(true); + hdmi_audio_writeb(AHB_DMA_STOP, STOP, 0x1); + priv->tx_active = false; + break; + default: + return -EINVAL; + } + + return 0; +} + +static snd_pcm_uframes_t hdmi_dma_pointer(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct hdmi_dma_priv *priv = runtime->private_data; + + return bytes_to_frames(runtime, priv->offset); +} + +static struct snd_pcm_hardware snd_imx_hardware = { + .info = SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_PAUSE | + SNDRV_PCM_INFO_RESUME, + .formats = MXC_HDMI_FORMATS_PLAYBACK, + .rate_min = 32000, + .channels_min = 2, + .channels_max = 8, + .buffer_bytes_max = HDMI_PCM_BUF_SIZE, + .period_bytes_min = HDMI_DMA_PERIOD_BYTES / 2, + .period_bytes_max = HDMI_DMA_PERIOD_BYTES / 2, + .periods_min = 8, + .periods_max = 8, + .fifo_size = 0, +}; + +static void hdmi_dma_irq_enable(struct hdmi_dma_priv *priv) +{ + unsigned long flags; + + hdmi_writeb(0xff, HDMI_AHB_DMA_POL); + hdmi_writeb(0xff, HDMI_AHB_DMA_BUFFPOL); + + spin_lock_irqsave(&priv->irq_lock, flags); + + hdmi_writeb(0xff, HDMI_IH_AHBDMAAUD_STAT0); + hdmi_writeb(0xff, HDMI_IH_MUTE_AHBDMAAUD_STAT0); + hdmi_dma_irq_set(false); + hdmi_mask(0); + + spin_unlock_irqrestore(&priv->irq_lock, flags); +} + +static void hdmi_dma_irq_disable(struct hdmi_dma_priv *priv) +{ + unsigned long flags; + + spin_lock_irqsave(&priv->irq_lock, flags); + + hdmi_dma_irq_set(true); + hdmi_writeb(0x0, HDMI_IH_MUTE_AHBDMAAUD_STAT0); + hdmi_writeb(0xff, HDMI_IH_AHBDMAAUD_STAT0); + hdmi_mask(1); + + spin_unlock_irqrestore(&priv->irq_lock, flags); +} + +static int hdmi_dma_open(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct device *dev = rtd->platform->dev; + struct hdmi_dma_priv *priv = dev_get_drvdata(dev); + int ret; + + runtime->private_data = priv; + + ret = mxc_hdmi_register_audio(substream); + if (ret < 0) { + dev_err(dev, "HDMI Video is not ready!\n"); + return ret; + } + + hdmi_audio_writeb(AHB_DMA_CONF0, SW_FIFO_RST, 0x1); + + ret = snd_pcm_hw_constraint_integer(substream->runtime, + SNDRV_PCM_HW_PARAM_PERIODS); + if (ret < 0) + return ret; + + snd_soc_set_runtime_hwparams(substream, &snd_imx_hardware); + + hdmi_dma_irq_enable(priv); + + return 0; +} + +static int hdmi_dma_close(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct hdmi_dma_priv *priv = runtime->private_data; + + hdmi_dma_irq_disable(priv); + mxc_hdmi_unregister_audio(substream); + + return 0; +} + +static struct snd_pcm_ops imx_hdmi_dma_pcm_ops = { + .open = hdmi_dma_open, + .close = hdmi_dma_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = hdmi_dma_hw_params, + .hw_free = hdmi_dma_hw_free, + .trigger = hdmi_dma_trigger, + .pointer = hdmi_dma_pointer, + .copy_user = hdmi_dma_copy_user, +}; + +static int imx_hdmi_dma_pcm_new(struct snd_soc_pcm_runtime *rtd) +{ + struct hdmi_dma_priv *priv = dev_get_drvdata(rtd->platform->dev); + struct snd_card *card = rtd->card->snd_card; + struct snd_pcm_substream *substream; + struct snd_pcm *pcm = rtd->pcm; + u64 dma_mask = DMA_BIT_MASK(32); + int ret = 0; + + if (!card->dev->dma_mask) + card->dev->dma_mask = &dma_mask; + if (!card->dev->coherent_dma_mask) + card->dev->coherent_dma_mask = DMA_BIT_MASK(32); + + substream = pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream; + + ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, pcm->card->dev, + HDMI_PCM_BUF_SIZE, &substream->dma_buffer); + if (ret) { + dev_err(card->dev, "failed to alloc playback dma buffer\n"); + return ret; + } + + priv->substream = substream; + + /* Alloc the hw_buffer */ + ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, pcm->card->dev, + HDMI_DMA_BUF_SIZE, &priv->hw_buffer); + if (ret) { + dev_err(card->dev, "failed to alloc hw dma buffer\n"); + return ret; + } + + return ret; +} + +static void imx_hdmi_dma_pcm_free(struct snd_pcm *pcm) +{ + int stream = SNDRV_PCM_STREAM_PLAYBACK; + struct snd_pcm_substream *substream = pcm->streams[stream].substream; + struct snd_soc_pcm_runtime *rtd = pcm->private_data; + struct hdmi_dma_priv *priv = dev_get_drvdata(rtd->platform->dev); + + if (substream) { + snd_dma_free_pages(&substream->dma_buffer); + substream->dma_buffer.area = NULL; + substream->dma_buffer.addr = 0; + } + + /* Free the hw_buffer */ + snd_dma_free_pages(&priv->hw_buffer); + priv->hw_buffer.area = NULL; + priv->hw_buffer.addr = 0; +} + +static struct snd_soc_platform_driver imx_hdmi_platform = { + .ops = &imx_hdmi_dma_pcm_ops, + .pcm_new = imx_hdmi_dma_pcm_new, + .pcm_free = imx_hdmi_dma_pcm_free, +}; + +static int imx_soc_platform_probe(struct platform_device *pdev) +{ + struct imx_hdmi *hdmi_drvdata = platform_get_drvdata(pdev); + struct hdmi_dma_priv *priv; + int ret = 0; + + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) { + dev_err(&pdev->dev, "Failed to alloc hdmi_dma\n"); + return -ENOMEM; + } + + priv->hdmi_sdma_t = dma_alloc_coherent(NULL, + sizeof(struct hdmi_sdma_script), + &priv->phy_hdmi_sdma_t, GFP_KERNEL); + if (!priv->hdmi_sdma_t) { + dev_err(&pdev->dev, "Failed to alloc hdmi_sdma_t\n"); + return -ENOMEM; + } + + priv->tx_active = false; + spin_lock_init(&priv->irq_lock); + + priv->pdev = hdmi_drvdata->pdev; + + hdmi_dma_init_iec_header(); + + dev_set_drvdata(&pdev->dev, priv); + + switch (hdmi_readb(HDMI_REVISION_ID)) { + case 0x0a: + snd_imx_hardware.period_bytes_max = HDMI_DMA_PERIOD_BYTES / 4; + snd_imx_hardware.period_bytes_min = HDMI_DMA_PERIOD_BYTES / 4; + break; + default: + break; + } + + ret = snd_soc_register_platform(&pdev->dev, &imx_hdmi_platform); + if (ret) + goto err_plat; + + return 0; + +err_plat: + dma_free_coherent(NULL, sizeof(struct hdmi_sdma_script), + priv->hdmi_sdma_t, priv->phy_hdmi_sdma_t); + + return ret; +} + +static int imx_soc_platform_remove(struct platform_device *pdev) +{ + struct hdmi_dma_priv *priv = dev_get_drvdata(&pdev->dev); + + dma_free_coherent(NULL, sizeof(struct hdmi_sdma_script), + priv->hdmi_sdma_t, priv->phy_hdmi_sdma_t); + + snd_soc_unregister_platform(&pdev->dev); + + return 0; +} + +static struct platform_driver imx_hdmi_dma_driver = { + .driver = { + .name = "imx-hdmi-audio", + .owner = THIS_MODULE, + }, + .probe = imx_soc_platform_probe, + .remove = imx_soc_platform_remove, +}; + +module_platform_driver(imx_hdmi_dma_driver); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("i.MX HDMI audio DMA"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/fsl/imx-hdmi.c b/sound/soc/fsl/imx-hdmi.c new file mode 100644 index 000000000000..a66ea0d6355e --- /dev/null +++ b/sound/soc/fsl/imx-hdmi.c @@ -0,0 +1,114 @@ +/* + * ASoC HDMI Transmitter driver for IMX development boards + * + * Copyright (C) 2011-2016 Freescale Semiconductor, Inc. + * + * based on stmp3780_devb_hdmi.c + * + * Vladimir Barinov <vbarinov@embeddedalley.com> + * + * Copyright 2008 SigmaTel, Inc + * Copyright 2008 Embedded Alley Solutions, Inc + * + * This file is licensed under the terms of the GNU General Public License + * version 2. This program is licensed "as is" without any warranty of any + * kind, whether express or implied. + */ + +#include <linux/module.h> +#include <linux/of_platform.h> +#include <linux/mfd/mxc-hdmi-core.h> +#include <sound/soc.h> + +#include "imx-hdmi.h" + +/* imx digital audio interface glue - connects codec <--> CPU */ +static struct snd_soc_dai_link imx_hdmi_dai_link = { + .name = "i.MX HDMI Audio Tx", + .stream_name = "i.MX HDMI Audio Tx", + .codec_dai_name = "hdmi-hifi", + .codec_name = "hdmi-audio-codec", + .platform_name = "imx-hdmi-audio", +}; + +static struct snd_soc_card snd_soc_card_imx_hdmi = { + .name = "imx-hdmi-soc", + .dai_link = &imx_hdmi_dai_link, + .num_links = 1, + .owner = THIS_MODULE, +}; + +static int imx_hdmi_audio_probe(struct platform_device *pdev) +{ + struct device_node *hdmi_np, *np = pdev->dev.of_node; + struct snd_soc_card *card = &snd_soc_card_imx_hdmi; + struct platform_device *hdmi_pdev; + int ret = 0; + + if (!hdmi_get_registered()) { + dev_err(&pdev->dev, "initialize HDMI-audio failed. load HDMI-video first!\n"); + return -ENODEV; + } + + hdmi_np = of_parse_phandle(np, "hdmi-controller", 0); + if (!hdmi_np) { + dev_err(&pdev->dev, "failed to find hdmi-audio cpudai\n"); + ret = -EINVAL; + goto end; + } + + hdmi_pdev = of_find_device_by_node(hdmi_np); + if (!hdmi_pdev) { + dev_err(&pdev->dev, "failed to find SSI platform device\n"); + ret = -EINVAL; + goto end; + } + + card->dev = &pdev->dev; + card->dai_link->cpu_dai_name = dev_name(&hdmi_pdev->dev); + + platform_set_drvdata(pdev, card); + + ret = snd_soc_register_card(card); + if (ret) + dev_err(&pdev->dev, "failed to register card: %d\n", ret); + +end: + if (hdmi_np) + of_node_put(hdmi_np); + + return ret; +} + +static int imx_hdmi_audio_remove(struct platform_device *pdev) +{ + struct snd_soc_card *card = platform_get_drvdata(pdev); + + snd_soc_unregister_card(card); + + return 0; +} + +static const struct of_device_id imx_hdmi_dt_ids[] = { + { .compatible = "fsl,imx-audio-hdmi", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, imx_hdmi_dt_ids); + +static struct platform_driver imx_hdmi_audio_driver = { + .probe = imx_hdmi_audio_probe, + .remove = imx_hdmi_audio_remove, + .driver = { + .of_match_table = imx_hdmi_dt_ids, + .name = "imx-audio-hdmi", + .owner = THIS_MODULE, + .pm = &snd_soc_pm_ops, + }, +}; + +module_platform_driver(imx_hdmi_audio_driver); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("IMX HDMI TX ASoC driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:imx-audio-hdmi"); diff --git a/sound/soc/fsl/imx-hdmi.h b/sound/soc/fsl/imx-hdmi.h new file mode 100644 index 000000000000..d06ce9c34d32 --- /dev/null +++ b/sound/soc/fsl/imx-hdmi.h @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2011-2014 Freescale Semiconductor, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef __IMX_HDMI_H +#define __IMX_HDMI_H + +struct imx_hdmi_sdma_params { + dma_addr_t phyaddr; + u32 buffer_num; + int dma; +}; + +struct imx_hdmi { + struct snd_soc_dai_driver cpu_dai_drv; + struct platform_device *codec_dev; + struct platform_device *dma_dev; + struct platform_device *pdev; + struct clk *isfr_clk; + struct clk *iahb_clk; + struct clk *mipi_core_clk; +}; + +#define HDMI_MAX_RATES 7 +#define HDMI_MAX_SAMPLE_SIZE 3 +#define HDMI_MAX_CHANNEL_CONSTRAINTS 4 + +#define MXC_HDMI_RATES_PLAYBACK \ + (SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 | \ + SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000 | \ + SNDRV_PCM_RATE_176400 | SNDRV_PCM_RATE_192000) + +#define MXC_HDMI_FORMATS_PLAYBACK \ + (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE) + +union hdmi_audio_header_t { + uint64_t U; + struct { + unsigned consumer:1; + unsigned linear_pcm:1; + unsigned copyright:1; + unsigned pre_emphasis:3; + unsigned mode:2; + + unsigned category_code:8; + + unsigned source:4; + unsigned channel:4; + + unsigned sample_freq:4; + unsigned clock_acc:2; + unsigned reserved0:2; + + unsigned word_length:4; + unsigned org_sample_freq:4; + + unsigned cgms_a:2; + unsigned reserved1:6; + + unsigned reserved2:8; + + unsigned reserved3:8; + } B; + unsigned char status[8]; +}; + +union hdmi_audio_dma_data_t { + uint32_t U; + struct { + unsigned data:24; + unsigned v:1; + unsigned u:1; + unsigned c:1; + unsigned p:1; + unsigned b:1; + unsigned reserved:3; + } B; +}; + +extern union hdmi_audio_header_t iec_header; + +#define hdmi_audio_writeb(reg, bit, val) \ + do { \ + hdmi_mask_writeb(val, HDMI_ ## reg, \ + HDMI_ ## reg ## _ ## bit ## _OFFSET, \ + HDMI_ ## reg ## _ ## bit ## _MASK); \ + pr_debug("Set reg: HDMI_" #reg " (0x%x) "\ + "bit: HDMI_" #reg "_" #bit " (%d) to val: %x\n", \ + HDMI_ ## reg, HDMI_ ## reg ## _ ## bit ## _OFFSET, val); \ + } while (0) + +#endif /* __IMX_HDMI_H */ |