/* * NVCSI driver for T194 * * Copyright (c) 2017-2019, 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 "nvcsi-t194.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "camera/nvcsi/csi5_fops.h" #include "dev.h" #include "bus_client.h" #include "nvhost_acm.h" #include "t194/t194.h" #include "media/csi.h" #include "deskew.h" #include "vhost/vhost.h" /* PG rate based on max ISP throughput */ #define PG_CLK_RATE 102000000 /* width of interface between VI and CSI */ #define CSI_BUS_WIDTH 64 /* number of lanes per brick */ #define NUM_LANES 4 #define PHY_OFFSET 0x10000U #define CIL_A_SW_RESET 0x11024U #define CIL_B_SW_RESET 0x110b0U #define CSIA (1 << 20) #define CSIH (1 << 27) static long long input_stats; static struct tegra_csi_device *mc_csi; struct t194_nvcsi { struct platform_device *pdev; struct tegra_csi_device csi; struct dentry *dir; void __iomem *io; struct tegra_prod *prod_list; atomic_t on; }; static const struct of_device_id tegra194_nvcsi_of_match[] = { { .compatible = "nvidia,tegra194-nvcsi", .data = &t19_nvcsi_info, }, { }, }; struct t194_nvcsi_file_private { struct platform_device *pdev; struct nvcsi_deskew_context deskew_ctx; }; static int nvcsi_deskew_debugfs_init(struct t194_nvcsi *nvcsi); static void nvcsi_deskew_debugfs_remove(struct t194_nvcsi *nvcsi); static int nvhost_nvcsi_prod_apply_virt_WAR(struct t194_nvcsi *nvcsi, unsigned int phy_mode) { dev_info(&nvcsi->pdev->dev, "%s: nvcsi prod setting virt WAR active\n", __func__); return vhost_prod_apply(nvcsi->pdev, phy_mode); } static int nvhost_nvcsi_cil_sw_reset_virt_WAR(struct platform_device *pdev, unsigned int lanes, unsigned int enable) { dev_info(&pdev->dev, "%s: nvcsi cil sw reset virt WAR active\n", __func__); return vhost_cil_sw_reset(pdev, lanes, enable); } static long t194_nvcsi_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { struct t194_nvcsi_file_private *filepriv = file->private_data; int ret; switch (cmd) { // sensor must be turned on before calling this ioctl, and streaming // should be started shortly after. case NVHOST_NVCSI_IOCTL_DESKEW_SETUP: { unsigned int active_lanes; dev_dbg(mc_csi->dev, "ioctl: deskew_setup\n"); ret = copy_from_user(&active_lanes, (const void __user *)arg, sizeof(unsigned int)); if (ret) { return -EINVAL; } else { filepriv->deskew_ctx.deskew_lanes = active_lanes; return nvcsi_deskew_setup(&filepriv->deskew_ctx); } } case NVHOST_NVCSI_IOCTL_DESKEW_APPLY: { dev_dbg(mc_csi->dev, "ioctl: deskew_apply\n"); ret = nvcsi_deskew_apply_check(&filepriv->deskew_ctx); return ret; } case NVHOST_NVCSI_IOCTL_PROD_APPLY: { unsigned int phy_mode; int err; struct t194_nvcsi *nvcsi = nvhost_get_private_data( filepriv->pdev); dev_dbg(mc_csi->dev, "ioctl: prod_apply\n"); ret = copy_from_user(&phy_mode, (const void __user *)arg, sizeof(unsigned int)); if (ret) return -EINVAL; if (!nvcsi->io) return nvhost_nvcsi_prod_apply_virt_WAR(nvcsi, phy_mode); err = tegra_prod_set_by_name(&nvcsi->io, "prod", nvcsi->prod_list); if (err) { dev_err(&nvcsi->pdev->dev, "%s: prod set fail (err=%d)\n", __func__, err); return err; } if (phy_mode == PHY_CPHY_MODE) err = tegra_prod_set_by_name(&nvcsi->io, "prod_c_cphy_mode", nvcsi->prod_list); else err = tegra_prod_set_by_name(&nvcsi->io, "prod_c_dphy_mode", nvcsi->prod_list); if (err) dev_err(&nvcsi->pdev->dev, "%s: phy_prod set fail (err=%d)\n", __func__, err); return err; } } return -ENOIOCTLCMD; } static int t194_nvcsi_open(struct inode *inode, struct file *file) { struct nvhost_device_data *pdata = container_of(inode->i_cdev, struct nvhost_device_data, ctrl_cdev); struct platform_device *pdev = pdata->pdev; struct t194_nvcsi_file_private *filepriv; filepriv = kzalloc(sizeof(*filepriv), GFP_KERNEL); if (unlikely(filepriv == NULL)) return -ENOMEM; filepriv->pdev = pdev; file->private_data = filepriv; return nonseekable_open(inode, file); } static int t194_nvcsi_release(struct inode *inode, struct file *file) { struct t194_nvcsi_file_private *filepriv = file->private_data; kfree(filepriv); return 0; } const struct file_operations tegra194_nvcsi_ctrl_ops = { .owner = THIS_MODULE, .llseek = no_llseek, .unlocked_ioctl = t194_nvcsi_ioctl, #ifdef CONFIG_COMPAT .compat_ioctl = t194_nvcsi_ioctl, #endif .open = t194_nvcsi_open, .release = t194_nvcsi_release, }; static void nvcsi_apply_prod(struct platform_device *pdev) { int err = -ENODEV; struct t194_nvcsi *nvcsi = nvhost_get_private_data(pdev); struct tegra_csi_channel *chan; u32 phy_mode; bool is_cphy; if (!list_empty(&nvcsi->csi.csi_chans)) { chan = list_first_entry(&nvcsi->csi.csi_chans, struct tegra_csi_channel, list); phy_mode = read_phy_mode_from_dt(chan); is_cphy = (phy_mode == CSI_PHY_MODE_CPHY); err = tegra_prod_set_by_name(&nvcsi->io, "prod", nvcsi->prod_list); if (err) { dev_err(&pdev->dev, "%s: prod set fail (err=%d)\n", __func__, err); } if (is_cphy) err = tegra_prod_set_by_name(&nvcsi->io, "prod_c_cphy_mode", nvcsi->prod_list); else err = tegra_prod_set_by_name(&nvcsi->io, "prod_c_dphy_mode", nvcsi->prod_list); if (err) { dev_err(&pdev->dev, "%s: prod set fail (err=%d)\n", __func__, err); } } } static int nvcsi_prod_apply_thread(void *data) { struct platform_device *pdev = data; struct t194_nvcsi *nvcsi = nvhost_get_private_data(pdev); /* * rtcpu finishes poweron after ~120ms after nvcsi_finalize_poweron * so sleep for around that much before checking rtcpu * power state again */ while (!tegra_camrtc_is_rtcpu_powered()) usleep_range(1000*125, 1000*126); nvcsi_apply_prod(pdev); tegra_csi_mipi_calibrate(&nvcsi->csi, true); atomic_set(&nvcsi->on, 1); return 0; } int tegra194_nvcsi_finalize_poweron(struct platform_device *pdev) { struct t194_nvcsi *nvcsi = nvhost_get_private_data(pdev); if (atomic_read(&nvcsi->on) == 1) return 0; /* rtcpu resets nvcsi registers, so we set prod settings * after rtcpu has finished resetting the registers * which happens during rtcpu's poweron call * TODO: fix this dependency */ if (!tegra_camrtc_is_rtcpu_powered()) kthread_run(nvcsi_prod_apply_thread, pdev, "nvcsi-t194-prod"); else { nvcsi_apply_prod(pdev); tegra_csi_mipi_calibrate(&nvcsi->csi, true); atomic_set(&nvcsi->on, 1); } return 0; } EXPORT_SYMBOL_GPL(tegra194_nvcsi_finalize_poweron); int tegra194_nvcsi_prepare_poweroff(struct platform_device *pdev) { struct t194_nvcsi *nvcsi = nvhost_get_private_data(pdev); int err = 0; if (atomic_read(&nvcsi->on) == 0) return 0; err = tegra_csi_mipi_calibrate(&nvcsi->csi, false); if (err) { dev_err(&pdev->dev, "calibration failed\n"); return err; } atomic_set(&nvcsi->on, 0); return 0; } EXPORT_SYMBOL_GPL(tegra194_nvcsi_prepare_poweroff); int tegra194_nvcsi_cil_sw_reset(int lanes, int enable) { unsigned int phy_num = 0U; unsigned int val = enable ? (SW_RESET1_EN | SW_RESET0_EN) : 0U; unsigned int addr, i; struct t194_nvcsi *nvcsi = nvhost_get_private_data(mc_csi->pdev); if (!nvcsi->io) return nvhost_nvcsi_cil_sw_reset_virt_WAR(mc_csi->pdev, lanes, enable); for (i = CSIA; i < CSIH; i = i << 2U) { if (lanes & i) { addr = CIL_A_SW_RESET + (PHY_OFFSET * phy_num); host1x_writel(mc_csi->pdev, addr, val); } if (lanes & (i << 1U)) { addr = CIL_B_SW_RESET + (PHY_OFFSET * phy_num); host1x_writel(mc_csi->pdev, addr, val); } phy_num++; } return 0; } EXPORT_SYMBOL_GPL(tegra194_nvcsi_cil_sw_reset); int t194_nvcsi_early_probe(struct platform_device *pdev) { struct nvhost_device_data *pdata; struct t194_nvcsi *nvcsi; pdata = (void *)of_device_get_match_data(&pdev->dev); if (unlikely(pdata == NULL)) { dev_WARN(&pdev->dev, "no platform data\n"); return -ENODATA; } nvcsi = devm_kzalloc(&pdev->dev, sizeof(*nvcsi), GFP_KERNEL); if (!nvcsi) { return -ENOMEM; } pdata->pdev = pdev; nvcsi->pdev = pdev; mutex_init(&pdata->lock); platform_set_drvdata(pdev, pdata); mc_csi = &nvcsi->csi; pdata->private_data = nvcsi; return 0; } int t194_nvcsi_late_probe(struct platform_device *pdev) { struct nvhost_device_data *pdata = platform_get_drvdata(pdev); struct t194_nvcsi *nvcsi = pdata->private_data; struct tegra_camera_dev_info csi_info; int err; nvcsi->prod_list = devm_tegra_prod_get(&pdev->dev); if (IS_ERR(nvcsi->prod_list)) { pr_err("%s: Can not find nvcsi prod node\n", __func__); return -ENODEV; } memset(&csi_info, 0, sizeof(csi_info)); csi_info.pdev = pdev; csi_info.hw_type = HWTYPE_CSI; csi_info.use_max = true; csi_info.bus_width = CSI_BUS_WIDTH; csi_info.lane_num = NUM_LANES; csi_info.pg_clk_rate = PG_CLK_RATE; err = tegra_camera_device_register(&csi_info, nvcsi); if (err) return err; nvcsi->pdev = pdev; nvcsi->csi.fops = &csi5_fops; atomic_set(&nvcsi->on, 0); err = tegra_csi_media_controller_init(&nvcsi->csi, pdev); nvcsi_deskew_platform_setup(&nvcsi->csi, true); if (err < 0) return err; nvcsi_deskew_debugfs_init(nvcsi); return 0; } static int t194_nvcsi_probe(struct platform_device *pdev) { struct resource *mem; void __iomem *regs; int err; struct nvhost_device_data *pdata; struct t194_nvcsi *nvcsi; err = t194_nvcsi_early_probe(pdev); if (err) return err; pdata = platform_get_drvdata(pdev); mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (!mem) { dev_err(&pdev->dev, "No memory resource\n"); return -EINVAL; } regs = devm_ioremap_nocache(&pdev->dev, mem->start, resource_size(mem)); if (!regs) { dev_err(&pdev->dev, "ioremap failed\n"); return -ENOMEM; } nvcsi = pdata->private_data; nvcsi->io = regs; err = nvhost_client_device_get_resources(pdev); if (err) return err; err = nvhost_module_init(pdev); if (err) return err; err = nvhost_client_device_init(pdev); if (err) { nvhost_module_deinit(pdev); goto err_client_device_init; } err = t194_nvcsi_late_probe(pdev); if (err) goto err_mediacontroller_init; return 0; err_mediacontroller_init: nvhost_client_device_release(pdev); err_client_device_init: pdata->private_data = NULL; return err; } static int __exit t194_nvcsi_remove(struct platform_device *dev) { struct nvhost_device_data *pdata = platform_get_drvdata(dev); struct t194_nvcsi *nvcsi = pdata->private_data; tegra_camera_device_unregister(nvcsi); mc_csi = NULL; nvcsi_deskew_debugfs_remove(nvcsi); tegra_csi_media_controller_remove(&nvcsi->csi); return 0; } static struct platform_driver t194_nvcsi_driver = { .probe = t194_nvcsi_probe, .remove = __exit_p(t194_nvcsi_remove), .driver = { .owner = THIS_MODULE, .name = "t194-nvcsi", #ifdef CONFIG_OF .of_match_table = tegra194_nvcsi_of_match, #endif #ifdef CONFIG_PM .pm = &nvhost_module_pm_ops, #endif }, }; static int dbgfs_deskew_stats(struct seq_file *s, void *data) { deskew_dbgfs_deskew_stats(s); return 0; } static int dbgfs_open(struct inode *inode, struct file *file) { return single_open(file, dbgfs_deskew_stats, inode->i_private); } static const struct file_operations dbg_show_ops = { .open = dbgfs_open, .read = seq_read, .llseek = seq_lseek, .release = single_release }; static int dbgfs_calc_bound(struct seq_file *s, void *data) { deskew_dbgfs_calc_bound(s, input_stats); return 0; } static int dbg_calc_open(struct inode *inode, struct file *file) { return single_open(file, dbgfs_calc_bound, inode->i_private); } static const struct file_operations dbg_calc_ops = { .open = dbg_calc_open, .read = seq_read, .llseek = seq_lseek, .release = single_release }; static void nvcsi_deskew_debugfs_remove(struct t194_nvcsi *nvcsi) { debugfs_remove_recursive(nvcsi->dir); } static int nvcsi_deskew_debugfs_init(struct t194_nvcsi *nvcsi) { struct dentry *val; nvcsi->dir = debugfs_create_dir("deskew", NULL); if (!nvcsi->dir) return -ENOMEM; val = debugfs_create_file("stats", S_IRUGO, nvcsi->dir, mc_csi, &dbg_show_ops); if (!val) goto err_debugfs; val = debugfs_create_x64("input_status", S_IRUGO | S_IWUSR, nvcsi->dir, &input_stats); if (!val) goto err_debugfs; val = debugfs_create_file("calc_bound", S_IRUGO | S_IWUSR, nvcsi->dir, mc_csi, &dbg_calc_ops); if (!val) goto err_debugfs; return 0; err_debugfs: dev_err(mc_csi->dev, "Fail to create debugfs\n"); debugfs_remove_recursive(nvcsi->dir); return -ENOMEM; } module_platform_driver(t194_nvcsi_driver);