staticint audio_ssp_init_portregs(struct cygnus_aio_port *aio)
{
u32 value, fci_id; int status = 0;
switch (aio->port_type) { case PORT_TDM:
value = readl(aio->cygaud->audio + aio->regs.i2s_stream_cfg);
value &= ~I2S_STREAM_CFG_MASK;
/* Set Group ID */
writel(aio->portnum,
aio->cygaud->audio + aio->regs.bf_sourcech_grp);
/* Configure the AUD_FMM_IOP_OUT_I2S_x_STREAM_CFG reg */
value |= aio->portnum << I2S_OUT_STREAM_CFG_GROUP_ID;
value |= aio->portnum; /* FCI ID is the port num */
value |= CH_GRP_STEREO << I2S_OUT_STREAM_CFG_CHANNEL_GROUPING;
writel(value, aio->cygaud->audio + aio->regs.i2s_stream_cfg);
/* Configure the AUD_FMM_BF_CTRL_SOURCECH_CFGX reg */
value = readl(aio->cygaud->audio + aio->regs.bf_sourcech_cfg);
value &= ~BIT(BF_SRC_CFGX_NOT_PAUSE_WHEN_EMPTY);
value |= BIT(BF_SRC_CFGX_SFIFO_SZ_DOUBLE);
value |= BIT(BF_SRC_CFGX_PROCESS_SEQ_ID_VALID);
writel(value, aio->cygaud->audio + aio->regs.bf_sourcech_cfg);
/* Configure the AUD_FMM_IOP_IN_I2S_x_CAP_STREAM_CFG_0 reg */
value = readl(aio->cygaud->i2s_in +
aio->regs.i2s_cap_stream_cfg);
value &= ~I2S_CAP_STREAM_CFG_MASK;
value |= aio->portnum << I2S_IN_STREAM_CFG_0_GROUP_ID;
writel(value, aio->cygaud->i2s_in +
aio->regs.i2s_cap_stream_cfg);
value = readl(aio->cygaud->audio + aio->regs.bf_destch_cfg);
value |= BIT(BF_DST_CFGX_DFIFO_SZ_DOUBLE);
value &= ~BIT(BF_DST_CFGX_NOT_PAUSE_WHEN_FULL);
value |= (fci_id << BF_DST_CFGX_FCI_ID);
value |= BIT(BF_DST_CFGX_PROC_SEQ_ID_VALID);
writel(value, aio->cygaud->audio + aio->regs.bf_destch_cfg);
/* Enable the transmit pin for this port */
value = readl(aio->cygaud->audio + AUD_MISC_SEROUT_OE_REG_BASE);
value &= ~BIT((aio->portnum * 4) + AUD_MISC_SEROUT_SDAT_OE);
writel(value, aio->cygaud->audio + AUD_MISC_SEROUT_OE_REG_BASE); break; case PORT_SPDIF:
writel(aio->portnum, aio->cygaud->audio + BF_SRC_GRP3_OFFSET);
value = readl(aio->cygaud->audio + SPDIF_CTRL_OFFSET);
value |= BIT(SPDIF_0_OUT_DITHER_ENA);
writel(value, aio->cygaud->audio + SPDIF_CTRL_OFFSET);
/* Enable and set the FCI ID for the SPDIF channel */
value = readl(aio->cygaud->audio + SPDIF_STREAM_CFG_OFFSET);
value &= ~SPDIF_STREAM_CFG_MASK;
value |= aio->portnum; /* FCI ID is the port num */
value |= BIT(SPDIF_0_OUT_STREAM_ENA);
writel(value, aio->cygaud->audio + SPDIF_STREAM_CFG_OFFSET);
value = readl(aio->cygaud->audio + aio->regs.bf_sourcech_cfg);
value &= ~BIT(BF_SRC_CFGX_NOT_PAUSE_WHEN_EMPTY);
value |= BIT(BF_SRC_CFGX_SFIFO_SZ_DOUBLE);
value |= BIT(BF_SRC_CFGX_PROCESS_SEQ_ID_VALID);
writel(value, aio->cygaud->audio + aio->regs.bf_sourcech_cfg);
/* Enable the spdif output pin */
value = readl(aio->cygaud->audio + AUD_MISC_SEROUT_OE_REG_BASE);
value &= ~BIT(AUD_MISC_SEROUT_SPDIF_OE);
writel(value, aio->cygaud->audio + AUD_MISC_SEROUT_OE_REG_BASE); break; default:
dev_err(aio->cygaud->dev, "Port not supported\n");
status = -EINVAL;
}
value = readl(aio->cygaud->audio + aio->regs.i2s_cfg);
value |= BIT(I2S_OUT_CFGX_CLK_ENA);
value |= BIT(I2S_OUT_CFGX_DATA_ENABLE);
writel(value, aio->cygaud->audio + aio->regs.i2s_cfg);
value = readl(aio->cygaud->i2s_in + aio->regs.i2s_cap_stream_cfg);
value |= BIT(I2S_IN_STREAM_CFG_CAP_ENA);
writel(value, aio->cygaud->i2s_in + aio->regs.i2s_cap_stream_cfg);
value = readl(aio->cygaud->i2s_in + aio->regs.i2s_cap_stream_cfg);
value &= ~BIT(I2S_IN_STREAM_CFG_CAP_ENA);
writel(value, aio->cygaud->i2s_in + aio->regs.i2s_cap_stream_cfg);
aio->streams_on &= ~CAPTURE_STREAM_MASK;
/* If both playback and capture are off */ if (!aio->streams_on) {
value = readl(aio->cygaud->audio + aio->regs.i2s_cfg);
value &= ~BIT(I2S_OUT_CFGX_CLK_ENA);
value &= ~BIT(I2S_OUT_CFGX_DATA_ENABLE);
writel(value, aio->cygaud->audio + aio->regs.i2s_cfg);
}
value = readl(aio->cygaud->audio + aio->regs.i2s_cfg);
value |= BIT(I2S_OUT_CFGX_CLK_ENA);
value |= BIT(I2S_OUT_CFGX_DATA_ENABLE);
writel(value, aio->cygaud->audio + aio->regs.i2s_cfg);
value = readl(aio->cygaud->audio + aio->regs.bf_sourcech_cfg);
value |= BIT(BF_SRC_CFGX_SFIFO_ENA);
writel(value, aio->cygaud->audio + aio->regs.bf_sourcech_cfg);
aio->streams_on |= PLAYBACK_STREAM_MASK; break; case PORT_SPDIF:
value = readl(aio->cygaud->audio + SPDIF_FORMAT_CFG_OFFSET);
value |= 0x3;
writel(value, aio->cygaud->audio + SPDIF_FORMAT_CFG_OFFSET);
value = readl(aio->cygaud->audio + aio->regs.bf_sourcech_cfg);
value |= BIT(BF_SRC_CFGX_SFIFO_ENA);
writel(value, aio->cygaud->audio + aio->regs.bf_sourcech_cfg); break; default:
dev_err(aio->cygaud->dev, "Port not supported %d\n", aio->portnum);
status = -EINVAL;
}
return status;
}
staticint audio_ssp_out_disable(struct cygnus_aio_port *aio)
{
u32 value; int status = 0;
switch (aio->port_type) { case PORT_TDM:
aio->streams_on &= ~PLAYBACK_STREAM_MASK;
/* If both playback and capture are off */ if (!aio->streams_on) {
value = readl(aio->cygaud->audio + aio->regs.i2s_cfg);
value &= ~BIT(I2S_OUT_CFGX_CLK_ENA);
value &= ~BIT(I2S_OUT_CFGX_DATA_ENABLE);
writel(value, aio->cygaud->audio + aio->regs.i2s_cfg);
}
/* set group_sync_dis = 1 */
value = readl(aio->cygaud->audio + BF_SRC_GRP_SYNC_DIS_OFFSET);
value |= BIT(aio->portnum);
writel(value, aio->cygaud->audio + BF_SRC_GRP_SYNC_DIS_OFFSET);
/* * Check if the bit clock can be generated from the given MCLK. * MCLK must be a perfect multiple of bit clock and must be one of the * following values... (2,4,6,8,10,12,14)
*/ if ((aio->mclk % bit_rate) != 0) return -EINVAL;
ratio = aio->mclk / bit_rate; switch (ratio) { case 2: case 4: case 6: case 8: case 10: case 12: case 14:
mclk_rate = ratio / 2; break;
/* Set number of bitclks per frame */
value = readl(aio->cygaud->audio + aio->regs.i2s_cfg);
value &= ~(mask << I2S_OUT_CFGX_SCLKS_PER_1FS_DIV32);
value |= sclk << I2S_OUT_CFGX_SCLKS_PER_1FS_DIV32;
writel(value, aio->cygaud->audio + aio->regs.i2s_cfg);
dev_dbg(aio->cygaud->dev, "SCLKS_PER_1FS_DIV32 = 0x%x\n", value); break; case PORT_SPDIF: break; default:
dev_err(aio->cygaud->dev, "Unknown port type\n"); return -EINVAL;
}
/* Set MCLK_RATE ssp port (spdif and ssp are the same) */
value = readl(aio->cygaud->audio + aio->regs.i2s_mclk_cfg);
value &= ~(0xf << I2S_OUT_MCLKRATE_SHIFT);
value |= (mclk_rate << I2S_OUT_MCLKRATE_SHIFT);
writel(value, aio->cygaud->audio + aio->regs.i2s_mclk_cfg);
switch (aio->mode) { case CYGNUS_SSPMODE_TDM: if ((rate == 192000) && (params_channels(params) > 4)) {
dev_err(aio->cygaud->dev, "Cannot run %d channels at %dHz\n",
params_channels(params), rate); return -EINVAL;
} break; case CYGNUS_SSPMODE_I2S:
aio->bit_per_frame = 64; /* I2S must be 64 bit per frame */ break; default:
dev_err(aio->cygaud->dev, "%s port running in unknown mode\n", __func__); return -EINVAL;
}
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
value = readl(aio->cygaud->audio + aio->regs.bf_sourcech_cfg);
value &= ~BIT(BF_SRC_CFGX_BUFFER_PAIR_ENABLE);
value &= ~BIT(BF_SRC_CFGX_SAMPLE_CH_MODE);
writel(value, aio->cygaud->audio + aio->regs.bf_sourcech_cfg);
switch (params_format(params)) { case SNDRV_PCM_FORMAT_S16_LE:
bitres = 16; break;
case SNDRV_PCM_FORMAT_S32_LE: /* 32 bit mode is coded as 0 */
bitres = 0; break;
default: return -EINVAL;
}
value = readl(aio->cygaud->audio + aio->regs.bf_sourcech_cfg);
value &= ~(mask << BF_SRC_CFGX_BIT_RES);
value |= (bitres << BF_SRC_CFGX_BIT_RES);
writel(value, aio->cygaud->audio + aio->regs.bf_sourcech_cfg);
} else {
switch (params_format(params)) { case SNDRV_PCM_FORMAT_S16_LE:
value = readl(aio->cygaud->audio +
aio->regs.bf_destch_cfg);
value |= BIT(BF_DST_CFGX_CAP_MODE);
writel(value, aio->cygaud->audio +
aio->regs.bf_destch_cfg); break;
case SNDRV_PCM_FORMAT_S32_LE:
value = readl(aio->cygaud->audio +
aio->regs.bf_destch_cfg);
value &= ~BIT(BF_DST_CFGX_CAP_MODE);
writel(value, aio->cygaud->audio +
aio->regs.bf_destch_cfg); break;
default: return -EINVAL;
}
}
aio->lrclk = rate;
if (!aio->is_slave)
ret = cygnus_ssp_set_clocks(aio);
return ret;
}
/* * This function sets the mclk frequency for pll clock
*/ staticint cygnus_ssp_set_sysclk(struct snd_soc_dai *dai, int clk_id, unsignedint freq, int dir)
{ int sel;
u32 value; struct cygnus_aio_port *aio = cygnus_dai_get_portinfo(dai); struct cygnus_audio *cygaud = snd_soc_dai_get_drvdata(dai);
dev_dbg(aio->cygaud->dev, "%s Enter port = %d\n", __func__, aio->portnum);
sel = pll_configure_mclk(cygaud, freq, aio); if (sel < 0) {
dev_err(aio->cygaud->dev, "%s Setting mclk failed.\n", __func__); return -EINVAL;
}
aio->mclk = freq;
dev_dbg(aio->cygaud->dev, "%s Setting MCLKSEL to %d\n", __func__, sel);
value = readl(aio->cygaud->audio + aio->regs.i2s_mclk_cfg);
value &= ~(0xf << I2S_OUT_PLLCLKSEL_SHIFT);
value |= (sel << I2S_OUT_PLLCLKSEL_SHIFT);
writel(value, aio->cygaud->audio + aio->regs.i2s_mclk_cfg);
val = readl(aio->cygaud->audio + aio->regs.i2s_mclk_cfg);
val &= CYGNUS_PLLCLKSEL_MASK; if (val >= ARRAY_SIZE(aio->cygaud->audio_clk)) {
dev_err(aio->cygaud->dev, "Clk index %u is out of bounds\n",
val); return;
}
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { if (aio->clk_trace.play_clk_en) {
clk_disable_unprepare(aio->cygaud->
audio_clk[val]);
aio->clk_trace.play_clk_en = false;
}
} else { if (aio->clk_trace.cap_clk_en) {
clk_disable_unprepare(aio->cygaud->
audio_clk[val]);
aio->clk_trace.cap_clk_en = false;
}
}
}
}
/* * Bit Update Notes * 31 Yes TDM Mode (1 = TDM, 0 = i2s) * 30 Yes Slave Mode (1 = Slave, 0 = Master) * 29:26 No Sclks per frame * 25:18 Yes FS Width * 17:14 No Valid Slots * 13 No Bits (1 = 16 bits, 0 = 32 bits) * 12:08 No Bits per samp * 07 Yes Justifcation (1 = LSB, 0 = MSB) * 06 Yes Alignment (1 = Delay 1 clk, 0 = no delay * 05 Yes SCLK polarity (1 = Rising, 0 = Falling) * 04 Yes LRCLK Polarity (1 = High for left, 0 = Low for left) * 03:02 Yes Reserved - write as zero * 01 No Data Enable * 00 No CLK Enable
*/ #define I2S_OUT_CFG_REG_UPDATE_MASK 0x3C03FF03
/* Input cfg is same as output, but the FS width is not a valid field */ #define I2S_IN_CFG_REG_UPDATE_MASK (I2S_OUT_CFG_REG_UPDATE_MASK | 0x03FC0000)
int cygnus_ssp_set_custom_fsync_width(struct snd_soc_dai *cpu_dai, int len)
{ struct cygnus_aio_port *aio = cygnus_dai_get_portinfo(cpu_dai);
case SND_SOC_DAIFMT_DSP_A: case SND_SOC_DAIFMT_DSP_B:
ssp_newcfg |= BIT(I2S_OUT_CFGX_TDM_MODE);
/* DSP_A = data after FS, DSP_B = data during FS */ if ((fmt & SND_SOC_DAIFMT_FORMAT_MASK) == SND_SOC_DAIFMT_DSP_A)
ssp_newcfg |= BIT(I2S_OUT_CFGX_DATA_ALIGNMENT);
/* * SSP out cfg. * Retain bits we do not want to update, then OR in new bits
*/
ssp_curcfg = readl(aio->cygaud->audio + aio->regs.i2s_cfg);
ssp_outcfg = (ssp_curcfg & I2S_OUT_CFG_REG_UPDATE_MASK) | ssp_newcfg;
writel(ssp_outcfg, aio->cygaud->audio + aio->regs.i2s_cfg);
/* * SSP in cfg. * Retain bits we do not want to update, then OR in new bits
*/
ssp_curcfg = readl(aio->cygaud->i2s_in + aio->regs.i2s_cap_cfg);
ssp_incfg = (ssp_curcfg & I2S_IN_CFG_REG_UPDATE_MASK) | ssp_newcfg;
writel(ssp_incfg, aio->cygaud->i2s_in + aio->regs.i2s_cap_cfg);
val = readl(aio->cygaud->audio + AUD_MISC_SEROUT_OE_REG_BASE);
/* * Configure the word clk and bit clk as output or tristate * Each port has 4 bits for controlling its pins. * Shift the mask based upon port number.
*/
mask = BIT(AUD_MISC_SEROUT_LRCK_OE)
| BIT(AUD_MISC_SEROUT_SCLK_OE)
| BIT(AUD_MISC_SEROUT_MCLK_OE);
mask = mask << (aio->portnum * 4); if (aio->is_slave) /* Set bit for tri-state */
val |= mask; else /* Clear bit for drive */
val &= ~mask;
dev_dbg(aio->cygaud->dev, "%s cmd %d at port = %d\n", __func__, cmd, aio->portnum);
switch (cmd) { case SNDRV_PCM_TRIGGER_START: case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: case SNDRV_PCM_TRIGGER_RESUME: if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
audio_ssp_out_enable(aio); else
audio_ssp_in_enable(aio);
cygaud->active_ports++;
break;
case SNDRV_PCM_TRIGGER_STOP: case SNDRV_PCM_TRIGGER_PAUSE_PUSH: case SNDRV_PCM_TRIGGER_SUSPEND: if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
audio_ssp_out_disable(aio); else
audio_ssp_in_disable(aio);
cygaud->active_ports--; break;
default: return -EINVAL;
}
return 0;
}
staticint cygnus_set_dai_tdm_slot(struct snd_soc_dai *cpu_dai, unsignedint tx_mask, unsignedint rx_mask, int slots, int slot_width)
{ struct cygnus_aio_port *aio = cygnus_dai_get_portinfo(cpu_dai);
u32 value; int bits_per_slot = 0; /* default to 32-bits per slot */ int frame_bits; unsignedint active_slots; bool found = false; int i;
if (tx_mask != rx_mask) {
dev_err(aio->cygaud->dev, "%s tx_mask must equal rx_mask\n", __func__); return -EINVAL;
}
active_slots = hweight32(tx_mask);
if (active_slots > 16) return -EINVAL;
/* Slot value must be even */ if (active_slots % 2) return -EINVAL;
/* We encode 16 slots as 0 in the reg */ if (active_slots == 16)
active_slots = 0;
/* Slot Width is either 16 or 32 */ switch (slot_width) { case 16:
bits_per_slot = 1; break; case 32:
bits_per_slot = 0; break; default:
bits_per_slot = 0;
dev_warn(aio->cygaud->dev, "%s Defaulting Slot Width to 32\n", __func__);
}
frame_bits = slots * slot_width;
for (i = 0; i < ARRAY_SIZE(ssp_valid_tdm_framesize); i++) { if (ssp_valid_tdm_framesize[i] == frame_bits) {
found = true; break;
}
}
if (!found) {
dev_err(aio->cygaud->dev, "%s In TDM mode, frame bits INVALID (%d)\n",
__func__, frame_bits); return -EINVAL;
}
/* Set capture side of ssp port */
value = readl(aio->cygaud->i2s_in + aio->regs.i2s_cap_cfg);
value &= ~(0xf << I2S_OUT_CFGX_VALID_SLOT);
value |= (active_slots << I2S_OUT_CFGX_VALID_SLOT);
value &= ~BIT(I2S_OUT_CFGX_BITS_PER_SLOT);
value |= (bits_per_slot << I2S_OUT_CFGX_BITS_PER_SLOT);
writel(value, aio->cygaud->i2s_in + aio->regs.i2s_cap_cfg);
/* Set playback side of ssp port */
value = readl(aio->cygaud->audio + aio->regs.i2s_cfg);
value &= ~(0xf << I2S_OUT_CFGX_VALID_SLOT);
value |= (active_slots << I2S_OUT_CFGX_VALID_SLOT);
value &= ~BIT(I2S_OUT_CFGX_BITS_PER_SLOT);
value |= (bits_per_slot << I2S_OUT_CFGX_BITS_PER_SLOT);
writel(value, aio->cygaud->audio + aio->regs.i2s_cfg);
val = readl(aio->cygaud->audio + aio->regs.i2s_mclk_cfg);
val &= CYGNUS_PLLCLKSEL_MASK; if (val >= ARRAY_SIZE(aio->cygaud->audio_clk)) {
dev_err(aio->cygaud->dev, "Clk index %u is out of bounds\n",
val); return -EINVAL;
}
if (aio->clk_trace.cap_clk_en)
clk_disable_unprepare(aio->cygaud->audio_clk[val]); if (aio->clk_trace.play_clk_en)
clk_disable_unprepare(aio->cygaud->audio_clk[val]);
aio->pll_clk_num = val;
}
return 0;
}
staticint cygnus_ssp_suspend(struct snd_soc_component *component)
{ struct snd_soc_dai *dai; int ret = 0;
for_each_component_dais(component, dai)
ret |= __cygnus_ssp_suspend(dai);
/* For the purposes of this code SPDIF can be I2S mode */
aio->mode = CYGNUS_SSPMODE_I2S; break; default:
dev_err(&pdev->dev, "Bad value for port_type %d\n", port_type); return -EINVAL;
}
for (i = 0; i < ARRAY_SIZE(cygaud->audio_clk); i++) {
snprintf(clk_name, PROP_LEN_MAX, "ch%d_audio", i);
cygaud->audio_clk[i] = devm_clk_get(&pdev->dev, clk_name); if (IS_ERR(cygaud->audio_clk[i])) return PTR_ERR(cygaud->audio_clk[i]);
}
return 0;
}
staticint cygnus_ssp_probe(struct platform_device *pdev)
{ struct device *dev = &pdev->dev; struct device_node *child_node; struct cygnus_audio *cygaud; int err; int node_count; int active_port_count;
cygaud = devm_kzalloc(dev, sizeof(struct cygnus_audio), GFP_KERNEL); if (!cygaud) return -ENOMEM;
dev_set_drvdata(dev, cygaud);
cygaud->audio = devm_platform_ioremap_resource_byname(pdev, "aud"); if (IS_ERR(cygaud->audio)) return PTR_ERR(cygaud->audio);
cygaud->i2s_in = devm_platform_ioremap_resource_byname(pdev, "i2s_in"); if (IS_ERR(cygaud->i2s_in)) return PTR_ERR(cygaud->i2s_in);
/* Tri-state all controlable pins until we know that we need them */
writel(CYGNUS_SSP_TRISTATE_MASK,
cygaud->audio + AUD_MISC_SEROUT_OE_REG_BASE);
node_count = of_get_child_count(pdev->dev.of_node); if ((node_count < 1) || (node_count > CYGNUS_MAX_PORTS)) {
dev_err(dev, "child nodes is %d. Must be between 1 and %d\n",
node_count, CYGNUS_MAX_PORTS); return -EINVAL;
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.