713 lines
17 KiB
C

// SPDX-License-Identifier: GPL-2.0
/*
* ALSA SoC Audio Layer - Rockchip Multi-DAIS driver
*
* Copyright (c) 2018 Rockchip Electronics Co., Ltd.
* Author: Sugar Zhang <sugar.zhang@rock-chips.com>
*
*/
#include <linux/module.h>
#include <linux/mfd/syscon.h>
#include <linux/of_device.h>
#include <linux/pm_runtime.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
#include "rockchip_multi_dais.h"
#define BITCLOCK_INV_STR "bitclock-inversion"
#define FRAME_INV_STR "frame-inversion"
#define BITCLOCK_MASTER_STR "bitclock-master"
#define FRAME_MASTER_STR "frame-master"
#define DAIS_DRV_NAME "rockchip-mdais"
#define RK3308_GRF_SOC_CON2 0x308
#define SOUND_NAME_PREFIX "sound-name-prefix"
#define I2S_CKR 0x8
#define IS_I2S_TRCM(v) ((v) & GENMASK(29, 28))
static inline struct rk_mdais_dev *to_info(struct snd_soc_dai *dai)
{
return snd_soc_dai_get_drvdata(dai);
}
static inline unsigned int *mdais_channel_maps(struct rk_mdais_dev *mdais,
struct snd_pcm_substream *substream)
{
return substream->stream ? mdais->capture_channel_maps :
mdais->playback_channel_maps;
}
static void hw_refine_channels(struct snd_pcm_hw_params *params,
unsigned int channel)
{
struct snd_interval *c =
hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS);
c->min = channel;
c->max = channel;
}
static int rockchip_mdais_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *dai)
{
struct rk_mdais_dev *mdais = to_info(dai);
struct snd_pcm_hw_params *cparams;
struct snd_soc_dai *child;
unsigned int *channel_maps;
unsigned int freq;
int ret = 0, i = 0;
cparams = kmemdup(params, sizeof(*params), GFP_KERNEL);
if (IS_ERR(cparams))
return PTR_ERR(cparams);
channel_maps = mdais_channel_maps(mdais, substream);
for (i = 0; i < mdais->num_dais; i++) {
child = mdais->dais[i].dai;
if (!channel_maps[i])
continue;
if (mdais->mclk_fs_maps[i] > 0) {
freq = params_rate(params) * mdais->mclk_fs_maps[i];
ret = snd_soc_dai_set_sysclk(child, substream->stream, freq,
SND_SOC_CLOCK_OUT);
if (ret && ret != -ENOTSUPP) {
dev_err(dai->dev, "Set sysclk(%uHZ) failed: %d\n", freq, ret);
break;
}
}
hw_refine_channels(cparams, channel_maps[i]);
if (child->driver->ops && child->driver->ops->hw_params) {
ret = child->driver->ops->hw_params(substream, cparams, child);
if (ret < 0) {
dev_err(dai->dev, "Failed to set %s hw params: %d\n",
dai->name, ret);
break;
}
}
}
kfree(cparams);
return ret;
}
static int rockchip_mdais_hw_free(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
struct rk_mdais_dev *mdais = to_info(dai);
struct snd_soc_dai *child;
unsigned int *channel_maps;
int ret = 0, i = 0;
channel_maps = mdais_channel_maps(mdais, substream);
for (i = 0; i < mdais->num_dais; i++) {
child = mdais->dais[i].dai;
if (!channel_maps[i])
continue;
if (child->driver->ops && child->driver->ops->hw_free) {
ret = child->driver->ops->hw_free(substream, child);
if (ret < 0)
return ret;
}
}
return 0;
}
static int rockchip_mdais_trigger(struct snd_pcm_substream *substream,
int cmd, struct snd_soc_dai *dai)
{
struct rk_mdais_dev *mdais = to_info(dai);
struct snd_soc_dai *child;
unsigned int *channel_maps;
int ret = 0, i = 0;
channel_maps = mdais_channel_maps(mdais, substream);
for (i = 0; i < mdais->num_dais; i++) {
/* skip DAIs which have no channel mapping */
if (!channel_maps[i])
continue;
child = mdais->dais[i].dai;
if (child->driver->ops && child->driver->ops->trigger) {
ret = child->driver->ops->trigger(substream,
cmd, child);
if (ret < 0)
return ret;
}
}
return 0;
}
static int rockchip_mdais_startup(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
struct rk_mdais_dev *mdais = to_info(dai);
struct snd_soc_dai *child;
unsigned int *channel_maps;
int ret = 0, i = 0;
channel_maps = mdais_channel_maps(mdais, substream);
for (i = 0; i < mdais->num_dais; i++) {
if (!channel_maps[i])
continue;
child = mdais->dais[i].dai;
if (child->driver->ops && child->driver->ops->startup) {
ret = child->driver->ops->startup(substream, child);
if (ret < 0)
return ret;
}
}
return 0;
}
static void rockchip_mdais_shutdown(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
struct rk_mdais_dev *mdais = to_info(dai);
struct snd_soc_dai *child;
unsigned int *channel_maps;
int i = 0;
channel_maps = mdais_channel_maps(mdais, substream);
for (i = 0; i < mdais->num_dais; i++) {
if (!channel_maps[i])
continue;
child = mdais->dais[i].dai;
if (child->driver->ops && child->driver->ops->shutdown) {
child->driver->ops->shutdown(substream, child);
}
}
}
static int rockchip_mdais_prepare(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
struct rk_mdais_dev *mdais = to_info(dai);
struct snd_soc_dai *child;
unsigned int *channel_maps;
int ret = 0, i = 0;
channel_maps = mdais_channel_maps(mdais, substream);
for (i = 0; i < mdais->num_dais; i++) {
if (!channel_maps[i])
continue;
child = mdais->dais[i].dai;
if (child->driver->ops && child->driver->ops->prepare) {
ret = child->driver->ops->prepare(substream, child);
if (ret < 0)
return ret;
}
}
return 0;
}
static int rockchip_mdais_set_sysclk(struct snd_soc_dai *cpu_dai, int clk_id,
unsigned int freq, int dir)
{
struct rk_mdais_dev *mdais = to_info(cpu_dai);
struct snd_soc_dai *child;
int ret, i = 0;
for (i = 0; i < mdais->num_dais; i++) {
child = mdais->dais[i].dai;
if (mdais->mclk_fs_maps[i] > 0)
continue;
ret = snd_soc_dai_set_sysclk(child, clk_id, freq, dir);
if (ret && ret != -ENOTSUPP) {
dev_err(cpu_dai->dev, "Set soc_dai sysclk(%uHZ) failed: %d\n", freq, ret);
return ret;
}
}
return 0;
}
static int rockchip_mdais_set_fmt(struct snd_soc_dai *cpu_dai,
unsigned int fmt)
{
struct rk_mdais_dev *mdais = to_info(cpu_dai);
struct snd_soc_dai *child;
unsigned int dai_fmt;
int ret, i = 0;
for (i = 0; i < mdais->num_dais; i++) {
child = mdais->dais[i].dai;
dai_fmt = fmt;
if (mdais->dais[i].fmt_msk) {
dai_fmt &= ~(mdais->dais[i].fmt_msk);
dai_fmt |= mdais->dais[i].fmt;
}
ret = snd_soc_dai_set_fmt(child, dai_fmt);
if (ret && ret != -ENOTSUPP)
return ret;
}
return 0;
}
static int rockchip_mdais_tdm_slot(struct snd_soc_dai *dai,
unsigned int tx_mask, unsigned int rx_mask,
int slots, int slot_width)
{
struct rk_mdais_dev *mdais = to_info(dai);
struct snd_soc_dai *child;
int ret, i = 0;
for (i = 0; i < mdais->num_dais; i++) {
child = mdais->dais[i].dai;
ret = snd_soc_dai_set_tdm_slot(child, tx_mask, rx_mask,
slots, slot_width);
if (ret && ret != -ENOTSUPP)
return ret;
}
return 0;
}
static int rockchip_mdais_dai_probe(struct snd_soc_dai *dai)
{
struct rk_mdais_dev *mdais = to_info(dai);
struct snd_soc_component *comp;
struct snd_soc_dai *child;
const char *str;
int ret, i = 0;
if (dai->probed)
return 0;
for (i = 0; i < mdais->num_dais; i++) {
child = mdais->dais[i].dai;
comp = child->component;
if (!child->probed && child->driver->probe) {
if (!comp->name_prefix) {
ret = device_property_read_string(child->dev,
SOUND_NAME_PREFIX, &str);
if (!ret)
comp->name_prefix = str;
}
comp->card = dai->component->card;
ret = child->driver->probe(child);
if (ret < 0) {
dev_err(child->dev,
"Failed to probe DAI %s: %d\n",
child->name, ret);
return ret;
}
ret = snd_soc_add_component_controls(comp,
comp->driver->controls,
comp->driver->num_controls);
if (ret)
dev_err(dai->dev, "%s: Failed to add controls, should add '%s' in DT\n",
dev_name(child->dev), SOUND_NAME_PREFIX);
dai->probed = 1;
}
}
return 0;
}
static const struct snd_soc_dai_ops rockchip_mdais_dai_ops = {
.hw_params = rockchip_mdais_hw_params,
.hw_free = rockchip_mdais_hw_free,
.set_sysclk = rockchip_mdais_set_sysclk,
.set_fmt = rockchip_mdais_set_fmt,
.set_tdm_slot = rockchip_mdais_tdm_slot,
.trigger = rockchip_mdais_trigger,
.startup = rockchip_mdais_startup,
.shutdown = rockchip_mdais_shutdown,
.prepare = rockchip_mdais_prepare,
};
static const struct snd_soc_component_driver rockchip_mdais_component = {
.name = DAIS_DRV_NAME,
.legacy_dai_naming = 1,
};
static const struct of_device_id rockchip_mdais_match[] = {
{ .compatible = "rockchip,multi-dais", },
{ .compatible = "rockchip,rk3308-multi-dais", },
{},
};
static struct snd_soc_dai *rockchip_mdais_find_dai(struct device_node *np)
{
struct snd_soc_dai_link_component dai_component = { 0 };
dai_component.of_node = np;
return snd_soc_find_dai_with_mutex(&dai_component);
}
static int mdais_runtime_suspend(struct device *dev)
{
struct rk_mdais_dev *mdais = dev_get_drvdata(dev);
struct snd_soc_dai *child;
int i = 0;
for (i = 0; i < mdais->num_dais; i++) {
child = mdais->dais[i].dai;
pm_runtime_put(child->dev);
}
return 0;
}
static int mdais_runtime_resume(struct device *dev)
{
struct rk_mdais_dev *mdais = dev_get_drvdata(dev);
struct snd_soc_dai *child;
int i = 0;
for (i = 0; i < mdais->num_dais; i++) {
child = mdais->dais[i].dai;
pm_runtime_get_sync(child->dev);
}
return 0;
}
static int mdais_read_prop_array(struct device_node *node,
const char *propname,
unsigned int *array, int num)
{
int ret = 0;
memset(array, 0, sizeof(*array) * num);
if (of_property_read_bool(node, propname)) {
ret = of_property_read_u32_array(node, propname, array, num);
if (ret)
ret = -EINVAL;
} else {
ret = -EINVAL;
}
return ret;
}
static void mdais_parse_daifmt(struct device_node *node, struct rk_dai *dais,
int num_dai)
{
unsigned int cinv[MAX_DAIS], finv[MAX_DAIS];
unsigned int cmst[MAX_DAIS], fmst[MAX_DAIS];
unsigned int format = 0, format_mask = 0;
int i = 0, ret = 0;
ret = mdais_read_prop_array(node, BITCLOCK_INV_STR, cinv, num_dai);
if (!ret)
format_mask |= SND_SOC_DAIFMT_INV_MASK;
ret = mdais_read_prop_array(node, FRAME_INV_STR, finv, num_dai);
if (!ret)
format_mask |= SND_SOC_DAIFMT_INV_MASK;
ret = mdais_read_prop_array(node, BITCLOCK_MASTER_STR, cmst, num_dai);
if (!ret)
format_mask |= SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK;
ret = mdais_read_prop_array(node, FRAME_MASTER_STR, fmst, num_dai);
if (!ret)
format_mask |= SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK;
for (i = 0; i < num_dai; i++) {
format = 0;
switch ((cinv[i] << 4) + finv[i]) {
case 0x11:
format |= SND_SOC_DAIFMT_IB_IF;
break;
case 0x10:
format |= SND_SOC_DAIFMT_IB_NF;
break;
case 0x01:
format |= SND_SOC_DAIFMT_NB_IF;
break;
default:
/* SND_SOC_DAIFMT_NB_NF is default */
break;
}
switch ((cmst[i] << 4) + fmst[i]) {
case 0x11:
format |= SND_SOC_DAIFMT_BP_FP;
break;
case 0x10:
format |= SND_SOC_DAIFMT_BP_FC;
break;
case 0x01:
format |= SND_SOC_DAIFMT_BC_FP;
break;
default:
format |= SND_SOC_DAIFMT_BC_FC;
break;
}
dais[i].fmt = format & format_mask;
dais[i].fmt_msk = format_mask;
}
}
static int rockchip_mdais_dai_prepare(struct platform_device *pdev,
struct snd_soc_dai_driver **soc_dai)
{
struct snd_soc_dai_driver rockchip_mdais_dai = {
.probe = rockchip_mdais_dai_probe,
.playback = {
.stream_name = "Playback",
.channels_min = 1,
.channels_max = 512,
.rates = SNDRV_PCM_RATE_8000_384000,
.formats = (SNDRV_PCM_FMTBIT_S8 |
SNDRV_PCM_FMTBIT_S16_LE |
SNDRV_PCM_FMTBIT_S20_3LE |
SNDRV_PCM_FMTBIT_S24_LE |
SNDRV_PCM_FMTBIT_S32_LE),
},
.capture = {
.stream_name = "Capture",
.channels_min = 1,
.channels_max = 512,
.rates = SNDRV_PCM_RATE_8000_384000,
.formats = (SNDRV_PCM_FMTBIT_S8 |
SNDRV_PCM_FMTBIT_S16_LE |
SNDRV_PCM_FMTBIT_S20_3LE |
SNDRV_PCM_FMTBIT_S24_LE |
SNDRV_PCM_FMTBIT_S32_LE),
},
.ops = &rockchip_mdais_dai_ops,
};
*soc_dai = devm_kmemdup(&pdev->dev, &rockchip_mdais_dai,
sizeof(rockchip_mdais_dai), GFP_KERNEL);
if (!(*soc_dai))
return -ENOMEM;
return 0;
}
static void mdais_fixup_dai(struct snd_soc_dai_driver *soc_dai,
struct rk_mdais_dev *mdais)
{
int i, tch, rch;
unsigned int *tx_maps, *rx_maps;
tch = 0;
rch = 0;
tx_maps = mdais->playback_channel_maps;
rx_maps = mdais->capture_channel_maps;
for (i = 0; i < mdais->num_dais; i++) {
tch += tx_maps[i];
rch += rx_maps[i];
}
soc_dai->playback.channels_min = tch;
soc_dai->playback.channels_max = tch;
soc_dai->capture.channels_min = rch;
soc_dai->capture.channels_max = rch;
}
static int rockchip_mdais_probe(struct platform_device *pdev)
{
struct device_node *np = pdev->dev.of_node;
struct platform_device *sub_pdev;
struct rk_mdais_dev *mdais;
struct device_node *node;
struct snd_soc_dai_driver *soc_dai;
struct rk_dai *dais;
unsigned int *map, val;
int count, mp_count;
int ret = 0, i = 0;
ret = rockchip_mdais_dai_prepare(pdev, &soc_dai);
if (ret < 0)
return ret;
mdais = devm_kzalloc(&pdev->dev, sizeof(*mdais), GFP_KERNEL);
if (!mdais)
return -ENOMEM;
count = of_count_phandle_with_args(np, "dais", NULL);
if (count < 0 || count > MAX_DAIS)
return -EINVAL;
mp_count = of_property_count_u32_elems(np, "capture,channel-mapping");
if (mp_count != count)
return -EINVAL;
mp_count = of_property_count_u32_elems(np, "playback,channel-mapping");
if (mp_count != count)
return -EINVAL;
mdais->num_dais = count;
dais = devm_kcalloc(&pdev->dev, count,
sizeof(*dais), GFP_KERNEL);
if (!dais)
return -ENOMEM;
map = devm_kcalloc(&pdev->dev, count,
sizeof(*map), GFP_KERNEL);
if (!map)
return -ENOMEM;
ret = of_property_read_u32_array(np, "capture,channel-mapping",
map, count);
if (ret)
return -EINVAL;
mdais->capture_channel_maps = map;
map = devm_kcalloc(&pdev->dev, count,
sizeof(*map), GFP_KERNEL);
if (!map)
return -ENOMEM;
ret = of_property_read_u32_array(np, "playback,channel-mapping",
map, count);
if (ret)
return -EINVAL;
mdais->playback_channel_maps = map;
map = devm_kcalloc(&pdev->dev, count,
sizeof(*map), GFP_KERNEL);
if (!map)
return -ENOMEM;
ret = of_property_read_u32_array(np, "mclk-fs-mapping",
map, count);
if (ret)
memset(map, 0x0, sizeof(*map) * count);
mdais->mclk_fs_maps = map;
for (i = 0; i < count; i++) {
node = of_parse_phandle(np, "dais", i);
sub_pdev = of_find_device_by_node(node);
if (!sub_pdev) {
dev_err(&pdev->dev, "fail to find subnode dev\n");
return -ENODEV;
}
dais[i].of_node = node;
dais[i].dev = &sub_pdev->dev;
dais[i].dai = rockchip_mdais_find_dai(node);
if (!dais[i].dai)
return -EPROBE_DEFER;
if (strstr(dev_driver_string(dais[i].dai->dev), "i2s")) {
val = snd_soc_component_read(dais[i].dai->component, I2S_CKR);
dais[i].trcm = IS_I2S_TRCM(val);
}
}
mdais_parse_daifmt(np, dais, count);
mdais_fixup_dai(soc_dai, mdais);
if (of_device_is_compatible(np, "rockchip,rk3308-multi-dais")) {
struct regmap *grf;
const char *name;
unsigned int i2s0_fmt = 0, i2s1_fmt = 0;
for (i = 0; i < count; i++) {
name = dev_name(dais[i].dev);
if (strstr(name, "ff300000"))
i2s0_fmt = dais[i].fmt;
else if (strstr(name, "ff310000"))
i2s1_fmt = dais[i].fmt;
}
i2s0_fmt &= SND_SOC_DAIFMT_MASTER_MASK;
i2s1_fmt &= SND_SOC_DAIFMT_MASTER_MASK;
if ((i2s0_fmt == SND_SOC_DAIFMT_CBS_CFS &&
i2s1_fmt == SND_SOC_DAIFMT_CBM_CFM) ||
(i2s0_fmt == SND_SOC_DAIFMT_CBM_CFM &&
i2s1_fmt == SND_SOC_DAIFMT_CBS_CFS)) {
grf = syscon_regmap_lookup_by_phandle(np,
"rockchip,grf");
if (IS_ERR(grf))
return PTR_ERR(grf);
dev_info(&pdev->dev, "enable i2s 16ch ctrl en\n");
regmap_write(grf, RK3308_GRF_SOC_CON2,
BIT(14) << 16 | BIT(14));
}
}
mdais->dais = dais;
mdais->dev = &pdev->dev;
dev_set_drvdata(&pdev->dev, mdais);
pm_runtime_enable(&pdev->dev);
if (!pm_runtime_enabled(&pdev->dev)) {
ret = mdais_runtime_resume(&pdev->dev);
if (ret)
goto err_pm_disable;
}
ret = snd_dmaengine_mpcm_register(mdais);
if (ret) {
dev_err(&pdev->dev, "Could not register PCM\n");
goto err_suspend;
}
ret = devm_snd_soc_register_component(&pdev->dev,
&rockchip_mdais_component,
soc_dai, 1);
if (ret) {
dev_err(&pdev->dev, "could not register dai: %d\n", ret);
goto err_suspend;
}
return 0;
err_suspend:
if (!pm_runtime_status_suspended(&pdev->dev))
mdais_runtime_resume(&pdev->dev);
err_pm_disable:
pm_runtime_disable(&pdev->dev);
return ret;
}
static int rockchip_mdais_remove(struct platform_device *pdev)
{
snd_dmaengine_mpcm_unregister(&pdev->dev);
pm_runtime_disable(&pdev->dev);
if (!pm_runtime_status_suspended(&pdev->dev))
mdais_runtime_suspend(&pdev->dev);
return 0;
}
static const struct dev_pm_ops rockchip_mdais_pm_ops = {
SET_RUNTIME_PM_OPS(mdais_runtime_suspend, mdais_runtime_resume,
NULL)
};
static struct platform_driver rockchip_mdais_driver = {
.probe = rockchip_mdais_probe,
.remove = rockchip_mdais_remove,
.driver = {
.name = DAIS_DRV_NAME,
.of_match_table = of_match_ptr(rockchip_mdais_match),
.pm = &rockchip_mdais_pm_ops,
},
};
module_platform_driver(rockchip_mdais_driver);
MODULE_DESCRIPTION("ROCKCHIP MULTI-DAIS ASoC Interface");
MODULE_AUTHOR("Sugar Zhang <sugar.zhang@rock-chips.com>");
MODULE_LICENSE("GPL v2");
MODULE_ALIAS("platform:" DAIS_DRV_NAME);
MODULE_DEVICE_TABLE(of, rockchip_mdais_match);