Due to issues with the Internet.ee domain registry, our main domain, paste.ee, is currently disabled due to abuse reports. We are looking into alternative domains to continue operation, but for now the pastee.dev domain is the primary domain.
If you wish to blame someone, blame the scum using this site as a malware host.
Description: imx-wm8510.c
Submitted on February 28, 2019 at 08:51 AM

imx-wm8510.c (Text)

/*
 * Copyright (c) 2015 exceet electronics GmbH
 * Author: Frieder Schrempf <frieder.schrempf@exceet.de>
 *
 * Recording not tested !!!
 *
 * Based on imx-wm8962.c
 * Copyright (C) 2012 Freescale Semiconductor, Inc.
 * Copyright (C) 2012 Linaro Ltd.
 *
 * The code contained herein is licensed under the GNU General Public
 * License. You may obtain a copy of the GNU General Public License
 * Version 2 or later at the following locations:
 *
 * http://www.opensource.org/licenses/gpl-license.html
 * http://www.gnu.org/copyleft/gpl.html
 */

#include <linux/module.h>
#include <linux/of_platform.h>
#include <linux/i2c.h>
#include <linux/of_gpio.h>
#include <linux/slab.h>
#include <linux/gpio.h>
#include <linux/clk.h>
#include <sound/soc.h>
#include <sound/jack.h>
#include <sound/control.h>
#include <sound/pcm_params.h>
#include <sound/soc-dapm.h>
#include <linux/pinctrl/consumer.h>

#include "../codecs/wm8510.h"
#include "imx-audmux.h"

#define DAI_NAME_SIZE	32

struct imx_wm8510_data {
	struct snd_soc_dai_link dai;
	struct snd_soc_card card;
	char codec_dai_name[DAI_NAME_SIZE];
	char platform_name[DAI_NAME_SIZE];
	struct clk *codec_clk;
	unsigned int mclk_frequency;
	unsigned int mclk_source;
};

struct imx_priv {
	bool amic_mono;
	bool dmic_mono;
	struct snd_soc_codec *codec;
	struct platform_device *pdev;
	struct snd_pcm_substream *first_stream;
	struct snd_card *snd_card;
};
static struct imx_priv card_priv;

static const struct snd_soc_dapm_widget imx_wm8510_dapm_widgets[] = {
	SND_SOC_DAPM_MIC("Int Mic", NULL),
	SND_SOC_DAPM_SPK("Ext Spk", NULL),
};

static int imx_hifi_hw_params(struct snd_pcm_substream *substream,
				     struct snd_pcm_hw_params *params)
{
	struct snd_soc_pcm_runtime *rtd = substream->private_data;
	struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
	struct snd_soc_dai *codec_dai = rtd->codec_dai;
	struct imx_priv *priv = &card_priv;
	struct device *dev = &priv->pdev->dev;
	struct snd_soc_card *card = codec_dai->codec->card;
	struct imx_wm8510_data *data = snd_soc_card_get_drvdata(card);
	unsigned int channels = params_channels(params);
	unsigned int sample_rate = params_rate(params);
	snd_pcm_format_t sample_format = params_format(params);
	u32 dai_format, pll_out = 0, bclk = 0, mclk_div = 0;
	int ret = 0;


	dai_format = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
	SND_SOC_DAIFMT_CBM_CFM;

	/* set codec DAI configuration */
	ret = snd_soc_dai_set_fmt(codec_dai, dai_format);
	if (ret < 0) {
		pr_err("imx-wm8510: Failed to set CODEC DAI CONFIGURATION fmt (%d)\n", ret);
		return ret;
	}

	/* set i.MX active slot mask */
	snd_soc_dai_set_tdm_slot(cpu_dai,
				 channels == 1 ? 0xfffffffe : 0xfffffffc,
				 channels == 1 ? 0xfffffffe : 0xfffffffc,
				 2, 32);

	dai_format = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_IF |
		SND_SOC_DAIFMT_CBM_CFM;

	/* set cpu DAI configuration */
	ret = snd_soc_dai_set_fmt(cpu_dai, dai_format);
	if (ret < 0) {
		pr_err("imx-wm8510: Failed to set CPU DAI CONFIGURATION fmt (%d)\n", ret);
		return ret;
	}

