| .. | .. |
|---|
| 1 | +// SPDX-License-Identifier: GPL-2.0-only |
|---|
| 1 | 2 | /* |
|---|
| 2 | | - * Copyright (c) 2016, NVIDIA CORPORATION. All rights reserved. |
|---|
| 3 | | - * |
|---|
| 4 | | - * This program is free software; you can redistribute it and/or modify it |
|---|
| 5 | | - * under the terms and conditions of the GNU General Public License, |
|---|
| 6 | | - * version 2, as published by the Free Software Foundation. |
|---|
| 7 | | - * |
|---|
| 8 | | - * This program is distributed in the hope it will be useful, but WITHOUT |
|---|
| 9 | | - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
|---|
| 10 | | - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for |
|---|
| 11 | | - * more details. |
|---|
| 3 | + * Copyright (c) 2016-2018, NVIDIA CORPORATION. All rights reserved. |
|---|
| 12 | 4 | */ |
|---|
| 13 | 5 | |
|---|
| 6 | +#include <linux/delay.h> |
|---|
| 14 | 7 | #include <linux/interrupt.h> |
|---|
| 15 | 8 | #include <linux/io.h> |
|---|
| 16 | 9 | #include <linux/mailbox_controller.h> |
|---|
| 17 | 10 | #include <linux/of.h> |
|---|
| 18 | 11 | #include <linux/of_device.h> |
|---|
| 19 | 12 | #include <linux/platform_device.h> |
|---|
| 13 | +#include <linux/pm.h> |
|---|
| 20 | 14 | #include <linux/slab.h> |
|---|
| 21 | 15 | |
|---|
| 16 | +#include <soc/tegra/fuse.h> |
|---|
| 17 | + |
|---|
| 22 | 18 | #include <dt-bindings/mailbox/tegra186-hsp.h> |
|---|
| 19 | + |
|---|
| 20 | +#include "mailbox.h" |
|---|
| 21 | + |
|---|
| 22 | +#define HSP_INT_IE(x) (0x100 + ((x) * 4)) |
|---|
| 23 | +#define HSP_INT_IV 0x300 |
|---|
| 24 | +#define HSP_INT_IR 0x304 |
|---|
| 25 | + |
|---|
| 26 | +#define HSP_INT_EMPTY_SHIFT 0 |
|---|
| 27 | +#define HSP_INT_EMPTY_MASK 0xff |
|---|
| 28 | +#define HSP_INT_FULL_SHIFT 8 |
|---|
| 29 | +#define HSP_INT_FULL_MASK 0xff |
|---|
| 23 | 30 | |
|---|
| 24 | 31 | #define HSP_INT_DIMENSIONING 0x380 |
|---|
| 25 | 32 | #define HSP_nSM_SHIFT 0 |
|---|
| .. | .. |
|---|
| 33 | 40 | #define HSP_DB_ENABLE 0x4 |
|---|
| 34 | 41 | #define HSP_DB_RAW 0x8 |
|---|
| 35 | 42 | #define HSP_DB_PENDING 0xc |
|---|
| 43 | + |
|---|
| 44 | +#define HSP_SM_SHRD_MBOX 0x0 |
|---|
| 45 | +#define HSP_SM_SHRD_MBOX_FULL BIT(31) |
|---|
| 46 | +#define HSP_SM_SHRD_MBOX_FULL_INT_IE 0x04 |
|---|
| 47 | +#define HSP_SM_SHRD_MBOX_EMPTY_INT_IE 0x08 |
|---|
| 36 | 48 | |
|---|
| 37 | 49 | #define HSP_DB_CCPLEX 1 |
|---|
| 38 | 50 | #define HSP_DB_BPMP 3 |
|---|
| .. | .. |
|---|
| 55 | 67 | unsigned int index; |
|---|
| 56 | 68 | }; |
|---|
| 57 | 69 | |
|---|
| 70 | +struct tegra_hsp_mailbox { |
|---|
| 71 | + struct tegra_hsp_channel channel; |
|---|
| 72 | + unsigned int index; |
|---|
| 73 | + bool producer; |
|---|
| 74 | +}; |
|---|
| 75 | + |
|---|
| 58 | 76 | struct tegra_hsp_db_map { |
|---|
| 59 | 77 | const char *name; |
|---|
| 60 | 78 | unsigned int master; |
|---|
| .. | .. |
|---|
| 63 | 81 | |
|---|
| 64 | 82 | struct tegra_hsp_soc { |
|---|
| 65 | 83 | const struct tegra_hsp_db_map *map; |
|---|
| 84 | + bool has_per_mb_ie; |
|---|
| 66 | 85 | }; |
|---|
| 67 | 86 | |
|---|
| 68 | 87 | struct tegra_hsp { |
|---|
| 88 | + struct device *dev; |
|---|
| 69 | 89 | const struct tegra_hsp_soc *soc; |
|---|
| 70 | | - struct mbox_controller mbox; |
|---|
| 90 | + struct mbox_controller mbox_db; |
|---|
| 91 | + struct mbox_controller mbox_sm; |
|---|
| 71 | 92 | void __iomem *regs; |
|---|
| 72 | | - unsigned int irq; |
|---|
| 93 | + unsigned int doorbell_irq; |
|---|
| 94 | + unsigned int *shared_irqs; |
|---|
| 95 | + unsigned int shared_irq; |
|---|
| 73 | 96 | unsigned int num_sm; |
|---|
| 74 | 97 | unsigned int num_as; |
|---|
| 75 | 98 | unsigned int num_ss; |
|---|
| .. | .. |
|---|
| 78 | 101 | spinlock_t lock; |
|---|
| 79 | 102 | |
|---|
| 80 | 103 | struct list_head doorbells; |
|---|
| 81 | | -}; |
|---|
| 104 | + struct tegra_hsp_mailbox *mailboxes; |
|---|
| 82 | 105 | |
|---|
| 83 | | -static inline struct tegra_hsp * |
|---|
| 84 | | -to_tegra_hsp(struct mbox_controller *mbox) |
|---|
| 85 | | -{ |
|---|
| 86 | | - return container_of(mbox, struct tegra_hsp, mbox); |
|---|
| 87 | | -} |
|---|
| 106 | + unsigned long mask; |
|---|
| 107 | +}; |
|---|
| 88 | 108 | |
|---|
| 89 | 109 | static inline u32 tegra_hsp_readl(struct tegra_hsp *hsp, unsigned int offset) |
|---|
| 90 | 110 | { |
|---|
| .. | .. |
|---|
| 158 | 178 | |
|---|
| 159 | 179 | spin_lock(&hsp->lock); |
|---|
| 160 | 180 | |
|---|
| 161 | | - for_each_set_bit(master, &value, hsp->mbox.num_chans) { |
|---|
| 181 | + for_each_set_bit(master, &value, hsp->mbox_db.num_chans) { |
|---|
| 162 | 182 | struct tegra_hsp_doorbell *db; |
|---|
| 163 | 183 | |
|---|
| 164 | 184 | db = __tegra_hsp_doorbell_get(hsp, master); |
|---|
| .. | .. |
|---|
| 182 | 202 | return IRQ_HANDLED; |
|---|
| 183 | 203 | } |
|---|
| 184 | 204 | |
|---|
| 205 | +static irqreturn_t tegra_hsp_shared_irq(int irq, void *data) |
|---|
| 206 | +{ |
|---|
| 207 | + struct tegra_hsp *hsp = data; |
|---|
| 208 | + unsigned long bit, mask; |
|---|
| 209 | + u32 status, value; |
|---|
| 210 | + void *msg; |
|---|
| 211 | + |
|---|
| 212 | + status = tegra_hsp_readl(hsp, HSP_INT_IR) & hsp->mask; |
|---|
| 213 | + |
|---|
| 214 | + /* process EMPTY interrupts first */ |
|---|
| 215 | + mask = (status >> HSP_INT_EMPTY_SHIFT) & HSP_INT_EMPTY_MASK; |
|---|
| 216 | + |
|---|
| 217 | + for_each_set_bit(bit, &mask, hsp->num_sm) { |
|---|
| 218 | + struct tegra_hsp_mailbox *mb = &hsp->mailboxes[bit]; |
|---|
| 219 | + |
|---|
| 220 | + if (mb->producer) { |
|---|
| 221 | + /* |
|---|
| 222 | + * Disable EMPTY interrupts until data is sent with |
|---|
| 223 | + * the next message. These interrupts are level- |
|---|
| 224 | + * triggered, so if we kept them enabled they would |
|---|
| 225 | + * constantly trigger until we next write data into |
|---|
| 226 | + * the message. |
|---|
| 227 | + */ |
|---|
| 228 | + spin_lock(&hsp->lock); |
|---|
| 229 | + |
|---|
| 230 | + hsp->mask &= ~BIT(HSP_INT_EMPTY_SHIFT + mb->index); |
|---|
| 231 | + tegra_hsp_writel(hsp, hsp->mask, |
|---|
| 232 | + HSP_INT_IE(hsp->shared_irq)); |
|---|
| 233 | + |
|---|
| 234 | + spin_unlock(&hsp->lock); |
|---|
| 235 | + |
|---|
| 236 | + mbox_chan_txdone(mb->channel.chan, 0); |
|---|
| 237 | + } |
|---|
| 238 | + } |
|---|
| 239 | + |
|---|
| 240 | + /* process FULL interrupts */ |
|---|
| 241 | + mask = (status >> HSP_INT_FULL_SHIFT) & HSP_INT_FULL_MASK; |
|---|
| 242 | + |
|---|
| 243 | + for_each_set_bit(bit, &mask, hsp->num_sm) { |
|---|
| 244 | + struct tegra_hsp_mailbox *mb = &hsp->mailboxes[bit]; |
|---|
| 245 | + |
|---|
| 246 | + if (!mb->producer) { |
|---|
| 247 | + value = tegra_hsp_channel_readl(&mb->channel, |
|---|
| 248 | + HSP_SM_SHRD_MBOX); |
|---|
| 249 | + value &= ~HSP_SM_SHRD_MBOX_FULL; |
|---|
| 250 | + msg = (void *)(unsigned long)value; |
|---|
| 251 | + mbox_chan_received_data(mb->channel.chan, msg); |
|---|
| 252 | + |
|---|
| 253 | + /* |
|---|
| 254 | + * Need to clear all bits here since some producers, |
|---|
| 255 | + * such as TCU, depend on fields in the register |
|---|
| 256 | + * getting cleared by the consumer. |
|---|
| 257 | + * |
|---|
| 258 | + * The mailbox API doesn't give the consumers a way |
|---|
| 259 | + * of doing that explicitly, so we have to make sure |
|---|
| 260 | + * we cover all possible cases. |
|---|
| 261 | + */ |
|---|
| 262 | + tegra_hsp_channel_writel(&mb->channel, 0x0, |
|---|
| 263 | + HSP_SM_SHRD_MBOX); |
|---|
| 264 | + } |
|---|
| 265 | + } |
|---|
| 266 | + |
|---|
| 267 | + return IRQ_HANDLED; |
|---|
| 268 | +} |
|---|
| 269 | + |
|---|
| 185 | 270 | static struct tegra_hsp_channel * |
|---|
| 186 | 271 | tegra_hsp_doorbell_create(struct tegra_hsp *hsp, const char *name, |
|---|
| 187 | 272 | unsigned int master, unsigned int index) |
|---|
| .. | .. |
|---|
| 190 | 275 | unsigned int offset; |
|---|
| 191 | 276 | unsigned long flags; |
|---|
| 192 | 277 | |
|---|
| 193 | | - db = kzalloc(sizeof(*db), GFP_KERNEL); |
|---|
| 278 | + db = devm_kzalloc(hsp->dev, sizeof(*db), GFP_KERNEL); |
|---|
| 194 | 279 | if (!db) |
|---|
| 195 | 280 | return ERR_PTR(-ENOMEM); |
|---|
| 196 | 281 | |
|---|
| 197 | | - offset = (1 + (hsp->num_sm / 2) + hsp->num_ss + hsp->num_as) << 16; |
|---|
| 282 | + offset = (1 + (hsp->num_sm / 2) + hsp->num_ss + hsp->num_as) * SZ_64K; |
|---|
| 198 | 283 | offset += index * 0x100; |
|---|
| 199 | 284 | |
|---|
| 200 | 285 | db->channel.regs = hsp->regs + offset; |
|---|
| 201 | 286 | db->channel.hsp = hsp; |
|---|
| 202 | 287 | |
|---|
| 203 | | - db->name = kstrdup_const(name, GFP_KERNEL); |
|---|
| 288 | + db->name = devm_kstrdup_const(hsp->dev, name, GFP_KERNEL); |
|---|
| 204 | 289 | db->master = master; |
|---|
| 205 | 290 | db->index = index; |
|---|
| 206 | 291 | |
|---|
| .. | .. |
|---|
| 209 | 294 | spin_unlock_irqrestore(&hsp->lock, flags); |
|---|
| 210 | 295 | |
|---|
| 211 | 296 | return &db->channel; |
|---|
| 212 | | -} |
|---|
| 213 | | - |
|---|
| 214 | | -static void __tegra_hsp_doorbell_destroy(struct tegra_hsp_doorbell *db) |
|---|
| 215 | | -{ |
|---|
| 216 | | - list_del(&db->list); |
|---|
| 217 | | - kfree_const(db->name); |
|---|
| 218 | | - kfree(db); |
|---|
| 219 | 297 | } |
|---|
| 220 | 298 | |
|---|
| 221 | 299 | static int tegra_hsp_doorbell_send_data(struct mbox_chan *chan, void *data) |
|---|
| .. | .. |
|---|
| 235 | 313 | unsigned long flags; |
|---|
| 236 | 314 | u32 value; |
|---|
| 237 | 315 | |
|---|
| 238 | | - if (db->master >= hsp->mbox.num_chans) { |
|---|
| 239 | | - dev_err(hsp->mbox.dev, |
|---|
| 316 | + if (db->master >= chan->mbox->num_chans) { |
|---|
| 317 | + dev_err(chan->mbox->dev, |
|---|
| 240 | 318 | "invalid master ID %u for HSP channel\n", |
|---|
| 241 | 319 | db->master); |
|---|
| 242 | 320 | return -EINVAL; |
|---|
| .. | .. |
|---|
| 246 | 324 | if (!ccplex) |
|---|
| 247 | 325 | return -ENODEV; |
|---|
| 248 | 326 | |
|---|
| 249 | | - if (!tegra_hsp_doorbell_can_ring(db)) |
|---|
| 327 | + /* |
|---|
| 328 | + * On simulation platforms the BPMP hasn't had a chance yet to mark |
|---|
| 329 | + * the doorbell as ringable by the CCPLEX, so we want to skip extra |
|---|
| 330 | + * checks here. |
|---|
| 331 | + */ |
|---|
| 332 | + if (tegra_is_silicon() && !tegra_hsp_doorbell_can_ring(db)) |
|---|
| 250 | 333 | return -ENODEV; |
|---|
| 251 | 334 | |
|---|
| 252 | 335 | spin_lock_irqsave(&hsp->lock, flags); |
|---|
| .. | .. |
|---|
| 281 | 364 | spin_unlock_irqrestore(&hsp->lock, flags); |
|---|
| 282 | 365 | } |
|---|
| 283 | 366 | |
|---|
| 284 | | -static const struct mbox_chan_ops tegra_hsp_doorbell_ops = { |
|---|
| 367 | +static const struct mbox_chan_ops tegra_hsp_db_ops = { |
|---|
| 285 | 368 | .send_data = tegra_hsp_doorbell_send_data, |
|---|
| 286 | 369 | .startup = tegra_hsp_doorbell_startup, |
|---|
| 287 | 370 | .shutdown = tegra_hsp_doorbell_shutdown, |
|---|
| 288 | 371 | }; |
|---|
| 289 | 372 | |
|---|
| 290 | | -static struct mbox_chan *of_tegra_hsp_xlate(struct mbox_controller *mbox, |
|---|
| 373 | +static int tegra_hsp_mailbox_send_data(struct mbox_chan *chan, void *data) |
|---|
| 374 | +{ |
|---|
| 375 | + struct tegra_hsp_mailbox *mb = chan->con_priv; |
|---|
| 376 | + struct tegra_hsp *hsp = mb->channel.hsp; |
|---|
| 377 | + unsigned long flags; |
|---|
| 378 | + u32 value; |
|---|
| 379 | + |
|---|
| 380 | + if (WARN_ON(!mb->producer)) |
|---|
| 381 | + return -EPERM; |
|---|
| 382 | + |
|---|
| 383 | + /* copy data and mark mailbox full */ |
|---|
| 384 | + value = (u32)(unsigned long)data; |
|---|
| 385 | + value |= HSP_SM_SHRD_MBOX_FULL; |
|---|
| 386 | + |
|---|
| 387 | + tegra_hsp_channel_writel(&mb->channel, value, HSP_SM_SHRD_MBOX); |
|---|
| 388 | + |
|---|
| 389 | + /* enable EMPTY interrupt for the shared mailbox */ |
|---|
| 390 | + spin_lock_irqsave(&hsp->lock, flags); |
|---|
| 391 | + |
|---|
| 392 | + hsp->mask |= BIT(HSP_INT_EMPTY_SHIFT + mb->index); |
|---|
| 393 | + tegra_hsp_writel(hsp, hsp->mask, HSP_INT_IE(hsp->shared_irq)); |
|---|
| 394 | + |
|---|
| 395 | + spin_unlock_irqrestore(&hsp->lock, flags); |
|---|
| 396 | + |
|---|
| 397 | + return 0; |
|---|
| 398 | +} |
|---|
| 399 | + |
|---|
| 400 | +static int tegra_hsp_mailbox_flush(struct mbox_chan *chan, |
|---|
| 401 | + unsigned long timeout) |
|---|
| 402 | +{ |
|---|
| 403 | + struct tegra_hsp_mailbox *mb = chan->con_priv; |
|---|
| 404 | + struct tegra_hsp_channel *ch = &mb->channel; |
|---|
| 405 | + u32 value; |
|---|
| 406 | + |
|---|
| 407 | + timeout = jiffies + msecs_to_jiffies(timeout); |
|---|
| 408 | + |
|---|
| 409 | + while (time_before(jiffies, timeout)) { |
|---|
| 410 | + value = tegra_hsp_channel_readl(ch, HSP_SM_SHRD_MBOX); |
|---|
| 411 | + if ((value & HSP_SM_SHRD_MBOX_FULL) == 0) { |
|---|
| 412 | + mbox_chan_txdone(chan, 0); |
|---|
| 413 | + |
|---|
| 414 | + /* Wait until channel is empty */ |
|---|
| 415 | + if (chan->active_req != NULL) |
|---|
| 416 | + continue; |
|---|
| 417 | + |
|---|
| 418 | + return 0; |
|---|
| 419 | + } |
|---|
| 420 | + |
|---|
| 421 | + udelay(1); |
|---|
| 422 | + } |
|---|
| 423 | + |
|---|
| 424 | + return -ETIME; |
|---|
| 425 | +} |
|---|
| 426 | + |
|---|
| 427 | +static int tegra_hsp_mailbox_startup(struct mbox_chan *chan) |
|---|
| 428 | +{ |
|---|
| 429 | + struct tegra_hsp_mailbox *mb = chan->con_priv; |
|---|
| 430 | + struct tegra_hsp_channel *ch = &mb->channel; |
|---|
| 431 | + struct tegra_hsp *hsp = mb->channel.hsp; |
|---|
| 432 | + unsigned long flags; |
|---|
| 433 | + |
|---|
| 434 | + chan->txdone_method = TXDONE_BY_IRQ; |
|---|
| 435 | + |
|---|
| 436 | + /* |
|---|
| 437 | + * Shared mailboxes start out as consumers by default. FULL and EMPTY |
|---|
| 438 | + * interrupts are coalesced at the same shared interrupt. |
|---|
| 439 | + * |
|---|
| 440 | + * Keep EMPTY interrupts disabled at startup and only enable them when |
|---|
| 441 | + * the mailbox is actually full. This is required because the FULL and |
|---|
| 442 | + * EMPTY interrupts are level-triggered, so keeping EMPTY interrupts |
|---|
| 443 | + * enabled all the time would cause an interrupt storm while mailboxes |
|---|
| 444 | + * are idle. |
|---|
| 445 | + */ |
|---|
| 446 | + |
|---|
| 447 | + spin_lock_irqsave(&hsp->lock, flags); |
|---|
| 448 | + |
|---|
| 449 | + if (mb->producer) |
|---|
| 450 | + hsp->mask &= ~BIT(HSP_INT_EMPTY_SHIFT + mb->index); |
|---|
| 451 | + else |
|---|
| 452 | + hsp->mask |= BIT(HSP_INT_FULL_SHIFT + mb->index); |
|---|
| 453 | + |
|---|
| 454 | + tegra_hsp_writel(hsp, hsp->mask, HSP_INT_IE(hsp->shared_irq)); |
|---|
| 455 | + |
|---|
| 456 | + spin_unlock_irqrestore(&hsp->lock, flags); |
|---|
| 457 | + |
|---|
| 458 | + if (hsp->soc->has_per_mb_ie) { |
|---|
| 459 | + if (mb->producer) |
|---|
| 460 | + tegra_hsp_channel_writel(ch, 0x0, |
|---|
| 461 | + HSP_SM_SHRD_MBOX_EMPTY_INT_IE); |
|---|
| 462 | + else |
|---|
| 463 | + tegra_hsp_channel_writel(ch, 0x1, |
|---|
| 464 | + HSP_SM_SHRD_MBOX_FULL_INT_IE); |
|---|
| 465 | + } |
|---|
| 466 | + |
|---|
| 467 | + return 0; |
|---|
| 468 | +} |
|---|
| 469 | + |
|---|
| 470 | +static void tegra_hsp_mailbox_shutdown(struct mbox_chan *chan) |
|---|
| 471 | +{ |
|---|
| 472 | + struct tegra_hsp_mailbox *mb = chan->con_priv; |
|---|
| 473 | + struct tegra_hsp_channel *ch = &mb->channel; |
|---|
| 474 | + struct tegra_hsp *hsp = mb->channel.hsp; |
|---|
| 475 | + unsigned long flags; |
|---|
| 476 | + |
|---|
| 477 | + if (hsp->soc->has_per_mb_ie) { |
|---|
| 478 | + if (mb->producer) |
|---|
| 479 | + tegra_hsp_channel_writel(ch, 0x0, |
|---|
| 480 | + HSP_SM_SHRD_MBOX_EMPTY_INT_IE); |
|---|
| 481 | + else |
|---|
| 482 | + tegra_hsp_channel_writel(ch, 0x0, |
|---|
| 483 | + HSP_SM_SHRD_MBOX_FULL_INT_IE); |
|---|
| 484 | + } |
|---|
| 485 | + |
|---|
| 486 | + spin_lock_irqsave(&hsp->lock, flags); |
|---|
| 487 | + |
|---|
| 488 | + if (mb->producer) |
|---|
| 489 | + hsp->mask &= ~BIT(HSP_INT_EMPTY_SHIFT + mb->index); |
|---|
| 490 | + else |
|---|
| 491 | + hsp->mask &= ~BIT(HSP_INT_FULL_SHIFT + mb->index); |
|---|
| 492 | + |
|---|
| 493 | + tegra_hsp_writel(hsp, hsp->mask, HSP_INT_IE(hsp->shared_irq)); |
|---|
| 494 | + |
|---|
| 495 | + spin_unlock_irqrestore(&hsp->lock, flags); |
|---|
| 496 | +} |
|---|
| 497 | + |
|---|
| 498 | +static const struct mbox_chan_ops tegra_hsp_sm_ops = { |
|---|
| 499 | + .send_data = tegra_hsp_mailbox_send_data, |
|---|
| 500 | + .flush = tegra_hsp_mailbox_flush, |
|---|
| 501 | + .startup = tegra_hsp_mailbox_startup, |
|---|
| 502 | + .shutdown = tegra_hsp_mailbox_shutdown, |
|---|
| 503 | +}; |
|---|
| 504 | + |
|---|
| 505 | +static struct mbox_chan *tegra_hsp_db_xlate(struct mbox_controller *mbox, |
|---|
| 291 | 506 | const struct of_phandle_args *args) |
|---|
| 292 | 507 | { |
|---|
| 508 | + struct tegra_hsp *hsp = container_of(mbox, struct tegra_hsp, mbox_db); |
|---|
| 509 | + unsigned int type = args->args[0], master = args->args[1]; |
|---|
| 293 | 510 | struct tegra_hsp_channel *channel = ERR_PTR(-ENODEV); |
|---|
| 294 | | - struct tegra_hsp *hsp = to_tegra_hsp(mbox); |
|---|
| 295 | | - unsigned int type = args->args[0]; |
|---|
| 296 | | - unsigned int master = args->args[1]; |
|---|
| 297 | 511 | struct tegra_hsp_doorbell *db; |
|---|
| 298 | 512 | struct mbox_chan *chan; |
|---|
| 299 | 513 | unsigned long flags; |
|---|
| 300 | 514 | unsigned int i; |
|---|
| 301 | 515 | |
|---|
| 302 | | - switch (type) { |
|---|
| 303 | | - case TEGRA_HSP_MBOX_TYPE_DB: |
|---|
| 304 | | - db = tegra_hsp_doorbell_get(hsp, master); |
|---|
| 305 | | - if (db) |
|---|
| 306 | | - channel = &db->channel; |
|---|
| 516 | + if (type != TEGRA_HSP_MBOX_TYPE_DB || !hsp->doorbell_irq) |
|---|
| 517 | + return ERR_PTR(-ENODEV); |
|---|
| 307 | 518 | |
|---|
| 308 | | - break; |
|---|
| 309 | | - |
|---|
| 310 | | - default: |
|---|
| 311 | | - break; |
|---|
| 312 | | - } |
|---|
| 519 | + db = tegra_hsp_doorbell_get(hsp, master); |
|---|
| 520 | + if (db) |
|---|
| 521 | + channel = &db->channel; |
|---|
| 313 | 522 | |
|---|
| 314 | 523 | if (IS_ERR(channel)) |
|---|
| 315 | 524 | return ERR_CAST(channel); |
|---|
| 316 | 525 | |
|---|
| 317 | 526 | spin_lock_irqsave(&hsp->lock, flags); |
|---|
| 318 | 527 | |
|---|
| 319 | | - for (i = 0; i < hsp->mbox.num_chans; i++) { |
|---|
| 320 | | - chan = &hsp->mbox.chans[i]; |
|---|
| 528 | + for (i = 0; i < mbox->num_chans; i++) { |
|---|
| 529 | + chan = &mbox->chans[i]; |
|---|
| 321 | 530 | if (!chan->con_priv) { |
|---|
| 322 | | - chan->con_priv = channel; |
|---|
| 323 | 531 | channel->chan = chan; |
|---|
| 532 | + chan->con_priv = db; |
|---|
| 324 | 533 | break; |
|---|
| 325 | 534 | } |
|---|
| 326 | 535 | |
|---|
| .. | .. |
|---|
| 332 | 541 | return chan ?: ERR_PTR(-EBUSY); |
|---|
| 333 | 542 | } |
|---|
| 334 | 543 | |
|---|
| 335 | | -static void tegra_hsp_remove_doorbells(struct tegra_hsp *hsp) |
|---|
| 544 | +static struct mbox_chan *tegra_hsp_sm_xlate(struct mbox_controller *mbox, |
|---|
| 545 | + const struct of_phandle_args *args) |
|---|
| 336 | 546 | { |
|---|
| 337 | | - struct tegra_hsp_doorbell *db, *tmp; |
|---|
| 338 | | - unsigned long flags; |
|---|
| 547 | + struct tegra_hsp *hsp = container_of(mbox, struct tegra_hsp, mbox_sm); |
|---|
| 548 | + unsigned int type = args->args[0], index; |
|---|
| 549 | + struct tegra_hsp_mailbox *mb; |
|---|
| 339 | 550 | |
|---|
| 340 | | - spin_lock_irqsave(&hsp->lock, flags); |
|---|
| 551 | + index = args->args[1] & TEGRA_HSP_SM_MASK; |
|---|
| 341 | 552 | |
|---|
| 342 | | - list_for_each_entry_safe(db, tmp, &hsp->doorbells, list) |
|---|
| 343 | | - __tegra_hsp_doorbell_destroy(db); |
|---|
| 553 | + if (type != TEGRA_HSP_MBOX_TYPE_SM || !hsp->shared_irqs || |
|---|
| 554 | + index >= hsp->num_sm) |
|---|
| 555 | + return ERR_PTR(-ENODEV); |
|---|
| 344 | 556 | |
|---|
| 345 | | - spin_unlock_irqrestore(&hsp->lock, flags); |
|---|
| 557 | + mb = &hsp->mailboxes[index]; |
|---|
| 558 | + |
|---|
| 559 | + if ((args->args[1] & TEGRA_HSP_SM_FLAG_TX) == 0) |
|---|
| 560 | + mb->producer = false; |
|---|
| 561 | + else |
|---|
| 562 | + mb->producer = true; |
|---|
| 563 | + |
|---|
| 564 | + return mb->channel.chan; |
|---|
| 346 | 565 | } |
|---|
| 347 | 566 | |
|---|
| 348 | 567 | static int tegra_hsp_add_doorbells(struct tegra_hsp *hsp) |
|---|
| .. | .. |
|---|
| 353 | 572 | while (map->name) { |
|---|
| 354 | 573 | channel = tegra_hsp_doorbell_create(hsp, map->name, |
|---|
| 355 | 574 | map->master, map->index); |
|---|
| 356 | | - if (IS_ERR(channel)) { |
|---|
| 357 | | - tegra_hsp_remove_doorbells(hsp); |
|---|
| 575 | + if (IS_ERR(channel)) |
|---|
| 358 | 576 | return PTR_ERR(channel); |
|---|
| 359 | | - } |
|---|
| 360 | 577 | |
|---|
| 361 | 578 | map++; |
|---|
| 579 | + } |
|---|
| 580 | + |
|---|
| 581 | + return 0; |
|---|
| 582 | +} |
|---|
| 583 | + |
|---|
| 584 | +static int tegra_hsp_add_mailboxes(struct tegra_hsp *hsp, struct device *dev) |
|---|
| 585 | +{ |
|---|
| 586 | + int i; |
|---|
| 587 | + |
|---|
| 588 | + hsp->mailboxes = devm_kcalloc(dev, hsp->num_sm, sizeof(*hsp->mailboxes), |
|---|
| 589 | + GFP_KERNEL); |
|---|
| 590 | + if (!hsp->mailboxes) |
|---|
| 591 | + return -ENOMEM; |
|---|
| 592 | + |
|---|
| 593 | + for (i = 0; i < hsp->num_sm; i++) { |
|---|
| 594 | + struct tegra_hsp_mailbox *mb = &hsp->mailboxes[i]; |
|---|
| 595 | + |
|---|
| 596 | + mb->index = i; |
|---|
| 597 | + |
|---|
| 598 | + mb->channel.hsp = hsp; |
|---|
| 599 | + mb->channel.regs = hsp->regs + SZ_64K + i * SZ_32K; |
|---|
| 600 | + mb->channel.chan = &hsp->mbox_sm.chans[i]; |
|---|
| 601 | + mb->channel.chan->con_priv = mb; |
|---|
| 602 | + } |
|---|
| 603 | + |
|---|
| 604 | + return 0; |
|---|
| 605 | +} |
|---|
| 606 | + |
|---|
| 607 | +static int tegra_hsp_request_shared_irq(struct tegra_hsp *hsp) |
|---|
| 608 | +{ |
|---|
| 609 | + unsigned int i, irq = 0; |
|---|
| 610 | + int err; |
|---|
| 611 | + |
|---|
| 612 | + for (i = 0; i < hsp->num_si; i++) { |
|---|
| 613 | + irq = hsp->shared_irqs[i]; |
|---|
| 614 | + if (irq <= 0) |
|---|
| 615 | + continue; |
|---|
| 616 | + |
|---|
| 617 | + err = devm_request_irq(hsp->dev, irq, tegra_hsp_shared_irq, 0, |
|---|
| 618 | + dev_name(hsp->dev), hsp); |
|---|
| 619 | + if (err < 0) { |
|---|
| 620 | + dev_err(hsp->dev, "failed to request interrupt: %d\n", |
|---|
| 621 | + err); |
|---|
| 622 | + continue; |
|---|
| 623 | + } |
|---|
| 624 | + |
|---|
| 625 | + hsp->shared_irq = i; |
|---|
| 626 | + |
|---|
| 627 | + /* disable all interrupts */ |
|---|
| 628 | + tegra_hsp_writel(hsp, 0, HSP_INT_IE(hsp->shared_irq)); |
|---|
| 629 | + |
|---|
| 630 | + dev_dbg(hsp->dev, "interrupt requested: %u\n", irq); |
|---|
| 631 | + |
|---|
| 632 | + break; |
|---|
| 633 | + } |
|---|
| 634 | + |
|---|
| 635 | + if (i == hsp->num_si) { |
|---|
| 636 | + dev_err(hsp->dev, "failed to find available interrupt\n"); |
|---|
| 637 | + return -ENOENT; |
|---|
| 362 | 638 | } |
|---|
| 363 | 639 | |
|---|
| 364 | 640 | return 0; |
|---|
| .. | .. |
|---|
| 368 | 644 | { |
|---|
| 369 | 645 | struct tegra_hsp *hsp; |
|---|
| 370 | 646 | struct resource *res; |
|---|
| 647 | + unsigned int i; |
|---|
| 371 | 648 | u32 value; |
|---|
| 372 | 649 | int err; |
|---|
| 373 | 650 | |
|---|
| .. | .. |
|---|
| 375 | 652 | if (!hsp) |
|---|
| 376 | 653 | return -ENOMEM; |
|---|
| 377 | 654 | |
|---|
| 655 | + hsp->dev = &pdev->dev; |
|---|
| 378 | 656 | hsp->soc = of_device_get_match_data(&pdev->dev); |
|---|
| 379 | 657 | INIT_LIST_HEAD(&hsp->doorbells); |
|---|
| 380 | 658 | spin_lock_init(&hsp->lock); |
|---|
| .. | .. |
|---|
| 391 | 669 | hsp->num_db = (value >> HSP_nDB_SHIFT) & HSP_nINT_MASK; |
|---|
| 392 | 670 | hsp->num_si = (value >> HSP_nSI_SHIFT) & HSP_nINT_MASK; |
|---|
| 393 | 671 | |
|---|
| 394 | | - err = platform_get_irq_byname(pdev, "doorbell"); |
|---|
| 672 | + err = platform_get_irq_byname_optional(pdev, "doorbell"); |
|---|
| 673 | + if (err >= 0) |
|---|
| 674 | + hsp->doorbell_irq = err; |
|---|
| 675 | + |
|---|
| 676 | + if (hsp->num_si > 0) { |
|---|
| 677 | + unsigned int count = 0; |
|---|
| 678 | + |
|---|
| 679 | + hsp->shared_irqs = devm_kcalloc(&pdev->dev, hsp->num_si, |
|---|
| 680 | + sizeof(*hsp->shared_irqs), |
|---|
| 681 | + GFP_KERNEL); |
|---|
| 682 | + if (!hsp->shared_irqs) |
|---|
| 683 | + return -ENOMEM; |
|---|
| 684 | + |
|---|
| 685 | + for (i = 0; i < hsp->num_si; i++) { |
|---|
| 686 | + char *name; |
|---|
| 687 | + |
|---|
| 688 | + name = kasprintf(GFP_KERNEL, "shared%u", i); |
|---|
| 689 | + if (!name) |
|---|
| 690 | + return -ENOMEM; |
|---|
| 691 | + |
|---|
| 692 | + err = platform_get_irq_byname_optional(pdev, name); |
|---|
| 693 | + if (err >= 0) { |
|---|
| 694 | + hsp->shared_irqs[i] = err; |
|---|
| 695 | + count++; |
|---|
| 696 | + } |
|---|
| 697 | + |
|---|
| 698 | + kfree(name); |
|---|
| 699 | + } |
|---|
| 700 | + |
|---|
| 701 | + if (count == 0) { |
|---|
| 702 | + devm_kfree(&pdev->dev, hsp->shared_irqs); |
|---|
| 703 | + hsp->shared_irqs = NULL; |
|---|
| 704 | + } |
|---|
| 705 | + } |
|---|
| 706 | + |
|---|
| 707 | + /* setup the doorbell controller */ |
|---|
| 708 | + hsp->mbox_db.of_xlate = tegra_hsp_db_xlate; |
|---|
| 709 | + hsp->mbox_db.num_chans = 32; |
|---|
| 710 | + hsp->mbox_db.dev = &pdev->dev; |
|---|
| 711 | + hsp->mbox_db.ops = &tegra_hsp_db_ops; |
|---|
| 712 | + |
|---|
| 713 | + hsp->mbox_db.chans = devm_kcalloc(&pdev->dev, hsp->mbox_db.num_chans, |
|---|
| 714 | + sizeof(*hsp->mbox_db.chans), |
|---|
| 715 | + GFP_KERNEL); |
|---|
| 716 | + if (!hsp->mbox_db.chans) |
|---|
| 717 | + return -ENOMEM; |
|---|
| 718 | + |
|---|
| 719 | + if (hsp->doorbell_irq) { |
|---|
| 720 | + err = tegra_hsp_add_doorbells(hsp); |
|---|
| 721 | + if (err < 0) { |
|---|
| 722 | + dev_err(&pdev->dev, "failed to add doorbells: %d\n", |
|---|
| 723 | + err); |
|---|
| 724 | + return err; |
|---|
| 725 | + } |
|---|
| 726 | + } |
|---|
| 727 | + |
|---|
| 728 | + err = devm_mbox_controller_register(&pdev->dev, &hsp->mbox_db); |
|---|
| 395 | 729 | if (err < 0) { |
|---|
| 396 | | - dev_err(&pdev->dev, "failed to get doorbell IRQ: %d\n", err); |
|---|
| 730 | + dev_err(&pdev->dev, "failed to register doorbell mailbox: %d\n", |
|---|
| 731 | + err); |
|---|
| 397 | 732 | return err; |
|---|
| 398 | 733 | } |
|---|
| 399 | 734 | |
|---|
| 400 | | - hsp->irq = err; |
|---|
| 735 | + /* setup the shared mailbox controller */ |
|---|
| 736 | + hsp->mbox_sm.of_xlate = tegra_hsp_sm_xlate; |
|---|
| 737 | + hsp->mbox_sm.num_chans = hsp->num_sm; |
|---|
| 738 | + hsp->mbox_sm.dev = &pdev->dev; |
|---|
| 739 | + hsp->mbox_sm.ops = &tegra_hsp_sm_ops; |
|---|
| 401 | 740 | |
|---|
| 402 | | - hsp->mbox.of_xlate = of_tegra_hsp_xlate; |
|---|
| 403 | | - hsp->mbox.num_chans = 32; |
|---|
| 404 | | - hsp->mbox.dev = &pdev->dev; |
|---|
| 405 | | - hsp->mbox.txdone_irq = false; |
|---|
| 406 | | - hsp->mbox.txdone_poll = false; |
|---|
| 407 | | - hsp->mbox.ops = &tegra_hsp_doorbell_ops; |
|---|
| 408 | | - |
|---|
| 409 | | - hsp->mbox.chans = devm_kcalloc(&pdev->dev, hsp->mbox.num_chans, |
|---|
| 410 | | - sizeof(*hsp->mbox.chans), |
|---|
| 411 | | - GFP_KERNEL); |
|---|
| 412 | | - if (!hsp->mbox.chans) |
|---|
| 741 | + hsp->mbox_sm.chans = devm_kcalloc(&pdev->dev, hsp->mbox_sm.num_chans, |
|---|
| 742 | + sizeof(*hsp->mbox_sm.chans), |
|---|
| 743 | + GFP_KERNEL); |
|---|
| 744 | + if (!hsp->mbox_sm.chans) |
|---|
| 413 | 745 | return -ENOMEM; |
|---|
| 414 | 746 | |
|---|
| 415 | | - err = tegra_hsp_add_doorbells(hsp); |
|---|
| 747 | + if (hsp->shared_irqs) { |
|---|
| 748 | + err = tegra_hsp_add_mailboxes(hsp, &pdev->dev); |
|---|
| 749 | + if (err < 0) { |
|---|
| 750 | + dev_err(&pdev->dev, "failed to add mailboxes: %d\n", |
|---|
| 751 | + err); |
|---|
| 752 | + return err; |
|---|
| 753 | + } |
|---|
| 754 | + } |
|---|
| 755 | + |
|---|
| 756 | + err = devm_mbox_controller_register(&pdev->dev, &hsp->mbox_sm); |
|---|
| 416 | 757 | if (err < 0) { |
|---|
| 417 | | - dev_err(&pdev->dev, "failed to add doorbells: %d\n", err); |
|---|
| 758 | + dev_err(&pdev->dev, "failed to register shared mailbox: %d\n", |
|---|
| 759 | + err); |
|---|
| 418 | 760 | return err; |
|---|
| 419 | 761 | } |
|---|
| 420 | 762 | |
|---|
| 421 | 763 | platform_set_drvdata(pdev, hsp); |
|---|
| 422 | 764 | |
|---|
| 423 | | - err = mbox_controller_register(&hsp->mbox); |
|---|
| 424 | | - if (err) { |
|---|
| 425 | | - dev_err(&pdev->dev, "failed to register mailbox: %d\n", err); |
|---|
| 426 | | - tegra_hsp_remove_doorbells(hsp); |
|---|
| 427 | | - return err; |
|---|
| 765 | + if (hsp->doorbell_irq) { |
|---|
| 766 | + err = devm_request_irq(&pdev->dev, hsp->doorbell_irq, |
|---|
| 767 | + tegra_hsp_doorbell_irq, IRQF_NO_SUSPEND, |
|---|
| 768 | + dev_name(&pdev->dev), hsp); |
|---|
| 769 | + if (err < 0) { |
|---|
| 770 | + dev_err(&pdev->dev, |
|---|
| 771 | + "failed to request doorbell IRQ#%u: %d\n", |
|---|
| 772 | + hsp->doorbell_irq, err); |
|---|
| 773 | + return err; |
|---|
| 774 | + } |
|---|
| 428 | 775 | } |
|---|
| 429 | 776 | |
|---|
| 430 | | - err = devm_request_irq(&pdev->dev, hsp->irq, tegra_hsp_doorbell_irq, |
|---|
| 431 | | - IRQF_NO_SUSPEND, dev_name(&pdev->dev), hsp); |
|---|
| 432 | | - if (err < 0) { |
|---|
| 433 | | - dev_err(&pdev->dev, "failed to request IRQ#%u: %d\n", |
|---|
| 434 | | - hsp->irq, err); |
|---|
| 435 | | - return err; |
|---|
| 777 | + if (hsp->shared_irqs) { |
|---|
| 778 | + err = tegra_hsp_request_shared_irq(hsp); |
|---|
| 779 | + if (err < 0) |
|---|
| 780 | + return err; |
|---|
| 436 | 781 | } |
|---|
| 437 | 782 | |
|---|
| 438 | 783 | return 0; |
|---|
| 439 | 784 | } |
|---|
| 440 | 785 | |
|---|
| 441 | | -static int tegra_hsp_remove(struct platform_device *pdev) |
|---|
| 786 | +static int __maybe_unused tegra_hsp_resume(struct device *dev) |
|---|
| 442 | 787 | { |
|---|
| 443 | | - struct tegra_hsp *hsp = platform_get_drvdata(pdev); |
|---|
| 788 | + struct tegra_hsp *hsp = dev_get_drvdata(dev); |
|---|
| 789 | + unsigned int i; |
|---|
| 790 | + struct tegra_hsp_doorbell *db; |
|---|
| 444 | 791 | |
|---|
| 445 | | - mbox_controller_unregister(&hsp->mbox); |
|---|
| 446 | | - tegra_hsp_remove_doorbells(hsp); |
|---|
| 792 | + list_for_each_entry(db, &hsp->doorbells, list) { |
|---|
| 793 | + if (db && db->channel.chan) |
|---|
| 794 | + tegra_hsp_doorbell_startup(db->channel.chan); |
|---|
| 795 | + } |
|---|
| 796 | + |
|---|
| 797 | + if (hsp->mailboxes) { |
|---|
| 798 | + for (i = 0; i < hsp->num_sm; i++) { |
|---|
| 799 | + struct tegra_hsp_mailbox *mb = &hsp->mailboxes[i]; |
|---|
| 800 | + |
|---|
| 801 | + if (mb->channel.chan->cl) |
|---|
| 802 | + tegra_hsp_mailbox_startup(mb->channel.chan); |
|---|
| 803 | + } |
|---|
| 804 | + } |
|---|
| 447 | 805 | |
|---|
| 448 | 806 | return 0; |
|---|
| 449 | 807 | } |
|---|
| 808 | + |
|---|
| 809 | +static const struct dev_pm_ops tegra_hsp_pm_ops = { |
|---|
| 810 | + .resume_noirq = tegra_hsp_resume, |
|---|
| 811 | +}; |
|---|
| 450 | 812 | |
|---|
| 451 | 813 | static const struct tegra_hsp_db_map tegra186_hsp_db_map[] = { |
|---|
| 452 | 814 | { "ccplex", TEGRA_HSP_DB_MASTER_CCPLEX, HSP_DB_CCPLEX, }, |
|---|
| .. | .. |
|---|
| 456 | 818 | |
|---|
| 457 | 819 | static const struct tegra_hsp_soc tegra186_hsp_soc = { |
|---|
| 458 | 820 | .map = tegra186_hsp_db_map, |
|---|
| 821 | + .has_per_mb_ie = false, |
|---|
| 822 | +}; |
|---|
| 823 | + |
|---|
| 824 | +static const struct tegra_hsp_soc tegra194_hsp_soc = { |
|---|
| 825 | + .map = tegra186_hsp_db_map, |
|---|
| 826 | + .has_per_mb_ie = true, |
|---|
| 459 | 827 | }; |
|---|
| 460 | 828 | |
|---|
| 461 | 829 | static const struct of_device_id tegra_hsp_match[] = { |
|---|
| 462 | 830 | { .compatible = "nvidia,tegra186-hsp", .data = &tegra186_hsp_soc }, |
|---|
| 831 | + { .compatible = "nvidia,tegra194-hsp", .data = &tegra194_hsp_soc }, |
|---|
| 463 | 832 | { } |
|---|
| 464 | 833 | }; |
|---|
| 465 | 834 | |
|---|
| .. | .. |
|---|
| 467 | 836 | .driver = { |
|---|
| 468 | 837 | .name = "tegra-hsp", |
|---|
| 469 | 838 | .of_match_table = tegra_hsp_match, |
|---|
| 839 | + .pm = &tegra_hsp_pm_ops, |
|---|
| 470 | 840 | }, |
|---|
| 471 | 841 | .probe = tegra_hsp_probe, |
|---|
| 472 | | - .remove = tegra_hsp_remove, |
|---|
| 473 | 842 | }; |
|---|
| 474 | 843 | |
|---|
| 475 | 844 | static int __init tegra_hsp_init(void) |
|---|