1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | /* |
3 | * Copyright 2019 NXP |
4 | * Author: Daniel Baluta <daniel.baluta@nxp.com> |
5 | * |
6 | * Implementation of the DSP IPC interface (host side) |
7 | */ |
8 | |
9 | #include <linux/firmware/imx/dsp.h> |
10 | #include <linux/kernel.h> |
11 | #include <linux/mailbox_client.h> |
12 | #include <linux/module.h> |
13 | #include <linux/platform_device.h> |
14 | #include <linux/slab.h> |
15 | |
16 | /* |
17 | * imx_dsp_ring_doorbell - triggers an interrupt on the other side (DSP) |
18 | * |
19 | * @dsp: DSP IPC handle |
20 | * @chan_idx: index of the channel where to trigger the interrupt |
21 | * |
22 | * Returns non-negative value for success, negative value for error |
23 | */ |
24 | int imx_dsp_ring_doorbell(struct imx_dsp_ipc *ipc, unsigned int idx) |
25 | { |
26 | int ret; |
27 | struct imx_dsp_chan *dsp_chan; |
28 | |
29 | if (idx >= DSP_MU_CHAN_NUM) |
30 | return -EINVAL; |
31 | |
32 | dsp_chan = &ipc->chans[idx]; |
33 | ret = mbox_send_message(chan: dsp_chan->ch, NULL); |
34 | if (ret < 0) |
35 | return ret; |
36 | |
37 | return 0; |
38 | } |
39 | EXPORT_SYMBOL(imx_dsp_ring_doorbell); |
40 | |
41 | /* |
42 | * imx_dsp_handle_rx - rx callback used by imx mailbox |
43 | * |
44 | * @c: mbox client |
45 | * @msg: message received |
46 | * |
47 | * Users of DSP IPC will need to privde handle_reply and handle_request |
48 | * callbacks. |
49 | */ |
50 | static void imx_dsp_handle_rx(struct mbox_client *c, void *msg) |
51 | { |
52 | struct imx_dsp_chan *chan = container_of(c, struct imx_dsp_chan, cl); |
53 | |
54 | if (chan->idx == 0) { |
55 | chan->ipc->ops->handle_reply(chan->ipc); |
56 | } else { |
57 | chan->ipc->ops->handle_request(chan->ipc); |
58 | imx_dsp_ring_doorbell(chan->ipc, 1); |
59 | } |
60 | } |
61 | |
62 | struct mbox_chan *imx_dsp_request_channel(struct imx_dsp_ipc *dsp_ipc, int idx) |
63 | { |
64 | struct imx_dsp_chan *dsp_chan; |
65 | |
66 | if (idx >= DSP_MU_CHAN_NUM) |
67 | return ERR_PTR(error: -EINVAL); |
68 | |
69 | dsp_chan = &dsp_ipc->chans[idx]; |
70 | dsp_chan->ch = mbox_request_channel_byname(cl: &dsp_chan->cl, name: dsp_chan->name); |
71 | return dsp_chan->ch; |
72 | } |
73 | EXPORT_SYMBOL(imx_dsp_request_channel); |
74 | |
75 | void imx_dsp_free_channel(struct imx_dsp_ipc *dsp_ipc, int idx) |
76 | { |
77 | struct imx_dsp_chan *dsp_chan; |
78 | |
79 | if (idx >= DSP_MU_CHAN_NUM) |
80 | return; |
81 | |
82 | dsp_chan = &dsp_ipc->chans[idx]; |
83 | mbox_free_channel(chan: dsp_chan->ch); |
84 | } |
85 | EXPORT_SYMBOL(imx_dsp_free_channel); |
86 | |
87 | static int imx_dsp_setup_channels(struct imx_dsp_ipc *dsp_ipc) |
88 | { |
89 | struct device *dev = dsp_ipc->dev; |
90 | struct imx_dsp_chan *dsp_chan; |
91 | struct mbox_client *cl; |
92 | char *chan_name; |
93 | int ret; |
94 | int i, j; |
95 | |
96 | for (i = 0; i < DSP_MU_CHAN_NUM; i++) { |
97 | if (i < 2) |
98 | chan_name = kasprintf(GFP_KERNEL, fmt: "txdb%d" , i); |
99 | else |
100 | chan_name = kasprintf(GFP_KERNEL, fmt: "rxdb%d" , i - 2); |
101 | |
102 | if (!chan_name) |
103 | return -ENOMEM; |
104 | |
105 | dsp_chan = &dsp_ipc->chans[i]; |
106 | dsp_chan->name = chan_name; |
107 | cl = &dsp_chan->cl; |
108 | cl->dev = dev; |
109 | cl->tx_block = false; |
110 | cl->knows_txdone = true; |
111 | cl->rx_callback = imx_dsp_handle_rx; |
112 | |
113 | dsp_chan->ipc = dsp_ipc; |
114 | dsp_chan->idx = i % 2; |
115 | dsp_chan->ch = mbox_request_channel_byname(cl, name: chan_name); |
116 | if (IS_ERR(ptr: dsp_chan->ch)) { |
117 | ret = PTR_ERR(ptr: dsp_chan->ch); |
118 | if (ret != -EPROBE_DEFER) |
119 | dev_err(dev, "Failed to request mbox chan %s ret %d\n" , |
120 | chan_name, ret); |
121 | kfree(objp: dsp_chan->name); |
122 | goto out; |
123 | } |
124 | |
125 | dev_dbg(dev, "request mbox chan %s\n" , chan_name); |
126 | } |
127 | |
128 | return 0; |
129 | out: |
130 | for (j = 0; j < i; j++) { |
131 | dsp_chan = &dsp_ipc->chans[j]; |
132 | mbox_free_channel(chan: dsp_chan->ch); |
133 | kfree(objp: dsp_chan->name); |
134 | } |
135 | |
136 | return ret; |
137 | } |
138 | |
139 | static int imx_dsp_probe(struct platform_device *pdev) |
140 | { |
141 | struct device *dev = &pdev->dev; |
142 | struct imx_dsp_ipc *dsp_ipc; |
143 | int ret; |
144 | |
145 | device_set_of_node_from_dev(dev: &pdev->dev, dev2: pdev->dev.parent); |
146 | |
147 | dsp_ipc = devm_kzalloc(dev, size: sizeof(*dsp_ipc), GFP_KERNEL); |
148 | if (!dsp_ipc) |
149 | return -ENOMEM; |
150 | |
151 | dsp_ipc->dev = dev; |
152 | dev_set_drvdata(dev, data: dsp_ipc); |
153 | |
154 | ret = imx_dsp_setup_channels(dsp_ipc); |
155 | if (ret < 0) |
156 | return ret; |
157 | |
158 | dev_info(dev, "NXP i.MX DSP IPC initialized\n" ); |
159 | |
160 | return 0; |
161 | } |
162 | |
163 | static void imx_dsp_remove(struct platform_device *pdev) |
164 | { |
165 | struct imx_dsp_chan *dsp_chan; |
166 | struct imx_dsp_ipc *dsp_ipc; |
167 | int i; |
168 | |
169 | dsp_ipc = dev_get_drvdata(dev: &pdev->dev); |
170 | |
171 | for (i = 0; i < DSP_MU_CHAN_NUM; i++) { |
172 | dsp_chan = &dsp_ipc->chans[i]; |
173 | mbox_free_channel(chan: dsp_chan->ch); |
174 | kfree(objp: dsp_chan->name); |
175 | } |
176 | } |
177 | |
178 | static struct platform_driver imx_dsp_driver = { |
179 | .driver = { |
180 | .name = "imx-dsp" , |
181 | }, |
182 | .probe = imx_dsp_probe, |
183 | .remove_new = imx_dsp_remove, |
184 | }; |
185 | builtin_platform_driver(imx_dsp_driver); |
186 | |
187 | MODULE_AUTHOR("Daniel Baluta <daniel.baluta@nxp.com>" ); |
188 | MODULE_DESCRIPTION("IMX DSP IPC protocol driver" ); |
189 | MODULE_LICENSE("GPL v2" ); |
190 | |