Commit | Line | Data |
---|---|---|
2aec85b2 TG |
1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | // Copyright (C) 2014 Broadcom Corporation | |
b580c52d SB |
3 | |
4 | /* | |
5 | * iProc SDHCI platform driver | |
6 | */ | |
7 | ||
7c7ba433 | 8 | #include <linux/acpi.h> |
b580c52d SB |
9 | #include <linux/delay.h> |
10 | #include <linux/module.h> | |
11 | #include <linux/mmc/host.h> | |
12 | #include <linux/of.h> | |
13 | #include <linux/of_device.h> | |
14 | #include "sdhci-pltfm.h" | |
15 | ||
16 | struct sdhci_iproc_data { | |
17 | const struct sdhci_pltfm_data *pdata; | |
18 | u32 caps; | |
19 | u32 caps1; | |
b17b4ab8 | 20 | u32 mmc_caps; |
b580c52d SB |
21 | }; |
22 | ||
23 | struct sdhci_iproc_host { | |
24 | const struct sdhci_iproc_data *data; | |
25 | u32 shadow_cmd; | |
26 | u32 shadow_blk; | |
5f651b87 CD |
27 | bool is_cmd_shadowed; |
28 | bool is_blk_shadowed; | |
b580c52d SB |
29 | }; |
30 | ||
31 | #define REG_OFFSET_IN_BITS(reg) ((reg) << 3 & 0x18) | |
32 | ||
33 | static inline u32 sdhci_iproc_readl(struct sdhci_host *host, int reg) | |
34 | { | |
35 | u32 val = readl(host->ioaddr + reg); | |
36 | ||
37 | pr_debug("%s: readl [0x%02x] 0x%08x\n", | |
38 | mmc_hostname(host->mmc), reg, val); | |
39 | return val; | |
40 | } | |
41 | ||
42 | static u16 sdhci_iproc_readw(struct sdhci_host *host, int reg) | |
43 | { | |
5f651b87 CD |
44 | struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); |
45 | struct sdhci_iproc_host *iproc_host = sdhci_pltfm_priv(pltfm_host); | |
46 | u32 val; | |
47 | u16 word; | |
48 | ||
49 | if ((reg == SDHCI_TRANSFER_MODE) && iproc_host->is_cmd_shadowed) { | |
50 | /* Get the saved transfer mode */ | |
51 | val = iproc_host->shadow_cmd; | |
52 | } else if ((reg == SDHCI_BLOCK_SIZE || reg == SDHCI_BLOCK_COUNT) && | |
53 | iproc_host->is_blk_shadowed) { | |
54 | /* Get the saved block info */ | |
55 | val = iproc_host->shadow_blk; | |
56 | } else { | |
57 | val = sdhci_iproc_readl(host, (reg & ~3)); | |
58 | } | |
59 | word = val >> REG_OFFSET_IN_BITS(reg) & 0xffff; | |
b580c52d SB |
60 | return word; |
61 | } | |
62 | ||
63 | static u8 sdhci_iproc_readb(struct sdhci_host *host, int reg) | |
64 | { | |
65 | u32 val = sdhci_iproc_readl(host, (reg & ~3)); | |
66 | u8 byte = val >> REG_OFFSET_IN_BITS(reg) & 0xff; | |
67 | return byte; | |
68 | } | |
69 | ||
70 | static inline void sdhci_iproc_writel(struct sdhci_host *host, u32 val, int reg) | |
71 | { | |
72 | pr_debug("%s: writel [0x%02x] 0x%08x\n", | |
73 | mmc_hostname(host->mmc), reg, val); | |
74 | ||
75 | writel(val, host->ioaddr + reg); | |
76 | ||
77 | if (host->clock <= 400000) { | |
78 | /* Round up to micro-second four SD clock delay */ | |
79 | if (host->clock) | |
80 | udelay((4 * 1000000 + host->clock - 1) / host->clock); | |
81 | else | |
82 | udelay(10); | |
83 | } | |
84 | } | |
85 | ||
86 | /* | |
87 | * The Arasan has a bugette whereby it may lose the content of successive | |
88 | * writes to the same register that are within two SD-card clock cycles of | |
89 | * each other (a clock domain crossing problem). The data | |
90 | * register does not have this problem, which is just as well - otherwise we'd | |
91 | * have to nobble the DMA engine too. | |
92 | * | |
93 | * This wouldn't be a problem with the code except that we can only write the | |
94 | * controller with 32-bit writes. So two different 16-bit registers are | |
95 | * written back to back creates the problem. | |
96 | * | |
97 | * In reality, this only happens when SDHCI_BLOCK_SIZE and SDHCI_BLOCK_COUNT | |
98 | * are written followed by SDHCI_TRANSFER_MODE and SDHCI_COMMAND. | |
99 | * The BLOCK_SIZE and BLOCK_COUNT are meaningless until a command issued so | |
100 | * the work around can be further optimized. We can keep shadow values of | |
101 | * BLOCK_SIZE, BLOCK_COUNT, and TRANSFER_MODE until a COMMAND is issued. | |
102 | * Then, write the BLOCK_SIZE+BLOCK_COUNT in a single 32-bit write followed | |
103 | * by the TRANSFER+COMMAND in another 32-bit write. | |
104 | */ | |
105 | static void sdhci_iproc_writew(struct sdhci_host *host, u16 val, int reg) | |
106 | { | |
107 | struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); | |
b1ddaa3d | 108 | struct sdhci_iproc_host *iproc_host = sdhci_pltfm_priv(pltfm_host); |
b580c52d SB |
109 | u32 word_shift = REG_OFFSET_IN_BITS(reg); |
110 | u32 mask = 0xffff << word_shift; | |
111 | u32 oldval, newval; | |
112 | ||
113 | if (reg == SDHCI_COMMAND) { | |
114 | /* Write the block now as we are issuing a command */ | |
5f651b87 | 115 | if (iproc_host->is_blk_shadowed) { |
b580c52d SB |
116 | sdhci_iproc_writel(host, iproc_host->shadow_blk, |
117 | SDHCI_BLOCK_SIZE); | |
5f651b87 | 118 | iproc_host->is_blk_shadowed = false; |
b580c52d SB |
119 | } |
120 | oldval = iproc_host->shadow_cmd; | |
5f651b87 CD |
121 | iproc_host->is_cmd_shadowed = false; |
122 | } else if ((reg == SDHCI_BLOCK_SIZE || reg == SDHCI_BLOCK_COUNT) && | |
123 | iproc_host->is_blk_shadowed) { | |
b580c52d SB |
124 | /* Block size and count are stored in shadow reg */ |
125 | oldval = iproc_host->shadow_blk; | |
126 | } else { | |
127 | /* Read reg, all other registers are not shadowed */ | |
128 | oldval = sdhci_iproc_readl(host, (reg & ~3)); | |
129 | } | |
130 | newval = (oldval & ~mask) | (val << word_shift); | |
131 | ||
132 | if (reg == SDHCI_TRANSFER_MODE) { | |
133 | /* Save the transfer mode until the command is issued */ | |
134 | iproc_host->shadow_cmd = newval; | |
5f651b87 | 135 | iproc_host->is_cmd_shadowed = true; |
b580c52d SB |
136 | } else if (reg == SDHCI_BLOCK_SIZE || reg == SDHCI_BLOCK_COUNT) { |
137 | /* Save the block info until the command is issued */ | |
138 | iproc_host->shadow_blk = newval; | |
5f651b87 | 139 | iproc_host->is_blk_shadowed = true; |
b580c52d SB |
140 | } else { |
141 | /* Command or other regular 32-bit write */ | |
142 | sdhci_iproc_writel(host, newval, reg & ~3); | |
143 | } | |
144 | } | |
145 | ||
146 | static void sdhci_iproc_writeb(struct sdhci_host *host, u8 val, int reg) | |
147 | { | |
148 | u32 oldval = sdhci_iproc_readl(host, (reg & ~3)); | |
149 | u32 byte_shift = REG_OFFSET_IN_BITS(reg); | |
150 | u32 mask = 0xff << byte_shift; | |
151 | u32 newval = (oldval & ~mask) | (val << byte_shift); | |
152 | ||
153 | sdhci_iproc_writel(host, newval, reg & ~3); | |
154 | } | |
155 | ||
7c7ba433 SM |
156 | static unsigned int sdhci_iproc_get_max_clock(struct sdhci_host *host) |
157 | { | |
158 | struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); | |
159 | ||
160 | if (pltfm_host->clk) | |
161 | return sdhci_pltfm_clk_get_max_clock(host); | |
162 | else | |
163 | return pltfm_host->clock; | |
164 | } | |
165 | ||
c9107dd0 NSJ |
166 | /* |
167 | * There is a known bug on BCM2711's SDHCI core integration where the | |
168 | * controller will hang when the difference between the core clock and the bus | |
169 | * clock is too great. Specifically this can be reproduced under the following | |
170 | * conditions: | |
171 | * | |
172 | * - No SD card plugged in, polling thread is running, probing cards at | |
173 | * 100 kHz. | |
174 | * - BCM2711's core clock configured at 500MHz or more | |
175 | * | |
176 | * So we set 200kHz as the minimum clock frequency available for that SoC. | |
177 | */ | |
178 | static unsigned int sdhci_iproc_bcm2711_get_min_clock(struct sdhci_host *host) | |
179 | { | |
180 | return 200000; | |
181 | } | |
182 | ||
b580c52d | 183 | static const struct sdhci_ops sdhci_iproc_ops = { |
c833e92b | 184 | .set_clock = sdhci_set_clock, |
7c7ba433 | 185 | .get_max_clock = sdhci_iproc_get_max_clock, |
c833e92b SB |
186 | .set_bus_width = sdhci_set_bus_width, |
187 | .reset = sdhci_reset, | |
188 | .set_uhs_signaling = sdhci_set_uhs_signaling, | |
189 | }; | |
190 | ||
191 | static const struct sdhci_ops sdhci_iproc_32only_ops = { | |
b580c52d SB |
192 | .read_l = sdhci_iproc_readl, |
193 | .read_w = sdhci_iproc_readw, | |
194 | .read_b = sdhci_iproc_readb, | |
195 | .write_l = sdhci_iproc_writel, | |
196 | .write_w = sdhci_iproc_writew, | |
197 | .write_b = sdhci_iproc_writeb, | |
198 | .set_clock = sdhci_set_clock, | |
7c7ba433 | 199 | .get_max_clock = sdhci_iproc_get_max_clock, |
b580c52d SB |
200 | .set_bus_width = sdhci_set_bus_width, |
201 | .reset = sdhci_reset, | |
202 | .set_uhs_signaling = sdhci_set_uhs_signaling, | |
203 | }; | |
204 | ||
c833e92b | 205 | static const struct sdhci_pltfm_data sdhci_iproc_cygnus_pltfm_data = { |
b7dfa695 TH |
206 | .quirks = SDHCI_QUIRK_DATA_TIMEOUT_USES_SDCLK | |
207 | SDHCI_QUIRK_NO_HISPD_BIT, | |
3de06d5a | 208 | .quirks2 = SDHCI_QUIRK2_ACMD23_BROKEN | SDHCI_QUIRK2_HOST_OFF_CARD_ON, |
c833e92b SB |
209 | .ops = &sdhci_iproc_32only_ops, |
210 | }; | |
211 | ||
212 | static const struct sdhci_iproc_data iproc_cygnus_data = { | |
213 | .pdata = &sdhci_iproc_cygnus_pltfm_data, | |
214 | .caps = ((0x1 << SDHCI_MAX_BLOCK_SHIFT) | |
215 | & SDHCI_MAX_BLOCK_MASK) | | |
216 | SDHCI_CAN_VDD_330 | | |
217 | SDHCI_CAN_VDD_180 | | |
218 | SDHCI_CAN_DO_SUSPEND | | |
219 | SDHCI_CAN_DO_HISPD | | |
220 | SDHCI_CAN_DO_ADMA2 | | |
221 | SDHCI_CAN_DO_SDMA, | |
222 | .caps1 = SDHCI_DRIVER_TYPE_C | | |
223 | SDHCI_DRIVER_TYPE_D | | |
224 | SDHCI_SUPPORT_DDR50, | |
225 | .mmc_caps = MMC_CAP_1_8V_DDR, | |
226 | }; | |
227 | ||
b580c52d | 228 | static const struct sdhci_pltfm_data sdhci_iproc_pltfm_data = { |
f5f968f2 | 229 | .quirks = SDHCI_QUIRK_DATA_TIMEOUT_USES_SDCLK | |
ec0970e0 TH |
230 | SDHCI_QUIRK_MULTIBLOCK_READ_ACMD12 | |
231 | SDHCI_QUIRK_NO_HISPD_BIT, | |
b580c52d SB |
232 | .quirks2 = SDHCI_QUIRK2_ACMD23_BROKEN, |
233 | .ops = &sdhci_iproc_ops, | |
234 | }; | |
235 | ||
236 | static const struct sdhci_iproc_data iproc_data = { | |
237 | .pdata = &sdhci_iproc_pltfm_data, | |
1883edd1 SW |
238 | .caps = ((0x1 << SDHCI_MAX_BLOCK_SHIFT) |
239 | & SDHCI_MAX_BLOCK_MASK) | | |
240 | SDHCI_CAN_VDD_330 | | |
241 | SDHCI_CAN_VDD_180 | | |
242 | SDHCI_CAN_DO_SUSPEND | | |
243 | SDHCI_CAN_DO_HISPD | | |
244 | SDHCI_CAN_DO_ADMA2 | | |
245 | SDHCI_CAN_DO_SDMA, | |
246 | .caps1 = SDHCI_DRIVER_TYPE_C | | |
247 | SDHCI_DRIVER_TYPE_D | | |
248 | SDHCI_SUPPORT_DDR50, | |
b580c52d SB |
249 | }; |
250 | ||
77cb7d3a SW |
251 | static const struct sdhci_pltfm_data sdhci_bcm2835_pltfm_data = { |
252 | .quirks = SDHCI_QUIRK_BROKEN_CARD_DETECTION | | |
253 | SDHCI_QUIRK_DATA_TIMEOUT_USES_SDCLK | | |
c82c2775 SW |
254 | SDHCI_QUIRK_MISSING_CAPS | |
255 | SDHCI_QUIRK_NO_HISPD_BIT, | |
7f7a385d | 256 | .quirks2 = SDHCI_QUIRK2_PRESET_VALUE_BROKEN, |
c833e92b | 257 | .ops = &sdhci_iproc_32only_ops, |
77cb7d3a SW |
258 | }; |
259 | ||
260 | static const struct sdhci_iproc_data bcm2835_data = { | |
261 | .pdata = &sdhci_bcm2835_pltfm_data, | |
40165de2 SW |
262 | .caps = ((0x1 << SDHCI_MAX_BLOCK_SHIFT) |
263 | & SDHCI_MAX_BLOCK_MASK) | | |
264 | SDHCI_CAN_VDD_330 | | |
c82c2775 SW |
265 | SDHCI_CAN_DO_HISPD, |
266 | .caps1 = SDHCI_DRIVER_TYPE_A | | |
267 | SDHCI_DRIVER_TYPE_C, | |
77cb7d3a SW |
268 | .mmc_caps = 0x00000000, |
269 | }; | |
270 | ||
f87391ee NSJ |
271 | static const struct sdhci_ops sdhci_iproc_bcm2711_ops = { |
272 | .read_l = sdhci_iproc_readl, | |
273 | .read_w = sdhci_iproc_readw, | |
274 | .read_b = sdhci_iproc_readb, | |
275 | .write_l = sdhci_iproc_writel, | |
276 | .write_w = sdhci_iproc_writew, | |
277 | .write_b = sdhci_iproc_writeb, | |
278 | .set_clock = sdhci_set_clock, | |
279 | .set_power = sdhci_set_power_and_bus_voltage, | |
280 | .get_max_clock = sdhci_iproc_get_max_clock, | |
c9107dd0 | 281 | .get_min_clock = sdhci_iproc_bcm2711_get_min_clock, |
f87391ee NSJ |
282 | .set_bus_width = sdhci_set_bus_width, |
283 | .reset = sdhci_reset, | |
284 | .set_uhs_signaling = sdhci_set_uhs_signaling, | |
285 | }; | |
286 | ||
f84e411c | 287 | static const struct sdhci_pltfm_data sdhci_bcm2711_pltfm_data = { |
885814a9 | 288 | .quirks = SDHCI_QUIRK_MULTIBLOCK_READ_ACMD12, |
f87391ee | 289 | .ops = &sdhci_iproc_bcm2711_ops, |
f84e411c SW |
290 | }; |
291 | ||
292 | static const struct sdhci_iproc_data bcm2711_data = { | |
293 | .pdata = &sdhci_bcm2711_pltfm_data, | |
8d62fa83 | 294 | .mmc_caps = MMC_CAP_3_3V_DDR, |
f84e411c SW |
295 | }; |
296 | ||
98b5ce4c AC |
297 | static const struct sdhci_pltfm_data sdhci_bcm7211a0_pltfm_data = { |
298 | .quirks = SDHCI_QUIRK_MISSING_CAPS | | |
299 | SDHCI_QUIRK_BROKEN_TIMEOUT_VAL | | |
300 | SDHCI_QUIRK_BROKEN_DMA | | |
301 | SDHCI_QUIRK_BROKEN_ADMA, | |
302 | .ops = &sdhci_iproc_ops, | |
303 | }; | |
304 | ||
305 | #define BCM7211A0_BASE_CLK_MHZ 100 | |
306 | static const struct sdhci_iproc_data bcm7211a0_data = { | |
307 | .pdata = &sdhci_bcm7211a0_pltfm_data, | |
308 | .caps = ((BCM7211A0_BASE_CLK_MHZ / 2) << SDHCI_TIMEOUT_CLK_SHIFT) | | |
309 | (BCM7211A0_BASE_CLK_MHZ << SDHCI_CLOCK_BASE_SHIFT) | | |
310 | ((0x2 << SDHCI_MAX_BLOCK_SHIFT) | |
311 | & SDHCI_MAX_BLOCK_MASK) | | |
312 | SDHCI_CAN_VDD_330 | | |
313 | SDHCI_CAN_VDD_180 | | |
314 | SDHCI_CAN_DO_SUSPEND | | |
315 | SDHCI_CAN_DO_HISPD, | |
316 | .caps1 = SDHCI_DRIVER_TYPE_C | | |
317 | SDHCI_DRIVER_TYPE_D, | |
318 | }; | |
319 | ||
b580c52d | 320 | static const struct of_device_id sdhci_iproc_of_match[] = { |
77cb7d3a | 321 | { .compatible = "brcm,bcm2835-sdhci", .data = &bcm2835_data }, |
f84e411c | 322 | { .compatible = "brcm,bcm2711-emmc2", .data = &bcm2711_data }, |
c833e92b SB |
323 | { .compatible = "brcm,sdhci-iproc-cygnus", .data = &iproc_cygnus_data}, |
324 | { .compatible = "brcm,sdhci-iproc", .data = &iproc_data }, | |
98b5ce4c | 325 | { .compatible = "brcm,bcm7211a0-sdhci", .data = &bcm7211a0_data }, |
b580c52d SB |
326 | { } |
327 | }; | |
328 | MODULE_DEVICE_TABLE(of, sdhci_iproc_of_match); | |
329 | ||
edfa69de | 330 | #ifdef CONFIG_ACPI |
4f9833d3 JL |
331 | /* |
332 | * This is a duplicate of bcm2835_(pltfrm_)data without caps quirks | |
333 | * which are provided by the ACPI table. | |
334 | */ | |
335 | static const struct sdhci_pltfm_data sdhci_bcm_arasan_data = { | |
336 | .quirks = SDHCI_QUIRK_BROKEN_CARD_DETECTION | | |
337 | SDHCI_QUIRK_DATA_TIMEOUT_USES_SDCLK | | |
338 | SDHCI_QUIRK_NO_HISPD_BIT, | |
339 | .quirks2 = SDHCI_QUIRK2_PRESET_VALUE_BROKEN, | |
340 | .ops = &sdhci_iproc_32only_ops, | |
341 | }; | |
342 | ||
343 | static const struct sdhci_iproc_data bcm_arasan_data = { | |
344 | .pdata = &sdhci_bcm_arasan_data, | |
345 | }; | |
346 | ||
7c7ba433 SM |
347 | static const struct acpi_device_id sdhci_iproc_acpi_ids[] = { |
348 | { .id = "BRCM5871", .driver_data = (kernel_ulong_t)&iproc_cygnus_data }, | |
349 | { .id = "BRCM5872", .driver_data = (kernel_ulong_t)&iproc_data }, | |
4f9833d3 JL |
350 | { .id = "BCM2847", .driver_data = (kernel_ulong_t)&bcm_arasan_data }, |
351 | { .id = "BRCME88C", .driver_data = (kernel_ulong_t)&bcm2711_data }, | |
7c7ba433 SM |
352 | { /* sentinel */ } |
353 | }; | |
354 | MODULE_DEVICE_TABLE(acpi, sdhci_iproc_acpi_ids); | |
edfa69de | 355 | #endif |
7c7ba433 | 356 | |
b580c52d SB |
357 | static int sdhci_iproc_probe(struct platform_device *pdev) |
358 | { | |
7c7ba433 SM |
359 | struct device *dev = &pdev->dev; |
360 | const struct sdhci_iproc_data *iproc_data = NULL; | |
b580c52d SB |
361 | struct sdhci_host *host; |
362 | struct sdhci_iproc_host *iproc_host; | |
363 | struct sdhci_pltfm_host *pltfm_host; | |
364 | int ret; | |
365 | ||
7c7ba433 SM |
366 | iproc_data = device_get_match_data(dev); |
367 | if (!iproc_data) | |
368 | return -ENODEV; | |
b580c52d SB |
369 | |
370 | host = sdhci_pltfm_init(pdev, iproc_data->pdata, sizeof(*iproc_host)); | |
371 | if (IS_ERR(host)) | |
372 | return PTR_ERR(host); | |
373 | ||
374 | pltfm_host = sdhci_priv(host); | |
375 | iproc_host = sdhci_pltfm_priv(pltfm_host); | |
376 | ||
377 | iproc_host->data = iproc_data; | |
378 | ||
2bd44dad SW |
379 | ret = mmc_of_parse(host->mmc); |
380 | if (ret) | |
381 | goto err; | |
382 | ||
7c7ba433 | 383 | sdhci_get_property(pdev); |
b580c52d | 384 | |
b17b4ab8 | 385 | host->mmc->caps |= iproc_host->data->mmc_caps; |
b580c52d | 386 | |
7c7ba433 SM |
387 | if (dev->of_node) { |
388 | pltfm_host->clk = devm_clk_get(dev, NULL); | |
389 | if (IS_ERR(pltfm_host->clk)) { | |
390 | ret = PTR_ERR(pltfm_host->clk); | |
391 | goto err; | |
392 | } | |
393 | ret = clk_prepare_enable(pltfm_host->clk); | |
394 | if (ret) { | |
395 | dev_err(dev, "failed to enable host clk\n"); | |
396 | goto err; | |
397 | } | |
9f24b0f2 | 398 | } |
b580c52d SB |
399 | |
400 | if (iproc_host->data->pdata->quirks & SDHCI_QUIRK_MISSING_CAPS) { | |
401 | host->caps = iproc_host->data->caps; | |
402 | host->caps1 = iproc_host->data->caps1; | |
403 | } | |
404 | ||
1d6ad057 SW |
405 | ret = sdhci_add_host(host); |
406 | if (ret) | |
9f24b0f2 | 407 | goto err_clk; |
1d6ad057 SW |
408 | |
409 | return 0; | |
b580c52d | 410 | |
9f24b0f2 | 411 | err_clk: |
7c7ba433 SM |
412 | if (dev->of_node) |
413 | clk_disable_unprepare(pltfm_host->clk); | |
b580c52d SB |
414 | err: |
415 | sdhci_pltfm_free(pdev); | |
416 | return ret; | |
417 | } | |
418 | ||
98b5ce4c AC |
419 | static void sdhci_iproc_shutdown(struct platform_device *pdev) |
420 | { | |
421 | sdhci_pltfm_suspend(&pdev->dev); | |
422 | } | |
423 | ||
b580c52d SB |
424 | static struct platform_driver sdhci_iproc_driver = { |
425 | .driver = { | |
426 | .name = "sdhci-iproc", | |
21b2cec6 | 427 | .probe_type = PROBE_PREFER_ASYNCHRONOUS, |
b580c52d | 428 | .of_match_table = sdhci_iproc_of_match, |
7c7ba433 | 429 | .acpi_match_table = ACPI_PTR(sdhci_iproc_acpi_ids), |
fa243f64 | 430 | .pm = &sdhci_pltfm_pmops, |
b580c52d SB |
431 | }, |
432 | .probe = sdhci_iproc_probe, | |
d1a13c5e | 433 | .remove = sdhci_pltfm_unregister, |
98b5ce4c | 434 | .shutdown = sdhci_iproc_shutdown, |
b580c52d SB |
435 | }; |
436 | module_platform_driver(sdhci_iproc_driver); | |
437 | ||
438 | MODULE_AUTHOR("Broadcom"); | |
439 | MODULE_DESCRIPTION("IPROC SDHCI driver"); | |
440 | MODULE_LICENSE("GPL v2"); |