// 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 <linux/clk.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_device.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)
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);
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);
clk_disable_unprepare(drvdata->mi2s_osr_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