18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0+ 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * Copyright: 2017-2018 Cadence Design Systems, Inc. 48c2ecf20Sopenharmony_ci */ 58c2ecf20Sopenharmony_ci 68c2ecf20Sopenharmony_ci#include <linux/bitops.h> 78c2ecf20Sopenharmony_ci#include <linux/clk.h> 88c2ecf20Sopenharmony_ci#include <linux/io.h> 98c2ecf20Sopenharmony_ci#include <linux/module.h> 108c2ecf20Sopenharmony_ci#include <linux/of_address.h> 118c2ecf20Sopenharmony_ci#include <linux/of_device.h> 128c2ecf20Sopenharmony_ci#include <linux/platform_device.h> 138c2ecf20Sopenharmony_ci#include <linux/reset.h> 148c2ecf20Sopenharmony_ci 158c2ecf20Sopenharmony_ci#include <linux/phy/phy.h> 168c2ecf20Sopenharmony_ci#include <linux/phy/phy-mipi-dphy.h> 178c2ecf20Sopenharmony_ci 188c2ecf20Sopenharmony_ci#define REG_WAKEUP_TIME_NS 800 198c2ecf20Sopenharmony_ci#define DPHY_PLL_RATE_HZ 108000000 208c2ecf20Sopenharmony_ci 218c2ecf20Sopenharmony_ci/* DPHY registers */ 228c2ecf20Sopenharmony_ci#define DPHY_PMA_CMN(reg) (reg) 238c2ecf20Sopenharmony_ci#define DPHY_PMA_LCLK(reg) (0x100 + (reg)) 248c2ecf20Sopenharmony_ci#define DPHY_PMA_LDATA(lane, reg) (0x200 + ((lane) * 0x100) + (reg)) 258c2ecf20Sopenharmony_ci#define DPHY_PMA_RCLK(reg) (0x600 + (reg)) 268c2ecf20Sopenharmony_ci#define DPHY_PMA_RDATA(lane, reg) (0x700 + ((lane) * 0x100) + (reg)) 278c2ecf20Sopenharmony_ci#define DPHY_PCS(reg) (0xb00 + (reg)) 288c2ecf20Sopenharmony_ci 298c2ecf20Sopenharmony_ci#define DPHY_CMN_SSM DPHY_PMA_CMN(0x20) 308c2ecf20Sopenharmony_ci#define DPHY_CMN_SSM_EN BIT(0) 318c2ecf20Sopenharmony_ci#define DPHY_CMN_TX_MODE_EN BIT(9) 328c2ecf20Sopenharmony_ci 338c2ecf20Sopenharmony_ci#define DPHY_CMN_PWM DPHY_PMA_CMN(0x40) 348c2ecf20Sopenharmony_ci#define DPHY_CMN_PWM_DIV(x) ((x) << 20) 358c2ecf20Sopenharmony_ci#define DPHY_CMN_PWM_LOW(x) ((x) << 10) 368c2ecf20Sopenharmony_ci#define DPHY_CMN_PWM_HIGH(x) (x) 378c2ecf20Sopenharmony_ci 388c2ecf20Sopenharmony_ci#define DPHY_CMN_FBDIV DPHY_PMA_CMN(0x4c) 398c2ecf20Sopenharmony_ci#define DPHY_CMN_FBDIV_VAL(low, high) (((high) << 11) | ((low) << 22)) 408c2ecf20Sopenharmony_ci#define DPHY_CMN_FBDIV_FROM_REG (BIT(10) | BIT(21)) 418c2ecf20Sopenharmony_ci 428c2ecf20Sopenharmony_ci#define DPHY_CMN_OPIPDIV DPHY_PMA_CMN(0x50) 438c2ecf20Sopenharmony_ci#define DPHY_CMN_IPDIV_FROM_REG BIT(0) 448c2ecf20Sopenharmony_ci#define DPHY_CMN_IPDIV(x) ((x) << 1) 458c2ecf20Sopenharmony_ci#define DPHY_CMN_OPDIV_FROM_REG BIT(6) 468c2ecf20Sopenharmony_ci#define DPHY_CMN_OPDIV(x) ((x) << 7) 478c2ecf20Sopenharmony_ci 488c2ecf20Sopenharmony_ci#define DPHY_PSM_CFG DPHY_PCS(0x4) 498c2ecf20Sopenharmony_ci#define DPHY_PSM_CFG_FROM_REG BIT(0) 508c2ecf20Sopenharmony_ci#define DPHY_PSM_CLK_DIV(x) ((x) << 1) 518c2ecf20Sopenharmony_ci 528c2ecf20Sopenharmony_ci#define DSI_HBP_FRAME_OVERHEAD 12 538c2ecf20Sopenharmony_ci#define DSI_HSA_FRAME_OVERHEAD 14 548c2ecf20Sopenharmony_ci#define DSI_HFP_FRAME_OVERHEAD 6 558c2ecf20Sopenharmony_ci#define DSI_HSS_VSS_VSE_FRAME_OVERHEAD 4 568c2ecf20Sopenharmony_ci#define DSI_BLANKING_FRAME_OVERHEAD 6 578c2ecf20Sopenharmony_ci#define DSI_NULL_FRAME_OVERHEAD 6 588c2ecf20Sopenharmony_ci#define DSI_EOT_PKT_SIZE 4 598c2ecf20Sopenharmony_ci 608c2ecf20Sopenharmony_cistruct cdns_dphy_cfg { 618c2ecf20Sopenharmony_ci u8 pll_ipdiv; 628c2ecf20Sopenharmony_ci u8 pll_opdiv; 638c2ecf20Sopenharmony_ci u16 pll_fbdiv; 648c2ecf20Sopenharmony_ci unsigned int nlanes; 658c2ecf20Sopenharmony_ci}; 668c2ecf20Sopenharmony_ci 678c2ecf20Sopenharmony_cienum cdns_dphy_clk_lane_cfg { 688c2ecf20Sopenharmony_ci DPHY_CLK_CFG_LEFT_DRIVES_ALL = 0, 698c2ecf20Sopenharmony_ci DPHY_CLK_CFG_LEFT_DRIVES_RIGHT = 1, 708c2ecf20Sopenharmony_ci DPHY_CLK_CFG_LEFT_DRIVES_LEFT = 2, 718c2ecf20Sopenharmony_ci DPHY_CLK_CFG_RIGHT_DRIVES_ALL = 3, 728c2ecf20Sopenharmony_ci}; 738c2ecf20Sopenharmony_ci 748c2ecf20Sopenharmony_cistruct cdns_dphy; 758c2ecf20Sopenharmony_cistruct cdns_dphy_ops { 768c2ecf20Sopenharmony_ci int (*probe)(struct cdns_dphy *dphy); 778c2ecf20Sopenharmony_ci void (*remove)(struct cdns_dphy *dphy); 788c2ecf20Sopenharmony_ci void (*set_psm_div)(struct cdns_dphy *dphy, u8 div); 798c2ecf20Sopenharmony_ci void (*set_clk_lane_cfg)(struct cdns_dphy *dphy, 808c2ecf20Sopenharmony_ci enum cdns_dphy_clk_lane_cfg cfg); 818c2ecf20Sopenharmony_ci void (*set_pll_cfg)(struct cdns_dphy *dphy, 828c2ecf20Sopenharmony_ci const struct cdns_dphy_cfg *cfg); 838c2ecf20Sopenharmony_ci unsigned long (*get_wakeup_time_ns)(struct cdns_dphy *dphy); 848c2ecf20Sopenharmony_ci}; 858c2ecf20Sopenharmony_ci 868c2ecf20Sopenharmony_cistruct cdns_dphy { 878c2ecf20Sopenharmony_ci struct cdns_dphy_cfg cfg; 888c2ecf20Sopenharmony_ci void __iomem *regs; 898c2ecf20Sopenharmony_ci struct clk *psm_clk; 908c2ecf20Sopenharmony_ci struct clk *pll_ref_clk; 918c2ecf20Sopenharmony_ci const struct cdns_dphy_ops *ops; 928c2ecf20Sopenharmony_ci struct phy *phy; 938c2ecf20Sopenharmony_ci}; 948c2ecf20Sopenharmony_ci 958c2ecf20Sopenharmony_cistatic int cdns_dsi_get_dphy_pll_cfg(struct cdns_dphy *dphy, 968c2ecf20Sopenharmony_ci struct cdns_dphy_cfg *cfg, 978c2ecf20Sopenharmony_ci struct phy_configure_opts_mipi_dphy *opts, 988c2ecf20Sopenharmony_ci unsigned int *dsi_hfp_ext) 998c2ecf20Sopenharmony_ci{ 1008c2ecf20Sopenharmony_ci unsigned long pll_ref_hz = clk_get_rate(dphy->pll_ref_clk); 1018c2ecf20Sopenharmony_ci u64 dlane_bps; 1028c2ecf20Sopenharmony_ci 1038c2ecf20Sopenharmony_ci memset(cfg, 0, sizeof(*cfg)); 1048c2ecf20Sopenharmony_ci 1058c2ecf20Sopenharmony_ci if (pll_ref_hz < 9600000 || pll_ref_hz >= 150000000) 1068c2ecf20Sopenharmony_ci return -EINVAL; 1078c2ecf20Sopenharmony_ci else if (pll_ref_hz < 19200000) 1088c2ecf20Sopenharmony_ci cfg->pll_ipdiv = 1; 1098c2ecf20Sopenharmony_ci else if (pll_ref_hz < 38400000) 1108c2ecf20Sopenharmony_ci cfg->pll_ipdiv = 2; 1118c2ecf20Sopenharmony_ci else if (pll_ref_hz < 76800000) 1128c2ecf20Sopenharmony_ci cfg->pll_ipdiv = 4; 1138c2ecf20Sopenharmony_ci else 1148c2ecf20Sopenharmony_ci cfg->pll_ipdiv = 8; 1158c2ecf20Sopenharmony_ci 1168c2ecf20Sopenharmony_ci dlane_bps = opts->hs_clk_rate; 1178c2ecf20Sopenharmony_ci 1188c2ecf20Sopenharmony_ci if (dlane_bps > 2500000000UL || dlane_bps < 160000000UL) 1198c2ecf20Sopenharmony_ci return -EINVAL; 1208c2ecf20Sopenharmony_ci else if (dlane_bps >= 1250000000) 1218c2ecf20Sopenharmony_ci cfg->pll_opdiv = 1; 1228c2ecf20Sopenharmony_ci else if (dlane_bps >= 630000000) 1238c2ecf20Sopenharmony_ci cfg->pll_opdiv = 2; 1248c2ecf20Sopenharmony_ci else if (dlane_bps >= 320000000) 1258c2ecf20Sopenharmony_ci cfg->pll_opdiv = 4; 1268c2ecf20Sopenharmony_ci else if (dlane_bps >= 160000000) 1278c2ecf20Sopenharmony_ci cfg->pll_opdiv = 8; 1288c2ecf20Sopenharmony_ci 1298c2ecf20Sopenharmony_ci cfg->pll_fbdiv = DIV_ROUND_UP_ULL(dlane_bps * 2 * cfg->pll_opdiv * 1308c2ecf20Sopenharmony_ci cfg->pll_ipdiv, 1318c2ecf20Sopenharmony_ci pll_ref_hz); 1328c2ecf20Sopenharmony_ci 1338c2ecf20Sopenharmony_ci return 0; 1348c2ecf20Sopenharmony_ci} 1358c2ecf20Sopenharmony_ci 1368c2ecf20Sopenharmony_cistatic int cdns_dphy_setup_psm(struct cdns_dphy *dphy) 1378c2ecf20Sopenharmony_ci{ 1388c2ecf20Sopenharmony_ci unsigned long psm_clk_hz = clk_get_rate(dphy->psm_clk); 1398c2ecf20Sopenharmony_ci unsigned long psm_div; 1408c2ecf20Sopenharmony_ci 1418c2ecf20Sopenharmony_ci if (!psm_clk_hz || psm_clk_hz > 100000000) 1428c2ecf20Sopenharmony_ci return -EINVAL; 1438c2ecf20Sopenharmony_ci 1448c2ecf20Sopenharmony_ci psm_div = DIV_ROUND_CLOSEST(psm_clk_hz, 1000000); 1458c2ecf20Sopenharmony_ci if (dphy->ops->set_psm_div) 1468c2ecf20Sopenharmony_ci dphy->ops->set_psm_div(dphy, psm_div); 1478c2ecf20Sopenharmony_ci 1488c2ecf20Sopenharmony_ci return 0; 1498c2ecf20Sopenharmony_ci} 1508c2ecf20Sopenharmony_ci 1518c2ecf20Sopenharmony_cistatic void cdns_dphy_set_clk_lane_cfg(struct cdns_dphy *dphy, 1528c2ecf20Sopenharmony_ci enum cdns_dphy_clk_lane_cfg cfg) 1538c2ecf20Sopenharmony_ci{ 1548c2ecf20Sopenharmony_ci if (dphy->ops->set_clk_lane_cfg) 1558c2ecf20Sopenharmony_ci dphy->ops->set_clk_lane_cfg(dphy, cfg); 1568c2ecf20Sopenharmony_ci} 1578c2ecf20Sopenharmony_ci 1588c2ecf20Sopenharmony_cistatic void cdns_dphy_set_pll_cfg(struct cdns_dphy *dphy, 1598c2ecf20Sopenharmony_ci const struct cdns_dphy_cfg *cfg) 1608c2ecf20Sopenharmony_ci{ 1618c2ecf20Sopenharmony_ci if (dphy->ops->set_pll_cfg) 1628c2ecf20Sopenharmony_ci dphy->ops->set_pll_cfg(dphy, cfg); 1638c2ecf20Sopenharmony_ci} 1648c2ecf20Sopenharmony_ci 1658c2ecf20Sopenharmony_cistatic unsigned long cdns_dphy_get_wakeup_time_ns(struct cdns_dphy *dphy) 1668c2ecf20Sopenharmony_ci{ 1678c2ecf20Sopenharmony_ci return dphy->ops->get_wakeup_time_ns(dphy); 1688c2ecf20Sopenharmony_ci} 1698c2ecf20Sopenharmony_ci 1708c2ecf20Sopenharmony_cistatic unsigned long cdns_dphy_ref_get_wakeup_time_ns(struct cdns_dphy *dphy) 1718c2ecf20Sopenharmony_ci{ 1728c2ecf20Sopenharmony_ci /* Default wakeup time is 800 ns (in a simulated environment). */ 1738c2ecf20Sopenharmony_ci return 800; 1748c2ecf20Sopenharmony_ci} 1758c2ecf20Sopenharmony_ci 1768c2ecf20Sopenharmony_cistatic void cdns_dphy_ref_set_pll_cfg(struct cdns_dphy *dphy, 1778c2ecf20Sopenharmony_ci const struct cdns_dphy_cfg *cfg) 1788c2ecf20Sopenharmony_ci{ 1798c2ecf20Sopenharmony_ci u32 fbdiv_low, fbdiv_high; 1808c2ecf20Sopenharmony_ci 1818c2ecf20Sopenharmony_ci fbdiv_low = (cfg->pll_fbdiv / 4) - 2; 1828c2ecf20Sopenharmony_ci fbdiv_high = cfg->pll_fbdiv - fbdiv_low - 2; 1838c2ecf20Sopenharmony_ci 1848c2ecf20Sopenharmony_ci writel(DPHY_CMN_IPDIV_FROM_REG | DPHY_CMN_OPDIV_FROM_REG | 1858c2ecf20Sopenharmony_ci DPHY_CMN_IPDIV(cfg->pll_ipdiv) | 1868c2ecf20Sopenharmony_ci DPHY_CMN_OPDIV(cfg->pll_opdiv), 1878c2ecf20Sopenharmony_ci dphy->regs + DPHY_CMN_OPIPDIV); 1888c2ecf20Sopenharmony_ci writel(DPHY_CMN_FBDIV_FROM_REG | 1898c2ecf20Sopenharmony_ci DPHY_CMN_FBDIV_VAL(fbdiv_low, fbdiv_high), 1908c2ecf20Sopenharmony_ci dphy->regs + DPHY_CMN_FBDIV); 1918c2ecf20Sopenharmony_ci writel(DPHY_CMN_PWM_HIGH(6) | DPHY_CMN_PWM_LOW(0x101) | 1928c2ecf20Sopenharmony_ci DPHY_CMN_PWM_DIV(0x8), 1938c2ecf20Sopenharmony_ci dphy->regs + DPHY_CMN_PWM); 1948c2ecf20Sopenharmony_ci} 1958c2ecf20Sopenharmony_ci 1968c2ecf20Sopenharmony_cistatic void cdns_dphy_ref_set_psm_div(struct cdns_dphy *dphy, u8 div) 1978c2ecf20Sopenharmony_ci{ 1988c2ecf20Sopenharmony_ci writel(DPHY_PSM_CFG_FROM_REG | DPHY_PSM_CLK_DIV(div), 1998c2ecf20Sopenharmony_ci dphy->regs + DPHY_PSM_CFG); 2008c2ecf20Sopenharmony_ci} 2018c2ecf20Sopenharmony_ci 2028c2ecf20Sopenharmony_ci/* 2038c2ecf20Sopenharmony_ci * This is the reference implementation of DPHY hooks. Specific integration of 2048c2ecf20Sopenharmony_ci * this IP may have to re-implement some of them depending on how they decided 2058c2ecf20Sopenharmony_ci * to wire things in the SoC. 2068c2ecf20Sopenharmony_ci */ 2078c2ecf20Sopenharmony_cistatic const struct cdns_dphy_ops ref_dphy_ops = { 2088c2ecf20Sopenharmony_ci .get_wakeup_time_ns = cdns_dphy_ref_get_wakeup_time_ns, 2098c2ecf20Sopenharmony_ci .set_pll_cfg = cdns_dphy_ref_set_pll_cfg, 2108c2ecf20Sopenharmony_ci .set_psm_div = cdns_dphy_ref_set_psm_div, 2118c2ecf20Sopenharmony_ci}; 2128c2ecf20Sopenharmony_ci 2138c2ecf20Sopenharmony_cistatic int cdns_dphy_config_from_opts(struct phy *phy, 2148c2ecf20Sopenharmony_ci struct phy_configure_opts_mipi_dphy *opts, 2158c2ecf20Sopenharmony_ci struct cdns_dphy_cfg *cfg) 2168c2ecf20Sopenharmony_ci{ 2178c2ecf20Sopenharmony_ci struct cdns_dphy *dphy = phy_get_drvdata(phy); 2188c2ecf20Sopenharmony_ci unsigned int dsi_hfp_ext = 0; 2198c2ecf20Sopenharmony_ci int ret; 2208c2ecf20Sopenharmony_ci 2218c2ecf20Sopenharmony_ci ret = phy_mipi_dphy_config_validate(opts); 2228c2ecf20Sopenharmony_ci if (ret) 2238c2ecf20Sopenharmony_ci return ret; 2248c2ecf20Sopenharmony_ci 2258c2ecf20Sopenharmony_ci ret = cdns_dsi_get_dphy_pll_cfg(dphy, cfg, 2268c2ecf20Sopenharmony_ci opts, &dsi_hfp_ext); 2278c2ecf20Sopenharmony_ci if (ret) 2288c2ecf20Sopenharmony_ci return ret; 2298c2ecf20Sopenharmony_ci 2308c2ecf20Sopenharmony_ci opts->wakeup = cdns_dphy_get_wakeup_time_ns(dphy) / 1000; 2318c2ecf20Sopenharmony_ci 2328c2ecf20Sopenharmony_ci return 0; 2338c2ecf20Sopenharmony_ci} 2348c2ecf20Sopenharmony_ci 2358c2ecf20Sopenharmony_cistatic int cdns_dphy_validate(struct phy *phy, enum phy_mode mode, int submode, 2368c2ecf20Sopenharmony_ci union phy_configure_opts *opts) 2378c2ecf20Sopenharmony_ci{ 2388c2ecf20Sopenharmony_ci struct cdns_dphy_cfg cfg = { 0 }; 2398c2ecf20Sopenharmony_ci 2408c2ecf20Sopenharmony_ci if (mode != PHY_MODE_MIPI_DPHY) 2418c2ecf20Sopenharmony_ci return -EINVAL; 2428c2ecf20Sopenharmony_ci 2438c2ecf20Sopenharmony_ci return cdns_dphy_config_from_opts(phy, &opts->mipi_dphy, &cfg); 2448c2ecf20Sopenharmony_ci} 2458c2ecf20Sopenharmony_ci 2468c2ecf20Sopenharmony_cistatic int cdns_dphy_configure(struct phy *phy, union phy_configure_opts *opts) 2478c2ecf20Sopenharmony_ci{ 2488c2ecf20Sopenharmony_ci struct cdns_dphy *dphy = phy_get_drvdata(phy); 2498c2ecf20Sopenharmony_ci struct cdns_dphy_cfg cfg = { 0 }; 2508c2ecf20Sopenharmony_ci int ret; 2518c2ecf20Sopenharmony_ci 2528c2ecf20Sopenharmony_ci ret = cdns_dphy_config_from_opts(phy, &opts->mipi_dphy, &cfg); 2538c2ecf20Sopenharmony_ci if (ret) 2548c2ecf20Sopenharmony_ci return ret; 2558c2ecf20Sopenharmony_ci 2568c2ecf20Sopenharmony_ci /* 2578c2ecf20Sopenharmony_ci * Configure the internal PSM clk divider so that the DPHY has a 2588c2ecf20Sopenharmony_ci * 1MHz clk (or something close). 2598c2ecf20Sopenharmony_ci */ 2608c2ecf20Sopenharmony_ci ret = cdns_dphy_setup_psm(dphy); 2618c2ecf20Sopenharmony_ci if (ret) 2628c2ecf20Sopenharmony_ci return ret; 2638c2ecf20Sopenharmony_ci 2648c2ecf20Sopenharmony_ci /* 2658c2ecf20Sopenharmony_ci * Configure attach clk lanes to data lanes: the DPHY has 2 clk lanes 2668c2ecf20Sopenharmony_ci * and 8 data lanes, each clk lane can be attache different set of 2678c2ecf20Sopenharmony_ci * data lanes. The 2 groups are named 'left' and 'right', so here we 2688c2ecf20Sopenharmony_ci * just say that we want the 'left' clk lane to drive the 'left' data 2698c2ecf20Sopenharmony_ci * lanes. 2708c2ecf20Sopenharmony_ci */ 2718c2ecf20Sopenharmony_ci cdns_dphy_set_clk_lane_cfg(dphy, DPHY_CLK_CFG_LEFT_DRIVES_LEFT); 2728c2ecf20Sopenharmony_ci 2738c2ecf20Sopenharmony_ci /* 2748c2ecf20Sopenharmony_ci * Configure the DPHY PLL that will be used to generate the TX byte 2758c2ecf20Sopenharmony_ci * clk. 2768c2ecf20Sopenharmony_ci */ 2778c2ecf20Sopenharmony_ci cdns_dphy_set_pll_cfg(dphy, &cfg); 2788c2ecf20Sopenharmony_ci 2798c2ecf20Sopenharmony_ci return 0; 2808c2ecf20Sopenharmony_ci} 2818c2ecf20Sopenharmony_ci 2828c2ecf20Sopenharmony_cistatic int cdns_dphy_power_on(struct phy *phy) 2838c2ecf20Sopenharmony_ci{ 2848c2ecf20Sopenharmony_ci struct cdns_dphy *dphy = phy_get_drvdata(phy); 2858c2ecf20Sopenharmony_ci 2868c2ecf20Sopenharmony_ci clk_prepare_enable(dphy->psm_clk); 2878c2ecf20Sopenharmony_ci clk_prepare_enable(dphy->pll_ref_clk); 2888c2ecf20Sopenharmony_ci 2898c2ecf20Sopenharmony_ci /* Start TX state machine. */ 2908c2ecf20Sopenharmony_ci writel(DPHY_CMN_SSM_EN | DPHY_CMN_TX_MODE_EN, 2918c2ecf20Sopenharmony_ci dphy->regs + DPHY_CMN_SSM); 2928c2ecf20Sopenharmony_ci 2938c2ecf20Sopenharmony_ci return 0; 2948c2ecf20Sopenharmony_ci} 2958c2ecf20Sopenharmony_ci 2968c2ecf20Sopenharmony_cistatic int cdns_dphy_power_off(struct phy *phy) 2978c2ecf20Sopenharmony_ci{ 2988c2ecf20Sopenharmony_ci struct cdns_dphy *dphy = phy_get_drvdata(phy); 2998c2ecf20Sopenharmony_ci 3008c2ecf20Sopenharmony_ci clk_disable_unprepare(dphy->pll_ref_clk); 3018c2ecf20Sopenharmony_ci clk_disable_unprepare(dphy->psm_clk); 3028c2ecf20Sopenharmony_ci 3038c2ecf20Sopenharmony_ci return 0; 3048c2ecf20Sopenharmony_ci} 3058c2ecf20Sopenharmony_ci 3068c2ecf20Sopenharmony_cistatic const struct phy_ops cdns_dphy_ops = { 3078c2ecf20Sopenharmony_ci .configure = cdns_dphy_configure, 3088c2ecf20Sopenharmony_ci .validate = cdns_dphy_validate, 3098c2ecf20Sopenharmony_ci .power_on = cdns_dphy_power_on, 3108c2ecf20Sopenharmony_ci .power_off = cdns_dphy_power_off, 3118c2ecf20Sopenharmony_ci}; 3128c2ecf20Sopenharmony_ci 3138c2ecf20Sopenharmony_cistatic int cdns_dphy_probe(struct platform_device *pdev) 3148c2ecf20Sopenharmony_ci{ 3158c2ecf20Sopenharmony_ci struct phy_provider *phy_provider; 3168c2ecf20Sopenharmony_ci struct cdns_dphy *dphy; 3178c2ecf20Sopenharmony_ci struct resource *res; 3188c2ecf20Sopenharmony_ci int ret; 3198c2ecf20Sopenharmony_ci 3208c2ecf20Sopenharmony_ci dphy = devm_kzalloc(&pdev->dev, sizeof(*dphy), GFP_KERNEL); 3218c2ecf20Sopenharmony_ci if (!dphy) 3228c2ecf20Sopenharmony_ci return -ENOMEM; 3238c2ecf20Sopenharmony_ci dev_set_drvdata(&pdev->dev, dphy); 3248c2ecf20Sopenharmony_ci 3258c2ecf20Sopenharmony_ci dphy->ops = of_device_get_match_data(&pdev->dev); 3268c2ecf20Sopenharmony_ci if (!dphy->ops) 3278c2ecf20Sopenharmony_ci return -EINVAL; 3288c2ecf20Sopenharmony_ci 3298c2ecf20Sopenharmony_ci res = platform_get_resource(pdev, IORESOURCE_MEM, 0); 3308c2ecf20Sopenharmony_ci dphy->regs = devm_ioremap_resource(&pdev->dev, res); 3318c2ecf20Sopenharmony_ci if (IS_ERR(dphy->regs)) 3328c2ecf20Sopenharmony_ci return PTR_ERR(dphy->regs); 3338c2ecf20Sopenharmony_ci 3348c2ecf20Sopenharmony_ci dphy->psm_clk = devm_clk_get(&pdev->dev, "psm"); 3358c2ecf20Sopenharmony_ci if (IS_ERR(dphy->psm_clk)) 3368c2ecf20Sopenharmony_ci return PTR_ERR(dphy->psm_clk); 3378c2ecf20Sopenharmony_ci 3388c2ecf20Sopenharmony_ci dphy->pll_ref_clk = devm_clk_get(&pdev->dev, "pll_ref"); 3398c2ecf20Sopenharmony_ci if (IS_ERR(dphy->pll_ref_clk)) 3408c2ecf20Sopenharmony_ci return PTR_ERR(dphy->pll_ref_clk); 3418c2ecf20Sopenharmony_ci 3428c2ecf20Sopenharmony_ci if (dphy->ops->probe) { 3438c2ecf20Sopenharmony_ci ret = dphy->ops->probe(dphy); 3448c2ecf20Sopenharmony_ci if (ret) 3458c2ecf20Sopenharmony_ci return ret; 3468c2ecf20Sopenharmony_ci } 3478c2ecf20Sopenharmony_ci 3488c2ecf20Sopenharmony_ci dphy->phy = devm_phy_create(&pdev->dev, NULL, &cdns_dphy_ops); 3498c2ecf20Sopenharmony_ci if (IS_ERR(dphy->phy)) { 3508c2ecf20Sopenharmony_ci dev_err(&pdev->dev, "failed to create PHY\n"); 3518c2ecf20Sopenharmony_ci if (dphy->ops->remove) 3528c2ecf20Sopenharmony_ci dphy->ops->remove(dphy); 3538c2ecf20Sopenharmony_ci return PTR_ERR(dphy->phy); 3548c2ecf20Sopenharmony_ci } 3558c2ecf20Sopenharmony_ci 3568c2ecf20Sopenharmony_ci phy_set_drvdata(dphy->phy, dphy); 3578c2ecf20Sopenharmony_ci phy_provider = devm_of_phy_provider_register(&pdev->dev, 3588c2ecf20Sopenharmony_ci of_phy_simple_xlate); 3598c2ecf20Sopenharmony_ci 3608c2ecf20Sopenharmony_ci return PTR_ERR_OR_ZERO(phy_provider); 3618c2ecf20Sopenharmony_ci} 3628c2ecf20Sopenharmony_ci 3638c2ecf20Sopenharmony_cistatic int cdns_dphy_remove(struct platform_device *pdev) 3648c2ecf20Sopenharmony_ci{ 3658c2ecf20Sopenharmony_ci struct cdns_dphy *dphy = dev_get_drvdata(&pdev->dev); 3668c2ecf20Sopenharmony_ci 3678c2ecf20Sopenharmony_ci if (dphy->ops->remove) 3688c2ecf20Sopenharmony_ci dphy->ops->remove(dphy); 3698c2ecf20Sopenharmony_ci 3708c2ecf20Sopenharmony_ci return 0; 3718c2ecf20Sopenharmony_ci} 3728c2ecf20Sopenharmony_ci 3738c2ecf20Sopenharmony_cistatic const struct of_device_id cdns_dphy_of_match[] = { 3748c2ecf20Sopenharmony_ci { .compatible = "cdns,dphy", .data = &ref_dphy_ops }, 3758c2ecf20Sopenharmony_ci { /* sentinel */ }, 3768c2ecf20Sopenharmony_ci}; 3778c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(of, cdns_dphy_of_match); 3788c2ecf20Sopenharmony_ci 3798c2ecf20Sopenharmony_cistatic struct platform_driver cdns_dphy_platform_driver = { 3808c2ecf20Sopenharmony_ci .probe = cdns_dphy_probe, 3818c2ecf20Sopenharmony_ci .remove = cdns_dphy_remove, 3828c2ecf20Sopenharmony_ci .driver = { 3838c2ecf20Sopenharmony_ci .name = "cdns-mipi-dphy", 3848c2ecf20Sopenharmony_ci .of_match_table = cdns_dphy_of_match, 3858c2ecf20Sopenharmony_ci }, 3868c2ecf20Sopenharmony_ci}; 3878c2ecf20Sopenharmony_cimodule_platform_driver(cdns_dphy_platform_driver); 3888c2ecf20Sopenharmony_ci 3898c2ecf20Sopenharmony_ciMODULE_AUTHOR("Maxime Ripard <maxime.ripard@bootlin.com>"); 3908c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("Cadence MIPI D-PHY Driver"); 3918c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL"); 392