| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * Copyright (C) 2019 Amlogic, Inc. All rights reserved. |
| * |
| */ |
| |
| /*#define DEBUG*/ |
| |
| #include <linux/module.h> |
| #include <linux/moduleparam.h> |
| #include <linux/platform_device.h> |
| #include <linux/init.h> |
| #include <linux/errno.h> |
| #include <linux/ioctl.h> |
| #include <linux/of.h> |
| #include <linux/of_device.h> |
| #include <linux/of_platform.h> |
| #include <linux/clk.h> |
| #include <sound/core.h> |
| #include <sound/pcm.h> |
| #include <sound/initval.h> |
| #include <sound/control.h> |
| #include <sound/soc.h> |
| #include <sound/pcm_params.h> |
| #include <sound/tlv.h> |
| #include <sound/asoundef.h> |
| |
| #include "ddr_mngr.h" |
| #include "audio_utils.h" |
| #include "frhdmirx_hw.h" |
| #include "resample.h" |
| #include "earc.h" |
| |
| #include "../common/misc.h" |
| #include "../common/debug.h" |
| |
| #if (defined CONFIG_AMLOGIC_MEDIA_TVIN_HDMI ||\ |
| defined CONFIG_AMLOGIC_MEDIA_TVIN_HDMI_MODULE) |
| #include <linux/amlogic/media/frame_provider/tvin/tvin.h> |
| #endif |
| |
| #ifdef CONFIG_AMLOGIC_MEDIA_VIDEO |
| #include <linux/amlogic/media/video_sink/video.h> |
| #endif |
| |
| #define DRV_NAME "EXTN" |
| #define MAX_INT 0x7ffffff |
| |
| #define MAX_AUDIO_EDID_LENGTH 30 |
| |
| struct extn_chipinfo { |
| int arc_version; |
| bool PAO_channel_sync; |
| int frhdmirx_version; |
| }; |
| |
| struct extn { |
| struct aml_audio_controller *actrl; |
| struct device *dev; |
| |
| struct toddr *tddr; |
| struct frddr *fddr; |
| |
| int sysclk_freq; |
| |
| int irq_frhdmirx; |
| /* protection of request/free not in pair */ |
| bool irq_on; |
| |
| /* |
| * 0: select spdif lane; |
| * 1: select PAO mode; |
| */ |
| int hdmirx_mode; |
| |
| /* |
| * arc source sel: |
| * 0: hi_hdmirx_arc_cntl[5] |
| * 1: audio_spdif_a |
| * 2: audio_spdif_b |
| * 4: hdmir_aud_spdif |
| */ |
| int arc_src; |
| int arc_en; |
| |
| /* check whether irq generating |
| * if not, reset |
| * 'cuase no irq from nonpcm2pcm, do it by sw. |
| */ |
| unsigned int frhdmirx_cnt; /* irq counter */ |
| unsigned int frhdmirx_last_cnt; |
| unsigned int frhdmirx_same_cnt; |
| bool nonpcm_flag; |
| |
| struct extn_chipinfo *chipinfo; |
| int audio_type; |
| struct snd_aes_iec958 iec; |
| int frhdmirx_version; |
| |
| char default_edid[MAX_AUDIO_EDID_LENGTH]; |
| int default_edid_size; |
| char user_setting_edid[MAX_AUDIO_EDID_LENGTH]; |
| int user_setting_edid_size; |
| int hdmiin_audio_output_mode; |
| }; |
| |
| #define EXTN_BUFFER_BYTES (512 * 1024) |
| #define EXTN_RATES (SNDRV_PCM_RATE_8000_192000) |
| #define EXTN_FORMATS (SNDRV_PCM_FMTBIT_S16_LE |\ |
| SNDRV_PCM_FMTBIT_S24_LE |\ |
| SNDRV_PCM_FMTBIT_S32_LE) |
| |
| static struct extn *s_extn; |
| static const struct snd_pcm_hardware extn_hardware = { |
| .info = |
| SNDRV_PCM_INFO_MMAP | |
| SNDRV_PCM_INFO_MMAP_VALID | |
| SNDRV_PCM_INFO_INTERLEAVED | |
| SNDRV_PCM_INFO_BLOCK_TRANSFER | SNDRV_PCM_INFO_PAUSE, |
| |
| .formats = EXTN_FORMATS, |
| |
| .period_bytes_min = 64, |
| .period_bytes_max = 128 * 1024, |
| .periods_min = 2, |
| .periods_max = 1024, |
| .buffer_bytes_max = EXTN_BUFFER_BYTES, |
| |
| .rate_min = 8000, |
| .rate_max = 192000, |
| .channels_min = 1, |
| .channels_max = 32, |
| }; |
| |
| static snd_pcm_uframes_t extn_pointer(struct snd_pcm_substream *substream); |
| |
| int get_hdmirx_mode(void) |
| { |
| if (s_extn) |
| return s_extn->hdmirx_mode; |
| return 0; |
| } |
| |
| int get_arc_version(void) |
| { |
| if (s_extn) |
| return s_extn->chipinfo->arc_version; |
| return 0; |
| } |
| |
| static void frhdmirx_nonpcm2pcm_clr_reset(struct extn *p_extn) |
| { |
| p_extn->frhdmirx_cnt = 0; |
| p_extn->frhdmirx_last_cnt = 0; |
| p_extn->frhdmirx_same_cnt = 0; |
| } |
| |
| static irqreturn_t extn_ddr_isr(int irq, void *devid) |
| { |
| struct snd_pcm_substream *substream = |
| (struct snd_pcm_substream *)devid; |
| struct snd_soc_pcm_runtime *rtd = substream->private_data; |
| struct device *dev = rtd->cpu_dai->dev; |
| struct extn *p_extn = (struct extn *)dev_get_drvdata(dev); |
| unsigned int period_dur_ms = 0; |
| |
| if (!snd_pcm_running(substream)) |
| return IRQ_HANDLED; |
| |
| snd_pcm_period_elapsed(substream); |
| |
| #ifdef DEBUG_IRQ |
| if (substream->stream == SNDRV_PCM_STREAM_CAPTURE && p_extn->tddr) { |
| unsigned int addr = extn_pointer(substream); |
| struct snd_pcm_runtime *runtime = substream->runtime; |
| |
| get_time_stamp(TODDR, p_extn->tddr->fifo_id, addr, |
| (runtime->control->appl_ptr) % (runtime->buffer_size), |
| runtime->buffer_size); |
| } |
| #endif |
| |
| if (p_extn->frhdmirx_version == T7_FRHDMIRX) { |
| frhdmirx_clr_SPDIF_irq_bits_for_t7_version(); |
| return IRQ_HANDLED; |
| } |
| |
| period_dur_ms = substream->runtime->period_size * 1000 / substream->runtime->rate; |
| |
| /* check pcm or nonpcm for PAO*/ |
| if (p_extn->hdmirx_mode == HDMIRX_MODE_PAO) { |
| /* by default, we check about 100 ms duration for the |
| * hdmirx data pipeline, if no PaPb there after 100ms, clear the PaPb flag |
| */ |
| unsigned int timeout_thres = 100 / period_dur_ms; |
| |
| #if (defined CONFIG_AMLOGIC_MEDIA_TVIN_HDMI ||\ |
| defined CONFIG_AMLOGIC_MEDIA_TVIN_HDMI_MODULE) |
| int sample_rate_index = get_hdmi_sample_rate_index(); |
| |
| /*192K & 176K audio*/ |
| if (sample_rate_index == 7 || sample_rate_index == 6) |
| timeout_thres = timeout_thres * 2; |
| #endif |
| if (p_extn->frhdmirx_last_cnt == p_extn->frhdmirx_cnt) { |
| p_extn->frhdmirx_same_cnt++; |
| |
| if (p_extn->frhdmirx_same_cnt > timeout_thres) |
| frhdmirx_nonpcm2pcm_clr_reset(p_extn); |
| |
| if (p_extn->frhdmirx_cnt == 0) |
| p_extn->nonpcm_flag = false; |
| } else { |
| p_extn->frhdmirx_last_cnt = p_extn->frhdmirx_cnt; |
| p_extn->frhdmirx_same_cnt = 0; |
| p_extn->nonpcm_flag = true; |
| frhdmirx_clr_PAO_irq_bits(); |
| } |
| } else { |
| frhdmirx_clr_SPDIF_irq_bits(); |
| } |
| |
| return IRQ_HANDLED; |
| } |
| |
| static irqreturn_t frhdmirx_isr(int irq, void *devid) |
| { |
| struct extn *p_extn = (struct extn *)devid; |
| |
| p_extn->frhdmirx_cnt++; |
| if (p_extn->frhdmirx_cnt > MAX_INT - 2) |
| frhdmirx_nonpcm2pcm_clr_reset(p_extn); |
| |
| return IRQ_HANDLED; |
| } |
| |
| static int extn_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->cpu_dai->dev; |
| struct extn *p_extn; |
| int ret = 0; |
| |
| pr_info("asoc debug: %s-%d\n", __func__, __LINE__); |
| |
| p_extn = (struct extn *)dev_get_drvdata(dev); |
| |
| snd_soc_set_runtime_hwparams(substream, &extn_hardware); |
| snd_pcm_lib_preallocate_pages(substream, SNDRV_DMA_TYPE_DEV, |
| dev, EXTN_BUFFER_BYTES / 2, EXTN_BUFFER_BYTES); |
| |
| if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { |
| p_extn->fddr = aml_audio_register_frddr(dev, |
| p_extn->actrl, |
| extn_ddr_isr, substream, false); |
| if (!p_extn->fddr) { |
| ret = -ENXIO; |
| dev_err(dev, "failed to claim from ddr\n"); |
| goto err_ddr; |
| } |
| } else { |
| p_extn->tddr = aml_audio_register_toddr(dev, |
| p_extn->actrl, |
| extn_ddr_isr, substream); |
| if (!p_extn->tddr) { |
| ret = -ENXIO; |
| dev_err(dev, "failed to claim to ddr\n"); |
| goto err_ddr; |
| } |
| |
| if (toddr_src_get() == FRHDMIRX) { |
| ret = request_irq(p_extn->irq_frhdmirx, |
| frhdmirx_isr, IRQF_SHARED, |
| "irq_frhdmirx", p_extn); |
| if (ret) { |
| ret = -ENXIO; |
| dev_err(p_extn->dev, "failed to claim irq_frhdmirx %u, ret: %d\n", |
| p_extn->irq_frhdmirx, ret); |
| goto err_irq; |
| } |
| p_extn->irq_on = true; |
| } |
| } |
| |
| runtime->private_data = p_extn; |
| |
| return 0; |
| |
| err_irq: |
| aml_audio_unregister_toddr(p_extn->dev, substream); |
| err_ddr: |
| snd_pcm_lib_preallocate_free(substream); |
| return ret; |
| } |
| |
| static int extn_close(struct snd_pcm_substream *substream) |
| { |
| struct snd_pcm_runtime *runtime = substream->runtime; |
| struct extn *p_extn = runtime->private_data; |
| |
| pr_info("asoc debug: %s-%d\n", __func__, __LINE__); |
| |
| if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { |
| aml_audio_unregister_frddr(p_extn->dev, substream); |
| } else { |
| aml_audio_unregister_toddr(p_extn->dev, substream); |
| |
| if (p_extn->irq_on) { |
| frhdmirx_nonpcm2pcm_clr_reset(p_extn); |
| frhdmirx_clr_all_irq_bits(p_extn->frhdmirx_version); |
| free_irq(p_extn->irq_frhdmirx, p_extn); |
| p_extn->irq_on = false; |
| } |
| } |
| runtime->private_data = NULL; |
| snd_pcm_lib_preallocate_free(substream); |
| |
| return 0; |
| } |
| |
| static int extn_hw_params(struct snd_pcm_substream *substream, |
| struct snd_pcm_hw_params *hw_params) |
| { |
| return snd_pcm_lib_malloc_pages(substream, |
| params_buffer_bytes(hw_params)); |
| } |
| |
| static int extn_hw_free(struct snd_pcm_substream *substream) |
| { |
| snd_pcm_lib_free_pages(substream); |
| |
| return 0; |
| } |
| |
| static int extn_trigger(struct snd_pcm_substream *substream, int cmd) |
| { |
| return 0; |
| } |
| |
| static int extn_prepare(struct snd_pcm_substream *substream) |
| { |
| struct snd_pcm_runtime *runtime = substream->runtime; |
| struct extn *p_extn = runtime->private_data; |
| unsigned int start_addr, end_addr, int_addr; |
| unsigned int period, threshold; |
| |
| start_addr = runtime->dma_addr; |
| end_addr = start_addr + runtime->dma_bytes - FIFO_BURST; |
| period = frames_to_bytes(runtime, runtime->period_size); |
| int_addr = period / FIFO_BURST; |
| |
| if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { |
| struct frddr *fr = p_extn->fddr; |
| |
| /* |
| * Contrast minimum of period and fifo depth, |
| * and set the value as half. |
| */ |
| threshold = min(period, fr->chipinfo->fifo_depth); |
| threshold /= 2; |
| /* Use all the fifo */ |
| aml_frddr_set_fifos(fr, fr->chipinfo->fifo_depth, threshold); |
| |
| aml_frddr_set_buf(fr, start_addr, end_addr); |
| aml_frddr_set_intrpt(fr, int_addr); |
| } else { |
| struct toddr *to = p_extn->tddr; |
| |
| /* |
| * Contrast minimum of period and fifo depth, |
| * and set the value as half. |
| */ |
| threshold = min(period, to->chipinfo->fifo_depth); |
| threshold /= 2; |
| aml_toddr_set_fifos(to, threshold); |
| |
| aml_toddr_set_buf(to, start_addr, end_addr); |
| aml_toddr_set_intrpt(to, int_addr); |
| } |
| |
| return 0; |
| } |
| |
| static snd_pcm_uframes_t extn_pointer(struct snd_pcm_substream *substream) |
| { |
| struct snd_pcm_runtime *runtime = substream->runtime; |
| struct extn *p_extn = runtime->private_data; |
| unsigned int addr, start_addr; |
| snd_pcm_uframes_t frames; |
| |
| start_addr = runtime->dma_addr; |
| if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) |
| addr = aml_frddr_get_position(p_extn->fddr); |
| else |
| addr = aml_toddr_get_position(p_extn->tddr); |
| |
| frames = bytes_to_frames(runtime, addr - start_addr); |
| if (frames > runtime->buffer_size) |
| frames = 0; |
| |
| return frames; |
| } |
| |
| static int extn_mmap(struct snd_pcm_substream *substream, |
| struct vm_area_struct *vma) |
| { |
| return snd_pcm_lib_default_mmap(substream, vma); |
| } |
| |
| static struct snd_pcm_ops extn_ops = { |
| .open = extn_open, |
| .close = extn_close, |
| .ioctl = snd_pcm_lib_ioctl, |
| .hw_params = extn_hw_params, |
| .hw_free = extn_hw_free, |
| .prepare = extn_prepare, |
| .trigger = extn_trigger, |
| .pointer = extn_pointer, |
| .mmap = extn_mmap, |
| }; |
| |
| static int arc_get_enable(struct snd_kcontrol *kcontrol, |
| struct snd_ctl_elem_value *ucontrol) |
| { |
| struct extn *p_extn = snd_kcontrol_chip(kcontrol); |
| |
| if (!p_extn) |
| return 0; |
| |
| ucontrol->value.integer.value[0] = p_extn->arc_en; |
| |
| return 0; |
| } |
| |
| static int arc_set_enable(struct snd_kcontrol *kcontrol, |
| struct snd_ctl_elem_value *ucontrol) |
| { |
| struct extn *p_extn = snd_kcontrol_chip(kcontrol); |
| |
| if (!p_extn) |
| return 0; |
| |
| p_extn->arc_en = ucontrol->value.integer.value[0]; |
| |
| if (p_extn->chipinfo && p_extn->chipinfo->arc_version == TL1_ARC) |
| arc_source_enable(p_extn->arc_src, p_extn->arc_en); |
| else if (p_extn->chipinfo && p_extn->chipinfo->arc_version >= TM2_ARC) |
| arc_enable(p_extn->arc_en, p_extn->chipinfo->arc_version); |
| |
| return 0; |
| } |
| |
| static const struct snd_kcontrol_new extn_arc_controls[] = { |
| SOC_SINGLE_BOOL_EXT("HDMI ARC Switch", |
| 0, |
| arc_get_enable, |
| arc_set_enable), |
| }; |
| |
| static int extn_create_controls(struct snd_card *card, |
| struct extn *p_extn) |
| { |
| unsigned int idx; |
| int err; |
| |
| for (idx = 0; idx < ARRAY_SIZE(extn_arc_controls); idx++) { |
| err = snd_ctl_add(card, |
| snd_ctl_new1(&extn_arc_controls[idx], p_extn)); |
| if (err < 0) |
| return err; |
| } |
| |
| return 0; |
| } |
| |
| static int extn_dai_probe(struct snd_soc_dai *cpu_dai) |
| { |
| struct snd_card *card = cpu_dai->component->card->snd_card; |
| struct extn *p_extn = snd_soc_dai_get_drvdata(cpu_dai); |
| |
| pr_info("asoc debug: %s-%d\n", __func__, __LINE__); |
| |
| extn_create_controls(card, p_extn); |
| |
| if (p_extn->chipinfo && p_extn->chipinfo->arc_version == TL1_ARC) |
| arc_source_enable(p_extn->arc_src, false); |
| |
| if (p_extn->chipinfo && p_extn->chipinfo->arc_version >= TM2_ARC) { |
| /* set arc_earc_source_select by earc device exist or not */ |
| if (is_earc_spdif()) |
| arc_earc_source_select(EARCTX_SPDIF_TO_HDMIRX); |
| else |
| arc_earc_source_select(SPDIFA_TO_HDMIRX); |
| } |
| |
| if (get_audioresample(RESAMPLE_A)) |
| resample_set(RESAMPLE_A, RATE_48K); |
| |
| return 0; |
| } |
| |
| static int extn_dai_prepare(struct snd_pcm_substream *substream, |
| struct snd_soc_dai *cpu_dai) |
| { |
| struct snd_pcm_runtime *runtime = substream->runtime; |
| struct extn *p_extn = snd_soc_dai_get_drvdata(cpu_dai); |
| unsigned int bit_depth = snd_pcm_format_width(runtime->format); |
| |
| if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { |
| struct frddr *fr = p_extn->fddr; |
| enum frddr_dest dst = frddr_src_get(); |
| |
| pr_info("%s Expected frddr dst:%s\n", |
| __func__, |
| frddr_src_get_str(dst)); |
| |
| aml_frddr_select_dst(fr, dst); |
| } else { |
| struct toddr *to = p_extn->tddr; |
| unsigned int msb = 0, lsb = 0, toddr_type = 0; |
| enum toddr_src src = toddr_src_get(); |
| struct toddr_fmt fmt; |
| |
| if (bit_depth == 24) |
| toddr_type = 4; |
| else |
| toddr_type = 0; |
| |
| if (src == FRATV) { |
| /* Now tv supports 48k, 16bits */ |
| if (bit_depth != 16 || runtime->rate != 48000) { |
| pr_err("not support sample rate:%d, bits:%d\n", |
| runtime->rate, bit_depth); |
| return -EINVAL; |
| } |
| |
| msb = 15; |
| lsb = 0; |
| |
| /* commented it, selected by atv demod, |
| * select 0 for non standard signal. |
| */ |
| /* fratv_src_select(1); */ |
| } else if (src == FRHDMIRX) { |
| if (bit_depth == 32) |
| toddr_type = 3; |
| else if (bit_depth == 24) |
| toddr_type = 4; |
| else |
| toddr_type = 0; |
| |
| if (p_extn->hdmirx_mode == HDMIRX_MODE_PAO) { /* PAO */ |
| msb = 28 - 1 - 4; |
| lsb = (bit_depth == 16) ? 24 - bit_depth : 4; |
| } else { /* SPDIFIN */ |
| msb = 28 - 1; |
| lsb = (bit_depth <= 24) ? 28 - bit_depth : 4; |
| } |
| |
| if (get_resample_version() >= T5_RESAMPLE && |
| get_resample_source(RESAMPLE_A) == FRHDMIRX) { |
| msb = 31; |
| lsb = 32 - bit_depth; |
| } |
| |
| frhdmirx_ctrl(runtime->channels, |
| p_extn->hdmirx_mode, |
| p_extn->frhdmirx_version); |
| if (p_extn->frhdmirx_version != T7_FRHDMIRX) |
| frhdmirx_src_select(p_extn->hdmirx_mode); |
| } else { |
| pr_info("Not support toddr src:%s\n", |
| toddr_src_get_str(src)); |
| return -EINVAL; |
| } |
| |
| pr_debug("%s Expected toddr src:%s, m:%d, n:%d, toddr type:%d\n", |
| __func__, toddr_src_get_str(src), |
| msb, lsb, toddr_type); |
| |
| fmt.type = toddr_type; |
| fmt.msb = msb; |
| fmt.lsb = lsb; |
| fmt.endian = 0; |
| fmt.bit_depth = bit_depth; |
| fmt.ch_num = runtime->channels; |
| fmt.rate = runtime->rate; |
| |
| aml_toddr_select_src(to, src); |
| aml_toddr_set_format(to, &fmt); |
| } |
| |
| return 0; |
| } |
| |
| static int extn_dai_trigger(struct snd_pcm_substream *substream, int cmd, |
| struct snd_soc_dai *cpu_dai) |
| { |
| struct extn *p_extn = snd_soc_dai_get_drvdata(cpu_dai); |
| enum toddr_src src = toddr_src_get(); |
| |
| switch (cmd) { |
| case SNDRV_PCM_TRIGGER_START: |
| case SNDRV_PCM_TRIGGER_RESUME: |
| case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: |
| if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { |
| dev_info(substream->pcm->card->dev, "External Playback enable\n"); |
| |
| aml_frddr_enable(p_extn->fddr, true); |
| } else { |
| dev_dbg(substream->pcm->card->dev, |
| "External Capture enable\n"); |
| |
| if (src == FRATV) |
| fratv_enable(true); |
| else if (src == FRHDMIRX) |
| frhdmirx_enable(true, p_extn->frhdmirx_version); |
| |
| aml_toddr_enable(p_extn->tddr, true); |
| } |
| break; |
| case SNDRV_PCM_TRIGGER_STOP: |
| case SNDRV_PCM_TRIGGER_SUSPEND: |
| case SNDRV_PCM_TRIGGER_PAUSE_PUSH: |
| if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { |
| dev_info(substream->pcm->card->dev, "External Playback disable\n"); |
| |
| aml_frddr_enable(p_extn->fddr, false); |
| } else { |
| bool toddr_stopped = false; |
| |
| if (src == FRATV) |
| fratv_enable(false); |
| else if (src == FRHDMIRX) |
| frhdmirx_enable(false, p_extn->frhdmirx_version); |
| |
| dev_dbg(substream->pcm->card->dev, |
| "External Capture disable\n"); |
| |
| toddr_stopped = aml_toddr_burst_finished(p_extn->tddr); |
| if (toddr_stopped) |
| aml_toddr_enable(p_extn->tddr, false); |
| else |
| pr_err("%s(), toddr may be stuck\n", __func__); |
| } |
| break; |
| default: |
| return -EINVAL; |
| } |
| |
| return 0; |
| } |
| |
| static int extn_dai_hw_params(struct snd_pcm_substream *substream, |
| struct snd_pcm_hw_params *params, |
| struct snd_soc_dai *cpu_dai) |
| { |
| struct extn *p_extn = snd_soc_dai_get_drvdata(cpu_dai); |
| unsigned int rate = params_rate(params); |
| int ret = 0; |
| |
| pr_info("%s:rate:%d, sysclk:%d\n", |
| __func__, |
| rate, |
| p_extn->sysclk_freq); |
| |
| return ret; |
| } |
| |
| static int extn_dai_set_sysclk(struct snd_soc_dai *cpu_dai, |
| int clk_id, unsigned int freq, int dir) |
| { |
| struct extn *p_extn = snd_soc_dai_get_drvdata(cpu_dai); |
| |
| p_extn->sysclk_freq = freq; |
| pr_info("extn_dai_set_sysclk, %d, %d, %d\n", |
| clk_id, freq, dir); |
| |
| return 0; |
| } |
| |
| static struct snd_soc_dai_ops extn_dai_ops = { |
| .prepare = extn_dai_prepare, |
| .trigger = extn_dai_trigger, |
| .hw_params = extn_dai_hw_params, |
| .set_sysclk = extn_dai_set_sysclk, |
| }; |
| |
| static struct snd_soc_dai_driver extn_dai[] = { |
| { |
| .name = "EXTN", |
| .id = 0, |
| .probe = extn_dai_probe, |
| .playback = { |
| .channels_min = 1, |
| .channels_max = 32, |
| .rates = EXTN_RATES, |
| .formats = EXTN_FORMATS, |
| }, |
| .capture = { |
| .channels_min = 1, |
| .channels_max = 32, |
| .rates = EXTN_RATES, |
| .formats = EXTN_FORMATS, |
| }, |
| .ops = &extn_dai_ops, |
| }, |
| }; |
| |
| static int frhdmirx_get_mode(struct snd_kcontrol *kcontrol, |
| struct snd_ctl_elem_value *ucontrol) |
| { |
| struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); |
| struct extn *p_extn = dev_get_drvdata(component->dev); |
| |
| if (!p_extn) |
| return 0; |
| |
| ucontrol->value.integer.value[0] = p_extn->hdmirx_mode; |
| |
| return 0; |
| } |
| |
| static int frhdmirx_set_mode(struct snd_kcontrol *kcontrol, |
| struct snd_ctl_elem_value *ucontrol) |
| { |
| struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); |
| struct extn *p_extn = dev_get_drvdata(component->dev); |
| |
| if (!p_extn) |
| return 0; |
| if (p_extn->frhdmirx_version == T7_FRHDMIRX) { |
| pr_err("T7_FRHDMIRX, can't switch mode\n"); |
| return 0; |
| } |
| |
| p_extn->hdmirx_mode = ucontrol->value.integer.value[0]; |
| |
| return 0; |
| } |
| |
| #if (defined CONFIG_AMLOGIC_MEDIA_TVIN_HDMI ||\ |
| defined CONFIG_AMLOGIC_MEDIA_TVIN_HDMI_MODULE) |
| /* spdif in audio format detect: LPCM or NONE-LPCM */ |
| struct spdif_audio_info { |
| unsigned char aud_type; |
| /*IEC61937 package presamble Pc value*/ |
| short pc; |
| char *aud_type_str; |
| }; |
| |
| static const char *const spdif_audio_type_texts[] = { |
| "LPCM", |
| "AC3", |
| "EAC3", |
| "DTS", |
| "DTS-HD", |
| "TRUEHD", |
| "PAUSE" |
| }; |
| |
| static const struct spdif_audio_info type_texts[] = { |
| {0, 0, "LPCM"}, |
| {1, 0x1, "AC3"}, |
| {2, 0x15, "EAC3"}, |
| {3, 0xb, "DTS-I"}, |
| {3, 0x0c, "DTS-II"}, |
| {3, 0x0d, "DTS-III"}, |
| {4, 0x11, "DTS-IV"}, |
| {5, 0x16, "TRUEHD"}, |
| {6, 0x103, "PAUSE"}, |
| {6, 0x003, "PAUSE"}, |
| {6, 0x100, "PAUSE"}, |
| }; |
| |
| static const struct soc_enum hdmirx_audio_type_enum = |
| SOC_ENUM_SINGLE(SND_SOC_NOPM, 0, ARRAY_SIZE(spdif_audio_type_texts), |
| spdif_audio_type_texts); |
| |
| static int hdmiin_check_audio_type(struct extn *p_extn) |
| { |
| int total_num = sizeof(type_texts) / sizeof(struct spdif_audio_info); |
| int pc = frhdmirx_get_chan_status_pc(p_extn->hdmirx_mode, p_extn->frhdmirx_version); |
| int audio_type = 0; |
| int i; |
| |
| if (!p_extn->nonpcm_flag && p_extn->hdmirx_mode) |
| return audio_type; |
| |
| for (i = 0; i < total_num; i++) { |
| if (pc == type_texts[i].pc) { |
| audio_type = type_texts[i].aud_type; |
| /* only for TL1 */ |
| if (audio_type != p_extn->audio_type && |
| p_extn->chipinfo && |
| !p_extn->chipinfo->PAO_channel_sync) { |
| frhdmirx_afifo_reset(); |
| p_extn->audio_type = audio_type; |
| } |
| break; |
| } |
| } |
| |
| pr_debug("%s audio type:%d\n", __func__, audio_type); |
| |
| return audio_type; |
| } |
| |
| static int hdmirx_audio_type_get_enum(struct snd_kcontrol *kcontrol, |
| struct snd_ctl_elem_value *ucontrol) |
| { |
| struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); |
| struct extn *p_extn = dev_get_drvdata(component->dev); |
| |
| if (!p_extn) |
| return 0; |
| |
| ucontrol->value.enumerated.item[0] = |
| hdmiin_check_audio_type(p_extn); |
| |
| return 0; |
| } |
| |
| int aml_get_audio_edid(struct snd_kcontrol *kcontrol, |
| unsigned int __user *bytes, |
| unsigned int size) |
| { |
| struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); |
| struct extn *p_extn = dev_get_drvdata(component->dev); |
| struct snd_ctl_tlv *tlv; |
| char *val = (char *)bytes + sizeof(*tlv); |
| int res; |
| |
| if (p_extn->default_edid_size == 0 && |
| p_extn->user_setting_edid_size == 0) { |
| p_extn->default_edid_size = |
| (int)rx_edid_get_aud_sad(p_extn->default_edid); |
| memcpy(p_extn->user_setting_edid, p_extn->default_edid, |
| p_extn->default_edid_size); |
| } |
| |
| res = copy_to_user(val, p_extn->user_setting_edid, |
| MAX_AUDIO_EDID_LENGTH); |
| |
| return 0; |
| } |
| |
| int aml_set_audio_edid(struct snd_kcontrol *kcontrol, |
| const unsigned int __user *bytes, |
| unsigned int size) |
| { |
| struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); |
| struct extn *p_extn = dev_get_drvdata(component->dev); |
| struct snd_ctl_tlv *tlv; |
| char *val = (char *)bytes + sizeof(*tlv); |
| int res, edid_size = size - sizeof(*tlv); |
| |
| pr_info("%s edid_size = %d\n", __func__, edid_size); |
| if ((edid_size % 3) != 0 || edid_size > (MAX_AUDIO_EDID_LENGTH - 3)) |
| return -EFAULT; |
| |
| if (edid_size == 0) { |
| /* restore the default edid */ |
| p_extn->default_edid_size = |
| (int)rx_edid_get_aud_sad(p_extn->default_edid); |
| memset(p_extn->user_setting_edid, 0, MAX_AUDIO_EDID_LENGTH); |
| memcpy(p_extn->user_setting_edid, p_extn->default_edid, |
| p_extn->default_edid_size); |
| rx_edid_set_aud_sad(NULL, 0); |
| pr_info("%s default_edid_size = %d\n", __func__, |
| p_extn->default_edid_size); |
| } else { |
| /* update user setting edid */ |
| /* first 3 bytes for pcm, don't update. */ |
| memset(p_extn->user_setting_edid + 3, 0, |
| MAX_AUDIO_EDID_LENGTH - 3); |
| res = copy_from_user(p_extn->user_setting_edid + 3, |
| val, edid_size); |
| if (res) |
| return -EFAULT; |
| |
| rx_edid_set_aud_sad(p_extn->user_setting_edid, edid_size + 3); |
| } |
| p_extn->user_setting_edid_size = edid_size; |
| |
| return 0; |
| } |
| |
| int aml_get_hdmiin_audio_bitwidth(struct snd_kcontrol *kcontrol, |
| struct snd_ctl_elem_value *ucontrol) |
| { |
| struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); |
| struct extn *p_extn = dev_get_drvdata(component->dev); |
| struct snd_aes_iec958 *iec; |
| u32 status = 0, wl_status = 0; |
| u32 wl; |
| |
| if (!p_extn) |
| return 0; |
| |
| iec = &p_extn->iec; |
| status = frhdmirx_get_ch_status(1, p_extn->frhdmirx_version); |
| iec->status[4] = status & 0xff; |
| wl_status = iec->status[4] & IEC958_AES4_CON_WORDLEN; |
| if (iec->status[4] & IEC958_AES4_CON_MAX_WORDLEN_24) { |
| switch (wl_status) { |
| case IEC958_AES4_CON_WORDLEN_NOTID: |
| default: |
| wl = 0; |
| break; |
| case IEC958_AES4_CON_WORDLEN_20_16: |
| wl = 2; |
| break; |
| case IEC958_AES4_CON_WORDLEN_24_20: |
| wl = 3; |
| break; |
| } |
| } else { |
| switch (wl_status) { |
| case IEC958_AES4_CON_WORDLEN_NOTID: |
| default: |
| wl = 0; |
| break; |
| case IEC958_AES4_CON_WORDLEN_20_16: |
| wl = 1; |
| break; |
| case IEC958_AES4_CON_WORDLEN_24_20: |
| wl = 2; |
| break; |
| } |
| } |
| |
| ucontrol->value.integer.value[0] = wl; |
| |
| return 0; |
| } |
| |
| static const char * const hdmiin_audio_output_mode_names[] = { |
| /* 0 */ "SPDIF", |
| /* 1 */ "I2S", |
| /* 2 */ "TDM", |
| }; |
| |
| const struct soc_enum hdmiin_audio_output_mode_enum = |
| SOC_ENUM_SINGLE(SND_SOC_NOPM, 0, |
| ARRAY_SIZE(hdmiin_audio_output_mode_names), |
| hdmiin_audio_output_mode_names); |
| |
| int aml_get_hdmiin_audio_output_mode(struct snd_kcontrol *kcontrol, |
| struct snd_ctl_elem_value *ucontrol) |
| { |
| struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); |
| struct extn *p_extn = dev_get_drvdata(component->dev); |
| |
| ucontrol->value.integer.value[0] = p_extn->hdmiin_audio_output_mode; |
| return 0; |
| } |
| |
| int aml_set_hdmiin_audio_output_mode(struct snd_kcontrol *kcontrol, |
| struct snd_ctl_elem_value *ucontrol) |
| { |
| struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); |
| struct extn *p_extn = dev_get_drvdata(component->dev); |
| |
| rx_set_audio_param(ucontrol->value.integer.value[0]); |
| p_extn->hdmiin_audio_output_mode = ucontrol->value.integer.value[0]; |
| return 0; |
| } |
| |
| #endif |
| |
| #ifdef CONFIG_AMLOGIC_MEDIA_VIDEO |
| static int get_tvin_video_delay_enum(struct snd_kcontrol *kcontrol, |
| struct snd_ctl_elem_value *ucontrol) |
| { |
| ucontrol->value.enumerated.item[0] = get_tvin_delay(); |
| |
| return 0; |
| } |
| |
| static int set_tvin_video_delay_enum(struct snd_kcontrol *kcontrol, |
| struct snd_ctl_elem_value *ucontrol) |
| { |
| unsigned int value = 0; |
| unsigned int max_delay = get_tvin_delay_max_ms(); |
| unsigned int min_delay = get_tvin_delay_min_ms(); |
| |
| value = ucontrol->value.integer.value[0]; |
| if (!max_delay || !min_delay) |
| return 0; |
| |
| if (value > max_delay) |
| value = max_delay; |
| else if (value < min_delay) |
| value = min_delay; |
| |
| set_tvin_delay_duration(value); |
| set_tvin_delay_start(1); |
| |
| pr_info("%s, set tvin video delay [%dms], range: (%d~%dms)\n", |
| __func__, value, min_delay, max_delay); |
| |
| return 0; |
| } |
| |
| static int get_tvin_video_max_delay_enum(struct snd_kcontrol *kcontrol, |
| struct snd_ctl_elem_value *ucontrol) |
| { |
| ucontrol->value.enumerated.item[0] = get_tvin_delay_max_ms(); |
| |
| return 0; |
| } |
| |
| static int get_tvin_video_min_delay_enum(struct snd_kcontrol *kcontrol, |
| struct snd_ctl_elem_value *ucontrol) |
| { |
| ucontrol->value.enumerated.item[0] = get_tvin_delay_min_ms(); |
| |
| return 0; |
| } |
| #endif |
| |
| static const struct snd_kcontrol_new extn_controls[] = { |
| /* In */ |
| SOC_SINGLE_BOOL_EXT("SPDIFIN PAO", |
| 0, |
| frhdmirx_get_mode, |
| frhdmirx_set_mode), |
| |
| #if (defined CONFIG_AMLOGIC_ATV_DEMOD ||\ |
| defined CONFIG_AMLOGIC_ATV_DEMOD_MODULE) |
| SOC_ENUM_EXT("ATV audio stable", |
| atv_audio_status_enum, |
| aml_get_atv_audio_stable, |
| NULL), |
| #endif |
| |
| #if (defined CONFIG_AMLOGIC_MEDIA_TVIN_AVDETECT ||\ |
| defined CONFIG_AMLOGIC_MEDIA_TVIN_AVDETECT_MODULE) |
| SOC_ENUM_EXT("AV audio stable", |
| av_audio_status_enum, |
| aml_get_av_audio_stable, |
| NULL), |
| #endif |
| |
| #if (defined CONFIG_AMLOGIC_MEDIA_TVIN_HDMI ||\ |
| defined CONFIG_AMLOGIC_MEDIA_TVIN_HDMI_MODULE) |
| SOC_ENUM_EXT("HDMIIN audio stable", |
| hdmi_in_status_enum[0], |
| aml_get_hdmiin_audio_stable, |
| NULL), |
| |
| SOC_ENUM_EXT("HDMIIN audio samplerate", |
| hdmi_in_status_enum[1], |
| aml_get_hdmiin_audio_samplerate, |
| NULL), |
| |
| SOC_ENUM_EXT("HDMIIN audio channels", |
| hdmi_in_status_enum[2], |
| aml_get_hdmiin_audio_channels, |
| NULL), |
| |
| SOC_ENUM_EXT("HDMIIN audio format", |
| hdmi_in_status_enum[3], |
| aml_get_hdmiin_audio_format, |
| NULL), |
| |
| SOC_ENUM_EXT("HDMIIN Audio Packet", |
| hdmi_in_status_enum[4], |
| aml_get_hdmiin_audio_packet, |
| NULL), |
| |
| SOC_ENUM_EXT("HDMIIN Audio bit width", |
| hdmi_in_status_enum[5], |
| aml_get_hdmiin_audio_bitwidth, |
| NULL), |
| |
| /* normally use "HDMIIN AUDIO EDID" to update audio edid*/ |
| SOC_SINGLE_BOOL_EXT("HDMI ATMOS EDID Switch", |
| 0, |
| aml_get_atmos_audio_edid, |
| aml_set_atmos_audio_edid), |
| |
| SOC_ENUM_EXT("HDMIIN Audio Type", |
| hdmirx_audio_type_enum, |
| hdmirx_audio_type_get_enum, |
| NULL), |
| |
| SND_SOC_BYTES_TLV("HDMIIN AUDIO EDID", |
| MAX_AUDIO_EDID_LENGTH, |
| aml_get_audio_edid, |
| aml_set_audio_edid), |
| SOC_ENUM_EXT("HDMIIN Audio output mode", |
| hdmiin_audio_output_mode_enum, |
| aml_get_hdmiin_audio_output_mode, |
| aml_set_hdmiin_audio_output_mode), |
| #endif |
| #ifdef CONFIG_AMLOGIC_MEDIA_VIDEO |
| SOC_SINGLE_EXT("TVIN VIDEO DELAY", |
| 0, 0, 400, 0, |
| get_tvin_video_delay_enum, |
| set_tvin_video_delay_enum), |
| SOC_SINGLE_EXT("TVIN VIDEO MIN DELAY", |
| 0, 0, 0, 0, |
| get_tvin_video_min_delay_enum, |
| NULL), |
| SOC_SINGLE_EXT("TVIN VIDEO MAX DELAY", |
| 0, 0, 0, 0, |
| get_tvin_video_max_delay_enum, |
| NULL), |
| #endif |
| }; |
| |
| static const struct snd_soc_component_driver extn_component = { |
| .name = DRV_NAME, |
| .controls = extn_controls, |
| .ops = &extn_ops, |
| .num_controls = ARRAY_SIZE(extn_controls), |
| }; |
| |
| #ifndef CONFIG_AMLOGIC_REMOVE_OLD |
| struct extn_chipinfo tl1_extn_chipinfo = { |
| .arc_version = TL1_ARC, |
| .PAO_channel_sync = false, |
| .frhdmirx_version = TL1_FRHDMIRX, |
| }; |
| #endif |
| |
| struct extn_chipinfo tm2_extn_chipinfo = { |
| .arc_version = TM2_ARC, |
| .PAO_channel_sync = true, |
| .frhdmirx_version = TL1_FRHDMIRX, |
| }; |
| |
| struct extn_chipinfo t7_extn_chipinfo = { |
| .arc_version = T7_ARC, |
| .PAO_channel_sync = false, |
| .frhdmirx_version = T7_FRHDMIRX, |
| }; |
| |
| static const struct of_device_id extn_device_id[] = { |
| #ifndef CONFIG_AMLOGIC_REMOVE_OLD |
| { |
| .compatible = "amlogic, snd-extn", |
| }, |
| { |
| .compatible = "amlogic, tl1-snd-extn", |
| .data = &tl1_extn_chipinfo, |
| }, |
| #endif |
| { |
| .compatible = "amlogic, tm2-snd-extn", |
| .data = &tm2_extn_chipinfo, |
| }, |
| { |
| .compatible = "amlogic, t7-snd-extn", |
| .data = &t7_extn_chipinfo, |
| }, |
| {} |
| }; |
| |
| MODULE_DEVICE_TABLE(of, extn_device_id); |
| |
| static int extn_platform_probe(struct platform_device *pdev) |
| { |
| struct device_node *node = pdev->dev.of_node; |
| struct device_node *node_prt = NULL; |
| struct platform_device *pdev_parent; |
| struct device *dev = &pdev->dev; |
| struct aml_audio_controller *actrl = NULL; |
| struct extn *p_extn = NULL; |
| struct extn_chipinfo *p_chipinfo; |
| int ret = 0; |
| |
| p_extn = devm_kzalloc(dev, sizeof(struct extn), GFP_KERNEL); |
| if (!p_extn) |
| return -ENOMEM; |
| |
| p_extn->dev = dev; |
| dev_set_drvdata(dev, p_extn); |
| |
| /* match data */ |
| p_chipinfo = (struct extn_chipinfo *)of_device_get_match_data(dev); |
| p_extn->frhdmirx_version = 0; |
| if (!p_chipinfo) { |
| dev_warn_once(dev, "check whether to update chipinfo, exit\n"); |
| return -EINVAL; |
| } else { |
| p_extn->chipinfo = p_chipinfo; |
| p_extn->frhdmirx_version = p_chipinfo->frhdmirx_version; |
| } |
| |
| /* get audio controller */ |
| node_prt = of_get_parent(node); |
| if (!node_prt) |
| return -ENXIO; |
| |
| pdev_parent = of_find_device_by_node(node_prt); |
| of_node_put(node_prt); |
| actrl = (struct aml_audio_controller *) |
| platform_get_drvdata(pdev_parent); |
| p_extn->actrl = actrl; |
| |
| /* irqs */ |
| p_extn->irq_frhdmirx = platform_get_irq_byname(pdev, "irq_frhdmirx"); |
| if (p_extn->irq_frhdmirx < 0) { |
| dev_err(dev, "Failed to get irq_frhdmirx:%d\n", |
| p_extn->irq_frhdmirx); |
| return -ENXIO; |
| } |
| p_extn->irq_on = false; |
| |
| /* Default ARC SRC */ |
| p_extn->arc_src = SPDIFA_TO_HDMIRX; |
| |
| if (p_extn->chipinfo) { |
| if (p_extn->chipinfo->PAO_channel_sync) { |
| p_extn->hdmirx_mode = HDMIRX_MODE_PAO; |
| #if (defined CONFIG_AMLOGIC_MEDIA_TVIN_HDMI ||\ |
| defined CONFIG_AMLOGIC_MEDIA_TVIN_HDMI_MODULE) |
| rx_set_audio_param(HDMIRX_MODE_PAO); |
| #endif |
| } else { |
| p_extn->hdmirx_mode = HDMIRX_MODE_SPDIFIN; |
| #if (defined CONFIG_AMLOGIC_MEDIA_TVIN_HDMI ||\ |
| defined CONFIG_AMLOGIC_MEDIA_TVIN_HDMI_MODULE) |
| rx_set_audio_param(HDMIRX_MODE_SPDIFIN); |
| #endif |
| } |
| } |
| |
| s_extn = p_extn; |
| ret = devm_snd_soc_register_component(&pdev->dev, |
| &extn_component, |
| extn_dai, |
| ARRAY_SIZE(extn_dai)); |
| if (ret) { |
| dev_err(&pdev->dev, |
| "snd_soc_register_component failed\n"); |
| return ret; |
| } |
| |
| pr_info("%s, register soc platform\n", __func__); |
| return 0; |
| } |
| |
| struct platform_driver extn_driver = { |
| .driver = { |
| .name = DRV_NAME, |
| .of_match_table = extn_device_id, |
| }, |
| .probe = extn_platform_probe, |
| }; |
| |
| int __init extn_init(void) |
| { |
| return platform_driver_register(&(extn_driver)); |
| } |
| |
| void __exit extn_exit(void) |
| { |
| platform_driver_unregister(&extn_driver); |
| } |
| |
| #ifndef MODULE |
| module_init(extn_init); |
| module_exit(extn_exit); |
| MODULE_AUTHOR("Amlogic, Inc."); |
| MODULE_DESCRIPTION("Amlogic External Input/Output ASoc driver"); |
| MODULE_LICENSE("GPL"); |
| MODULE_ALIAS("Platform:" DRV_NAME); |
| MODULE_DEVICE_TABLE(of, extn_device_id); |
| #endif |