xref: /linux/drivers/net/wireless/morsemicro/mm81x/sdio.c (revision 91ec2035134982b98fab0609a9fd8480e8217dc1)
1*b1906ceaSLachlan Hodges // SPDX-License-Identifier: GPL-2.0-only
2*b1906ceaSLachlan Hodges /*
3*b1906ceaSLachlan Hodges  * Copyright (c) 2017-2026 Morse Micro
4*b1906ceaSLachlan Hodges  */
5*b1906ceaSLachlan Hodges #include <linux/kernel.h>
6*b1906ceaSLachlan Hodges #include <linux/module.h>
7*b1906ceaSLachlan Hodges #include <linux/slab.h>
8*b1906ceaSLachlan Hodges #include <linux/workqueue.h>
9*b1906ceaSLachlan Hodges #include <linux/mmc/card.h>
10*b1906ceaSLachlan Hodges #include <linux/mmc/mmc.h>
11*b1906ceaSLachlan Hodges #include <linux/mmc/host.h>
12*b1906ceaSLachlan Hodges #include <linux/mmc/sdio_func.h>
13*b1906ceaSLachlan Hodges #include <linux/mmc/sdio_ids.h>
14*b1906ceaSLachlan Hodges #include <linux/mmc/sdio.h>
15*b1906ceaSLachlan Hodges #include <linux/mmc/sd.h>
16*b1906ceaSLachlan Hodges #include "hw.h"
17*b1906ceaSLachlan Hodges #include "core.h"
18*b1906ceaSLachlan Hodges #include "bus.h"
19*b1906ceaSLachlan Hodges #include "mac.h"
20*b1906ceaSLachlan Hodges #include "fw.h"
21*b1906ceaSLachlan Hodges #include "hif.h"
22*b1906ceaSLachlan Hodges 
23*b1906ceaSLachlan Hodges /*
24*b1906ceaSLachlan Hodges  * Value to indicate that the base address for bulk/register
25*b1906ceaSLachlan Hodges  * read/writes has yet to be set
26*b1906ceaSLachlan Hodges  */
27*b1906ceaSLachlan Hodges #define MM81X_SDIO_BASE_ADDR_UNSET 0xFFFFFFFF
28*b1906ceaSLachlan Hodges 
29*b1906ceaSLachlan Hodges #define MM81X_SDIO_ALIGNMENT (8)
30*b1906ceaSLachlan Hodges 
31*b1906ceaSLachlan Hodges #define MM81X_SDIO_REG_ADDRESS_BASE 0x10000
32*b1906ceaSLachlan Hodges #define MM81X_SDIO_REG_ADDRESS_WINDOW_0 MM81X_SDIO_REG_ADDRESS_BASE
33*b1906ceaSLachlan Hodges #define MM81X_SDIO_REG_ADDRESS_WINDOW_1 (MM81X_SDIO_REG_ADDRESS_BASE + 1)
34*b1906ceaSLachlan Hodges #define MM81X_SDIO_REG_ADDRESS_CONFIG (MM81X_SDIO_REG_ADDRESS_BASE + 2)
35*b1906ceaSLachlan Hodges 
36*b1906ceaSLachlan Hodges struct mm81x_sdio {
37*b1906ceaSLachlan Hodges 	bool enabled;
38*b1906ceaSLachlan Hodges 	u32 bulk_addr_base;
39*b1906ceaSLachlan Hodges 	u32 register_addr_base;
40*b1906ceaSLachlan Hodges 	struct sdio_func *func;
41*b1906ceaSLachlan Hodges 	const struct sdio_device_id *id;
42*b1906ceaSLachlan Hodges };
43*b1906ceaSLachlan Hodges 
irq_handler(struct sdio_func * func1)44*b1906ceaSLachlan Hodges static void irq_handler(struct sdio_func *func1)
45*b1906ceaSLachlan Hodges {
46*b1906ceaSLachlan Hodges 	struct sdio_func *func = func1->card->sdio_func[1];
47*b1906ceaSLachlan Hodges 	struct mm81x *mors = sdio_get_drvdata(func);
48*b1906ceaSLachlan Hodges 
49*b1906ceaSLachlan Hodges 	mm81x_hw_irq_handle(mors);
50*b1906ceaSLachlan Hodges }
51*b1906ceaSLachlan Hodges 
mm81x_sdio_enable_irq(struct mm81x_sdio * sdio)52*b1906ceaSLachlan Hodges static int mm81x_sdio_enable_irq(struct mm81x_sdio *sdio)
53*b1906ceaSLachlan Hodges {
54*b1906ceaSLachlan Hodges 	int ret;
55*b1906ceaSLachlan Hodges 	struct sdio_func *func = sdio->func;
56*b1906ceaSLachlan Hodges 	struct sdio_func *func1 = func->card->sdio_func[0];
57*b1906ceaSLachlan Hodges 	struct mm81x *mors = sdio_get_drvdata(func);
58*b1906ceaSLachlan Hodges 
59*b1906ceaSLachlan Hodges 	sdio_claim_host(func);
60*b1906ceaSLachlan Hodges 	ret = sdio_claim_irq(func1, irq_handler);
61*b1906ceaSLachlan Hodges 	if (ret)
62*b1906ceaSLachlan Hodges 		dev_err(mors->dev, "Failed to enable sdio irq: %d\n", ret);
63*b1906ceaSLachlan Hodges 
64*b1906ceaSLachlan Hodges 	sdio_release_host(func);
65*b1906ceaSLachlan Hodges 	return ret;
66*b1906ceaSLachlan Hodges }
67*b1906ceaSLachlan Hodges 
mm81x_sdio_disable_irq(struct mm81x_sdio * sdio)68*b1906ceaSLachlan Hodges static void mm81x_sdio_disable_irq(struct mm81x_sdio *sdio)
69*b1906ceaSLachlan Hodges {
70*b1906ceaSLachlan Hodges 	struct sdio_func *func = sdio->func;
71*b1906ceaSLachlan Hodges 	struct sdio_func *func1 = func->card->sdio_func[0];
72*b1906ceaSLachlan Hodges 
73*b1906ceaSLachlan Hodges 	sdio_claim_host(func);
74*b1906ceaSLachlan Hodges 	sdio_release_irq(func1);
75*b1906ceaSLachlan Hodges 	sdio_release_host(func);
76*b1906ceaSLachlan Hodges }
77*b1906ceaSLachlan Hodges 
mm81x_sdio_set_irq(struct mm81x * mors,bool enable)78*b1906ceaSLachlan Hodges static void mm81x_sdio_set_irq(struct mm81x *mors, bool enable)
79*b1906ceaSLachlan Hodges {
80*b1906ceaSLachlan Hodges 	struct mm81x_sdio *sdio = (struct mm81x_sdio *)mors->drv_priv;
81*b1906ceaSLachlan Hodges 
82*b1906ceaSLachlan Hodges 	if (enable)
83*b1906ceaSLachlan Hodges 		mm81x_sdio_enable_irq(sdio);
84*b1906ceaSLachlan Hodges 	else
85*b1906ceaSLachlan Hodges 		mm81x_sdio_disable_irq(sdio);
86*b1906ceaSLachlan Hodges }
87*b1906ceaSLachlan Hodges 
mm81x_sdio_calculate_base_address(u32 address,u8 access)88*b1906ceaSLachlan Hodges static u32 mm81x_sdio_calculate_base_address(u32 address, u8 access)
89*b1906ceaSLachlan Hodges {
90*b1906ceaSLachlan Hodges 	return (address & MM81X_SDIO_RW_ADDR_BOUNDARY_MASK) | (access & 0x3);
91*b1906ceaSLachlan Hodges }
92*b1906ceaSLachlan Hodges 
mm81x_sdio_reset_base_address(struct mm81x_sdio * sdio)93*b1906ceaSLachlan Hodges static void mm81x_sdio_reset_base_address(struct mm81x_sdio *sdio)
94*b1906ceaSLachlan Hodges {
95*b1906ceaSLachlan Hodges 	sdio->bulk_addr_base = MM81X_SDIO_BASE_ADDR_UNSET;
96*b1906ceaSLachlan Hodges 	sdio->register_addr_base = MM81X_SDIO_BASE_ADDR_UNSET;
97*b1906ceaSLachlan Hodges }
98*b1906ceaSLachlan Hodges 
mm81x_sdio_set_func_address_base(struct mm81x_sdio * sdio,struct sdio_func * func,u32 address,u8 access)99*b1906ceaSLachlan Hodges static int mm81x_sdio_set_func_address_base(struct mm81x_sdio *sdio,
100*b1906ceaSLachlan Hodges 					    struct sdio_func *func, u32 address,
101*b1906ceaSLachlan Hodges 					    u8 access)
102*b1906ceaSLachlan Hodges {
103*b1906ceaSLachlan Hodges 	int ret = 0;
104*b1906ceaSLachlan Hodges 	int retries = 0;
105*b1906ceaSLachlan Hodges 	static const int max_retries = 3;
106*b1906ceaSLachlan Hodges 	struct sdio_func *func2 = sdio->func;
107*b1906ceaSLachlan Hodges 	struct mm81x *mors = sdio_get_drvdata(sdio->func);
108*b1906ceaSLachlan Hodges 	s32 calculated_addr_base =
109*b1906ceaSLachlan Hodges 		mm81x_sdio_calculate_base_address(address, access);
110*b1906ceaSLachlan Hodges 	u32 *current_addr_base = func == func2 ? &sdio->bulk_addr_base :
111*b1906ceaSLachlan Hodges 						 &sdio->register_addr_base;
112*b1906ceaSLachlan Hodges 
113*b1906ceaSLachlan Hodges 	if ((*current_addr_base) == calculated_addr_base &&
114*b1906ceaSLachlan Hodges 	    *current_addr_base != MM81X_SDIO_BASE_ADDR_UNSET)
115*b1906ceaSLachlan Hodges 		return ret;
116*b1906ceaSLachlan Hodges 
117*b1906ceaSLachlan Hodges retry:
118*b1906ceaSLachlan Hodges 	sdio_writeb(func, (u8)u32_get_bits(address, GENMASK(23, 16)),
119*b1906ceaSLachlan Hodges 		    MM81X_SDIO_REG_ADDRESS_WINDOW_0, &ret);
120*b1906ceaSLachlan Hodges 	if (ret)
121*b1906ceaSLachlan Hodges 		goto err;
122*b1906ceaSLachlan Hodges 
123*b1906ceaSLachlan Hodges 	sdio_writeb(func, (u8)u32_get_bits(address, GENMASK(31, 24)),
124*b1906ceaSLachlan Hodges 		    MM81X_SDIO_REG_ADDRESS_WINDOW_1, &ret);
125*b1906ceaSLachlan Hodges 	if (ret)
126*b1906ceaSLachlan Hodges 		goto err;
127*b1906ceaSLachlan Hodges 
128*b1906ceaSLachlan Hodges 	sdio_writeb(func, access & 0x3, MM81X_SDIO_REG_ADDRESS_CONFIG, &ret);
129*b1906ceaSLachlan Hodges 	if (ret)
130*b1906ceaSLachlan Hodges 		goto err;
131*b1906ceaSLachlan Hodges 
132*b1906ceaSLachlan Hodges 	*current_addr_base = calculated_addr_base;
133*b1906ceaSLachlan Hodges 	if (retries)
134*b1906ceaSLachlan Hodges 		dev_dbg(mors->dev, "%s succeeded after %d retries\n", __func__,
135*b1906ceaSLachlan Hodges 			retries);
136*b1906ceaSLachlan Hodges 
137*b1906ceaSLachlan Hodges 	return ret;
138*b1906ceaSLachlan Hodges err:
139*b1906ceaSLachlan Hodges 	retries++;
140*b1906ceaSLachlan Hodges 	if (ret == -ETIMEDOUT && retries <= max_retries) {
141*b1906ceaSLachlan Hodges 		dev_dbg(mors->dev, "%s failed (%d), retrying (%d/%d)\n",
142*b1906ceaSLachlan Hodges 			__func__, ret, retries, max_retries);
143*b1906ceaSLachlan Hodges 		goto retry;
144*b1906ceaSLachlan Hodges 	}
145*b1906ceaSLachlan Hodges 
146*b1906ceaSLachlan Hodges 	*current_addr_base = MM81X_SDIO_BASE_ADDR_UNSET;
147*b1906ceaSLachlan Hodges 	return ret;
148*b1906ceaSLachlan Hodges }
149*b1906ceaSLachlan Hodges 
mm81x_sdio_mem_write_block(struct mm81x_sdio * sdio,u32 address,u8 * data,ssize_t size)150*b1906ceaSLachlan Hodges static int mm81x_sdio_mem_write_block(struct mm81x_sdio *sdio, u32 address,
151*b1906ceaSLachlan Hodges 				      u8 *data, ssize_t size)
152*b1906ceaSLachlan Hodges {
153*b1906ceaSLachlan Hodges 	int ret;
154*b1906ceaSLachlan Hodges 	struct sdio_func *func2 = sdio->func;
155*b1906ceaSLachlan Hodges 	struct mm81x *mors = sdio_get_drvdata(sdio->func);
156*b1906ceaSLachlan Hodges 
157*b1906ceaSLachlan Hodges 	mm81x_sdio_set_func_address_base(sdio, func2, address,
158*b1906ceaSLachlan Hodges 					 MM81X_CONFIG_ACCESS_4BYTE);
159*b1906ceaSLachlan Hodges 	if (unlikely(!IS_ALIGNED((uintptr_t)data,
160*b1906ceaSLachlan Hodges 				 mors->bus_ops->bulk_alignment))) {
161*b1906ceaSLachlan Hodges 		ret = -EBADE;
162*b1906ceaSLachlan Hodges 		goto exit;
163*b1906ceaSLachlan Hodges 	}
164*b1906ceaSLachlan Hodges 
165*b1906ceaSLachlan Hodges 	address &= 0x0000FFFF; /* remove base and keep offset */
166*b1906ceaSLachlan Hodges 	ret = sdio_memcpy_toio(func2, address, data, size);
167*b1906ceaSLachlan Hodges 	if (ret)
168*b1906ceaSLachlan Hodges 		goto exit;
169*b1906ceaSLachlan Hodges 
170*b1906ceaSLachlan Hodges 	ret = size;
171*b1906ceaSLachlan Hodges exit:
172*b1906ceaSLachlan Hodges 	return ret;
173*b1906ceaSLachlan Hodges }
174*b1906ceaSLachlan Hodges 
mm81x_sdio_mem_write_byte(struct mm81x_sdio * sdio,u32 address,u8 * data,ssize_t size)175*b1906ceaSLachlan Hodges static int mm81x_sdio_mem_write_byte(struct mm81x_sdio *sdio, u32 address,
176*b1906ceaSLachlan Hodges 				     u8 *data, ssize_t size)
177*b1906ceaSLachlan Hodges {
178*b1906ceaSLachlan Hodges 	int i, ret;
179*b1906ceaSLachlan Hodges 	struct sdio_func *func1 = sdio->func->card->sdio_func[0];
180*b1906ceaSLachlan Hodges 
181*b1906ceaSLachlan Hodges 	mm81x_sdio_set_func_address_base(sdio, func1, address,
182*b1906ceaSLachlan Hodges 					 MM81X_CONFIG_ACCESS_1BYTE);
183*b1906ceaSLachlan Hodges 
184*b1906ceaSLachlan Hodges 	address &= 0x0000FFFF; /* remove base and keep offset */
185*b1906ceaSLachlan Hodges 	for (i = 0; i < size; i++) {
186*b1906ceaSLachlan Hodges 		sdio_writeb(func1, data[i], address + i, (int *)&ret);
187*b1906ceaSLachlan Hodges 		if (ret)
188*b1906ceaSLachlan Hodges 			goto exit;
189*b1906ceaSLachlan Hodges 	}
190*b1906ceaSLachlan Hodges 
191*b1906ceaSLachlan Hodges 	ret = size;
192*b1906ceaSLachlan Hodges exit:
193*b1906ceaSLachlan Hodges 	return ret;
194*b1906ceaSLachlan Hodges }
195*b1906ceaSLachlan Hodges 
mm81x_sdio_claim_host(struct mm81x * mors)196*b1906ceaSLachlan Hodges static void mm81x_sdio_claim_host(struct mm81x *mors)
197*b1906ceaSLachlan Hodges {
198*b1906ceaSLachlan Hodges 	struct mm81x_sdio *sdio = (struct mm81x_sdio *)mors->drv_priv;
199*b1906ceaSLachlan Hodges 	struct sdio_func *func = sdio->func;
200*b1906ceaSLachlan Hodges 
201*b1906ceaSLachlan Hodges 	sdio_claim_host(func);
202*b1906ceaSLachlan Hodges }
203*b1906ceaSLachlan Hodges 
mm81x_sdio_release_host(struct mm81x * mors)204*b1906ceaSLachlan Hodges static void mm81x_sdio_release_host(struct mm81x *mors)
205*b1906ceaSLachlan Hodges {
206*b1906ceaSLachlan Hodges 	struct mm81x_sdio *sdio = (struct mm81x_sdio *)mors->drv_priv;
207*b1906ceaSLachlan Hodges 	struct sdio_func *func = sdio->func;
208*b1906ceaSLachlan Hodges 
209*b1906ceaSLachlan Hodges 	sdio_release_host(func);
210*b1906ceaSLachlan Hodges }
211*b1906ceaSLachlan Hodges 
mm81x_sdio_mem_read_block(struct mm81x_sdio * sdio,u32 address,u8 * data,ssize_t size)212*b1906ceaSLachlan Hodges static int mm81x_sdio_mem_read_block(struct mm81x_sdio *sdio, u32 address,
213*b1906ceaSLachlan Hodges 				     u8 *data, ssize_t size)
214*b1906ceaSLachlan Hodges {
215*b1906ceaSLachlan Hodges 	int ret;
216*b1906ceaSLachlan Hodges 	struct sdio_func *func2 = sdio->func;
217*b1906ceaSLachlan Hodges 	struct mm81x *mors = sdio_get_drvdata(sdio->func);
218*b1906ceaSLachlan Hodges 
219*b1906ceaSLachlan Hodges 	mm81x_sdio_set_func_address_base(sdio, func2, address,
220*b1906ceaSLachlan Hodges 					 MM81X_CONFIG_ACCESS_4BYTE);
221*b1906ceaSLachlan Hodges 	if (unlikely(!IS_ALIGNED((uintptr_t)data,
222*b1906ceaSLachlan Hodges 				 mors->bus_ops->bulk_alignment))) {
223*b1906ceaSLachlan Hodges 		ret = -EBADE;
224*b1906ceaSLachlan Hodges 		goto exit;
225*b1906ceaSLachlan Hodges 	}
226*b1906ceaSLachlan Hodges 
227*b1906ceaSLachlan Hodges 	address &= 0x0000FFFF; /* remove base and keep offset */
228*b1906ceaSLachlan Hodges 	ret = sdio_memcpy_fromio(func2, data, address, size);
229*b1906ceaSLachlan Hodges 	if (ret)
230*b1906ceaSLachlan Hodges 		goto exit;
231*b1906ceaSLachlan Hodges 
232*b1906ceaSLachlan Hodges 	/*
233*b1906ceaSLachlan Hodges 	 * Observed sometimes that SDIO read repeats the first 4-bytes
234*b1906ceaSLachlan Hodges 	 * word twice, overwriting second word (hence, tail will be
235*b1906ceaSLachlan Hodges 	 * overwritten with 'sync' byte). When this happens, reading
236*b1906ceaSLachlan Hodges 	 * will fetch the correct word. NB: if repeated again, pass it
237*b1906ceaSLachlan Hodges 	 * anyway and upper layers will handle it
238*b1906ceaSLachlan Hodges 	 */
239*b1906ceaSLachlan Hodges 
240*b1906ceaSLachlan Hodges 	if (size >= 8 && memcmp(data, data + 4, 4) == 0)
241*b1906ceaSLachlan Hodges 		sdio_memcpy_fromio(func2, data, address, 8);
242*b1906ceaSLachlan Hodges 
243*b1906ceaSLachlan Hodges 	ret = size;
244*b1906ceaSLachlan Hodges exit:
245*b1906ceaSLachlan Hodges 	return ret;
246*b1906ceaSLachlan Hodges }
247*b1906ceaSLachlan Hodges 
mm81x_sdio_mem_read_byte(struct mm81x_sdio * sdio,u32 address,u8 * data,ssize_t size)248*b1906ceaSLachlan Hodges static int mm81x_sdio_mem_read_byte(struct mm81x_sdio *sdio, u32 address,
249*b1906ceaSLachlan Hodges 				    u8 *data, ssize_t size)
250*b1906ceaSLachlan Hodges {
251*b1906ceaSLachlan Hodges 	int i, ret;
252*b1906ceaSLachlan Hodges 	struct sdio_func *func1 = sdio->func->card->sdio_func[0];
253*b1906ceaSLachlan Hodges 
254*b1906ceaSLachlan Hodges 	mm81x_sdio_set_func_address_base(sdio, func1, address,
255*b1906ceaSLachlan Hodges 					 MM81X_CONFIG_ACCESS_1BYTE);
256*b1906ceaSLachlan Hodges 
257*b1906ceaSLachlan Hodges 	address &= 0x0000FFFF; /* remove base and keep offset */
258*b1906ceaSLachlan Hodges 	for (i = 0; i < size; i++) {
259*b1906ceaSLachlan Hodges 		data[i] = sdio_readb(func1, address + i, (int *)&ret);
260*b1906ceaSLachlan Hodges 		if (ret)
261*b1906ceaSLachlan Hodges 			goto exit;
262*b1906ceaSLachlan Hodges 	}
263*b1906ceaSLachlan Hodges 
264*b1906ceaSLachlan Hodges 	ret = size;
265*b1906ceaSLachlan Hodges exit:
266*b1906ceaSLachlan Hodges 	return ret;
267*b1906ceaSLachlan Hodges }
268*b1906ceaSLachlan Hodges 
mm81x_sdio_dm_write(struct mm81x * mors,u32 address,const u8 * data,int len)269*b1906ceaSLachlan Hodges static int mm81x_sdio_dm_write(struct mm81x *mors, u32 address, const u8 *data,
270*b1906ceaSLachlan Hodges 			       int len)
271*b1906ceaSLachlan Hodges {
272*b1906ceaSLachlan Hodges 	int ret = 0;
273*b1906ceaSLachlan Hodges 	int block_len, byte_len;
274*b1906ceaSLachlan Hodges 	struct mm81x_sdio *sdio = (struct mm81x_sdio *)mors->drv_priv;
275*b1906ceaSLachlan Hodges 	int remaining = len;
276*b1906ceaSLachlan Hodges 	int offset = 0;
277*b1906ceaSLachlan Hodges 
278*b1906ceaSLachlan Hodges 	if (remaining > 0 && address & 0x3) {
279*b1906ceaSLachlan Hodges 		len = 4 - (address & 0x3);
280*b1906ceaSLachlan Hodges 		ret = mm81x_sdio_mem_write_byte(sdio, address, (u8 *)data, len);
281*b1906ceaSLachlan Hodges 		if (ret != len)
282*b1906ceaSLachlan Hodges 			return -EIO;
283*b1906ceaSLachlan Hodges 
284*b1906ceaSLachlan Hodges 		offset += len;
285*b1906ceaSLachlan Hodges 		remaining -= len;
286*b1906ceaSLachlan Hodges 	}
287*b1906ceaSLachlan Hodges 
288*b1906ceaSLachlan Hodges 	while ((remaining) > 0) {
289*b1906ceaSLachlan Hodges 		/*
290*b1906ceaSLachlan Hodges 		 * We can only write up to the end of a single window in
291*b1906ceaSLachlan Hodges 		 * each write operation.
292*b1906ceaSLachlan Hodges 		 */
293*b1906ceaSLachlan Hodges 		u32 window_end = (address + offset) |
294*b1906ceaSLachlan Hodges 				 ~MM81X_SDIO_RW_ADDR_BOUNDARY_MASK;
295*b1906ceaSLachlan Hodges 
296*b1906ceaSLachlan Hodges 		len = min(remaining, (int)(window_end + 1 - address - offset));
297*b1906ceaSLachlan Hodges 		block_len = len & ~0x3;
298*b1906ceaSLachlan Hodges 		byte_len = len & 0x3;
299*b1906ceaSLachlan Hodges 
300*b1906ceaSLachlan Hodges 		if (block_len) {
301*b1906ceaSLachlan Hodges 			ret = mm81x_sdio_mem_write_block(sdio, address + offset,
302*b1906ceaSLachlan Hodges 							 (u8 *)(data + offset),
303*b1906ceaSLachlan Hodges 							 block_len);
304*b1906ceaSLachlan Hodges 			if (ret != block_len)
305*b1906ceaSLachlan Hodges 				return -EIO;
306*b1906ceaSLachlan Hodges 
307*b1906ceaSLachlan Hodges 			offset += block_len;
308*b1906ceaSLachlan Hodges 		}
309*b1906ceaSLachlan Hodges 
310*b1906ceaSLachlan Hodges 		if (byte_len) {
311*b1906ceaSLachlan Hodges 			ret = mm81x_sdio_mem_write_byte(sdio, address + offset,
312*b1906ceaSLachlan Hodges 							(u8 *)(data + offset),
313*b1906ceaSLachlan Hodges 							byte_len);
314*b1906ceaSLachlan Hodges 			if (ret != byte_len)
315*b1906ceaSLachlan Hodges 				return -EIO;
316*b1906ceaSLachlan Hodges 
317*b1906ceaSLachlan Hodges 			offset += byte_len;
318*b1906ceaSLachlan Hodges 		}
319*b1906ceaSLachlan Hodges 
320*b1906ceaSLachlan Hodges 		remaining -= len;
321*b1906ceaSLachlan Hodges 	}
322*b1906ceaSLachlan Hodges 
323*b1906ceaSLachlan Hodges 	return 0;
324*b1906ceaSLachlan Hodges }
325*b1906ceaSLachlan Hodges 
mm81x_sdio_dm_read(struct mm81x * mors,u32 address,u8 * data,int len)326*b1906ceaSLachlan Hodges static int mm81x_sdio_dm_read(struct mm81x *mors, u32 address, u8 *data,
327*b1906ceaSLachlan Hodges 			      int len)
328*b1906ceaSLachlan Hodges {
329*b1906ceaSLachlan Hodges 	int ret = 0;
330*b1906ceaSLachlan Hodges 	int block_len, byte_len;
331*b1906ceaSLachlan Hodges 	struct mm81x_sdio *sdio = (struct mm81x_sdio *)mors->drv_priv;
332*b1906ceaSLachlan Hodges 	int remaining = len;
333*b1906ceaSLachlan Hodges 	int offset = 0;
334*b1906ceaSLachlan Hodges 
335*b1906ceaSLachlan Hodges 	if (remaining > 0 && address & 0x3) {
336*b1906ceaSLachlan Hodges 		len = 4 - (address & 0x3);
337*b1906ceaSLachlan Hodges 		ret = mm81x_sdio_mem_read_byte(sdio, address, data, len);
338*b1906ceaSLachlan Hodges 		if (ret != len)
339*b1906ceaSLachlan Hodges 			return -EIO;
340*b1906ceaSLachlan Hodges 
341*b1906ceaSLachlan Hodges 		offset += len;
342*b1906ceaSLachlan Hodges 		remaining -= len;
343*b1906ceaSLachlan Hodges 	}
344*b1906ceaSLachlan Hodges 
345*b1906ceaSLachlan Hodges 	while (remaining > 0) {
346*b1906ceaSLachlan Hodges 		/*
347*b1906ceaSLachlan Hodges 		 * We can only read up to the end of a single window in
348*b1906ceaSLachlan Hodges 		 * each read operation.
349*b1906ceaSLachlan Hodges 		 */
350*b1906ceaSLachlan Hodges 		u32 window_end = (address + offset) |
351*b1906ceaSLachlan Hodges 				 ~MM81X_SDIO_RW_ADDR_BOUNDARY_MASK;
352*b1906ceaSLachlan Hodges 
353*b1906ceaSLachlan Hodges 		len = min(remaining, (int)(window_end + 1 - address - offset));
354*b1906ceaSLachlan Hodges 		block_len = len & ~0x3;
355*b1906ceaSLachlan Hodges 		byte_len = len & 0x3;
356*b1906ceaSLachlan Hodges 
357*b1906ceaSLachlan Hodges 		if (block_len) {
358*b1906ceaSLachlan Hodges 			ret = mm81x_sdio_mem_read_block(sdio, address + offset,
359*b1906ceaSLachlan Hodges 							data + offset,
360*b1906ceaSLachlan Hodges 							block_len);
361*b1906ceaSLachlan Hodges 			if (ret != block_len)
362*b1906ceaSLachlan Hodges 				return -EIO;
363*b1906ceaSLachlan Hodges 
364*b1906ceaSLachlan Hodges 			offset += block_len;
365*b1906ceaSLachlan Hodges 		}
366*b1906ceaSLachlan Hodges 
367*b1906ceaSLachlan Hodges 		if (byte_len) {
368*b1906ceaSLachlan Hodges 			ret = mm81x_sdio_mem_read_byte(sdio, address + offset,
369*b1906ceaSLachlan Hodges 						       data + offset, byte_len);
370*b1906ceaSLachlan Hodges 			if (ret != byte_len)
371*b1906ceaSLachlan Hodges 				return -EIO;
372*b1906ceaSLachlan Hodges 
373*b1906ceaSLachlan Hodges 			offset += byte_len;
374*b1906ceaSLachlan Hodges 		}
375*b1906ceaSLachlan Hodges 
376*b1906ceaSLachlan Hodges 		remaining -= len;
377*b1906ceaSLachlan Hodges 	}
378*b1906ceaSLachlan Hodges 
379*b1906ceaSLachlan Hodges 	return 0;
380*b1906ceaSLachlan Hodges }
381*b1906ceaSLachlan Hodges 
mm81x_sdio_reg32_write(struct mm81x * mors,u32 address,u32 val)382*b1906ceaSLachlan Hodges static int mm81x_sdio_reg32_write(struct mm81x *mors, u32 address, u32 val)
383*b1906ceaSLachlan Hodges {
384*b1906ceaSLachlan Hodges 	ssize_t ret = 0;
385*b1906ceaSLachlan Hodges 	u32 original_address = address;
386*b1906ceaSLachlan Hodges 	struct mm81x_sdio *sdio = (struct mm81x_sdio *)mors->drv_priv;
387*b1906ceaSLachlan Hodges 	struct sdio_func *func1 = sdio->func->card->sdio_func[0];
388*b1906ceaSLachlan Hodges 
389*b1906ceaSLachlan Hodges 	mm81x_sdio_set_func_address_base(sdio, func1, address,
390*b1906ceaSLachlan Hodges 					 MM81X_CONFIG_ACCESS_4BYTE);
391*b1906ceaSLachlan Hodges 
392*b1906ceaSLachlan Hodges 	address &= 0x0000FFFF;
393*b1906ceaSLachlan Hodges 	sdio_writel(func1, (__force u32)cpu_to_le32(val),
394*b1906ceaSLachlan Hodges 		    (__force u32)cpu_to_le32(address), (int *)&ret);
395*b1906ceaSLachlan Hodges 	if (ret)
396*b1906ceaSLachlan Hodges 		goto error;
397*b1906ceaSLachlan Hodges 
398*b1906ceaSLachlan Hodges 	return 0;
399*b1906ceaSLachlan Hodges 
400*b1906ceaSLachlan Hodges error:
401*b1906ceaSLachlan Hodges 	if (original_address == MM81X_REG_RESET(mors) &&
402*b1906ceaSLachlan Hodges 	    val == MM81X_REG_RESET_VALUE(mors)) {
403*b1906ceaSLachlan Hodges 		dev_dbg(mors->dev,
404*b1906ceaSLachlan Hodges 			"SDIO reset detected, invalidating base addr\n");
405*b1906ceaSLachlan Hodges 		mm81x_sdio_reset_base_address(sdio);
406*b1906ceaSLachlan Hodges 	}
407*b1906ceaSLachlan Hodges 
408*b1906ceaSLachlan Hodges 	return -EIO;
409*b1906ceaSLachlan Hodges }
410*b1906ceaSLachlan Hodges 
mm81x_sdio_reg32_read(struct mm81x * mors,u32 address,u32 * val)411*b1906ceaSLachlan Hodges static int mm81x_sdio_reg32_read(struct mm81x *mors, u32 address, u32 *val)
412*b1906ceaSLachlan Hodges {
413*b1906ceaSLachlan Hodges 	u32 value;
414*b1906ceaSLachlan Hodges 	ssize_t ret = 0;
415*b1906ceaSLachlan Hodges 	struct mm81x_sdio *sdio = (struct mm81x_sdio *)mors->drv_priv;
416*b1906ceaSLachlan Hodges 	struct sdio_func *func1 = sdio->func->card->sdio_func[0];
417*b1906ceaSLachlan Hodges 
418*b1906ceaSLachlan Hodges 	mm81x_sdio_set_func_address_base(sdio, func1, address,
419*b1906ceaSLachlan Hodges 					 MM81X_CONFIG_ACCESS_4BYTE);
420*b1906ceaSLachlan Hodges 
421*b1906ceaSLachlan Hodges 	address &= 0x0000FFFF;
422*b1906ceaSLachlan Hodges 	value = sdio_readl(func1, (__force u32)cpu_to_le32(address),
423*b1906ceaSLachlan Hodges 			   (int *)&ret);
424*b1906ceaSLachlan Hodges 	if (ret)
425*b1906ceaSLachlan Hodges 		return ret;
426*b1906ceaSLachlan Hodges 
427*b1906ceaSLachlan Hodges 	*val = le32_to_cpup((__le32 *)&value);
428*b1906ceaSLachlan Hodges 	return 0;
429*b1906ceaSLachlan Hodges }
430*b1906ceaSLachlan Hodges 
mm81x_sdio_bus_enable(struct mm81x * mors,bool enable)431*b1906ceaSLachlan Hodges static void mm81x_sdio_bus_enable(struct mm81x *mors, bool enable)
432*b1906ceaSLachlan Hodges {
433*b1906ceaSLachlan Hodges 	struct mm81x_sdio *sdio = (struct mm81x_sdio *)mors->drv_priv;
434*b1906ceaSLachlan Hodges 	struct sdio_func *func = sdio->func;
435*b1906ceaSLachlan Hodges 	struct mmc_host *host = func->card->host;
436*b1906ceaSLachlan Hodges 
437*b1906ceaSLachlan Hodges 	sdio_claim_host(func);
438*b1906ceaSLachlan Hodges 
439*b1906ceaSLachlan Hodges 	if (enable) {
440*b1906ceaSLachlan Hodges 		/*
441*b1906ceaSLachlan Hodges 		 * No need to do anything special to re-enable the sdio bus.
442*b1906ceaSLachlan Hodges 		 * This will happen automatically when a read/write is
443*b1906ceaSLachlan Hodges 		 * attempted and sdio->bulk_addr_base == 0.
444*b1906ceaSLachlan Hodges 		 */
445*b1906ceaSLachlan Hodges 		sdio->enabled = true;
446*b1906ceaSLachlan Hodges 		host->ops->enable_sdio_irq(host, 1);
447*b1906ceaSLachlan Hodges 		dev_dbg(mors->dev, "%s: enabling bus\n", __func__);
448*b1906ceaSLachlan Hodges 	} else {
449*b1906ceaSLachlan Hodges 		host->ops->enable_sdio_irq(host, 0);
450*b1906ceaSLachlan Hodges 		mm81x_sdio_reset_base_address(sdio);
451*b1906ceaSLachlan Hodges 		sdio->enabled = false;
452*b1906ceaSLachlan Hodges 		dev_dbg(mors->dev, "%s: disabling bus\n", __func__);
453*b1906ceaSLachlan Hodges 	}
454*b1906ceaSLachlan Hodges 
455*b1906ceaSLachlan Hodges 	sdio_release_host(func);
456*b1906ceaSLachlan Hodges }
457*b1906ceaSLachlan Hodges 
mm81x_sdio_reset(struct sdio_func * func)458*b1906ceaSLachlan Hodges static void mm81x_sdio_reset(struct sdio_func *func)
459*b1906ceaSLachlan Hodges {
460*b1906ceaSLachlan Hodges 	sdio_claim_host(func);
461*b1906ceaSLachlan Hodges 	sdio_disable_func(func);
462*b1906ceaSLachlan Hodges 	sdio_release_host(func);
463*b1906ceaSLachlan Hodges 
464*b1906ceaSLachlan Hodges 	mdelay(20);
465*b1906ceaSLachlan Hodges 
466*b1906ceaSLachlan Hodges 	sdio_claim_host(func);
467*b1906ceaSLachlan Hodges 	sdio_disable_func(func);
468*b1906ceaSLachlan Hodges 	mmc_hw_reset(func->card);
469*b1906ceaSLachlan Hodges 	sdio_enable_func(func);
470*b1906ceaSLachlan Hodges 	sdio_release_host(func);
471*b1906ceaSLachlan Hodges }
472*b1906ceaSLachlan Hodges 
mm81x_sdio_config_burst_mode(struct mm81x * mors,bool enable_burst)473*b1906ceaSLachlan Hodges static void mm81x_sdio_config_burst_mode(struct mm81x *mors, bool enable_burst)
474*b1906ceaSLachlan Hodges {
475*b1906ceaSLachlan Hodges 	u8 burst_mode = (enable_burst) ? SDIO_WORD_BURST_SIZE_16 :
476*b1906ceaSLachlan Hodges 					 SDIO_WORD_BURST_DISABLE;
477*b1906ceaSLachlan Hodges 
478*b1906ceaSLachlan Hodges 	mm81x_hw_enable_burst_mode(mors, burst_mode);
479*b1906ceaSLachlan Hodges }
480*b1906ceaSLachlan Hodges 
481*b1906ceaSLachlan Hodges static const struct mm81x_bus_ops mm81x_sdio_ops = {
482*b1906ceaSLachlan Hodges 	.dm_read = mm81x_sdio_dm_read,
483*b1906ceaSLachlan Hodges 	.dm_write = mm81x_sdio_dm_write,
484*b1906ceaSLachlan Hodges 	.reg32_read = mm81x_sdio_reg32_read,
485*b1906ceaSLachlan Hodges 	.reg32_write = mm81x_sdio_reg32_write,
486*b1906ceaSLachlan Hodges 	.set_bus_enable = mm81x_sdio_bus_enable,
487*b1906ceaSLachlan Hodges 	.claim = mm81x_sdio_claim_host,
488*b1906ceaSLachlan Hodges 	.release = mm81x_sdio_release_host,
489*b1906ceaSLachlan Hodges 	.config_burst_mode = mm81x_sdio_config_burst_mode,
490*b1906ceaSLachlan Hodges 	.set_irq = mm81x_sdio_set_irq,
491*b1906ceaSLachlan Hodges 	.bulk_alignment = MM81X_SDIO_ALIGNMENT
492*b1906ceaSLachlan Hodges };
493*b1906ceaSLachlan Hodges 
mm81x_sdio_enable(struct mm81x_sdio * sdio)494*b1906ceaSLachlan Hodges static int mm81x_sdio_enable(struct mm81x_sdio *sdio)
495*b1906ceaSLachlan Hodges {
496*b1906ceaSLachlan Hodges 	int ret;
497*b1906ceaSLachlan Hodges 	struct sdio_func *func = sdio->func;
498*b1906ceaSLachlan Hodges 	struct mm81x *mors = sdio_get_drvdata(func);
499*b1906ceaSLachlan Hodges 
500*b1906ceaSLachlan Hodges 	sdio_claim_host(func);
501*b1906ceaSLachlan Hodges 	ret = sdio_enable_func(func);
502*b1906ceaSLachlan Hodges 	if (ret)
503*b1906ceaSLachlan Hodges 		dev_err(mors->dev, "sdio_enable_func failed: %d\n", ret);
504*b1906ceaSLachlan Hodges 	sdio_release_host(func);
505*b1906ceaSLachlan Hodges 	return ret;
506*b1906ceaSLachlan Hodges }
507*b1906ceaSLachlan Hodges 
mm81x_sdio_release(struct mm81x_sdio * sdio)508*b1906ceaSLachlan Hodges static void mm81x_sdio_release(struct mm81x_sdio *sdio)
509*b1906ceaSLachlan Hodges {
510*b1906ceaSLachlan Hodges 	struct sdio_func *func = sdio->func;
511*b1906ceaSLachlan Hodges 
512*b1906ceaSLachlan Hodges 	sdio_claim_host(func);
513*b1906ceaSLachlan Hodges 	sdio_disable_func(func);
514*b1906ceaSLachlan Hodges 	sdio_release_host(func);
515*b1906ceaSLachlan Hodges }
516*b1906ceaSLachlan Hodges 
mm81x_sdio_probe(struct sdio_func * func,const struct sdio_device_id * id)517*b1906ceaSLachlan Hodges static int mm81x_sdio_probe(struct sdio_func *func,
518*b1906ceaSLachlan Hodges 			    const struct sdio_device_id *id)
519*b1906ceaSLachlan Hodges {
520*b1906ceaSLachlan Hodges 	int ret = 0;
521*b1906ceaSLachlan Hodges 	struct mm81x *mors = NULL;
522*b1906ceaSLachlan Hodges 	struct mm81x_sdio *sdio;
523*b1906ceaSLachlan Hodges 	struct device *dev = &func->dev;
524*b1906ceaSLachlan Hodges 
525*b1906ceaSLachlan Hodges 	if (func->num == 1)
526*b1906ceaSLachlan Hodges 		return 0;
527*b1906ceaSLachlan Hodges 
528*b1906ceaSLachlan Hodges 	if (func->num != 2)
529*b1906ceaSLachlan Hodges 		return -ENODEV;
530*b1906ceaSLachlan Hodges 
531*b1906ceaSLachlan Hodges 	mors = mm81x_core_alloc(sizeof(*sdio), dev);
532*b1906ceaSLachlan Hodges 	if (!mors)
533*b1906ceaSLachlan Hodges 		return -ENOMEM;
534*b1906ceaSLachlan Hodges 
535*b1906ceaSLachlan Hodges 	mors->bus_ops = &mm81x_sdio_ops;
536*b1906ceaSLachlan Hodges 	mors->bus_type = MM81X_BUS_TYPE_SDIO;
537*b1906ceaSLachlan Hodges 
538*b1906ceaSLachlan Hodges 	sdio = (struct mm81x_sdio *)mors->drv_priv;
539*b1906ceaSLachlan Hodges 	sdio->func = func;
540*b1906ceaSLachlan Hodges 	sdio->id = id;
541*b1906ceaSLachlan Hodges 	sdio->enabled = true;
542*b1906ceaSLachlan Hodges 	mm81x_sdio_reset_base_address(sdio);
543*b1906ceaSLachlan Hodges 
544*b1906ceaSLachlan Hodges 	sdio_set_drvdata(func, mors);
545*b1906ceaSLachlan Hodges 
546*b1906ceaSLachlan Hodges 	ret = mm81x_sdio_enable(sdio);
547*b1906ceaSLachlan Hodges 	if (ret)
548*b1906ceaSLachlan Hodges 		goto err_core_free;
549*b1906ceaSLachlan Hodges 
550*b1906ceaSLachlan Hodges 	mm81x_sdio_config_burst_mode(mors, true);
551*b1906ceaSLachlan Hodges 
552*b1906ceaSLachlan Hodges 	ret = mm81x_core_init(mors);
553*b1906ceaSLachlan Hodges 	if (ret)
554*b1906ceaSLachlan Hodges 		goto err_sdio_release;
555*b1906ceaSLachlan Hodges 
556*b1906ceaSLachlan Hodges 	ret = mm81x_sdio_enable_irq(sdio);
557*b1906ceaSLachlan Hodges 	if (ret)
558*b1906ceaSLachlan Hodges 		goto err_core_deinit;
559*b1906ceaSLachlan Hodges 
560*b1906ceaSLachlan Hodges 	ret = mm81x_core_register(mors);
561*b1906ceaSLachlan Hodges 	if (ret)
562*b1906ceaSLachlan Hodges 		goto err_disable_irq;
563*b1906ceaSLachlan Hodges 
564*b1906ceaSLachlan Hodges 	return 0;
565*b1906ceaSLachlan Hodges 
566*b1906ceaSLachlan Hodges err_disable_irq:
567*b1906ceaSLachlan Hodges 	mm81x_sdio_disable_irq(sdio);
568*b1906ceaSLachlan Hodges err_core_deinit:
569*b1906ceaSLachlan Hodges 	mm81x_core_deinit(mors);
570*b1906ceaSLachlan Hodges err_sdio_release:
571*b1906ceaSLachlan Hodges 	mm81x_sdio_release(sdio);
572*b1906ceaSLachlan Hodges err_core_free:
573*b1906ceaSLachlan Hodges 	mm81x_core_free(mors);
574*b1906ceaSLachlan Hodges 	return ret;
575*b1906ceaSLachlan Hodges }
576*b1906ceaSLachlan Hodges 
mm81x_sdio_remove(struct sdio_func * func)577*b1906ceaSLachlan Hodges static void mm81x_sdio_remove(struct sdio_func *func)
578*b1906ceaSLachlan Hodges {
579*b1906ceaSLachlan Hodges 	struct mm81x *mors = sdio_get_drvdata(func);
580*b1906ceaSLachlan Hodges 	struct mm81x_sdio *sdio = (struct mm81x_sdio *)mors->drv_priv;
581*b1906ceaSLachlan Hodges 
582*b1906ceaSLachlan Hodges 	if (!mors)
583*b1906ceaSLachlan Hodges 		return;
584*b1906ceaSLachlan Hodges 
585*b1906ceaSLachlan Hodges 	mm81x_core_unregister(mors);
586*b1906ceaSLachlan Hodges 	mm81x_sdio_disable_irq(sdio);
587*b1906ceaSLachlan Hodges 	mm81x_core_deinit(mors);
588*b1906ceaSLachlan Hodges 	mm81x_sdio_release(sdio);
589*b1906ceaSLachlan Hodges 	mm81x_sdio_reset(func);
590*b1906ceaSLachlan Hodges 	mm81x_core_free(mors);
591*b1906ceaSLachlan Hodges 	sdio_set_drvdata(func, NULL);
592*b1906ceaSLachlan Hodges }
593*b1906ceaSLachlan Hodges 
594*b1906ceaSLachlan Hodges static const struct sdio_device_id mm81x_sdio_devices[] = {
595*b1906ceaSLachlan Hodges 	{ SDIO_DEVICE(SDIO_VENDOR_ID_MORSEMICRO,
596*b1906ceaSLachlan Hodges 		      SDIO_DEVICE_ID_MORSEMICRO_MM8108) },
597*b1906ceaSLachlan Hodges 	{},
598*b1906ceaSLachlan Hodges };
599*b1906ceaSLachlan Hodges 
600*b1906ceaSLachlan Hodges MODULE_DEVICE_TABLE(sdio, mm81x_sdio_devices);
601*b1906ceaSLachlan Hodges 
602*b1906ceaSLachlan Hodges static struct sdio_driver mm81x_sdio_driver = {
603*b1906ceaSLachlan Hodges 	.name = "mm81x_sdio",
604*b1906ceaSLachlan Hodges 	.id_table = mm81x_sdio_devices,
605*b1906ceaSLachlan Hodges 	.probe = mm81x_sdio_probe,
606*b1906ceaSLachlan Hodges 	.remove = mm81x_sdio_remove,
607*b1906ceaSLachlan Hodges };
608*b1906ceaSLachlan Hodges 
609*b1906ceaSLachlan Hodges module_sdio_driver(mm81x_sdio_driver);
610*b1906ceaSLachlan Hodges 
611*b1906ceaSLachlan Hodges MODULE_AUTHOR("Morse Micro");
612*b1906ceaSLachlan Hodges MODULE_DESCRIPTION("Driver support for Morse Micro MM81X SDIO devices");
613*b1906ceaSLachlan Hodges MODULE_LICENSE("Dual BSD/GPL");
614