blob: d25cb6994256e600a5599e6105065d141f54a261 [file] [edit]
// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
/*
* Copyright (c) 2019 Amlogic, Inc. All rights reserved.
*/
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/sysfs.h>
#include <linux/device.h>
#include <linux/slab.h>
#include <linux/types.h>
#include <linux/uaccess.h>
#include <linux/platform_device.h>
#include <linux/io.h>
#include <linux/kasan.h>
#include <linux/highmem.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/of_gpio.h>
#include <linux/of_address.h>
#include <linux/of_platform.h>
#include <linux/printk.h>
#include <linux/string.h>
#include <linux/arm-smccc.h>
#include <linux/psci.h>
#include <linux/workqueue.h>
#include <linux/mutex.h>
#include <uapi/linux/psci.h>
#include <linux/debugfs.h>
#include <linux/sched/signal.h>
#include <linux/dma-mapping.h>
#include <linux/of_reserved_mem.h>
#include <linux/vmalloc.h>
#include <linux/clk.h>
#include <asm/cacheflush.h>
#include <linux/amlogic/scpi_protocol.h>
#include <linux/delay.h>
#include <linux/fs.h>
#include "bridge_common.h"
#include "bridge_ringbuffer.h"
#include "bridge_pcm_hal.h"
#define DRIVER_NAME "audio-bridge"
#define PREFIX "aml-audio-line,"
#define RING_BUFFER_SIZE (1024 * 10)
static struct list_head bridge_devs = LIST_HEAD_INIT(bridge_devs);
static ssize_t bridge_enable_show(struct kobject *kobj,
struct kobj_attribute *attr, char *buf)
{
struct audio_pcm_bridge_t *bridge =
container_of(attr, struct audio_pcm_bridge_t, enable_attr);
if (!bridge)
return sprintf(buf, "%s err\n", attr->attr.name);
return sprintf(buf, "%s\n", bridge->enable ? "enable" : "disable");
}
static ssize_t bridge_enable_store(struct kobject *kobj,
struct kobj_attribute *attr,
const char *buf, size_t len)
{
int ret;
int check;
struct audio_pcm_bridge_t *bridge =
container_of(attr, struct audio_pcm_bridge_t, enable_attr);
if (!bridge)
return len;
ret = kstrtouint(buf, 10, &check);
if (ret < 0) {
pr_err("%s err!", __func__);
return len;
}
if (check) {
bridge->enable = true;
bridge->audio_cap->start(bridge->audio_cap);
bridge->audio_play->start(bridge->audio_play);
} else {
bridge->enable = false;
bridge->audio_cap->stop(bridge->audio_cap);
bridge->audio_play->stop(bridge->audio_play);
}
return len;
}
static ssize_t bridge_line_show(struct kobject *kobj,
struct kobj_attribute *attr, char *buf)
{
struct audio_pcm_bridge_t *bridge =
container_of(attr, struct audio_pcm_bridge_t, line_attr);
if (!bridge)
return sprintf(buf, "%s err\n", attr->attr.name);
return sprintf(buf, "%s-%s-%s<--->%s-%s-%s\n",
find_core_desc(bridge->audio_cap->coreid),
find_card_desc(bridge->audio_cap->cardid),
find_mode_desc(bridge->audio_cap->modeid),
find_core_desc(bridge->audio_play->coreid),
find_card_desc(bridge->audio_play->cardid),
find_mode_desc(bridge->audio_play->modeid));
}
static ssize_t bridge_ringbuf_show(struct kobject *kobj,
struct kobj_attribute *attr, char *buf)
{
struct audio_pcm_bridge_t *bridge =
container_of(attr, struct audio_pcm_bridge_t, ringbuf_attr);
if (!bridge)
return sprintf(buf, "%s err\n", attr->attr.name);
return sprintf(buf, "size:%d, use:%d free:%d\n", bridge_ring_buffer_len(bridge->rb),
bridge_ring_buffer_used_len(bridge->rb), bridge_ring_buffer_free_len(bridge->rb));
}
static ssize_t bridge_isolated_show(struct kobject *kobj,
struct kobj_attribute *attr, char *buf)
{
struct audio_pcm_bridge_t *bridge =
container_of(attr, struct audio_pcm_bridge_t, isolated_attr);
if (!bridge)
return sprintf(buf, "%s err\n", attr->attr.name);
return sprintf(buf, "%s\n", bridge->isolated_enable ? "enable" : "disable");
}
static ssize_t bridge_isolated_store(struct kobject *kobj,
struct kobj_attribute *attr,
const char *buf, size_t len)
{
int ret;
int check;
struct audio_pcm_bridge_t *bridge =
container_of(attr, struct audio_pcm_bridge_t, isolated_attr);
if (!bridge)
return len;
ret = kstrtouint(buf, 10, &check);
if (ret < 0) {
pr_err("%s err!", __func__);
return len;
}
if (check)
bridge->isolated_enable = true;
else
bridge->isolated_enable = false;
return len;
}
static struct audio_pcm_function_t *aml_bridge_parse_of(struct device *dev,
struct device_node *node, char *name)
{
int ret;
u32 core, card;
enum PCM_MODE mode;
struct audio_pcm_function_t *audio_p;
struct device_node *nb = of_get_child_by_name(node, name);
u32 enable = 0;
if (!nb)
goto err_parse0;
of_property_read_u32(nb, "core", &core);
of_property_read_u32(nb, "sound-card", &card);
if (!strcmp(name, "src"))
mode = PCM_CAPTURE;
else
mode = PCM_PLAYBACK;
audio_p = audio_pcm_alloc(dev, core, card, mode);
if (!audio_p)
goto err_parse0;
of_property_read_u32(nb, "channels", &audio_p->channels);
of_property_read_u32(nb, "sample-rate", &audio_p->rate);
of_property_read_u32(nb, "format", &audio_p->format);
of_property_read_u32(nb, "create-vir-card", &enable);
if (enable && audio_p->create_virtual_sound_card)
audio_p->create_virtual_sound_card(audio_p);
audio_p->enable_create_card = enable;
if (!audio_p->set_hw)
goto err_parse1;
ret = audio_p->set_hw(audio_p, audio_p->channels, audio_p->rate, audio_p->format);
if (ret < 0)
goto err_parse1;
pr_info("%s [core:%d card:%d] channels:%d rate:%d format:%d create-vir-card:%d\n",
name, core, card, audio_p->channels,
audio_p->rate, audio_p->format, enable);
return audio_p;
err_parse1:
if (enable && audio_p->destroy_virtual_sound_card)
audio_p->destroy_virtual_sound_card(audio_p);
audio_pcm_free(audio_p);
err_parse0:
return NULL;
}
static int aml_bridge_create_attribute(struct device *dev, struct audio_pcm_bridge_t *bridge)
{
int ret = 0;
char bridge_name[128];
snprintf(bridge_name, sizeof(bridge_name), "bridge%d", bridge->id);
bridge->kobj = kobject_create_and_add(bridge_name, &dev->kobj);
if (!bridge->kobj) {
ret = -ENOMEM;
goto err_attr0;
}
bridge->enable_attr.attr.name = __stringify(enable);
bridge->enable_attr.attr.mode = VERIFY_OCTAL_PERMISSIONS(0644);
bridge->enable_attr.show = bridge_enable_show;
bridge->enable_attr.store = bridge_enable_store;
ret = sysfs_create_file(bridge->kobj, &bridge->enable_attr.attr);
if (ret < 0) {
ret = -ENOMEM;
goto err_attr1;
}
bridge->line_attr.attr.name = __stringify(bridge_line);
bridge->line_attr.attr.mode = VERIFY_OCTAL_PERMISSIONS(0444);
bridge->line_attr.show = bridge_line_show;
ret = sysfs_create_file(bridge->kobj, &bridge->line_attr.attr);
if (ret < 0) {
ret = -ENOMEM;
goto err_attr2;
}
bridge->ringbuf_attr.attr.name = __stringify(bridge_ringbuf);
bridge->ringbuf_attr.attr.mode = VERIFY_OCTAL_PERMISSIONS(0444);
bridge->ringbuf_attr.show = bridge_ringbuf_show;
ret = sysfs_create_file(bridge->kobj, &bridge->ringbuf_attr.attr);
if (ret < 0) {
ret = -ENOMEM;
goto err_attr3;
}
bridge->isolated_attr.attr.name = __stringify(bridge_isolated);
bridge->isolated_attr.attr.mode = VERIFY_OCTAL_PERMISSIONS(0644);
bridge->isolated_attr.show = bridge_isolated_show;
bridge->isolated_attr.store = bridge_isolated_store;
ret = sysfs_create_file(bridge->kobj, &bridge->isolated_attr.attr);
if (ret < 0) {
ret = -ENOMEM;
goto err_attr4;
}
if (bridge->audio_cap->create_attr) {
ret = bridge->audio_cap->create_attr(bridge->audio_cap, bridge->kobj);
if (ret < 0) {
ret = -EINVAL;
goto err_attr5;
}
}
if (bridge->audio_play->create_attr) {
ret = bridge->audio_play->create_attr(bridge->audio_play, bridge->kobj);
if (ret < 0) {
ret = -EINVAL;
goto err_attr6;
}
}
return 0;
err_attr6:
if (bridge->audio_cap->destroy_attr)
bridge->audio_cap->destroy_attr(bridge->audio_cap, bridge->kobj);
err_attr5:
sysfs_remove_file(bridge->kobj, &bridge->isolated_attr.attr);
err_attr4:
sysfs_remove_file(bridge->kobj, &bridge->ringbuf_attr.attr);
err_attr3:
sysfs_remove_file(bridge->kobj, &bridge->line_attr.attr);
err_attr2:
sysfs_remove_file(bridge->kobj, &bridge->enable_attr.attr);
err_attr1:
kobject_put(bridge->kobj);
err_attr0:
return ret;
}
static void aml_bridge_destroy_attribute(struct audio_pcm_bridge_t *bridge)
{
if (bridge->audio_play->destroy_attr)
bridge->audio_play->destroy_attr(bridge->audio_play, bridge->kobj);
if (bridge->audio_cap->destroy_attr)
bridge->audio_cap->destroy_attr(bridge->audio_cap, bridge->kobj);
sysfs_remove_file(bridge->kobj, &bridge->isolated_attr.attr);
sysfs_remove_file(bridge->kobj, &bridge->ringbuf_attr.attr);
sysfs_remove_file(bridge->kobj, &bridge->line_attr.attr);
sysfs_remove_file(bridge->kobj, &bridge->enable_attr.attr);
kobject_put(bridge->kobj);
}
static void bridge_control_work(struct work_struct *data)
{
struct audio_pcm_bridge_t *bridge = container_of(data, struct audio_pcm_bridge_t,
ctr_work);
if (bridge->audio_cap && bridge->audio_cap->control)
bridge->audio_cap->control(bridge->audio_cap,
bridge->bridge_ctr_cmd, bridge->bridge_ctr_value);
if (bridge->audio_play && bridge->audio_play->control)
bridge->audio_play->control(bridge->audio_play,
bridge->bridge_ctr_cmd, bridge->bridge_ctr_value);
}
static int aml_bridge_init(struct device *dev, struct device_node *node, int idx)
{
int ret = 0;
u32 value;
struct audio_pcm_bridge_t *bridge;
bridge = vzalloc(sizeof(*bridge));
if (!bridge) {
ret = -ENOMEM;
goto err_init0;
}
bridge->id = idx;
bridge->rb = bridge_ring_buffer_init(RING_BUFFER_SIZE);
if (!bridge->rb) {
ret = -ENOMEM;
goto err_init1;
}
bridge->audio_cap = aml_bridge_parse_of(dev, node, "src");
if (!bridge->audio_cap) {
ret = -ENOMEM;
goto err_init2;
}
bridge->audio_play = aml_bridge_parse_of(dev, node, "dst");
if (!bridge->audio_play) {
ret = -ENOMEM;
goto err_init3;
}
if (!bridge->audio_cap->start || !bridge->audio_play->start) {
ret = -EINVAL;
goto err_init4;
}
bridge->audio_cap->rb = bridge->rb;
bridge->audio_play->rb = bridge->rb;
bridge->audio_cap->opposite = bridge->audio_play;
bridge->audio_play->opposite = bridge->audio_cap;
bridge->audio_cap->audio_bridge = bridge;
bridge->audio_play->audio_bridge = bridge;
ret = aml_bridge_create_attribute(dev, bridge);
if (ret < 0) {
ret = -EINVAL;
goto err_init4;
}
ret = of_property_read_u32(node, "isolated", &value);
if (ret >= 0 && value)
bridge->isolated_enable = true;
ret = of_property_read_u32(node, "def-volume", &value);
if (ret >= 0 && value >= VOLUME_MIN && value <= VOLUME_MAX)
bridge->def_volume = value;
else
bridge->def_volume = VOLUME_MAX;
if (bridge->audio_cap->control)
bridge->audio_cap->control(bridge->audio_cap, PCM_VOLUME, bridge->def_volume);
if (bridge->audio_play->control)
bridge->audio_play->control(bridge->audio_play, PCM_VOLUME, bridge->def_volume);
ret = of_property_read_u32(node, "auto-running", &value);
if (ret >= 0 && value) {
bridge->enable = true;
ret = bridge->audio_cap->start(bridge->audio_cap);
if (ret < 0) {
ret = -EINVAL;
goto err_init5;
}
ret = bridge->audio_play->start(bridge->audio_play);
if (ret < 0) {
ret = -EINVAL;
goto err_init6;
}
}
INIT_WORK(&bridge->ctr_work, bridge_control_work);
list_add_tail(&bridge->pcm_list, &bridge_devs);
return ret;
err_init6:
bridge->audio_cap->stop(bridge->audio_cap);
err_init5:
aml_bridge_destroy_attribute(bridge);
err_init4:
audio_pcm_free(bridge->audio_play);
err_init3:
audio_pcm_free(bridge->audio_cap);
err_init2:
bridge_ring_buffer_release(bridge->rb);
err_init1:
vfree(bridge);
err_init0:
return ret;
}
static int aml_bridge_free(void)
{
struct audio_pcm_bridge_t *cur, *n;
list_for_each_entry_safe(cur, n, &bridge_devs, pcm_list) {
cur->enable = false;
list_del(&cur->pcm_list);
if (cur->kobj)
aml_bridge_destroy_attribute(cur);
if (cur->audio_cap) {
if (cur->audio_cap->enable_create_card &&
cur->audio_cap->destroy_virtual_sound_card) {
cur->audio_cap->destroy_virtual_sound_card(cur->audio_cap);
}
cur->audio_cap->stop(cur->audio_cap);
audio_pcm_free(cur->audio_cap);
cur->audio_cap = NULL;
}
if (cur->audio_play) {
if (cur->audio_play->enable_create_card &&
cur->audio_play->destroy_virtual_sound_card) {
cur->audio_play->destroy_virtual_sound_card(cur->audio_play);
}
cur->audio_play->stop(cur->audio_play);
audio_pcm_free(cur->audio_play);
cur->audio_play = NULL;
}
if (cur->rb) {
bridge_ring_buffer_release(cur->rb);
cur->rb = NULL;
}
vfree(cur);
}
return 0;
}
#ifdef CONFIG_PM_SLEEP
static int audio_bridge_prepare(struct device *dev)
{
return 0;
}
static void audio_bridge_complete(struct device *dev)
{
}
static int audio_bridge_suspend(struct device *dev)
{
struct audio_pcm_bridge_t *cur, *n;
list_for_each_entry_safe(cur, n, &bridge_devs, pcm_list) {
if (cur->enable) {
if (cur->audio_cap)
cur->audio_cap->stop(cur->audio_cap);
if (cur->audio_play)
cur->audio_play->stop(cur->audio_play);
}
}
return 0;
}
static int audio_bridge_resume(struct device *dev)
{
struct audio_pcm_bridge_t *cur, *n;
list_for_each_entry_safe(cur, n, &bridge_devs, pcm_list) {
if (cur->enable) {
if (cur->audio_cap)
cur->audio_cap->start(cur->audio_cap);
if (cur->audio_play)
cur->audio_play->start(cur->audio_play);
}
}
return 0;
}
static const struct dev_pm_ops audio_bridge_dev_pm_ops = {
.prepare = audio_bridge_prepare,
.complete = audio_bridge_complete,
SET_SYSTEM_SLEEP_PM_OPS(audio_bridge_suspend, audio_bridge_resume)
};
#endif
static int audio_bridge_probe(struct platform_device *pdev)
{
int ret;
int i = 0;
struct device_node *node = pdev->dev.of_node;
struct device *dev = &pdev->dev;
struct device_node *dai_link;
dai_link = of_get_child_by_name(node, PREFIX "link");
if (dai_link) {
struct device_node *np = NULL;
for_each_child_of_node(node, np) {
dev_info(dev, "\tlink %d:\n", i);
ret = aml_bridge_init(dev, np, i);
if (ret < 0) {
of_node_put(np);
goto err_probe1;
}
i++;
}
}
dev_info(dev, "%s init done\n", __func__);
return 0;
err_probe1:
aml_bridge_free();
return ret;
}
static int audio_bridge_remove(struct platform_device *pdev)
{
aml_bridge_free();
return 0;
}
static const struct of_device_id audio_bridge_of_match[] = {
{ .compatible = "amlogic, audio-bridge" },
{},
};
static struct platform_driver audio_bridge_driver = {
.probe = audio_bridge_probe,
.remove = audio_bridge_remove,
.driver = {
.owner = THIS_MODULE,
.name = DRIVER_NAME,
.of_match_table = audio_bridge_of_match,
#ifdef CONFIG_PM_SLEEP
.pm = &audio_bridge_dev_pm_ops,
#endif
},
};
static int __init audio_bridge_init(void)
{
return platform_driver_register(&audio_bridge_driver);
}
static void __exit audio_bridge_exit(void)
{
platform_driver_unregister(&audio_bridge_driver);
}
module_init(audio_bridge_init);
module_exit(audio_bridge_exit);
MODULE_AUTHOR("ziheng li <ziheng.li@amlogic.com>");
MODULE_LICENSE("GPL");