Commit | Line | Data |
---|---|---|
f52d9c4f PG |
1 | /* |
2 | * Support for SDHCI on STMicroelectronics SoCs | |
3 | * | |
4 | * Copyright (C) 2014 STMicroelectronics Ltd | |
5 | * Author: Giuseppe Cavallaro <peppe.cavallaro@st.com> | |
6 | * Contributors: Peter Griffin <peter.griffin@linaro.org> | |
7 | * | |
8 | * Based on sdhci-cns3xxx.c | |
9 | * | |
10 | * This program is free software; you can redistribute it and/or modify | |
11 | * it under the terms of the GNU General Public License version 2 as | |
12 | * published by the Free Software Foundation. | |
13 | * | |
14 | * This program is distributed in the hope that it will be useful, | |
15 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
16 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
17 | * GNU General Public License for more details. | |
18 | * | |
19 | */ | |
20 | ||
21 | #include <linux/io.h> | |
22 | #include <linux/of.h> | |
23 | #include <linux/module.h> | |
24 | #include <linux/err.h> | |
25 | #include <linux/mmc/host.h> | |
26 | ||
27 | #include "sdhci-pltfm.h" | |
28 | ||
29 | static u32 sdhci_st_readl(struct sdhci_host *host, int reg) | |
30 | { | |
31 | u32 ret; | |
32 | ||
33 | switch (reg) { | |
34 | case SDHCI_CAPABILITIES: | |
35 | ret = readl_relaxed(host->ioaddr + reg); | |
36 | /* Support 3.3V and 1.8V */ | |
37 | ret &= ~SDHCI_CAN_VDD_300; | |
38 | break; | |
39 | default: | |
40 | ret = readl_relaxed(host->ioaddr + reg); | |
41 | } | |
42 | return ret; | |
43 | } | |
44 | ||
45 | static const struct sdhci_ops sdhci_st_ops = { | |
46 | .get_max_clock = sdhci_pltfm_clk_get_max_clock, | |
47 | .set_clock = sdhci_set_clock, | |
48 | .set_bus_width = sdhci_set_bus_width, | |
49 | .read_l = sdhci_st_readl, | |
50 | .reset = sdhci_reset, | |
51 | }; | |
52 | ||
53 | static const struct sdhci_pltfm_data sdhci_st_pdata = { | |
54 | .ops = &sdhci_st_ops, | |
55 | .quirks = SDHCI_QUIRK_NO_ENDATTR_IN_NOPDESC | | |
56 | SDHCI_QUIRK_CAP_CLOCK_BASE_BROKEN, | |
57 | }; | |
58 | ||
59 | ||
60 | static int sdhci_st_probe(struct platform_device *pdev) | |
61 | { | |
62 | struct sdhci_host *host; | |
63 | struct sdhci_pltfm_host *pltfm_host; | |
64 | struct clk *clk; | |
65 | int ret = 0; | |
66 | u16 host_version; | |
67 | ||
68 | clk = devm_clk_get(&pdev->dev, "mmc"); | |
69 | if (IS_ERR(clk)) { | |
70 | dev_err(&pdev->dev, "Peripheral clk not found\n"); | |
71 | return PTR_ERR(clk); | |
72 | } | |
73 | ||
74 | host = sdhci_pltfm_init(pdev, &sdhci_st_pdata, 0); | |
75 | if (IS_ERR(host)) { | |
76 | dev_err(&pdev->dev, "Failed sdhci_pltfm_init\n"); | |
77 | return PTR_ERR(host); | |
78 | } | |
79 | ||
80 | ret = mmc_of_parse(host->mmc); | |
81 | ||
82 | if (ret) { | |
83 | dev_err(&pdev->dev, "Failed mmc_of_parse\n"); | |
84 | return ret; | |
85 | } | |
86 | ||
87 | clk_prepare_enable(clk); | |
88 | ||
89 | pltfm_host = sdhci_priv(host); | |
90 | pltfm_host->clk = clk; | |
91 | ||
92 | ret = sdhci_add_host(host); | |
93 | if (ret) { | |
94 | dev_err(&pdev->dev, "Failed sdhci_add_host\n"); | |
95 | goto err_out; | |
96 | } | |
97 | ||
98 | platform_set_drvdata(pdev, host); | |
99 | ||
100 | host_version = readw_relaxed((host->ioaddr + SDHCI_HOST_VERSION)); | |
101 | ||
102 | dev_info(&pdev->dev, "SDHCI ST Initialised: Host Version: 0x%x Vendor Version 0x%x\n", | |
103 | ((host_version & SDHCI_SPEC_VER_MASK) >> SDHCI_SPEC_VER_SHIFT), | |
104 | ((host_version & SDHCI_VENDOR_VER_MASK) >> | |
105 | SDHCI_VENDOR_VER_SHIFT)); | |
106 | ||
107 | return 0; | |
108 | ||
109 | err_out: | |
110 | clk_disable_unprepare(clk); | |
111 | sdhci_pltfm_free(pdev); | |
112 | ||
113 | return ret; | |
114 | } | |
115 | ||
116 | static int sdhci_st_remove(struct platform_device *pdev) | |
117 | { | |
118 | struct sdhci_host *host = platform_get_drvdata(pdev); | |
119 | struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); | |
120 | ||
121 | clk_disable_unprepare(pltfm_host->clk); | |
122 | ||
123 | return sdhci_pltfm_unregister(pdev); | |
124 | } | |
125 | ||
126 | #ifdef CONFIG_PM_SLEEP | |
127 | static int sdhci_st_suspend(struct device *dev) | |
128 | { | |
129 | struct sdhci_host *host = dev_get_drvdata(dev); | |
130 | struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); | |
131 | int ret = sdhci_suspend_host(host); | |
132 | ||
133 | if (ret) | |
134 | goto out; | |
135 | ||
136 | clk_disable_unprepare(pltfm_host->clk); | |
137 | out: | |
138 | return ret; | |
139 | } | |
140 | ||
141 | static int sdhci_st_resume(struct device *dev) | |
142 | { | |
143 | struct sdhci_host *host = dev_get_drvdata(dev); | |
144 | struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); | |
145 | ||
146 | clk_prepare_enable(pltfm_host->clk); | |
147 | ||
148 | return sdhci_resume_host(host); | |
149 | } | |
150 | #endif | |
151 | ||
152 | static SIMPLE_DEV_PM_OPS(sdhci_st_pmops, sdhci_st_suspend, sdhci_st_resume); | |
153 | ||
154 | static const struct of_device_id st_sdhci_match[] = { | |
155 | { .compatible = "st,sdhci" }, | |
156 | {}, | |
157 | }; | |
158 | ||
159 | MODULE_DEVICE_TABLE(of, st_sdhci_match); | |
160 | ||
161 | static struct platform_driver sdhci_st_driver = { | |
162 | .probe = sdhci_st_probe, | |
163 | .remove = sdhci_st_remove, | |
164 | .driver = { | |
165 | .name = "sdhci-st", | |
166 | .pm = &sdhci_st_pmops, | |
167 | .of_match_table = of_match_ptr(st_sdhci_match), | |
168 | }, | |
169 | }; | |
170 | ||
171 | module_platform_driver(sdhci_st_driver); | |
172 | ||
173 | MODULE_DESCRIPTION("SDHCI driver for STMicroelectronics SoCs"); | |
174 | MODULE_AUTHOR("Giuseppe Cavallaro <peppe.cavallaro@st.com>"); | |
175 | MODULE_LICENSE("GPL v2"); | |
176 | MODULE_ALIAS("platform:st-sdhci"); |