1 // SPDX-License-Identifier: GPL-2.0-only
5 * Copyright (c) 2017-2020, Silicon Laboratories, Inc.
6 * Copyright (c) 2010, ST-Ericsson
8 #include <linux/module.h>
9 #include <linux/mmc/sdio.h>
10 #include <linux/mmc/sdio_func.h>
11 #include <linux/mmc/card.h>
12 #include <linux/interrupt.h>
14 #include <linux/of_irq.h>
15 #include <linux/irq.h>
16 #include <linux/align.h>
24 static const struct wfx_platform_data pdata_wf200
= {
25 .file_fw
= "wfx/wfm_wf200",
26 .file_pds
= "wfx/wf200.pds",
29 static const struct wfx_platform_data pdata_brd4001a
= {
30 .file_fw
= "wfx/wfm_wf200",
31 .file_pds
= "wfx/brd4001a.pds",
34 static const struct wfx_platform_data pdata_brd8022a
= {
35 .file_fw
= "wfx/wfm_wf200",
36 .file_pds
= "wfx/brd8022a.pds",
39 static const struct wfx_platform_data pdata_brd8023a
= {
40 .file_fw
= "wfx/wfm_wf200",
41 .file_pds
= "wfx/brd8023a.pds",
44 struct wfx_sdio_priv
{
45 struct sdio_func
*func
;
52 static int wfx_sdio_copy_from_io(void *priv
, unsigned int reg_id
, void *dst
, size_t count
)
54 struct wfx_sdio_priv
*bus
= priv
;
55 unsigned int sdio_addr
= reg_id
<< 2;
58 WARN(reg_id
> 7, "chip only has 7 registers");
59 WARN(!IS_ALIGNED((uintptr_t)dst
, 4), "unaligned buffer address");
60 WARN(!IS_ALIGNED(count
, 4), "unaligned buffer size");
62 /* Use queue mode buffers */
63 if (reg_id
== WFX_REG_IN_OUT_QUEUE
)
64 sdio_addr
|= (bus
->buf_id_rx
+ 1) << 7;
65 ret
= sdio_memcpy_fromio(bus
->func
, dst
, sdio_addr
, count
);
66 if (!ret
&& reg_id
== WFX_REG_IN_OUT_QUEUE
)
67 bus
->buf_id_rx
= (bus
->buf_id_rx
+ 1) % 4;
72 static int wfx_sdio_copy_to_io(void *priv
, unsigned int reg_id
, const void *src
, size_t count
)
74 struct wfx_sdio_priv
*bus
= priv
;
75 unsigned int sdio_addr
= reg_id
<< 2;
78 WARN(reg_id
> 7, "chip only has 7 registers");
79 WARN(!IS_ALIGNED((uintptr_t)src
, 4), "unaligned buffer address");
80 WARN(!IS_ALIGNED(count
, 4), "unaligned buffer size");
82 /* Use queue mode buffers */
83 if (reg_id
== WFX_REG_IN_OUT_QUEUE
)
84 sdio_addr
|= bus
->buf_id_tx
<< 7;
85 /* FIXME: discards 'const' qualifier for src */
86 ret
= sdio_memcpy_toio(bus
->func
, sdio_addr
, (void *)src
, count
);
87 if (!ret
&& reg_id
== WFX_REG_IN_OUT_QUEUE
)
88 bus
->buf_id_tx
= (bus
->buf_id_tx
+ 1) % 32;
93 static void wfx_sdio_lock(void *priv
)
95 struct wfx_sdio_priv
*bus
= priv
;
97 sdio_claim_host(bus
->func
);
100 static void wfx_sdio_unlock(void *priv
)
102 struct wfx_sdio_priv
*bus
= priv
;
104 sdio_release_host(bus
->func
);
107 static void wfx_sdio_irq_handler(struct sdio_func
*func
)
109 struct wfx_sdio_priv
*bus
= sdio_get_drvdata(func
);
111 wfx_bh_request_rx(bus
->core
);
114 static irqreturn_t
wfx_sdio_irq_handler_ext(int irq
, void *priv
)
116 struct wfx_sdio_priv
*bus
= priv
;
118 sdio_claim_host(bus
->func
);
119 wfx_bh_request_rx(bus
->core
);
120 sdio_release_host(bus
->func
);
124 static int wfx_sdio_irq_subscribe(void *priv
)
126 struct wfx_sdio_priv
*bus
= priv
;
132 sdio_claim_host(bus
->func
);
133 ret
= sdio_claim_irq(bus
->func
, wfx_sdio_irq_handler
);
134 sdio_release_host(bus
->func
);
138 flags
= irq_get_trigger_type(bus
->of_irq
);
140 flags
= IRQF_TRIGGER_HIGH
;
141 flags
|= IRQF_ONESHOT
;
142 ret
= devm_request_threaded_irq(&bus
->func
->dev
, bus
->of_irq
, NULL
,
143 wfx_sdio_irq_handler_ext
, flags
, "wfx", bus
);
146 sdio_claim_host(bus
->func
);
147 cccr
= sdio_f0_readb(bus
->func
, SDIO_CCCR_IENx
, NULL
);
149 cccr
|= BIT(bus
->func
->num
);
150 sdio_f0_writeb(bus
->func
, cccr
, SDIO_CCCR_IENx
, NULL
);
151 sdio_release_host(bus
->func
);
155 static int wfx_sdio_irq_unsubscribe(void *priv
)
157 struct wfx_sdio_priv
*bus
= priv
;
161 devm_free_irq(&bus
->func
->dev
, bus
->of_irq
, bus
);
162 sdio_claim_host(bus
->func
);
163 ret
= sdio_release_irq(bus
->func
);
164 sdio_release_host(bus
->func
);
168 static size_t wfx_sdio_align_size(void *priv
, size_t size
)
170 struct wfx_sdio_priv
*bus
= priv
;
172 return sdio_align_size(bus
->func
, size
);
175 static const struct wfx_hwbus_ops wfx_sdio_hwbus_ops
= {
176 .copy_from_io
= wfx_sdio_copy_from_io
,
177 .copy_to_io
= wfx_sdio_copy_to_io
,
178 .irq_subscribe
= wfx_sdio_irq_subscribe
,
179 .irq_unsubscribe
= wfx_sdio_irq_unsubscribe
,
180 .lock
= wfx_sdio_lock
,
181 .unlock
= wfx_sdio_unlock
,
182 .align_size
= wfx_sdio_align_size
,
185 static const struct of_device_id wfx_sdio_of_match
[] = {
186 { .compatible
= "silabs,wf200", .data
= &pdata_wf200
},
187 { .compatible
= "silabs,brd4001a", .data
= &pdata_brd4001a
},
188 { .compatible
= "silabs,brd8022a", .data
= &pdata_brd8022a
},
189 { .compatible
= "silabs,brd8023a", .data
= &pdata_brd8023a
},
192 MODULE_DEVICE_TABLE(of
, wfx_sdio_of_match
);
194 static int wfx_sdio_probe(struct sdio_func
*func
, const struct sdio_device_id
*id
)
196 const struct wfx_platform_data
*pdata
= of_device_get_match_data(&func
->dev
);
197 struct device_node
*np
= func
->dev
.of_node
;
198 struct wfx_sdio_priv
*bus
;
201 if (func
->num
!= 1) {
202 dev_err(&func
->dev
, "SDIO function number is %d while it should always be 1 (unsupported chip?)\n",
208 dev_warn(&func
->dev
, "no compatible device found in DT\n");
212 bus
= devm_kzalloc(&func
->dev
, sizeof(*bus
), GFP_KERNEL
);
217 bus
->of_irq
= irq_of_parse_and_map(np
, 0);
218 sdio_set_drvdata(func
, bus
);
220 sdio_claim_host(func
);
221 ret
= sdio_enable_func(func
);
222 /* Block of 64 bytes is more efficient than 512B for frame sizes < 4k */
223 sdio_set_block_size(func
, 64);
224 sdio_release_host(func
);
228 bus
->core
= wfx_init_common(&func
->dev
, pdata
, &wfx_sdio_hwbus_ops
, bus
);
234 ret
= wfx_probe(bus
->core
);
241 sdio_claim_host(func
);
242 sdio_disable_func(func
);
243 sdio_release_host(func
);
247 static void wfx_sdio_remove(struct sdio_func
*func
)
249 struct wfx_sdio_priv
*bus
= sdio_get_drvdata(func
);
251 wfx_release(bus
->core
);
252 sdio_claim_host(func
);
253 sdio_disable_func(func
);
254 sdio_release_host(func
);
257 static const struct sdio_device_id wfx_sdio_ids
[] = {
258 /* WF200 does not have official VID/PID */
259 { SDIO_DEVICE(0x0000, 0x1000) },
262 MODULE_DEVICE_TABLE(sdio
, wfx_sdio_ids
);
264 struct sdio_driver wfx_sdio_driver
= {
266 .id_table
= wfx_sdio_ids
,
267 .probe
= wfx_sdio_probe
,
268 .remove
= wfx_sdio_remove
,
270 .of_match_table
= wfx_sdio_of_match
,