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