/* * Copyright (c) 2015-2017, NVIDIA CORPORATION. All rights reserved. * * This program is free software; you can redistribute it and/or modify it * under the terms and conditions of the GNU General Public License, * version 2, as published by the Free Software Foundation. * * This program is distributed in the hope it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* This has to be a multiple of the cache line size */ static inline int ivc_min_frame_size(void) { return cache_line_size(); } #define TEGRA_AON_HSP_DATA_ARRAY_SIZE 3 #define IPCBUF_SIZE 2097152 #define SMBOX_IVC_NOTIFY_MASK 0xFFFF #define SHRD_SEM_OFFSET 0x10000 #define SHRD_SEM_SET 0x4u #define SHRD_SEM_CLR 0x8u #define AON_SS_MAX 8 #define IVC_INIT_TIMEOUT_US (200000) enum smbox_msgs { SMBOX_IVC_READY_MSG = 0xAAAA5555, SMBOX_IVC_DBG_ENABLE = 0xAAAA6666, SMBOX_IVC_NOTIFY = 0x0000AABB, }; enum ivc_tasks_dbg_enable { IVC_TASKS_GLOBAL_DBG_ENABLE = 0, IVC_ECHO_TASK_DBG_ENABLE = 1, IVC_DBG_TASK_DBG_ENABLE = 2, IVC_SPI_TASK_DBG_ENABLE = 3, IVC_TASK_ENABLE_MAX = 4, IVC_TASKS_MAX = 31, IVC_TASKS_DBG_ENABLE_BIT = 31, }; struct tegra_aon { struct mbox_controller mbox; struct tegra_hsp_sm_pair *hsp_sm_pair; void __iomem *ss_base; void *ipcbuf; dma_addr_t ipcbuf_dma; size_t ipcbuf_size; u32 ivc_carveout_base_ss; u32 ivc_carveout_size_ss; u32 ivc_dbg_enable_ss; u32 ivc_tx_ss; u32 ivc_rx_ss; }; struct tegra_aon_ivc_chan { struct ivc ivc; char *name; int chan_id; struct tegra_aon *aon; bool last_tx_done; }; static void __iomem *tegra_aon_hsp_ss_reg(const struct tegra_aon *aon, u32 ss) { return aon->ss_base + (SHRD_SEM_OFFSET * ss); } static u32 tegra_aon_hsp_ss_status(const struct tegra_aon *aon, u32 ss) { void __iomem *reg; WARN_ON(ss >= AON_SS_MAX); reg = tegra_aon_hsp_ss_reg(aon, ss); return readl(reg); } static void tegra_aon_hsp_ss_set(const struct tegra_aon *aon, u32 ss, u32 bits) { void __iomem *reg; WARN_ON(ss >= AON_SS_MAX); reg = tegra_aon_hsp_ss_reg(aon, ss); writel(bits, reg + SHRD_SEM_SET); } static void tegra_aon_hsp_ss_clr(const struct tegra_aon *aon, u32 ss, u32 bits) { void __iomem *reg; WARN_ON(ss >= AON_SS_MAX); reg = tegra_aon_hsp_ss_reg(aon, ss); writel(bits, reg + SHRD_SEM_CLR); } static void tegra_aon_notify_remote(struct ivc *ivc) { struct tegra_aon_ivc_chan *ivc_chan; ivc_chan = container_of(ivc, struct tegra_aon_ivc_chan, ivc); tegra_aon_hsp_ss_set(ivc_chan->aon, ivc_chan->aon->ivc_tx_ss, BIT(ivc_chan->chan_id)); tegra_hsp_sm_pair_write(ivc_chan->aon->hsp_sm_pair, SMBOX_IVC_NOTIFY); } static void tegra_aon_rx_handler(struct tegra_aon *aon, u32 ivc_chans) { struct mbox_chan *mbox_chan; struct ivc *ivc; struct tegra_aon_ivc_chan *ivc_chan; struct tegra_aon_mbox_msg msg; int i; ivc_chans &= BIT(aon->mbox.num_chans) - 1; while (ivc_chans) { i = __builtin_ctz(ivc_chans); ivc_chans &= ~BIT(i); mbox_chan = &aon->mbox.chans[i]; ivc_chan = (struct tegra_aon_ivc_chan *)mbox_chan->con_priv; /* check if mailbox client exists */ if (ivc_chan->chan_id == -1) continue; ivc = &ivc_chan->ivc; while (tegra_ivc_can_read(ivc)) { msg.data = tegra_ivc_read_get_next_frame(ivc); msg.length = ivc->frame_size; mbox_chan_received_data(mbox_chan, &msg); tegra_ivc_read_advance(ivc); } } } static u32 tegra_aon_hsp_sm_full_notify(void *data, u32 value) { struct tegra_aon *aon = data; u32 ss_val; if (value != SMBOX_IVC_NOTIFY) { dev_err(aon->mbox.dev, "Invalid IVC notification\n"); return 0; } ss_val = tegra_aon_hsp_ss_status(aon, aon->ivc_rx_ss); tegra_aon_hsp_ss_clr(aon, aon->ivc_rx_ss, ss_val); tegra_aon_rx_handler(aon, ss_val); return 0; } #define NV(p) "nvidia," p static int tegra_aon_parse_channel(struct device *dev, struct mbox_chan *mbox_chan, struct device_node *ch_node, int chan_id) { struct tegra_aon *aon; struct tegra_aon_ivc_chan *ivc_chan; struct { u32 rx, tx; } start, end; u32 nframes, frame_size; int ret = 0; aon = platform_get_drvdata(to_platform_device(dev)); /* Sanity check */ if (!mbox_chan || !ch_node || !dev || !aon) return -EINVAL; ret = of_property_read_u32_array(ch_node, "reg", &start.rx, 2); if (ret) { dev_err(dev, "missing <%s> property\n", "reg"); return ret; } ret = of_property_read_u32(ch_node, NV("frame-count"), &nframes); if (ret) { dev_err(dev, "missing <%s> property\n", NV("frame-count")); return ret; } ret = of_property_read_u32(ch_node, NV("frame-size"), &frame_size); if (ret) { dev_err(dev, "missing <%s> property\n", NV("frame-size")); return ret; } if (!nframes) { dev_err(dev, "Invalid property\n"); return -EINVAL; } if (frame_size < ivc_min_frame_size()) { dev_err(dev, "Invalid property\n"); return -EINVAL; } end.rx = start.rx + tegra_ivc_total_queue_size(nframes * frame_size); end.tx = start.tx + tegra_ivc_total_queue_size(nframes * frame_size); if (end.rx > aon->ipcbuf_size) { dev_err(dev, "%s buffer exceeds ivc size\n", "rx"); return -EINVAL; } if (end.tx > aon->ipcbuf_size) { dev_err(dev, "%s buffer exceeds ivc size\n", "tx"); return -EINVAL; } if (start.tx < start.rx ? end.tx > start.rx : end.rx > start.tx) { dev_err(dev, "rx and tx buffers overlap on channel %s\n", ch_node->name); return -EINVAL; } ivc_chan = devm_kzalloc(dev, sizeof(*ivc_chan), GFP_KERNEL); if (!ivc_chan) { dev_err(dev, "Failed to allocate AON IVC channel\n"); return -ENOMEM; } ivc_chan->name = devm_kstrdup(dev, ch_node->name, GFP_KERNEL); if (!ivc_chan->name) return -ENOMEM; ivc_chan->chan_id = chan_id; /* Allocate the IVC links */ ret = tegra_ivc_init_with_dma_handle(&ivc_chan->ivc, (unsigned long)aon->ipcbuf + start.rx, (u64)aon->ipcbuf_dma + start.rx, (unsigned long)aon->ipcbuf + start.tx, (u64)aon->ipcbuf_dma + start.tx, nframes, frame_size, dev, tegra_aon_notify_remote); if (ret) { dev_err(dev, "failed to instantiate IVC.\n"); return ret; } ivc_chan->aon = aon; mbox_chan->con_priv = ivc_chan; dev_dbg(dev, "%s: RX: 0x%x-0x%x TX: 0x%x-0x%x\n", ivc_chan->name, start.rx, end.rx, start.tx, end.tx); return ret; } static int tegra_aon_check_channels_overlap(struct device *dev, struct tegra_aon_ivc_chan *ch0, struct tegra_aon_ivc_chan *ch1) { unsigned s0, s1; unsigned long tx0, rx0, tx1, rx1; if (ch0 == NULL || ch1 == NULL) return -EINVAL; tx0 = (unsigned long)ch0->ivc.tx_channel; rx0 = (unsigned long)ch0->ivc.rx_channel; s0 = ch0->ivc.nframes * ch0->ivc.frame_size; s0 = tegra_ivc_total_queue_size(s0); tx1 = (unsigned long)ch1->ivc.tx_channel; rx1 = (unsigned long)ch1->ivc.rx_channel; s1 = ch1->ivc.nframes * ch1->ivc.frame_size; s1 = tegra_ivc_total_queue_size(s1); if ((tx0 < tx1 ? tx0 + s0 > tx1 : tx1 + s1 > tx0) || (rx0 < tx1 ? rx0 + s0 > tx1 : tx1 + s1 > rx0) || (rx0 < rx1 ? rx0 + s0 > rx1 : rx1 + s1 > rx0) || (tx0 < rx1 ? tx0 + s0 > rx1 : rx1 + s1 > tx0)) { dev_err(dev, "ivc buffers overlap on channels %s and %s\n", ch0->name, ch1->name); return -EINVAL; } return 0; } static int tegra_aon_validate_channels(struct device *dev) { struct tegra_aon *aon; struct tegra_aon_ivc_chan *i_chan, *j_chan; int i, j; int ret; aon = dev_get_drvdata(dev); for (i = 0; i < aon->mbox.num_chans; i++) { i_chan = aon->mbox.chans[i].con_priv; for (j = i + 1; j < aon->mbox.num_chans; j++) { j_chan = aon->mbox.chans[j].con_priv; ret = tegra_aon_check_channels_overlap(dev, i_chan, j_chan); if (ret) return ret; } } return 0; } static int tegra_aon_parse_channels(struct device *dev) { struct tegra_aon *aon; struct device_node *reg_node, *ch_node; int ret, i; aon = dev_get_drvdata(dev); i = 0; for_each_child_of_node(dev->of_node, reg_node) { if (strcmp(reg_node->name, "ivc-channels")) continue; for_each_child_of_node(reg_node, ch_node) { ret = tegra_aon_parse_channel(dev, &aon->mbox.chans[i], ch_node, i); i++; if (ret) { dev_err(dev, "failed to parse a channel\n"); return ret; } } break; } return tegra_aon_validate_channels(dev); } static int tegra_aon_mbox_get_max_txsize(struct mbox_chan *mbox_chan) { struct tegra_aon_ivc_chan *ivc_chan; ivc_chan = (struct tegra_aon_ivc_chan *)mbox_chan->con_priv; return ivc_chan->ivc.frame_size; } static int tegra_aon_mbox_send_data(struct mbox_chan *mbox_chan, void *data) { struct tegra_aon_ivc_chan *ivc_chan; struct tegra_aon_mbox_msg *msg; int bytes; int ret; msg = (struct tegra_aon_mbox_msg *)data; ivc_chan = (struct tegra_aon_ivc_chan *)mbox_chan->con_priv; bytes = tegra_ivc_write(&ivc_chan->ivc, msg->data, msg->length); ret = (bytes != msg->length) ? -EBUSY : 0; if (bytes < 0) { pr_err("%s mbox send failed with error %d\n", __func__, bytes); ret = bytes; } ivc_chan->last_tx_done = (ret == 0); return ret; } static int tegra_aon_mbox_startup(struct mbox_chan *mbox_chan) { return 0; } static void tegra_aon_mbox_shutdown(struct mbox_chan *mbox_chan) { struct tegra_aon_ivc_chan *ivc_chan; ivc_chan = (struct tegra_aon_ivc_chan *)mbox_chan->con_priv; ivc_chan->chan_id = -1; } static bool tegra_aon_mbox_last_tx_done(struct mbox_chan *mbox_chan) { struct tegra_aon_ivc_chan *ivc_chan; ivc_chan = (struct tegra_aon_ivc_chan *)mbox_chan->con_priv; return ivc_chan->last_tx_done; } static struct mbox_chan_ops tegra_aon_mbox_chan_ops = { .get_max_txsize = tegra_aon_mbox_get_max_txsize, .send_data = tegra_aon_mbox_send_data, .startup = tegra_aon_mbox_startup, .shutdown = tegra_aon_mbox_shutdown, .last_tx_done = tegra_aon_mbox_last_tx_done, }; static int tegra_aon_count_ivc_channels(struct device_node *dev_node) { int num = 0; struct device_node *child_node; for_each_child_of_node(dev_node, child_node) { if (strcmp(child_node->name, "ivc-channels")) continue; num = of_get_child_count(child_node); break; } return num; } static ssize_t store_ivc_dbg(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct tegra_aon *aon = dev_get_drvdata(dev); u32 shrdsem_msg; u32 channel; u32 enable; int ret; if (count > ivc_min_frame_size()) return -EINVAL; ret = kstrtouint(buf, 0, &channel); if (ret) return -EINVAL; enable = channel & BIT(IVC_TASKS_DBG_ENABLE_BIT); channel &= ~BIT(IVC_TASKS_DBG_ENABLE_BIT); if (channel >= BIT(IVC_TASK_ENABLE_MAX)) return -EINVAL; shrdsem_msg = channel | enable; tegra_aon_hsp_ss_set(aon, aon->ivc_dbg_enable_ss, shrdsem_msg); tegra_hsp_sm_pair_write(aon->hsp_sm_pair, SMBOX_IVC_DBG_ENABLE); return count; } static DEVICE_ATTR(ivc_dbg, S_IWUSR, NULL, store_ivc_dbg); static int tegra_aon_probe(struct platform_device *pdev) { struct tegra_aon *aon; struct device *dev = &pdev->dev; struct device_node *dn = dev->of_node; int num_chans; int ret = 0; ktime_t tstart; dev_dbg(&pdev->dev, "tegra aon driver probe Start\n"); aon = devm_kzalloc(&pdev->dev, sizeof(*aon), GFP_KERNEL); if (!aon) return -ENOMEM; platform_set_drvdata(pdev, aon); aon->ipcbuf_size = IPCBUF_SIZE; aon->ipcbuf = dmam_alloc_coherent(dev, aon->ipcbuf_size, &aon->ipcbuf_dma, GFP_KERNEL | __GFP_ZERO); if (!aon->ipcbuf) { dev_err(dev, "failed to allocate IPC memory\n"); return -ENOMEM; } aon->ss_base = of_iomap(dn, 0); if (!aon->ss_base) { dev_err(dev, "failed to map shared semaphore IO space\n"); return -EINVAL; } ret = of_property_read_u32(dn, NV("ivc-carveout-base-ss"), &aon->ivc_carveout_base_ss); if (ret) { dev_err(dev, "missing <%s> property\n", NV("ivc-carveout-base-ss")); return ret; } ret = of_property_read_u32(dn, NV("ivc-carveout-size-ss"), &aon->ivc_carveout_size_ss); if (ret) { dev_err(dev, "missing <%s> property\n", NV("ivc-carveout-size-ss")); return ret; } ret = of_property_read_u32(dn, NV("ivc-dbg-enable-ss"), &aon->ivc_dbg_enable_ss); if (ret) { dev_err(dev, "missing <%s> property\n", NV("ivc-dbg-enable-ss")); return ret; } ret = of_property_read_u32(dn, NV("ivc-rx-ss"), &aon->ivc_rx_ss); if (ret) { dev_err(dev, "missing <%s> property\n", NV("ivc-rx-ss")); return ret; } ret = of_property_read_u32(dn, NV("ivc-tx-ss"), &aon->ivc_tx_ss); if (ret) { dev_err(dev, "missing <%s> property\n", NV("ivc-tx-ss")); return ret; } num_chans = tegra_aon_count_ivc_channels(dn); if (num_chans <= 0) { dev_err(dev, "no ivc channels\n"); ret = -EINVAL; goto exit; } aon->mbox.dev = &pdev->dev; aon->mbox.chans = devm_kzalloc(&pdev->dev, num_chans * sizeof(*aon->mbox.chans), GFP_KERNEL); if (!aon->mbox.chans) { ret = -ENOMEM; goto exit; } aon->mbox.num_chans = num_chans; aon->mbox.ops = &tegra_aon_mbox_chan_ops; aon->mbox.txdone_poll = true; aon->mbox.txpoll_period = 1; /* Parse out all channels from DT */ ret = tegra_aon_parse_channels(dev); if (ret) { dev_err(dev, "ivc-channels set up failed: %d\n", ret); goto exit; } /* Fetch the shared mailbox pair associated with IVC tx and rx */ aon->hsp_sm_pair = of_tegra_hsp_sm_pair_by_name(dn, "ivc-pair", tegra_aon_hsp_sm_full_notify, NULL, aon); if (IS_ERR(aon->hsp_sm_pair)) { ret = PTR_ERR(aon->hsp_sm_pair); if (ret != -EPROBE_DEFER) dev_err(dev, "failed to fetch mailbox pair : %d\n", ret); goto exit; } ret = device_create_file(dev, &dev_attr_ivc_dbg); if (ret) { dev_err(dev, "failed to create device file: %d\n", ret); tegra_hsp_sm_pair_free(aon->hsp_sm_pair); mbox_controller_unregister(&aon->mbox); goto exit; } tegra_aon_hsp_ss_set(aon, aon->ivc_carveout_base_ss, (u32)aon->ipcbuf_dma); tegra_aon_hsp_ss_set(aon, aon->ivc_carveout_size_ss, (u32)aon->ipcbuf_size); tegra_hsp_sm_pair_write(aon->hsp_sm_pair, SMBOX_IVC_READY_MSG); tstart = ktime_get(); while (!tegra_hsp_sm_pair_is_empty(aon->hsp_sm_pair)) { if (ktime_us_delta(ktime_get(), tstart) > IVC_INIT_TIMEOUT_US) { dev_err(dev, "IVC init timeout\n"); tegra_hsp_sm_pair_free(aon->hsp_sm_pair); ret = -ETIMEDOUT; goto exit; } } ret = mbox_controller_register(&aon->mbox); if (ret) { dev_err(&pdev->dev, "failed to register mailbox: %d\n", ret); tegra_hsp_sm_pair_free(aon->hsp_sm_pair); goto exit; } dev_info(&pdev->dev, "tegra aon driver probe OK\n"); return ret; exit: iounmap(aon->ss_base); return ret; } static int tegra_aon_remove(struct platform_device *pdev) { struct tegra_aon *aon = platform_get_drvdata(pdev); iounmap(aon->ss_base); mbox_controller_unregister(&aon->mbox); tegra_hsp_sm_pair_free(aon->hsp_sm_pair); return 0; } static const struct of_device_id tegra_aon_of_match[] = { { .compatible = NV("tegra186-aon"), }, {}, }; MODULE_DEVICE_TABLE(of, tegra_aon_of_match); static struct platform_driver tegra_aon_driver = { .probe = tegra_aon_probe, .remove = tegra_aon_remove, .driver = { .owner = THIS_MODULE, .name = "tegra_aon", .of_match_table = of_match_ptr(tegra_aon_of_match), }, }; module_platform_driver(tegra_aon_driver);