mirror of
https://github.com/torvalds/linux.git
synced 2024-12-15 15:41:58 +00:00
2b27bdcc20
Based on 1 normalized pattern(s): this program is free software you can redistribute it and or modify it under the terms of the gnu general public license version 2 as published by the free software foundation 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 you should have received a copy of the gnu general public license along with this program if not write to the free software foundation inc 51 franklin st fifth floor boston ma 02110 1301 usa extracted by the scancode license scanner the SPDX license identifier GPL-2.0-only has been chosen to replace the boilerplate/reference in 246 file(s). Signed-off-by: Thomas Gleixner <tglx@linutronix.de> Reviewed-by: Alexios Zavras <alexios.zavras@intel.com> Reviewed-by: Allison Randal <allison@lohutok.net> Cc: linux-spdx@vger.kernel.org Link: https://lkml.kernel.org/r/20190530000436.674189849@linutronix.de Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.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/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 <linux/of_device.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 (!dai->active)
|
|
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);
|
|
|
|
pm_qos_remove_request(&dmic->pm_qos_req);
|
|
|
|
if (!dai->active)
|
|
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;
|
|
/* fall through */
|
|
case 4:
|
|
dmic->ch_enabled |= OMAP_DMIC_UP2_ENABLE;
|
|
/* fall through */
|
|
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 (pm_qos_request_active(&dmic->pm_qos_req))
|
|
pm_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 const struct snd_soc_dai_ops omap_dmic_dai_ops = {
|
|
.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 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 struct snd_soc_dai_driver omap_dmic_dai = {
|
|
.name = "omap-dmic",
|
|
.probe = omap_dmic_probe,
|
|
.remove = omap_dmic_remove,
|
|
.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",
|
|
};
|
|
|
|
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, "cant 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";
|
|
|
|
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "mpu");
|
|
dmic->io_base = devm_ioremap_resource(&pdev->dev, res);
|
|
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");
|