1 /*
2  * Copyright (c) 2014 Linaro Ltd.
3  * Copyright (c) 2014 Hisilicon Limited.
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  */
10 
11 #include <linux/delay.h>
12 #include <linux/io.h>
13 #include <linux/mfd/syscon.h>
14 #include <linux/module.h>
15 #include <linux/phy/phy.h>
16 #include <linux/platform_device.h>
17 #include <linux/regmap.h>
18 
19 #define SATA_PHY0_CTLL		0xa0
20 #define MPLL_MULTIPLIER_SHIFT	1
21 #define MPLL_MULTIPLIER_MASK	0xfe
22 #define MPLL_MULTIPLIER_50M	0x3c
23 #define MPLL_MULTIPLIER_100M	0x1e
24 #define PHY_RESET		BIT(0)
25 #define REF_SSP_EN		BIT(9)
26 #define SSC_EN			BIT(10)
27 #define REF_USE_PAD		BIT(23)
28 
29 #define SATA_PORT_PHYCTL	0x174
30 #define SPEED_MODE_MASK		0x6f0000
31 #define HALF_RATE_SHIFT		16
32 #define PHY_CONFIG_SHIFT	18
33 #define GEN2_EN_SHIFT		21
34 #define SPEED_CTRL		BIT(20)
35 
36 #define SATA_PORT_PHYCTL1	0x148
37 #define AMPLITUDE_MASK		0x3ffffe
38 #define AMPLITUDE_GEN3		0x68
39 #define AMPLITUDE_GEN3_SHIFT	15
40 #define AMPLITUDE_GEN2		0x56
41 #define AMPLITUDE_GEN2_SHIFT	8
42 #define AMPLITUDE_GEN1		0x56
43 #define AMPLITUDE_GEN1_SHIFT	1
44 
45 #define SATA_PORT_PHYCTL2	0x14c
46 #define PREEMPH_MASK		0x3ffff
47 #define PREEMPH_GEN3		0x20
48 #define PREEMPH_GEN3_SHIFT	12
49 #define PREEMPH_GEN2		0x15
50 #define PREEMPH_GEN2_SHIFT	6
51 #define PREEMPH_GEN1		0x5
52 #define PREEMPH_GEN1_SHIFT	0
53 
54 struct hix5hd2_priv {
55 	void __iomem	*base;
56 	struct regmap	*peri_ctrl;
57 };
58 
59 enum phy_speed_mode {
60 	SPEED_MODE_GEN1 = 0,
61 	SPEED_MODE_GEN2 = 1,
62 	SPEED_MODE_GEN3 = 2,
63 };
64 
65 static int hix5hd2_sata_phy_init(struct phy *phy)
66 {
67 	struct hix5hd2_priv *priv = phy_get_drvdata(phy);
68 	u32 val, data[2];
69 	int ret;
70 
71 	if (priv->peri_ctrl) {
72 		ret = of_property_read_u32_array(phy->dev.of_node,
73 						 "hisilicon,power-reg",
74 						 &data[0], 2);
75 		if (ret) {
76 			dev_err(&phy->dev, "Fail read hisilicon,power-reg\n");
77 			return ret;
78 		}
79 
80 		regmap_update_bits(priv->peri_ctrl, data[0],
81 				   BIT(data[1]), BIT(data[1]));
82 	}
83 
84 	/* reset phy */
85 	val = readl_relaxed(priv->base + SATA_PHY0_CTLL);
86 	val &= ~(MPLL_MULTIPLIER_MASK | REF_USE_PAD);
87 	val |= MPLL_MULTIPLIER_50M << MPLL_MULTIPLIER_SHIFT |
88 	       REF_SSP_EN | PHY_RESET;
89 	writel_relaxed(val, priv->base + SATA_PHY0_CTLL);
90 	msleep(20);
91 	val &= ~PHY_RESET;
92 	writel_relaxed(val, priv->base + SATA_PHY0_CTLL);
93 
94 	val = readl_relaxed(priv->base + SATA_PORT_PHYCTL1);
95 	val &= ~AMPLITUDE_MASK;
96 	val |= AMPLITUDE_GEN3 << AMPLITUDE_GEN3_SHIFT |
97 	       AMPLITUDE_GEN2 << AMPLITUDE_GEN2_SHIFT |
98 	       AMPLITUDE_GEN1 << AMPLITUDE_GEN1_SHIFT;
99 	writel_relaxed(val, priv->base + SATA_PORT_PHYCTL1);
100 
101 	val = readl_relaxed(priv->base + SATA_PORT_PHYCTL2);
102 	val &= ~PREEMPH_MASK;
103 	val |= PREEMPH_GEN3 << PREEMPH_GEN3_SHIFT |
104 	       PREEMPH_GEN2 << PREEMPH_GEN2_SHIFT |
105 	       PREEMPH_GEN1 << PREEMPH_GEN1_SHIFT;
106 	writel_relaxed(val, priv->base + SATA_PORT_PHYCTL2);
107 
108 	/* ensure PHYCTRL setting takes effect */
109 	val = readl_relaxed(priv->base + SATA_PORT_PHYCTL);
110 	val &= ~SPEED_MODE_MASK;
111 	val |= SPEED_MODE_GEN1 << HALF_RATE_SHIFT |
112 	       SPEED_MODE_GEN1 << PHY_CONFIG_SHIFT |
113 	       SPEED_MODE_GEN1 << GEN2_EN_SHIFT | SPEED_CTRL;
114 	writel_relaxed(val, priv->base + SATA_PORT_PHYCTL);
115 
116 	msleep(20);
117 	val &= ~SPEED_MODE_MASK;
118 	val |= SPEED_MODE_GEN3 << HALF_RATE_SHIFT |
119 	       SPEED_MODE_GEN3 << PHY_CONFIG_SHIFT |
120 	       SPEED_MODE_GEN3 << GEN2_EN_SHIFT | SPEED_CTRL;
121 	writel_relaxed(val, priv->base + SATA_PORT_PHYCTL);
122 
123 	val &= ~(SPEED_MODE_MASK | SPEED_CTRL);
124 	val |= SPEED_MODE_GEN2 << HALF_RATE_SHIFT |
125 	       SPEED_MODE_GEN2 << PHY_CONFIG_SHIFT |
126 	       SPEED_MODE_GEN2 << GEN2_EN_SHIFT;
127 	writel_relaxed(val, priv->base + SATA_PORT_PHYCTL);
128 
129 	return 0;
130 }
131 
132 static const struct phy_ops hix5hd2_sata_phy_ops = {
133 	.init		= hix5hd2_sata_phy_init,
134 	.owner		= THIS_MODULE,
135 };
136 
137 static int hix5hd2_sata_phy_probe(struct platform_device *pdev)
138 {
139 	struct phy_provider *phy_provider;
140 	struct device *dev = &pdev->dev;
141 	struct resource *res;
142 	struct phy *phy;
143 	struct hix5hd2_priv *priv;
144 
145 	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
146 	if (!priv)
147 		return -ENOMEM;
148 
149 	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
150 	if (!res)
151 		return -EINVAL;
152 
153 	priv->base = devm_ioremap(dev, res->start, resource_size(res));
154 	if (!priv->base)
155 		return -ENOMEM;
156 
157 	priv->peri_ctrl = syscon_regmap_lookup_by_phandle(dev->of_node,
158 					"hisilicon,peripheral-syscon");
159 	if (IS_ERR(priv->peri_ctrl))
160 		priv->peri_ctrl = NULL;
161 
162 	phy = devm_phy_create(dev, NULL, &hix5hd2_sata_phy_ops);
163 	if (IS_ERR(phy)) {
164 		dev_err(dev, "failed to create PHY\n");
165 		return PTR_ERR(phy);
166 	}
167 
168 	phy_set_drvdata(phy, priv);
169 	phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate);
170 	return PTR_ERR_OR_ZERO(phy_provider);
171 }
172 
173 static const struct of_device_id hix5hd2_sata_phy_of_match[] = {
174 	{.compatible = "hisilicon,hix5hd2-sata-phy",},
175 	{ },
176 };
177 MODULE_DEVICE_TABLE(of, hix5hd2_sata_phy_of_match);
178 
179 static struct platform_driver hix5hd2_sata_phy_driver = {
180 	.probe	= hix5hd2_sata_phy_probe,
181 	.driver = {
182 		.name	= "hix5hd2-sata-phy",
183 		.of_match_table	= hix5hd2_sata_phy_of_match,
184 	}
185 };
186 module_platform_driver(hix5hd2_sata_phy_driver);
187 
188 MODULE_AUTHOR("Jiancheng Xue <xuejiancheng@huawei.com>");
189 MODULE_DESCRIPTION("HISILICON HIX5HD2 SATA PHY driver");
190 MODULE_ALIAS("platform:hix5hd2-sata-phy");
191 MODULE_LICENSE("GPL v2");
192