// SPDX-License-Identifier: GPL-2.0
//
// Freescale Generic ASoC Sound Card driver with ASRC
//
// Copyright (C) 2014 Freescale Semiconductor, Inc.
//
// Author: Nicolin Chen <nicoleotsuka@gmail.com>
#include <linux/clk.h>
#include <linux/i2c.h>
#include <linux/module.h>
#include <linux/of_platform.h>
#if IS_ENABLED(CONFIG_SND_AC97_CODEC)
#include <sound/ac97_codec.h>
#endif
#include <sound/pcm_params.h>
#include <sound/soc.h>
#include <sound/jack.h>
#include <sound/simple_card_utils.h>
#include "fsl_esai.h"
#include "fsl_sai.h"
#include "imx-audmux.h"
#include "../codecs/sgtl5000.h"
#include "../codecs/wm8962.h"
#include "../codecs/wm8960.h"
#include "../codecs/wm8994.h"
#include "../codecs/tlv320aic31xx.h"
#include "../codecs/nau8822.h"
#include "../codecs/wm8904.h"
#define DRIVER_NAME "fsl-asoc-card"
#define CS427x_SYSCLK_MCLK 0
#define RX 0
#define TX 1
/* Default DAI format without Master and Slave flag */
#define DAI_FMT_BASE (SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF)
/**
* struct codec_priv - CODEC private data
* @mclk: Main clock of the CODEC
* @mclk_freq: Clock rate of MCLK
* @free_freq: Clock rate of MCLK for hw_free()
* @mclk_id: MCLK (or main clock) id for set_sysclk()
* @fll_id: FLL (or secordary clock) id for set_sysclk()
* @pll_id: PLL id for set_pll()
*/
struct codec_priv {
struct clk *mclk;
unsigned long mclk_freq;
unsigned long free_freq;
u32 mclk_id;
int fll_id;
int pll_id;
};
/**
* struct cpu_priv - CPU private data
* @sysclk_freq: SYSCLK rates for set_sysclk()
* @sysclk_dir: SYSCLK directions for set_sysclk()
* @sysclk_id: SYSCLK ids for set_sysclk()
* @slot_width: Slot width of each frame
* @slot_num: Number of slots of each frame
*
* Note: [1] for tx and [0] for rx
*/
struct cpu_priv {
unsigned long sysclk_freq[2];
u32 sysclk_dir[2];
u32 sysclk_id[2];
u32 slot_width;
u32 slot_num;
};
/**
* struct fsl_asoc_card_priv - Freescale Generic ASOC card private data
* @dai_link: DAI link structure including normal one and DPCM link
* @hp_jack: Headphone Jack structure
* @mic_jack: Microphone Jack structure
* @pdev: platform device pointer
* @codec_priv: CODEC private data
* @cpu_priv: CPU private data
* @card: ASoC card structure
* @streams: Mask of current active streams
* @sample_rate: Current sample rate
* @sample_format: Current sample format
* @asrc_rate: ASRC sample rate used by Back-Ends
* @asrc_format: ASRC sample format used by Back-Ends
* @dai_fmt: DAI format between CPU and CODEC
* @name: Card name
*/
struct fsl_asoc_card_priv {
struct snd_soc_dai_link dai_link[3];
struct simple_util_jack hp_jack;
struct simple_util_jack mic_jack;
struct platform_device *pdev;
struct codec_priv codec_priv[2];
struct cpu_priv cpu_priv;
struct snd_soc_card card;
u8 streams;
u32 sample_rate;
snd_pcm_format_t sample_format;
u32 asrc_rate;
snd_pcm_format_t asrc_format;
u32 dai_fmt;
char name[32];
};
/*
* This dapm route map exists for DPCM link only.
* The other routes shall go through Device Tree.
*
* Note: keep all ASRC routes in the second half
* to drop them easily for non-ASRC cases.
*/
static const struct snd_soc_dapm_route audio_map[] = {
/* 1st half -- Normal DAPM routes */
{"Playback", NULL, "CPU-Playback"},
{"CPU-Capture", NULL, "Capture"},
/* 2nd half -- ASRC DAPM routes */
{"CPU-Playback", NULL, "ASRC-Playback"},
{"ASRC-Capture", NULL, "CPU-Capture"},
};
static const struct snd_soc_dapm_route audio_map_ac97[] = {
/* 1st half -- Normal DAPM routes */
{"AC97 Playback", NULL, "CPU AC97 Playback"},
{"CPU AC97 Capture", NULL, "AC97 Capture"},
/* 2nd half -- ASRC DAPM routes */
{"CPU AC97 Playback", NULL, "ASRC-Playback"},
{"ASRC-Capture", NULL, "CPU AC97 Capture"},
};
static const struct snd_soc_dapm_route audio_map_tx[] = {
/* 1st half -- Normal DAPM routes */
{"Playback", NULL, "CPU-Playback"},
/* 2nd half -- ASRC DAPM routes */
{"CPU-Playback", NULL, "ASRC-Playback"},
};
static const struct snd_soc_dapm_route audio_map_rx[] = {
/* 1st half -- Normal DAPM routes */
{"CPU-Capture", NULL, "Capture"},
/* 2nd half -- ASRC DAPM routes */
{"ASRC-Capture", NULL, "CPU-Capture"},
};
/* Add all possible widgets into here without being redundant */
static const struct snd_soc_dapm_widget fsl_asoc_card_dapm_widgets[] = {
SND_SOC_DAPM_LINE("Line Out Jack", NULL),
SND_SOC_DAPM_LINE("Line In Jack", NULL),
SND_SOC_DAPM_HP("Headphone Jack", NULL),
SND_SOC_DAPM_SPK("Ext Spk", NULL),
SND_SOC_DAPM_MIC("Mic Jack", NULL),
SND_SOC_DAPM_MIC("AMIC", NULL),
SND_SOC_DAPM_MIC("DMIC", NULL),
};
static bool fsl_asoc_card_is_ac97(struct fsl_asoc_card_priv *priv)
{
return priv->dai_fmt == SND_SOC_DAIFMT_AC97;
}
static int fsl_asoc_card_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
struct fsl_asoc_card_priv *priv = snd_soc_card_get_drvdata(rtd->card);
bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
struct codec_priv *codec_priv;
struct snd_soc_dai *codec_dai;
struct cpu_priv *cpu_priv = &priv->cpu_priv;
struct device *dev = rtd->card->dev;
unsigned int pll_out;
int codec_idx;
int ret;
priv->sample_rate = params_rate(params);
priv->sample_format = params_format(params);
priv->streams |= BIT(substream->stream);
if (fsl_asoc_card_is_ac97(priv))
return 0;
/* Specific configurations of DAIs starts from here */
ret = snd_soc_dai_set_sysclk(snd_soc_rtd_to_cpu(rtd, 0), cpu_priv->sy