/* Get the active PCM substream; * Call had_substream_put() for unreferecing. * Don't call this inside had_spinlock, as it takes by itself
*/ staticstruct snd_pcm_substream *
had_substream_get(struct snd_intelhad *intelhaddata)
{ struct snd_pcm_substream *substream; unsignedlong flags;
/* Unref the active PCM substream; * Don't call this inside had_spinlock, as it takes by itself
*/ staticvoid had_substream_put(struct snd_intelhad *intelhaddata)
{ unsignedlong flags;
/* * enable / disable audio configuration * * The normal read/modify should not directly be used on VLV2 for * updating AUD_CONFIG register. * This is because: * Bit6 of AUD_CONFIG register is writeonly due to a silicon bug on VLV2 * HDMI IP. As a result a read-modify of AUD_CONFIG register will always * clear bit6. AUD_CONFIG[6:4] represents the "channels" field of the * register. This field should be 1xy binary for configuration with 6 or * more channels. Read-modify of AUD_CONFIG (Eg. for enabling audio) * causes the "channels" field to be updated as 0xy binary resulting in * bad audio. The fix is to always write the AUD_CONFIG[6:4] with * appropriate value when doing read-modify of AUD_CONFIG register.
*/ staticvoid had_enable_audio(struct snd_intelhad *intelhaddata, bool enable)
{ /* update the cached value */
intelhaddata->aud_config.regx.aud_en = enable;
had_write_register(intelhaddata, AUD_CONFIG,
intelhaddata->aud_config.regval);
}
/* forcibly ACKs to both BUFFER_DONE and BUFFER_UNDERRUN interrupts */ staticvoid had_ack_irqs(struct snd_intelhad *ctx)
{
u32 status_reg;
/* * initialize audio channel status registers * This function is called in the prepare callback
*/ staticint had_prog_status_reg(struct snd_pcm_substream *substream, struct snd_intelhad *intelhaddata)
{ union aud_ch_status_0 ch_stat0 = {.regval = 0}; union aud_ch_status_1 ch_stat1 = {.regval = 0};
/* * function to initialize audio * registers and buffer configuration registers * This function is called in the prepare callback
*/ staticint had_init_audio_ctrl(struct snd_pcm_substream *substream, struct snd_intelhad *intelhaddata)
{ union aud_cfg cfg_val = {.regval = 0}; union aud_buf_config buf_cfg = {.regval = 0};
u8 channels;
/* * Compute derived values in channel_allocations[].
*/ staticvoid init_channel_allocations(void)
{ int i, j; struct cea_channel_speaker_allocation *p;
for (i = 0; i < ARRAY_SIZE(channel_allocations); i++) {
p = channel_allocations + i;
p->channels = 0;
p->spk_mask = 0; for (j = 0; j < ARRAY_SIZE(p->speakers); j++) if (p->speakers[j]) {
p->channels++;
p->spk_mask |= p->speakers[j];
}
}
}
/* * The transformation takes two steps: * * eld->spk_alloc => (eld_speaker_allocation_bits[]) => spk_mask * spk_mask => (channel_allocations[]) => ai->CA * * TODO: it could select the wrong CA from multiple candidates.
*/ staticint had_channel_allocation(struct snd_intelhad *intelhaddata, int channels)
{ int i; int ca = 0; int spk_mask = 0;
/* * CA defaults to 0 for basic stereo audio
*/ if (channels <= 2) return 0;
/* * expand ELD's speaker allocation mask * * ELD tells the speaker mask in a compact(paired) form, * expand ELD's notions to match the ones used by Audio InfoFrame.
*/
for (i = 0; i < ARRAY_SIZE(eld_speaker_allocation_bits); i++) { if (intelhaddata->eld[DRM_ELD_SPEAKER] & (1 << i))
spk_mask |= eld_speaker_allocation_bits[i];
}
/* search for the first working match in the CA table */ for (i = 0; i < ARRAY_SIZE(channel_allocations); i++) { if (channels == channel_allocations[i].channels &&
(spk_mask & channel_allocations[i].spk_mask) ==
channel_allocations[i].spk_mask) {
ca = channel_allocations[i].ca_index; break;
}
}
dev_dbg(intelhaddata->dev, "select CA 0x%x for %d\n", ca, channels);
return ca;
}
/* from speaker bit mask to ALSA API channel position */ staticint spk_to_chmap(int spk)
{ conststruct channel_map_table *t = map_tables;
for (; t->map; t++) { if (t->spk_mask == spk) return t->map;
} return 0;
}
staticvoid had_build_channel_allocation_map(struct snd_intelhad *intelhaddata)
{ int i, c; int spk_mask = 0; struct snd_pcm_chmap_elem *chmap;
u8 eld_high, eld_high_mask = 0xF0;
u8 high_msb;
/* * Sink may support more than 8 channels, if eld_high has more than * one bit set. SOC supports max 8 channels. * Refer eld_speaker_allocation_bits, for sink speaker allocation
*/
/* if 0x2F < eld < 0x4F fall back to 0x2f, else fall back to 0x4F */
eld_high = intelhaddata->eld[DRM_ELD_SPEAKER] & eld_high_mask; if ((eld_high & (eld_high-1)) && (eld_high > 0x1F)) { /* eld_high & (eld_high-1): if more than 1 bit set */ /* 0x1F: 7 channels */ for (i = 1; i < 4; i++) {
high_msb = eld_high & (0x80 >> i); if (high_msb) {
intelhaddata->eld[DRM_ELD_SPEAKER] &=
high_msb | 0xF; break;
}
}
}
for (i = 0; i < ARRAY_SIZE(eld_speaker_allocation_bits); i++) { if (intelhaddata->eld[DRM_ELD_SPEAKER] & (1 << i))
spk_mask |= eld_speaker_allocation_bits[i];
}
for (i = 0; i < ARRAY_SIZE(channel_allocations); i++) { if (spk_mask == channel_allocations[i].spk_mask) { for (c = 0; c < channel_allocations[i].channels; c++) {
chmap->map[c] = spk_to_chmap(
channel_allocations[i].speakers[
(MAX_SPEAKERS - 1) - c]);
}
chmap->channels = channel_allocations[i].channels;
intelhaddata->chmap->chmap = chmap; break;
}
} if (i >= ARRAY_SIZE(channel_allocations))
kfree(chmap);
}
chmap = intelhaddata->chmap->chmap; for (i = 0; i < chmap->channels; i++)
ucontrol->value.integer.value[i] = chmap->map[i];
mutex_unlock(&intelhaddata->mutex);
return 0;
}
staticint had_register_chmap_ctls(struct snd_intelhad *intelhaddata, struct snd_pcm *pcm)
{ int err;
/* * Initialize Data Island Packets registers * This function is called in the prepare callback
*/ staticvoid had_prog_dip(struct snd_pcm_substream *substream, struct snd_intelhad *intelhaddata)
{ int i; union aud_ctrl_st ctrl_state = {.regval = 0}; union aud_info_frame2 frame2 = {.regval = 0}; union aud_info_frame3 frame3 = {.regval = 0};
u8 checksum = 0;
u32 info_frame; int channels; int ca;
/* Calculte the byte wide checksum for all valid DIP words */ for (i = 0; i < BYTES_PER_WORD; i++)
checksum += (info_frame >> (i * 8)) & 0xff; for (i = 0; i < BYTES_PER_WORD; i++)
checksum += (frame2.regval >> (i * 8)) & 0xff; for (i = 0; i < BYTES_PER_WORD; i++)
checksum += (frame3.regval >> (i * 8)) & 0xff;
/* program remaining DIP words with zero */ for (i = 0; i < HAD_MAX_DIP_WORDS-VALID_DIP_WORDS; i++)
had_write_register(intelhaddata, AUD_HDMIW_INFOFR, 0x0);
/* * Program HDMI audio CTS value * * @aud_samp_freq: sampling frequency of audio data * @tmds: sampling frequency of the display data * @link_rate: DP link rate * @n_param: N value, depends on aud_samp_freq * @intelhaddata: substream private data * * Program CTS register based on the audio and display sampling frequency
*/ staticvoid had_prog_cts(u32 aud_samp_freq, u32 tmds, u32 link_rate,
u32 n_param, struct snd_intelhad *intelhaddata)
{
u32 cts_val;
u64 dividend, divisor;
if (intelhaddata->dp_output) { /* Substitute cts_val with Maud according to DP 1.2 spec*/
cts_val = had_calculate_maud_value(aud_samp_freq, link_rate);
} else { /* Calculate CTS according to HDMI 1.3a spec*/
dividend = (u64)tmds * n_param*1000;
divisor = 128 * aud_samp_freq;
cts_val = div64_u64(dividend, divisor);
}
dev_dbg(intelhaddata->dev, "TMDS value=%d, N value=%d, CTS Value=%d\n",
tmds, n_param, cts_val);
had_write_register(intelhaddata, AUD_HDMI_CTS, (BIT(24) | cts_val));
}
staticint had_calculate_n_value(u32 aud_samp_freq)
{ int n_val;
/* Select N according to HDMI 1.3a spec*/ switch (aud_samp_freq) { case AUD_SAMPLE_RATE_32:
n_val = 4096; break;
/* * Program HDMI audio N value * * @aud_samp_freq: sampling frequency of audio data * @n_param: N value, depends on aud_samp_freq * @intelhaddata: substream private data * * This function is called in the prepare callback. * It programs based on the audio and display sampling frequency
*/ staticint had_prog_n(u32 aud_samp_freq, u32 *n_param, struct snd_intelhad *intelhaddata)
{ int n_val;
if (intelhaddata->dp_output) { /* * According to DP specs, Maud and Naud values hold * a relationship, which is stated as: * Maud/Naud = 512 * fs / f_LS_Clk * where, fs is the sampling frequency of the audio stream * and Naud is 32768 for Async clock.
*/
/* * PCM ring buffer handling * * The hardware provides a ring buffer with the fixed 4 buffer descriptors * (BDs). The driver maps these 4 BDs onto the PCM ring buffer. The mapping * moves at each period elapsed. The below illustrates how it works: * * At time=0 * PCM | 0 | 1 | 2 | 3 | 4 | 5 | .... |n-1| * BD | 0 | 1 | 2 | 3 | * * At time=1 (period elapsed) * PCM | 0 | 1 | 2 | 3 | 4 | 5 | .... |n-1| * BD | 1 | 2 | 3 | 0 | * * At time=2 (second period elapsed) * PCM | 0 | 1 | 2 | 3 | 4 | 5 | .... |n-1| * BD | 2 | 3 | 0 | 1 | * * The bd_head field points to the index of the BD to be read. It's also the * position to be filled at next. The pcm_head and the pcm_filled fields * point to the indices of the current position and of the next position to * be filled, respectively. For PCM buffer there are both _head and _filled * because they may be difference when nperiods > 4. For example, in the * example above at t=1, bd_head=1 and pcm_head=1 while pcm_filled=5: * * pcm_head (=1) --v v-- pcm_filled (=5) * PCM | 0 | 1 | 2 | 3 | 4 | 5 | .... |n-1| * BD | 1 | 2 | 3 | 0 | * bd_head (=1) --^ ^-- next to fill (= bd_head) * * For nperiods < 4, the remaining BDs out of 4 are marked as invalid, so that * the hardware skips those BDs in the loop. * * An exceptional setup is the case with nperiods=1. Since we have to update * BDs after finishing one BD processing, we'd need at least two BDs, where * both BDs point to the same content, the same address, the same size of the * whole PCM buffer.
*/
/* Set up a buffer descriptor at the "filled" position */ staticvoid had_prog_bd(struct snd_pcm_substream *substream, struct snd_intelhad *intelhaddata)
{ int idx = intelhaddata->bd_head; int ofs = intelhaddata->pcmbuf_filled * intelhaddata->period_bytes;
u32 addr = substream->runtime->dma_addr + ofs;
/* advance the indices to the next */
intelhaddata->bd_head++;
intelhaddata->bd_head %= intelhaddata->num_bds;
intelhaddata->pcmbuf_filled++;
intelhaddata->pcmbuf_filled %= substream->runtime->periods;
}
/* invalidate a buffer descriptor with the given index */ staticvoid had_invalidate_bd(struct snd_intelhad *intelhaddata, int idx)
{
had_write_register(intelhaddata, AUD_BUF_ADDR(idx), 0);
had_write_register(intelhaddata, AUD_BUF_LEN(idx), 0);
}
/* Initial programming of ring buffer */ staticvoid had_init_ringbuf(struct snd_pcm_substream *substream, struct snd_intelhad *intelhaddata)
{ struct snd_pcm_runtime *runtime = substream->runtime; int i, num_periods;
num_periods = runtime->periods;
intelhaddata->num_bds = min(num_periods, HAD_NUM_OF_RING_BUFS); /* set the minimum 2 BDs for num_periods=1 */
intelhaddata->num_bds = max(intelhaddata->num_bds, 2U);
intelhaddata->period_bytes =
frames_to_bytes(runtime, runtime->period_size);
WARN_ON(intelhaddata->period_bytes & 0x3f);
for (i = 0; i < HAD_NUM_OF_RING_BUFS; i++) { if (i < intelhaddata->num_bds)
had_prog_bd(substream, intelhaddata); else/* invalidate the rest */
had_invalidate_bd(intelhaddata, i);
}
intelhaddata->bd_head = 0; /* reset at head again before starting */
}
/* process a bd, advance to the next */ staticvoid had_advance_ringbuf(struct snd_pcm_substream *substream, struct snd_intelhad *intelhaddata)
{ int num_periods = substream->runtime->periods;
/* reprogram the next buffer */
had_prog_bd(substream, intelhaddata);
/* proceed to next */
intelhaddata->pcmbuf_head++;
intelhaddata->pcmbuf_head %= num_periods;
}
/* process the current BD(s); * returns the current PCM buffer byte position, or -EPIPE for underrun.
*/ staticint had_process_ringbuf(struct snd_pcm_substream *substream, struct snd_intelhad *intelhaddata)
{ int len, processed; unsignedlong flags;
processed = 0;
spin_lock_irqsave(&intelhaddata->had_spinlock, flags); for (;;) { /* get the remaining bytes on the buffer */
had_read_register(intelhaddata,
AUD_BUF_LEN(intelhaddata->bd_head),
&len); if (len < 0 || len > intelhaddata->period_bytes) {
dev_dbg(intelhaddata->dev, "Invalid buf length %d\n",
len);
len = -EPIPE; goto out;
}
if (len > 0) /* OK, this is the current buffer */ break;
/* len=0 => already empty, check the next buffer */ if (++processed >= intelhaddata->num_bds) {
len = -EPIPE; /* all empty? - report underrun */ goto out;
}
had_advance_ringbuf(substream, intelhaddata);
}
len = intelhaddata->period_bytes - len;
len += intelhaddata->period_bytes * intelhaddata->pcmbuf_head;
out:
spin_unlock_irqrestore(&intelhaddata->had_spinlock, flags); return len;
}
/* called from irq handler */ staticvoid had_process_buffer_done(struct snd_intelhad *intelhaddata)
{ struct snd_pcm_substream *substream;
substream = had_substream_get(intelhaddata); if (!substream) return; /* no stream? - bail out */
if (!intelhaddata->connected) {
snd_pcm_stop_xrun(substream); goto out; /* disconnected? - bail out */
}
/* process or stop the stream */ if (had_process_ringbuf(substream, intelhaddata) < 0)
snd_pcm_stop_xrun(substream); else
snd_pcm_period_elapsed(substream);
out:
had_substream_put(intelhaddata);
}
/* * The interrupt status 'sticky' bits might not be cleared by * setting '1' to that bit once...
*/ staticvoid wait_clear_underrun_bit(struct snd_intelhad *intelhaddata)
{ int i;
u32 val;
for (i = 0; i < 100; i++) { /* clear bit30, 31 AUD_HDMI_STATUS */
had_read_register(intelhaddata, AUD_HDMI_STATUS, &val); if (!(val & AUD_HDMI_STATUS_MASK_UNDERRUN)) return;
udelay(100);
cond_resched();
had_write_register(intelhaddata, AUD_HDMI_STATUS, val);
}
dev_err(intelhaddata->dev, "Unable to clear UNDERRUN bits\n");
}
/* Perform some reset procedure after stopping the stream; * this is called from prepare or hw_free callbacks once after trigger STOP * or underrun has been processed in order to settle down the h/w state.
*/ staticint had_pcm_sync_stop(struct snd_pcm_substream *substream)
{ struct snd_intelhad *intelhaddata = snd_pcm_substream_chip(substream);
retval = pm_runtime_resume_and_get(intelhaddata->dev); if (retval < 0) return retval;
/* set the runtime hw parameter with local snd_pcm_hardware struct */
runtime->hw = had_pcm_hardware;
retval = snd_pcm_hw_constraint_integer(runtime,
SNDRV_PCM_HW_PARAM_PERIODS); if (retval < 0) goto error;
/* Make sure, that the period size is always aligned * 64byte boundary
*/
retval = snd_pcm_hw_constraint_step(substream->runtime, 0,
SNDRV_PCM_HW_PARAM_PERIOD_BYTES, 64); if (retval < 0) goto error;
spin_lock(&intelhaddata->had_spinlock); switch (cmd) { case SNDRV_PCM_TRIGGER_START: case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: case SNDRV_PCM_TRIGGER_RESUME: /* Enable Audio */
had_ack_irqs(intelhaddata); /* FIXME: do we need this? */
had_enable_audio(intelhaddata, true); break;
case SNDRV_PCM_TRIGGER_STOP: case SNDRV_PCM_TRIGGER_PAUSE_PUSH: /* Disable Audio */
had_enable_audio(intelhaddata, false); break;
if (!intelhaddata->connected) return SNDRV_PCM_POS_XRUN;
len = had_process_ringbuf(substream, intelhaddata); if (len < 0) return SNDRV_PCM_POS_XRUN;
len = bytes_to_frames(substream->runtime, len); /* wrapping may happen when periods=1 */
len %= substream->runtime->buffer_size; return len;
}
/* process hot plug, called from wq with mutex locked */ staticvoid had_process_hot_plug(struct snd_intelhad *intelhaddata)
{ struct snd_pcm_substream *substream;
/* process hot unplug, called from wq with mutex locked */ staticvoid had_process_hot_unplug(struct snd_intelhad *intelhaddata)
{ struct snd_pcm_substream *substream;
spin_lock_irq(&intelhaddata->had_spinlock); if (!intelhaddata->connected) {
dev_dbg(intelhaddata->dev, "Device already disconnected\n");
spin_unlock_irq(&intelhaddata->had_spinlock); return;
for_each_pipe(card_ctx, pipe) { /* use raw register access to ack IRQs even while disconnected */
audio_stat[pipe] = had_read_register_raw(card_ctx, pipe,
AUD_HDMI_STATUS) &
(HDMI_AUDIO_UNDERRUN | HDMI_AUDIO_BUFFER_DONE);
if (audio_stat[pipe])
had_write_register_raw(card_ctx, pipe,
AUD_HDMI_STATUS, audio_stat[pipe]);
}
if (audio_stat[pipe] & HDMI_AUDIO_BUFFER_DONE)
had_process_buffer_done(ctx); if (audio_stat[pipe] & HDMI_AUDIO_UNDERRUN)
had_process_buffer_underrun(ctx);
}
return IRQ_HANDLED;
}
/* * monitor plug/unplug notification from i915; just kick off the work
*/ staticvoid notify_audio_lpe(struct platform_device *pdev, int port)
{ struct snd_intelhad_card *card_ctx = platform_get_drvdata(pdev); struct snd_intelhad *ctx;
ctx = &card_ctx->pcm_ctx[port];
ret = snd_pcm_new(card, INTEL_HAD, port, MAX_PB_STREAMS,
MAX_CAP_STREAMS, &pcm); if (ret) return ret;
/* setup private data which can be retrieved when required */
pcm->private_data = ctx;
pcm->info_flags = 0;
strscpy(pcm->name, card->shortname, sizeof(pcm->name)); /* setup the ops for playback */
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &had_pcm_ops);
/* allocate dma pages; * try to allocate 600k buffer as default which is large enough
*/
snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_DEV_WC,
card->dev, HAD_DEFAULT_BUFFER,
HAD_MAX_BUFFER);
/* create controls */ for (i = 0; i < ARRAY_SIZE(had_controls); i++) { struct snd_kcontrol *kctl;
kctl = snd_ctl_new1(&had_controls[i], ctx); if (!kctl) return -ENOMEM;
kctl->id.device = pcm->device;
ret = snd_ctl_add(card, kctl); if (ret < 0) return ret;
}
/* Register channel map controls */
ret = had_register_chmap_ctls(ctx, pcm); if (ret < 0) return ret;
ret = had_create_jack(ctx, pcm); if (ret < 0) return ret;
}
ret = snd_card_register(card); if (ret) return ret;
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.