diff options
| author | Ley Foon Tan <lftan@altera.com> | 2015-02-04 03:32:18 -0500 |
|---|---|---|
| committer | Jassi Brar <jaswinder.singh@linaro.org> | 2015-02-06 00:28:27 -0500 |
| commit | f62092f6d77dfd9214ae753a24b76ba4ecd801d7 (patch) | |
| tree | bbd52e987eb8151254d2bddfb8fe2e907e0f4dce /drivers/mailbox | |
| parent | 01340df8d3cd4d8f1773d5f1f569b77bbfce31ad (diff) | |
mailbox: Add Altera mailbox driver
The Altera mailbox allows for interprocessor communication. It supports
only one channel and work as either sender or receiver.
Signed-off-by: Ley Foon Tan <lftan@altera.com>
Diffstat (limited to 'drivers/mailbox')
| -rw-r--r-- | drivers/mailbox/Kconfig | 6 | ||||
| -rw-r--r-- | drivers/mailbox/Makefile | 2 | ||||
| -rw-r--r-- | drivers/mailbox/mailbox-altera.c | 388 |
3 files changed, 396 insertions, 0 deletions
diff --git a/drivers/mailbox/Kconfig b/drivers/mailbox/Kconfig index c04fed9eb15d..84325f267acf 100644 --- a/drivers/mailbox/Kconfig +++ b/drivers/mailbox/Kconfig | |||
| @@ -45,4 +45,10 @@ config PCC | |||
| 45 | states). Select this driver if your platform implements the | 45 | states). Select this driver if your platform implements the |
| 46 | PCC clients mentioned above. | 46 | PCC clients mentioned above. |
| 47 | 47 | ||
| 48 | config ALTERA_MBOX | ||
| 49 | tristate "Altera Mailbox" | ||
| 50 | help | ||
| 51 | An implementation of the Altera Mailbox soft core. It is used | ||
| 52 | to send message between processors. Say Y here if you want to use the | ||
| 53 | Altera mailbox support. | ||
| 48 | endif | 54 | endif |
diff --git a/drivers/mailbox/Makefile b/drivers/mailbox/Makefile index dd412c22208b..2e79231154cf 100644 --- a/drivers/mailbox/Makefile +++ b/drivers/mailbox/Makefile | |||
| @@ -7,3 +7,5 @@ obj-$(CONFIG_PL320_MBOX) += pl320-ipc.o | |||
| 7 | obj-$(CONFIG_OMAP2PLUS_MBOX) += omap-mailbox.o | 7 | obj-$(CONFIG_OMAP2PLUS_MBOX) += omap-mailbox.o |
| 8 | 8 | ||
| 9 | obj-$(CONFIG_PCC) += pcc.o | 9 | obj-$(CONFIG_PCC) += pcc.o |
| 10 | |||
| 11 | obj-$(CONFIG_ALTERA_MBOX) += mailbox-altera.o | ||
diff --git a/drivers/mailbox/mailbox-altera.c b/drivers/mailbox/mailbox-altera.c new file mode 100644 index 000000000000..a266265677d3 --- /dev/null +++ b/drivers/mailbox/mailbox-altera.c | |||
| @@ -0,0 +1,388 @@ | |||
| 1 | /* | ||
| 2 | * Copyright Altera Corporation (C) 2013-2014. 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. | ||
| 12 | * | ||
| 13 | * You should have received a copy of the GNU General Public License along with | ||
| 14 | * this program. If not, see <http://www.gnu.org/licenses/>. | ||
| 15 | */ | ||
| 16 | |||
| 17 | #include <linux/device.h> | ||
| 18 | #include <linux/interrupt.h> | ||
| 19 | #include <linux/io.h> | ||
| 20 | #include <linux/kernel.h> | ||
| 21 | #include <linux/mailbox_controller.h> | ||
| 22 | #include <linux/module.h> | ||
| 23 | #include <linux/of.h> | ||
| 24 | #include <linux/platform_device.h> | ||
| 25 | |||
| 26 | #define DRIVER_NAME "altera-mailbox" | ||
| 27 | |||
| 28 | #define MAILBOX_CMD_REG 0x00 | ||
| 29 | #define MAILBOX_PTR_REG 0x04 | ||
| 30 | #define MAILBOX_STS_REG 0x08 | ||
| 31 | #define MAILBOX_INTMASK_REG 0x0C | ||
| 32 | |||
| 33 | #define INT_PENDING_MSK 0x1 | ||
| 34 | #define INT_SPACE_MSK 0x2 | ||
| 35 | |||
| 36 | #define STS_PENDING_MSK 0x1 | ||
| 37 | #define STS_FULL_MSK 0x2 | ||
| 38 | #define STS_FULL_OFT 0x1 | ||
| 39 | |||
| 40 | #define MBOX_PENDING(status) (((status) & STS_PENDING_MSK)) | ||
| 41 | #define MBOX_FULL(status) (((status) & STS_FULL_MSK) >> STS_FULL_OFT) | ||
| 42 | |||
| 43 | enum altera_mbox_msg { | ||
| 44 | MBOX_CMD = 0, | ||
| 45 | MBOX_PTR, | ||
| 46 | }; | ||
| 47 | |||
| 48 | #define MBOX_POLLING_MS 5 /* polling interval 5ms */ | ||
| 49 | |||
| 50 | struct altera_mbox { | ||
| 51 | bool is_sender; /* 1-sender, 0-receiver */ | ||
| 52 | bool intr_mode; | ||
| 53 | int irq; | ||
| 54 | void __iomem *mbox_base; | ||
| 55 | struct device *dev; | ||
| 56 | struct mbox_controller controller; | ||
| 57 | |||
| 58 | /* If the controller supports only RX polling mode */ | ||
| 59 | struct timer_list rxpoll_timer; | ||
| 60 | }; | ||
| 61 | |||
| 62 | static struct altera_mbox *mbox_chan_to_altera_mbox(struct mbox_chan *chan) | ||
| 63 | { | ||
| 64 | if (!chan || !chan->con_priv) | ||
| 65 | return NULL; | ||
| 66 | |||
| 67 | return (struct altera_mbox *)chan->con_priv; | ||
| 68 | } | ||
| 69 | |||
| 70 | static inline int altera_mbox_full(struct altera_mbox *mbox) | ||
| 71 | { | ||
| 72 | u32 status; | ||
| 73 | |||
| 74 | status = readl_relaxed(mbox->mbox_base + MAILBOX_STS_REG); | ||
| 75 | return MBOX_FULL(status); | ||
| 76 | } | ||
| 77 | |||
| 78 | static inline int altera_mbox_pending(struct altera_mbox *mbox) | ||
| 79 | { | ||
| 80 | u32 status; | ||
| 81 | |||
| 82 | status = readl_relaxed(mbox->mbox_base + MAILBOX_STS_REG); | ||
| 83 | return MBOX_PENDING(status); | ||
| 84 | } | ||
| 85 | |||
| 86 | static void altera_mbox_rx_intmask(struct altera_mbox *mbox, bool enable) | ||
| 87 | { | ||
| 88 | u32 mask; | ||
| 89 | |||
| 90 | mask = readl_relaxed(mbox->mbox_base + MAILBOX_INTMASK_REG); | ||
| 91 | if (enable) | ||
| 92 | mask |= INT_PENDING_MSK; | ||
| 93 | else | ||
| 94 | mask &= ~INT_PENDING_MSK; | ||
| 95 | writel_relaxed(mask, mbox->mbox_base + MAILBOX_INTMASK_REG); | ||
| 96 | } | ||
| 97 | |||
| 98 | static void altera_mbox_tx_intmask(struct altera_mbox *mbox, bool enable) | ||
| 99 | { | ||
| 100 | u32 mask; | ||
| 101 | |||
| 102 | mask = readl_relaxed(mbox->mbox_base + MAILBOX_INTMASK_REG); | ||
| 103 | if (enable) | ||
| 104 | mask |= INT_SPACE_MSK; | ||
| 105 | else | ||
| 106 | mask &= ~INT_SPACE_MSK; | ||
| 107 | writel_relaxed(mask, mbox->mbox_base + MAILBOX_INTMASK_REG); | ||
| 108 | } | ||
| 109 | |||
| 110 | static bool altera_mbox_is_sender(struct altera_mbox *mbox) | ||
| 111 | { | ||
| 112 | u32 reg; | ||
| 113 | /* Write a magic number to PTR register and read back this register. | ||
| 114 | * This register is read-write if it is a sender. | ||
| 115 | */ | ||
| 116 | #define MBOX_MAGIC 0xA5A5AA55 | ||
| 117 | writel_relaxed(MBOX_MAGIC, mbox->mbox_base + MAILBOX_PTR_REG); | ||
| 118 | reg = readl_relaxed(mbox->mbox_base + MAILBOX_PTR_REG); | ||
| 119 | if (reg == MBOX_MAGIC) { | ||
| 120 | /* Clear to 0 */ | ||
| 121 | writel_relaxed(0, mbox->mbox_base + MAILBOX_PTR_REG); | ||
| 122 | return true; | ||
| 123 | } | ||
| 124 | return false; | ||
| 125 | } | ||
| 126 | |||
| 127 | static void altera_mbox_rx_data(struct mbox_chan *chan) | ||
| 128 | { | ||
| 129 | struct altera_mbox *mbox = mbox_chan_to_altera_mbox(chan); | ||
| 130 | u32 data[2]; | ||
| 131 | |||
| 132 | if (altera_mbox_pending(mbox)) { | ||
| 133 | data[MBOX_PTR] = | ||
| 134 | readl_relaxed(mbox->mbox_base + MAILBOX_PTR_REG); | ||
| 135 | data[MBOX_CMD] = | ||
| 136 | readl_relaxed(mbox->mbox_base + MAILBOX_CMD_REG); | ||
| 137 | mbox_chan_received_data(chan, (void *)data); | ||
| 138 | } | ||
| 139 | } | ||
| 140 | |||
| 141 | static void altera_mbox_poll_rx(unsigned long data) | ||
| 142 | { | ||
| 143 | struct mbox_chan *chan = (struct mbox_chan *)data; | ||
| 144 | struct altera_mbox *mbox = mbox_chan_to_altera_mbox(chan); | ||
| 145 | |||
| 146 | altera_mbox_rx_data(chan); | ||
| 147 | |||
| 148 | mod_timer(&mbox->rxpoll_timer, | ||
| 149 | jiffies + msecs_to_jiffies(MBOX_POLLING_MS)); | ||
| 150 | } | ||
| 151 | |||
| 152 | static irqreturn_t altera_mbox_tx_interrupt(int irq, void *p) | ||
| 153 | { | ||
| 154 | struct mbox_chan *chan = (struct mbox_chan *)p; | ||
| 155 | struct altera_mbox *mbox = mbox_chan_to_altera_mbox(chan); | ||
| 156 | |||
| 157 | altera_mbox_tx_intmask(mbox, false); | ||
| 158 | mbox_chan_txdone(chan, 0); | ||
| 159 | |||
| 160 | return IRQ_HANDLED; | ||
| 161 | } | ||
| 162 | |||
| 163 | static irqreturn_t altera_mbox_rx_interrupt(int irq, void *p) | ||
| 164 | { | ||
| 165 | struct mbox_chan *chan = (struct mbox_chan *)p; | ||
| 166 | |||
| 167 | altera_mbox_rx_data(chan); | ||
| 168 | return IRQ_HANDLED; | ||
| 169 | } | ||
| 170 | |||
| 171 | static int altera_mbox_startup_sender(struct mbox_chan *chan) | ||
| 172 | { | ||
| 173 | int ret; | ||
| 174 | struct altera_mbox *mbox = mbox_chan_to_altera_mbox(chan); | ||
| 175 | |||
| 176 | if (mbox->intr_mode) { | ||
| 177 | ret = request_irq(mbox->irq, altera_mbox_tx_interrupt, 0, | ||
| 178 | DRIVER_NAME, chan); | ||
| 179 | if (unlikely(ret)) { | ||
| 180 | dev_err(mbox->dev, | ||
| 181 | "failed to register mailbox interrupt:%d\n", | ||
| 182 | ret); | ||
| 183 | return ret; | ||
| 184 | } | ||
| 185 | } | ||
| 186 | |||
| 187 | return 0; | ||
| 188 | } | ||
| 189 | |||
| 190 | static int altera_mbox_startup_receiver(struct mbox_chan *chan) | ||
| 191 | { | ||
| 192 | int ret; | ||
| 193 | struct altera_mbox *mbox = mbox_chan_to_altera_mbox(chan); | ||
| 194 | |||
| 195 | if (mbox->intr_mode) { | ||
| 196 | ret = request_irq(mbox->irq, altera_mbox_rx_interrupt, 0, | ||
| 197 | DRIVER_NAME, chan); | ||
| 198 | if (unlikely(ret)) { | ||
| 199 | mbox->intr_mode = false; | ||
| 200 | goto polling; /* use polling if failed */ | ||
| 201 | } | ||
| 202 | |||
| 203 | altera_mbox_rx_intmask(mbox, true); | ||
| 204 | return 0; | ||
| 205 | } | ||
| 206 | |||
| 207 | polling: | ||
| 208 | /* Setup polling timer */ | ||
| 209 | setup_timer(&mbox->rxpoll_timer, altera_mbox_poll_rx, | ||
| 210 | (unsigned long)chan); | ||
| 211 | mod_timer(&mbox->rxpoll_timer, | ||
| 212 | jiffies + msecs_to_jiffies(MBOX_POLLING_MS)); | ||
| 213 | |||
| 214 | return 0; | ||
| 215 | } | ||
| 216 | |||
| 217 | static int altera_mbox_send_data(struct mbox_chan *chan, void *data) | ||
| 218 | { | ||
| 219 | struct altera_mbox *mbox = mbox_chan_to_altera_mbox(chan); | ||
| 220 | u32 *udata = (u32 *)data; | ||
| 221 | |||
| 222 | if (!mbox || !data) | ||
| 223 | return -EINVAL; | ||
| 224 | if (!mbox->is_sender) { | ||
| 225 | dev_warn(mbox->dev, | ||
| 226 | "failed to send. This is receiver mailbox.\n"); | ||
| 227 | return -EINVAL; | ||
| 228 | } | ||
| 229 | |||
| 230 | if (altera_mbox_full(mbox)) | ||
| 231 | return -EBUSY; | ||
| 232 | |||
| 233 | /* Enable interrupt before send */ | ||
| 234 | if (mbox->intr_mode) | ||
| 235 | altera_mbox_tx_intmask(mbox, true); | ||
| 236 | |||
| 237 | /* Pointer register must write before command register */ | ||
| 238 | writel_relaxed(udata[MBOX_PTR], mbox->mbox_base + MAILBOX_PTR_REG); | ||
| 239 | writel_relaxed(udata[MBOX_CMD], mbox->mbox_base + MAILBOX_CMD_REG); | ||
| 240 | |||
| 241 | return 0; | ||
| 242 | } | ||
| 243 | |||
| 244 | static bool altera_mbox_last_tx_done(struct mbox_chan *chan) | ||
| 245 | { | ||
| 246 | struct altera_mbox *mbox = mbox_chan_to_altera_mbox(chan); | ||
| 247 | |||
| 248 | /* Return false if mailbox is full */ | ||
| 249 | return altera_mbox_full(mbox) ? false : true; | ||
| 250 | } | ||
| 251 | |||
| 252 | static bool altera_mbox_peek_data(struct mbox_chan *chan) | ||
| 253 | { | ||
| 254 | struct altera_mbox *mbox = mbox_chan_to_altera_mbox(chan); | ||
| 255 | |||
| 256 | return altera_mbox_pending(mbox) ? true : false; | ||
| 257 | } | ||
| 258 | |||
| 259 | static int altera_mbox_startup(struct mbox_chan *chan) | ||
| 260 | { | ||
| 261 | struct altera_mbox *mbox = mbox_chan_to_altera_mbox(chan); | ||
| 262 | int ret = 0; | ||
| 263 | |||
| 264 | if (!mbox) | ||
| 265 | return -EINVAL; | ||
| 266 | |||
| 267 | if (mbox->is_sender) | ||
| 268 | ret = altera_mbox_startup_sender(chan); | ||
| 269 | else | ||
| 270 | ret = altera_mbox_startup_receiver(chan); | ||
| 271 | |||
| 272 | return ret; | ||
| 273 | } | ||
| 274 | |||
| 275 | static void altera_mbox_shutdown(struct mbox_chan *chan) | ||
| 276 | { | ||
| 277 | struct altera_mbox *mbox = mbox_chan_to_altera_mbox(chan); | ||
| 278 | |||
| 279 | if (mbox->intr_mode) { | ||
| 280 | /* Unmask all interrupt masks */ | ||
| 281 | writel_relaxed(~0, mbox->mbox_base + MAILBOX_INTMASK_REG); | ||
| 282 | free_irq(mbox->irq, chan); | ||
| 283 | } else if (!mbox->is_sender) { | ||
| 284 | del_timer_sync(&mbox->rxpoll_timer); | ||
| 285 | } | ||
| 286 | } | ||
| 287 | |||
| 288 | static struct mbox_chan_ops altera_mbox_ops = { | ||
| 289 | .send_data = altera_mbox_send_data, | ||
| 290 | .startup = altera_mbox_startup, | ||
| 291 | .shutdown = altera_mbox_shutdown, | ||
| 292 | .last_tx_done = altera_mbox_last_tx_done, | ||
| 293 | .peek_data = altera_mbox_peek_data, | ||
| 294 | }; | ||
| 295 | |||
| 296 | static int altera_mbox_probe(struct platform_device *pdev) | ||
| 297 | { | ||
| 298 | struct altera_mbox *mbox; | ||
| 299 | struct resource *regs; | ||
| 300 | struct mbox_chan *chans; | ||
| 301 | int ret; | ||
| 302 | |||
| 303 | mbox = devm_kzalloc(&pdev->dev, sizeof(*mbox), | ||
| 304 | GFP_KERNEL); | ||
| 305 | if (!mbox) | ||
| 306 | return -ENOMEM; | ||
| 307 | |||
| 308 | /* Allocated one channel */ | ||
| 309 | chans = devm_kzalloc(&pdev->dev, sizeof(*chans), GFP_KERNEL); | ||
| 310 | if (!chans) | ||
| 311 | return -ENOMEM; | ||
| 312 | |||
| 313 | regs = platform_get_resource(pdev, IORESOURCE_MEM, 0); | ||
| 314 | |||
| 315 | mbox->mbox_base = devm_ioremap_resource(&pdev->dev, regs); | ||
| 316 | if (IS_ERR(mbox->mbox_base)) | ||
| 317 | return PTR_ERR(mbox->mbox_base); | ||
| 318 | |||
| 319 | /* Check is it a sender or receiver? */ | ||
| 320 | mbox->is_sender = altera_mbox_is_sender(mbox); | ||
| 321 | |||
| 322 | mbox->irq = platform_get_irq(pdev, 0); | ||
| 323 | if (mbox->irq >= 0) | ||
| 324 | mbox->intr_mode = true; | ||
| 325 | |||
| 326 | mbox->dev = &pdev->dev; | ||
| 327 | |||
| 328 | /* Hardware supports only one channel. */ | ||
| 329 | chans[0].con_priv = mbox; | ||
| 330 | mbox->controller.dev = mbox->dev; | ||
| 331 | mbox->controller.num_chans = 1; | ||
| 332 | mbox->controller.chans = chans; | ||
| 333 | mbox->controller.ops = &altera_mbox_ops; | ||
| 334 | |||
| 335 | if (mbox->is_sender) { | ||
| 336 | if (mbox->intr_mode) { | ||
| 337 | mbox->controller.txdone_irq = true; | ||
| 338 | } else { | ||
| 339 | mbox->controller.txdone_poll = true; | ||
| 340 | mbox->controller.txpoll_period = MBOX_POLLING_MS; | ||
| 341 | } | ||
| 342 | } | ||
| 343 | |||
| 344 | ret = mbox_controller_register(&mbox->controller); | ||
| 345 | if (ret) { | ||
| 346 | dev_err(&pdev->dev, "Register mailbox failed\n"); | ||
| 347 | goto err; | ||
| 348 | } | ||
| 349 | |||
| 350 | platform_set_drvdata(pdev, mbox); | ||
| 351 | err: | ||
| 352 | return ret; | ||
| 353 | } | ||
| 354 | |||
| 355 | static int altera_mbox_remove(struct platform_device *pdev) | ||
| 356 | { | ||
| 357 | struct altera_mbox *mbox = platform_get_drvdata(pdev); | ||
| 358 | |||
| 359 | if (!mbox) | ||
| 360 | return -EINVAL; | ||
| 361 | |||
| 362 | mbox_controller_unregister(&mbox->controller); | ||
| 363 | |||
| 364 | return 0; | ||
| 365 | } | ||
| 366 | |||
| 367 | static const struct of_device_id altera_mbox_match[] = { | ||
| 368 | { .compatible = "altr,mailbox-1.0" }, | ||
| 369 | { /* Sentinel */ } | ||
| 370 | }; | ||
| 371 | |||
| 372 | MODULE_DEVICE_TABLE(of, altera_mbox_match); | ||
| 373 | |||
| 374 | static struct platform_driver altera_mbox_driver = { | ||
| 375 | .probe = altera_mbox_probe, | ||
| 376 | .remove = altera_mbox_remove, | ||
| 377 | .driver = { | ||
| 378 | .name = DRIVER_NAME, | ||
| 379 | .of_match_table = altera_mbox_match, | ||
| 380 | }, | ||
| 381 | }; | ||
| 382 | |||
| 383 | module_platform_driver(altera_mbox_driver); | ||
| 384 | |||
| 385 | MODULE_LICENSE("GPL v2"); | ||
| 386 | MODULE_DESCRIPTION("Altera mailbox specific functions"); | ||
| 387 | MODULE_AUTHOR("Ley Foon Tan <lftan@altera.com>"); | ||
| 388 | MODULE_ALIAS("platform:altera-mailbox"); | ||
