--- /dev/null
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2013 NVIDIA Corporation
+ * Copyright (c) 2023 Svyatoslav Ryhel <clamor95@gmail.com>
+ */
+
+#include <dm.h>
+#include <clk.h>
+#include <misc.h>
+#include <linux/delay.h>
+#include <linux/iopoll.h>
+
+#include <asm/io.h>
+
+/* MIPI control registers 0x00 ~ 0x60 */
+struct mipi_ctlr {
+ uint mipi_cal_ctrl;
+ uint mipi_cal_autocal_ctrl;
+ uint mipi_cal_status;
+
+ uint unused1[2];
+
+ uint mipi_cal_config_csia;
+ uint mipi_cal_config_csib;
+ uint mipi_cal_config_csic;
+ uint mipi_cal_config_csid;
+ uint mipi_cal_config_csie;
+
+ uint unused2[4];
+
+ uint mipi_cal_config_dsia;
+ uint mipi_cal_config_dsib;
+ uint mipi_cal_config_dsic;
+ uint mipi_cal_config_dsid;
+
+ uint unused3[4];
+
+ uint mipi_cal_bias_pad_cfg0;
+ uint mipi_cal_bias_pad_cfg1;
+ uint mipi_cal_bias_pad_cfg2;
+};
+
+#define MIPI_CAL_CTRL_NOISE_FILTER(x) (((x) & 0xf) << 26)
+#define MIPI_CAL_CTRL_PRESCALE(x) (((x) & 0x3) << 24)
+#define MIPI_CAL_CTRL_CLKEN_OVR BIT(4)
+#define MIPI_CAL_CTRL_START BIT(0)
+
+#define MIPI_CAL_STATUS_DONE BIT(16)
+#define MIPI_CAL_STATUS_ACTIVE BIT(0)
+
+#define MIPI_CAL_OVERIDE(x) (((x) & 0x1) << 30)
+#define MIPI_CAL_SEL(x) (((x) & 0x1) << 21)
+#define MIPI_CAL_HSPDOS(x) (((x) & 0x1f) << 16)
+#define MIPI_CAL_HSPUOS(x) (((x) & 0x1f) << 8)
+#define MIPI_CAL_TERMOS(x) (((x) & 0x1f) << 0)
+
+#define MIPI_CAL_BIAS_PAD_PDVCLAMP BIT(1)
+#define MIPI_CAL_BIAS_PAD_E_VCLAMP_REF BIT(0)
+
+#define MIPI_CAL_BIAS_PAD_DRV_DN_REF(x) (((x) & 0x7) << 16)
+#define MIPI_CAL_BIAS_PAD_DRV_UP_REF(x) (((x) & 0x7) << 8)
+
+#define MIPI_CAL_BIAS_PAD_VCLAMP(x) (((x) & 0x7) << 16)
+#define MIPI_CAL_BIAS_PAD_VAUXP(x) (((x) & 0x7) << 4)
+#define MIPI_CAL_BIAS_PAD_PDVREG BIT(1)
+
+struct tegra_mipi_priv {
+ struct mipi_ctlr *mipi;
+ struct clk *mipi_cal;
+};
+
+static int tegra_mipi_calibrate(struct udevice *dev, int offset, const void *buf,
+ int size)
+{
+ struct tegra_mipi_priv *priv = dev_get_priv(dev);
+ u32 value;
+
+ value = MIPI_CAL_BIAS_PAD_DRV_DN_REF(0x2) |
+ MIPI_CAL_BIAS_PAD_DRV_UP_REF(0x0);
+ writel(value, &priv->mipi->mipi_cal_bias_pad_cfg1);
+
+ value = readl(&priv->mipi->mipi_cal_bias_pad_cfg2);
+ value &= ~MIPI_CAL_BIAS_PAD_VCLAMP(0x7);
+ value &= ~MIPI_CAL_BIAS_PAD_VAUXP(0x7);
+ writel(value, &priv->mipi->mipi_cal_bias_pad_cfg2);
+
+ value = MIPI_CAL_OVERIDE(0x0) | MIPI_CAL_SEL(0x1) |
+ MIPI_CAL_HSPDOS(0x0) | MIPI_CAL_HSPUOS(0x4) |
+ MIPI_CAL_TERMOS(0x5);
+ writel(value, &priv->mipi->mipi_cal_config_dsia);
+ writel(value, &priv->mipi->mipi_cal_config_dsib);
+
+ /* Deselect PAD C */
+ value = readl(&priv->mipi->mipi_cal_config_dsic);
+ value &= ~(MIPI_CAL_SEL(0x1));
+ writel(value, &priv->mipi->mipi_cal_config_dsic);
+
+ /* Deselect PAD D */
+ value = readl(&priv->mipi->mipi_cal_config_dsid);
+ value &= ~(MIPI_CAL_SEL(0x1));
+ writel(value, &priv->mipi->mipi_cal_config_dsid);
+
+ value = readl(&priv->mipi->mipi_cal_ctrl);
+ value &= ~MIPI_CAL_CTRL_NOISE_FILTER(0xf);
+ value &= ~MIPI_CAL_CTRL_PRESCALE(0x3);
+ value |= MIPI_CAL_CTRL_NOISE_FILTER(0xa) |
+ MIPI_CAL_CTRL_PRESCALE(0x2) |
+ MIPI_CAL_CTRL_CLKEN_OVR;
+ writel(value, &priv->mipi->mipi_cal_ctrl);
+
+ /* clear any pending status bits */
+ value = readl(&priv->mipi->mipi_cal_status);
+ writel(value, &priv->mipi->mipi_cal_status);
+
+ value = readl(&priv->mipi->mipi_cal_ctrl);
+ value |= MIPI_CAL_CTRL_START;
+ writel(value, &priv->mipi->mipi_cal_ctrl);
+
+ /*
+ * Wait for min 72uS to let calibration logic finish calibration
+ * sequence codes before waiting for pads idle state to apply the
+ * results.
+ */
+ udelay(80);
+
+ return readl_poll_sleep_timeout(&priv->mipi->mipi_cal_status, value,
+ !(value & MIPI_CAL_STATUS_ACTIVE) &&
+ (value & MIPI_CAL_STATUS_DONE), 100,
+ 250000);
+}
+
+static int tegra_mipi_enable(struct udevice *dev, bool val)
+{
+ struct tegra_mipi_priv *priv = dev_get_priv(dev);
+ u32 value;
+
+ clk_enable(priv->mipi_cal);
+
+ value = readl(&priv->mipi->mipi_cal_bias_pad_cfg0);
+ value &= ~MIPI_CAL_BIAS_PAD_PDVCLAMP;
+ value |= MIPI_CAL_BIAS_PAD_E_VCLAMP_REF;
+ writel(value, &priv->mipi->mipi_cal_bias_pad_cfg0);
+
+ value = readl(&priv->mipi->mipi_cal_bias_pad_cfg2);
+ value &= ~MIPI_CAL_BIAS_PAD_PDVREG;
+ writel(value, &priv->mipi->mipi_cal_bias_pad_cfg2);
+
+ return 0;
+}
+
+static const struct misc_ops tegra_mipi_ops = {
+ .write = tegra_mipi_calibrate,
+ .set_enabled = tegra_mipi_enable,
+};
+
+static int tegra_mipi_probe(struct udevice *dev)
+{
+ struct tegra_mipi_priv *priv = dev_get_priv(dev);
+
+ priv->mipi = (struct mipi_ctlr *)dev_read_addr_ptr(dev);
+ if (!priv->mipi) {
+ log_debug("%s: no MIPI controller address\n", __func__);
+ return -EINVAL;
+ }
+
+ priv->mipi_cal = devm_clk_get(dev, NULL);
+ if (IS_ERR(priv->mipi_cal)) {
+ log_debug("%s: Could not get MIPI clock: %ld\n",
+ __func__, PTR_ERR(priv->mipi_cal));
+ return PTR_ERR(priv->mipi_cal);
+ }
+
+ return 0;
+}
+
+static const struct udevice_id tegra_mipi_ids[] = {
+ { .compatible = "nvidia,tegra114-mipi" },
+ { }
+};
+
+U_BOOT_DRIVER(tegra_mipi) = {
+ .name = "tegra_mipi",
+ .id = UCLASS_MISC,
+ .ops = &tegra_mipi_ops,
+ .of_match = tegra_mipi_ids,
+ .probe = tegra_mipi_probe,
+ .priv_auto = sizeof(struct tegra_mipi_priv),
+};