	/*
	* Figure out PLL and BCLK dividers for WM8510
	*/
	switch (sample_rate) {
	case 48000:
		pll_out = 24576000;
		mclk_div = WM8510_MCLKDIV_2;
		bclk = WM8510_BCLKDIV_8;
		break;

	case 44100:
		pll_out = 22579200;
		mclk_div = WM8510_MCLKDIV_2;
		bclk = WM8510_BCLKDIV_8;
		break;

	case 22050:
		pll_out = 22579200;
		mclk_div = WM8510_MCLKDIV_4;
		bclk = WM8510_BCLKDIV_8;
		break;

	case 16000:
		pll_out = 24576000;
		mclk_div = WM8510_MCLKDIV_6;
		bclk = WM8510_BCLKDIV_8;
		break;

	case 11025:
		pll_out = 22579200;
		mclk_div = WM8510_MCLKDIV_8;
		bclk = WM8510_BCLKDIV_8;
		break;

	case 8000:
		pll_out = 24576000;
		mclk_div = WM8510_MCLKDIV_12;
		bclk = WM8510_BCLKDIV_8;
		break;

	default:
		pr_warning("imx-wm8510: Unsupported sample rate %d\n",sample_rate);
		return -EINVAL;
	}

	ret = snd_soc_dai_set_clkdiv(codec_dai, WM8510_BCLKDIV, bclk);
	if (ret < 0) {
		pr_warning("imx-wm8510: Failed to set CODEC DAI BCLKDIV (%d)\n", ret);
		return ret;
	}

	ret = snd_soc_dai_set_pll(codec_dai, 0, 0, data->mclk_frequency, pll_out);
	if (ret < 0)
		pr_err("imx-wm8510: Failed to start PLL: %d\n", ret);


	ret = snd_soc_dai_set_clkdiv(codec_dai, WM8510_MCLKDIV, mclk_div);
	if (ret < 0) {
		pr_warning("imx-wm8510: Failed to set CODEC MCLKDIV (%d)\n",ret);
		return ret;
	}

	return 0;
}

static struct snd_soc_ops imx_hifi_ops = {
	.hw_params = imx_hifi_hw_params,
};

