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