// SPDX-License-Identifier: GPL-2.0-only /* * Renesas R-Car MFIS (Multifunctional Interface) driver * * Copyright (C) Renesas Solutions Corp. * Kuninori Morimoto * Wolfram Sang */ #include #include #include #include #include #include #include #include #include #include #include #include #define MFISWPCNTR 0x0900 #define MFISWACNTR 0x0904 #define MFIS_X5H_IICR(i) ((i) * 0x1000 + 0x00) #define MFIS_X5H_EICR(i) ((i) * 0x1000 + 0x04) #define MFIS_UNPROTECT_KEY 0xACCE0000 struct mfis_priv; struct mfis_reg { void __iomem *base; resource_size_t start; struct mfis_priv *priv; }; struct mfis_info { u32 unprotect_mask; unsigned int mb_num_channels; unsigned int mb_reg_comes_from_dt:1; unsigned int mb_tx_uses_eicr:1; unsigned int mb_channels_are_unidir:1; u32 (*mb_calc_reg)(u32 chan_num, bool tx_uses_eicr, bool is_only_rx); }; struct mfis_chan_priv { u32 reg; int irq; }; struct mfis_priv { spinlock_t unprotect_lock; /* guards access to the unprotection reg */ struct device *dev; struct mfis_reg common_reg; struct mfis_reg mbox_reg; const struct mfis_info *info; /* mailbox private data */ struct mbox_controller mbox; struct mfis_chan_priv *chan_privs; }; static u32 mfis_read(struct mfis_reg *mreg, unsigned int reg) { return ioread32(mreg->base + reg); } static void mfis_write(struct mfis_reg *mreg, u32 reg, u32 val) { struct mfis_priv *priv = mreg->priv; u32 unprotect_mask = priv->info->unprotect_mask; unsigned long flags; u32 unprotect_code; /* * [Gen4] key: 0xACCE0000, mask: 0x0000FFFF * [Gen5] key: 0xACC00000, mask: 0x000FFFFF */ unprotect_code = (MFIS_UNPROTECT_KEY & ~unprotect_mask) | ((mreg->start + reg) & unprotect_mask); spin_lock_irqsave(&priv->unprotect_lock, flags); iowrite32(unprotect_code, priv->common_reg.base + MFISWACNTR); iowrite32(val, mreg->base + reg); spin_unlock_irqrestore(&priv->unprotect_lock, flags); } /******************************************************** * Mailbox * ********************************************************/ #define mfis_mb_mbox_to_priv(_m) container_of((_m), struct mfis_priv, mbox) static irqreturn_t mfis_mb_iicr_interrupt(int irq, void *data) { struct mbox_chan *chan = data; struct mfis_priv *priv = mfis_mb_mbox_to_priv(chan->mbox); struct mfis_chan_priv *chan_priv = chan->con_priv; mbox_chan_received_data(chan, NULL); /* Stop remote(!) doorbell */ mfis_write(&priv->mbox_reg, chan_priv->reg, 0); return IRQ_HANDLED; } static int mfis_mb_startup(struct mbox_chan *chan) { struct mfis_chan_priv *chan_priv = chan->con_priv; if (!chan_priv->irq) return 0; return request_irq(chan_priv->irq, mfis_mb_iicr_interrupt, 0, dev_name(chan->mbox->dev), chan); } static void mfis_mb_shutdown(struct mbox_chan *chan) { struct mfis_chan_priv *chan_priv = chan->con_priv; if (chan_priv->irq) free_irq(chan_priv->irq, chan); } static int mfis_mb_iicr_send_data(struct mbox_chan *chan, void *data) { struct mfis_priv *priv = mfis_mb_mbox_to_priv(chan->mbox); struct mfis_chan_priv *chan_priv = chan->con_priv; /* Our doorbell still active? */ if (mfis_read(&priv->mbox_reg, chan_priv->reg) & BIT(0)) return -EBUSY; /* Start our doorbell */ mfis_write(&priv->mbox_reg, chan_priv->reg, BIT(0)); return 0; } static bool mfis_mb_iicr_last_tx_done(struct mbox_chan *chan) { struct mfis_priv *priv = mfis_mb_mbox_to_priv(chan->mbox); struct mfis_chan_priv *chan_priv = chan->con_priv; /* Our doorbell still active? */ return !(mfis_read(&priv->mbox_reg, chan_priv->reg) & BIT(0)); } /* For MFIS variants using the IICR/EICR register pair */ static const struct mbox_chan_ops mfis_iicr_ops = { .startup = mfis_mb_startup, .shutdown = mfis_mb_shutdown, .send_data = mfis_mb_iicr_send_data, .last_tx_done = mfis_mb_iicr_last_tx_done, }; static u32 mfis_mb_r8a779g0_calc_reg(u32 chan_num, bool tx_uses_eicr, bool is_only_rx) { unsigned int i, k; u32 reg; i = chan_num & 3; k = chan_num >> 2; if (is_only_rx) { if (k < 2) reg = 0x9404 + 0x1020 * k + 0x08 * i; else reg = 0xb504 + 0x08 * i; } else { if (k < 2) reg = 0x1400 + 0x1008 * i + 0x20 * k; else reg = 0x1500 + 0x1008 * i; } return reg; } static u32 mfis_mb_r8a78000_calc_reg(u32 chan_num, bool tx_uses_eicr, bool is_only_rx) { return (tx_uses_eicr ^ is_only_rx) ? MFIS_X5H_EICR(chan_num) : MFIS_X5H_IICR(chan_num); } static struct mbox_chan *mfis_mb_of_xlate(struct mbox_controller *mbox, const struct of_phandle_args *sp) { struct mfis_priv *priv = mfis_mb_mbox_to_priv(mbox); struct mfis_chan_priv *chan_priv; bool tx_uses_eicr, is_only_rx; u32 chan_num, chan_flags; struct mbox_chan *chan; if (sp->args_count != 2) return ERR_PTR(-EINVAL); chan_num = sp->args[0]; chan_flags = sp->args[1]; if (chan_num >= priv->info->mb_num_channels) return ERR_PTR(-EINVAL); /* Channel layout is described in mfis_mb_probe() */ if (priv->info->mb_channels_are_unidir) { is_only_rx = chan_flags & MFIS_CHANNEL_RX; chan = mbox->chans + 2 * chan_num + is_only_rx; } else { is_only_rx = false; chan = mbox->chans + chan_num; } if (priv->info->mb_reg_comes_from_dt) { tx_uses_eicr = chan_flags & MFIS_CHANNEL_EICR; if (tx_uses_eicr) chan += mbox->num_chans / 2; } else { tx_uses_eicr = priv->info->mb_tx_uses_eicr; } chan_priv = chan->con_priv; chan_priv->reg = priv->info->mb_calc_reg(chan_num, tx_uses_eicr, is_only_rx); if (!priv->info->mb_channels_are_unidir || is_only_rx) { char irqname[8]; char suffix = tx_uses_eicr ? 'i' : 'e'; /* "ch0i" or "ch0e" */ scnprintf(irqname, sizeof(irqname), "ch%u%c", chan_num, suffix); chan_priv->irq = of_irq_get_byname(mbox->dev->of_node, irqname); if (chan_priv->irq < 0) return ERR_PTR(chan_priv->irq); if (chan_priv->irq == 0) return ERR_PTR(-ENOENT); } return chan; } static int mfis_mb_probe(struct mfis_priv *priv) { unsigned int num_chan = priv->info->mb_num_channels; struct device *dev = priv->dev; struct mbox_controller *mbox; struct mbox_chan *chan; if (priv->info->mb_channels_are_unidir) { /* Channel layout: Ch0-TX, Ch0-RX, Ch1-TX... */ num_chan *= 2; } if (priv->info->mb_reg_comes_from_dt) { /* Channel layout: IICR channels, EICR channels */ num_chan *= 2; } chan = devm_kcalloc(dev, num_chan, sizeof(*chan), GFP_KERNEL); if (!chan) return -ENOMEM; priv->chan_privs = devm_kcalloc(dev, num_chan, sizeof(*priv->chan_privs), GFP_KERNEL); if (!priv->chan_privs) return -ENOMEM; mbox = &priv->mbox; for (unsigned int i = 0; i < num_chan; i++) chan[i].con_priv = &priv->chan_privs[i]; mbox->chans = chan; mbox->num_chans = num_chan; mbox->txdone_poll = true; mbox->ops = &mfis_iicr_ops; mbox->dev = dev; mbox->of_xlate = mfis_mb_of_xlate; return devm_mbox_controller_register(dev, mbox); } /******************************************************** * Common * ********************************************************/ static int mfis_reg_probe(struct platform_device *pdev, struct mfis_priv *priv, struct mfis_reg *mreg, const char *name, bool required) { struct resource *res; void __iomem *base; res = platform_get_resource_byname(pdev, IORESOURCE_MEM, name); /* If there is no mailbox resource, registers are in the common space */ if (!res && !required) { *mreg = priv->common_reg; } else { base = devm_ioremap_resource(&pdev->dev, res); if (IS_ERR(base)) return PTR_ERR(base); mreg->base = base; mreg->start = res->start; mreg->priv = priv; } return 0; } static int mfis_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct mfis_priv *priv; int ret; priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); if (!priv) return -ENOMEM; priv->dev = dev; priv->info = of_device_get_match_data(dev); if (!priv->info) return -ENOENT; spin_lock_init(&priv->unprotect_lock); ret = mfis_reg_probe(pdev, priv, &priv->common_reg, "common", true); if (ret) return ret; ret = mfis_reg_probe(pdev, priv, &priv->mbox_reg, "mboxes", false); if (ret) return ret; return mfis_mb_probe(priv); } static const struct mfis_info mfis_info_r8a779g0 = { .unprotect_mask = 0x0000ffff, .mb_num_channels = 12, .mb_channels_are_unidir = true, .mb_calc_reg = mfis_mb_r8a779g0_calc_reg, }; static const struct mfis_info mfis_info_r8a78000 = { .unprotect_mask = 0x000fffff, .mb_num_channels = 64, .mb_reg_comes_from_dt = true, .mb_channels_are_unidir = true, .mb_calc_reg = mfis_mb_r8a78000_calc_reg, }; static const struct mfis_info mfis_info_r8a78000_scp = { .unprotect_mask = 0x000fffff, .mb_num_channels = 32, .mb_tx_uses_eicr = true, .mb_channels_are_unidir = true, .mb_calc_reg = mfis_mb_r8a78000_calc_reg, }; static const struct of_device_id mfis_mfd_of_match[] = { { .compatible = "renesas,r8a779g0-mfis", .data = &mfis_info_r8a779g0, }, { .compatible = "renesas,r8a779h0-mfis", .data = &mfis_info_r8a779g0, }, { .compatible = "renesas,r8a78000-mfis", .data = &mfis_info_r8a78000, }, { .compatible = "renesas,r8a78000-mfis-scp", .data = &mfis_info_r8a78000_scp, }, {} }; MODULE_DEVICE_TABLE(of, mfis_mfd_of_match); static struct platform_driver mfis_driver = { .driver = { .name = "rcar-mfis", .of_match_table = mfis_mfd_of_match, .suppress_bind_attrs = true, }, .probe = mfis_probe, }; module_platform_driver(mfis_driver); MODULE_AUTHOR("Kuninori Morimoto "); MODULE_AUTHOR("Wolfram Sang "); MODULE_LICENSE("GPL"); MODULE_DESCRIPTION("Renesas R-Car MFIS driver");