Files
thead-kernel/sound/soc/thead/light-tdm.c
thead_admin afef388b8e Linux_SDK_V1.2.1
Signed-off-by: thead_admin <occ_thead@service.alibaba.com>
2023-08-19 15:26:40 +08:00

548 lines
16 KiB
C

/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (C) 2021 Alibaba Group Holding Limited.
*/
#include <linux/delay.h>
#include <linux/dma-mapping.h>
#include <linux/pm_runtime.h>
#include <linux/io.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/scatterlist.h>
#include <linux/sh_dma.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/workqueue.h>
#include <sound/soc.h>
#include <sound/pcm_params.h>
#include <sound/sh_fsi.h>
#include "light-pcm.h"
#include "light-tdm.h"
#include <linux/dmaengine.h>
#include <linux/regmap.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/initval.h>
#include <sound/dmaengine_pcm.h>
#include <linux/mfd/syscon.h>
#include <dt-bindings/pinctrl/light-fm-aon-pinctrl.h>
#define LIGHT_RATES SNDRV_PCM_RATE_8000_384000
#define LIGHT_FMTS (SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8)
static int light_tdm_dai_probe(struct snd_soc_dai *dai)
{
struct light_tdm_priv *tdm = snd_soc_dai_get_drvdata(dai);
if (tdm) {
snd_soc_dai_init_dma_data(dai, NULL, &tdm->dma_params_rx);
} else {
return -EIO;
}
return 0;
}
static int light_tdm_dai_startup(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
struct light_tdm_priv *priv = snd_soc_dai_get_drvdata(dai);
if (priv->slot_num != 1) {
return 0;
}
return pm_runtime_get_sync(dai->dev);
}
static void light_tdm_snd_rxctrl(struct light_tdm_priv *priv, char on)
{
if (priv->slot_num != 1) {
return;
}
regmap_update_bits(priv->regmap, TDM_DMACTL,
DMACTL_DMAEN_MSK, DMACTL_DMAEN_SEL(on));
regmap_update_bits(priv->regmap, TDM_TDMEN,
TDMCTL_TDMEN_MSK, TDMCTL_TDMEN_SEL(on));
u32 dmactl;
u32 tdmen;
regmap_read(priv->regmap, TDM_DMACTL, &dmactl);
regmap_read(priv->regmap, TDM_TDMEN, &tdmen);
//printk("%s TDM_DMACTL=0x%x TDM_TDMEN=0x%x\n", __func__, dmactl, tdmen);
return;
}
static void light_tdm_dai_shutdown(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
struct light_tdm_priv *priv = snd_soc_dai_get_drvdata(dai);
if (priv->slot_num != 1) {
return;
}
if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
light_tdm_snd_rxctrl(priv, 0);
pm_runtime_put(dai->dev);
return;
}
static int light_tdm_dai_trigger(struct snd_pcm_substream *substream, int cmd, struct snd_soc_dai *dai)
{
int ret = 0;
struct light_tdm_priv *priv = snd_soc_dai_get_drvdata(dai);
switch(cmd) {
case SNDRV_PCM_TRIGGER_START:
case SNDRV_PCM_TRIGGER_RESUME:
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
light_tdm_snd_rxctrl(priv, 1);
break;
case SNDRV_PCM_TRIGGER_STOP:
case SNDRV_PCM_TRIGGER_SUSPEND:
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
light_tdm_snd_rxctrl(priv, 0);
break;
default:
return -EINVAL;
}
return ret;
}
static int light_tdm_set_fmt_dai(struct snd_soc_dai *dai, unsigned int fmt)
{
struct light_tdm_priv *priv = snd_soc_dai_get_drvdata(dai);
u32 cnfin = 0;
if (priv->slot_num != 1) {
return 0;
}
switch(fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
case SND_SOC_DAIFMT_DSP_B:
break;
default:
pr_err("Unknown fmt dai\n");
return -EIO;
}
regmap_update_bits(priv->regmap, TDM_TDMCTL,
TDMCTL_MODE_MSK,
TDMCTL_MODE_SEL(TDM_MODE_MASTER));
regmap_update_bits(priv->regmap, TDM_TDMCTL,
TDMCTL_SPEDGE_MSK,
TDMCTL_SPEDGE_SEL(1));
regmap_update_bits(priv->regmap, TDM_DMADL,
DMACTL_DMADL_MSK, DMACTL_DMADL_SEL(0));
u32 tdmctl;
u32 dmadl;
regmap_read(priv->regmap, TDM_TDMCTL, &tdmctl);
regmap_read(priv->regmap, TDM_DMADL, &dmadl);
return 0;
}
static u32 light_special_sample_rates[] = { 11025, 22050, 44100, 88200 };
#define AUDIO_DIVCLK0 49152000
#define AUDIO_DIVCLK1 135475200
static void light_tdm_set_div(struct light_tdm_priv *priv, struct snd_pcm_hw_params *params)
{
bool is_divclk1 = false; //audio_divclk1 for 44.1k...etc. audio_divclk0 for 48k....etc
u32 src_clk;
u32 div0, i, width;
u32 sample_rate = params_rate(params);
switch(params_format(params)) {
case SNDRV_PCM_FORMAT_S16_LE:
width = 16;
break;
case SNDRV_PCM_FORMAT_S24_LE:
width = 24;
break;
case SNDRV_PCM_FORMAT_S32_LE:
width = 32;
break;
default:
pr_err("Unknown data format\n");
return;
}
for (i = 0; i < ARRAY_SIZE(light_special_sample_rates); i++) {
if (light_special_sample_rates[i] == sample_rate) {
is_divclk1 = true;
break;
}
}
if (is_divclk1 == false) { // audio_divclk0=49152000 for 48k
regmap_update_bits(priv->audio_cpr_regmap,
CPR_PERI_CLK_SEL_REG, CPR_TDM_SRC_SEL_MSK, CPR_TDM_SRC_SEL(0));
src_clk = AUDIO_DIVCLK0;
} else { // audio_divclk1=135475200 for 44.1k
regmap_update_bits(priv->audio_cpr_regmap,
CPR_PERI_CLK_SEL_REG, CPR_TDM_SRC_SEL_MSK, CPR_TDM_SRC_SEL(2));
src_clk = AUDIO_DIVCLK1;
}
div0 = src_clk / (sample_rate * (width * priv->slots));
regmap_update_bits(priv->regmap, TDM_DIV0_LEVEL,
TDMCTL_DIV0_MASK, div0);
//printk("src_clk=%d sample_rate=%d priv->slots=%d width=%d div0=%d\n", src_clk, sample_rate, priv->slots, width, div0);
}
static int light_tdm_dai_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, struct snd_soc_dai *dai)
{
struct light_tdm_priv *priv = snd_soc_dai_get_drvdata(dai);
u32 datawth, chn_num;
if ( params_channels(params) != 1) {
pr_err("Not support channel num\n");
return -EINVAL;
}
if (priv->slot_num != 1) {
return 0;
}
switch(params_format(params)) {
case SNDRV_PCM_FORMAT_S16_LE:
datawth = TDMCTL_DATAWTH_16BIT_PACKED; // TDMCTL_DATAWTH_16BIT
break;
case SNDRV_PCM_FORMAT_S24_LE:
datawth = TDMCTL_DATAWTH_24BIT;
break;
case SNDRV_PCM_FORMAT_S32_LE:
datawth = TDMCTL_DATAWTH_32BIT;
break;
default:
pr_err("Unknown data format\n");
return -EINVAL;
}
switch(priv->slots) {
case 2:
chn_num = TDMCTL_CHNUM_2;
break;
case 4:
chn_num = TDMCTL_CHNUM_4;
break;
case 6:
chn_num = TDMCTL_CHNUM_6;
break;
case 8:
chn_num = TDMCTL_CHNUM_8;
break;
default:
pr_err("Not support slot num\n");
return -EINVAL;
}
regmap_update_bits(priv->regmap, TDM_TDMCTL,
TDMCTL_DATAWTH_MSK, TDMCTL_DATAWTH_SEL(datawth));
regmap_update_bits(priv->regmap, TDM_TDMCTL,
TDMCTL_CHNUM_MSK, TDMCTL_CHNUM_SEL(chn_num));
u32 tdmctl;
regmap_read(priv->regmap, TDM_TDMCTL, &tdmctl);
//printk("%s TDM_TDMCTL=0x%x\n", __func__, tdmctl);
light_tdm_set_div(priv, params);
return 0;
}
static const struct snd_soc_dai_ops light_tdm_dai_ops = {
.startup = light_tdm_dai_startup,
.shutdown = light_tdm_dai_shutdown,
.trigger = light_tdm_dai_trigger,
.set_fmt = light_tdm_set_fmt_dai,
.hw_params = light_tdm_dai_hw_params,
};
static struct snd_soc_dai_driver light_tdm_soc_dai = {
.probe = light_tdm_dai_probe,
.capture = {
.rates = LIGHT_RATES,
.formats = LIGHT_FMTS,
.channels_min = 1,
.channels_max = 1,
},
.ops = &light_tdm_dai_ops,
};
static const struct snd_soc_component_driver light_tdm_soc_component = {
.name = "light_tdm",
};
static void light_tdm_pinctrl(struct light_tdm_priv *tdm_priv)
{
//GPIO_SEL
regmap_update_bits(tdm_priv->audio_pin_regmap,
0x0,0x38000000, 0x38000000);
//MUX
regmap_update_bits(tdm_priv->audio_pin_regmap,
0x8,0xFC00000, 0x5400000);
//strength
regmap_update_bits(tdm_priv->audio_pin_regmap,
0x44,0x0f, 0x07); // AUDIO_PA28
regmap_update_bits(tdm_priv->audio_pin_regmap,
0x40,0xF0000, 0x70000); // AUDIO_PA27
return;
}
static const struct regmap_config light_tdm_regmap_config = {
.reg_bits = 32,
.reg_stride = 4,
.val_bits = 32,
.max_register = TDM_DIV0_LEVEL,
.cache_type = REGCACHE_FLAT,
};
static int light_tdm_runtime_suspend(struct device *dev)
{
struct light_tdm_priv *priv = dev_get_drvdata(dev);
if (priv->slot_num != 1) {
return 0;
}
regcache_cache_only(priv->regmap, true);
clk_disable_unprepare(priv->clk);
return 0;
}
static int light_tdm_runtime_resume(struct device *dev)
{
struct light_tdm_priv *priv = dev_get_drvdata(dev);
int ret;
if (priv->slot_num != 1) {
return 0;
}
ret = clk_prepare_enable(priv->clk);
if (ret) {
dev_err(priv->dev, "clock enable failed %d\n", ret);
return ret;
}
regcache_cache_only(priv->regmap, false);
return ret;
}
static const struct of_device_id light_tdm_of_match[] = {
{ .compatible = "light,light-tdm"},
{},
};
MODULE_DEVICE_TABLE(of, light_tdm_of_match);
irqreturn_t tdm_interrupt(int irq, void* dev_id)
{
struct light_tdm_priv* priv = (struct light_tdm_priv*)dev_id;
return IRQ_HANDLED;
}
static int light_tdm_probe(struct platform_device *pdev)
{
struct device_node *np = pdev->dev.of_node;
const char *sprop;
const uint32_t *iprop;
struct light_tdm_priv *tdm_priv;
struct resource *res;
struct device *dev = &pdev->dev;
unsigned int irq;
int data_register, ret = 0;
tdm_priv = devm_kzalloc(&pdev->dev, sizeof(struct light_tdm_priv), GFP_KERNEL);
if (!tdm_priv) {
return -ENOMEM;
}
tdm_priv->dev = dev;
sprop = of_get_property(np, "light,mode", NULL);
if (sprop) {
if (!strcmp(sprop, "i2s-master")) {
tdm_priv->mode = TDM_MODE_MASTER;
} else if (!strcmp(sprop, "i2s-slave")) {
tdm_priv->mode = TDM_MODE_SLAVE;
} else {
dev_err(dev, "invalid light,mode\n");
return -EINVAL;
}
}
iprop = of_get_property(np, "light,dma_maxburst", NULL);
if (iprop)
tdm_priv->dma_maxburst = be32_to_cpup(iprop);
else
tdm_priv->dma_maxburst = 8;
iprop = of_get_property(np, "light,tdm_slots", NULL);
if (iprop) {
if (be32_to_cpup(iprop) == 2 || be32_to_cpup(iprop) == 4 || be32_to_cpup(iprop) == 6 || be32_to_cpup(iprop) == 8) {
tdm_priv->slots = be32_to_cpup(iprop);
} else {
dev_err(dev, "invalid light,tdm_slots\n");
return -EINVAL;
}
} else {
tdm_priv->slots = 8;
}
iprop = of_get_property(np, "light,tdm_slot_num", NULL);
if (iprop) {
if (be32_to_cpup(iprop) >=1 && be32_to_cpup(iprop) <=8 ) {
tdm_priv->slot_num = be32_to_cpup(iprop);
} else {
dev_err(dev, "invalid light,tdm_slot_num\n");
return -EINVAL;
}
} else {
dev_err(dev, "invalid light,tdm_slot_num\n");
return -EINVAL;
}
dev_set_drvdata(dev, tdm_priv);
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (tdm_priv->slot_num == 1) {
tdm_priv->clk = devm_clk_get(&pdev->dev, "pclk");
if (IS_ERR(tdm_priv->clk))
return PTR_ERR(tdm_priv->clk);
tdm_priv->regs = devm_ioremap_resource(dev, res);
if (IS_ERR(tdm_priv->regs)) {
return PTR_ERR(tdm_priv->regs);
}
tdm_priv->regmap = devm_regmap_init_mmio(dev, tdm_priv->regs,
&light_tdm_regmap_config);
if (IS_ERR(tdm_priv->regmap)) {
dev_err(dev, "Failed to initialise managed register map\n");
return PTR_ERR(tdm_priv->regmap);
}
tdm_priv->audio_pin_regmap = syscon_regmap_lookup_by_phandle(np, "audio-pin-regmap");
if (IS_ERR(tdm_priv->audio_pin_regmap)) {
dev_err(dev, "cannot find regmap for audio system register\n");
return -EINVAL;
} else {
//light_tdm_pinctrl(tdm_priv);
}
tdm_priv->audio_cpr_regmap = syscon_regmap_lookup_by_phandle(np, "audio-cpr-regmap");
if (IS_ERR(tdm_priv->audio_cpr_regmap)) {
dev_err(dev, "cannot find regmap for audio cpr register\n");
return -EINVAL;
}
//AUDIO_DIV1 set to 1/6. 812.8512MHz / 6 = 135.4752MHz
regmap_update_bits(tdm_priv->audio_cpr_regmap,
CPR_PERI_DIV_SEL_REG, CPR_AUDIO_DIV1_SEL_MSK, CPR_AUDIO_DIV1_SEL(5));
//AUDIO_DIV0 set to 1/6. 294.912MHz / 6 = 49.152MHz
regmap_update_bits(tdm_priv->audio_cpr_regmap,
CPR_PERI_DIV_SEL_REG, CPR_AUDIO_DIV0_SEL_MSK, CPR_AUDIO_DIV0_SEL(5));
pm_runtime_enable(&pdev->dev);
pm_runtime_resume_and_get(&pdev->dev); // clk gate is enabled by hardware as default register value
pm_runtime_put_sync(&pdev->dev);
tdm_priv->irq = platform_get_irq(pdev, 0);
if (tdm_priv->irq== 0) {
dev_err(dev, "could not map IRQ.\n");
return -ENXIO;
}
//ret = request_irq(tdm_priv->irq , tdm_interrupt,
// IRQF_SHARED|IRQF_TRIGGER_RISING, "AUDIO_TDM_IRQ", (char *)tdm_priv);
//if (ret) {
// dev_err(dev, "%s[%d]:request irq error!\n", __func__, __LINE__);
// return ret;
//}
}
switch(tdm_priv->slot_num) {
case 1:
data_register = TDM_LDR1;
break;
case 2:
data_register = TDM_RDR1;
break;
case 3:
data_register = TDM_LDR2;
break;
case 4:
data_register = TDM_RDR2;
break;
case 5:
data_register = TDM_LDR3;
break;
case 6:
data_register = TDM_RDR3;
break;
case 7:
data_register = TDM_LDR4;
break;
case 8:
data_register = TDM_RDR4;
break;
default:
dev_err(dev, "invalid slot_num\n");
return -EINVAL;
}
tdm_priv->dma_params_rx.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
tdm_priv->dma_params_rx.maxburst = tdm_priv->dma_maxburst;
tdm_priv->dma_params_rx.addr = res->start + data_register;
ret = light_pcm_dma_init(pdev, LIGHT_TDM_DMABUF_SIZE);
if (ret) {
dev_err(dev, "light_pcm_dma_init error\n");
return -EIO;
}
ret = devm_snd_soc_register_component(dev, &light_tdm_soc_component,
&light_tdm_soc_dai, 1);
if (ret < 0) {
dev_err(&pdev->dev, "cannot snd component register\n");
}
return ret;
}
static int light_tdm_remove(struct platform_device *pdev)
{
struct light_tdm_priv *tdm_priv = dev_get_drvdata(&pdev->dev);
pm_runtime_disable(&pdev->dev);
if (!pm_runtime_status_suspended(&pdev->dev))
light_tdm_runtime_suspend(&pdev->dev);
clk_disable_unprepare(tdm_priv->clk);
return 0;
}
static const struct dev_pm_ops light_tdm_pm_ops = {
SET_RUNTIME_PM_OPS(light_tdm_runtime_suspend, light_tdm_runtime_resume, NULL)
};
static struct platform_driver light_tdm_driver = {
.driver = {
.name = "light-tdm-audio",
.pm = &light_tdm_pm_ops,
.of_match_table = light_tdm_of_match,
},
.probe = light_tdm_probe,
.remove = light_tdm_remove,
};
module_platform_driver(light_tdm_driver);
MODULE_AUTHOR("nanli.yd <nanli.yd@linux.alibaba.com>");
MODULE_DESCRIPTION("Thead Light audio driver");
MODULE_LICENSE("GPL v2");