114bcf3e1SWarner Losh /*-
214bcf3e1SWarner Losh * Copyright (c) 2016-2017 Ilya Bakulin
314bcf3e1SWarner Losh * All rights reserved.
414bcf3e1SWarner Losh *
514bcf3e1SWarner Losh * Redistribution and use in source and binary forms, with or without
614bcf3e1SWarner Losh * modification, are permitted provided that the following conditions
714bcf3e1SWarner Losh * are met:
814bcf3e1SWarner Losh * 1. Redistributions of source code must retain the above copyright
914bcf3e1SWarner Losh * notice, this list of conditions and the following disclaimer.
1014bcf3e1SWarner Losh * 2. Redistributions in binary form must reproduce the above copyright
1114bcf3e1SWarner Losh * notice, this list of conditions and the following disclaimer in the
1214bcf3e1SWarner Losh * documentation and/or other materials provided with the distribution.
1314bcf3e1SWarner Losh *
1414bcf3e1SWarner Losh * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
1514bcf3e1SWarner Losh * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
1614bcf3e1SWarner Losh * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
1714bcf3e1SWarner Losh * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
1814bcf3e1SWarner Losh * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
1914bcf3e1SWarner Losh * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
2014bcf3e1SWarner Losh * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
2114bcf3e1SWarner Losh * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
2214bcf3e1SWarner Losh * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
2314bcf3e1SWarner Losh * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
2414bcf3e1SWarner Losh * SUCH DAMAGE.
2514bcf3e1SWarner Losh *
26*029c02a3SIlya Bakulin * Copyright (c) 2010 Broadcom Corporation
27*029c02a3SIlya Bakulin *
28*029c02a3SIlya Bakulin * Permission to use, copy, modify, and/or distribute this software for any
29*029c02a3SIlya Bakulin * purpose with or without fee is hereby granted, provided that the above
30*029c02a3SIlya Bakulin * copyright notice and this permission notice appear in all copies.
31*029c02a3SIlya Bakulin *
32*029c02a3SIlya Bakulin * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
33*029c02a3SIlya Bakulin * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
34*029c02a3SIlya Bakulin * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
35*029c02a3SIlya Bakulin * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
36*029c02a3SIlya Bakulin * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
37*029c02a3SIlya Bakulin * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
38*029c02a3SIlya Bakulin * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
3914bcf3e1SWarner Losh */
4014bcf3e1SWarner Losh
4114bcf3e1SWarner Losh #include <sys/cdefs.h>
4214bcf3e1SWarner Losh #include <sys/ioctl.h>
4314bcf3e1SWarner Losh #include <sys/stdint.h>
4414bcf3e1SWarner Losh #include <sys/types.h>
4514bcf3e1SWarner Losh #include <sys/stat.h>
4614bcf3e1SWarner Losh #include <sys/endian.h>
4714bcf3e1SWarner Losh #include <sys/sbuf.h>
4814bcf3e1SWarner Losh #include <sys/mman.h>
4914bcf3e1SWarner Losh
5014bcf3e1SWarner Losh #include <stdio.h>
5114bcf3e1SWarner Losh #include <stdlib.h>
5214bcf3e1SWarner Losh #include <string.h>
5314bcf3e1SWarner Losh #include <unistd.h>
5414bcf3e1SWarner Losh #include <inttypes.h>
5514bcf3e1SWarner Losh #include <limits.h>
5614bcf3e1SWarner Losh #include <fcntl.h>
5714bcf3e1SWarner Losh #include <ctype.h>
5814bcf3e1SWarner Losh #include <err.h>
5914bcf3e1SWarner Losh #include <libutil.h>
6014bcf3e1SWarner Losh #include <unistd.h>
6114bcf3e1SWarner Losh
6214bcf3e1SWarner Losh #include <cam/cam.h>
6314bcf3e1SWarner Losh #include <cam/cam_debug.h>
6414bcf3e1SWarner Losh #include <cam/cam_ccb.h>
6514bcf3e1SWarner Losh #include <cam/mmc/mmc_all.h>
6614bcf3e1SWarner Losh #include <camlib.h>
6714bcf3e1SWarner Losh
68*029c02a3SIlya Bakulin #include "linux_compat.h"
69*029c02a3SIlya Bakulin #include "linux_sdio_compat.h"
70*029c02a3SIlya Bakulin #include "cam_sdio.h"
71*029c02a3SIlya Bakulin #include "brcmfmac_sdio.h"
72*029c02a3SIlya Bakulin #include "brcmfmac_bus.h"
7314bcf3e1SWarner Losh
7414bcf3e1SWarner Losh static void probe_bcrm(struct cam_device *dev);
7514bcf3e1SWarner Losh
7614bcf3e1SWarner Losh /*
7714bcf3e1SWarner Losh * How Linux driver works
7814bcf3e1SWarner Losh *
7914bcf3e1SWarner Losh * The probing begins by calling brcmf_ops_sdio_probe() which is defined as probe function in struct sdio_driver. http://lxr.free-electrons.com/source/drivers/net/wireless/broadcom/brcm80211/brcmfmac/bcmsdh.c#L1126
8014bcf3e1SWarner Losh *
8114bcf3e1SWarner Losh * The driver does black magic by copying func struct for F2 and setting func number to zero there, to create an F0 func structure :)
8214bcf3e1SWarner Losh * Driver state changes to BRCMF_SDIOD_DOWN.
8314bcf3e1SWarner Losh * ops_sdio_probe() then calls brcmf_sdio_probe() -- at this point it has filled in sdiodev struct with the pointers to all three functions (F0, F1, F2).
8414bcf3e1SWarner Losh *
8514bcf3e1SWarner Losh * brcmf_sdiod_probe() sets block sizes for F1 and F2. It sets F1 block size to 64 and F2 to 512, not consulting the values stored in SDIO CCCR / FBR registers!
8614bcf3e1SWarner Losh * Then it increases timeout for F2 (what is this?!)
8714bcf3e1SWarner Losh * Then it enables F1
8814bcf3e1SWarner Losh * Then it attaches "freezer" (without PM this is NOP)
8914bcf3e1SWarner Losh * Finally it calls brcmf_sdio_probe() http://lxr.free-electrons.com/source/drivers/net/wireless/broadcom/brcm80211/brcmfmac/sdio.c#L4082
9014bcf3e1SWarner Losh *
9114bcf3e1SWarner Losh * Here high-level workqueues and sg tables are allocated.
9214bcf3e1SWarner Losh * It then calls brcmf_sdio_probe_attach()
9314bcf3e1SWarner Losh *
9414bcf3e1SWarner Losh * Here at the beginning there is a pr_debug() call with brcmf_sdiod_regrl() inside to addr #define SI_ENUM_BASE 0x18000000.
9514bcf3e1SWarner Losh * Return value is 0x16044330.
9614bcf3e1SWarner Losh * Then turns off PLL: byte-write BRCMF_INIT_CLKCTL1 (0x28) -> SBSDIO_FUNC1_CHIPCLKCSR (0x1000E)
9714bcf3e1SWarner Losh * Then it reads value back, should be 0xe8.
9814bcf3e1SWarner Losh * Then calls brcmf_chip_attach()
9914bcf3e1SWarner Losh *
10014bcf3e1SWarner Losh * http://lxr.free-electrons.com/source/drivers/net/wireless/broadcom/brcm80211/brcmfmac/chip.c#L1054
10114bcf3e1SWarner Losh * This func enumerates and resets all the cores on the dongle.
10214bcf3e1SWarner Losh * - brcmf_sdio_buscoreprep(): force clock to ALPAvail req only:
10314bcf3e1SWarner Losh * SBSDIO_FORCE_HW_CLKREQ_OFF | SBSDIO_ALP_AVAIL_REQ -> SBSDIO_FUNC1_CHIPCLKCSR
10414bcf3e1SWarner Losh * Wait up to 15ms to !SBSDIO_ALPAV(clkval) of the value from CLKCSR.
10514bcf3e1SWarner Losh * Force ALP:
10614bcf3e1SWarner Losh * SBSDIO_FORCE_HW_CLKREQ_OFF | SBSDIO_FORCE_ALP (0x21)-> SBSDIO_FUNC1_CHIPCLKCSR
10714bcf3e1SWarner Losh * Disaable SDIO pullups:
10814bcf3e1SWarner Losh * byte 0 -> SBSDIO_FUNC1_SDIOPULLUP (0x0001000f)
10914bcf3e1SWarner Losh *
11014bcf3e1SWarner Losh * Calls brcmf_chip_recognition()
11114bcf3e1SWarner Losh * http://lxr.free-electrons.com/source/drivers/net/wireless/broadcom/brcm80211/brcmfmac/chip.c#L908
11214bcf3e1SWarner Losh * Read 0x18000000. Get 0x16044330: chip 4330 rev 4
11314bcf3e1SWarner Losh * AXI chip, call brcmf_chip_dmp_erom_scan() to get info about all cores.
11414bcf3e1SWarner Losh * Then brcmf_chip_cores_check() to check that CPU and RAM are found,
11514bcf3e1SWarner Losh *
11614bcf3e1SWarner Losh * Setting cores to passive: not clear which of CR4/CA7/CM3 our chip has.
11714bcf3e1SWarner Losh * Quite a few r/w calls to different parts of the chip to reset cores....
11814bcf3e1SWarner Losh * Finally get_raminfo() called to fill in RAM info:
11914bcf3e1SWarner Losh * brcmf_chip_get_raminfo: RAM: base=0x0 size=294912 (0x48000) sr=0 (0x0)
12014bcf3e1SWarner Losh * http://lxr.free-electrons.com/source/drivers/net/wireless/broadcom/brcm80211/brcmfmac/chip.c#L700
12114bcf3e1SWarner Losh *
12214bcf3e1SWarner Losh * Then brcmf_chip_setup() is called, this prints and fills in chipcommon rev and PMU caps:
12314bcf3e1SWarner Losh * brcmf_chip_setup: ccrev=39, pmurev=12, pmucaps=0x19583c0c
12414bcf3e1SWarner Losh * http://lxr.free-electrons.com/source/drivers/net/wireless/broadcom/brcm80211/brcmfmac/chip.c#L1015
12514bcf3e1SWarner Losh * Bus-specific setup code is NOP for SDIO.
12614bcf3e1SWarner Losh *
12714bcf3e1SWarner Losh * brcmf_sdio_kso_init() is called.
12814bcf3e1SWarner Losh * Here it first reads 0x1 from SBSDIO_FUNC1_SLEEPCSR 0x18000650 and then writes it back... WTF?
12914bcf3e1SWarner Losh *
13014bcf3e1SWarner Losh * brcmf_sdio_drivestrengthinit() is called
13114bcf3e1SWarner Losh * http://lxr.free-electrons.com/source/drivers/net/wireless/broadcom/brcm80211/brcmfmac/sdio.c#L3630
13214bcf3e1SWarner Losh *
13314bcf3e1SWarner Losh * Set card control so an SDIO card reset does a WLAN backplane reset
13414bcf3e1SWarner Losh * set PMUControl so a backplane reset does PMU state reload
13514bcf3e1SWarner Losh * === end of brcmf_sdio_probe_attach ===
13614bcf3e1SWarner Losh
13714bcf3e1SWarner Losh **** Finished reading at http://lxr.free-electrons.com/source/drivers/net/wireless/broadcom/brcm80211/brcmfmac/sdio.c#L4152, line 2025 in the dump
13814bcf3e1SWarner Losh
13914bcf3e1SWarner Losh * === How register reading works ===
14014bcf3e1SWarner Losh * http://lxr.free-electrons.com/source/drivers/net/wireless/broadcom/brcm80211/brcmfmac/bcmsdh.c#L357
14114bcf3e1SWarner Losh * The address to read from is written to three byte-sized registers of F1:
14214bcf3e1SWarner Losh * - SBSDIO_FUNC1_SBADDRLOW 0x1000A
14314bcf3e1SWarner Losh * - SBSDIO_FUNC1_SBADDRMID 0x1000B
14414bcf3e1SWarner Losh * - SBSDIO_FUNC1_SBADDRHIGH 0x1000C
14514bcf3e1SWarner Losh * If this is 32-bit read , a flag is set. The address is ANDed with SBSDIO_SB_OFT_ADDR_MASK which is 0x07FFF.
14614bcf3e1SWarner Losh * Then brcmf_sdiod_regrw_helper() is called to read the reply.
14714bcf3e1SWarner Losh * http://lxr.free-electrons.com/source/drivers/net/wireless/broadcom/brcm80211/brcmfmac/bcmsdh.c#L306
14814bcf3e1SWarner Losh * Based on the address it figures out where to read it from (CCCR / FBR in F0, or somewhere in F1).
14914bcf3e1SWarner Losh * Reads are retried three times.
15014bcf3e1SWarner Losh * 1-byte IO is done with CMD52, more is read with CMD53 with address increment (not FIFO mode).
15114bcf3e1SWarner Losh * http://lxr.free-electrons.com/source/drivers/mmc/core/sdio_io.c#L458
15214bcf3e1SWarner Losh * ==================================
15314bcf3e1SWarner Losh *
15414bcf3e1SWarner Losh *
15514bcf3e1SWarner Losh */
156*029c02a3SIlya Bakulin
157*029c02a3SIlya Bakulin /* BRCM-specific functions */
158*029c02a3SIlya Bakulin #define SDIOH_API_ACCESS_RETRY_LIMIT 2
159*029c02a3SIlya Bakulin #define SI_ENUM_BASE 0x18000000
160*029c02a3SIlya Bakulin #define REPLY_MAGIC 0x16044330
161*029c02a3SIlya Bakulin #define brcmf_err(fmt, ...) brcmf_dbg(0, fmt, ##__VA_ARGS__)
162*029c02a3SIlya Bakulin #define brcmf_dbg(level, fmt, ...) printf(fmt, ##__VA_ARGS__)
163*029c02a3SIlya Bakulin
164*029c02a3SIlya Bakulin struct brcmf_sdio_dev {
165*029c02a3SIlya Bakulin struct cam_device *cam_dev;
166*029c02a3SIlya Bakulin u32 sbwad; /* Save backplane window address */
167*029c02a3SIlya Bakulin struct brcmf_bus *bus_if;
168*029c02a3SIlya Bakulin enum brcmf_sdiod_state state;
169*029c02a3SIlya Bakulin struct sdio_func *func[8];
170*029c02a3SIlya Bakulin };
171*029c02a3SIlya Bakulin
172*029c02a3SIlya Bakulin void brcmf_bus_change_state(struct brcmf_bus *bus, enum brcmf_bus_state state);
173*029c02a3SIlya Bakulin void brcmf_sdiod_change_state(struct brcmf_sdio_dev *sdiodev,
174*029c02a3SIlya Bakulin enum brcmf_sdiod_state state);
175*029c02a3SIlya Bakulin static int brcmf_sdiod_request_data(struct brcmf_sdio_dev *sdiodev, u8 fn, u32 addr,
176*029c02a3SIlya Bakulin u8 regsz, void *data, bool write);
177*029c02a3SIlya Bakulin static int brcmf_sdiod_set_sbaddr_window(struct brcmf_sdio_dev *sdiodev, u32 address);
178*029c02a3SIlya Bakulin static int brcmf_sdiod_addrprep(struct brcmf_sdio_dev *sdiodev, uint width, u32 *addr);
179*029c02a3SIlya Bakulin u32 brcmf_sdiod_regrl(struct brcmf_sdio_dev *sdiodev, u32 addr, int *ret);
180*029c02a3SIlya Bakulin
181*029c02a3SIlya Bakulin static void bailout(int ret);
182*029c02a3SIlya Bakulin
183*029c02a3SIlya Bakulin static void
bailout(int ret)184*029c02a3SIlya Bakulin bailout(int ret) {
185*029c02a3SIlya Bakulin if (ret == 0)
186*029c02a3SIlya Bakulin return;
187*029c02a3SIlya Bakulin errx(1, "Operation returned error %d", ret);
188*029c02a3SIlya Bakulin }
189*029c02a3SIlya Bakulin
190*029c02a3SIlya Bakulin void
brcmf_bus_change_state(struct brcmf_bus * bus,enum brcmf_bus_state state)191*029c02a3SIlya Bakulin brcmf_bus_change_state(struct brcmf_bus *bus, enum brcmf_bus_state state)
192*029c02a3SIlya Bakulin {
193*029c02a3SIlya Bakulin bus->state = state;
194*029c02a3SIlya Bakulin }
195*029c02a3SIlya Bakulin
brcmf_sdiod_change_state(struct brcmf_sdio_dev * sdiodev,enum brcmf_sdiod_state state)196*029c02a3SIlya Bakulin void brcmf_sdiod_change_state(struct brcmf_sdio_dev *sdiodev,
197*029c02a3SIlya Bakulin enum brcmf_sdiod_state state)
198*029c02a3SIlya Bakulin {
199*029c02a3SIlya Bakulin if (sdiodev->state == BRCMF_SDIOD_NOMEDIUM ||
200*029c02a3SIlya Bakulin state == sdiodev->state)
201*029c02a3SIlya Bakulin return;
202*029c02a3SIlya Bakulin
203*029c02a3SIlya Bakulin //brcmf_dbg(TRACE, "%d -> %d\n", sdiodev->state, state);
204*029c02a3SIlya Bakulin switch (sdiodev->state) {
205*029c02a3SIlya Bakulin case BRCMF_SDIOD_DATA:
206*029c02a3SIlya Bakulin /* any other state means bus interface is down */
207*029c02a3SIlya Bakulin brcmf_bus_change_state(sdiodev->bus_if, BRCMF_BUS_DOWN);
208*029c02a3SIlya Bakulin break;
209*029c02a3SIlya Bakulin case BRCMF_SDIOD_DOWN:
210*029c02a3SIlya Bakulin /* transition from DOWN to DATA means bus interface is up */
211*029c02a3SIlya Bakulin if (state == BRCMF_SDIOD_DATA)
212*029c02a3SIlya Bakulin brcmf_bus_change_state(sdiodev->bus_if, BRCMF_BUS_UP);
213*029c02a3SIlya Bakulin break;
214*029c02a3SIlya Bakulin default:
215*029c02a3SIlya Bakulin break;
216*029c02a3SIlya Bakulin }
217*029c02a3SIlya Bakulin sdiodev->state = state;
218*029c02a3SIlya Bakulin }
219*029c02a3SIlya Bakulin
brcmf_sdiod_f0_writeb(struct sdio_func * func,uint regaddr,u8 byte)220*029c02a3SIlya Bakulin static inline int brcmf_sdiod_f0_writeb(struct sdio_func *func,
221*029c02a3SIlya Bakulin uint regaddr, u8 byte) {
222*029c02a3SIlya Bakulin int err_ret;
223*029c02a3SIlya Bakulin
224*029c02a3SIlya Bakulin /*
225*029c02a3SIlya Bakulin * Can only directly write to some F0 registers.
226*029c02a3SIlya Bakulin * Handle CCCR_IENx and CCCR_ABORT command
227*029c02a3SIlya Bakulin * as a special case.
228*029c02a3SIlya Bakulin */
229*029c02a3SIlya Bakulin if ((regaddr == SDIO_CCCR_ABORT) ||
230*029c02a3SIlya Bakulin (regaddr == SDIO_CCCR_IENx))
231*029c02a3SIlya Bakulin sdio_writeb(func, byte, regaddr, &err_ret);
232*029c02a3SIlya Bakulin else
233*029c02a3SIlya Bakulin sdio_f0_writeb(func, byte, regaddr, &err_ret);
234*029c02a3SIlya Bakulin
235*029c02a3SIlya Bakulin return err_ret;
236*029c02a3SIlya Bakulin }
237*029c02a3SIlya Bakulin
brcmf_sdiod_request_data(struct brcmf_sdio_dev * sdiodev,u8 fn,u32 addr,u8 regsz,void * data,bool write)238*029c02a3SIlya Bakulin static int brcmf_sdiod_request_data(struct brcmf_sdio_dev *sdiodev, u8 fn, u32 addr, u8 regsz, void *data, bool write)
239*029c02a3SIlya Bakulin {
240*029c02a3SIlya Bakulin struct sdio_func *func;
241*029c02a3SIlya Bakulin int ret = -EINVAL;
242*029c02a3SIlya Bakulin
243*029c02a3SIlya Bakulin brcmf_dbg(SDIO, "rw=%d, func=%d, addr=0x%05x, nbytes=%d\n",
244*029c02a3SIlya Bakulin write, fn, addr, regsz);
245*029c02a3SIlya Bakulin
246*029c02a3SIlya Bakulin /* only allow byte access on F0 */
247*029c02a3SIlya Bakulin if (WARN_ON(regsz > 1 && !fn))
248*029c02a3SIlya Bakulin return -EINVAL;
249*029c02a3SIlya Bakulin func = sdiodev->func[fn];
250*029c02a3SIlya Bakulin
251*029c02a3SIlya Bakulin switch (regsz) {
252*029c02a3SIlya Bakulin case sizeof(u8):
253*029c02a3SIlya Bakulin if (write) {
254*029c02a3SIlya Bakulin if (fn)
255*029c02a3SIlya Bakulin sdio_writeb(func, *(u8 *)data, addr, &ret);
256*029c02a3SIlya Bakulin else
257*029c02a3SIlya Bakulin ret = brcmf_sdiod_f0_writeb(func, addr,
258*029c02a3SIlya Bakulin *(u8 *)data);
259*029c02a3SIlya Bakulin } else {
260*029c02a3SIlya Bakulin if (fn)
261*029c02a3SIlya Bakulin *(u8 *)data = sdio_readb(func, addr, &ret);
262*029c02a3SIlya Bakulin else
263*029c02a3SIlya Bakulin *(u8 *)data = sdio_f0_readb(func, addr, &ret);
264*029c02a3SIlya Bakulin }
265*029c02a3SIlya Bakulin break;
266*029c02a3SIlya Bakulin case sizeof(u16):
267*029c02a3SIlya Bakulin if (write)
268*029c02a3SIlya Bakulin sdio_writew(func, *(u16 *)data, addr, &ret);
269*029c02a3SIlya Bakulin else
270*029c02a3SIlya Bakulin *(u16 *)data = sdio_readw(func, addr, &ret);
271*029c02a3SIlya Bakulin break;
272*029c02a3SIlya Bakulin case sizeof(u32):
273*029c02a3SIlya Bakulin if (write)
274*029c02a3SIlya Bakulin sdio_writel(func, *(u32 *)data, addr, &ret);
275*029c02a3SIlya Bakulin else
276*029c02a3SIlya Bakulin *(u32 *)data = sdio_readl(func, addr, &ret);
277*029c02a3SIlya Bakulin break;
278*029c02a3SIlya Bakulin default:
279*029c02a3SIlya Bakulin brcmf_err("invalid size: %d\n", regsz);
280*029c02a3SIlya Bakulin break;
281*029c02a3SIlya Bakulin }
282*029c02a3SIlya Bakulin
283*029c02a3SIlya Bakulin if (ret)
284*029c02a3SIlya Bakulin brcmf_dbg(SDIO, "failed to %s data F%d@0x%05x, err: %d\n",
285*029c02a3SIlya Bakulin write ? "write" : "read", fn, addr, ret);
286*029c02a3SIlya Bakulin
287*029c02a3SIlya Bakulin return ret;
288*029c02a3SIlya Bakulin }
289*029c02a3SIlya Bakulin
290*029c02a3SIlya Bakulin static int
brcmf_sdiod_addrprep(struct brcmf_sdio_dev * sdiodev,uint width,u32 * addr)291*029c02a3SIlya Bakulin brcmf_sdiod_addrprep(struct brcmf_sdio_dev *sdiodev, uint width, u32 *addr)
292*029c02a3SIlya Bakulin {
293*029c02a3SIlya Bakulin uint bar0 = *addr & ~SBSDIO_SB_OFT_ADDR_MASK;
294*029c02a3SIlya Bakulin int err = 0;
295*029c02a3SIlya Bakulin
296*029c02a3SIlya Bakulin if (bar0 != sdiodev->sbwad) {
297*029c02a3SIlya Bakulin err = brcmf_sdiod_set_sbaddr_window(sdiodev, bar0);
298*029c02a3SIlya Bakulin if (err)
299*029c02a3SIlya Bakulin return err;
300*029c02a3SIlya Bakulin
301*029c02a3SIlya Bakulin sdiodev->sbwad = bar0;
302*029c02a3SIlya Bakulin }
303*029c02a3SIlya Bakulin
304*029c02a3SIlya Bakulin *addr &= SBSDIO_SB_OFT_ADDR_MASK;
305*029c02a3SIlya Bakulin
306*029c02a3SIlya Bakulin if (width == 4)
307*029c02a3SIlya Bakulin *addr |= SBSDIO_SB_ACCESS_2_4B_FLAG;
308*029c02a3SIlya Bakulin
309*029c02a3SIlya Bakulin return 0;
310*029c02a3SIlya Bakulin }
311*029c02a3SIlya Bakulin
brcmf_sdiod_regrw_helper(struct brcmf_sdio_dev * sdiodev,u32 addr,u8 regsz,void * data,bool write)312*029c02a3SIlya Bakulin static int brcmf_sdiod_regrw_helper(struct brcmf_sdio_dev *sdiodev, u32 addr, u8 regsz, void *data, bool write) {
313*029c02a3SIlya Bakulin u8 func;
314*029c02a3SIlya Bakulin s32 retry = 0;
315*029c02a3SIlya Bakulin int ret;
316*029c02a3SIlya Bakulin
317*029c02a3SIlya Bakulin if (sdiodev->state == BRCMF_SDIOD_NOMEDIUM)
318*029c02a3SIlya Bakulin return -ENOMEDIUM;
319*029c02a3SIlya Bakulin
320*029c02a3SIlya Bakulin /*
321*029c02a3SIlya Bakulin * figure out how to read the register based on address range
322*029c02a3SIlya Bakulin * 0x00 ~ 0x7FF: function 0 CCCR and FBR
323*029c02a3SIlya Bakulin * 0x10000 ~ 0x1FFFF: function 1 miscellaneous registers
324*029c02a3SIlya Bakulin * The rest: function 1 silicon backplane core registers
325*029c02a3SIlya Bakulin */
326*029c02a3SIlya Bakulin if ((addr & ~REG_F0_REG_MASK) == 0)
327*029c02a3SIlya Bakulin func = SDIO_FUNC_0;
328*029c02a3SIlya Bakulin else
329*029c02a3SIlya Bakulin func = SDIO_FUNC_1;
330*029c02a3SIlya Bakulin
331*029c02a3SIlya Bakulin do {
332*029c02a3SIlya Bakulin if (!write)
333*029c02a3SIlya Bakulin memset(data, 0, regsz);
334*029c02a3SIlya Bakulin /* for retry wait for 1 ms till bus get settled down */
335*029c02a3SIlya Bakulin if (retry)
336*029c02a3SIlya Bakulin usleep_range(1000, 2000);
337*029c02a3SIlya Bakulin ret = brcmf_sdiod_request_data(sdiodev, func, addr, regsz,
338*029c02a3SIlya Bakulin data, write);
339*029c02a3SIlya Bakulin } while (ret != 0 && ret != -ENOMEDIUM &&
340*029c02a3SIlya Bakulin retry++ < SDIOH_API_ACCESS_RETRY_LIMIT);
341*029c02a3SIlya Bakulin
342*029c02a3SIlya Bakulin if (ret == -ENOMEDIUM)
343*029c02a3SIlya Bakulin brcmf_sdiod_change_state(sdiodev, BRCMF_SDIOD_NOMEDIUM);
344*029c02a3SIlya Bakulin else if (ret != 0) {
345*029c02a3SIlya Bakulin /*
346*029c02a3SIlya Bakulin * SleepCSR register access can fail when
347*029c02a3SIlya Bakulin * waking up the device so reduce this noise
348*029c02a3SIlya Bakulin * in the logs.
349*029c02a3SIlya Bakulin */
350*029c02a3SIlya Bakulin if (addr != SBSDIO_FUNC1_SLEEPCSR)
351*029c02a3SIlya Bakulin brcmf_err("failed to %s data F%d@0x%05x, err: %d\n",
352*029c02a3SIlya Bakulin write ? "write" : "read", func, addr, ret);
353*029c02a3SIlya Bakulin else
354*029c02a3SIlya Bakulin brcmf_dbg(SDIO, "failed to %s data F%d@0x%05x, err: %d\n",
355*029c02a3SIlya Bakulin write ? "write" : "read", func, addr, ret);
356*029c02a3SIlya Bakulin }
357*029c02a3SIlya Bakulin return ret;
358*029c02a3SIlya Bakulin }
359*029c02a3SIlya Bakulin
360*029c02a3SIlya Bakulin static int
brcmf_sdiod_set_sbaddr_window(struct brcmf_sdio_dev * sdiodev,u32 address)361*029c02a3SIlya Bakulin brcmf_sdiod_set_sbaddr_window(struct brcmf_sdio_dev *sdiodev, u32 address)
362*029c02a3SIlya Bakulin {
363*029c02a3SIlya Bakulin int err = 0, i;
364*029c02a3SIlya Bakulin u8 addr[3];
365*029c02a3SIlya Bakulin
366*029c02a3SIlya Bakulin if (sdiodev->state == BRCMF_SDIOD_NOMEDIUM)
367*029c02a3SIlya Bakulin return -ENOMEDIUM;
368*029c02a3SIlya Bakulin
369*029c02a3SIlya Bakulin addr[0] = (address >> 8) & SBSDIO_SBADDRLOW_MASK;
370*029c02a3SIlya Bakulin addr[1] = (address >> 16) & SBSDIO_SBADDRMID_MASK;
371*029c02a3SIlya Bakulin addr[2] = (address >> 24) & SBSDIO_SBADDRHIGH_MASK;
372*029c02a3SIlya Bakulin
373*029c02a3SIlya Bakulin for (i = 0; i < 3; i++) {
374*029c02a3SIlya Bakulin err = brcmf_sdiod_regrw_helper(sdiodev,
375*029c02a3SIlya Bakulin SBSDIO_FUNC1_SBADDRLOW + i,
376*029c02a3SIlya Bakulin sizeof(u8), &addr[i], true);
377*029c02a3SIlya Bakulin if (err) {
378*029c02a3SIlya Bakulin brcmf_err("failed at addr: 0x%0x\n",
379*029c02a3SIlya Bakulin SBSDIO_FUNC1_SBADDRLOW + i);
380*029c02a3SIlya Bakulin break;
381*029c02a3SIlya Bakulin }
382*029c02a3SIlya Bakulin }
383*029c02a3SIlya Bakulin
384*029c02a3SIlya Bakulin return err;
385*029c02a3SIlya Bakulin }
386*029c02a3SIlya Bakulin
brcmf_sdiod_regrl(struct brcmf_sdio_dev * sdiodev,u32 addr,int * ret)387*029c02a3SIlya Bakulin u32 brcmf_sdiod_regrl(struct brcmf_sdio_dev *sdiodev, u32 addr, int *ret)
388*029c02a3SIlya Bakulin {
389*029c02a3SIlya Bakulin u32 data = 0;
390*029c02a3SIlya Bakulin int retval;
391*029c02a3SIlya Bakulin
392*029c02a3SIlya Bakulin brcmf_dbg(SDIO, "addr:0x%08x\n", addr);
393*029c02a3SIlya Bakulin retval = brcmf_sdiod_addrprep(sdiodev, sizeof(data), &addr);
394*029c02a3SIlya Bakulin if (retval)
395*029c02a3SIlya Bakulin goto done;
396*029c02a3SIlya Bakulin retval = brcmf_sdiod_regrw_helper(sdiodev, addr, sizeof(data), &data,
397*029c02a3SIlya Bakulin false);
398*029c02a3SIlya Bakulin brcmf_dbg(SDIO, "data:0x%08x\n", data);
399*029c02a3SIlya Bakulin
400*029c02a3SIlya Bakulin done:
401*029c02a3SIlya Bakulin if (ret)
402*029c02a3SIlya Bakulin *ret = retval;
403*029c02a3SIlya Bakulin
404*029c02a3SIlya Bakulin return data;
405*029c02a3SIlya Bakulin }
406*029c02a3SIlya Bakulin
407*029c02a3SIlya Bakulin /********************************************************/
40814bcf3e1SWarner Losh __unused
40914bcf3e1SWarner Losh static void
probe_bcrm(struct cam_device * dev)41014bcf3e1SWarner Losh probe_bcrm(struct cam_device *dev) {
41114bcf3e1SWarner Losh uint32_t cis_addr;
41214bcf3e1SWarner Losh struct cis_info info;
41314bcf3e1SWarner Losh
41414bcf3e1SWarner Losh sdio_card_set_bus_width(dev, bus_width_4);
41514bcf3e1SWarner Losh cis_addr = sdio_get_common_cis_addr(dev);
41614bcf3e1SWarner Losh printf("CIS address: %04X\n", cis_addr);
41714bcf3e1SWarner Losh
41814bcf3e1SWarner Losh memset(&info, 0, sizeof(info));
41914bcf3e1SWarner Losh sdio_func_read_cis(dev, 0, cis_addr, &info);
42014bcf3e1SWarner Losh printf("Vendor 0x%04X product 0x%04X\n", info.man_id, info.prod_id);
42114bcf3e1SWarner Losh }
422*029c02a3SIlya Bakulin
423*029c02a3SIlya Bakulin __unused static uint8_t*
mmap_fw()42414bcf3e1SWarner Losh mmap_fw() {
42514bcf3e1SWarner Losh const char fw_path[] = "/home/kibab/repos/fbsd-bbb/brcm-firmware/brcmfmac4330-sdio.bin";
42614bcf3e1SWarner Losh struct stat sb;
42714bcf3e1SWarner Losh uint8_t *fw_ptr;
42814bcf3e1SWarner Losh
42914bcf3e1SWarner Losh int fd = open(fw_path, O_RDONLY);
43014bcf3e1SWarner Losh if (fd < 0)
43114bcf3e1SWarner Losh errx(1, "Cannot open firmware file");
43214bcf3e1SWarner Losh if (fstat(fd, &sb) < 0)
43314bcf3e1SWarner Losh errx(1, "Cannot get file stat");
43414bcf3e1SWarner Losh fw_ptr = mmap(NULL, sb.st_size, PROT_READ, 0, fd, 0);
43514bcf3e1SWarner Losh if (fw_ptr == MAP_FAILED)
43614bcf3e1SWarner Losh errx(1, "Cannot map the file");
43714bcf3e1SWarner Losh
43814bcf3e1SWarner Losh return fw_ptr;
43914bcf3e1SWarner Losh }
44014bcf3e1SWarner Losh
44114bcf3e1SWarner Losh static void
usage()44214bcf3e1SWarner Losh usage() {
44314bcf3e1SWarner Losh printf("sdiotool -u <pass_dev_unit>\n");
44414bcf3e1SWarner Losh exit(0);
44514bcf3e1SWarner Losh }
44614bcf3e1SWarner Losh
447*029c02a3SIlya Bakulin struct card_info {
448*029c02a3SIlya Bakulin uint8_t num_funcs;
449*029c02a3SIlya Bakulin struct cis_info f[8];
450*029c02a3SIlya Bakulin };
451*029c02a3SIlya Bakulin
452*029c02a3SIlya Bakulin /*
453*029c02a3SIlya Bakulin * TODO: We should add SDIO card info about at least number of
454*029c02a3SIlya Bakulin * available functions to struct cam_device and use it instead
455*029c02a3SIlya Bakulin * of checking for man_id = 0x00 for detecting number of functions
456*029c02a3SIlya Bakulin */
45714bcf3e1SWarner Losh static void
get_sdio_card_info(struct cam_device * dev,struct card_info * ci)458*029c02a3SIlya Bakulin get_sdio_card_info(struct cam_device *dev, struct card_info *ci) {
45914bcf3e1SWarner Losh uint32_t cis_addr;
46014bcf3e1SWarner Losh uint32_t fbr_addr;
461*029c02a3SIlya Bakulin int ret;
46214bcf3e1SWarner Losh
46314bcf3e1SWarner Losh cis_addr = sdio_get_common_cis_addr(dev);
46414bcf3e1SWarner Losh
465*029c02a3SIlya Bakulin memset(ci, 0, sizeof(struct card_info));
466*029c02a3SIlya Bakulin sdio_func_read_cis(dev, 0, cis_addr, &ci->f[0]);
46714bcf3e1SWarner Losh printf("F0: Vendor 0x%04X product 0x%04X max block size %d bytes\n",
468*029c02a3SIlya Bakulin ci->f[0].man_id, ci->f[0].prod_id, ci->f[0].max_block_size);
469*029c02a3SIlya Bakulin for (int i = 1; i <= 7; i++) {
47014bcf3e1SWarner Losh fbr_addr = SD_IO_FBR_START * i + 0x9;
471*029c02a3SIlya Bakulin cis_addr = sdio_read_1(dev, 0, fbr_addr++, &ret);bailout(ret);
472*029c02a3SIlya Bakulin cis_addr |= sdio_read_1(dev, 0, fbr_addr++, &ret) << 8;
473*029c02a3SIlya Bakulin cis_addr |= sdio_read_1(dev, 0, fbr_addr++, &ret) << 16;
474*029c02a3SIlya Bakulin sdio_func_read_cis(dev, i, cis_addr, &ci->f[i]);
47514bcf3e1SWarner Losh printf("F%d: Vendor 0x%04X product 0x%04X max block size %d bytes\n",
476*029c02a3SIlya Bakulin i, ci->f[i].man_id, ci->f[i].prod_id, ci->f[i].max_block_size);
477*029c02a3SIlya Bakulin if (ci->f[i].man_id == 0) {
478*029c02a3SIlya Bakulin printf("F%d doesn't exist\n", i);
479*029c02a3SIlya Bakulin break;
48014bcf3e1SWarner Losh }
481*029c02a3SIlya Bakulin ci->num_funcs++;
48214bcf3e1SWarner Losh }
48314bcf3e1SWarner Losh }
48414bcf3e1SWarner Losh
48514bcf3e1SWarner Losh int
main(int argc,char ** argv)48614bcf3e1SWarner Losh main(int argc, char **argv) {
48714bcf3e1SWarner Losh char device[] = "pass";
48814bcf3e1SWarner Losh int unit = 0;
48914bcf3e1SWarner Losh int func = 0;
49014bcf3e1SWarner Losh __unused uint8_t *fw_ptr;
49114bcf3e1SWarner Losh int ch;
49214bcf3e1SWarner Losh struct cam_device *cam_dev;
493*029c02a3SIlya Bakulin int ret;
494*029c02a3SIlya Bakulin struct card_info ci;
49514bcf3e1SWarner Losh
49614bcf3e1SWarner Losh //fw_ptr = mmap_fw();
49714bcf3e1SWarner Losh
498*029c02a3SIlya Bakulin while ((ch = getopt(argc, argv, "fu:")) != -1) {
49914bcf3e1SWarner Losh switch (ch) {
50014bcf3e1SWarner Losh case 'u':
50114bcf3e1SWarner Losh unit = (int) strtol(optarg, NULL, 10);
50214bcf3e1SWarner Losh break;
50314bcf3e1SWarner Losh case 'f':
50414bcf3e1SWarner Losh func = (int) strtol(optarg, NULL, 10);
505*029c02a3SIlya Bakulin break;
50614bcf3e1SWarner Losh case '?':
50714bcf3e1SWarner Losh default:
50814bcf3e1SWarner Losh usage();
50914bcf3e1SWarner Losh }
51014bcf3e1SWarner Losh }
51114bcf3e1SWarner Losh argc -= optind;
51214bcf3e1SWarner Losh argv += optind;
51314bcf3e1SWarner Losh
51414bcf3e1SWarner Losh if ((cam_dev = cam_open_spec_device(device, unit, O_RDWR, NULL)) == NULL)
51514bcf3e1SWarner Losh errx(1, "Cannot open device");
51614bcf3e1SWarner Losh
517*029c02a3SIlya Bakulin get_sdio_card_info(cam_dev, &ci);
51814bcf3e1SWarner Losh
519*029c02a3SIlya Bakulin /* For now, everything non-broadcom is out of the question */
520*029c02a3SIlya Bakulin if (ci.f[0].man_id != 0x02D0) {
521*029c02a3SIlya Bakulin printf("The card is not a Broadcom device\n");
522*029c02a3SIlya Bakulin exit(1);
523*029c02a3SIlya Bakulin }
524*029c02a3SIlya Bakulin /* Init structures */
525*029c02a3SIlya Bakulin struct brcmf_sdio_dev brcmf_dev;
526*029c02a3SIlya Bakulin struct brcmf_bus bus_if;
527*029c02a3SIlya Bakulin struct sdio_func f0, f1, f2;
528*029c02a3SIlya Bakulin bus_if.state = BRCMF_BUS_DOWN;
529*029c02a3SIlya Bakulin brcmf_dev.cam_dev = cam_dev;
530*029c02a3SIlya Bakulin brcmf_dev.bus_if = &bus_if;
531*029c02a3SIlya Bakulin brcmf_dev.state = BRCMF_SDIOD_DOWN;
53214bcf3e1SWarner Losh
533*029c02a3SIlya Bakulin /* Fill in functions */
534*029c02a3SIlya Bakulin brcmf_dev.func[0] = &f0;
535*029c02a3SIlya Bakulin brcmf_dev.func[1] = &f1;
536*029c02a3SIlya Bakulin brcmf_dev.func[2] = &f2;
53714bcf3e1SWarner Losh
538*029c02a3SIlya Bakulin brcmf_dev.func[0]->dev = brcmf_dev.func[1]->dev
539*029c02a3SIlya Bakulin = brcmf_dev.func[2]->dev = cam_dev;
540*029c02a3SIlya Bakulin brcmf_dev.func[0]->num = 0;
541*029c02a3SIlya Bakulin brcmf_dev.func[1]->num = 1;
542*029c02a3SIlya Bakulin brcmf_dev.func[2]->num = 2;
54314bcf3e1SWarner Losh
544*029c02a3SIlya Bakulin ret = sdio_func_enable(cam_dev, 1, 1);bailout(ret);
545*029c02a3SIlya Bakulin uint32_t magic = brcmf_sdiod_regrl(&brcmf_dev, 0x18000000, &ret);
546*029c02a3SIlya Bakulin printf("Magic = %08x\n", magic);
547*029c02a3SIlya Bakulin if (magic != REPLY_MAGIC) {
548*029c02a3SIlya Bakulin errx(1, "Reply magic is incorrect: expected %08x, got %08x",
549*029c02a3SIlya Bakulin REPLY_MAGIC, magic);
550*029c02a3SIlya Bakulin }
55114bcf3e1SWarner Losh cam_close_spec_device(cam_dev);
55214bcf3e1SWarner Losh }
553