blob: 63b8835a535925bba34a9ec7e9b8fe475a8b8246 [file] [log] [blame]
/*
* sound/soc/amlogic/meson/pcm_dai.c
*
* Copyright (C) 2017 Amlogic, Inc. 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 as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* 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.
*
*/
#undef pr_fmt
#define pr_fmt(fmt) "snd_pcm_dai: " fmt
#include <linux/init.h>
#include <linux/module.h>
#include <linux/interrupt.h>
#include <linux/device.h>
#include <linux/delay.h>
#include <linux/clk.h>
#include <linux/gpio/consumer.h>
#include <linux/pinctrl/consumer.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/initval.h>
#include <sound/soc.h>
#include "pcm_dai.h"
#include "pcm.h"
#include "i2s.h"
#include "audio_hw_pcm.h"
#include <linux/of.h>
#define DEV_NAME "aml-pcm-dai"
#define AML_DAI_PCM_RATES (SNDRV_PCM_RATE_8000_192000)
#define AML_DAI_PCM_FORMATS (SNDRV_PCM_FMTBIT_S16_LE |\
SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE)
#define PCM_DEFAULT_SAMPLERATE 8000
#define PCM_DEFAULT_MCLK_RATIO_SR 256
#define PCM_24BIT_MCLK_RATIO_SR 384
#define PCM_32BIT_MCLK_RATIO_SR 512
static int aml_pcm_set_clk(struct aml_pcm *pcm, unsigned long rate)
{
int ret = 0;
ret = clk_set_rate(pcm->clk_mpll, rate * 60);
if (ret) {
pr_info("Cannot set pcm mpll\n");
return ret;
}
ret = clk_set_parent(pcm->clk_pcm_mclk, pcm->clk_mpll);
if (ret) {
pr_info("Cannot set pcm mclk parent\n");
return ret;
}
ret = clk_set_rate(pcm->clk_pcm_mclk, rate);
if (ret) {
pr_info("Cannot set pcm mclk rate:%lu\n", rate);
return ret;
}
return 0;
}
static int aml_pcm_dai_startup(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
pr_debug("***Entered %s\n", __func__);
return 0;
}
static void aml_pcm_dai_shutdown(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
pr_debug("***Entered %s\n", __func__);
}
static int aml_pcm_dai_prepare(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
struct snd_pcm_runtime *runtime = substream->runtime;
struct aml_pcm_runtime_data *prtd = runtime->private_data;
struct aml_pcm *pcm = snd_soc_dai_get_drvdata(dai);
int mclk_rate, pcm_bit;
pr_debug("***Entered %s\n", __func__);
/* set bclk */
if (runtime->format == SNDRV_PCM_FORMAT_S32_LE)
pcm_bit = 32;
else if (runtime->format == SNDRV_PCM_FORMAT_S24_LE)
pcm_bit = 24;
else
pcm_bit = 16;
mclk_rate = runtime->rate * pcm_bit * runtime->channels;
pr_info("%s rate:%d, bits:%d, channels:%d, mclk:%d\n",
__func__,
runtime->rate,
pcm_bit,
runtime->channels,
mclk_rate);
aml_pcm_set_clk(pcm, mclk_rate);
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
pr_info(
"%s playback stream buffer start: %ld size: 0x%x\n",
__func__, (long)prtd->buffer_start, prtd->buffer_size);
pcm_out_set_buf(prtd->buffer_start, prtd->buffer_size);
} else {
pr_info(
"%s capture stream buffer start: %ld size: 0x%x\n",
__func__, (long)prtd->buffer_start, prtd->buffer_size);
pcm_in_set_buf(prtd->buffer_start, prtd->buffer_size);
}
memset(runtime->dma_area, 0, runtime->dma_bytes);
prtd->buffer_offset = 0;
prtd->data_size = 0;
prtd->period_elapsed = 0;
return 0;
}
static int aml_pcm_dai_trigger(struct snd_pcm_substream *substream, int cmd,
struct snd_soc_dai *dai)
{
struct aml_pcm *pcm_p = dev_get_drvdata(dai->dev);
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
case SNDRV_PCM_TRIGGER_RESUME:
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
/* TODO */
if (pcm_p && pcm_p->pcm_mode) {
pr_info("aiu pcm master stream %d enable\n\n",
substream->stream);
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
pcm_master_out_enable(substream, 1);
else
pcm_master_in_enable(substream, 1);
} else {
pr_info("aiu slave pcm stream %d enable\n\n",
substream->stream);
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
pcm_out_enable(substream, 1);
else
pcm_in_enable(substream, 1);
}
break;
case SNDRV_PCM_TRIGGER_STOP:
case SNDRV_PCM_TRIGGER_SUSPEND:
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
if (pcm_p && pcm_p->pcm_mode) {
pr_info("aiu master pcm stream %d disable\n\n",
substream->stream);
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
pcm_master_out_enable(substream, 0);
else
pcm_master_in_enable(substream, 0);
} else {
pr_info("aiu slave pcm stream %d disable\n\n",
substream->stream);
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
pcm_out_enable(substream, 0);
else
pcm_in_enable(substream, 0);
}
break;
default:
return -EINVAL;
}
return 0;
}
static int aml_pcm_dai_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *dai)
{
pr_debug("***Entered %s:%s\n", __FILE__, __func__);
return 0;
}
static int aml_pcm_dai_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
{
pr_debug("***Entered %s\n", __func__);
aml_set_pcm_format(fmt & SND_SOC_DAIFMT_FORMAT_MASK);
return 0;
}
static int aml_pcm_dai_set_sysclk(struct snd_soc_dai *dai,
int clk_id, unsigned int freq, int dir)
{
pr_debug("***Entered %s\n", __func__);
return 0;
}
#ifdef CONFIG_PM
static int aml_pcm_dai_suspend(struct snd_soc_dai *dai)
{
pr_debug("***Entered %s\n", __func__);
return 0;
}
static int aml_pcm_dai_resume(struct snd_soc_dai *dai)
{
pr_debug("***Entered %s\n", __func__);
return 0;
}
#else /* CONFIG_PM */
#define aml_pcm_dai_suspend NULL
#define aml_pcm_dai_resume NULL
#endif /* CONFIG_PM */
static struct snd_soc_dai_ops aml_pcm_dai_ops = {
.startup = aml_pcm_dai_startup,
.shutdown = aml_pcm_dai_shutdown,
.prepare = aml_pcm_dai_prepare,
.trigger = aml_pcm_dai_trigger,
.hw_params = aml_pcm_dai_hw_params,
.set_fmt = aml_pcm_dai_set_fmt,
.set_sysclk = aml_pcm_dai_set_sysclk,
};
struct snd_soc_dai_driver aml_pcm_dai[] = {
{
.suspend = aml_pcm_dai_suspend,
.resume = aml_pcm_dai_resume,
.playback = {
.channels_min = 1,
.channels_max = 16,
.rates = AML_DAI_PCM_RATES,
.formats = AML_DAI_PCM_FORMATS,},
.capture = {
.channels_min = 1,
.channels_max = 16,
.rates = AML_DAI_PCM_RATES,
.formats = AML_DAI_PCM_FORMATS,},
.ops = &aml_pcm_dai_ops,
},
};
EXPORT_SYMBOL_GPL(aml_pcm_dai);
static const struct snd_soc_component_driver aml_component = {
.name = DEV_NAME,
};
static int aml_pcm_dai_probe(struct platform_device *pdev)
{
struct pinctrl *pin_ctl;
struct aml_pcm *pcm_p = NULL;
int ret;
pr_debug("enter %s\n", __func__);
pin_ctl = devm_pinctrl_get_select(&pdev->dev, "audio_pcm");
if (IS_ERR(pin_ctl)) {
pin_ctl = NULL;
pr_err("aml audio pcm dai pinmux set error!\n");
}
pcm_p = devm_kzalloc(&pdev->dev, sizeof(struct aml_pcm), GFP_KERNEL);
if (!pcm_p) {
/*dev_err(&pdev->dev, "Can't allocate pcm_p\n");*/
ret = -ENOMEM;
goto err;
}
dev_set_drvdata(&pdev->dev, pcm_p);
/* is PCM master? */
ret =
of_property_read_u32((&pdev->dev)->of_node, "pcm_mode",
&pcm_p->pcm_mode);
pr_info("pcm mode detection =%d\n", pcm_p->pcm_mode);
if (pcm_p->pcm_mode) {
pcm_p->clk_mpll = devm_clk_get(&pdev->dev, "mpll0");
if (IS_ERR(pcm_p->clk_mpll)) {
dev_err(&pdev->dev, "Can't retrieve mpll0 clock\n");
ret = PTR_ERR(pcm_p->clk_mpll);
goto err;
}
pcm_p->clk_pcm_mclk = devm_clk_get(&pdev->dev, "pcm_mclk");
if (IS_ERR(pcm_p->clk_pcm_mclk)) {
dev_err(&pdev->dev, "Can't retrieve clk_pcm_mclk clock\n");
ret = PTR_ERR(pcm_p->clk_pcm_mclk);
goto err;
}
pcm_p->clk_pcm_sync = devm_clk_get(&pdev->dev, "pcm_sclk");
if (IS_ERR(pcm_p->clk_pcm_sync)) {
dev_err(&pdev->dev, "Can't retrieve clk_pcm_sync clock\n");
ret = PTR_ERR(pcm_p->clk_pcm_sync);
goto err;
}
/* Default 256fs */
ret = aml_pcm_set_clk(pcm_p,
PCM_DEFAULT_SAMPLERATE * PCM_DEFAULT_MCLK_RATIO_SR);
if (ret < 0) {
dev_err(&pdev->dev, "Can't set aml_pcm clk :%d\n", ret);
goto err;
}
ret = clk_prepare_enable(pcm_p->clk_pcm_mclk);
if (ret) {
dev_err(&pdev->dev,
"Can't enable pcm clk_pcm_mclk clock: %d\n", ret);
goto err;
}
ret = clk_prepare_enable(pcm_p->clk_pcm_sync);
if (ret) {
dev_err(&pdev->dev,
"Can't enable pcm clk_pcm_sync clock: %d\n", ret);
goto err;
}
}
ret = snd_soc_register_component(&pdev->dev, &aml_component,
aml_pcm_dai, ARRAY_SIZE(aml_pcm_dai));
err:
return ret;
}
static int aml_pcm_dai_remove(struct platform_device *pdev)
{
struct aml_pcm *pcm_priv = dev_get_drvdata(&pdev->dev);
clk_disable_unprepare(pcm_priv->clk_pcm_mclk);
clk_disable_unprepare(pcm_priv->clk_pcm_sync);
snd_soc_unregister_component(&pdev->dev);
return 0;
}
static void aml_pcm_dai_plat_shutdown(struct platform_device *pdev)
{
struct aml_pcm *pcm_priv = dev_get_drvdata(&pdev->dev);
clk_disable_unprepare(pcm_priv->clk_pcm_mclk);
clk_disable_unprepare(pcm_priv->clk_pcm_sync);
snd_soc_unregister_component(&pdev->dev);
}
#ifdef CONFIG_OF
static const struct of_device_id amlogic_pcm_dai_match[] = {
{.compatible = "amlogic, aml-pcm-dai",
},
{},
};
#else
#define amlogic_pcm_dai_match NULL
#endif
static struct platform_driver aml_pcm_dai_driver = {
.driver = {
.name = DEV_NAME,
.owner = THIS_MODULE,
.of_match_table = amlogic_pcm_dai_match,
},
.probe = aml_pcm_dai_probe,
.remove = aml_pcm_dai_remove,
.shutdown = aml_pcm_dai_plat_shutdown,
};
static int __init aml_pcm_dai_modinit(void)
{
return platform_driver_register(&aml_pcm_dai_driver);
}
module_init(aml_pcm_dai_modinit);
static void __exit aml_pcm_dai_modexit(void)
{
platform_driver_unregister(&aml_pcm_dai_driver);
}
module_exit(aml_pcm_dai_modexit);
/* Module information */
MODULE_AUTHOR("AMLogic, Inc.");
MODULE_DESCRIPTION("AML DAI driver for ALSA");
MODULE_LICENSE("GPL");