18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0+
28c2ecf20Sopenharmony_ci//
38c2ecf20Sopenharmony_ci// Copyright 2012 Freescale Semiconductor, Inc.
48c2ecf20Sopenharmony_ci// Copyright 2012 Linaro Ltd.
58c2ecf20Sopenharmony_ci
68c2ecf20Sopenharmony_ci#include <linux/module.h>
78c2ecf20Sopenharmony_ci#include <linux/of.h>
88c2ecf20Sopenharmony_ci#include <linux/of_platform.h>
98c2ecf20Sopenharmony_ci#include <linux/i2c.h>
108c2ecf20Sopenharmony_ci#include <linux/clk.h>
118c2ecf20Sopenharmony_ci#include <sound/soc.h>
128c2ecf20Sopenharmony_ci
138c2ecf20Sopenharmony_ci#include "../codecs/sgtl5000.h"
148c2ecf20Sopenharmony_ci#include "imx-audmux.h"
158c2ecf20Sopenharmony_ci
168c2ecf20Sopenharmony_ci#define DAI_NAME_SIZE	32
178c2ecf20Sopenharmony_ci
188c2ecf20Sopenharmony_cistruct imx_sgtl5000_data {
198c2ecf20Sopenharmony_ci	struct snd_soc_dai_link dai;
208c2ecf20Sopenharmony_ci	struct snd_soc_card card;
218c2ecf20Sopenharmony_ci	char codec_dai_name[DAI_NAME_SIZE];
228c2ecf20Sopenharmony_ci	char platform_name[DAI_NAME_SIZE];
238c2ecf20Sopenharmony_ci	struct clk *codec_clk;
248c2ecf20Sopenharmony_ci	unsigned int clk_frequency;
258c2ecf20Sopenharmony_ci};
268c2ecf20Sopenharmony_ci
278c2ecf20Sopenharmony_cistatic int imx_sgtl5000_dai_init(struct snd_soc_pcm_runtime *rtd)
288c2ecf20Sopenharmony_ci{
298c2ecf20Sopenharmony_ci	struct imx_sgtl5000_data *data = snd_soc_card_get_drvdata(rtd->card);
308c2ecf20Sopenharmony_ci	struct device *dev = rtd->card->dev;
318c2ecf20Sopenharmony_ci	int ret;
328c2ecf20Sopenharmony_ci
338c2ecf20Sopenharmony_ci	ret = snd_soc_dai_set_sysclk(asoc_rtd_to_codec(rtd, 0), SGTL5000_SYSCLK,
348c2ecf20Sopenharmony_ci				     data->clk_frequency, SND_SOC_CLOCK_IN);
358c2ecf20Sopenharmony_ci	if (ret) {
368c2ecf20Sopenharmony_ci		dev_err(dev, "could not set codec driver clock params\n");
378c2ecf20Sopenharmony_ci		return ret;
388c2ecf20Sopenharmony_ci	}
398c2ecf20Sopenharmony_ci
408c2ecf20Sopenharmony_ci	return 0;
418c2ecf20Sopenharmony_ci}
428c2ecf20Sopenharmony_ci
438c2ecf20Sopenharmony_cistatic const struct snd_soc_dapm_widget imx_sgtl5000_dapm_widgets[] = {
448c2ecf20Sopenharmony_ci	SND_SOC_DAPM_MIC("Mic Jack", NULL),
458c2ecf20Sopenharmony_ci	SND_SOC_DAPM_LINE("Line In Jack", NULL),
468c2ecf20Sopenharmony_ci	SND_SOC_DAPM_HP("Headphone Jack", NULL),
478c2ecf20Sopenharmony_ci	SND_SOC_DAPM_SPK("Line Out Jack", NULL),
488c2ecf20Sopenharmony_ci	SND_SOC_DAPM_SPK("Ext Spk", NULL),
498c2ecf20Sopenharmony_ci};
508c2ecf20Sopenharmony_ci
518c2ecf20Sopenharmony_cistatic int imx_sgtl5000_probe(struct platform_device *pdev)
528c2ecf20Sopenharmony_ci{
538c2ecf20Sopenharmony_ci	struct device_node *np = pdev->dev.of_node;
548c2ecf20Sopenharmony_ci	struct device_node *ssi_np, *codec_np;
558c2ecf20Sopenharmony_ci	struct platform_device *ssi_pdev;
568c2ecf20Sopenharmony_ci	struct i2c_client *codec_dev;
578c2ecf20Sopenharmony_ci	struct imx_sgtl5000_data *data = NULL;
588c2ecf20Sopenharmony_ci	struct snd_soc_dai_link_component *comp;
598c2ecf20Sopenharmony_ci	int int_port, ext_port;
608c2ecf20Sopenharmony_ci	int ret;
618c2ecf20Sopenharmony_ci
628c2ecf20Sopenharmony_ci	ret = of_property_read_u32(np, "mux-int-port", &int_port);
638c2ecf20Sopenharmony_ci	if (ret) {
648c2ecf20Sopenharmony_ci		dev_err(&pdev->dev, "mux-int-port missing or invalid\n");
658c2ecf20Sopenharmony_ci		return ret;
668c2ecf20Sopenharmony_ci	}
678c2ecf20Sopenharmony_ci	ret = of_property_read_u32(np, "mux-ext-port", &ext_port);
688c2ecf20Sopenharmony_ci	if (ret) {
698c2ecf20Sopenharmony_ci		dev_err(&pdev->dev, "mux-ext-port missing or invalid\n");
708c2ecf20Sopenharmony_ci		return ret;
718c2ecf20Sopenharmony_ci	}
728c2ecf20Sopenharmony_ci
738c2ecf20Sopenharmony_ci	/*
748c2ecf20Sopenharmony_ci	 * The port numbering in the hardware manual starts at 1, while
758c2ecf20Sopenharmony_ci	 * the audmux API expects it starts at 0.
768c2ecf20Sopenharmony_ci	 */
778c2ecf20Sopenharmony_ci	int_port--;
788c2ecf20Sopenharmony_ci	ext_port--;
798c2ecf20Sopenharmony_ci	ret = imx_audmux_v2_configure_port(int_port,
808c2ecf20Sopenharmony_ci			IMX_AUDMUX_V2_PTCR_SYN |
818c2ecf20Sopenharmony_ci			IMX_AUDMUX_V2_PTCR_TFSEL(ext_port) |
828c2ecf20Sopenharmony_ci			IMX_AUDMUX_V2_PTCR_TCSEL(ext_port) |
838c2ecf20Sopenharmony_ci			IMX_AUDMUX_V2_PTCR_TFSDIR |
848c2ecf20Sopenharmony_ci			IMX_AUDMUX_V2_PTCR_TCLKDIR,
858c2ecf20Sopenharmony_ci			IMX_AUDMUX_V2_PDCR_RXDSEL(ext_port));
868c2ecf20Sopenharmony_ci	if (ret) {
878c2ecf20Sopenharmony_ci		dev_err(&pdev->dev, "audmux internal port setup failed\n");
888c2ecf20Sopenharmony_ci		return ret;
898c2ecf20Sopenharmony_ci	}
908c2ecf20Sopenharmony_ci	ret = imx_audmux_v2_configure_port(ext_port,
918c2ecf20Sopenharmony_ci			IMX_AUDMUX_V2_PTCR_SYN,
928c2ecf20Sopenharmony_ci			IMX_AUDMUX_V2_PDCR_RXDSEL(int_port));
938c2ecf20Sopenharmony_ci	if (ret) {
948c2ecf20Sopenharmony_ci		dev_err(&pdev->dev, "audmux external port setup failed\n");
958c2ecf20Sopenharmony_ci		return ret;
968c2ecf20Sopenharmony_ci	}
978c2ecf20Sopenharmony_ci
988c2ecf20Sopenharmony_ci	ssi_np = of_parse_phandle(pdev->dev.of_node, "ssi-controller", 0);
998c2ecf20Sopenharmony_ci	codec_np = of_parse_phandle(pdev->dev.of_node, "audio-codec", 0);
1008c2ecf20Sopenharmony_ci	if (!ssi_np || !codec_np) {
1018c2ecf20Sopenharmony_ci		dev_err(&pdev->dev, "phandle missing or invalid\n");
1028c2ecf20Sopenharmony_ci		ret = -EINVAL;
1038c2ecf20Sopenharmony_ci		goto fail;
1048c2ecf20Sopenharmony_ci	}
1058c2ecf20Sopenharmony_ci
1068c2ecf20Sopenharmony_ci	ssi_pdev = of_find_device_by_node(ssi_np);
1078c2ecf20Sopenharmony_ci	if (!ssi_pdev) {
1088c2ecf20Sopenharmony_ci		dev_dbg(&pdev->dev, "failed to find SSI platform device\n");
1098c2ecf20Sopenharmony_ci		ret = -EPROBE_DEFER;
1108c2ecf20Sopenharmony_ci		goto fail;
1118c2ecf20Sopenharmony_ci	}
1128c2ecf20Sopenharmony_ci	put_device(&ssi_pdev->dev);
1138c2ecf20Sopenharmony_ci	codec_dev = of_find_i2c_device_by_node(codec_np);
1148c2ecf20Sopenharmony_ci	if (!codec_dev) {
1158c2ecf20Sopenharmony_ci		dev_dbg(&pdev->dev, "failed to find codec platform device\n");
1168c2ecf20Sopenharmony_ci		ret = -EPROBE_DEFER;
1178c2ecf20Sopenharmony_ci		goto fail;
1188c2ecf20Sopenharmony_ci	}
1198c2ecf20Sopenharmony_ci
1208c2ecf20Sopenharmony_ci	data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
1218c2ecf20Sopenharmony_ci	if (!data) {
1228c2ecf20Sopenharmony_ci		ret = -ENOMEM;
1238c2ecf20Sopenharmony_ci		goto put_device;
1248c2ecf20Sopenharmony_ci	}
1258c2ecf20Sopenharmony_ci
1268c2ecf20Sopenharmony_ci	comp = devm_kzalloc(&pdev->dev, 3 * sizeof(*comp), GFP_KERNEL);
1278c2ecf20Sopenharmony_ci	if (!comp) {
1288c2ecf20Sopenharmony_ci		ret = -ENOMEM;
1298c2ecf20Sopenharmony_ci		goto put_device;
1308c2ecf20Sopenharmony_ci	}
1318c2ecf20Sopenharmony_ci
1328c2ecf20Sopenharmony_ci	data->codec_clk = clk_get(&codec_dev->dev, NULL);
1338c2ecf20Sopenharmony_ci	if (IS_ERR(data->codec_clk)) {
1348c2ecf20Sopenharmony_ci		ret = PTR_ERR(data->codec_clk);
1358c2ecf20Sopenharmony_ci		goto put_device;
1368c2ecf20Sopenharmony_ci	}
1378c2ecf20Sopenharmony_ci
1388c2ecf20Sopenharmony_ci	data->clk_frequency = clk_get_rate(data->codec_clk);
1398c2ecf20Sopenharmony_ci
1408c2ecf20Sopenharmony_ci	data->dai.cpus		= &comp[0];
1418c2ecf20Sopenharmony_ci	data->dai.codecs	= &comp[1];
1428c2ecf20Sopenharmony_ci	data->dai.platforms	= &comp[2];
1438c2ecf20Sopenharmony_ci
1448c2ecf20Sopenharmony_ci	data->dai.num_cpus	= 1;
1458c2ecf20Sopenharmony_ci	data->dai.num_codecs	= 1;
1468c2ecf20Sopenharmony_ci	data->dai.num_platforms	= 1;
1478c2ecf20Sopenharmony_ci
1488c2ecf20Sopenharmony_ci	data->dai.name = "HiFi";
1498c2ecf20Sopenharmony_ci	data->dai.stream_name = "HiFi";
1508c2ecf20Sopenharmony_ci	data->dai.codecs->dai_name = "sgtl5000";
1518c2ecf20Sopenharmony_ci	data->dai.codecs->of_node = codec_np;
1528c2ecf20Sopenharmony_ci	data->dai.cpus->of_node = ssi_np;
1538c2ecf20Sopenharmony_ci	data->dai.platforms->of_node = ssi_np;
1548c2ecf20Sopenharmony_ci	data->dai.init = &imx_sgtl5000_dai_init;
1558c2ecf20Sopenharmony_ci	data->dai.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
1568c2ecf20Sopenharmony_ci			    SND_SOC_DAIFMT_CBM_CFM;
1578c2ecf20Sopenharmony_ci
1588c2ecf20Sopenharmony_ci	data->card.dev = &pdev->dev;
1598c2ecf20Sopenharmony_ci	ret = snd_soc_of_parse_card_name(&data->card, "model");
1608c2ecf20Sopenharmony_ci	if (ret)
1618c2ecf20Sopenharmony_ci		goto put_device;
1628c2ecf20Sopenharmony_ci	ret = snd_soc_of_parse_audio_routing(&data->card, "audio-routing");
1638c2ecf20Sopenharmony_ci	if (ret)
1648c2ecf20Sopenharmony_ci		goto put_device;
1658c2ecf20Sopenharmony_ci	data->card.num_links = 1;
1668c2ecf20Sopenharmony_ci	data->card.owner = THIS_MODULE;
1678c2ecf20Sopenharmony_ci	data->card.dai_link = &data->dai;
1688c2ecf20Sopenharmony_ci	data->card.dapm_widgets = imx_sgtl5000_dapm_widgets;
1698c2ecf20Sopenharmony_ci	data->card.num_dapm_widgets = ARRAY_SIZE(imx_sgtl5000_dapm_widgets);
1708c2ecf20Sopenharmony_ci
1718c2ecf20Sopenharmony_ci	platform_set_drvdata(pdev, &data->card);
1728c2ecf20Sopenharmony_ci	snd_soc_card_set_drvdata(&data->card, data);
1738c2ecf20Sopenharmony_ci
1748c2ecf20Sopenharmony_ci	ret = devm_snd_soc_register_card(&pdev->dev, &data->card);
1758c2ecf20Sopenharmony_ci	if (ret) {
1768c2ecf20Sopenharmony_ci		if (ret != -EPROBE_DEFER)
1778c2ecf20Sopenharmony_ci			dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n",
1788c2ecf20Sopenharmony_ci				ret);
1798c2ecf20Sopenharmony_ci		goto put_device;
1808c2ecf20Sopenharmony_ci	}
1818c2ecf20Sopenharmony_ci
1828c2ecf20Sopenharmony_ci	of_node_put(ssi_np);
1838c2ecf20Sopenharmony_ci	of_node_put(codec_np);
1848c2ecf20Sopenharmony_ci
1858c2ecf20Sopenharmony_ci	return 0;
1868c2ecf20Sopenharmony_ci
1878c2ecf20Sopenharmony_ciput_device:
1888c2ecf20Sopenharmony_ci	put_device(&codec_dev->dev);
1898c2ecf20Sopenharmony_cifail:
1908c2ecf20Sopenharmony_ci	if (data && !IS_ERR(data->codec_clk))
1918c2ecf20Sopenharmony_ci		clk_put(data->codec_clk);
1928c2ecf20Sopenharmony_ci	of_node_put(ssi_np);
1938c2ecf20Sopenharmony_ci	of_node_put(codec_np);
1948c2ecf20Sopenharmony_ci
1958c2ecf20Sopenharmony_ci	return ret;
1968c2ecf20Sopenharmony_ci}
1978c2ecf20Sopenharmony_ci
1988c2ecf20Sopenharmony_cistatic int imx_sgtl5000_remove(struct platform_device *pdev)
1998c2ecf20Sopenharmony_ci{
2008c2ecf20Sopenharmony_ci	struct snd_soc_card *card = platform_get_drvdata(pdev);
2018c2ecf20Sopenharmony_ci	struct imx_sgtl5000_data *data = snd_soc_card_get_drvdata(card);
2028c2ecf20Sopenharmony_ci
2038c2ecf20Sopenharmony_ci	clk_put(data->codec_clk);
2048c2ecf20Sopenharmony_ci
2058c2ecf20Sopenharmony_ci	return 0;
2068c2ecf20Sopenharmony_ci}
2078c2ecf20Sopenharmony_ci
2088c2ecf20Sopenharmony_cistatic const struct of_device_id imx_sgtl5000_dt_ids[] = {
2098c2ecf20Sopenharmony_ci	{ .compatible = "fsl,imx-audio-sgtl5000", },
2108c2ecf20Sopenharmony_ci	{ /* sentinel */ }
2118c2ecf20Sopenharmony_ci};
2128c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(of, imx_sgtl5000_dt_ids);
2138c2ecf20Sopenharmony_ci
2148c2ecf20Sopenharmony_cistatic struct platform_driver imx_sgtl5000_driver = {
2158c2ecf20Sopenharmony_ci	.driver = {
2168c2ecf20Sopenharmony_ci		.name = "imx-sgtl5000",
2178c2ecf20Sopenharmony_ci		.pm = &snd_soc_pm_ops,
2188c2ecf20Sopenharmony_ci		.of_match_table = imx_sgtl5000_dt_ids,
2198c2ecf20Sopenharmony_ci	},
2208c2ecf20Sopenharmony_ci	.probe = imx_sgtl5000_probe,
2218c2ecf20Sopenharmony_ci	.remove = imx_sgtl5000_remove,
2228c2ecf20Sopenharmony_ci};
2238c2ecf20Sopenharmony_cimodule_platform_driver(imx_sgtl5000_driver);
2248c2ecf20Sopenharmony_ci
2258c2ecf20Sopenharmony_ciMODULE_AUTHOR("Shawn Guo <shawn.guo@linaro.org>");
2268c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("Freescale i.MX SGTL5000 ASoC machine driver");
2278c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL v2");
2288c2ecf20Sopenharmony_ciMODULE_ALIAS("platform:imx-sgtl5000");
229