diff --git a/Documentation/devicetree/bindings/sound/imx-audio-si476x.txt b/Documentation/devicetree/bindings/sound/imx-audio-si476x.txt new file mode 100644 index 000000000000..53cd34afe6b8 --- /dev/null +++ b/Documentation/devicetree/bindings/sound/imx-audio-si476x.txt @@ -0,0 +1,24 @@ +Freescale i.MX audio complex with si476x codec + +Required properties: +- compatible : "fsl,imx-audio-si476x" +- model : The user-visible name of this sound complex +- ssi-controller : The phandle of the i.MX SSI controller + +- mux-int-port : The internal port of the i.MX audio muxer (AUDMUX) +- mux-ext-port : The external port of the i.MX audio muxer + +Note: The AUDMUX port numbering should start at 1, which is consistent with +hardware manual. + +Example: + +sound { + compatible = "fsl,imx-audio-si476x", + "fsl,imx-tuner-si476x"; + model = "imx-radio-si476x"; + + ssi-controller = <&ssi1>; + mux-int-port = <2>; + mux-ext-port = <5>; +}; diff --git a/drivers/media/radio/radio-si476x.c b/drivers/media/radio/radio-si476x.c index b203296de977..c2490267f254 100644 --- a/drivers/media/radio/radio-si476x.c +++ b/drivers/media/radio/radio-si476x.c @@ -988,6 +988,14 @@ static int si476x_radio_s_ctrl(struct v4l2_ctrl *ctrl) } break; + case V4L2_CID_AUDIO_MUTE: + if (ctrl->val) + retval = regmap_write(radio->core->regmap, + SI476X_PROP_AUDIO_MUTE, 3); + else + retval = regmap_write(radio->core->regmap, + SI476X_PROP_AUDIO_MUTE, 0); + break; default: retval = -EINVAL; break; @@ -1515,6 +1523,16 @@ static int si476x_radio_probe(struct platform_device *pdev) goto exit; } + ctrl = v4l2_ctrl_new_std(&radio->ctrl_handler, &si476x_ctrl_ops, + V4L2_CID_AUDIO_MUTE, + 0, 1, 1, 0); + rval = radio->ctrl_handler.error; + if (ctrl == NULL && rval) { + dev_err(&pdev->dev, "Could not initialize V4L2_CID_AUDIO_MUTE control %d\n", + rval); + goto exit; + } + if (si476x_core_has_diversity(radio->core)) { si476x_ctrls[SI476X_IDX_DIVERSITY_MODE].def = si476x_phase_diversity_mode_to_idx(radio->core->diversity_mode); diff --git a/drivers/mfd/si476x-i2c.c b/drivers/mfd/si476x-i2c.c index c8d28b844def..accdcdc7f23f 100644 --- a/drivers/mfd/si476x-i2c.c +++ b/drivers/mfd/si476x-i2c.c @@ -97,7 +97,7 @@ static int si476x_core_config_pinmux(struct si476x_core *core) static inline void si476x_core_schedule_polling_work(struct si476x_core *core) { - schedule_delayed_work(&core->status_monitor, + queue_delayed_work(system_freezable_wq, &core->status_monitor, usecs_to_jiffies(SI476X_STATUS_POLL_US)); } @@ -294,7 +294,7 @@ int si476x_core_set_power_state(struct si476x_core *core, */ udelay(100); - err = si476x_core_start(core, false); + err = si476x_core_start(core, true); if (err < 0) goto disable_regulators; @@ -303,7 +303,7 @@ int si476x_core_set_power_state(struct si476x_core *core, case SI476X_POWER_DOWN: core->power_state = next_state; - err = si476x_core_stop(core, false); + err = si476x_core_stop(core, true); if (err < 0) core->power_state = SI476X_POWER_INCONSISTENT; disable_regulators: @@ -729,8 +729,15 @@ static int si476x_core_probe(struct i2c_client *client, memcpy(&core->pinmux, &pdata->pinmux, sizeof(struct si476x_pinmux)); } else { - dev_err(&client->dev, "No platform data provided\n"); - return -EINVAL; + dev_warn(&client->dev, "Using default platform data.\n"); + core->power_up_parameters.xcload = 0x28; + core->power_up_parameters.func = SI476X_FUNC_FM_RECEIVER; + core->power_up_parameters.freq = SI476X_FREQ_37P209375_MHZ; + core->diversity_mode = SI476X_PHDIV_DISABLED; + core->pinmux.dclk = SI476X_DCLK_DAUDIO; + core->pinmux.dfs = SI476X_DFS_DAUDIO; + core->pinmux.dout = SI476X_DOUT_I2S_OUTPUT; + core->pinmux.xout = SI476X_XOUT_TRISTATE; } core->supplies[0].supply = "vd"; @@ -789,12 +796,18 @@ static int si476x_core_probe(struct i2c_client *client, core->chip_id = id->driver_data; + /* Power down si476x first */ + si476x_core_stop(core, true); + rval = si476x_core_get_revision_info(core); if (rval < 0) { rval = -ENODEV; goto free_kfifo; } + if (of_property_read_bool(client->dev.of_node, "revision-a10")) + core->revision = SI476X_REVISION_A10; + cell_num = 0; cell = &core->cells[SI476X_RADIO_CELL]; @@ -810,6 +823,7 @@ static int si476x_core_probe(struct i2c_client *client, core->pinmux.xout == SI476X_XOUT_TRISTATE) { cell = &core->cells[SI476X_CODEC_CELL]; cell->name = "si476x-codec"; + cell->of_compatible = "si476x-codec"; cell_num++; } #endif diff --git a/include/linux/mfd/si476x-core.h b/include/linux/mfd/si476x-core.h index 4708c2b8512a..e591c050dce1 100644 --- a/include/linux/mfd/si476x-core.h +++ b/include/linux/mfd/si476x-core.h @@ -484,6 +484,8 @@ enum si476x_common_receiver_properties { SI476X_PROP_DIGITAL_IO_OUTPUT_SAMPLE_RATE = 0x0202, SI476X_PROP_DIGITAL_IO_OUTPUT_FORMAT = 0x0203, + SI476X_PROP_AUDIO_MUTE = 0x0301, + SI476X_PROP_SEEK_BAND_BOTTOM = 0x1100, SI476X_PROP_SEEK_BAND_TOP = 0x1101, SI476X_PROP_SEEK_FREQUENCY_SPACING = 0x1102, diff --git a/sound/soc/fsl/Kconfig b/sound/soc/fsl/Kconfig index 208250dcd876..5832934c526f 100644 --- a/sound/soc/fsl/Kconfig +++ b/sound/soc/fsl/Kconfig @@ -489,6 +489,18 @@ config SND_SOC_IMX_DSP Say Y if you want to add support for SoC audio on an i.MX board with IMX DSP. +config SND_SOC_IMX_SI476X + tristate "SoC Audio support for i.MX boards with si476x" + select SND_SOC_IMX_PCM_DMA + select SND_SOC_IMX_AUDMUX + select SND_SOC_FSL_SSI + select SND_SOC_FSL_UTILS + select SND_SOC_SI476X + help + SoC Audio support for i.MX boards with SI476x + Say Y if you want to add support for Soc audio for the AMFM Tuner chip + SI476x module. + endif # SND_IMX_SOC endmenu diff --git a/sound/soc/fsl/Makefile b/sound/soc/fsl/Makefile index 8be44b869a8f..de1bb892c7aa 100644 --- a/sound/soc/fsl/Makefile +++ b/sound/soc/fsl/Makefile @@ -82,6 +82,7 @@ snd-soc-imx-ak5558-objs := imx-ak5558.o snd-soc-imx-ak4497-objs := imx-ak4497.o snd-soc-imx-micfil-objs := imx-micfil.o snd-soc-imx-dsp-objs := imx-dsp.o +snd-soc-imx-si476x-objs := imx-si476x.o obj-$(CONFIG_SND_SOC_EUKREA_TLV320) += snd-soc-eukrea-tlv320.o obj-$(CONFIG_SND_SOC_PHYCORE_AC97) += snd-soc-phycore-ac97.o @@ -105,3 +106,4 @@ obj-$(CONFIG_SND_SOC_IMX_AK5558) += snd-soc-imx-ak5558.o obj-$(CONFIG_SND_SOC_IMX_AK4497) += snd-soc-imx-ak4497.o obj-$(CONFIG_SND_SOC_IMX_MICFIL) += snd-soc-imx-micfil.o obj-$(CONFIG_SND_SOC_IMX_DSP) += snd-soc-imx-dsp.o +obj-$(CONFIG_SND_SOC_IMX_SI476X) += snd-soc-imx-si476x.o diff --git a/sound/soc/fsl/imx-si476x.c b/sound/soc/fsl/imx-si476x.c new file mode 100644 index 000000000000..a5112a7d8d5a --- /dev/null +++ b/sound/soc/fsl/imx-si476x.c @@ -0,0 +1,194 @@ +/* + * Copyright (C) 2008-2016 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; + 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; + } + + return ret; +} + +static struct snd_soc_ops imx_si476x_ops = { + .hw_params = imx_si476x_hw_params, +}; + +SND_SOC_DAILINK_DEFS(hifi, + DAILINK_COMP_ARRAY(COMP_EMPTY()), + DAILINK_COMP_ARRAY(COMP_CODEC(NULL, "si476x-codec")), + DAILINK_COMP_ARRAY(COMP_EMPTY())); + +static struct snd_soc_dai_link imx_dai = { + .name = "imx-si476x", + .stream_name = "imx-si476x", + .ops = &imx_si476x_ops, + SND_SOC_DAILINK_REG(hifi), +}; + +static struct snd_soc_card snd_soc_card_imx_3stack = { + .name = "imx-audio-si476x", + .dai_link = &imx_dai, + .num_links = 1, + .owner = THIS_MODULE, +}; + +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 = NULL; + 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->cpus->dai_name = dev_name(&ssi_pdev->dev); + card->dai_link->platforms->of_node = ssi_np; + card->dai_link->codecs->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", + .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");