File Index
Files and directories
Sign in
FILE

bus_sdio.c

File preview with syntax highlighting.

linux-6.19.14/drivers/net/wireless/silabs/wfx/bus_sdio.c C
  1// SPDX-License-Identifier: GPL-2.0-only
  2/*
  3 * SDIO interface.
  4 *
  5 * Copyright (c) 2017-2020, Silicon Laboratories, Inc.
  6 * Copyright (c) 2010, ST-Ericsson
  7 */
  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>
 13#include <linux/of.h>
 14#include <linux/of_irq.h>
 15#include <linux/irq.h>
 16#include <linux/align.h>
 17#include <linux/pm.h>
 18
 19#include "bus.h"
 20#include "wfx.h"
 21#include "hwio.h"
 22#include "main.h"
 23#include "bh.h"
 24
 25static const struct wfx_platform_data pdata_wf200 = {
 26	.file_fw = "wfx/wfm_wf200",
 27	.file_pds = "wfx/wf200.pds",
 28};
 29
 30static const struct wfx_platform_data pdata_brd4001a = {
 31	.file_fw = "wfx/wfm_wf200",
 32	.file_pds = "wfx/brd4001a.pds",
 33};
 34
 35static const struct wfx_platform_data pdata_brd8022a = {
 36	.file_fw = "wfx/wfm_wf200",
 37	.file_pds = "wfx/brd8022a.pds",
 38};
 39
 40static const struct wfx_platform_data pdata_brd8023a = {
 41	.file_fw = "wfx/wfm_wf200",
 42	.file_pds = "wfx/brd8023a.pds",
 43};
 44
 45struct wfx_sdio_priv {
 46	struct sdio_func *func;
 47	struct wfx_dev *core;
 48	u8 buf_id_tx;
 49	u8 buf_id_rx;
 50	int of_irq;
 51};
 52
 53static int wfx_sdio_copy_from_io(void *priv, unsigned int reg_id, void *dst, size_t count)
 54{
 55	struct wfx_sdio_priv *bus = priv;
 56	unsigned int sdio_addr = reg_id << 2;
 57	int ret;
 58
 59	WARN(reg_id > 7, "chip only has 7 registers");
 60	WARN(!IS_ALIGNED((uintptr_t)dst, 4), "unaligned buffer address");
 61	WARN(!IS_ALIGNED(count, 4), "unaligned buffer size");
 62
 63	/* Use queue mode buffers */
 64	if (reg_id == WFX_REG_IN_OUT_QUEUE)
 65		sdio_addr |= (bus->buf_id_rx + 1) << 7;
 66	ret = sdio_memcpy_fromio(bus->func, dst, sdio_addr, count);
 67	if (!ret && reg_id == WFX_REG_IN_OUT_QUEUE)
 68		bus->buf_id_rx = (bus->buf_id_rx + 1) % 4;
 69
 70	return ret;
 71}
 72
 73static int wfx_sdio_copy_to_io(void *priv, unsigned int reg_id, const void *src, size_t count)
 74{
 75	struct wfx_sdio_priv *bus = priv;
 76	unsigned int sdio_addr = reg_id << 2;
 77	int ret;
 78
 79	WARN(reg_id > 7, "chip only has 7 registers");
 80	WARN(!IS_ALIGNED((uintptr_t)src, 4), "unaligned buffer address");
 81	WARN(!IS_ALIGNED(count, 4), "unaligned buffer size");
 82
 83	/* Use queue mode buffers */
 84	if (reg_id == WFX_REG_IN_OUT_QUEUE)
 85		sdio_addr |= bus->buf_id_tx << 7;
 86	/* FIXME: discards 'const' qualifier for src */
 87	ret = sdio_memcpy_toio(bus->func, sdio_addr, (void *)src, count);
 88	if (!ret && reg_id == WFX_REG_IN_OUT_QUEUE)
 89		bus->buf_id_tx = (bus->buf_id_tx + 1) % 32;
 90
 91	return ret;
 92}
 93
 94static void wfx_sdio_lock(void *priv)
 95{
 96	struct wfx_sdio_priv *bus = priv;
 97
 98	sdio_claim_host(bus->func);
 99}
