tegrakernel/kernel/nvidia/sound/soc/tegra-virt-alt/tegra_virt_alt_ivc.c

266 lines
6.1 KiB
C
Raw Normal View History

2022-02-16 09:13:02 -06:00
/*
* IVC based library for AUDIO server
*
* Copyright (c) 2015-2019, NVIDIA CORPORATION. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope 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.
*/
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/resource.h>
#include <linux/of.h>
#include <linux/sched.h>
#include <linux/delay.h>
#include <linux/tegra-ivc.h>
#include <linux/spinlock.h>
#include <linux/hardirq.h>
#include <linux/interrupt.h>
#include <linux/iopoll.h>
#include "tegra_virt_alt_ivc.h"
#include "tegra_virt_alt_ivc_common.h"
static struct nvaudio_ivc_ctxt *saved_ivc_ctxt;
static void nvaudio_ivc_deinit(struct nvaudio_ivc_ctxt *ictxt);
static int nvaudio_ivc_init(struct nvaudio_ivc_ctxt *ictxt);
int nvaudio_ivc_send_retry(struct nvaudio_ivc_ctxt *ictxt,
struct nvaudio_ivc_msg *msg, int size)
{
int err = 0;
int dcnt = 50;
if (!ictxt || !ictxt->ivck || !msg || !size)
return -EINVAL;
err = nvaudio_ivc_send(ictxt, msg, size);
while (err < 0 && dcnt--) {
udelay(100);
err = nvaudio_ivc_send(ictxt, msg, size);
}
return (dcnt < 0) ? -ETIMEDOUT : err;
}
EXPORT_SYMBOL_GPL(nvaudio_ivc_send_retry);
int nvaudio_ivc_send(struct nvaudio_ivc_ctxt *ictxt,
struct nvaudio_ivc_msg *msg, int size)
{
int len = 0;
unsigned long flags = 0;
int err = 0;
int dcnt = 50;
if (!ictxt || !ictxt->ivck || !msg || !size)
return -EINVAL;
while (tegra_hv_ivc_channel_notified(ictxt->ivck) != 0) {
dev_err(ictxt->dev, "channel notified returns non zero\n");
dcnt--;
udelay(100);
if (!dcnt)
return -EIO;
}
spin_lock_irqsave(&ictxt->ivck_tx_lock, flags);
if (!tegra_hv_ivc_can_write(ictxt->ivck)) {
err = -EBUSY;
goto fail;
}
len = tegra_hv_ivc_write(ictxt->ivck, msg, size);
if (len != size) {
pr_err("%s: write Error\n", __func__);
err = -EIO;
goto fail;
}
err = len;
fail:
spin_unlock_irqrestore(&ictxt->ivck_tx_lock, flags);
return err;
}
EXPORT_SYMBOL_GPL(nvaudio_ivc_send);
int nvaudio_ivc_send_receive(struct nvaudio_ivc_ctxt *ictxt,
struct nvaudio_ivc_msg *rx_msg, int size)
{
int len = 0;
unsigned long flags = 0, flags1 = 0;
int err = 0;
int dcnt = 50;
int status = -1;
struct nvaudio_ivc_msg *msg = rx_msg;
if (!ictxt || !ictxt->ivck || !msg || !size)
return -EINVAL;
while (tegra_hv_ivc_channel_notified(ictxt->ivck) != 0) {
dev_err(ictxt->dev, "channel notified returns non zero\n");
dcnt--;
udelay(100);
if (!dcnt)
return -EIO;
}
spin_lock_irqsave(&ictxt->ivck_rx_lock, flags);
spin_lock_irqsave(&ictxt->ivck_tx_lock, flags1);
if (!tegra_hv_ivc_can_write(ictxt->ivck)) {
err = -EBUSY;
spin_unlock_irqrestore(&ictxt->ivck_tx_lock, flags1);
goto fail;
}
len = tegra_hv_ivc_write(ictxt->ivck, msg, size);
if (len != size) {
pr_err("%s: write Error\n", __func__);
err = -EIO;
spin_unlock_irqrestore(&ictxt->ivck_tx_lock, flags1);
goto fail;
}
spin_unlock_irqrestore(&ictxt->ivck_tx_lock, flags1);
err = readx_poll_timeout_atomic(tegra_hv_ivc_can_read, ictxt->ivck,
status, status, 10, NVAUDIO_IVC_WAIT_TIMEOUT);
if (err == -ETIMEDOUT) {
pr_err("%s: Waited too long for msg reply\n", __func__);
goto fail;
}
/* Message available */
memset(rx_msg, 0, sizeof(struct nvaudio_ivc_msg));
len = tegra_hv_ivc_read(ictxt->ivck,
rx_msg,
sizeof(struct nvaudio_ivc_msg));
if (len != sizeof(struct nvaudio_ivc_msg)) {
dev_err(ictxt->dev, "IVC read failure (msg size error)\n");
err = -1;
goto fail;
}
err = len;
fail:
spin_unlock_irqrestore(&ictxt->ivck_rx_lock, flags);
return err;
}
EXPORT_SYMBOL_GPL(nvaudio_ivc_send_receive);
/* Every communication with the server is identified
* with this ivc context.
* There can be one outstanding request to the server per
* ivc context.
*/
struct nvaudio_ivc_ctxt *nvaudio_ivc_alloc_ctxt(struct device *dev)
{
struct nvaudio_ivc_ctxt *ictxt = NULL;
if (saved_ivc_ctxt)
return saved_ivc_ctxt;
ictxt = devm_kzalloc(dev, sizeof(struct nvaudio_ivc_ctxt),
GFP_KERNEL);
if (ictxt == NULL) {
dev_err(dev, "unable to allocate mem for ivc context\n");
return NULL;
}
saved_ivc_ctxt = ictxt;
ictxt->dev = dev;
ictxt->timeout = 250; /* Not used in polling */
if (nvaudio_ivc_init(ictxt) != 0) {
dev_err(dev, "nvaudio_ivc_init failed\n");
goto fail;
}
spin_lock_init(&ictxt->ivck_rx_lock);
spin_lock_init(&ictxt->ivck_tx_lock);
tegra_hv_ivc_channel_reset(ictxt->ivck);
return ictxt;
fail:
nvaudio_ivc_free_ctxt(dev);
return NULL;
}
EXPORT_SYMBOL_GPL(nvaudio_ivc_alloc_ctxt);
/* hook to domain destroy */
void nvaudio_ivc_free_ctxt(struct device *dev)
{
if (saved_ivc_ctxt) {
nvaudio_ivc_deinit(saved_ivc_ctxt);
devm_kfree(dev, saved_ivc_ctxt);
saved_ivc_ctxt = NULL;
}
}
EXPORT_SYMBOL_GPL(nvaudio_ivc_free_ctxt);
static void nvaudio_ivc_deinit(struct nvaudio_ivc_ctxt *ictxt)
{
if (ictxt)
tegra_hv_ivc_unreserve(ictxt->ivck);
}
static int nvaudio_ivc_init(struct nvaudio_ivc_ctxt *ictxt)
{
int err, ivc_queue;
struct device_node *dn, *hv_dn;
struct device *dev = ictxt->dev;
struct tegra_hv_ivc_cookie *ivck = NULL;
dn = dev->of_node;
if (dn == NULL) {
dev_err(dev, "No OF data\n");
return -EINVAL;
}
hv_dn = of_parse_phandle(dn, "ivc_queue", 0);
if (hv_dn == NULL) {
dev_err(dev, "Failed to parse phandle of ivc prop\n");
return -EINVAL;
}
err = of_property_read_u32_index(dn, "ivc_queue", 1, &ivc_queue);
if (err != 0) {
dev_err(dev, "Failed to read IVC property ID\n");
of_node_put(hv_dn);
return -EINVAL;
}
ivck = tegra_hv_ivc_reserve(hv_dn, ivc_queue, NULL);
if (IS_ERR_OR_NULL(ivck)) {
dev_err(dev, "Failed to reserve ivc queue %d\n", ivc_queue);
if (ivck == ERR_PTR(-EPROBE_DEFER))
return -EPROBE_DEFER;
else
return -EINVAL;
}
of_node_put(hv_dn);
ictxt->ivc_queue = ivc_queue;
ictxt->ivck = ivck;
return 0;
}
MODULE_LICENSE("GPL");