FILE
sprd-mailbox.c
File preview with syntax highlighting.
linux-6.19.14/drivers/mailbox/sprd-mailbox.c
1// SPDX-License-Identifier: GPL-2.0-only 2/* 3 * Spreadtrum mailbox driver 4 * 5 * Copyright (c) 2020 Spreadtrum Communications Inc. 6 */ 7 8#include <linux/delay.h> 9#include <linux/err.h> 10#include <linux/interrupt.h> 11#include <linux/io.h> 12#include <linux/mailbox_controller.h> 13#include <linux/module.h> 14#include <linux/of.h> 15#include <linux/platform_device.h> 16#include <linux/clk.h> 17 18#define SPRD_MBOX_ID 0x0 19#define SPRD_MBOX_MSG_LOW 0x4 20#define SPRD_MBOX_MSG_HIGH 0x8 21#define SPRD_MBOX_TRIGGER 0xc 22#define SPRD_MBOX_FIFO_RST 0x10 23#define SPRD_MBOX_FIFO_STS 0x14 24#define SPRD_MBOX_IRQ_STS 0x18 25#define SPRD_MBOX_IRQ_MSK 0x1c 26#define SPRD_MBOX_LOCK 0x20 27#define SPRD_MBOX_FIFO_DEPTH 0x24 28 29/* Bit and mask definition for inbox's SPRD_MBOX_FIFO_STS register */ 30#define SPRD_INBOX_FIFO_DELIVER_MASK GENMASK(23, 16) 31#define SPRD_INBOX_FIFO_OVERLOW_MASK GENMASK(15, 8) 32#define SPRD_INBOX_FIFO_DELIVER_SHIFT 16 33#define SPRD_INBOX_FIFO_BUSY_MASK GENMASK(7, 0) 34 35/* Bit and mask definition for SPRD_MBOX_IRQ_STS register */ 36#define SPRD_MBOX_IRQ_CLR BIT(0) 37 38/* Bit and mask definition for outbox's SPRD_MBOX_FIFO_STS register */ 39#define SPRD_OUTBOX_FIFO_FULL BIT(2) 40#define SPRD_OUTBOX_FIFO_WR_SHIFT 16 41#define SPRD_OUTBOX_FIFO_RD_SHIFT 24 42#define SPRD_OUTBOX_FIFO_POS_MASK GENMASK(7, 0) 43 44/* Bit and mask definition for inbox's SPRD_MBOX_IRQ_MSK register */ 45#define SPRD_INBOX_FIFO_BLOCK_IRQ BIT(0) 46#define SPRD_INBOX_FIFO_OVERFLOW_IRQ BIT(1) 47#define SPRD_INBOX_FIFO_DELIVER_IRQ BIT(2) 48#define SPRD_INBOX_FIFO_IRQ_MASK GENMASK(2, 0) 49 50/* Bit and mask definition for outbox's SPRD_MBOX_IRQ_MSK register */ 51#define SPRD_OUTBOX_FIFO_NOT_EMPTY_IRQ BIT(0) 52#define SPRD_OUTBOX_FIFO_IRQ_MASK GENMASK(4, 0) 53 54#define SPRD_OUTBOX_BASE_SPAN 0x1000 55#define SPRD_MBOX_CHAN_MAX 8 56#define SPRD_SUPP_INBOX_ID_SC9863A 7 57 58struct sprd_mbox_priv { 59 struct mbox_controller mbox; 60 struct device *dev; 61 void __iomem *inbox_base; 62 void __iomem *outbox_base; 63 /* Base register address for supplementary outbox */ 64 void __iomem *supp_base; 65 u32 outbox_fifo_depth; 66 67 struct mutex lock; 68 u32 refcnt; 69 struct mbox_chan chan[SPRD_MBOX_CHAN_MAX]; 70}; 71 72static struct sprd_mbox_priv *to_sprd_mbox_priv(struct mbox_controller *mbox) 73{ 74 return container_of(mbox, struct sprd_mbox_priv, mbox); 75} 76 77static u32 sprd_mbox_get_fifo_len(struct sprd_mbox_priv *priv, u32 fifo_sts) 78{ 79 u32 wr_pos = (fifo_sts >> SPRD_OUTBOX_FIFO_WR_SHIFT) & 80 SPRD_OUTBOX_FIFO_POS_MASK; 81 u32 rd_pos = (fifo_sts >> SPRD_OUTBOX_FIFO_RD_SHIFT) & 82 SPRD_OUTBOX_FIFO_POS_MASK; 83 u32 fifo_len; 84 85 /* 86 * If the read pointer is equal with write pointer, which means the fifo 87 * is full or empty. 88 */ 89 if (wr_pos == rd_pos) { 90 if (fifo_sts & SPRD_OUTBOX_FIFO_FULL) 91 fifo_len = priv->outbox_fifo_depth; 92 else 93 fifo_len = 0; 94 } else if (wr_pos > rd_pos) { 95 fifo_len = wr_pos - rd_pos; 96 } else { 97 fifo_len = priv->outbox_fifo_depth - rd_pos + wr_pos; 98 } 99 100 return fifo_len; 101} 102 103static irqreturn_t do_outbox_isr(void __iomem *base, struct sprd_mbox_priv *priv) 104{ 105 struct mbox_chan *chan; 106 u32 fifo_sts, fifo_len, msg[2]; 107 int i, id; 108 109 fifo_sts = readl(base + SPRD_MBOX_FIFO_STS); 110 111 fifo_len = sprd_mbox_get_fifo_len(priv, fifo_sts); 112 if (!fifo_len) { 113 dev_warn_ratelimited(priv->dev, "spurious outbox interrupt\n"); 114 return IRQ_NONE; 115 } 116 117 for (i = 0; i < fifo_len; i++) { 118 msg[0] = readl(base + SPRD_MBOX_MSG_LOW); 119 msg[1] = readl(base + SPRD_MBOX_MSG_HIGH); 120 id = readl(base + SPRD_MBOX_ID); 121 122 chan = &priv->chan[id]; 123 if (chan->cl) 124 mbox_chan_received_data(chan, (void *)msg); 125 else 126 dev_warn_ratelimited(priv->dev, 127 "message's been dropped at ch[%d]\n", id); 128 129 /* Trigger to update outbox FIFO pointer */ 130 writel(0x1, base + SPRD_MBOX_TRIGGER); 131 } 132 133 /* Clear irq status after reading all message. */ 134 writel(SPRD_MBOX_IRQ_CLR, base + SPRD_MBOX_IRQ_STS); 135 136 return IRQ_HANDLED; 137} 138 139static irqreturn_t sprd_mbox_outbox_isr(int irq, void *data) 140{ 141 struct sprd_mbox_priv *priv = data; 142 143 return do_outbox_isr(priv->outbox_base, priv); 144} 145 146static irqreturn_t sprd_mbox_supp_isr(int irq, void *data) 147{ 148 struct sprd_mbox_priv *priv = data; 149 150 return do_outbox_isr(priv->supp_base, priv); 151} 152 153static irqreturn_t sprd_mbox_inbox_isr(int irq, void *data) 154{ 155 struct sprd_mbox_priv *priv = data; 156 struct mbox_chan *chan; 157 u32 fifo_sts, send_sts, busy, id; 158 159 fifo_sts = readl(priv->inbox_base + SPRD_MBOX_FIFO_STS); 160 161 /* Get the inbox data delivery status */ 162 send_sts = (fifo_sts & SPRD_INBOX_FIFO_DELIVER_MASK) >> 163 SPRD_INBOX_FIFO_DELIVER_SHIFT; 164 if (!send_sts) { 165 dev_warn_ratelimited(priv->dev, "spurious inbox interrupt\n"); 166 return IRQ_NONE; 167 } 168 169 /* Clear FIFO delivery and overflow status first */ 170 writel(fifo_sts & 171 (SPRD_INBOX_FIFO_DELIVER_MASK | SPRD_INBOX_FIFO_OVERLOW_MASK), 172 priv->inbox_base + SPRD_MBOX_FIFO_RST); 173 174 while (send_sts) { 175 id = __ffs(send_sts); 176 send_sts &= (send_sts - 1); 177 178 chan = &priv->chan[id]; 179 180 /* 181 * Check if the message was fetched by remote target, if yes, 182 * that means the transmission has been completed. 183 */ 184 busy = fifo_sts & SPRD_INBOX_FIFO_BUSY_MASK; 185 if (!(busy & BIT(id))) 186 mbox_chan_txdone(chan, 0); 187 } 188 189 /* Clear irq status */ 190 writel(SPRD_MBOX_IRQ_CLR, priv->inbox_base + SPRD_MBOX_IRQ_STS); 191 192 return IRQ_HANDLED; 193} 194 195static int sprd_mbox_send_data(struct mbox_chan *chan, void *msg) 196{ 197 struct sprd_mbox_priv *priv = to_sprd_mbox_priv(chan->mbox); 198 unsigned long id = (unsigned long)chan->con_priv; 199 u32 *data = msg; 200 201 /* Write data into inbox FIFO, and only support 8 bytes every time */ 202 writel(data[0], priv->inbox_base + SPRD_MBOX_MSG_LOW); 203 writel(data[1], priv->inbox_base + SPRD_MBOX_MSG_HIGH); 204 205 /* Set target core id */ 206 writel(id, priv->inbox_base + SPRD_MBOX_ID); 207 208 /* Trigger remote request */ 209 writel(0x1, priv->inbox_base + SPRD_MBOX_TRIGGER); 210 211 return 0; 212} 213 214static int sprd_mbox_flush(struct mbox_chan *chan, unsigned long timeout) 215{ 216 struct sprd_mbox_priv *priv = to_sprd_mbox_priv(chan->mbox); 217 unsigned long id = (unsigned long)chan->con_priv; 218 u32 busy; 219 220 timeout = jiffies + msecs_to_jiffies(timeout); 221 222 while (time_before(jiffies, timeout)) { 223 busy = readl(priv->inbox_base + SPRD_MBOX_FIFO_STS) & 224 SPRD_INBOX_FIFO_BUSY_MASK; 225 if (!(busy & BIT(id))) { 226 mbox_chan_txdone(chan, 0); 227 return 0; 228 } 229 230 udelay(1); 231 } 232 233 return -ETIME; 234} 235 236static int sprd_mbox_startup(struct mbox_chan *chan) 237{ 238 struct sprd_mbox_priv *priv = to_sprd_mbox_priv(chan->mbox); 239 u32 val; 240 241 mutex_lock(&priv->lock); 242 if (priv->refcnt++ == 0) { 243 /* Select outbox FIFO mode and reset the outbox FIFO status */ 244 writel(0x0, priv->outbox_base + SPRD_MBOX_FIFO_RST); 245 246 /* Enable inbox FIFO delivery interrupt */ 247 val = SPRD_INBOX_FIFO_IRQ_MASK; 248 val &= ~SPRD_INBOX_FIFO_DELIVER_IRQ; 249 writel(val, priv->inbox_base + SPRD_MBOX_IRQ_MSK); 250 251 /* Enable outbox FIFO not empty interrupt */ 252 val = SPRD_OUTBOX_FIFO_IRQ_MASK; 253 val &= ~SPRD_OUTBOX_FIFO_NOT_EMPTY_IRQ; 254 writel(val, priv->outbox_base + SPRD_MBOX_IRQ_MSK); 255 256 /* Enable supplementary outbox as the fundamental one */ 257 if (priv->supp_base) { 258 writel(0x0, priv->supp_base + SPRD_MBOX_FIFO_RST); 259 writel(val, priv->supp_base + SPRD_MBOX_IRQ_MSK); 260 } 261 } 262 mutex_unlock(&priv->lock); 263 264 return 0; 265} 266 267static void sprd_mbox_shutdown(struct mbox_chan *chan) 268{ 269 struct sprd_mbox_priv *priv = to_sprd_mbox_priv(chan->mbox); 270 271 mutex_lock(&priv->lock); 272 if (--priv->refcnt == 0) { 273 /* Disable inbox & outbox interrupt */ 274 writel(SPRD_INBOX_FIFO_IRQ_MASK, priv->inbox_base + SPRD_MBOX_IRQ_MSK); 275 writel(SPRD_OUTBOX_FIFO_IRQ_MASK, priv->outbox_base + SPRD_MBOX_IRQ_MSK); 276 277 if (priv->supp_base) 278 writel(SPRD_OUTBOX_FIFO_IRQ_MASK, 279 priv->supp_base + SPRD_MBOX_IRQ_MSK); 280 } 281 mutex_unlock(&priv->lock); 282} 283 284static const struct mbox_chan_ops sprd_mbox_ops = { 285 .send_data = sprd_mbox_send_data, 286 .flush = sprd_mbox_flush, 287 .startup = sprd_mbox_startup, 288 .shutdown = sprd_mbox_shutdown, 289}; 290 291static int sprd_mbox_probe(struct platform_device *pdev) 292{ 293 struct device *dev = &pdev->dev; 294 struct sprd_mbox_priv *priv; 295 int ret, inbox_irq, outbox_irq, supp_irq; 296 unsigned long id, supp; 297 struct clk *clk; 298 299 priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); 300 if (!priv) 301 return -ENOMEM; 302 303 priv->dev = dev; 304 mutex_init(&priv->lock); 305 306 /* 307 * Unisoc mailbox uses an inbox to send messages to the target 308 * core, and uses (an) outbox(es) to receive messages from other 309 * cores. 310 * 311 * Thus in general the mailbox controller supplies 2 different 312 * register addresses and IRQ numbers for inbox and outbox. 313 * 314 * If necessary, a supplementary inbox could be enabled optionally 315 * with an independent FIFO and an extra interrupt. 316 */ 317 priv->inbox_base = devm_platform_ioremap_resource(pdev, 0); 318 if (IS_ERR(priv->inbox_base)) 319 return PTR_ERR(priv->inbox_base); 320 321 priv->outbox_base = devm_platform_ioremap_resource(pdev, 1); 322 if (IS_ERR(priv->outbox_base)) 323 return PTR_ERR(priv->outbox_base); 324 325 clk = devm_clk_get_enabled(dev, "enable"); 326 if (IS_ERR(clk)) { 327 dev_err(dev, "failed to get mailbox clock\n"); 328 return PTR_ERR(clk); 329 } 330 331 inbox_irq = platform_get_irq_byname(pdev, "inbox"); 332 if (inbox_irq < 0) 333 return inbox_irq; 334 335 ret = devm_request_irq(dev, inbox_irq, sprd_mbox_inbox_isr, 336 IRQF_NO_SUSPEND, dev_name(dev), priv); 337 if (ret) { 338 dev_err(dev, "failed to request inbox IRQ: %d\n", ret); 339 return ret; 340 } 341 342 outbox_irq = platform_get_irq_byname(pdev, "outbox"); 343 if (outbox_irq < 0) 344 return outbox_irq; 345 346 ret = devm_request_irq(dev, outbox_irq, sprd_mbox_outbox_isr, 347 IRQF_NO_SUSPEND, dev_name(dev), priv); 348 if (ret) { 349 dev_err(dev, "failed to request outbox IRQ: %d\n", ret); 350 return ret; 351 } 352 353 /* Supplementary outbox IRQ is optional */ 354 supp_irq = platform_get_irq_byname(pdev, "supp-outbox"); 355 if (supp_irq > 0) { 356 ret = devm_request_irq(dev, supp_irq, sprd_mbox_supp_isr, 357 IRQF_NO_SUSPEND, dev_name(dev), priv); 358 if (ret) { 359 dev_err(dev, "failed to request outbox IRQ: %d\n", ret); 360 return ret; 361 } 362 363 supp = (unsigned long) of_device_get_match_data(dev); 364 if (!supp) { 365 dev_err(dev, "no supplementary outbox specified\n"); 366 return -ENODEV; 367 } 368 priv->supp_base = priv->outbox_base + (SPRD_OUTBOX_BASE_SPAN * supp); 369 } 370 371 /* Get the default outbox FIFO depth */ 372 priv->outbox_fifo_depth = 373 readl(priv->outbox_base + SPRD_MBOX_FIFO_DEPTH) + 1; 374 priv->mbox.dev = dev; 375 priv->mbox.chans = &priv->chan[0]; 376 priv->mbox.num_chans = SPRD_MBOX_CHAN_MAX; 377 priv->mbox.ops = &sprd_mbox_ops; 378 priv->mbox.txdone_irq = true; 379 380 for (id = 0; id < SPRD_MBOX_CHAN_MAX; id++) 381 priv->chan[id].con_priv = (void *)id; 382 383 ret = devm_mbox_controller_register(dev, &priv->mbox); 384 if (ret) { 385 dev_err(dev, "failed to register mailbox: %d\n", ret); 386 return ret; 387 } 388 389 return 0; 390} 391 392static const struct of_device_id sprd_mbox_of_match[] = { 393 { .compatible = "sprd,sc9860-mailbox" }, 394 { .compatible = "sprd,sc9863a-mailbox", 395 .data = (void *)SPRD_SUPP_INBOX_ID_SC9863A }, 396 { }, 397}; 398MODULE_DEVICE_TABLE(of, sprd_mbox_of_match); 399 400static struct platform_driver sprd_mbox_driver = { 401 .driver = { 402 .name = "sprd-mailbox", 403 .of_match_table = sprd_mbox_of_match, 404 }, 405 .probe = sprd_mbox_probe, 406}; 407module_platform_driver(sprd_mbox_driver); 408 409MODULE_AUTHOR("Baolin Wang <baolin.wang@unisoc.com>"); 410MODULE_DESCRIPTION("Spreadtrum mailbox driver"); 411MODULE_LICENSE("GPL v2");
~/
Read-only file preview.