/* * sound\soc\sunxi\snd_sunxi_ahub.c * (C) Copyright 2021-2025 * AllWinner Technology Co., Ltd. * Dby * * 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 "snd_sunxi_log.h" #include "snd_sunxi_pcm.h" #include "snd_sunxi_hdmi.h" #include "snd_sunxi_ahub.h" #define HLOG "AHUB" #define DRV_NAME "sunxi-snd-plat-ahub" static int sunxi_ahub_dai_startup(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { struct sunxi_ahub *ahub = snd_soc_dai_get_drvdata(dai); struct regmap *regmap = NULL; unsigned int apb_num, tdm_num; SND_LOG_DEBUG(HLOG, "\n"); regmap = ahub->mem.regmap; apb_num = ahub->dts.apb_num; tdm_num = ahub->dts.tdm_num; if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { snd_soc_dai_set_dma_data(dai, substream, &ahub->playback_dma_param); } else { snd_soc_dai_set_dma_data(dai, substream, &ahub->capture_dma_param); } /* APBIF & I2S of RST and GAT */ if (tdm_num > 3 || apb_num > 2) { SND_LOG_ERR(HLOG, "unspport tdm num or apbif num\n"); return -EINVAL; } if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { regmap_update_bits(regmap, SUNXI_AHUB_RST, 0x1 << (APBIF_TXDIF0_RST - apb_num), 0x1 << (APBIF_TXDIF0_RST - apb_num)); regmap_update_bits(regmap, SUNXI_AHUB_GAT, 0x1 << (APBIF_TXDIF0_GAT - apb_num), 0x1 << (APBIF_TXDIF0_GAT - apb_num)); } else { regmap_update_bits(regmap, SUNXI_AHUB_RST, 0x1 << (APBIF_RXDIF0_RST - apb_num), 0x1 << (APBIF_RXDIF0_RST - apb_num)); regmap_update_bits(regmap, SUNXI_AHUB_GAT, 0x1 << (APBIF_RXDIF0_GAT - apb_num), 0x1 << (APBIF_RXDIF0_GAT - apb_num)); } return 0; } static int sunxi_ahub_dai_set_pll(struct snd_soc_dai *dai, int pll_id, int source, unsigned int freq_in, unsigned int freq_out) { struct sunxi_ahub *ahub = snd_soc_dai_get_drvdata(dai); struct sunxi_ahub_clk *clk = NULL; SND_LOG_DEBUG(HLOG, "stream -> %s, freq_in ->%u, freq_out ->%u\n", pll_id ? "IN" : "OUT", freq_in, freq_out); if (IS_ERR_OR_NULL(ahub)) { SND_LOG_ERR(HLOG, "ahub is null.\n"); return -ENOMEM; } clk = &ahub->clk; if (freq_in > 24576000) { if (clk_set_parent(clk->clk_module, clk->clk_pllx4)) { SND_LOG_ERR(HLOG, "set parent of clk_module to pllx4 failed\n"); return -EINVAL; } if (clk_set_rate(clk->clk_pllx4, freq_in)) { SND_LOG_ERR(HLOG, "freq : %u pllx4 clk unsupport\n", freq_in); return -EINVAL; } } else { if (clk_set_parent(clk->clk_module, clk->clk_pll)) { SND_LOG_ERR(HLOG, "set parent of clk_module to pll failed\n"); return -EINVAL; } if (clk_set_rate(clk->clk_pll, freq_in)) { SND_LOG_ERR(HLOG, "freq : %u pll clk unsupport\n", freq_in); return -EINVAL; } } if (clk_set_rate(clk->clk_module, freq_out)) { SND_LOG_ERR(HLOG, "freq : %u module clk unsupport\n", freq_out); return -EINVAL; } ahub->pllclk_freq = freq_in; ahub->mclk_freq = freq_out; return 0; } static int sunxi_ahub_dai_set_sysclk(struct snd_soc_dai *dai, int clk_id, unsigned int freq, int dir) { struct sunxi_ahub *ahub = snd_soc_dai_get_drvdata(dai); struct regmap *regmap = NULL; unsigned int tdm_num; unsigned int mclk_ratio, mclk_ratio_map; SND_LOG_DEBUG(HLOG, "\n"); if (IS_ERR_OR_NULL(ahub)) { SND_LOG_ERR(HLOG, "ahub is null.\n"); return -ENOMEM; } regmap = ahub->mem.regmap; tdm_num = ahub->dts.tdm_num; if (freq == 0) { regmap_update_bits(regmap, SUNXI_AHUB_I2S_CLKD(tdm_num), 0x1 << I2S_CLKD_MCLK, 0x0 << I2S_CLKD_MCLK); SND_LOG_DEBUG(HLOG, "mclk freq: 0\n"); return 0; } if (ahub->pllclk_freq == 0) { SND_LOG_ERR(HLOG, "pllclk freq is invalid\n"); return -ENOMEM; } mclk_ratio = ahub->pllclk_freq / freq; switch (mclk_ratio) { case 1: mclk_ratio_map = 1; break; case 2: mclk_ratio_map = 2; break; case 4: mclk_ratio_map = 3; break; case 6: mclk_ratio_map = 4; break; case 8: mclk_ratio_map = 5; break; case 12: mclk_ratio_map = 6; break; case 16: mclk_ratio_map = 7; break; case 24: mclk_ratio_map = 8; break; case 32: mclk_ratio_map = 9; break; case 48: mclk_ratio_map = 10; break; case 64: mclk_ratio_map = 11; break; case 96: mclk_ratio_map = 12; break; case 128: mclk_ratio_map = 13; break; case 176: mclk_ratio_map = 14; break; case 192: mclk_ratio_map = 15; break; default: regmap_update_bits(regmap, SUNXI_AHUB_I2S_CLKD(tdm_num), 0x1 << I2S_CLKD_MCLK, 0x0 << I2S_CLKD_MCLK); SND_LOG_ERR(HLOG, "mclk freq div unsupport\n"); return -EINVAL; } regmap_update_bits(regmap, SUNXI_AHUB_I2S_CLKD(tdm_num), 0xf << I2S_CLKD_MCLKDIV, mclk_ratio_map << I2S_CLKD_MCLKDIV); regmap_update_bits(regmap, SUNXI_AHUB_I2S_CLKD(tdm_num), 0x1 << I2S_CLKD_MCLK, 0x1 << I2S_CLKD_MCLK); return 0; } static int sunxi_ahub_dai_set_bclk_ratio(struct snd_soc_dai *dai, unsigned int ratio) { struct sunxi_ahub *ahub = snd_soc_dai_get_drvdata(dai); struct regmap *regmap = NULL; unsigned int tdm_num; unsigned int bclk_ratio; SND_LOG_DEBUG(HLOG, "\n"); if (IS_ERR_OR_NULL(ahub)) { SND_LOG_ERR(HLOG, "ahub is null.\n"); return -ENOMEM; } regmap = ahub->mem.regmap; tdm_num = ahub->dts.tdm_num; /* ratio -> cpudai pllclk / pcm rate */ switch (ratio) { case 1: bclk_ratio = 1; break; case 2: bclk_ratio = 2; break; case 4: bclk_ratio = 3; break; case 6: bclk_ratio = 4; break; case 8: bclk_ratio = 5; break; case 12: bclk_ratio = 6; break; case 16: bclk_ratio = 7; break; case 24: bclk_ratio = 8; break; case 32: bclk_ratio = 9; break; case 48: bclk_ratio = 10; break; case 64: bclk_ratio = 11; break; case 96: bclk_ratio = 12; break; case 128: bclk_ratio = 13; break; case 176: bclk_ratio = 14; break; case 192: bclk_ratio = 15; break; default: SND_LOG_ERR(HLOG, "bclk freq div unsupport\n"); return -EINVAL; } regmap_update_bits(regmap, SUNXI_AHUB_I2S_CLKD(tdm_num), 0xf << I2S_CLKD_BCLKDIV, bclk_ratio << I2S_CLKD_BCLKDIV); return 0; } static int sunxi_ahub_dai_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) { struct sunxi_ahub *ahub = snd_soc_dai_get_drvdata(dai); struct regmap *regmap = NULL; unsigned int tdm_num, tx_pin, rx_pin; unsigned int mode, offset; unsigned int lrck_polarity, brck_polarity; SND_LOG_DEBUG(HLOG, "\n"); ahub->fmt = fmt; if (IS_ERR_OR_NULL(ahub)) { SND_LOG_ERR(HLOG, "ahub is null.\n"); return -ENOMEM; } regmap = ahub->mem.regmap; tdm_num = ahub->dts.tdm_num; tx_pin = ahub->dts.tx_pin; rx_pin = ahub->dts.rx_pin; /* set TDM format */ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { case SND_SOC_DAIFMT_I2S: mode = 1; offset = 1; break; case SND_SOC_DAIFMT_RIGHT_J: mode = 2; offset = 0; break; case SND_SOC_DAIFMT_LEFT_J: mode = 1; offset = 0; break; case SND_SOC_DAIFMT_DSP_A: mode = 0; offset = 1; /* L data MSB after FRM LRC (short frame) */ regmap_update_bits(regmap, SUNXI_AHUB_I2S_FMT0(tdm_num), 0x1 << I2S_FMT0_LRCK_WIDTH, 0x0 << I2S_FMT0_LRCK_WIDTH); break; case SND_SOC_DAIFMT_DSP_B: mode = 0; offset = 0; /* L data MSB during FRM LRC (long frame) */ regmap_update_bits(regmap, SUNXI_AHUB_I2S_FMT0(tdm_num), 0x1 << I2S_FMT0_LRCK_WIDTH, 0x1 << I2S_FMT0_LRCK_WIDTH); break; default: SND_LOG_ERR(HLOG, "format setting failed\n"); return -EINVAL; } regmap_update_bits(regmap, SUNXI_AHUB_I2S_CTL(tdm_num), 0x3 << I2S_CTL_MODE, mode << I2S_CTL_MODE); /* regmap_update_bits(regmap, SUNXI_AHUB_I2S_OUT_SLOT(tdm_num, tx_pin), * 0x3 << I2S_OUT_OFFSET, offset << I2S_OUT_OFFSET); */ regmap_update_bits(regmap, SUNXI_AHUB_I2S_OUT_SLOT(tdm_num, 0), 0x3 << I2S_OUT_OFFSET, offset << I2S_OUT_OFFSET); regmap_update_bits(regmap, SUNXI_AHUB_I2S_OUT_SLOT(tdm_num, 1), 0x3 << I2S_OUT_OFFSET, offset << I2S_OUT_OFFSET); regmap_update_bits(regmap, SUNXI_AHUB_I2S_OUT_SLOT(tdm_num, 2), 0x3 << I2S_OUT_OFFSET, offset << I2S_OUT_OFFSET); regmap_update_bits(regmap, SUNXI_AHUB_I2S_OUT_SLOT(tdm_num, 3), 0x3 << I2S_OUT_OFFSET, offset << I2S_OUT_OFFSET); regmap_update_bits(regmap, SUNXI_AHUB_I2S_IN_SLOT(tdm_num), 0x3 << I2S_IN_OFFSET, offset << I2S_IN_OFFSET); /* set lrck & bclk polarity */ switch (fmt & SND_SOC_DAIFMT_INV_MASK) { case SND_SOC_DAIFMT_NB_NF: lrck_polarity = 0; brck_polarity = 0; break; case SND_SOC_DAIFMT_NB_IF: lrck_polarity = 1; brck_polarity = 0; break; case SND_SOC_DAIFMT_IB_NF: lrck_polarity = 0; brck_polarity = 1; break; case SND_SOC_DAIFMT_IB_IF: lrck_polarity = 1; brck_polarity = 1; break; default: SND_LOG_ERR(HLOG, "invert clk setting failed\n"); return -EINVAL; } regmap_update_bits(regmap, SUNXI_AHUB_I2S_FMT0(tdm_num), 0x1 << I2S_FMT0_LRCK_POLARITY, lrck_polarity << I2S_FMT0_LRCK_POLARITY); regmap_update_bits(regmap, SUNXI_AHUB_I2S_FMT0(tdm_num), 0x1 << I2S_FMT0_BCLK_POLARITY, brck_polarity << I2S_FMT0_BCLK_POLARITY); /* set master/slave */ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { case SND_SOC_DAIFMT_CBM_CFM: /* lrck & bclk dir input */ regmap_update_bits(regmap, SUNXI_AHUB_I2S_CTL(tdm_num), 0x1 << I2S_CTL_CLK_OUT, 0x0 << I2S_CTL_CLK_OUT); break; case SND_SOC_DAIFMT_CBS_CFS: /* lrck & bclk dir output */ regmap_update_bits(regmap, SUNXI_AHUB_I2S_CTL(tdm_num), 0x1 << I2S_CTL_CLK_OUT, 0x1 << I2S_CTL_CLK_OUT); break; default: SND_LOG_ERR(HLOG, "unknown master/slave format\n"); return -EINVAL; } return 0; } static int sunxi_ahub_dai_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask, unsigned int rx_mask, int slots, int slot_width) { struct sunxi_ahub *ahub = snd_soc_dai_get_drvdata(dai); struct regmap *regmap = NULL; unsigned int tdm_num, tx_pin, rx_pin; unsigned int slot_width_map, lrck_width_map; SND_LOG_DEBUG(HLOG, "\n"); if (IS_ERR_OR_NULL(ahub)) { SND_LOG_ERR(HLOG, "ahub is null\n"); return -ENOMEM; } regmap = ahub->mem.regmap; tdm_num = ahub->dts.tdm_num; tx_pin = ahub->dts.tx_pin; rx_pin = ahub->dts.rx_pin; switch (slot_width) { case 8: slot_width_map = 1; break; case 12: slot_width_map = 2; break; case 16: slot_width_map = 3; break; case 20: slot_width_map = 4; break; case 24: slot_width_map = 5; break; case 28: slot_width_map = 6; break; case 32: slot_width_map = 7; break; default: SND_LOG_ERR(HLOG, "unknown slot width\n"); return -EINVAL; } regmap_update_bits(regmap, SUNXI_AHUB_I2S_FMT0(tdm_num), 0x7 << I2S_FMT0_SW, slot_width_map << I2S_FMT0_SW); /* bclk num of per channel * I2S/RIGHT_J/LEFT_J -> lrck long total is lrck_width_map * 2 * DSP_A/DAP_B -> lrck long total is lrck_width_map * 1 */ switch (ahub->fmt & SND_SOC_DAIFMT_FORMAT_MASK) { case SND_SOC_DAIFMT_I2S: case SND_SOC_DAIFMT_RIGHT_J: case SND_SOC_DAIFMT_LEFT_J: slots /= 2; break; case SND_SOC_DAIFMT_DSP_A: case SND_SOC_DAIFMT_DSP_B: break; default: SND_LOG_ERR(HLOG, "unsupoort format\n"); return -EINVAL; } lrck_width_map = slots * slot_width - 1; regmap_update_bits(regmap, SUNXI_AHUB_I2S_FMT0(tdm_num), 0x3ff << I2S_FMT0_LRCK_PERIOD, lrck_width_map << I2S_FMT0_LRCK_PERIOD); return 0; } static int sunxi_ahub_dai_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) { int ret; struct sunxi_ahub *ahub = snd_soc_dai_get_drvdata(dai); struct regmap *regmap = NULL; unsigned int apb_num, tdm_num, tx_pin, rx_pin; unsigned int channels; unsigned int channels_en[16] = { 0x0001, 0x0003, 0x0007, 0x000f, 0x001f, 0x003f, 0x007f, 0x00ff, 0x01ff, 0x03ff, 0x07ff, 0x0fff, 0x1fff, 0x3fff, 0x7fff, 0xffff }; SND_LOG_DEBUG(HLOG, "\n"); if (IS_ERR_OR_NULL(ahub)) { SND_LOG_ERR(HLOG, "ahub is null.\n"); return -ENOMEM; } regmap = ahub->mem.regmap; apb_num = ahub->dts.apb_num; tdm_num = ahub->dts.tdm_num; tx_pin = ahub->dts.tx_pin; rx_pin = ahub->dts.rx_pin; if (ahub->dts.dai_type == SUNXI_DAI_HDMI_TYPE) { ahub->hdmi_fmt = snd_sunxi_hdmi_get_fmt(); SND_LOG_DEBUG(HLOG, "hdmi fmt -> %d\n", ahub->hdmi_fmt); } /* set bits */ switch (params_format(params)) { case SNDRV_PCM_FORMAT_S16_LE: /* apbifn bits */ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { if (ahub->dts.dai_type == SUNXI_DAI_HDMI_TYPE) { if (ahub->hdmi_fmt > HDMI_FMT_PCM) { regmap_update_bits(regmap, SUNXI_AHUB_APBIF_TX_CTL(apb_num), 0x7 << APBIF_TX_WS, 0x7 << APBIF_TX_WS); regmap_update_bits(regmap, SUNXI_AHUB_APBIF_TXFIFO_CTL(apb_num), 0x1 << APBIF_TX_TXIM, 0x0 << APBIF_TX_TXIM); } else { regmap_update_bits(regmap, SUNXI_AHUB_APBIF_TX_CTL(apb_num), 0x7 << APBIF_TX_WS, 0x3 << APBIF_TX_WS); regmap_update_bits(regmap, SUNXI_AHUB_APBIF_TXFIFO_CTL(apb_num), 0x1 << APBIF_TX_TXIM, 0x1 << APBIF_TX_TXIM); } } else { regmap_update_bits(regmap, SUNXI_AHUB_APBIF_TX_CTL(apb_num), 0x7 << APBIF_TX_WS, 0x3 << APBIF_TX_WS); regmap_update_bits(regmap, SUNXI_AHUB_APBIF_TXFIFO_CTL(apb_num), 0x1 << APBIF_TX_TXIM, 0x1 << APBIF_TX_TXIM); } } else { regmap_update_bits(regmap, SUNXI_AHUB_APBIF_RX_CTL(apb_num), 0x7 << APBIF_RX_WS, 0x3 << APBIF_RX_WS); regmap_update_bits(regmap, SUNXI_AHUB_APBIF_RXFIFO_CTL(apb_num), 0x3 << APBIF_RX_RXOM, 0x1 << APBIF_RX_RXOM); } /* tdmn bits */ if (ahub->dts.dai_type == SUNXI_DAI_HDMI_TYPE) { if (ahub->hdmi_fmt > HDMI_FMT_PCM) { regmap_update_bits(regmap, SUNXI_AHUB_I2S_FMT0(tdm_num), 0x7 << I2S_FMT0_SR, 0x5 << I2S_FMT0_SR); } else { regmap_update_bits(regmap, SUNXI_AHUB_I2S_FMT0(tdm_num), 0x7 << I2S_FMT0_SR, 0x3 << I2S_FMT0_SR); } } else { regmap_update_bits(regmap, SUNXI_AHUB_I2S_FMT0(tdm_num), 0x7 << I2S_FMT0_SR, 0x3 << I2S_FMT0_SR); } break; case SNDRV_PCM_FORMAT_S20_3LE: case SNDRV_PCM_FORMAT_S24_LE: if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { regmap_update_bits(regmap, SUNXI_AHUB_APBIF_TX_CTL(apb_num), 0x7 << APBIF_TX_WS, 0x5 << APBIF_TX_WS); regmap_update_bits(regmap, SUNXI_AHUB_APBIF_TXFIFO_CTL(apb_num), 0x1 << APBIF_TX_TXIM, 0x1 << APBIF_TX_TXIM); } else { regmap_update_bits(regmap, SUNXI_AHUB_APBIF_RX_CTL(apb_num), 0x7 << APBIF_RX_WS, 0x5 << APBIF_RX_WS); regmap_update_bits(regmap, SUNXI_AHUB_APBIF_RXFIFO_CTL(apb_num), 0x3 << APBIF_RX_RXOM, 0x1 << APBIF_RX_RXOM); } regmap_update_bits(regmap, SUNXI_AHUB_I2S_FMT0(tdm_num), 0x7 << I2S_FMT0_SR, 0x5 << I2S_FMT0_SR); break; case SNDRV_PCM_FORMAT_S32_LE: if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { regmap_update_bits(regmap, SUNXI_AHUB_APBIF_TX_CTL(apb_num), 0x7 << APBIF_TX_WS, 0x7 << APBIF_TX_WS); regmap_update_bits(regmap, SUNXI_AHUB_APBIF_TXFIFO_CTL(apb_num), 0x1 << APBIF_TX_TXIM, 0x1 << APBIF_TX_TXIM); } else { regmap_update_bits(regmap, SUNXI_AHUB_APBIF_RX_CTL(apb_num), 0x7 << APBIF_RX_WS, 0x7 << APBIF_RX_WS); regmap_update_bits(regmap, SUNXI_AHUB_APBIF_RXFIFO_CTL(apb_num), 0x3 << APBIF_RX_RXOM, 0x1 << APBIF_RX_RXOM); } regmap_update_bits(regmap, SUNXI_AHUB_I2S_FMT0(tdm_num), 0x7 << I2S_FMT0_SR, 0x7 << I2S_FMT0_SR); break; default: SND_LOG_ERR(HLOG, "unrecognized format bits\n"); return -EINVAL; } /* set channels */ channels = params_channels(params); if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { /* apbifn channels */ regmap_update_bits(regmap, SUNXI_AHUB_APBIF_TX_CTL(apb_num), 0xf << APBIF_TX_CHAN_NUM, (channels - 1) << APBIF_TX_CHAN_NUM); /* tdmn channels */ regmap_update_bits(regmap, SUNXI_AHUB_I2S_CHCFG(tdm_num), 0xf << I2S_CHCFG_TX_CHANNUM, (channels - 1) << I2S_CHCFG_TX_CHANNUM); if (ahub->dts.dai_type == SUNXI_DAI_HDMI_TYPE) { regmap_write(regmap, SUNXI_AHUB_I2S_OUT_CHMAP0(tdm_num, 0), 0x10); if (ahub->hdmi_fmt > HDMI_FMT_PCM) { regmap_write(regmap, SUNXI_AHUB_I2S_OUT_CHMAP0(tdm_num, 1), 0x32); regmap_write(regmap, SUNXI_AHUB_I2S_OUT_CHMAP0(tdm_num, 2), 0x54); regmap_write(regmap, SUNXI_AHUB_I2S_OUT_CHMAP0(tdm_num, 3), 0x76); } else { if (channels > 2) { regmap_write(regmap, SUNXI_AHUB_I2S_OUT_CHMAP0(tdm_num, 1), 0x23); /* only 5.1 & 7.1 */ if (channels > 4) { /* 5.1 hit this */ if (channels == 6) { regmap_write(regmap, SUNXI_AHUB_I2S_OUT_CHMAP0(tdm_num, 2), 0x54); } else { regmap_write(regmap, SUNXI_AHUB_I2S_OUT_CHMAP0(tdm_num, 2), 0x76); } } if (channels > 6) { regmap_write(regmap, SUNXI_AHUB_I2S_OUT_CHMAP0(tdm_num, 3), 0x54); } } } regmap_update_bits(regmap, SUNXI_AHUB_I2S_OUT_SLOT(tdm_num, 0), 0xf << I2S_OUT_SLOT_NUM, 0x1 << I2S_OUT_SLOT_NUM); regmap_update_bits(regmap, SUNXI_AHUB_I2S_OUT_SLOT(tdm_num, 0), 0xffff << I2S_OUT_SLOT_EN, 0x3 << I2S_OUT_SLOT_EN); regmap_update_bits(regmap, SUNXI_AHUB_I2S_OUT_SLOT(tdm_num, 1), 0xf << I2S_OUT_SLOT_NUM, 0x1 << I2S_OUT_SLOT_NUM); regmap_update_bits(regmap, SUNXI_AHUB_I2S_OUT_SLOT(tdm_num, 1), 0xffff << I2S_OUT_SLOT_EN, 0x3 << I2S_OUT_SLOT_EN); regmap_update_bits(regmap, SUNXI_AHUB_I2S_OUT_SLOT(tdm_num, 2), 0xf << I2S_OUT_SLOT_NUM, 0x1 << I2S_OUT_SLOT_NUM); regmap_update_bits(regmap, SUNXI_AHUB_I2S_OUT_SLOT(tdm_num, 2), 0xffff << I2S_OUT_SLOT_EN, 0x3 << I2S_OUT_SLOT_EN); regmap_update_bits(regmap, SUNXI_AHUB_I2S_OUT_SLOT(tdm_num, 3), 0xf << I2S_OUT_SLOT_NUM, 0x1 << I2S_OUT_SLOT_NUM); regmap_update_bits(regmap, SUNXI_AHUB_I2S_OUT_SLOT(tdm_num, 3), 0xffff << I2S_OUT_SLOT_EN, 0x3 << I2S_OUT_SLOT_EN); } else { regmap_update_bits(regmap, SUNXI_AHUB_I2S_OUT_SLOT(tdm_num, tx_pin), 0xf << I2S_OUT_SLOT_NUM, (channels - 1) << I2S_OUT_SLOT_NUM); regmap_update_bits(regmap, SUNXI_AHUB_I2S_OUT_SLOT(tdm_num, tx_pin), 0xffff << I2S_OUT_SLOT_EN, channels_en[channels - 1] << I2S_OUT_SLOT_EN); } } else { /* apbifn channels */ regmap_update_bits(regmap, SUNXI_AHUB_APBIF_RX_CTL(apb_num), 0xf << APBIF_RX_CHAN_NUM, (channels - 1) << APBIF_RX_CHAN_NUM); /* tdmn channels */ regmap_update_bits(regmap, SUNXI_AHUB_I2S_CHCFG(tdm_num), 0xf << I2S_CHCFG_RX_CHANNUM, (channels - 1) << I2S_CHCFG_RX_CHANNUM); regmap_update_bits(regmap, SUNXI_AHUB_I2S_IN_SLOT(tdm_num), 0xf << I2S_IN_SLOT_NUM, (channels - 1) << I2S_IN_SLOT_NUM); } /* set rate (set at lrck & bclk, nothing set at this function) */ if (ahub->dts.dai_type == SUNXI_DAI_HDMI_TYPE) { ret = snd_sunxi_hdmi_hw_params(params, ahub->hdmi_fmt); if (ret) { SND_LOG_ERR(HLOG, "hdmi audio hw_params set failed\n"); return ret; } ret = snd_sunxi_hdmi_prepare(); if (ret) { SND_LOG_ERR(HLOG, "hdmi audio prepare failed\n"); return ret; } } sunxi_ahub_dam_ctrl(true, apb_num, tdm_num, channels); return 0; } static int sunxi_ahub_dai_hw_free(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { struct sunxi_ahub *ahub = snd_soc_dai_get_drvdata(dai); SND_LOG_DEBUG(HLOG, "\n"); sunxi_ahub_dam_ctrl(false, ahub->dts.apb_num, ahub->dts.tdm_num, 0); return 0; } static void sunxi_ahub_dai_tx_route(struct sunxi_ahub *ahub, bool enable) { struct regmap *regmap = NULL; unsigned int tdm_num, tx_pin; unsigned int apb_num; SND_LOG_DEBUG(HLOG, "%s\n", enable ? "on" : "off"); regmap = ahub->mem.regmap; tdm_num = ahub->dts.tdm_num; tx_pin = ahub->dts.tx_pin; apb_num = ahub->dts.apb_num; if (enable) goto tx_route_enable; else goto tx_route_disable; tx_route_enable: if (ahub->dts.dai_type != SUNXI_DAI_HDMI_TYPE) { regmap_update_bits(regmap, SUNXI_AHUB_I2S_CTL(tdm_num), 0x1 << (I2S_CTL_SDO0_EN + tx_pin), 0x1 << (I2S_CTL_SDO0_EN + tx_pin)); } regmap_update_bits(regmap, SUNXI_AHUB_I2S_CTL(tdm_num), 0x1 << I2S_CTL_TXEN, 0x1 << I2S_CTL_TXEN); regmap_update_bits(regmap, SUNXI_AHUB_I2S_CTL(tdm_num), 0x1 << I2S_CTL_OUT_MUTE, 0x0 << I2S_CTL_OUT_MUTE); /* start apbif tx */ regmap_update_bits(regmap, SUNXI_AHUB_APBIF_TX_CTL(apb_num), 0x1 << APBIF_TX_START, 0x1 << APBIF_TX_START); /* enable tx drq */ regmap_update_bits(regmap, SUNXI_AHUB_APBIF_TX_IRQ_CTL(apb_num), 0x1 << APBIF_TX_DRQ, 0x1 << APBIF_TX_DRQ); return; tx_route_disable: regmap_update_bits(regmap, SUNXI_AHUB_I2S_CTL(tdm_num), 0x1 << I2S_CTL_OUT_MUTE, 0x1 << I2S_CTL_OUT_MUTE); regmap_update_bits(regmap, SUNXI_AHUB_I2S_CTL(tdm_num), 0x1 << I2S_CTL_TXEN, 0x0 << I2S_CTL_TXEN); if (ahub->dts.dai_type != SUNXI_DAI_HDMI_TYPE) { regmap_update_bits(regmap, SUNXI_AHUB_I2S_CTL(tdm_num), 0x1 << (I2S_CTL_SDO0_EN + tx_pin), 0x0 << (I2S_CTL_SDO0_EN + tx_pin)); } /* stop apbif tx */ regmap_update_bits(regmap, SUNXI_AHUB_APBIF_TX_CTL(apb_num), 0x1 << APBIF_TX_START, 0x0 << APBIF_TX_START); /* disable tx drq */ regmap_update_bits(regmap, SUNXI_AHUB_APBIF_TX_IRQ_CTL(apb_num), 0x1 << APBIF_TX_DRQ, 0x0 << APBIF_TX_DRQ); return; } static void sunxi_ahub_dai_rx_route(struct sunxi_ahub *ahub, bool enable) { struct regmap *regmap = NULL; unsigned int tdm_num, rx_pin; unsigned int apb_num; SND_LOG_DEBUG(HLOG, "%s\n", enable ? "on" : "off"); regmap = ahub->mem.regmap; tdm_num = ahub->dts.tdm_num; rx_pin = ahub->dts.rx_pin; apb_num = ahub->dts.apb_num; if (enable) goto rx_route_enable; else goto rx_route_disable; rx_route_enable: regmap_update_bits(regmap, SUNXI_AHUB_I2S_CTL(tdm_num), 0x1 << (I2S_CTL_SDI0_EN + rx_pin), 0x1 << (I2S_CTL_SDI0_EN + rx_pin)); regmap_update_bits(regmap, SUNXI_AHUB_I2S_CTL(tdm_num), 0x1 << I2S_CTL_RXEN, 0x1 << I2S_CTL_RXEN); /* start apbif rx */ regmap_update_bits(regmap, SUNXI_AHUB_APBIF_RX_CTL(apb_num), 0x1 << APBIF_RX_START, 0x1 << APBIF_RX_START); /* enable rx drq */ regmap_update_bits(regmap, SUNXI_AHUB_APBIF_RX_IRQ_CTL(apb_num), 0x1 << APBIF_RX_DRQ, 0x1 << APBIF_RX_DRQ); return; rx_route_disable: regmap_update_bits(regmap, SUNXI_AHUB_I2S_CTL(tdm_num), 0x1 << I2S_CTL_RXEN, 0x0 << I2S_CTL_RXEN); regmap_update_bits(regmap, SUNXI_AHUB_I2S_CTL(tdm_num), 0x1 << (I2S_CTL_SDI0_EN + rx_pin), 0x0 << (I2S_CTL_SDI0_EN + rx_pin)); /* stop apbif rx */ regmap_update_bits(regmap, SUNXI_AHUB_APBIF_RX_CTL(apb_num), 0x1 << APBIF_RX_START, 0x0 << APBIF_RX_START); /* disable rx drq */ regmap_update_bits(regmap, SUNXI_AHUB_APBIF_RX_IRQ_CTL(apb_num), 0x1 << APBIF_RX_DRQ, 0x0 << APBIF_RX_DRQ); return; } static int sunxi_ahub_dai_trigger(struct snd_pcm_substream *substream, int cmd, struct snd_soc_dai *dai) { struct sunxi_ahub *ahub = snd_soc_dai_get_drvdata(dai); SND_LOG_DEBUG(HLOG, "\n"); if (IS_ERR_OR_NULL(ahub)) { SND_LOG_ERR(HLOG, "ahub is null.\n"); return -ENOMEM; } switch (cmd) { case SNDRV_PCM_TRIGGER_START: case SNDRV_PCM_TRIGGER_RESUME: case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { sunxi_ahub_dai_tx_route(ahub, true); } else { sunxi_ahub_dai_rx_route(ahub, true); } break; case SNDRV_PCM_TRIGGER_STOP: case SNDRV_PCM_TRIGGER_SUSPEND: case SNDRV_PCM_TRIGGER_PAUSE_PUSH: if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { sunxi_ahub_dai_tx_route(ahub, false); } else { sunxi_ahub_dai_rx_route(ahub, false); } break; default: return -EINVAL; } return 0; } static int sunxi_ahub_dai_prepare(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { struct sunxi_ahub *ahub = snd_soc_dai_get_drvdata(dai); struct regmap *regmap = NULL; unsigned int apb_num; SND_LOG_DEBUG(HLOG, "\n"); if (IS_ERR_OR_NULL(ahub)) { SND_LOG_ERR(HLOG, "ahub is null.\n"); return -ENOMEM; } regmap = ahub->mem.regmap; apb_num = ahub->dts.apb_num; if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { /* clear txfifo */ regmap_update_bits(regmap, SUNXI_AHUB_APBIF_TXFIFO_CTL(apb_num), 0x1 << APBIF_TX_FTX, 0x1 << APBIF_TX_FTX); /* clear tx o/u irq */ regmap_write(regmap, SUNXI_AHUB_APBIF_TX_IRQ_STA(apb_num), (0x1 << APBIF_TX_OV_PEND) | (0x1 << APBIF_TX_EM_PEND)); /* clear tx fifo cnt */ regmap_write(regmap, SUNXI_AHUB_APBIF_TXFIFO_CNT(apb_num), 0); } else { /* clear rxfifo */ regmap_update_bits(regmap, SUNXI_AHUB_APBIF_RXFIFO_CTL(apb_num), 0x1 << APBIF_RX_FRX, 0x1 << APBIF_RX_FRX); /* clear rx o/u irq */ regmap_write(regmap, SUNXI_AHUB_APBIF_RX_IRQ_STA(apb_num), (0x1 << APBIF_RX_UV_PEND) | (0x1 << APBIF_RX_AV_PEND)); /* clear rx fifo cnt */ regmap_write(regmap, SUNXI_AHUB_APBIF_RXFIFO_CNT(apb_num), 0); } return 0; } static void sunxi_ahub_dai_shutdown(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { struct sunxi_ahub *ahub = snd_soc_dai_get_drvdata(dai); struct regmap *regmap = NULL; unsigned int apb_num, tdm_num; SND_LOG_DEBUG(HLOG, "\n"); regmap = ahub->mem.regmap; apb_num = ahub->dts.apb_num; tdm_num = ahub->dts.tdm_num; if (ahub->dts.dai_type == SUNXI_DAI_HDMI_TYPE) { snd_sunxi_hdmi_shutdown(); } /* APBIF & I2S of RST and GAT */ if (tdm_num > 3 || apb_num > 2) { SND_LOG_ERR(HLOG, "unspport tdm num or apbif num\n"); return; } if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { regmap_update_bits(regmap, SUNXI_AHUB_RST, 0x1 << (APBIF_TXDIF0_RST - apb_num), 0x0 << (APBIF_TXDIF0_RST - apb_num)); regmap_update_bits(regmap, SUNXI_AHUB_GAT, 0x1 << (APBIF_TXDIF0_GAT - apb_num), 0x0 << (APBIF_TXDIF0_GAT - apb_num)); } else { regmap_update_bits(regmap, SUNXI_AHUB_RST, 0x1 << (APBIF_RXDIF0_RST - apb_num), 0x0 << (APBIF_RXDIF0_RST - apb_num)); regmap_update_bits(regmap, SUNXI_AHUB_GAT, 0x1 << (APBIF_RXDIF0_GAT - apb_num), 0x0 << (APBIF_RXDIF0_GAT - apb_num)); } } static const struct snd_soc_dai_ops sunxi_ahub_dai_ops = { /* call by machine */ .set_pll = sunxi_ahub_dai_set_pll, // set pllclk .set_sysclk = sunxi_ahub_dai_set_sysclk, // set mclk .set_bclk_ratio = sunxi_ahub_dai_set_bclk_ratio,// set bclk freq .set_tdm_slot = sunxi_ahub_dai_set_tdm_slot, // set slot num and width .set_fmt = sunxi_ahub_dai_set_fmt, // set tdm fmt /* call by asoc */ .startup = sunxi_ahub_dai_startup, .hw_params = sunxi_ahub_dai_hw_params, // set hardware params .hw_free = sunxi_ahub_dai_hw_free, .prepare = sunxi_ahub_dai_prepare, // clean irq and fifo .trigger = sunxi_ahub_dai_trigger, // set drq .shutdown = sunxi_ahub_dai_shutdown, }; static void snd_soc_sunxi_ahub_init(struct sunxi_ahub *ahub) { struct regmap *regmap = NULL; unsigned int apb_num, tdm_num, tx_pin, rx_pin; unsigned int reg_val = 0; unsigned int rx_pin_map = 0; unsigned int tdm_to_apb = 0; unsigned int apb_to_tdm = 0; SND_LOG_DEBUG(HLOG, "\n"); regmap = ahub->mem.regmap; apb_num = ahub->dts.apb_num; tdm_num = ahub->dts.tdm_num; tx_pin = ahub->dts.tx_pin; rx_pin = ahub->dts.rx_pin; regmap_update_bits(regmap, SUNXI_AHUB_I2S_CTL(tdm_num), 0x1 << I2S_CTL_GEN, 0x1 << I2S_CTL_GEN); regmap_update_bits(regmap, SUNXI_AHUB_RST, 0x1 << (I2S0_RST - tdm_num), 0x1 << (I2S0_RST - tdm_num)); regmap_update_bits(regmap, SUNXI_AHUB_GAT, 0x1 << (I2S0_GAT - tdm_num), 0x1 << (I2S0_GAT - tdm_num)); /* tdm tx channels map */ regmap_write(regmap, SUNXI_AHUB_I2S_OUT_CHMAP0(tdm_num, tx_pin), 0x76543210); regmap_write(regmap, SUNXI_AHUB_I2S_OUT_CHMAP1(tdm_num, tx_pin), 0xFEDCBA98); /* tdm rx channels map */ rx_pin_map = (rx_pin << 4) | (rx_pin << 12) | (rx_pin << 20) | (rx_pin << 28); reg_val = 0x03020100 | rx_pin_map; regmap_write(regmap, SUNXI_AHUB_I2S_IN_CHMAP0(tdm_num), reg_val); reg_val = 0x07060504 | rx_pin_map; regmap_write(regmap, SUNXI_AHUB_I2S_IN_CHMAP1(tdm_num), reg_val); reg_val = 0x0B0A0908 | rx_pin_map; regmap_write(regmap, SUNXI_AHUB_I2S_IN_CHMAP2(tdm_num), reg_val); reg_val = 0x0F0E0D0C | rx_pin_map; regmap_write(regmap, SUNXI_AHUB_I2S_IN_CHMAP3(tdm_num), reg_val); /* tdm tx & rx data fmt * 1. MSB first * 2. transfer 0 after each sample in each slot * 3. linear PCM */ regmap_write(regmap, SUNXI_AHUB_I2S_FMT1(tdm_num), 0x30); /* apbif tx & rx data fmt * 1. MSB first * 2. trigger level tx -> 0x20, rx -> 0x40 */ regmap_update_bits(regmap, SUNXI_AHUB_APBIF_TXFIFO_CTL(apb_num), 0x1 << APBIF_TX_TXIM, 0x0 << APBIF_TX_TXIM); regmap_update_bits(regmap, SUNXI_AHUB_APBIF_TXFIFO_CTL(apb_num), 0x3f << APBIF_TX_LEVEL, 0x20 << APBIF_TX_LEVEL); regmap_update_bits(regmap, SUNXI_AHUB_APBIF_RXFIFO_CTL(apb_num), 0x3 << APBIF_RX_RXOM, 0x0 << APBIF_RX_RXOM); regmap_update_bits(regmap, SUNXI_AHUB_APBIF_RXFIFO_CTL(apb_num), 0x7f << APBIF_RX_LEVEL, 0x40 << APBIF_RX_LEVEL); /* apbif <-> tdm */ switch (tdm_num) { case 0: tdm_to_apb = APBIF_RX_I2S0_TXDIF; break; case 1: tdm_to_apb = APBIF_RX_I2S1_TXDIF; break; case 2: tdm_to_apb = APBIF_RX_I2S2_TXDIF; break; case 3: tdm_to_apb = APBIF_RX_I2S3_TXDIF; break; default: SND_LOG_ERR(HLOG, "unspport tdm num\n"); return; } regmap_write(regmap, SUNXI_AHUB_APBIF_RXFIFO_CONT(apb_num), 0x1 << tdm_to_apb); switch (apb_num) { case 0: apb_to_tdm = I2S_RX_APBIF_TXDIF0; break; case 1: apb_to_tdm = I2S_RX_APBIF_TXDIF1; break; case 2: apb_to_tdm = I2S_RX_APBIF_TXDIF2; break; default: SND_LOG_ERR(HLOG, "unspport apb num\n"); return; } regmap_write(regmap, SUNXI_AHUB_I2S_RXCONT(tdm_num), 0x1 << apb_to_tdm); /* default setting HDMIAUDIO clk from ahub */ if (ahub->dts.dai_type == SUNXI_DAI_HDMI_TYPE) { regmap_write(regmap, SUNXI_AHUB_CTL, 0x1 << HDMI_SRC_SEL); regmap_update_bits(regmap, SUNXI_AHUB_I2S_CTL(tdm_num), 1 << I2S_CTL_SDO0_EN, 1 << I2S_CTL_SDO0_EN); regmap_update_bits(regmap, SUNXI_AHUB_I2S_CTL(tdm_num), 1 << I2S_CTL_SDO1_EN, 1 << I2S_CTL_SDO1_EN); regmap_update_bits(regmap, SUNXI_AHUB_I2S_CTL(tdm_num), 1 << I2S_CTL_SDO2_EN, 1 << I2S_CTL_SDO2_EN); regmap_update_bits(regmap, SUNXI_AHUB_I2S_CTL(tdm_num), 1 << I2S_CTL_SDO3_EN, 1 << I2S_CTL_SDO3_EN); } } static int sunxi_ahub_dai_probe(struct snd_soc_dai *dai) { struct sunxi_ahub *ahub = snd_soc_dai_get_drvdata(dai); SND_LOG_DEBUG(HLOG, "\n"); /* pcm_new will using the dma_param about the cma and fifo params. */ snd_soc_dai_init_dma_data(dai, &ahub->playback_dma_param, &ahub->capture_dma_param); snd_soc_sunxi_ahub_init(ahub); return 0; } static int sunxi_ahub_dai_remove(struct snd_soc_dai *dai) { struct sunxi_ahub *ahub = snd_soc_dai_get_drvdata(dai); struct regmap *regmap = NULL; unsigned int tdm_num; SND_LOG_DEBUG(HLOG, "\n"); regmap = ahub->mem.regmap; tdm_num = ahub->dts.tdm_num; regmap_update_bits(regmap, SUNXI_AHUB_I2S_CTL(tdm_num), 0x1 << I2S_CTL_GEN, 0x0 << I2S_CTL_GEN); regmap_update_bits(regmap, SUNXI_AHUB_RST, 0x1 << (I2S0_RST - tdm_num), 0x0 << (I2S0_RST - tdm_num)); regmap_update_bits(regmap, SUNXI_AHUB_GAT, 0x1 << (I2S0_GAT - tdm_num), 0x0 << (I2S0_GAT - tdm_num)); return 0; } static int sunxi_ahub_dai_suspend(struct snd_soc_dai *dai) { struct sunxi_ahub *ahub = snd_soc_dai_get_drvdata(dai); struct regmap *regmap = NULL; unsigned int apb_num, tdm_num; SND_LOG_DEBUG(HLOG, "\n"); regmap = ahub->mem.regmap; apb_num = ahub->dts.apb_num; tdm_num = ahub->dts.tdm_num; if (ahub->dts.dai_type == SUNXI_DAI_HDMI_TYPE) { regmap_update_bits(regmap, SUNXI_AHUB_I2S_CTL(tdm_num), 1 << I2S_CTL_SDO0_EN, 0 << I2S_CTL_SDO0_EN); regmap_update_bits(regmap, SUNXI_AHUB_I2S_CTL(tdm_num), 1 << I2S_CTL_SDO1_EN, 0 << I2S_CTL_SDO1_EN); regmap_update_bits(regmap, SUNXI_AHUB_I2S_CTL(tdm_num), 1 << I2S_CTL_SDO2_EN, 0 << I2S_CTL_SDO2_EN); regmap_update_bits(regmap, SUNXI_AHUB_I2S_CTL(tdm_num), 1 << I2S_CTL_SDO3_EN, 0 << I2S_CTL_SDO3_EN); } return 0; } static int sunxi_ahub_dai_resume(struct snd_soc_dai *dai) { struct sunxi_ahub *ahub = snd_soc_dai_get_drvdata(dai); SND_LOG_DEBUG(HLOG, "\n"); snd_soc_sunxi_ahub_init(ahub); return 0; } static struct snd_soc_dai_driver sunxi_ahub_dai = { .name = "ahub_plat", .probe = sunxi_ahub_dai_probe, .remove = sunxi_ahub_dai_remove, .suspend = sunxi_ahub_dai_suspend, .resume = sunxi_ahub_dai_resume, .playback = { .stream_name = "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 = "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_dai_ops, }; static int sunxi_ahub_probe(struct snd_soc_component *component) { int ret; struct sunxi_ahub *ahub = snd_soc_component_get_drvdata(component); SND_LOG_DEBUG(HLOG, "\n"); if (ahub->dts.dai_type == SUNXI_DAI_HDMI_TYPE) { ret = snd_sunxi_hdmi_init(); if (ret) { SND_LOG_ERR(HLOG, "hdmi audio init failed\n"); return -1; } ret = snd_sunxi_hdmi_add_controls(component); if (ret) { SND_LOG_ERR(HLOG, "add hdmiaudio kcontrols failed\n"); return -1; } ahub->hdmi_fmt = snd_sunxi_hdmi_get_fmt(); } else { ahub->hdmi_fmt = HDMI_FMT_NULL; } return 0; } int sunxi_loopback_debug_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { unsigned int reg_val; struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); struct sunxi_ahub *ahub = snd_soc_component_get_drvdata(component); struct sunxi_ahub_mem *mem = &ahub->mem; struct sunxi_ahub_dts *dts = &ahub->dts; regmap_read(mem->regmap, SUNXI_AHUB_I2S_CTL(dts->tdm_num), ®_val); ucontrol->value.integer.value[0] = ((reg_val & (1 << I2S_CTL_LOOP0)) ? 1 : 0); return 0; } int sunxi_loopback_debug_set(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); struct sunxi_ahub *ahub = snd_soc_component_get_drvdata(component); struct sunxi_ahub_mem *mem = &ahub->mem; struct sunxi_ahub_dts *dts = &ahub->dts; switch (ucontrol->value.integer.value[0]) { case 0: regmap_update_bits(mem->regmap, SUNXI_AHUB_I2S_CTL(dts->tdm_num), 1 << I2S_CTL_LOOP0, 0 << I2S_CTL_LOOP0); break; case 1: regmap_update_bits(mem->regmap, SUNXI_AHUB_I2S_CTL(dts->tdm_num), 1 << I2S_CTL_LOOP0, 1 << I2S_CTL_LOOP0); break; default: return -EINVAL; } return 0; } static const struct snd_kcontrol_new sunxi_ahub_controls[] = { SOC_SINGLE_EXT("loopback debug", SND_SOC_NOPM, 0, 1, 0, sunxi_loopback_debug_get, sunxi_loopback_debug_set), }; static struct snd_soc_component_driver sunxi_ahub_component = { .name = DRV_NAME, .probe = sunxi_ahub_probe, .controls = sunxi_ahub_controls, .num_controls = ARRAY_SIZE(sunxi_ahub_controls), }; /******************************************************************************* * for kernel source ******************************************************************************/ static int snd_soc_sunxi_ahub_pin_init(struct platform_device *pdev, struct device_node *np, struct sunxi_ahub_pinctl *pin) { int ret = 0; SND_LOG_DEBUG(HLOG, "\n"); if (of_property_read_bool(np, "pinctrl-used")) { pin->pinctrl_used = 1; } else { pin->pinctrl_used = 0; SND_LOG_DEBUG(HLOG, "unused pinctrl\n"); return 0; } pin->pinctrl = devm_pinctrl_get(&pdev->dev); if (IS_ERR_OR_NULL(pin->pinctrl)) { SND_LOG_ERR(HLOG, "pinctrl get failed\n"); ret = -EINVAL; return ret; } pin->pinstate = pinctrl_lookup_state(pin->pinctrl, PINCTRL_STATE_DEFAULT); if (IS_ERR_OR_NULL(pin->pinstate)) { SND_LOG_ERR(HLOG, "pinctrl default state get fail\n"); ret = -EINVAL; goto err_loopup_pinstate; } pin->pinstate_sleep = pinctrl_lookup_state(pin->pinctrl, PINCTRL_STATE_SLEEP); if (IS_ERR_OR_NULL(pin->pinstate_sleep)) { SND_LOG_ERR(HLOG, "pinctrl sleep state get failed\n"); ret = -EINVAL; goto err_loopup_pin_sleep; } ret = pinctrl_select_state(pin->pinctrl, pin->pinstate); if (ret < 0) { SND_LOG_ERR(HLOG, "daudio set pinctrl default state fail\n"); ret = -EBUSY; goto err_pinctrl_select_default; } return 0; err_pinctrl_select_default: err_loopup_pin_sleep: err_loopup_pinstate: devm_pinctrl_put(pin->pinctrl); return ret; } static int snd_soc_sunxi_ahub_dts_params_init(struct platform_device *pdev, struct device_node *np, struct sunxi_ahub_dts *dts) { int ret = 0; unsigned int temp_val = 0; SND_LOG_DEBUG(HLOG, "\n"); /* get dma params */ ret = of_property_read_u32(np, "playback-cma", &temp_val); if (ret < 0) { dts->playback_cma = SUNXI_AUDIO_CMA_MAX_KBYTES; SND_LOG_WARN(HLOG, "playback-cma missing, using default value\n"); } else { if (temp_val > SUNXI_AUDIO_CMA_MAX_KBYTES) temp_val = SUNXI_AUDIO_CMA_MAX_KBYTES; else if (temp_val < SUNXI_AUDIO_CMA_MIN_KBYTES) temp_val = SUNXI_AUDIO_CMA_MIN_KBYTES; dts->playback_cma = temp_val; } ret = of_property_read_u32(np, "capture-cma", &temp_val); if (ret != 0) { dts->capture_cma = SUNXI_AUDIO_CMA_MAX_KBYTES; SND_LOG_WARN(HLOG, "capture-cma missing, using default value\n"); } else { if (temp_val > SUNXI_AUDIO_CMA_MAX_KBYTES) temp_val = SUNXI_AUDIO_CMA_MAX_KBYTES; else if (temp_val < SUNXI_AUDIO_CMA_MIN_KBYTES) temp_val = SUNXI_AUDIO_CMA_MIN_KBYTES; dts->capture_cma = temp_val; } ret = of_property_read_u32(np, "tx-fifo-size", &temp_val); if (ret != 0) { dts->playback_fifo_size = SUNXI_AUDIO_FIFO_SIZE; SND_LOG_WARN(HLOG, "tx-fifo-size miss, using default value\n"); } else { dts->playback_fifo_size = temp_val; } ret = of_property_read_u32(np, "rx-fifo-size", &temp_val); if (ret != 0) { dts->capture_fifo_size = SUNXI_AUDIO_FIFO_SIZE; SND_LOG_WARN(HLOG, "rx-fifo-size miss,using default value\n"); } else { dts->capture_fifo_size = temp_val; } /* get tdm fmt of apb_num & tdm_num & tx/rx_pin */ ret = of_property_read_u32(np, "apb-num", &temp_val); if (ret < 0) { SND_LOG_WARN(HLOG, "apb-num config missing\n"); dts->apb_num = 0; } else { if (temp_val > 2) { /* APBIFn (n = 0~2) */ dts->apb_num = 0; SND_LOG_WARN(HLOG, "apb-num config invalid\n"); } else { dts->apb_num = temp_val; } } ret = of_property_read_u32(np, "tdm-num", &temp_val); if (ret < 0) { SND_LOG_WARN(HLOG, "tdm-num config missing\n"); dts->tdm_num = 0; } else { if (temp_val > 3) { /* I2Sn (n = 0~3) */ dts->tdm_num = 0; SND_LOG_WARN(HLOG, "tdm-num config invalid\n"); } else { dts->tdm_num = temp_val; } } ret = of_property_read_u32(np, "tx-pin", &temp_val); if (ret < 0) { SND_LOG_WARN(HLOG, "tx-pin config missing\n"); dts->tx_pin = 0; } else { if (temp_val > 3) { /* I2S_DOUTn (n = 0~3) */ dts->tx_pin = 0; SND_LOG_WARN(HLOG, "tx-pin config invalid\n"); } else { dts->tx_pin = temp_val; } } ret = of_property_read_u32(np, "rx-pin", &temp_val); if (ret < 0) { SND_LOG_WARN(HLOG, "rx-pin config missing\n"); dts->rx_pin = 0; } else { if (temp_val > 3) { /* I2S_DINTn (n = 0~3) */ dts->rx_pin = 0; SND_LOG_WARN(HLOG, "rx-pin config invalid\n"); } else { dts->rx_pin = temp_val; } } ret = snd_sunxi_hdmi_get_dai_type(np, &dts->dai_type); if (ret) dts->dai_type = SUNXI_DAI_I2S_TYPE; if (dts->dai_type == SUNXI_DAI_HDMI_TYPE) SND_LOG_DEBUG(HLOG, "dai-type : HDMI\n"); else SND_LOG_DEBUG(HLOG, "dai-type : I2S\n"); SND_LOG_DEBUG(HLOG, "playback-cma : %zu\n", dts->playback_cma); SND_LOG_DEBUG(HLOG, "capture-cma : %zu\n", dts->capture_cma); SND_LOG_DEBUG(HLOG, "tx-fifo-size : %zu\n", dts->playback_fifo_size); SND_LOG_DEBUG(HLOG, "rx-fifo-size : %zu\n", dts->capture_fifo_size); SND_LOG_DEBUG(HLOG, "apb-num : %u\n", dts->apb_num); SND_LOG_DEBUG(HLOG, "tdm-num : %u\n", dts->tdm_num); SND_LOG_DEBUG(HLOG, "tx-pin : %u\n", dts->tx_pin); SND_LOG_DEBUG(HLOG, "rx-pin : %u\n", dts->rx_pin); return 0; }; static int snd_soc_sunxi_ahub_regulator_init(struct platform_device *pdev, struct device_node *np, struct sunxi_ahub_regulator *regulator) { int ret = 0; SND_LOG_DEBUG(HLOG, "\n"); regulator->regulator_name = NULL; if (of_property_read_string(np, "ahub-regulator", ®ulator->regulator_name)) { SND_LOG_DEBUG(HLOG, "regulator missing\n"); regulator->regulator = NULL; return 0; } regulator->regulator = regulator_get(NULL, regulator->regulator_name); if (IS_ERR_OR_NULL(regulator->regulator)) { SND_LOG_ERR(HLOG, "get duaido vcc-pin failed\n"); ret = -EFAULT; goto err_regulator_get; } ret = regulator_set_voltage(regulator->regulator, 3300000, 3300000); if (ret < 0) { SND_LOG_ERR(HLOG, "set duaido voltage failed\n"); ret = -EFAULT; goto err_regulator_set_vol; } ret = regulator_enable(regulator->regulator); if (ret < 0) { SND_LOG_ERR(HLOG, "enable duaido vcc-pin failed\n"); ret = -EFAULT; goto err_regulator_enable; } return 0; err_regulator_enable: err_regulator_set_vol: if (regulator->regulator) regulator_put(regulator->regulator); err_regulator_get: return ret; }; static void snd_soc_sunxi_dma_params_init(struct sunxi_ahub *ahub) { struct resource *res = ahub->mem.res; struct sunxi_ahub_dts *dts = &ahub->dts; SND_LOG_DEBUG(HLOG, "\n"); ahub->playback_dma_param.src_maxburst = 4; ahub->playback_dma_param.dst_maxburst = 4; ahub->playback_dma_param.dma_addr = res->start + SUNXI_AHUB_APBIF_TXFIFO(dts->apb_num); ahub->playback_dma_param.cma_kbytes = dts->playback_cma; ahub->playback_dma_param.fifo_size = dts->playback_fifo_size; ahub->playback_dma_param.dma_drq_type_num = DRQDST_AHUB0_TX + dts->apb_num; ahub->capture_dma_param.src_maxburst = 4; ahub->capture_dma_param.dst_maxburst = 4; ahub->capture_dma_param.dma_addr = res->start + SUNXI_AHUB_APBIF_RXFIFO(dts->apb_num); ahub->capture_dma_param.cma_kbytes = dts->capture_cma; ahub->capture_dma_param.fifo_size = dts->capture_fifo_size; ahub->capture_dma_param.dma_drq_type_num = DRQSRC_AHUB0_RX + dts->apb_num; }; static int sunxi_ahub_dev_probe(struct platform_device *pdev) { int ret; struct device_node *np = pdev->dev.of_node; struct sunxi_ahub *ahub = NULL; struct sunxi_ahub_mem *mem = NULL; struct sunxi_ahub_clk *clk = NULL; struct sunxi_ahub_pinctl *pin = NULL; struct sunxi_ahub_dts *dts = NULL; struct sunxi_ahub_regulator *regulator = NULL; SND_LOG_DEBUG(HLOG, "\n"); ahub = devm_kzalloc(&pdev->dev, sizeof(struct sunxi_ahub), GFP_KERNEL); if (IS_ERR_OR_NULL(ahub)) { SND_LOG_ERR(HLOG, "alloc sunxi_ahub failed\n"); ret = -ENOMEM; goto err_devm_malloc_sunxi_daudio; } dev_set_drvdata(&pdev->dev, ahub); ahub->dev = &pdev->dev; mem = &ahub->mem; clk = &ahub->clk; pin = &ahub->pin; dts = &ahub->dts; regulator = &ahub->regulator; ret = snd_soc_sunxi_ahub_mem_get(mem); if (ret) { SND_LOG_ERR(HLOG, "remap get failed\n"); ret = -EINVAL; goto err_snd_soc_sunxi_ahub_mem_get; } ret = snd_soc_sunxi_ahub_clk_get(clk); if (ret) { SND_LOG_ERR(HLOG, "clk get failed\n"); ret = -EINVAL; goto err_snd_soc_sunxi_ahub_clk_get; } ret = snd_soc_sunxi_ahub_dts_params_init(pdev, np, dts); if (ret) { SND_LOG_ERR(HLOG, "dts init failed\n"); ret = -EINVAL; goto err_snd_soc_sunxi_ahub_dts_params_init; } if (dts->dai_type != SUNXI_DAI_HDMI_TYPE) { ret = snd_soc_sunxi_ahub_pin_init(pdev, np, pin); if (ret) { SND_LOG_ERR(HLOG, "pinctrl init failed\n"); ret = -EINVAL; goto err_snd_soc_sunxi_ahub_pin_init; } } ret = snd_soc_sunxi_ahub_regulator_init(pdev, np, regulator); if (ret) { SND_LOG_ERR(HLOG, "regulator init failed\n"); ret = -EINVAL; goto err_snd_soc_sunxi_ahub_regulator_init; } snd_soc_sunxi_dma_params_init(ahub); ret = snd_soc_register_component(&pdev->dev, &sunxi_ahub_component, &sunxi_ahub_dai, 1); if (ret) { SND_LOG_ERR(HLOG, "component register failed\n"); ret = -ENOMEM; goto err_snd_soc_register_component; } if (dts->dai_type == SUNXI_DAI_HDMI_TYPE) ret = snd_sunxi_hdmi_platform_register(&pdev->dev); else ret = snd_sunxi_dma_platform_register(&pdev->dev); if (ret) { SND_LOG_ERR(HLOG, "register ASoC platform failed\n"); ret = -ENOMEM; goto err_snd_sunxi_platform_register; } SND_LOG_DEBUG(HLOG, "register ahub platform success\n"); return 0; err_snd_sunxi_platform_register: snd_soc_unregister_component(&pdev->dev); err_snd_soc_register_component: err_snd_soc_sunxi_ahub_regulator_init: err_snd_soc_sunxi_ahub_dts_params_init: err_snd_soc_sunxi_ahub_pin_init: err_snd_soc_sunxi_ahub_clk_get: err_snd_soc_sunxi_ahub_mem_get: devm_kfree(&pdev->dev, ahub); err_devm_malloc_sunxi_daudio: of_node_put(np); return ret; } static int sunxi_ahub_dev_remove(struct platform_device *pdev) { struct sunxi_ahub *ahub = dev_get_drvdata(&pdev->dev); struct sunxi_ahub_dts *dts = &ahub->dts; struct sunxi_ahub_pinctl *pin = &ahub->pin; struct sunxi_ahub_regulator *regulator = &ahub->regulator; SND_LOG_DEBUG(HLOG, "\n"); if (dts->dai_type == SUNXI_DAI_HDMI_TYPE) snd_sunxi_hdmi_platform_unregister(&pdev->dev); else snd_sunxi_dma_platform_unregister(&pdev->dev); snd_soc_unregister_component(&pdev->dev); if (regulator->regulator) { if (!IS_ERR_OR_NULL(regulator->regulator)) { regulator_disable(regulator->regulator); regulator_put(regulator->regulator); } } if (pin->pinctrl_used) devm_pinctrl_put(pin->pinctrl); devm_kfree(&pdev->dev, ahub); SND_LOG_DEBUG(HLOG, "unregister ahub platform success\n"); return 0; } static const struct of_device_id sunxi_ahub_of_match[] = { { .compatible = "allwinner," DRV_NAME, }, {}, }; MODULE_DEVICE_TABLE(of, sunxi_ahub_of_match); static struct platform_driver sunxi_ahub_driver = { .driver = { .name = DRV_NAME, .owner = THIS_MODULE, .of_match_table = sunxi_ahub_of_match, }, .probe = sunxi_ahub_dev_probe, .remove = sunxi_ahub_dev_remove, }; int __init sunxi_ahub_dev_init(void) { int ret; ret = platform_driver_register(&sunxi_ahub_driver); if (ret != 0) { SND_LOG_ERR(HLOG, "platform driver register failed\n"); return -EINVAL; } return ret; } void __exit sunxi_ahub_dev_exit(void) { platform_driver_unregister(&sunxi_ahub_driver); } late_initcall(sunxi_ahub_dev_init); module_exit(sunxi_ahub_dev_exit); MODULE_AUTHOR("Dby@allwinnertech.com"); MODULE_LICENSE("GPL"); MODULE_DESCRIPTION("sunxi soundcard platform of ahub");