/* * SYNCPT DISPLAY CHANNEL DRIVER * * 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. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include #include #include #include #include #include #include #include #include #include #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 14, 0) #include #endif #include #include struct tegra_ivc_syncpt_display { wait_queue_head_t waitq; struct dentry *root; struct completion echo_complete; }; #define TEGRA_IVC_DC_SYNCPT_TIMEOUT_MS_DEFAULT 100 static int camrtc_syncpt_display_channel_echo(struct seq_file *file, void *data); #define INIT_OPEN_FOPS(_open) { \ .open = _open, \ .read = seq_read, \ .llseek = seq_lseek, \ .release = single_release \ } #define DEFINE_SEQ_FOPS(_fops_, _show_) \ static int _fops_ ## _open(struct inode *inode, struct file *file) \ { \ return single_open(file, _show_, inode->i_private); \ } \ static const struct file_operations _fops_ = INIT_OPEN_FOPS(_fops_ ## _open) static int camrtc_show_rtcpu_utils_echo(struct seq_file *file, void *data) { return camrtc_syncpt_display_channel_echo(file, data); } DEFINE_SEQ_FOPS(camrtc_dbgfs_fops_syncpt_display_channel_echo, camrtc_show_rtcpu_utils_echo); static int camrtc_syncpt_display_debugfs_init(struct tegra_ivc_channel *ch) { struct tegra_ivc_syncpt_display *drvData = tegra_ivc_channel_get_drvdata(ch); struct dentry *dir; char const *name = "rtcpu-utils"; drvData->root = dir = debugfs_create_dir(name, NULL); if (dir == NULL){ return -ENOMEM; } if (!debugfs_create_file("syncpt-echo", S_IRUGO, dir, ch, &camrtc_dbgfs_fops_syncpt_display_channel_echo)) { return -ENOMEM; } return 0; } static void tegra_ivc_channel_syncpt_display_recv(struct tegra_ivc_channel *chan, const void *data, size_t len) { struct tegra_ivc_syncpt_display *drvData = tegra_ivc_channel_get_drvdata(chan); complete(&drvData->echo_complete); } static void tegra_ivc_channel_syncpt_display_notify(struct tegra_ivc_channel *chan) { struct tegra_ivc_syncpt_display *drvData = tegra_ivc_channel_get_drvdata(chan); wake_up(&drvData->waitq); while (tegra_ivc_can_read(&chan->ivc)) { const void* data = tegra_ivc_read_get_next_frame(&chan->ivc); int length = chan->ivc.frame_size; tegra_ivc_channel_syncpt_display_recv(chan, data, length); tegra_ivc_read_advance(&chan->ivc); } } static int tegra_ivc_syncpt_display_channel_send(struct tegra_ivc_channel *chan, void *req, uint32_t req_length) { struct tegra_ivc_syncpt_display *drvData = tegra_ivc_channel_get_drvdata(chan); int ret = 0; long timeout = 0; timeout = wait_event_interruptible_timeout(drvData->waitq, tegra_ivc_can_write(&chan->ivc), timeout); if (timeout <= 0) { ret = timeout ?: -ETIMEDOUT; pr_err("%s: timeout (%ldms) for writing expired.\n", __func__, timeout); return ret; } ret = tegra_ivc_write(&chan->ivc, req, req_length); if (ret < 0) { pr_err("%s: tegra_ivc_write failed with %d.\n", __func__, ret); return ret; } return 0; } static int tegra_ivc_syncpt_display_channel_prepare(struct tegra_ivc_channel *chan) { int err = 0; err = tegra_ivc_channel_runtime_get(chan); if (err) tegra_ivc_channel_runtime_put(chan); return err; } static void tegra_ivc_syncpt_display_channel_complete(struct tegra_ivc_channel *chan) { tegra_ivc_channel_runtime_put(chan); } static int camrtc_syncpt_display_channel_echo(struct seq_file *file, void *data) { struct tegra_ivc_channel *ch = file->private; struct tegra_ivc_syncpt_display *drvData = tegra_ivc_channel_get_drvdata(ch); int ret = 0; u64 req = 0xDEADDEAD; u64 sent,recv; ret = tegra_ivc_syncpt_display_channel_prepare(ch); if(ret) return ret; sent = sched_clock(); init_completion(&drvData->echo_complete); ret = tegra_ivc_syncpt_display_channel_send(ch,&req,sizeof(req)); tegra_ivc_syncpt_display_channel_complete(ch); if(ret) { complete(&drvData->echo_complete); } else { ret = wait_for_completion_timeout(&drvData->echo_complete, TEGRA_IVC_DC_SYNCPT_TIMEOUT_MS_DEFAULT); } if (ret <= 0) { seq_printf(file, "Syncpt channel echo timeout expired "); } else { recv = sched_clock(); seq_printf(file, "roundtrip=%llu.%03llu us " "(sent=%llu.%09llu recv=%llu.%09llu)\n", (recv - sent) / 1000, (recv - sent) % 1000, sent / 1000000000, sent % 1000000000, recv / 1000000000, recv % 1000000000); } return ret; } static int tegra_ivc_channel_syncpt_display_probe(struct tegra_ivc_channel *chan) { struct tegra_ivc_syncpt_display *drvData = tegra_ivc_channel_get_drvdata(chan); drvData = devm_kzalloc(&chan->dev, sizeof(*drvData), GFP_KERNEL); if (unlikely(drvData == NULL)) return -ENOMEM; init_waitqueue_head(&drvData->waitq); tegra_ivc_channel_set_drvdata(chan, drvData); return camrtc_syncpt_display_debugfs_init(chan); } static void tegra_ivc_channel_syncpt_display_remove(struct tegra_ivc_channel *chan) { struct tegra_ivc_syncpt_display *drvData = tegra_ivc_channel_get_drvdata(chan); debugfs_remove_recursive(drvData->root); } static struct of_device_id tegra_ivc_channel_syncpt_display_of_match[] = { { .compatible = "nvidia,tegra-ivc-syncpt-disp" }, { }, }; static const struct tegra_ivc_channel_ops tegra_ivc_channel_syncpt_display_ops = { .probe = tegra_ivc_channel_syncpt_display_probe, .remove = tegra_ivc_channel_syncpt_display_remove, .notify = tegra_ivc_channel_syncpt_display_notify, }; static struct tegra_ivc_driver tegra_ivc_channel_syncpt_display_driver = { .driver = { .name = "tegra-ivc-syncpt-display", .bus = &tegra_ivc_bus_type, .owner = THIS_MODULE, .of_match_table = tegra_ivc_channel_syncpt_display_of_match, }, .dev_type = &tegra_ivc_channel_type, .ops.channel = &tegra_ivc_channel_syncpt_display_ops, }; tegra_ivc_module_driver(tegra_ivc_channel_syncpt_display_driver);