blob: aa096072e8386b7fbe291d5422197c8d0f21a67d [file] [log] [blame]
/*
* sound/soc/amlogic/auge/resample.c
*
* Copyright (C) 2017 Amlogic, Inc. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* 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.
*
*/
#define DEBUG
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/platform_device.h>
#include <linux/clk.h>
#include <linux/clk-provider.h>
#include <sound/soc.h>
#include <sound/tlv.h>
#include "resample.h"
#include "regs.h"
#include "iomap.h"
#define DRV_NAME "audioresample"
#define CLK_RATIO 256
/*#define __PTM_RESAMPLE_CLK__*/
//#define RESAMPLE_A 0
//#define RESAMPLE_B 1
struct resample_chipinfo {
int num; /* support resample a/b */
enum resample_idx id;
bool dividor_fn;
};
struct audioresample {
struct device *dev;
/* mpll0~3, hifi pll, div3~4, gp0 */
struct clk *pll;
/* mst_mclk_a~f, slv_sclk_a~j */
struct clk *sclk;
/* resample clk */
struct clk *clk;
struct resample_chipinfo *chipinfo;
enum resample_idx id;
/*which module should be resampled */
enum toddr_src resample_module;
/* resample to the rate */
int out_rate;
/* sync with auge_resample_texts */
enum samplerate_index asrc_rate_idx;
bool enable;
};
struct audioresample *s_resample_a;
struct audioresample *s_resample_b;
static struct audioresample *get_audioresample(enum resample_idx id)
{
struct audioresample *p_resample;
p_resample = ((id == RESAMPLE_A) ? s_resample_a : s_resample_b);
if (!p_resample) {
pr_debug("Not init audio resample\n");
return NULL;
}
return p_resample;
}
int get_resample_module_num(void)
{
struct audioresample *p_resample = get_audioresample(RESAMPLE_A);
if (p_resample && p_resample->chipinfo)
return p_resample->chipinfo->num;
return 1;
}
int set_resample_source(enum resample_idx id, enum toddr_src src)
{
struct audioresample *p_resample = get_audioresample(id);
(void)p_resample;
(void)src;
//p_resample->resample_module = src;
return 0;
}
static int set_resample_rate_index(
enum resample_idx id, enum samplerate_index index)
{
struct audioresample *p_resample = get_audioresample(id);
p_resample->asrc_rate_idx = index;
return 0;
}
static enum samplerate_index get_resample_rate_index(
enum resample_idx id)
{
struct audioresample *p_resample = get_audioresample(id);
return p_resample->asrc_rate_idx;
}
static int resample_clk_set(struct audioresample *p_resample)
{
int ret = 0;
/* enable clock */
if (p_resample->enable) {
if (p_resample->out_rate) {
#ifdef __PTM_RESAMPLE_CLK__
clk_set_rate(p_resample->pll,
p_resample->out_rate * CLK_RATIO * 2 * 14);
#else
clk_set_rate(p_resample->pll,
p_resample->out_rate * CLK_RATIO * 2);
#endif
clk_set_rate(p_resample->sclk,
p_resample->out_rate * CLK_RATIO);
clk_set_rate(p_resample->clk,
p_resample->out_rate * CLK_RATIO);
} else {
/* defaule resample clk */
clk_set_rate(p_resample->pll, 48000 * CLK_RATIO * 2);
clk_set_rate(p_resample->sclk, 48000 * CLK_RATIO);
clk_set_rate(p_resample->clk, 48000 * CLK_RATIO);
}
#if 0
ret = clk_prepare_enable(p_resample->pll);
if (ret) {
pr_err("Can't enable pll clock: %d\n", ret);
return -EINVAL;
}
ret = clk_prepare_enable(p_resample->sclk);
if (ret) {
pr_err("Can't enable resample_src clock: %d\n",
ret);
return -EINVAL;
}
ret = clk_prepare_enable(p_resample->clk);
if (ret) {
pr_err("Can't enable resample_clk clock: %d\n",
ret);
return -EINVAL;
}
#endif
pr_info("%s, resample_pll:%lu, sclk:%lu, clk:%lu\n",
__func__,
clk_get_rate(p_resample->pll),
clk_get_rate(p_resample->sclk),
clk_get_rate(p_resample->clk));
} else {
#if 0
clk_disable_unprepare(p_resample->clk);
clk_disable_unprepare(p_resample->sclk);
clk_disable_unprepare(p_resample->pll);
#endif
}
return ret;
}
static void audio_resample_init(struct audioresample *p_resample)
{
resample_clk_set(p_resample);
aml_set_resample(p_resample->id, p_resample->enable,
p_resample->resample_module);
}
static int audio_resample_set(
struct audioresample *p_resample,
bool enable, int rate)
{
if (!p_resample)
return 0;
p_resample->enable = enable;
p_resample->out_rate = rate;
audio_resample_init(p_resample);
return 0;
}
static const char *const auge_resample_texts[] = {
"Disable",
"Enable:32K",
"Enable:44K",
"Enable:48K",
"Enable:88K",
"Enable:96K",
"Enable:176K",
"Enable:192K",
};
static int resample_idx2rate(enum samplerate_index index)
{
int rate = 0;
if (index == RATE_OFF)
rate = 0;
else if (index == RATE_32K)
rate = 32000;
else if (index == RATE_44K)
rate = 44100;
else if (index == RATE_48K)
rate = 48000;
else if (index == RATE_88K)
rate = 88200;
else if (index == RATE_96K)
rate = 96000;
else if (index == RATE_176K)
rate = 176400;
else if (index == RATE_192K)
rate = 192000;
return rate;
}
static const struct soc_enum auge_resample_enum =
SOC_ENUM_SINGLE(SND_SOC_NOPM, 0, ARRAY_SIZE(auge_resample_texts),
auge_resample_texts);
static int resample_get_enum(
struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct audioresample *p_resample = snd_kcontrol_chip(kcontrol);
if (!p_resample) {
pr_debug("audio resample is not init\n");
return 0;
}
ucontrol->value.enumerated.item[0] = p_resample->asrc_rate_idx;
return 0;
}
int resample_set(enum resample_idx id, enum samplerate_index index)
{
int resample_rate = 0;
struct audioresample *p_resample = get_audioresample(id);
int ret = 0;
pr_info("%s resample_%c to %s, last %s\n",
__func__,
(id == RESAMPLE_A) ? 'a' : 'b',
auge_resample_texts[index],
auge_resample_texts[p_resample->asrc_rate_idx]);
if (!p_resample)
return 0;
//if (index == p_resample->asrc_rate_idx)
// return 0;
p_resample->asrc_rate_idx = index;
resample_rate = resample_idx2rate(index);
ret = audio_resample_set(p_resample, (bool)index, resample_rate);
if (ret)
return ret;
if (index == RATE_OFF)
resample_disable(p_resample->id);
else {
resample_init(p_resample->id, resample_rate);
resample_set_hw_param(p_resample->id, index);
}
return 0;
}
int resample_set_inner_rate(enum resample_idx id)
{
enum samplerate_index index = get_resample_rate_index(id);
pr_debug("%s() index %d\n", __func__, id);
return resample_set(id, index);
}
static int resample_set_enum(
struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct audioresample *p_resample = snd_kcontrol_chip(kcontrol);
int index = ucontrol->value.enumerated.item[0];
if (!p_resample) {
pr_debug("audio resample is not init\n");
return 0;
}
set_resample_rate_index(p_resample->id, index);
resample_set_inner_rate(p_resample->id);
return 0;
}
static int mixer_audiobus_read(
struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct soc_mixer_control *mc =
(struct soc_mixer_control *)kcontrol->private_value;
unsigned int reg = mc->reg;
unsigned int shift = mc->shift;
unsigned int max = mc->max;
unsigned int invert = mc->invert;
unsigned int value =
(((unsigned int)audiobus_read(reg)) >> shift) & max;
if (invert)
value = (~value) & max;
ucontrol->value.integer.value[0] = value;
return 0;
}
static int mixer_audiobus_write(
struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct soc_mixer_control *mc =
(struct soc_mixer_control *)kcontrol->private_value;
unsigned int reg = mc->reg;
unsigned int shift = mc->shift;
unsigned int max = mc->max;
unsigned int invert = mc->invert;
unsigned int value = ucontrol->value.integer.value[0];
unsigned int reg_value = (unsigned int)audiobus_read(reg);
if (invert)
value = (~value) & max;
max = ~(max << shift);
reg_value &= max;
reg_value |= (value << shift);
audiobus_write(reg, reg_value);
return 0;
}
/* resample module
* keep sync with enum toddr_src in ddr_mngr.h
*/
static const char *const auge_resample_module_texts[] = {
"TDMIN_A",
"TDMIN_B",
"TDMIN_C",
"SPDIFIN",
"PDMIN",
"FRATV", /* NONE for axg, g12a, g12b */
"TDMIN_LB",
"LOOPBACK_A",
"FRHDMIRX", /* from tl1 chipset*/
"LOOPBACK_B",
"SPDIFIN_LB",
"RESERVED_0",
"RESERVED_1",
"RESERVED_2",
"RESERVED_3",
"VAD",
};
static const struct soc_enum auge_resample_module_enum =
SOC_ENUM_SINGLE(SND_SOC_NOPM, 0, ARRAY_SIZE(auge_resample_module_texts),
auge_resample_module_texts);
static int resample_module_get_enum(
struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct audioresample *p_resample = snd_kcontrol_chip(kcontrol);
if (!p_resample) {
pr_debug("audio resample is not init\n");
return 0;
}
ucontrol->value.enumerated.item[0] = p_resample->resample_module;
return 0;
}
static int resample_module_set_enum(
struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct audioresample *p_resample = snd_kcontrol_chip(kcontrol);
if (!p_resample) {
pr_debug("audio resample is not init\n");
return 0;
}
p_resample->resample_module = ucontrol->value.enumerated.item[0];
/* update info to ddr */
aml_set_resample(p_resample->id,
p_resample->enable,
p_resample->resample_module);
return 0;
}
static const struct snd_kcontrol_new asrc_a_controls[] = {
SOC_ENUM_EXT("Hardware resample enable",
auge_resample_enum,
resample_get_enum,
resample_set_enum),
SOC_SINGLE_EXT_TLV("Hw resample pause enable",
EE_AUDIO_RESAMPLEA_CTRL2, 24, 0x1, 0,
mixer_audiobus_read, mixer_audiobus_write,
NULL),
SOC_SINGLE_EXT_TLV("Hw resample pause thd",
EE_AUDIO_RESAMPLEA_CTRL2, 0, 0xffffff, 0,
mixer_audiobus_read, mixer_audiobus_write,
NULL),
SOC_ENUM_EXT("Hw resample module",
auge_resample_module_enum,
resample_module_get_enum,
resample_module_set_enum),
};
static const struct snd_kcontrol_new asrc_b_controls[] = {
SOC_ENUM_EXT("Hardware resample b enable",
auge_resample_enum,
resample_get_enum,
resample_set_enum),
SOC_SINGLE_EXT_TLV("Hw resample b pause enable",
EE_AUDIO_RESAMPLEB_CTRL2, 24, 0x1, 0,
mixer_audiobus_read, mixer_audiobus_write,
NULL),
SOC_SINGLE_EXT_TLV("Hw resample b pause thd",
EE_AUDIO_RESAMPLEB_CTRL2, 0, 0xffffff, 0,
mixer_audiobus_read, mixer_audiobus_write,
NULL),
SOC_ENUM_EXT("Hw resample b module",
auge_resample_module_enum,
resample_module_get_enum,
resample_module_set_enum),
};
int card_add_resample_kcontrols(struct snd_soc_card *card)
{
unsigned int idx;
int err;
if (s_resample_a) {
for (idx = 0; idx < ARRAY_SIZE(asrc_a_controls); idx++) {
err = snd_ctl_add(card->snd_card,
snd_ctl_new1(&asrc_a_controls[idx],
s_resample_a));
if (err < 0)
return err;
}
}
if (s_resample_b) {
for (idx = 0; idx < ARRAY_SIZE(asrc_b_controls); idx++) {
err = snd_ctl_add(card->snd_card,
snd_ctl_new1(&asrc_b_controls[idx],
s_resample_b));
if (err < 0)
return err;
}
}
return 0;
}
static struct resample_chipinfo g12a_resample_chipinfo = {
.dividor_fn = true,
};
static struct resample_chipinfo tl1_resample_a_chipinfo = {
.num = 2,
.id = RESAMPLE_A,
.dividor_fn = true,
};
static struct resample_chipinfo tl1_resample_b_chipinfo = {
.num = 2,
.id = RESAMPLE_B,
.dividor_fn = true,
};
static const struct of_device_id resample_device_id[] = {
{
.compatible = "amlogic, axg-resample",
},
{
.compatible = "amlogic, g12a-resample",
.data = &g12a_resample_chipinfo,
},
{
.compatible = "amlogic, tl1-resample-a",
.data = &tl1_resample_a_chipinfo,
},
{
.compatible = "amlogic, tl1-resample-b",
.data = &tl1_resample_b_chipinfo,
},
{}
};
MODULE_DEVICE_TABLE(of, resample_device_id);
static int resample_platform_probe(struct platform_device *pdev)
{
struct audioresample *p_resample;
struct device *dev = &pdev->dev;
struct resample_chipinfo *p_chipinfo;
unsigned int resample_module;
int ret;
pr_info("%s\n", __func__);
p_resample = devm_kzalloc(&pdev->dev,
sizeof(struct audioresample),
GFP_KERNEL);
if (!p_resample) {
/*dev_err(&pdev->dev, "Can't allocate pcm_p\n");*/
return -ENOMEM;
}
/* match data */
p_chipinfo = (struct resample_chipinfo *)
of_device_get_match_data(dev);
if (!p_chipinfo)
dev_warn_once(dev, "check whether to update resample chipinfo\n");
else
p_resample->id = p_chipinfo->id;
p_resample->chipinfo = p_chipinfo;
ret = of_property_read_u32(pdev->dev.of_node, "resample_module",
&resample_module);
if (ret < 0) {
dev_err(&pdev->dev, "Can't retrieve resample_module\n");
return -EINVAL;
}
/* config from dts */
p_resample->resample_module = resample_module;
p_resample->pll = devm_clk_get(&pdev->dev, "resample_pll");
if (IS_ERR(p_resample->pll)) {
dev_err(&pdev->dev,
"Can't retrieve resample_pll clock\n");
ret = PTR_ERR(p_resample->pll);
return ret;
}
p_resample->sclk = devm_clk_get(&pdev->dev, "resample_src");
if (IS_ERR(p_resample->sclk)) {
dev_err(&pdev->dev,
"Can't retrieve resample_src clock\n");
ret = PTR_ERR(p_resample->sclk);
return ret;
}
p_resample->clk = devm_clk_get(&pdev->dev, "resample_clk");
if (IS_ERR(p_resample->clk)) {
dev_err(&pdev->dev,
"Can't retrieve resample_clk clock\n");
ret = PTR_ERR(p_resample->clk);
return ret;
}
ret = clk_set_parent(p_resample->sclk, p_resample->pll);
if (ret) {
dev_err(&pdev->dev,
"Can't set resample_src parent clock\n");
ret = PTR_ERR(p_resample->sclk);
return ret;
}
ret = clk_set_parent(p_resample->clk, p_resample->sclk);
if (ret) {
dev_err(&pdev->dev,
"Can't set resample_clk parent clock\n");
ret = PTR_ERR(p_resample->clk);
return ret;
}
ret = clk_prepare_enable(p_resample->clk);
if (ret) {
pr_err("Can't enable resample_clk clock: %d\n",
ret);
return ret;
}
p_resample->dev = dev;
if (p_chipinfo && p_chipinfo->id == 1)
s_resample_b = p_resample;
else
s_resample_a = p_resample;
return 0;
}
static struct platform_driver resample_platform_driver = {
.driver = {
.name = DRV_NAME,
.owner = THIS_MODULE,
.of_match_table = of_match_ptr(resample_device_id),
},
.probe = resample_platform_probe,
};
module_platform_driver(resample_platform_driver);