16b24e03eSFabio Estevam // SPDX-License-Identifier: GPL-2.0+ 26b24e03eSFabio Estevam // 36b24e03eSFabio Estevam // Copyright 2012 Freescale Semiconductor, Inc. 46b24e03eSFabio Estevam // Copyright 2012 Linaro Ltd. 5c448303eSShawn Guo 6c448303eSShawn Guo #include <linux/module.h> 7c448303eSShawn Guo #include <linux/of.h> 8c448303eSShawn Guo #include <linux/of_platform.h> 9687b81d0SWolfram Sang #include <linux/i2c.h> 1081e8e492SRichard Zhao #include <linux/clk.h> 11c448303eSShawn Guo #include <sound/soc.h> 12c448303eSShawn Guo 13c448303eSShawn Guo #include "../codecs/sgtl5000.h" 14c448303eSShawn Guo #include "imx-audmux.h" 15c448303eSShawn Guo 16c448303eSShawn Guo #define DAI_NAME_SIZE 32 17c448303eSShawn Guo 18c448303eSShawn Guo struct imx_sgtl5000_data { 19c448303eSShawn Guo struct snd_soc_dai_link dai; 20c448303eSShawn Guo struct snd_soc_card card; 21c448303eSShawn Guo char codec_dai_name[DAI_NAME_SIZE]; 22c448303eSShawn Guo char platform_name[DAI_NAME_SIZE]; 2381e8e492SRichard Zhao struct clk *codec_clk; 24c448303eSShawn Guo unsigned int clk_frequency; 25c448303eSShawn Guo }; 26c448303eSShawn Guo 27c448303eSShawn Guo static int imx_sgtl5000_dai_init(struct snd_soc_pcm_runtime *rtd) 28c448303eSShawn Guo { 2947cf84e1SShawn Guo struct imx_sgtl5000_data *data = snd_soc_card_get_drvdata(rtd->card); 30c448303eSShawn Guo struct device *dev = rtd->card->dev; 31c448303eSShawn Guo int ret; 32c448303eSShawn Guo 3317198ae7SKuninori Morimoto ret = snd_soc_dai_set_sysclk(asoc_rtd_to_codec(rtd, 0), SGTL5000_SYSCLK, 34c448303eSShawn Guo data->clk_frequency, SND_SOC_CLOCK_IN); 35c448303eSShawn Guo if (ret) { 36c448303eSShawn Guo dev_err(dev, "could not set codec driver clock params\n"); 37c448303eSShawn Guo return ret; 38c448303eSShawn Guo } 39c448303eSShawn Guo 40c448303eSShawn Guo return 0; 41c448303eSShawn Guo } 42c448303eSShawn Guo 43172b4c5cSShawn Guo static const struct snd_soc_dapm_widget imx_sgtl5000_dapm_widgets[] = { 44172b4c5cSShawn Guo SND_SOC_DAPM_MIC("Mic Jack", NULL), 45172b4c5cSShawn Guo SND_SOC_DAPM_LINE("Line In Jack", NULL), 46172b4c5cSShawn Guo SND_SOC_DAPM_HP("Headphone Jack", NULL), 47172b4c5cSShawn Guo SND_SOC_DAPM_SPK("Line Out Jack", NULL), 48172b4c5cSShawn Guo SND_SOC_DAPM_SPK("Ext Spk", NULL), 49172b4c5cSShawn Guo }; 50172b4c5cSShawn Guo 51a0a3d518SBill Pemberton static int imx_sgtl5000_probe(struct platform_device *pdev) 52c448303eSShawn Guo { 53c448303eSShawn Guo struct device_node *np = pdev->dev.of_node; 54c448303eSShawn Guo struct device_node *ssi_np, *codec_np; 55c448303eSShawn Guo struct platform_device *ssi_pdev; 5681e8e492SRichard Zhao struct i2c_client *codec_dev; 5750d4a790SPhilipp Zabel struct imx_sgtl5000_data *data = NULL; 583026ef68SKuninori Morimoto struct snd_soc_dai_link_component *comp; 59c448303eSShawn Guo int int_port, ext_port; 60c448303eSShawn Guo int ret; 61c448303eSShawn Guo 62c448303eSShawn Guo ret = of_property_read_u32(np, "mux-int-port", &int_port); 63c448303eSShawn Guo if (ret) { 64c448303eSShawn Guo dev_err(&pdev->dev, "mux-int-port missing or invalid\n"); 65c448303eSShawn Guo return ret; 66c448303eSShawn Guo } 67c448303eSShawn Guo ret = of_property_read_u32(np, "mux-ext-port", &ext_port); 68c448303eSShawn Guo if (ret) { 69c448303eSShawn Guo dev_err(&pdev->dev, "mux-ext-port missing or invalid\n"); 70c448303eSShawn Guo return ret; 71c448303eSShawn Guo } 72c448303eSShawn Guo 73c448303eSShawn Guo /* 74c448303eSShawn Guo * The port numbering in the hardware manual starts at 1, while 75c448303eSShawn Guo * the audmux API expects it starts at 0. 76c448303eSShawn Guo */ 77c448303eSShawn Guo int_port--; 78c448303eSShawn Guo ext_port--; 79c448303eSShawn Guo ret = imx_audmux_v2_configure_port(int_port, 80c448303eSShawn Guo IMX_AUDMUX_V2_PTCR_SYN | 81c448303eSShawn Guo IMX_AUDMUX_V2_PTCR_TFSEL(ext_port) | 82c448303eSShawn Guo IMX_AUDMUX_V2_PTCR_TCSEL(ext_port) | 83c448303eSShawn Guo IMX_AUDMUX_V2_PTCR_TFSDIR | 84c448303eSShawn Guo IMX_AUDMUX_V2_PTCR_TCLKDIR, 85c448303eSShawn Guo IMX_AUDMUX_V2_PDCR_RXDSEL(ext_port)); 86c448303eSShawn Guo if (ret) { 87c448303eSShawn Guo dev_err(&pdev->dev, "audmux internal port setup failed\n"); 88c448303eSShawn Guo return ret; 89c448303eSShawn Guo } 90db8b624dSJulia Lawall ret = imx_audmux_v2_configure_port(ext_port, 91ef3207c5SHui Wang IMX_AUDMUX_V2_PTCR_SYN, 92c448303eSShawn Guo IMX_AUDMUX_V2_PDCR_RXDSEL(int_port)); 93c448303eSShawn Guo if (ret) { 94c448303eSShawn Guo dev_err(&pdev->dev, "audmux external port setup failed\n"); 95c448303eSShawn Guo return ret; 96c448303eSShawn Guo } 97c448303eSShawn Guo 98c448303eSShawn Guo ssi_np = of_parse_phandle(pdev->dev.of_node, "ssi-controller", 0); 99c448303eSShawn Guo codec_np = of_parse_phandle(pdev->dev.of_node, "audio-codec", 0); 100c448303eSShawn Guo if (!ssi_np || !codec_np) { 101c448303eSShawn Guo dev_err(&pdev->dev, "phandle missing or invalid\n"); 102717071dcSRichard Zhao ret = -EINVAL; 103717071dcSRichard Zhao goto fail; 104c448303eSShawn Guo } 105c448303eSShawn Guo 106c448303eSShawn Guo ssi_pdev = of_find_device_by_node(ssi_np); 107c448303eSShawn Guo if (!ssi_pdev) { 108691beb02SStefan Agner dev_dbg(&pdev->dev, "failed to find SSI platform device\n"); 10928e5ca73SArnaud Patard (Rtp) ret = -EPROBE_DEFER; 110717071dcSRichard Zhao goto fail; 111c448303eSShawn Guo } 1128fa857daSWen Yang put_device(&ssi_pdev->dev); 11381e8e492SRichard Zhao codec_dev = of_find_i2c_device_by_node(codec_np); 11481e8e492SRichard Zhao if (!codec_dev) { 115691beb02SStefan Agner dev_dbg(&pdev->dev, "failed to find codec platform device\n"); 116d9866572SStefan Agner ret = -EPROBE_DEFER; 117d9866572SStefan Agner goto fail; 11881e8e492SRichard Zhao } 119c448303eSShawn Guo 120c448303eSShawn Guo data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); 121717071dcSRichard Zhao if (!data) { 122717071dcSRichard Zhao ret = -ENOMEM; 12341cd312dSMiaoqian Lin goto put_device; 124717071dcSRichard Zhao } 125c448303eSShawn Guo 1262b1407c9SKuninori Morimoto comp = devm_kzalloc(&pdev->dev, 3 * sizeof(*comp), GFP_KERNEL); 1273026ef68SKuninori Morimoto if (!comp) { 1283026ef68SKuninori Morimoto ret = -ENOMEM; 12941cd312dSMiaoqian Lin goto put_device; 1303026ef68SKuninori Morimoto } 1313026ef68SKuninori Morimoto 132a8b22c1cSPhilipp Zabel data->codec_clk = clk_get(&codec_dev->dev, NULL); 133f1269ae4SWei Yongjun if (IS_ERR(data->codec_clk)) { 134f1269ae4SWei Yongjun ret = PTR_ERR(data->codec_clk); 13541cd312dSMiaoqian Lin goto put_device; 136f1269ae4SWei Yongjun } 1379e13f345SFabio Estevam 13881e8e492SRichard Zhao data->clk_frequency = clk_get_rate(data->codec_clk); 139c448303eSShawn Guo 1403026ef68SKuninori Morimoto data->dai.cpus = &comp[0]; 1413026ef68SKuninori Morimoto data->dai.codecs = &comp[1]; 1422b1407c9SKuninori Morimoto data->dai.platforms = &comp[2]; 1433026ef68SKuninori Morimoto 1443026ef68SKuninori Morimoto data->dai.num_cpus = 1; 1453026ef68SKuninori Morimoto data->dai.num_codecs = 1; 1462b1407c9SKuninori Morimoto data->dai.num_platforms = 1; 1473026ef68SKuninori Morimoto 148c448303eSShawn Guo data->dai.name = "HiFi"; 149c448303eSShawn Guo data->dai.stream_name = "HiFi"; 1503026ef68SKuninori Morimoto data->dai.codecs->dai_name = "sgtl5000"; 1513026ef68SKuninori Morimoto data->dai.codecs->of_node = codec_np; 1523026ef68SKuninori Morimoto data->dai.cpus->of_node = ssi_np; 1532b1407c9SKuninori Morimoto data->dai.platforms->of_node = ssi_np; 154c448303eSShawn Guo data->dai.init = &imx_sgtl5000_dai_init; 155c448303eSShawn Guo data->dai.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | 156419099b4SMark Brown SND_SOC_DAIFMT_CBP_CFP; 157c448303eSShawn Guo 158c448303eSShawn Guo data->card.dev = &pdev->dev; 159c448303eSShawn Guo ret = snd_soc_of_parse_card_name(&data->card, "model"); 160c448303eSShawn Guo if (ret) 16141cd312dSMiaoqian Lin goto put_device; 162172b4c5cSShawn Guo ret = snd_soc_of_parse_audio_routing(&data->card, "audio-routing"); 163172b4c5cSShawn Guo if (ret) 16441cd312dSMiaoqian Lin goto put_device; 165c448303eSShawn Guo data->card.num_links = 1; 16629df4306SLothar Waßmann data->card.owner = THIS_MODULE; 167c448303eSShawn Guo data->card.dai_link = &data->dai; 168172b4c5cSShawn Guo data->card.dapm_widgets = imx_sgtl5000_dapm_widgets; 169172b4c5cSShawn Guo data->card.num_dapm_widgets = ARRAY_SIZE(imx_sgtl5000_dapm_widgets); 170c448303eSShawn Guo 17147cf84e1SShawn Guo platform_set_drvdata(pdev, &data->card); 17247cf84e1SShawn Guo snd_soc_card_set_drvdata(&data->card, data); 17347cf84e1SShawn Guo 17401984a47SSachin Kamat ret = devm_snd_soc_register_card(&pdev->dev, &data->card); 175c448303eSShawn Guo if (ret) { 1762e6f557cSKuninori Morimoto dev_err_probe(&pdev->dev, ret, "snd_soc_register_card failed\n"); 17741cd312dSMiaoqian Lin goto put_device; 178c448303eSShawn Guo } 179c448303eSShawn Guo 1802fc059f2SFabio Estevam of_node_put(ssi_np); 1812fc059f2SFabio Estevam of_node_put(codec_np); 1822fc059f2SFabio Estevam 1832fc059f2SFabio Estevam return 0; 1842fc059f2SFabio Estevam 18541cd312dSMiaoqian Lin put_device: 18641cd312dSMiaoqian Lin put_device(&codec_dev->dev); 187717071dcSRichard Zhao fail: 18850d4a790SPhilipp Zabel if (data && !IS_ERR(data->codec_clk)) 189a8b22c1cSPhilipp Zabel clk_put(data->codec_clk); 190c448303eSShawn Guo of_node_put(ssi_np); 191c448303eSShawn Guo of_node_put(codec_np); 192c448303eSShawn Guo 193717071dcSRichard Zhao return ret; 194c448303eSShawn Guo } 195c448303eSShawn Guo 196*b8a148daSUwe Kleine-König static void imx_sgtl5000_remove(struct platform_device *pdev) 197c448303eSShawn Guo { 19847cf84e1SShawn Guo struct snd_soc_card *card = platform_get_drvdata(pdev); 19947cf84e1SShawn Guo struct imx_sgtl5000_data *data = snd_soc_card_get_drvdata(card); 200c448303eSShawn Guo 201a8b22c1cSPhilipp Zabel clk_put(data->codec_clk); 202c448303eSShawn Guo } 203c448303eSShawn Guo 204c448303eSShawn Guo static const struct of_device_id imx_sgtl5000_dt_ids[] = { 205c448303eSShawn Guo { .compatible = "fsl,imx-audio-sgtl5000", }, 206c448303eSShawn Guo { /* sentinel */ } 207c448303eSShawn Guo }; 208c448303eSShawn Guo MODULE_DEVICE_TABLE(of, imx_sgtl5000_dt_ids); 209c448303eSShawn Guo 210c448303eSShawn Guo static struct platform_driver imx_sgtl5000_driver = { 211c448303eSShawn Guo .driver = { 212c448303eSShawn Guo .name = "imx-sgtl5000", 2131abe729fSNicolin Chen .pm = &snd_soc_pm_ops, 214c448303eSShawn Guo .of_match_table = imx_sgtl5000_dt_ids, 215c448303eSShawn Guo }, 216c448303eSShawn Guo .probe = imx_sgtl5000_probe, 217*b8a148daSUwe Kleine-König .remove_new = imx_sgtl5000_remove, 218c448303eSShawn Guo }; 219c448303eSShawn Guo module_platform_driver(imx_sgtl5000_driver); 220c448303eSShawn Guo 221c448303eSShawn Guo MODULE_AUTHOR("Shawn Guo <shawn.guo@linaro.org>"); 222c448303eSShawn Guo MODULE_DESCRIPTION("Freescale i.MX SGTL5000 ASoC machine driver"); 223c448303eSShawn Guo MODULE_LICENSE("GPL v2"); 224c448303eSShawn Guo MODULE_ALIAS("platform:imx-sgtl5000"); 225