/*
* Intel SST Haswell/Broadwell PCM Support
*
* Copyright (C) 2013, Intel Corporation. All rights reserved.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License version
* 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*/
#include <linux/module.h>
#include <linux/dma-mapping.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/delay.h>
#include <asm/page.h>
#include <asm/pgtable.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/dmaengine_pcm.h>
#include <sound/soc.h>
#include <sound/tlv.h>
#include <sound/compress_driver.h>
#include "sst-haswell-ipc.h"
#include "sst-dsp-priv.h"
#include "sst-dsp.h"
#define HSW_PCM_COUNT 6
#define HSW_VOLUME_MAX 0x7FFFFFFF /* 0dB */
/* simple volume table */
static const u32 volume_map[] = {
HSW_VOLUME_MAX >> 30,
HSW_VOLUME_MAX >> 29,
HSW_VOLUME_MAX >> 28,
HSW_VOLUME_MAX >> 27,
HSW_VOLUME_MAX >> 26,
HSW_VOLUME_MAX >> 25,
HSW_VOLUME_MAX >> 24,
HSW_VOLUME_MAX >> 23,
HSW_VOLUME_MAX >> 22,
HSW_VOLUME_MAX >> 21,
HSW_VOLUME_MAX >> 20,
HSW_VOLUME_MAX >> 19,
HSW_VOLUME_MAX >> 18,
HSW_VOLUME_MAX >> 17,
HSW_VOLUME_MAX >> 16,
HSW_VOLUME_MAX >> 15,
HSW_VOLUME_MAX >> 14,
HSW_VOLUME_MAX >> 13,
HSW_VOLUME_MAX >> 12,
HSW_VOLUME_MAX >> 11,
HSW_VOLUME_MAX >> 10,
HSW_VOLUME_MAX >> 9,
HSW_VOLUME_MAX >> 8,
HSW_VOLUME_MAX >> 7,
HSW_VOLUME_MAX >> 6,
HSW_VOLUME_MAX >> 5,
HSW_VOLUME_MAX >> 4,
HSW_VOLUME_MAX >> 3,
HSW_VOLUME_MAX >> 2,
HSW_VOLUME_MAX >> 1,
HSW_VOLUME_MAX >> 0,
};
#define HSW_PCM_PERIODS_MAX 64
#define HSW_PCM_PERIODS_MIN 2
static const struct snd_pcm_hardware hsw_pcm_hardware = {
.info = SNDRV_PCM_INFO_MMAP |
SNDRV_PCM_INFO_MMAP_VALID |
SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_PAUSE |
SNDRV_PCM_INFO_RESUME |
SNDRV_PCM_INFO_NO_PERIOD_WAKEUP,
.formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FORMAT_S24_LE |
SNDRV_PCM_FMTBIT_S32_LE,
.period_bytes_min = PAGE_SIZE,
.period_bytes_max = (HSW_PCM_PERIODS_MAX / HSW_PCM_PERIODS_MIN) * PAGE_SIZE,
.periods_min = HSW_PCM_PERIODS_MIN,
.periods_max = HSW_PCM_PERIODS_MAX,
.buffer_bytes_max = HSW_PCM_PERIODS_MAX * PAGE_SIZE,
};
/* private data for each PCM DSP stream */
struct hsw_pcm_data {
int dai_id;
struct sst_hsw_stream *stream;
u32 volume[2];
struct snd_pcm_substream *substream;
struct snd_compr_stream *cstream;
unsigned int wpos;
struct mutex mutex;
bool allocated;
};
/* private data for the driver */
struct hsw_priv_data {
/* runtime DSP */
struct sst_hsw *hsw;
/* page tables */
struct snd_dma_buffer dmab[HSW_PCM_COUNT][2];
/* DAI data */
struct hsw_pcm_data pcm[HSW_PCM_COUNT];
};
static u32 hsw_notify_pointer(struct sst_hsw_stream *stream, void *data);
static inline u32 hsw_mixer_to_ipc(unsigned int value)
{
if (value >= ARRAY_SIZE(volume_map))
return volume_map[0];
else
return volume_map[value];
}
static inline unsigned int hsw_ipc_to_mixer(u32 value)
{
int i;
for (i = 0; i < ARRAY_SIZE(volume_map); i++) {
if (volume_map[i] >= value)
return i;
}
return i - 1;
}
static int hsw_stream_volume_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_platform *platform = snd_kcontrol_chip(kcontrol);
struct soc_mixer_control *mc =
(struct soc_mixer_control *)kcontrol->private_value;
struct hsw_priv_data *pdata =
snd_soc_platform_get_drvdata(platform);
struct hsw_pcm_data *pcm_data = &pdata->pcm[mc->reg];
struct sst_hsw *hsw = pdata->hsw;
u32 volume;
mutex_lock(&pcm_data->mutex);
if (!pcm_data->stream) {
pcm_data->volume[0] =
hsw_mixer_to_ipc(ucontrol->value.integer.value[0]);
pcm_data->volume[1] =
hsw_mixer_to_ipc(ucontrol->value.integer.value[1]);
mutex_unlock(&pcm_data->mutex);
return 0;
}
if (ucontrol->value.integer.value[0] ==
ucontrol->value.integer.value[1]) {
volume = hsw_mixer_to_ipc(ucontrol->value.integer.value[0]);
sst_hsw_stream_set_volume(hsw, pcm_data->stream, 0, 2, volume);
} else {
volume = hsw_mixer_to_ipc(ucontrol->value.int