Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

phy: qcom: Add driver for QCOM IPQ806x SATA PHY

Add a PHY driver for uses with AHCI based SATA controller driver on the
IPQ806x family of SoCs.

Signed-off-by: Kumar Gala <galak@codeaurora.org>
Signed-off-by: Kishon Vijay Abraham I <kishon@ti.com>

authored by

Kumar Gala and committed by
Kishon Vijay Abraham I
4f6160d4 175f02eb

+219
+7
drivers/phy/Kconfig
··· 191 191 depends on OF 192 192 select GENERIC_PHY 193 193 194 + config PHY_QCOM_IPQ806X_SATA 195 + tristate "Qualcomm IPQ806x SATA SerDes/PHY driver" 196 + depends on ARCH_QCOM 197 + depends on HAS_IOMEM 198 + depends on OF 199 + select GENERIC_PHY 200 + 194 201 endmenu
+1
drivers/phy/Makefile
··· 23 23 obj-$(CONFIG_PHY_EXYNOS5_USBDRD) += phy-exynos5-usbdrd.o 24 24 obj-$(CONFIG_PHY_XGENE) += phy-xgene.o 25 25 obj-$(CONFIG_PHY_QCOM_APQ8064_SATA) += phy-qcom-apq8064-sata.o 26 + obj-$(CONFIG_PHY_QCOM_IPQ806X_SATA) += phy-qcom-ipq806x-sata.o
+211
drivers/phy/phy-qcom-ipq806x-sata.c
··· 1 + /* 2 + * Copyright (c) 2014, The Linux Foundation. All rights reserved. 3 + * 4 + * This program is free software; you can redistribute it and/or modify 5 + * it under the terms of the GNU General Public License version 2 and 6 + * only version 2 as published by the Free Software Foundation. 7 + * 8 + * This program is distributed in the hope that it will be useful, 9 + * but WITHOUT ANY WARRANTY; without even the implied warranty of 10 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 + * GNU General Public License for more details. 12 + */ 13 + 14 + #include <linux/io.h> 15 + #include <linux/kernel.h> 16 + #include <linux/module.h> 17 + #include <linux/of.h> 18 + #include <linux/of_address.h> 19 + #include <linux/time.h> 20 + #include <linux/delay.h> 21 + #include <linux/clk.h> 22 + #include <linux/slab.h> 23 + #include <linux/platform_device.h> 24 + #include <linux/phy/phy.h> 25 + 26 + struct qcom_ipq806x_sata_phy { 27 + void __iomem *mmio; 28 + struct clk *cfg_clk; 29 + struct device *dev; 30 + }; 31 + 32 + #define __set(v, a, b) (((v) << (b)) & GENMASK(a, b)) 33 + 34 + #define SATA_PHY_P0_PARAM0 0x200 35 + #define SATA_PHY_P0_PARAM0_P0_TX_PREEMPH_GEN3(x) __set(x, 17, 12) 36 + #define SATA_PHY_P0_PARAM0_P0_TX_PREEMPH_GEN3_MASK GENMASK(17, 12) 37 + #define SATA_PHY_P0_PARAM0_P0_TX_PREEMPH_GEN2(x) __set(x, 11, 6) 38 + #define SATA_PHY_P0_PARAM0_P0_TX_PREEMPH_GEN2_MASK GENMASK(11, 6) 39 + #define SATA_PHY_P0_PARAM0_P0_TX_PREEMPH_GEN1(x) __set(x, 5, 0) 40 + #define SATA_PHY_P0_PARAM0_P0_TX_PREEMPH_GEN1_MASK GENMASK(5, 0) 41 + 42 + #define SATA_PHY_P0_PARAM1 0x204 43 + #define SATA_PHY_P0_PARAM1_RESERVED_BITS31_21(x) __set(x, 31, 21) 44 + #define SATA_PHY_P0_PARAM1_P0_TX_AMPLITUDE_GEN3(x) __set(x, 20, 14) 45 + #define SATA_PHY_P0_PARAM1_P0_TX_AMPLITUDE_GEN3_MASK GENMASK(20, 14) 46 + #define SATA_PHY_P0_PARAM1_P0_TX_AMPLITUDE_GEN2(x) __set(x, 13, 7) 47 + #define SATA_PHY_P0_PARAM1_P0_TX_AMPLITUDE_GEN2_MASK GENMASK(13, 7) 48 + #define SATA_PHY_P0_PARAM1_P0_TX_AMPLITUDE_GEN1(x) __set(x, 6, 0) 49 + #define SATA_PHY_P0_PARAM1_P0_TX_AMPLITUDE_GEN1_MASK GENMASK(6, 0) 50 + 51 + #define SATA_PHY_P0_PARAM2 0x208 52 + #define SATA_PHY_P0_PARAM2_RX_EQ(x) __set(x, 20, 18) 53 + #define SATA_PHY_P0_PARAM2_RX_EQ_MASK GENMASK(20, 18) 54 + 55 + #define SATA_PHY_P0_PARAM3 0x20C 56 + #define SATA_PHY_SSC_EN 0x8 57 + #define SATA_PHY_P0_PARAM4 0x210 58 + #define SATA_PHY_REF_SSP_EN 0x2 59 + #define SATA_PHY_RESET 0x1 60 + 61 + static int qcom_ipq806x_sata_phy_init(struct phy *generic_phy) 62 + { 63 + struct qcom_ipq806x_sata_phy *phy = phy_get_drvdata(generic_phy); 64 + u32 reg; 65 + 66 + /* Setting SSC_EN to 1 */ 67 + reg = readl_relaxed(phy->mmio + SATA_PHY_P0_PARAM3); 68 + reg = reg | SATA_PHY_SSC_EN; 69 + writel_relaxed(reg, phy->mmio + SATA_PHY_P0_PARAM3); 70 + 71 + reg = readl_relaxed(phy->mmio + SATA_PHY_P0_PARAM0) & 72 + ~(SATA_PHY_P0_PARAM0_P0_TX_PREEMPH_GEN3_MASK | 73 + SATA_PHY_P0_PARAM0_P0_TX_PREEMPH_GEN2_MASK | 74 + SATA_PHY_P0_PARAM0_P0_TX_PREEMPH_GEN1_MASK); 75 + reg |= SATA_PHY_P0_PARAM0_P0_TX_PREEMPH_GEN3(0xf); 76 + writel_relaxed(reg, phy->mmio + SATA_PHY_P0_PARAM0); 77 + 78 + reg = readl_relaxed(phy->mmio + SATA_PHY_P0_PARAM1) & 79 + ~(SATA_PHY_P0_PARAM1_P0_TX_AMPLITUDE_GEN3_MASK | 80 + SATA_PHY_P0_PARAM1_P0_TX_AMPLITUDE_GEN2_MASK | 81 + SATA_PHY_P0_PARAM1_P0_TX_AMPLITUDE_GEN1_MASK); 82 + reg |= SATA_PHY_P0_PARAM1_P0_TX_AMPLITUDE_GEN3(0x55) | 83 + SATA_PHY_P0_PARAM1_P0_TX_AMPLITUDE_GEN2(0x55) | 84 + SATA_PHY_P0_PARAM1_P0_TX_AMPLITUDE_GEN1(0x55); 85 + writel_relaxed(reg, phy->mmio + SATA_PHY_P0_PARAM1); 86 + 87 + reg = readl_relaxed(phy->mmio + SATA_PHY_P0_PARAM2) & 88 + ~SATA_PHY_P0_PARAM2_RX_EQ_MASK; 89 + reg |= SATA_PHY_P0_PARAM2_RX_EQ(0x3); 90 + writel_relaxed(reg, phy->mmio + SATA_PHY_P0_PARAM2); 91 + 92 + /* Setting PHY_RESET to 1 */ 93 + reg = readl_relaxed(phy->mmio + SATA_PHY_P0_PARAM4); 94 + reg = reg | SATA_PHY_RESET; 95 + writel_relaxed(reg, phy->mmio + SATA_PHY_P0_PARAM4); 96 + 97 + /* Setting REF_SSP_EN to 1 */ 98 + reg = readl_relaxed(phy->mmio + SATA_PHY_P0_PARAM4); 99 + reg = reg | SATA_PHY_REF_SSP_EN | SATA_PHY_RESET; 100 + writel_relaxed(reg, phy->mmio + SATA_PHY_P0_PARAM4); 101 + 102 + /* make sure all changes complete before we let the PHY out of reset */ 103 + mb(); 104 + 105 + /* sleep for max. 50us more to combine processor wakeups */ 106 + usleep_range(20, 20 + 50); 107 + 108 + /* Clearing PHY_RESET to 0 */ 109 + reg = readl_relaxed(phy->mmio + SATA_PHY_P0_PARAM4); 110 + reg = reg & ~SATA_PHY_RESET; 111 + writel_relaxed(reg, phy->mmio + SATA_PHY_P0_PARAM4); 112 + 113 + return 0; 114 + } 115 + 116 + static int qcom_ipq806x_sata_phy_exit(struct phy *generic_phy) 117 + { 118 + struct qcom_ipq806x_sata_phy *phy = phy_get_drvdata(generic_phy); 119 + u32 reg; 120 + 121 + /* Setting PHY_RESET to 1 */ 122 + reg = readl_relaxed(phy->mmio + SATA_PHY_P0_PARAM4); 123 + reg = reg | SATA_PHY_RESET; 124 + writel_relaxed(reg, phy->mmio + SATA_PHY_P0_PARAM4); 125 + 126 + return 0; 127 + } 128 + 129 + static struct phy_ops qcom_ipq806x_sata_phy_ops = { 130 + .init = qcom_ipq806x_sata_phy_init, 131 + .exit = qcom_ipq806x_sata_phy_exit, 132 + .owner = THIS_MODULE, 133 + }; 134 + 135 + static int qcom_ipq806x_sata_phy_probe(struct platform_device *pdev) 136 + { 137 + struct qcom_ipq806x_sata_phy *phy; 138 + struct device *dev = &pdev->dev; 139 + struct resource *res; 140 + struct phy_provider *phy_provider; 141 + struct phy *generic_phy; 142 + int ret; 143 + 144 + phy = devm_kzalloc(dev, sizeof(*phy), GFP_KERNEL); 145 + if (!phy) 146 + return -ENOMEM; 147 + 148 + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); 149 + phy->mmio = devm_ioremap_resource(dev, res); 150 + if (IS_ERR(phy->mmio)) 151 + return PTR_ERR(phy->mmio); 152 + 153 + generic_phy = devm_phy_create(dev, NULL, &qcom_ipq806x_sata_phy_ops, 154 + NULL); 155 + if (IS_ERR(generic_phy)) { 156 + dev_err(dev, "%s: failed to create phy\n", __func__); 157 + return PTR_ERR(generic_phy); 158 + } 159 + 160 + phy->dev = dev; 161 + phy_set_drvdata(generic_phy, phy); 162 + platform_set_drvdata(pdev, phy); 163 + 164 + phy->cfg_clk = devm_clk_get(dev, "cfg"); 165 + if (IS_ERR(phy->cfg_clk)) { 166 + dev_err(dev, "Failed to get sata cfg clock\n"); 167 + return PTR_ERR(phy->cfg_clk); 168 + } 169 + 170 + ret = clk_prepare_enable(phy->cfg_clk); 171 + if (ret) 172 + return ret; 173 + 174 + phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); 175 + if (IS_ERR(phy_provider)) { 176 + clk_disable_unprepare(phy->cfg_clk); 177 + dev_err(dev, "%s: failed to register phy\n", __func__); 178 + return PTR_ERR(phy_provider); 179 + } 180 + 181 + return 0; 182 + } 183 + 184 + static int qcom_ipq806x_sata_phy_remove(struct platform_device *pdev) 185 + { 186 + struct qcom_ipq806x_sata_phy *phy = platform_get_drvdata(pdev); 187 + 188 + clk_disable_unprepare(phy->cfg_clk); 189 + 190 + return 0; 191 + } 192 + 193 + static const struct of_device_id qcom_ipq806x_sata_phy_of_match[] = { 194 + { .compatible = "qcom,ipq806x-sata-phy" }, 195 + { }, 196 + }; 197 + MODULE_DEVICE_TABLE(of, qcom_ipq806x_sata_phy_of_match); 198 + 199 + static struct platform_driver qcom_ipq806x_sata_phy_driver = { 200 + .probe = qcom_ipq806x_sata_phy_probe, 201 + .remove = qcom_ipq806x_sata_phy_remove, 202 + .driver = { 203 + .name = "qcom-ipq806x-sata-phy", 204 + .owner = THIS_MODULE, 205 + .of_match_table = qcom_ipq806x_sata_phy_of_match, 206 + } 207 + }; 208 + module_platform_driver(qcom_ipq806x_sata_phy_driver); 209 + 210 + MODULE_DESCRIPTION("QCOM IPQ806x SATA PHY driver"); 211 + MODULE_LICENSE("GPL v2");