1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Copyright (C) 2012 STMicroelectronics Limited |
4 | * |
5 | * Authors: Francesco Virlinzi <francesco.virlinzi@st.com> |
6 | * Alexandre Torgue <alexandre.torgue@st.com> |
7 | */ |
8 | |
9 | #include <linux/init.h> |
10 | #include <linux/module.h> |
11 | #include <linux/export.h> |
12 | #include <linux/platform_device.h> |
13 | #include <linux/clk.h> |
14 | #include <linux/of.h> |
15 | #include <linux/ahci_platform.h> |
16 | #include <linux/libata.h> |
17 | #include <linux/reset.h> |
18 | #include <linux/io.h> |
19 | #include <linux/dma-mapping.h> |
20 | |
21 | #include "ahci.h" |
22 | |
23 | #define DRV_NAME "st_ahci" |
24 | |
25 | #define ST_AHCI_OOBR 0xbc |
26 | #define ST_AHCI_OOBR_WE BIT(31) |
27 | #define ST_AHCI_OOBR_CWMIN_SHIFT 24 |
28 | #define ST_AHCI_OOBR_CWMAX_SHIFT 16 |
29 | #define ST_AHCI_OOBR_CIMIN_SHIFT 8 |
30 | #define ST_AHCI_OOBR_CIMAX_SHIFT 0 |
31 | |
32 | struct st_ahci_drv_data { |
33 | struct platform_device *ahci; |
34 | struct reset_control *pwr; |
35 | struct reset_control *sw_rst; |
36 | struct reset_control *pwr_rst; |
37 | }; |
38 | |
39 | static void st_ahci_configure_oob(void __iomem *mmio) |
40 | { |
41 | unsigned long old_val, new_val; |
42 | |
43 | new_val = (0x02 << ST_AHCI_OOBR_CWMIN_SHIFT) | |
44 | (0x04 << ST_AHCI_OOBR_CWMAX_SHIFT) | |
45 | (0x08 << ST_AHCI_OOBR_CIMIN_SHIFT) | |
46 | (0x0C << ST_AHCI_OOBR_CIMAX_SHIFT); |
47 | |
48 | old_val = readl(addr: mmio + ST_AHCI_OOBR); |
49 | writel(val: old_val | ST_AHCI_OOBR_WE, addr: mmio + ST_AHCI_OOBR); |
50 | writel(val: new_val | ST_AHCI_OOBR_WE, addr: mmio + ST_AHCI_OOBR); |
51 | writel(val: new_val, addr: mmio + ST_AHCI_OOBR); |
52 | } |
53 | |
54 | static int st_ahci_deassert_resets(struct ahci_host_priv *hpriv, |
55 | struct device *dev) |
56 | { |
57 | struct st_ahci_drv_data *drv_data = hpriv->plat_data; |
58 | int err; |
59 | |
60 | if (drv_data->pwr) { |
61 | err = reset_control_deassert(rstc: drv_data->pwr); |
62 | if (err) { |
63 | dev_err(dev, "unable to bring out of pwrdwn\n" ); |
64 | return err; |
65 | } |
66 | } |
67 | |
68 | if (drv_data->sw_rst) { |
69 | err = reset_control_deassert(rstc: drv_data->sw_rst); |
70 | if (err) { |
71 | dev_err(dev, "unable to bring out of sw-rst\n" ); |
72 | return err; |
73 | } |
74 | } |
75 | |
76 | if (drv_data->pwr_rst) { |
77 | err = reset_control_deassert(rstc: drv_data->pwr_rst); |
78 | if (err) { |
79 | dev_err(dev, "unable to bring out of pwr-rst\n" ); |
80 | return err; |
81 | } |
82 | } |
83 | |
84 | return 0; |
85 | } |
86 | |
87 | static void st_ahci_host_stop(struct ata_host *host) |
88 | { |
89 | struct ahci_host_priv *hpriv = host->private_data; |
90 | struct st_ahci_drv_data *drv_data = hpriv->plat_data; |
91 | struct device *dev = host->dev; |
92 | int err; |
93 | |
94 | if (drv_data->pwr) { |
95 | err = reset_control_assert(rstc: drv_data->pwr); |
96 | if (err) |
97 | dev_err(dev, "unable to pwrdwn\n" ); |
98 | } |
99 | |
100 | ahci_platform_disable_resources(hpriv); |
101 | } |
102 | |
103 | static int st_ahci_probe_resets(struct ahci_host_priv *hpriv, |
104 | struct device *dev) |
105 | { |
106 | struct st_ahci_drv_data *drv_data = hpriv->plat_data; |
107 | |
108 | drv_data->pwr = devm_reset_control_get(dev, id: "pwr-dwn" ); |
109 | if (IS_ERR(ptr: drv_data->pwr)) { |
110 | dev_info(dev, "power reset control not defined\n" ); |
111 | drv_data->pwr = NULL; |
112 | } |
113 | |
114 | drv_data->sw_rst = devm_reset_control_get(dev, id: "sw-rst" ); |
115 | if (IS_ERR(ptr: drv_data->sw_rst)) { |
116 | dev_info(dev, "soft reset control not defined\n" ); |
117 | drv_data->sw_rst = NULL; |
118 | } |
119 | |
120 | drv_data->pwr_rst = devm_reset_control_get(dev, id: "pwr-rst" ); |
121 | if (IS_ERR(ptr: drv_data->pwr_rst)) { |
122 | dev_dbg(dev, "power soft reset control not defined\n" ); |
123 | drv_data->pwr_rst = NULL; |
124 | } |
125 | |
126 | return st_ahci_deassert_resets(hpriv, dev); |
127 | } |
128 | |
129 | static struct ata_port_operations st_ahci_port_ops = { |
130 | .inherits = &ahci_platform_ops, |
131 | .host_stop = st_ahci_host_stop, |
132 | }; |
133 | |
134 | static const struct ata_port_info st_ahci_port_info = { |
135 | .flags = AHCI_FLAG_COMMON, |
136 | .pio_mask = ATA_PIO4, |
137 | .udma_mask = ATA_UDMA6, |
138 | .port_ops = &st_ahci_port_ops, |
139 | }; |
140 | |
141 | static const struct scsi_host_template ahci_platform_sht = { |
142 | AHCI_SHT(DRV_NAME), |
143 | }; |
144 | |
145 | static int st_ahci_probe(struct platform_device *pdev) |
146 | { |
147 | struct st_ahci_drv_data *drv_data; |
148 | struct ahci_host_priv *hpriv; |
149 | int err; |
150 | |
151 | drv_data = devm_kzalloc(dev: &pdev->dev, size: sizeof(*drv_data), GFP_KERNEL); |
152 | if (!drv_data) |
153 | return -ENOMEM; |
154 | |
155 | hpriv = ahci_platform_get_resources(pdev, flags: 0); |
156 | if (IS_ERR(ptr: hpriv)) |
157 | return PTR_ERR(ptr: hpriv); |
158 | hpriv->plat_data = drv_data; |
159 | |
160 | err = st_ahci_probe_resets(hpriv, dev: &pdev->dev); |
161 | if (err) |
162 | return err; |
163 | |
164 | err = ahci_platform_enable_resources(hpriv); |
165 | if (err) |
166 | return err; |
167 | |
168 | st_ahci_configure_oob(mmio: hpriv->mmio); |
169 | |
170 | err = ahci_platform_init_host(pdev, hpriv, pi_template: &st_ahci_port_info, |
171 | sht: &ahci_platform_sht); |
172 | if (err) { |
173 | ahci_platform_disable_resources(hpriv); |
174 | return err; |
175 | } |
176 | |
177 | return 0; |
178 | } |
179 | |
180 | #ifdef CONFIG_PM_SLEEP |
181 | static int st_ahci_suspend(struct device *dev) |
182 | { |
183 | struct ata_host *host = dev_get_drvdata(dev); |
184 | struct ahci_host_priv *hpriv = host->private_data; |
185 | struct st_ahci_drv_data *drv_data = hpriv->plat_data; |
186 | int err; |
187 | |
188 | err = ahci_platform_suspend_host(dev); |
189 | if (err) |
190 | return err; |
191 | |
192 | if (drv_data->pwr) { |
193 | err = reset_control_assert(rstc: drv_data->pwr); |
194 | if (err) { |
195 | dev_err(dev, "unable to pwrdwn" ); |
196 | return err; |
197 | } |
198 | } |
199 | |
200 | ahci_platform_disable_resources(hpriv); |
201 | |
202 | return 0; |
203 | } |
204 | |
205 | static int st_ahci_resume(struct device *dev) |
206 | { |
207 | struct ata_host *host = dev_get_drvdata(dev); |
208 | struct ahci_host_priv *hpriv = host->private_data; |
209 | int err; |
210 | |
211 | err = ahci_platform_enable_resources(hpriv); |
212 | if (err) |
213 | return err; |
214 | |
215 | err = st_ahci_deassert_resets(hpriv, dev); |
216 | if (err) { |
217 | ahci_platform_disable_resources(hpriv); |
218 | return err; |
219 | } |
220 | |
221 | st_ahci_configure_oob(mmio: hpriv->mmio); |
222 | |
223 | return ahci_platform_resume_host(dev); |
224 | } |
225 | #endif |
226 | |
227 | static SIMPLE_DEV_PM_OPS(st_ahci_pm_ops, st_ahci_suspend, st_ahci_resume); |
228 | |
229 | static const struct of_device_id st_ahci_match[] = { |
230 | { .compatible = "st,ahci" , }, |
231 | { /* sentinel */ } |
232 | }; |
233 | MODULE_DEVICE_TABLE(of, st_ahci_match); |
234 | |
235 | static struct platform_driver st_ahci_driver = { |
236 | .driver = { |
237 | .name = DRV_NAME, |
238 | .pm = &st_ahci_pm_ops, |
239 | .of_match_table = st_ahci_match, |
240 | }, |
241 | .probe = st_ahci_probe, |
242 | .remove_new = ata_platform_remove_one, |
243 | }; |
244 | module_platform_driver(st_ahci_driver); |
245 | |
246 | MODULE_AUTHOR("Alexandre Torgue <alexandre.torgue@st.com>" ); |
247 | MODULE_AUTHOR("Francesco Virlinzi <francesco.virlinzi@st.com>" ); |
248 | MODULE_DESCRIPTION("STMicroelectronics SATA AHCI Driver" ); |
249 | MODULE_LICENSE("GPL v2" ); |
250 | |