phy-samsung-ufs.c (9357B)
1// SPDX-License-Identifier: GPL-2.0-only 2/* 3 * UFS PHY driver for Samsung SoC 4 * 5 * Copyright (C) 2020 Samsung Electronics Co., Ltd. 6 * Author: Seungwon Jeon <essuuj@gmail.com> 7 * Author: Alim Akhtar <alim.akhtar@samsung.com> 8 * 9 */ 10#include <linux/clk.h> 11#include <linux/delay.h> 12#include <linux/err.h> 13#include <linux/of.h> 14#include <linux/io.h> 15#include <linux/iopoll.h> 16#include <linux/mfd/syscon.h> 17#include <linux/module.h> 18#include <linux/phy/phy.h> 19#include <linux/platform_device.h> 20#include <linux/regmap.h> 21 22#include "phy-samsung-ufs.h" 23 24#define for_each_phy_lane(phy, i) \ 25 for (i = 0; i < (phy)->lane_cnt; i++) 26#define for_each_phy_cfg(cfg) \ 27 for (; (cfg)->id; (cfg)++) 28 29#define PHY_DEF_LANE_CNT 1 30 31static void samsung_ufs_phy_config(struct samsung_ufs_phy *phy, 32 const struct samsung_ufs_phy_cfg *cfg, 33 u8 lane) 34{ 35 enum {LANE_0, LANE_1}; /* lane index */ 36 37 switch (lane) { 38 case LANE_0: 39 writel(cfg->val, (phy)->reg_pma + cfg->off_0); 40 break; 41 case LANE_1: 42 if (cfg->id == PHY_TRSV_BLK) 43 writel(cfg->val, (phy)->reg_pma + cfg->off_1); 44 break; 45 } 46} 47 48static int samsung_ufs_phy_wait_for_lock_acq(struct phy *phy) 49{ 50 struct samsung_ufs_phy *ufs_phy = get_samsung_ufs_phy(phy); 51 const unsigned int timeout_us = 100000; 52 const unsigned int sleep_us = 10; 53 u32 val; 54 int err; 55 56 err = readl_poll_timeout( 57 ufs_phy->reg_pma + PHY_APB_ADDR(PHY_PLL_LOCK_STATUS), 58 val, (val & PHY_PLL_LOCK_BIT), sleep_us, timeout_us); 59 if (err) { 60 dev_err(ufs_phy->dev, 61 "failed to get phy pll lock acquisition %d\n", err); 62 goto out; 63 } 64 65 err = readl_poll_timeout( 66 ufs_phy->reg_pma + PHY_APB_ADDR(PHY_CDR_LOCK_STATUS), 67 val, (val & PHY_CDR_LOCK_BIT), sleep_us, timeout_us); 68 if (err) 69 dev_err(ufs_phy->dev, 70 "failed to get phy cdr lock acquisition %d\n", err); 71out: 72 return err; 73} 74 75static int samsung_ufs_phy_calibrate(struct phy *phy) 76{ 77 struct samsung_ufs_phy *ufs_phy = get_samsung_ufs_phy(phy); 78 struct samsung_ufs_phy_cfg **cfgs = ufs_phy->cfg; 79 const struct samsung_ufs_phy_cfg *cfg; 80 int err = 0; 81 int i; 82 83 if (unlikely(ufs_phy->ufs_phy_state < CFG_PRE_INIT || 84 ufs_phy->ufs_phy_state >= CFG_TAG_MAX)) { 85 dev_err(ufs_phy->dev, "invalid phy config index %d\n", ufs_phy->ufs_phy_state); 86 return -EINVAL; 87 } 88 89 cfg = cfgs[ufs_phy->ufs_phy_state]; 90 if (!cfg) 91 goto out; 92 93 for_each_phy_cfg(cfg) { 94 for_each_phy_lane(ufs_phy, i) { 95 samsung_ufs_phy_config(ufs_phy, cfg, i); 96 } 97 } 98 99 if (ufs_phy->ufs_phy_state == CFG_POST_PWR_HS) 100 err = samsung_ufs_phy_wait_for_lock_acq(phy); 101 102 /** 103 * In Samsung ufshci, PHY need to be calibrated at different 104 * stages / state mainly before Linkstartup, after Linkstartup, 105 * before power mode change and after power mode change. 106 * Below state machine to make sure to calibrate PHY in each 107 * state. Here after configuring PHY in a given state, will 108 * change the state to next state so that next state phy 109 * calibration value can be programed 110 */ 111out: 112 switch (ufs_phy->ufs_phy_state) { 113 case CFG_PRE_INIT: 114 ufs_phy->ufs_phy_state = CFG_POST_INIT; 115 break; 116 case CFG_POST_INIT: 117 ufs_phy->ufs_phy_state = CFG_PRE_PWR_HS; 118 break; 119 case CFG_PRE_PWR_HS: 120 ufs_phy->ufs_phy_state = CFG_POST_PWR_HS; 121 break; 122 case CFG_POST_PWR_HS: 123 /* Change back to INIT state */ 124 ufs_phy->ufs_phy_state = CFG_PRE_INIT; 125 break; 126 default: 127 dev_err(ufs_phy->dev, "wrong state for phy calibration\n"); 128 } 129 130 return err; 131} 132 133static int samsung_ufs_phy_symbol_clk_init(struct samsung_ufs_phy *phy) 134{ 135 int ret; 136 137 phy->tx0_symbol_clk = devm_clk_get(phy->dev, "tx0_symbol_clk"); 138 if (IS_ERR(phy->tx0_symbol_clk)) { 139 dev_err(phy->dev, "failed to get tx0_symbol_clk clock\n"); 140 return PTR_ERR(phy->tx0_symbol_clk); 141 } 142 143 phy->rx0_symbol_clk = devm_clk_get(phy->dev, "rx0_symbol_clk"); 144 if (IS_ERR(phy->rx0_symbol_clk)) { 145 dev_err(phy->dev, "failed to get rx0_symbol_clk clock\n"); 146 return PTR_ERR(phy->rx0_symbol_clk); 147 } 148 149 phy->rx1_symbol_clk = devm_clk_get(phy->dev, "rx1_symbol_clk"); 150 if (IS_ERR(phy->rx1_symbol_clk)) { 151 dev_err(phy->dev, "failed to get rx1_symbol_clk clock\n"); 152 return PTR_ERR(phy->rx1_symbol_clk); 153 } 154 155 ret = clk_prepare_enable(phy->tx0_symbol_clk); 156 if (ret) { 157 dev_err(phy->dev, "%s: tx0_symbol_clk enable failed %d\n", __func__, ret); 158 goto out; 159 } 160 161 ret = clk_prepare_enable(phy->rx0_symbol_clk); 162 if (ret) { 163 dev_err(phy->dev, "%s: rx0_symbol_clk enable failed %d\n", __func__, ret); 164 goto out_disable_tx0_clk; 165 } 166 167 ret = clk_prepare_enable(phy->rx1_symbol_clk); 168 if (ret) { 169 dev_err(phy->dev, "%s: rx1_symbol_clk enable failed %d\n", __func__, ret); 170 goto out_disable_rx0_clk; 171 } 172 173 return 0; 174 175out_disable_rx0_clk: 176 clk_disable_unprepare(phy->rx0_symbol_clk); 177out_disable_tx0_clk: 178 clk_disable_unprepare(phy->tx0_symbol_clk); 179out: 180 return ret; 181} 182 183static int samsung_ufs_phy_clks_init(struct samsung_ufs_phy *phy) 184{ 185 int ret; 186 187 phy->ref_clk = devm_clk_get(phy->dev, "ref_clk"); 188 if (IS_ERR(phy->ref_clk)) 189 dev_err(phy->dev, "failed to get ref_clk clock\n"); 190 191 ret = clk_prepare_enable(phy->ref_clk); 192 if (ret) { 193 dev_err(phy->dev, "%s: ref_clk enable failed %d\n", __func__, ret); 194 return ret; 195 } 196 197 dev_dbg(phy->dev, "UFS MPHY ref_clk_rate = %ld\n", clk_get_rate(phy->ref_clk)); 198 199 return 0; 200} 201 202static int samsung_ufs_phy_init(struct phy *phy) 203{ 204 struct samsung_ufs_phy *ss_phy = get_samsung_ufs_phy(phy); 205 int ret; 206 207 ss_phy->lane_cnt = phy->attrs.bus_width; 208 ss_phy->ufs_phy_state = CFG_PRE_INIT; 209 210 if (ss_phy->drvdata->has_symbol_clk) { 211 ret = samsung_ufs_phy_symbol_clk_init(ss_phy); 212 if (ret) 213 dev_err(ss_phy->dev, "failed to set ufs phy symbol clocks\n"); 214 } 215 216 ret = samsung_ufs_phy_clks_init(ss_phy); 217 if (ret) 218 dev_err(ss_phy->dev, "failed to set ufs phy clocks\n"); 219 220 ret = samsung_ufs_phy_calibrate(phy); 221 if (ret) 222 dev_err(ss_phy->dev, "ufs phy calibration failed\n"); 223 224 return ret; 225} 226 227static int samsung_ufs_phy_power_on(struct phy *phy) 228{ 229 struct samsung_ufs_phy *ss_phy = get_samsung_ufs_phy(phy); 230 231 samsung_ufs_phy_ctrl_isol(ss_phy, false); 232 return 0; 233} 234 235static int samsung_ufs_phy_power_off(struct phy *phy) 236{ 237 struct samsung_ufs_phy *ss_phy = get_samsung_ufs_phy(phy); 238 239 samsung_ufs_phy_ctrl_isol(ss_phy, true); 240 return 0; 241} 242 243static int samsung_ufs_phy_set_mode(struct phy *generic_phy, 244 enum phy_mode mode, int submode) 245{ 246 struct samsung_ufs_phy *ss_phy = get_samsung_ufs_phy(generic_phy); 247 248 ss_phy->mode = PHY_MODE_INVALID; 249 250 if (mode > 0) 251 ss_phy->mode = mode; 252 253 return 0; 254} 255 256static int samsung_ufs_phy_exit(struct phy *phy) 257{ 258 struct samsung_ufs_phy *ss_phy = get_samsung_ufs_phy(phy); 259 260 clk_disable_unprepare(ss_phy->ref_clk); 261 262 if (ss_phy->drvdata->has_symbol_clk) { 263 clk_disable_unprepare(ss_phy->tx0_symbol_clk); 264 clk_disable_unprepare(ss_phy->rx0_symbol_clk); 265 clk_disable_unprepare(ss_phy->rx1_symbol_clk); 266 } 267 268 return 0; 269} 270 271static const struct phy_ops samsung_ufs_phy_ops = { 272 .init = samsung_ufs_phy_init, 273 .exit = samsung_ufs_phy_exit, 274 .power_on = samsung_ufs_phy_power_on, 275 .power_off = samsung_ufs_phy_power_off, 276 .calibrate = samsung_ufs_phy_calibrate, 277 .set_mode = samsung_ufs_phy_set_mode, 278 .owner = THIS_MODULE, 279}; 280 281static const struct of_device_id samsung_ufs_phy_match[]; 282 283static int samsung_ufs_phy_probe(struct platform_device *pdev) 284{ 285 struct device *dev = &pdev->dev; 286 const struct of_device_id *match; 287 struct samsung_ufs_phy *phy; 288 struct phy *gen_phy; 289 struct phy_provider *phy_provider; 290 const struct samsung_ufs_phy_drvdata *drvdata; 291 int err = 0; 292 293 match = of_match_node(samsung_ufs_phy_match, dev->of_node); 294 if (!match) { 295 err = -EINVAL; 296 dev_err(dev, "failed to get match_node\n"); 297 goto out; 298 } 299 300 phy = devm_kzalloc(dev, sizeof(*phy), GFP_KERNEL); 301 if (!phy) { 302 err = -ENOMEM; 303 goto out; 304 } 305 306 phy->reg_pma = devm_platform_ioremap_resource_byname(pdev, "phy-pma"); 307 if (IS_ERR(phy->reg_pma)) { 308 err = PTR_ERR(phy->reg_pma); 309 goto out; 310 } 311 312 phy->reg_pmu = syscon_regmap_lookup_by_phandle( 313 dev->of_node, "samsung,pmu-syscon"); 314 if (IS_ERR(phy->reg_pmu)) { 315 err = PTR_ERR(phy->reg_pmu); 316 dev_err(dev, "failed syscon remap for pmu\n"); 317 goto out; 318 } 319 320 gen_phy = devm_phy_create(dev, NULL, &samsung_ufs_phy_ops); 321 if (IS_ERR(gen_phy)) { 322 err = PTR_ERR(gen_phy); 323 dev_err(dev, "failed to create PHY for ufs-phy\n"); 324 goto out; 325 } 326 327 drvdata = match->data; 328 phy->dev = dev; 329 phy->drvdata = drvdata; 330 phy->cfg = (struct samsung_ufs_phy_cfg **)drvdata->cfg; 331 phy->isol = &drvdata->isol; 332 phy->lane_cnt = PHY_DEF_LANE_CNT; 333 334 phy_set_drvdata(gen_phy, phy); 335 336 phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); 337 if (IS_ERR(phy_provider)) { 338 err = PTR_ERR(phy_provider); 339 dev_err(dev, "failed to register phy-provider\n"); 340 goto out; 341 } 342out: 343 return err; 344} 345 346static const struct of_device_id samsung_ufs_phy_match[] = { 347 { 348 .compatible = "samsung,exynos7-ufs-phy", 349 .data = &exynos7_ufs_phy, 350 }, { 351 .compatible = "samsung,exynosautov9-ufs-phy", 352 .data = &exynosautov9_ufs_phy, 353 }, 354 {}, 355}; 356MODULE_DEVICE_TABLE(of, samsung_ufs_phy_match); 357 358static struct platform_driver samsung_ufs_phy_driver = { 359 .probe = samsung_ufs_phy_probe, 360 .driver = { 361 .name = "samsung-ufs-phy", 362 .of_match_table = samsung_ufs_phy_match, 363 }, 364}; 365module_platform_driver(samsung_ufs_phy_driver); 366MODULE_DESCRIPTION("Samsung SoC UFS PHY Driver"); 367MODULE_AUTHOR("Seungwon Jeon <essuuj@gmail.com>"); 368MODULE_AUTHOR("Alim Akhtar <alim.akhtar@samsung.com>"); 369MODULE_LICENSE("GPL v2");