blob: 8d9125978eb20ed024b252b52acd323e1dca2fda [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0
/* Copyright (C) 2018 Synaptics Incorporated */
#include <linux/module.h>
#include <linux/init.h>
#include <linux/spinlock.h>
#include <linux/dma-mapping.h>
#include <linux/delay.h>
#include <linux/irq.h>
#include <sound/core.h>
#include <sound/info.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/pcm-indirect.h>
#include <sound/soc.h>
#include "api_avio_dhub.h"
#include "api_dhub.h"
#include "berlin_pcm.h"
#include "berlin_spdif.h"
enum subframe_type {
TYPE_B = 0,
TYPE_M = 1,
TYPE_W = 2,
};
/*
* 32-bits packed spdif data
*/
struct spdif_frame {
int16_t sync : 4;
int16_t validflag : 1;
int16_t user : 1;
int16_t channelstatus : 1;
int16_t paritybit : 1;
int16_t audiosample1 : 8;
int16_t audiosample2;
};
#define DMA_BUFFER_SIZE (8 * 1024)
#define DMA_BUFFER_MIN (DMA_BUFFER_SIZE >> 2)
#define MAX_BUFFER_SIZE (DMA_BUFFER_SIZE << 1)
#define SPDIF_BLOCK_SIZE 192
struct berlin_playback {
/*
* Driver-specific debug proc entry
*/
struct snd_info_entry *entry;
/*
* Tracks the base address of the last submitted DMA block.
* Moved to next period in ISR.
* read in berlin_playback_pointer.
*
* Total size of ALSA buffer in the format received from userspace.
* Note that 16 bit input is padded out to 32.
*/
unsigned int dma_offset;
/*
* Is there a submitted DMA request?
* Set when a DMA request is submitted to DHUB.
* Cleared on 'stop' or ISR.
*/
bool ma_dma_pending;
bool spdif_dma_pending;
/*
* Indicates if page memory is allocated
*/
bool pages_allocated;
/*
* Instance lock between ISR and higher contexts.
*/
spinlock_t lock;
/* spdif DMA buffer */
unsigned char *spdif_dma_area; /* dma buf for spdifo */
dma_addr_t spdif_dma_addr; /* phy addr of spdif buf */
unsigned int spdif_buf_size; /* size of dma area */
unsigned int spdif_ratio;
/* PCM DMA buffer */
unsigned char *pcm_dma_area;
dma_addr_t pcm_dma_addr;
unsigned int pcm_buf_size;
unsigned int pcm_ratio;
/* hw parameter */
unsigned int sample_rate;
unsigned int sample_format;
unsigned int channel_num;
ssize_t buf_size;
ssize_t period_size;
/* for spdif encoding */
unsigned int spdif_frames;
unsigned char channel_status[24];
/* playback status */
bool playing;
unsigned int output_mode;
unsigned int intr_updates; // tracing the src interrupt happened
struct snd_pcm_substream *ss;
struct snd_pcm_indirect pcm_indirect;
unsigned int spdif_ch;
unsigned int i2s_ch;
struct berlin_chip *chip;
long long start_dma_count;
long long isr_count;
};
//Sample rate fixed to 48k now, avpll setting
static unsigned int berlin_playback_rates[] = {
8000, 11025, 12000, 16000, 22050, 24000, 32000, 44100, 48000,
64000, 88200, 96000
};
static struct snd_pcm_hw_constraint_list berlin_constraints_rates = {
.count = ARRAY_SIZE(berlin_playback_rates),
.list = berlin_playback_rates,
.mask = 0,
};
static void *zero_dma_buf;
static dma_addr_t zero_dma_addr;
static void spdif_enc_subframe(uint32_t *subframe,
const uint32_t data, uint32_t sync_type,
uint8_t v, uint8_t u, uint8_t c)
{
struct spdif_frame *spdif = (struct spdif_frame *)subframe;
int16_t high_16 = (data >> 16), low_16 = ((data >> 8) & 0xff);
int32_t parity = ((v + u + c) & 1);
spdif->sync = sync_type;
spdif->validflag = v;
spdif->user = u;
spdif->channelstatus = c;
spdif->paritybit = parity;
spdif->audiosample2 = high_16;
spdif->audiosample1 = low_16;
}
/*
* Kicks off a DMA transfer to audio IO interface for the |berlin_pcm|.
* Must be called with instance spinlock held.
* Must be called only when instance is in playing state.
*/
static void start_dma_if_needed(struct berlin_playback *bp)
{
dma_addr_t dma_source_address;
int dma_size;
assert_spin_locked(&bp->lock);
if (!bp->playing) {
snd_printd("%s: playing: %u\n", __func__, bp->playing);
return;
}
if (bp->pcm_indirect.hw_ready < bp->period_size) {
berlin_report_xrun(bp->chip, PCM_UNDERRUN);
snd_printk("%s: underrun! hw_ready: %d, %lld %lld\n", __func__,
bp->pcm_indirect.hw_ready, bp->start_dma_count, bp->isr_count);
return;
}
if (bp->ma_dma_pending && bp->spdif_dma_pending)
return;
if ((bp->output_mode & I2SO_MODE)
&& !bp->ma_dma_pending) {
dma_source_address = bp->pcm_dma_addr + bp->dma_offset * bp->pcm_ratio;
dma_size = bp->period_size * bp->pcm_ratio;
bp->ma_dma_pending = true;
dhub_channel_write_cmd(&AG_dhubHandle.dhub,
bp->i2s_ch,
dma_source_address, dma_size,
0, 0, 0, 1, 0, 0);
bp->start_dma_count++;
}
if ((bp->output_mode & SPDIFO_MODE)
&& !bp->spdif_dma_pending) {
dma_source_address = bp->spdif_dma_addr + bp->dma_offset * bp->spdif_ratio;
dma_size = bp->period_size * bp->spdif_ratio;
bp->spdif_dma_pending = true;
dhub_channel_write_cmd(&AG_dhubHandle.dhub,
bp->spdif_ch,
dma_source_address, dma_size,
0, 0, 0, 1, 0, 0);
}
}
static void berlin_playback_start_zero_dma(struct berlin_playback *bp)
{
assert_spin_locked(&bp->lock);
if (bp->output_mode & I2SO_MODE) {
DhubChannelClear(&AG_dhubHandle.dhub, bp->i2s_ch, 0);
dhub_channel_write_cmd(&AG_dhubHandle.dhub,
bp->i2s_ch,
zero_dma_addr, DMA_BUFFER_SIZE,
0, 0, 0, 0, 0, 0);
}
if (bp->output_mode & SPDIFO_MODE) {
DhubChannelClear(&AG_dhubHandle.dhub, bp->spdif_ch, 0);
dhub_channel_write_cmd(&AG_dhubHandle.dhub,
bp->spdif_ch,
zero_dma_addr, DMA_BUFFER_SIZE,
0, 0, 0, 0, 0, 0);
}
}
static void berlin_playback_trigger_start(struct snd_pcm_substream *ss)
{
struct snd_pcm_runtime *runtime = ss->runtime;
struct berlin_playback *bp = runtime->private_data;
unsigned long flags;
spin_lock_irqsave(&bp->lock, flags);
if (bp->playing) {
spin_unlock_irqrestore(&bp->lock, flags);
return;
}
bp->playing = true;
berlin_playback_start_zero_dma(bp);
start_dma_if_needed(bp);
spin_unlock_irqrestore(&bp->lock, flags);
snd_printd("%s: finished.\n", __func__);
}
static void berlin_playback_trigger_stop(struct snd_pcm_substream *ss)
{
struct snd_pcm_runtime *runtime = ss->runtime;
struct berlin_playback *bp = runtime->private_data;
unsigned long flags;
spin_lock_irqsave(&bp->lock, flags);
bp->start_dma_count = 0;
bp->isr_count = 0;
berlin_playback_start_zero_dma(bp);
bp->playing = false;
bp->ma_dma_pending = false;
bp->spdif_dma_pending = false;
spin_unlock_irqrestore(&bp->lock, flags);
snd_printd("%s: finished.\n", __func__);
}
static const struct snd_pcm_hardware berlin_playback_hw = {
.info = (SNDRV_PCM_INFO_MMAP
| SNDRV_PCM_INFO_INTERLEAVED
| SNDRV_PCM_INFO_MMAP_VALID
| SNDRV_PCM_INFO_PAUSE
| SNDRV_PCM_INFO_RESUME),
.formats = (SNDRV_PCM_FMTBIT_S32_LE
| SNDRV_PCM_FMTBIT_S16_LE),
.rates = (SNDRV_PCM_RATE_8000_96000
| SNDRV_PCM_RATE_KNOT),
.channels_min = 1,
.channels_max = 2,
.buffer_bytes_max = MAX_BUFFER_SIZE,
.period_bytes_min = DMA_BUFFER_MIN,
.period_bytes_max = DMA_BUFFER_SIZE,
.periods_min = 2,
.periods_max = MAX_BUFFER_SIZE / DMA_BUFFER_MIN,
.fifo_size = 0
};
static void berlin_runtime_free(struct snd_pcm_runtime *runtime)
{
struct berlin_playback *bp = runtime->private_data;
if (bp) {
if (bp->spdif_dma_area) {
dma_free_coherent(NULL, bp->spdif_buf_size,
bp->spdif_dma_area,
bp->spdif_dma_addr);
bp->spdif_dma_area = NULL;
bp->spdif_dma_addr = 0;
}
if (bp->pcm_dma_area) {
dma_free_coherent(NULL, bp->pcm_buf_size,
bp->pcm_dma_area,
bp->pcm_dma_addr);
bp->pcm_dma_area = NULL;
bp->pcm_dma_addr = 0;
}
if (bp->entry)
snd_info_free_entry(bp->entry);
kfree(bp);
runtime->private_data = NULL;
}
}
#ifdef CONFIG_SND_VERBOSE_PROCFS
static void debug_entry(struct snd_info_entry *entry,
struct snd_info_buffer *buffer)
{
struct berlin_playback *bp =
(struct berlin_playback *)entry->private_data;
unsigned long flags;
spin_lock_irqsave(&bp->lock, flags);
snd_iprintf(buffer, "playing:\t\t%d\n", bp->playing);
snd_iprintf(buffer, "dma_pending:\t\tma-%d spdif-%d\n",
bp->ma_dma_pending, bp->spdif_dma_pending);
snd_iprintf(buffer, "start_dma_count:\t%lld\n", bp->start_dma_count);
snd_iprintf(buffer, "isr_count:\t\t%lld\n", bp->isr_count);
snd_iprintf(buffer, "output_mode:\t\t%d\n", bp->output_mode);
snd_iprintf(buffer, "current_dma_offset:\t%u\n",
bp->dma_offset);
snd_iprintf(buffer, "\n");
snd_iprintf(buffer, "indirect.hw_data:\t%u\n",
bp->pcm_indirect.hw_data);
snd_iprintf(buffer, "indirect.hw_io:\t\t%u\n",
bp->pcm_indirect.hw_io);
snd_iprintf(buffer, "indirect.hw_ready:\t%d\n",
bp->pcm_indirect.hw_ready);
snd_iprintf(buffer, "indirect.sw_data:\t%u\n",
bp->pcm_indirect.hw_data);
snd_iprintf(buffer, "indirect.sw_io:\t\t%u\n",
bp->pcm_indirect.hw_io);
snd_iprintf(buffer, "indirect.sw_ready:\t%d\n",
bp->pcm_indirect.sw_ready);
snd_iprintf(buffer, "indirect.appl_ptr:\t%lu\n",
bp->pcm_indirect.appl_ptr);
snd_iprintf(buffer, "\n");
snd_iprintf(buffer, "substream->runtime->control->appl_ptr:\t%lu\n",
bp->ss->runtime->control->appl_ptr);
snd_iprintf(buffer, "substream->runtime->status->hw_ptr:\t%lu\n",
bp->ss->runtime->status->hw_ptr);
spin_unlock_irqrestore(&bp->lock, flags);
}
#endif
int berlin_playback_open(struct snd_pcm_substream *ss)
{
struct snd_pcm_runtime *runtime = ss->runtime;
struct berlin_playback *bp;
int err;
snd_printd("%s: start.\n", __func__);
err = snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
&berlin_constraints_rates);
if (err < 0)
return err;
bp = kzalloc(sizeof(*bp), GFP_KERNEL);
if (bp == NULL)
return -ENOMEM;
runtime->private_data = bp;
runtime->private_free = berlin_runtime_free;
runtime->hw = berlin_playback_hw;
/* TODO: remove this dependency after split spdif/i2s play */
#ifdef CONFIG_SND_VERBOSE_PROCFS
bp->entry = snd_info_create_card_entry(ss->pcm->card,
"debug",
ss->proc_root);
if (!bp->entry)
snd_printd("%s: couldn't create debug entry\n", __func__);
else {
snd_info_set_text_ops(bp->entry, bp, debug_entry);
snd_info_register(bp->entry);
}
#endif
bp->ma_dma_pending = false;
bp->spdif_dma_pending = false;
bp->playing = false;
bp->pages_allocated = false;
bp->intr_updates = 0;
bp->ss = ss;
bp->i2s_ch = -1;
bp->spdif_ch = -1;
bp->start_dma_count = 0;
bp->isr_count = 0;
spin_lock_init(&bp->lock);
snd_printd("%s: finished.\n", __func__);
return 0;
}
int berlin_playback_close(struct snd_pcm_substream *ss)
{
struct snd_pcm_runtime *runtime = ss->runtime;
struct berlin_playback *bp = runtime->private_data;
if (bp) {
if (bp->entry) {
snd_info_free_entry(bp->entry);
bp->entry = NULL;
}
kfree(bp);
runtime->private_data = NULL;
}
snd_printd("%s: finished.\n", __func__);
return 0;
}
int berlin_playback_hw_free(struct snd_pcm_substream *ss)
{
struct snd_pcm_runtime *runtime = ss->runtime;
struct berlin_playback *bp = runtime->private_data;
struct berlin_chip *chip = bp->chip;
if (chip) {
struct platform_device *pdev = chip->pdev;
if (bp->spdif_dma_area) {
dma_free_coherent(&pdev->dev,
bp->spdif_buf_size,
bp->spdif_dma_area,
bp->spdif_dma_addr);
bp->spdif_dma_area = NULL;
bp->spdif_dma_addr = 0;
}
if (bp->pcm_dma_area) {
dma_free_coherent(&pdev->dev,
bp->pcm_buf_size,
bp->pcm_dma_area,
bp->pcm_dma_addr);
bp->pcm_dma_area = NULL;
bp->pcm_dma_addr = 0;
}
}
if (bp->pages_allocated == true) {
snd_pcm_lib_free_pages(ss);
bp->pages_allocated = false;
}
return 0;
}
int berlin_playback_hw_params(struct snd_pcm_substream *ss,
struct snd_pcm_hw_params *p)
{
struct snd_pcm_runtime *runtime = ss->runtime;
struct berlin_playback *bp = runtime->private_data;
struct snd_soc_pcm_runtime *rtd = ss->private_data;
struct device *dev = rtd->platform->dev;
struct berlin_chip *chip = dev_get_drvdata(dev);
struct platform_device *pdev = chip->pdev;
struct spdif_cs *chnsts;
int err;
bp->chip = chip;
berlin_playback_hw_free(ss);
err = snd_pcm_lib_malloc_pages(ss, params_buffer_bytes(p));
if (err < 0) {
snd_printk("failed to allocated pages for buffers\n");
return err;
}
bp->pages_allocated = true;
snd_printd("%s: sample_rate:%d channels:%d format:%s\n", __func__,
params_rate(p), params_channels(p),
snd_pcm_format_name(params_format(p)));
bp->sample_rate = params_rate(p);
bp->sample_format = params_format(p);
bp->channel_num = params_channels(p);
bp->period_size = params_period_bytes(p);
bp->buf_size = params_buffer_bytes(p);
bp->pcm_ratio = 1;
if (bp->sample_format == SNDRV_PCM_FORMAT_S16_LE)
bp->pcm_ratio <<= 1;
if (bp->channel_num == 1)
bp->pcm_ratio <<= 1;
if (bp->output_mode & PCMMONO_MODE)
bp->pcm_ratio >>= 1;
bp->pcm_buf_size = bp->buf_size * bp->pcm_ratio;
bp->pcm_dma_area =
dma_zalloc_coherent(&pdev->dev, bp->pcm_buf_size,
&bp->pcm_dma_addr, GFP_KERNEL);
if (!bp->pcm_dma_area) {
snd_printk("%s: failed to allocate PCM DMA area\n", __func__);
goto err_pcm_dma;
}
#ifdef SPDIF_CLOCK_PATTERN
bp->spdif_ratio = bp->pcm_ratio * 2;
#else
bp->spdif_ratio = bp->pcm_ratio;
#endif
bp->spdif_buf_size = bp->buf_size * bp->spdif_ratio;
bp->spdif_dma_area =
dma_zalloc_coherent(&pdev->dev, bp->spdif_buf_size,
&bp->spdif_dma_addr, GFP_KERNEL);
if (!bp->spdif_dma_area) {
snd_printk("%s: failed to allocate SPDIF DMA area\n", __func__);
goto err_spdif_dma;
}
/* initialize spdif channel status */
chnsts = (struct spdif_cs *)&(bp->channel_status[0]);
spdif_init_channel_status(chnsts, bp->sample_rate);
if (zero_dma_buf == NULL) {
zero_dma_buf = devm_kzalloc(&pdev->dev, DMA_BUFFER_SIZE,
GFP_KERNEL);
if (!zero_dma_buf) {
snd_printk("%s: failed to allocate zero DMA area\n",
__func__);
goto err_zero_dma;
}
zero_dma_addr = dma_map_single(&pdev->dev, zero_dma_buf,
DMA_BUFFER_SIZE, DMA_TO_DEVICE);
}
return 0;
err_zero_dma:
if (bp->spdif_dma_area)
dma_free_coherent(&pdev->dev, bp->spdif_buf_size,
bp->spdif_dma_area, bp->spdif_dma_addr);
bp->spdif_dma_area = NULL;
bp->spdif_dma_addr = 0;
err_spdif_dma:
if (bp->pcm_dma_area)
dma_free_coherent(&pdev->dev, bp->pcm_buf_size,
bp->pcm_dma_area, bp->pcm_dma_addr);
bp->pcm_dma_area = NULL;
bp->pcm_dma_addr = 0;
err_pcm_dma:
snd_pcm_lib_free_pages(ss);
return -ENOMEM;
}
int berlin_playback_prepare(struct snd_pcm_substream *ss)
{
struct snd_pcm_runtime *runtime = ss->runtime;
struct berlin_playback *bp = runtime->private_data;
unsigned long flags;
spin_lock_irqsave(&bp->lock, flags);
bp->dma_offset = 0;
memset(&bp->pcm_indirect, 0, sizeof(bp->pcm_indirect));
bp->pcm_indirect.hw_buffer_size = bp->buf_size;
bp->pcm_indirect.sw_buffer_size = snd_pcm_lib_buffer_bytes(ss);
spin_unlock_irqrestore(&bp->lock, flags);
snd_printd("%s finished. buffer: %zd period: %zd hw %zd sw %zd\n",
__func__,
snd_pcm_lib_buffer_bytes(ss),
snd_pcm_lib_period_bytes(ss),
bp->pcm_indirect.hw_buffer_size,
bp->pcm_indirect.sw_buffer_size);
return 0;
}
int berlin_playback_trigger(struct snd_pcm_substream *ss, int cmd)
{
int ret = 0;
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
case SNDRV_PCM_TRIGGER_RESUME:
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
berlin_playback_trigger_start(ss);
break;
case SNDRV_PCM_TRIGGER_STOP:
case SNDRV_PCM_TRIGGER_SUSPEND:
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
berlin_playback_trigger_stop(ss);
break;
default:
ret = -EINVAL;
}
return ret;
}
snd_pcm_uframes_t
berlin_playback_pointer(struct snd_pcm_substream *ss)
{
struct snd_pcm_runtime *runtime = ss->runtime;
struct berlin_playback *bp = runtime->private_data;
uint32_t buf_pos;
unsigned long flags;
spin_lock_irqsave(&bp->lock, flags);
buf_pos = bp->dma_offset;
spin_unlock_irqrestore(&bp->lock, flags);
return snd_pcm_indirect_playback_pointer(ss,
&bp->pcm_indirect, buf_pos);
}
// Encodes |frames| number of stereo S32LE frames from |pcm_in|
// to |spdif_out| SPDIF frames (64-bits per frame)
static void spdif_encode(struct berlin_playback *bp,
const int32_t *pcm_in, int32_t *spdif_out,
int frames)
{
int i;
for (i = 0; i < frames; ++i) {
unsigned char channel_status =
spdif_get_channel_status(bp->channel_status,
bp->spdif_frames);
uint32_t sync_word = bp->spdif_frames ? TYPE_M : TYPE_B;
#ifdef SPDIF_CLOCK_PATTERN
spdif_enc_subframe(&spdif_out[i * 4],
pcm_in[i * 2], sync_word, 0, 0,
channel_status);
sync_word = TYPE_W;
spdif_enc_subframe(&spdif_out[(i * 4) + 2],
pcm_in[(i * 2) + 1], sync_word, 0, 0,
channel_status);
#else
spdif_enc_subframe(&spdif_out[i * 2],
pcm_in[i * 2], sync_word, 0, 0,
channel_status);
sync_word = TYPE_W;
spdif_enc_subframe(&spdif_out[(i * 2) + 1],
pcm_in[(i * 2) + 1], sync_word, 0, 0,
channel_status);
#endif
++bp->spdif_frames;
bp->spdif_frames %= SPDIF_BLOCK_SIZE;
}
}
static int berlin_playback_copy(struct snd_pcm_substream *ss,
int channel, snd_pcm_uframes_t pos,
void *buf, size_t bytes)
{
struct snd_pcm_runtime *runtime = ss->runtime;
struct berlin_playback *bp = runtime->private_data;
int32_t *pcm_buf = (int32_t *)(bp->pcm_dma_area +
pos * bp->pcm_ratio);
int32_t *spdif_buf = (int32_t *)(bp->spdif_dma_area +
pos * bp->spdif_ratio);
const int frames = bytes /
(bp->channel_num *
snd_pcm_format_width(bp->sample_format) / 8);
int i;
if (pos >= bp->buf_size)
return -EINVAL;
if (bp->pcm_indirect.hw_ready >= bp->period_size) {
const unsigned long dma_b = bp->dma_offset;
const unsigned long dma_e = dma_b + bp->period_size - 1;
const unsigned long write_b = pos;
const unsigned long write_e = write_b + bytes - 1;
// Write begin position shouldn't be in DMA area.
if ((dma_b <= write_b) && (write_b <= dma_e)) {
snd_printk("%s: db:%lu <= wb:%lu <= de:%lu\n",
__func__, dma_b, write_b, dma_e);
}
// Write end position shouldn't be in DMA area.
if ((dma_b <= write_e) && (write_e <= dma_e)) {
snd_printk("%s: db:%lu <= we:%lu <= de:%lu\n",
__func__, dma_b, write_e, dma_e);
}
// Write shouldn't overlap DMA area.
if ((write_b <= dma_b) && (write_e >= dma_e)) {
snd_printk("%s: wb:%lu <= db:%lu && we:%lu >= de:%lu\n",
__func__, write_b, dma_b,
write_e, dma_e);
}
}
if (bp->sample_format == SNDRV_PCM_FORMAT_S16_LE) {
const int16_t *s16_pcm_source = (int16_t *)buf;
int16_t *s16_pcm_dst = (int16_t *)pcm_buf;
if (bp->channel_num == 1)
for (i = 0; i < frames; ++i) {
if (bp->output_mode & PCMMONO_MODE) {
*s16_pcm_dst++ = s16_pcm_source[i];
*s16_pcm_dst++ = s16_pcm_source[i];
} else {
const int32_t s32_sample =
s16_pcm_source[i] << 16;
*pcm_buf++ = s32_sample;
*pcm_buf++ = s32_sample;
}
}
else {
/* only for PCMMONO mode, keep original 16 bits */
if (bp->output_mode & PCMMONO_MODE)
memcpy((void *)pcm_buf, buf, frames<<2);
else
for (i = 0; i < (frames<<1); ++i)
*pcm_buf++ = s16_pcm_source[i] << 16;
}
} else if (bp->sample_format == SNDRV_PCM_FORMAT_S32_LE) {
const int32_t *s32_pcm_source = (int32_t *)buf;
if (bp->channel_num == 1)
for (i = 0; i < frames; ++i) {
*pcm_buf++ = s32_pcm_source[i];
*pcm_buf++ = s32_pcm_source[i];
}
else
memcpy((void *)pcm_buf, buf, frames<<3);
} else {
return -EINVAL;
}
if (bp->output_mode & SPDIFO_MODE)
spdif_encode(bp, pcm_buf, spdif_buf, frames);
return 0;
}
static void berlin_playback_transfer(struct snd_pcm_substream *ss,
struct snd_pcm_indirect *rec,
size_t bytes)
{
struct snd_pcm_runtime *runtime = ss->runtime;
struct berlin_playback *bp = runtime->private_data;
void *src = (void *)(runtime->dma_area + rec->sw_data);
if (!src)
return;
berlin_playback_copy(ss, bp->channel_num,
rec->hw_data, src, bytes);
}
static inline void
berlin_direct_playback_transfer(struct snd_pcm_substream *substream,
struct snd_pcm_indirect *rec,
snd_pcm_indirect_copy_t copy)
{
struct snd_pcm_runtime *runtime = substream->runtime;
snd_pcm_uframes_t appl_ptr = runtime->control->appl_ptr;
snd_pcm_sframes_t diff = appl_ptr - rec->appl_ptr;
int qsize;
if (diff) {
if (diff < -(snd_pcm_sframes_t) (runtime->boundary / 2))
diff += runtime->boundary;
if (diff < 0)
return;
rec->sw_ready += (int)frames_to_bytes(runtime, diff);
rec->appl_ptr = appl_ptr;
}
if (runtime->stop_threshold == runtime->boundary)
rec->sw_ready = snd_pcm_lib_period_bytes(substream);
qsize = rec->hw_queue_size ? rec->hw_queue_size : rec->hw_buffer_size;
while (rec->hw_ready < qsize) {
unsigned int hw_to_end = rec->hw_buffer_size - rec->hw_data;
unsigned int sw_to_end = rec->sw_buffer_size - rec->sw_data;
unsigned int bytes = qsize - rec->hw_ready;
if (hw_to_end < bytes)
bytes = hw_to_end;
if (sw_to_end < bytes)
bytes = sw_to_end;
if (!bytes)
break;
copy(substream, rec, bytes);
rec->hw_data += bytes;
if (rec->hw_data == rec->hw_buffer_size)
rec->hw_data = 0;
rec->sw_data += bytes;
if (rec->sw_data == rec->sw_buffer_size)
rec->sw_data = 0;
rec->hw_ready += bytes;
}
}
void berlin_playback_set_ch_mode(struct snd_pcm_substream *ss,
u32 ch_num, u32 *ch, u32 mode)
{
struct snd_pcm_runtime *runtime = ss->runtime;
struct berlin_playback *bp = runtime->private_data;
if (ch_num > 1) {
snd_printk("only support 1 dhub channel play");
return;
}
if (bp) {
bp->output_mode |= mode;
if (mode & I2SO_MODE)
bp->i2s_ch = ch ? ch[0] : 0;
else if (mode & SPDIFO_MODE)
bp->spdif_ch = ch ? ch[0] : 0;
else if (mode == 0) {
bp->output_mode = 0;
bp->i2s_ch = 0;
bp->spdif_ch = 0;
} else
snd_printk("invalid mode setting\n");
}
}
int berlin_playback_ack(struct snd_pcm_substream *ss)
{
struct snd_pcm_runtime *runtime = ss->runtime;
struct berlin_playback *bp = runtime->private_data;
struct snd_pcm_indirect *pcm_indirect = &bp->pcm_indirect;
unsigned long flags;
pcm_indirect->hw_queue_size = bp->buf_size;
if (runtime->tstamp_mode == SNDRV_PCM_TSTAMP_ENABLE)
berlin_direct_playback_transfer(ss, pcm_indirect,
berlin_playback_transfer);
else
snd_pcm_indirect_playback_transfer(ss, pcm_indirect,
berlin_playback_transfer);
spin_lock_irqsave(&bp->lock, flags);
start_dma_if_needed(bp);
spin_unlock_irqrestore(&bp->lock, flags);
return 0;
}
int berlin_playback_isr(struct snd_pcm_substream *ss,
unsigned int chanId)
{
struct snd_pcm_runtime *runtime = ss->runtime;
struct berlin_playback *bp = runtime->private_data;
//TODO: remove this define
#if BERLIN_XRUN_ENABLE
// FIFO fullness
unsigned int dhub_cnt;
#endif
bool all_update = false;
spin_lock(&bp->lock);
bp->isr_count++;
/* If we were not pending, avoid pointer manipulation */
if (!bp->ma_dma_pending && !bp->spdif_dma_pending) {
spin_unlock(&bp->lock);
snd_printd("stream %p no dma pending, %lld, %lld\n", ss, bp->start_dma_count, bp->isr_count);
return 0;
}
if (chanId == bp->i2s_ch) {
if (bp->intr_updates & I2SO_MODE)
snd_printd("stream %p dual i2s intrupt\n", ss);
bp->ma_dma_pending = false;
} else if (chanId == bp->spdif_ch) {
if (bp->intr_updates & SPDIFO_MODE)
snd_printd("stream %p dual spdif interrupt\n", ss);
bp->spdif_dma_pending = false;
}
/* If we are not running, do not chain, and clear pending */
if (!bp->playing) {
snd_printd("not playing, %lld, %lld\n", bp->start_dma_count, bp->isr_count);
spin_unlock(&bp->lock);
return 0;
}
/* Roll the DMA pointer, and chain if needed */
if ((chanId == bp->i2s_ch) && (bp->output_mode & I2SO_MODE))
bp->intr_updates |= I2SO_MODE;
if ((chanId == bp->spdif_ch) && (bp->output_mode & SPDIFO_MODE))
bp->intr_updates |= SPDIFO_MODE;
if (bp->intr_updates ==
(bp->output_mode & (SPDIFO_MODE | I2SO_MODE))) {
bp->dma_offset += bp->period_size;
bp->dma_offset %= bp->buf_size;
all_update = true;
bp->intr_updates = 0;
}
#if BERLIN_XRUN_ENABLE
/* to query the consumer data count register to check fifo fullness */
dhub_cnt = aio_query_datacnt(chanId);
// According to Marvell, we only use the lower 16 bits
dhub_cnt &= 0xffff;
// The size of output FIFO is 1024 bytes
// dhub count is in 64 bit unit, this value is 8*8 = 64 bytes
// the typical value is 128, which is 128*8 =1024 bytes
if (dhub_cnt <= 8) {
berlin_report_xrun(bp->chip, FIFO_UNDERRUN);
snd_printd("Dhub cnt: %d bytes, FIFO nearly empty\n",
dhub_cnt * 8);
}
#endif
spin_unlock(&bp->lock);
if (all_update)
snd_pcm_period_elapsed(ss);
spin_lock(&bp->lock);
start_dma_if_needed(bp);
spin_unlock(&bp->lock);
return 0;
}