// SPDX-License-Identifier: GPL-2.0 /* * Copyright (c) 2019 MediaTek Inc. * Author: Michael Hsiao */ /******************************************************************************* * * Filename: * --------- * mt_soc_pcm_hdmi.c * * Project: * -------- * Audio Driver Kernel Function * * Description: * ------------ * Audio hdmi playback * * Author: * ------- * Chipeng Chang * *------------------------------------------------------------------------------ * * ****************************************************************************** */ /***************************************************************************** * C O M P I L E R F L A G S *****************************************************************************/ /***************************************************************************** * E X T E R N A L R E F E R E N C E S *****************************************************************************/ #include "mt6799-hdmi.h" #include "mtk-auddrv-afe.h" #include "mtk-auddrv-ana.h" #include "mtk-auddrv-clk.h" #include "mtk-auddrv-def.h" #include "mtk-auddrv-kernel.h" #include "mtk-soc-afe-control.h" #include "mtk-soc-hdmi-type.h" #include "mtk-soc-pcm-common.h" #include "mtk-soc-pcm-common.h" #include "mtk-soc-pcm-platform.h" #include /* information about */ static struct afe_mem_control_t *pMemControl; static bool mHDMIPrepareDone; static struct audio_hdmi_format mAudioHDMIFormat; static struct snd_dma_buffer *HDMI_dma_buf; static DEFINE_SPINLOCK(auddrv_hdmi_lock); /* * function implementation */ static int mtk_hdmi_probe(struct platform_device *pdev); static int mtk_pcm_hdmi_close(struct snd_pcm_substream *substream); static int mtk_afe_hdmi_component_probe(struct snd_soc_component *component); #define _DEBUG_TDM_KERNEL_ 1 static unsigned int table_sgen_golden_values[64] = { 0x0FE50FE5, 0x285E1C44, 0x3F4A285E, 0x53C73414, 0x650C3F4A, 0x726F49E3, 0x7B6C53C7, 0x7FAB5CDC, 0x7F02650C, 0x79776C43, 0x6F42726F, 0x60C67781, 0x4E917B6C, 0x39587E27, 0x21EB7FAB, 0x09307FF4, 0xF01A7F02, 0xD7A17CD6, 0xC0B67977, 0xAC3874ED, 0x9AF36F42, 0x8D906884, 0x849360C6, 0x80545818, 0x80FD4E91, 0x86884449, 0x90BD3958, 0x9F3A2DDA, 0xB16E21EB, 0xC6A715A8, 0xDE140930, 0xF6CFFCA1, 0x0FE5F01A, 0x285EE3BB, 0x3F4AD7A1, 0x53C7CBEB, 0x650CC0B6, 0x726FB61C, 0x7B6CAC38, 0x7FABA323, 0x7F029AF3, 0x797793BC, 0x6F428D90, 0x60C6887E, 0x4E918493, 0x395881D8, 0x21EB8054, 0x0930800B, 0xF01A80FD, 0xD7A18329, 0xC0B68688, 0xAC388B12, 0x9AF390BD, 0x8D90977B, 0x84939F3A, 0x8054A7E7, 0x80FDB16E, 0x8688BBB6, 0x90BDC6A7, 0x9F3AD225, 0xB16EDE14, 0xC6A7EA57, 0xDE14F6CF, 0xF6CF035E}; static unsigned int table_sgen_4ch_golden_values[128] = { /* step 2 1 4 3 */ /* ch2 ch1 ch4 ch3 */ 0x0FE50FE5, 0x0FE50FE5, 0x285E1C44, 0x3F4A3414, 0x3F4A285E, 0x650C53C7, 0x53C73414, 0x7B6C6C43, 0x650C3F4A, 0x7F027B6C, 0x726F49E3, 0x6F427FF4, 0x7B6C53C7, 0x4E917977, 0x7FAB5CDC, 0x21EB6884, 0x7F02650C, 0xF01A4E91, 0x79776C43, 0xC0B62DDA, 0x6F42726F, 0x9AF30930, 0x60C67781, 0x8493E3BB, 0x4E917B6C, 0x80FDC0B6, 0x39587E27, 0x90BDA323, 0x21EB7FAB, 0xB16E8D90, 0x09307FF4, 0xDE1481D8, 0xF01A7F02, 0x0FE580FD, 0xD7A17CD6, 0x0FE58B12, 0xC0B67977, 0x3F4A9F3A, 0xAC3874ED, 0x650CBBB6, 0x9AF36F42, 0x7B6CDE14, 0x8D906884, 0x7F02035E, 0x849360C6, 0x6F420FE5, 0x80545818, 0x4E913414, 0x80FD4E91, 0x21EB53C7, 0x86884449, 0xF01A6C43, 0x90BD3958, 0xC0B67B6C, 0x9F3A2DDA, 0x9AF37FF4, 0xB16E21EB, 0x84937977, 0xC6A715A8, 0x80FD6884, 0xDE140930, 0x90BD4E91, 0xF6CFFCA1, 0xB16E2DDA, 0x0FE5F01A, 0xDE140930, 0x285EE3BB, 0x0FE5E3BB, 0x3F4AD7A1, 0x0FE5C0B6, 0x53C7CBEB, 0x3F4AA323, 0x650CC0B6, 0x650C8D90, 0x726FB61C, 0x7B6C81D8, 0x7B6CAC38, 0x7F0280FD, 0x7FABA323, 0x6F428B12, 0x7F029AF3, 0x4E919F3A, 0x797793BC, 0x21EBBBB6, 0x6F428D90, 0xF01ADE14, 0x60C6887E, 0xC0B6035E, 0x4E918493, 0x9AF30FE5, 0x395881D8, 0x84933414, 0x21EB8054, 0x80FD53C7, 0x0930800B, 0x90BD6C43, 0xF01A80FD, 0xB16E7B6C, 0xD7A18329, 0xDE147FF4, 0xC0B68688, 0x0FE57977, 0xAC388B12, 0x0FE56884, 0x9AF390BD, 0x3F4A4E91, 0x8D90977B, 0x650C2DDA, 0x84939F3A, 0x7B6C0930, 0x8054A7E7, 0x7F02E3BB, 0x80FDB16E, 0x6F42C0B6, 0x8688BBB6, 0x4E91A323, 0x90BDC6A7, 0x21EB8D90, 0x9F3AD225, 0xF01A81D8, 0xB16EDE14, 0xC0B680FD, 0xC6A7EA57, 0x9AF38B12, 0xDE14F6CF, 0x84939F3A, 0xF6CF035E, 0x80FDBBB6}; static unsigned int table_sgen_8ch_golden_values[] = { 0x0FE50000, 0x0FE50FE5, 0x0FE50FE5, 0x0FE50FE5, 0x285E0000, 0x3F4A3414, 0x285E1C44, 0x3F4A3414, 0x3F4A0000, 0x650C53C7, 0x3F4A285E, 0x650C53C7, 0x53C70000, 0x7B6C6C43, 0x53C73414, 0x7B6C6C43, 0x650C0000, 0x7F027B6C, 0x650C3F4A, 0x7F027B6C, 0x726F0000, 0x6F427FF4, 0x726F49E3, 0x6F427FF4, 0x7B6C0000, 0x4E917977, 0x7B6C53C7, 0x4E917977, 0x7FAB0000, 0x21EB6884, 0x7FAB5CDC, 0x21EB6884, 0x7F020000, 0xF01A4E91, 0x7F02650C, 0xF01A4E91, 0x79770000, 0xC0B62DDA, 0x79776C43, 0xC0B62DDA, 0x6F420000, 0x9AF30930, 0x6F42726F, 0x9AF30930, 0x60C60000, 0x8493E3BB, 0x60C67781, 0x8493E3BB, 0x4E910000, 0x80FDC0B6, 0x4E917B6C, 0x80FDC0B6, 0x39580000, 0x90BDA323, 0x39587E27, 0x90BDA323, 0x21EB0000, 0xB16E8D90, 0x21EB7FAB, 0xB16E8D90, 0x09300000, 0xDE1481D8, 0x09307FF4, 0xDE1481D8, 0xF01A0000, 0x0FE580FD, 0xF01A7F02, 0x0FE580FD, 0xD7A10000, 0x0FE58B12, 0xD7A17CD6, 0x0FE58B12, 0xC0B60000, 0x3F4A9F3A, 0xC0B67977, 0x3F4A9F3A, 0xAC380000, 0x650CBBB6, 0xAC3874ED, 0x650CBBB6, 0x9AF30000, 0x7B6CDE14, 0x9AF36F42, 0x7B6CDE14, 0x8D900000, 0x7F02035E, 0x8D906884, 0x7F02035E, 0x84930000, 0x6F420FE5, 0x849360C6, 0x6F420FE5, 0x80540000, 0x4E913414, 0x80545818, 0x4E913414, 0x80FD0000, 0x21EB53C7, 0x80FD4E91, 0x21EB53C7, 0x86880000, 0xF01A6C43, 0x86884449, 0xF01A6C43, 0x90BD0000, 0xC0B67B6C, 0x90BD3958, 0xC0B67B6C, 0x9F3A0000, 0x9AF37FF4, 0x9F3A2DDA, 0x9AF37FF4, 0xB16E0000, 0x84937977, 0xB16E21EB, 0x84937977, 0xC6A70000, 0x80FD6884, 0xC6A715A8, 0x80FD6884, 0xDE140000, 0x90BD4E91, 0xDE140930, 0x90BD4E91, 0xF6CF0000, 0xB16E2DDA, 0xF6CFFCA1, 0xB16E2DDA, 0x0FE50000, 0xDE140930, 0x0FE5F01A, 0xDE140930, 0x285E0000, 0x0FE5E3BB, 0x285EE3BB, 0x0FE5E3BB, 0x3F4A0000, 0x0FE5C0B6, 0x3F4AD7A1, 0x0FE5C0B6, 0x53C70000, 0x3F4AA323, 0x53C7CBEB, 0x3F4AA323, 0x650C0000, 0x650C8D90, 0x650CC0B6, 0x650C8D90, 0x726F0000, 0x7B6C81D8, 0x726FB61C, 0x7B6C81D8, 0x7B6C0000, 0x7F0280FD, 0x7B6CAC38, 0x7F0280FD, 0x7FAB0000, 0x6F428B12, 0x7FABA323, 0x6F428B12, 0x7F020000, 0x4E919F3A, 0x7F029AF3, 0x4E919F3A, 0x79770000, 0x21EBBBB6, 0x797793BC, 0x21EBBBB6, 0x6F420000, 0xF01ADE14, 0x6F428D90, 0xF01ADE14, 0x60C60000, 0xC0B6035E, 0x60C6887E, 0xC0B6035E, 0x4E910000, 0x9AF30FE5, 0x4E918493, 0x9AF30FE5, 0x39580000, 0x84933414, 0x395881D8, 0x84933414, 0x21EB0000, 0x80FD53C7, 0x21EB8054, 0x80FD53C7, 0x09300000, 0x90BD6C43, 0x0930800B, 0x90BD6C43, 0xF01A0000, 0xB16E7B6C, 0xF01A80FD, 0xB16E7B6C, 0xD7A10000, 0xDE147FF4, 0xD7A18329, 0xDE147FF4, 0xC0B60000, 0x0FE57977, 0xC0B68688, 0x0FE57977, 0xAC380000, 0x0FE56884, 0xAC388B12, 0x0FE56884, 0x9AF30000, 0x3F4A4E91, 0x9AF390BD, 0x3F4A4E91, 0x8D900000, 0x650C2DDA, 0x8D90977B, 0x650C2DDA, 0x84930000, 0x7B6C0930, 0x84939F3A, 0x7B6C0930, 0x80540000, 0x7F02E3BB, 0x8054A7E7, 0x7F02E3BB, 0x80FD0000, 0x6F42C0B6, 0x80FDB16E, 0x6F42C0B6, 0x86880000, 0x4E91A323, 0x8688BBB6, 0x4E91A323, 0x90BD0000, 0x21EB8D90, 0x90BDC6A7, 0x21EB8D90, 0x9F3A0000, 0xF01A81D8, 0x9F3AD225, 0xF01A81D8, 0xB16E0000, 0xC0B680FD, 0xB16EDE14, 0xC0B680FD, 0xC6A70000, 0x9AF38B12, 0xC6A7EA57, 0x9AF38B12, 0xDE140000, 0x84939F3A, 0xDE14F6CF, 0x84939F3A, 0xF6CF0000, 0x80FDBBB6, 0xF6CF035E, 0x80FDBBB6, }; static void copysinewavetohdmi(unsigned int channels) { uint8_t *Bufferaddr; int Hdmi_Buffer_length; unsigned int arraybytes; unsigned int *SinewaveArr; int i; Bufferaddr = HDMI_dma_buf->area; Hdmi_Buffer_length = HDMI_dma_buf->bytes; SinewaveArr = NULL; arraybytes = 0; i = 0; if (channels == 2) { arraybytes = ARRAY_SIZE(table_sgen_golden_values) * sizeof(table_sgen_golden_values[0]); SinewaveArr = table_sgen_golden_values; } if (channels == 4) { arraybytes = ARRAY_SIZE(table_sgen_4ch_golden_values) * sizeof(table_sgen_4ch_golden_values[0]); SinewaveArr = table_sgen_4ch_golden_values; } if (channels == 8) { arraybytes = ARRAY_SIZE(table_sgen_8ch_golden_values) * sizeof(table_sgen_8ch_golden_values[0]); SinewaveArr = table_sgen_8ch_golden_values; } if (channels == 0) { memset_io((void *)(Bufferaddr), 0x7f7f7f7f, Hdmi_Buffer_length); /* using for observe data */ pr_info("use fix pattern Bufferaddr = %p Hhdmi_Buffer_length = %d\n", Bufferaddr, Hdmi_Buffer_length); return; } pr_debug("%s buffer area = %p arraybytes = %d bufferlength = %zu\n", __func__, HDMI_dma_buf->area, arraybytes, HDMI_dma_buf->bytes); for (i = 0; i < HDMI_dma_buf->bytes; i += arraybytes) { memcpy((void *)(Bufferaddr + i), (void *)SinewaveArr, arraybytes); } for (i = 0; i < 512; i++) pr_debug("Bufferaddr[%d] = %x\n", i, *(Bufferaddr + i)); } static void SetHDMIAddress(void) { Afe_Set_Reg(AFE_HDMI_BASE, HDMI_dma_buf->addr, 0xffffffff); Afe_Set_Reg(AFE_HDMI_END, HDMI_dma_buf->addr + (HDMI_dma_buf->bytes - 1), 0xffffffff); } static int mHdmi_sidegen_control; static int mHdmi_display_control; static const char *const HDMI_SIDEGEN[] = {"Off", "On"}; static const char *const HDMI_DISPLAY[] = {"MHL", "SLIMPORT"}; static bool irq5_user; static const struct soc_enum Audio_Hdmi_Enum[] = { SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(HDMI_SIDEGEN), HDMI_SIDEGEN), SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(HDMI_DISPLAY), HDMI_DISPLAY), }; static int Audio_hdmi_SideGen_Get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { pr_debug("%s(), mHdmi_sidegen_control = %d\n", __func__, mHdmi_sidegen_control); ucontrol->value.integer.value[0] = mHdmi_sidegen_control; return 0; } bool set_tdm_lrck_inverse_by_channel(int out_channel) { if (out_channel > 2) return SetTDMLrckInverse(false); else return SetTDMLrckInverse(true); } static int Audio_hdmi_SideGen_Set(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct audio_hdmi_format *ptrAudioHDMIFormat; unsigned int runsamplerate = 44100; unsigned int outchannel = 2; /* unsigned int HDMIchannel = 8; */ snd_pcm_format_t format = SNDRV_PCM_FORMAT_S16_LE; ptrAudioHDMIFormat = &mAudioHDMIFormat; if (ucontrol->value.enumerated.item[0] > ARRAY_SIZE(HDMI_SIDEGEN)) { pr_err("return -EINVAL\n"); return -EINVAL; } mHdmi_sidegen_control = ucontrol->value.integer.value[0]; if (mHdmi_sidegen_control) { unsigned int MclkDiv = 0; pr_debug("%s(),mHdmi_sidegen_control\n", __func__); /* Open */ AudDrv_Clk_On(); mtk_Hdmi_Configuration_Init((void *)ptrAudioHDMIFormat); mtk_Hdmi_Clock_Set((void *)ptrAudioHDMIFormat); EnableApll1(true); EnableApll2(true); EnableI2SCLKDiv(ptrAudioHDMIFormat->mI2S_MCKDIV, true); EnableI2SCLKDiv(ptrAudioHDMIFormat->mI2S_BCKDIV, true); AudDrv_APLL1Tuner_Clk_On(); AudDrv_APLL2Tuner_Clk_On(); /* Hw params */ SetHighAddr(Soc_Aud_Digital_Block_MEM_HDMI, true, HDMI_dma_buf->addr); AudDrv_Emi_Clk_On(); SetHDMIAddress(); /* Prepare */ mtk_Hdmi_Configuration_Set((void *)ptrAudioHDMIFormat, runsamplerate, outchannel, format, mHdmi_display_control); MclkDiv = SetCLkMclk(ptrAudioHDMIFormat->mI2Snum, runsamplerate); /* SET hdmi channels , samplerate and formats */ SetMemIfFetchFormatPerSample( Soc_Aud_Digital_Block_MEM_HDMI, ptrAudioHDMIFormat->mMemIfFetchFormatPerSample); SetHDMIdatalength(ptrAudioHDMIFormat->mHDMI_Data_Lens); SetTDMDatalength(ptrAudioHDMIFormat->mTDM_Data_Lens); /*SetCLkBclk(MclkDiv, runtime->rate, runtime->channels, * Soc_Aud_I2S_WLEN_WLEN_16BITS); */ SetCLkBclk(MclkDiv, runsamplerate, outchannel, ptrAudioHDMIFormat->mClock_Data_Lens); SetTDMLrckWidth(ptrAudioHDMIFormat->mTDM_LRCK); SetTDMbckcycle(ptrAudioHDMIFormat->mClock_Data_Lens); SetTDMChannelsSdata(ptrAudioHDMIFormat->msDATA_Channels); SetTDMDatalength(ptrAudioHDMIFormat->mTDM_Data_Lens); /* SetTDMLrckInverse(true); */ SetTDMBckInverse(false); set_tdm_lrck_inverse_by_channel(outchannel); /* SetHDMIsamplerate(runtime->rate); */ SetHDMIChannels(outchannel); SetTDMI2Smode(Soc_Aud_I2S_FORMAT_I2S); mtk_Hdmi_Set_Sdata((void *)ptrAudioHDMIFormat); SetHDMIClockInverse(); mtk_Hdmi_Set_Interconnection((void *)ptrAudioHDMIFormat); /* Start */ SetHDMIEnable(true); SetTDMEnable(true); AudDrv_TDM_Clk_On(); SetMemoryPathEnable(Soc_Aud_Digital_Block_MEM_HDMI, true); #ifdef _DEBUG_TDM_KERNEL_ SetConnection(Soc_Aud_InterCon_Connection, Soc_Aud_InterConnectionInput_I00, Soc_Aud_InterConnectionOutput_O09); SetConnection(Soc_Aud_InterCon_Connection, Soc_Aud_InterConnectionInput_I01, Soc_Aud_InterConnectionOutput_O10); SetHDMIDebugEnable(true); #endif copysinewavetohdmi(outchannel); if (!irq5_user) { irq_add_user(&irq5_user, irq_request_number( Soc_Aud_Digital_Block_MEM_HDMI), runsamplerate, 1024); irq5_user = true; } EnableAfe(true); SetHDMIDumpReg(); } else { SetMemoryPathEnable(Soc_Aud_Digital_Block_MEM_HDMI, false); SetHDMIEnable(false); SetTDMEnable(false); AudDrv_Emi_Clk_Off(); AudDrv_APLL1Tuner_Clk_Off(); AudDrv_APLL2Tuner_Clk_Off(); EnableI2SCLKDiv(ptrAudioHDMIFormat->mI2S_MCKDIV, false); EnableI2SCLKDiv(ptrAudioHDMIFormat->mI2S_BCKDIV, false); EnableApll1(false); EnableApll2(false); EnableAfe(false); AudDrv_TDM_Clk_Off(); /* disable HDMI CK */ AudDrv_Clk_Off(); } return 0; } static int Audio_hdmi_disport_Get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { pr_debug("Audio_hdmi_slimport_Get = %d\n", mHdmi_display_control); ucontrol->value.integer.value[0] = mHdmi_display_control; return 0; } static int Audio_hdmi_disport_Set(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { pr_debug("Audio_hdmi_slimport_Set = %d\n", mHdmi_display_control); if (ucontrol->value.enumerated.item[0] > ARRAY_SIZE(HDMI_DISPLAY)) { pr_err("return -EINVAL\n"); return -EINVAL; } mHdmi_display_control = ucontrol->value.integer.value[0]; return 0; } static const struct snd_kcontrol_new Audio_snd_hdmi_controls[] = { SOC_ENUM_EXT("Audio_Hdmi_SideGen_Switch", Audio_Hdmi_Enum[0], Audio_hdmi_SideGen_Get, Audio_hdmi_SideGen_Set), SOC_ENUM_EXT("Audio_Hdmi_Display_Switch", Audio_Hdmi_Enum[1], Audio_hdmi_disport_Get, Audio_hdmi_disport_Set), }; static struct snd_pcm_hardware mtk_hdmi_hardware = { .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_RESUME | SNDRV_PCM_INFO_MMAP_VALID), .formats = SND_SOC_ADV_MT_FMTS, .rates = SOC_HIGH_USE_RATE, .rate_min = SOC_HIGH_USE_RATE_MIN, .rate_max = SOC_HIGH_USE_RATE_MAX, .channels_min = HDMI_USE_CHANNELS_MIN, .channels_max = HDMI_USE_CHANNELS_MAX, .buffer_bytes_max = HDMI_MAX_BUFFER_SIZE, .period_bytes_max = HDMI_MAX_PERIODBYTE_SIZE, .periods_min = HDMI_MIN_PERIOD_SIZE, .periods_max = HDMI_MAX_8CH_24BIT_PERIOD_SIZE, .fifo_size = 0, }; static int mtk_pcm_hdmi_stop(struct snd_pcm_substream *substream) { struct afe_block_t *Afe_Block = &(pMemControl->rBlock); pr_debug("%s()\n", __func__); irq_remove_user(substream, irq_request_number(Soc_Aud_Digital_Block_MEM_HDMI)); SetMemoryPathEnable(Soc_Aud_Digital_Block_MEM_HDMI, false); #ifdef _DEBUG_TDM_KERNEL_ SetHDMIDebugEnable(false); /*msleep(1); */ #endif SetTDMEnable(false); /* disable TDM */ SetHDMIEnable(false); AudDrv_TDM_Clk_Off(); /* disable HDMI CK */ EnableAfe(false); RemoveMemifSubStream(Soc_Aud_Digital_Block_MEM_HDMI, substream); Afe_Block->u4DMAReadIdx = 0; Afe_Block->u4WriteIdx = 0; Afe_Block->u4DataRemained = 0; return 0; } static snd_pcm_uframes_t mtk_pcm_hdmi_pointer(struct snd_pcm_substream *substream) { kal_int32 HW_memory_index = 0; kal_int32 HW_Cur_ReadIdx = 0; kal_uint32 Frameidx = 0; kal_int32 Afe_consumed_bytes = 0; struct afe_block_t *Afe_Block = &(pMemControl->rBlock); #if defined(HDMI_DEBUG_LOG) pr_debug(" %s Afe_Block->u4DMAReadIdx = 0x%x\n", __func__, Afe_Block->u4DMAReadIdx); #endif if (GetMemoryPathEnable(Soc_Aud_Digital_Block_MEM_HDMI) == true) { HW_Cur_ReadIdx = Afe_Get_Reg(AFE_HDMI_CUR); if (HW_Cur_ReadIdx == 0) { #if defined(HDMI_DEBUG_LOG) pr_debug("[Auddrv] HW_Cur_ReadIdx ==0\n"); #endif HW_Cur_ReadIdx = Afe_Block->pucPhysBufAddr; } HW_memory_index = (HW_Cur_ReadIdx - Afe_Block->pucPhysBufAddr); if (HW_memory_index >= Afe_Block->u4DMAReadIdx) { Afe_consumed_bytes = HW_memory_index - Afe_Block->u4DMAReadIdx; } else { Afe_consumed_bytes = Afe_Block->u4BufferSize + HW_memory_index - Afe_Block->u4DMAReadIdx; } Afe_Block->u4DataRemained -= Afe_consumed_bytes; Afe_Block->u4DMAReadIdx += Afe_consumed_bytes; Afe_Block->u4DMAReadIdx %= Afe_Block->u4BufferSize; #if defined(HDMI_DEBUG_LOG) pr_debug( "[Auddrv] HW_Cur_ReadIdx = 0x%x, HW_memory_index = 0x%x, Afe_consumed_bytes = 0x%x\n", HW_Cur_ReadIdx, HW_memory_index, Afe_consumed_bytes); #endif return audio_bytes_to_frame(substream, Afe_Block->u4DMAReadIdx); } Frameidx = audio_bytes_to_frame(substream, Afe_Block->u4DMAReadIdx); return Frameidx; } static void SetHDMIBuffer(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *hw_params) { kal_uint32 u4tmpMrg1; struct snd_pcm_runtime *runtime = substream->runtime; struct afe_block_t *pblock = &(pMemControl->rBlock); pblock->pucPhysBufAddr = runtime->dma_addr; pblock->pucVirtBufAddr = runtime->dma_area; pblock->u4BufferSize = runtime->dma_bytes; pblock->u4SampleNumMask = 0x001f; /* 32 byte align */ pblock->u4WriteIdx = 0; pblock->u4DMAReadIdx = 0; pblock->u4DataRemained = 0; pblock->u4fsyncflag = false; pblock->uResetFlag = true; #if defined(HDMI_DEBUG_LOG) pr_debug("%s, dma_bytes = %d, dma_area = %p, dma_addr = 0x%x\n", __func__, pblock->u4BufferSize, pblock->pucVirtBufAddr, pblock->pucPhysBufAddr); #endif Afe_Set_Reg(AFE_HDMI_BASE, pblock->pucPhysBufAddr, 0xffffffff); Afe_Set_Reg(AFE_HDMI_END, pblock->pucPhysBufAddr + (pblock->u4BufferSize - 1), 0xffffffff); u4tmpMrg1 = Afe_Get_Reg(AFE_HDMI_BASE); u4tmpMrg1 &= 0x00ffffff; #if defined(HDMI_DEBUG_LOG) pr_debug("%s(), AFE_HDMI_BASE = 0x%x\n", __func__, u4tmpMrg1); #endif } static int mtk_pcm_hdmi_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *hw_params) { struct snd_pcm_runtime *runtime = substream->runtime; struct snd_dma_buffer *dma_buf = &substream->dma_buffer; int ret = 0; #if defined(HDMI_DEBUG_LOG) pr_debug("%s\n", __func__); #endif dma_buf->dev.type = SNDRV_DMA_TYPE_DEV; dma_buf->dev.dev = substream->pcm->card->dev; dma_buf->private_data = NULL; #if defined(HDMI_DEBUG_LOG) pr_debug("%s(), HDMI_dma_buf->area\n", __func__); #endif HDMI_dma_buf->bytes = substream->runtime->dma_bytes = params_buffer_bytes(hw_params); runtime->dma_area = HDMI_dma_buf->area; runtime->dma_addr = HDMI_dma_buf->addr; SetHighAddr(Soc_Aud_Digital_Block_MEM_HDMI, true, runtime->dma_addr); AudDrv_Emi_Clk_On(); #if defined(HDMI_DEBUG_LOG) pr_debug("2 dma_bytes = %zu dma_area = %p dma_addr = 0x%lx\n", substream->runtime->dma_bytes, substream->runtime->dma_area, (long)substream->runtime->dma_addr); #endif SetHDMIBuffer(substream, hw_params); return ret; } static int mtk_pcm_hdmi_hw_free(struct snd_pcm_substream *substream) { AudDrv_Emi_Clk_Off(); return 0; } /* Conventional and unconventional sample rate supported */ static unsigned int supported_sample_rates[] = {8000, 11025, 12000, 16000, 22050, 24000, 32000, 44100, 48000, 88200, 96000, 192000}; static struct snd_pcm_hw_constraint_list constraints_sample_rates = { .count = ARRAY_SIZE(supported_sample_rates), .list = supported_sample_rates, .mask = 0, }; static int mtk_pcm_hdmi_open(struct snd_pcm_substream *substream) { struct audio_hdmi_format *ptrAudioHDMIFormat; struct snd_pcm_runtime *runtime; int ret; runtime = substream->runtime; ptrAudioHDMIFormat = &mAudioHDMIFormat; pr_debug("%s()\n", __func__); pMemControl = Get_Mem_ControlT(Soc_Aud_Digital_Block_MEM_HDMI); runtime->hw = mtk_hdmi_hardware; AudDrv_Clk_On(); mtk_Hdmi_Configuration_Init((void *)ptrAudioHDMIFormat); mtk_Hdmi_Clock_Set((void *)ptrAudioHDMIFormat); memcpy((void *)(&(runtime->hw)), (void *)&mtk_hdmi_hardware, sizeof(struct snd_pcm_hardware)); ret = snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, &constraints_sample_rates); /* print for hw pcm information */ #if defined(HDMI_DEBUG_LOG) pr_debug( "%s(), runtime rate = %d, channels = %d, substream->pcm->device = %d\n", __func__, runtime->rate, runtime->channels, substream->pcm->device); #endif runtime->hw.info |= SNDRV_PCM_INFO_INTERLEAVED; runtime->hw.info |= SNDRV_PCM_INFO_NONINTERLEAVED; runtime->hw.info |= SNDRV_PCM_INFO_MMAP_VALID; EnableApll1(true); EnableApll2(true); EnableI2SCLKDiv(ptrAudioHDMIFormat->mI2S_MCKDIV, true); EnableI2SCLKDiv(ptrAudioHDMIFormat->mI2S_BCKDIV, true); AudDrv_APLL1Tuner_Clk_On(); AudDrv_APLL2Tuner_Clk_On(); #if defined(HDMI_DEBUG_LOG) pr_debug("%s(), return\n", __func__); #endif return 0; } static int mtk_pcm_hdmi_close(struct snd_pcm_substream *substream) { struct audio_hdmi_format *ptrAudioHDMIFormat; ptrAudioHDMIFormat = &mAudioHDMIFormat; pr_debug("%s\n", __func__); AudDrv_APLL1Tuner_Clk_Off(); AudDrv_APLL2Tuner_Clk_Off(); mHDMIPrepareDone = false; EnableI2SCLKDiv(ptrAudioHDMIFormat->mI2S_MCKDIV, false); EnableI2SCLKDiv(ptrAudioHDMIFormat->mI2S_BCKDIV, false); EnableApll1(false); EnableApll2(false); AudDrv_Clk_Off(); return 0; } static int mtk_pcm_hdmi_prepare(struct snd_pcm_substream *substream) { struct snd_pcm_runtime *runtime; struct audio_hdmi_format *ptrAudioHDMIFormat; unsigned int MclkDiv; runtime = substream->runtime; ptrAudioHDMIFormat = &mAudioHDMIFormat; pr_debug("%s format =%d, rate = %d ch = %d size = %lu, control=%d\n", __func__, runtime->format, runtime->rate, runtime->channels, runtime->period_size, mHdmi_display_control); if (mHDMIPrepareDone == false) { mtk_Hdmi_Configuration_Set((void *)ptrAudioHDMIFormat, runtime->rate, runtime->channels, runtime->format, mHdmi_display_control); MclkDiv = SetCLkMclk(ptrAudioHDMIFormat->mI2Snum, runtime->rate); /* SET hdmi channels , samplerate and formats */ SetMemIfFetchFormatPerSample( Soc_Aud_Digital_Block_MEM_HDMI, ptrAudioHDMIFormat->mMemIfFetchFormatPerSample); SetHDMIdatalength(ptrAudioHDMIFormat->mHDMI_Data_Lens); SetTDMDatalength(ptrAudioHDMIFormat->mTDM_Data_Lens); /*SetCLkBclk(MclkDiv, runtime->rate, runtime->channels, * Soc_Aud_I2S_WLEN_WLEN_16BITS); */ SetCLkBclk(MclkDiv, runtime->rate, ptrAudioHDMIFormat->mHDMI_Channels, ptrAudioHDMIFormat->mClock_Data_Lens); SetTDMLrckWidth(ptrAudioHDMIFormat->mTDM_LRCK); SetTDMbckcycle(ptrAudioHDMIFormat->mClock_Data_Lens); SetTDMChannelsSdata(ptrAudioHDMIFormat->msDATA_Channels); SetTDMDatalength(ptrAudioHDMIFormat->mTDM_Data_Lens); /*SetTDMLrckInverse(true);*/ SetTDMBckInverse(false); if (runtime->channels > 2) SetTDMLrckInverse(false); else SetTDMLrckInverse(true); /*SetHDMIsamplerate(runtime->rate);*/ #ifdef __2CH_TO_8CH SetHDMIChannels(8); #else SetHDMIChannels(runtime->channels); #endif SetTDMI2Smode(Soc_Aud_I2S_FORMAT_I2S); mtk_Hdmi_Set_Sdata((void *)ptrAudioHDMIFormat); SetHDMIClockInverse(); mtk_Hdmi_Set_Interconnection((void *)ptrAudioHDMIFormat); mHDMIPrepareDone = true; } return 0; } static int mtk_pcm_hdmi_start(struct snd_pcm_substream *substream) { /* unsigned int u32AudioI2S = 0; */ SetMemifSubStream(Soc_Aud_Digital_Block_MEM_HDMI, substream); SetHDMIEnable(true); SetTDMEnable(true); /*Afe_Set_Reg(AUDIO_AUDIO_TOP_CON0, 0 << 20, 1 << 20); // enable HDMI * CK */ AudDrv_TDM_Clk_On(); SetMemoryPathEnable(Soc_Aud_Digital_Block_MEM_HDMI, true); #ifdef _DEBUG_TDM_KERNEL_ SetConnection(Soc_Aud_InterCon_Connection, Soc_Aud_InterConnectionInput_I00, Soc_Aud_InterConnectionOutput_O09); SetConnection(Soc_Aud_InterCon_Connection, Soc_Aud_InterConnectionInput_I01, Soc_Aud_InterConnectionOutput_O10); SetHDMIDebugEnable(true); #endif #ifdef _TONE_TEST copysinewavetohdmi(substream->runtime->channels); #endif /* here to set interrupt */ /* ALPS01889945 , stereo , multi channel switch A/V sync issue */ /* 32bit , stereo , 64 BCK for one count, (hal size)8192 bytes/(64/8) = * 1024 count */ irq_add_user(substream, irq_request_number(Soc_Aud_Digital_Block_MEM_HDMI), substream->runtime->rate, 1024); EnableAfe(true); SetHDMIDumpReg(); return 0; } static int mtk_pcm_hdmi_trigger(struct snd_pcm_substream *substream, int cmd) { #if defined(HDMI_DEBUG_LOG) pr_debug("%s(), cmd = %d\n", __func__, cmd); #endif switch (cmd) { case SNDRV_PCM_TRIGGER_START: case SNDRV_PCM_TRIGGER_RESUME: return mtk_pcm_hdmi_start(substream); case SNDRV_PCM_TRIGGER_STOP: case SNDRV_PCM_TRIGGER_SUSPEND: return mtk_pcm_hdmi_stop(substream); } return -EINVAL; } static int mtk_pcm_hdmi_copy(struct snd_pcm_substream *substream, int channel, unsigned long pos, void __user *buf, unsigned long bytes) { struct afe_block_t *Afe_Block = NULL; int copy_size = 0, Afe_WriteIdx_tmp; unsigned long flags; char *data_w_ptr = (char *)buf; unsigned long count = bytes; /* check which memif nned to be write */ Afe_Block = &(pMemControl->rBlock); /* handle for buffer management */ #if defined(HDMI_DEBUG_LOG) pr_debug( "%s(), count = %d, WriteIdx = %x, ReadIdx = %x, DataRemained = %x\n", __func__, (kal_uint32)count, Afe_Block->u4WriteIdx, Afe_Block->u4DMAReadIdx, Afe_Block->u4DataRemained); #endif if (Afe_Block->u4BufferSize == 0) { #if defined(HDMI_DEBUG_LOG) pr_debug("AudDrv_write: u4BufferSize=0 Error"); #endif return 0; } spin_lock_irqsave(&auddrv_hdmi_lock, flags); copy_size = Afe_Block->u4BufferSize - Afe_Block->u4DataRemained; /* free space of the buffer */ spin_unlock_irqrestore(&auddrv_hdmi_lock, flags); if (count <= copy_size) { if (copy_size < 0) copy_size = 0; else copy_size = count; } if (copy_size != 0) { spin_lock_irqsave(&auddrv_hdmi_lock, flags); Afe_WriteIdx_tmp = Afe_Block->u4WriteIdx; spin_unlock_irqrestore(&auddrv_hdmi_lock, flags); if (Afe_WriteIdx_tmp + copy_size < Afe_Block->u4BufferSize) { /* copy once */ if (!access_ok(VERIFY_READ, data_w_ptr, copy_size)) { #if defined(HDMI_DEBUG_LOG) pr_debug( "%s(), 0ptr invalid data_w_ptr = %p, size = %d, u4BufferSize = %d, u4DataRemained = %d", __func__, data_w_ptr, copy_size, Afe_Block->u4BufferSize, Afe_Block->u4DataRemained); #endif } else { #if defined(HDMI_DEBUG_LOG) pr_debug( "%s(), WriteIdx = 0x%x, data_w_ptr = %p, copy_size = 0x%x\n", __func__, Afe_Block->pucVirtBufAddr + Afe_WriteIdx_tmp, data_w_ptr, copy_size); #endif if (copy_from_user((Afe_Block->pucVirtBufAddr + Afe_WriteIdx_tmp), data_w_ptr, copy_size)) { #if defined(HDMI_DEBUG_LOG) pr_debug("AudDrv_write Fail copy from user\n"); #endif return -1; } } spin_lock_irqsave(&auddrv_hdmi_lock, flags); Afe_Block->u4DataRemained += copy_size; Afe_Block->u4WriteIdx = Afe_WriteIdx_tmp + copy_size; Afe_Block->u4WriteIdx %= Afe_Block->u4BufferSize; spin_unlock_irqrestore(&auddrv_hdmi_lock, flags); data_w_ptr += copy_size; count -= copy_size; #if defined(HDMI_DEBUG_LOG) pr_debug( "%s(), finish1, copy_size = %x, WriteIdx = %x, ReadIdx = %x, Remained = %x \r\n", __func__, copy_size, Afe_Block->u4WriteIdx, Afe_Block->u4DMAReadIdx, Afe_Block->u4DataRemained); #endif } else { /* copy twice */ kal_uint32 size_1 = 0, size_2 = 0; size_1 = Afe_Block->u4BufferSize - Afe_WriteIdx_tmp; size_2 = copy_size - size_1; if (!access_ok(VERIFY_READ, data_w_ptr, size_1)) { #if defined(HDMI_DEBUG_LOG) pr_warn("HDMI_write 1ptr invalid data_w_ptr = %p, size_1 = %d, u4BufferSize = %d, u4DataRemained = %d", data_w_ptr, size_1, Afe_Block->u4BufferSize, Afe_Block->u4DataRemained); #endif } else { #if defined(HDMI_DEBUG_LOG) pr_debug( "%s(), WriteIdx = %x, data_w_ptr = %p, size_1 = %x\n", __func__, Afe_Block->pucVirtBufAddr + Afe_WriteIdx_tmp, data_w_ptr, size_1); #endif if ((copy_from_user((Afe_Block->pucVirtBufAddr + Afe_WriteIdx_tmp), data_w_ptr, size_1))) { #if defined(HDMI_DEBUG_LOG) pr_warn("HDMI_write Fail 1 copy from user"); #endif return -1; } } spin_lock_irqsave(&auddrv_hdmi_lock, flags); Afe_Block->u4DataRemained += size_1; Afe_Block->u4WriteIdx = Afe_WriteIdx_tmp + size_1; Afe_Block->u4WriteIdx %= Afe_Block->u4BufferSize; Afe_WriteIdx_tmp = Afe_Block->u4WriteIdx; spin_unlock_irqrestore(&auddrv_hdmi_lock, flags); if (!access_ok(VERIFY_READ, data_w_ptr + size_1, size_2)) { #if defined(HDMI_DEBUG_LOG) pr_warn( "HDMI_write 2ptr invalid data_w_ptr=%p, size_1=%d, size_2=%d u4BufferSize=%d, u4DataRemained=%d", data_w_ptr, size_1, size_2, Afe_Block->u4BufferSize, Afe_Block->u4DataRemained); #endif } else { #if defined(HDMI_DEBUG_LOG) pr_debug( "%s(), WriteIdx = %x, data_w_ptr+size_1 = %p, size_2 = %x\n", __func__, Afe_Block->pucVirtBufAddr + Afe_WriteIdx_tmp, data_w_ptr + size_1, size_2); #endif if ((copy_from_user((Afe_Block->pucVirtBufAddr + Afe_WriteIdx_tmp), (data_w_ptr + size_1), size_2))) { #if defined(HDMI_DEBUG_LOG) pr_warn( "%s(), Fail 2 copy from user", __func__); #endif return -1; } } spin_lock_irqsave(&auddrv_hdmi_lock, flags); Afe_Block->u4DataRemained += size_2; Afe_Block->u4WriteIdx = Afe_WriteIdx_tmp + size_2; Afe_Block->u4WriteIdx %= Afe_Block->u4BufferSize; spin_unlock_irqrestore(&auddrv_hdmi_lock, flags); count -= copy_size; data_w_ptr += copy_size; #if defined(HDMI_DEBUG_LOG) pr_debug( "%s(), finish2, size = %x, WriteIdx = %x, ReadIdx = %x, DataRemained = %x \r\n", __func__, copy_size, Afe_Block->u4WriteIdx, Afe_Block->u4DMAReadIdx, Afe_Block->u4DataRemained); #endif } } return 0; /* pr_debug("dummy_pcm_copy return\n"); */ } static int mtk_pcm_hdmi_silence(struct snd_pcm_substream *substream, int channel, unsigned long pos, unsigned long bytes) { return 0; /* do nothing */ } static void *dummy_page[2]; static struct page *mtk_pcm_page(struct snd_pcm_substream *substream, unsigned long offset) { return virt_to_page(dummy_page[substream->stream]); /* the same page */ } static struct snd_pcm_ops mtk_hdmi_ops = { .open = mtk_pcm_hdmi_open, .close = mtk_pcm_hdmi_close, .ioctl = snd_pcm_lib_ioctl, .hw_params = mtk_pcm_hdmi_hw_params, .hw_free = mtk_pcm_hdmi_hw_free, .prepare = mtk_pcm_hdmi_prepare, .trigger = mtk_pcm_hdmi_trigger, .pointer = mtk_pcm_hdmi_pointer, .copy_user = mtk_pcm_hdmi_copy, .fill_silence = mtk_pcm_hdmi_silence, .page = mtk_pcm_page, }; static const struct snd_soc_component_driver mtk_hdmi_soc_component = { .name = AFE_PCM_NAME, .ops = &mtk_hdmi_ops, .probe = mtk_afe_hdmi_component_probe, }; static int mtk_hdmi_probe(struct platform_device *pdev) { #if defined(HDMI_DEBUG_LOG) pr_debug("%s\n", __func__); #endif pdev->dev.coherent_dma_mask = DMA_BIT_MASK(32); if (!pdev->dev.dma_mask) pdev->dev.dma_mask = &pdev->dev.coherent_dma_mask; if (pdev->dev.of_node) dev_set_name(&pdev->dev, "%s", MT_SOC_HDMI_PCM); pdev->name = pdev->dev.kobj.name; #if defined(HDMI_DEBUG_LOG) pr_debug("%s: dev name %s\n", __func__, dev_name(&pdev->dev)); #endif return snd_soc_register_component(&pdev->dev, &mtk_hdmi_soc_component, NULL, 0); } static int mtk_afe_hdmi_component_probe(struct snd_soc_component *component) { pr_debug("%s\n", __func__); /* allocate dram */ AudDrv_Allocate_mem_Buffer(component->dev, Soc_Aud_Digital_Block_MEM_HDMI, HDMI_MAX_BUFFER_SIZE); HDMI_dma_buf = Get_Mem_Buffer(Soc_Aud_Digital_Block_MEM_HDMI); snd_soc_add_component_controls(component, Audio_snd_hdmi_controls, ARRAY_SIZE(Audio_snd_hdmi_controls)); return 0; } static int mtk_afe_remove(struct platform_device *pdev) { #if defined(HDMI_DEBUG_LOG) pr_debug("%s\n", __func__); #endif snd_soc_unregister_component(&pdev->dev); return 0; } #ifdef CONFIG_OF static const struct of_device_id mt_soc_pcm_hdmi_of_ids[] = { { .compatible = "mediatek,mt_soc_pcm_hdmi", }, {} }; #endif static struct platform_driver mtk_hdmi_driver = { .driver = { .name = MT_SOC_HDMI_PCM, .owner = THIS_MODULE, #ifdef CONFIG_OF .of_match_table = mt_soc_pcm_hdmi_of_ids, #endif }, .probe = mtk_hdmi_probe, .remove = mtk_afe_remove, }; #ifndef CONFIG_OF static struct platform_device *soc_mtkhdmi_dev; #endif static int __init mtk_hdmi_soc_platform_init(void) { int ret; #if defined(HDMI_DEBUG_LOG) pr_debug("%s\n", __func__); #endif #ifndef CONFIG_OF soc_mtkhdmi_dev = platform_device_alloc(MT_SOC_HDMI_PCM, -1); if (!soc_mtkhdmi_dev) return -ENOMEM; ret = platform_device_add(soc_mtkhdmi_dev); if (ret != 0) { platform_device_put(soc_mtkhdmi_dev); return ret; } #endif ret = platform_driver_register(&mtk_hdmi_driver); return ret; } module_init(mtk_hdmi_soc_platform_init); static void __exit mtk_hdmi_soc_platform_exit(void) { platform_driver_unregister(&mtk_hdmi_driver); } module_exit(mtk_hdmi_soc_platform_exit); /* EXPORT_SYMBOL(Auddrv_Hdmi_Interrupt_Handler); */ MODULE_DESCRIPTION("AFE PCM module platform driver"); MODULE_LICENSE("GPL");