162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0+
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Copyright 2019 NXP
462306a36Sopenharmony_ci *  Author: Daniel Baluta <daniel.baluta@nxp.com>
562306a36Sopenharmony_ci *
662306a36Sopenharmony_ci * Implementation of the DSP IPC interface (host side)
762306a36Sopenharmony_ci */
862306a36Sopenharmony_ci
962306a36Sopenharmony_ci#include <linux/firmware/imx/dsp.h>
1062306a36Sopenharmony_ci#include <linux/kernel.h>
1162306a36Sopenharmony_ci#include <linux/mailbox_client.h>
1262306a36Sopenharmony_ci#include <linux/module.h>
1362306a36Sopenharmony_ci#include <linux/platform_device.h>
1462306a36Sopenharmony_ci#include <linux/slab.h>
1562306a36Sopenharmony_ci
1662306a36Sopenharmony_ci/*
1762306a36Sopenharmony_ci * imx_dsp_ring_doorbell - triggers an interrupt on the other side (DSP)
1862306a36Sopenharmony_ci *
1962306a36Sopenharmony_ci * @dsp: DSP IPC handle
2062306a36Sopenharmony_ci * @chan_idx: index of the channel where to trigger the interrupt
2162306a36Sopenharmony_ci *
2262306a36Sopenharmony_ci * Returns non-negative value for success, negative value for error
2362306a36Sopenharmony_ci */
2462306a36Sopenharmony_ciint imx_dsp_ring_doorbell(struct imx_dsp_ipc *ipc, unsigned int idx)
2562306a36Sopenharmony_ci{
2662306a36Sopenharmony_ci	int ret;
2762306a36Sopenharmony_ci	struct imx_dsp_chan *dsp_chan;
2862306a36Sopenharmony_ci
2962306a36Sopenharmony_ci	if (idx >= DSP_MU_CHAN_NUM)
3062306a36Sopenharmony_ci		return -EINVAL;
3162306a36Sopenharmony_ci
3262306a36Sopenharmony_ci	dsp_chan = &ipc->chans[idx];
3362306a36Sopenharmony_ci	ret = mbox_send_message(dsp_chan->ch, NULL);
3462306a36Sopenharmony_ci	if (ret < 0)
3562306a36Sopenharmony_ci		return ret;
3662306a36Sopenharmony_ci
3762306a36Sopenharmony_ci	return 0;
3862306a36Sopenharmony_ci}
3962306a36Sopenharmony_ciEXPORT_SYMBOL(imx_dsp_ring_doorbell);
4062306a36Sopenharmony_ci
4162306a36Sopenharmony_ci/*
4262306a36Sopenharmony_ci * imx_dsp_handle_rx - rx callback used by imx mailbox
4362306a36Sopenharmony_ci *
4462306a36Sopenharmony_ci * @c: mbox client
4562306a36Sopenharmony_ci * @msg: message received
4662306a36Sopenharmony_ci *
4762306a36Sopenharmony_ci * Users of DSP IPC will need to privde handle_reply and handle_request
4862306a36Sopenharmony_ci * callbacks.
4962306a36Sopenharmony_ci */
5062306a36Sopenharmony_cistatic void imx_dsp_handle_rx(struct mbox_client *c, void *msg)
5162306a36Sopenharmony_ci{
5262306a36Sopenharmony_ci	struct imx_dsp_chan *chan = container_of(c, struct imx_dsp_chan, cl);
5362306a36Sopenharmony_ci
5462306a36Sopenharmony_ci	if (chan->idx == 0) {
5562306a36Sopenharmony_ci		chan->ipc->ops->handle_reply(chan->ipc);
5662306a36Sopenharmony_ci	} else {
5762306a36Sopenharmony_ci		chan->ipc->ops->handle_request(chan->ipc);
5862306a36Sopenharmony_ci		imx_dsp_ring_doorbell(chan->ipc, 1);
5962306a36Sopenharmony_ci	}
6062306a36Sopenharmony_ci}
6162306a36Sopenharmony_ci
6262306a36Sopenharmony_cistruct mbox_chan *imx_dsp_request_channel(struct imx_dsp_ipc *dsp_ipc, int idx)
6362306a36Sopenharmony_ci{
6462306a36Sopenharmony_ci	struct imx_dsp_chan *dsp_chan;
6562306a36Sopenharmony_ci
6662306a36Sopenharmony_ci	if (idx >= DSP_MU_CHAN_NUM)
6762306a36Sopenharmony_ci		return ERR_PTR(-EINVAL);
6862306a36Sopenharmony_ci
6962306a36Sopenharmony_ci	dsp_chan = &dsp_ipc->chans[idx];
7062306a36Sopenharmony_ci	dsp_chan->ch = mbox_request_channel_byname(&dsp_chan->cl, dsp_chan->name);
7162306a36Sopenharmony_ci	return dsp_chan->ch;
7262306a36Sopenharmony_ci}
7362306a36Sopenharmony_ciEXPORT_SYMBOL(imx_dsp_request_channel);
7462306a36Sopenharmony_ci
7562306a36Sopenharmony_civoid imx_dsp_free_channel(struct imx_dsp_ipc *dsp_ipc, int idx)
7662306a36Sopenharmony_ci{
7762306a36Sopenharmony_ci	struct imx_dsp_chan *dsp_chan;
7862306a36Sopenharmony_ci
7962306a36Sopenharmony_ci	if (idx >= DSP_MU_CHAN_NUM)
8062306a36Sopenharmony_ci		return;
8162306a36Sopenharmony_ci
8262306a36Sopenharmony_ci	dsp_chan = &dsp_ipc->chans[idx];
8362306a36Sopenharmony_ci	mbox_free_channel(dsp_chan->ch);
8462306a36Sopenharmony_ci}
8562306a36Sopenharmony_ciEXPORT_SYMBOL(imx_dsp_free_channel);
8662306a36Sopenharmony_ci
8762306a36Sopenharmony_cistatic int imx_dsp_setup_channels(struct imx_dsp_ipc *dsp_ipc)
8862306a36Sopenharmony_ci{
8962306a36Sopenharmony_ci	struct device *dev = dsp_ipc->dev;
9062306a36Sopenharmony_ci	struct imx_dsp_chan *dsp_chan;
9162306a36Sopenharmony_ci	struct mbox_client *cl;
9262306a36Sopenharmony_ci	char *chan_name;
9362306a36Sopenharmony_ci	int ret;
9462306a36Sopenharmony_ci	int i, j;
9562306a36Sopenharmony_ci
9662306a36Sopenharmony_ci	for (i = 0; i < DSP_MU_CHAN_NUM; i++) {
9762306a36Sopenharmony_ci		if (i < 2)
9862306a36Sopenharmony_ci			chan_name = kasprintf(GFP_KERNEL, "txdb%d", i);
9962306a36Sopenharmony_ci		else
10062306a36Sopenharmony_ci			chan_name = kasprintf(GFP_KERNEL, "rxdb%d", i - 2);
10162306a36Sopenharmony_ci
10262306a36Sopenharmony_ci		if (!chan_name)
10362306a36Sopenharmony_ci			return -ENOMEM;
10462306a36Sopenharmony_ci
10562306a36Sopenharmony_ci		dsp_chan = &dsp_ipc->chans[i];
10662306a36Sopenharmony_ci		dsp_chan->name = chan_name;
10762306a36Sopenharmony_ci		cl = &dsp_chan->cl;
10862306a36Sopenharmony_ci		cl->dev = dev;
10962306a36Sopenharmony_ci		cl->tx_block = false;
11062306a36Sopenharmony_ci		cl->knows_txdone = true;
11162306a36Sopenharmony_ci		cl->rx_callback = imx_dsp_handle_rx;
11262306a36Sopenharmony_ci
11362306a36Sopenharmony_ci		dsp_chan->ipc = dsp_ipc;
11462306a36Sopenharmony_ci		dsp_chan->idx = i % 2;
11562306a36Sopenharmony_ci		dsp_chan->ch = mbox_request_channel_byname(cl, chan_name);
11662306a36Sopenharmony_ci		if (IS_ERR(dsp_chan->ch)) {
11762306a36Sopenharmony_ci			ret = PTR_ERR(dsp_chan->ch);
11862306a36Sopenharmony_ci			if (ret != -EPROBE_DEFER)
11962306a36Sopenharmony_ci				dev_err(dev, "Failed to request mbox chan %s ret %d\n",
12062306a36Sopenharmony_ci					chan_name, ret);
12162306a36Sopenharmony_ci			kfree(dsp_chan->name);
12262306a36Sopenharmony_ci			goto out;
12362306a36Sopenharmony_ci		}
12462306a36Sopenharmony_ci
12562306a36Sopenharmony_ci		dev_dbg(dev, "request mbox chan %s\n", chan_name);
12662306a36Sopenharmony_ci	}
12762306a36Sopenharmony_ci
12862306a36Sopenharmony_ci	return 0;
12962306a36Sopenharmony_ciout:
13062306a36Sopenharmony_ci	for (j = 0; j < i; j++) {
13162306a36Sopenharmony_ci		dsp_chan = &dsp_ipc->chans[j];
13262306a36Sopenharmony_ci		mbox_free_channel(dsp_chan->ch);
13362306a36Sopenharmony_ci		kfree(dsp_chan->name);
13462306a36Sopenharmony_ci	}
13562306a36Sopenharmony_ci
13662306a36Sopenharmony_ci	return ret;
13762306a36Sopenharmony_ci}
13862306a36Sopenharmony_ci
13962306a36Sopenharmony_cistatic int imx_dsp_probe(struct platform_device *pdev)
14062306a36Sopenharmony_ci{
14162306a36Sopenharmony_ci	struct device *dev = &pdev->dev;
14262306a36Sopenharmony_ci	struct imx_dsp_ipc *dsp_ipc;
14362306a36Sopenharmony_ci	int ret;
14462306a36Sopenharmony_ci
14562306a36Sopenharmony_ci	device_set_of_node_from_dev(&pdev->dev, pdev->dev.parent);
14662306a36Sopenharmony_ci
14762306a36Sopenharmony_ci	dsp_ipc = devm_kzalloc(dev, sizeof(*dsp_ipc), GFP_KERNEL);
14862306a36Sopenharmony_ci	if (!dsp_ipc)
14962306a36Sopenharmony_ci		return -ENOMEM;
15062306a36Sopenharmony_ci
15162306a36Sopenharmony_ci	dsp_ipc->dev = dev;
15262306a36Sopenharmony_ci	dev_set_drvdata(dev, dsp_ipc);
15362306a36Sopenharmony_ci
15462306a36Sopenharmony_ci	ret = imx_dsp_setup_channels(dsp_ipc);
15562306a36Sopenharmony_ci	if (ret < 0)
15662306a36Sopenharmony_ci		return ret;
15762306a36Sopenharmony_ci
15862306a36Sopenharmony_ci	dev_info(dev, "NXP i.MX DSP IPC initialized\n");
15962306a36Sopenharmony_ci
16062306a36Sopenharmony_ci	return 0;
16162306a36Sopenharmony_ci}
16262306a36Sopenharmony_ci
16362306a36Sopenharmony_cistatic int imx_dsp_remove(struct platform_device *pdev)
16462306a36Sopenharmony_ci{
16562306a36Sopenharmony_ci	struct imx_dsp_chan *dsp_chan;
16662306a36Sopenharmony_ci	struct imx_dsp_ipc *dsp_ipc;
16762306a36Sopenharmony_ci	int i;
16862306a36Sopenharmony_ci
16962306a36Sopenharmony_ci	dsp_ipc = dev_get_drvdata(&pdev->dev);
17062306a36Sopenharmony_ci
17162306a36Sopenharmony_ci	for (i = 0; i < DSP_MU_CHAN_NUM; i++) {
17262306a36Sopenharmony_ci		dsp_chan = &dsp_ipc->chans[i];
17362306a36Sopenharmony_ci		mbox_free_channel(dsp_chan->ch);
17462306a36Sopenharmony_ci		kfree(dsp_chan->name);
17562306a36Sopenharmony_ci	}
17662306a36Sopenharmony_ci
17762306a36Sopenharmony_ci	return 0;
17862306a36Sopenharmony_ci}
17962306a36Sopenharmony_ci
18062306a36Sopenharmony_cistatic struct platform_driver imx_dsp_driver = {
18162306a36Sopenharmony_ci	.driver = {
18262306a36Sopenharmony_ci		.name = "imx-dsp",
18362306a36Sopenharmony_ci	},
18462306a36Sopenharmony_ci	.probe = imx_dsp_probe,
18562306a36Sopenharmony_ci	.remove = imx_dsp_remove,
18662306a36Sopenharmony_ci};
18762306a36Sopenharmony_cibuiltin_platform_driver(imx_dsp_driver);
18862306a36Sopenharmony_ci
18962306a36Sopenharmony_ciMODULE_AUTHOR("Daniel Baluta <daniel.baluta@nxp.com>");
19062306a36Sopenharmony_ciMODULE_DESCRIPTION("IMX DSP IPC protocol driver");
19162306a36Sopenharmony_ciMODULE_LICENSE("GPL v2");
192