// SPDX-License-Identifier: GPL-2.0 // // Common functions for loongson I2S controller driver // // Copyright (C) 2023 Loongson Technology Corporation Limited. // Author: Yingkun Meng // #include #include #include #include #include #include #include #include #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 chans = params_channels(params); 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(sysclk, (bits * chans * fs * 2)) - 1; 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) | mclk_ratio; 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 */ ret = loongson_i2s_enable_mclk(i2s); 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 = { .stream_name = "CPU-Playback", .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, }; 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) };