1831 lines
50 KiB
C
1831 lines
50 KiB
C
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
/*
|
|
* ALSA SoC Audio Layer - Rockchip ASRC Controller driver
|
|
*
|
|
* Copyright (c) 2024 Rockchip Electronics Co., Ltd.
|
|
*/
|
|
|
|
#include <linux/clk.h>
|
|
#include <linux/clk-provider.h>
|
|
#include <linux/dma-mapping.h>
|
|
#include <linux/mfd/syscon.h>
|
|
#include <linux/module.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/pm_runtime.h>
|
|
#include <linux/reset.h>
|
|
#include <sound/core.h>
|
|
#include <sound/dmaengine_pcm.h>
|
|
#include <sound/pcm.h>
|
|
#include <sound/pcm_params.h>
|
|
#include <sound/soc.h>
|
|
#include <sound/tlv.h>
|
|
|
|
#include "rockchip_asrc.h"
|
|
|
|
/*
|
|
* structure:
|
|
* tx: memory->asrc->sai->codec
|
|
* rx: codec->sai->asrc->memory
|
|
* dma structure:
|
|
* memory -> asrc dma_tx -> asrc tx_mem -> sai dma_tx -> codec
|
|
* codec -> sai dma_rx -> asrc rx_mem -> asrc dma_rx -> memory
|
|
*
|
|
* So the asrc and other dai's dma driver must be added in the component.
|
|
*
|
|
* The asoc path with asrc should be prepared in rockchip_multicodecs.c
|
|
*/
|
|
|
|
#define DRV_NAME "rockchip-asrc"
|
|
/* directions */
|
|
#define IN 0
|
|
#define OUT 1
|
|
#define MAXBURST_PER_FIFO 8
|
|
#define DEFAULT_SAMPLE_RATE 48000
|
|
#define ASRC_DEFAULT_CLK 200000000
|
|
|
|
/* Platform Definition */
|
|
/* rk3576 */
|
|
#define RK3576_SYS_GRF_SOC_CON9 0x24
|
|
#define ASRC0_4CH_SRC_SEL_SHIFT 0
|
|
#define ASRC0_4CH_SRC_SEL_MASK (0x1f << (ASRC0_4CH_SRC_SEL_SHIFT + 16))
|
|
#define ASRC0_4CH_SRC_SEL(x) (x << ASRC0_4CH_SRC_SEL_SHIFT)
|
|
#define ASRC0_4CH_DST_SEL_SHIFT 5
|
|
#define ASRC0_4CH_DST_SEL_MASK (0x1f << (ASRC0_4CH_DST_SEL_SHIFT + 16))
|
|
#define ASRC0_4CH_DST_SEL(x) (x << ASRC0_4CH_DST_SEL_SHIFT)
|
|
#define ASRC1_4CH_SRC_SEL_SHIFT 10
|
|
#define ASRC1_4CH_SRC_SEL_MASK (0x1f << (ASRC1_4CH_SRC_SEL_SHIFT + 16))
|
|
#define ASRC1_4CH_SRC_SEL(x) (x << (ASRC1_4CH_SRC_SEL_SHIFT))
|
|
|
|
#define RK3576_SYS_GRF_SOC_CON10 0x28
|
|
#define ASRC1_4CH_DST_SEL_SHIFT 0
|
|
#define ASRC1_4CH_DST_SEL_MASK (0x1f << (ASRC1_4CH_DST_SEL_SHIFT + 16))
|
|
#define ASRC1_4CH_DST_SEL(x) (x << ASRC1_4CH_DST_SEL_SHIFT)
|
|
#define ASRC2_2CH_SRC_SEL_SHIFT 5
|
|
#define ASRC2_2CH_SRC_SEL_MASK (0x1f << (ASRC2_2CH_SRC_SEL_SHIFT+16))
|
|
#define ASRC2_2CH_SRC_SEL(x) (x << ASRC2_2CH_SRC_SEL_SHIFT)
|
|
#define ASRC2_2CH_DST_SEL_SHIFT 10
|
|
#define ASRC2_2CH_DST_SEL_MASK (0x1f << (ASRC2_2CH_DST_SEL_SHIFT + 16))
|
|
#define ASRC2_2CH_DST_SEL(x) (x << ASRC2_2CH_DST_SEL_SHIFT)
|
|
|
|
#define RK3576_SYS_GRF_SOC_CON11 0x2c
|
|
#define ASRC3_2CH_SRC_SEL_SHIFT 0
|
|
#define ASRC3_2CH_SRC_SEL_MASK (0x1f << (ASRC3_2CH_SRC_SEL_SHIFT + 16))
|
|
#define ASRC3_2CH_SRC_SEL(x) (x << ASRC3_2CH_SRC_SEL_SHIFT)
|
|
#define ASRC3_2CH_DST_SEL_SHIFT 5
|
|
#define ASRC3_2CH_DST_SEL_MASK (0x1f << (ASRC3_2CH_DST_SEL_SHIFT + 16))
|
|
#define ASRC3_2CH_DST_SEL(x) (x << ASRC3_2CH_DST_SEL_SHIFT)
|
|
|
|
#define RK3576_SRC_LRCK_FROM_SAI0 0x0
|
|
#define RK3576_SRC_LRCK_FROM_SAI1 0x1
|
|
#define RK3576_SRC_LRCK_FROM_SAI2 0x2
|
|
#define RK3576_SRC_LRCK_FROM_SAI3 0x3
|
|
#define RK3576_SRC_LRCK_FROM_SAI4 0x4
|
|
#define RK3576_SRC_LRCK_FROM_SAI5 0x5
|
|
#define RK3576_SRC_LRCK_FROM_SAI6 0x6
|
|
#define RK3576_SRC_LRCK_FROM_SAI7 0x7
|
|
#define RK3576_SRC_LRCK_FROM_SAI8 0x8
|
|
#define RK3576_SRC_LRCK_FROM_SAI9 0x9
|
|
#define RK3576_SRC_LRCK_FROM_PDM0 0xa
|
|
#define RK3576_SRC_LRCK_FROM_PDM1 0xb
|
|
#define RK3576_SRC_LRCK_FROM_SPDIF_RX0 0xc
|
|
#define RK3576_SRC_LRCK_FROM_SPDIF_RX1 0xd
|
|
#define RK3576_SRC_LRCK_FROM_SPDIF_RX2 0xe
|
|
#define RK3576_SRC_LRCK_FROM_CRU0 0xf
|
|
#define RK3576_SRC_LRCK_FROM_CRU1 0x10
|
|
|
|
#define RK3576_DST_LRCK_FROM_SAI0 0x0
|
|
#define RK3576_DST_LRCK_FROM_SAI1 0x1
|
|
#define RK3576_DST_LRCK_FROM_SAI2 0x2
|
|
#define RK3576_DST_LRCK_FROM_SAI3 0x3
|
|
#define RK3576_DST_LRCK_FROM_SAI4 0x4
|
|
#define RK3576_DST_LRCK_FROM_SAI5 0x5
|
|
#define RK3576_DST_LRCK_FROM_SAI6 0x6
|
|
#define RK3576_DST_LRCK_FROM_SAI7 0x7
|
|
#define RK3576_DST_LRCK_FROM_SAI8 0x8
|
|
#define RK3576_DST_LRCK_FROM_SAI9 0x9
|
|
#define RK3576_DST_LRCK_FROM_SPDIF_TX0 0xa
|
|
#define RK3576_DST_LRCK_FROM_SPDIF_TX1 0xb
|
|
#define RK3576_DST_LRCK_FROM_SPDIF_TX2 0xc
|
|
#define RK3576_DST_LRCK_FROM_SPDIF_TX3 0xd
|
|
#define RK3576_DST_LRCK_FROM_SPDIF_TX4 0xe
|
|
#define RK3576_DST_LRCK_FROM_SPDIF_TX5 0xf
|
|
#define RK3576_DST_LRCK_FROM_CRU0 0x10
|
|
#define RK3576_DST_LRCK_FROM_CRU1 0x11
|
|
|
|
#define RK3576_ASRC0 0x2a690000
|
|
#define RK3576_ASRC1 0x2a6a0000
|
|
#define RK3576_ASRC2 0x2a6b0000
|
|
#define RK3576_ASRC3 0x2a6c0000
|
|
|
|
/* Common Definition */
|
|
#define DAI_ID_UNKNOWN -1
|
|
#define DAI_ID_ASRC0 0
|
|
#define DAI_ID_ASRC1 1
|
|
#define DAI_ID_ASRC2 2
|
|
#define DAI_ID_ASRC3 3
|
|
#define DAI_ID_ASRC4 4
|
|
#define DAI_ID_ASRC5 5
|
|
#define DAI_ID_ASRC6 6
|
|
#define DAI_ID_ASRC7 7
|
|
#define DAI_ID_ASRC8 8
|
|
#define DAI_ID_ASRC9 9
|
|
#define DAI_ID_ASRC10 10
|
|
#define DAI_ID_ASRC11 11
|
|
#define DAI_ID_ASRC12 12
|
|
#define DAI_ID_ASRC13 13
|
|
#define DAI_ID_ASRC14 14
|
|
#define DAI_ID_ASRC15 15
|
|
#define DAI_ID_SAI0 16
|
|
#define DAI_ID_SAI1 17
|
|
#define DAI_ID_SAI2 18
|
|
#define DAI_ID_SAI3 19
|
|
#define DAI_ID_SAI4 20
|
|
#define DAI_ID_SAI5 21
|
|
#define DAI_ID_SAI6 22
|
|
#define DAI_ID_SAI7 23
|
|
#define DAI_ID_SAI8 24
|
|
#define DAI_ID_SAI9 25
|
|
#define DAI_ID_SAI10 26
|
|
#define DAI_ID_SAI11 27
|
|
#define DAI_ID_SAI12 28
|
|
#define DAI_ID_SAI13 29
|
|
#define DAI_ID_SAI14 30
|
|
#define DAI_ID_SAI15 31
|
|
#define DAI_ID_PDM0 32
|
|
#define DAI_ID_PDM1 33
|
|
#define DAI_ID_PDM2 34
|
|
#define DAI_ID_PDM3 35
|
|
#define DAI_ID_PDM4 36
|
|
#define DAI_ID_PDM5 37
|
|
#define DAI_ID_PDM6 38
|
|
#define DAI_ID_PDM7 39
|
|
#define DAI_ID_SPDIF_TX0 40
|
|
#define DAI_ID_SPDIF_TX1 41
|
|
#define DAI_ID_SPDIF_TX2 42
|
|
#define DAI_ID_SPDIF_TX3 43
|
|
#define DAI_ID_SPDIF_TX4 44
|
|
#define DAI_ID_SPDIF_TX5 45
|
|
#define DAI_ID_SPDIF_TX6 46
|
|
#define DAI_ID_SPDIF_TX7 47
|
|
#define DAI_ID_SPDIF_RX0 48
|
|
#define DAI_ID_SPDIF_RX1 49
|
|
#define DAI_ID_SPDIF_RX2 50
|
|
#define DAI_ID_SPDIF_RX3 51
|
|
#define DAI_ID_SPDIF_RX4 52
|
|
#define DAI_ID_SPDIF_RX5 53
|
|
#define DAI_ID_SPDIF_RX6 54
|
|
#define DAI_ID_SPDIF_RX7 55
|
|
#define DAI_ID_MAX 64
|
|
|
|
struct rk_asrc_soc_data {
|
|
int (*lrck_clk_init)(struct device *dev);
|
|
int (*lrck_clk_set)(struct device *dev);
|
|
int (*lrck_clk_en)(struct device *dev);
|
|
int (*lrck_clk_dis)(struct device *dev);
|
|
int lrck_source_freq;
|
|
};
|
|
|
|
struct rockchip_asrc_pair {
|
|
struct rockchip_asrc *asrc;
|
|
unsigned int error;
|
|
unsigned int channels;
|
|
struct dma_async_tx_descriptor *desc[2];
|
|
struct dma_chan *dma_chan[2];
|
|
unsigned int pos;
|
|
bool req_dma_chan;
|
|
void *private;
|
|
void *private_m2m;
|
|
};
|
|
|
|
struct rockchip_asrc {
|
|
struct device *dev;
|
|
struct platform_device *pdev;
|
|
struct regmap *grf;
|
|
struct regmap *regmap;
|
|
struct clk *mclk;
|
|
struct clk *hclk;
|
|
struct clk *cru_src0; // Used if src or dst is MEM.
|
|
struct clk *cru_src1;
|
|
struct clk *src_lrck;
|
|
struct clk *dst_lrck;
|
|
struct clk *src_lrck_parent;
|
|
struct clk *dst_lrck_parent;
|
|
struct list_head clk_list_head;
|
|
struct snd_pcm_substream *substreams[SNDRV_PCM_STREAM_LAST + 1];
|
|
const struct rk_asrc_soc_data *soc_data;
|
|
struct rockchip_asrc_pair *pair[2];
|
|
struct snd_dmaengine_dai_dma_data dma_data_rx;
|
|
struct snd_dmaengine_dai_dma_data dma_data_tx;
|
|
dma_addr_t paddr;
|
|
int chan_num;
|
|
int sample_bits;
|
|
int sample_rate;
|
|
int resample_rate;
|
|
int dst_link_dai_id; /* This must be set firstly by amixer/tinymixer */
|
|
int src_link_dai_id; /* This must be set firstly by amixer/tinymixer */
|
|
};
|
|
|
|
static int rockchip_asrc_calculate_ratio(struct rockchip_asrc *asrc,
|
|
int numerator, int denominator)
|
|
{
|
|
int i, integerPart, remainder, ratio, digit;
|
|
unsigned int temp = 0;
|
|
|
|
if (denominator == 0) {
|
|
dev_err(asrc->dev, "The denominator can not be zero.\n");
|
|
return 0;
|
|
}
|
|
|
|
integerPart = numerator / denominator;
|
|
remainder = numerator % denominator;
|
|
ratio = integerPart << 22;
|
|
|
|
for (i = 0; i < 8; i++) {
|
|
remainder <<= 4;
|
|
digit = remainder / denominator;
|
|
temp |= (digit << (28 - i * 4));
|
|
remainder %= denominator;
|
|
}
|
|
|
|
ratio += (temp >> 10);
|
|
|
|
return ratio;
|
|
}
|
|
|
|
static int rockchip_asrc_calculate_dma_thresh(int dma_burst, struct rockchip_asrc *asrc)
|
|
{
|
|
int n = asrc->sample_bits == 16 ? 1 : 0;
|
|
|
|
return dma_burst * (n + 1) / (asrc->chan_num + 1) / 2 - 1;
|
|
}
|
|
|
|
static bool rockchip_asrc_is_link_mem(int dai_id)
|
|
{
|
|
return (DAI_ID_ASRC0 <= dai_id && dai_id <= DAI_ID_ASRC15);
|
|
}
|
|
|
|
static struct dma_chan *rockchip_asrc_get_dma_channel(struct rockchip_asrc *asrc, bool dir)
|
|
{
|
|
struct dma_chan *chan = NULL;
|
|
|
|
if (dir == OUT)
|
|
chan = dma_request_chan(asrc->dev, "rx");
|
|
else
|
|
chan = dma_request_chan(asrc->dev, "tx");
|
|
|
|
return chan;
|
|
}
|
|
|
|
static void rockchip_asrc_ratio_update(struct rockchip_asrc *asrc, int stream)
|
|
{
|
|
/* get the sampling frequency, then set the ratio */
|
|
regmap_write(asrc->regmap, ASRC_SAMPLE_RATE, asrc->sample_rate);
|
|
regmap_write(asrc->regmap, ASRC_RESAMPLE_RATE, asrc->resample_rate);
|
|
|
|
regmap_write(asrc->regmap, ASRC_MANUAL_RATIO,
|
|
rockchip_asrc_calculate_ratio(asrc, asrc->sample_rate, asrc->resample_rate));
|
|
}
|
|
|
|
static int rockchip_asrc_lrck_clks_set(struct rockchip_asrc *asrc)
|
|
{
|
|
if (asrc->src_link_dai_id < 0 || asrc->dst_link_dai_id < 0) {
|
|
dev_warn(asrc->dev, "Invalid DAI_ID, Please set dai id by amixer or tinymix firstly!\n");
|
|
return -EINVAL;
|
|
}
|
|
/*
|
|
* Set which device attach to asrc, then set their
|
|
* clks which connect to asrc.
|
|
*/
|
|
return asrc->soc_data->lrck_clk_set(asrc->dev);
|
|
}
|
|
|
|
static int rockchip_asrc_lrck_clks_en(struct rockchip_asrc *asrc)
|
|
{
|
|
return asrc->soc_data->lrck_clk_en(asrc->dev);
|
|
}
|
|
|
|
static int rockchip_asrc_lrck_clks_dis(struct rockchip_asrc *asrc)
|
|
{
|
|
return asrc->soc_data->lrck_clk_dis(asrc->dev);
|
|
}
|
|
|
|
static int rockchip_asrc_start(struct rockchip_asrc *asrc, int stream)
|
|
{
|
|
unsigned int val = 0;
|
|
int ret;
|
|
|
|
ret = rockchip_asrc_lrck_clks_set(asrc);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = rockchip_asrc_lrck_clks_en(asrc);
|
|
if (ret)
|
|
return ret;
|
|
|
|
rockchip_asrc_ratio_update(asrc, stream);
|
|
/* Set the real time here */
|
|
if (rockchip_asrc_is_link_mem(asrc->src_link_dai_id) &&
|
|
rockchip_asrc_is_link_mem(asrc->dst_link_dai_id))
|
|
val = ASRC_M2M;
|
|
else if (rockchip_asrc_is_link_mem(asrc->src_link_dai_id) &&
|
|
!rockchip_asrc_is_link_mem(asrc->dst_link_dai_id))
|
|
val = ASRC_M2D;
|
|
else if (!rockchip_asrc_is_link_mem(asrc->src_link_dai_id) &&
|
|
rockchip_asrc_is_link_mem(asrc->dst_link_dai_id))
|
|
val = ASRC_S2M;
|
|
else
|
|
val = ASRC_S2D;
|
|
|
|
regmap_update_bits(asrc->regmap, ASRC_CON,
|
|
ASRC_MODE_MSK | ASRC_OUT_MSK |
|
|
ASRC_IN_MSK | ASRC_REAL_TIME_MODE_MSK,
|
|
ASRC_REAL_TIME | ASRC_OUT_START |
|
|
ASRC_IN_START | val);
|
|
regmap_update_bits(asrc->regmap, ASRC_INT_CON,
|
|
ASRC_CONV_ERROR_MSK, ASRC_CONV_ERROR_EN);
|
|
/* Now the dma is single direction */
|
|
regmap_update_bits(asrc->regmap, ASRC_CON, ASRC_MSK, ASRC_EN);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int rockchip_asrc_stop(struct rockchip_asrc *asrc, int stream)
|
|
{
|
|
regmap_update_bits(asrc->regmap, ASRC_CON,
|
|
ASRC_OUT_MSK | ASRC_IN_MSK,
|
|
ASRC_OUT_STOP | ASRC_IN_STOP);
|
|
regmap_update_bits(asrc->regmap, ASRC_INT_CON,
|
|
ASRC_CONV_ERROR_MSK, ASRC_CONV_ERROR_DIS);
|
|
regmap_update_bits(asrc->regmap, ASRC_CON, ASRC_MSK, ASRC_DIS);
|
|
rockchip_asrc_lrck_clks_dis(asrc);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int rockchip_asrc_startup(struct snd_pcm_substream *substream,
|
|
struct snd_soc_dai *dai)
|
|
{
|
|
struct rockchip_asrc *asrc = snd_soc_dai_get_drvdata(dai);
|
|
|
|
if (asrc->substreams[substream->stream])
|
|
return -EBUSY;
|
|
|
|
asrc->substreams[substream->stream] = substream;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void rockchip_asrc_shutdown(struct snd_pcm_substream *substream,
|
|
struct snd_soc_dai *dai)
|
|
{
|
|
struct rockchip_asrc *asrc = snd_soc_dai_get_drvdata(dai);
|
|
|
|
asrc->substreams[substream->stream] = NULL;
|
|
}
|
|
|
|
static int rockchip_asrc_hw_params(struct snd_pcm_substream *substream,
|
|
struct snd_pcm_hw_params *params,
|
|
struct snd_soc_dai *dai)
|
|
{
|
|
struct rockchip_asrc *asrc = snd_soc_dai_get_drvdata(dai);
|
|
unsigned int val;
|
|
|
|
/* Set sample rate and resample rate */
|
|
asrc->sample_rate = params_rate(params);
|
|
|
|
/* Set channel */
|
|
regmap_update_bits(asrc->regmap, ASRC_CON, ASRC_CHAN_NUM_MSK,
|
|
ASRC_CHAN_NUM(params_channels(params)));
|
|
asrc->chan_num = (params_channels(params) - 2) / 2;
|
|
/* Set size */
|
|
switch (params_format(params)) {
|
|
case SNDRV_PCM_FORMAT_S16_LE:
|
|
val = ASRC_IWL_16BIT | ASRC_OWL_16BIT |
|
|
ASRC_OFMT_16 | ASRC_IFMT_16;
|
|
asrc->sample_bits = 16;
|
|
break;
|
|
case SNDRV_PCM_FORMAT_S24_LE:
|
|
case SNDRV_PCM_FORMAT_S32_LE:
|
|
val = ASRC_IWL_24BIT | ASRC_OWL_24BIT |
|
|
ASRC_OFMT_32 | ASRC_IFMT_32;
|
|
asrc->sample_bits = 32;
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
regmap_update_bits(asrc->regmap, ASRC_DATA_FMT,
|
|
ASRC_OWL_MSK | ASRC_IWL_MSK |
|
|
ASRC_OFMT_MSK | ASRC_IFMT_MSK,
|
|
val);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int rockchip_asrc_trigger(struct snd_pcm_substream *substream,
|
|
int cmd, struct snd_soc_dai *dai)
|
|
{
|
|
struct rockchip_asrc *asrc = snd_soc_dai_get_drvdata(dai);
|
|
int ret = 0;
|
|
|
|
switch (cmd) {
|
|
case SNDRV_PCM_TRIGGER_START:
|
|
case SNDRV_PCM_TRIGGER_RESUME:
|
|
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
|
|
ret = rockchip_asrc_start(asrc, substream->stream);
|
|
break;
|
|
case SNDRV_PCM_TRIGGER_SUSPEND:
|
|
case SNDRV_PCM_TRIGGER_STOP:
|
|
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
|
|
ret = rockchip_asrc_stop(asrc, substream->stream);
|
|
break;
|
|
default:
|
|
ret = -EINVAL;
|
|
break;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int rockchip_asrc_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
|
|
{
|
|
/* No need to set here */
|
|
return 0;
|
|
}
|
|
|
|
static int rockchip_asrc_set_sysclk(struct snd_soc_dai *dai, int clk_id,
|
|
unsigned int freq, int dir)
|
|
{
|
|
struct rockchip_asrc *asrc = snd_soc_dai_get_drvdata(dai);
|
|
int ret;
|
|
|
|
/* Set the module clock */
|
|
ret = clk_set_rate(asrc->mclk, ASRC_DEFAULT_CLK);
|
|
if (ret)
|
|
dev_err(asrc->dev, "Failed to set mclk %d\n", ret);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct snd_soc_dai_ops rockchip_asrc_dai_ops = {
|
|
.startup = rockchip_asrc_startup,
|
|
.shutdown = rockchip_asrc_shutdown,
|
|
.hw_params = rockchip_asrc_hw_params,
|
|
.trigger = rockchip_asrc_trigger,
|
|
.set_fmt = rockchip_asrc_set_fmt,
|
|
.set_sysclk = rockchip_asrc_set_sysclk,
|
|
};
|
|
|
|
static int rockchip_asrc_dai_probe(struct snd_soc_dai *dai)
|
|
{
|
|
struct rockchip_asrc *asrc = snd_soc_dai_get_drvdata(dai);
|
|
|
|
snd_soc_dai_init_dma_data(dai, &asrc->dma_data_tx,
|
|
&asrc->dma_data_rx);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct snd_soc_dai_driver rockchip_asrc_dai = {
|
|
.playback = {
|
|
.stream_name = "Playback",
|
|
.channels_min = 1,
|
|
.channels_max = 4,
|
|
.rates = SNDRV_PCM_RATE_8000_192000,
|
|
.formats = SNDRV_PCM_FMTBIT_S16_LE |
|
|
SNDRV_PCM_FMTBIT_S24_LE |
|
|
SNDRV_PCM_FMTBIT_S32_LE,
|
|
},
|
|
.capture = {
|
|
.stream_name = "Capture",
|
|
.channels_min = 1,
|
|
.channels_max = 4,
|
|
.rates = SNDRV_PCM_RATE_8000_192000,
|
|
.formats = SNDRV_PCM_FMTBIT_S16_LE |
|
|
SNDRV_PCM_FMTBIT_S24_LE |
|
|
SNDRV_PCM_FMTBIT_S32_LE,
|
|
},
|
|
.probe = rockchip_asrc_dai_probe,
|
|
.ops = &rockchip_asrc_dai_ops,
|
|
.symmetric_rate = 1,
|
|
};
|
|
|
|
static bool rockchip_asrc_readable_reg(struct device *dev, unsigned int reg)
|
|
{
|
|
switch (reg) {
|
|
case ASRC_VERSION:
|
|
case ASRC_CON:
|
|
case ASRC_CLKDIV_CON:
|
|
case ASRC_DATA_FMT:
|
|
case ASRC_LOOP_CON0:
|
|
case ASRC_LOOP_CON1:
|
|
case ASRC_LOOP_CON2:
|
|
case ASRC_MANUAL_RATIO:
|
|
case ASRC_SAMPLE_RATE:
|
|
case ASRC_RESAMPLE_RATE:
|
|
case ASRC_TRACK_PERIOD:
|
|
case ASRC_RATIO_MARGIN:
|
|
case ASRC_LRCK_MARGIN:
|
|
case ASRC_FETCH_LEN:
|
|
case ASRC_DMA_THRESH:
|
|
case ASRC_INT_CON:
|
|
case ASRC_INT_ST:
|
|
case ASRC_ST:
|
|
case ASRC_RATIO_ST:
|
|
case ASRC_RESAMPLE_RATE_ST:
|
|
case ASRC_THETA_CNT_ST:
|
|
case ASRC_DECI_THETA_ACC_ST:
|
|
case ASRC_FIFO_IN_WRCNT:
|
|
case ASRC_FIFO_IN_RDCNT:
|
|
case ASRC_FIFO_OUT_WRCNT:
|
|
case ASRC_FIFO_OUT_RDCNT:
|
|
case ASRC_RXDR:
|
|
case ASRC_TXDR:
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
static bool rockchip_asrc_writeable_reg(struct device *dev, unsigned int reg)
|
|
{
|
|
switch (reg) {
|
|
case ASRC_CON:
|
|
case ASRC_CLKDIV_CON:
|
|
case ASRC_DATA_FMT:
|
|
case ASRC_LOOP_CON0:
|
|
case ASRC_LOOP_CON1:
|
|
case ASRC_LOOP_CON2:
|
|
case ASRC_MANUAL_RATIO:
|
|
case ASRC_SAMPLE_RATE:
|
|
case ASRC_RESAMPLE_RATE:
|
|
case ASRC_TRACK_PERIOD:
|
|
case ASRC_RATIO_MARGIN:
|
|
case ASRC_LRCK_MARGIN:
|
|
case ASRC_FETCH_LEN:
|
|
case ASRC_DMA_THRESH:
|
|
case ASRC_INT_CON:
|
|
case ASRC_INT_ST:
|
|
case ASRC_FIFO_IN_WRCNT:
|
|
case ASRC_FIFO_IN_RDCNT:
|
|
case ASRC_RXDR:
|
|
case ASRC_TXDR:
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
static bool rockchip_asrc_volatile_reg(struct device *dev, unsigned int reg)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
static bool rockchip_asrc_precious_reg(struct device *dev, unsigned int reg)
|
|
{
|
|
if (reg == ASRC_RXDR)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
static const struct reg_default rockchip_asrc_reg[] = {
|
|
{ ASRC_VERSION, 0x00000001 },
|
|
};
|
|
|
|
static const struct regmap_config rockchip_asrc_regmap_config = {
|
|
.reg_bits = 32,
|
|
.reg_stride = 4,
|
|
.val_bits = 32,
|
|
.max_register = ASRC_FIFO_OUT_DATA,
|
|
.reg_defaults = rockchip_asrc_reg,
|
|
.num_reg_defaults = ARRAY_SIZE(rockchip_asrc_reg),
|
|
.readable_reg = rockchip_asrc_readable_reg,
|
|
.writeable_reg = rockchip_asrc_writeable_reg,
|
|
.volatile_reg = rockchip_asrc_volatile_reg,
|
|
.precious_reg = rockchip_asrc_precious_reg,
|
|
.cache_type = REGCACHE_FLAT,
|
|
};
|
|
|
|
#define RK_ASRC_DMABUF_SIZE (16 * 1024)
|
|
|
|
static struct snd_pcm_hardware snd_rk_hardware = {
|
|
.info = SNDRV_PCM_INFO_INTERLEAVED |
|
|
SNDRV_PCM_INFO_BLOCK_TRANSFER |
|
|
SNDRV_PCM_INFO_MMAP |
|
|
SNDRV_PCM_INFO_MMAP_VALID,
|
|
.buffer_bytes_max = RK_ASRC_DMABUF_SIZE,
|
|
.period_bytes_min = 128,
|
|
.period_bytes_max = 0x2000, /* Limited by asrc, max 0x4000 */
|
|
.periods_min = 2,
|
|
.periods_max = 2,
|
|
.fifo_size = 0,
|
|
};
|
|
|
|
static void rockchip_asrc_dma_complete(void *arg)
|
|
{
|
|
struct snd_pcm_substream *substream = arg;
|
|
struct snd_pcm_runtime *runtime = substream->runtime;
|
|
struct rockchip_asrc_pair *pair = runtime->private_data;
|
|
|
|
pair->pos += snd_pcm_lib_period_bytes(substream);
|
|
if (pair->pos >= snd_pcm_lib_buffer_bytes(substream))
|
|
pair->pos = 0;
|
|
|
|
snd_pcm_period_elapsed(substream);
|
|
}
|
|
|
|
static int rockchip_asrc_dma_prepare_and_submit(struct snd_pcm_substream *substream,
|
|
struct snd_soc_component *component)
|
|
{
|
|
u8 dir = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? OUT : IN;
|
|
struct snd_pcm_runtime *runtime = substream->runtime;
|
|
struct rockchip_asrc_pair *pair = runtime->private_data;
|
|
struct rockchip_asrc *asrc = pair->asrc;
|
|
struct device *dev = component->dev;
|
|
unsigned long flags = DMA_CTRL_ACK;
|
|
|
|
/* Prepare and submit Front-End DMA channel */
|
|
if (!substream->runtime->no_period_wakeup)
|
|
flags |= DMA_PREP_INTERRUPT;
|
|
|
|
pair->pos = 0;
|
|
pair->desc[!dir] = dmaengine_prep_dma_cyclic(
|
|
pair->dma_chan[!dir], runtime->dma_addr,
|
|
snd_pcm_lib_buffer_bytes(substream),
|
|
snd_pcm_lib_period_bytes(substream),
|
|
dir == OUT ? DMA_MEM_TO_DEV : DMA_DEV_TO_MEM, flags);
|
|
if (!pair->desc[!dir]) {
|
|
dev_err(dev, "failed to prepare slave DMA for Front-End\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
pair->desc[!dir]->callback = rockchip_asrc_dma_complete;
|
|
pair->desc[!dir]->callback_param = substream;
|
|
|
|
dmaengine_submit(pair->desc[!dir]);
|
|
|
|
/* Prepare and submit Back-End DMA channel */
|
|
pair->desc[dir] = dmaengine_prep_dma_cyclic(
|
|
pair->dma_chan[dir],
|
|
dir == OUT ? (asrc->paddr + ASRC_FIFO_OUT_DATA) : (asrc->paddr + ASRC_FIFO_IN_DATA),
|
|
RK_ASRC_DMABUF_SIZE, RK_ASRC_DMABUF_SIZE / 2,
|
|
dir == OUT ? DMA_MEM_TO_DEV : DMA_DEV_TO_MEM, 0);
|
|
if (!pair->desc[dir]) {
|
|
dev_err(dev, "failed to prepare slave DMA for Back-End\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
dmaengine_submit(pair->desc[dir]);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int rockchip_asrc_dma_trigger(struct snd_soc_component *component,
|
|
struct snd_pcm_substream *substream, int cmd)
|
|
{
|
|
struct snd_pcm_runtime *runtime = substream->runtime;
|
|
struct rockchip_asrc_pair *pair = runtime->private_data;
|
|
int ret;
|
|
|
|
switch (cmd) {
|
|
case SNDRV_PCM_TRIGGER_START:
|
|
case SNDRV_PCM_TRIGGER_RESUME:
|
|
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
|
|
ret = rockchip_asrc_dma_prepare_and_submit(substream, component);
|
|
if (ret)
|
|
return ret;
|
|
dma_async_issue_pending(pair->dma_chan[IN]);
|
|
dma_async_issue_pending(pair->dma_chan[OUT]);
|
|
break;
|
|
case SNDRV_PCM_TRIGGER_STOP:
|
|
case SNDRV_PCM_TRIGGER_SUSPEND:
|
|
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
|
|
dmaengine_terminate_async(pair->dma_chan[OUT]);
|
|
dmaengine_terminate_async(pair->dma_chan[IN]);
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int rockchip_asrc_dma_hw_params(struct snd_soc_component *component,
|
|
struct snd_pcm_substream *substream,
|
|
struct snd_pcm_hw_params *params)
|
|
{
|
|
struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
|
|
bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
|
|
struct snd_dmaengine_dai_dma_data *dma_params_fe = NULL;
|
|
struct snd_dmaengine_dai_dma_data *dma_params_be = NULL;
|
|
struct snd_pcm_runtime *runtime = substream->runtime;
|
|
struct rockchip_asrc_pair *pair = runtime->private_data;
|
|
struct dma_chan *tmp_chan = NULL, *be_chan = NULL;
|
|
struct snd_soc_component *component_be = NULL;
|
|
struct rockchip_asrc *asrc = pair->asrc;
|
|
struct dma_slave_config config_fe = {}, config_be = {};
|
|
struct device *dev = component->dev;
|
|
int stream = substream->stream;
|
|
struct snd_soc_dpcm *dpcm;
|
|
struct device *dev_be;
|
|
u8 dir = tx ? OUT : IN;
|
|
dma_cap_mask_t mask;
|
|
int ret, dma_thresh;
|
|
|
|
/* Fetch the Back-End dma_data from DPCM */
|
|
for_each_dpcm_be(rtd, stream, dpcm) {
|
|
struct snd_soc_pcm_runtime *be = dpcm->be;
|
|
struct snd_pcm_substream *substream_be;
|
|
struct snd_soc_dai *dai = asoc_rtd_to_cpu(be, 0);
|
|
|
|
if (dpcm->fe != rtd)
|
|
continue;
|
|
|
|
substream_be = snd_soc_dpcm_get_substream(be, stream);
|
|
dma_params_be = snd_soc_dai_get_dma_data(dai, substream_be);
|
|
dev_be = dai->dev;
|
|
break;
|
|
}
|
|
|
|
if (!dma_params_be) {
|
|
dev_err(dev, "failed to get the substream of Back-End\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* Override dma_data of the Front-End and config its dmaengine */
|
|
dma_params_fe = snd_soc_dai_get_dma_data(asoc_rtd_to_cpu(rtd, 0), substream);
|
|
if (tx)
|
|
dma_params_fe->addr = asrc->paddr + ASRC_RXDR;
|
|
else
|
|
dma_params_fe->addr = asrc->paddr + ASRC_TXDR;
|
|
|
|
dma_params_fe->maxburst = dma_params_be->maxburst;
|
|
|
|
pair->dma_chan[!dir] = rockchip_asrc_get_dma_channel(asrc, !dir);
|
|
if (IS_ERR_OR_NULL(pair->dma_chan[!dir])) {
|
|
dev_err(dev, "failed to request DMA channel\n");
|
|
ret = PTR_ERR(pair->dma_chan[!dir]);
|
|
pair->dma_chan[!dir] = NULL;
|
|
return ret;
|
|
}
|
|
|
|
ret = snd_dmaengine_pcm_prepare_slave_config(substream, params, &config_fe);
|
|
if (ret) {
|
|
dev_err(dev, "failed to prepare DMA config for Front-End\n");
|
|
return ret;
|
|
}
|
|
|
|
if (tx && rockchip_asrc_is_link_mem(asrc->src_link_dai_id)) {
|
|
dma_thresh = rockchip_asrc_calculate_dma_thresh(config_fe.dst_maxburst, asrc);
|
|
regmap_update_bits(asrc->regmap, ASRC_DMA_THRESH,
|
|
ASRC_DMA_TX_THRESH_MSK | ASRC_IN_THRESH_MSK,
|
|
ASRC_DMA_TX_THRESH(dma_thresh) | ASRC_IN_THRESH(dma_thresh));
|
|
} else if (!tx && rockchip_asrc_is_link_mem(asrc->dst_link_dai_id)) {
|
|
dma_thresh = rockchip_asrc_calculate_dma_thresh(config_fe.src_maxburst, asrc);
|
|
regmap_update_bits(asrc->regmap, ASRC_DMA_THRESH,
|
|
ASRC_DMA_RX_THRESH_MSK | ASRC_IN_THRESH_MSK,
|
|
ASRC_DMA_RX_THRESH(dma_thresh) | ASRC_IN_THRESH(dma_thresh));
|
|
}
|
|
|
|
ret = dmaengine_slave_config(pair->dma_chan[!dir], &config_fe);
|
|
if (ret) {
|
|
dev_err(dev, "failed to config DMA channel for Front-End\n");
|
|
return ret;
|
|
}
|
|
|
|
/* Request and config DMA channel for Back-End */
|
|
dma_cap_zero(mask);
|
|
dma_cap_set(DMA_SLAVE, mask);
|
|
dma_cap_set(DMA_CYCLIC, mask);
|
|
|
|
/*
|
|
* The Back-End device might have already requested a DMA channel,
|
|
* so try to reuse it first, and then request a new one upon NULL.
|
|
*/
|
|
if (!IS_ENABLED(CONFIG_SND_SOC_DYNAMIC_DMA_CHAN)) {
|
|
component_be = snd_soc_lookup_component_nolocked(dev_be, SND_DMAENGINE_PCM_DRV_NAME);
|
|
if (component_be) {
|
|
be_chan = soc_component_to_pcm(component_be)->chan[substream->stream];
|
|
tmp_chan = be_chan;
|
|
}
|
|
}
|
|
|
|
if (!tmp_chan) {
|
|
tmp_chan = dma_request_chan(dev_be, tx ? "tx" : "rx");
|
|
if (IS_ERR(tmp_chan)) {
|
|
dev_err(dev, "failed to request DMA channel for Back-End\n");
|
|
return PTR_ERR(tmp_chan);
|
|
}
|
|
}
|
|
|
|
pair->dma_chan[dir] = tmp_chan;
|
|
/* Do not flag to release if we are reusing the Back-End one */
|
|
pair->req_dma_chan = !be_chan;
|
|
|
|
if (!pair->dma_chan[dir]) {
|
|
dev_err(dev, "failed to request DMA channel for Back-End\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
config_be.direction = (dir == OUT ? DMA_MEM_TO_DEV : DMA_DEV_TO_MEM);
|
|
config_be.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
|
|
config_be.src_maxburst = dma_params_be->maxburst;
|
|
config_be.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
|
|
config_be.dst_maxburst = dma_params_be->maxburst;
|
|
|
|
if (tx) {
|
|
config_be.src_addr = asrc->paddr + ASRC_FIFO_OUT_DATA;
|
|
config_be.dst_addr = dma_params_be->addr;
|
|
} else {
|
|
config_be.dst_addr = asrc->paddr + ASRC_FIFO_IN_DATA;
|
|
config_be.src_addr = dma_params_be->addr;
|
|
}
|
|
|
|
ret = dmaengine_slave_config(pair->dma_chan[dir], &config_be);
|
|
if (ret) {
|
|
dev_err(dev, "failed to config DMA channel for Back-End\n");
|
|
if (pair->req_dma_chan)
|
|
dma_release_channel(pair->dma_chan[dir]);
|
|
return ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int rockchip_asrc_dma_hw_free(struct snd_soc_component *component,
|
|
struct snd_pcm_substream *substream)
|
|
{
|
|
bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
|
|
struct snd_pcm_runtime *runtime = substream->runtime;
|
|
struct rockchip_asrc_pair *pair = runtime->private_data;
|
|
u8 dir = tx ? OUT : IN;
|
|
|
|
if (pair->dma_chan[!dir])
|
|
dma_release_channel(pair->dma_chan[!dir]);
|
|
|
|
/* release dev_to_dev chan if we aren't reusing the Back-End one */
|
|
if (pair->dma_chan[dir] && pair->req_dma_chan)
|
|
dma_release_channel(pair->dma_chan[dir]);
|
|
|
|
pair->dma_chan[!dir] = NULL;
|
|
pair->dma_chan[dir] = NULL;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int rockchip_asrc_dma_startup(struct snd_soc_component *component,
|
|
struct snd_pcm_substream *substream)
|
|
{
|
|
bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
|
|
struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
|
|
struct snd_pcm_runtime *runtime = substream->runtime;
|
|
struct snd_dmaengine_dai_dma_data *dma_data;
|
|
struct device *dev = component->dev;
|
|
struct rockchip_asrc *asrc = dev_get_drvdata(dev);
|
|
struct rockchip_asrc_pair *pair;
|
|
struct dma_chan *tmp_chan = NULL;
|
|
u8 dir = tx ? OUT : IN;
|
|
bool release_pair = true;
|
|
int ret = 0;
|
|
|
|
ret = snd_pcm_hw_constraint_integer(substream->runtime,
|
|
SNDRV_PCM_HW_PARAM_PERIODS);
|
|
if (ret < 0) {
|
|
dev_err(dev, "failed to set pcm hw params periods\n");
|
|
return ret;
|
|
}
|
|
|
|
pair = kzalloc(sizeof(*pair), GFP_KERNEL);
|
|
if (!pair)
|
|
return -ENOMEM;
|
|
|
|
pair->asrc = asrc;
|
|
|
|
runtime->private_data = pair;
|
|
|
|
/* Request a dummy pair, which will be released later.
|
|
* Request pair function needs channel num as input, for this
|
|
* dummy pair, we just request "1" channel temporarily.
|
|
*/
|
|
/* Request a dummy dma channel, which will be released later. */
|
|
dma_data = snd_soc_dai_get_dma_data(asoc_rtd_to_cpu(rtd, 0), substream);
|
|
|
|
tmp_chan = rockchip_asrc_get_dma_channel(asrc, dir);
|
|
if (IS_ERR(tmp_chan)) {
|
|
dev_err(dev, "failed to get dma channel\n");
|
|
ret = -EINVAL;
|
|
goto req_pair_err;
|
|
}
|
|
|
|
/* Refine the snd_rk_hardware according to caps of DMA. */
|
|
ret = snd_dmaengine_pcm_refine_runtime_hwparams(substream,
|
|
dma_data,
|
|
&snd_rk_hardware,
|
|
tmp_chan);
|
|
if (ret < 0) {
|
|
dev_err(dev, "failed to refine runtime hwparams\n");
|
|
goto out;
|
|
}
|
|
|
|
release_pair = false;
|
|
snd_soc_set_runtime_hwparams(substream, &snd_rk_hardware);
|
|
|
|
out:
|
|
dma_release_channel(tmp_chan);
|
|
|
|
req_pair_err:
|
|
if (release_pair)
|
|
kfree(pair);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int rockchip_asrc_dma_shutdown(struct snd_soc_component *component,
|
|
struct snd_pcm_substream *substream)
|
|
{
|
|
struct snd_pcm_runtime *runtime = substream->runtime;
|
|
struct rockchip_asrc_pair *pair = runtime->private_data;
|
|
|
|
if (!pair)
|
|
return 0;
|
|
|
|
kfree(pair);
|
|
runtime->private_data = NULL;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static snd_pcm_uframes_t rockchip_asrc_dma_pcm_pointer(struct snd_soc_component *component,
|
|
struct snd_pcm_substream *substream)
|
|
{
|
|
struct snd_pcm_runtime *runtime = substream->runtime;
|
|
struct rockchip_asrc_pair *pair = runtime->private_data;
|
|
|
|
return bytes_to_frames(substream->runtime, pair->pos);
|
|
}
|
|
|
|
static int rockchip_asrc_dma_pcm_new(struct snd_soc_component *component,
|
|
struct snd_soc_pcm_runtime *rtd)
|
|
{
|
|
struct snd_card *card = rtd->card->snd_card;
|
|
struct snd_pcm *pcm = rtd->pcm;
|
|
int ret;
|
|
|
|
ret = dma_coerce_mask_and_coherent(card->dev, DMA_BIT_MASK(32));
|
|
if (ret) {
|
|
dev_err(card->dev, "failed to set DMA mask\n");
|
|
return ret;
|
|
}
|
|
|
|
return snd_pcm_set_fixed_buffer_all(pcm, SNDRV_DMA_TYPE_DEV,
|
|
card->dev, RK_ASRC_DMABUF_SIZE);
|
|
}
|
|
|
|
static const char * const asrc_link_dai_text[] = {
|
|
"asrc0", "asrc1", "asrc2", "asrc3",
|
|
"asrc4", "asrc5", "asrc6", "asrc7",
|
|
"asrc8", "asrc9", "asrc10", "asrc11",
|
|
"asrc12", "asrc13", "asrc14", "asrc15",
|
|
"sai0", "sai1", "sai2", "sai3",
|
|
"sai4", "sai5", "sai6", "sai7",
|
|
"sai8", "sai9", "sai10", "sai11",
|
|
"sai12", "sai13", "sai14", "sai15",
|
|
"pdm0", "pdm1", "pdm2", "pdm3",
|
|
"pdm4", "pdm5", "pdm6", "pdm7",
|
|
"spdiftx0", "spdiftx1", "spdiftx2", "spdiftx4",
|
|
"spdiftx4", "spdiftx5", "spdiftx6", "spdiftx7",
|
|
"spdifrx0", "spdifrx1", "spdifrx2", "spdifrx4",
|
|
"spdifrx4", "spdifrx5", "spdifrx6", "spdifrx7" };
|
|
|
|
static const struct soc_enum asrc_tx_link_dai_enum =
|
|
SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(asrc_link_dai_text), asrc_link_dai_text);
|
|
static const struct soc_enum asrc_rx_link_dai_enum =
|
|
SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(asrc_link_dai_text), asrc_link_dai_text);
|
|
|
|
static int rockchip_asrc_tx_dai_get(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol);
|
|
struct rockchip_asrc *asrc = snd_soc_component_get_drvdata(component);
|
|
|
|
ucontrol->value.enumerated.item[0] = asrc->dst_link_dai_id;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int rockchip_asrc_tx_dai_put(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol);
|
|
struct rockchip_asrc *asrc = snd_soc_component_get_drvdata(component);
|
|
int num;
|
|
|
|
num = ucontrol->value.enumerated.item[0];
|
|
if (num >= ARRAY_SIZE(asrc_link_dai_text))
|
|
return -EINVAL;
|
|
|
|
asrc->dst_link_dai_id = num;
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int rockchip_asrc_rx_dai_get(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol);
|
|
struct rockchip_asrc *asrc = snd_soc_component_get_drvdata(component);
|
|
|
|
ucontrol->value.enumerated.item[0] = asrc->src_link_dai_id;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int rockchip_asrc_rx_dai_put(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol);
|
|
struct rockchip_asrc *asrc = snd_soc_component_get_drvdata(component);
|
|
int num;
|
|
|
|
num = ucontrol->value.enumerated.item[0];
|
|
if (num >= ARRAY_SIZE(asrc_link_dai_text))
|
|
return -EINVAL;
|
|
|
|
asrc->src_link_dai_id = num;
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int rockchip_asrc_resample_rate_get(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol);
|
|
struct rockchip_asrc *asrc = snd_soc_component_get_drvdata(component);
|
|
|
|
ucontrol->value.integer.value[0] = asrc->resample_rate;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int rockchip_asrc_resample_rate_put(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol);
|
|
struct rockchip_asrc *asrc = snd_soc_component_get_drvdata(component);
|
|
|
|
if ((ucontrol->value.integer.value[0] < 0) ||
|
|
(ucontrol->value.integer.value[0] > 192000))
|
|
return -EINVAL;
|
|
|
|
asrc->resample_rate = ucontrol->value.integer.value[0];
|
|
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* 1.resamplerate
|
|
* 2.dev rx & tx lrck select
|
|
*/
|
|
static const struct snd_kcontrol_new rockchip_asrc_controls[] = {
|
|
SOC_ENUM_EXT("TX LINK DAI Select", asrc_tx_link_dai_enum,
|
|
rockchip_asrc_tx_dai_get, rockchip_asrc_tx_dai_put),
|
|
SOC_ENUM_EXT("RX LINK DAI Select", asrc_rx_link_dai_enum,
|
|
rockchip_asrc_rx_dai_get, rockchip_asrc_rx_dai_put),
|
|
|
|
SOC_SINGLE_EXT("SET RESAMPLE RATE", 0, 0, 192000, 0,
|
|
rockchip_asrc_resample_rate_get,
|
|
rockchip_asrc_resample_rate_put),
|
|
};
|
|
|
|
static const struct snd_soc_component_driver rockchip_asrc_component = {
|
|
.name = DRV_NAME,
|
|
.controls = rockchip_asrc_controls,
|
|
.num_controls = ARRAY_SIZE(rockchip_asrc_controls),
|
|
.hw_params = rockchip_asrc_dma_hw_params,
|
|
.hw_free = rockchip_asrc_dma_hw_free,
|
|
.trigger = rockchip_asrc_dma_trigger,
|
|
.open = rockchip_asrc_dma_startup,
|
|
.close = rockchip_asrc_dma_shutdown,
|
|
.pointer = rockchip_asrc_dma_pcm_pointer,
|
|
.pcm_construct = rockchip_asrc_dma_pcm_new,
|
|
.legacy_dai_naming = 1,
|
|
};
|
|
|
|
static int rockchip_asrc_init(struct rockchip_asrc *asrc)
|
|
{
|
|
/* clear interrupt */
|
|
regmap_write(asrc->regmap, ASRC_INT_CON, 0x0);
|
|
|
|
/* Set the dma config */
|
|
regmap_update_bits(asrc->regmap, ASRC_DMA_THRESH,
|
|
ASRC_DMA_RX_THRESH_MSK | ASRC_DMA_TX_THRESH_MSK |
|
|
ASRC_OUT_THRESH_MSK | ASRC_IN_THRESH_MSK |
|
|
ASRC_NEG_THRESH_MSK | ASRC_POS_THRESH_MSK,
|
|
ASRC_DMA_RX_THRESH(3) | ASRC_DMA_TX_THRESH(0x3) |
|
|
ASRC_OUT_THRESH(11) | ASRC_IN_THRESH(3) |
|
|
ASRC_NEG_THRESH(3) | ASRC_POS_THRESH(0x1c));
|
|
/*
|
|
* Set default mode: real time, S2D, track and ratio exc
|
|
* track: track clock, but no adjust ratio
|
|
* ratio exc: adjust ratio
|
|
* ratio filt: sliding average filtering ratio to prevent anomalies
|
|
*/
|
|
regmap_update_bits(asrc->regmap, ASRC_CON,
|
|
ASRC_RATIO_TRACK_MODE | ASRC_RATIO_TRACK_MSK | ASRC_RATIO_EXC_MSK |
|
|
ASRC_RATIO_FILT_MSK | ASRC_REAL_TIME_MODE_MSK | ASRC_MODE_MSK,
|
|
ASRC_RATIO_TRACK_MODE | ASRC_RATIO_TRACK_DIS | ASRC_RATIO_EXC_DIS |
|
|
ASRC_RATIO_FILT_EN | ASRC_S2D | ASRC_REAL_TIME);
|
|
|
|
regmap_update_bits(asrc->regmap, ASRC_TRACK_PERIOD,
|
|
ASRC_RATIO_TRACK_DIV_MSK | ASRC_RATIO_TRACK_PERIOD_MSK,
|
|
ASRC_RATIO_TRACK_DIV(3) | ASRC_RATIO_TRACK_PERIOD(1023));
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int rockchip_asrc_runtime_suspend(struct device *dev)
|
|
{
|
|
struct rockchip_asrc *asrc = dev_get_drvdata(dev);
|
|
|
|
regcache_cache_only(asrc->regmap, true);
|
|
|
|
clk_disable_unprepare(asrc->mclk);
|
|
clk_disable_unprepare(asrc->hclk);
|
|
rockchip_asrc_lrck_clks_dis(asrc);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int rockchip_asrc_runtime_resume(struct device *dev)
|
|
{
|
|
struct rockchip_asrc *asrc = dev_get_drvdata(dev);
|
|
int ret;
|
|
|
|
ret = clk_prepare_enable(asrc->hclk);
|
|
if (ret)
|
|
goto err_hclk;
|
|
|
|
ret = clk_prepare_enable(asrc->mclk);
|
|
if (ret)
|
|
goto err_mclk;
|
|
|
|
ret = rockchip_asrc_lrck_clks_en(asrc);
|
|
if (ret)
|
|
goto err_asrc_lrck_clks;
|
|
|
|
regcache_cache_only(asrc->regmap, false);
|
|
regcache_mark_dirty(asrc->regmap);
|
|
|
|
ret = regcache_sync(asrc->regmap);
|
|
if (ret)
|
|
goto err_regmap;
|
|
return 0;
|
|
err_regmap:
|
|
rockchip_asrc_lrck_clks_dis(asrc);
|
|
err_asrc_lrck_clks:
|
|
clk_disable_unprepare(asrc->mclk);
|
|
err_mclk:
|
|
clk_disable_unprepare(asrc->hclk);
|
|
err_hclk:
|
|
return ret;
|
|
}
|
|
|
|
static irqreturn_t rockchip_asrc_isr(int irq, void *devid)
|
|
{
|
|
struct rockchip_asrc *asrc = (struct rockchip_asrc *)devid;
|
|
u32 status;
|
|
|
|
regmap_read(asrc->regmap, ASRC_INT_ST, &status);
|
|
/* clear the interrupt */
|
|
regmap_write(asrc->regmap, ASRC_INT_ST, status);
|
|
if (status & ASRC_FIFO_OUT_EMPTY_ST)
|
|
dev_err_ratelimited(asrc->dev, "ASRC FIFO out empty\n");
|
|
|
|
if (status & ASRC_FIFO_OUT_FULL_ST)
|
|
dev_err_ratelimited(asrc->dev, "ASRC FIFO out full\n");
|
|
|
|
if (status & ASRC_FIFO_IN_EMPTY_ST)
|
|
dev_err_ratelimited(asrc->dev, "ASRC FIFO in empty\n");
|
|
|
|
if (status & ASRC_FIFO_IN_FULL_ST)
|
|
dev_err_ratelimited(asrc->dev, "ASRC FIFO in full\n");
|
|
|
|
if (status & ASRC_CONV_ERROR_ST)
|
|
dev_err_ratelimited(asrc->dev, "ASRC conv error\n");
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static int rockchip_asrc_get_clk_all_parents_name(struct clk *clk, char *clk_names[], int n)
|
|
{
|
|
struct clk_hw *hw = __clk_get_hw(clk);
|
|
unsigned int num_parents = clk_hw_get_num_parents(hw);
|
|
struct clk_hw *parent;
|
|
int i;
|
|
|
|
if (num_parents > n)
|
|
num_parents = n;
|
|
|
|
for (i = 0; i < num_parents; i++) {
|
|
parent = clk_hw_get_parent_by_index(hw, i);
|
|
if (!parent)
|
|
break;
|
|
clk_names[i] = (char *)clk_hw_get_name(parent);
|
|
}
|
|
|
|
return i;
|
|
}
|
|
|
|
static struct clk *rockchip_asrc_get_clk_parent_by_name(struct clk *clk, char *clk_names[], int n, char *name)
|
|
{
|
|
struct clk_hw *hw = __clk_get_hw(clk);
|
|
struct clk_hw *parent = NULL;
|
|
char *result = NULL;
|
|
int i;
|
|
|
|
for (i = 0; i < n; i++) {
|
|
result = strstr(clk_names[i], name);
|
|
if (!result) {
|
|
continue;
|
|
} else {
|
|
parent = clk_hw_get_parent_by_index(hw, i);
|
|
return parent->clk;
|
|
}
|
|
}
|
|
|
|
return ERR_PTR(-ENOENT);
|
|
}
|
|
|
|
static struct clk *rockchip_asrc_get_clk_parent(struct clk *clk, char *clk_names[], int n, char *name)
|
|
{
|
|
struct clk *parent;
|
|
char *name_temp;
|
|
int name_len;
|
|
|
|
parent = rockchip_asrc_get_clk_parent_by_name(clk, clk_names, n, name);
|
|
if (!IS_ERR(parent))
|
|
return parent;
|
|
|
|
name_len = strlen(name);
|
|
if ((name_len > 1) && (name[name_len - 1] == '0') &&
|
|
(name[name_len - 2] < '0' || name[name_len - 2] > '9')) {
|
|
name_temp = kstrdup(name, GFP_KERNEL);
|
|
if (!name_temp)
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
name_temp[name_len - 1] = 0;
|
|
parent = rockchip_asrc_get_clk_parent_by_name(clk, clk_names, n, name_temp);
|
|
kfree(name_temp);
|
|
|
|
return parent;
|
|
}
|
|
|
|
return ERR_PTR(-ENOENT);
|
|
}
|
|
|
|
static void rockchip_asrc_lrck_div_set(struct rockchip_asrc *asrc)
|
|
{
|
|
int dst_lrck_div = 0, src_lrck_div = 0;
|
|
|
|
switch (asrc->src_link_dai_id) {
|
|
case DAI_ID_ASRC0 ... DAI_ID_ASRC15:
|
|
src_lrck_div = asrc->soc_data->lrck_source_freq / asrc->sample_rate;
|
|
break;
|
|
case DAI_ID_SPDIF_TX0 ... DAI_ID_SPDIF_RX7:
|
|
src_lrck_div = 128;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
switch (asrc->dst_link_dai_id) {
|
|
case DAI_ID_ASRC0 ... DAI_ID_ASRC15:
|
|
dst_lrck_div = asrc->soc_data->lrck_source_freq / asrc->resample_rate;
|
|
break;
|
|
case DAI_ID_SPDIF_TX0 ... DAI_ID_SPDIF_RX7:
|
|
dst_lrck_div = 128;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (dst_lrck_div) {
|
|
regmap_update_bits(asrc->regmap, ASRC_CLKDIV_CON,
|
|
ASRC_DST_LRCK_DIV_MSK |
|
|
ASRC_DST_LRCK_DIV_CON_MSK,
|
|
ASRC_DST_LRCK_DIV_EN |
|
|
ASRC_DST_LRCK_DIV(dst_lrck_div));
|
|
}
|
|
|
|
if (src_lrck_div) {
|
|
regmap_update_bits(asrc->regmap, ASRC_CLKDIV_CON,
|
|
ASRC_SRC_LRCK_DIV_MSK |
|
|
ASRC_SRC_LRCK_DIV_CON_MSK,
|
|
ASRC_SRC_LRCK_DIV_EN |
|
|
ASRC_SRC_LRCK_DIV(src_lrck_div));
|
|
}
|
|
}
|
|
|
|
static void rockchip_asrc_lrck_div_close(struct rockchip_asrc *asrc)
|
|
{
|
|
regmap_update_bits(asrc->regmap, ASRC_CLKDIV_CON,
|
|
ASRC_DST_LRCK_DIV_MSK,
|
|
ASRC_DST_LRCK_DIV_DIS);
|
|
regmap_update_bits(asrc->regmap, ASRC_CLKDIV_CON,
|
|
ASRC_SRC_LRCK_DIV_MSK,
|
|
ASRC_SRC_LRCK_DIV_DIS);
|
|
}
|
|
|
|
static int rk3506_asrc_lrck_clk_init(struct device *dev)
|
|
{
|
|
struct rockchip_asrc *asrc = dev_get_drvdata(dev);
|
|
|
|
asrc->src_lrck = devm_clk_get(dev, "src_lrck");
|
|
if (IS_ERR(asrc->src_lrck)) {
|
|
dev_err(dev, "Failed to get src_clk\n");
|
|
return PTR_ERR(asrc->src_lrck);
|
|
}
|
|
|
|
asrc->dst_lrck = devm_clk_get(dev, "dst_lrck");
|
|
if (IS_ERR(asrc->dst_lrck)) {
|
|
dev_err(dev, "Failed to get dst_lrck\n");
|
|
return PTR_ERR(asrc->dst_lrck);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int rk3506_asrc_lrck_clk_set(struct device *dev)
|
|
{
|
|
struct rockchip_asrc *asrc = dev_get_drvdata(dev);
|
|
char *clk_names[DAI_ID_MAX] = {0};
|
|
int n;
|
|
|
|
n = rockchip_asrc_get_clk_all_parents_name(asrc->dst_lrck, clk_names, ARRAY_SIZE(clk_names));
|
|
asrc->dst_lrck_parent = rockchip_asrc_get_clk_parent(asrc->dst_lrck, clk_names, n,
|
|
(char *)asrc_link_dai_text[asrc->dst_link_dai_id]);
|
|
if (IS_ERR(asrc->dst_lrck_parent)) {
|
|
dev_err(dev, "Failed to get dst_lrck_parent\n");
|
|
return PTR_ERR(asrc->dst_lrck_parent);
|
|
}
|
|
|
|
n = rockchip_asrc_get_clk_all_parents_name(asrc->src_lrck, clk_names, ARRAY_SIZE(clk_names));
|
|
asrc->src_lrck_parent = rockchip_asrc_get_clk_parent(asrc->src_lrck, clk_names, n,
|
|
(char *)asrc_link_dai_text[asrc->src_link_dai_id]);
|
|
if (IS_ERR(asrc->src_lrck_parent)) {
|
|
dev_err(dev, "Failed to get src_lrck_parent\n");
|
|
return PTR_ERR(asrc->src_lrck_parent);
|
|
}
|
|
|
|
clk_set_parent(asrc->src_lrck, asrc->src_lrck_parent);
|
|
clk_set_parent(asrc->dst_lrck, asrc->dst_lrck_parent);
|
|
if (rockchip_asrc_is_link_mem(asrc->src_link_dai_id)) {
|
|
if (clk_set_rate(asrc->src_lrck_parent, asrc->soc_data->lrck_source_freq)) {
|
|
dev_err(asrc->dev, "Failed to set src_lrck_parent, freq is %d\n",
|
|
asrc->soc_data->lrck_source_freq);
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
|
|
if (rockchip_asrc_is_link_mem(asrc->dst_link_dai_id)) {
|
|
if (clk_set_rate(asrc->dst_lrck_parent, asrc->soc_data->lrck_source_freq)) {
|
|
dev_err(asrc->dev, "Failed to set dst_lrck_parent, freq is %d\n",
|
|
asrc->soc_data->lrck_source_freq);
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
|
|
rockchip_asrc_lrck_div_set(asrc);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int rk3506_asrc_lrck_clk_en(struct device *dev)
|
|
{
|
|
struct rockchip_asrc *asrc = dev_get_drvdata(dev);
|
|
int ret = 0;
|
|
|
|
ret = clk_prepare_enable(asrc->src_lrck_parent);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = clk_prepare_enable(asrc->dst_lrck_parent);
|
|
if (ret)
|
|
goto err_dst_lrck_parent;
|
|
|
|
ret = clk_prepare_enable(asrc->src_lrck);
|
|
if (ret)
|
|
goto err_src_lrck;
|
|
|
|
ret = clk_prepare_enable(asrc->dst_lrck);
|
|
if (ret)
|
|
goto err_dst_lrck;
|
|
|
|
return 0;
|
|
err_dst_lrck:
|
|
clk_disable_unprepare(asrc->src_lrck);
|
|
err_src_lrck:
|
|
clk_disable_unprepare(asrc->dst_lrck_parent);
|
|
err_dst_lrck_parent:
|
|
clk_disable_unprepare(asrc->src_lrck_parent);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int rk3506_asrc_lrck_clk_dis(struct device *dev)
|
|
{
|
|
struct rockchip_asrc *asrc = dev_get_drvdata(dev);
|
|
|
|
clk_disable_unprepare(asrc->src_lrck);
|
|
clk_disable_unprepare(asrc->dst_lrck);
|
|
clk_disable_unprepare(asrc->src_lrck_parent);
|
|
clk_disable_unprepare(asrc->dst_lrck_parent);
|
|
rockchip_asrc_lrck_div_close(asrc);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int rk3576_asrc_lrck_clk_init(struct device *dev)
|
|
{
|
|
struct rockchip_asrc *asrc = dev_get_drvdata(dev);
|
|
struct device_node *node = dev->of_node;
|
|
|
|
asrc->grf = syscon_regmap_lookup_by_phandle(node, "rockchip,grf");
|
|
if (IS_ERR(asrc->grf))
|
|
return PTR_ERR(asrc->grf);
|
|
|
|
asrc->cru_src0 = devm_clk_get(dev, "cru_src0");
|
|
if (IS_ERR(asrc->cru_src0)) {
|
|
dev_err(dev, "Failed to get cru_src0\n");
|
|
return PTR_ERR(asrc->cru_src0);
|
|
}
|
|
|
|
asrc->cru_src1 = devm_clk_get(dev, "cru_src1");
|
|
if (IS_ERR(asrc->cru_src1)) {
|
|
dev_err(dev, "Failed to get cru_src1\n");
|
|
return PTR_ERR(asrc->cru_src1);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int rk3576_asrc_lrck_clk_set(struct device *dev)
|
|
{
|
|
struct rockchip_asrc *asrc = dev_get_drvdata(dev);
|
|
unsigned int dst_val = 0, src_val = 0;
|
|
|
|
switch (asrc->dst_link_dai_id) {
|
|
case DAI_ID_ASRC0:
|
|
dst_val = RK3576_DST_LRCK_FROM_CRU0;
|
|
break;
|
|
case DAI_ID_ASRC1:
|
|
dst_val = RK3576_DST_LRCK_FROM_CRU1;
|
|
break;
|
|
case DAI_ID_SAI0:
|
|
dst_val = RK3576_DST_LRCK_FROM_SAI0;
|
|
break;
|
|
case DAI_ID_SAI1:
|
|
dst_val = RK3576_DST_LRCK_FROM_SAI1;
|
|
break;
|
|
case DAI_ID_SAI2:
|
|
dst_val = RK3576_DST_LRCK_FROM_SAI2;
|
|
break;
|
|
case DAI_ID_SAI3:
|
|
dst_val = RK3576_DST_LRCK_FROM_SAI3;
|
|
break;
|
|
case DAI_ID_SAI4:
|
|
dst_val = RK3576_DST_LRCK_FROM_SAI4;
|
|
break;
|
|
case DAI_ID_SAI5:
|
|
dst_val = RK3576_DST_LRCK_FROM_SAI5;
|
|
break;
|
|
case DAI_ID_SAI6:
|
|
dst_val = RK3576_DST_LRCK_FROM_SAI6;
|
|
break;
|
|
case DAI_ID_SAI7:
|
|
dst_val = RK3576_DST_LRCK_FROM_SAI7;
|
|
break;
|
|
case DAI_ID_SAI8:
|
|
dst_val = RK3576_DST_LRCK_FROM_SAI8;
|
|
break;
|
|
case DAI_ID_SAI9:
|
|
dst_val = RK3576_DST_LRCK_FROM_SAI9;
|
|
break;
|
|
case DAI_ID_SPDIF_TX0:
|
|
dst_val = RK3576_DST_LRCK_FROM_SPDIF_TX0;
|
|
break;
|
|
case DAI_ID_SPDIF_TX1:
|
|
dst_val = RK3576_DST_LRCK_FROM_SPDIF_TX1;
|
|
break;
|
|
case DAI_ID_SPDIF_TX2:
|
|
dst_val = RK3576_DST_LRCK_FROM_SPDIF_TX2;
|
|
break;
|
|
case DAI_ID_SPDIF_TX3:
|
|
dst_val = RK3576_DST_LRCK_FROM_SPDIF_TX3;
|
|
break;
|
|
case DAI_ID_SPDIF_TX4:
|
|
dst_val = RK3576_DST_LRCK_FROM_SPDIF_TX4;
|
|
break;
|
|
case DAI_ID_SPDIF_TX5:
|
|
dst_val = RK3576_DST_LRCK_FROM_SPDIF_TX5;
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
switch (asrc->src_link_dai_id) {
|
|
case DAI_ID_ASRC0:
|
|
src_val = RK3576_SRC_LRCK_FROM_CRU0;
|
|
break;
|
|
case DAI_ID_ASRC1:
|
|
src_val = RK3576_SRC_LRCK_FROM_CRU1;
|
|
break;
|
|
case DAI_ID_SAI0:
|
|
src_val = RK3576_SRC_LRCK_FROM_SAI0;
|
|
break;
|
|
case DAI_ID_SAI1:
|
|
src_val = RK3576_SRC_LRCK_FROM_SAI1;
|
|
break;
|
|
case DAI_ID_SAI2:
|
|
src_val = RK3576_SRC_LRCK_FROM_SAI2;
|
|
break;
|
|
case DAI_ID_SAI3:
|
|
src_val = RK3576_SRC_LRCK_FROM_SAI3;
|
|
break;
|
|
case DAI_ID_SAI4:
|
|
src_val = RK3576_SRC_LRCK_FROM_SAI4;
|
|
break;
|
|
case DAI_ID_SAI5:
|
|
src_val = RK3576_SRC_LRCK_FROM_SAI5;
|
|
break;
|
|
case DAI_ID_SAI6:
|
|
src_val = RK3576_SRC_LRCK_FROM_SAI6;
|
|
break;
|
|
case DAI_ID_SAI7:
|
|
src_val = RK3576_SRC_LRCK_FROM_SAI7;
|
|
break;
|
|
case DAI_ID_SAI8:
|
|
src_val = RK3576_SRC_LRCK_FROM_SAI8;
|
|
break;
|
|
case DAI_ID_SAI9:
|
|
src_val = RK3576_SRC_LRCK_FROM_SAI9;
|
|
break;
|
|
case DAI_ID_PDM0:
|
|
src_val = RK3576_SRC_LRCK_FROM_PDM0;
|
|
break;
|
|
case DAI_ID_PDM1:
|
|
src_val = RK3576_SRC_LRCK_FROM_PDM1;
|
|
break;
|
|
case DAI_ID_SPDIF_RX0:
|
|
src_val = RK3576_SRC_LRCK_FROM_SPDIF_RX0;
|
|
break;
|
|
case DAI_ID_SPDIF_RX1:
|
|
src_val = RK3576_SRC_LRCK_FROM_SPDIF_RX1;
|
|
break;
|
|
case DAI_ID_SPDIF_RX2:
|
|
src_val = RK3576_SRC_LRCK_FROM_SPDIF_RX2;
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (asrc->paddr == RK3576_ASRC0) {
|
|
regmap_write(asrc->grf, RK3576_SYS_GRF_SOC_CON9,
|
|
ASRC0_4CH_SRC_SEL_MASK | ASRC0_4CH_DST_SEL_MASK |
|
|
ASRC0_4CH_SRC_SEL(src_val) | ASRC0_4CH_DST_SEL(dst_val));
|
|
} else if (asrc->paddr == RK3576_ASRC1) {
|
|
regmap_write(asrc->grf, RK3576_SYS_GRF_SOC_CON9,
|
|
ASRC1_4CH_SRC_SEL_MASK | ASRC1_4CH_SRC_SEL(src_val));
|
|
regmap_write(asrc->grf, RK3576_SYS_GRF_SOC_CON10,
|
|
ASRC1_4CH_DST_SEL_MASK | ASRC1_4CH_DST_SEL(dst_val));
|
|
} else if (asrc->paddr == RK3576_ASRC2) {
|
|
regmap_write(asrc->grf, RK3576_SYS_GRF_SOC_CON10,
|
|
ASRC2_2CH_SRC_SEL_MASK | ASRC2_2CH_DST_SEL_MASK |
|
|
ASRC2_2CH_SRC_SEL(src_val) | ASRC2_2CH_DST_SEL(dst_val));
|
|
} else if (asrc->paddr == RK3576_ASRC3) {
|
|
regmap_write(asrc->grf, RK3576_SYS_GRF_SOC_CON11,
|
|
ASRC3_2CH_SRC_SEL_MASK | ASRC3_2CH_DST_SEL_MASK |
|
|
ASRC3_2CH_SRC_SEL(src_val) | ASRC3_2CH_DST_SEL(dst_val));
|
|
} else {
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (rockchip_asrc_is_link_mem(asrc->src_link_dai_id)) {
|
|
if (clk_set_rate(asrc->cru_src0, asrc->soc_data->lrck_source_freq)) {
|
|
dev_err(asrc->dev, "Failed to set cru_src0, freq is %d\n",
|
|
asrc->soc_data->lrck_source_freq);
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
|
|
if (rockchip_asrc_is_link_mem(asrc->dst_link_dai_id)) {
|
|
if (clk_set_rate(asrc->cru_src1, asrc->soc_data->lrck_source_freq)) {
|
|
dev_err(asrc->dev, "Failed to set cru_src1, freq is %d\n",
|
|
asrc->soc_data->lrck_source_freq);
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
|
|
rockchip_asrc_lrck_div_set(asrc);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int rk3576_asrc_lrck_clk_en(struct device *dev)
|
|
{
|
|
struct rockchip_asrc *asrc = dev_get_drvdata(dev);
|
|
int ret = 0;
|
|
|
|
ret = clk_prepare_enable(asrc->cru_src0);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = clk_prepare_enable(asrc->cru_src1);
|
|
if (ret)
|
|
clk_disable_unprepare(asrc->cru_src0);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int rk3576_asrc_lrck_clk_dis(struct device *dev)
|
|
{
|
|
struct rockchip_asrc *asrc = dev_get_drvdata(dev);
|
|
|
|
clk_disable_unprepare(asrc->cru_src0);
|
|
clk_disable_unprepare(asrc->cru_src1);
|
|
rockchip_asrc_lrck_div_close(asrc);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct rk_asrc_soc_data rk3506_data = {
|
|
.lrck_clk_init = rk3506_asrc_lrck_clk_init,
|
|
.lrck_clk_set = rk3506_asrc_lrck_clk_set,
|
|
.lrck_clk_en = rk3506_asrc_lrck_clk_en,
|
|
.lrck_clk_dis = rk3506_asrc_lrck_clk_dis,
|
|
.lrck_source_freq = 98304000,
|
|
};
|
|
|
|
static const struct rk_asrc_soc_data rk3576_data = {
|
|
.lrck_clk_init = rk3576_asrc_lrck_clk_init,
|
|
.lrck_clk_set = rk3576_asrc_lrck_clk_set,
|
|
.lrck_clk_en = rk3576_asrc_lrck_clk_en,
|
|
.lrck_clk_dis = rk3576_asrc_lrck_clk_dis,
|
|
.lrck_source_freq = 49152000,
|
|
};
|
|
|
|
static const struct of_device_id rockchip_asrc_match[] = {
|
|
{ .compatible = "rockchip,rk3506-asrc", .data = &rk3506_data},
|
|
{ .compatible = "rockchip,rk3576-asrc", .data = &rk3576_data},
|
|
{}
|
|
};
|
|
MODULE_DEVICE_TABLE(of, rockchip_asrc_match);
|
|
|
|
static int rockchip_asrc_probe(struct platform_device *pdev)
|
|
{
|
|
struct device_node *node = pdev->dev.of_node;
|
|
struct rockchip_asrc *asrc;
|
|
struct resource *res;
|
|
void __iomem *regs;
|
|
int ret, irq;
|
|
|
|
asrc = devm_kzalloc(&pdev->dev, sizeof(*asrc), GFP_KERNEL);
|
|
if (!asrc)
|
|
return -ENOMEM;
|
|
|
|
asrc->dev = &pdev->dev;
|
|
asrc->pdev = pdev;
|
|
dev_set_drvdata(&pdev->dev, asrc);
|
|
regs = devm_platform_get_and_ioremap_resource(pdev, 0, &res);
|
|
if (IS_ERR(regs))
|
|
return PTR_ERR(regs);
|
|
|
|
asrc->paddr = res->start;
|
|
asrc->soc_data = device_get_match_data(&pdev->dev);
|
|
if (!asrc->soc_data)
|
|
return -EINVAL;
|
|
|
|
asrc->regmap = devm_regmap_init_mmio(&pdev->dev, regs,
|
|
&rockchip_asrc_regmap_config);
|
|
if (IS_ERR(asrc->regmap)) {
|
|
dev_err(&pdev->dev, "failed to init regmap\n");
|
|
return PTR_ERR(asrc->regmap);
|
|
}
|
|
|
|
irq = platform_get_irq_optional(pdev, 0);
|
|
if (irq > 0) {
|
|
ret = devm_request_irq(&pdev->dev, irq, rockchip_asrc_isr,
|
|
IRQF_SHARED, node->name, asrc);
|
|
if (ret) {
|
|
dev_err(&pdev->dev, "Failed to request irq %d\n", irq);
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
ret = asrc->soc_data->lrck_clk_init(asrc->dev);
|
|
if (ret)
|
|
return ret;
|
|
|
|
asrc->mclk = devm_clk_get(&pdev->dev, "mclk");
|
|
if (IS_ERR(asrc->mclk)) {
|
|
dev_err(&pdev->dev, "Failed to get mclk\n");
|
|
return PTR_ERR(asrc->mclk);
|
|
}
|
|
|
|
asrc->hclk = devm_clk_get(&pdev->dev, "hclk");
|
|
if (IS_ERR(asrc->hclk)) {
|
|
dev_err(&pdev->dev, "Failed to get hclk\n");
|
|
return PTR_ERR(asrc->hclk);
|
|
}
|
|
|
|
ret = clk_prepare_enable(asrc->hclk);
|
|
if (ret)
|
|
return ret;
|
|
/* Set the default value here */
|
|
asrc->resample_rate = DEFAULT_SAMPLE_RATE;
|
|
asrc->sample_rate = DEFAULT_SAMPLE_RATE;
|
|
|
|
/* Set the default link dai here */
|
|
asrc->dst_link_dai_id = DAI_ID_UNKNOWN;
|
|
asrc->src_link_dai_id = DAI_ID_UNKNOWN;
|
|
|
|
/* DMA data init */
|
|
asrc->dma_data_tx.addr = res->start + ASRC_TXDR;
|
|
asrc->dma_data_tx.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
|
|
asrc->dma_data_tx.maxburst = MAXBURST_PER_FIFO;
|
|
asrc->dma_data_rx.addr = res->start + ASRC_RXDR;
|
|
asrc->dma_data_rx.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
|
|
asrc->dma_data_rx.maxburst = MAXBURST_PER_FIFO;
|
|
|
|
platform_set_drvdata(pdev, asrc);
|
|
pm_runtime_enable(&pdev->dev);
|
|
if (!pm_runtime_enabled(&pdev->dev)) {
|
|
ret = rockchip_asrc_runtime_resume(&pdev->dev);
|
|
if (ret)
|
|
goto err_runtime_disable;
|
|
}
|
|
|
|
ret = pm_runtime_resume_and_get(&pdev->dev);
|
|
if (ret < 0)
|
|
goto err_runtime_suspend;
|
|
|
|
ret = rockchip_asrc_init(asrc);
|
|
if (ret) {
|
|
dev_err(&pdev->dev, "Asrc init error.\n");
|
|
goto err_runtime_suspend;
|
|
}
|
|
|
|
ret = devm_snd_soc_register_component(&pdev->dev,
|
|
&rockchip_asrc_component,
|
|
&rockchip_asrc_dai, 1);
|
|
if (ret)
|
|
goto err_runtime_suspend;
|
|
|
|
clk_disable_unprepare(asrc->hclk);
|
|
|
|
return 0;
|
|
|
|
err_runtime_suspend:
|
|
if (!pm_runtime_status_suspended(&pdev->dev))
|
|
rockchip_asrc_runtime_suspend(&pdev->dev);
|
|
|
|
err_runtime_disable:
|
|
pm_runtime_disable(&pdev->dev);
|
|
clk_disable_unprepare(asrc->hclk);
|
|
return ret;
|
|
}
|
|
|
|
static int rockchip_asrc_remove(struct platform_device *pdev)
|
|
{
|
|
struct rockchip_asrc *asrc = dev_get_drvdata(&pdev->dev);
|
|
|
|
pm_runtime_disable(&pdev->dev);
|
|
if (!pm_runtime_status_suspended(&pdev->dev))
|
|
rockchip_asrc_runtime_suspend(&pdev->dev);
|
|
|
|
clk_disable_unprepare(asrc->hclk);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct dev_pm_ops rockchip_asrc_pm_ops = {
|
|
SET_RUNTIME_PM_OPS(rockchip_asrc_runtime_suspend, rockchip_asrc_runtime_resume, NULL)
|
|
SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, pm_runtime_force_resume)
|
|
};
|
|
|
|
static struct platform_driver rockchip_asrc_driver = {
|
|
.probe = rockchip_asrc_probe,
|
|
.remove = rockchip_asrc_remove,
|
|
.driver = {
|
|
.name = DRV_NAME,
|
|
.of_match_table = rockchip_asrc_match,
|
|
.pm = &rockchip_asrc_pm_ops,
|
|
},
|
|
};
|
|
module_platform_driver(rockchip_asrc_driver);
|
|
|
|
MODULE_DESCRIPTION("Rockchip ASRC ASoC Interface");
|
|
MODULE_AUTHOR("Jason Zhu <jason.zhu@rock-chips.com>");
|
|
MODULE_LICENSE("GPL");
|