// SPDX-License-Identifier: GPL-2.0 /* * ALSA SoC Audio Layer - Rockchip Multi-DAIS driver * * Copyright (c) 2018 Rockchip Electronics Co., Ltd. * Author: Sugar Zhang * */ #include #include #include #include #include #include #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 "); MODULE_LICENSE("GPL v2"); MODULE_ALIAS("platform:" DAIS_DRV_NAME); MODULE_DEVICE_TABLE(of, rockchip_mdais_match);