| // 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), |
| }; |