/* * sound\soc\sunxi\sunxi_ahub.c * (C) Copyright 2015-2017 * Allwinner Technology Co., Ltd. * Wolfgang huang * * some simple description for this code * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "sunxi_ahub.h" #include "sunxi-pcm.h" #define DRV_NAME "sunxi-ahub" struct sunxi_ahub_priv { struct device *dev; void __iomem *membase; struct regmap *regmap; struct clk *pllclk; struct clk *moduleclk; } sunxi_ahub_dev; static struct sunxi_ahub_priv *sunxi_ahub = &sunxi_ahub_dev; static const struct regmap_config sunxi_ahub_regmap_config = { .reg_bits = 32, .reg_stride = 4, .val_bits = 32, .max_register = SUNXI_AHUB_DAM_GAIN_CTL7(1), .cache_type = REGCACHE_RBTREE, }; unsigned int sunxi_ahub_read(unsigned int reg) { return readl(sunxi_ahub->membase + reg); } /* reslove conflict with regmap_update_bits using spin_lock */ int sunxi_ahub_update_bits( unsigned int reg, unsigned int mask, unsigned int val) { unsigned int tmp, orig; orig = readl(sunxi_ahub->membase + reg); tmp = orig & ~mask; tmp |= val & mask; if (tmp != orig) writel(tmp, sunxi_ahub->membase + reg); return 0; } EXPORT_SYMBOL_GPL(sunxi_ahub_update_bits); struct regmap *sunxi_ahub_regmap_init(struct platform_device *pdev) { struct resource res, *memregion; struct device_node *np = pdev->dev.of_node; int ret; if (!sunxi_ahub->regmap) { ret = of_address_to_resource(np, 0, &res); if (ret) return NULL; memregion = devm_request_mem_region(&pdev->dev, res.start, resource_size(&res), DRV_NAME); if (!memregion) return NULL; sunxi_ahub->membase = ioremap(res.start, resource_size(&res)); if (!sunxi_ahub->membase) return NULL; sunxi_ahub->regmap = devm_regmap_init_mmio(&pdev->dev, sunxi_ahub->membase, &sunxi_ahub_regmap_config); if (IS_ERR_OR_NULL(sunxi_ahub->regmap)) { ret = PTR_ERR(sunxi_ahub->regmap); return NULL; } } return sunxi_ahub->regmap; } EXPORT_SYMBOL_GPL(sunxi_ahub_regmap_init); static const char * const apbif_mux_text[] = { "NONE", "APBIF_TXDIF0", "APBIF_TXDIF1", "APBIF_TXDIF2", "I2S0_TXDIF", "I2S1_TXDIF", "I2S2_TXDIF", "I2S3_TXDIF", "DAM0_TXDIF", "DAM1_TXDIF", }; static const unsigned int apbif_mux_values[] = { 0, 1<dapm); struct sunxi_ahub_priv *sunxi_ahub = snd_soc_codec_get_drvdata(codec); int reg_bit = 0; int i; for (i = 0; i < ARRAY_SIZE(mod_str_conv); i++) if (!strncmp(mod_str_conv[i].str, w->name, strlen(mod_str_conv[i].str))) { reg_bit = mod_str_conv[i].regbit; break; } switch (event) { case SND_SOC_DAPM_PRE_PMU: regmap_update_bits(sunxi_ahub->regmap, SUNXI_AHUB_RST, (0x1<regmap, SUNXI_AHUB_GAT, (0x1<regmap, SUNXI_AHUB_GAT, (0x1<regmap, SUNXI_AHUB_RST, (0x1<regmap, SUNXI_AHUB_CTL, 1<regmap, SUNXI_AHUB_DAM_MIX_CTL0(i), 0x01110000); regmap_write(sunxi_ahub->regmap, SUNXI_AHUB_DAM_MIX_CTL1(i), 0x03330222); regmap_write(sunxi_ahub->regmap, SUNXI_AHUB_DAM_MIX_CTL2(i), 0x05550444); regmap_write(sunxi_ahub->regmap, SUNXI_AHUB_DAM_MIX_CTL3(i), 0x07770666); regmap_write(sunxi_ahub->regmap, SUNXI_AHUB_DAM_MIX_CTL4(i), 0x09990888); regmap_write(sunxi_ahub->regmap, SUNXI_AHUB_DAM_MIX_CTL5(i), 0x0bbb0aaa); regmap_write(sunxi_ahub->regmap, SUNXI_AHUB_DAM_MIX_CTL6(i), 0x0ddd0ccc); regmap_write(sunxi_ahub->regmap, SUNXI_AHUB_DAM_MIX_CTL7(i), 0x0fff0eee); /* setting default audio hub volume */ regmap_write(sunxi_ahub->regmap, SUNXI_AHUB_DAM_GAIN_CTL0(i), 0x01110111); regmap_write(sunxi_ahub->regmap, SUNXI_AHUB_DAM_GAIN_CTL1(i), 0x01110111); regmap_write(sunxi_ahub->regmap, SUNXI_AHUB_DAM_GAIN_CTL2(i), 0x01110111); regmap_write(sunxi_ahub->regmap, SUNXI_AHUB_DAM_GAIN_CTL3(i), 0x01110111); regmap_write(sunxi_ahub->regmap, SUNXI_AHUB_DAM_GAIN_CTL4(i), 0x01110111); regmap_write(sunxi_ahub->regmap, SUNXI_AHUB_DAM_GAIN_CTL5(i), 0x01110111); regmap_write(sunxi_ahub->regmap, SUNXI_AHUB_DAM_GAIN_CTL6(i), 0x01110111); regmap_write(sunxi_ahub->regmap, SUNXI_AHUB_DAM_GAIN_CTL7(i), 0x01110111); } } static int sunxi_ahub_codec_dai_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) { struct sunxi_ahub_priv *sunxi_ahub = snd_soc_codec_get_drvdata(dai->codec); struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_card *card = rtd->card; struct sunxi_hdmi_priv *sunxi_hdmi = snd_soc_card_get_drvdata(card); switch (params_format(params)) { case SNDRV_PCM_FORMAT_S16_LE: if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { /* special handle for HDMI rawdata mode */ if (sunxi_hdmi->hdmi_format > 1) { regmap_update_bits(sunxi_ahub->regmap, SUNXI_AHUB_APBIF_TX_CTL(dai->id), (7<regmap, SUNXI_AHUB_APBIF_TXFIFO_CTL(dai->id), (1<regmap, SUNXI_AHUB_APBIF_TX_CTL(dai->id), (7<regmap, SUNXI_AHUB_APBIF_TXFIFO_CTL(dai->id), (1<regmap, SUNXI_AHUB_APBIF_RX_CTL(dai->id), (7<regmap, SUNXI_AHUB_APBIF_RXFIFO_CTL(dai->id), (3<stream == SNDRV_PCM_STREAM_PLAYBACK) { regmap_update_bits(sunxi_ahub->regmap, SUNXI_AHUB_APBIF_TX_CTL(dai->id), (7<regmap, SUNXI_AHUB_APBIF_TXFIFO_CTL(dai->id), (1<regmap, SUNXI_AHUB_APBIF_RX_CTL(dai->id), (7<regmap, SUNXI_AHUB_APBIF_RXFIFO_CTL(dai->id), (3<stream == SNDRV_PCM_STREAM_PLAYBACK) { regmap_update_bits(sunxi_ahub->regmap, SUNXI_AHUB_APBIF_TX_CTL(dai->id), (7<regmap, SUNXI_AHUB_APBIF_TXFIFO_CTL(dai->id), (1<regmap, SUNXI_AHUB_APBIF_RX_CTL(dai->id), (7<regmap, SUNXI_AHUB_APBIF_RXFIFO_CTL(dai->id), (3<dev, "unsupport format"); return -EINVAL; } if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) regmap_update_bits(sunxi_ahub->regmap, SUNXI_AHUB_APBIF_TX_CTL(dai->id), (0xf<regmap, SUNXI_AHUB_APBIF_RX_CTL(dai->id), (0xf<channel. */ regmap_update_bits(sunxi_ahub->regmap, SUNXI_AHUB_DAM_CTL(0), (0xf<regmap, SUNXI_AHUB_DAM_CTL(0), (0xf<regmap, SUNXI_AHUB_DAM_CTL(0), (0xf<regmap, SUNXI_AHUB_DAM_CTL(0), (0xf<regmap, SUNXI_AHUB_DAM_CTL(1), (0xf<regmap, SUNXI_AHUB_DAM_CTL(1), (0xf<regmap, SUNXI_AHUB_DAM_CTL(1), (0xf<regmap, SUNXI_AHUB_DAM_CTL(1), (0xf<stream == SNDRV_PCM_STREAM_PLAYBACK) { sunxi_ahub_update_bits( SUNXI_AHUB_APBIF_TXFIFO_CTL(dai->id), (1<pllclk, freq)) { dev_err(sunxi_ahub->dev, "set pllclk rate failed\n"); return -EINVAL; } return 0; } static int sunxi_ahub_codec_dai_prepare(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { struct sunxi_ahub_priv *sunxi_ahub = snd_soc_dai_get_drvdata(dai); if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { regmap_update_bits(sunxi_ahub->regmap, SUNXI_AHUB_APBIF_TXFIFO_CTL(dai->id), (1<regmap, SUNXI_AHUB_APBIF_TX_IRQ_STA(dai->id), (1<regmap, SUNXI_AHUB_APBIF_TXFIFO_CNT(dai->id), 0); } else { regmap_update_bits(sunxi_ahub->regmap, SUNXI_AHUB_APBIF_RXFIFO_CTL(dai->id), (1<regmap, SUNXI_AHUB_APBIF_RX_IRQ_STA(dai->id), (1<regmap, SUNXI_AHUB_APBIF_RXFIFO_CNT(dai->id), 0); } return 0; } static const struct snd_soc_dai_ops sunxi_ahub_codec_dai_ops = { .hw_params = sunxi_ahub_codec_dai_hw_params, .set_sysclk = sunxi_ahub_codec_dai_set_sysclk, .trigger = sunxi_ahub_codec_dai_trigger, .prepare = sunxi_ahub_codec_dai_prepare, }; /* ahub codec dai */ static struct snd_soc_dai_driver sunxi_ahub_codec_dais[] = { { .name = "sunxi-ahub-aif1", .id = 0, .playback = { .stream_name = "AIF1 Playback", .channels_min = 1, .channels_max = 16, .rates = SNDRV_PCM_RATE_8000_192000 | SNDRV_PCM_RATE_KNOT, .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE, }, .capture = { .stream_name = "AIF1 Capture", .channels_min = 1, .channels_max = 16, .rates = SNDRV_PCM_RATE_8000_192000 | SNDRV_PCM_RATE_KNOT, .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE, }, .ops = &sunxi_ahub_codec_dai_ops, }, { .name = "sunxi-ahub-aif2", .id = 1, .playback = { .stream_name = "AIF2 Playback", .channels_min = 1, .channels_max = 16, .rates = SNDRV_PCM_RATE_8000_192000 | SNDRV_PCM_RATE_KNOT, .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE, }, .capture = { .stream_name = "AIF2 Capture", .channels_min = 1, .channels_max = 16, .rates = SNDRV_PCM_RATE_8000_192000 | SNDRV_PCM_RATE_KNOT, .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE, }, .ops = &sunxi_ahub_codec_dai_ops, }, { .name = "sunxi-ahub-aif3", .id = 2, .playback = { .stream_name = "AIF3 Playback", .channels_min = 1, .channels_max = 16, .rates = SNDRV_PCM_RATE_8000_192000 | SNDRV_PCM_RATE_KNOT, .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE, }, .capture = { .stream_name = "AIF3 Capture", .channels_min = 1, .channels_max = 16, .rates = SNDRV_PCM_RATE_8000_192000 | SNDRV_PCM_RATE_KNOT, .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE, }, .ops = &sunxi_ahub_codec_dai_ops, } }; static int sunxi_ahub_codec_probe(struct snd_soc_codec *codec) { struct snd_soc_dapm_context *dapm = snd_soc_codec_get_dapm(codec); snd_soc_dapm_new_controls(dapm, sunxi_ahub_codec_dapm_widgets, ARRAY_SIZE(sunxi_ahub_codec_dapm_widgets)); snd_soc_dapm_add_routes(dapm, sunxi_ahub_codec_dapm_routes, ARRAY_SIZE(sunxi_ahub_codec_dapm_routes)); snd_soc_add_codec_controls(codec, ahub_controls, ARRAY_SIZE(ahub_controls)); sunxi_ahub_codec_init(codec); return 0; } static int sunxi_ahub_codec_suspend(struct snd_soc_codec *codec) { struct sunxi_ahub_priv *sunxi_ahub = snd_soc_codec_get_drvdata(codec); pr_debug("Enter %s\n", __func__); clk_disable_unprepare(sunxi_ahub->moduleclk); clk_disable_unprepare(sunxi_ahub->pllclk); pr_debug("End %s\n", __func__); return 0; } static int sunxi_ahub_codec_resume(struct snd_soc_codec *codec) { struct sunxi_ahub_priv *sunxi_ahub = snd_soc_codec_get_drvdata(codec); pr_debug("Enter %s\n", __func__); if (clk_prepare_enable(sunxi_ahub->pllclk)) { dev_err(sunxi_ahub->dev, "pllclk resume failed\n"); return -EBUSY; } if (clk_prepare_enable(sunxi_ahub->moduleclk)) { dev_err(sunxi_ahub->dev, "moduleclk resume failed\n"); return -EBUSY; } sunxi_ahub_codec_init(codec); sunxi_ahub_cpudai_init(); pr_debug("End %s\n", __func__); return 0; } static struct snd_soc_codec_driver soc_ahub_dev_sunxi = { .probe = sunxi_ahub_codec_probe, .suspend = sunxi_ahub_codec_suspend, .resume = sunxi_ahub_codec_resume, .ignore_pmdown_time = 1, }; static int sunxi_ahub_dev_probe(struct platform_device *pdev) { struct device_node *np = pdev->dev.of_node; int ret; dev_set_drvdata(&pdev->dev, sunxi_ahub); sunxi_ahub->dev = &pdev->dev; sunxi_ahub->pllclk = of_clk_get(np, 0); if (IS_ERR_OR_NULL(sunxi_ahub->pllclk)) { dev_err(&pdev->dev, "pllclk get failed\n"); ret = PTR_ERR(sunxi_ahub->pllclk); goto err_node_put; } sunxi_ahub->moduleclk = of_clk_get(np, 1); if (IS_ERR_OR_NULL(sunxi_ahub->moduleclk)) { dev_err(&pdev->dev, "moduleclk get failed\n"); ret = PTR_ERR(sunxi_ahub->moduleclk); goto err_pllclk_put; } if (clk_set_parent(sunxi_ahub->moduleclk, sunxi_ahub->pllclk)) { dev_err(&pdev->dev, "set parent of moduleclk to pllclk fail\n"); ret = -EBUSY; goto err_moduleclk_put; } clk_prepare_enable(sunxi_ahub->pllclk); clk_prepare_enable(sunxi_ahub->moduleclk); sunxi_ahub->regmap = sunxi_ahub_regmap_init(pdev); if (!sunxi_ahub->regmap) { dev_err(&pdev->dev, "regmap not init ok\n"); ret = -ENOMEM; goto err_moduleclk_put; } ret = snd_soc_register_codec(&pdev->dev, &soc_ahub_dev_sunxi, sunxi_ahub_codec_dais, ARRAY_SIZE(sunxi_ahub_codec_dais)); if (ret) { dev_err(&pdev->dev, "component register failed\n"); ret = -ENOMEM; goto err_moduleclk_put; } return 0; err_moduleclk_put: clk_put(sunxi_ahub->moduleclk); err_pllclk_put: clk_put(sunxi_ahub->pllclk); err_node_put: of_node_put(np); return ret; } static int __exit sunxi_ahub_dev_remove(struct platform_device *pdev) { struct sunxi_ahub_priv *sunxi_ahub = dev_get_drvdata(&pdev->dev); snd_soc_unregister_component(&pdev->dev); clk_put(sunxi_ahub->moduleclk); clk_put(sunxi_ahub->pllclk); return 0; } static const struct of_device_id sunxi_ahub_of_match[] = { { .compatible = "allwinner,sunxi-ahub", }, {}, }; static struct platform_driver sunxi_ahub_driver = { .probe = sunxi_ahub_dev_probe, .remove = __exit_p(sunxi_ahub_dev_remove), .driver = { .name = DRV_NAME, .owner = THIS_MODULE, .of_match_table = sunxi_ahub_of_match, }, }; module_platform_driver(sunxi_ahub_driver); MODULE_AUTHOR("wolfgang huang "); MODULE_DESCRIPTION("SUNXI Audio Hub Codec ASoC Interface"); MODULE_LICENSE("GPL"); MODULE_ALIAS("platform:sunxi-ahub");