| /* |
| * Copyright (c) 2013-2014, The Linux Foundation. All rights reserved. |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License version 2 and |
| * only version 2 as published by the Free Software Foundation. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| */ |
| |
| #include <linux/io.h> |
| #include <linux/kernel.h> |
| #include <linux/module.h> |
| #include <linux/of.h> |
| #include <linux/of_address.h> |
| #include <linux/time.h> |
| #include <linux/delay.h> |
| #include <linux/clk.h> |
| #include <linux/slab.h> |
| #include <linux/platform_device.h> |
| #include <linux/phy/phy.h> |
| #include <linux/iopoll.h> |
| #include <linux/regulator/consumer.h> |
| |
| /* QSERDES COMMON registers */ |
| #define QSERDES_COM_SYS_CLK_CTRL 0x000 |
| #define QSERDES_COM_PLL_IP_SETI 0x018 |
| #define QSERDES_COM_PLL_CP_SETI 0x024 |
| #define QSERDES_COM_PLL_IP_SETP 0x028 |
| #define QSERDES_COM_PLL_CP_SETP 0x02c |
| #define QSERDES_COM_SYSCLK_EN_SEL 0x038 |
| #define QSERDES_COM_RESETSM_CNTRL 0x040 |
| #define QSERDES_COM_PLLLOCK_CMP1 0x044 |
| #define QSERDES_COM_PLLLOCK_CMP2 0x048 |
| #define QSERDES_COM_PLLLOCK_CMP3 0x04c |
| #define QSERDES_COM_PLLLOCK_CMP_EN 0x050 |
| #define QSERDES_COM_DEC_START1 0x064 |
| #define QSERDES_COM_SSC_EN_CENTER 0x06c |
| #define QSERDES_COM_SSC_ADJ_PER1 0x070 |
| #define QSERDES_COM_SSC_ADJ_PER2 0x074 |
| #define QSERDES_COM_SSC_PER1 0x078 |
| #define QSERDES_COM_SSC_PER2 0x07c |
| #define QSERDES_COM_SSC_STEP_SIZE1 0x080 |
| #define QSERDES_COM_SSC_STEP_SIZE2 0x084 |
| #define QSERDES_COM_DIV_FRAC_START1 0x098 |
| #define QSERDES_COM_DIV_FRAC_START2 0x09c |
| #define QSERDES_COM_DIV_FRAC_START3 0x0a0 |
| #define QSERDES_COM_DEC_START2 0x0a4 |
| #define QSERDES_COM_PLL_CRCTRL 0x0ac |
| #define QSERDES_COM_RESET_SM 0x0bc |
| |
| /* QSERDES TX registers */ |
| #define QSERDES_TX_BIST_MODE_LANENO 0x100 |
| #define QSERDES_TX_TX_EMP_POST1_LVL 0x108 |
| #define QSERDES_TX_TX_DRV_LVL 0x10c |
| |
| /* QSERDES RX registers */ |
| #define QSERDES_RX_CDR_CONTROL 0x200 |
| #define QSERDES_RX_CDR_CONTROL2 0x210 |
| #define QSERDES_RX_RX_EQ_GAIN12 0x230 |
| #define QSERDES_RX_PWM_CNTRL1 0x280 |
| #define QSERDES_RX_PWM_CNTRL2 0x284 |
| #define QSERDES_RX_CDR_CONTROL_QUARTER 0x29c |
| |
| /* SATA PHY registers */ |
| #define SATA_PHY_SERDES_START 0x300 |
| #define SATA_PHY_CMN_PWR_CTRL 0x304 |
| #define SATA_PHY_RX_PWR_CTRL 0x308 |
| #define SATA_PHY_TX_PWR_CTRL 0x30c |
| #define SATA_PHY_LANE_CTRL1 0x318 |
| #define SATA_PHY_CDR_CTRL0 0x358 |
| #define SATA_PHY_CDR_CTRL1 0x35c |
| #define SATA_PHY_TX_DRV_WAKEUP 0x360 |
| #define SATA_PHY_CLK_BUF_SETTLING 0x364 |
| #define SATA_PHY_SPDNEG_CFG0 0x370 |
| #define SATA_PHY_SPDNEG_CFG1 0x374 |
| #define SATA_PHY_POW_DWN_CTRL0 0x380 |
| #define SATA_PHY_ALIGNP 0x3a4 |
| |
| #define MAX_PROP_NAME 32 |
| #define VDDA_PHY_MIN_UV 950000 |
| #define VDDA_PHY_MAX_UV 1000000 |
| #define VDDA_PLL_MIN_UV 1800000 |
| #define VDDA_PLL_MAX_UV 1800000 |
| |
| struct msm_sata_phy_vreg { |
| const char *name; |
| struct regulator *reg; |
| int max_uA; |
| int min_uV; |
| int max_uV; |
| bool enabled; |
| }; |
| |
| struct msm_sata_phy { |
| struct device *dev; |
| void __iomem *mmio; |
| void __iomem *phy_sel; |
| struct clk *ref_clk_src; |
| struct clk *ref_clk_parent; |
| struct clk *ref_clk; |
| struct clk *rxoob_clk; |
| bool is_ref_clk_enabled; |
| bool is_rxoob_clk_enabled; |
| struct msm_sata_phy_vreg vdda_pll; |
| struct msm_sata_phy_vreg vdda_phy; |
| bool is_powered_on; |
| }; |
| |
| static int msm_sata_enable_phy_rxoob_clk(struct msm_sata_phy *phy) |
| { |
| int err = 0; |
| |
| if (phy->is_rxoob_clk_enabled) |
| goto out; |
| |
| /* set max. 100MHz */ |
| err = clk_set_rate(phy->rxoob_clk, 100000000); |
| if (err) { |
| dev_err(phy->dev, "%s: rxoob_clk set rate failed %d\n", |
| __func__, err); |
| goto out; |
| } |
| |
| err = clk_prepare_enable(phy->rxoob_clk); |
| if (err) { |
| dev_err(phy->dev, "%s: rxoob_clk enable failed %d\n", |
| __func__, err); |
| goto out; |
| } |
| |
| phy->is_rxoob_clk_enabled = true; |
| out: |
| return err; |
| } |
| |
| static void msm_sata_disable_phy_rxoob_clk(struct msm_sata_phy *phy) |
| { |
| if (phy->is_rxoob_clk_enabled) { |
| clk_disable_unprepare(phy->rxoob_clk); |
| phy->is_rxoob_clk_enabled = false; |
| } |
| } |
| |
| static int msm_sata_enable_phy_ref_clk(struct msm_sata_phy *phy) |
| { |
| int err = 0; |
| |
| if (phy->is_ref_clk_enabled) |
| goto out; |
| |
| /* |
| * reference clock is propagated in a daisy-chained manner from |
| * source to phy, so ungate them at each stage. |
| */ |
| err = clk_prepare_enable(phy->ref_clk_src); |
| if (err) { |
| dev_err(phy->dev, "%s: ref_clk_src enable failed %d\n", |
| __func__, err); |
| goto out; |
| } |
| |
| err = clk_prepare_enable(phy->ref_clk_parent); |
| if (err) { |
| dev_err(phy->dev, "%s: ref_clk_parent enable failed %d\n", |
| __func__, err); |
| goto out_disable_src; |
| } |
| |
| err = clk_prepare_enable(phy->ref_clk); |
| if (err) { |
| dev_err(phy->dev, "%s: ref_clk enable failed %d\n", |
| __func__, err); |
| goto out_disable_parent; |
| } |
| |
| phy->is_ref_clk_enabled = true; |
| goto out; |
| |
| out_disable_parent: |
| clk_disable_unprepare(phy->ref_clk_parent); |
| out_disable_src: |
| clk_disable_unprepare(phy->ref_clk_src); |
| out: |
| return err; |
| } |
| |
| static void msm_sata_disable_phy_ref_clk(struct msm_sata_phy *phy) |
| { |
| if (phy->is_ref_clk_enabled) { |
| clk_disable_unprepare(phy->ref_clk); |
| clk_disable_unprepare(phy->ref_clk_parent); |
| clk_disable_unprepare(phy->ref_clk_src); |
| phy->is_ref_clk_enabled = false; |
| } |
| } |
| |
| static int msm_sata_phy_cfg_vreg(struct device *dev, |
| struct msm_sata_phy_vreg *vreg, bool on) |
| { |
| int err = 0; |
| struct regulator *reg = vreg->reg; |
| const char *name = vreg->name; |
| int min_uV, uA_load; |
| |
| BUG_ON(!vreg); |
| |
| if (regulator_count_voltages(reg) > 0) { |
| min_uV = on ? vreg->min_uV : 0; |
| err = regulator_set_voltage(reg, min_uV, vreg->max_uV); |
| if (err) { |
| dev_err(dev, "%s: %s set voltage failed, err=%d\n", |
| __func__, name, err); |
| goto out; |
| } |
| |
| uA_load = on ? vreg->max_uA : 0; |
| err = regulator_set_optimum_mode(reg, uA_load); |
| if (err >= 0) { |
| /* |
| * regulator_set_optimum_mode() returns new regulator |
| * mode upon success. |
| */ |
| err = 0; |
| } else { |
| dev_err(dev, "%s: %s set optimum mode(uA_load=%d) failed, err=%d\n", |
| __func__, name, uA_load, err); |
| goto out; |
| } |
| } |
| out: |
| return err; |
| } |
| |
| static int msm_sata_phy_enable_vreg(struct msm_sata_phy *phy, |
| struct msm_sata_phy_vreg *vreg) |
| { |
| struct device *dev = phy->dev; |
| int err = 0; |
| |
| if (!vreg || vreg->enabled) |
| goto out; |
| |
| err = msm_sata_phy_cfg_vreg(dev, vreg, true); |
| if (!err) |
| err = regulator_enable(vreg->reg); |
| |
| if (!err) |
| vreg->enabled = true; |
| else |
| dev_err(dev, "%s: %s enable failed, err=%d\n", |
| __func__, vreg->name, err); |
| out: |
| return err; |
| } |
| |
| static int msm_sata_phy_disable_vreg(struct msm_sata_phy *phy, |
| struct msm_sata_phy_vreg *vreg) |
| { |
| struct device *dev = phy->dev; |
| int err = 0; |
| |
| if (!vreg || !vreg->enabled) |
| goto out; |
| |
| err = regulator_disable(vreg->reg); |
| |
| if (!err) { |
| /* ignore errors on applying disable config */ |
| msm_sata_phy_cfg_vreg(dev, vreg, false); |
| vreg->enabled = false; |
| } else { |
| dev_err(dev, "%s: %s disable failed, err=%d\n", |
| __func__, vreg->name, err); |
| } |
| out: |
| return err; |
| } |
| |
| static int msm_sata_phy_init_vreg(struct device *dev, |
| struct msm_sata_phy_vreg *vreg, const char *name) |
| { |
| int err = 0; |
| char prop_name[MAX_PROP_NAME]; |
| |
| vreg->name = kstrdup(name, GFP_KERNEL); |
| if (!vreg->name) { |
| err = -ENOMEM; |
| goto out; |
| } |
| |
| vreg->reg = devm_regulator_get(dev, name); |
| if (IS_ERR(vreg->reg)) { |
| err = PTR_ERR(vreg->reg); |
| dev_err(dev, "failed to get %s, %d\n", name, err); |
| goto out; |
| } |
| |
| if (dev->of_node) { |
| snprintf(prop_name, MAX_PROP_NAME, "%s-max-microamp", name); |
| err = of_property_read_u32(dev->of_node, |
| prop_name, &vreg->max_uA); |
| if (err && err != -EINVAL) { |
| dev_err(dev, "%s: failed to read %s\n", |
| __func__, prop_name); |
| goto out; |
| } else if (err == -EINVAL || !vreg->max_uA) { |
| if (regulator_count_voltages(vreg->reg) > 0) { |
| dev_err(dev, "%s: %s is mandatory\n", |
| __func__, prop_name); |
| goto out; |
| } |
| err = 0; |
| } |
| } |
| |
| if (!strcmp(name, "vdda-pll")) { |
| vreg->max_uV = VDDA_PLL_MAX_UV; |
| vreg->min_uV = VDDA_PLL_MIN_UV; |
| } else if (!strcmp(name, "vdda-phy")) { |
| vreg->max_uV = VDDA_PHY_MAX_UV; |
| vreg->min_uV = VDDA_PHY_MIN_UV; |
| } |
| |
| out: |
| if (err) |
| kfree(vreg->name); |
| return err; |
| } |
| |
| static int msm_sata_phy_clk_get(struct device *dev, |
| const char *name, struct clk **clk_out) |
| { |
| struct clk *clk; |
| int err = 0; |
| |
| clk = devm_clk_get(dev, name); |
| if (IS_ERR(clk)) { |
| err = PTR_ERR(clk); |
| dev_err(dev, "failed to get %s err %d", name, err); |
| } else { |
| *clk_out = clk; |
| } |
| |
| return err; |
| } |
| |
| static int msm_sata_phy_power_up(struct msm_sata_phy *phy) |
| { |
| int err = 0; |
| u32 reg; |
| struct device *dev = phy->dev; |
| |
| if (phy->phy_sel) { |
| /* Select SATA PHY */ |
| writel_relaxed(0x0, phy->phy_sel); |
| |
| /* |
| * SATA PHY must be selected before configuring the PHY. |
| * The phy_sel and phy_mmio may be in different register space |
| * and *_relaxed version doesn't ensure ordering in such case. |
| */ |
| mb(); |
| } |
| |
| /* SATA PHY powerup sequence */ |
| |
| /* PWM configurations */ |
| writel_relaxed(0x08, phy->mmio + QSERDES_RX_PWM_CNTRL1); |
| writel_relaxed(0x40, phy->mmio + QSERDES_RX_PWM_CNTRL2); |
| |
| /* Configure PHY power control to operate in mission mode */ |
| writel_relaxed(0x01, phy->mmio + SATA_PHY_POW_DWN_CTRL0); |
| |
| /* CDR counter selected between 30-40 */ |
| writel_relaxed(0x25, phy->mmio + SATA_PHY_CDR_CTRL0); |
| |
| /* Wakeup counter values are set to Maximum */ |
| writel_relaxed(0x0f, phy->mmio + SATA_PHY_CLK_BUF_SETTLING); |
| writel_relaxed(0xff, phy->mmio + SATA_PHY_TX_DRV_WAKEUP); |
| writel_relaxed(0xff, phy->mmio + SATA_PHY_SPDNEG_CFG0); |
| writel_relaxed(0x25, phy->mmio + SATA_PHY_CDR_CTRL0); |
| |
| /* PLL register settings */ |
| writel_relaxed(0xec, phy->mmio + QSERDES_COM_PLL_CRCTRL); |
| writel_relaxed(0x01, phy->mmio + QSERDES_COM_PLL_IP_SETI); |
| writel_relaxed(0x3f, phy->mmio + QSERDES_COM_PLL_CP_SETI); |
| writel_relaxed(0x0f, phy->mmio + QSERDES_COM_PLL_IP_SETP); |
| writel_relaxed(0x13, phy->mmio + QSERDES_COM_PLL_CP_SETP); |
| |
| /* PCS settings for COMMON, TX, RX paths */ |
| writel_relaxed(0x5b, phy->mmio + SATA_PHY_CMN_PWR_CTRL); |
| writel_relaxed(0x32, phy->mmio + SATA_PHY_TX_PWR_CTRL); |
| writel_relaxed(0x83, phy->mmio + SATA_PHY_RX_PWR_CTRL); |
| writel_relaxed(0x7b, phy->mmio + SATA_PHY_CMN_PWR_CTRL); |
| |
| /* Ref clk frequency select - 19.2Mhz selected */ |
| writel_relaxed(0x08, phy->mmio + QSERDES_COM_SYSCLK_EN_SEL); |
| writel_relaxed(0x06, phy->mmio + QSERDES_COM_SYS_CLK_CTRL); |
| |
| /* Decimal and Fractional dividers configuration */ |
| writel_relaxed(0x9c, phy->mmio + QSERDES_COM_DEC_START1); |
| writel_relaxed(0x03, phy->mmio + QSERDES_COM_DEC_START2); |
| writel_relaxed(0xff, phy->mmio + QSERDES_COM_DIV_FRAC_START1); |
| writel_relaxed(0xff, phy->mmio + QSERDES_COM_DIV_FRAC_START2); |
| writel_relaxed(0x13, phy->mmio + QSERDES_COM_DIV_FRAC_START3); |
| |
| /* PLL configurations */ |
| writel_relaxed(0xff, phy->mmio + QSERDES_COM_PLLLOCK_CMP1); |
| writel_relaxed(0x7c, phy->mmio + QSERDES_COM_PLLLOCK_CMP2); |
| writel_relaxed(0x00, phy->mmio + QSERDES_COM_PLLLOCK_CMP3); |
| writel_relaxed(0x01, phy->mmio + QSERDES_COM_PLLLOCK_CMP_EN); |
| |
| /* Other Resetsm configurations - FAST_VCO_TUNE */ |
| writel_relaxed(0x10, phy->mmio + QSERDES_COM_RESETSM_CNTRL); |
| |
| /* PI configurations- First Order threshold and Second order gain */ |
| writel_relaxed(0xeb, phy->mmio + QSERDES_RX_CDR_CONTROL); |
| writel_relaxed(0x5a, phy->mmio + QSERDES_RX_CDR_CONTROL2); |
| /* Config required only on reference boards with shared PHY */ |
| if (phy->phy_sel) |
| writel_relaxed(0x1a, phy->mmio + |
| QSERDES_RX_CDR_CONTROL_QUARTER); |
| |
| /* TX configurations */ |
| /* TX config differences between shared & dedicated PHY */ |
| if (phy->phy_sel) |
| writel_relaxed(0x1f, phy->mmio + QSERDES_TX_TX_DRV_LVL); |
| else |
| writel_relaxed(0x16, phy->mmio + QSERDES_TX_TX_DRV_LVL); |
| writel_relaxed(0x00, phy->mmio + QSERDES_TX_BIST_MODE_LANENO); |
| writel_relaxed(0x30, phy->mmio + QSERDES_TX_TX_EMP_POST1_LVL); |
| |
| /* RX config differences between shared & dedicated PHY */ |
| if (!phy->phy_sel) { |
| writel_relaxed(0x44, phy->mmio + QSERDES_RX_RX_EQ_GAIN12); |
| writel_relaxed(0x01, phy->mmio + SATA_PHY_CDR_CTRL1); |
| } |
| |
| /* SSC Configurations and Serdes start */ |
| writel_relaxed(0x00, phy->mmio + QSERDES_COM_SSC_EN_CENTER); |
| writel_relaxed(0x31, phy->mmio + QSERDES_COM_SSC_PER1); |
| writel_relaxed(0x01, phy->mmio + QSERDES_COM_SSC_PER2); |
| writel_relaxed(0x01, phy->mmio + QSERDES_COM_SSC_ADJ_PER1); |
| writel_relaxed(0x00, phy->mmio + QSERDES_COM_SSC_ADJ_PER2); |
| writel_relaxed(0x3f, phy->mmio + QSERDES_COM_SSC_STEP_SIZE1); |
| writel_relaxed(0x05, phy->mmio + QSERDES_COM_SSC_STEP_SIZE2); |
| writel_relaxed(0x01, phy->mmio + QSERDES_COM_SSC_EN_CENTER); |
| |
| /* |
| * Flush all delayed writes before sleeping to ensure that PHY |
| * configuration is applied. |
| */ |
| mb(); |
| |
| /* Sleep for 1ms before starting serdes */ |
| usleep(1000); |
| |
| /* Start serdes */ |
| writel_relaxed(0x01, phy->mmio + SATA_PHY_SERDES_START); |
| |
| /* |
| * Read RESETSM status until SERDES is ready, |
| * timeout after 1 sec |
| */ |
| err = readl_poll_timeout(phy->mmio + QSERDES_COM_RESET_SM, reg, |
| (reg & (1 << 5)), 100, 1000000); |
| if (err) { |
| dev_err(dev, "%s: poll timeout QSERDES_COM_RESET_SM, status: 0x%x\n", |
| __func__, readl_relaxed(phy->mmio + |
| QSERDES_COM_RESET_SM)); |
| goto out; |
| } |
| |
| /* RX configurations */ |
| writel_relaxed(0x5f, phy->mmio + SATA_PHY_LANE_CTRL1); |
| writel_relaxed(0x43, phy->mmio + SATA_PHY_ALIGNP); |
| |
| dev_dbg(dev, "SATA PHY powered up in functional mode\n"); |
| out: |
| /* power down PHY in case of failure */ |
| if (err) |
| writel_relaxed(0x0, phy->mmio + SATA_PHY_POW_DWN_CTRL0); |
| |
| return err; |
| } |
| |
| static int msm_sata_phy_power_off(struct phy *generic_phy) |
| { |
| struct msm_sata_phy *phy = phy_get_drvdata(generic_phy); |
| |
| writel_relaxed(0x0, phy->mmio + SATA_PHY_POW_DWN_CTRL0); |
| /* |
| * Ensure that the PHY is power down before gating power. |
| * It is possible that PHY regulators might be turned on if |
| * other PHY's share the same regulators. |
| */ |
| mb(); |
| |
| msm_sata_disable_phy_rxoob_clk(phy); |
| msm_sata_disable_phy_ref_clk(phy); |
| |
| msm_sata_phy_disable_vreg(phy, &phy->vdda_pll); |
| msm_sata_phy_disable_vreg(phy, &phy->vdda_phy); |
| phy->is_powered_on = false; |
| |
| return 0; |
| } |
| |
| static int msm_sata_phy_power_on(struct phy *generic_phy) |
| { |
| int err; |
| struct msm_sata_phy *phy = phy_get_drvdata(generic_phy); |
| |
| err = msm_sata_phy_enable_vreg(phy, &phy->vdda_phy); |
| if (err) |
| goto out; |
| |
| /* vdda_pll also enables ref clock LDOs so enable it first */ |
| err = msm_sata_phy_enable_vreg(phy, &phy->vdda_pll); |
| if (err) |
| goto out_disable_phy; |
| |
| err = msm_sata_enable_phy_ref_clk(phy); |
| if (err) |
| goto out_disable_pll; |
| |
| err = msm_sata_enable_phy_rxoob_clk(phy); |
| if (err) |
| goto out_disable_ref; |
| |
| err = msm_sata_phy_power_up(phy); |
| if (err) |
| goto out_disable_rxoob; |
| |
| phy->is_powered_on = true; |
| goto out; |
| |
| out_disable_rxoob: |
| msm_sata_disable_phy_rxoob_clk(phy); |
| out_disable_ref: |
| msm_sata_disable_phy_ref_clk(phy); |
| out_disable_pll: |
| msm_sata_phy_disable_vreg(phy, &phy->vdda_pll); |
| out_disable_phy: |
| msm_sata_phy_disable_vreg(phy, &phy->vdda_phy); |
| out: |
| return err; |
| } |
| |
| static int msm_sata_phy_init(struct phy *generic_phy) |
| { |
| int err; |
| struct msm_sata_phy *phy = phy_get_drvdata(generic_phy); |
| struct device *dev = phy->dev; |
| |
| err = msm_sata_phy_clk_get(dev, "ref_clk_src", &phy->ref_clk_src); |
| if (err) |
| goto out; |
| |
| err = msm_sata_phy_clk_get(dev, "ref_clk_parent", &phy->ref_clk_parent); |
| if (err) |
| goto out; |
| |
| err = msm_sata_phy_clk_get(dev, "ref_clk", &phy->ref_clk); |
| if (err) |
| goto out; |
| |
| err = msm_sata_phy_clk_get(dev, "rxoob_clk", &phy->rxoob_clk); |
| if (err) |
| goto out; |
| |
| err = msm_sata_phy_init_vreg(dev, &phy->vdda_pll, "vdda-pll"); |
| if (err) |
| goto out; |
| |
| err = msm_sata_phy_init_vreg(dev, &phy->vdda_phy, "vdda-phy"); |
| if (err) |
| goto out; |
| out: |
| return err; |
| } |
| |
| static int msm_sata_phy_exit(struct phy *generic_phy) |
| { |
| struct msm_sata_phy *phy = phy_get_drvdata(generic_phy); |
| |
| if (phy->is_powered_on) |
| msm_sata_phy_power_off(generic_phy); |
| |
| return 0; |
| } |
| |
| static struct phy_ops msm_sata_phy_ops = { |
| .init = msm_sata_phy_init, |
| .exit = msm_sata_phy_exit, |
| .power_on = msm_sata_phy_power_on, |
| .power_off = msm_sata_phy_power_off, |
| .owner = THIS_MODULE, |
| }; |
| |
| static int msm_sata_phy_probe(struct platform_device *pdev) |
| { |
| int err = 0; |
| struct msm_sata_phy *phy; |
| struct device *dev = &pdev->dev; |
| struct resource *res; |
| struct phy_provider *phy_provider; |
| struct phy *generic_phy; |
| |
| phy = devm_kzalloc(dev, sizeof(*phy), GFP_KERNEL); |
| if (!phy) { |
| err = -ENOMEM; |
| dev_err(dev, "%s: failed to allocate phy\n", __func__); |
| goto out; |
| } |
| |
| res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "phy_sel"); |
| phy->phy_sel = devm_ioremap_resource(dev, res); |
| if (IS_ERR(phy->phy_sel)) { |
| err = PTR_ERR(phy->phy_sel); |
| /* phy_sel resource is optional */ |
| phy->phy_sel = 0; |
| dev_dbg(dev, "%s: phy select resource get failed %d\n", |
| __func__, err); |
| } |
| |
| res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "phy_mem"); |
| phy->mmio = devm_ioremap_resource(dev, res); |
| if (IS_ERR(phy->mmio)) { |
| err = PTR_ERR(phy->mmio); |
| dev_err(dev, "%s: phy mmio get resource failed %d\n", |
| __func__, err); |
| goto out; |
| } |
| |
| phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); |
| if (IS_ERR(phy_provider)) { |
| err = PTR_ERR(phy_provider); |
| dev_err(dev, "%s: failed to register phy %d\n", __func__, err); |
| goto out; |
| } |
| |
| generic_phy = devm_phy_create(dev, &msm_sata_phy_ops, NULL); |
| if (IS_ERR(generic_phy)) { |
| err = PTR_ERR(generic_phy); |
| dev_err(dev, "%s: failed to create phy %d\n", __func__, err); |
| goto out; |
| } |
| |
| phy->dev = dev; |
| phy_set_drvdata(generic_phy, phy); |
| |
| return 0; |
| out: |
| return err; |
| } |
| |
| static const struct of_device_id msm_sata_phy_of_match[] = { |
| { .compatible = "qcom,sataphy" }, |
| { }, |
| }; |
| MODULE_DEVICE_TABLE(of, msm_sata_phy_of_match); |
| |
| static struct platform_driver msm_sata_phy_driver = { |
| .probe = msm_sata_phy_probe, |
| .driver = { |
| .name = "msm-sata-phy", |
| .owner = THIS_MODULE, |
| .of_match_table = msm_sata_phy_of_match, |
| } |
| }; |
| module_platform_driver(msm_sata_phy_driver); |
| |
| MODULE_DESCRIPTION("MSM 6Gbps SATA PHY driver"); |
| MODULE_LICENSE("GPL v2"); |