blob: f9cb9faefda16d9e46c8de14306ea66514d77bc0 [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0
/* Copyright (C) 2018 Synaptics Incorporated */
#include <linux/gpio/consumer.h>
#include <linux/of_platform.h>
#include <linux/module.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
/* TODO: add correct route and widgets here or dts */
static const struct snd_soc_dapm_route berlin_asoc_route[] = {
};
/* TODO: Add all possible widgets here or dts */
static const struct snd_soc_dapm_widget berlin_asoc_widgets[] = {
};
static int berlin_asoc_link_hwparam(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_dai *cpu = rtd->cpu_dai;
struct snd_soc_dai *codec = rtd->codec_dai;
int ret = 0, cret = 0;
/* set CPU dai format */
if (strstr(cpu->name, "outdai")) {
// revert both bclk and fsync
ret = snd_soc_dai_set_fmt(cpu, SND_SOC_DAIFMT_IB_IF);
if (ret) {
dev_err(rtd->dev, "error setting daifmt 0x%x to %s\n",
SND_SOC_DAIFMT_IB_IF, cpu->name);
return ret;
}
}
/* try to set codec dai fmt */
if (strstr(codec->name, "tas2770")) {
/* set codec DAI slots, 2 channels, slot width 32 */
cret = snd_soc_dai_set_tdm_slot(codec, 0xFF, 0xFF, 2, 32);
if (cret)
dev_err(rtd->dev, "setting codec %s slot err %d\n",
codec->name, cret);
}
if (strstr(codec->name, "cx9000")) {
cret = snd_soc_dai_set_sysclk(codec, 1, 3072000, 0);
if (cret)
dev_err(rtd->dev, "error %d set codec %s\n",
cret, codec->name);
cret = snd_soc_dai_set_bclk_ratio(codec, 64);
if (cret)
dev_err(rtd->dev, "error %d set codec %s bclkratio \n",
cret, codec->name);
}
if (strstr(codec->name, "tas5825")) {
cret = snd_soc_dai_set_sysclk(cpu, 0xFFFF, 0xFFFF, 0xFFFF);
if (cret)
dev_err(rtd->dev, "setting cpu %s set sysclk err %d\n",
cpu->name, cret);
/* 5ms delay to wait for clock stable */
usleep_range(3500, 5000);
cret = snd_soc_dai_set_sysclk(codec, 0xFFFF, 0xFFFF, 0xFFFF);
if (cret)
dev_err(rtd->dev, "setting codec %s set sysclk err %d\n",
codec->name, cret);
}
return ret;
}
static struct snd_soc_ops asoc_link_ops = {
.hw_params = &berlin_asoc_link_hwparam,
};
struct berlin_asoc_priv {
struct snd_soc_dai_link dai_link[16];
u32 link_num;
struct platform_device *pdev;
struct snd_soc_card card;
struct gpio_desc *mute_status_gpio;
bool mute_status_gpio_output;
struct gpio_desc *power_gpio;
char name[32];
};
static int snd_berlin_rate_control_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{
uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
uinfo->count = 1;
uinfo->value.integer.min = -500;
uinfo->value.integer.max = 500;
uinfo->value.integer.step = 1;
return 0;
}
static int snd_berlin_rate_control_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
//TODO: add ppm func back here
#if 0
int ppm_base, ppm_now;
AVPLL_GetPPM(&ppm_base, &ppm_now);
ucontrol->value.integer.value[0] = (ppm_now - ppm_base);
snd_printd("%s: get ppm %d\n", __func__, ppm_now - ppm_base);
#endif
return 0;
}
static int snd_berlin_rate_control_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
//TODO: add ppm func back here
#if 0
int ppm_base, ppm_now;
int ppm, current_ppm;
ppm = ucontrol->value.integer.value[0];
if ((ppm < -500) || (ppm > 500))
return -1;
AVPLL_GetPPM(&ppm_base, &ppm_now);
current_ppm = (ppm_now - ppm_base);
if (ppm != current_ppm) {
AVPLL_AdjustPPM(ppm_base - ppm_now + ppm);
snd_printd("%s: adjust ppm %d -> %d\n", __func__,
current_ppm, ppm);
return 1;
}
#endif
return 0;
}
static struct snd_kcontrol_new snd_berlin_rate_control = {
.iface = SNDRV_CTL_ELEM_IFACE_PCM,
.name = "PCM Playback Rate Offset",
.index = 0,
.device = 0,
.subdevice = 0,
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
.private_value = 0xffff,
.get = snd_berlin_rate_control_get,
.put = snd_berlin_rate_control_put,
.info = snd_berlin_rate_control_info
};
static const char * const snd_berlin_amp_mode_text[] = {"5V", "24V"};
static SOC_ENUM_SINGLE_EXT_DECL(soc_enum_amp_mode, snd_berlin_amp_mode_text);
static int snd_berlin_amp_mode_control_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_card *card = snd_kcontrol_chip(kcontrol);
struct berlin_asoc_priv *priv = snd_soc_card_get_drvdata(card);
if (priv->power_gpio) {
ucontrol->value.integer.value[0] =
gpiod_get_value_cansleep(priv->power_gpio);
} else {
snd_printk("No power gpio during get.\n");
}
return 0;
}
static int snd_berlin_amp_mode_control_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_card *card = snd_kcontrol_chip(kcontrol);
struct berlin_asoc_priv *priv = snd_soc_card_get_drvdata(card);
if (priv->power_gpio) {
gpiod_set_value_cansleep(priv->power_gpio, !!ucontrol->value.integer.value[0]);
} else {
snd_printk("No power gpio during put.\n");
}
return 0;
}
static const struct snd_kcontrol_new snd_berlin_amp_mode_control = SOC_ENUM_EXT(
"Amp Mode",
soc_enum_amp_mode,
snd_berlin_amp_mode_control_get,
snd_berlin_amp_mode_control_put);
#define MAX_FALLBACK_LINKS 10
struct berlin_asoc_fblink {
s32 linkid;
struct snd_soc_dai_link fblink;
};
static struct berlin_asoc_priv *dev_to_berlin_asoc(struct device *dev)
{
struct snd_card *card = NULL;
card = dev_to_snd_card(dev);
if (card == NULL)
return NULL;
return (struct berlin_asoc_priv *)card->private_data;
}
static ssize_t mic_mute_state_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct berlin_asoc_priv *priv = dev_to_berlin_asoc(dev);
ssize_t status;
if (priv == NULL)
return -ENODEV;
status = sprintf(buf, "%d\n", gpiod_get_value_cansleep(priv->mute_status_gpio));
return status;
}
static ssize_t mic_mute_state_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t size) {
struct berlin_asoc_priv *priv = dev_to_berlin_asoc(dev);
ssize_t status;
long value;
if (priv == NULL)
return -ENODEV;
if (!priv->mute_status_gpio_output)
return -EINVAL;
status = kstrtol(buf, 0, &value);
if (status == 0) {
gpiod_set_value_cansleep(priv->mute_status_gpio, value);
status = size;
}
return status;
}
static DEVICE_ATTR_RW(mic_mute_state);
extern struct attribute_group berlin_sysfs_group;
static int berlin_asoc_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct device_node *cpu_np, *codec_np, *platform_np, *iter;
struct device_node *np = pdev->dev.of_node;
struct berlin_asoc_priv *priv;
struct snd_kcontrol *kctl;
int ret, i, linka_num = 0, linkb_num = 0;
struct berlin_asoc_fblink fblinks[MAX_FALLBACK_LINKS];
priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
for (i = 0; i < MAX_FALLBACK_LINKS; i++)
fblinks[i].linkid = -1;
priv->mute_status_gpio_output = of_property_read_bool(np, "mute-gpio-output");
if (priv->mute_status_gpio_output)
priv->mute_status_gpio = devm_gpiod_get_optional(dev, "mute", GPIOD_OUT_LOW);
else
priv->mute_status_gpio = devm_gpiod_get_optional(dev, "mute", GPIOD_IN);
if (IS_ERR(priv->mute_status_gpio))
return PTR_ERR(priv->mute_status_gpio);
priv->power_gpio = devm_gpiod_get_optional(dev, "power", GPIOD_OUT_HIGH);
if (priv->power_gpio) {
snd_printk("power-gpio exists.\n");
if (IS_ERR(priv->power_gpio)) {
snd_printk("power-gpio error. PTR_ERR: %d\n", PTR_ERR(priv->power_gpio));
return PTR_ERR(priv->power_gpio);
}
} else {
snd_printk("No power GPIO.\n");
}
for_each_child_of_node(np, iter) {
struct snd_soc_dai_link *plink;
if (!of_device_is_available(iter))
continue;
snd_printd("iter %s available\n", iter->name);
/* dai-link */
plink = &priv->dai_link[priv->link_num];
/* Either cpu of_node or name */
cpu_np = of_parse_phandle(iter, "cpu-node", 0);
if (cpu_np)
plink->cpu_of_node = cpu_np;
else {
of_property_read_string(iter, "cpu-name",
&plink->cpu_name);
if (plink->cpu_name == NULL)
continue;
}
/* Either codec of_node or name */
codec_np = of_parse_phandle(iter, "codec-node", 0);
if (codec_np) {
plink->codec_of_node = codec_np;
//TODO: add slot parse
//snd_soc_of_parse_tdm_slot(codec_np, &);
} else {
of_property_read_string(iter, "codec-name",
&plink->codec_name);
if (plink->codec_name == NULL)
continue;
}
of_property_read_string(iter, "codec-dai-name",
&plink->codec_dai_name);
/* Either platform of_node or name */
platform_np = of_parse_phandle(iter, "platform-node", 0);
if (platform_np)
plink->platform_of_node = platform_np;
else {
of_property_read_string(iter, "platform-name",
&plink->platform_name);
if (plink->platform_name == NULL)
continue;
}
of_property_read_string(iter, "link-name",
&plink->name);
of_property_read_string(iter, "stream-name",
&plink->stream_name);
snprintf(priv->name, sizeof(priv->name), "%s-%s",
plink->cpu_name, plink->codec_name);
plink->ops = &asoc_link_ops;
snd_printd("dai %d: cpu dai %s codec dai %s\n",
priv->link_num,
plink->cpu_dai_name, plink->codec_dai_name);
//backup codea link# and codecb link
if (of_property_read_bool(iter, "codeca"))
fblinks[linka_num++].linkid = priv->link_num++;
else if (of_property_read_bool(iter, "codecb")) {
memcpy(&fblinks[linkb_num++].fblink, plink,
sizeof(struct snd_soc_dai_link));
memset(plink, 0, sizeof(struct snd_soc_dai_link));
} else
priv->link_num++;
}
priv->pdev = pdev;
priv->card.dev = &pdev->dev;
priv->card.name = priv->name;
priv->card.dai_link = priv->dai_link;
priv->card.num_links = priv->link_num;
priv->card.dapm_routes = berlin_asoc_route;
priv->card.num_dapm_routes = ARRAY_SIZE(berlin_asoc_route);
priv->card.dapm_widgets = berlin_asoc_widgets;
priv->card.num_dapm_widgets = ARRAY_SIZE(berlin_asoc_widgets);
//TODO: add audio routing in dts and enable this parse
snd_soc_card_set_drvdata(&priv->card, priv);
ret = devm_snd_soc_register_card(&pdev->dev, &priv->card);
if (ret) {
snd_printk("register card failed (%d)\n", ret);
if (linkb_num <= 0 || linka_num != linkb_num) {
snd_printk("no codecb or ab mismatch [%d %d]\n",
linka_num, linkb_num);
return ret;
}
//try codecb
for (i = 0; i < linkb_num; i ++) {
int linka_id = fblinks[i].linkid;
snd_printk("replace codec %s with %s, id %d\n",
priv->card.dai_link[linka_id].codec_dai_name,
fblinks[i].fblink.codec_dai_name, linka_id);
memset(&priv->card.dai_link[linka_id],
0, sizeof(struct snd_soc_dai_link));
memcpy(&priv->card.dai_link[linka_id],
&fblinks[i].fblink,
sizeof(struct snd_soc_dai_link));
}
snd_printk("try codecb\n");
ret = devm_snd_soc_register_card(&pdev->dev,
&priv->card);
if (ret) {
snd_printk("failed again, giveup (%d)\n", ret);
return ret;
}
}
priv->card.snd_card->private_data = priv;
//TODO: add ppm func back here
#if 0
kctl = snd_ctl_new1(&snd_berlin_rate_control, NULL);
if (kctl) {
ret = snd_ctl_add(priv->card.snd_card, kctl);
if (ret < 0)
snd_printk("error adding rate control: %d\n", ret);
} else
snd_printk("snd_ctl_new1 FAIL for rate control!\n");
#endif
if (priv->power_gpio) {
ret = snd_soc_add_card_controls(
&priv->card, &snd_berlin_amp_mode_control, 1);
if (ret < 0)
snd_printk("error adding amp mode control: %d\n", ret);
}
ret = sysfs_create_file(&priv->card.snd_card->card_dev.kobj,
&dev_attr_mic_mute_state.attr);
if (ret) {
snd_printk("error create mic_mute sysfs (%d)\n", ret);
return ret;
}
ret = sysfs_create_group(&priv->card.snd_card->card_dev.kobj,
&berlin_sysfs_group);
if (ret) {
snd_printk("error create xrun sysfs group (%d)\n", ret);
return ret;
}
snd_printd("%s: probe done\n", __func__);
return ret;
}
static int berlin_asoc_remove(struct platform_device *pdev)
{
struct snd_soc_card *card= platform_get_drvdata(pdev);
sysfs_remove_file(&card->snd_card->card_dev.kobj,
&dev_attr_mic_mute_state.attr);
return 0;
}
static const struct of_device_id berlin_asoc_dt_ids[] = {
{ .compatible = "syna,berlin-asoc", },
{ .compatible = "syna,as370-asoc", },
{}
};
MODULE_DEVICE_TABLE(of, berlin_asoc_dt_ids);
static struct platform_driver berlin_asoc_driver = {
.probe = berlin_asoc_probe,
.remove = berlin_asoc_remove,
.driver = {
.name = "syna-berlin-asoc",
.of_match_table = berlin_asoc_dt_ids,
},
};
module_platform_driver(berlin_asoc_driver);
MODULE_DESCRIPTION("Synaptics Berlin ASoC ALSA driver");
MODULE_ALIAS("platform:berlin-asoc");
MODULE_LICENSE("GPL v2");