162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0+ 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Copyright: 2017-2018 Cadence Design Systems, Inc. 462306a36Sopenharmony_ci */ 562306a36Sopenharmony_ci 662306a36Sopenharmony_ci#include <linux/bitfield.h> 762306a36Sopenharmony_ci#include <linux/bitops.h> 862306a36Sopenharmony_ci#include <linux/clk.h> 962306a36Sopenharmony_ci#include <linux/io.h> 1062306a36Sopenharmony_ci#include <linux/iopoll.h> 1162306a36Sopenharmony_ci#include <linux/module.h> 1262306a36Sopenharmony_ci#include <linux/of.h> 1362306a36Sopenharmony_ci#include <linux/platform_device.h> 1462306a36Sopenharmony_ci#include <linux/reset.h> 1562306a36Sopenharmony_ci 1662306a36Sopenharmony_ci#include <linux/phy/phy.h> 1762306a36Sopenharmony_ci#include <linux/phy/phy-mipi-dphy.h> 1862306a36Sopenharmony_ci 1962306a36Sopenharmony_ci#define REG_WAKEUP_TIME_NS 800 2062306a36Sopenharmony_ci#define DPHY_PLL_RATE_HZ 108000000 2162306a36Sopenharmony_ci#define POLL_TIMEOUT_US 1000 2262306a36Sopenharmony_ci 2362306a36Sopenharmony_ci/* DPHY registers */ 2462306a36Sopenharmony_ci#define DPHY_PMA_CMN(reg) (reg) 2562306a36Sopenharmony_ci#define DPHY_PMA_LCLK(reg) (0x100 + (reg)) 2662306a36Sopenharmony_ci#define DPHY_PMA_LDATA(lane, reg) (0x200 + ((lane) * 0x100) + (reg)) 2762306a36Sopenharmony_ci#define DPHY_PMA_RCLK(reg) (0x600 + (reg)) 2862306a36Sopenharmony_ci#define DPHY_PMA_RDATA(lane, reg) (0x700 + ((lane) * 0x100) + (reg)) 2962306a36Sopenharmony_ci#define DPHY_PCS(reg) (0xb00 + (reg)) 3062306a36Sopenharmony_ci 3162306a36Sopenharmony_ci#define DPHY_CMN_SSM DPHY_PMA_CMN(0x20) 3262306a36Sopenharmony_ci#define DPHY_CMN_SSM_EN BIT(0) 3362306a36Sopenharmony_ci#define DPHY_CMN_TX_MODE_EN BIT(9) 3462306a36Sopenharmony_ci 3562306a36Sopenharmony_ci#define DPHY_CMN_PWM DPHY_PMA_CMN(0x40) 3662306a36Sopenharmony_ci#define DPHY_CMN_PWM_DIV(x) ((x) << 20) 3762306a36Sopenharmony_ci#define DPHY_CMN_PWM_LOW(x) ((x) << 10) 3862306a36Sopenharmony_ci#define DPHY_CMN_PWM_HIGH(x) (x) 3962306a36Sopenharmony_ci 4062306a36Sopenharmony_ci#define DPHY_CMN_FBDIV DPHY_PMA_CMN(0x4c) 4162306a36Sopenharmony_ci#define DPHY_CMN_FBDIV_VAL(low, high) (((high) << 11) | ((low) << 22)) 4262306a36Sopenharmony_ci#define DPHY_CMN_FBDIV_FROM_REG (BIT(10) | BIT(21)) 4362306a36Sopenharmony_ci 4462306a36Sopenharmony_ci#define DPHY_CMN_OPIPDIV DPHY_PMA_CMN(0x50) 4562306a36Sopenharmony_ci#define DPHY_CMN_IPDIV_FROM_REG BIT(0) 4662306a36Sopenharmony_ci#define DPHY_CMN_IPDIV(x) ((x) << 1) 4762306a36Sopenharmony_ci#define DPHY_CMN_OPDIV_FROM_REG BIT(6) 4862306a36Sopenharmony_ci#define DPHY_CMN_OPDIV(x) ((x) << 7) 4962306a36Sopenharmony_ci 5062306a36Sopenharmony_ci#define DPHY_BAND_CFG DPHY_PCS(0x0) 5162306a36Sopenharmony_ci#define DPHY_BAND_CFG_LEFT_BAND GENMASK(4, 0) 5262306a36Sopenharmony_ci#define DPHY_BAND_CFG_RIGHT_BAND GENMASK(9, 5) 5362306a36Sopenharmony_ci 5462306a36Sopenharmony_ci#define DPHY_PSM_CFG DPHY_PCS(0x4) 5562306a36Sopenharmony_ci#define DPHY_PSM_CFG_FROM_REG BIT(0) 5662306a36Sopenharmony_ci#define DPHY_PSM_CLK_DIV(x) ((x) << 1) 5762306a36Sopenharmony_ci 5862306a36Sopenharmony_ci#define DSI_HBP_FRAME_OVERHEAD 12 5962306a36Sopenharmony_ci#define DSI_HSA_FRAME_OVERHEAD 14 6062306a36Sopenharmony_ci#define DSI_HFP_FRAME_OVERHEAD 6 6162306a36Sopenharmony_ci#define DSI_HSS_VSS_VSE_FRAME_OVERHEAD 4 6262306a36Sopenharmony_ci#define DSI_BLANKING_FRAME_OVERHEAD 6 6362306a36Sopenharmony_ci#define DSI_NULL_FRAME_OVERHEAD 6 6462306a36Sopenharmony_ci#define DSI_EOT_PKT_SIZE 4 6562306a36Sopenharmony_ci 6662306a36Sopenharmony_ci#define DPHY_TX_J721E_WIZ_PLL_CTRL 0xF04 6762306a36Sopenharmony_ci#define DPHY_TX_J721E_WIZ_STATUS 0xF08 6862306a36Sopenharmony_ci#define DPHY_TX_J721E_WIZ_RST_CTRL 0xF0C 6962306a36Sopenharmony_ci#define DPHY_TX_J721E_WIZ_PSM_FREQ 0xF10 7062306a36Sopenharmony_ci 7162306a36Sopenharmony_ci#define DPHY_TX_J721E_WIZ_IPDIV GENMASK(4, 0) 7262306a36Sopenharmony_ci#define DPHY_TX_J721E_WIZ_OPDIV GENMASK(13, 8) 7362306a36Sopenharmony_ci#define DPHY_TX_J721E_WIZ_FBDIV GENMASK(25, 16) 7462306a36Sopenharmony_ci#define DPHY_TX_J721E_WIZ_LANE_RSTB BIT(31) 7562306a36Sopenharmony_ci#define DPHY_TX_WIZ_PLL_LOCK BIT(31) 7662306a36Sopenharmony_ci#define DPHY_TX_WIZ_O_CMN_READY BIT(31) 7762306a36Sopenharmony_ci 7862306a36Sopenharmony_cistruct cdns_dphy_cfg { 7962306a36Sopenharmony_ci u8 pll_ipdiv; 8062306a36Sopenharmony_ci u8 pll_opdiv; 8162306a36Sopenharmony_ci u16 pll_fbdiv; 8262306a36Sopenharmony_ci unsigned int nlanes; 8362306a36Sopenharmony_ci}; 8462306a36Sopenharmony_ci 8562306a36Sopenharmony_cienum cdns_dphy_clk_lane_cfg { 8662306a36Sopenharmony_ci DPHY_CLK_CFG_LEFT_DRIVES_ALL = 0, 8762306a36Sopenharmony_ci DPHY_CLK_CFG_LEFT_DRIVES_RIGHT = 1, 8862306a36Sopenharmony_ci DPHY_CLK_CFG_LEFT_DRIVES_LEFT = 2, 8962306a36Sopenharmony_ci DPHY_CLK_CFG_RIGHT_DRIVES_ALL = 3, 9062306a36Sopenharmony_ci}; 9162306a36Sopenharmony_ci 9262306a36Sopenharmony_cistruct cdns_dphy; 9362306a36Sopenharmony_cistruct cdns_dphy_ops { 9462306a36Sopenharmony_ci int (*probe)(struct cdns_dphy *dphy); 9562306a36Sopenharmony_ci void (*remove)(struct cdns_dphy *dphy); 9662306a36Sopenharmony_ci void (*set_psm_div)(struct cdns_dphy *dphy, u8 div); 9762306a36Sopenharmony_ci void (*set_clk_lane_cfg)(struct cdns_dphy *dphy, 9862306a36Sopenharmony_ci enum cdns_dphy_clk_lane_cfg cfg); 9962306a36Sopenharmony_ci void (*set_pll_cfg)(struct cdns_dphy *dphy, 10062306a36Sopenharmony_ci const struct cdns_dphy_cfg *cfg); 10162306a36Sopenharmony_ci unsigned long (*get_wakeup_time_ns)(struct cdns_dphy *dphy); 10262306a36Sopenharmony_ci}; 10362306a36Sopenharmony_ci 10462306a36Sopenharmony_cistruct cdns_dphy { 10562306a36Sopenharmony_ci struct cdns_dphy_cfg cfg; 10662306a36Sopenharmony_ci void __iomem *regs; 10762306a36Sopenharmony_ci struct clk *psm_clk; 10862306a36Sopenharmony_ci struct clk *pll_ref_clk; 10962306a36Sopenharmony_ci const struct cdns_dphy_ops *ops; 11062306a36Sopenharmony_ci struct phy *phy; 11162306a36Sopenharmony_ci}; 11262306a36Sopenharmony_ci 11362306a36Sopenharmony_ci/* Order of bands is important since the index is the band number. */ 11462306a36Sopenharmony_cistatic const unsigned int tx_bands[] = { 11562306a36Sopenharmony_ci 80, 100, 120, 160, 200, 240, 320, 390, 450, 510, 560, 640, 690, 770, 11662306a36Sopenharmony_ci 870, 950, 1000, 1200, 1400, 1600, 1800, 2000, 2200, 2500 11762306a36Sopenharmony_ci}; 11862306a36Sopenharmony_ci 11962306a36Sopenharmony_cistatic int cdns_dsi_get_dphy_pll_cfg(struct cdns_dphy *dphy, 12062306a36Sopenharmony_ci struct cdns_dphy_cfg *cfg, 12162306a36Sopenharmony_ci struct phy_configure_opts_mipi_dphy *opts, 12262306a36Sopenharmony_ci unsigned int *dsi_hfp_ext) 12362306a36Sopenharmony_ci{ 12462306a36Sopenharmony_ci unsigned long pll_ref_hz = clk_get_rate(dphy->pll_ref_clk); 12562306a36Sopenharmony_ci u64 dlane_bps; 12662306a36Sopenharmony_ci 12762306a36Sopenharmony_ci memset(cfg, 0, sizeof(*cfg)); 12862306a36Sopenharmony_ci 12962306a36Sopenharmony_ci if (pll_ref_hz < 9600000 || pll_ref_hz >= 150000000) 13062306a36Sopenharmony_ci return -EINVAL; 13162306a36Sopenharmony_ci else if (pll_ref_hz < 19200000) 13262306a36Sopenharmony_ci cfg->pll_ipdiv = 1; 13362306a36Sopenharmony_ci else if (pll_ref_hz < 38400000) 13462306a36Sopenharmony_ci cfg->pll_ipdiv = 2; 13562306a36Sopenharmony_ci else if (pll_ref_hz < 76800000) 13662306a36Sopenharmony_ci cfg->pll_ipdiv = 4; 13762306a36Sopenharmony_ci else 13862306a36Sopenharmony_ci cfg->pll_ipdiv = 8; 13962306a36Sopenharmony_ci 14062306a36Sopenharmony_ci dlane_bps = opts->hs_clk_rate; 14162306a36Sopenharmony_ci 14262306a36Sopenharmony_ci if (dlane_bps > 2500000000UL || dlane_bps < 160000000UL) 14362306a36Sopenharmony_ci return -EINVAL; 14462306a36Sopenharmony_ci else if (dlane_bps >= 1250000000) 14562306a36Sopenharmony_ci cfg->pll_opdiv = 1; 14662306a36Sopenharmony_ci else if (dlane_bps >= 630000000) 14762306a36Sopenharmony_ci cfg->pll_opdiv = 2; 14862306a36Sopenharmony_ci else if (dlane_bps >= 320000000) 14962306a36Sopenharmony_ci cfg->pll_opdiv = 4; 15062306a36Sopenharmony_ci else if (dlane_bps >= 160000000) 15162306a36Sopenharmony_ci cfg->pll_opdiv = 8; 15262306a36Sopenharmony_ci 15362306a36Sopenharmony_ci cfg->pll_fbdiv = DIV_ROUND_UP_ULL(dlane_bps * 2 * cfg->pll_opdiv * 15462306a36Sopenharmony_ci cfg->pll_ipdiv, 15562306a36Sopenharmony_ci pll_ref_hz); 15662306a36Sopenharmony_ci 15762306a36Sopenharmony_ci return 0; 15862306a36Sopenharmony_ci} 15962306a36Sopenharmony_ci 16062306a36Sopenharmony_cistatic int cdns_dphy_setup_psm(struct cdns_dphy *dphy) 16162306a36Sopenharmony_ci{ 16262306a36Sopenharmony_ci unsigned long psm_clk_hz = clk_get_rate(dphy->psm_clk); 16362306a36Sopenharmony_ci unsigned long psm_div; 16462306a36Sopenharmony_ci 16562306a36Sopenharmony_ci if (!psm_clk_hz || psm_clk_hz > 100000000) 16662306a36Sopenharmony_ci return -EINVAL; 16762306a36Sopenharmony_ci 16862306a36Sopenharmony_ci psm_div = DIV_ROUND_CLOSEST(psm_clk_hz, 1000000); 16962306a36Sopenharmony_ci if (dphy->ops->set_psm_div) 17062306a36Sopenharmony_ci dphy->ops->set_psm_div(dphy, psm_div); 17162306a36Sopenharmony_ci 17262306a36Sopenharmony_ci return 0; 17362306a36Sopenharmony_ci} 17462306a36Sopenharmony_ci 17562306a36Sopenharmony_cistatic void cdns_dphy_set_clk_lane_cfg(struct cdns_dphy *dphy, 17662306a36Sopenharmony_ci enum cdns_dphy_clk_lane_cfg cfg) 17762306a36Sopenharmony_ci{ 17862306a36Sopenharmony_ci if (dphy->ops->set_clk_lane_cfg) 17962306a36Sopenharmony_ci dphy->ops->set_clk_lane_cfg(dphy, cfg); 18062306a36Sopenharmony_ci} 18162306a36Sopenharmony_ci 18262306a36Sopenharmony_cistatic void cdns_dphy_set_pll_cfg(struct cdns_dphy *dphy, 18362306a36Sopenharmony_ci const struct cdns_dphy_cfg *cfg) 18462306a36Sopenharmony_ci{ 18562306a36Sopenharmony_ci if (dphy->ops->set_pll_cfg) 18662306a36Sopenharmony_ci dphy->ops->set_pll_cfg(dphy, cfg); 18762306a36Sopenharmony_ci} 18862306a36Sopenharmony_ci 18962306a36Sopenharmony_cistatic unsigned long cdns_dphy_get_wakeup_time_ns(struct cdns_dphy *dphy) 19062306a36Sopenharmony_ci{ 19162306a36Sopenharmony_ci return dphy->ops->get_wakeup_time_ns(dphy); 19262306a36Sopenharmony_ci} 19362306a36Sopenharmony_ci 19462306a36Sopenharmony_cistatic unsigned long cdns_dphy_ref_get_wakeup_time_ns(struct cdns_dphy *dphy) 19562306a36Sopenharmony_ci{ 19662306a36Sopenharmony_ci /* Default wakeup time is 800 ns (in a simulated environment). */ 19762306a36Sopenharmony_ci return 800; 19862306a36Sopenharmony_ci} 19962306a36Sopenharmony_ci 20062306a36Sopenharmony_cistatic void cdns_dphy_ref_set_pll_cfg(struct cdns_dphy *dphy, 20162306a36Sopenharmony_ci const struct cdns_dphy_cfg *cfg) 20262306a36Sopenharmony_ci{ 20362306a36Sopenharmony_ci u32 fbdiv_low, fbdiv_high; 20462306a36Sopenharmony_ci 20562306a36Sopenharmony_ci fbdiv_low = (cfg->pll_fbdiv / 4) - 2; 20662306a36Sopenharmony_ci fbdiv_high = cfg->pll_fbdiv - fbdiv_low - 2; 20762306a36Sopenharmony_ci 20862306a36Sopenharmony_ci writel(DPHY_CMN_IPDIV_FROM_REG | DPHY_CMN_OPDIV_FROM_REG | 20962306a36Sopenharmony_ci DPHY_CMN_IPDIV(cfg->pll_ipdiv) | 21062306a36Sopenharmony_ci DPHY_CMN_OPDIV(cfg->pll_opdiv), 21162306a36Sopenharmony_ci dphy->regs + DPHY_CMN_OPIPDIV); 21262306a36Sopenharmony_ci writel(DPHY_CMN_FBDIV_FROM_REG | 21362306a36Sopenharmony_ci DPHY_CMN_FBDIV_VAL(fbdiv_low, fbdiv_high), 21462306a36Sopenharmony_ci dphy->regs + DPHY_CMN_FBDIV); 21562306a36Sopenharmony_ci writel(DPHY_CMN_PWM_HIGH(6) | DPHY_CMN_PWM_LOW(0x101) | 21662306a36Sopenharmony_ci DPHY_CMN_PWM_DIV(0x8), 21762306a36Sopenharmony_ci dphy->regs + DPHY_CMN_PWM); 21862306a36Sopenharmony_ci} 21962306a36Sopenharmony_ci 22062306a36Sopenharmony_cistatic void cdns_dphy_ref_set_psm_div(struct cdns_dphy *dphy, u8 div) 22162306a36Sopenharmony_ci{ 22262306a36Sopenharmony_ci writel(DPHY_PSM_CFG_FROM_REG | DPHY_PSM_CLK_DIV(div), 22362306a36Sopenharmony_ci dphy->regs + DPHY_PSM_CFG); 22462306a36Sopenharmony_ci} 22562306a36Sopenharmony_ci 22662306a36Sopenharmony_cistatic unsigned long cdns_dphy_j721e_get_wakeup_time_ns(struct cdns_dphy *dphy) 22762306a36Sopenharmony_ci{ 22862306a36Sopenharmony_ci /* Minimum wakeup time as per MIPI D-PHY spec v1.2 */ 22962306a36Sopenharmony_ci return 1000000; 23062306a36Sopenharmony_ci} 23162306a36Sopenharmony_ci 23262306a36Sopenharmony_cistatic void cdns_dphy_j721e_set_pll_cfg(struct cdns_dphy *dphy, 23362306a36Sopenharmony_ci const struct cdns_dphy_cfg *cfg) 23462306a36Sopenharmony_ci{ 23562306a36Sopenharmony_ci u32 status; 23662306a36Sopenharmony_ci 23762306a36Sopenharmony_ci /* 23862306a36Sopenharmony_ci * set the PWM and PLL Byteclk divider settings to recommended values 23962306a36Sopenharmony_ci * which is same as that of in ref ops 24062306a36Sopenharmony_ci */ 24162306a36Sopenharmony_ci writel(DPHY_CMN_PWM_HIGH(6) | DPHY_CMN_PWM_LOW(0x101) | 24262306a36Sopenharmony_ci DPHY_CMN_PWM_DIV(0x8), 24362306a36Sopenharmony_ci dphy->regs + DPHY_CMN_PWM); 24462306a36Sopenharmony_ci 24562306a36Sopenharmony_ci writel((FIELD_PREP(DPHY_TX_J721E_WIZ_IPDIV, cfg->pll_ipdiv) | 24662306a36Sopenharmony_ci FIELD_PREP(DPHY_TX_J721E_WIZ_OPDIV, cfg->pll_opdiv) | 24762306a36Sopenharmony_ci FIELD_PREP(DPHY_TX_J721E_WIZ_FBDIV, cfg->pll_fbdiv)), 24862306a36Sopenharmony_ci dphy->regs + DPHY_TX_J721E_WIZ_PLL_CTRL); 24962306a36Sopenharmony_ci 25062306a36Sopenharmony_ci writel(DPHY_TX_J721E_WIZ_LANE_RSTB, 25162306a36Sopenharmony_ci dphy->regs + DPHY_TX_J721E_WIZ_RST_CTRL); 25262306a36Sopenharmony_ci 25362306a36Sopenharmony_ci readl_poll_timeout(dphy->regs + DPHY_TX_J721E_WIZ_PLL_CTRL, status, 25462306a36Sopenharmony_ci (status & DPHY_TX_WIZ_PLL_LOCK), 0, POLL_TIMEOUT_US); 25562306a36Sopenharmony_ci 25662306a36Sopenharmony_ci readl_poll_timeout(dphy->regs + DPHY_TX_J721E_WIZ_STATUS, status, 25762306a36Sopenharmony_ci (status & DPHY_TX_WIZ_O_CMN_READY), 0, 25862306a36Sopenharmony_ci POLL_TIMEOUT_US); 25962306a36Sopenharmony_ci} 26062306a36Sopenharmony_ci 26162306a36Sopenharmony_cistatic void cdns_dphy_j721e_set_psm_div(struct cdns_dphy *dphy, u8 div) 26262306a36Sopenharmony_ci{ 26362306a36Sopenharmony_ci writel(div, dphy->regs + DPHY_TX_J721E_WIZ_PSM_FREQ); 26462306a36Sopenharmony_ci} 26562306a36Sopenharmony_ci 26662306a36Sopenharmony_ci/* 26762306a36Sopenharmony_ci * This is the reference implementation of DPHY hooks. Specific integration of 26862306a36Sopenharmony_ci * this IP may have to re-implement some of them depending on how they decided 26962306a36Sopenharmony_ci * to wire things in the SoC. 27062306a36Sopenharmony_ci */ 27162306a36Sopenharmony_cistatic const struct cdns_dphy_ops ref_dphy_ops = { 27262306a36Sopenharmony_ci .get_wakeup_time_ns = cdns_dphy_ref_get_wakeup_time_ns, 27362306a36Sopenharmony_ci .set_pll_cfg = cdns_dphy_ref_set_pll_cfg, 27462306a36Sopenharmony_ci .set_psm_div = cdns_dphy_ref_set_psm_div, 27562306a36Sopenharmony_ci}; 27662306a36Sopenharmony_ci 27762306a36Sopenharmony_cistatic const struct cdns_dphy_ops j721e_dphy_ops = { 27862306a36Sopenharmony_ci .get_wakeup_time_ns = cdns_dphy_j721e_get_wakeup_time_ns, 27962306a36Sopenharmony_ci .set_pll_cfg = cdns_dphy_j721e_set_pll_cfg, 28062306a36Sopenharmony_ci .set_psm_div = cdns_dphy_j721e_set_psm_div, 28162306a36Sopenharmony_ci}; 28262306a36Sopenharmony_ci 28362306a36Sopenharmony_cistatic int cdns_dphy_config_from_opts(struct phy *phy, 28462306a36Sopenharmony_ci struct phy_configure_opts_mipi_dphy *opts, 28562306a36Sopenharmony_ci struct cdns_dphy_cfg *cfg) 28662306a36Sopenharmony_ci{ 28762306a36Sopenharmony_ci struct cdns_dphy *dphy = phy_get_drvdata(phy); 28862306a36Sopenharmony_ci unsigned int dsi_hfp_ext = 0; 28962306a36Sopenharmony_ci int ret; 29062306a36Sopenharmony_ci 29162306a36Sopenharmony_ci ret = phy_mipi_dphy_config_validate(opts); 29262306a36Sopenharmony_ci if (ret) 29362306a36Sopenharmony_ci return ret; 29462306a36Sopenharmony_ci 29562306a36Sopenharmony_ci ret = cdns_dsi_get_dphy_pll_cfg(dphy, cfg, 29662306a36Sopenharmony_ci opts, &dsi_hfp_ext); 29762306a36Sopenharmony_ci if (ret) 29862306a36Sopenharmony_ci return ret; 29962306a36Sopenharmony_ci 30062306a36Sopenharmony_ci opts->wakeup = cdns_dphy_get_wakeup_time_ns(dphy) / 1000; 30162306a36Sopenharmony_ci 30262306a36Sopenharmony_ci return 0; 30362306a36Sopenharmony_ci} 30462306a36Sopenharmony_ci 30562306a36Sopenharmony_cistatic int cdns_dphy_tx_get_band_ctrl(unsigned long hs_clk_rate) 30662306a36Sopenharmony_ci{ 30762306a36Sopenharmony_ci unsigned int rate; 30862306a36Sopenharmony_ci int i; 30962306a36Sopenharmony_ci 31062306a36Sopenharmony_ci rate = hs_clk_rate / 1000000UL; 31162306a36Sopenharmony_ci 31262306a36Sopenharmony_ci if (rate < tx_bands[0]) 31362306a36Sopenharmony_ci return -EOPNOTSUPP; 31462306a36Sopenharmony_ci 31562306a36Sopenharmony_ci for (i = 0; i < ARRAY_SIZE(tx_bands) - 1; i++) { 31662306a36Sopenharmony_ci if (rate >= tx_bands[i] && rate < tx_bands[i + 1]) 31762306a36Sopenharmony_ci return i; 31862306a36Sopenharmony_ci } 31962306a36Sopenharmony_ci 32062306a36Sopenharmony_ci return -EOPNOTSUPP; 32162306a36Sopenharmony_ci} 32262306a36Sopenharmony_ci 32362306a36Sopenharmony_cistatic int cdns_dphy_validate(struct phy *phy, enum phy_mode mode, int submode, 32462306a36Sopenharmony_ci union phy_configure_opts *opts) 32562306a36Sopenharmony_ci{ 32662306a36Sopenharmony_ci struct cdns_dphy_cfg cfg = { 0 }; 32762306a36Sopenharmony_ci 32862306a36Sopenharmony_ci if (mode != PHY_MODE_MIPI_DPHY) 32962306a36Sopenharmony_ci return -EINVAL; 33062306a36Sopenharmony_ci 33162306a36Sopenharmony_ci return cdns_dphy_config_from_opts(phy, &opts->mipi_dphy, &cfg); 33262306a36Sopenharmony_ci} 33362306a36Sopenharmony_ci 33462306a36Sopenharmony_cistatic int cdns_dphy_configure(struct phy *phy, union phy_configure_opts *opts) 33562306a36Sopenharmony_ci{ 33662306a36Sopenharmony_ci struct cdns_dphy *dphy = phy_get_drvdata(phy); 33762306a36Sopenharmony_ci struct cdns_dphy_cfg cfg = { 0 }; 33862306a36Sopenharmony_ci int ret, band_ctrl; 33962306a36Sopenharmony_ci unsigned int reg; 34062306a36Sopenharmony_ci 34162306a36Sopenharmony_ci ret = cdns_dphy_config_from_opts(phy, &opts->mipi_dphy, &cfg); 34262306a36Sopenharmony_ci if (ret) 34362306a36Sopenharmony_ci return ret; 34462306a36Sopenharmony_ci 34562306a36Sopenharmony_ci /* 34662306a36Sopenharmony_ci * Configure the internal PSM clk divider so that the DPHY has a 34762306a36Sopenharmony_ci * 1MHz clk (or something close). 34862306a36Sopenharmony_ci */ 34962306a36Sopenharmony_ci ret = cdns_dphy_setup_psm(dphy); 35062306a36Sopenharmony_ci if (ret) 35162306a36Sopenharmony_ci return ret; 35262306a36Sopenharmony_ci 35362306a36Sopenharmony_ci /* 35462306a36Sopenharmony_ci * Configure attach clk lanes to data lanes: the DPHY has 2 clk lanes 35562306a36Sopenharmony_ci * and 8 data lanes, each clk lane can be attache different set of 35662306a36Sopenharmony_ci * data lanes. The 2 groups are named 'left' and 'right', so here we 35762306a36Sopenharmony_ci * just say that we want the 'left' clk lane to drive the 'left' data 35862306a36Sopenharmony_ci * lanes. 35962306a36Sopenharmony_ci */ 36062306a36Sopenharmony_ci cdns_dphy_set_clk_lane_cfg(dphy, DPHY_CLK_CFG_LEFT_DRIVES_LEFT); 36162306a36Sopenharmony_ci 36262306a36Sopenharmony_ci /* 36362306a36Sopenharmony_ci * Configure the DPHY PLL that will be used to generate the TX byte 36462306a36Sopenharmony_ci * clk. 36562306a36Sopenharmony_ci */ 36662306a36Sopenharmony_ci cdns_dphy_set_pll_cfg(dphy, &cfg); 36762306a36Sopenharmony_ci 36862306a36Sopenharmony_ci band_ctrl = cdns_dphy_tx_get_band_ctrl(opts->mipi_dphy.hs_clk_rate); 36962306a36Sopenharmony_ci if (band_ctrl < 0) 37062306a36Sopenharmony_ci return band_ctrl; 37162306a36Sopenharmony_ci 37262306a36Sopenharmony_ci reg = FIELD_PREP(DPHY_BAND_CFG_LEFT_BAND, band_ctrl) | 37362306a36Sopenharmony_ci FIELD_PREP(DPHY_BAND_CFG_RIGHT_BAND, band_ctrl); 37462306a36Sopenharmony_ci writel(reg, dphy->regs + DPHY_BAND_CFG); 37562306a36Sopenharmony_ci 37662306a36Sopenharmony_ci return 0; 37762306a36Sopenharmony_ci} 37862306a36Sopenharmony_ci 37962306a36Sopenharmony_cistatic int cdns_dphy_power_on(struct phy *phy) 38062306a36Sopenharmony_ci{ 38162306a36Sopenharmony_ci struct cdns_dphy *dphy = phy_get_drvdata(phy); 38262306a36Sopenharmony_ci 38362306a36Sopenharmony_ci clk_prepare_enable(dphy->psm_clk); 38462306a36Sopenharmony_ci clk_prepare_enable(dphy->pll_ref_clk); 38562306a36Sopenharmony_ci 38662306a36Sopenharmony_ci /* Start TX state machine. */ 38762306a36Sopenharmony_ci writel(DPHY_CMN_SSM_EN | DPHY_CMN_TX_MODE_EN, 38862306a36Sopenharmony_ci dphy->regs + DPHY_CMN_SSM); 38962306a36Sopenharmony_ci 39062306a36Sopenharmony_ci return 0; 39162306a36Sopenharmony_ci} 39262306a36Sopenharmony_ci 39362306a36Sopenharmony_cistatic int cdns_dphy_power_off(struct phy *phy) 39462306a36Sopenharmony_ci{ 39562306a36Sopenharmony_ci struct cdns_dphy *dphy = phy_get_drvdata(phy); 39662306a36Sopenharmony_ci 39762306a36Sopenharmony_ci clk_disable_unprepare(dphy->pll_ref_clk); 39862306a36Sopenharmony_ci clk_disable_unprepare(dphy->psm_clk); 39962306a36Sopenharmony_ci 40062306a36Sopenharmony_ci return 0; 40162306a36Sopenharmony_ci} 40262306a36Sopenharmony_ci 40362306a36Sopenharmony_cistatic const struct phy_ops cdns_dphy_ops = { 40462306a36Sopenharmony_ci .configure = cdns_dphy_configure, 40562306a36Sopenharmony_ci .validate = cdns_dphy_validate, 40662306a36Sopenharmony_ci .power_on = cdns_dphy_power_on, 40762306a36Sopenharmony_ci .power_off = cdns_dphy_power_off, 40862306a36Sopenharmony_ci}; 40962306a36Sopenharmony_ci 41062306a36Sopenharmony_cistatic int cdns_dphy_probe(struct platform_device *pdev) 41162306a36Sopenharmony_ci{ 41262306a36Sopenharmony_ci struct phy_provider *phy_provider; 41362306a36Sopenharmony_ci struct cdns_dphy *dphy; 41462306a36Sopenharmony_ci int ret; 41562306a36Sopenharmony_ci 41662306a36Sopenharmony_ci dphy = devm_kzalloc(&pdev->dev, sizeof(*dphy), GFP_KERNEL); 41762306a36Sopenharmony_ci if (!dphy) 41862306a36Sopenharmony_ci return -ENOMEM; 41962306a36Sopenharmony_ci dev_set_drvdata(&pdev->dev, dphy); 42062306a36Sopenharmony_ci 42162306a36Sopenharmony_ci dphy->ops = of_device_get_match_data(&pdev->dev); 42262306a36Sopenharmony_ci if (!dphy->ops) 42362306a36Sopenharmony_ci return -EINVAL; 42462306a36Sopenharmony_ci 42562306a36Sopenharmony_ci dphy->regs = devm_platform_ioremap_resource(pdev, 0); 42662306a36Sopenharmony_ci if (IS_ERR(dphy->regs)) 42762306a36Sopenharmony_ci return PTR_ERR(dphy->regs); 42862306a36Sopenharmony_ci 42962306a36Sopenharmony_ci dphy->psm_clk = devm_clk_get(&pdev->dev, "psm"); 43062306a36Sopenharmony_ci if (IS_ERR(dphy->psm_clk)) 43162306a36Sopenharmony_ci return PTR_ERR(dphy->psm_clk); 43262306a36Sopenharmony_ci 43362306a36Sopenharmony_ci dphy->pll_ref_clk = devm_clk_get(&pdev->dev, "pll_ref"); 43462306a36Sopenharmony_ci if (IS_ERR(dphy->pll_ref_clk)) 43562306a36Sopenharmony_ci return PTR_ERR(dphy->pll_ref_clk); 43662306a36Sopenharmony_ci 43762306a36Sopenharmony_ci if (dphy->ops->probe) { 43862306a36Sopenharmony_ci ret = dphy->ops->probe(dphy); 43962306a36Sopenharmony_ci if (ret) 44062306a36Sopenharmony_ci return ret; 44162306a36Sopenharmony_ci } 44262306a36Sopenharmony_ci 44362306a36Sopenharmony_ci dphy->phy = devm_phy_create(&pdev->dev, NULL, &cdns_dphy_ops); 44462306a36Sopenharmony_ci if (IS_ERR(dphy->phy)) { 44562306a36Sopenharmony_ci dev_err(&pdev->dev, "failed to create PHY\n"); 44662306a36Sopenharmony_ci if (dphy->ops->remove) 44762306a36Sopenharmony_ci dphy->ops->remove(dphy); 44862306a36Sopenharmony_ci return PTR_ERR(dphy->phy); 44962306a36Sopenharmony_ci } 45062306a36Sopenharmony_ci 45162306a36Sopenharmony_ci phy_set_drvdata(dphy->phy, dphy); 45262306a36Sopenharmony_ci phy_provider = devm_of_phy_provider_register(&pdev->dev, 45362306a36Sopenharmony_ci of_phy_simple_xlate); 45462306a36Sopenharmony_ci 45562306a36Sopenharmony_ci return PTR_ERR_OR_ZERO(phy_provider); 45662306a36Sopenharmony_ci} 45762306a36Sopenharmony_ci 45862306a36Sopenharmony_cistatic void cdns_dphy_remove(struct platform_device *pdev) 45962306a36Sopenharmony_ci{ 46062306a36Sopenharmony_ci struct cdns_dphy *dphy = dev_get_drvdata(&pdev->dev); 46162306a36Sopenharmony_ci 46262306a36Sopenharmony_ci if (dphy->ops->remove) 46362306a36Sopenharmony_ci dphy->ops->remove(dphy); 46462306a36Sopenharmony_ci} 46562306a36Sopenharmony_ci 46662306a36Sopenharmony_cistatic const struct of_device_id cdns_dphy_of_match[] = { 46762306a36Sopenharmony_ci { .compatible = "cdns,dphy", .data = &ref_dphy_ops }, 46862306a36Sopenharmony_ci { .compatible = "ti,j721e-dphy", .data = &j721e_dphy_ops }, 46962306a36Sopenharmony_ci { /* sentinel */ }, 47062306a36Sopenharmony_ci}; 47162306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, cdns_dphy_of_match); 47262306a36Sopenharmony_ci 47362306a36Sopenharmony_cistatic struct platform_driver cdns_dphy_platform_driver = { 47462306a36Sopenharmony_ci .probe = cdns_dphy_probe, 47562306a36Sopenharmony_ci .remove_new = cdns_dphy_remove, 47662306a36Sopenharmony_ci .driver = { 47762306a36Sopenharmony_ci .name = "cdns-mipi-dphy", 47862306a36Sopenharmony_ci .of_match_table = cdns_dphy_of_match, 47962306a36Sopenharmony_ci }, 48062306a36Sopenharmony_ci}; 48162306a36Sopenharmony_cimodule_platform_driver(cdns_dphy_platform_driver); 48262306a36Sopenharmony_ci 48362306a36Sopenharmony_ciMODULE_AUTHOR("Maxime Ripard <maxime.ripard@bootlin.com>"); 48462306a36Sopenharmony_ciMODULE_DESCRIPTION("Cadence MIPI D-PHY Driver"); 48562306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 486