/* * Loop through the substreams attached to this SSC. If * a DMA-related interrupt occurred on that substream, call * the DMA interrupt handler function, if one has been * registered in the dma_params structure by the PCM driver.
*/ for (i = 0; i < ARRAY_SIZE(ssc_p->dma_params); i++) {
dma_params = ssc_p->dma_params[i];
/* * When the bit clock is input, limit the maximum rate according to the * Serial Clock Ratio Considerations section from the SSC documentation: * * The Transmitter and the Receiver can be programmed to operate * with the clock signals provided on either the TK or RK pins. * This allows the SSC to support many slave-mode data transfers. * In this case, the maximum clock speed allowed on the RK pin is: * - Peripheral clock divided by 2 if Receiver Frame Synchro is input * - Peripheral clock divided by 3 if Receiver Frame Synchro is output * In addition, the maximum clock speed allowed on the TK pin is: * - Peripheral clock divided by 6 if Transmit Frame Synchro is input * - Peripheral clock divided by 2 if Transmit Frame Synchro is output * * When the bit clock is output, limit the rate according to the * SSC divider restrictions.
*/ staticint atmel_ssc_hw_rule_rate(struct snd_pcm_hw_params *params, struct snd_pcm_hw_rule *rule)
{ struct atmel_ssc_info *ssc_p = rule->private; struct ssc_device *ssc = ssc_p->ssc; struct snd_interval *i = hw_param_interval(params, rule->var); struct snd_interval t; struct snd_ratnum r = {
.den_min = 1,
.den_max = 4095,
.den_step = 1,
}; unsignedint num = 0, den = 0; int frame_size; int mck_div = 2; int ret;
frame_size = snd_soc_params_to_frame_size(params); if (frame_size < 0) return frame_size;
switch (ssc_p->daifmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) { case SND_SOC_DAIFMT_BC_FP: if ((ssc_p->dir_mask & SSC_DIR_MASK_CAPTURE)
&& ssc->clk_from_rk_pin) /* Receiver Frame Synchro (i.e. capture) * is output (format is _CFS) and the RK pin * is used for input (format is _CBM_).
*/
mck_div = 3; break;
case SND_SOC_DAIFMT_BC_FC: if ((ssc_p->dir_mask & SSC_DIR_MASK_PLAYBACK)
&& !ssc->clk_from_rk_pin) /* Transmit Frame Synchro (i.e. playback) * is input (format is _CFM) and the TK pin * is used for input (format _CBM_ but not * using the RK pin).
*/
mck_div = 6; break;
}
/* * Record the DAI format for use in hw_params().
*/ staticint atmel_ssc_set_dai_fmt(struct snd_soc_dai *cpu_dai, unsignedint fmt)
{ struct platform_device *pdev = to_platform_device(cpu_dai->dev); struct atmel_ssc_info *ssc_p = &ssc_info[pdev->id];
ssc_p->daifmt = fmt; return 0;
}
/* * Record SSC clock dividers for use in hw_params().
*/ staticint atmel_ssc_set_dai_clkdiv(struct snd_soc_dai *cpu_dai, int div_id, int div)
{ struct platform_device *pdev = to_platform_device(cpu_dai->dev); struct atmel_ssc_info *ssc_p = &ssc_info[pdev->id];
switch (div_id) { case ATMEL_SSC_CMR_DIV: /* * The same master clock divider is used for both * transmit and receive, so if a value has already * been set, it must match this value.
*/ if (ssc_p->dir_mask !=
(SSC_DIR_MASK_PLAYBACK | SSC_DIR_MASK_CAPTURE))
ssc_p->cmr_div = div; elseif (ssc_p->cmr_div == 0)
ssc_p->cmr_div = div; else if (div != ssc_p->cmr_div) return -EBUSY;
ssc_p->forced_divider |= BIT(ATMEL_SSC_CMR_DIV); break;
case ATMEL_SSC_TCMR_PERIOD:
ssc_p->tcmr_period = div;
ssc_p->forced_divider |= BIT(ATMEL_SSC_TCMR_PERIOD); break;
case ATMEL_SSC_RCMR_PERIOD:
ssc_p->rcmr_period = div;
ssc_p->forced_divider |= BIT(ATMEL_SSC_RCMR_PERIOD); break;
default: return -EINVAL;
}
return 0;
}
/* Is the cpu-dai master of the frame clock? */ staticint atmel_ssc_cfs(struct atmel_ssc_info *ssc_p)
{ switch (ssc_p->daifmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) { case SND_SOC_DAIFMT_BC_FP: case SND_SOC_DAIFMT_BP_FP: return 1;
} return 0;
}
/* Is the cpu-dai master of the bit clock? */ staticint atmel_ssc_cbs(struct atmel_ssc_info *ssc_p)
{ switch (ssc_p->daifmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) { case SND_SOC_DAIFMT_BP_FC: case SND_SOC_DAIFMT_BP_FP: return 1;
} return 0;
}
/* * Currently, there is only one set of dma params for * each direction. If more are added, this code will * have to be changed to select the proper set.
*/ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
dir = 0; else
dir = 1;
/* * If the cpu dai should provide BCLK, but noone has provided the * divider needed for that to work, fall back to something sensible.
*/
cmr_div = ssc_p->cmr_div; if (!(ssc_p->forced_divider & BIT(ATMEL_SSC_CMR_DIV)) &&
atmel_ssc_cbs(ssc_p)) { int bclk_rate = snd_soc_params_to_bclk(params);
if (bclk_rate < 0) {
dev_err(dai->dev, "unable to calculate cmr_div: %d\n",
bclk_rate); return bclk_rate;
}
/* * If the cpu dai should provide LRCLK, but noone has provided the * dividers needed for that to work, fall back to something sensible.
*/
tcmr_period = ssc_p->tcmr_period;
rcmr_period = ssc_p->rcmr_period; if (atmel_ssc_cfs(ssc_p)) { int frame_size = snd_soc_params_to_frame_size(params);
if (frame_size < 0) {
dev_err(dai->dev, "unable to calculate tx/rx cmr_period: %d\n",
frame_size); return frame_size;
}
case SND_SOC_DAIFMT_DSP_A: /* * DSP/PCM Mode A format * * Data is transferred on first BCLK after LRC pulse rising * edge.If stereo, the right channel data is contiguous with * the left channel data.
*/
fs_osync = SSC_FSOS_POSITIVE;
fs_edge = SSC_START_RISING_RF;
fslen = fslen_ext = 0;
if (atmel_ssc_cbs(ssc_p)) { /* * SSC provides BCLK * * The SSC transmit and receive clocks are generated from the * MCK divider, and the BCLK signal is output * on the SSC TK line.
*/
rcmr |= SSC_BF(RCMR_CKS, SSC_CKS_DIV)
| SSC_BF(RCMR_CKO, SSC_CKO_NONE);
if (!snd_soc_component_active(component)) return 0;
ssc_p = &ssc_info[pdev->id];
/* Save the status register before disabling transmit and receive */
ssc_p->ssc_state.ssc_sr = ssc_readl(ssc_p->ssc->regs, SR);
ssc_writel(ssc_p->ssc->regs, CR, SSC_BIT(CR_TXDIS) | SSC_BIT(CR_RXDIS));
/* Save the current interrupt mask, then disable unmasked interrupts */
ssc_p->ssc_state.ssc_imr = ssc_readl(ssc_p->ssc->regs, IMR);
ssc_writel(ssc_p->ssc->regs, IDR, ssc_p->ssc_state.ssc_imr);
/* S24_LE is not supported if more than 2 channels (of TDM slots) are used. */ #define ATMEL_SSC_FORMATS (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE |\
SNDRV_PCM_FMTBIT_S32_LE)
ret = devm_snd_soc_register_component(dev, &atmel_ssc_component,
&atmel_ssc_dai, 1); if (ret) {
dev_err(dev, "Could not register DAI: %d\n", ret); return ret;
}
if (ssc->pdata->use_dma)
ret = atmel_pcm_dma_platform_register(dev); else
ret = atmel_pcm_pdc_platform_register(dev);
if (ret) {
dev_err(dev, "Could not register PCM: %d\n", ret); return ret;
}
return 0;
}
/** * atmel_ssc_set_audio - Allocate the specified SSC for audio use. * @ssc_id: SSD ID in [0, NUM_SSC_DEVICES[
*/ int atmel_ssc_set_audio(int ssc_id)
{ struct ssc_device *ssc;
/* If we can grab the SSC briefly to parent the DAI device off it */
ssc = ssc_request(ssc_id); if (IS_ERR(ssc)) {
pr_err("Unable to parent ASoC SSC DAI on SSC: %ld\n",
PTR_ERR(ssc)); return PTR_ERR(ssc);
} else {
ssc_info[ssc_id].ssc = ssc;
}
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.