diff options
Diffstat (limited to 'drivers/phy/phy-imx93-mipi-dphy.c')
-rw-r--r-- | drivers/phy/phy-imx93-mipi-dphy.c | 527 |
1 files changed, 527 insertions, 0 deletions
diff --git a/drivers/phy/phy-imx93-mipi-dphy.c b/drivers/phy/phy-imx93-mipi-dphy.c new file mode 100644 index 0000000000..54f2d03c1d --- /dev/null +++ b/drivers/phy/phy-imx93-mipi-dphy.c @@ -0,0 +1,527 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright 2022 NXP + */ + +#include <common.h> +#include <asm/io.h> +#include <dm.h> +#include <errno.h> +#include <generic-phy.h> +#include <linux/bitfield.h> +#include <linux/bitops.h> +#include <linux/err.h> +#include <linux/delay.h> +#include <clk.h> +#include <regmap.h> +#include <dm/device_compat.h> +#include <phy-mipi-dphy.h> +#include <div64.h> + +/* DPHY registers */ +#define DSI_REG 0x4c +#define CFGCLKFREQRANGE_MASK GENMASK(5, 0) +#define CFGCLKFREQRANGE(x) FIELD_PREP(CFGCLKFREQRANGE_MASK, (x)) +#define CLKSEL_MASK GENMASK(7, 6) +#define CLKSEL_STOP FIELD_PREP(CLKSEL_MASK, 0) +#define CLKSEL_GEN FIELD_PREP(CLKSEL_MASK, 1) +#define CLKSEL_EXT FIELD_PREP(CLKSEL_MASK, 2) +#define HSFREQRANGE_MASK GENMASK(14, 8) +#define HSFREQRANGE(x) FIELD_PREP(HSFREQRANGE_MASK, (x)) +#define UPDATE_PLL BIT(17) +#define SHADOW_CLR BIT(18) +#define CLK_EXT BIT(19) + +#define DSI_WRITE_REG0 0x50 +#define M_MASK GENMASK(9, 0) +#define M(x) FIELD_PREP(M_MASK, ((x) - 2)) +#define N_MASK GENMASK(13, 10) +#define N(x) FIELD_PREP(N_MASK, ((x) - 1)) +#define VCO_CTRL_MASK GENMASK(19, 14) +#define VCO_CTRL(x) FIELD_PREP(VCO_CTRL_MASK, (x)) +#define PROP_CTRL_MASK GENMASK(25, 20) +#define PROP_CTRL(x) FIELD_PREP(PROP_CTRL_MASK, (x)) +#define INT_CTRL_MASK GENMASK(31, 26) +#define INT_CTRL(x) FIELD_PREP(INT_CTRL_MASK, (x)) + +#define DSI_WRITE_REG1 0x54 +#define GMP_CTRL_MASK GENMASK(1, 0) +#define GMP_CTRL(x) FIELD_PREP(GMP_CTRL_MASK, (x)) +#define CPBIAS_CTRL_MASK GENMASK(8, 2) +#define CPBIAS_CTRL(x) FIELD_PREP(CPBIAS_CTRL_MASK, (x)) +#define PLL_SHADOW_CTRL BIT(9) + +#define DSI_READ_REG1 0x5c +#define LOCK_PLL BIT(10) + +#define MHZ(x) ((x) * 1000000UL) + +#define REF_CLK_RATE_MAX MHZ(64) +#define REF_CLK_RATE_MIN MHZ(2) +#define FOUT_MAX MHZ(1250) +#define FOUT_MIN MHZ(40) +#define FVCO_DIV_FACTOR MHZ(80) + +#define MBPS(x) ((x) * 1000000UL) + +#define DATA_RATE_MAX_SPEED MBPS(2500) +#define DATA_RATE_MIN_SPEED MBPS(80) + +#define M_MAX 625UL +#define M_MIN 64UL + +#define N_MAX 16U +#define N_MIN 1U + +#define PLL_LOCK_SLEEP 10 +#define PLL_LOCK_TIMEOUT 1000 + +struct dw_dphy_cfg { + u32 m; /* PLL Feedback Multiplication Ratio */ + u32 n; /* PLL Input Frequency Division Ratio */ +}; + +struct dw_dphy_priv { + struct regmap *regmap; + struct clk ref_clk; + struct clk cfg_clk; + unsigned long ref_clk_rate; +}; + +struct dw_dphy_vco_prop { + unsigned int max_fout; + u8 vco_cntl; + u8 prop_cntl; +}; + +struct dw_dphy_hsfreqrange { + unsigned int max_mbps; + u8 hsfreqrange; +}; + +/* Databook Table 3-13 Charge-pump Programmability */ +static const struct dw_dphy_vco_prop vco_prop_map[] = { + { 55, 0x3f, 0x0d }, + { 82, 0x37, 0x0d }, + { 110, 0x2f, 0x0d }, + { 165, 0x27, 0x0d }, + { 220, 0x1f, 0x0d }, + { 330, 0x17, 0x0d }, + { 440, 0x0f, 0x0d }, + { 660, 0x07, 0x0d }, + { 1149, 0x03, 0x0d }, + { 1152, 0x01, 0x0d }, + { 1250, 0x01, 0x0e }, +}; + +/* Databook Table 5-7 Frequency Ranges and Defaults */ +static const struct dw_dphy_hsfreqrange hsfreqrange_map[] = { + { 89, 0x00 }, + { 99, 0x10 }, + { 109, 0x20 }, + { 119, 0x30 }, + { 129, 0x01 }, + { 139, 0x11 }, + { 149, 0x21 }, + { 159, 0x31 }, + { 169, 0x02 }, + { 179, 0x12 }, + { 189, 0x22 }, + { 204, 0x32 }, + { 219, 0x03 }, + { 234, 0x13 }, + { 249, 0x23 }, + { 274, 0x33 }, + { 299, 0x04 }, + { 324, 0x14 }, + { 349, 0x25 }, + { 399, 0x35 }, + { 449, 0x05 }, + { 499, 0x16 }, + { 549, 0x26 }, + { 599, 0x37 }, + { 649, 0x07 }, + { 699, 0x18 }, + { 749, 0x28 }, + { 799, 0x39 }, + { 849, 0x09 }, + { 899, 0x19 }, + { 949, 0x29 }, + { 999, 0x3a }, + { 1049, 0x0a }, + { 1099, 0x1a }, + { 1149, 0x2a }, + { 1199, 0x3b }, + { 1249, 0x0b }, + { 1299, 0x1b }, + { 1349, 0x2b }, + { 1399, 0x3c }, + { 1449, 0x0c }, + { 1499, 0x1c }, + { 1549, 0x2c }, + { 1599, 0x3d }, + { 1649, 0x0d }, + { 1699, 0x1d }, + { 1749, 0x2e }, + { 1799, 0x3e }, + { 1849, 0x0e }, + { 1899, 0x1e }, + { 1949, 0x2f }, + { 1999, 0x3f }, + { 2049, 0x0f }, + { 2099, 0x40 }, + { 2149, 0x41 }, + { 2199, 0x42 }, + { 2249, 0x43 }, + { 2299, 0x44 }, + { 2349, 0x45 }, + { 2399, 0x46 }, + { 2449, 0x47 }, + { 2499, 0x48 }, + { 2500, 0x49 }, +}; + +static int phy_write(struct phy *phy, u32 value, unsigned int reg) +{ + struct dw_dphy_priv *priv = dev_get_priv(phy->dev); + int ret; + + ret = regmap_write(priv->regmap, reg, value); + if (ret < 0) + dev_err(phy->dev, "failed to write reg %u: %d\n", reg, ret); + return ret; +} + +static inline unsigned long data_rate_to_fout(unsigned long data_rate) +{ + /* Fout is half of data rate */ + return data_rate / 2; +} + +static int +dw_dphy_config_from_opts(struct phy *phy, + struct phy_configure_opts_mipi_dphy *dphy_opts, + struct dw_dphy_cfg *cfg) +{ + struct dw_dphy_priv *priv = dev_get_priv(phy->dev); + unsigned long fin = priv->ref_clk_rate; + unsigned long fout; + unsigned long best_fout = 0; + unsigned int fvco_div; + unsigned int min_n, max_n, n, best_n; + unsigned long m, best_m; + unsigned long min_delta = ULONG_MAX; + unsigned long tmp, delta; + + if (dphy_opts->hs_clk_rate < DATA_RATE_MIN_SPEED || + dphy_opts->hs_clk_rate > DATA_RATE_MAX_SPEED) { + dev_dbg(phy->dev, "invalid data rate per lane: %lu\n", + dphy_opts->hs_clk_rate); + return -EINVAL; + } + + fout = data_rate_to_fout(dphy_opts->hs_clk_rate); + + /* Fout = Fvco / Fvco_div = (Fin * M) / (Fvco_div * N) */ + fvco_div = 8UL / min(DIV_ROUND_UP(fout, FVCO_DIV_FACTOR), 8UL); + + /* limitation: 2MHz <= Fin / N <= 8MHz */ + min_n = DIV_ROUND_UP(fin, MHZ(8)); + max_n = DIV_ROUND_DOWN_ULL(fin, MHZ(2)); + + /* clamp possible N(s) */ + min_n = clamp(min_n, N_MIN, N_MAX); + max_n = clamp(max_n, N_MIN, N_MAX); + + dev_dbg(phy->dev, "Fout = %lu, Fvco_div = %u, n_range = [%u, %u]\n", + fout, fvco_div, min_n, max_n); + + for (n = min_n; n <= max_n; n++) { + /* M = (Fout * N * Fvco_div) / Fin */ + tmp = fout * n * fvco_div; + m = DIV_ROUND_CLOSEST(tmp, fin); + + /* check M range */ + if (m < M_MIN || m > M_MAX) + continue; + + /* calculate temporary Fout */ + tmp = m * fin; + do_div(tmp, n * fvco_div); + if (tmp < FOUT_MIN || tmp > FOUT_MAX) + continue; + + delta = abs(fout - tmp); + if (delta < min_delta) { + best_n = n; + best_m = m; + min_delta = delta; + best_fout = tmp; + } + } + + if (best_fout) { + cfg->m = best_m; + cfg->n = best_n; + dphy_opts->hs_clk_rate = best_fout * 2; + dev_dbg(phy->dev, "best Fout = %lu, m = %u, n = %u\n", + best_fout, cfg->m, cfg->n); + } else { + dev_dbg(phy->dev, "failed to find best Fout\n"); + return -EINVAL; + } + + return 0; +} + +static void dw_dphy_clear_shadow(struct phy *phy) +{ + /* Select clock generation first. */ + phy_write(phy, CLKSEL_GEN, DSI_REG); + + /* Clear shadow after clock selection is done a while. */ + udelay(2); + phy_write(phy, CLKSEL_GEN | SHADOW_CLR, DSI_REG); + + /* + * A minimum pulse of 5ns on shadow_clear signal, + * according to Databook Figure 3-3 Initialization Timing Diagram. + */ + udelay(2); + phy_write(phy, CLKSEL_GEN, DSI_REG); +} + +static u32 dw_dphy_get_cfgclkrange(struct phy *phy) +{ + struct dw_dphy_priv *priv = dev_get_priv(phy->dev); + + return (clk_get_rate(&priv->cfg_clk) / MHZ(1) - 17) * 4; +} + +static u8 dw_dphy_get_hsfreqrange(struct phy_configure_opts_mipi_dphy *dphy_opts) +{ + unsigned int mbps = dphy_opts->hs_clk_rate / MHZ(1); + int i; + + for (i = 0; i < ARRAY_SIZE(hsfreqrange_map); i++) + if (mbps <= hsfreqrange_map[i].max_mbps) + return hsfreqrange_map[i].hsfreqrange; + + return 0; +} + +static u8 dw_dphy_get_vco(struct phy_configure_opts_mipi_dphy *dphy_opts) +{ + unsigned int fout = data_rate_to_fout(dphy_opts->hs_clk_rate) / MHZ(1); + int i; + + for (i = 0; i < ARRAY_SIZE(vco_prop_map); i++) + if (fout <= vco_prop_map[i].max_fout) + return vco_prop_map[i].vco_cntl; + + return 0; +} + +static u8 dw_dphy_get_prop(struct phy_configure_opts_mipi_dphy *dphy_opts) +{ + unsigned int fout = data_rate_to_fout(dphy_opts->hs_clk_rate) / MHZ(1); + int i; + + for (i = 0; i < ARRAY_SIZE(vco_prop_map); i++) + if (fout <= vco_prop_map[i].max_fout) + return vco_prop_map[i].prop_cntl; + + return 0; +} + +static int dw_dphy_configure(struct phy *phy, void *params) +{ + struct dw_dphy_cfg cfg = { 0 }; + u32 val; + int ret; + struct phy_configure_opts_mipi_dphy *opts = (struct phy_configure_opts_mipi_dphy *)params; + + ret = dw_dphy_config_from_opts(phy, opts, &cfg); + if (ret) + return ret; + + dw_dphy_clear_shadow(phy); + + /* reg */ + val = CLKSEL_GEN | + CFGCLKFREQRANGE(dw_dphy_get_cfgclkrange(phy)) | + HSFREQRANGE(dw_dphy_get_hsfreqrange(opts)); + phy_write(phy, val, DSI_REG); + + /* w_reg0 */ + val = M(cfg.m) | N(cfg.n) | INT_CTRL(0) | + VCO_CTRL(dw_dphy_get_vco(opts)) | + PROP_CTRL(dw_dphy_get_prop(opts)); + phy_write(phy, val, DSI_WRITE_REG0); + + /* w_reg1 */ + phy_write(phy, GMP_CTRL(1) | CPBIAS_CTRL(0x10), DSI_WRITE_REG1); + + return 0; +} + +static void dw_dphy_clear_reg(struct phy *phy) +{ + phy_write(phy, 0, DSI_REG); + phy_write(phy, 0, DSI_WRITE_REG0); + phy_write(phy, 0, DSI_WRITE_REG1); +} + +static int dw_dphy_init(struct phy *phy) +{ + struct dw_dphy_priv *priv = dev_get_priv(phy->dev); + int ret; + + ret = clk_prepare_enable(&priv->cfg_clk); + if (ret < 0) { + dev_err(phy->dev, "failed to enable config clock: %d\n", ret); + return ret; + } + + dw_dphy_clear_reg(phy); + + return 0; +} + +static int dw_dphy_exit(struct phy *phy) +{ + struct dw_dphy_priv *priv = dev_get_priv(phy->dev); + + dw_dphy_clear_reg(phy); + clk_disable_unprepare(&priv->cfg_clk); + + return 0; +} + +static int dw_dphy_update_pll(struct phy *phy) +{ + struct dw_dphy_priv *priv = dev_get_priv(phy->dev); + int ret; + + ret = regmap_update_bits(priv->regmap, DSI_REG, UPDATE_PLL, UPDATE_PLL); + if (ret < 0) { + dev_err(phy->dev, "failed to set UPDATE_PLL: %d\n", ret); + return ret; + } + + /* + * The updatepll signal should be asserted for a minimum of four clkin + * cycles, according to Databook Figure 3-3 Initialization Timing + * Diagram. + */ + udelay(10); + + ret = regmap_update_bits(priv->regmap, DSI_REG, UPDATE_PLL, 0); + if (ret < 0) { + dev_err(phy->dev, "failed to clear UPDATE_PLL: %d\n", ret); + return ret; + } + + return 0; +} + +static int dw_dphy_power_on(struct phy *phy) +{ + struct dw_dphy_priv *priv = dev_get_priv(phy->dev); + int ret; + + ret = clk_prepare_enable(&priv->ref_clk); + if (ret < 0) { + dev_err(phy->dev, "failed to enable ref clock: %d\n", ret); + return ret; + } + + /* + * At least 10 refclk cycles are required before updatePLL assertion, + * according to Databook Figure 3-3 Initialization Timing Diagram. + */ + udelay(10); + + ret = dw_dphy_update_pll(phy); + if (ret < 0) { + clk_disable_unprepare(&priv->ref_clk); + return ret; + } + + return 0; +} + +static int dw_dphy_power_off(struct phy *phy) +{ + struct dw_dphy_priv *priv = dev_get_priv(phy->dev); + + dw_dphy_clear_reg(phy); + clk_disable_unprepare(&priv->ref_clk); + + return 0; +} + +static const struct phy_ops imx_dw_dphy_phy_ops = { + .init = dw_dphy_init, + .exit = dw_dphy_exit, + .power_on = dw_dphy_power_on, + .power_off = dw_dphy_power_off, + .configure = dw_dphy_configure, +}; + +static int imx_dw_dphy_probe(struct udevice *dev) +{ + struct dw_dphy_priv *priv = dev_get_priv(dev); + int ret; + + ret = regmap_init_mem(ofnode_get_parent(dev_ofnode(dev)), &priv->regmap); + if (ret) { + dev_err(dev, "failed to get regmap %d\n", ret); + return ret; + } + +#if CONFIG_IS_ENABLED(CLK) + ret = clk_get_by_name(dev, "phy_cfg", &priv->cfg_clk); + if (ret) { + dev_err(dev, "failed to get config clock %d\n", ret); + return ret; + } + + ret = clk_get_by_name(dev, "phy_ref", &priv->ref_clk); + if (ret) { + dev_err(dev, "failed to get ref clock %d\n", ret); + return ret; + } + + priv->ref_clk_rate = clk_get_rate(&priv->ref_clk); + if (priv->ref_clk_rate < REF_CLK_RATE_MIN || + priv->ref_clk_rate > REF_CLK_RATE_MAX) { + dev_err(dev, "invalid ref clock rate %lu\n", + priv->ref_clk_rate); + return -EINVAL; + } + dev_dbg(dev, "ref clock rate: %lu\n", priv->ref_clk_rate); +#endif + + return 0; +} + +static int imx_dw_dphy_remove(struct udevice *dev) +{ + return 0; +} + +static const struct udevice_id imx_dw_mipi_dphy_of_match[] = { + { .compatible = "fsl,imx93-mipi-dphy" }, + { /* sentinel */ } +}; + +U_BOOT_DRIVER(imx_dw_mipi_dphy) = { + .name = "imx_dw_mipi_dphy", + .id = UCLASS_PHY, + .of_match = imx_dw_mipi_dphy_of_match, + .probe = imx_dw_dphy_probe, + .remove = imx_dw_dphy_remove, + .ops = &imx_dw_dphy_phy_ops, + .priv_auto = sizeof(struct dw_dphy_priv), +}; |