18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * Copyright (c) 2015, Fuzhou Rockchip Electronics Co., Ltd 48c2ecf20Sopenharmony_ci */ 58c2ecf20Sopenharmony_ci 68c2ecf20Sopenharmony_ci#include <linux/clk.h> 78c2ecf20Sopenharmony_ci#include <linux/interrupt.h> 88c2ecf20Sopenharmony_ci#include <linux/io.h> 98c2ecf20Sopenharmony_ci#include <linux/kernel.h> 108c2ecf20Sopenharmony_ci#include <linux/mailbox_controller.h> 118c2ecf20Sopenharmony_ci#include <linux/module.h> 128c2ecf20Sopenharmony_ci#include <linux/of_device.h> 138c2ecf20Sopenharmony_ci#include <linux/platform_device.h> 148c2ecf20Sopenharmony_ci 158c2ecf20Sopenharmony_ci#define MAILBOX_A2B_INTEN 0x00 168c2ecf20Sopenharmony_ci#define MAILBOX_A2B_STATUS 0x04 178c2ecf20Sopenharmony_ci#define MAILBOX_A2B_CMD(x) (0x08 + (x) * 8) 188c2ecf20Sopenharmony_ci#define MAILBOX_A2B_DAT(x) (0x0c + (x) * 8) 198c2ecf20Sopenharmony_ci 208c2ecf20Sopenharmony_ci#define MAILBOX_B2A_INTEN 0x28 218c2ecf20Sopenharmony_ci#define MAILBOX_B2A_STATUS 0x2C 228c2ecf20Sopenharmony_ci#define MAILBOX_B2A_CMD(x) (0x30 + (x) * 8) 238c2ecf20Sopenharmony_ci#define MAILBOX_B2A_DAT(x) (0x34 + (x) * 8) 248c2ecf20Sopenharmony_ci 258c2ecf20Sopenharmony_cistruct rockchip_mbox_msg { 268c2ecf20Sopenharmony_ci u32 cmd; 278c2ecf20Sopenharmony_ci int rx_size; 288c2ecf20Sopenharmony_ci}; 298c2ecf20Sopenharmony_ci 308c2ecf20Sopenharmony_cistruct rockchip_mbox_data { 318c2ecf20Sopenharmony_ci int num_chans; 328c2ecf20Sopenharmony_ci}; 338c2ecf20Sopenharmony_ci 348c2ecf20Sopenharmony_cistruct rockchip_mbox_chan { 358c2ecf20Sopenharmony_ci int idx; 368c2ecf20Sopenharmony_ci int irq; 378c2ecf20Sopenharmony_ci struct rockchip_mbox_msg *msg; 388c2ecf20Sopenharmony_ci struct rockchip_mbox *mb; 398c2ecf20Sopenharmony_ci}; 408c2ecf20Sopenharmony_ci 418c2ecf20Sopenharmony_cistruct rockchip_mbox { 428c2ecf20Sopenharmony_ci struct mbox_controller mbox; 438c2ecf20Sopenharmony_ci struct clk *pclk; 448c2ecf20Sopenharmony_ci void __iomem *mbox_base; 458c2ecf20Sopenharmony_ci 468c2ecf20Sopenharmony_ci /* The maximum size of buf for each channel */ 478c2ecf20Sopenharmony_ci u32 buf_size; 488c2ecf20Sopenharmony_ci 498c2ecf20Sopenharmony_ci struct rockchip_mbox_chan *chans; 508c2ecf20Sopenharmony_ci}; 518c2ecf20Sopenharmony_ci 528c2ecf20Sopenharmony_cistatic int rockchip_mbox_send_data(struct mbox_chan *chan, void *data) 538c2ecf20Sopenharmony_ci{ 548c2ecf20Sopenharmony_ci struct rockchip_mbox *mb = dev_get_drvdata(chan->mbox->dev); 558c2ecf20Sopenharmony_ci struct rockchip_mbox_msg *msg = data; 568c2ecf20Sopenharmony_ci struct rockchip_mbox_chan *chans = mb->chans; 578c2ecf20Sopenharmony_ci 588c2ecf20Sopenharmony_ci if (!msg) 598c2ecf20Sopenharmony_ci return -EINVAL; 608c2ecf20Sopenharmony_ci 618c2ecf20Sopenharmony_ci if (msg->rx_size > mb->buf_size) { 628c2ecf20Sopenharmony_ci dev_err(mb->mbox.dev, "Transmit size over buf size(%d)\n", 638c2ecf20Sopenharmony_ci mb->buf_size); 648c2ecf20Sopenharmony_ci return -EINVAL; 658c2ecf20Sopenharmony_ci } 668c2ecf20Sopenharmony_ci 678c2ecf20Sopenharmony_ci dev_dbg(mb->mbox.dev, "Chan[%d]: A2B message, cmd 0x%08x\n", 688c2ecf20Sopenharmony_ci chans->idx, msg->cmd); 698c2ecf20Sopenharmony_ci 708c2ecf20Sopenharmony_ci mb->chans[chans->idx].msg = msg; 718c2ecf20Sopenharmony_ci 728c2ecf20Sopenharmony_ci writel_relaxed(msg->cmd, mb->mbox_base + MAILBOX_A2B_CMD(chans->idx)); 738c2ecf20Sopenharmony_ci writel_relaxed(msg->rx_size, mb->mbox_base + 748c2ecf20Sopenharmony_ci MAILBOX_A2B_DAT(chans->idx)); 758c2ecf20Sopenharmony_ci 768c2ecf20Sopenharmony_ci return 0; 778c2ecf20Sopenharmony_ci} 788c2ecf20Sopenharmony_ci 798c2ecf20Sopenharmony_cistatic int rockchip_mbox_startup(struct mbox_chan *chan) 808c2ecf20Sopenharmony_ci{ 818c2ecf20Sopenharmony_ci struct rockchip_mbox *mb = dev_get_drvdata(chan->mbox->dev); 828c2ecf20Sopenharmony_ci 838c2ecf20Sopenharmony_ci /* Enable all B2A interrupts */ 848c2ecf20Sopenharmony_ci writel_relaxed((1 << mb->mbox.num_chans) - 1, 858c2ecf20Sopenharmony_ci mb->mbox_base + MAILBOX_B2A_INTEN); 868c2ecf20Sopenharmony_ci 878c2ecf20Sopenharmony_ci return 0; 888c2ecf20Sopenharmony_ci} 898c2ecf20Sopenharmony_ci 908c2ecf20Sopenharmony_cistatic void rockchip_mbox_shutdown(struct mbox_chan *chan) 918c2ecf20Sopenharmony_ci{ 928c2ecf20Sopenharmony_ci struct rockchip_mbox *mb = dev_get_drvdata(chan->mbox->dev); 938c2ecf20Sopenharmony_ci struct rockchip_mbox_chan *chans = mb->chans; 948c2ecf20Sopenharmony_ci 958c2ecf20Sopenharmony_ci /* Disable all B2A interrupts */ 968c2ecf20Sopenharmony_ci writel_relaxed(0, mb->mbox_base + MAILBOX_B2A_INTEN); 978c2ecf20Sopenharmony_ci 988c2ecf20Sopenharmony_ci mb->chans[chans->idx].msg = NULL; 998c2ecf20Sopenharmony_ci} 1008c2ecf20Sopenharmony_ci 1018c2ecf20Sopenharmony_cistatic const struct mbox_chan_ops rockchip_mbox_chan_ops = { 1028c2ecf20Sopenharmony_ci .send_data = rockchip_mbox_send_data, 1038c2ecf20Sopenharmony_ci .startup = rockchip_mbox_startup, 1048c2ecf20Sopenharmony_ci .shutdown = rockchip_mbox_shutdown, 1058c2ecf20Sopenharmony_ci}; 1068c2ecf20Sopenharmony_ci 1078c2ecf20Sopenharmony_cistatic irqreturn_t rockchip_mbox_irq(int irq, void *dev_id) 1088c2ecf20Sopenharmony_ci{ 1098c2ecf20Sopenharmony_ci int idx; 1108c2ecf20Sopenharmony_ci struct rockchip_mbox *mb = (struct rockchip_mbox *)dev_id; 1118c2ecf20Sopenharmony_ci u32 status = readl_relaxed(mb->mbox_base + MAILBOX_B2A_STATUS); 1128c2ecf20Sopenharmony_ci 1138c2ecf20Sopenharmony_ci for (idx = 0; idx < mb->mbox.num_chans; idx++) { 1148c2ecf20Sopenharmony_ci if ((status & (1 << idx)) && (irq == mb->chans[idx].irq)) { 1158c2ecf20Sopenharmony_ci /* Clear mbox interrupt */ 1168c2ecf20Sopenharmony_ci writel_relaxed(1 << idx, 1178c2ecf20Sopenharmony_ci mb->mbox_base + MAILBOX_B2A_STATUS); 1188c2ecf20Sopenharmony_ci return IRQ_WAKE_THREAD; 1198c2ecf20Sopenharmony_ci } 1208c2ecf20Sopenharmony_ci } 1218c2ecf20Sopenharmony_ci 1228c2ecf20Sopenharmony_ci return IRQ_NONE; 1238c2ecf20Sopenharmony_ci} 1248c2ecf20Sopenharmony_ci 1258c2ecf20Sopenharmony_cistatic irqreturn_t rockchip_mbox_isr(int irq, void *dev_id) 1268c2ecf20Sopenharmony_ci{ 1278c2ecf20Sopenharmony_ci int idx; 1288c2ecf20Sopenharmony_ci struct rockchip_mbox_msg *msg = NULL; 1298c2ecf20Sopenharmony_ci struct rockchip_mbox *mb = (struct rockchip_mbox *)dev_id; 1308c2ecf20Sopenharmony_ci 1318c2ecf20Sopenharmony_ci for (idx = 0; idx < mb->mbox.num_chans; idx++) { 1328c2ecf20Sopenharmony_ci if (irq != mb->chans[idx].irq) 1338c2ecf20Sopenharmony_ci continue; 1348c2ecf20Sopenharmony_ci 1358c2ecf20Sopenharmony_ci msg = mb->chans[idx].msg; 1368c2ecf20Sopenharmony_ci if (!msg) { 1378c2ecf20Sopenharmony_ci dev_err(mb->mbox.dev, 1388c2ecf20Sopenharmony_ci "Chan[%d]: B2A message is NULL\n", idx); 1398c2ecf20Sopenharmony_ci break; /* spurious */ 1408c2ecf20Sopenharmony_ci } 1418c2ecf20Sopenharmony_ci 1428c2ecf20Sopenharmony_ci mbox_chan_received_data(&mb->mbox.chans[idx], msg); 1438c2ecf20Sopenharmony_ci mb->chans[idx].msg = NULL; 1448c2ecf20Sopenharmony_ci 1458c2ecf20Sopenharmony_ci dev_dbg(mb->mbox.dev, "Chan[%d]: B2A message, cmd 0x%08x\n", 1468c2ecf20Sopenharmony_ci idx, msg->cmd); 1478c2ecf20Sopenharmony_ci 1488c2ecf20Sopenharmony_ci break; 1498c2ecf20Sopenharmony_ci } 1508c2ecf20Sopenharmony_ci 1518c2ecf20Sopenharmony_ci return IRQ_HANDLED; 1528c2ecf20Sopenharmony_ci} 1538c2ecf20Sopenharmony_ci 1548c2ecf20Sopenharmony_cistatic const struct rockchip_mbox_data rk3368_drv_data = { 1558c2ecf20Sopenharmony_ci .num_chans = 4, 1568c2ecf20Sopenharmony_ci}; 1578c2ecf20Sopenharmony_ci 1588c2ecf20Sopenharmony_cistatic const struct of_device_id rockchip_mbox_of_match[] = { 1598c2ecf20Sopenharmony_ci { .compatible = "rockchip,rk3368-mailbox", .data = &rk3368_drv_data}, 1608c2ecf20Sopenharmony_ci { }, 1618c2ecf20Sopenharmony_ci}; 1628c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(of, rockchp_mbox_of_match); 1638c2ecf20Sopenharmony_ci 1648c2ecf20Sopenharmony_cistatic int rockchip_mbox_probe(struct platform_device *pdev) 1658c2ecf20Sopenharmony_ci{ 1668c2ecf20Sopenharmony_ci struct rockchip_mbox *mb; 1678c2ecf20Sopenharmony_ci const struct of_device_id *match; 1688c2ecf20Sopenharmony_ci const struct rockchip_mbox_data *drv_data; 1698c2ecf20Sopenharmony_ci struct resource *res; 1708c2ecf20Sopenharmony_ci int ret, irq, i; 1718c2ecf20Sopenharmony_ci 1728c2ecf20Sopenharmony_ci if (!pdev->dev.of_node) 1738c2ecf20Sopenharmony_ci return -ENODEV; 1748c2ecf20Sopenharmony_ci 1758c2ecf20Sopenharmony_ci match = of_match_node(rockchip_mbox_of_match, pdev->dev.of_node); 1768c2ecf20Sopenharmony_ci drv_data = (const struct rockchip_mbox_data *)match->data; 1778c2ecf20Sopenharmony_ci 1788c2ecf20Sopenharmony_ci mb = devm_kzalloc(&pdev->dev, sizeof(*mb), GFP_KERNEL); 1798c2ecf20Sopenharmony_ci if (!mb) 1808c2ecf20Sopenharmony_ci return -ENOMEM; 1818c2ecf20Sopenharmony_ci 1828c2ecf20Sopenharmony_ci mb->chans = devm_kcalloc(&pdev->dev, drv_data->num_chans, 1838c2ecf20Sopenharmony_ci sizeof(*mb->chans), GFP_KERNEL); 1848c2ecf20Sopenharmony_ci if (!mb->chans) 1858c2ecf20Sopenharmony_ci return -ENOMEM; 1868c2ecf20Sopenharmony_ci 1878c2ecf20Sopenharmony_ci mb->mbox.chans = devm_kcalloc(&pdev->dev, drv_data->num_chans, 1888c2ecf20Sopenharmony_ci sizeof(*mb->mbox.chans), GFP_KERNEL); 1898c2ecf20Sopenharmony_ci if (!mb->mbox.chans) 1908c2ecf20Sopenharmony_ci return -ENOMEM; 1918c2ecf20Sopenharmony_ci 1928c2ecf20Sopenharmony_ci platform_set_drvdata(pdev, mb); 1938c2ecf20Sopenharmony_ci 1948c2ecf20Sopenharmony_ci mb->mbox.dev = &pdev->dev; 1958c2ecf20Sopenharmony_ci mb->mbox.num_chans = drv_data->num_chans; 1968c2ecf20Sopenharmony_ci mb->mbox.ops = &rockchip_mbox_chan_ops; 1978c2ecf20Sopenharmony_ci mb->mbox.txdone_irq = true; 1988c2ecf20Sopenharmony_ci 1998c2ecf20Sopenharmony_ci res = platform_get_resource(pdev, IORESOURCE_MEM, 0); 2008c2ecf20Sopenharmony_ci if (!res) 2018c2ecf20Sopenharmony_ci return -ENODEV; 2028c2ecf20Sopenharmony_ci 2038c2ecf20Sopenharmony_ci mb->mbox_base = devm_ioremap_resource(&pdev->dev, res); 2048c2ecf20Sopenharmony_ci if (IS_ERR(mb->mbox_base)) 2058c2ecf20Sopenharmony_ci return PTR_ERR(mb->mbox_base); 2068c2ecf20Sopenharmony_ci 2078c2ecf20Sopenharmony_ci /* Each channel has two buffers for A2B and B2A */ 2088c2ecf20Sopenharmony_ci mb->buf_size = (size_t)resource_size(res) / (drv_data->num_chans * 2); 2098c2ecf20Sopenharmony_ci 2108c2ecf20Sopenharmony_ci mb->pclk = devm_clk_get(&pdev->dev, "pclk_mailbox"); 2118c2ecf20Sopenharmony_ci if (IS_ERR(mb->pclk)) { 2128c2ecf20Sopenharmony_ci ret = PTR_ERR(mb->pclk); 2138c2ecf20Sopenharmony_ci dev_err(&pdev->dev, "failed to get pclk_mailbox clock: %d\n", 2148c2ecf20Sopenharmony_ci ret); 2158c2ecf20Sopenharmony_ci return ret; 2168c2ecf20Sopenharmony_ci } 2178c2ecf20Sopenharmony_ci 2188c2ecf20Sopenharmony_ci ret = clk_prepare_enable(mb->pclk); 2198c2ecf20Sopenharmony_ci if (ret) { 2208c2ecf20Sopenharmony_ci dev_err(&pdev->dev, "failed to enable pclk: %d\n", ret); 2218c2ecf20Sopenharmony_ci return ret; 2228c2ecf20Sopenharmony_ci } 2238c2ecf20Sopenharmony_ci 2248c2ecf20Sopenharmony_ci for (i = 0; i < mb->mbox.num_chans; i++) { 2258c2ecf20Sopenharmony_ci irq = platform_get_irq(pdev, i); 2268c2ecf20Sopenharmony_ci if (irq < 0) 2278c2ecf20Sopenharmony_ci return irq; 2288c2ecf20Sopenharmony_ci 2298c2ecf20Sopenharmony_ci ret = devm_request_threaded_irq(&pdev->dev, irq, 2308c2ecf20Sopenharmony_ci rockchip_mbox_irq, 2318c2ecf20Sopenharmony_ci rockchip_mbox_isr, IRQF_ONESHOT, 2328c2ecf20Sopenharmony_ci dev_name(&pdev->dev), mb); 2338c2ecf20Sopenharmony_ci if (ret < 0) 2348c2ecf20Sopenharmony_ci return ret; 2358c2ecf20Sopenharmony_ci 2368c2ecf20Sopenharmony_ci mb->chans[i].idx = i; 2378c2ecf20Sopenharmony_ci mb->chans[i].irq = irq; 2388c2ecf20Sopenharmony_ci mb->chans[i].mb = mb; 2398c2ecf20Sopenharmony_ci mb->chans[i].msg = NULL; 2408c2ecf20Sopenharmony_ci } 2418c2ecf20Sopenharmony_ci 2428c2ecf20Sopenharmony_ci ret = devm_mbox_controller_register(&pdev->dev, &mb->mbox); 2438c2ecf20Sopenharmony_ci if (ret < 0) 2448c2ecf20Sopenharmony_ci dev_err(&pdev->dev, "Failed to register mailbox: %d\n", ret); 2458c2ecf20Sopenharmony_ci 2468c2ecf20Sopenharmony_ci return ret; 2478c2ecf20Sopenharmony_ci} 2488c2ecf20Sopenharmony_ci 2498c2ecf20Sopenharmony_cistatic struct platform_driver rockchip_mbox_driver = { 2508c2ecf20Sopenharmony_ci .probe = rockchip_mbox_probe, 2518c2ecf20Sopenharmony_ci .driver = { 2528c2ecf20Sopenharmony_ci .name = "rockchip-mailbox", 2538c2ecf20Sopenharmony_ci .of_match_table = of_match_ptr(rockchip_mbox_of_match), 2548c2ecf20Sopenharmony_ci }, 2558c2ecf20Sopenharmony_ci}; 2568c2ecf20Sopenharmony_ci 2578c2ecf20Sopenharmony_cimodule_platform_driver(rockchip_mbox_driver); 2588c2ecf20Sopenharmony_ci 2598c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL v2"); 2608c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("Rockchip mailbox: communicate between CPU cores and MCU"); 2618c2ecf20Sopenharmony_ciMODULE_AUTHOR("Addy Ke <addy.ke@rock-chips.com>"); 2628c2ecf20Sopenharmony_ciMODULE_AUTHOR("Caesar Wang <wxt@rock-chips.com>"); 263