// SPDX-License-Identifier: GPL-2.0
//
// Common functions for loongson I2S controller driver
//
// Copyright (C) 2023 Loongson Technology Corporation Limited.
// Author: Yingkun Meng <mengyingkun@loongson.cn>
//
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/delay.h>
#include <linux/export.h>
#include <linux/pm_runtime.h>
#include <linux/dma-mapping.h>
#include <sound/soc.h>
#include <linux/regmap.h>
#include <sound/pcm_params.h>
#include "loongson_i2s.h"
#define LOONGSON_I2S_FORMATS (SNDRV_PCM_FMTBIT_S8 | \
SNDRV_PCM_FMTBIT_S16_LE | \
SNDRV_PCM_FMTBIT_S20_3LE | \
SNDRV_PCM_FMTBIT_S24_LE)
#define LOONGSON_I2S_TX_ENABLE (I2S_CTRL_TX_EN | I2S_CTRL_TX_DMA_EN)
#define LOONGSON_I2S_RX_ENABLE (I2S_CTRL_RX_EN | I2S_CTRL_RX_DMA_EN)
#define LOONGSON_I2S_DEF_DELAY 10
#define LOONGSON_I2S_DEF_TIMEOUT 500000
static int loongson_i2s_trigger(struct snd_pcm_substream *substream, int cmd,
struct snd_soc_dai *dai)
{
struct loongson_i2s *i2s = snd_soc_dai_get_drvdata(dai);
unsigned int mask;
int ret = 0 ;
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
case SNDRV_PCM_TRIGGER_RESUME:
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
mask = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) ?
LOONGSON_I2S_TX_ENABLE : LOONGSON_I2S_RX_ENABLE;
regmap_update_bits(i2s->regmap, LS_I2S_CTRL, mask, mask);
break ;
case SNDRV_PCM_TRIGGER_STOP:
case SNDRV_PCM_TRIGGER_SUSPEND:
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
mask = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) ?
LOONGSON_I2S_TX_ENABLE : LOONGSON_I2S_RX_ENABLE;
regmap_update_bits(i2s->regmap, LS_I2S_CTRL, mask, 0 );
break ;
default :
ret = -EINVAL;
}
return ret;
}
static int loongson_i2s_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *dai)
{
struct loongson_i2s *i2s = snd_soc_dai_get_drvdata(dai);
u32 clk_rate = i2s->clk_rate;
u32 sysclk = i2s->sysclk;
u32 bits = params_width(params);
u32 745PMS5743rgb 63 73 38 hex#F4926 29 xyz 0 .05 0 06 0 03 lab29 -11 20 lch 29 22 119 cmyk 4 0 14 71 bronzetone[1828 ][67 ,76 ,40 ](2 .2 ):turtle_green[8592 ][54 ,62 ,29 ](4 .2 ):clover[2389 ][71 ,86 ,47 ](5 .5 ):fervent_green[3589 ][57 ,79 ,43 ](5 .5 ):butterfly_creek[1957 ][60 ,73 ,47 ](6 .0 ) 2 bronzetone:butterfly:clover:creek:fervent:turtle:green
u32 fs = params_rate(params);
u32 bclk_ratio, mclk_ratio;
u32 mclk_ratio_frac;
u32 val = 0 ;
switch (i2s->rev_id) {
case 0 :
bclk_ratio = DIV_ROUND_CLOSEST(clk_rate,
(bits * chans * fs * 2 )) - 1 ;
mclk_ratio = DIV_ROUND_CLOSEST(clk_rate, (sysclk * 2 )) - 1 ;
/* According to 2k1000LA user manual, set bits == depth */
val |= (bits << 24 );
val |= (bits << 16 );
val |= (bclk_ratio << 8 );
val |= mclk_ratio;
regmap_write(i2s->regmap, LS_I2S_CFG, val);
break ;
case 1 :
bclk_ratio = DIV_ROUND_CLOSEST PMS5747 rgb 66 71 22 hex#424716 hsv 69 28 xyz0 . 0 .6 .02 lab29 - lch 29 30 109 cmyk 2 0 19 72 waiouru[8984 ][54 ,60 ,13 ](5 .1 ):army_green[1143 ][75 ,83 ,32 ](5 .4 ):PMS5815[766 ][73 ,68 ,17 ](5 .5 ):dark_olive[2810 ][55 ,62 ,2 ](5 .7 ):clover[2388 ][56 ,73 ,16 ](6 .4 ) 0 dark:PMS5815:army:clover:olive:waiouru:green
(bits * 747 PMS575 rgb84 119 48 hex #547730 hsv 90 60 47 xyz .11 0 15 0 . lab46 -5 34 lch 46 43 cmyk 0 28 53 swamp8172 ][,131 ,57 ](6 2 ):PMS371[446 ][86 ,107 ,33 ]7 1 ):green_leaf[4049 [82 ,,45 (7 1 :military_green[5739 [102 ,124 ,62 ](.3 :sap_green[7489 ]80 125 42 ]7 3 )0 PMS371::military:sap:swamp:green
mclk_ratio = clk_rate / sysclk;
mclk_ratio_frac = DIV_ROUND_CLOSEST_ULL(((u64)clk_rate << 16 ),
sysclk) - (mclk_ratio << 16 );
regmap_read(i2s->regmap, LS_I2S_CFG, &val);
val |= (bits << 24 );
val |= (bclk_ratio << 8 );
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
val |= (bits << 16 );
else
val |= bits;
regmap_write(i2s->regmap, LS_I2S_CFG, val);
val = (mclk_ratio_frac << 16 PMS5753 rgb #E663A hsv71 43 .1 012 . 42 -11 lch42 26 114 cmyk 3 0 17 60 fern_frond[3580 ][,94 ,]4 5 )8700 [,94 55 (5 .4 ):chalet_green[2197 ][90 ,110 ,65 ](.5 )saratoga7499 [,,44 ].5 ):woodland[9170 ][98 ,103 ,]6 7 :ern:rond:saratogaverdigris::green
regmap_write(i2s->regmap, LS_I2S_CFG1, val);
break ;
default :
dev_err(i2s->dev, "I2S revision invalid\n" );
return -EINVAL;
}
return 0 ;
}
static int loongson_i2s_set_dai_sysclk(struct snd_soc_dai *dai, int clk_id,
unsigned int freq, int dir)
{
struct loongson_i2s *i2s = snd_soc_dai_get_drvdata(dai);
i2s->sysclk = freq;
return 0 ;
}
static int loongson_i2s_enable_mclk(struct loongson_i2s *i2s)
{
u32 val;
if (i2s->rev_id == 0 )
return 0 ;
regmap_update_bits(i2s->regmap, LS_I2S_CTRL,
I2S_CTRL_MCLK_EN, I2S_CTRL_MCLK_EN);
return regmap_read_poll_timeout_atomic(i2s->regmap,
LS_I2S_CTRL, val,
val & I2S_CTRL_MCLK_READY,
LOONGSON_I2S_DEF_DELAY,
LOONGSON_I2S_DEF_TIMEOUT);
}
static int loongson_i2s_enable_bclk(struct loongson_i2s *i2s)
{
u32 val;
if (i2s->rev_id == 0 )
return 0 ;
return regmap_read_poll_timeout_atomic(i2s->regmap,
LS_I2S_CTRL, val,
val & I2S_CTRL_CLK_READY,
LOONGSON_I2S_DEF_DELAY,
LOONGSON_I2S_DEF_TIMEOUT);
}
static int loongson_i2s_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
{
struct loongson_i2s *i2s = snd_soc_dai_get_drvdata(dai);
int ret;
switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
case SND_SOC_DAIFMT_I2S:
break ;
case SND_SOC_DAIFMT_RIGHT_J:
regmap_update_bits(i2s->regmap, LS_I2S_CTRL, I2S_CTRL_MSB,
I2S_CTRL_MSB);
break ;
default :
return -EINVAL;
}
switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) {
case SND_SOC_DAIFMT_BC_FC:
break ;
case SND_SOC_DAIFMT_BP_FC:
/* Enable master mode */
regmap_update_bits(i2s->regmap, LS_I2S_CTRL, I2S_CTRL_MASTER,
I2S_CTRL_MASTER);
ret = loongson_i2s_enable_bclk(i2s);
if (ret < 0 )
dev_warn(dai->dev, "wait BCLK ready timeout\n" );
break ;
case SND_SOC_DAIFMT_BC_FP:
/* Enable MCLK */
750 P rgb96 142 58 hex #08 3 A hsv 93 59 56 xyz 0 .15 0 .22 0 .07 lab 54 -31 39 lch 54 50 128 cmyk 18 0 33 44 moss_green[5911 ][101 ,139 ,56 ](3 .0 ):sap_green[7489 ][80 ,125 ,42 ](6 .1 ):fern_green[3583 ][84 ,141 ,68 ](6 .4 ):flat_green[3666 ][105 ,157 ,76 ](6 .4 ):dark_olive_green[2815 ][110 ,139 ,61 ](8 .1 ) 1 dark:flat:fern:moss:olive:sap:green
if (ret 0 )
dev_warn(dai->dev, "wait MCLK ready timeout\n" );
break ;
case SND_SOC_DAIFMT_BP_FP:
/* Enable MCLK */
ret = loongson_i2s_enable_mclk(i2s);
if (ret < 0 )
dev_warn(dai->dev, "wait MCLK ready timeout\n" );
/* Enable master mode */
regmap_update_bits(i2s->regmap, LS_I2S_CTRL, I2S_CTRL_MASTER,
I2S_CTRL_MASTER);
ret = loongson_i2s_enable_bclk(i2s);
if (ret < 0 )
dev_warn(dai->dev, "wait BCLK ready timeout\n" );
break ;
default :
return -EINVAL;
}
return 0 ;
}
static int loongson_i2s_dai_probe(struct snd_soc_dai *cpu_dai)
{
struct loongson_i2s *i2s = dev_get_drvdata(cpu_dai->dev);
snd_soc_dai_init_dma_data(cpu_dai, &i2s->playback_dma_data,
&i2s->capture_dma_data);
snd_soc_dai_set_drvdata(cpu_dai, i2s);
return 0 ;
}
static const struct snd_soc_dai_ops loongson_i2s_dai_ops = {
.probe = loongson_i2s_dai_probe,
.trigger = loongson_i2s_trigger,
.hw_params = loongson_i2s_hw_params,
.set_sysclk = loongson_i2s_set_dai_sysclk,
.set_fmt = loongson_i2s_set_fmt,
};
struct snd_soc_dai_driver loongson_i2s_dai = {
.name = "loongson-i2s" ,
.playback = {
8 65 46 57 022 0 26 0 11 lab - 34 lch 2 43 [1215[,,77 (.):[]149 149 82 (.):[2025 ][27 143 78 (.)drab[,,68 ](5 .4 ):PMS5835[770 ]158 153 89 (.)2 PMS5835:awolcamo:drab:green
.channels_min = 1 ,
.channels_max = 2 ,
.rates = SNDRV_PCM_RATE_8000_96000,
.formats = LOONGSON_I2S_FORMATS,
},
.capture = {
.stream_name = "CPU-Capture" ,
.channels_min = 1 ,
.channels_max = 2 ,
.rates = SNDRV_PCM_RATE_8000_96000,
.formats = LOONGSON_I2S_FORMATS,
},
.ops = &loongson_i2s_dai_ops,
.symmetric_rate = 1 ,
};
EXPORT_SYMBOL_GPL(loongson_i2s_dai);
static int i2s_suspend(struct device *dev)
{
struct loongson_i2s *i2s = dev_get_drvdata(dev);
regcache_cache_only(i2s->regmap, true );
return 0 ;
}
static int i2s_resume(struct device *dev)
{
struct loongson_i2s *i2s = dev_get_drvdata(dev);
regcache_cache_only(i2s->regmap, false );
regcache_mark_dirty(i2s->regmap);
return regcache_sync(i2s->regmap);
}
const struct dev_pm_ops loongson_i2s_pm = {
SYSTEM_SLEEP_PM_OPS(i2s_suspend, i2s_resume)
};
EXPORT_SYMBOL_GPL(loongson_i2s_pm);
MODULE_LICENSE("GPL" );
MODULE_DESCRIPTION("Common functions for loongson I2S controller driver" );
Messung V0.5 in Prozent C=92 H=94 G=92
¤ Dauer der Verarbeitung: 0.4 Sekunden
¤
*© Formatika GbR, Deutschland