aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/remoteproc/ste_modem_rproc.c
diff options
context:
space:
mode:
authorSjur Brændeland <sjur.brandeland@stericsson.com>2012-09-20 12:32:56 -0400
committerOhad Ben-Cohen <ohad@wizery.com>2012-09-22 09:35:13 -0400
commitec4d02d9180f407c41f8310a13b34e473c671fbb (patch)
tree2b8afeaa25f2948920cd3b50de83f55cefbd6614 /drivers/remoteproc/ste_modem_rproc.c
parent099a3f33c82b5153a4422eb92c648098b3f7c086 (diff)
remoteproc: Add STE modem driver
Add support for the STE modem shared memory driver. This driver hooks into the remoteproc framework in order to manage configuration and the virtio devices. This driver adds custom firmware handlers, because STE modem uses a custom firmware layout. Signed-off-by: Sjur Brændeland <sjur.brandeland@stericsson.com> cc: Linus Walleij <linus.walleij@linaro.org> cc: Alan Cox <alan@lxorguk.ukuu.org.uk> [ohad: validate mdev->ops, move setup() to probe/remove, trivial style changes] Signed-off-by: Ohad Ben-Cohen <ohad@wizery.com>
Diffstat (limited to 'drivers/remoteproc/ste_modem_rproc.c')
-rw-r--r--drivers/remoteproc/ste_modem_rproc.c322
1 files changed, 322 insertions, 0 deletions
diff --git a/drivers/remoteproc/ste_modem_rproc.c b/drivers/remoteproc/ste_modem_rproc.c
new file mode 100644
index 000000000000..a7743c069339
--- /dev/null
+++ b/drivers/remoteproc/ste_modem_rproc.c
@@ -0,0 +1,322 @@
1/*
2 * Copyright (C) ST-Ericsson AB 2012
3 * Author: Sjur Brændeland <sjur.brandeland@stericsson.com>
4 * License terms: GNU General Public License (GPL), version 2
5 */
6
7#include <linux/module.h>
8#include <linux/dma-mapping.h>
9#include <linux/remoteproc.h>
10#include <linux/ste_modem_shm.h>
11#include "remoteproc_internal.h"
12
13#define SPROC_FW_SIZE (50 * 4096)
14#define SPROC_MAX_TOC_ENTRIES 32
15#define SPROC_MAX_NOTIFY_ID 14
16#define SPROC_RESOURCE_NAME "rsc-table"
17#define SPROC_MODEM_NAME "ste-modem"
18#define SPROC_MODEM_FIRMWARE SPROC_MODEM_NAME "-fw.bin"
19
20#define sproc_dbg(sproc, fmt, ...) \
21 dev_dbg(&sproc->mdev->pdev.dev, fmt, ##__VA_ARGS__)
22#define sproc_err(sproc, fmt, ...) \
23 dev_err(&sproc->mdev->pdev.dev, fmt, ##__VA_ARGS__)
24
25/* STE-modem control structure */
26struct sproc {
27 struct rproc *rproc;
28 struct ste_modem_device *mdev;
29 int error;
30 void *fw_addr;
31 size_t fw_size;
32 dma_addr_t fw_dma_addr;
33};
34
35/* STE-Modem firmware entry */
36struct ste_toc_entry {
37 __le32 start;
38 __le32 size;
39 __le32 flags;
40 __le32 entry_point;
41 __le32 load_addr;
42 char name[12];
43};
44
45/*
46 * The Table Of Content is located at the start of the firmware image and
47 * at offset zero in the shared memory region. The resource table typically
48 * contains the initial boot image (boot strap) and other information elements
49 * such as remoteproc resource table. Each entry is identified by a unique
50 * name.
51 */
52struct ste_toc {
53 struct ste_toc_entry table[SPROC_MAX_TOC_ENTRIES];
54};
55
56/* Loads the firmware to shared memory. */
57static int sproc_load_segments(struct rproc *rproc, const struct firmware *fw)
58{
59 struct sproc *sproc = rproc->priv;
60
61 memcpy(sproc->fw_addr, fw->data, fw->size);
62
63 return 0;
64}
65
66/* Find the entry for resource table in the Table of Content */
67static struct ste_toc_entry *sproc_find_rsc_entry(const struct firmware *fw)
68{
69 int i;
70 struct ste_toc *toc;
71
72 if (!fw)
73 return NULL;
74
75 toc = (void *)fw->data;
76
77 /* Search the table for the resource table */
78 for (i = 0; i < SPROC_MAX_TOC_ENTRIES &&
79 toc->table[i].start != 0xffffffff; i++) {
80
81 if (!strncmp(toc->table[i].name, SPROC_RESOURCE_NAME,
82 sizeof(toc->table[i].name))) {
83 if (toc->table[i].start > fw->size)
84 return NULL;
85 return &toc->table[i];
86 }
87 }
88
89 return NULL;
90}
91
92/* Find the resource table inside the remote processor's firmware. */
93static struct resource_table *
94sproc_find_rsc_table(struct rproc *rproc, const struct firmware *fw,
95 int *tablesz)
96{
97 struct sproc *sproc = rproc->priv;
98 struct resource_table *table;
99 struct ste_toc_entry *entry;
100
101 entry = sproc_find_rsc_entry(fw);
102 if (!entry) {
103 sproc_err(sproc, "resource table not found in fw\n");
104 return NULL;
105 }
106
107 table = (void *)(fw->data + entry->start);
108
109 /* sanity check size and offset of resource table */
110 if (entry->start > SPROC_FW_SIZE ||
111 entry->size > SPROC_FW_SIZE ||
112 fw->size > SPROC_FW_SIZE ||
113 entry->start + entry->size > fw->size ||
114 sizeof(struct resource_table) > entry->size) {
115 sproc_err(sproc, "bad size of fw or resource table\n");
116 return NULL;
117 }
118
119 /* we don't support any version beyond the first */
120 if (table->ver != 1) {
121 sproc_err(sproc, "unsupported fw ver: %d\n", table->ver);
122 return NULL;
123 }
124
125 /* make sure reserved bytes are zeroes */
126 if (table->reserved[0] || table->reserved[1]) {
127 sproc_err(sproc, "non zero reserved bytes\n");
128 return NULL;
129 }
130
131 /* make sure the offsets array isn't truncated */
132 if (table->num > SPROC_MAX_TOC_ENTRIES ||
133 table->num * sizeof(table->offset[0]) +
134 sizeof(struct resource_table) > entry->size) {
135 sproc_err(sproc, "resource table incomplete\n");
136 return NULL;
137 }
138
139 /* If the fw size has grown, release the previous fw allocation */
140 if (SPROC_FW_SIZE < fw->size) {
141 sproc_err(sproc, "Insufficient space for fw (%d < %zd)\n",
142 SPROC_FW_SIZE, fw->size);
143 return NULL;
144 }
145
146 sproc->fw_size = fw->size;
147 *tablesz = entry->size;
148
149 return table;
150}
151
152/* STE modem firmware handler operations */
153const struct rproc_fw_ops sproc_fw_ops = {
154 .load = sproc_load_segments,
155 .find_rsc_table = sproc_find_rsc_table,
156};
157
158/* Kick the modem with specified notification id */
159static void sproc_kick(struct rproc *rproc, int vqid)
160{
161 struct sproc *sproc = rproc->priv;
162
163 sproc_dbg(sproc, "kick vqid:%d\n", vqid);
164
165 /*
166 * We need different notification IDs for RX and TX so add
167 * an offset on TX notification IDs.
168 */
169 sproc->mdev->ops.kick(sproc->mdev, vqid + SPROC_MAX_NOTIFY_ID);
170}
171
172/* Received a kick from a modem, kick the virtqueue */
173static void sproc_kick_callback(struct ste_modem_device *mdev, int vqid)
174{
175 struct sproc *sproc = mdev->drv_data;
176
177 if (rproc_vq_interrupt(sproc->rproc, vqid) == IRQ_NONE)
178 sproc_dbg(sproc, "no message was found in vqid %d\n", vqid);
179}
180
181struct ste_modem_dev_cb sproc_dev_cb = {
182 .kick = sproc_kick_callback,
183};
184
185/* Start the STE modem */
186static int sproc_start(struct rproc *rproc)
187{
188 struct sproc *sproc = rproc->priv;
189 int i, err;
190
191 sproc_dbg(sproc, "start ste-modem\n");
192
193 /* Sanity test the max_notifyid */
194 if (rproc->max_notifyid > SPROC_MAX_NOTIFY_ID) {
195 sproc_err(sproc, "Notification IDs too high:%d\n",
196 rproc->max_notifyid);
197 return -EINVAL;
198 }
199
200 /* Subscribe to notifications */
201 for (i = 0; i < rproc->max_notifyid; i++) {
202 err = sproc->mdev->ops.kick_subscribe(sproc->mdev, i);
203 if (err) {
204 sproc_err(sproc,
205 "subscription of kicks failed:%d\n", err);
206 return err;
207 }
208 }
209
210 /* Request modem start-up*/
211 return sproc->mdev->ops.power(sproc->mdev, true);
212}
213
214/* Stop the STE modem */
215static int sproc_stop(struct rproc *rproc)
216{
217 struct sproc *sproc = rproc->priv;
218 sproc_dbg(sproc, "stop ste-modem\n");
219
220 return sproc->mdev->ops.power(sproc->mdev, false);
221}
222
223static struct rproc_ops sproc_ops = {
224 .start = sproc_start,
225 .stop = sproc_stop,
226 .kick = sproc_kick,
227};
228
229/* STE modem device is unregistered */
230static int sproc_drv_remove(struct platform_device *pdev)
231{
232 struct ste_modem_device *mdev =
233 container_of(pdev, struct ste_modem_device, pdev);
234 struct sproc *sproc = mdev->drv_data;
235
236 sproc_dbg(sproc, "remove ste-modem\n");
237
238 /* Reset device callback functions */
239 sproc->mdev->ops.setup(sproc->mdev, NULL);
240
241 /* Unregister as remoteproc device */
242 rproc_del(sproc->rproc);
243 rproc_put(sproc->rproc);
244
245 mdev->drv_data = NULL;
246
247 return 0;
248}
249
250/* Handle probe of a modem device */
251static int sproc_probe(struct platform_device *pdev)
252{
253 struct ste_modem_device *mdev =
254 container_of(pdev, struct ste_modem_device, pdev);
255 struct sproc *sproc;
256 struct rproc *rproc;
257 int err;
258
259 dev_dbg(&mdev->pdev.dev, "probe ste-modem\n");
260
261 if (!mdev->ops.setup || !mdev->ops.kick || !mdev->ops.kick_subscribe ||
262 !mdev->ops.power) {
263 dev_err(&mdev->pdev.dev, "invalid mdev ops\n");
264 return -EINVAL;
265 }
266
267 rproc = rproc_alloc(&mdev->pdev.dev, mdev->pdev.name, &sproc_ops,
268 SPROC_MODEM_FIRMWARE, sizeof(*sproc));
269 if (!rproc)
270 return -ENOMEM;
271
272 sproc = rproc->priv;
273 sproc->mdev = mdev;
274 sproc->rproc = rproc;
275 mdev->drv_data = sproc;
276
277 /* Provide callback functions to modem device */
278 sproc->mdev->ops.setup(sproc->mdev, &sproc_dev_cb);
279
280 /* Set the STE-modem specific firmware handler */
281 rproc->fw_ops = &sproc_fw_ops;
282
283 /*
284 * STE-modem requires the firmware to be located
285 * at the start of the shared memory region. So we need to
286 * reserve space for firmware at the start.
287 */
288 sproc->fw_addr = dma_alloc_coherent(rproc->dev.parent, SPROC_FW_SIZE,
289 &sproc->fw_dma_addr,
290 GFP_KERNEL);
291 if (!sproc->fw_addr) {
292 sproc_err(sproc, "Cannot allocate memory for fw\n");
293 err = -ENOMEM;
294 goto free_rproc;
295 }
296
297 /* Register as a remoteproc device */
298 err = rproc_add(rproc);
299 if (err)
300 goto free_rproc;
301
302 return 0;
303
304free_rproc:
305 /* Reset device data upon error */
306 mdev->drv_data = NULL;
307 rproc_put(rproc);
308 return err;
309}
310
311static struct platform_driver sproc_driver = {
312 .driver = {
313 .name = SPROC_MODEM_NAME,
314 .owner = THIS_MODULE,
315 },
316 .probe = sproc_probe,
317 .remove = sproc_drv_remove,
318};
319
320module_platform_driver(sproc_driver);
321MODULE_LICENSE("GPL v2");
322MODULE_DESCRIPTION("STE Modem driver using the Remote Processor Framework");