linux/sound/soc/qcom/lpass-cpu.c
Uwe Kleine-König d0cc676c42
ASoC: qcom: lpass: Make asoc_qcom_lpass_cpu_platform_remove() return void
The .remove() callback for a platform driver returns an int which makes
many driver authors wrongly assume it's possible to do error handling by
returning an error code.  However the value returned is (mostly) ignored
and this typically results in resource leaks. To improve here there is a
quest to make the remove callback return void. In the first step of this
quest all drivers are converted to .remove_new() which already returns
void.

asoc_qcom_lpass_cpu_platform_remove() returned zero unconditionally.
Make it return void instead and convert all users to struct
platform_device::remove_new().

Signed-off-by: Uwe Kleine-König <u.kleine-koenig@pengutronix.de>
Link: https://lore.kernel.org/r/20231013221945.1489203-15-u.kleine-koenig@pengutronix.de
Signed-off-by: Mark Brown <broonie@kernel.org>
2023-10-23 13:29:47 +01:00

1298 lines
36 KiB
C

// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2010-2011,2013-2015 The Linux Foundation. All rights reserved.
*
* lpass-cpu.c -- ALSA SoC CPU DAI driver for QTi LPASS
*/
#include <dt-bindings/sound/qcom,lpass.h>
#include <linux/clk.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <linux/regmap.h>
#include <sound/soc.h>
#include <sound/soc-dai.h>
#include "lpass-lpaif-reg.h"
#include "lpass.h"
#define LPASS_CPU_MAX_MI2S_LINES 4
#define LPASS_CPU_I2S_SD0_MASK BIT(0)
#define LPASS_CPU_I2S_SD1_MASK BIT(1)
#define LPASS_CPU_I2S_SD2_MASK BIT(2)
#define LPASS_CPU_I2S_SD3_MASK BIT(3)
#define LPASS_CPU_I2S_SD0_1_MASK GENMASK(1, 0)
#define LPASS_CPU_I2S_SD2_3_MASK GENMASK(3, 2)
#define LPASS_CPU_I2S_SD0_1_2_MASK GENMASK(2, 0)
#define LPASS_CPU_I2S_SD0_1_2_3_MASK GENMASK(3, 0)
#define LPASS_REG_READ 1
#define LPASS_REG_WRITE 0
/*
* Channel maps for Quad channel playbacks on MI2S Secondary
*/
static struct snd_pcm_chmap_elem lpass_quad_chmaps[] = {
{ .channels = 4,
.map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_RL,
SNDRV_CHMAP_FR, SNDRV_CHMAP_RR } },
{ }
};
static int lpass_cpu_init_i2sctl_bitfields(struct device *dev,
struct lpaif_i2sctl *i2sctl, struct regmap *map)
{
struct lpass_data *drvdata = dev_get_drvdata(dev);
const struct lpass_variant *v = drvdata->variant;
i2sctl->loopback = devm_regmap_field_alloc(dev, map, v->loopback);
i2sctl->spken = devm_regmap_field_alloc(dev, map, v->spken);
i2sctl->spkmode = devm_regmap_field_alloc(dev, map, v->spkmode);
i2sctl->spkmono = devm_regmap_field_alloc(dev, map, v->spkmono);
i2sctl->micen = devm_regmap_field_alloc(dev, map, v->micen);
i2sctl->micmode = devm_regmap_field_alloc(dev, map, v->micmode);
i2sctl->micmono = devm_regmap_field_alloc(dev, map, v->micmono);
i2sctl->wssrc = devm_regmap_field_alloc(dev, map, v->wssrc);
i2sctl->bitwidth = devm_regmap_field_alloc(dev, map, v->bitwidth);
if (IS_ERR(i2sctl->loopback) || IS_ERR(i2sctl->spken) ||
IS_ERR(i2sctl->spkmode) || IS_ERR(i2sctl->spkmono) ||
IS_ERR(i2sctl->micen) || IS_ERR(i2sctl->micmode) ||
IS_ERR(i2sctl->micmono) || IS_ERR(i2sctl->wssrc) ||
IS_ERR(i2sctl->bitwidth))
return -EINVAL;
return 0;
}
static int lpass_cpu_daiops_set_sysclk(struct snd_soc_dai *dai, int clk_id,
unsigned int freq, int dir)
{
struct lpass_data *drvdata = snd_soc_dai_get_drvdata(dai);
int ret;
ret = clk_set_rate(drvdata->mi2s_osr_clk[dai->driver->id], freq);
if (ret)
dev_err(dai->dev, "error setting mi2s osrclk to %u: %d\n",
freq, ret);
return ret;
}
static int lpass_cpu_daiops_startup(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
struct lpass_data *drvdata = snd_soc_dai_get_drvdata(dai);
int ret;
ret = clk_prepare_enable(drvdata->mi2s_osr_clk[dai->driver->id]);
if (ret) {
dev_err(dai->dev, "error in enabling mi2s osr clk: %d\n", ret);
return ret;
}
ret = clk_prepare(drvdata->mi2s_bit_clk[dai->driver->id]);
if (ret) {
dev_err(dai->dev, "error in enabling mi2s bit clk: %d\n", ret);
clk_disable_unprepare(drvdata->mi2s_osr_clk[dai->driver->id]);
return ret;
}
return 0;
}
static void lpass_cpu_daiops_shutdown(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
struct lpass_data *drvdata = snd_soc_dai_get_drvdata(dai);
struct lpaif_i2sctl *i2sctl = drvdata->i2sctl;
unsigned int id = dai->driver->id;
clk_disable_unprepare(drvdata->mi2s_osr_clk[dai->driver->id]);
/*
* Ensure LRCLK is disabled even in device node validation.
* Will not impact if disabled in lpass_cpu_daiops_trigger()
* suspend.
*/
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
regmap_fields_write(i2sctl->spken, id, LPAIF_I2SCTL_SPKEN_DISABLE);
else
regmap_fields_write(i2sctl->micen, id, LPAIF_I2SCTL_MICEN_DISABLE);
/*
* BCLK may not be enabled if lpass_cpu_daiops_prepare is called before
* lpass_cpu_daiops_shutdown. It's paired with the clk_enable in
* lpass_cpu_daiops_prepare.
*/
if (drvdata->mi2s_was_prepared[dai->driver->id]) {
drvdata->mi2s_was_prepared[dai->driver->id] = false;
clk_disable(drvdata->mi2s_bit_clk[dai->driver->id]);
}
clk_unprepare(drvdata->mi2s_bit_clk[dai->driver->id]);
}
static int lpass_cpu_daiops_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params, struct snd_soc_dai *dai)
{
struct lpass_data *drvdata = snd_soc_dai_get_drvdata(dai);
struct lpaif_i2sctl *i2sctl = drvdata->i2sctl;
unsigned int id = dai->driver->id;
snd_pcm_format_t format = params_format(params);
unsigned int channels = params_channels(params);
unsigned int rate = params_rate(params);
unsigned int mode;
unsigned int regval;
int bitwidth, ret;
bitwidth = snd_pcm_format_width(format);
if (bitwidth < 0) {
dev_err(dai->dev, "invalid bit width given: %d\n", bitwidth);
return bitwidth;
}
ret = regmap_fields_write(i2sctl->loopback, id,
LPAIF_I2SCTL_LOOPBACK_DISABLE);
if (ret) {
dev_err(dai->dev, "error updating loopback field: %d\n", ret);
return ret;
}
ret = regmap_fields_write(i2sctl->wssrc, id,
LPAIF_I2SCTL_WSSRC_INTERNAL);
if (ret) {
dev_err(dai->dev, "error updating wssrc field: %d\n", ret);
return ret;
}
switch (bitwidth) {
case 16:
regval = LPAIF_I2SCTL_BITWIDTH_16;
break;
case 24:
regval = LPAIF_I2SCTL_BITWIDTH_24;
break;
case 32:
regval = LPAIF_I2SCTL_BITWIDTH_32;
break;
default:
dev_err(dai->dev, "invalid bitwidth given: %d\n", bitwidth);
return -EINVAL;
}
ret = regmap_fields_write(i2sctl->bitwidth, id, regval);
if (ret) {
dev_err(dai->dev, "error updating bitwidth field: %d\n", ret);
return ret;
}
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
mode = drvdata->mi2s_playback_sd_mode[id];
else
mode = drvdata->mi2s_capture_sd_mode[id];
if (!mode) {
dev_err(dai->dev, "no line is assigned\n");
return -EINVAL;
}
switch (channels) {
case 1:
case 2:
switch (mode) {
case LPAIF_I2SCTL_MODE_QUAD01:
case LPAIF_I2SCTL_MODE_6CH:
case LPAIF_I2SCTL_MODE_8CH:
mode = LPAIF_I2SCTL_MODE_SD0;
break;
case LPAIF_I2SCTL_MODE_QUAD23:
mode = LPAIF_I2SCTL_MODE_SD2;
break;
}
break;
case 4:
if (mode < LPAIF_I2SCTL_MODE_QUAD01) {
dev_err(dai->dev, "cannot configure 4 channels with mode %d\n",
mode);
return -EINVAL;
}
switch (mode) {
case LPAIF_I2SCTL_MODE_6CH:
case LPAIF_I2SCTL_MODE_8CH:
mode = LPAIF_I2SCTL_MODE_QUAD01;
break;
}
break;
case 6:
if (mode < LPAIF_I2SCTL_MODE_6CH) {
dev_err(dai->dev, "cannot configure 6 channels with mode %d\n",
mode);
return -EINVAL;
}
switch (mode) {
case LPAIF_I2SCTL_MODE_8CH:
mode = LPAIF_I2SCTL_MODE_6CH;
break;
}
break;
case 8:
if (mode < LPAIF_I2SCTL_MODE_8CH) {
dev_err(dai->dev, "cannot configure 8 channels with mode %d\n",
mode);
return -EINVAL;
}
break;
default:
dev_err(dai->dev, "invalid channels given: %u\n", channels);
return -EINVAL;
}
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
ret = regmap_fields_write(i2sctl->spkmode, id,
LPAIF_I2SCTL_SPKMODE(mode));
if (ret) {
dev_err(dai->dev, "error writing to i2sctl spkr mode: %d\n",
ret);
return ret;
}
if (channels >= 2)
ret = regmap_fields_write(i2sctl->spkmono, id,
LPAIF_I2SCTL_SPKMONO_STEREO);
else
ret = regmap_fields_write(i2sctl->spkmono, id,
LPAIF_I2SCTL_SPKMONO_MONO);
} else {
ret = regmap_fields_write(i2sctl->micmode, id,
LPAIF_I2SCTL_MICMODE(mode));
if (ret) {
dev_err(dai->dev, "error writing to i2sctl mic mode: %d\n",
ret);
return ret;
}
if (channels >= 2)
ret = regmap_fields_write(i2sctl->micmono, id,
LPAIF_I2SCTL_MICMONO_STEREO);
else
ret = regmap_fields_write(i2sctl->micmono, id,
LPAIF_I2SCTL_MICMONO_MONO);
}
if (ret) {
dev_err(dai->dev, "error writing to i2sctl channels mode: %d\n",
ret);
return ret;
}
ret = clk_set_rate(drvdata->mi2s_bit_clk[id],
rate * bitwidth * 2);
if (ret) {
dev_err(dai->dev, "error setting mi2s bitclk to %u: %d\n",
rate * bitwidth * 2, ret);
return ret;
}
return 0;
}
static int lpass_cpu_daiops_trigger(struct snd_pcm_substream *substream,
int cmd, struct snd_soc_dai *dai)
{
struct lpass_data *drvdata = snd_soc_dai_get_drvdata(dai);
struct lpaif_i2sctl *i2sctl = drvdata->i2sctl;
unsigned int id = dai->driver->id;
int ret = -EINVAL;
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
case SNDRV_PCM_TRIGGER_RESUME:
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
/*
* Ensure lpass BCLK/LRCLK is enabled during
* device resume as lpass_cpu_daiops_prepare() is not called
* after the device resumes. We don't check mi2s_was_prepared before
* enable/disable BCLK in trigger events because:
* 1. These trigger events are paired, so the BCLK
* enable_count is balanced.
* 2. the BCLK can be shared (ex: headset and headset mic),
* we need to increase the enable_count so that we don't
* turn off the shared BCLK while other devices are using
* it.
*/
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
ret = regmap_fields_write(i2sctl->spken, id,
LPAIF_I2SCTL_SPKEN_ENABLE);
} else {
ret = regmap_fields_write(i2sctl->micen, id,
LPAIF_I2SCTL_MICEN_ENABLE);
}
if (ret)
dev_err(dai->dev, "error writing to i2sctl reg: %d\n",
ret);
ret = clk_enable(drvdata->mi2s_bit_clk[id]);
if (ret) {
dev_err(dai->dev, "error in enabling mi2s bit clk: %d\n", ret);
clk_disable(drvdata->mi2s_osr_clk[id]);
return ret;
}
break;
case SNDRV_PCM_TRIGGER_STOP:
case SNDRV_PCM_TRIGGER_SUSPEND:
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
/*
* To ensure lpass BCLK/LRCLK is disabled during
* device suspend.
*/
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
ret = regmap_fields_write(i2sctl->spken, id,
LPAIF_I2SCTL_SPKEN_DISABLE);
} else {
ret = regmap_fields_write(i2sctl->micen, id,
LPAIF_I2SCTL_MICEN_DISABLE);
}
if (ret)
dev_err(dai->dev, "error writing to i2sctl reg: %d\n",
ret);
clk_disable(drvdata->mi2s_bit_clk[dai->driver->id]);
break;
}
return ret;
}
static int lpass_cpu_daiops_prepare(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
struct lpass_data *drvdata = snd_soc_dai_get_drvdata(dai);
struct lpaif_i2sctl *i2sctl = drvdata->i2sctl;
unsigned int id = dai->driver->id;
int ret;
/*
* Ensure lpass BCLK/LRCLK is enabled bit before playback/capture
* data flow starts. This allows other codec to have some delay before
* the data flow.
* (ex: to drop start up pop noise before capture starts).
*/
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
ret = regmap_fields_write(i2sctl->spken, id, LPAIF_I2SCTL_SPKEN_ENABLE);
else
ret = regmap_fields_write(i2sctl->micen, id, LPAIF_I2SCTL_MICEN_ENABLE);
if (ret) {
dev_err(dai->dev, "error writing to i2sctl reg: %d\n", ret);
return ret;
}
/*
* Check mi2s_was_prepared before enabling BCLK as lpass_cpu_daiops_prepare can
* be called multiple times. It's paired with the clk_disable in
* lpass_cpu_daiops_shutdown.
*/
if (!drvdata->mi2s_was_prepared[dai->driver->id]) {
ret = clk_enable(drvdata->mi2s_bit_clk[id]);
if (ret) {
dev_err(dai->dev, "error in enabling mi2s bit clk: %d\n", ret);
return ret;
}
drvdata->mi2s_was_prepared[dai->driver->id] = true;
}
return 0;
}
static int lpass_cpu_daiops_pcm_new(struct snd_soc_pcm_runtime *rtd, struct snd_soc_dai *dai)
{
int ret;
struct snd_soc_dai_driver *drv = dai->driver;
struct lpass_data *drvdata = snd_soc_dai_get_drvdata(dai);
if (drvdata->mi2s_playback_sd_mode[dai->id] == LPAIF_I2SCTL_MODE_QUAD01) {
ret = snd_pcm_add_chmap_ctls(rtd->pcm, SNDRV_PCM_STREAM_PLAYBACK,
lpass_quad_chmaps, drv->playback.channels_max, 0,
NULL);
if (ret < 0)
return ret;
}
return 0;
}
static int lpass_cpu_daiops_probe(struct snd_soc_dai *dai)
{
struct lpass_data *drvdata = snd_soc_dai_get_drvdata(dai);
int ret;
/* ensure audio hardware is disabled */
ret = regmap_write(drvdata->lpaif_map,
LPAIF_I2SCTL_REG(drvdata->variant, dai->driver->id), 0);
if (ret)
dev_err(dai->dev, "error writing to i2sctl reg: %d\n", ret);
return ret;
}
const struct snd_soc_dai_ops asoc_qcom_lpass_cpu_dai_ops = {
.probe = lpass_cpu_daiops_probe,
.set_sysclk = lpass_cpu_daiops_set_sysclk,
.startup = lpass_cpu_daiops_startup,
.shutdown = lpass_cpu_daiops_shutdown,
.hw_params = lpass_cpu_daiops_hw_params,
.trigger = lpass_cpu_daiops_trigger,
.prepare = lpass_cpu_daiops_prepare,
};
EXPORT_SYMBOL_GPL(asoc_qcom_lpass_cpu_dai_ops);
const struct snd_soc_dai_ops asoc_qcom_lpass_cpu_dai_ops2 = {
.pcm_new = lpass_cpu_daiops_pcm_new,
.probe = lpass_cpu_daiops_probe,
.set_sysclk = lpass_cpu_daiops_set_sysclk,
.startup = lpass_cpu_daiops_startup,
.shutdown = lpass_cpu_daiops_shutdown,
.hw_params = lpass_cpu_daiops_hw_params,
.trigger = lpass_cpu_daiops_trigger,
.prepare = lpass_cpu_daiops_prepare,
};
EXPORT_SYMBOL_GPL(asoc_qcom_lpass_cpu_dai_ops2);
static int asoc_qcom_of_xlate_dai_name(struct snd_soc_component *component,
const struct of_phandle_args *args,
const char **dai_name)
{
struct lpass_data *drvdata = snd_soc_component_get_drvdata(component);
const struct lpass_variant *variant = drvdata->variant;
int id = args->args[0];
int ret = -EINVAL;
int i;
for (i = 0; i < variant->num_dai; i++) {
if (variant->dai_driver[i].id == id) {
*dai_name = variant->dai_driver[i].name;
ret = 0;
break;
}
}
return ret;
}
static const struct snd_soc_component_driver lpass_cpu_comp_driver = {
.name = "lpass-cpu",
.of_xlate_dai_name = asoc_qcom_of_xlate_dai_name,
.legacy_dai_naming = 1,
};
static bool lpass_cpu_regmap_writeable(struct device *dev, unsigned int reg)
{
struct lpass_data *drvdata = dev_get_drvdata(dev);
const struct lpass_variant *v = drvdata->variant;
int i;
for (i = 0; i < v->i2s_ports; ++i)
if (reg == LPAIF_I2SCTL_REG(v, i))
return true;
for (i = 0; i < v->irq_ports; ++i) {
if (reg == LPAIF_IRQEN_REG(v, i))
return true;
if (reg == LPAIF_IRQCLEAR_REG(v, i))
return true;
}
for (i = 0; i < v->rdma_channels; ++i) {
if (reg == LPAIF_RDMACTL_REG(v, i))
return true;
if (reg == LPAIF_RDMABASE_REG(v, i))
return true;
if (reg == LPAIF_RDMABUFF_REG(v, i))
return true;
if (reg == LPAIF_RDMAPER_REG(v, i))
return true;
}
for (i = 0; i < v->wrdma_channels; ++i) {
if (reg == LPAIF_WRDMACTL_REG(v, i + v->wrdma_channel_start))
return true;
if (reg == LPAIF_WRDMABASE_REG(v, i + v->wrdma_channel_start))
return true;
if (reg == LPAIF_WRDMABUFF_REG(v, i + v->wrdma_channel_start))
return true;
if (reg == LPAIF_WRDMAPER_REG(v, i + v->wrdma_channel_start))
return true;
}
return false;
}
static bool lpass_cpu_regmap_readable(struct device *dev, unsigned int reg)
{
struct lpass_data *drvdata = dev_get_drvdata(dev);
const struct lpass_variant *v = drvdata->variant;
int i;
for (i = 0; i < v->i2s_ports; ++i)
if (reg == LPAIF_I2SCTL_REG(v, i))
return true;
for (i = 0; i < v->irq_ports; ++i) {
if (reg == LPAIF_IRQCLEAR_REG(v, i))
return true;
if (reg == LPAIF_IRQEN_REG(v, i))
return true;
if (reg == LPAIF_IRQSTAT_REG(v, i))
return true;
}
for (i = 0; i < v->rdma_channels; ++i) {
if (reg == LPAIF_RDMACTL_REG(v, i))
return true;
if (reg == LPAIF_RDMABASE_REG(v, i))
return true;
if (reg == LPAIF_RDMABUFF_REG(v, i))
return true;
if (reg == LPAIF_RDMACURR_REG(v, i))
return true;
if (reg == LPAIF_RDMAPER_REG(v, i))
return true;
}
for (i = 0; i < v->wrdma_channels; ++i) {
if (reg == LPAIF_WRDMACTL_REG(v, i + v->wrdma_channel_start))
return true;
if (reg == LPAIF_WRDMABASE_REG(v, i + v->wrdma_channel_start))
return true;
if (reg == LPAIF_WRDMABUFF_REG(v, i + v->wrdma_channel_start))
return true;
if (reg == LPAIF_WRDMACURR_REG(v, i + v->wrdma_channel_start))
return true;
if (reg == LPAIF_WRDMAPER_REG(v, i + v->wrdma_channel_start))
return true;
}
return false;
}
static bool lpass_cpu_regmap_volatile(struct device *dev, unsigned int reg)
{
struct lpass_data *drvdata = dev_get_drvdata(dev);
const struct lpass_variant *v = drvdata->variant;
int i;
for (i = 0; i < v->irq_ports; ++i) {
if (reg == LPAIF_IRQCLEAR_REG(v, i))
return true;
if (reg == LPAIF_IRQSTAT_REG(v, i))
return true;
}
for (i = 0; i < v->rdma_channels; ++i)
if (reg == LPAIF_RDMACURR_REG(v, i))
return true;
for (i = 0; i < v->wrdma_channels; ++i)
if (reg == LPAIF_WRDMACURR_REG(v, i + v->wrdma_channel_start))
return true;
return false;
}
static struct regmap_config lpass_cpu_regmap_config = {
.name = "lpass_cpu",
.reg_bits = 32,
.reg_stride = 4,
.val_bits = 32,
.writeable_reg = lpass_cpu_regmap_writeable,
.readable_reg = lpass_cpu_regmap_readable,
.volatile_reg = lpass_cpu_regmap_volatile,
.cache_type = REGCACHE_FLAT,
};
static int lpass_hdmi_init_bitfields(struct device *dev, struct regmap *map)
{
struct lpass_data *drvdata = dev_get_drvdata(dev);
const struct lpass_variant *v = drvdata->variant;
unsigned int i;
struct lpass_hdmi_tx_ctl *tx_ctl;
struct regmap_field *legacy_en;
struct lpass_vbit_ctrl *vbit_ctl;
struct regmap_field *tx_parity;
struct lpass_dp_metadata_ctl *meta_ctl;
struct lpass_sstream_ctl *sstream_ctl;
struct regmap_field *ch_msb;
struct regmap_field *ch_lsb;
struct lpass_hdmitx_dmactl *tx_dmactl;
int rval;
tx_ctl = devm_kzalloc(dev, sizeof(*tx_ctl), GFP_KERNEL);
if (!tx_ctl)
return -ENOMEM;
QCOM_REGMAP_FIELD_ALLOC(dev, map, v->soft_reset, tx_ctl->soft_reset);
QCOM_REGMAP_FIELD_ALLOC(dev, map, v->force_reset, tx_ctl->force_reset);
drvdata->tx_ctl = tx_ctl;
QCOM_REGMAP_FIELD_ALLOC(dev, map, v->legacy_en, legacy_en);
drvdata->hdmitx_legacy_en = legacy_en;
vbit_ctl = devm_kzalloc(dev, sizeof(*vbit_ctl), GFP_KERNEL);
if (!vbit_ctl)
return -ENOMEM;
QCOM_REGMAP_FIELD_ALLOC(dev, map, v->replace_vbit, vbit_ctl->replace_vbit);
QCOM_REGMAP_FIELD_ALLOC(dev, map, v->vbit_stream, vbit_ctl->vbit_stream);
drvdata->vbit_ctl = vbit_ctl;
QCOM_REGMAP_FIELD_ALLOC(dev, map, v->calc_en, tx_parity);
drvdata->hdmitx_parity_calc_en = tx_parity;
meta_ctl = devm_kzalloc(dev, sizeof(*meta_ctl), GFP_KERNEL);
if (!meta_ctl)
return -ENOMEM;
rval = devm_regmap_field_bulk_alloc(dev, map, &meta_ctl->mute, &v->mute, 7);
if (rval)
return rval;
drvdata->meta_ctl = meta_ctl;
sstream_ctl = devm_kzalloc(dev, sizeof(*sstream_ctl), GFP_KERNEL);
if (!sstream_ctl)
return -ENOMEM;
rval = devm_regmap_field_bulk_alloc(dev, map, &sstream_ctl->sstream_en, &v->sstream_en, 9);
if (rval)
return rval;
drvdata->sstream_ctl = sstream_ctl;
for (i = 0; i < LPASS_MAX_HDMI_DMA_CHANNELS; i++) {
QCOM_REGMAP_FIELD_ALLOC(dev, map, v->msb_bits, ch_msb);
drvdata->hdmitx_ch_msb[i] = ch_msb;
QCOM_REGMAP_FIELD_ALLOC(dev, map, v->lsb_bits, ch_lsb);
drvdata->hdmitx_ch_lsb[i] = ch_lsb;
tx_dmactl = devm_kzalloc(dev, sizeof(*tx_dmactl), GFP_KERNEL);
if (!tx_dmactl)
return -ENOMEM;
QCOM_REGMAP_FIELD_ALLOC(dev, map, v->use_hw_chs, tx_dmactl->use_hw_chs);
QCOM_REGMAP_FIELD_ALLOC(dev, map, v->use_hw_usr, tx_dmactl->use_hw_usr);
QCOM_REGMAP_FIELD_ALLOC(dev, map, v->hw_chs_sel, tx_dmactl->hw_chs_sel);
QCOM_REGMAP_FIELD_ALLOC(dev, map, v->hw_usr_sel, tx_dmactl->hw_usr_sel);
drvdata->hdmi_tx_dmactl[i] = tx_dmactl;
}
return 0;
}
static bool lpass_hdmi_regmap_writeable(struct device *dev, unsigned int reg)
{
struct lpass_data *drvdata = dev_get_drvdata(dev);
const struct lpass_variant *v = drvdata->variant;
int i;
if (reg == LPASS_HDMI_TX_CTL_ADDR(v))
return true;
if (reg == LPASS_HDMI_TX_LEGACY_ADDR(v))
return true;
if (reg == LPASS_HDMI_TX_VBIT_CTL_ADDR(v))
return true;
if (reg == LPASS_HDMI_TX_PARITY_ADDR(v))
return true;
if (reg == LPASS_HDMI_TX_DP_ADDR(v))
return true;
if (reg == LPASS_HDMI_TX_SSTREAM_ADDR(v))
return true;
if (reg == LPASS_HDMITX_APP_IRQEN_REG(v))
return true;
if (reg == LPASS_HDMITX_APP_IRQCLEAR_REG(v))
return true;
for (i = 0; i < v->hdmi_rdma_channels; i++) {
if (reg == LPASS_HDMI_TX_CH_LSB_ADDR(v, i))
return true;
if (reg == LPASS_HDMI_TX_CH_MSB_ADDR(v, i))
return true;
if (reg == LPASS_HDMI_TX_DMA_ADDR(v, i))
return true;
}
for (i = 0; i < v->hdmi_rdma_channels; ++i) {
if (reg == LPAIF_HDMI_RDMACTL_REG(v, i))
return true;
if (reg == LPAIF_HDMI_RDMABASE_REG(v, i))
return true;
if (reg == LPAIF_HDMI_RDMABUFF_REG(v, i))
return true;
if (reg == LPAIF_HDMI_RDMAPER_REG(v, i))
return true;
}
return false;
}
static bool lpass_hdmi_regmap_readable(struct device *dev, unsigned int reg)
{
struct lpass_data *drvdata = dev_get_drvdata(dev);
const struct lpass_variant *v = drvdata->variant;
int i;
if (reg == LPASS_HDMI_TX_CTL_ADDR(v))
return true;
if (reg == LPASS_HDMI_TX_LEGACY_ADDR(v))
return true;
if (reg == LPASS_HDMI_TX_VBIT_CTL_ADDR(v))
return true;
for (i = 0; i < v->hdmi_rdma_channels; i++) {
if (reg == LPASS_HDMI_TX_CH_LSB_ADDR(v, i))
return true;
if (reg == LPASS_HDMI_TX_CH_MSB_ADDR(v, i))
return true;
if (reg == LPASS_HDMI_TX_DMA_ADDR(v, i))
return true;
}
if (reg == LPASS_HDMI_TX_PARITY_ADDR(v))
return true;
if (reg == LPASS_HDMI_TX_DP_ADDR(v))
return true;
if (reg == LPASS_HDMI_TX_SSTREAM_ADDR(v))
return true;
if (reg == LPASS_HDMITX_APP_IRQEN_REG(v))
return true;
if (reg == LPASS_HDMITX_APP_IRQSTAT_REG(v))
return true;
for (i = 0; i < v->hdmi_rdma_channels; ++i) {
if (reg == LPAIF_HDMI_RDMACTL_REG(v, i))
return true;
if (reg == LPAIF_HDMI_RDMABASE_REG(v, i))
return true;
if (reg == LPAIF_HDMI_RDMABUFF_REG(v, i))
return true;
if (reg == LPAIF_HDMI_RDMAPER_REG(v, i))
return true;
if (reg == LPAIF_HDMI_RDMACURR_REG(v, i))
return true;
}
return false;
}
static bool lpass_hdmi_regmap_volatile(struct device *dev, unsigned int reg)
{
struct lpass_data *drvdata = dev_get_drvdata(dev);
const struct lpass_variant *v = drvdata->variant;
int i;
if (reg == LPASS_HDMITX_APP_IRQSTAT_REG(v))
return true;
if (reg == LPASS_HDMI_TX_LEGACY_ADDR(v))
return true;
if (reg == LPASS_HDMI_TX_VBIT_CTL_ADDR(v))
return true;
if (reg == LPASS_HDMI_TX_PARITY_ADDR(v))
return true;
for (i = 0; i < v->hdmi_rdma_channels; ++i) {
if (reg == LPAIF_HDMI_RDMACURR_REG(v, i))
return true;
if (reg == LPASS_HDMI_TX_DMA_ADDR(v, i))
return true;
if (reg == LPASS_HDMI_TX_CH_LSB_ADDR(v, i))
return true;
if (reg == LPASS_HDMI_TX_CH_MSB_ADDR(v, i))
return true;
}
return false;
}
static struct regmap_config lpass_hdmi_regmap_config = {
.name = "lpass_hdmi",
.reg_bits = 32,
.reg_stride = 4,
.val_bits = 32,
.writeable_reg = lpass_hdmi_regmap_writeable,
.readable_reg = lpass_hdmi_regmap_readable,
.volatile_reg = lpass_hdmi_regmap_volatile,
.cache_type = REGCACHE_FLAT,
};
static bool __lpass_rxtx_regmap_accessible(struct device *dev, unsigned int reg, bool rw)
{
struct lpass_data *drvdata = dev_get_drvdata(dev);
const struct lpass_variant *v = drvdata->variant;
int i;
for (i = 0; i < v->rxtx_irq_ports; ++i) {
if (reg == LPAIF_RXTX_IRQCLEAR_REG(v, i))
return true;
if (reg == LPAIF_RXTX_IRQEN_REG(v, i))
return true;
if (reg == LPAIF_RXTX_IRQSTAT_REG(v, i))
return true;
}
for (i = 0; i < v->rxtx_rdma_channels; ++i) {
if (reg == LPAIF_CDC_RXTX_RDMACTL_REG(v, i, LPASS_CDC_DMA_RX0))
return true;
if (reg == LPAIF_CDC_RXTX_RDMABASE_REG(v, i, LPASS_CDC_DMA_RX0))
return true;
if (reg == LPAIF_CDC_RXTX_RDMABUFF_REG(v, i, LPASS_CDC_DMA_RX0))
return true;
if (rw == LPASS_REG_READ) {
if (reg == LPAIF_CDC_RXTX_RDMACURR_REG(v, i, LPASS_CDC_DMA_RX0))
return true;
}
if (reg == LPAIF_CDC_RXTX_RDMAPER_REG(v, i, LPASS_CDC_DMA_RX0))
return true;
if (reg == LPAIF_CDC_RXTX_RDMA_INTF_REG(v, i, LPASS_CDC_DMA_RX0))
return true;
}
for (i = 0; i < v->rxtx_wrdma_channels; ++i) {
if (reg == LPAIF_CDC_RXTX_WRDMACTL_REG(v, i + v->rxtx_wrdma_channel_start,
LPASS_CDC_DMA_TX3))
return true;
if (reg == LPAIF_CDC_RXTX_WRDMABASE_REG(v, i + v->rxtx_wrdma_channel_start,
LPASS_CDC_DMA_TX3))
return true;
if (reg == LPAIF_CDC_RXTX_WRDMABUFF_REG(v, i + v->rxtx_wrdma_channel_start,
LPASS_CDC_DMA_TX3))
return true;
if (rw == LPASS_REG_READ) {
if (reg == LPAIF_CDC_RXTX_WRDMACURR_REG(v, i, LPASS_CDC_DMA_RX0))
return true;
}
if (reg == LPAIF_CDC_RXTX_WRDMAPER_REG(v, i + v->rxtx_wrdma_channel_start,
LPASS_CDC_DMA_TX3))
return true;
if (reg == LPAIF_CDC_RXTX_WRDMA_INTF_REG(v, i + v->rxtx_wrdma_channel_start,
LPASS_CDC_DMA_TX3))
return true;
}
return false;
}
static bool lpass_rxtx_regmap_writeable(struct device *dev, unsigned int reg)
{
return __lpass_rxtx_regmap_accessible(dev, reg, LPASS_REG_WRITE);
}
static bool lpass_rxtx_regmap_readable(struct device *dev, unsigned int reg)
{
return __lpass_rxtx_regmap_accessible(dev, reg, LPASS_REG_READ);
}
static bool lpass_rxtx_regmap_volatile(struct device *dev, unsigned int reg)
{
struct lpass_data *drvdata = dev_get_drvdata(dev);
const struct lpass_variant *v = drvdata->variant;
int i;
for (i = 0; i < v->rxtx_irq_ports; ++i) {
if (reg == LPAIF_RXTX_IRQCLEAR_REG(v, i))
return true;
if (reg == LPAIF_RXTX_IRQSTAT_REG(v, i))
return true;
}
for (i = 0; i < v->rxtx_rdma_channels; ++i)
if (reg == LPAIF_CDC_RXTX_RDMACURR_REG(v, i, LPASS_CDC_DMA_RX0))
return true;
for (i = 0; i < v->rxtx_wrdma_channels; ++i)
if (reg == LPAIF_CDC_RXTX_WRDMACURR_REG(v, i + v->rxtx_wrdma_channel_start,
LPASS_CDC_DMA_TX3))
return true;
return false;
}
static bool __lpass_va_regmap_accessible(struct device *dev, unsigned int reg, bool rw)
{
struct lpass_data *drvdata = dev_get_drvdata(dev);
const struct lpass_variant *v = drvdata->variant;
int i;
for (i = 0; i < v->va_irq_ports; ++i) {
if (reg == LPAIF_VA_IRQCLEAR_REG(v, i))
return true;
if (reg == LPAIF_VA_IRQEN_REG(v, i))
return true;
if (reg == LPAIF_VA_IRQSTAT_REG(v, i))
return true;
}
for (i = 0; i < v->va_wrdma_channels; ++i) {
if (reg == LPAIF_CDC_VA_WRDMACTL_REG(v, i + v->va_wrdma_channel_start,
LPASS_CDC_DMA_VA_TX0))
return true;
if (reg == LPAIF_CDC_VA_WRDMABASE_REG(v, i + v->va_wrdma_channel_start,
LPASS_CDC_DMA_VA_TX0))
return true;
if (reg == LPAIF_CDC_VA_WRDMABUFF_REG(v, i + v->va_wrdma_channel_start,
LPASS_CDC_DMA_VA_TX0))
return true;
if (rw == LPASS_REG_READ) {
if (reg == LPAIF_CDC_VA_WRDMACURR_REG(v, i + v->va_wrdma_channel_start,
LPASS_CDC_DMA_VA_TX0))
return true;
}
if (reg == LPAIF_CDC_VA_WRDMAPER_REG(v, i + v->va_wrdma_channel_start,
LPASS_CDC_DMA_VA_TX0))
return true;
if (reg == LPAIF_CDC_VA_WRDMA_INTF_REG(v, i + v->va_wrdma_channel_start,
LPASS_CDC_DMA_VA_TX0))
return true;
}
return false;
}
static bool lpass_va_regmap_writeable(struct device *dev, unsigned int reg)
{
return __lpass_va_regmap_accessible(dev, reg, LPASS_REG_WRITE);
}
static bool lpass_va_regmap_readable(struct device *dev, unsigned int reg)
{
return __lpass_va_regmap_accessible(dev, reg, LPASS_REG_READ);
}
static bool lpass_va_regmap_volatile(struct device *dev, unsigned int reg)
{
struct lpass_data *drvdata = dev_get_drvdata(dev);
const struct lpass_variant *v = drvdata->variant;
int i;
for (i = 0; i < v->va_irq_ports; ++i) {
if (reg == LPAIF_VA_IRQCLEAR_REG(v, i))
return true;
if (reg == LPAIF_VA_IRQSTAT_REG(v, i))
return true;
}
for (i = 0; i < v->va_wrdma_channels; ++i) {
if (reg == LPAIF_CDC_VA_WRDMACURR_REG(v, i + v->va_wrdma_channel_start,
LPASS_CDC_DMA_VA_TX0))
return true;
}
return false;
}
static struct regmap_config lpass_rxtx_regmap_config = {
.reg_bits = 32,
.reg_stride = 4,
.val_bits = 32,
.writeable_reg = lpass_rxtx_regmap_writeable,
.readable_reg = lpass_rxtx_regmap_readable,
.volatile_reg = lpass_rxtx_regmap_volatile,
.cache_type = REGCACHE_FLAT,
};
static struct regmap_config lpass_va_regmap_config = {
.reg_bits = 32,
.reg_stride = 4,
.val_bits = 32,
.writeable_reg = lpass_va_regmap_writeable,
.readable_reg = lpass_va_regmap_readable,
.volatile_reg = lpass_va_regmap_volatile,
.cache_type = REGCACHE_FLAT,
};
static unsigned int of_lpass_cpu_parse_sd_lines(struct device *dev,
struct device_node *node,
const char *name)
{
unsigned int lines[LPASS_CPU_MAX_MI2S_LINES];
unsigned int sd_line_mask = 0;
int num_lines, i;
num_lines = of_property_read_variable_u32_array(node, name, lines, 0,
LPASS_CPU_MAX_MI2S_LINES);
if (num_lines < 0)
return LPAIF_I2SCTL_MODE_NONE;
for (i = 0; i < num_lines; i++)
sd_line_mask |= BIT(lines[i]);
switch (sd_line_mask) {
case LPASS_CPU_I2S_SD0_MASK:
return LPAIF_I2SCTL_MODE_SD0;
case LPASS_CPU_I2S_SD1_MASK:
return LPAIF_I2SCTL_MODE_SD1;
case LPASS_CPU_I2S_SD2_MASK:
return LPAIF_I2SCTL_MODE_SD2;
case LPASS_CPU_I2S_SD3_MASK:
return LPAIF_I2SCTL_MODE_SD3;
case LPASS_CPU_I2S_SD0_1_MASK:
return LPAIF_I2SCTL_MODE_QUAD01;
case LPASS_CPU_I2S_SD2_3_MASK:
return LPAIF_I2SCTL_MODE_QUAD23;
case LPASS_CPU_I2S_SD0_1_2_MASK:
return LPAIF_I2SCTL_MODE_6CH;
case LPASS_CPU_I2S_SD0_1_2_3_MASK:
return LPAIF_I2SCTL_MODE_8CH;
default:
dev_err(dev, "Unsupported SD line mask: %#x\n", sd_line_mask);
return LPAIF_I2SCTL_MODE_NONE;
}
}
static void of_lpass_cpu_parse_dai_data(struct device *dev,
struct lpass_data *data)
{
struct device_node *node;
int ret, i, id;
/* Allow all channels by default for backwards compatibility */
for (i = 0; i < data->variant->num_dai; i++) {
id = data->variant->dai_driver[i].id;
data->mi2s_playback_sd_mode[id] = LPAIF_I2SCTL_MODE_8CH;
data->mi2s_capture_sd_mode[id] = LPAIF_I2SCTL_MODE_8CH;
}
for_each_child_of_node(dev->of_node, node) {
ret = of_property_read_u32(node, "reg", &id);
if (ret || id < 0) {
dev_err(dev, "valid dai id not found: %d\n", ret);
continue;
}
if (id == LPASS_DP_RX) {
data->hdmi_port_enable = 1;
} else if (is_cdc_dma_port(id)) {
data->codec_dma_enable = 1;
} else {
data->mi2s_playback_sd_mode[id] =
of_lpass_cpu_parse_sd_lines(dev, node,
"qcom,playback-sd-lines");
data->mi2s_capture_sd_mode[id] =
of_lpass_cpu_parse_sd_lines(dev, node,
"qcom,capture-sd-lines");
}
}
}
static int of_lpass_cdc_dma_clks_parse(struct device *dev,
struct lpass_data *data)
{
data->codec_mem0 = devm_clk_get(dev, "audio_cc_codec_mem0");
if (IS_ERR(data->codec_mem0))
return PTR_ERR(data->codec_mem0);
data->codec_mem1 = devm_clk_get(dev, "audio_cc_codec_mem1");
if (IS_ERR(data->codec_mem1))
return PTR_ERR(data->codec_mem1);
data->codec_mem2 = devm_clk_get(dev, "audio_cc_codec_mem2");
if (IS_ERR(data->codec_mem2))
return PTR_ERR(data->codec_mem2);
data->va_mem0 = devm_clk_get(dev, "aon_cc_va_mem0");
if (IS_ERR(data->va_mem0))
return PTR_ERR(data->va_mem0);
return 0;
}
int asoc_qcom_lpass_cpu_platform_probe(struct platform_device *pdev)
{
struct lpass_data *drvdata;
struct device_node *dsp_of_node;
struct resource *res;
const struct lpass_variant *variant;
struct device *dev = &pdev->dev;
int ret, i, dai_id;
dsp_of_node = of_parse_phandle(pdev->dev.of_node, "qcom,adsp", 0);
if (dsp_of_node) {
dev_err(dev, "DSP exists and holds audio resources\n");
of_node_put(dsp_of_node);
return -EBUSY;
}
drvdata = devm_kzalloc(dev, sizeof(struct lpass_data), GFP_KERNEL);
if (!drvdata)
return -ENOMEM;
platform_set_drvdata(pdev, drvdata);
variant = device_get_match_data(dev);
if (!variant)
return -EINVAL;
if (of_device_is_compatible(dev->of_node, "qcom,lpass-cpu-apq8016"))
dev_warn(dev, "qcom,lpass-cpu-apq8016 compatible is deprecated\n");
drvdata->variant = variant;
of_lpass_cpu_parse_dai_data(dev, drvdata);
if (drvdata->codec_dma_enable) {
drvdata->rxtx_lpaif =
devm_platform_ioremap_resource_byname(pdev, "lpass-rxtx-lpaif");
if (IS_ERR(drvdata->rxtx_lpaif))
return PTR_ERR(drvdata->rxtx_lpaif);
drvdata->va_lpaif = devm_platform_ioremap_resource_byname(pdev, "lpass-va-lpaif");
if (IS_ERR(drvdata->va_lpaif))
return PTR_ERR(drvdata->va_lpaif);
lpass_rxtx_regmap_config.max_register = LPAIF_CDC_RXTX_WRDMAPER_REG(variant,
variant->rxtx_wrdma_channels +
variant->rxtx_wrdma_channel_start, LPASS_CDC_DMA_TX3);
drvdata->rxtx_lpaif_map = devm_regmap_init_mmio(dev, drvdata->rxtx_lpaif,
&lpass_rxtx_regmap_config);
if (IS_ERR(drvdata->rxtx_lpaif_map))
return PTR_ERR(drvdata->rxtx_lpaif_map);
lpass_va_regmap_config.max_register = LPAIF_CDC_VA_WRDMAPER_REG(variant,
variant->va_wrdma_channels +
variant->va_wrdma_channel_start, LPASS_CDC_DMA_VA_TX0);
drvdata->va_lpaif_map = devm_regmap_init_mmio(dev, drvdata->va_lpaif,
&lpass_va_regmap_config);
if (IS_ERR(drvdata->va_lpaif_map))
return PTR_ERR(drvdata->va_lpaif_map);
ret = of_lpass_cdc_dma_clks_parse(dev, drvdata);
if (ret) {
dev_err(dev, "failed to get cdc dma clocks %d\n", ret);
return ret;
}
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "lpass-rxtx-cdc-dma-lpm");
drvdata->rxtx_cdc_dma_lpm_buf = res->start;
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "lpass-va-cdc-dma-lpm");
drvdata->va_cdc_dma_lpm_buf = res->start;
}
drvdata->lpaif = devm_platform_ioremap_resource_byname(pdev, "lpass-lpaif");
if (IS_ERR(drvdata->lpaif))
return PTR_ERR(drvdata->lpaif);
lpass_cpu_regmap_config.max_register = LPAIF_WRDMAPER_REG(variant,
variant->wrdma_channels +
variant->wrdma_channel_start);
drvdata->lpaif_map = devm_regmap_init_mmio(dev, drvdata->lpaif,
&lpass_cpu_regmap_config);
if (IS_ERR(drvdata->lpaif_map)) {
dev_err(dev, "error initializing regmap: %ld\n",
PTR_ERR(drvdata->lpaif_map));
return PTR_ERR(drvdata->lpaif_map);
}
if (drvdata->hdmi_port_enable) {
drvdata->hdmiif = devm_platform_ioremap_resource_byname(pdev, "lpass-hdmiif");
if (IS_ERR(drvdata->hdmiif))
return PTR_ERR(drvdata->hdmiif);
lpass_hdmi_regmap_config.max_register = LPAIF_HDMI_RDMAPER_REG(variant,
variant->hdmi_rdma_channels - 1);
drvdata->hdmiif_map = devm_regmap_init_mmio(dev, drvdata->hdmiif,
&lpass_hdmi_regmap_config);
if (IS_ERR(drvdata->hdmiif_map)) {
dev_err(dev, "error initializing regmap: %ld\n",
PTR_ERR(drvdata->hdmiif_map));
return PTR_ERR(drvdata->hdmiif_map);
}
}
if (variant->init) {
ret = variant->init(pdev);
if (ret) {
dev_err(dev, "error initializing variant: %d\n", ret);
return ret;
}
}
for (i = 0; i < variant->num_dai; i++) {
dai_id = variant->dai_driver[i].id;
if (dai_id == LPASS_DP_RX || is_cdc_dma_port(dai_id))
continue;
drvdata->mi2s_osr_clk[dai_id] = devm_clk_get_optional(dev,
variant->dai_osr_clk_names[i]);
drvdata->mi2s_bit_clk[dai_id] = devm_clk_get(dev,
variant->dai_bit_clk_names[i]);
if (IS_ERR(drvdata->mi2s_bit_clk[dai_id])) {
dev_err(dev,
"error getting %s: %ld\n",
variant->dai_bit_clk_names[i],
PTR_ERR(drvdata->mi2s_bit_clk[dai_id]));
return PTR_ERR(drvdata->mi2s_bit_clk[dai_id]);
}
if (drvdata->mi2s_playback_sd_mode[dai_id] ==
LPAIF_I2SCTL_MODE_QUAD01) {
variant->dai_driver[dai_id].playback.channels_min = 4;
variant->dai_driver[dai_id].playback.channels_max = 4;
}
}
/* Allocation for i2sctl regmap fields */
drvdata->i2sctl = devm_kzalloc(&pdev->dev, sizeof(struct lpaif_i2sctl),
GFP_KERNEL);
/* Initialize bitfields for dai I2SCTL register */
ret = lpass_cpu_init_i2sctl_bitfields(dev, drvdata->i2sctl,
drvdata->lpaif_map);
if (ret) {
dev_err(dev, "error init i2sctl field: %d\n", ret);
return ret;
}
if (drvdata->hdmi_port_enable) {
ret = lpass_hdmi_init_bitfields(dev, drvdata->hdmiif_map);
if (ret) {
dev_err(dev, "%s error hdmi init failed\n", __func__);
return ret;
}
}
ret = devm_snd_soc_register_component(dev,
&lpass_cpu_comp_driver,
variant->dai_driver,
variant->num_dai);
if (ret) {
dev_err(dev, "error registering cpu driver: %d\n", ret);
goto err;
}
ret = asoc_qcom_lpass_platform_register(pdev);
if (ret) {
dev_err(dev, "error registering platform driver: %d\n", ret);
goto err;
}
err:
return ret;
}
EXPORT_SYMBOL_GPL(asoc_qcom_lpass_cpu_platform_probe);
void asoc_qcom_lpass_cpu_platform_remove(struct platform_device *pdev)
{
struct lpass_data *drvdata = platform_get_drvdata(pdev);
if (drvdata->variant->exit)
drvdata->variant->exit(pdev);
}
EXPORT_SYMBOL_GPL(asoc_qcom_lpass_cpu_platform_remove);
void asoc_qcom_lpass_cpu_platform_shutdown(struct platform_device *pdev)
{
struct lpass_data *drvdata = platform_get_drvdata(pdev);
if (drvdata->variant->exit)
drvdata->variant->exit(pdev);
}
EXPORT_SYMBOL_GPL(asoc_qcom_lpass_cpu_platform_shutdown);
MODULE_DESCRIPTION("QTi LPASS CPU Driver");
MODULE_LICENSE("GPL v2");