1 | /* |
2 | * Support of SDHCI platform devices for Microchip PIC32. |
3 | * |
4 | * Copyright (C) 2015 Microchip |
5 | * Andrei Pistirica, Paul Thacker |
6 | * |
7 | * Inspired by sdhci-pltfm.c |
8 | * |
9 | * This file is licensed under the terms of the GNU General Public |
10 | * License version 2. This program is licensed "as is" without any |
11 | * warranty of any kind, whether express or implied. |
12 | */ |
13 | |
14 | #include <linux/clk.h> |
15 | #include <linux/delay.h> |
16 | #include <linux/highmem.h> |
17 | #include <linux/module.h> |
18 | #include <linux/interrupt.h> |
19 | #include <linux/irq.h> |
20 | #include <linux/of.h> |
21 | #include <linux/platform_device.h> |
22 | #include <linux/pm.h> |
23 | #include <linux/slab.h> |
24 | #include <linux/mmc/host.h> |
25 | #include <linux/io.h> |
26 | #include "sdhci.h" |
27 | #include "sdhci-pltfm.h" |
28 | #include <linux/platform_data/sdhci-pic32.h> |
29 | |
30 | #define SDH_SHARED_BUS_CTRL 0x000000E0 |
31 | #define SDH_SHARED_BUS_NR_CLK_PINS_MASK 0x7 |
32 | #define SDH_SHARED_BUS_NR_IRQ_PINS_MASK 0x30 |
33 | #define SDH_SHARED_BUS_CLK_PINS 0x10 |
34 | #define SDH_SHARED_BUS_IRQ_PINS 0x14 |
35 | #define SDH_CAPS_SDH_SLOT_TYPE_MASK 0xC0000000 |
36 | #define SDH_SLOT_TYPE_REMOVABLE 0x0 |
37 | #define SDH_SLOT_TYPE_EMBEDDED 0x1 |
38 | #define SDH_SLOT_TYPE_SHARED_BUS 0x2 |
39 | #define SDHCI_CTRL_CDSSEL 0x80 |
40 | #define SDHCI_CTRL_CDTLVL 0x40 |
41 | |
42 | #define ADMA_FIFO_RD_THSHLD 512 |
43 | #define ADMA_FIFO_WR_THSHLD 512 |
44 | |
45 | struct pic32_sdhci_priv { |
46 | struct platform_device *pdev; |
47 | struct clk *sys_clk; |
48 | struct clk *base_clk; |
49 | }; |
50 | |
51 | static unsigned int pic32_sdhci_get_max_clock(struct sdhci_host *host) |
52 | { |
53 | struct pic32_sdhci_priv *sdhci_pdata = sdhci_priv(host); |
54 | |
55 | return clk_get_rate(clk: sdhci_pdata->base_clk); |
56 | } |
57 | |
58 | static void pic32_sdhci_set_bus_width(struct sdhci_host *host, int width) |
59 | { |
60 | u8 ctrl; |
61 | |
62 | ctrl = sdhci_readb(host, SDHCI_HOST_CONTROL); |
63 | if (width == MMC_BUS_WIDTH_8) { |
64 | ctrl &= ~SDHCI_CTRL_4BITBUS; |
65 | if (host->version >= SDHCI_SPEC_300) |
66 | ctrl |= SDHCI_CTRL_8BITBUS; |
67 | } else { |
68 | if (host->version >= SDHCI_SPEC_300) |
69 | ctrl &= ~SDHCI_CTRL_8BITBUS; |
70 | if (width == MMC_BUS_WIDTH_4) |
71 | ctrl |= SDHCI_CTRL_4BITBUS; |
72 | else |
73 | ctrl &= ~SDHCI_CTRL_4BITBUS; |
74 | } |
75 | |
76 | /* CD select and test bits must be set for errata workaround. */ |
77 | ctrl &= ~SDHCI_CTRL_CDTLVL; |
78 | ctrl |= SDHCI_CTRL_CDSSEL; |
79 | sdhci_writeb(host, val: ctrl, SDHCI_HOST_CONTROL); |
80 | } |
81 | |
82 | static unsigned int pic32_sdhci_get_ro(struct sdhci_host *host) |
83 | { |
84 | /* |
85 | * The SDHCI_WRITE_PROTECT bit is unstable on current hardware so we |
86 | * can't depend on its value in any way. |
87 | */ |
88 | return 0; |
89 | } |
90 | |
91 | static const struct sdhci_ops pic32_sdhci_ops = { |
92 | .get_max_clock = pic32_sdhci_get_max_clock, |
93 | .set_clock = sdhci_set_clock, |
94 | .set_bus_width = pic32_sdhci_set_bus_width, |
95 | .reset = sdhci_reset, |
96 | .set_uhs_signaling = sdhci_set_uhs_signaling, |
97 | .get_ro = pic32_sdhci_get_ro, |
98 | }; |
99 | |
100 | static const struct sdhci_pltfm_data sdhci_pic32_pdata = { |
101 | .ops = &pic32_sdhci_ops, |
102 | .quirks = SDHCI_QUIRK_NO_HISPD_BIT, |
103 | .quirks2 = SDHCI_QUIRK2_NO_1_8_V, |
104 | }; |
105 | |
106 | static void pic32_sdhci_shared_bus(struct platform_device *pdev) |
107 | { |
108 | struct sdhci_host *host = platform_get_drvdata(pdev); |
109 | u32 bus = readl(addr: host->ioaddr + SDH_SHARED_BUS_CTRL); |
110 | u32 clk_pins = (bus & SDH_SHARED_BUS_NR_CLK_PINS_MASK) >> 0; |
111 | u32 irq_pins = (bus & SDH_SHARED_BUS_NR_IRQ_PINS_MASK) >> 4; |
112 | |
113 | /* select first clock */ |
114 | if (clk_pins & 1) |
115 | bus |= (1 << SDH_SHARED_BUS_CLK_PINS); |
116 | |
117 | /* select first interrupt */ |
118 | if (irq_pins & 1) |
119 | bus |= (1 << SDH_SHARED_BUS_IRQ_PINS); |
120 | |
121 | writel(val: bus, addr: host->ioaddr + SDH_SHARED_BUS_CTRL); |
122 | } |
123 | |
124 | static void pic32_sdhci_probe_platform(struct platform_device *pdev, |
125 | struct pic32_sdhci_priv *pdata) |
126 | { |
127 | u32 caps_slot_type; |
128 | struct sdhci_host *host = platform_get_drvdata(pdev); |
129 | |
130 | /* Check card slot connected on shared bus. */ |
131 | host->caps = readl(addr: host->ioaddr + SDHCI_CAPABILITIES); |
132 | caps_slot_type = (host->caps & SDH_CAPS_SDH_SLOT_TYPE_MASK) >> 30; |
133 | if (caps_slot_type == SDH_SLOT_TYPE_SHARED_BUS) |
134 | pic32_sdhci_shared_bus(pdev); |
135 | } |
136 | |
137 | static int pic32_sdhci_probe(struct platform_device *pdev) |
138 | { |
139 | struct sdhci_host *host; |
140 | struct sdhci_pltfm_host *pltfm_host; |
141 | struct pic32_sdhci_priv *sdhci_pdata; |
142 | struct pic32_sdhci_platform_data *plat_data; |
143 | int ret; |
144 | |
145 | host = sdhci_pltfm_init(pdev, pdata: &sdhci_pic32_pdata, |
146 | priv_size: sizeof(struct pic32_sdhci_priv)); |
147 | if (IS_ERR(ptr: host)) { |
148 | ret = PTR_ERR(ptr: host); |
149 | goto err; |
150 | } |
151 | |
152 | pltfm_host = sdhci_priv(host); |
153 | sdhci_pdata = sdhci_pltfm_priv(host: pltfm_host); |
154 | |
155 | plat_data = pdev->dev.platform_data; |
156 | if (plat_data && plat_data->setup_dma) { |
157 | ret = plat_data->setup_dma(ADMA_FIFO_RD_THSHLD, |
158 | ADMA_FIFO_WR_THSHLD); |
159 | if (ret) |
160 | goto err_host; |
161 | } |
162 | |
163 | sdhci_pdata->sys_clk = devm_clk_get(dev: &pdev->dev, id: "sys_clk" ); |
164 | if (IS_ERR(ptr: sdhci_pdata->sys_clk)) { |
165 | ret = PTR_ERR(ptr: sdhci_pdata->sys_clk); |
166 | dev_err(&pdev->dev, "Error getting clock\n" ); |
167 | goto err_host; |
168 | } |
169 | |
170 | ret = clk_prepare_enable(clk: sdhci_pdata->sys_clk); |
171 | if (ret) { |
172 | dev_err(&pdev->dev, "Error enabling clock\n" ); |
173 | goto err_host; |
174 | } |
175 | |
176 | sdhci_pdata->base_clk = devm_clk_get(dev: &pdev->dev, id: "base_clk" ); |
177 | if (IS_ERR(ptr: sdhci_pdata->base_clk)) { |
178 | ret = PTR_ERR(ptr: sdhci_pdata->base_clk); |
179 | dev_err(&pdev->dev, "Error getting clock\n" ); |
180 | goto err_sys_clk; |
181 | } |
182 | |
183 | ret = clk_prepare_enable(clk: sdhci_pdata->base_clk); |
184 | if (ret) { |
185 | dev_err(&pdev->dev, "Error enabling clock\n" ); |
186 | goto err_base_clk; |
187 | } |
188 | |
189 | ret = mmc_of_parse(host: host->mmc); |
190 | if (ret) |
191 | goto err_base_clk; |
192 | |
193 | pic32_sdhci_probe_platform(pdev, pdata: sdhci_pdata); |
194 | |
195 | ret = sdhci_add_host(host); |
196 | if (ret) |
197 | goto err_base_clk; |
198 | |
199 | dev_info(&pdev->dev, "Successfully added sdhci host\n" ); |
200 | return 0; |
201 | |
202 | err_base_clk: |
203 | clk_disable_unprepare(clk: sdhci_pdata->base_clk); |
204 | err_sys_clk: |
205 | clk_disable_unprepare(clk: sdhci_pdata->sys_clk); |
206 | err_host: |
207 | sdhci_pltfm_free(pdev); |
208 | err: |
209 | dev_err(&pdev->dev, "pic32-sdhci probe failed: %d\n" , ret); |
210 | return ret; |
211 | } |
212 | |
213 | static void pic32_sdhci_remove(struct platform_device *pdev) |
214 | { |
215 | struct sdhci_host *host = platform_get_drvdata(pdev); |
216 | struct pic32_sdhci_priv *sdhci_pdata = sdhci_priv(host); |
217 | u32 scratch; |
218 | |
219 | scratch = readl(addr: host->ioaddr + SDHCI_INT_STATUS); |
220 | sdhci_remove_host(host, dead: scratch == (u32)~0); |
221 | clk_disable_unprepare(clk: sdhci_pdata->base_clk); |
222 | clk_disable_unprepare(clk: sdhci_pdata->sys_clk); |
223 | sdhci_pltfm_free(pdev); |
224 | } |
225 | |
226 | static const struct of_device_id pic32_sdhci_id_table[] = { |
227 | { .compatible = "microchip,pic32mzda-sdhci" }, |
228 | {} |
229 | }; |
230 | MODULE_DEVICE_TABLE(of, pic32_sdhci_id_table); |
231 | |
232 | static struct platform_driver pic32_sdhci_driver = { |
233 | .driver = { |
234 | .name = "pic32-sdhci" , |
235 | .probe_type = PROBE_PREFER_ASYNCHRONOUS, |
236 | .of_match_table = of_match_ptr(pic32_sdhci_id_table), |
237 | }, |
238 | .probe = pic32_sdhci_probe, |
239 | .remove_new = pic32_sdhci_remove, |
240 | }; |
241 | |
242 | module_platform_driver(pic32_sdhci_driver); |
243 | |
244 | MODULE_DESCRIPTION("Microchip PIC32 SDHCI driver" ); |
245 | MODULE_AUTHOR("Pistirica Sorin Andrei & Sandeep Sheriker" ); |
246 | MODULE_LICENSE("GPL v2" ); |
247 | |