]>
Commit | Line | Data |
---|---|---|
9e54eae2 RZ |
1 | /* |
2 | * Freescale IMX AHCI SATA platform driver | |
3 | * Copyright 2013 Freescale Semiconductor, Inc. | |
4 | * | |
5 | * based on the AHCI SATA platform driver by Jeff Garzik and Anton Vorontsov | |
6 | * | |
7 | * This program is free software; you can redistribute it and/or modify it | |
8 | * under the terms and conditions of the GNU General Public License, | |
9 | * version 2, as published by the Free Software Foundation. | |
10 | * | |
11 | * This program is distributed in the hope it will be useful, but WITHOUT | |
12 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | |
13 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for | |
14 | * more details. | |
15 | * | |
16 | * You should have received a copy of the GNU General Public License along with | |
17 | * this program. If not, see <http://www.gnu.org/licenses/>. | |
18 | */ | |
19 | ||
20 | #include <linux/kernel.h> | |
21 | #include <linux/module.h> | |
22 | #include <linux/platform_device.h> | |
23 | #include <linux/regmap.h> | |
24 | #include <linux/ahci_platform.h> | |
25 | #include <linux/of_device.h> | |
26 | #include <linux/mfd/syscon.h> | |
27 | #include <linux/mfd/syscon/imx6q-iomuxc-gpr.h> | |
28 | #include "ahci.h" | |
29 | ||
30 | enum { | |
31 | HOST_TIMER1MS = 0xe0, /* Timer 1-ms */ | |
32 | }; | |
33 | ||
34 | struct imx_ahci_priv { | |
35 | struct platform_device *ahci_pdev; | |
36 | struct clk *sata_ref_clk; | |
37 | struct clk *ahb_clk; | |
38 | struct regmap *gpr; | |
39 | }; | |
40 | ||
41 | static int imx6q_sata_init(struct device *dev, void __iomem *mmio) | |
42 | { | |
43 | int ret = 0; | |
44 | unsigned int reg_val; | |
45 | struct imx_ahci_priv *imxpriv = dev_get_drvdata(dev->parent); | |
46 | ||
47 | imxpriv->gpr = | |
48 | syscon_regmap_lookup_by_compatible("fsl,imx6q-iomuxc-gpr"); | |
49 | if (IS_ERR(imxpriv->gpr)) { | |
50 | dev_err(dev, "failed to find fsl,imx6q-iomux-gpr regmap\n"); | |
51 | return PTR_ERR(imxpriv->gpr); | |
52 | } | |
53 | ||
54 | ret = clk_prepare_enable(imxpriv->sata_ref_clk); | |
55 | if (ret < 0) { | |
56 | dev_err(dev, "prepare-enable sata_ref clock err:%d\n", ret); | |
57 | return ret; | |
58 | } | |
59 | ||
60 | /* | |
61 | * set PHY Paremeters, two steps to configure the GPR13, | |
62 | * one write for rest of parameters, mask of first write | |
63 | * is 0x07fffffd, and the other one write for setting | |
64 | * the mpll_clk_en. | |
65 | */ | |
66 | regmap_update_bits(imxpriv->gpr, 0x34, IMX6Q_GPR13_SATA_RX_EQ_VAL_MASK | |
67 | | IMX6Q_GPR13_SATA_RX_LOS_LVL_MASK | |
68 | | IMX6Q_GPR13_SATA_RX_DPLL_MODE_MASK | |
69 | | IMX6Q_GPR13_SATA_SPD_MODE_MASK | |
70 | | IMX6Q_GPR13_SATA_MPLL_SS_EN | |
71 | | IMX6Q_GPR13_SATA_TX_ATTEN_MASK | |
72 | | IMX6Q_GPR13_SATA_TX_BOOST_MASK | |
73 | | IMX6Q_GPR13_SATA_TX_LVL_MASK | |
74 | | IMX6Q_GPR13_SATA_TX_EDGE_RATE | |
75 | , IMX6Q_GPR13_SATA_RX_EQ_VAL_3_0_DB | |
76 | | IMX6Q_GPR13_SATA_RX_LOS_LVL_SATA2M | |
77 | | IMX6Q_GPR13_SATA_RX_DPLL_MODE_2P_4F | |
78 | | IMX6Q_GPR13_SATA_SPD_MODE_3P0G | |
79 | | IMX6Q_GPR13_SATA_MPLL_SS_EN | |
80 | | IMX6Q_GPR13_SATA_TX_ATTEN_9_16 | |
81 | | IMX6Q_GPR13_SATA_TX_BOOST_3_33_DB | |
82 | | IMX6Q_GPR13_SATA_TX_LVL_1_025_V); | |
83 | regmap_update_bits(imxpriv->gpr, 0x34, IMX6Q_GPR13_SATA_MPLL_CLK_EN, | |
84 | IMX6Q_GPR13_SATA_MPLL_CLK_EN); | |
85 | usleep_range(100, 200); | |
86 | ||
87 | /* | |
88 | * Configure the HWINIT bits of the HOST_CAP and HOST_PORTS_IMPL, | |
89 | * and IP vendor specific register HOST_TIMER1MS. | |
90 | * Configure CAP_SSS (support stagered spin up). | |
91 | * Implement the port0. | |
92 | * Get the ahb clock rate, and configure the TIMER1MS register. | |
93 | */ | |
94 | reg_val = readl(mmio + HOST_CAP); | |
95 | if (!(reg_val & HOST_CAP_SSS)) { | |
96 | reg_val |= HOST_CAP_SSS; | |
97 | writel(reg_val, mmio + HOST_CAP); | |
98 | } | |
99 | reg_val = readl(mmio + HOST_PORTS_IMPL); | |
100 | if (!(reg_val & 0x1)) { | |
101 | reg_val |= 0x1; | |
102 | writel(reg_val, mmio + HOST_PORTS_IMPL); | |
103 | } | |
104 | ||
105 | reg_val = clk_get_rate(imxpriv->ahb_clk) / 1000; | |
106 | writel(reg_val, mmio + HOST_TIMER1MS); | |
107 | ||
108 | return 0; | |
109 | } | |
110 | ||
111 | static void imx6q_sata_exit(struct device *dev) | |
112 | { | |
113 | struct imx_ahci_priv *imxpriv = dev_get_drvdata(dev->parent); | |
114 | ||
115 | regmap_update_bits(imxpriv->gpr, 0x34, IMX6Q_GPR13_SATA_MPLL_CLK_EN, | |
116 | !IMX6Q_GPR13_SATA_MPLL_CLK_EN); | |
117 | clk_disable_unprepare(imxpriv->sata_ref_clk); | |
118 | } | |
119 | ||
120 | static struct ahci_platform_data imx6q_sata_pdata = { | |
121 | .init = imx6q_sata_init, | |
122 | .exit = imx6q_sata_exit, | |
123 | }; | |
124 | ||
125 | static const struct of_device_id imx_ahci_of_match[] = { | |
126 | { .compatible = "fsl,imx6q-ahci", .data = &imx6q_sata_pdata}, | |
127 | {}, | |
128 | }; | |
129 | MODULE_DEVICE_TABLE(of, imx_ahci_of_match); | |
130 | ||
131 | static int imx_ahci_probe(struct platform_device *pdev) | |
132 | { | |
133 | struct device *dev = &pdev->dev; | |
134 | struct resource *mem, *irq, res[2]; | |
135 | const struct of_device_id *of_id; | |
136 | const struct ahci_platform_data *pdata = NULL; | |
137 | struct imx_ahci_priv *imxpriv; | |
138 | struct device *ahci_dev; | |
139 | struct platform_device *ahci_pdev; | |
140 | int ret; | |
141 | ||
142 | imxpriv = devm_kzalloc(dev, sizeof(*imxpriv), GFP_KERNEL); | |
143 | if (!imxpriv) { | |
144 | dev_err(dev, "can't alloc ahci_host_priv\n"); | |
145 | return -ENOMEM; | |
146 | } | |
147 | ||
148 | ahci_pdev = platform_device_alloc("ahci", -1); | |
149 | if (!ahci_pdev) | |
150 | return -ENODEV; | |
151 | ||
152 | ahci_dev = &ahci_pdev->dev; | |
153 | ahci_dev->parent = dev; | |
154 | ||
155 | imxpriv->ahb_clk = devm_clk_get(dev, "ahb"); | |
156 | if (IS_ERR(imxpriv->ahb_clk)) { | |
157 | dev_err(dev, "can't get ahb clock.\n"); | |
158 | ret = PTR_ERR(imxpriv->ahb_clk); | |
159 | goto err_out; | |
160 | } | |
161 | ||
162 | imxpriv->sata_ref_clk = devm_clk_get(dev, "sata_ref"); | |
163 | if (IS_ERR(imxpriv->sata_ref_clk)) { | |
164 | dev_err(dev, "can't get sata_ref clock.\n"); | |
165 | ret = PTR_ERR(imxpriv->sata_ref_clk); | |
166 | goto err_out; | |
167 | } | |
168 | ||
169 | imxpriv->ahci_pdev = ahci_pdev; | |
170 | platform_set_drvdata(pdev, imxpriv); | |
171 | ||
172 | of_id = of_match_device(imx_ahci_of_match, dev); | |
173 | if (of_id) { | |
174 | pdata = of_id->data; | |
175 | } else { | |
176 | ret = -EINVAL; | |
177 | goto err_out; | |
178 | } | |
179 | ||
180 | mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); | |
181 | irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0); | |
182 | if (!mem || !irq) { | |
183 | dev_err(dev, "no mmio/irq resource\n"); | |
184 | ret = -ENOMEM; | |
185 | goto err_out; | |
186 | } | |
187 | ||
188 | res[0] = *mem; | |
189 | res[1] = *irq; | |
190 | ||
191 | ahci_dev->coherent_dma_mask = DMA_BIT_MASK(32); | |
192 | ahci_dev->dma_mask = &ahci_dev->coherent_dma_mask; | |
193 | ahci_dev->of_node = dev->of_node; | |
194 | ||
195 | ret = platform_device_add_resources(ahci_pdev, res, 2); | |
196 | if (ret) | |
197 | goto err_out; | |
198 | ||
199 | ret = platform_device_add_data(ahci_pdev, pdata, sizeof(*pdata)); | |
200 | if (ret) | |
201 | goto err_out; | |
202 | ||
203 | ret = platform_device_add(ahci_pdev); | |
204 | if (ret) { | |
205 | err_out: | |
206 | platform_device_put(ahci_pdev); | |
207 | return ret; | |
208 | } | |
209 | ||
210 | return 0; | |
211 | } | |
212 | ||
213 | static int imx_ahci_remove(struct platform_device *pdev) | |
214 | { | |
215 | struct imx_ahci_priv *imxpriv = platform_get_drvdata(pdev); | |
216 | struct platform_device *ahci_pdev = imxpriv->ahci_pdev; | |
217 | ||
218 | platform_device_unregister(ahci_pdev); | |
219 | return 0; | |
220 | } | |
221 | ||
222 | static struct platform_driver imx_ahci_driver = { | |
223 | .probe = imx_ahci_probe, | |
224 | .remove = imx_ahci_remove, | |
225 | .driver = { | |
226 | .name = "ahci-imx", | |
227 | .owner = THIS_MODULE, | |
228 | .of_match_table = imx_ahci_of_match, | |
229 | }, | |
230 | }; | |
231 | module_platform_driver(imx_ahci_driver); | |
232 | ||
233 | MODULE_DESCRIPTION("Freescale i.MX AHCI SATA platform driver"); | |
234 | MODULE_AUTHOR("Richard Zhu <[email protected]>"); | |
235 | MODULE_LICENSE("GPL"); | |
236 | MODULE_ALIAS("ahci:imx"); |