18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0+ 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * Copyright 2017,2018 NXP 48c2ecf20Sopenharmony_ci * Copyright 2019 Purism SPC 58c2ecf20Sopenharmony_ci */ 68c2ecf20Sopenharmony_ci 78c2ecf20Sopenharmony_ci#include <linux/clk.h> 88c2ecf20Sopenharmony_ci#include <linux/clk-provider.h> 98c2ecf20Sopenharmony_ci#include <linux/delay.h> 108c2ecf20Sopenharmony_ci#include <linux/io.h> 118c2ecf20Sopenharmony_ci#include <linux/kernel.h> 128c2ecf20Sopenharmony_ci#include <linux/module.h> 138c2ecf20Sopenharmony_ci#include <linux/of.h> 148c2ecf20Sopenharmony_ci#include <linux/of_platform.h> 158c2ecf20Sopenharmony_ci#include <linux/phy/phy.h> 168c2ecf20Sopenharmony_ci#include <linux/platform_device.h> 178c2ecf20Sopenharmony_ci#include <linux/regmap.h> 188c2ecf20Sopenharmony_ci 198c2ecf20Sopenharmony_ci/* DPHY registers */ 208c2ecf20Sopenharmony_ci#define DPHY_PD_DPHY 0x00 218c2ecf20Sopenharmony_ci#define DPHY_M_PRG_HS_PREPARE 0x04 228c2ecf20Sopenharmony_ci#define DPHY_MC_PRG_HS_PREPARE 0x08 238c2ecf20Sopenharmony_ci#define DPHY_M_PRG_HS_ZERO 0x0c 248c2ecf20Sopenharmony_ci#define DPHY_MC_PRG_HS_ZERO 0x10 258c2ecf20Sopenharmony_ci#define DPHY_M_PRG_HS_TRAIL 0x14 268c2ecf20Sopenharmony_ci#define DPHY_MC_PRG_HS_TRAIL 0x18 278c2ecf20Sopenharmony_ci#define DPHY_PD_PLL 0x1c 288c2ecf20Sopenharmony_ci#define DPHY_TST 0x20 298c2ecf20Sopenharmony_ci#define DPHY_CN 0x24 308c2ecf20Sopenharmony_ci#define DPHY_CM 0x28 318c2ecf20Sopenharmony_ci#define DPHY_CO 0x2c 328c2ecf20Sopenharmony_ci#define DPHY_LOCK 0x30 338c2ecf20Sopenharmony_ci#define DPHY_LOCK_BYP 0x34 348c2ecf20Sopenharmony_ci#define DPHY_REG_BYPASS_PLL 0x4C 358c2ecf20Sopenharmony_ci 368c2ecf20Sopenharmony_ci#define MBPS(x) ((x) * 1000000) 378c2ecf20Sopenharmony_ci 388c2ecf20Sopenharmony_ci#define DATA_RATE_MAX_SPEED MBPS(1500) 398c2ecf20Sopenharmony_ci#define DATA_RATE_MIN_SPEED MBPS(80) 408c2ecf20Sopenharmony_ci 418c2ecf20Sopenharmony_ci#define PLL_LOCK_SLEEP 10 428c2ecf20Sopenharmony_ci#define PLL_LOCK_TIMEOUT 1000 438c2ecf20Sopenharmony_ci 448c2ecf20Sopenharmony_ci#define CN_BUF 0xcb7a89c0 458c2ecf20Sopenharmony_ci#define CO_BUF 0x63 468c2ecf20Sopenharmony_ci#define CM(x) ( \ 478c2ecf20Sopenharmony_ci ((x) < 32) ? 0xe0 | ((x) - 16) : \ 488c2ecf20Sopenharmony_ci ((x) < 64) ? 0xc0 | ((x) - 32) : \ 498c2ecf20Sopenharmony_ci ((x) < 128) ? 0x80 | ((x) - 64) : \ 508c2ecf20Sopenharmony_ci ((x) - 128)) 518c2ecf20Sopenharmony_ci#define CN(x) (((x) == 1) ? 0x1f : (((CN_BUF) >> ((x) - 1)) & 0x1f)) 528c2ecf20Sopenharmony_ci#define CO(x) ((CO_BUF) >> (8 - (x)) & 0x03) 538c2ecf20Sopenharmony_ci 548c2ecf20Sopenharmony_ci/* PHY power on is active low */ 558c2ecf20Sopenharmony_ci#define PWR_ON 0 568c2ecf20Sopenharmony_ci#define PWR_OFF 1 578c2ecf20Sopenharmony_ci 588c2ecf20Sopenharmony_cienum mixel_dphy_devtype { 598c2ecf20Sopenharmony_ci MIXEL_IMX8MQ, 608c2ecf20Sopenharmony_ci}; 618c2ecf20Sopenharmony_ci 628c2ecf20Sopenharmony_cistruct mixel_dphy_devdata { 638c2ecf20Sopenharmony_ci u8 reg_tx_rcal; 648c2ecf20Sopenharmony_ci u8 reg_auto_pd_en; 658c2ecf20Sopenharmony_ci u8 reg_rxlprp; 668c2ecf20Sopenharmony_ci u8 reg_rxcdrp; 678c2ecf20Sopenharmony_ci u8 reg_rxhs_settle; 688c2ecf20Sopenharmony_ci}; 698c2ecf20Sopenharmony_ci 708c2ecf20Sopenharmony_cistatic const struct mixel_dphy_devdata mixel_dphy_devdata[] = { 718c2ecf20Sopenharmony_ci [MIXEL_IMX8MQ] = { 728c2ecf20Sopenharmony_ci .reg_tx_rcal = 0x38, 738c2ecf20Sopenharmony_ci .reg_auto_pd_en = 0x3c, 748c2ecf20Sopenharmony_ci .reg_rxlprp = 0x40, 758c2ecf20Sopenharmony_ci .reg_rxcdrp = 0x44, 768c2ecf20Sopenharmony_ci .reg_rxhs_settle = 0x48, 778c2ecf20Sopenharmony_ci }, 788c2ecf20Sopenharmony_ci}; 798c2ecf20Sopenharmony_ci 808c2ecf20Sopenharmony_cistruct mixel_dphy_cfg { 818c2ecf20Sopenharmony_ci /* DPHY PLL parameters */ 828c2ecf20Sopenharmony_ci u32 cm; 838c2ecf20Sopenharmony_ci u32 cn; 848c2ecf20Sopenharmony_ci u32 co; 858c2ecf20Sopenharmony_ci /* DPHY register values */ 868c2ecf20Sopenharmony_ci u8 mc_prg_hs_prepare; 878c2ecf20Sopenharmony_ci u8 m_prg_hs_prepare; 888c2ecf20Sopenharmony_ci u8 mc_prg_hs_zero; 898c2ecf20Sopenharmony_ci u8 m_prg_hs_zero; 908c2ecf20Sopenharmony_ci u8 mc_prg_hs_trail; 918c2ecf20Sopenharmony_ci u8 m_prg_hs_trail; 928c2ecf20Sopenharmony_ci u8 rxhs_settle; 938c2ecf20Sopenharmony_ci}; 948c2ecf20Sopenharmony_ci 958c2ecf20Sopenharmony_cistruct mixel_dphy_priv { 968c2ecf20Sopenharmony_ci struct mixel_dphy_cfg cfg; 978c2ecf20Sopenharmony_ci struct regmap *regmap; 988c2ecf20Sopenharmony_ci struct clk *phy_ref_clk; 998c2ecf20Sopenharmony_ci const struct mixel_dphy_devdata *devdata; 1008c2ecf20Sopenharmony_ci}; 1018c2ecf20Sopenharmony_ci 1028c2ecf20Sopenharmony_cistatic const struct regmap_config mixel_dphy_regmap_config = { 1038c2ecf20Sopenharmony_ci .reg_bits = 8, 1048c2ecf20Sopenharmony_ci .val_bits = 32, 1058c2ecf20Sopenharmony_ci .reg_stride = 4, 1068c2ecf20Sopenharmony_ci .max_register = DPHY_REG_BYPASS_PLL, 1078c2ecf20Sopenharmony_ci .name = "mipi-dphy", 1088c2ecf20Sopenharmony_ci}; 1098c2ecf20Sopenharmony_ci 1108c2ecf20Sopenharmony_cistatic int phy_write(struct phy *phy, u32 value, unsigned int reg) 1118c2ecf20Sopenharmony_ci{ 1128c2ecf20Sopenharmony_ci struct mixel_dphy_priv *priv = phy_get_drvdata(phy); 1138c2ecf20Sopenharmony_ci int ret; 1148c2ecf20Sopenharmony_ci 1158c2ecf20Sopenharmony_ci ret = regmap_write(priv->regmap, reg, value); 1168c2ecf20Sopenharmony_ci if (ret < 0) 1178c2ecf20Sopenharmony_ci dev_err(&phy->dev, "Failed to write DPHY reg %d: %d\n", reg, 1188c2ecf20Sopenharmony_ci ret); 1198c2ecf20Sopenharmony_ci return ret; 1208c2ecf20Sopenharmony_ci} 1218c2ecf20Sopenharmony_ci 1228c2ecf20Sopenharmony_ci/* 1238c2ecf20Sopenharmony_ci * Find a ratio close to the desired one using continued fraction 1248c2ecf20Sopenharmony_ci * approximation ending either at exact match or maximum allowed 1258c2ecf20Sopenharmony_ci * nominator, denominator. 1268c2ecf20Sopenharmony_ci */ 1278c2ecf20Sopenharmony_cistatic void get_best_ratio(u32 *pnum, u32 *pdenom, u32 max_n, u32 max_d) 1288c2ecf20Sopenharmony_ci{ 1298c2ecf20Sopenharmony_ci u32 a = *pnum; 1308c2ecf20Sopenharmony_ci u32 b = *pdenom; 1318c2ecf20Sopenharmony_ci u32 c; 1328c2ecf20Sopenharmony_ci u32 n[] = {0, 1}; 1338c2ecf20Sopenharmony_ci u32 d[] = {1, 0}; 1348c2ecf20Sopenharmony_ci u32 whole; 1358c2ecf20Sopenharmony_ci unsigned int i = 1; 1368c2ecf20Sopenharmony_ci 1378c2ecf20Sopenharmony_ci while (b) { 1388c2ecf20Sopenharmony_ci i ^= 1; 1398c2ecf20Sopenharmony_ci whole = a / b; 1408c2ecf20Sopenharmony_ci n[i] += (n[i ^ 1] * whole); 1418c2ecf20Sopenharmony_ci d[i] += (d[i ^ 1] * whole); 1428c2ecf20Sopenharmony_ci if ((n[i] > max_n) || (d[i] > max_d)) { 1438c2ecf20Sopenharmony_ci i ^= 1; 1448c2ecf20Sopenharmony_ci break; 1458c2ecf20Sopenharmony_ci } 1468c2ecf20Sopenharmony_ci c = a - (b * whole); 1478c2ecf20Sopenharmony_ci a = b; 1488c2ecf20Sopenharmony_ci b = c; 1498c2ecf20Sopenharmony_ci } 1508c2ecf20Sopenharmony_ci *pnum = n[i]; 1518c2ecf20Sopenharmony_ci *pdenom = d[i]; 1528c2ecf20Sopenharmony_ci} 1538c2ecf20Sopenharmony_ci 1548c2ecf20Sopenharmony_cistatic int mixel_dphy_config_from_opts(struct phy *phy, 1558c2ecf20Sopenharmony_ci struct phy_configure_opts_mipi_dphy *dphy_opts, 1568c2ecf20Sopenharmony_ci struct mixel_dphy_cfg *cfg) 1578c2ecf20Sopenharmony_ci{ 1588c2ecf20Sopenharmony_ci struct mixel_dphy_priv *priv = dev_get_drvdata(phy->dev.parent); 1598c2ecf20Sopenharmony_ci unsigned long ref_clk = clk_get_rate(priv->phy_ref_clk); 1608c2ecf20Sopenharmony_ci u32 lp_t, numerator, denominator; 1618c2ecf20Sopenharmony_ci unsigned long long tmp; 1628c2ecf20Sopenharmony_ci u32 n; 1638c2ecf20Sopenharmony_ci int i; 1648c2ecf20Sopenharmony_ci 1658c2ecf20Sopenharmony_ci if (dphy_opts->hs_clk_rate > DATA_RATE_MAX_SPEED || 1668c2ecf20Sopenharmony_ci dphy_opts->hs_clk_rate < DATA_RATE_MIN_SPEED) 1678c2ecf20Sopenharmony_ci return -EINVAL; 1688c2ecf20Sopenharmony_ci 1698c2ecf20Sopenharmony_ci numerator = dphy_opts->hs_clk_rate; 1708c2ecf20Sopenharmony_ci denominator = ref_clk; 1718c2ecf20Sopenharmony_ci get_best_ratio(&numerator, &denominator, 255, 256); 1728c2ecf20Sopenharmony_ci if (!numerator || !denominator) { 1738c2ecf20Sopenharmony_ci dev_err(&phy->dev, "Invalid %d/%d for %ld/%ld\n", 1748c2ecf20Sopenharmony_ci numerator, denominator, 1758c2ecf20Sopenharmony_ci dphy_opts->hs_clk_rate, ref_clk); 1768c2ecf20Sopenharmony_ci return -EINVAL; 1778c2ecf20Sopenharmony_ci } 1788c2ecf20Sopenharmony_ci 1798c2ecf20Sopenharmony_ci while ((numerator < 16) && (denominator <= 128)) { 1808c2ecf20Sopenharmony_ci numerator <<= 1; 1818c2ecf20Sopenharmony_ci denominator <<= 1; 1828c2ecf20Sopenharmony_ci } 1838c2ecf20Sopenharmony_ci /* 1848c2ecf20Sopenharmony_ci * CM ranges between 16 and 255 1858c2ecf20Sopenharmony_ci * CN ranges between 1 and 32 1868c2ecf20Sopenharmony_ci * CO is power of 2: 1, 2, 4, 8 1878c2ecf20Sopenharmony_ci */ 1888c2ecf20Sopenharmony_ci i = __ffs(denominator); 1898c2ecf20Sopenharmony_ci if (i > 3) 1908c2ecf20Sopenharmony_ci i = 3; 1918c2ecf20Sopenharmony_ci cfg->cn = denominator >> i; 1928c2ecf20Sopenharmony_ci cfg->co = 1 << i; 1938c2ecf20Sopenharmony_ci cfg->cm = numerator; 1948c2ecf20Sopenharmony_ci 1958c2ecf20Sopenharmony_ci if (cfg->cm < 16 || cfg->cm > 255 || 1968c2ecf20Sopenharmony_ci cfg->cn < 1 || cfg->cn > 32 || 1978c2ecf20Sopenharmony_ci cfg->co < 1 || cfg->co > 8) { 1988c2ecf20Sopenharmony_ci dev_err(&phy->dev, "Invalid CM/CN/CO values: %u/%u/%u\n", 1998c2ecf20Sopenharmony_ci cfg->cm, cfg->cn, cfg->co); 2008c2ecf20Sopenharmony_ci dev_err(&phy->dev, "for hs_clk/ref_clk=%ld/%ld ~ %d/%d\n", 2018c2ecf20Sopenharmony_ci dphy_opts->hs_clk_rate, ref_clk, 2028c2ecf20Sopenharmony_ci numerator, denominator); 2038c2ecf20Sopenharmony_ci return -EINVAL; 2048c2ecf20Sopenharmony_ci } 2058c2ecf20Sopenharmony_ci 2068c2ecf20Sopenharmony_ci dev_dbg(&phy->dev, "hs_clk/ref_clk=%ld/%ld ~ %d/%d\n", 2078c2ecf20Sopenharmony_ci dphy_opts->hs_clk_rate, ref_clk, numerator, denominator); 2088c2ecf20Sopenharmony_ci 2098c2ecf20Sopenharmony_ci /* LP clock period */ 2108c2ecf20Sopenharmony_ci tmp = 1000000000000LL; 2118c2ecf20Sopenharmony_ci do_div(tmp, dphy_opts->lp_clk_rate); /* ps */ 2128c2ecf20Sopenharmony_ci if (tmp > ULONG_MAX) 2138c2ecf20Sopenharmony_ci return -EINVAL; 2148c2ecf20Sopenharmony_ci 2158c2ecf20Sopenharmony_ci lp_t = tmp; 2168c2ecf20Sopenharmony_ci dev_dbg(&phy->dev, "LP clock %lu, period: %u ps\n", 2178c2ecf20Sopenharmony_ci dphy_opts->lp_clk_rate, lp_t); 2188c2ecf20Sopenharmony_ci 2198c2ecf20Sopenharmony_ci /* hs_prepare: in lp clock periods */ 2208c2ecf20Sopenharmony_ci if (2 * dphy_opts->hs_prepare > 5 * lp_t) { 2218c2ecf20Sopenharmony_ci dev_err(&phy->dev, 2228c2ecf20Sopenharmony_ci "hs_prepare (%u) > 2.5 * lp clock period (%u)\n", 2238c2ecf20Sopenharmony_ci dphy_opts->hs_prepare, lp_t); 2248c2ecf20Sopenharmony_ci return -EINVAL; 2258c2ecf20Sopenharmony_ci } 2268c2ecf20Sopenharmony_ci /* 00: lp_t, 01: 1.5 * lp_t, 10: 2 * lp_t, 11: 2.5 * lp_t */ 2278c2ecf20Sopenharmony_ci if (dphy_opts->hs_prepare < lp_t) { 2288c2ecf20Sopenharmony_ci n = 0; 2298c2ecf20Sopenharmony_ci } else { 2308c2ecf20Sopenharmony_ci tmp = 2 * (dphy_opts->hs_prepare - lp_t); 2318c2ecf20Sopenharmony_ci do_div(tmp, lp_t); 2328c2ecf20Sopenharmony_ci n = tmp; 2338c2ecf20Sopenharmony_ci } 2348c2ecf20Sopenharmony_ci cfg->m_prg_hs_prepare = n; 2358c2ecf20Sopenharmony_ci 2368c2ecf20Sopenharmony_ci /* clk_prepare: in lp clock periods */ 2378c2ecf20Sopenharmony_ci if (2 * dphy_opts->clk_prepare > 3 * lp_t) { 2388c2ecf20Sopenharmony_ci dev_err(&phy->dev, 2398c2ecf20Sopenharmony_ci "clk_prepare (%u) > 1.5 * lp clock period (%u)\n", 2408c2ecf20Sopenharmony_ci dphy_opts->clk_prepare, lp_t); 2418c2ecf20Sopenharmony_ci return -EINVAL; 2428c2ecf20Sopenharmony_ci } 2438c2ecf20Sopenharmony_ci /* 00: lp_t, 01: 1.5 * lp_t */ 2448c2ecf20Sopenharmony_ci cfg->mc_prg_hs_prepare = dphy_opts->clk_prepare > lp_t ? 1 : 0; 2458c2ecf20Sopenharmony_ci 2468c2ecf20Sopenharmony_ci /* hs_zero: formula from NXP BSP */ 2478c2ecf20Sopenharmony_ci n = (144 * (dphy_opts->hs_clk_rate / 1000000) - 47500) / 10000; 2488c2ecf20Sopenharmony_ci cfg->m_prg_hs_zero = n < 1 ? 1 : n; 2498c2ecf20Sopenharmony_ci 2508c2ecf20Sopenharmony_ci /* clk_zero: formula from NXP BSP */ 2518c2ecf20Sopenharmony_ci n = (34 * (dphy_opts->hs_clk_rate / 1000000) - 2500) / 1000; 2528c2ecf20Sopenharmony_ci cfg->mc_prg_hs_zero = n < 1 ? 1 : n; 2538c2ecf20Sopenharmony_ci 2548c2ecf20Sopenharmony_ci /* clk_trail, hs_trail: formula from NXP BSP */ 2558c2ecf20Sopenharmony_ci n = (103 * (dphy_opts->hs_clk_rate / 1000000) + 10000) / 10000; 2568c2ecf20Sopenharmony_ci if (n > 15) 2578c2ecf20Sopenharmony_ci n = 15; 2588c2ecf20Sopenharmony_ci if (n < 1) 2598c2ecf20Sopenharmony_ci n = 1; 2608c2ecf20Sopenharmony_ci cfg->m_prg_hs_trail = n; 2618c2ecf20Sopenharmony_ci cfg->mc_prg_hs_trail = n; 2628c2ecf20Sopenharmony_ci 2638c2ecf20Sopenharmony_ci /* rxhs_settle: formula from NXP BSP */ 2648c2ecf20Sopenharmony_ci if (dphy_opts->hs_clk_rate < MBPS(80)) 2658c2ecf20Sopenharmony_ci cfg->rxhs_settle = 0x0d; 2668c2ecf20Sopenharmony_ci else if (dphy_opts->hs_clk_rate < MBPS(90)) 2678c2ecf20Sopenharmony_ci cfg->rxhs_settle = 0x0c; 2688c2ecf20Sopenharmony_ci else if (dphy_opts->hs_clk_rate < MBPS(125)) 2698c2ecf20Sopenharmony_ci cfg->rxhs_settle = 0x0b; 2708c2ecf20Sopenharmony_ci else if (dphy_opts->hs_clk_rate < MBPS(150)) 2718c2ecf20Sopenharmony_ci cfg->rxhs_settle = 0x0a; 2728c2ecf20Sopenharmony_ci else if (dphy_opts->hs_clk_rate < MBPS(225)) 2738c2ecf20Sopenharmony_ci cfg->rxhs_settle = 0x09; 2748c2ecf20Sopenharmony_ci else if (dphy_opts->hs_clk_rate < MBPS(500)) 2758c2ecf20Sopenharmony_ci cfg->rxhs_settle = 0x08; 2768c2ecf20Sopenharmony_ci else 2778c2ecf20Sopenharmony_ci cfg->rxhs_settle = 0x07; 2788c2ecf20Sopenharmony_ci 2798c2ecf20Sopenharmony_ci dev_dbg(&phy->dev, "phy_config: %u %u %u %u %u %u %u\n", 2808c2ecf20Sopenharmony_ci cfg->m_prg_hs_prepare, cfg->mc_prg_hs_prepare, 2818c2ecf20Sopenharmony_ci cfg->m_prg_hs_zero, cfg->mc_prg_hs_zero, 2828c2ecf20Sopenharmony_ci cfg->m_prg_hs_trail, cfg->mc_prg_hs_trail, 2838c2ecf20Sopenharmony_ci cfg->rxhs_settle); 2848c2ecf20Sopenharmony_ci 2858c2ecf20Sopenharmony_ci return 0; 2868c2ecf20Sopenharmony_ci} 2878c2ecf20Sopenharmony_ci 2888c2ecf20Sopenharmony_cistatic void mixel_phy_set_hs_timings(struct phy *phy) 2898c2ecf20Sopenharmony_ci{ 2908c2ecf20Sopenharmony_ci struct mixel_dphy_priv *priv = phy_get_drvdata(phy); 2918c2ecf20Sopenharmony_ci 2928c2ecf20Sopenharmony_ci phy_write(phy, priv->cfg.m_prg_hs_prepare, DPHY_M_PRG_HS_PREPARE); 2938c2ecf20Sopenharmony_ci phy_write(phy, priv->cfg.mc_prg_hs_prepare, DPHY_MC_PRG_HS_PREPARE); 2948c2ecf20Sopenharmony_ci phy_write(phy, priv->cfg.m_prg_hs_zero, DPHY_M_PRG_HS_ZERO); 2958c2ecf20Sopenharmony_ci phy_write(phy, priv->cfg.mc_prg_hs_zero, DPHY_MC_PRG_HS_ZERO); 2968c2ecf20Sopenharmony_ci phy_write(phy, priv->cfg.m_prg_hs_trail, DPHY_M_PRG_HS_TRAIL); 2978c2ecf20Sopenharmony_ci phy_write(phy, priv->cfg.mc_prg_hs_trail, DPHY_MC_PRG_HS_TRAIL); 2988c2ecf20Sopenharmony_ci phy_write(phy, priv->cfg.rxhs_settle, priv->devdata->reg_rxhs_settle); 2998c2ecf20Sopenharmony_ci} 3008c2ecf20Sopenharmony_ci 3018c2ecf20Sopenharmony_cistatic int mixel_dphy_set_pll_params(struct phy *phy) 3028c2ecf20Sopenharmony_ci{ 3038c2ecf20Sopenharmony_ci struct mixel_dphy_priv *priv = dev_get_drvdata(phy->dev.parent); 3048c2ecf20Sopenharmony_ci 3058c2ecf20Sopenharmony_ci if (priv->cfg.cm < 16 || priv->cfg.cm > 255 || 3068c2ecf20Sopenharmony_ci priv->cfg.cn < 1 || priv->cfg.cn > 32 || 3078c2ecf20Sopenharmony_ci priv->cfg.co < 1 || priv->cfg.co > 8) { 3088c2ecf20Sopenharmony_ci dev_err(&phy->dev, "Invalid CM/CN/CO values! (%u/%u/%u)\n", 3098c2ecf20Sopenharmony_ci priv->cfg.cm, priv->cfg.cn, priv->cfg.co); 3108c2ecf20Sopenharmony_ci return -EINVAL; 3118c2ecf20Sopenharmony_ci } 3128c2ecf20Sopenharmony_ci dev_dbg(&phy->dev, "Using CM:%u CN:%u CO:%u\n", 3138c2ecf20Sopenharmony_ci priv->cfg.cm, priv->cfg.cn, priv->cfg.co); 3148c2ecf20Sopenharmony_ci phy_write(phy, CM(priv->cfg.cm), DPHY_CM); 3158c2ecf20Sopenharmony_ci phy_write(phy, CN(priv->cfg.cn), DPHY_CN); 3168c2ecf20Sopenharmony_ci phy_write(phy, CO(priv->cfg.co), DPHY_CO); 3178c2ecf20Sopenharmony_ci return 0; 3188c2ecf20Sopenharmony_ci} 3198c2ecf20Sopenharmony_ci 3208c2ecf20Sopenharmony_cistatic int mixel_dphy_configure(struct phy *phy, union phy_configure_opts *opts) 3218c2ecf20Sopenharmony_ci{ 3228c2ecf20Sopenharmony_ci struct mixel_dphy_priv *priv = phy_get_drvdata(phy); 3238c2ecf20Sopenharmony_ci struct mixel_dphy_cfg cfg = { 0 }; 3248c2ecf20Sopenharmony_ci int ret; 3258c2ecf20Sopenharmony_ci 3268c2ecf20Sopenharmony_ci ret = mixel_dphy_config_from_opts(phy, &opts->mipi_dphy, &cfg); 3278c2ecf20Sopenharmony_ci if (ret) 3288c2ecf20Sopenharmony_ci return ret; 3298c2ecf20Sopenharmony_ci 3308c2ecf20Sopenharmony_ci /* Update the configuration */ 3318c2ecf20Sopenharmony_ci memcpy(&priv->cfg, &cfg, sizeof(struct mixel_dphy_cfg)); 3328c2ecf20Sopenharmony_ci 3338c2ecf20Sopenharmony_ci phy_write(phy, 0x00, DPHY_LOCK_BYP); 3348c2ecf20Sopenharmony_ci phy_write(phy, 0x01, priv->devdata->reg_tx_rcal); 3358c2ecf20Sopenharmony_ci phy_write(phy, 0x00, priv->devdata->reg_auto_pd_en); 3368c2ecf20Sopenharmony_ci phy_write(phy, 0x02, priv->devdata->reg_rxlprp); 3378c2ecf20Sopenharmony_ci phy_write(phy, 0x02, priv->devdata->reg_rxcdrp); 3388c2ecf20Sopenharmony_ci phy_write(phy, 0x25, DPHY_TST); 3398c2ecf20Sopenharmony_ci 3408c2ecf20Sopenharmony_ci mixel_phy_set_hs_timings(phy); 3418c2ecf20Sopenharmony_ci ret = mixel_dphy_set_pll_params(phy); 3428c2ecf20Sopenharmony_ci if (ret < 0) 3438c2ecf20Sopenharmony_ci return ret; 3448c2ecf20Sopenharmony_ci 3458c2ecf20Sopenharmony_ci return 0; 3468c2ecf20Sopenharmony_ci} 3478c2ecf20Sopenharmony_ci 3488c2ecf20Sopenharmony_cistatic int mixel_dphy_validate(struct phy *phy, enum phy_mode mode, int submode, 3498c2ecf20Sopenharmony_ci union phy_configure_opts *opts) 3508c2ecf20Sopenharmony_ci{ 3518c2ecf20Sopenharmony_ci struct mixel_dphy_cfg cfg = { 0 }; 3528c2ecf20Sopenharmony_ci 3538c2ecf20Sopenharmony_ci if (mode != PHY_MODE_MIPI_DPHY) 3548c2ecf20Sopenharmony_ci return -EINVAL; 3558c2ecf20Sopenharmony_ci 3568c2ecf20Sopenharmony_ci return mixel_dphy_config_from_opts(phy, &opts->mipi_dphy, &cfg); 3578c2ecf20Sopenharmony_ci} 3588c2ecf20Sopenharmony_ci 3598c2ecf20Sopenharmony_cistatic int mixel_dphy_init(struct phy *phy) 3608c2ecf20Sopenharmony_ci{ 3618c2ecf20Sopenharmony_ci phy_write(phy, PWR_OFF, DPHY_PD_PLL); 3628c2ecf20Sopenharmony_ci phy_write(phy, PWR_OFF, DPHY_PD_DPHY); 3638c2ecf20Sopenharmony_ci 3648c2ecf20Sopenharmony_ci return 0; 3658c2ecf20Sopenharmony_ci} 3668c2ecf20Sopenharmony_ci 3678c2ecf20Sopenharmony_cistatic int mixel_dphy_exit(struct phy *phy) 3688c2ecf20Sopenharmony_ci{ 3698c2ecf20Sopenharmony_ci phy_write(phy, 0, DPHY_CM); 3708c2ecf20Sopenharmony_ci phy_write(phy, 0, DPHY_CN); 3718c2ecf20Sopenharmony_ci phy_write(phy, 0, DPHY_CO); 3728c2ecf20Sopenharmony_ci 3738c2ecf20Sopenharmony_ci return 0; 3748c2ecf20Sopenharmony_ci} 3758c2ecf20Sopenharmony_ci 3768c2ecf20Sopenharmony_cistatic int mixel_dphy_power_on(struct phy *phy) 3778c2ecf20Sopenharmony_ci{ 3788c2ecf20Sopenharmony_ci struct mixel_dphy_priv *priv = phy_get_drvdata(phy); 3798c2ecf20Sopenharmony_ci u32 locked; 3808c2ecf20Sopenharmony_ci int ret; 3818c2ecf20Sopenharmony_ci 3828c2ecf20Sopenharmony_ci ret = clk_prepare_enable(priv->phy_ref_clk); 3838c2ecf20Sopenharmony_ci if (ret < 0) 3848c2ecf20Sopenharmony_ci return ret; 3858c2ecf20Sopenharmony_ci 3868c2ecf20Sopenharmony_ci phy_write(phy, PWR_ON, DPHY_PD_PLL); 3878c2ecf20Sopenharmony_ci ret = regmap_read_poll_timeout(priv->regmap, DPHY_LOCK, locked, 3888c2ecf20Sopenharmony_ci locked, PLL_LOCK_SLEEP, 3898c2ecf20Sopenharmony_ci PLL_LOCK_TIMEOUT); 3908c2ecf20Sopenharmony_ci if (ret < 0) { 3918c2ecf20Sopenharmony_ci dev_err(&phy->dev, "Could not get DPHY lock (%d)!\n", ret); 3928c2ecf20Sopenharmony_ci goto clock_disable; 3938c2ecf20Sopenharmony_ci } 3948c2ecf20Sopenharmony_ci phy_write(phy, PWR_ON, DPHY_PD_DPHY); 3958c2ecf20Sopenharmony_ci 3968c2ecf20Sopenharmony_ci return 0; 3978c2ecf20Sopenharmony_ciclock_disable: 3988c2ecf20Sopenharmony_ci clk_disable_unprepare(priv->phy_ref_clk); 3998c2ecf20Sopenharmony_ci return ret; 4008c2ecf20Sopenharmony_ci} 4018c2ecf20Sopenharmony_ci 4028c2ecf20Sopenharmony_cistatic int mixel_dphy_power_off(struct phy *phy) 4038c2ecf20Sopenharmony_ci{ 4048c2ecf20Sopenharmony_ci struct mixel_dphy_priv *priv = phy_get_drvdata(phy); 4058c2ecf20Sopenharmony_ci 4068c2ecf20Sopenharmony_ci phy_write(phy, PWR_OFF, DPHY_PD_PLL); 4078c2ecf20Sopenharmony_ci phy_write(phy, PWR_OFF, DPHY_PD_DPHY); 4088c2ecf20Sopenharmony_ci 4098c2ecf20Sopenharmony_ci clk_disable_unprepare(priv->phy_ref_clk); 4108c2ecf20Sopenharmony_ci 4118c2ecf20Sopenharmony_ci return 0; 4128c2ecf20Sopenharmony_ci} 4138c2ecf20Sopenharmony_ci 4148c2ecf20Sopenharmony_cistatic const struct phy_ops mixel_dphy_phy_ops = { 4158c2ecf20Sopenharmony_ci .init = mixel_dphy_init, 4168c2ecf20Sopenharmony_ci .exit = mixel_dphy_exit, 4178c2ecf20Sopenharmony_ci .power_on = mixel_dphy_power_on, 4188c2ecf20Sopenharmony_ci .power_off = mixel_dphy_power_off, 4198c2ecf20Sopenharmony_ci .configure = mixel_dphy_configure, 4208c2ecf20Sopenharmony_ci .validate = mixel_dphy_validate, 4218c2ecf20Sopenharmony_ci .owner = THIS_MODULE, 4228c2ecf20Sopenharmony_ci}; 4238c2ecf20Sopenharmony_ci 4248c2ecf20Sopenharmony_cistatic const struct of_device_id mixel_dphy_of_match[] = { 4258c2ecf20Sopenharmony_ci { .compatible = "fsl,imx8mq-mipi-dphy", 4268c2ecf20Sopenharmony_ci .data = &mixel_dphy_devdata[MIXEL_IMX8MQ] }, 4278c2ecf20Sopenharmony_ci { /* sentinel */ }, 4288c2ecf20Sopenharmony_ci}; 4298c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(of, mixel_dphy_of_match); 4308c2ecf20Sopenharmony_ci 4318c2ecf20Sopenharmony_cistatic int mixel_dphy_probe(struct platform_device *pdev) 4328c2ecf20Sopenharmony_ci{ 4338c2ecf20Sopenharmony_ci struct device *dev = &pdev->dev; 4348c2ecf20Sopenharmony_ci struct device_node *np = dev->of_node; 4358c2ecf20Sopenharmony_ci struct phy_provider *phy_provider; 4368c2ecf20Sopenharmony_ci struct mixel_dphy_priv *priv; 4378c2ecf20Sopenharmony_ci struct resource *res; 4388c2ecf20Sopenharmony_ci struct phy *phy; 4398c2ecf20Sopenharmony_ci void __iomem *base; 4408c2ecf20Sopenharmony_ci 4418c2ecf20Sopenharmony_ci if (!np) 4428c2ecf20Sopenharmony_ci return -ENODEV; 4438c2ecf20Sopenharmony_ci 4448c2ecf20Sopenharmony_ci priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); 4458c2ecf20Sopenharmony_ci if (!priv) 4468c2ecf20Sopenharmony_ci return -ENOMEM; 4478c2ecf20Sopenharmony_ci 4488c2ecf20Sopenharmony_ci priv->devdata = of_device_get_match_data(&pdev->dev); 4498c2ecf20Sopenharmony_ci if (!priv->devdata) 4508c2ecf20Sopenharmony_ci return -EINVAL; 4518c2ecf20Sopenharmony_ci 4528c2ecf20Sopenharmony_ci res = platform_get_resource(pdev, IORESOURCE_MEM, 0); 4538c2ecf20Sopenharmony_ci base = devm_ioremap_resource(dev, res); 4548c2ecf20Sopenharmony_ci if (IS_ERR(base)) 4558c2ecf20Sopenharmony_ci return PTR_ERR(base); 4568c2ecf20Sopenharmony_ci 4578c2ecf20Sopenharmony_ci priv->regmap = devm_regmap_init_mmio(&pdev->dev, base, 4588c2ecf20Sopenharmony_ci &mixel_dphy_regmap_config); 4598c2ecf20Sopenharmony_ci if (IS_ERR(priv->regmap)) { 4608c2ecf20Sopenharmony_ci dev_err(dev, "Couldn't create the DPHY regmap\n"); 4618c2ecf20Sopenharmony_ci return PTR_ERR(priv->regmap); 4628c2ecf20Sopenharmony_ci } 4638c2ecf20Sopenharmony_ci 4648c2ecf20Sopenharmony_ci priv->phy_ref_clk = devm_clk_get(&pdev->dev, "phy_ref"); 4658c2ecf20Sopenharmony_ci if (IS_ERR(priv->phy_ref_clk)) { 4668c2ecf20Sopenharmony_ci dev_err(dev, "No phy_ref clock found\n"); 4678c2ecf20Sopenharmony_ci return PTR_ERR(priv->phy_ref_clk); 4688c2ecf20Sopenharmony_ci } 4698c2ecf20Sopenharmony_ci dev_dbg(dev, "phy_ref clock rate: %lu\n", 4708c2ecf20Sopenharmony_ci clk_get_rate(priv->phy_ref_clk)); 4718c2ecf20Sopenharmony_ci 4728c2ecf20Sopenharmony_ci dev_set_drvdata(dev, priv); 4738c2ecf20Sopenharmony_ci 4748c2ecf20Sopenharmony_ci phy = devm_phy_create(dev, np, &mixel_dphy_phy_ops); 4758c2ecf20Sopenharmony_ci if (IS_ERR(phy)) { 4768c2ecf20Sopenharmony_ci dev_err(dev, "Failed to create phy %ld\n", PTR_ERR(phy)); 4778c2ecf20Sopenharmony_ci return PTR_ERR(phy); 4788c2ecf20Sopenharmony_ci } 4798c2ecf20Sopenharmony_ci phy_set_drvdata(phy, priv); 4808c2ecf20Sopenharmony_ci 4818c2ecf20Sopenharmony_ci phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); 4828c2ecf20Sopenharmony_ci 4838c2ecf20Sopenharmony_ci return PTR_ERR_OR_ZERO(phy_provider); 4848c2ecf20Sopenharmony_ci} 4858c2ecf20Sopenharmony_ci 4868c2ecf20Sopenharmony_cistatic struct platform_driver mixel_dphy_driver = { 4878c2ecf20Sopenharmony_ci .probe = mixel_dphy_probe, 4888c2ecf20Sopenharmony_ci .driver = { 4898c2ecf20Sopenharmony_ci .name = "mixel-mipi-dphy", 4908c2ecf20Sopenharmony_ci .of_match_table = mixel_dphy_of_match, 4918c2ecf20Sopenharmony_ci } 4928c2ecf20Sopenharmony_ci}; 4938c2ecf20Sopenharmony_cimodule_platform_driver(mixel_dphy_driver); 4948c2ecf20Sopenharmony_ci 4958c2ecf20Sopenharmony_ciMODULE_AUTHOR("NXP Semiconductor"); 4968c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("Mixel MIPI-DSI PHY driver"); 4978c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL"); 498