mirror of
https://github.com/AuxXxilium/linux_dsm_epyc7002.git
synced 2024-12-26 18:25:27 +07:00
5765e78e84
Based on 1 normalized pattern(s): this source file is released under gpl v2 license no other versions see the copying file included in the main directory of this source distribution for the license terms and conditions extracted by the scancode license scanner the SPDX license identifier GPL-2.0-only has been chosen to replace the boilerplate/reference in 28 file(s). Signed-off-by: Thomas Gleixner <tglx@linutronix.de> Reviewed-by: Allison Randal <allison@lohutok.net> Reviewed-by: Armijn Hemel <armijn@tjaldur.nl> Cc: linux-spdx@vger.kernel.org Link: https://lkml.kernel.org/r/20190531081035.780831265@linutronix.de Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
487 lines
12 KiB
C
487 lines
12 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/**
|
|
* Copyright (C) 2008, Creative Technology Ltd. All Rights Reserved.
|
|
*
|
|
* @File ctpcm.c
|
|
*
|
|
* @Brief
|
|
* This file contains the definition of the pcm device functions.
|
|
*
|
|
* @Author Liu Chun
|
|
* @Date Apr 2 2008
|
|
*/
|
|
|
|
#include "ctpcm.h"
|
|
#include "cttimer.h"
|
|
#include <linux/slab.h>
|
|
#include <sound/pcm.h>
|
|
|
|
/* Hardware descriptions for playback */
|
|
static const struct snd_pcm_hardware ct_pcm_playback_hw = {
|
|
.info = (SNDRV_PCM_INFO_MMAP |
|
|
SNDRV_PCM_INFO_INTERLEAVED |
|
|
SNDRV_PCM_INFO_BLOCK_TRANSFER |
|
|
SNDRV_PCM_INFO_MMAP_VALID |
|
|
SNDRV_PCM_INFO_PAUSE),
|
|
.formats = (SNDRV_PCM_FMTBIT_U8 |
|
|
SNDRV_PCM_FMTBIT_S16_LE |
|
|
SNDRV_PCM_FMTBIT_S24_3LE |
|
|
SNDRV_PCM_FMTBIT_S32_LE |
|
|
SNDRV_PCM_FMTBIT_FLOAT_LE),
|
|
.rates = (SNDRV_PCM_RATE_CONTINUOUS |
|
|
SNDRV_PCM_RATE_8000_192000),
|
|
.rate_min = 8000,
|
|
.rate_max = 192000,
|
|
.channels_min = 1,
|
|
.channels_max = 2,
|
|
.buffer_bytes_max = (128*1024),
|
|
.period_bytes_min = (64),
|
|
.period_bytes_max = (128*1024),
|
|
.periods_min = 2,
|
|
.periods_max = 1024,
|
|
.fifo_size = 0,
|
|
};
|
|
|
|
static const struct snd_pcm_hardware ct_spdif_passthru_playback_hw = {
|
|
.info = (SNDRV_PCM_INFO_MMAP |
|
|
SNDRV_PCM_INFO_INTERLEAVED |
|
|
SNDRV_PCM_INFO_BLOCK_TRANSFER |
|
|
SNDRV_PCM_INFO_MMAP_VALID |
|
|
SNDRV_PCM_INFO_PAUSE),
|
|
.formats = SNDRV_PCM_FMTBIT_S16_LE,
|
|
.rates = (SNDRV_PCM_RATE_48000 |
|
|
SNDRV_PCM_RATE_44100 |
|
|
SNDRV_PCM_RATE_32000),
|
|
.rate_min = 32000,
|
|
.rate_max = 48000,
|
|
.channels_min = 2,
|
|
.channels_max = 2,
|
|
.buffer_bytes_max = (128*1024),
|
|
.period_bytes_min = (64),
|
|
.period_bytes_max = (128*1024),
|
|
.periods_min = 2,
|
|
.periods_max = 1024,
|
|
.fifo_size = 0,
|
|
};
|
|
|
|
/* Hardware descriptions for capture */
|
|
static const struct snd_pcm_hardware ct_pcm_capture_hw = {
|
|
.info = (SNDRV_PCM_INFO_MMAP |
|
|
SNDRV_PCM_INFO_INTERLEAVED |
|
|
SNDRV_PCM_INFO_BLOCK_TRANSFER |
|
|
SNDRV_PCM_INFO_PAUSE |
|
|
SNDRV_PCM_INFO_MMAP_VALID),
|
|
.formats = (SNDRV_PCM_FMTBIT_U8 |
|
|
SNDRV_PCM_FMTBIT_S16_LE |
|
|
SNDRV_PCM_FMTBIT_S24_3LE |
|
|
SNDRV_PCM_FMTBIT_S32_LE |
|
|
SNDRV_PCM_FMTBIT_FLOAT_LE),
|
|
.rates = (SNDRV_PCM_RATE_CONTINUOUS |
|
|
SNDRV_PCM_RATE_8000_96000),
|
|
.rate_min = 8000,
|
|
.rate_max = 96000,
|
|
.channels_min = 1,
|
|
.channels_max = 2,
|
|
.buffer_bytes_max = (128*1024),
|
|
.period_bytes_min = (384),
|
|
.period_bytes_max = (64*1024),
|
|
.periods_min = 2,
|
|
.periods_max = 1024,
|
|
.fifo_size = 0,
|
|
};
|
|
|
|
static void ct_atc_pcm_interrupt(struct ct_atc_pcm *atc_pcm)
|
|
{
|
|
struct ct_atc_pcm *apcm = atc_pcm;
|
|
|
|
if (!apcm->substream)
|
|
return;
|
|
|
|
snd_pcm_period_elapsed(apcm->substream);
|
|
}
|
|
|
|
static void ct_atc_pcm_free_substream(struct snd_pcm_runtime *runtime)
|
|
{
|
|
struct ct_atc_pcm *apcm = runtime->private_data;
|
|
struct ct_atc *atc = snd_pcm_substream_chip(apcm->substream);
|
|
|
|
atc->pcm_release_resources(atc, apcm);
|
|
ct_timer_instance_free(apcm->timer);
|
|
kfree(apcm);
|
|
runtime->private_data = NULL;
|
|
}
|
|
|
|
/* pcm playback operations */
|
|
static int ct_pcm_playback_open(struct snd_pcm_substream *substream)
|
|
{
|
|
struct ct_atc *atc = snd_pcm_substream_chip(substream);
|
|
struct snd_pcm_runtime *runtime = substream->runtime;
|
|
struct ct_atc_pcm *apcm;
|
|
int err;
|
|
|
|
apcm = kzalloc(sizeof(*apcm), GFP_KERNEL);
|
|
if (!apcm)
|
|
return -ENOMEM;
|
|
|
|
apcm->substream = substream;
|
|
apcm->interrupt = ct_atc_pcm_interrupt;
|
|
if (IEC958 == substream->pcm->device) {
|
|
runtime->hw = ct_spdif_passthru_playback_hw;
|
|
atc->spdif_out_passthru(atc, 1);
|
|
} else {
|
|
runtime->hw = ct_pcm_playback_hw;
|
|
if (FRONT == substream->pcm->device)
|
|
runtime->hw.channels_max = 8;
|
|
}
|
|
|
|
err = snd_pcm_hw_constraint_integer(runtime,
|
|
SNDRV_PCM_HW_PARAM_PERIODS);
|
|
if (err < 0)
|
|
goto free_pcm;
|
|
|
|
err = snd_pcm_hw_constraint_minmax(runtime,
|
|
SNDRV_PCM_HW_PARAM_BUFFER_BYTES,
|
|
1024, UINT_MAX);
|
|
if (err < 0)
|
|
goto free_pcm;
|
|
|
|
apcm->timer = ct_timer_instance_new(atc->timer, apcm);
|
|
if (!apcm->timer) {
|
|
err = -ENOMEM;
|
|
goto free_pcm;
|
|
}
|
|
runtime->private_data = apcm;
|
|
runtime->private_free = ct_atc_pcm_free_substream;
|
|
|
|
return 0;
|
|
|
|
free_pcm:
|
|
kfree(apcm);
|
|
return err;
|
|
}
|
|
|
|
static int ct_pcm_playback_close(struct snd_pcm_substream *substream)
|
|
{
|
|
struct ct_atc *atc = snd_pcm_substream_chip(substream);
|
|
|
|
/* TODO: Notify mixer inactive. */
|
|
if (IEC958 == substream->pcm->device)
|
|
atc->spdif_out_passthru(atc, 0);
|
|
|
|
/* The ct_atc_pcm object will be freed by runtime->private_free */
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ct_pcm_hw_params(struct snd_pcm_substream *substream,
|
|
struct snd_pcm_hw_params *hw_params)
|
|
{
|
|
struct ct_atc *atc = snd_pcm_substream_chip(substream);
|
|
struct ct_atc_pcm *apcm = substream->runtime->private_data;
|
|
int err;
|
|
|
|
err = snd_pcm_lib_malloc_pages(substream,
|
|
params_buffer_bytes(hw_params));
|
|
if (err < 0)
|
|
return err;
|
|
/* clear previous resources */
|
|
atc->pcm_release_resources(atc, apcm);
|
|
return err;
|
|
}
|
|
|
|
static int ct_pcm_hw_free(struct snd_pcm_substream *substream)
|
|
{
|
|
struct ct_atc *atc = snd_pcm_substream_chip(substream);
|
|
struct ct_atc_pcm *apcm = substream->runtime->private_data;
|
|
|
|
/* clear previous resources */
|
|
atc->pcm_release_resources(atc, apcm);
|
|
/* Free snd-allocated pages */
|
|
return snd_pcm_lib_free_pages(substream);
|
|
}
|
|
|
|
|
|
static int ct_pcm_playback_prepare(struct snd_pcm_substream *substream)
|
|
{
|
|
int err;
|
|
struct ct_atc *atc = snd_pcm_substream_chip(substream);
|
|
struct snd_pcm_runtime *runtime = substream->runtime;
|
|
struct ct_atc_pcm *apcm = runtime->private_data;
|
|
|
|
if (IEC958 == substream->pcm->device)
|
|
err = atc->spdif_passthru_playback_prepare(atc, apcm);
|
|
else
|
|
err = atc->pcm_playback_prepare(atc, apcm);
|
|
|
|
if (err < 0) {
|
|
dev_err(atc->card->dev,
|
|
"Preparing pcm playback failed!!!\n");
|
|
return err;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
ct_pcm_playback_trigger(struct snd_pcm_substream *substream, int cmd)
|
|
{
|
|
struct ct_atc *atc = snd_pcm_substream_chip(substream);
|
|
struct snd_pcm_runtime *runtime = substream->runtime;
|
|
struct ct_atc_pcm *apcm = runtime->private_data;
|
|
|
|
switch (cmd) {
|
|
case SNDRV_PCM_TRIGGER_START:
|
|
case SNDRV_PCM_TRIGGER_RESUME:
|
|
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
|
|
atc->pcm_playback_start(atc, apcm);
|
|
break;
|
|
case SNDRV_PCM_TRIGGER_STOP:
|
|
case SNDRV_PCM_TRIGGER_SUSPEND:
|
|
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
|
|
atc->pcm_playback_stop(atc, apcm);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static snd_pcm_uframes_t
|
|
ct_pcm_playback_pointer(struct snd_pcm_substream *substream)
|
|
{
|
|
unsigned long position;
|
|
struct ct_atc *atc = snd_pcm_substream_chip(substream);
|
|
struct snd_pcm_runtime *runtime = substream->runtime;
|
|
struct ct_atc_pcm *apcm = runtime->private_data;
|
|
|
|
/* Read out playback position */
|
|
position = atc->pcm_playback_position(atc, apcm);
|
|
position = bytes_to_frames(runtime, position);
|
|
if (position >= runtime->buffer_size)
|
|
position = 0;
|
|
return position;
|
|
}
|
|
|
|
/* pcm capture operations */
|
|
static int ct_pcm_capture_open(struct snd_pcm_substream *substream)
|
|
{
|
|
struct ct_atc *atc = snd_pcm_substream_chip(substream);
|
|
struct snd_pcm_runtime *runtime = substream->runtime;
|
|
struct ct_atc_pcm *apcm;
|
|
int err;
|
|
|
|
apcm = kzalloc(sizeof(*apcm), GFP_KERNEL);
|
|
if (!apcm)
|
|
return -ENOMEM;
|
|
|
|
apcm->started = 0;
|
|
apcm->substream = substream;
|
|
apcm->interrupt = ct_atc_pcm_interrupt;
|
|
runtime->hw = ct_pcm_capture_hw;
|
|
runtime->hw.rate_max = atc->rsr * atc->msr;
|
|
|
|
err = snd_pcm_hw_constraint_integer(runtime,
|
|
SNDRV_PCM_HW_PARAM_PERIODS);
|
|
if (err < 0)
|
|
goto free_pcm;
|
|
|
|
err = snd_pcm_hw_constraint_minmax(runtime,
|
|
SNDRV_PCM_HW_PARAM_BUFFER_BYTES,
|
|
1024, UINT_MAX);
|
|
if (err < 0)
|
|
goto free_pcm;
|
|
|
|
apcm->timer = ct_timer_instance_new(atc->timer, apcm);
|
|
if (!apcm->timer) {
|
|
err = -ENOMEM;
|
|
goto free_pcm;
|
|
}
|
|
runtime->private_data = apcm;
|
|
runtime->private_free = ct_atc_pcm_free_substream;
|
|
|
|
return 0;
|
|
|
|
free_pcm:
|
|
kfree(apcm);
|
|
return err;
|
|
}
|
|
|
|
static int ct_pcm_capture_close(struct snd_pcm_substream *substream)
|
|
{
|
|
/* The ct_atc_pcm object will be freed by runtime->private_free */
|
|
/* TODO: Notify mixer inactive. */
|
|
return 0;
|
|
}
|
|
|
|
static int ct_pcm_capture_prepare(struct snd_pcm_substream *substream)
|
|
{
|
|
int err;
|
|
struct ct_atc *atc = snd_pcm_substream_chip(substream);
|
|
struct snd_pcm_runtime *runtime = substream->runtime;
|
|
struct ct_atc_pcm *apcm = runtime->private_data;
|
|
|
|
err = atc->pcm_capture_prepare(atc, apcm);
|
|
if (err < 0) {
|
|
dev_err(atc->card->dev,
|
|
"Preparing pcm capture failed!!!\n");
|
|
return err;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
ct_pcm_capture_trigger(struct snd_pcm_substream *substream, int cmd)
|
|
{
|
|
struct ct_atc *atc = snd_pcm_substream_chip(substream);
|
|
struct snd_pcm_runtime *runtime = substream->runtime;
|
|
struct ct_atc_pcm *apcm = runtime->private_data;
|
|
|
|
switch (cmd) {
|
|
case SNDRV_PCM_TRIGGER_START:
|
|
atc->pcm_capture_start(atc, apcm);
|
|
break;
|
|
case SNDRV_PCM_TRIGGER_STOP:
|
|
atc->pcm_capture_stop(atc, apcm);
|
|
break;
|
|
default:
|
|
atc->pcm_capture_stop(atc, apcm);
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static snd_pcm_uframes_t
|
|
ct_pcm_capture_pointer(struct snd_pcm_substream *substream)
|
|
{
|
|
unsigned long position;
|
|
struct ct_atc *atc = snd_pcm_substream_chip(substream);
|
|
struct snd_pcm_runtime *runtime = substream->runtime;
|
|
struct ct_atc_pcm *apcm = runtime->private_data;
|
|
|
|
/* Read out playback position */
|
|
position = atc->pcm_capture_position(atc, apcm);
|
|
position = bytes_to_frames(runtime, position);
|
|
if (position >= runtime->buffer_size)
|
|
position = 0;
|
|
return position;
|
|
}
|
|
|
|
/* PCM operators for playback */
|
|
static const struct snd_pcm_ops ct_pcm_playback_ops = {
|
|
.open = ct_pcm_playback_open,
|
|
.close = ct_pcm_playback_close,
|
|
.ioctl = snd_pcm_lib_ioctl,
|
|
.hw_params = ct_pcm_hw_params,
|
|
.hw_free = ct_pcm_hw_free,
|
|
.prepare = ct_pcm_playback_prepare,
|
|
.trigger = ct_pcm_playback_trigger,
|
|
.pointer = ct_pcm_playback_pointer,
|
|
.page = snd_pcm_sgbuf_ops_page,
|
|
};
|
|
|
|
/* PCM operators for capture */
|
|
static const struct snd_pcm_ops ct_pcm_capture_ops = {
|
|
.open = ct_pcm_capture_open,
|
|
.close = ct_pcm_capture_close,
|
|
.ioctl = snd_pcm_lib_ioctl,
|
|
.hw_params = ct_pcm_hw_params,
|
|
.hw_free = ct_pcm_hw_free,
|
|
.prepare = ct_pcm_capture_prepare,
|
|
.trigger = ct_pcm_capture_trigger,
|
|
.pointer = ct_pcm_capture_pointer,
|
|
.page = snd_pcm_sgbuf_ops_page,
|
|
};
|
|
|
|
static const struct snd_pcm_chmap_elem surround_map[] = {
|
|
{ .channels = 1,
|
|
.map = { SNDRV_CHMAP_MONO } },
|
|
{ .channels = 2,
|
|
.map = { SNDRV_CHMAP_RL, SNDRV_CHMAP_RR } },
|
|
{ }
|
|
};
|
|
|
|
static const struct snd_pcm_chmap_elem clfe_map[] = {
|
|
{ .channels = 1,
|
|
.map = { SNDRV_CHMAP_MONO } },
|
|
{ .channels = 2,
|
|
.map = { SNDRV_CHMAP_FC, SNDRV_CHMAP_LFE } },
|
|
{ }
|
|
};
|
|
|
|
static const struct snd_pcm_chmap_elem side_map[] = {
|
|
{ .channels = 1,
|
|
.map = { SNDRV_CHMAP_MONO } },
|
|
{ .channels = 2,
|
|
.map = { SNDRV_CHMAP_SL, SNDRV_CHMAP_SR } },
|
|
{ }
|
|
};
|
|
|
|
/* Create ALSA pcm device */
|
|
int ct_alsa_pcm_create(struct ct_atc *atc,
|
|
enum CTALSADEVS device,
|
|
const char *device_name)
|
|
{
|
|
struct snd_pcm *pcm;
|
|
const struct snd_pcm_chmap_elem *map;
|
|
int chs;
|
|
int err;
|
|
int playback_count, capture_count;
|
|
|
|
playback_count = (IEC958 == device) ? 1 : 256;
|
|
capture_count = (FRONT == device) ? 1 : 0;
|
|
err = snd_pcm_new(atc->card, "ctxfi", device,
|
|
playback_count, capture_count, &pcm);
|
|
if (err < 0) {
|
|
dev_err(atc->card->dev, "snd_pcm_new failed!! Err=%d\n",
|
|
err);
|
|
return err;
|
|
}
|
|
|
|
pcm->private_data = atc;
|
|
pcm->info_flags = 0;
|
|
pcm->dev_subclass = SNDRV_PCM_SUBCLASS_GENERIC_MIX;
|
|
strlcpy(pcm->name, device_name, sizeof(pcm->name));
|
|
|
|
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &ct_pcm_playback_ops);
|
|
|
|
if (FRONT == device)
|
|
snd_pcm_set_ops(pcm,
|
|
SNDRV_PCM_STREAM_CAPTURE, &ct_pcm_capture_ops);
|
|
|
|
snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV_SG,
|
|
snd_dma_pci_data(atc->pci), 128*1024, 128*1024);
|
|
|
|
chs = 2;
|
|
switch (device) {
|
|
case FRONT:
|
|
chs = 8;
|
|
map = snd_pcm_std_chmaps;
|
|
break;
|
|
case SURROUND:
|
|
map = surround_map;
|
|
break;
|
|
case CLFE:
|
|
map = clfe_map;
|
|
break;
|
|
case SIDE:
|
|
map = side_map;
|
|
break;
|
|
default:
|
|
map = snd_pcm_std_chmaps;
|
|
break;
|
|
}
|
|
err = snd_pcm_add_chmap_ctls(pcm, SNDRV_PCM_STREAM_PLAYBACK, map, chs,
|
|
0, NULL);
|
|
if (err < 0)
|
|
return err;
|
|
|
|
#ifdef CONFIG_PM_SLEEP
|
|
atc->pcms[device] = pcm;
|
|
#endif
|
|
|
|
return 0;
|
|
}
|