/** * struct stm32_sai_sub_data - private data of SAI sub block (block A or B) * @pdev: device data pointer * @regmap: SAI register map pointer * @regmap_config: SAI sub block register map configuration pointer * @dma_params: dma configuration data for rx or tx channel * @cpu_dai_drv: DAI driver data pointer * @cpu_dai: DAI runtime data pointer * @substream: PCM substream data pointer * @pdata: SAI block parent data pointer * @np_sync_provider: synchronization provider node * @sai_ck: kernel clock feeding the SAI clock generator * @sai_mclk: master clock from SAI mclk provider * @phys_addr: SAI registers physical base address * @mclk_rate: SAI block master clock frequency (Hz). set at init * @id: SAI sub block id corresponding to sub-block A or B * @dir: SAI block direction (playback or capture). set at init * @master: SAI block mode flag. (true=master, false=slave) set at init * @spdif: SAI S/PDIF iec60958 mode flag. set at init * @sai_ck_used: flag set while exclusivity on SAI kernel clock is active * @fmt: SAI block format. relevant only for custom protocols. set at init * @sync: SAI block synchronization mode. (none, internal or external) * @synco: SAI block ext sync source (provider setting). (none, sub-block A/B) * @synci: SAI block ext sync source (client setting). (SAI sync provider index) * @fs_length: frame synchronization length. depends on protocol settings * @slots: rx or tx slot number * @slot_width: rx or tx slot width in bits * @slot_mask: rx or tx active slots mask. set at init or at runtime * @data_size: PCM data width. corresponds to PCM substream width. * @spdif_frm_cnt: S/PDIF playback frame counter * @iec958: iec958 data * @ctrl_lock: control lock * @irq_lock: prevent race condition with IRQ * @set_sai_ck_rate: set SAI kernel clock rate * @put_sai_ck_rate: put SAI kernel clock rate
*/ struct stm32_sai_sub_data { struct platform_device *pdev; struct regmap *regmap; conststruct regmap_config *regmap_config; struct snd_dmaengine_dai_dma_data dma_params; struct snd_soc_dai_driver cpu_dai_drv; struct snd_soc_dai *cpu_dai; struct snd_pcm_substream *substream; struct stm32_sai_data *pdata; struct device_node *np_sync_provider; struct clk *sai_ck; struct clk *sai_mclk;
dma_addr_t phys_addr; unsignedint mclk_rate; unsignedint id; int dir; bool master; bool spdif; bool sai_ck_used; int fmt; int sync; int synco; int synci; int fs_length; int slots; int slot_width; int slot_mask; int data_size; unsignedint spdif_frm_cnt; struct snd_aes_iec958 iec958; struct mutex ctrl_lock; /* protect resources accessed by controls */
spinlock_t irq_lock; /* used to prevent race condition with IRQ */ int (*set_sai_ck_rate)(struct stm32_sai_sub_data *sai, unsignedint rate); void (*put_sai_ck_rate)(struct stm32_sai_sub_data *sai);
};
staticbool stm32_sai_sub_readable_reg(struct device *dev, unsignedint reg)
{ switch (reg) { case STM_SAI_CR1_REGX: case STM_SAI_CR2_REGX: case STM_SAI_FRCR_REGX: case STM_SAI_SLOTR_REGX: case STM_SAI_IMR_REGX: case STM_SAI_SR_REGX: case STM_SAI_CLRFR_REGX: case STM_SAI_DR_REGX: case STM_SAI_PDMCR_REGX: case STM_SAI_PDMLY_REGX: returntrue; default: returnfalse;
}
}
staticbool stm32_sai_sub_volatile_reg(struct device *dev, unsignedint reg)
{ switch (reg) { case STM_SAI_DR_REGX: case STM_SAI_SR_REGX: returntrue; default: returnfalse;
}
}
staticbool stm32_sai_sub_writeable_reg(struct device *dev, unsignedint reg)
{ switch (reg) { case STM_SAI_CR1_REGX: case STM_SAI_CR2_REGX: case STM_SAI_FRCR_REGX: case STM_SAI_SLOTR_REGX: case STM_SAI_IMR_REGX: case STM_SAI_CLRFR_REGX: case STM_SAI_DR_REGX: case STM_SAI_PDMCR_REGX: case STM_SAI_PDMLY_REGX: returntrue; default: returnfalse;
}
}
staticint stm32_sai_get_clk_div(struct stm32_sai_sub_data *sai, unsignedlong input_rate, unsignedlong output_rate)
{ int version = sai->pdata->conf.version; int div;
div = DIV_ROUND_CLOSEST(input_rate, output_rate); if (div > SAI_XCR1_MCKDIV_MAX(version) || div <= 0) {
dev_err(&sai->pdev->dev, "Divider %d out of range\n", div); return -EINVAL;
}
dev_dbg(&sai->pdev->dev, "SAI divider %d\n", div);
if (input_rate % div)
dev_dbg(&sai->pdev->dev, "Rate not accurate. requested (%ld), actual (%ld)\n",
output_rate, input_rate / div);
return div;
}
staticint stm32_sai_set_clk_div(struct stm32_sai_sub_data *sai, unsignedint div)
{ int version = sai->pdata->conf.version; int ret, cr1, mask;
if (div > SAI_XCR1_MCKDIV_MAX(version)) {
dev_err(&sai->pdev->dev, "Divider %d out of range\n", div); return -EINVAL;
}
mask = SAI_XCR1_MCKDIV_MASK(SAI_XCR1_MCKDIV_WIDTH(version));
cr1 = SAI_XCR1_MCKDIV_SET(div);
ret = stm32_sai_sub_reg_up(sai, STM_SAI_CR1_REGX, mask, cr1); if (ret < 0)
dev_err(&sai->pdev->dev, "Failed to update CR1 register\n");
/* * Set minimum and maximum expected kernel clock frequency * - mclk on or spdif: * f_sai_ck = MCKDIV * mclk-fs * fs * Here typical 256 ratio is assumed for mclk-fs * - mclk off: * f_sai_ck = MCKDIV * FRL * fs * Where FRL=[8..256], MCKDIV=[1..n] (n depends on SAI version) * Set constraint MCKDIV * FRL <= 256, to ensure MCKDIV is in available range * f_sai_ck = sai_ck_max_rate * pow_of_two(FRL) / 256
*/
sai_ck_min_rate = rate * 256; if (!(rate % SAI_RATE_11K))
sai_ck_max_rate = SAI_MAX_SAMPLE_RATE_11K * 256; else
sai_ck_max_rate = SAI_MAX_SAMPLE_RATE_8K * 256;
/* * Request exclusivity, as the clock is shared by SAI sub-blocks and by * some SAI instances. This allows to ensure that the rate cannot be * changed while one or more SAIs are using the clock.
*/
clk_rate_exclusive_get(sai->sai_ck);
sai->sai_ck_used = true;
/* * Check current kernel clock rate. If it gives the expected accuracy * return immediately.
*/
sai_curr_rate = clk_get_rate(sai->sai_ck);
dev_dbg(&pdev->dev, "kernel clock rate: min [%u], max [%u], current [%u]",
sai_ck_min_rate, sai_ck_max_rate, sai_curr_rate); if (stm32_sai_rate_accurate(sai_ck_max_rate, sai_curr_rate) &&
sai_curr_rate >= sai_ck_min_rate) return 0;
/* * Otherwise try to set the maximum rate and check the new actual rate. * If the new rate does not give the expected accuracy, try to set * lower rates for the kernel clock.
*/
sai_ck_rate = sai_ck_max_rate;
div = 1; do { /* Check new rate accuracy. Return if ok */
sai_new_rate = clk_round_rate(sai->sai_ck, sai_ck_rate); if (stm32_sai_rate_accurate(sai_ck_rate, sai_new_rate)) {
ret = clk_set_rate(sai->sai_ck, sai_ck_rate); if (ret) {
dev_err(&pdev->dev, "Error %d setting sai_ck rate. %s",
ret, ret == -EBUSY ? "Active stream rates may be in conflict\n" : "\n"); goto err;
}
return 0;
}
/* Try a lower frequency */
div++;
sai_ck_rate = sai_ck_max_rate / div;
} while (sai_ck_rate >= sai_ck_min_rate);
/* No accurate rate found */
dev_err(&pdev->dev, "Failed to find an accurate rate");
if (flags & SAI_XIMR_OVRUDRIE) {
dev_err(&pdev->dev, "IRQ %s\n",
STM_SAI_IS_PLAYBACK(sai) ? "underrun" : "overrun");
status = SNDRV_PCM_STATE_XRUN;
}
if (flags & SAI_XIMR_MUTEDETIE)
dev_dbg(&pdev->dev, "IRQ mute detected\n");
if (flags & SAI_XIMR_WCKCFGIE) {
dev_err(&pdev->dev, "IRQ wrong clock configuration\n");
status = SNDRV_PCM_STATE_DISCONNECTED;
}
if (flags & SAI_XIMR_CNRDYIE)
dev_err(&pdev->dev, "IRQ Codec not ready\n");
if (flags & SAI_XIMR_AFSDETIE) {
dev_err(&pdev->dev, "IRQ Anticipated frame synchro\n");
status = SNDRV_PCM_STATE_XRUN;
}
if (flags & SAI_XIMR_LFSDETIE) {
dev_err(&pdev->dev, "IRQ Late frame synchro\n");
status = SNDRV_PCM_STATE_XRUN;
}
spin_lock(&sai->irq_lock); if (status != SNDRV_PCM_STATE_RUNNING && sai->substream)
snd_pcm_stop_xrun(sai->substream);
spin_unlock(&sai->irq_lock);
return IRQ_HANDLED;
}
staticint stm32_sai_set_sysclk(struct snd_soc_dai *cpu_dai, int clk_id, unsignedint freq, int dir)
{ struct stm32_sai_sub_data *sai = snd_soc_dai_get_drvdata(cpu_dai); int ret;
/* * The mclk rate is determined at runtime from the audio stream rate. * Skip calls to the set_sysclk callback that are not relevant during the * initialization phase.
*/ if (!snd_soc_card_is_instantiated(cpu_dai->component->card)) return 0;
if (dir == SND_SOC_CLOCK_OUT && sai->sai_mclk) {
ret = stm32_sai_sub_reg_up(sai, STM_SAI_CR1_REGX,
SAI_XCR1_NODIV,
freq ? 0 : SAI_XCR1_NODIV); if (ret < 0) return ret;
/* Assume shutdown if requested frequency is 0Hz */ if (!freq) { /* Release mclk rate only if rate was actually set */ if (sai->mclk_rate) {
clk_rate_exclusive_put(sai->sai_mclk);
sai->mclk_rate = 0;
}
if (sai->put_sai_ck_rate)
sai->put_sai_ck_rate(sai);
return 0;
}
/* If master clock is used, configure SAI kernel clock now */
ret = sai->set_sai_ck_rate(sai, freq); if (ret) return ret;
ret = clk_set_rate_exclusive(sai->sai_mclk, freq); if (ret) {
dev_err(cpu_dai->dev,
ret == -EBUSY ? "Active streams have incompatible rates" : "Could not set mclk rate\n"); return ret;
}
dev_dbg(cpu_dai->dev, "SAI MCLK frequency is %uHz\n", freq);
sai->mclk_rate = freq;
}
return 0;
}
staticint stm32_sai_set_dai_tdm_slot(struct snd_soc_dai *cpu_dai, u32 tx_mask,
u32 rx_mask, int slots, int slot_width)
{ struct stm32_sai_sub_data *sai = snd_soc_dai_get_drvdata(cpu_dai); int slotr, slotr_mask, slot_size;
if (STM_SAI_PROTOCOL_IS_SPDIF(sai)) {
dev_warn(cpu_dai->dev, "Slot setting relevant only for TDM\n"); return 0;
}
/* tx/rx mask set in machine init, if slot number defined in DT */ if (STM_SAI_IS_PLAYBACK(sai)) {
sai->slot_mask = tx_mask;
slotr |= SAI_XSLOTR_SLOTEN_SET(tx_mask);
}
if (STM_SAI_IS_CAPTURE(sai)) {
sai->slot_mask = rx_mask;
slotr |= SAI_XSLOTR_SLOTEN_SET(rx_mask);
}
/* Set slave mode if sub-block is synchronized with another SAI */ if (sai->sync) {
dev_dbg(cpu_dai->dev, "Synchronized SAI configured as slave\n");
cr1 |= SAI_XCR1_SLAVE;
sai->master = false;
}
cr1_mask |= SAI_XCR1_SLAVE;
conf_update:
ret = stm32_sai_sub_reg_up(sai, STM_SAI_CR1_REGX, cr1_mask, cr1); if (ret < 0) {
dev_err(cpu_dai->dev, "Failed to update CR1 register\n"); return ret;
}
/* * DMA bursts increment is set to 4 words. * SAI fifo threshold is set to half fifo, to keep enough space * for DMA incoming bursts.
*/
stm32_sai_sub_reg_wr(sai, STM_SAI_CR2_REGX,
SAI_XCR2_FFLUSH | SAI_XCR2_FTH_MASK,
SAI_XCR2_FFLUSH |
SAI_XCR2_FTH_SET(STM_SAI_FIFO_TH_HALF));
/* DS bits in CR1 not set for SPDIF (size forced to 24 bits).*/ if (STM_SAI_PROTOCOL_IS_SPDIF(sai)) {
sai->spdif_frm_cnt = 0; return 0;
}
/* Mode, data format and channel config */
cr1_mask = SAI_XCR1_DS_MASK; switch (params_format(params)) { case SNDRV_PCM_FORMAT_S8:
cr1 = SAI_XCR1_DS_SET(SAI_DATASIZE_8); break; case SNDRV_PCM_FORMAT_S16_LE:
cr1 = SAI_XCR1_DS_SET(SAI_DATASIZE_16); break; case SNDRV_PCM_FORMAT_S32_LE:
cr1 = SAI_XCR1_DS_SET(SAI_DATASIZE_32); break; default:
dev_err(cpu_dai->dev, "Data format not supported\n"); return -EINVAL;
}
/* * If SLOTSZ is set to auto in SLOTR, align slot width on data size * By default slot width = data size, if not forced from DT
*/
slot_sz = slotr & SAI_XSLOTR_SLOTSZ_MASK; if (slot_sz == SAI_XSLOTR_SLOTSZ_SET(SAI_SLOT_SIZE_AUTO))
sai->slot_width = sai->data_size;
if (sai->slot_width < sai->data_size) {
dev_err(cpu_dai->dev, "Data size %d larger than slot width\n",
sai->data_size); return -EINVAL;
}
/* Slot number is set to 2, if not specified in DT */ if (!sai->slots)
sai->slots = 2;
/* The number of slots in the audio frame is equal to NBSLOT[3:0] + 1*/
stm32_sai_sub_reg_up(sai, STM_SAI_SLOTR_REGX,
SAI_XSLOTR_NBSLOT_MASK,
SAI_XSLOTR_NBSLOT_SET((sai->slots - 1)));
/* Set default slots mask if not already set from DT */ if (!(slotr & SAI_XSLOTR_SLOTEN_MASK)) {
sai->slot_mask = (1 << sai->slots) - 1;
stm32_sai_sub_reg_up(sai,
STM_SAI_SLOTR_REGX, SAI_XSLOTR_SLOTEN_MASK,
SAI_XSLOTR_SLOTEN_SET(sai->slot_mask));
}
if (STM_SAI_PROTOCOL_IS_SPDIF(sai)) { /* Rate not already set in runtime structure */
substream->runtime->rate = params_rate(params);
stm32_sai_set_iec958_status(sai, substream->runtime);
} else {
ret = stm32_sai_set_slots(cpu_dai); if (ret < 0) return ret;
stm32_sai_set_frame(cpu_dai);
}
ret = stm32_sai_set_config(cpu_dai, substream, params); if (ret) return ret;
if (sai->master)
ret = stm32_sai_configure_clock(cpu_dai, params);
return ret;
}
staticint stm32_sai_trigger(struct snd_pcm_substream *substream, int cmd, struct snd_soc_dai *cpu_dai)
{ struct stm32_sai_sub_data *sai = snd_soc_dai_get_drvdata(cpu_dai); int ret;
switch (cmd) { case SNDRV_PCM_TRIGGER_START: case SNDRV_PCM_TRIGGER_RESUME: case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
dev_dbg(cpu_dai->dev, "Enable DMA and SAI\n");
/* Enable SAI */
ret = stm32_sai_sub_reg_up(sai, STM_SAI_CR1_REGX,
SAI_XCR1_SAIEN, SAI_XCR1_SAIEN); if (ret < 0)
dev_err(cpu_dai->dev, "Failed to update CR1 register\n"); break; case SNDRV_PCM_TRIGGER_SUSPEND: case SNDRV_PCM_TRIGGER_PAUSE_PUSH: case SNDRV_PCM_TRIGGER_STOP:
dev_dbg(cpu_dai->dev, "Disable DMA and SAI\n");
/* * Release kernel clock if following conditions are fulfilled * - Master clock is not used. Kernel clock won't be released trough sysclk * - Put handler is defined. Involve that clock is managed exclusively
*/ if (!sai->sai_mclk && sai->put_sai_ck_rate)
sai->put_sai_ck_rate(sai);
sai->dma_params.addr = (dma_addr_t)(sai->phys_addr + STM_SAI_DR_REGX); /* * DMA supports 4, 8 or 16 burst sizes. Burst size 4 is the best choice, * as it allows bytes, half-word and words transfers. (See DMA fifos * constraints).
*/
sai->dma_params.maxburst = 4; if (sai->pdata->conf.fifo_size < 8 || sai->pdata->conf.no_dma_burst)
sai->dma_params.maxburst = 1; /* Buswidth will be set by framework at runtime */
sai->dma_params.addr_width = DMA_SLAVE_BUSWIDTH_UNDEFINED;
if (STM_SAI_IS_PLAYBACK(sai))
snd_soc_dai_init_dma_data(cpu_dai, &sai->dma_params, NULL); else
snd_soc_dai_init_dma_data(cpu_dai, NULL, &sai->dma_params);
/* Next settings are not relevant for spdif mode */ if (STM_SAI_PROTOCOL_IS_SPDIF(sai)) return 0;
cr1_mask = SAI_XCR1_RX_TX; if (STM_SAI_IS_CAPTURE(sai))
cr1 |= SAI_XCR1_RX_TX;
/* Configure synchronization */ if (sai->sync == SAI_SYNC_EXTERNAL) { /* Configure synchro client and provider */
ret = sai->pdata->set_sync(sai->pdata, sai->np_sync_provider,
sai->synco, sai->synci); if (ret) return ret;
}
base = devm_platform_get_and_ioremap_resource(pdev, 0, &res); if (IS_ERR(base)) return PTR_ERR(base);
sai->phys_addr = res->start;
sai->regmap_config = &stm32_sai_sub_regmap_config_f4; /* Note: PDM registers not available for sub-block B */ if (STM_SAI_HAS_PDM(sai) && STM_SAI_IS_SUB_A(sai))
sai->regmap_config = &stm32_sai_sub_regmap_config_h7;
/* * Do not manage peripheral clock through regmap framework as this * can lead to circular locking issue with sai master clock provider. * Manage peripheral clock directly in driver instead.
*/
sai->regmap = devm_regmap_init_mmio(&pdev->dev, base,
sai->regmap_config); if (IS_ERR(sai->regmap)) return dev_err_probe(&pdev->dev, PTR_ERR(sai->regmap), "Regmap init error\n");
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.