/* * Deskew driver * * Copyright (c) 2014-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 "deskew.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "dev.h" #include "bus_client.h" #include "nvhost_acm.h" #include "t186/t186.h" #include "nvcsi.h" #include "camera/csi/csi4_fops.h" static struct tegra_csi_device *mc_csi; static struct mutex deskew_lock; static int debugfs_deskew_clk_stats_low[NVCSI_PHY_CIL_NUM_LANE]; static int debugfs_deskew_clk_stats_high[NVCSI_PHY_CIL_NUM_LANE]; static int debugfs_deskew_data_stats_low[NVCSI_PHY_CIL_NUM_LANE]; static int debugfs_deskew_data_stats_high[NVCSI_PHY_CIL_NUM_LANE]; static unsigned int enabled_deskew_lanes; static unsigned int done_deskew_lanes; static int nvcsi_deskew_apply_helper(unsigned int active_lanes); static bool is_t19x_or_greater; // a regmap for address changes between chips static uint32_t regs[REGS_COUNT]; static const uint32_t t186_regs[REGS_COUNT] = { 0x10090, //< NVCSI_STREAM_0_ERROR_STATUS2VI_MASK regs[0] 0x10890, //< NVCSI_STREAM_1_ERROR_STATUS2VI_MASK regs[1] 0x1010101, //< CFG_ERR_STATUS2VI_MASK_ALL regs[2] 0x10400, //< NVCSI_PHY_0_CILA_INTR_STATUS regs[3] 0x10404, //< NVCSI_PHY_0_CILA_INTR_MASK regs[4] 0x10c00, //< NVCSI_PHY_0_CILB_INTR_STATUS regs[5] 0x10c04, //< NVCSI_PHY_0_CILB_INTR_MASK regs[6] 0x18000, //< NVCSI_PHY_0_NVCSI_CIL_PHY_CTRL_0 regs[7] 0x18, //< NVCSI_CIL_A_SW_RESET_0_OFFSET regs[8] 0x2c, //< NVCSI_CIL_A_CLK_DESKEW_CTRL_0_OFFSET regs[9] 0x24, //< NVCSI_CIL_A_DPHY_INADJ_CTRL_0_OFFSET regs[10] 0x30, //< NVCSI_CIL_A_DATA_DESKEW_CTRL_0_OFFSET regs[11] 0x34, //< NVCSI_CIL_A_DPHY_DESKEW_STATUS_0_OFFSET regs[12] 0x38, //< NVCSI_CIL_A_DPHY_DESKEW_DATA_CALIB_STATUS_LOW_0_0_OFFSET regs[13] 0x3c, //< NVCSI_CIL_A_DPHY_DESKEW_DATA_CALIB_STATUS_HIGH_0_0_OFFSET regs[14] 0x48, //< NVCSI_CIL_A_DPHY_DESKEW_CLK_CALIB_STATUS_LOW_0_0_OFFSET regs[15] 0x4c, //< NVCSI_CIL_A_DPHY_DESKEW_CLK_CALIB_STATUS_HIGH_0_0_OFFSET regs[16] 0x5c, //< NVCSI_CIL_A_DESKEW_CONTROL_0_OFFSET regs[17] 0x5c, //< NVCSI_CIL_A_CONTROL_0_OFFSET regs[18] 0xf << 20, //< DESKEW_COMPARE regs[19] 20, //< DESKEW_COMPARE_SHIFT regs[20] 0xf << 16, //< DESKEW_SETTLE regs[21] 16, //< DESKEW_SETTLE_SHIFT regs[22] 0x3f << 8, //< CLK_SETTLE regs[23] 8, //< CLK_SETTLE_SHIFT0 regs[24] 0x7f << 0, //< THS_SETTLE0 regs[25] 0x7f << 0, //< THS_SETTLE1 regs[26] 0, //< THS_SETTLE0_SHIFT regs[27] 0, //< THS_SETTLE1_SHIFT regs[28] 0x88, //< NVCSI_CIL_B_DPHY_INADJ_CTRL_0_OFFSET regs[29] 0x90, //< NVCSI_CIL_B_CLK_DESKEW_CTRL_0_OFFSET regs[30] 0x94, //< NVCSI_CIL_B_DATA_DESKEW_CTRL_0_OFFSET regs[31] 0x98, //< NVCSI_CIL_B_DPHY_DESKEW_STATUS_0_OFFSET regs[32] 0xc0, //< NVCSI_CIL_B_DESKEW_CONTROL_0_OFFSET regs[33] 0xc0, //< NVCSI_CIL_B_CONTROL_0_OFFSET regs[34] 0x64, //< NVCSI_CIL_B_OFFSET regs[35] }; static const uint32_t t194_regs[REGS_COUNT] = { 0x101e4, //< NVCSI_STREAM_0_ERROR_STATUS2VI_MASK regs[0] 0x181e4, //< NVCSI_STREAM_1_ERROR_STATUS2VI_MASK regs[1] 0x0ffff, //< CFG_ERR_STATUS2VI_MASK_ALL regs[2] 0x10400, //< NVCSI_PHY_0_CILA_INTR_STATUS regs[3] 0x10408, //< NVCSI_PHY_0_CILA_INTR_MASK regs[4] 0x10800, //< NVCSI_PHY_0_CILB_INTR_STATUS regs[5] 0x10808, //< NVCSI_PHY_0_CILB_INTR_MASK regs[6] 0x11000, //< NVCSI_PHY_0_NVCSI_CIL_PHY_CTRL_0 regs[7] 0x24, //< NVCSI_CIL_A_SW_RESET_0_OFFSET regs[8] 0x38, //< NVCSI_CIL_A_CLK_DESKEW_CTRL_0_OFFSET regs[9] 0x30, //< NVCSI_CIL_A_DPHY_INADJ_CTRL_0_OFFSET regs[10] 0x3c, //< NVCSI_CIL_A_DATA_DESKEW_CTRL_0_OFFSET regs[11] 0x40, //< NVCSI_CIL_A_DPHY_DESKEW_STATUS_0_OFFSET regs[12] 0x44, //< NVCSI_CIL_A_DPHY_DESKEW_DATA_CALIB_STATUS_LOW_0_0_OFFSET regs[13] 0x48, //< NVCSI_CIL_A_DPHY_DESKEW_DATA_CALIB_STATUS_HIGH_0_0_OFFSET regs[14] 0x54, //< NVCSI_CIL_A_DPHY_DESKEW_CLK_CALIB_STATUS_LOW_0_0_OFFSET regs[15] 0x58, //< NVCSI_CIL_A_DPHY_DESKEW_CLK_CALIB_STATUS_HIGH_0_0_OFFSET regs[16] 0x6c, //< NVCSI_CIL_A_DESKEW_CONTROL_0_OFFSET regs[17] 0x70, //< NVCSI_CIL_A_CONTROL_0_OFFSET regs[18] 0xf << 4, //< DESKEW_COMPARE regs[19] 4, //< DESKEW_COMPARE_SHIFT regs[20] 0xf << 0, //< DESKEW_SETTLE regs[21] 0, //< DESKEW_SETTLE_SHIFT regs[22] 0x7f << 17, //< CLK_SETTLE regs[23] 17, //< CLK_SETTLE_SHIFT0 regs[24] 0xff << 1, //< THS_SETTLE0 regs[25] 0xff << 9, //< THS_SETTLE1 regs[26] 1, //< THS_SETTLE0_SHIFT regs[27] 9, //< THS_SETTLE1_SHIFT regs[28] 0xbc, //< NVCSI_CIL_B_DPHY_INADJ_CTRL_0_OFFSET regs[29] 0xc4, //< NVCSI_CIL_B_CLK_DESKEW_CTRL_0_OFFSET regs[30] 0xc8, //< NVCSI_CIL_B_DATA_DESKEW_CTRL_0_OFFSET regs[31] 0xcc, //< NVCSI_CIL_B_DPHY_DESKEW_STATUS_0_OFFSET regs[32] 0xf8, //< NVCSI_CIL_B_DESKEW_CONTROL_0_OFFSET regs[33] 0xfc, //< NVCSI_CIL_B_CONTROL_0_OFFSET regs[34] 0x8c, //< NVCSI_CIL_B_OFFSET regs[35] }; void nvcsi_deskew_platform_setup(struct tegra_csi_device *dev, bool t19x) { int i; mc_csi = dev; is_t19x_or_greater = t19x; mutex_init(&deskew_lock); enabled_deskew_lanes = 0; done_deskew_lanes = 0; if (is_t19x_or_greater) for (i = 0; i < REGS_COUNT; ++i) regs[i] = t194_regs[i]; else for (i = 0; i < REGS_COUNT; ++i) regs[i] = t186_regs[i]; } static inline void set_enabled_with_lock(unsigned int active_lanes) { mutex_lock(&deskew_lock); enabled_deskew_lanes |= active_lanes; mutex_unlock(&deskew_lock); } static inline void unset_enabled_with_lock(unsigned int active_lanes) { mutex_lock(&deskew_lock); enabled_deskew_lanes &= ~active_lanes; mutex_unlock(&deskew_lock); } static inline void set_done_with_lock(unsigned int done_lanes) { mutex_lock(&deskew_lock); done_deskew_lanes |= done_lanes; enabled_deskew_lanes &= ~done_lanes; mutex_unlock(&deskew_lock); } static inline void nvcsi_phy_write(unsigned int phy_num, unsigned int addr_offset, unsigned int val) { unsigned int addr; addr = NVCSI_PHY_0_NVCSI_CIL_PHY_CTRL_0 + NVCSI_PHY_OFFSET * phy_num +addr_offset; dev_dbg(mc_csi->dev, "%s: addr %x val %x\n", __func__, addr, val); host1x_writel(mc_csi->pdev, addr, val); } static inline unsigned int nvcsi_phy_readl(unsigned int phy_num, unsigned int addr_offset) { unsigned int addr; int val; addr = NVCSI_PHY_0_NVCSI_CIL_PHY_CTRL_0 + NVCSI_PHY_OFFSET * phy_num + addr_offset; val = host1x_readl(mc_csi->pdev, addr); dev_dbg(mc_csi->dev, "%s: addr %x val %x\n", __func__, addr, val); return val; } static void nvcsi_deskew_setup_start(unsigned int active_lanes) { unsigned int phy_num = 0; unsigned int cil_lanes = 0, cila_io_lanes = 0, cilb_io_lanes = 0; unsigned int remaining_lanes = active_lanes; unsigned int val = 0, newval = 0; dev_dbg(mc_csi->dev, "%s: active_lanes: %x\n", __func__, active_lanes); while (remaining_lanes) { cil_lanes = (active_lanes & (0x000f << (phy_num * 4))) >> (phy_num * 4); cila_io_lanes = cil_lanes & (NVCSI_PHY_0_NVCSI_CIL_A_IO0 | NVCSI_PHY_0_NVCSI_CIL_A_IO1); cilb_io_lanes = cil_lanes & (NVCSI_PHY_0_NVCSI_CIL_B_IO0 | NVCSI_PHY_0_NVCSI_CIL_B_IO1); remaining_lanes &= ~(0xf << (phy_num * 4)); if (cila_io_lanes) { /* * Disable single bit err when detecting leader * pattern */ newval = CFG_ERR_STATUS2VI_MASK_ALL; host1x_writel(mc_csi->pdev, NVCSI_STREAM_0_ERROR_STATUS2VI_MASK + NVCSI_PHY_OFFSET * phy_num, newval); val = nvcsi_phy_readl(phy_num, NVCSI_CIL_A_DESKEW_CONTROL_0_OFFSET); val = (val & (~(DESKEW_COMPARE | DESKEW_SETTLE) )) | (0x4 << DESKEW_COMPARE_SHIFT) | (0X6 << DESKEW_SETTLE_SHIFT); nvcsi_phy_write(phy_num, NVCSI_CIL_A_DESKEW_CONTROL_0_OFFSET, val); val = nvcsi_phy_readl(phy_num, NVCSI_CIL_A_CONTROL_0_OFFSET); val = (val & (~(CLK_SETTLE | THS_SETTLE0 | THS_SETTLE1))) | (0x19 << CLK_SETTLE_SHIFT0) | (0x16 << THS_SETTLE0_SHIFT) | (0x16 << THS_SETTLE1_SHIFT); nvcsi_phy_write(phy_num, NVCSI_CIL_A_CONTROL_0_OFFSET, val); if (is_t19x_or_greater) nvcsi_phy_write(phy_num, NVCSI_CIL_A_DPHY_DESKEW_RESULT_STATUS_OFFSET, 0 ); } if (cilb_io_lanes) { /* * Disable single bit err when detecting leader * pattern */ newval = CFG_ERR_STATUS2VI_MASK_ALL; host1x_writel(mc_csi->pdev, NVCSI_STREAM_1_ERROR_STATUS2VI_MASK + NVCSI_PHY_OFFSET * phy_num, newval); val = nvcsi_phy_readl(phy_num, NVCSI_CIL_B_DESKEW_CONTROL_0_OFFSET); val = (val & (~(DESKEW_COMPARE | DESKEW_SETTLE) )) | (0x4 << DESKEW_COMPARE_SHIFT) | (0X6 << DESKEW_SETTLE_SHIFT); nvcsi_phy_write(phy_num, NVCSI_CIL_B_DESKEW_CONTROL_0_OFFSET, val); val = nvcsi_phy_readl(phy_num, NVCSI_CIL_B_CONTROL_0_OFFSET); val = (val & (~(CLK_SETTLE | THS_SETTLE0 | THS_SETTLE1))) | (0x19 << CLK_SETTLE_SHIFT0) | (0x16 << THS_SETTLE0_SHIFT) | (0x16 << THS_SETTLE1_SHIFT); nvcsi_phy_write(phy_num, NVCSI_CIL_B_CONTROL_0_OFFSET, val); if (is_t19x_or_greater) nvcsi_phy_write(phy_num, NVCSI_CIL_B_DPHY_DESKEW_RESULT_STATUS_OFFSET, 0 ); } // HW sometimes throws an error during the deskew operation // without a delay here // let the nvcsi writes for deskew settings propagate properly // before enabling deskew usleep_range(40, 50); if (cila_io_lanes) { val = CLK_INADJ_LIMIT_HIGH; nvcsi_phy_write(phy_num, NVCSI_CIL_A_CLK_DESKEW_CTRL_0_OFFSET, val | CLK_INADJ_SWEEP_CTRL ); val = nvcsi_phy_readl(phy_num, NVCSI_CIL_A_DATA_DESKEW_CTRL_0_OFFSET); newval = ((cila_io_lanes & NVCSI_PHY_0_NVCSI_CIL_A_IO0) != 0 ? (DATA_INADJ_SWEEP_CTRL0 | DATA_INADJ_LIMIT_HIGH0) : 0) | ((cila_io_lanes & NVCSI_PHY_0_NVCSI_CIL_A_IO1) != 0 ? (DATA_INADJ_SWEEP_CTRL1 | DATA_INADJ_LIMIT_HIGH1) : 0); nvcsi_phy_write(phy_num, NVCSI_CIL_A_DATA_DESKEW_CTRL_0_OFFSET, ((val & ~(DATA_INADJ_SWEEP_CTRL0 | DATA_INADJ_SWEEP_CTRL1)) | newval)); } if (cilb_io_lanes) { val = CLK_INADJ_LIMIT_HIGH; nvcsi_phy_write(phy_num, NVCSI_CIL_B_CLK_DESKEW_CTRL_0_OFFSET, val | CLK_INADJ_SWEEP_CTRL ); val = nvcsi_phy_readl(phy_num, NVCSI_CIL_B_DATA_DESKEW_CTRL_0_OFFSET); newval = ((cilb_io_lanes & NVCSI_PHY_0_NVCSI_CIL_B_IO0) != 0 ? (DATA_INADJ_SWEEP_CTRL0 | DATA_INADJ_LIMIT_HIGH0) : 0) | ((cilb_io_lanes & NVCSI_PHY_0_NVCSI_CIL_B_IO1) != 0 ? (DATA_INADJ_SWEEP_CTRL1 | DATA_INADJ_LIMIT_HIGH1) : 0); nvcsi_phy_write(phy_num, NVCSI_CIL_B_DATA_DESKEW_CTRL_0_OFFSET, ((val & ~(DATA_INADJ_SWEEP_CTRL0 | DATA_INADJ_SWEEP_CTRL1)) | newval)); } phy_num++; } } static int wait_cila_done(unsigned int phy_num, unsigned int cila_io_lanes, unsigned long timeout) { bool done; unsigned int val; while (time_before(jiffies, timeout)) { done = true; val = nvcsi_phy_readl(phy_num, NVCSI_CIL_A_DPHY_DESKEW_STATUS_0_OFFSET); if (cila_io_lanes & NVCSI_PHY_0_NVCSI_CIL_A_IO0) done &= !!(val & DPHY_CALIB_DONE_IO0); if (cila_io_lanes & NVCSI_PHY_0_NVCSI_CIL_A_IO1) done &= !!(val & DPHY_CALIB_DONE_IO1); if (val & DPHY_CALIB_ERR_IO1 || val & DPHY_CALIB_ERR_IO0) return -EINVAL; if (done) return 0; usleep_range(5, 10); } return -ETIMEDOUT; } static int wait_cilb_done(unsigned int phy_num, unsigned int cilb_io_lanes, unsigned long timeout) { bool done; unsigned int val; while (time_before(jiffies, timeout)) { done = true; val = nvcsi_phy_readl(phy_num, NVCSI_CIL_B_DPHY_DESKEW_STATUS_0_OFFSET); if (cilb_io_lanes & NVCSI_PHY_0_NVCSI_CIL_B_IO0) done &= !!(val & DPHY_CALIB_DONE_IO0); if (cilb_io_lanes & NVCSI_PHY_0_NVCSI_CIL_B_IO1) done &= !!(val & DPHY_CALIB_DONE_IO1); if (val & DPHY_CALIB_ERR_IO1 || val & DPHY_CALIB_ERR_IO0) return -EINVAL; if (done) return 0; usleep_range(5, 10); } return -ETIMEDOUT; } static int nvcsi_deskew_thread(void *data) { int ret = 0; unsigned int phy_num = 0; unsigned int cil_lanes = 0, cila_io_lanes = 0, cilb_io_lanes = 0; struct nvcsi_deskew_context *ctx = data; unsigned int remaining_lanes = ctx->deskew_lanes; unsigned long timeout = 0; timeout = jiffies + msecs_to_jiffies(DESKEW_TIMEOUT_MSEC); while (remaining_lanes) { cil_lanes = (ctx->deskew_lanes & (0x000f << (phy_num * 4))) >> (phy_num * 4); cila_io_lanes = cil_lanes & (NVCSI_PHY_0_NVCSI_CIL_A_IO0 | NVCSI_PHY_0_NVCSI_CIL_A_IO1); cilb_io_lanes = cil_lanes & (NVCSI_PHY_0_NVCSI_CIL_B_IO0 | NVCSI_PHY_0_NVCSI_CIL_B_IO1); remaining_lanes &= ~(0xf << (phy_num * 4)); if (cila_io_lanes) { ret = wait_cila_done(phy_num, cila_io_lanes, timeout); if (ret) goto err; } if (cilb_io_lanes) { ret = wait_cilb_done(phy_num, cilb_io_lanes, timeout); if (ret) goto err; } phy_num++; } ret = nvcsi_deskew_apply_helper(ctx->deskew_lanes); if (!ret) { dev_info(mc_csi->dev, "deskew finished for lanes 0x%04x", ctx->deskew_lanes); set_done_with_lock(ctx->deskew_lanes); } else { dev_info(mc_csi->dev, "deskew apply helper failed for lanes 0x%04x", ctx->deskew_lanes); goto err; } complete(&ctx->thread_done); return 0; err: if (ret == -ETIMEDOUT) dev_info(mc_csi->dev, "deskew timed out for lanes 0x%04x", ctx->deskew_lanes); else if (ret == -EINVAL) dev_info(mc_csi->dev, "deskew calib err for lanes 0x%04x", ctx->deskew_lanes); unset_enabled_with_lock(ctx->deskew_lanes); complete(&ctx->thread_done); return ret; } int nvcsi_deskew_setup(struct nvcsi_deskew_context *ctx) { int ret = 0; unsigned int new_lanes; if (!ctx || !ctx->deskew_lanes) return -EINVAL; if (ctx->deskew_lanes >> NVCSI_PHY_CIL_NUM_LANE) { dev_err(mc_csi->dev, "%s Invalid lanes for deskew\n", __func__); return -EINVAL; } mutex_lock(&deskew_lock); done_deskew_lanes &= ~(ctx->deskew_lanes); mutex_unlock(&deskew_lock); new_lanes = ctx->deskew_lanes & ~enabled_deskew_lanes; if (new_lanes) { set_enabled_with_lock(new_lanes); nvcsi_deskew_setup_start(new_lanes); init_completion(&ctx->thread_done); ctx->deskew_kthread = kthread_run(nvcsi_deskew_thread, ctx, "deskew"); if (IS_ERR(ctx->deskew_kthread)) { ret = PTR_ERR(ctx->deskew_kthread); complete(&ctx->thread_done); } } return ret; } EXPORT_SYMBOL(nvcsi_deskew_setup); static inline unsigned int checkpass(unsigned long long stat) { /* Due to bug 200098288, use * mask101 = 0x5; mask111 = 0x7; mask1010 = 0xa; * to check if current trimmer setting results in passing * Algorithm is explained in NVCSI_CIL_IAS chapter 5.3 */ return ((stat & 0x5) == 0x5 || ((stat & 0x7) == 0x7) || (stat & 0xa) == 0xa); } /* compute_boundary: * This function find the flipping point when the trimmer settings starts * to pass/fail. * Each graph represent the 64-bit status,trimmer setting 0~0x3F * from Right to Left. * * pf: pass to fail, fp: fail to pass * * pf fp * __|------|__ * pf fp = 0 * ____|------- * pf=0x3f fp * ----------|__ */ static unsigned int compute_boundary(unsigned long long stat, unsigned int *x, unsigned int *w) { unsigned int was_pass, i = 0; int pf = -1, fp = -1; unsigned long long last_stat; was_pass = checkpass(stat); fp = (was_pass == 1 ? 0 : -1); last_stat = stat; while (i < 64) { if ((was_pass == 1) && (!(last_stat & 1))) { if (!checkpass(last_stat)) { pf = i; was_pass = 0; dev_dbg(mc_csi->dev, "pf %d\n", pf); } } else if ((was_pass == 0) && (last_stat & 1)) { if (checkpass(last_stat)) { fp = i; was_pass = 1; dev_dbg(mc_csi->dev, "fp %d\n", fp); } } i++; last_stat >>= 1; } dev_dbg(mc_csi->dev, "fp %d pf %d\n", fp, pf); if (fp == -1 && pf == -1) { dev_dbg(mc_csi->dev, "No passing record found, please retry\n"); return -EINVAL; } else if (pf == -1 && was_pass == 1) pf = 0x3f; *x = pf; *w = fp; dev_dbg(mc_csi->dev, "%s: stats %llx, f2p %d, p2f %d", __func__, stat, fp, pf); return 0; } static unsigned int error_boundary(unsigned int phy_num, unsigned int cil_bit, unsigned int *x, unsigned int *w, unsigned int *y, unsigned int *z) { unsigned int stats_low = 0, stats_high = 0, stats_offset = 0; unsigned long long result = 0; unsigned int is_cilb = 0, is_io1 = 0; is_cilb = (cil_bit > 1); is_io1 = (cil_bit % 2); dev_dbg(mc_csi->dev, "boundary for cilb?:%d io1?:%d\n", is_cilb, is_io1); stats_offset = is_cilb * NVCSI_CIL_B_OFFSET + is_io1 * NVCSI_DPHY_CALIB_STATUS_IO_OFFSET; /* step #1 clk lane */ stats_low = nvcsi_phy_readl(phy_num, stats_offset + NVCSI_CIL_A_DPHY_DESKEW_CLK_CALIB_STATUS_LOW_0_0_OFFSET); stats_high = nvcsi_phy_readl(phy_num, stats_offset + NVCSI_CIL_A_DPHY_DESKEW_CLK_CALIB_STATUS_HIGH_0_0_OFFSET); result = ((unsigned long long)stats_high) << 32 | stats_low; debugfs_deskew_clk_stats_low[cil_bit + phy_num * 4] = stats_low; debugfs_deskew_clk_stats_high[cil_bit + phy_num * 4] = stats_high; dev_dbg(mc_csi->dev, "clk boundary: 0x%016llx\n", result); if (compute_boundary(result, x, w)) return -EINVAL; /* step #2 data lane */ stats_low = nvcsi_phy_readl(phy_num, stats_offset + NVCSI_CIL_A_DPHY_DESKEW_DATA_CALIB_STATUS_LOW_0_0_OFFSET); stats_high = nvcsi_phy_readl(phy_num, stats_offset + NVCSI_CIL_A_DPHY_DESKEW_DATA_CALIB_STATUS_HIGH_0_0_OFFSET); result = ((unsigned long long)stats_high) << 32 | stats_low; debugfs_deskew_data_stats_low[cil_bit + phy_num * 4] = stats_low; debugfs_deskew_data_stats_high[cil_bit + phy_num * 4] = stats_high; dev_dbg(mc_csi->dev, "data boundary: 0x%016llx\n", result); if (compute_boundary(result, y, z)) return -EINVAL; return 0; } static void compute_trimmer(unsigned int *x, unsigned int *w, unsigned int *y, unsigned int *z, unsigned int *d, unsigned int *c) { int mid[4], base = 0; unsigned int i = 0; /* NVCSI_CIL_IAS Chapter 5.3 */ for (i = 0; i < 4; i++) { if (w[i] < z[i]) { y[i] = 0; z[i] = 0; } else if (w[i] > z[i]) { x[i] = 0; w[i] = 0; } mid[i] = ((y[i] + z[i]) - (x[i] + w[i])) >> 1; base = mid[i] < base ? mid[i] : base; } *c = -base; for (i = 0; i < 4; i++) d[i] = mid[i] - base; /* debug prints */ for (i = 0; i < 4; i++) dev_dbg(mc_csi->dev, "x %u w %u y %u z %u d %u\n", x[i], w[i], y[i], z[i], d[i]); dev_dbg(mc_csi->dev, "clk %u\n", *c); } static void set_trimmer(unsigned int phy_num, unsigned int cila, unsigned int cilb, unsigned int *d, unsigned int c) { unsigned int val = 0, val1 = 0; if (cila && cilb) { /* 4-lane */ val = SW_SET_DPHY_INADJ_CLK | SW_SET_DPHY_INADJ_IO0 | SW_SET_DPHY_INADJ_IO1 | (c << DPHY_INADJ_CLK_SHIFT) | (d[PHY_0_CIL_A_IO0] << DPHY_INADJ_IO0_SHIFT) | (d[PHY_0_CIL_A_IO1] << DPHY_INADJ_IO1_SHIFT); val1 = SW_SET_DPHY_INADJ_IO0 | SW_SET_DPHY_INADJ_IO1 | (d[PHY_0_CIL_B_IO0] << DPHY_INADJ_IO0_SHIFT)| (d[PHY_0_CIL_B_IO1] << DPHY_INADJ_IO1_SHIFT); nvcsi_phy_write(phy_num, NVCSI_CIL_A_DPHY_INADJ_CTRL_0_OFFSET, val); nvcsi_phy_write(phy_num, NVCSI_CIL_B_DPHY_INADJ_CTRL_0_OFFSET, val1); dev_dbg(mc_csi->dev, "cila %x cilb %x\n", val, val1); return; } /* TODO: * 2-lane and 1-lane cases cannot be verified since there * is no such sensor supported yet */ if (cila) { /* 2-lane and 1-lane*/ val1 = nvcsi_phy_readl(phy_num, NVCSI_CIL_A_DPHY_INADJ_CTRL_0_OFFSET); if (cila & NVCSI_PHY_0_NVCSI_CIL_A_IO0) { val1 &= ~(SW_SET_DPHY_INADJ_IO0 | DPHY_INADJ_IO0); val |= SW_SET_DPHY_INADJ_IO0 | (d[PHY_0_CIL_A_IO0] << DPHY_INADJ_IO0_SHIFT); } if (cila & NVCSI_PHY_0_NVCSI_CIL_A_IO1) { val1 &= ~(SW_SET_DPHY_INADJ_IO1 | DPHY_INADJ_IO1); val |= SW_SET_DPHY_INADJ_IO1 | (d[PHY_0_CIL_A_IO1] << DPHY_INADJ_IO1_SHIFT); } val1 &= ~(SW_SET_DPHY_INADJ_CLK | DPHY_INADJ_CLK); val |= SW_SET_DPHY_INADJ_CLK | (c << DPHY_INADJ_CLK_SHIFT); nvcsi_phy_write(phy_num, NVCSI_CIL_A_DPHY_INADJ_CTRL_0_OFFSET, val | val1); dev_dbg(mc_csi->dev, "cila %x\n", val | val1); } else { /* 2-lane and 1-lane*/ val1 = nvcsi_phy_readl(phy_num, NVCSI_CIL_B_DPHY_INADJ_CTRL_0_OFFSET); if (cilb & NVCSI_PHY_0_NVCSI_CIL_B_IO0) { val1 &= ~(SW_SET_DPHY_INADJ_IO0 | DPHY_INADJ_IO0); val |= SW_SET_DPHY_INADJ_IO0 | (d[PHY_0_CIL_B_IO0] << DPHY_INADJ_IO0_SHIFT); } if (cilb & NVCSI_PHY_0_NVCSI_CIL_B_IO1) { val1 &= ~(SW_SET_DPHY_INADJ_IO1 | DPHY_INADJ_IO1); val |= SW_SET_DPHY_INADJ_IO1 | (d[PHY_0_CIL_B_IO1] << DPHY_INADJ_IO1_SHIFT); } val1 &= ~(SW_SET_DPHY_INADJ_CLK | DPHY_INADJ_CLK); val |= SW_SET_DPHY_INADJ_CLK | (c << DPHY_INADJ_CLK_SHIFT); nvcsi_phy_write(phy_num, NVCSI_CIL_B_DPHY_INADJ_CTRL_0_OFFSET, val | val1); dev_dbg(mc_csi->dev, "cilb %x\n", val | val1); } } int nvcsi_deskew_apply_check(struct nvcsi_deskew_context *ctx) { unsigned long timeout = 0, timeleft = 1; if (!completion_done(&ctx->thread_done)) { timeout = msecs_to_jiffies(DESKEW_TIMEOUT_MSEC); timeleft = wait_for_completion_timeout(&ctx->thread_done, timeout); } if (!timeleft) return -ETIMEDOUT; if (ctx->deskew_lanes == (done_deskew_lanes & ctx->deskew_lanes)) { // sleep for a frame to make sure deskew result is reflected usleep_range(35*1000, 36*1000); return 0; } else return -EINVAL; } EXPORT_SYMBOL(nvcsi_deskew_apply_check); static int nvcsi_deskew_apply_helper(unsigned int active_lanes) { unsigned int phy_num = -1; unsigned int cil_lanes = 0, cila_io_lanes = 0, cilb_io_lanes = 0; unsigned int remaining_lanes = active_lanes; unsigned int i, j; dev_dbg(mc_csi->dev, "%s: interrupt lane: %x\n", __func__, active_lanes); while (remaining_lanes) { unsigned int x[4] = {0, 0, 0, 0}; unsigned int w[4] = {0, 0, 0, 0}; unsigned int y[4] = {0, 0, 0, 0}; unsigned int z[4] = {0, 0, 0, 0}; unsigned int d_trimmer[4] = {0, 0, 0, 0}; unsigned int clk_trimmer = 0; phy_num++; cil_lanes = (active_lanes & (0xf << (phy_num * 4))) >> (phy_num * 4); remaining_lanes &= ~(0xf << (phy_num * 4)); if (!cil_lanes) continue; cila_io_lanes = cil_lanes & (NVCSI_PHY_0_NVCSI_CIL_A_IO0 | NVCSI_PHY_0_NVCSI_CIL_A_IO1); cilb_io_lanes = cil_lanes & (NVCSI_PHY_0_NVCSI_CIL_B_IO0 | NVCSI_PHY_0_NVCSI_CIL_B_IO1); /* Step 1: Read status registers and compute error boundaries */ for (i = NVCSI_PHY_0_NVCSI_CIL_A_IO0, j = 0; i <= NVCSI_PHY_0_NVCSI_CIL_B_IO1; i <<= 1, j++) { if ((cil_lanes & i) == 0) continue; if (error_boundary(phy_num, j, &x[j], &w[j], &y[j], &z[j])) return -EINVAL; } /*step 2: compute trimmer value based on error boundaries */ compute_trimmer(x, w, y, z, d_trimmer, &clk_trimmer); /*step 3: Apply trimmer settings */ set_trimmer(phy_num, cila_io_lanes, cilb_io_lanes, d_trimmer, clk_trimmer); } return 0; } void deskew_dbgfs_calc_bound(struct seq_file *s, long long input_stats) { unsigned int x, w; seq_printf(s, "input: %llx\n", input_stats); compute_boundary(input_stats, &x, &w); seq_printf(s, "setting: x %u w %u\n", x, w); } void deskew_dbgfs_deskew_stats(struct seq_file *s) { unsigned int i = 0; seq_puts(s, "clk stats\n"); for (i = 0; i < NVCSI_PHY_CIL_NUM_LANE; i++) { seq_printf(s, "0x%08x 0x%08x\n", debugfs_deskew_clk_stats_high[i], debugfs_deskew_clk_stats_low[i]); } seq_puts(s, "data stats\n"); for (i = 0; i < NVCSI_PHY_CIL_NUM_LANE; i++) { seq_printf(s, "0x%08x 0x%08x\n", debugfs_deskew_data_stats_high[i], debugfs_deskew_data_stats_low[i]); } }