/* * Copyright (C) 2008-2014 Freescale Semiconductor, Inc. All Rights Reserved. */ /* * 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 #include #include #include #include "imx-audmux.h" static int imx_audmux_config(int slave, int master) { unsigned int ptcr, pdcr; slave = slave - 1; master = master - 1; ptcr = IMX_AUDMUX_V2_PTCR_SYN | IMX_AUDMUX_V2_PTCR_TFSDIR | IMX_AUDMUX_V2_PTCR_TFSEL(slave) | IMX_AUDMUX_V2_PTCR_TCLKDIR | IMX_AUDMUX_V2_PTCR_TCSEL(slave); pdcr = IMX_AUDMUX_V2_PDCR_RXDSEL(slave); imx_audmux_v2_configure_port(master, ptcr, pdcr); /* * According to RM, RCLKDIR and SYN should not be changed at same time. * So separate to two step for configuring this port. */ ptcr |= IMX_AUDMUX_V2_PTCR_RFSDIR | IMX_AUDMUX_V2_PTCR_RFSEL(slave) | IMX_AUDMUX_V2_PTCR_RCLKDIR | IMX_AUDMUX_V2_PTCR_RCSEL(slave); imx_audmux_v2_configure_port(master, ptcr, pdcr); ptcr = IMX_AUDMUX_V2_PTCR_SYN; pdcr = IMX_AUDMUX_V2_PDCR_RXDSEL(master); imx_audmux_v2_configure_port(slave, ptcr, pdcr); return 0; } static int imx_si476x_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; u32 channels = params_channels(params); u32 rate = params_rate(params); u32 bclk = rate * channels * 32; int ret = 0; /* set cpu DAI configuration */ ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS); if (ret) { dev_err(cpu_dai->dev, "failed to set dai fmt\n"); return ret; } ret = snd_soc_dai_set_tdm_slot(cpu_dai, channels == 1 ? 0xfffffffe : 0xfffffffc, channels == 1 ? 0xfffffffe : 0xfffffffc, 2, 32); if (ret) { dev_err(cpu_dai->dev, "failed to set dai tdm slot\n"); return ret; } ret = snd_soc_dai_set_sysclk(cpu_dai, 0, bclk, SND_SOC_CLOCK_OUT); if (ret) dev_err(cpu_dai->dev, "failed to set sysclk\n"); return ret; } static struct snd_soc_ops imx_si476x_ops = { .hw_params = imx_si476x_hw_params, }; static struct snd_soc_dai_link imx_dai = { .name = "imx-si476x", .stream_name = "imx-si476x", .codec_dai_name = "si476x-codec", .ops = &imx_si476x_ops, }; static struct snd_soc_card snd_soc_card_imx_3stack = { .name = "imx-audio-si476x", .dai_link = &imx_dai, .num_links = 1, }; static int imx_si476x_probe(struct platform_device *pdev) { struct snd_soc_card *card = &snd_soc_card_imx_3stack; struct device_node *ssi_np, *np = pdev->dev.of_node; struct platform_device *ssi_pdev; struct i2c_client *fm_dev; struct device_node *fm_np; int int_port, ext_port, ret; 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; } imx_audmux_config(int_port, ext_port); ssi_np = of_parse_phandle(pdev->dev.of_node, "ssi-controller", 0); if (!ssi_np) { dev_err(&pdev->dev, "phandle missing or invalid\n"); return -EINVAL; } ssi_pdev = of_find_device_by_node(ssi_np); if (!ssi_pdev) { dev_err(&pdev->dev, "failed to find SSI platform device\n"); ret = -EINVAL; goto end; } fm_np = of_parse_phandle(pdev->dev.of_node, "fm-controller", 0); if (!fm_np) { dev_err(&pdev->dev, "phandle missing or invalid\n"); ret = -EINVAL; goto end; } fm_dev = of_find_i2c_device_by_node(fm_np->parent); if (!fm_dev || !fm_dev->dev.driver) { dev_err(&pdev->dev, "failed to find FM platform device\n"); ret = -EINVAL; goto end; } card->dev = &pdev->dev; card->dai_link->cpu_dai_name = dev_name(&ssi_pdev->dev); card->dai_link->platform_of_node = ssi_np; card->dai_link->codec_of_node = fm_np; platform_set_drvdata(pdev, card); ret = snd_soc_register_card(card); if (ret) dev_err(&pdev->dev, "Failed to register card: %d\n", ret); end: if (ssi_np) of_node_put(ssi_np); if (fm_np) of_node_put(fm_np); return ret; } static int imx_si476x_remove(struct platform_device *pdev) { struct snd_soc_card *card = &snd_soc_card_imx_3stack; snd_soc_unregister_card(card); return 0; } static const struct of_device_id imx_si476x_dt_ids[] = { { .compatible = "fsl,imx-audio-si476x", }, { /* sentinel */ } }; MODULE_DEVICE_TABLE(of, imx_si476x_dt_ids); static struct platform_driver imx_si476x_driver = { .driver = { .name = "imx-tuner-si476x", .owner = THIS_MODULE, .pm = &snd_soc_pm_ops, .of_match_table = imx_si476x_dt_ids, }, .probe = imx_si476x_probe, .remove = imx_si476x_remove, }; module_platform_driver(imx_si476x_driver); /* Module information */ MODULE_AUTHOR("Freescale Semiconductor, Inc."); MODULE_DESCRIPTION("ALSA SoC i.MX si476x"); MODULE_LICENSE("GPL"); MODULE_ALIAS("platform:imx-tuner-si476x");