| // 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"); |