1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * MediaTek AHCI SATA driver |
4 | * |
5 | * Copyright (c) 2017 MediaTek Inc. |
6 | * Author: Ryder Lee <ryder.lee@mediatek.com> |
7 | */ |
8 | |
9 | #include <linux/ahci_platform.h> |
10 | #include <linux/kernel.h> |
11 | #include <linux/libata.h> |
12 | #include <linux/mfd/syscon.h> |
13 | #include <linux/module.h> |
14 | #include <linux/of.h> |
15 | #include <linux/platform_device.h> |
16 | #include <linux/pm.h> |
17 | #include <linux/regmap.h> |
18 | #include <linux/reset.h> |
19 | #include "ahci.h" |
20 | |
21 | #define DRV_NAME "ahci-mtk" |
22 | |
23 | #define SYS_CFG 0x14 |
24 | #define SYS_CFG_SATA_MSK GENMASK(31, 30) |
25 | #define SYS_CFG_SATA_EN BIT(31) |
26 | |
27 | struct mtk_ahci_plat { |
28 | struct regmap *mode; |
29 | struct reset_control *axi_rst; |
30 | struct reset_control *sw_rst; |
31 | struct reset_control *reg_rst; |
32 | }; |
33 | |
34 | static const struct ata_port_info ahci_port_info = { |
35 | .flags = AHCI_FLAG_COMMON, |
36 | .pio_mask = ATA_PIO4, |
37 | .udma_mask = ATA_UDMA6, |
38 | .port_ops = &ahci_platform_ops, |
39 | }; |
40 | |
41 | static const struct scsi_host_template ahci_platform_sht = { |
42 | AHCI_SHT(DRV_NAME), |
43 | }; |
44 | |
45 | static int mtk_ahci_platform_resets(struct ahci_host_priv *hpriv, |
46 | struct device *dev) |
47 | { |
48 | struct mtk_ahci_plat *plat = hpriv->plat_data; |
49 | int err; |
50 | |
51 | /* reset AXI bus and PHY part */ |
52 | plat->axi_rst = devm_reset_control_get_optional_exclusive(dev, id: "axi" ); |
53 | if (PTR_ERR(ptr: plat->axi_rst) == -EPROBE_DEFER) |
54 | return PTR_ERR(ptr: plat->axi_rst); |
55 | |
56 | plat->sw_rst = devm_reset_control_get_optional_exclusive(dev, id: "sw" ); |
57 | if (PTR_ERR(ptr: plat->sw_rst) == -EPROBE_DEFER) |
58 | return PTR_ERR(ptr: plat->sw_rst); |
59 | |
60 | plat->reg_rst = devm_reset_control_get_optional_exclusive(dev, id: "reg" ); |
61 | if (PTR_ERR(ptr: plat->reg_rst) == -EPROBE_DEFER) |
62 | return PTR_ERR(ptr: plat->reg_rst); |
63 | |
64 | err = reset_control_assert(rstc: plat->axi_rst); |
65 | if (err) { |
66 | dev_err(dev, "failed to assert AXI bus\n" ); |
67 | return err; |
68 | } |
69 | |
70 | err = reset_control_assert(rstc: plat->sw_rst); |
71 | if (err) { |
72 | dev_err(dev, "failed to assert PHY digital part\n" ); |
73 | return err; |
74 | } |
75 | |
76 | err = reset_control_assert(rstc: plat->reg_rst); |
77 | if (err) { |
78 | dev_err(dev, "failed to assert PHY register part\n" ); |
79 | return err; |
80 | } |
81 | |
82 | err = reset_control_deassert(rstc: plat->reg_rst); |
83 | if (err) { |
84 | dev_err(dev, "failed to deassert PHY register part\n" ); |
85 | return err; |
86 | } |
87 | |
88 | err = reset_control_deassert(rstc: plat->sw_rst); |
89 | if (err) { |
90 | dev_err(dev, "failed to deassert PHY digital part\n" ); |
91 | return err; |
92 | } |
93 | |
94 | err = reset_control_deassert(rstc: plat->axi_rst); |
95 | if (err) { |
96 | dev_err(dev, "failed to deassert AXI bus\n" ); |
97 | return err; |
98 | } |
99 | |
100 | return 0; |
101 | } |
102 | |
103 | static int mtk_ahci_parse_property(struct ahci_host_priv *hpriv, |
104 | struct device *dev) |
105 | { |
106 | struct mtk_ahci_plat *plat = hpriv->plat_data; |
107 | struct device_node *np = dev->of_node; |
108 | |
109 | /* enable SATA function if needed */ |
110 | if (of_property_present(np, propname: "mediatek,phy-mode" )) { |
111 | plat->mode = syscon_regmap_lookup_by_phandle( |
112 | np, property: "mediatek,phy-mode" ); |
113 | if (IS_ERR(ptr: plat->mode)) { |
114 | dev_err(dev, "missing phy-mode phandle\n" ); |
115 | return PTR_ERR(ptr: plat->mode); |
116 | } |
117 | |
118 | regmap_update_bits(map: plat->mode, SYS_CFG, SYS_CFG_SATA_MSK, |
119 | SYS_CFG_SATA_EN); |
120 | } |
121 | |
122 | return 0; |
123 | } |
124 | |
125 | static int mtk_ahci_probe(struct platform_device *pdev) |
126 | { |
127 | struct device *dev = &pdev->dev; |
128 | struct mtk_ahci_plat *plat; |
129 | struct ahci_host_priv *hpriv; |
130 | int err; |
131 | |
132 | plat = devm_kzalloc(dev, size: sizeof(*plat), GFP_KERNEL); |
133 | if (!plat) |
134 | return -ENOMEM; |
135 | |
136 | hpriv = ahci_platform_get_resources(pdev, flags: 0); |
137 | if (IS_ERR(ptr: hpriv)) |
138 | return PTR_ERR(ptr: hpriv); |
139 | |
140 | hpriv->plat_data = plat; |
141 | |
142 | err = mtk_ahci_parse_property(hpriv, dev); |
143 | if (err) |
144 | return err; |
145 | |
146 | err = mtk_ahci_platform_resets(hpriv, dev); |
147 | if (err) |
148 | return err; |
149 | |
150 | err = ahci_platform_enable_resources(hpriv); |
151 | if (err) |
152 | return err; |
153 | |
154 | err = ahci_platform_init_host(pdev, hpriv, pi_template: &ahci_port_info, |
155 | sht: &ahci_platform_sht); |
156 | if (err) |
157 | goto disable_resources; |
158 | |
159 | return 0; |
160 | |
161 | disable_resources: |
162 | ahci_platform_disable_resources(hpriv); |
163 | return err; |
164 | } |
165 | |
166 | static SIMPLE_DEV_PM_OPS(ahci_pm_ops, ahci_platform_suspend, |
167 | ahci_platform_resume); |
168 | |
169 | static const struct of_device_id ahci_of_match[] = { |
170 | { .compatible = "mediatek,mtk-ahci" , }, |
171 | { /* sentinel */ } |
172 | }; |
173 | MODULE_DEVICE_TABLE(of, ahci_of_match); |
174 | |
175 | static struct platform_driver mtk_ahci_driver = { |
176 | .probe = mtk_ahci_probe, |
177 | .remove_new = ata_platform_remove_one, |
178 | .driver = { |
179 | .name = DRV_NAME, |
180 | .of_match_table = ahci_of_match, |
181 | .pm = &ahci_pm_ops, |
182 | }, |
183 | }; |
184 | module_platform_driver(mtk_ahci_driver); |
185 | |
186 | MODULE_DESCRIPTION("MediaTek SATA AHCI Driver" ); |
187 | MODULE_LICENSE("GPL v2" ); |
188 | |