好几年前就一想完成两件挑战:
先是陆陆续续的完成了 QEMU 模拟器,还把它搞了成了一个 W802 虚拟开发板,我感觉这算是超额完成了自己的预计吧。
于是就剩个 sdio wifi 网卡的事情一直没有完成,其实几年前就在 W600 上实现了一个 uart wifi 驱动,可以工作在 linux 用户层,但是典型的 linux sdio wifi 网卡驱动一直没完成,所以这个周末我爆肝一天一夜,把这个目标给他实现了。
W800 支持 sdio slave 最高工作在 50 MHZ,算是典型的 sdio wifi 网卡应用,其 SDK 驱动接口使用也比较简单,可以很容易使用起来。
盘点了一下我手头可以做这个测试的 linux 设备,发现有 T113-s3、V3s、F1C100s 这三款的开发板可用,好吧,全是全志的,感觉已经 allwinner 了。
那么首先我就以 T113-s3 开发板来调试了,这是百问网韦老师出的 linux 开发板,之前一个朋友送我的,使用 tina-sdk 开发,开发环境比较完整和方便。
linux 的驱动框架非常成熟了,实现 sdio wifi 网卡的话,无非就是首先 sdio 总线识别到 sdio 设备,然后根据设备驱动注册的 id 进行匹配,匹配上之后就可以创建网络设备了,创建网络设备之后,就是使用 sdio 进行数据收发。
首先向 sdio 框架注册网卡驱动:
static const struct sdio_device_id w800_sdio_ids[] = {
{ SDIO_DEVICE(0x0296, 0x5347) },
{ /* end */ },
};
static struct sdio_driver w800_sdio_driver = {
.name = DRIVER_NAME,
.id_table = w800_sdio_ids,
.probe = w800_sdio_probe,
.remove = w800_sdio_remove,
#ifdef CONFIG_PM
.drv = {
.pm = &w800_sdio_pm_ops,
},
#endif
};
......
sdio_register_driver(&w800_sdio_driver);
W800 的 sdio slave 驱动默认配的 manufacturer id 是 0x0296、device id 是 0x5347,当然也是可以自定义修改为其它值。
检测到 sdio slave 设备时,总线就会根据 id_table 填写的 manufacturer id 和 device id 进行匹配,匹配之后之后就会调用 probe 函数,到时候就可以在 probe 函数中创建相关的网络设备了。
linux 中的 wifi 现在主要是依托于 cfg80211 框架,所以编程时就是需要创建 wiphy,然后把 wiphy 关联到 netdev,最后注册到网络框架中即可。
wiphy = wiphy_new(&wifi_cfg80211_ops, sizeof(*wpriv));
......
wiphy_register(wiphy)
......
netdev = alloc_netdev(sizeof(*npriv), "wlan%d", NET_NAME_UNKNOWN, ether_setup);
......
netdev->netdev_ops = &wifi_netdev_ops;
......
register_netdev(netdev)
其中,netdev_ops 就是网卡 up、down、接收、发送的对接回调,需要对接实现。cfg80211_ops 就是 cfg80211 框架需要实现 wifi 功能所对接的回调,想让 sdio wifi 网卡支持哪些 wifi 功能,就适配哪些即可,无需全部适配。
做为 station 模式使用时,最小的实现为仅需要实现这几个就够了:
static const struct cfg80211_ops wifi_cfg80211_ops = {
.scan = wifi_scan,
.connect = wifi_connect,
.disconnect = wifi_disconnect,
.add_key = wifi_add_key,
.del_key = wifi_del_key,
.set_default_key = wifi_set_default_key,
};
static const struct net_device_ops wifi_netdev_ops = {
.ndo_open = wifi_net_open,
.ndo_stop = wifi_net_stop,
.ndo_start_xmit = wifi_hard_start_xmit,
.ndo_set_mac_address = wifi_set_mac_address,
};
剩下就是实现 sdio 的数据发送和接收了,W800 的 sdio slave 所支持的 sdio 功能也比较标准(我都怀疑好多家芯片都是使用的同一个 sdio ip 实现的,甚至很多家的芯片连 manufacturer id 和 device id 都完全一样)。
linux 下读写 sdio 基本上用这俩接口就够了:
u8 sdio_readb(struct sdio_func *func, unsigned int addr, int *err_ret)
void sdio_writeb(struct sdio_func *func, u8 b, unsigned int addr, int *err_ret)
通常在收发数据前要做三件事:
sdio_enable_func(func)
sdio_set_block_size(func, SDIO_BLOCK_SIZE)
sdio_claim_irq(func, sdio_interrupt)
在给 W800 发送数据时,分为 cmd 和 data 两种通道,cmd 通道往 fn1 的 0x4000 地址填写,data 通道往 fn1 的 0x5000 地址填写(最后一个块往 0x15000 地址填写)。如:
sdio_memcpy_toio(func, 0x4000, buf, block_size);
......
sdio_memcpy_toio(func, 0x5000 + offset, buf + offset, block_size);
接收时长度参考 fn1 的寄存器读取即可,当 sdio 中断产生后,读取该寄存器就能知道本次收到的数据长度,接收时读取 fn1 的 0x6000 地址(最后一个块为 0x16000 地址)获取到数据。如:
sdio_memcpy_fromio(func, rx_buffer + rx_received, 0x6000 + offset, block_size);
当然每次操作 sdio 总线,都需要做保护:
sdio_claim_host(func);
sdio_release_host(func);
linux 下 sdio 的参数配置都在设备树中配置,不直接在代码中显式编程,如:
&sdc0 {
bus-width = <4>;
no-mmc;
no-sd;
broken-cd;
cap-sd-highspeed;
cap-sdio-irq;
keep-power-in-suspend;
max-frequency = <10000000>;
status = "okay";
};
因为 T113-s3 这块开发板只有 sdc0 可以用,而默认 sdc0 被配成 tf 卡启动,所以还需要修改 sun8iw20p1.dtsi 中的 sdc0 默认配置去掉 no-sdio
配置项。
因为咱这是开发板 + 杜邦线的方式搞的调试,50 MHZ + 4 线那是不可能达到的,所以就以 10 MHZ 做了测试。来张调通后的测试图:
这样是不是发现,其实实现也很简单,难的就是后续的性能调优罢了。
虽然 T113-s3 被大量产品所使用,但是总的来说价格还是有点高了,像我这样的穷逼总是想着找点便宜的 linux 开发板用用,前些年我在网上搜着看了一圈,发现最便宜的 linux 开发板似乎就是叫做荔枝派的国产排类开发板,于是就发现了两款比较具有性价比的型号:V3s 和 F1C100s,也就是 lichee zero 和 lichee nano。V3s 有两路 sdio ,可以一个用作 tf 卡来做启动,另一个用在 sdio wifi。F1C100s 就只有一路 sdio,想使用 sdio wifi 的话,就只能用 spiflash 烧写固件启动了,虽然 F1C100s 使用 spiflash 时没有用 tf 的好用,但是架不住价格便宜啊,想一想就二十来块钱的 linux 开发板再加上一块几块钱的 sdio wifi 卡,那真是性价比很高,学习和调试都很划算。所以我又在 V3s 和 F1C100s 上也分别尝试了一下,也都可以使用。
V3s 我用的 tf 做启动,使用 emdebian 根文件系统,整体可玩性较高:
F1C100s 只能用 spiflash 呀,我这块板载了 16 MB 的 spi nor-flash,使用 buildroot 刚好放下,可玩性差点,但也能用:
后来我想到,既然都实现了 sdio wifi 驱动了,为啥不把蓝牙驱动也给实现呢? W800 是具有蓝牙功能,所以这个完全是可行的,因为蓝牙的速率较低,一般的蓝牙驱动都是用的 uart 方案,正好蓝牙的 hci 也是标准,所以只需要把两边的串口连接起来,使用 hci 交互即可。基于此,我就简单验证了一下:
可以看到,这也是完全可行的,成功识别到蓝牙控制器。串口编程相比 sdio 就简单太多,所以我也就不在继续了。
最后,提供我编好的 sdio wifi 驱动,感兴趣的朋友可以去玩玩。
· W800 设备的烧录固件 w800_wifi.fls,这个需要烧录到 W800 设备。
· linux 上的 W800 固件 fw_w800.bin,这个需要放到 /lib/firmware 目录下。
· T113-s3 上的驱动模块 w800_wifi_t113-s3.ko,基于 tina-sdk linux-5.4 开发。
· V3s 上的驱动模块 w800_wifi_v3s.ko,基于主线 linux-5.2 开发。
· F1C100s 上的驱动模块 w800_wifi_f1c100s.ko,基于主线 linux-4.15 开发。