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>5.4-rM2-2.2.x-imx-squashed
parent
219d54332a
commit
ef0a172403
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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");
|
|
@ -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
|
File diff suppressed because it is too large
Load Diff
|
@ -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");
|
|
@ -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 */
|
Loading…
Reference in New Issue