100
101static void wfx_sdio_unlock(void *priv)
102{
103	struct wfx_sdio_priv *bus = priv;
104
105	sdio_release_host(bus->func);
106}
107
108static void wfx_sdio_irq_handler(struct sdio_func *func)
109{
110	struct wfx_sdio_priv *bus = sdio_get_drvdata(func);
111
112	wfx_bh_request_rx(bus->core);
113}
114
115static irqreturn_t wfx_sdio_irq_handler_ext(int irq, void *priv)
116{
117	struct wfx_sdio_priv *bus = priv;
118
119	sdio_claim_host(bus->func);
120	wfx_bh_request_rx(bus->core);
121	sdio_release_host(bus->func);
122	return IRQ_HANDLED;
123}
124
125static int wfx_sdio_irq_subscribe(void *priv)
126{
127	struct wfx_sdio_priv *bus = priv;
128	u32 flags;
129	int ret;
130	u8 cccr;
131
132	if (!bus->of_irq) {
133		sdio_claim_host(bus->func);
134		ret = sdio_claim_irq(bus->func, wfx_sdio_irq_handler);
135		sdio_release_host(bus->func);
136		return ret;
137	}
138
139	flags = irq_get_trigger_type(bus->of_irq);
140	if (!flags)
141		flags = IRQF_TRIGGER_HIGH;
142	flags |= IRQF_ONESHOT;
143	ret = devm_request_threaded_irq(&bus->func->dev, bus->of_irq, NULL,
144					wfx_sdio_irq_handler_ext, flags, "wfx", bus);
145	if (ret)
146		return ret;
147	sdio_claim_host(bus->func);
148	cccr = sdio_f0_readb(bus->func, SDIO_CCCR_IENx, NULL);
149	cccr |= BIT(0);
150	cccr |= BIT(bus->func->num);
151	sdio_f0_writeb(bus->func, cccr, SDIO_CCCR_IENx, NULL);
152	sdio_release_host(bus->func);
153	return 0;
154}
155
156static int wfx_sdio_irq_unsubscribe(void *priv)
157{
158	struct wfx_sdio_priv *bus = priv;
159	int ret;
160
161	if (bus->of_irq)
162		devm_free_irq(&bus->func->dev, bus->of_irq, bus);
163	sdio_claim_host(bus->func);
164	ret = sdio_release_irq(bus->func);
165	sdio_release_host(bus->func);
166	return ret;
167}
168
169static size_t wfx_sdio_align_size(void *priv, size_t size)
170{
171	struct wfx_sdio_priv *bus = priv;
172
173	return sdio_align_size(bus->func, size);
174}
175
176static void wfx_sdio_set_wakeup(void *priv, bool enabled)
177{
178	struct wfx_sdio_priv *bus = priv;
179
180	device_set_wakeup_enable(&bus->func->dev, enabled);
181}
182
183static const struct wfx_hwbus_ops wfx_sdio_hwbus_ops = {
184	.copy_from_io    = wfx_sdio_copy_from_io,
185	.copy_to_io      = wfx_sdio_copy_to_io,
186	.irq_subscribe   = wfx_sdio_irq_subscribe,
187	.irq_unsubscribe = wfx_sdio_irq_unsubscribe,
188	.lock            = wfx_sdio_lock,
189	.unlock          = wfx_sdio_unlock,
190	.align_size      = wfx_sdio_align_size,
191	.set_wakeup      = wfx_sdio_set_wakeup,
192};
193
194static const struct of_device_id wfx_sdio_of_match[] = {
195	{ .compatible = "silabs,wf200",    .data = &pdata_wf200 },
196	{ .compatible = "silabs,brd4001a", .data = &pdata_brd4001a },
197	{ .compatible = "silabs,brd8022a", .data = &pdata_brd8022a },
198	{ .compatible = "silabs,brd8023a", .data = &pdata_brd8023a },
199	{ },
200};
201MODULE_DEVICE_TABLE(of, wfx_sdio_of_match);
202
203static int wfx_sdio_suspend(struct device *dev)
204{
205	struct sdio_func *func = dev_to_sdio_func(dev);
206	struct wfx_sdio_priv *bus = sdio_get_drvdata(func);
207	int ret;
208
209	if (!device_may_wakeup(dev))
210		return 0;
211
212	flush_work(&bus->core->hif.bh);
213	/* Either "wakeup-source" attribute or out-of-band IRQ is required for
214	 * WoWLAN
215	 */
216	if (bus->of_irq) {
217		ret = enable_irq_wake(bus->of_irq);
218		if (ret)
219			return ret;
220	} else {
221		ret = sdio_set_host_pm_flags(func, MMC_PM_WAKE_SDIO_IRQ);
222		if (ret)
223			return ret;
224	}
225	return sdio_set_host_pm_flags(func, MMC_PM_KEEP_POWER);
226}
227
228static int wfx_sdio_resume(struct device *dev)
229{
230	struct sdio_func *func = dev_to_sdio_func(dev);
231	struct wfx_sdio_priv *bus = sdio_get_drvdata(func);
232
233	if (!device_may_wakeup(dev))
234		return 0;
235	if (bus->of_irq)
236		return disable_irq_wake(bus->of_irq);
237	else
238		return 0;
239}
240
241static int wfx_sdio_probe(struct sdio_func *func, const struct sdio_device_id *id)
242{
243	const struct wfx_platform_data *pdata = of_device_get_match_data(&func->dev);
244	mmc_pm_flag_t pm_flag = sdio_get_host_pm_caps(func);
245	struct device_node *np = func->dev.of_node;
246	struct wfx_sdio_priv *bus;
247	int ret;
248
249	if (func->num != 1) {
250		dev_err(&func->dev, "SDIO function number is %d while it should always be 1 (unsupported chip?)\n",
251			func->num);
252		return -ENODEV;
253	}
254
255	if (!pdata) {
256		dev_warn(&func->dev, "no compatible device found in DT\n");
257		return -ENODEV;
258	}
259
260	bus = devm_kzalloc(&func->dev, sizeof(*bus), GFP_KERNEL);
261	if (!bus)
262		return -ENOMEM;
263
264	bus->func = func;
265	bus->of_irq = irq_of_parse_and_map(np, 0);
266	sdio_set_drvdata(func, bus);
267
268	sdio_claim_host(func);
269	ret = sdio_enable_func(func);
270	/* Block of 64 bytes is more efficient than 512B for frame sizes < 4k */
271	sdio_set_block_size(func, 64);
272	sdio_release_host(func);
273	if (ret)
274		return ret;
275
276	bus->core = wfx_init_common(&func->dev, pdata, &wfx_sdio_hwbus_ops, bus);
277	if (!bus->core) {
278		ret = -EIO;
279		goto sdio_release;
280	}
281
282	ret = wfx_probe(bus->core);
283	if (ret)
284		goto sdio_release;
285
286	if (pm_flag & MMC_PM_KEEP_POWER)
287		device_set_wakeup_capable(&func->dev, true);
288
289	return 0;
290
291sdio_release:
292	sdio_claim_host(func);
293	sdio_disable_func(func);
294	sdio_release_host(func);
295	return ret;
296}
297
298static void wfx_sdio_remove(struct sdio_func *func)
299{
300	struct wfx_sdio_priv *bus = sdio_get_drvdata(func);
301
302	wfx_release(bus->core);
303	sdio_claim_host(func);
304	sdio_disable_func(func);
305	sdio_release_host(func);
306}
307
308static const struct sdio_device_id wfx_sdio_ids[] = {
309	/* WF200 does not have official VID/PID */
310	{ SDIO_DEVICE(0x0000, 0x1000) },
311	{ },
312};
313MODULE_DEVICE_TABLE(sdio, wfx_sdio_ids);
314
315static DEFINE_SIMPLE_DEV_PM_OPS(wfx_sdio_pm_ops, wfx_sdio_suspend, wfx_sdio_resume);
316
317struct sdio_driver wfx_sdio_driver = {
318	.name = "wfx-sdio",
319	.id_table = wfx_sdio_ids,
320	.probe = wfx_sdio_probe,
321	.remove = wfx_sdio_remove,
322	.drv = {
323		.of_match_table = wfx_sdio_of_match,
324		.pm = &wfx_sdio_pm_ops,
325	}
326};
~/ Read-only file preview.