static int imx_wm8510_probe(struct platform_device *pdev)
{
	struct device_node *np = pdev->dev.of_node;
	struct device_node *cpu_np, *codec_np;
	struct platform_device *cpu_pdev;
	struct imx_priv *priv = &card_priv;
	struct i2c_client *codec_dev;
	struct imx_wm8510_data *data;
	int int_port, ext_port;
	int ret;

	priv->pdev = pdev;

	cpu_np = of_parse_phandle(pdev->dev.of_node, "cpu-dai", 0);
	if (!cpu_np) {
		dev_err(&pdev->dev, "cpu dai phandle missing or invalid\n");
		ret = -EINVAL;
		goto fail;
	}

	if (!strstr(cpu_np->name, "ssi"))
		goto audmux_bypass;

	ret = of_property_read_u32(np, "mux-int-port", &int_port);
	if (ret) {
		dev_err(&pdev->dev, "mux-int-port missing or invalid\n");
		return ret;
	}
	ret = of_property_read_u32(np, "mux-ext-port", &ext_port);
	if (ret) {
		dev_err(&pdev->dev, "mux-ext-port missing or invalid\n");
		return ret;
	}

	/*
	 * The port numbering in the hardware manual starts at 1, while
	 * the audmux API expects it starts at 0.
	 */
	int_port--;
	ext_port--;
	ret = imx_audmux_v2_configure_port(int_port,
			IMX_AUDMUX_V2_PTCR_SYN |
			IMX_AUDMUX_V2_PTCR_TFSEL(ext_port) |
			IMX_AUDMUX_V2_PTCR_TCSEL(ext_port) |
			IMX_AUDMUX_V2_PTCR_TFSDIR |
			IMX_AUDMUX_V2_PTCR_TCLKDIR,
			IMX_AUDMUX_V2_PDCR_RXDSEL(ext_port));
	if (ret) {
		dev_err(&pdev->dev, "audmux internal port setup failed\n");
		return ret;
	}
	imx_audmux_v2_configure_port(ext_port,
			IMX_AUDMUX_V2_PTCR_SYN,
			IMX_AUDMUX_V2_PDCR_RXDSEL(int_port));
	if (ret) {
		dev_err(&pdev->dev, "audmux external port setup failed\n");
		return ret;
	}

	audmux_bypass:
	codec_np = of_parse_phandle(pdev->dev.of_node, "audio-codec", 0);
	if (!codec_np) {
		dev_err(&pdev->dev, "phandle missing or invalid\n");
		ret = -EINVAL;
		goto fail;
	}

	cpu_pdev = of_find_device_by_node(cpu_np);
	if (!cpu_pdev) {
		dev_err(&pdev->dev, "failed to find SSI platform device\n");
		ret = -EINVAL;
		goto fail;
	}
	codec_dev = of_find_i2c_device_by_node(codec_np);
	if (!codec_dev) {
		dev_err(&pdev->dev, "failed to find codec platform device\n");
		ret = -EINVAL;
		goto fail;
	}

	priv->first_stream = NULL;

	data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
	if (!data) {
		ret = -ENOMEM;
		goto fail;
	}

	
	data->codec_clk = devm_clk_get(&codec_dev->dev, "audio_mclk");
	if (IS_ERR(data->codec_clk)) {
		ret = PTR_ERR(data->codec_clk);
		dev_err(&codec_dev->dev, "failed to get codec clk: %d\n", ret);
		goto fail;
	}

	ret = of_property_read_u32(codec_dev->dev.of_node, "mclk_frequency", (u32 *) &(data->mclk_frequency));
	if (ret) {
		dev_err(&codec_dev->dev, "mclk missing or invalid\n");
		return ret;
	}

	data->mclk_frequency = clk_get_rate(data->codec_clk);
	ret = clk_prepare_enable(data->codec_clk);
	if (ret) {
		dev_err(&codec_dev->dev, "failed to enable mclk\n");
		return ret;
	}

	priv->amic_mono = of_property_read_bool(codec_np, "amic-mono");
	priv->dmic_mono = of_property_read_bool(codec_np, "dmic-mono");

	data->dai.name = "WM8510";
	data->dai.stream_name = "WM8510 PCM";
	data->dai.codec_dai_name = "wm8510-hifi";
	data->dai.codec_of_node = codec_np;
	data->dai.cpu_dai_name = dev_name(&cpu_pdev->dev);
	data->dai.platform_of_node = cpu_np;
	data->dai.ops = &imx_hifi_ops;
	data->dai.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
			    SND_SOC_DAIFMT_CBM_CFM;

	data->card.dev = &pdev->dev;
	ret = snd_soc_of_parse_card_name(&data->card, "model");
	if (ret)
		goto fail;
	ret = snd_soc_of_parse_audio_routing(&data->card, "audio-routing");
	if (ret)
		goto fail;
	data->card.num_links = 1;
	data->card.dai_link = &data->dai;
	data->card.dapm_widgets = imx_wm8510_dapm_widgets;
	data->card.num_dapm_widgets = ARRAY_SIZE(imx_wm8510_dapm_widgets);

	platform_set_drvdata(pdev, &data->card);
	snd_soc_card_set_drvdata(&data->card, data);

	ret = snd_soc_register_card(&data->card);
	if (ret) {
		dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", ret);
		goto fail;
	}

	priv->snd_card = data->card.snd_card;

fail:
	if (cpu_np)
		of_node_put(cpu_np);
	if (codec_np)
		of_node_put(codec_np);

	return ret;
}

static int imx_wm8510_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_wm8510_dt_ids[] = {
	{ .compatible = "fsl,imx-audio-wm8510", },
	{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, imx_wm8510_dt_ids);

static struct platform_driver imx_wm8510_driver = {
	.driver = {
		.name = "imx-wm8510",
		.owner = THIS_MODULE,
		.pm = &snd_soc_pm_ops,
		.of_match_table = imx_wm8510_dt_ids,
	},
	.probe = imx_wm8510_probe,
	.remove = imx_wm8510_remove,
};
module_platform_driver(imx_wm8510_driver);

MODULE_AUTHOR("exceet electronics GmbH");
MODULE_DESCRIPTION("Freescale i.MX WM8510 ASoC machine driver");
MODULE_LICENSE("GPL v2");
MODULE_ALIAS("platform:imx-wm8510");