mirror of
https://mirrors.bfsu.edu.cn/git/linux.git
synced 2024-12-04 01:24:12 +08:00
340d79a14d
The DT of_device.h and of_platform.h date back to the separate of_platform_bus_type before it was merged into the regular platform bus. As part of that merge prepping Arm DT support 13 years ago, they "temporarily" include each other. They also include platform_device.h and of.h. As a result, there's a pretty much random mix of those include files used throughout the tree. In order to detangle these headers and replace the implicit includes with struct declarations, users need to explicitly include the correct includes. Acked-by: Jernej Skrabec <jernej.skrabec@gmail.com> Reviewed-by: AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com> Acked-by: Charles Keepax <ckeepax@opensource.cirrus.com> Reviewed-by: Claudiu Beznea <claudiu.beznea@tuxon.dev> # for at91 Signed-off-by: Rob Herring <robh@kernel.org> Link: https://lore.kernel.org/r/20231006-dt-asoc-header-cleanups-v3-1-13a4f0f7fee6@kernel.org Signed-off-by: Mark Brown <broonie@kernel.org>
528 lines
12 KiB
C
528 lines
12 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* omap-dmic.c -- OMAP ASoC DMIC DAI driver
|
|
*
|
|
* Copyright (C) 2010 - 2011 Texas Instruments
|
|
*
|
|
* Author: David Lambert <dlambert@ti.com>
|
|
* Misael Lopez Cruz <misael.lopez@ti.com>
|
|
* Liam Girdwood <lrg@ti.com>
|
|
* Peter Ujfalusi <peter.ujfalusi@ti.com>
|
|
*/
|
|
|
|
#include <linux/init.h>
|
|
#include <linux/mod_devicetable.h>
|
|
#include <linux/module.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/err.h>
|
|
#include <linux/clk.h>
|
|
#include <linux/io.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/pm_runtime.h>
|
|
|
|
#include <sound/core.h>
|
|
#include <sound/pcm.h>
|
|
#include <sound/pcm_params.h>
|
|
#include <sound/initval.h>
|
|
#include <sound/soc.h>
|
|
#include <sound/dmaengine_pcm.h>
|
|
|
|
#include "omap-dmic.h"
|
|
#include "sdma-pcm.h"
|
|
|
|
struct omap_dmic {
|
|
struct device *dev;
|
|
void __iomem *io_base;
|
|
struct clk *fclk;
|
|
struct pm_qos_request pm_qos_req;
|
|
int latency;
|
|
int fclk_freq;
|
|
int out_freq;
|
|
int clk_div;
|
|
int sysclk;
|
|
int threshold;
|
|
u32 ch_enabled;
|
|
bool active;
|
|
struct mutex mutex;
|
|
|
|
struct snd_dmaengine_dai_dma_data dma_data;
|
|
};
|
|
|
|
static inline void omap_dmic_write(struct omap_dmic *dmic, u16 reg, u32 val)
|
|
{
|
|
writel_relaxed(val, dmic->io_base + reg);
|
|
}
|
|
|
|
static inline int omap_dmic_read(struct omap_dmic *dmic, u16 reg)
|
|
{
|
|
return readl_relaxed(dmic->io_base + reg);
|
|
}
|
|
|
|
static inline void omap_dmic_start(struct omap_dmic *dmic)
|
|
{
|
|
u32 ctrl = omap_dmic_read(dmic, OMAP_DMIC_CTRL_REG);
|
|
|
|
/* Configure DMA controller */
|
|
omap_dmic_write(dmic, OMAP_DMIC_DMAENABLE_SET_REG,
|
|
OMAP_DMIC_DMA_ENABLE);
|
|
|
|
omap_dmic_write(dmic, OMAP_DMIC_CTRL_REG, ctrl | dmic->ch_enabled);
|
|
}
|
|
|
|
static inline void omap_dmic_stop(struct omap_dmic *dmic)
|
|
{
|
|
u32 ctrl = omap_dmic_read(dmic, OMAP_DMIC_CTRL_REG);
|
|
omap_dmic_write(dmic, OMAP_DMIC_CTRL_REG,
|
|
ctrl & ~OMAP_DMIC_UP_ENABLE_MASK);
|
|
|
|
/* Disable DMA request generation */
|
|
omap_dmic_write(dmic, OMAP_DMIC_DMAENABLE_CLR_REG,
|
|
OMAP_DMIC_DMA_ENABLE);
|
|
|
|
}
|
|
|
|
static inline int dmic_is_enabled(struct omap_dmic *dmic)
|
|
{
|
|
return omap_dmic_read(dmic, OMAP_DMIC_CTRL_REG) &
|
|
OMAP_DMIC_UP_ENABLE_MASK;
|
|
}
|
|
|
|
static int omap_dmic_dai_startup(struct snd_pcm_substream *substream,
|
|
struct snd_soc_dai *dai)
|
|
{
|
|
struct omap_dmic *dmic = snd_soc_dai_get_drvdata(dai);
|
|
int ret = 0;
|
|
|
|
mutex_lock(&dmic->mutex);
|
|
|
|
if (!snd_soc_dai_active(dai))
|
|
dmic->active = 1;
|
|
else
|
|
ret = -EBUSY;
|
|
|
|
mutex_unlock(&dmic->mutex);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void omap_dmic_dai_shutdown(struct snd_pcm_substream *substream,
|
|
struct snd_soc_dai *dai)
|
|
{
|
|
struct omap_dmic *dmic = snd_soc_dai_get_drvdata(dai);
|
|
|
|
mutex_lock(&dmic->mutex);
|
|
|
|
cpu_latency_qos_remove_request(&dmic->pm_qos_req);
|
|
|
|
if (!snd_soc_dai_active(dai))
|
|
dmic->active = 0;
|
|
|
|
mutex_unlock(&dmic->mutex);
|
|
}
|
|
|
|
static int omap_dmic_select_divider(struct omap_dmic *dmic, int sample_rate)
|
|
{
|
|
int divider = -EINVAL;
|
|
|
|
/*
|
|
* 192KHz rate is only supported with 19.2MHz/3.84MHz clock
|
|
* configuration.
|
|
*/
|
|
if (sample_rate == 192000) {
|
|
if (dmic->fclk_freq == 19200000 && dmic->out_freq == 3840000)
|
|
divider = 0x6; /* Divider: 5 (192KHz sampling rate) */
|
|
else
|
|
dev_err(dmic->dev,
|
|
"invalid clock configuration for 192KHz\n");
|
|
|
|
return divider;
|
|
}
|
|
|
|
switch (dmic->out_freq) {
|
|
case 1536000:
|
|
if (dmic->fclk_freq != 24576000)
|
|
goto div_err;
|
|
divider = 0x4; /* Divider: 16 */
|
|
break;
|
|
case 2400000:
|
|
switch (dmic->fclk_freq) {
|
|
case 12000000:
|
|
divider = 0x5; /* Divider: 5 */
|
|
break;
|
|
case 19200000:
|
|
divider = 0x0; /* Divider: 8 */
|
|
break;
|
|
case 24000000:
|
|
divider = 0x2; /* Divider: 10 */
|
|
break;
|
|
default:
|
|
goto div_err;
|
|
}
|
|
break;
|
|
case 3072000:
|
|
if (dmic->fclk_freq != 24576000)
|
|
goto div_err;
|
|
divider = 0x3; /* Divider: 8 */
|
|
break;
|
|
case 3840000:
|
|
if (dmic->fclk_freq != 19200000)
|
|
goto div_err;
|
|
divider = 0x1; /* Divider: 5 (96KHz sampling rate) */
|
|
break;
|
|
default:
|
|
dev_err(dmic->dev, "invalid out frequency: %dHz\n",
|
|
dmic->out_freq);
|
|
break;
|
|
}
|
|
|
|
return divider;
|
|
|
|
div_err:
|
|
dev_err(dmic->dev, "invalid out frequency %dHz for %dHz input\n",
|
|
dmic->out_freq, dmic->fclk_freq);
|
|
return -EINVAL;
|
|
}
|
|
|
|
static int omap_dmic_dai_hw_params(struct snd_pcm_substream *substream,
|
|
struct snd_pcm_hw_params *params,
|
|
struct snd_soc_dai *dai)
|
|
{
|
|
struct omap_dmic *dmic = snd_soc_dai_get_drvdata(dai);
|
|
struct snd_dmaengine_dai_dma_data *dma_data;
|
|
int channels;
|
|
|
|
dmic->clk_div = omap_dmic_select_divider(dmic, params_rate(params));
|
|
if (dmic->clk_div < 0) {
|
|
dev_err(dmic->dev, "no valid divider for %dHz from %dHz\n",
|
|
dmic->out_freq, dmic->fclk_freq);
|
|
return -EINVAL;
|
|
}
|
|
|
|
dmic->ch_enabled = 0;
|
|
channels = params_channels(params);
|
|
switch (channels) {
|
|
case 6:
|
|
dmic->ch_enabled |= OMAP_DMIC_UP3_ENABLE;
|
|
fallthrough;
|
|
case 4:
|
|
dmic->ch_enabled |= OMAP_DMIC_UP2_ENABLE;
|
|
fallthrough;
|
|
case 2:
|
|
dmic->ch_enabled |= OMAP_DMIC_UP1_ENABLE;
|
|
break;
|
|
default:
|
|
dev_err(dmic->dev, "invalid number of legacy channels\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* packet size is threshold * channels */
|
|
dma_data = snd_soc_dai_get_dma_data(dai, substream);
|
|
dma_data->maxburst = dmic->threshold * channels;
|
|
dmic->latency = (OMAP_DMIC_THRES_MAX - dmic->threshold) * USEC_PER_SEC /
|
|
params_rate(params);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int omap_dmic_dai_prepare(struct snd_pcm_substream *substream,
|
|
struct snd_soc_dai *dai)
|
|
{
|
|
struct omap_dmic *dmic = snd_soc_dai_get_drvdata(dai);
|
|
u32 ctrl;
|
|
|
|
if (cpu_latency_qos_request_active(&dmic->pm_qos_req))
|
|
cpu_latency_qos_update_request(&dmic->pm_qos_req,
|
|
dmic->latency);
|
|
|
|
/* Configure uplink threshold */
|
|
omap_dmic_write(dmic, OMAP_DMIC_FIFO_CTRL_REG, dmic->threshold);
|
|
|
|
ctrl = omap_dmic_read(dmic, OMAP_DMIC_CTRL_REG);
|
|
|
|
/* Set dmic out format */
|
|
ctrl &= ~(OMAP_DMIC_FORMAT | OMAP_DMIC_POLAR_MASK);
|
|
ctrl |= (OMAP_DMICOUTFORMAT_LJUST | OMAP_DMIC_POLAR1 |
|
|
OMAP_DMIC_POLAR2 | OMAP_DMIC_POLAR3);
|
|
|
|
/* Configure dmic clock divider */
|
|
ctrl &= ~OMAP_DMIC_CLK_DIV_MASK;
|
|
ctrl |= OMAP_DMIC_CLK_DIV(dmic->clk_div);
|
|
|
|
omap_dmic_write(dmic, OMAP_DMIC_CTRL_REG, ctrl);
|
|
|
|
omap_dmic_write(dmic, OMAP_DMIC_CTRL_REG,
|
|
ctrl | OMAP_DMICOUTFORMAT_LJUST | OMAP_DMIC_POLAR1 |
|
|
OMAP_DMIC_POLAR2 | OMAP_DMIC_POLAR3);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int omap_dmic_dai_trigger(struct snd_pcm_substream *substream,
|
|
int cmd, struct snd_soc_dai *dai)
|
|
{
|
|
struct omap_dmic *dmic = snd_soc_dai_get_drvdata(dai);
|
|
|
|
switch (cmd) {
|
|
case SNDRV_PCM_TRIGGER_START:
|
|
omap_dmic_start(dmic);
|
|
break;
|
|
case SNDRV_PCM_TRIGGER_STOP:
|
|
omap_dmic_stop(dmic);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int omap_dmic_select_fclk(struct omap_dmic *dmic, int clk_id,
|
|
unsigned int freq)
|
|
{
|
|
struct clk *parent_clk, *mux;
|
|
char *parent_clk_name;
|
|
int ret = 0;
|
|
|
|
switch (freq) {
|
|
case 12000000:
|
|
case 19200000:
|
|
case 24000000:
|
|
case 24576000:
|
|
break;
|
|
default:
|
|
dev_err(dmic->dev, "invalid input frequency: %dHz\n", freq);
|
|
dmic->fclk_freq = 0;
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (dmic->sysclk == clk_id) {
|
|
dmic->fclk_freq = freq;
|
|
return 0;
|
|
}
|
|
|
|
/* re-parent not allowed if a stream is ongoing */
|
|
if (dmic->active && dmic_is_enabled(dmic)) {
|
|
dev_err(dmic->dev, "can't re-parent when DMIC active\n");
|
|
return -EBUSY;
|
|
}
|
|
|
|
switch (clk_id) {
|
|
case OMAP_DMIC_SYSCLK_PAD_CLKS:
|
|
parent_clk_name = "pad_clks_ck";
|
|
break;
|
|
case OMAP_DMIC_SYSCLK_SLIMBLUS_CLKS:
|
|
parent_clk_name = "slimbus_clk";
|
|
break;
|
|
case OMAP_DMIC_SYSCLK_SYNC_MUX_CLKS:
|
|
parent_clk_name = "dmic_sync_mux_ck";
|
|
break;
|
|
default:
|
|
dev_err(dmic->dev, "fclk clk_id (%d) not supported\n", clk_id);
|
|
return -EINVAL;
|
|
}
|
|
|
|
parent_clk = clk_get(dmic->dev, parent_clk_name);
|
|
if (IS_ERR(parent_clk)) {
|
|
dev_err(dmic->dev, "can't get %s\n", parent_clk_name);
|
|
return -ENODEV;
|
|
}
|
|
|
|
mux = clk_get_parent(dmic->fclk);
|
|
if (IS_ERR(mux)) {
|
|
dev_err(dmic->dev, "can't get fck mux parent\n");
|
|
clk_put(parent_clk);
|
|
return -ENODEV;
|
|
}
|
|
|
|
mutex_lock(&dmic->mutex);
|
|
if (dmic->active) {
|
|
/* disable clock while reparenting */
|
|
pm_runtime_put_sync(dmic->dev);
|
|
ret = clk_set_parent(mux, parent_clk);
|
|
pm_runtime_get_sync(dmic->dev);
|
|
} else {
|
|
ret = clk_set_parent(mux, parent_clk);
|
|
}
|
|
mutex_unlock(&dmic->mutex);
|
|
|
|
if (ret < 0) {
|
|
dev_err(dmic->dev, "re-parent failed\n");
|
|
goto err_busy;
|
|
}
|
|
|
|
dmic->sysclk = clk_id;
|
|
dmic->fclk_freq = freq;
|
|
|
|
err_busy:
|
|
clk_put(mux);
|
|
clk_put(parent_clk);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int omap_dmic_select_outclk(struct omap_dmic *dmic, int clk_id,
|
|
unsigned int freq)
|
|
{
|
|
int ret = 0;
|
|
|
|
if (clk_id != OMAP_DMIC_ABE_DMIC_CLK) {
|
|
dev_err(dmic->dev, "output clk_id (%d) not supported\n",
|
|
clk_id);
|
|
return -EINVAL;
|
|
}
|
|
|
|
switch (freq) {
|
|
case 1536000:
|
|
case 2400000:
|
|
case 3072000:
|
|
case 3840000:
|
|
dmic->out_freq = freq;
|
|
break;
|
|
default:
|
|
dev_err(dmic->dev, "invalid out frequency: %dHz\n", freq);
|
|
dmic->out_freq = 0;
|
|
ret = -EINVAL;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int omap_dmic_set_dai_sysclk(struct snd_soc_dai *dai, int clk_id,
|
|
unsigned int freq, int dir)
|
|
{
|
|
struct omap_dmic *dmic = snd_soc_dai_get_drvdata(dai);
|
|
|
|
if (dir == SND_SOC_CLOCK_IN)
|
|
return omap_dmic_select_fclk(dmic, clk_id, freq);
|
|
else if (dir == SND_SOC_CLOCK_OUT)
|
|
return omap_dmic_select_outclk(dmic, clk_id, freq);
|
|
|
|
dev_err(dmic->dev, "invalid clock direction (%d)\n", dir);
|
|
return -EINVAL;
|
|
}
|
|
|
|
static int omap_dmic_probe(struct snd_soc_dai *dai)
|
|
{
|
|
struct omap_dmic *dmic = snd_soc_dai_get_drvdata(dai);
|
|
|
|
pm_runtime_enable(dmic->dev);
|
|
|
|
/* Disable lines while request is ongoing */
|
|
pm_runtime_get_sync(dmic->dev);
|
|
omap_dmic_write(dmic, OMAP_DMIC_CTRL_REG, 0x00);
|
|
pm_runtime_put_sync(dmic->dev);
|
|
|
|
/* Configure DMIC threshold value */
|
|
dmic->threshold = OMAP_DMIC_THRES_MAX - 3;
|
|
|
|
snd_soc_dai_init_dma_data(dai, NULL, &dmic->dma_data);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int omap_dmic_remove(struct snd_soc_dai *dai)
|
|
{
|
|
struct omap_dmic *dmic = snd_soc_dai_get_drvdata(dai);
|
|
|
|
pm_runtime_disable(dmic->dev);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct snd_soc_dai_ops omap_dmic_dai_ops = {
|
|
.probe = omap_dmic_probe,
|
|
.remove = omap_dmic_remove,
|
|
.startup = omap_dmic_dai_startup,
|
|
.shutdown = omap_dmic_dai_shutdown,
|
|
.hw_params = omap_dmic_dai_hw_params,
|
|
.prepare = omap_dmic_dai_prepare,
|
|
.trigger = omap_dmic_dai_trigger,
|
|
.set_sysclk = omap_dmic_set_dai_sysclk,
|
|
};
|
|
|
|
static struct snd_soc_dai_driver omap_dmic_dai = {
|
|
.name = "omap-dmic",
|
|
.capture = {
|
|
.channels_min = 2,
|
|
.channels_max = 6,
|
|
.rates = SNDRV_PCM_RATE_96000 | SNDRV_PCM_RATE_192000,
|
|
.formats = SNDRV_PCM_FMTBIT_S32_LE,
|
|
.sig_bits = 24,
|
|
},
|
|
.ops = &omap_dmic_dai_ops,
|
|
};
|
|
|
|
static const struct snd_soc_component_driver omap_dmic_component = {
|
|
.name = "omap-dmic",
|
|
.legacy_dai_naming = 1,
|
|
};
|
|
|
|
static int asoc_dmic_probe(struct platform_device *pdev)
|
|
{
|
|
struct omap_dmic *dmic;
|
|
struct resource *res;
|
|
int ret;
|
|
|
|
dmic = devm_kzalloc(&pdev->dev, sizeof(struct omap_dmic), GFP_KERNEL);
|
|
if (!dmic)
|
|
return -ENOMEM;
|
|
|
|
platform_set_drvdata(pdev, dmic);
|
|
dmic->dev = &pdev->dev;
|
|
dmic->sysclk = OMAP_DMIC_SYSCLK_SYNC_MUX_CLKS;
|
|
|
|
mutex_init(&dmic->mutex);
|
|
|
|
dmic->fclk = devm_clk_get(dmic->dev, "fck");
|
|
if (IS_ERR(dmic->fclk)) {
|
|
dev_err(dmic->dev, "can't get fck\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "dma");
|
|
if (!res) {
|
|
dev_err(dmic->dev, "invalid dma memory resource\n");
|
|
return -ENODEV;
|
|
}
|
|
dmic->dma_data.addr = res->start + OMAP_DMIC_DATA_REG;
|
|
|
|
dmic->dma_data.filter_data = "up_link";
|
|
|
|
dmic->io_base = devm_platform_ioremap_resource_byname(pdev, "mpu");
|
|
if (IS_ERR(dmic->io_base))
|
|
return PTR_ERR(dmic->io_base);
|
|
|
|
ret = devm_snd_soc_register_component(&pdev->dev,
|
|
&omap_dmic_component,
|
|
&omap_dmic_dai, 1);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = sdma_pcm_platform_register(&pdev->dev, NULL, "up_link");
|
|
if (ret)
|
|
return ret;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct of_device_id omap_dmic_of_match[] = {
|
|
{ .compatible = "ti,omap4-dmic", },
|
|
{ }
|
|
};
|
|
MODULE_DEVICE_TABLE(of, omap_dmic_of_match);
|
|
|
|
static struct platform_driver asoc_dmic_driver = {
|
|
.driver = {
|
|
.name = "omap-dmic",
|
|
.of_match_table = omap_dmic_of_match,
|
|
},
|
|
.probe = asoc_dmic_probe,
|
|
};
|
|
|
|
module_platform_driver(asoc_dmic_driver);
|
|
|
|
MODULE_ALIAS("platform:omap-dmic");
|
|
MODULE_AUTHOR("Peter Ujfalusi <peter.ujfalusi@ti.com>");
|
|
MODULE_DESCRIPTION("OMAP DMIC ASoC Interface");
|
|
MODULE_LICENSE("GPL");
|