Rockchip RK3399 - WiFi AP6356驱动
----------------------------------------------------------------------------------------------------------------------------
开发板 :NanoPC-T4开发板
eMMC :16GB
LPDDR3 :4GB
显示屏 :15.6英寸HDMI接口显示屏
u-boot :uboot 2023.04
linux :6.3、5.2.8
----------------------------------------------------------------------------------------------------------------------------
最初我的计划是从uboot开始讲起的,然后再来讲内核部分,但是在移植linux 6.3内核的时候发现其在支持WiFi模组AP6356时需要对内核代码进行大量的调整,不得不先过来研究一下AP6356驱动。
需要注意的是:本篇博客是以linux 6.3版本为主体介绍的,但是内容同样适用于linux 5.2.8版本。
一、AP6356介绍
1.1 WiFi介绍
我们日常生活中,已经离不开WiFi,那到底是什么是WiFi?WiFi是一种基于IEEE 802.11标准的无线局域网技术,常用于在短距离范围内(例如建筑物、办公室或家庭)提供无线互联网接入。WiFi技术允许设备通过无线信号进行通信,例如智能手机、平板电脑、笔记本电脑、智能电视等设备都可以通过WiFi连接到互联网。
WiFi技术的实现主要依靠一系列硬件和软件设备,主要包括以下几个方面:
- 无线接入点:它是一个中央设备,通常被连接到网络的有线服务器或互联网服务提供商提供的调制解调器上。无线接入点可以转发数据流,也可以管理网络中的所有设备,并确保数据的安全性;
- 无线网卡:它是一种网络适配器,它通过将电脑或其他设备上的数据编码成一个无线信号,使得数据能够在无线网络上进行传输;
- 无线路由器:它是一个设备,将无线信号从无线网卡发送到无线接入点,并确定最佳的路由来实现无线数据传输;;
- 网络管理软件:这些软件可以辅助管理网络,例如配置无线接入点、设置网络加密等;
在实际应用中,这些硬件和软件设备通过遵循IEEE 802.11标准并使用无线电波来进行通信,从而实现了无线互联网的连接和数据传输。
这里顺带说一下WLAN,WLAN英文全称是Wireless Local Area Networks,无线局域网络。WiFi是实现WLAN的一种技术。
1.1.1 WiFi模组
这里提到了WiFi模组,那和前面说的无线网卡、还有WiFi模块有啥区别呢?本质上,无论是无线网卡、还是WiFi模块、WiFi模组都是一种东西,就是提供无线上网的一种设备,但是从使用者的角度对其进行了具体的区分。
- 无线网卡:不同点在网卡,网卡分开就是网(上网)和卡(用于插入对应接口的插槽的集成电路电路板,也就是强调即插即用);
- WiFi模块:不同点在模块,这里的模块感觉来自于计算机系统设计过程中的功能模块和设备模块。换言之,WiFi模块,是计算机系统中用于无线上网的设备模块(这个设备模块往往集成了核心的无线通信芯片和相关外围电路),重点强调功能和模块化设计;
- WiFi模组:不同点在模组,这里的模组更强调器件的组装特性。换言之,无线模组,强调计算机系统硬件组装时的无线通信部分,重点强调硬件的组装;
WiFi功能在SoC中有两种方式:
- 集成在SoC中,芯片代表有MTK(联发科)和高通的芯片,因为这两家芯片厂商的通讯技术都比较厉害,所以一般都会在他们自己的ScC中添加WiFi的模块,从而降低成本和降低板子的面积;
- SoC通过外部接口连接WiFi模组,WiFi模组常用的通讯接口有SDIO、USB、PCIe、SPI、UART等;
常用的WiFi芯片厂商有NXP、瑞昱(Realtek)、博通Broadcom、MTK、Atheros等,然后一些模组厂商会使用这些公司的芯片来做一下WiFi模组,常见的模组厂商有富士康、海华、高盛达等等,模组厂商会调制好一些WiFi的芯片射频参数,写入模组当中,如果直接使用芯片在生产的时候需要调试WiFi的射频参数,而使用WiFi模组可以降低直接使用芯片的难度。
注意:模组和芯片有什么区别呢?模组通常是指将一个或多个芯片、电源管理电路、射频信号处理器、天线、外部连接器等封装在一个完整的系统中。这种设计可方便地集成到其他系统中,例如智能手机、物联网设备、手持设备等,以实现特定的功能和应用。更通俗的点说就是芯片是集成电路的一个核心,而模组是在芯片和其他外围电路之间提供完整系统解决方案的整合器。
1.1.2 WiFi蓝牙二合一硬件接口
为了降低成本,现在很多模组都是集成了WiFi和蓝牙功能的,所以我们不只看到有WiFi使用的SDIO接口,还有用于蓝牙通讯的UART接口,还有PCM接口。
我们播放歌曲的时候音乐的数据的传输使用的是UART接口,如果是蓝牙通话的时候使用的是PCM接口。所以我们可以在WiFi和蓝牙二合一的芯片上看到3种接口,其中SDIO是WiFi的,UART和PCM是蓝牙的。
我们使用的NanoPC-T4开发板搭载的就是一款集成了WiFi和蓝牙功能的二合一WiFi模组,型号为AP6356,基于Boardcom(博通)的WiFi芯片。
1.2 AP6356
AP6356是一款支持WiFi(2.4G/5G))+ BT蓝牙 4.1(2.4G)的模组,符合IEEE802.11a/b/g/n/ac 2x2 MIMO标准,并可在802.11n中通过双流达到867Mbps的速度以连接无线局域网。该集成模块提供SDIO/PCIe接口用于WiFi,UART/PCM接口用于蓝牙。
1.2.1 功能框图
AP6356功能框图如下所示,该图来自AP6356 datasheet:
AP6356一共包含了50个引脚,具体如下:
编号 | 名称 | 类型 | 描述 |
1 | GND | --- | Ground connections |
2 | WL/BT_ANT0 | I/O | RF I/O port0 |
3 | GND | --- | Ground connections |
4 | GND | --- | Ground connections |
5 | GND | --- | Ground connections |
6 | GND | --- | Ground connections |
7 | GND | --- | Ground connections |
8 | GND | --- | Ground connections |
9 | WL_ANT1 | I/O | RF I/O port1 |
10 | GND | --- | Ground connections |
11 | GND | --- | Ground connections |
12 | PCIE_PERST | I | PCIE system reset |
13 | XTAL_OUT | O | External Crystal out |
14 | XTAL_IN | I | External Crystal in/ Single clock source in |
15 | WL_REG_ON | I | Low asserting reset for WiFi core |
16 | WL_HOST_WAKE | O | WLAN to wake-up HOST |
17 | SDIO_DATA_CMD | I/O | SDIO command line |
18 | SDIO_DATA_CLK | I/O | SDIO clock line |
19 | SDIO_DATA_3 | I/O | SDIO data line 3 |
20 | SDIO_DATA_2 | I/O | SDIO data line 2 |
21 | SDIO_DATA_1 | I/O | SDIO data line 1 |
22 | SDIO_DATA_0 | I/O | SDIO data line 0 |
23 | GND | --- | Ground connections |
24 | PCIE_PME_L | O | PCIE power management event output |
25 | VIN_LDO | P | Internal Buck voltage generation pin |
26 | VIN_LDO_OUT | P | Internal Buck voltage generation pin |
27 | PCM_SYNC | I/O | PCM sync signal |
28 | PCM_IN | I | PCM data input |
29 | PCM_OUT | O | PCM Data output |
30 | PCM_CLK | I/O | PCM clock |
31 | LPO | I | External Low Power Clock input (32.768KHz) |
32 | GND | --- | Ground connections |
33 | PCIE_REFCLK_N | I | PCIE differential clock inputs 100MHz differential |
34 | VDDIO | P | I/O Voltage supply input |
35 | PCIE_REFCLK_P | I | PCIE differential clock inputs 100MHz differential |
36 | VBAT | P | Main power voltage source input |
37 | PCIE_CLKREQ_L | O | PCIE clock request signal |
38 | BT_REG_ON | I | Low asserting reset for Bluetooth core |
39 | GND | --- | Ground connections |
40 | UART_TXD | O | Bluetooth UART interface |
41 | UART_RXD | I | Bluetooth UART interface |
42 | UART_RTS_N | O | Bluetooth UART interface |
43 | UART_CTS_N | I | Bluetooth UART interface |
44 | PCIE_RDN | I | PCIE receiver differential pair |
45 | PCIE_RDP | I | PCIE receiver differential pair |
46 | PCIE_TDN | O | PCIE transmitter differential pair |
47 | PCIE_TDP | O | PCIE transmitter differential pair |
48 | GPIO8_9 | I | Mode selection, 1=PCIE mode , 0=SDIO mode |
49 | BT_WAKE | I | HOST wake-up Bluetooth device |
50 | BT_HOST_WAKE | O | Bluetooth device to wake-up HOST |
1.2.2 电源
AP6356需要两个电源供应,VBAT、VDDIO;
- VBAT:输入供应电源,引脚编号为36;
- VDDIO:数字/蓝牙/SDIO/I/O电压,引脚编号为34;
1.2.3 SDIO引脚
AP6356支持SDIO 3.0版本,并且向下兼容SDIO2.0接口;其工作在1.8V、3.3V电压下(由VDDIO引脚决定),4位数据宽度;- 工作在1.8V电压下(超高速模式):SDR50(100Mbps)、SDR104(208MHz)、DDR50(50MHz);
- 工作在3.3V电压下(高速模式):默认速度(25MHZ)、高速(50MHZ);
需要注意的是:WiFi部分共有两部分供电组成,一个是主控端(即RK3399)的IO:CLK/CMD/D0~D3;另一个是WiFi模块的IO的供电,由WiFi模块中的VDDIO引脚供电,两部分供电必须一致否则会导致WiFi异常。
AP6356具有停止SDIO时钟和将中断信号映射到GPIO引脚的能力,WiFi模组想要打开SDIO接口时,会向主机发送 ‘out-of-band’中断信号,还提供了从WiFi模组内部强制控制门控时钟的能力。
以下是各个功能的说明:
- 功能0:标准SDIO功能,最大块大小/字节数=32B;
- 功能1:背板功能,用于访问内部SoC地址空间,最大块大小/字节数=64B;
- 功能2:WiFi功能,用于通过DMA进行高效的WiFi数据包传输,最大块大小/字节数=512B;
SDIO引脚描述:
SD 4-Bit Mode |
|
DATA0 | Data Line 0 |
DATA1 | Data Line 1 or Interrupt |
DATA2 | Data Line 2 or Read Wait |
DATA3 | Data Line 3 |
CLK | Clock |
CMD | Command Line |
此外与WiFi相关比较重要的引脚还有:
- WL_REG_ON:用于WiFi部分的使能;
- WL_HOST_WAKE:当WiFi部分接收到数据时,可以通过中断唤醒CPU;
- GPIO8_9:模式选择,0为SDIO模式,1位PCIe模式;
1.3 电路原理图
下图是我们使用的NanoPC-T4开发板AP6356的接线图:
需要注意的是:
- 在下图的右下角标注了VBAT电压范围为3.0~4.8V,输入电压源来自VCC3V3_SYS,这个信号是由电源输入的12V电源经过稳压管NB680GD输出得到的;
- VDDIO输入电压为1.8V,输入电压来自rk808电源管理芯片61号引脚输出的;
- LPO(外部低功耗时钟输入)输入引脚连接的是RTC_CLKO_WIFI,而该引脚来自rk808电源管理芯片67号输出引脚CLK32KOUT2;
1.3.1 WiFi相关引脚接线
这里我们需要关注一下RK3399与AP6356 WiFi相关引脚的接线:
AP6356 | RK3399 | 其他 | 功能 |
XTAL_OUT | 37.4MHZ晶振 | ||
XTAL_IN | 37.4MHZ晶振 | ||
WL_REG_ON | WIFI_REG_ON_H(GPIO0_B2) | 开启/关闭WiFi | |
WL_HOST_WAKE | WIFI_HOST_WAKE_L(GPIO0_A3) | WiFi设备唤醒主机 | |
SDIO_DATA_CMD | SDIO0_CMD(GPIO2_D0) | SDIO Command Line | |
SDIO_DATA_CLK | SDIO0_CLK(GPIO2_D1) | SDIO Clock | |
SDIO_DATA_3 | SDIO0_D3(GPIO2_C7) | Data Line 3 | |
SDIO_DATA_2 | SDIO0_D2(GPIO2_C6) | Data Line 2 or Read Wait | |
SDIO_DATA_1 | SDIO0_D1(GPIO2_C5) | Data Line 1 or Interrupt | |
SDIO_DATA_0 | SDIO0_D0(GPIO2_C4) | Data Line 0 | |
LPO | rk808电源管理芯片67号输出引脚CLK32KOUT2 | 外部低功耗时钟输入 |
二、时序图
2.1 上电时序
AP6356有一些信号,允许主机通过启用或禁用蓝牙、WiFi来控制功耗。下面描述了这些信号,此外,提供了图表以指示各种操作状态的正确时序。所示的时序值是最小要求值:更长的延迟也是可以接受的。
- WL_REG_ON:开启和关闭WiFi功能;
- BT_REG_ON:开启和关闭蓝牙功能,该引脚必须驱动到高电平或低电平(不能让其悬空);
2.1.1 WiFi开启、蓝牙开启
2.1.2 WiFi关闭、蓝牙关闭
2.1.3 WiFi开启、蓝牙关闭
2.1.3 WiFi关闭、蓝牙开启
2.2 SDIO时序
2.2.1 默认模式
2.2.2 高速模式
2.2.3 SDR模式
2.2.4 DDR50模式
三、相关驱动介绍
SDIO总线和USB总线类似,SDIO也有两端,其中一端为主机(Host)端,另一端是设备端(Device),采用Host-Device这样的设计是为了简化Device的设计,所有的通信都由Host端发出命令开始、在Device端只要能解析Host发出的命令,就可以同Host进行通信了,SDIO的Host可以连接多个Device。
SDIO的驱动可以分为:
- SDIO主机控制器驱动:针对不同主机端的SDIO控制器的驱动;
- 主机端SDIO设备驱动:针对不同客户端的设备驱动程序。如SD卡、T-Flash卡、SDIO接口的GPS和WiFi等设备驱动;
而WiFi设备驱动实际上又涉及到多个驱动模块,比如rkfill驱动,802.11模块驱动、以及WiFI设备自身驱动。
3.1 rfkill驱动
rfkill驱动是和平台SoC有关联的的,一般是SoC厂家提供,这部分的功能是给RF设备(无线通信设备、比如蓝牙、WiFi)上下电使用,比如唤醒引脚配置,当RF设备接收到数据的时候用来唤醒系统,比如上下电引脚配置,用于开启/禁用RF设备,这部分的功能就集成在rfkill-wlan.c、rfkill-bt.c。
关于rfkill 蓝牙驱动驱动、rfkill WiFi驱动源码分析参考Rockchip RK3399 - rfkill子系统。
3.2 WiFI设备自身驱动
WiFI设备自身驱动一般都是由WiFi芯片厂家提供,比如WiFi芯片厂商瑞昱、博通;这部分是标准的基本不用修改,不管是NXP、Rockchip、全志等平台都是使用这一套代码。
四、设备树以及源码调整
由于linux 6.3版本并没有直接支持AP6356,因此为了支持AP6356这里需要修改的内容比较多,因为这涉及到到了源码以及设备树的调整。
4.1 MMC驱动配置
4.1.1 新增sdio_pwrseq设备节点
修改arch/arm64/boot/dts/rockchip/rk3399-evb.dts,在根节点下新增sdio_pwrseq设备节点。sdio_pwrseq设备节点节点用于控制WiFi功能的开启和关闭。
sdio_pwrseq: sdio-pwrseq { compatible = "mmc-pwrseq-simple"; clocks = <&rk808 1>; clock-names = "ext_clock"; pinctrl-names = "default"; pinctrl-0 = <&wifi_enable_h>; /* * On the module itself this is one of these (depending * on the actual card populated): * - SDIO_RESET_L_WL_REG_ON * - PDN (power down when low) */ reset-gpios = <&gpio0 10 GPIO_ACTIVE_LOW>; /* GPIO0_B2 */ };
其中属性:
- clocks:指定了设备使用的时钟源,即使用rk808时钟控制器ID为1的时钟,rk808是一个时钟提供者clock provider;
- clock-names:指定了所使用的时钟的名称,即 "ext_clock";
- pinctrl-names:设置了引脚的默认状态,引脚配置设置为wifi_enable_h;
- pinctrl-0:指定了default状态的对应的引脚配置,即wifi_enable_h;
- reset-gpios:指定wlan芯片所使用的的引脚为GPIO0_B2(这个引脚是gpio0控制器的第 10 个引脚,GPIO0_B2连接的是AP6356 的WL_REG_ON引脚),低电平有效,即低电平时会关闭WiFi功能;
sdio_pwrseq设备节点对应的驱动文件为drivers/mmc/core/pwrseq_simple.c,这个后面再来分析。
4.1.2 新增wifi_enable_h引脚配置节点
在介绍引脚配置节点之前,我们需要了解RK3399 GPIO的一些基础知识,RK3399共有5组GPIO口,依次为GPIO0~GPIO4;每一组又以A0~A7、B0~B7、C0~C7、D0~D7作为编号区分。
由于sdio_pwrseq节点配置了default状态对应的引脚配置节点为wifi_enable_h,因此需要在pinctrl节点下增加引脚配置节点wifi_enable_h:
sdio-pwrseq { wifi_enable_h: wifi-enable-h { rockchip,pins = <0 RK_PB2 RK_FUNC_GPIO &pcfg_pull_none>; }; };
其中属性rockchip,pins = <PIN_BANK PIN_BANK_IDX MUX &phandle>的意义如下:
- PIN_BANK:引脚所在的 bank;GPIO0~GPIO3依次对应0~3;
- PIN_BANK_IDX:引脚所在bank的引脚号;0~31,;
- MUX:功能复用配置,0 表示普通 GPIO,1-N 表示特殊的功能复用;
- phandle:引脚的电气特性,例如内部上拉、电流强度等;
因此此处配置GPIO0_B2引脚功能为GPIO,电气特性为pcfg_pull_none,表示普通配置;
更多关于节点属性含义含义参考:Documentation/devicetree/bindings/pinctrl/rockchip,pinctrl.txt;Documentation/devicetree/bindings/pinctrl/pinctrl-bindings.txt。
4.1.3 新增sdio0设备节点属性
sdio0设备节点已经在arch/arm64/boot/dts/rockchip/rk3399.dtsi文件中定义,在根节点下我们可以找到:
sdio0: dwmmc@fe310000 { compatible = "rockchip,rk3399-dw-mshc", "rockchip,rk3288-dw-mshc"; reg = <0x0 0xfe310000 0x0 0x4000>; interrupts = <GIC_SPI 64 IRQ_TYPE_LEVEL_HIGH 0>; max-frequency = <150000000>; clocks = <&cru HCLK_SDIO>, <&cru SCLK_SDIO>, <&cru SCLK_SDIO_DRV>, <&cru SCLK_SDIO_SAMPLE>; clock-names = "biu", "ciu", "ciu-drive", "ciu-sample"; fifo-depth = <0x100>; power-domains = <&power RK3399_PD_SDIOAUDIO>; resets = <&cru SRST_SDIO0>; reset-names = "reset"; status = "disabled"; };
同时可以在pinctrl节点下可以找到sdio0引脚配置节点:

sdio0 { sdio0_bus1: sdio0-bus1 { rockchip,pins = <2 RK_PC4 1 &pcfg_pull_up>; }; sdio0_bus4: sdio0-bus4 { rockchip,pins = <2 RK_PC4 1 &pcfg_pull_up>, <2 RK_PC5 1 &pcfg_pull_up>, <2 RK_PC6 1 &pcfg_pull_up>, <2 RK_PC7 1 &pcfg_pull_up>; }; sdio0_cmd: sdio0-cmd { rockchip,pins = <2 RK_PD0 1 &pcfg_pull_up>; }; sdio0_clk: sdio0-clk { rockchip,pins = <2 RK_PD1 1 &pcfg_pull_none>; }; sdio0_cd: sdio0-cd { rockchip,pins = <2 RK_PD2 1 &pcfg_pull_up>; }; sdio0_pwr: sdio0-pwr { rockchip,pins = <2 RK_PD3 1 &pcfg_pull_up>; }; sdio0_bkpwr: sdio0-bkpwr { rockchip,pins = <2 RK_PD4 1 &pcfg_pull_up>; }; sdio0_wp: sdio0-wp { rockchip,pins = <0 RK_PA3 1 &pcfg_pull_up>; }; sdio0_int: sdio0-int { rockchip,pins = <0 RK_PA4 1 &pcfg_pull_up>; }; };
这里我们需要在arch/arm64/boot/dts/rockchip/rk3399-evb.dts文件为sdio0设备节点新增属性:
&sdio0 { clock-frequency = <150000000>; clock-freq-min-max = <200000 150000000>; supports-sdio; bus-width = <4>; disable-wp; cap-sd-highspeed; cap-sdio-irq; keep-power-in-suspend; mmc-pwrseq = <&sdio_pwrseq>; non-removable; num-slots = <1>; pinctrl-names = "default"; pinctrl-0 = <&sdio0_bus4 &sdio0_cmd &sdio0_clk>; sd-uhs-sdr104; status = "okay"; };
4.2 WiFi驱动配置
Rockchip官方平台linux版本(4.4、4.19、5.10)实现了一套兼容多款WiFi芯片的自适应框架,位于drivers/net/wireless/rockchip_wlan路径下,简单来说就是将多款WiFi芯片的驱动编译成xxx.ok文件,比如常用的WiFi芯片厂商瑞昱、博通的WiFi芯片的驱动统一编译成不同的xxx.ko文件。
但是我们使用的Linux 6.3版本并没有drivers/net/wireless/rockchip_wlan这个文件夹,因此我们要不将这个文件夹从Rockchip官方平台linux版本拷贝过来(拷贝过来后需要进行配置、源码调整,不然编译不过),要不然只能另辟蹊径。
AP6356使用的是博通BCM4356方案,所以在linux中,使能brcmfmac模块驱动即可;如果不知道使用的博通哪种方案,可以先不配置固件,在内核启动的时候会提示缺少哪一种固件,比如linux 5.2.8版本错误:
[ 4.087532] brcmfmac: brcmf_fw_alloc_request: using brcm/brcmfmac4356-sdio for chip BCM4356/2 [ 4.097518] brcmfmac mmc0:0001:1: Direct firmware load for brcm/brcmfmac4356-sdio.bin failed with error -2
linux 6.3版本错误:
[ 831.957933] brcmfmac: brcmf_fw_alloc_request: using brcm/brcmfmac4356-sdio for chip BCM4356/2 [ 831.968205] brcmfmac mmc1:0001:1: Direct firmware load for brcm/brcmfmac4356-sdio.rockchip,rk3399-evb.bin failed with error -2 [ 832.083413] brcmfmac: brcmf_fwvid_request_module: mod=wcc: failed 256 [ 832.090694] ieee80211 phy0: brcmf_attach: brcmf_fwvid_attach failed [ 832.121230] brcmfmac: brcmf_sdio_firmware_callback: brcmf_attach failed
从2.6.24开始,内核收录的是b43驱动,该驱动是当初博通的驱动没有开源,有大佬通过逆向工程得到的驱动源码;
从2.6.39开始,博通完全开源了硬件驱动,内核发布版本中包含此驱动,分为brcmsmac和brcmfmac;
- brcmfmac:提供原生硬MAC支持,支持较新的芯片支持,支持AP模式、P2P模式、高级加密;
- brcmsmac:提供基于mac802.11的软MAC支持,仅支持较老的芯片,比如BCM4313、BCM43224、BCM43225;
驱动文件在drivers/net/wireless/broadcom目录下;
root@zhengyang:/work/sambashare/rk399/linux-6.3# ll drivers/net/wireless/broadcom 总用量 28 drwxr-xr-x 5 root root 4096 Jun 4 14:10 ./ drwxr-xr-x 18 root root 4096 May 17 01:26 ../ drwxr-xr-x 2 root root 4096 May 17 01:26 b43/ drwxr-xr-x 2 root root 4096 May 17 01:26 b43legacy/ drwxr-xr-x 6 root root 4096 May 17 01:26 brcm80211/ -rw-r--r-- 1 root root 657 May 17 01:26 Kconfig -rw-r--r-- 1 root root 181 May 17 01:26 Makefile root@zhengyang:/work/sambashare/s3c2440/linux-6.3# ll drivers/net/wireless/broadcom/brcm80211/ 总用量 32 drwxr-xr-x 6 root root 4096 Jun 4 14:19 ./ drwxr-xr-x 5 root root 4096 Jun 4 14:14 ../ drwxr-xr-x 2 root root 4096 May 17 01:26 brcmfmac/ drwxr-xr-x 3 root root 4096 May 17 01:26 brcmsmac/ drwxr-xr-x 2 root root 4096 May 17 01:26 brcmutil/ drwxr-xr-x 2 root root 4096 May 17 01:26 include/ -rw-r--r-- 1 root root 2771 May 17 01:26 Kconfig -rw-r--r-- 1 root root 1001 May 17 01:26 Makefile
4.2.1 配置brcmfmac驱动
需要配置内核支持brcmfmac驱动:
Device Drivers ---> [*] Network device support ---> [*] Wireless LAN ---> [*] Broadcom devices < > Broadcom 43xx wireless support (mac80211 stack) # CONFIG_B43 < > Broadcom 43xx-legacy wireless support (mac80211 stack) # CONFIG_B43LEGACY < > Broadcom IEEE802.11n PCIe SoftMAC WLAN driver # CONFIG_BRCMSMAC 支持较老的芯片 [M] Broadcom FullMAC WLAN driver # CONFIG_BRCMFMAC 支持较新的芯片 [*] SDIO bus interface support for FullMAC driver # CONFIG_BRCMFMAC_SDIO [ ] USB bus interface support for FullMAC driver # CONFIG_BRCMFMAC_USB [*] Broadcom device tracing # CONFIG_BRCM_TRACING [*] Broadcom driver debug functions # CONFIG_BRCMDBG
由于brcmfmac驱动会加载固件/lib/firmware/brcm/brcmfmac4356-sdio.bin,因此这里只能将Broadcom FullMAC WLAN driver编译成模块。
如果编译进内核,由于加载固件在根文件系统挂载之前,导致出现无法找到固件文件的错误。
4.2.2 配置IEEE 802.11驱动
需要配置内核支持IEEE 802.11驱动:
[*] Networking support --->
-*- Wireless --->
<M> cfg80211 - wireless configuration API
cfg80211.ko驱动模块同样需要加载固件/lib/firmware/regulatory.db,因此这里也只能将cfg80211 - wireless configuration API编译成模块。
4.2.3 固件下载
打开linux根目录下drivers/net/wireless/broadcom/brcm80211/brcmfmac/sdio.c文件,搜索4356,定位到:
BRCMF_FW_DEF(43143, "brcmfmac43143-sdio"); BRCMF_FW_DEF(43241B0, "brcmfmac43241b0-sdio"); BRCMF_FW_DEF(43241B4, "brcmfmac43241b4-sdio"); BRCMF_FW_DEF(43241B5, "brcmfmac43241b5-sdio"); BRCMF_FW_DEF(4329, "brcmfmac4329-sdio"); BRCMF_FW_DEF(4330, "brcmfmac4330-sdio"); BRCMF_FW_DEF(4334, "brcmfmac4334-sdio"); BRCMF_FW_DEF(43340, "brcmfmac43340-sdio"); BRCMF_FW_DEF(4335, "brcmfmac4335-sdio"); BRCMF_FW_DEF(43362, "brcmfmac43362-sdio"); BRCMF_FW_DEF(4339, "brcmfmac4339-sdio"); BRCMF_FW_DEF(43430A0, "brcmfmac43430a0-sdio"); /* Note the names are not postfixed with a1 for backward compatibility */ BRCMF_FW_CLM_DEF(43430A1, "brcmfmac43430-sdio"); BRCMF_FW_DEF(43430B0, "brcmfmac43430b0-sdio"); BRCMF_FW_CLM_DEF(43439, "brcmfmac43439-sdio"); BRCMF_FW_CLM_DEF(43455, "brcmfmac43455-sdio"); BRCMF_FW_DEF(43456, "brcmfmac43456-sdio"); BRCMF_FW_CLM_DEF(4354, "brcmfmac4354-sdio"); BRCMF_FW_CLM_DEF(4356, "brcmfmac4356-sdio"); BRCMF_FW_DEF(4359, "brcmfmac4359-sdio"); BRCMF_FW_CLM_DEF(4373, "brcmfmac4373-sdio"); BRCMF_FW_CLM_DEF(43012, "brcmfmac43012-sdio"); BRCMF_FW_CLM_DEF(43752, "brcmfmac43752-sdio");
可以看到4356对应的固件文件为brcmfmac4356-sdio.bin。固件使用armbian提供的:https://github.com/armbian/firmware/tree/master/brcm;
我们把文件全部下载下来,这里面也包含了固件firmware/regulatory.db,下载完成后在内核启动后我们通过scp命令将这些文件拷贝到开发板根文件系统的/lib/目录中;
root@zhengyang:/work/sambashare/rk3399/linux-6.3# git clone https://github.com/armbian/firmware.git --depth 1
4.3 rfkill驱动配置
4.3.1 源码拷贝
将Rockchip官方linux 5.10版本net/rfkill/rfkill-bt.c、net/rfkill/rfkill-wlan.c、rfkill-bt.h、rfkill-wlan.h等文件复制到linux 6.3 net/rfkill路径下;
root@zhengyang:/work/sambashare/rk3399/linux-6.3# cp ../rockchip/linux-5.10/net/rfkill/rfkill-bt.c ./net/rfkill/ root@zhengyang:/work/sambashare/rk3399/linux-6.3# cp ../rockchip/linux-5.10/net/rfkill/rfkill-wlan.c ./net/rfkill/ root@zhengyang:/work/sambashare/rk3399/linux-6.3# cp ../rockchip/linux-5.10/include/linux/rfkill-bt.h ./include/linux/ root@zhengyang:/work/sambashare/rk3399/linux-6.3# cp ../rockchip/linux-5.10/include/linux/rfkill-wlan.h ./include/linux/ root@zhengyang:/work/sambashare/rk3399/linux-6.3# cp ../rockchip/linux-5.10/include/linux/wakelock.h ./include/linux/ root@zhengyang:/work/sambashare/rk3399/linux-6.3# cp -R ../rockchip/linux-5.10/include/linux/rockchip ./include/linux/ root@zhengyang:/work/sambashare/rk3399/linux-6.3# cp -R ../rockchip/linux-5.10/include/linux/soc/rockchip ./include/linux/soc
(1) linux 5.2.8
对于linux 5.2.8版本需要替换net/rfkill/rfkill-bt.c内容如下(来自Rockchip linux 4.19版本):

/* * Copyright (C) 2012 ROCKCHIP, Inc. * * This software is licensed under the terms of the GNU General Public * License version 2, as published by the Free Software Foundation, and * may be copied, distributed, and modified under those terms. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * */ /* Rock-chips rfkill driver for bluetooth * */ #include <linux/kernel.h> #include <linux/platform_device.h> #include <linux/module.h> #include <linux/rfkill.h> #include <linux/init.h> #include <linux/slab.h> #include <linux/delay.h> #include <linux/rfkill-bt.h> #include <linux/rfkill-wlan.h> #include <linux/wakelock.h> #include <linux/interrupt.h> #include <asm/irq.h> #include <linux/suspend.h> #include <linux/proc_fs.h> #include <linux/uaccess.h> #include <linux/gpio.h> #include <linux/fs.h> #include <dt-bindings/gpio/gpio.h> #include <uapi/linux/rfkill.h> #ifdef CONFIG_OF #include <linux/of.h> #include <linux/of_device.h> #include <linux/of_gpio.h> #endif #if 0 #define DBG(x...) pr_info("[BT_RFKILL]: " x) #else #define DBG(x...) #endif #define LOG(x...) pr_info("[BT_RFKILL]: " x) #define BT_WAKEUP_TIMEOUT 10000 #define BT_IRQ_WAKELOCK_TIMEOUT (10 * 1000) #define BT_BLOCKED true #define BT_UNBLOCK false #define BT_SLEEP true #define BT_WAKEUP false enum { IOMUX_FNORMAL = 0, IOMUX_FGPIO, IOMUX_FMUX, }; struct rfkill_rk_data { struct rfkill_rk_platform_data *pdata; struct platform_device *pdev; struct rfkill *rfkill_dev; struct wake_lock bt_irq_wl; struct delayed_work bt_sleep_delay_work; int irq_req; }; static struct rfkill_rk_data *g_rfkill = NULL; static const char bt_name[] = #if defined(CONFIG_BCM4330) #if defined(CONFIG_BT_MODULE_NH660) "nh660" #else "bcm4330" #endif #elif defined(CONFIG_RK903) #if defined(CONFIG_RKWIFI_26M) "rk903_26M" #else "rk903" #endif #elif defined(CONFIG_BCM4329) "bcm4329" #elif defined(CONFIG_MV8787) "mv8787" #elif defined(CONFIG_AP6210) #if defined(CONFIG_RKWIFI_26M) "ap6210" #else "ap6210_24M" #endif #elif defined(CONFIG_AP6330) "ap6330" #elif defined(CONFIG_AP6476) "ap6476" #elif defined(CONFIG_AP6493) "ap6493" #elif defined(CONFIG_AP6441) "ap6441" #elif defined(CONFIG_AP6335) "ap6335" #elif defined(CONFIG_GB86302I) "gb86302i" #else "bt_default" #endif ; static irqreturn_t rfkill_rk_wake_host_irq(int irq, void *dev) { struct rfkill_rk_data *rfkill = dev; LOG("BT_WAKE_HOST IRQ fired\n"); DBG("BT IRQ wakeup, request %dms wakelock\n", BT_IRQ_WAKELOCK_TIMEOUT); wake_lock_timeout(&rfkill->bt_irq_wl, msecs_to_jiffies(BT_IRQ_WAKELOCK_TIMEOUT)); return IRQ_HANDLED; } static int rfkill_rk_setup_gpio(struct platform_device *pdev, struct rfkill_rk_gpio *gpio, const char *prefix, const char *name) { if (gpio_is_valid(gpio->io)) { int ret = 0; sprintf(gpio->name, "%s_%s", prefix, name); ret = devm_gpio_request(&pdev->dev, gpio->io, gpio->name); if (ret) { LOG("Failed to get %s gpio.\n", gpio->name); return -1; } } return 0; } static int rfkill_rk_setup_wake_irq(struct rfkill_rk_data *rfkill, int flag) { int ret = 0; struct rfkill_rk_irq *irq = &rfkill->pdata->wake_host_irq; if (!flag) { rfkill->irq_req = 0; ret = rfkill_rk_setup_gpio(rfkill->pdev, &irq->gpio, rfkill->pdata->name, "wake_host"); if (ret) goto fail1; } if (gpio_is_valid(irq->gpio.io)) { if (rfkill->irq_req) { rfkill->irq_req = 0; free_irq(irq->irq, rfkill); } LOG("Request irq for bt wakeup host\n"); irq->irq = gpio_to_irq(irq->gpio.io); sprintf(irq->name, "%s_irq", irq->gpio.name); ret = request_irq(irq->irq, rfkill_rk_wake_host_irq, (irq->gpio.enable == GPIO_ACTIVE_LOW) ? IRQF_TRIGGER_FALLING : IRQF_TRIGGER_RISING, irq->name, rfkill); if (ret) goto fail2; rfkill->irq_req = 1; LOG("** disable irq\n"); disable_irq(irq->irq); /*ret = disable_irq_wake(irq->irq);init irq wake is disabled,no need to disable*/ } return ret; fail2: gpio_free(irq->gpio.io); fail1: return ret; } static inline void rfkill_rk_sleep_bt_internal(struct rfkill_rk_data *rfkill, bool sleep) { struct rfkill_rk_gpio *wake = &rfkill->pdata->wake_gpio; DBG("*** bt sleep: %d ***\n", sleep); #ifndef CONFIG_BK3515A_COMBO gpio_direction_output(wake->io, sleep ? !wake->enable : wake->enable); #else if (!sleep) { DBG("HOST_UART0_TX pull down 10us\n"); if (rfkill_rk_setup_gpio(rfkill->pdev, wake, rfkill->pdata->name, "wake") != 0) { return; } gpio_direction_output(wake->io, wake->enable); usleep_range(10, 20); gpio_direction_output(wake->io, !wake->enable); gpio_free(wake->io); } #endif } static void rfkill_rk_delay_sleep_bt(struct work_struct *work) { struct rfkill_rk_data *rfkill = NULL; DBG("Enter %s\n", __func__); rfkill = container_of(work, struct rfkill_rk_data, bt_sleep_delay_work.work); rfkill_rk_sleep_bt_internal(rfkill, BT_SLEEP); } void rfkill_rk_sleep_bt(bool sleep) { struct rfkill_rk_data *rfkill = g_rfkill; struct rfkill_rk_gpio *wake; bool ret; DBG("Enter %s\n", __func__); if (!rfkill) { LOG("*** RFKILL is empty???\n"); return; } wake = &rfkill->pdata->wake_gpio; if (!gpio_is_valid(wake->io)) { DBG("*** Not support bt wakeup and sleep\n"); return; } ret = cancel_delayed_work_sync(&rfkill->bt_sleep_delay_work); rfkill_rk_sleep_bt_internal(rfkill, sleep); #ifdef CONFIG_BT_AUTOSLEEP if (sleep == BT_WAKEUP) { schedule_delayed_work(&rfkill->bt_sleep_delay_work, msecs_to_jiffies(BT_WAKEUP_TIMEOUT)); } #endif } EXPORT_SYMBOL(rfkill_rk_sleep_bt); static int bt_power_state = 0; int rfkill_get_bt_power_state(int *power, bool *toggle) { struct rfkill_rk_data *mrfkill = g_rfkill; if (!mrfkill) { LOG("%s: rfkill-bt driver has not Successful initialized\n", __func__); return -1; } *toggle = mrfkill->pdata->power_toggle; *power = bt_power_state; return 0; } static int rfkill_rk_set_power(void *data, bool blocked) { struct rfkill_rk_data *rfkill = data; struct rfkill_rk_gpio *wake_host = &rfkill->pdata->wake_host_irq.gpio; struct rfkill_rk_gpio *poweron = &rfkill->pdata->poweron_gpio; struct rfkill_rk_gpio *reset = &rfkill->pdata->reset_gpio; struct rfkill_rk_gpio *rts = &rfkill->pdata->rts_gpio; struct pinctrl *pinctrl = rfkill->pdata->pinctrl; int wifi_power = 0; bool toggle = false; DBG("Enter %s\n", __func__); DBG("Set blocked:%d\n", blocked); toggle = rfkill->pdata->power_toggle; DBG("%s: toggle = %s\n", __func__, toggle ? "true" : "false"); if (!blocked) { if (toggle) { rfkill_set_wifi_bt_power(1); msleep(100); } rfkill_rk_sleep_bt(BT_WAKEUP); // ensure bt is wakeup if (gpio_is_valid(wake_host->io)) { LOG("%s: set bt wake_host high!\n", __func__); gpio_direction_output(wake_host->io, 1); msleep(20); } if (gpio_is_valid(poweron->io)) { if (gpio_get_value(poweron->io) == !poweron->enable) { gpio_direction_output(poweron->io, !poweron->enable); msleep(20); gpio_direction_output(poweron->io, poweron->enable); msleep(20); if (gpio_is_valid(wake_host->io)) gpio_direction_input(wake_host->io); } } if (gpio_is_valid(reset->io)) { if (gpio_get_value(reset->io) == !reset->enable) { gpio_direction_output(reset->io, !reset->enable); msleep(20); gpio_direction_output(reset->io, reset->enable); } } if (pinctrl && gpio_is_valid(rts->io)) { pinctrl_select_state(pinctrl, rts->gpio_state); LOG("ENABLE UART_RTS\n"); gpio_direction_output(rts->io, rts->enable); msleep(100); LOG("DISABLE UART_RTS\n"); gpio_direction_output(rts->io, !rts->enable); pinctrl_select_state(pinctrl, rts->default_state); } bt_power_state = 1; LOG("bt turn on power\n"); rfkill_rk_setup_wake_irq(rfkill, 1); } else { if (gpio_is_valid(poweron->io)) { if (gpio_get_value(poweron->io) == poweron->enable) { gpio_direction_output(poweron->io, !poweron->enable); msleep(20); } } bt_power_state = 0; LOG("bt shut off power\n"); if (gpio_is_valid(reset->io)) { if (gpio_get_value(reset->io) == reset->enable) { gpio_direction_output(reset->io, !reset->enable); msleep(20); } } if (toggle) { if (rfkill_get_wifi_power_state(&wifi_power)) { LOG("%s: cannot get wifi power state!\n", __func__); return -EPERM; } if (!wifi_power) { LOG("%s: bt will set vbat to low\n", __func__); rfkill_set_wifi_bt_power(0); } else { LOG("%s: bt shouldn't control the vbat\n", __func__); } } } return 0; } static int rfkill_rk_pm_prepare(struct device *dev) { struct rfkill_rk_data *rfkill = g_rfkill; struct rfkill_rk_gpio *rts; struct rfkill_rk_irq *wake_host_irq; struct pinctrl *pinctrl = rfkill->pdata->pinctrl; DBG("Enter %s\n", __func__); if (!rfkill) return 0; rts = &rfkill->pdata->rts_gpio; wake_host_irq = &rfkill->pdata->wake_host_irq; //To prevent uart to receive bt data when suspended if (pinctrl && gpio_is_valid(rts->io)) { DBG("Disable UART_RTS\n"); pinctrl_select_state(pinctrl, rts->gpio_state); gpio_direction_output(rts->io, !rts->enable); } #ifdef CONFIG_BT_AUTOSLEEP rfkill_rk_sleep_bt(BT_SLEEP); #endif // enable bt wakeup host if (gpio_is_valid(wake_host_irq->gpio.io) && bt_power_state) { DBG("enable irq for bt wakeup host\n"); enable_irq(wake_host_irq->irq); enable_irq_wake(wake_host_irq->irq); } #ifdef CONFIG_RFKILL_RESET rfkill_set_states(rfkill->rfkill_dev, BT_BLOCKED, false); rfkill_rk_set_power(rfkill, BT_BLOCKED); #endif return 0; } static void rfkill_rk_pm_complete(struct device *dev) { struct rfkill_rk_data *rfkill = g_rfkill; struct rfkill_rk_irq *wake_host_irq; struct rfkill_rk_gpio *rts; struct pinctrl *pinctrl = rfkill->pdata->pinctrl; DBG("Enter %s\n", __func__); if (!rfkill) return; wake_host_irq = &rfkill->pdata->wake_host_irq; rts = &rfkill->pdata->rts_gpio; if (gpio_is_valid(wake_host_irq->gpio.io) && bt_power_state) { LOG("** disable irq\n"); disable_irq(wake_host_irq->irq); disable_irq_wake(wake_host_irq->irq); } if (pinctrl && gpio_is_valid(rts->io)) { DBG("Enable UART_RTS\n"); gpio_direction_output(rts->io, rts->enable); pinctrl_select_state(pinctrl, rts->default_state); } } static const struct rfkill_ops rfkill_rk_ops = { .set_block = rfkill_rk_set_power, }; #define PROC_DIR "bluetooth/sleep" static struct proc_dir_entry *bluetooth_dir, *sleep_dir; static ssize_t bluesleep_read_proc_lpm(struct file *file, char __user *buffer, size_t count, loff_t *data) { return sprintf(buffer, "unsupported to read\n"); } static ssize_t bluesleep_write_proc_lpm(struct file *file, const char __user *buffer, size_t count, loff_t *data) { return count; } static ssize_t bluesleep_read_proc_btwrite(struct file *file, char __user *buffer, size_t count, loff_t *data) { return sprintf(buffer, "unsupported to read\n"); } static ssize_t bluesleep_write_proc_btwrite(struct file *file, const char __user *buffer, size_t count, loff_t *data) { char b; if (count < 1) return -EINVAL; if (copy_from_user(&b, buffer, 1)) return -EFAULT; DBG("btwrite %c\n", b); /* HCI_DEV_WRITE */ if (b != '0') rfkill_rk_sleep_bt(BT_WAKEUP); else rfkill_rk_sleep_bt(BT_SLEEP); return count; } #ifdef CONFIG_OF static int bluetooth_platdata_parse_dt(struct device *dev, struct rfkill_rk_platform_data *data) { struct device_node *node = dev->of_node; int gpio; enum of_gpio_flags flags; if (!node) return -ENODEV; memset(data, 0, sizeof(*data)); if (of_find_property(node, "wifi-bt-power-toggle", NULL)) { data->power_toggle = true; LOG("%s: get property wifi-bt-power-toggle.\n", __func__); } else { data->power_toggle = false; } gpio = of_get_named_gpio_flags(node, "uart_rts_gpios", 0, &flags); if (gpio_is_valid(gpio)) { data->rts_gpio.io = gpio; data->rts_gpio.enable = (flags == GPIO_ACTIVE_HIGH) ? 1 : 0; LOG("%s: get property: uart_rts_gpios = %d.\n", __func__, gpio); data->pinctrl = devm_pinctrl_get(dev); if (!IS_ERR(data->pinctrl)) { data->rts_gpio.default_state = pinctrl_lookup_state(data->pinctrl, "default"); data->rts_gpio.gpio_state = pinctrl_lookup_state(data->pinctrl, "rts_gpio"); } else { data->pinctrl = NULL; LOG("%s: dts does't define the uart rts iomux.\n", __func__); return -EINVAL; } } else { data->pinctrl = NULL; data->rts_gpio.io = -EINVAL; LOG("%s: uart_rts_gpios is no-in-use.\n", __func__); } gpio = of_get_named_gpio_flags(node, "BT,power_gpio", 0, &flags); if (gpio_is_valid(gpio)) { data->poweron_gpio.io = gpio; data->poweron_gpio.enable = (flags == GPIO_ACTIVE_HIGH) ? 1 : 0; LOG("%s: get property: BT,power_gpio = %d.\n", __func__, gpio); } else { data->poweron_gpio.io = -1; } gpio = of_get_named_gpio_flags(node, "BT,reset_gpio", 0, &flags); if (gpio_is_valid(gpio)) { data->reset_gpio.io = gpio; data->reset_gpio.enable = (flags == GPIO_ACTIVE_HIGH) ? 1 : 0; LOG("%s: get property: BT,reset_gpio = %d.\n", __func__, gpio); } else { data->reset_gpio.io = -1; } gpio = of_get_named_gpio_flags(node, "BT,wake_gpio", 0, &flags); if (gpio_is_valid(gpio)) { data->wake_gpio.io = gpio; data->wake_gpio.enable = (flags == GPIO_ACTIVE_HIGH) ? 1 : 0; LOG("%s: get property: BT,wake_gpio = %d.\n", __func__, gpio); } else { data->wake_gpio.io = -1; } gpio = of_get_named_gpio_flags(node, "BT,wake_host_irq", 0, &flags); if (gpio_is_valid(gpio)) { data->wake_host_irq.gpio.io = gpio; data->wake_host_irq.gpio.enable = flags; LOG("%s: get property: BT,wake_host_irq = %d.\n", __func__, gpio); } else { data->wake_host_irq.gpio.io = -1; } data->ext_clk = devm_clk_get(dev, "ext_clock"); if (IS_ERR(data->ext_clk)) { LOG("%s: clk_get failed!!!.\n", __func__); } else { clk_prepare_enable(data->ext_clk); } return 0; } #endif //CONFIG_OF static const struct file_operations bluesleep_lpm = { .owner = THIS_MODULE, .read = bluesleep_read_proc_lpm, .write = bluesleep_write_proc_lpm, }; static const struct file_operations bluesleep_btwrite = { .owner = THIS_MODULE, .read = bluesleep_read_proc_btwrite, .write = bluesleep_write_proc_btwrite, }; static int rfkill_rk_probe(struct platform_device *pdev) { struct rfkill_rk_data *rfkill; struct rfkill_rk_platform_data *pdata = pdev->dev.platform_data; int ret = 0; struct proc_dir_entry *ent; DBG("Enter %s\n", __func__); if (!pdata) { #ifdef CONFIG_OF pdata = devm_kzalloc(&pdev->dev, sizeof(struct rfkill_rk_platform_data), GFP_KERNEL); if (!pdata) return -ENOMEM; ret = bluetooth_platdata_parse_dt(&pdev->dev, pdata); if (ret < 0) { #endif LOG("%s: No platform data specified\n", __func__); return ret; #ifdef CONFIG_OF } #endif } pdata->name = (char *)bt_name; pdata->type = RFKILL_TYPE_BLUETOOTH; rfkill = devm_kzalloc(&pdev->dev, sizeof(*rfkill), GFP_KERNEL); if (!rfkill) return -ENOMEM; rfkill->pdata = pdata; rfkill->pdev = pdev; g_rfkill = rfkill; bluetooth_dir = proc_mkdir("bluetooth", NULL); if (!bluetooth_dir) { LOG("Unable to create /proc/bluetooth directory"); return -ENOMEM; } sleep_dir = proc_mkdir("sleep", bluetooth_dir); if (!sleep_dir) { LOG("Unable to create /proc/%s directory", PROC_DIR); return -ENOMEM; } /* read/write proc entries */ ent = proc_create("lpm", 0, sleep_dir, &bluesleep_lpm); if (!ent) { LOG("Unable to create /proc/%s/lpm entry", PROC_DIR); ret = -ENOMEM; goto fail_alloc; } /* read/write proc entries */ ent = proc_create("btwrite", 0, sleep_dir, &bluesleep_btwrite); if (!ent) { LOG("Unable to create /proc/%s/btwrite entry", PROC_DIR); ret = -ENOMEM; goto fail_alloc; } DBG("init gpio\n"); ret = rfkill_rk_setup_gpio(pdev, &pdata->poweron_gpio, pdata->name, "poweron"); if (ret) goto fail_gpio; ret = rfkill_rk_setup_gpio(pdev, &pdata->reset_gpio, pdata->name, "reset"); if (ret) goto fail_gpio; ret = rfkill_rk_setup_gpio(pdev, &pdata->wake_gpio, pdata->name, "wake"); if (ret) goto fail_gpio; ret = rfkill_rk_setup_gpio(pdev, &pdata->rts_gpio, rfkill->pdata->name, "rts"); if (ret) goto fail_gpio; wake_lock_init(&rfkill->bt_irq_wl, WAKE_LOCK_SUSPEND, "rfkill_rk_irq_wl"); ret = rfkill_rk_setup_wake_irq(rfkill, 0); if (ret) goto fail_setup_wake_irq; DBG("setup rfkill\n"); rfkill->rfkill_dev = rfkill_alloc(pdata->name, &pdev->dev, pdata->type, &rfkill_rk_ops, rfkill); if (!rfkill->rfkill_dev) goto fail_alloc; rfkill_set_states(rfkill->rfkill_dev, BT_BLOCKED, false); ret = rfkill_register(rfkill->rfkill_dev); if (ret < 0) goto fail_rfkill; INIT_DELAYED_WORK(&rfkill->bt_sleep_delay_work, rfkill_rk_delay_sleep_bt); //rfkill_rk_set_power(rfkill, BT_BLOCKED); // bt turn off power if (gpio_is_valid(pdata->poweron_gpio.io)) { gpio_direction_output(pdata->poweron_gpio.io, !pdata->poweron_gpio.enable); } if (gpio_is_valid(pdata->reset_gpio.io)) { gpio_direction_output(pdata->reset_gpio.io, !pdata->reset_gpio.enable); } platform_set_drvdata(pdev, rfkill); LOG("%s device registered.\n", pdata->name); return 0; fail_rfkill: rfkill_destroy(rfkill->rfkill_dev); fail_alloc: remove_proc_entry("btwrite", sleep_dir); remove_proc_entry("lpm", sleep_dir); fail_setup_wake_irq: wake_lock_destroy(&rfkill->bt_irq_wl); fail_gpio: g_rfkill = NULL; return ret; } static int rfkill_rk_remove(struct platform_device *pdev) { struct rfkill_rk_data *rfkill = platform_get_drvdata(pdev); LOG("Enter %s\n", __func__); rfkill_unregister(rfkill->rfkill_dev); rfkill_destroy(rfkill->rfkill_dev); cancel_delayed_work_sync(&rfkill->bt_sleep_delay_work); // free gpio if (gpio_is_valid(rfkill->pdata->rts_gpio.io)) gpio_free(rfkill->pdata->rts_gpio.io); if (gpio_is_valid(rfkill->pdata->wake_host_irq.gpio.io)) { free_irq(rfkill->pdata->wake_host_irq.irq, rfkill); #ifndef CONFIG_BK3515A_COMBO gpio_free(rfkill->pdata->wake_host_irq.gpio.io); #endif } #ifndef CONFIG_BK3515A_COMBO if (gpio_is_valid(rfkill->pdata->wake_gpio.io)) gpio_free(rfkill->pdata->wake_gpio.io); #endif if (gpio_is_valid(rfkill->pdata->reset_gpio.io)) gpio_free(rfkill->pdata->reset_gpio.io); if (gpio_is_valid(rfkill->pdata->poweron_gpio.io)) gpio_free(rfkill->pdata->poweron_gpio.io); clk_disable_unprepare(rfkill->pdata->ext_clk); wake_lock_destroy(&rfkill->bt_irq_wl); g_rfkill = NULL; return 0; } static const struct dev_pm_ops rfkill_rk_pm_ops = { .prepare = rfkill_rk_pm_prepare, .complete = rfkill_rk_pm_complete, }; #ifdef CONFIG_OF static struct of_device_id bt_platdata_of_match[] = { { .compatible = "bluetooth-platdata" }, {} }; MODULE_DEVICE_TABLE(of, bt_platdata_of_match); #endif //CONFIG_OF static struct platform_driver rfkill_rk_driver = { .probe = rfkill_rk_probe, .remove = rfkill_rk_remove, .driver = { .name = "rfkill_bt", .owner = THIS_MODULE, .pm = &rfkill_rk_pm_ops, .of_match_table = of_match_ptr(bt_platdata_of_match), }, }; static int __init rfkill_rk_init(void) { int err; LOG("Enter %s\n", __func__); err = rfkill_wlan_init(); if (err) return err; return platform_driver_register(&rfkill_rk_driver); } static void __exit rfkill_rk_exit(void) { LOG("Enter %s\n", __func__); platform_driver_unregister(&rfkill_rk_driver); rfkill_wlan_exit(); } module_init(rfkill_rk_init); module_exit(rfkill_rk_exit); MODULE_DESCRIPTION("rock-chips rfkill for Bluetooth v0.3"); MODULE_AUTHOR("cmy@rock-chips.com, gwl@rock-chips.com"); MODULE_LICENSE("GPL");
(2) linux 6.3
对于linux 6.3版本,需要修改include/linux/of_gpio.h、drivers/gpio/gpiolib-of.c、net/rfkill/rfkill-bt.c、net/rfkill/rfkill-wlan.c文件:
include/linux/of_gpio.h文件添加:
extern int of_get_named_gpio_flags(struct device_node *np, const char *list_name, int index, unsigned long *flags);
drivers/gpio/gpiolib-of.c文件添加:
int of_get_named_gpio_flags(struct device_node *np, const char *list_name, int index, unsigned long *flags) { struct gpio_desc *desc; enum of_gpio_flags of_flags; desc = of_get_named_gpiod_flags(np, list_name, index, &of_flags); if (IS_ERR(desc)) return PTR_ERR(desc); *flags = of_convert_gpio_flags(of_flags); return desc_to_gpio(desc); }
net/rfkill/rfkill-bt.c修改为:

/* * Copyright (C) 2012 ROCKCHIP, Inc. * * This software is licensed under the terms of the GNU General Public * License version 2, as published by the Free Software Foundation, and * may be copied, distributed, and modified under those terms. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * */ /* Rock-chips rfkill driver for bluetooth * */ #include <linux/kernel.h> #include <linux/platform_device.h> #include <linux/module.h> #include <linux/rfkill.h> #include <linux/init.h> #include <linux/slab.h> #include <linux/delay.h> #include <linux/rfkill-bt.h> #include <linux/rfkill-wlan.h> #include <linux/wakelock.h> #include <linux/interrupt.h> #include <asm/irq.h> #include <linux/suspend.h> #include <linux/proc_fs.h> #include <linux/uaccess.h> #include <linux/gpio.h> #include <linux/fs.h> #include <dt-bindings/gpio/gpio.h> #include <uapi/linux/rfkill.h> #include <linux/pinctrl/consumer.h> #ifdef CONFIG_OF #include <linux/of.h> #include <linux/of_device.h> #include <linux/of_gpio.h> #endif #if 0 #define DBG(x...) pr_info("[BT_RFKILL]: " x) #else #define DBG(x...) #endif #define LOG(x...) pr_info("[BT_RFKILL]: " x) #define BT_WAKEUP_TIMEOUT 10000 #define BT_IRQ_WAKELOCK_TIMEOUT (10 * 1000) #define BT_BLOCKED true #define BT_UNBLOCK false #define BT_SLEEP true #define BT_WAKEUP false enum { IOMUX_FNORMAL = 0, IOMUX_FGPIO, IOMUX_FMUX, }; struct rfkill_rk_data { struct rfkill_rk_platform_data *pdata; struct platform_device *pdev; struct rfkill *rfkill_dev; struct wake_lock bt_irq_wl; struct delayed_work bt_sleep_delay_work; int irq_req; }; static struct rfkill_rk_data *g_rfkill = NULL; static const char bt_name[] = #if defined(CONFIG_BCM4330) #if defined(CONFIG_BT_MODULE_NH660) "nh660" #else "bcm4330" #endif #elif defined(CONFIG_RK903) #if defined(CONFIG_RKWIFI_26M) "rk903_26M" #else "rk903" #endif #elif defined(CONFIG_BCM4329) "bcm4329" #elif defined(CONFIG_MV8787) "mv8787" #elif defined(CONFIG_AP6210) #if defined(CONFIG_RKWIFI_26M) "ap6210" #else "ap6210_24M" #endif #elif defined(CONFIG_AP6330) "ap6330" #elif defined(CONFIG_AP6476) "ap6476" #elif defined(CONFIG_AP6493) "ap6493" #elif defined(CONFIG_AP6441) "ap6441" #elif defined(CONFIG_AP6335) "ap6335" #elif defined(CONFIG_GB86302I) "gb86302i" #else "bt_default" #endif ; static irqreturn_t rfkill_rk_wake_host_irq(int irq, void *dev) { struct rfkill_rk_data *rfkill = dev; LOG("BT_WAKE_HOST IRQ fired\n"); DBG("BT IRQ wakeup, request %dms wakelock\n", BT_IRQ_WAKELOCK_TIMEOUT); wake_lock_timeout(&rfkill->bt_irq_wl, msecs_to_jiffies(BT_IRQ_WAKELOCK_TIMEOUT)); return IRQ_HANDLED; } static int rfkill_rk_setup_gpio(struct platform_device *pdev, struct rfkill_rk_gpio *gpio, const char *prefix, const char *name) { if (gpio_is_valid(gpio->io)) { int ret = 0; sprintf(gpio->name, "%s_%s", prefix, name); ret = devm_gpio_request(&pdev->dev, gpio->io, gpio->name); if (ret) { LOG("Failed to get %s gpio.\n", gpio->name); return -1; } } return 0; } static int rfkill_rk_setup_wake_irq(struct rfkill_rk_data *rfkill, int flag) { int ret = 0; struct rfkill_rk_irq *irq = &rfkill->pdata->wake_host_irq; if (!flag) { rfkill->irq_req = 0; ret = rfkill_rk_setup_gpio(rfkill->pdev, &irq->gpio, rfkill->pdata->name, "wake_host"); if (ret) goto fail1; } if (gpio_is_valid(irq->gpio.io)) { if (rfkill->irq_req) { rfkill->irq_req = 0; free_irq(irq->irq, rfkill); } LOG("Request irq for bt wakeup host\n"); irq->irq = gpio_to_irq(irq->gpio.io); sprintf(irq->name, "%s_irq", irq->gpio.name); ret = request_irq(irq->irq, rfkill_rk_wake_host_irq, (irq->gpio.enable == GPIO_ACTIVE_LOW) ? IRQF_TRIGGER_FALLING : IRQF_TRIGGER_RISING, irq->name, rfkill); if (ret) goto fail2; rfkill->irq_req = 1; LOG("** disable irq\n"); disable_irq(irq->irq); /*ret = disable_irq_wake(irq->irq);init irq wake is disabled,no need to disable*/ } return ret; fail2: gpio_free(irq->gpio.io); fail1: return ret; } static inline void rfkill_rk_sleep_bt_internal(struct rfkill_rk_data *rfkill, bool sleep) { struct rfkill_rk_gpio *wake = &rfkill->pdata->wake_gpio; DBG("*** bt sleep: %d ***\n", sleep); #ifndef CONFIG_BK3515A_COMBO gpio_direction_output(wake->io, sleep ? !wake->enable : wake->enable); #else if (!sleep) { DBG("HOST_UART0_TX pull down 10us\n"); if (rfkill_rk_setup_gpio(rfkill->pdev, wake, rfkill->pdata->name, "wake") != 0) { return; } gpio_direction_output(wake->io, wake->enable); usleep_range(10, 20); gpio_direction_output(wake->io, !wake->enable); gpio_free(wake->io); } #endif } static void rfkill_rk_delay_sleep_bt(struct work_struct *work) { struct rfkill_rk_data *rfkill = NULL; DBG("Enter %s\n", __func__); rfkill = container_of(work, struct rfkill_rk_data, bt_sleep_delay_work.work); rfkill_rk_sleep_bt_internal(rfkill, BT_SLEEP); } void rfkill_rk_sleep_bt(bool sleep) { struct rfkill_rk_data *rfkill = g_rfkill; struct rfkill_rk_gpio *wake; bool ret; DBG("Enter %s\n", __func__); if (!rfkill) { LOG("*** RFKILL is empty???\n"); return; } wake = &rfkill->pdata->wake_gpio; if (!gpio_is_valid(wake->io)) { DBG("*** Not support bt wakeup and sleep\n"); return; } ret = cancel_delayed_work_sync(&rfkill->bt_sleep_delay_work); rfkill_rk_sleep_bt_internal(rfkill, sleep); #ifdef CONFIG_BT_AUTOSLEEP if (sleep == BT_WAKEUP) { schedule_delayed_work(&rfkill->bt_sleep_delay_work, msecs_to_jiffies(BT_WAKEUP_TIMEOUT)); } #endif } EXPORT_SYMBOL(rfkill_rk_sleep_bt); static int bt_power_state = 0; int rfkill_get_bt_power_state(int *power, bool *toggle) { struct rfkill_rk_data *mrfkill = g_rfkill; if (!mrfkill) { LOG("%s: rfkill-bt driver has not Successful initialized\n", __func__); return -1; } *toggle = mrfkill->pdata->power_toggle; *power = bt_power_state; return 0; } static int rfkill_rk_set_power(void *data, bool blocked) { struct rfkill_rk_data *rfkill = data; struct rfkill_rk_gpio *wake_host = &rfkill->pdata->wake_host_irq.gpio; struct rfkill_rk_gpio *poweron = &rfkill->pdata->poweron_gpio; struct rfkill_rk_gpio *reset = &rfkill->pdata->reset_gpio; struct rfkill_rk_gpio *rts = &rfkill->pdata->rts_gpio; struct pinctrl *pinctrl = rfkill->pdata->pinctrl; int wifi_power = 0; bool toggle = false; DBG("Enter %s\n", __func__); DBG("Set blocked:%d\n", blocked); toggle = rfkill->pdata->power_toggle; DBG("%s: toggle = %s\n", __func__, toggle ? "true" : "false"); if (!blocked) { if (toggle) { rfkill_set_wifi_bt_power(1); msleep(100); } rfkill_rk_sleep_bt(BT_WAKEUP); // ensure bt is wakeup if (gpio_is_valid(wake_host->io)) { LOG("%s: set bt wake_host high!\n", __func__); gpio_direction_output(wake_host->io, 1); msleep(20); } if (gpio_is_valid(poweron->io)) { if (gpio_get_value(poweron->io) == !poweron->enable) { gpio_direction_output(poweron->io, !poweron->enable); msleep(20); gpio_direction_output(poweron->io, poweron->enable); msleep(20); if (gpio_is_valid(wake_host->io)) gpio_direction_input(wake_host->io); } } if (gpio_is_valid(reset->io)) { if (gpio_get_value(reset->io) == !reset->enable) { gpio_direction_output(reset->io, !reset->enable); msleep(20); gpio_direction_output(reset->io, reset->enable); } } if (pinctrl && gpio_is_valid(rts->io)) { pinctrl_select_state(pinctrl, rts->gpio_state); LOG("ENABLE UART_RTS\n"); gpio_direction_output(rts->io, rts->enable); msleep(100); LOG("DISABLE UART_RTS\n"); gpio_direction_output(rts->io, !rts->enable); pinctrl_select_state(pinctrl, rts->default_state); } bt_power_state = 1; LOG("bt turn on power\n"); rfkill_rk_setup_wake_irq(rfkill, 1); } else { if (gpio_is_valid(poweron->io)) { if (gpio_get_value(poweron->io) == poweron->enable) { gpio_direction_output(poweron->io, !poweron->enable); msleep(20); } } bt_power_state = 0; LOG("bt shut off power\n"); if (gpio_is_valid(reset->io)) { if (gpio_get_value(reset->io) == reset->enable) { gpio_direction_output(reset->io, !reset->enable); msleep(20); } } if (toggle) { if (rfkill_get_wifi_power_state(&wifi_power)) { LOG("%s: cannot get wifi power state!\n", __func__); return -EPERM; } if (!wifi_power) { LOG("%s: bt will set vbat to low\n", __func__); rfkill_set_wifi_bt_power(0); } else { LOG("%s: bt shouldn't control the vbat\n", __func__); } } } return 0; } static int rfkill_rk_pm_prepare(struct device *dev) { struct rfkill_rk_data *rfkill = g_rfkill; struct rfkill_rk_gpio *rts; struct rfkill_rk_irq *wake_host_irq; struct pinctrl *pinctrl = rfkill->pdata->pinctrl; DBG("Enter %s\n", __func__); if (!rfkill) return 0; rts = &rfkill->pdata->rts_gpio; wake_host_irq = &rfkill->pdata->wake_host_irq; //To prevent uart to receive bt data when suspended if (pinctrl && gpio_is_valid(rts->io)) { DBG("Disable UART_RTS\n"); pinctrl_select_state(pinctrl, rts->gpio_state); gpio_direction_output(rts->io, !rts->enable); } #ifdef CONFIG_BT_AUTOSLEEP rfkill_rk_sleep_bt(BT_SLEEP); #endif // enable bt wakeup host if (gpio_is_valid(wake_host_irq->gpio.io) && bt_power_state) { DBG("enable irq for bt wakeup host\n"); enable_irq(wake_host_irq->irq); enable_irq_wake(wake_host_irq->irq); } #ifdef CONFIG_RFKILL_RESET rfkill_set_states(rfkill->rfkill_dev, BT_BLOCKED, false); rfkill_rk_set_power(rfkill, BT_BLOCKED); #endif return 0; } static void rfkill_rk_pm_complete(struct device *dev) { struct rfkill_rk_data *rfkill = g_rfkill; struct rfkill_rk_irq *wake_host_irq; struct rfkill_rk_gpio *rts; struct pinctrl *pinctrl = rfkill->pdata->pinctrl; DBG("Enter %s\n", __func__); if (!rfkill) return; wake_host_irq = &rfkill->pdata->wake_host_irq; rts = &rfkill->pdata->rts_gpio; if (gpio_is_valid(wake_host_irq->gpio.io) && bt_power_state) { LOG("** disable irq\n"); disable_irq(wake_host_irq->irq); disable_irq_wake(wake_host_irq->irq); } if (pinctrl && gpio_is_valid(rts->io)) { DBG("Enable UART_RTS\n"); gpio_direction_output(rts->io, rts->enable); pinctrl_select_state(pinctrl, rts->default_state); } } static const struct rfkill_ops rfkill_rk_ops = { .set_block = rfkill_rk_set_power, }; #define PROC_DIR "bluetooth/sleep" static struct proc_dir_entry *bluetooth_dir, *sleep_dir; static ssize_t bluesleep_read_proc_lpm(struct file *file, char __user *buffer, size_t count, loff_t *data) { return sprintf(buffer, "unsupported to read\n"); } static ssize_t bluesleep_write_proc_lpm(struct file *file, const char __user *buffer, size_t count, loff_t *data) { return count; } static ssize_t bluesleep_read_proc_btwrite(struct file *file, char __user *buffer, size_t count, loff_t *data) { return sprintf(buffer, "unsupported to read\n"); } static ssize_t bluesleep_write_proc_btwrite(struct file *file, const char __user *buffer, size_t count, loff_t *data) { char b; if (count < 1) return -EINVAL; if (copy_from_user(&b, buffer, 1)) return -EFAULT; DBG("btwrite %c\n", b); /* HCI_DEV_WRITE */ if (b != '0') rfkill_rk_sleep_bt(BT_WAKEUP); else rfkill_rk_sleep_bt(BT_SLEEP); return count; } #ifdef CONFIG_OF static int bluetooth_platdata_parse_dt(struct device *dev, struct rfkill_rk_platform_data *data) { struct device_node *node = dev->of_node; int gpio; unsigned long flags; if (!node) return -ENODEV; memset(data, 0, sizeof(*data)); if (of_find_property(node, "wifi-bt-power-toggle", NULL)) { data->power_toggle = true; LOG("%s: get property wifi-bt-power-toggle.\n", __func__); } else { data->power_toggle = false; } gpio = of_get_named_gpio_flags(node, "uart_rts_gpios", 0, &flags); if (gpio_is_valid(gpio)) { data->rts_gpio.io = gpio; data->rts_gpio.enable = (flags == GPIO_ACTIVE_HIGH) ? 1 : 0; LOG("%s: get property: uart_rts_gpios = %d.\n", __func__, gpio); data->pinctrl = devm_pinctrl_get(dev); if (!IS_ERR(data->pinctrl)) { data->rts_gpio.default_state = pinctrl_lookup_state(data->pinctrl, "default"); data->rts_gpio.gpio_state = pinctrl_lookup_state(data->pinctrl, "rts_gpio"); } else { data->pinctrl = NULL; LOG("%s: dts does't define the uart rts iomux.\n", __func__); return -EINVAL; } } else { data->pinctrl = NULL; data->rts_gpio.io = -EINVAL; LOG("%s: uart_rts_gpios is no-in-use.\n", __func__); } gpio = of_get_named_gpio_flags(node, "BT,power_gpio", 0, &flags); if (gpio_is_valid(gpio)) { data->poweron_gpio.io = gpio; data->poweron_gpio.enable = (flags == GPIO_ACTIVE_HIGH) ? 1 : 0; LOG("%s: get property: BT,power_gpio = %d.\n", __func__, gpio); } else { data->poweron_gpio.io = -1; } gpio = of_get_named_gpio_flags(node, "BT,reset_gpio", 0, &flags); if (gpio_is_valid(gpio)) { data->reset_gpio.io = gpio; data->reset_gpio.enable = (flags == GPIO_ACTIVE_HIGH) ? 1 : 0; LOG("%s: get property: BT,reset_gpio = %d.\n", __func__, gpio); } else { data->reset_gpio.io = -1; } gpio = of_get_named_gpio_flags(node, "BT,wake_gpio", 0, &flags); if (gpio_is_valid(gpio)) { data->wake_gpio.io = gpio; data->wake_gpio.enable = (flags == GPIO_ACTIVE_HIGH) ? 1 : 0; LOG("%s: get property: BT,wake_gpio = %d.\n", __func__, gpio); } else { data->wake_gpio.io = -1; } gpio = of_get_named_gpio_flags(node, "BT,wake_host_irq", 0, &flags); if (gpio_is_valid(gpio)) { data->wake_host_irq.gpio.io = gpio; data->wake_host_irq.gpio.enable = flags; LOG("%s: get property: BT,wake_host_irq = %d.\n", __func__, gpio); } else { data->wake_host_irq.gpio.io = -1; } data->ext_clk = devm_clk_get(dev, "ext_clock"); if (IS_ERR(data->ext_clk)) { LOG("%s: clk_get failed!!!.\n", __func__); } else { clk_prepare_enable(data->ext_clk); } return 0; } #endif //CONFIG_OF static const struct proc_ops bluesleep_lpm = { .proc_read = bluesleep_read_proc_lpm, .proc_write = bluesleep_write_proc_lpm, }; static const struct proc_ops bluesleep_btwrite = { .proc_read = bluesleep_read_proc_btwrite, .proc_write = bluesleep_write_proc_btwrite, }; static int rfkill_rk_probe(struct platform_device *pdev) { struct rfkill_rk_data *rfkill; struct rfkill_rk_platform_data *pdata = pdev->dev.platform_data; int ret = 0; struct proc_dir_entry *ent; DBG("Enter %s\n", __func__); if (!pdata) { #ifdef CONFIG_OF pdata = devm_kzalloc(&pdev->dev, sizeof(struct rfkill_rk_platform_data), GFP_KERNEL); if (!pdata) return -ENOMEM; ret = bluetooth_platdata_parse_dt(&pdev->dev, pdata); if (ret < 0) { #endif LOG("%s: No platform data specified\n", __func__); return ret; #ifdef CONFIG_OF } #endif } pdata->name = (char *)bt_name; pdata->type = RFKILL_TYPE_BLUETOOTH; rfkill = devm_kzalloc(&pdev->dev, sizeof(*rfkill), GFP_KERNEL); if (!rfkill) return -ENOMEM; rfkill->pdata = pdata; rfkill->pdev = pdev; g_rfkill = rfkill; bluetooth_dir = proc_mkdir("bluetooth", NULL); if (!bluetooth_dir) { LOG("Unable to create /proc/bluetooth directory"); return -ENOMEM; } sleep_dir = proc_mkdir("sleep", bluetooth_dir); if (!sleep_dir) { LOG("Unable to create /proc/%s directory", PROC_DIR); return -ENOMEM; } /* read/write proc entries */ ent = proc_create("lpm", 0, sleep_dir, &bluesleep_lpm); if (!ent) { LOG("Unable to create /proc/%s/lpm entry", PROC_DIR); ret = -ENOMEM; goto fail_alloc; } /* read/write proc entries */ ent = proc_create("btwrite", 0, sleep_dir, &bluesleep_btwrite); if (!ent) { LOG("Unable to create /proc/%s/btwrite entry", PROC_DIR); ret = -ENOMEM; goto fail_alloc; } DBG("init gpio\n"); ret = rfkill_rk_setup_gpio(pdev, &pdata->poweron_gpio, pdata->name, "poweron"); if (ret) goto fail_gpio; ret = rfkill_rk_setup_gpio(pdev, &pdata->reset_gpio, pdata->name, "reset"); if (ret) goto fail_gpio; ret = rfkill_rk_setup_gpio(pdev, &pdata->wake_gpio, pdata->name, "wake"); if (ret) goto fail_gpio; ret = rfkill_rk_setup_gpio(pdev, &pdata->rts_gpio, rfkill->pdata->name, "rts"); if (ret) goto fail_gpio; wake_lock_init(&rfkill->bt_irq_wl, WAKE_LOCK_SUSPEND, "rfkill_rk_irq_wl"); ret = rfkill_rk_setup_wake_irq(rfkill, 0); if (ret) goto fail_setup_wake_irq; DBG("setup rfkill\n"); rfkill->rfkill_dev = rfkill_alloc(pdata->name, &pdev->dev, pdata->type, &rfkill_rk_ops, rfkill); if (!rfkill->rfkill_dev) goto fail_alloc; rfkill_set_states(rfkill->rfkill_dev, BT_BLOCKED, false); ret = rfkill_register(rfkill->rfkill_dev); if (ret < 0) goto fail_rfkill; INIT_DELAYED_WORK(&rfkill->bt_sleep_delay_work, rfkill_rk_delay_sleep_bt); //rfkill_rk_set_power(rfkill, BT_BLOCKED); // bt turn off power if (gpio_is_valid(pdata->poweron_gpio.io)) { gpio_direction_output(pdata->poweron_gpio.io, !pdata->poweron_gpio.enable); } if (gpio_is_valid(pdata->reset_gpio.io)) { gpio_direction_output(pdata->reset_gpio.io, !pdata->reset_gpio.enable); } platform_set_drvdata(pdev, rfkill); LOG("%s device registered.\n", pdata->name); return 0; fail_rfkill: rfkill_destroy(rfkill->rfkill_dev); fail_alloc: remove_proc_entry("btwrite", sleep_dir); remove_proc_entry("lpm", sleep_dir); fail_setup_wake_irq: wake_lock_destroy(&rfkill->bt_irq_wl); fail_gpio: g_rfkill = NULL; return ret; } static int rfkill_rk_remove(struct platform_device *pdev) { struct rfkill_rk_data *rfkill = platform_get_drvdata(pdev); LOG("Enter %s\n", __func__); rfkill_unregister(rfkill->rfkill_dev); rfkill_destroy(rfkill->rfkill_dev); cancel_delayed_work_sync(&rfkill->bt_sleep_delay_work); // free gpio if (gpio_is_valid(rfkill->pdata->rts_gpio.io)) gpio_free(rfkill->pdata->rts_gpio.io); if (gpio_is_valid(rfkill->pdata->wake_host_irq.gpio.io)) { free_irq(rfkill->pdata->wake_host_irq.irq, rfkill); #ifndef CONFIG_BK3515A_COMBO gpio_free(rfkill->pdata->wake_host_irq.gpio.io); #endif } #ifndef CONFIG_BK3515A_COMBO if (gpio_is_valid(rfkill->pdata->wake_gpio.io)) gpio_free(rfkill->pdata->wake_gpio.io); #endif if (gpio_is_valid(rfkill->pdata->reset_gpio.io)) gpio_free(rfkill->pdata->reset_gpio.io); if (gpio_is_valid(rfkill->pdata->poweron_gpio.io)) gpio_free(rfkill->pdata->poweron_gpio.io); clk_disable_unprepare(rfkill->pdata->ext_clk); wake_lock_destroy(&rfkill->bt_irq_wl); g_rfkill = NULL; return 0; } static const struct dev_pm_ops rfkill_rk_pm_ops = { .prepare = rfkill_rk_pm_prepare, .complete = rfkill_rk_pm_complete, }; #ifdef CONFIG_OF static struct of_device_id bt_platdata_of_match[] = { { .compatible = "bluetooth-platdata" }, {} }; MODULE_DEVICE_TABLE(of, bt_platdata_of_match); #endif //CONFIG_OF static struct platform_driver rfkill_rk_driver = { .probe = rfkill_rk_probe, .remove = rfkill_rk_remove, .driver = { .name = "rfkill_bt", .owner = THIS_MODULE, .pm = &rfkill_rk_pm_ops, .of_match_table = of_match_ptr(bt_platdata_of_match), }, }; static int __init rfkill_rk_init(void) { int err; LOG("Enter %s\n", __func__); err = rfkill_wlan_init(); if (err) return err; return platform_driver_register(&rfkill_rk_driver); } static void __exit rfkill_rk_exit(void) { LOG("Enter %s\n", __func__); platform_driver_unregister(&rfkill_rk_driver); rfkill_wlan_exit(); } module_init(rfkill_rk_init); module_exit(rfkill_rk_exit); MODULE_DESCRIPTION("rock-chips rfkill for Bluetooth v0.3"); MODULE_AUTHOR("cmy@rock-chips.com, gwl@rock-chips.com"); MODULE_LICENSE("GPL");
net/rfkill/rfkill-wlan.c修改为:

/* * Copyright (C) 2012 ROCKCHIP, Inc. * * This software is licensed under the terms of the GNU General Public * License version 2, as published by the Free Software Foundation, and * may be copied, distributed, and modified under those terms. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * */ /* Rock-chips rfkill driver for wifi */ #include <linux/kernel.h> #include <linux/platform_device.h> #include <linux/module.h> #include <linux/rfkill.h> #include <linux/init.h> #include <linux/slab.h> #include <linux/regulator/consumer.h> #include <linux/delay.h> #include <linux/rfkill-wlan.h> #include <linux/rfkill-bt.h> #include <linux/wakelock.h> #include <linux/interrupt.h> #include <asm/irq.h> #include <linux/suspend.h> #include <linux/proc_fs.h> #include <linux/uaccess.h> #include <linux/gpio.h> #include <dt-bindings/gpio/gpio.h> #include <linux/skbuff.h> #include <linux/fb.h> #include <linux/rockchip/grf.h> #include <linux/regmap.h> #include <linux/mfd/syscon.h> #include <linux/mmc/host.h> #ifdef CONFIG_OF #include <linux/of.h> #include <linux/of_device.h> #include <linux/of_gpio.h> #endif #include <linux/soc/rockchip/rk_vendor_storage.h> #include <linux/device.h> #include "../../drivers/mmc/core/pwrseq.h" #if 0 #define DBG(x...) pr_info("[WLAN_RFKILL]: " x) #else #define DBG(x...) #endif #define LOG(x...) pr_info("[WLAN_RFKILL]: " x) struct rfkill_wlan_data { struct rksdmmc_gpio_wifi_moudle *pdata; struct wake_lock wlan_irq_wl; }; static struct rfkill_wlan_data *g_rfkill = NULL; static int power_set_time = 0; static int wifi_bt_vbat_state; static int wifi_power_state; static const char wlan_name[] = "rkwifi"; static char wifi_chip_type_string[64]; /*********************************************************** * * Broadcom Wifi Static Memory * **********************************************************/ #ifdef CONFIG_RKWIFI #define BCM_STATIC_MEMORY_SUPPORT 0 #else #define BCM_STATIC_MEMORY_SUPPORT 0 #endif //=========================== #if BCM_STATIC_MEMORY_SUPPORT #define PREALLOC_WLAN_SEC_NUM 4 #define PREALLOC_WLAN_BUF_NUM 160 #define PREALLOC_WLAN_SECTION_HEADER 0 #define WLAN_SKB_BUF_NUM 16 #define WLAN_SECTION_SIZE_0 (12 * 1024) #define WLAN_SECTION_SIZE_1 (12 * 1024) #define WLAN_SECTION_SIZE_2 (32 * 1024) #define WLAN_SECTION_SIZE_3 (136 * 1024) #define WLAN_SECTION_SIZE_4 (4 * 1024) #define WLAN_SECTION_SIZE_5 (64 * 1024) #define WLAN_SECTION_SIZE_6 (4 * 1024) #define WLAN_SECTION_SIZE_7 (4 * 1024) static struct sk_buff *wlan_static_skb[WLAN_SKB_BUF_NUM + 1]; struct wifi_mem_prealloc { void *mem_ptr; unsigned long size; }; static struct wifi_mem_prealloc wifi_mem_array[8] = { { NULL, (WLAN_SECTION_SIZE_0) }, { NULL, (WLAN_SECTION_SIZE_1) }, { NULL, (WLAN_SECTION_SIZE_2) }, { NULL, (WLAN_SECTION_SIZE_3) }, { NULL, (WLAN_SECTION_SIZE_4) }, { NULL, (WLAN_SECTION_SIZE_5) }, { NULL, (WLAN_SECTION_SIZE_6) }, { NULL, (WLAN_SECTION_SIZE_7) } }; static int rockchip_init_wifi_mem(void) { int i; int j; for (i = 0; i < WLAN_SKB_BUF_NUM; i++) { wlan_static_skb[i] = dev_alloc_skb(((i < (WLAN_SKB_BUF_NUM / 2)) ? (PAGE_SIZE * 1) : (PAGE_SIZE * 2))); if (!wlan_static_skb[i]) goto err_skb_alloc; } wlan_static_skb[i] = dev_alloc_skb((PAGE_SIZE * 4)); if (!wlan_static_skb[i]) goto err_skb_alloc; for (i = 0; i <= 7; i++) { wifi_mem_array[i].mem_ptr = kmalloc(wifi_mem_array[i].size, GFP_KERNEL); if (!wifi_mem_array[i].mem_ptr) goto err_mem_alloc; } return 0; err_mem_alloc: pr_err("Failed to mem_alloc for WLAN\n"); for (j = 0; j < i; j++) kfree(wifi_mem_array[j].mem_ptr); i = WLAN_SKB_BUF_NUM; err_skb_alloc: pr_err("Failed to skb_alloc for WLAN\n"); for (j = 0; j < i; j++) dev_kfree_skb(wlan_static_skb[j]); dev_kfree_skb(wlan_static_skb[j]); return -ENOMEM; } void *rockchip_mem_prealloc(int section, unsigned long size) { if (section == PREALLOC_WLAN_SEC_NUM) return wlan_static_skb; if (section < 0 || section > 7) return NULL; if (wifi_mem_array[section].size < size) return NULL; return wifi_mem_array[section].mem_ptr; } #else void *rockchip_mem_prealloc(int section, unsigned long size) { return NULL; } #endif EXPORT_SYMBOL(rockchip_mem_prealloc); int rfkill_set_wifi_bt_power(int on) { struct rfkill_wlan_data *mrfkill = g_rfkill; struct rksdmmc_gpio *vbat; LOG("%s: %d\n", __func__, on); if (!mrfkill) { LOG("%s: rfkill-wlan driver has not Successful initialized\n", __func__); return -1; } vbat = &mrfkill->pdata->vbat_n; if (on) { if (gpio_is_valid(vbat->io)) gpio_direction_output(vbat->io, vbat->enable); } else { if (gpio_is_valid(vbat->io)) gpio_direction_output(vbat->io, !(vbat->enable)); } wifi_bt_vbat_state = on; return 0; } /************************************************************************** * * get wifi power state Func * *************************************************************************/ int rfkill_get_wifi_power_state(int *power) { struct rfkill_wlan_data *mrfkill = g_rfkill; if (!mrfkill) { LOG("%s: rfkill-wlan driver has not Successful initialized\n", __func__); return -1; } *power = wifi_power_state; return 0; } EXPORT_SYMBOL(rfkill_get_wifi_power_state); /************************************************************************** * * Wifi Power Control Func * 0 -> power off * 1 -> power on * *************************************************************************/ int rockchip_wifi_power(int on) { struct rfkill_wlan_data *mrfkill = g_rfkill; struct rksdmmc_gpio *poweron, *reset; struct regulator *ldo = NULL; int bt_power = 0; bool toggle = false; LOG("%s: %d\n", __func__, on); if (!mrfkill) { LOG("%s: rfkill-wlan driver has not Successful initialized\n", __func__); return -1; } if (mrfkill->pdata->wifi_power_remain && power_set_time) { LOG("%s: wifi power is setted to be remain on.", __func__); return 0; } power_set_time++; if (!rfkill_get_bt_power_state(&bt_power, &toggle)) { LOG("%s: toggle = %s\n", __func__, toggle ? "true" : "false"); } if (mrfkill->pdata->mregulator.power_ctrl_by_pmu) { int ret = -1; char *ldostr; int level = mrfkill->pdata->mregulator.enable; ldostr = mrfkill->pdata->mregulator.pmu_regulator; if (!ldostr) return -1; ldo = regulator_get(NULL, ldostr); if (!ldo || IS_ERR(ldo)) { LOG("\n\n\n%s get ldo error,please mod this\n\n\n", __func__); return -1; } if (on == level) { regulator_set_voltage(ldo, 3000000, 3000000); LOG("%s: %s enabled\n", __func__, ldostr); ret = regulator_enable(ldo); if (ret) LOG("ldo enable failed\n"); wifi_power_state = 1; LOG("wifi turn on power.\n"); } else { LOG("%s: %s disabled\n", __func__, ldostr); while (regulator_is_enabled(ldo) > 0) { ret = regulator_disable(ldo); if (ret) LOG("ldo disable failed\n"); } wifi_power_state = 0; LOG("wifi shut off power.\n"); } regulator_put(ldo); msleep(100); } else { poweron = &mrfkill->pdata->power_n; reset = &mrfkill->pdata->reset_n; if (on) { if (toggle) { rfkill_set_wifi_bt_power(1); msleep(100); } if (gpio_is_valid(poweron->io)) { gpio_direction_output(poweron->io, poweron->enable); msleep(100); } if (gpio_is_valid(reset->io)) { gpio_direction_output(reset->io, reset->enable); msleep(100); } wifi_power_state = 1; LOG("wifi turn on power [GPIO%d-%d]\n", poweron->io, poweron->enable); } else { if (gpio_is_valid(poweron->io)) { printk("wifi power off\n"); gpio_direction_output(poweron->io, !(poweron->enable)); msleep(100); } if (gpio_is_valid(reset->io)) { gpio_direction_output(reset->io, !(reset->enable)); } wifi_power_state = 0; if (toggle) { if (!bt_power) { LOG("%s: wifi will set vbat to low\n", __func__); rfkill_set_wifi_bt_power(0); } else { LOG("%s: wifi shouldn't control the vbat\n", __func__); } } LOG("wifi shut off power [GPIO%d-%d]\n", poweron->io, !poweron->enable); } } return 0; } EXPORT_SYMBOL(rockchip_wifi_power); /************************************************************************** * * Wifi Sdio Detect Func * *************************************************************************/ int rockchip_wifi_set_carddetect(int val) { return 0; } EXPORT_SYMBOL(rockchip_wifi_set_carddetect); /************************************************************************** * * Wifi Get Interrupt irq Func * *************************************************************************/ int rockchip_wifi_get_oob_irq(void) { struct rfkill_wlan_data *mrfkill = g_rfkill; struct rksdmmc_gpio *wifi_int_irq; LOG("%s: Enter\n", __func__); if (!mrfkill) { LOG("%s: rfkill-wlan driver has not Successful initialized\n", __func__); return -1; } wifi_int_irq = &mrfkill->pdata->wifi_int_b; if (gpio_is_valid(wifi_int_irq->io)) { return gpio_to_irq(wifi_int_irq->io); //return wifi_int_irq->io; } else { LOG("%s: wifi OOB pin isn't defined.\n", __func__); } return -1; } EXPORT_SYMBOL(rockchip_wifi_get_oob_irq); int rockchip_wifi_get_oob_irq_flag(void) { struct rfkill_wlan_data *mrfkill = g_rfkill; struct rksdmmc_gpio *wifi_int_irq; int gpio_flags = -1; if (mrfkill) { wifi_int_irq = &mrfkill->pdata->wifi_int_b; if (gpio_is_valid(wifi_int_irq->io)) gpio_flags = wifi_int_irq->enable; } return gpio_flags; } EXPORT_SYMBOL(rockchip_wifi_get_oob_irq_flag); /************************************************************************** * * Wifi Reset Func * *************************************************************************/ int rockchip_wifi_reset(int on) { return 0; } EXPORT_SYMBOL(rockchip_wifi_reset); /************************************************************************** * * Wifi MAC custom Func * *************************************************************************/ #include <linux/etherdevice.h> #include <linux/errno.h> u8 wifi_custom_mac_addr[6] = { 0, 0, 0, 0, 0, 0 }; //#define RANDOM_ADDRESS_SAVE static int get_wifi_addr_vendor(unsigned char *addr) { int ret; int count = 5; while (count-- > 0) { if (is_rk_vendor_ready()) break; /* sleep 500ms wait rk vendor driver ready */ msleep(500); } ret = rk_vendor_read(WIFI_MAC_ID, addr, 6); if (ret != 6 || is_zero_ether_addr(addr)) { LOG("%s: rk_vendor_read wifi mac address failed (%d)\n", __func__, ret); #ifdef CONFIG_WIFI_GENERATE_RANDOM_MAC_ADDR random_ether_addr(addr); LOG("%s: generate random wifi mac address: " "%02x:%02x:%02x:%02x:%02x:%02x\n", __func__, addr[0], addr[1], addr[2], addr[3], addr[4], addr[5]); ret = rk_vendor_write(WIFI_MAC_ID, addr, 6); if (ret != 0) { LOG("%s: rk_vendor_write failed %d\n" __func__, ret); memset(addr, 0, 6); return -1; } #else return -1; #endif } else { LOG("%s: rk_vendor_read wifi mac address: " "%02x:%02x:%02x:%02x:%02x:%02x\n", __func__, addr[0], addr[1], addr[2], addr[3], addr[4], addr[5]); } return 0; } int rockchip_wifi_mac_addr(unsigned char *buf) { char mac_buf[20] = { 0 }; LOG("%s: enter.\n", __func__); // from vendor storage if (is_zero_ether_addr(wifi_custom_mac_addr)) { if (get_wifi_addr_vendor(wifi_custom_mac_addr) != 0) return -1; } sprintf(mac_buf, "%02x:%02x:%02x:%02x:%02x:%02x", wifi_custom_mac_addr[0], wifi_custom_mac_addr[1], wifi_custom_mac_addr[2], wifi_custom_mac_addr[3], wifi_custom_mac_addr[4], wifi_custom_mac_addr[5]); LOG("falsh wifi_custom_mac_addr=[%s]\n", mac_buf); if (is_valid_ether_addr(wifi_custom_mac_addr)) { if (!strncmp(wifi_chip_type_string, "rtl", 3)) wifi_custom_mac_addr[0] &= ~0x2; // for p2p } else { LOG("This mac address is not valid, ignored...\n"); return -1; } memcpy(buf, wifi_custom_mac_addr, 6); return 0; } EXPORT_SYMBOL(rockchip_wifi_mac_addr); /************************************************************************** * * wifi get country code func * *************************************************************************/ struct cntry_locales_custom { char iso_abbrev[4]; /* ISO 3166-1 country abbreviation */ char custom_locale[4]; /* Custom firmware locale */ int custom_locale_rev; /* Custom local revisin default -1 */ }; static struct cntry_locales_custom country_cloc; void *rockchip_wifi_country_code(char *ccode) { struct cntry_locales_custom *mcloc; LOG("%s: set country code [%s]\n", __func__, ccode); mcloc = &country_cloc; memcpy(mcloc->custom_locale, ccode, 4); mcloc->custom_locale_rev = 0; return mcloc; } EXPORT_SYMBOL(rockchip_wifi_country_code); /**************************************************************************/ static int rfkill_rk_setup_gpio(struct rksdmmc_gpio *gpio, const char *prefix, const char *name) { if (gpio_is_valid(gpio->io)) { int ret = 0; sprintf(gpio->name, "%s_%s", prefix, name); ret = gpio_request(gpio->io, gpio->name); if (ret) { LOG("Failed to get %s gpio.\n", gpio->name); return -1; } } return 0; } #ifdef CONFIG_OF static int wlan_platdata_parse_dt(struct device *dev, struct rksdmmc_gpio_wifi_moudle *data) { struct device_node *node = dev->of_node; const char *strings; u32 value; int gpio, ret; unsigned long flags; u32 ext_clk_value = 0; if (!node) return -ENODEV; memset(data, 0, sizeof(*data)); #ifdef CONFIG_MFD_SYSCON data->grf = syscon_regmap_lookup_by_phandle(node, "rockchip,grf"); if (IS_ERR(data->grf)) { LOG("can't find rockchip,grf property\n"); //return -1; } #endif ret = of_property_read_string(node, "wifi_chip_type", &strings); if (ret) { LOG("%s: Can not read wifi_chip_type, set default to rkwifi.\n", __func__); strcpy(wifi_chip_type_string, "rkwifi"); } else { if (strings && strlen(strings) < 64) strcpy(wifi_chip_type_string, strings); } LOG("%s: wifi_chip_type = %s\n", __func__, wifi_chip_type_string); if (of_find_property(node, "keep_wifi_power_on", NULL)) { data->wifi_power_remain = true; LOG("%s: wifi power remain\n", __func__); } else { data->wifi_power_remain = false; LOG("%s: enable wifi power control.\n", __func__); } if (of_find_property(node, "power_ctrl_by_pmu", NULL)) { data->mregulator.power_ctrl_by_pmu = true; ret = of_property_read_string(node, "power_pmu_regulator", &strings); if (ret) { LOG("%s: Can not read property: power_pmu_regulator.\n", __func__); data->mregulator.power_ctrl_by_pmu = false; } else { LOG("%s: wifi power controlled by pmu(%s).\n", __func__, strings); sprintf(data->mregulator.pmu_regulator, "%s", strings); } ret = of_property_read_u32(node, "power_pmu_enable_level", &value); if (ret) { LOG("%s: Can not read: power_pmu_enable_level.\n", __func__); data->mregulator.power_ctrl_by_pmu = false; } else { LOG("%s: wifi power controlled by pmu(level = %s).\n", __func__, (value == 1) ? "HIGH" : "LOW"); data->mregulator.enable = value; } } else { data->mregulator.power_ctrl_by_pmu = false; LOG("%s: wifi power controled by gpio.\n", __func__); gpio = of_get_named_gpio_flags(node, "WIFI,poweren_gpio", 0, &flags); if (gpio_is_valid(gpio)) { data->power_n.io = gpio; data->power_n.enable = (flags == GPIO_ACTIVE_HIGH) ? 1 : 0; LOG("%s: WIFI,poweren_gpio = %d flags = %d.\n", __func__, gpio, flags); } else { data->power_n.io = -1; } gpio = of_get_named_gpio_flags(node, "WIFI,vbat_gpio", 0, &flags); if (gpio_is_valid(gpio)) { data->vbat_n.io = gpio; data->vbat_n.enable = (flags == GPIO_ACTIVE_HIGH) ? 1 : 0; LOG("%s: WIFI,vbat_gpio = %d, flags = %d.\n", __func__, gpio, flags); } else { data->vbat_n.io = -1; } gpio = of_get_named_gpio_flags(node, "WIFI,reset_gpio", 0, &flags); if (gpio_is_valid(gpio)) { data->reset_n.io = gpio; data->reset_n.enable = (flags == GPIO_ACTIVE_HIGH) ? 1 : 0; LOG("%s: WIFI,reset_gpio = %d, flags = %d.\n", __func__, gpio, flags); } else { data->reset_n.io = -1; } gpio = of_get_named_gpio_flags(node, "WIFI,host_wake_irq", 0, &flags); if (gpio_is_valid(gpio)) { data->wifi_int_b.io = gpio; data->wifi_int_b.enable = !flags; LOG("%s: WIFI,host_wake_irq = %d, flags = %d.\n", __func__, gpio, flags); } else { data->wifi_int_b.io = -1; } } data->ext_clk = devm_clk_get(dev, "clk_wifi"); if (IS_ERR(data->ext_clk)) { LOG("%s: The ref_wifi_clk not found !\n", __func__); } else { of_property_read_u32(node, "ref-clock-frequency", &ext_clk_value); if (ext_clk_value > 0) { ret = clk_set_rate(data->ext_clk, ext_clk_value); if (ret) LOG("%s: set ref clk error!\n", __func__); } ret = clk_prepare_enable(data->ext_clk); if (ret) LOG("%s: enable ref clk error!\n", __func__); /* WIFI clock (REF_CLKOUT) output enable. * 1'b0: drive disable * 1'b1: output enable */ if (of_machine_is_compatible("rockchip,rk3308")) regmap_write(data->grf, 0x0314, 0x00020002); } return 0; } #endif //CONFIG_OF #if defined(CONFIG_HAS_EARLYSUSPEND) #include <linux/earlysuspend.h> static void wlan_early_suspend(struct early_suspend *h) { LOG("%s :enter\n", __func__); return; } static void wlan_late_resume(struct early_suspend *h) { LOG("%s :enter\n", __func__); return; } struct early_suspend wlan_early_suspend { .level = EARLY_SUSPEND_LEVEL_BLANK_SCREEN - 20; .suspend = wlan_early_suspend; .resume = wlan_late_resume; } #endif static void rfkill_wlan_early_suspend(void) { //LOG("%s :enter\n", __func__); return; } static void rfkill_wlan_later_resume(void) { //LOG("%s :enter\n", __func__); return; } static int rfkill_wlan_fb_event_notify(struct notifier_block *self, unsigned long action, void *data) { struct fb_event *event = data; int blank_mode = *((int *)event->data); switch (blank_mode) { case FB_BLANK_UNBLANK: rfkill_wlan_later_resume(); break; case FB_BLANK_NORMAL: rfkill_wlan_early_suspend(); break; default: rfkill_wlan_early_suspend(); break; } return 0; } static struct notifier_block rfkill_wlan_fb_notifier = { .notifier_call = rfkill_wlan_fb_event_notify, }; static ssize_t wifi_power_show(struct class *cls, struct class_attribute *attr, char *_buf) { return sprintf(_buf, "%d\n", wifi_power_state); } static ssize_t wifi_power_store(struct class *cls, struct class_attribute *attr, const char *_buf, size_t _count) { long poweren = 0; if (kstrtol(_buf, 10, &poweren) < 0) return -EINVAL; LOG("%s: poweren = %ld\n", __func__, poweren); if (poweren > 0) rockchip_wifi_power(1); else rockchip_wifi_power(0); return _count; } static CLASS_ATTR_RW(wifi_power); static ssize_t wifi_bt_vbat_show(struct class *cls, struct class_attribute *attr, char *_buf) { return sprintf(_buf, "%d\n", wifi_bt_vbat_state); } static ssize_t wifi_bt_vbat_store(struct class *cls, struct class_attribute *attr, const char *_buf, size_t _count) { long vbat = 0; if (kstrtol(_buf, 10, &vbat) < 0) return -EINVAL; LOG("%s: vbat = %ld\n", __func__, vbat); if (vbat > 0) rfkill_set_wifi_bt_power(1); else rfkill_set_wifi_bt_power(0); return _count; } static CLASS_ATTR_RW(wifi_bt_vbat); static ssize_t wifi_set_carddetect_store(struct class *cls, struct class_attribute *attr, const char *_buf, size_t _count) { long val = 0; if (kstrtol(_buf, 10, &val) < 0) return -EINVAL; LOG("%s: val = %ld\n", __func__, val); if (val > 0) rockchip_wifi_set_carddetect(1); else rockchip_wifi_set_carddetect(0); return _count; } static CLASS_ATTR_WO(wifi_set_carddetect); static struct attribute *rkwifi_power_attrs[] = { &class_attr_wifi_power.attr, &class_attr_wifi_bt_vbat.attr, &class_attr_wifi_set_carddetect.attr, NULL, }; ATTRIBUTE_GROUPS(rkwifi_power); /** Device model classes */ static struct class rkwifi_power = { .name = "rkwifi", .class_groups = rkwifi_power_groups, }; static int rfkill_wlan_probe(struct platform_device *pdev) { struct rfkill_wlan_data *rfkill; struct rksdmmc_gpio_wifi_moudle *pdata = pdev->dev.platform_data; int ret = -1; LOG("Enter %s\n", __func__); class_register(&rkwifi_power); if (!pdata) { #ifdef CONFIG_OF pdata = kzalloc(sizeof(*pdata), GFP_KERNEL); if (!pdata) return -ENOMEM; ret = wlan_platdata_parse_dt(&pdev->dev, pdata); if (ret < 0) { #endif LOG("%s: No platform data specified\n", __func__); return ret; #ifdef CONFIG_OF } #endif } rfkill = kzalloc(sizeof(*rfkill), GFP_KERNEL); if (!rfkill) goto rfkill_alloc_fail; rfkill->pdata = pdata; g_rfkill = rfkill; LOG("%s: init gpio\n", __func__); if (!pdata->mregulator.power_ctrl_by_pmu) { ret = rfkill_rk_setup_gpio(&pdata->vbat_n, wlan_name, "wlan_vbat"); if (ret) goto fail_alloc; ret = rfkill_rk_setup_gpio(&pdata->reset_n, wlan_name, "wlan_reset"); if (ret) goto fail_alloc; } wake_lock_init(&rfkill->wlan_irq_wl, WAKE_LOCK_SUSPEND, "rfkill_wlan_wake"); rfkill_set_wifi_bt_power(1); #ifdef CONFIG_SDIO_KEEPALIVE if (gpio_is_valid(pdata->power_n.io) && gpio_direction_output(pdata->power_n.io, pdata->power_n.enable); #endif if (pdata->wifi_power_remain) rockchip_wifi_power(1); #if BCM_STATIC_MEMORY_SUPPORT rockchip_init_wifi_mem(); #endif #if defined(CONFIG_HAS_EARLYSUSPEND) register_early_suspend(wlan_early_suspend); #endif fb_register_client(&rfkill_wlan_fb_notifier); LOG("Exit %s\n", __func__); return 0; fail_alloc: kfree(rfkill); rfkill_alloc_fail: kfree(pdata); g_rfkill = NULL; return ret; } static int rfkill_wlan_remove(struct platform_device *pdev) { struct rfkill_wlan_data *rfkill = platform_get_drvdata(pdev); LOG("Enter %s\n", __func__); wake_lock_destroy(&rfkill->wlan_irq_wl); fb_unregister_client(&rfkill_wlan_fb_notifier); if (gpio_is_valid(rfkill->pdata->power_n.io)) gpio_free(rfkill->pdata->power_n.io); if (gpio_is_valid(rfkill->pdata->reset_n.io)) gpio_free(rfkill->pdata->reset_n.io); kfree(rfkill); g_rfkill = NULL; return 0; } static void rfkill_wlan_shutdown(struct platform_device *pdev) { LOG("Enter %s\n", __func__); rockchip_wifi_power(0); rfkill_set_wifi_bt_power(0); } static int rfkill_wlan_suspend(struct platform_device *pdev, pm_message_t state) { LOG("Enter %s\n", __func__); return 0; } static int rfkill_wlan_resume(struct platform_device *pdev) { LOG("Enter %s\n", __func__); return 0; } #ifdef CONFIG_OF static struct of_device_id wlan_platdata_of_match[] = { { .compatible = "wlan-platdata" }, {} }; MODULE_DEVICE_TABLE(of, wlan_platdata_of_match); #endif //CONFIG_OF static struct platform_driver rfkill_wlan_driver = { .probe = rfkill_wlan_probe, .remove = rfkill_wlan_remove, .shutdown = rfkill_wlan_shutdown, .suspend = rfkill_wlan_suspend, .resume = rfkill_wlan_resume, .driver = { .name = "wlan-platdata", .owner = THIS_MODULE, .of_match_table = of_match_ptr(wlan_platdata_of_match), }, }; int __init rfkill_wlan_init(void) { LOG("Enter %s\n", __func__); return platform_driver_register(&rfkill_wlan_driver); } void __exit rfkill_wlan_exit(void) { LOG("Enter %s\n", __func__); platform_driver_unregister(&rfkill_wlan_driver); } MODULE_DESCRIPTION("rock-chips rfkill for wifi v0.1"); MODULE_AUTHOR("gwl@rock-chips.com"); MODULE_LICENSE("GPL");
此外,无论针对linux 5.28还是linux 6.3,均需要修改net/rfkill/Makefile添加如下代码:
rfkill-rk-y += rfkill-wlan.o rfkill-bt.o
obj-$(CONFIG_RFKILL_RK) += rfkill-rk.o
修改net/rfkill/Kconfig添加如下代码:
config RFKILL_RK tristate "Rockchip RFKILL driver" depends on RFKILL depends on MMC depends on ARCH_ROCKCHIP default n help Rockchip rfkill driver for rk29/rk3X
4.3.2 新增wireless-wlan设备节点
为了支持rfkill WiFi驱动,在arch/arm64/boot/dts/rockchip/rk3399-evb.dts根节点下新增:
wireless-wlan { compatible = "wlan-platdata"; rockchip,grf = <&grf>; wifi_chip_type = "ap6356"; sdio_vref = <1800>; WIFI,host_wake_irq = <&gpio0 3 GPIO_ACTIVE_HIGH>; /* GPIO0_a3 */ status = "okay"; };
这里比较重要的属性:
- wifi_chip_type:指定WiFi芯片型号;
- WIFI,host_wake_irq:指定WiFi芯片唤醒主机的中断引脚,这里配置的位RK399 GPIO0_A3引脚;
4.3.3 新增wireless-bluetooth设备节点
为了支持rfkill蓝牙驱动,我们需要在arch/arm64/boot/dts/rockchip/rk3399-evb.dts根节点下新增:
wireless-bluetooth { compatible = "bluetooth-platdata"; clocks = <&rk808 1>; clock-names = "ext_clock"; //wifi-bt-power-toggle; uart_rts_gpios = <&gpio2 19 GPIO_ACTIVE_LOW>; /* GPIO2_C3 */ pinctrl-names = "default", "rts_gpio"; pinctrl-0 = <&uart0_rts>; pinctrl-1 = <&uart0_gpios>; //BT,power_gpio = <&gpio3 19 GPIO_ACTIVE_HIGH>; /* GPIOx_xx */ BT,reset_gpio = <&gpio0 9 GPIO_ACTIVE_HIGH>; /* GPIO0_B1 */ BT,wake_gpio = <&gpio2 26 GPIO_ACTIVE_HIGH>; /* GPIO2_D2 */ BT,wake_host_irq = <&gpio0 4 GPIO_ACTIVE_HIGH>; /* GPIO0_A4 */ status = "okay"; };
其中:
- clocks:指定了设备使用的时钟源,即使用rk808时钟控制器ID为1的时钟,rk808是一个时钟提供者clock provider;
- clock-names:指定了所使用的时钟的名称,即 "ext_clock";
- uart_rts_gpios:表示控制蓝牙串口请求发送引脚UART_CTS_N为GPIO2_C3,低电平有效;
- pinctrl-names:表示该设备节点支持两种pinctrl模式,分别为default和rts_gpio;
- pinctrl-0:设置default状态对应的引脚配置为uart0_rts;uart0_rts定义GPIO2_C3功能复用为UART RST;
- pinctrl-1:表设置rts_gpio状态对应的引脚配置为uart0_gpios;uart0_gpios定义GPIO2_C3功能复用为GPIO;
- BT,reset_gpio:配置蓝牙复位引脚BT_REG_ON为GPIO0_B1,高电平有效;
- BT,wake_gpio:配置主机唤醒蓝牙设备引脚BT_WAKE为GPIO2_D2,高电平有效;
- BT,wake_host_irq:配置蓝牙设备唤醒主机引脚BT_HOST_WAKE为GPIO0_A4,高电平有效;
- status:表示该设备状态为正常运行;
这里顺带看一下uart0_rts、uart0_gpios配置:
uart0_rts: uart0-rts { rockchip,pins = <2 RK_PC3 1 &pcfg_pull_none>; }; // 这个也是要新增的,默认没有这个配置 在&pinctrl下新增该子节点 wireless-bluetooth { uart0_gpios: uart0-gpios { rockchip,pins = <2 RK_PC3 RK_FUNC_GPIO &pcfg_pull_none>; }; };
4.3.4 配置内核
需要配置内核:
[*] Networking support ---> <*> RF switch subsystem support ---> <*> Rockchip RFKILL driver
4.4 rk808驱动配置
在arch/arm64/boot/dts/rockchip/rk3399-evb.dts中添加如下节点:

&i2c0 { clock-frequency = <400000>; i2c-scl-rising-time-ns = <160>; i2c-scl-falling-time-ns = <30>; status = "okay"; vdd_cpu_b: regulator@40 { compatible = "silergy,syr827"; reg = <0x40>; fcs,suspend-voltage-selector = <1>; pinctrl-names = "default"; pinctrl-0 = <&cpu_b_sleep>; regulator-always-on; regulator-boot-on; regulator-min-microvolt = <712500>; regulator-max-microvolt = <1500000>; regulator-name = "vdd_cpu_b"; regulator-ramp-delay = <1000>; vin-supply = <&vcc3v3_sys>; regulator-state-mem { regulator-off-in-suspend; }; }; vdd_gpu: regulator@41 { compatible = "silergy,syr828"; reg = <0x41>; fcs,suspend-voltage-selector = <1>; pinctrl-names = "default"; pinctrl-0 = <&gpu_sleep>; regulator-always-on; regulator-boot-on; regulator-min-microvolt = <712500>; regulator-max-microvolt = <1500000>; regulator-name = "vdd_gpu"; regulator-ramp-delay = <1000>; vin-supply = <&vcc3v3_sys>; regulator-state-mem { regulator-off-in-suspend; }; }; rk808: pmic@1b { compatible = "rockchip,rk808"; reg = <0x1b>; clock-output-names = "xin32k", "rtc_clko_wifi"; #clock-cells = <1>; interrupt-parent = <&gpio1>; interrupts = <21 IRQ_TYPE_LEVEL_LOW>; pinctrl-names = "default"; pinctrl-0 = <&pmic_int_l>; rockchip,system-power-controller; wakeup-source; vcc1-supply = <&vcc3v3_sys>; vcc2-supply = <&vcc3v3_sys>; vcc3-supply = <&vcc3v3_sys>; vcc4-supply = <&vcc3v3_sys>; vcc6-supply = <&vcc3v3_sys>; vcc7-supply = <&vcc3v3_sys>; vcc8-supply = <&vcc3v3_sys>; vcc9-supply = <&vcc3v3_sys>; vcc10-supply = <&vcc3v3_sys>; vcc11-supply = <&vcc3v3_sys>; vcc12-supply = <&vcc3v3_sys>; vddio-supply = <&vcc_3v0>; regulators { vdd_center: DCDC_REG1 { regulator-always-on; regulator-boot-on; regulator-min-microvolt = <750000>; regulator-max-microvolt = <1350000>; regulator-name = "vdd_center"; regulator-ramp-delay = <6001>; regulator-state-mem { regulator-off-in-suspend; }; }; vdd_cpu_l: DCDC_REG2 { regulator-always-on; regulator-boot-on; regulator-min-microvolt = <750000>; regulator-max-microvolt = <1350000>; regulator-name = "vdd_cpu_l"; regulator-ramp-delay = <6001>; regulator-state-mem { regulator-off-in-suspend; }; }; vcc_ddr: DCDC_REG3 { regulator-always-on; regulator-boot-on; regulator-name = "vcc_ddr"; regulator-state-mem { regulator-on-in-suspend; }; }; vcc_1v8: DCDC_REG4 { regulator-always-on; regulator-boot-on; regulator-min-microvolt = <1800000>; regulator-max-microvolt = <1800000>; regulator-name = "vcc_1v8"; regulator-state-mem { regulator-on-in-suspend; regulator-suspend-microvolt = <1800000>; }; }; vcc1v8_cam: LDO_REG1 { regulator-always-on; regulator-boot-on; regulator-min-microvolt = <1800000>; regulator-max-microvolt = <1800000>; regulator-name = "vcc1v8_cam"; regulator-state-mem { regulator-off-in-suspend; }; }; vcc3v0_touch: LDO_REG2 { regulator-always-on; regulator-boot-on; regulator-min-microvolt = <3000000>; regulator-max-microvolt = <3000000>; regulator-name = "vcc3v0_touch"; regulator-state-mem { regulator-off-in-suspend; }; }; vcc1v8_pmupll: LDO_REG3 { regulator-always-on; regulator-boot-on; regulator-min-microvolt = <1800000>; regulator-max-microvolt = <1800000>; regulator-name = "vcc1v8_pmupll"; regulator-state-mem { regulator-on-in-suspend; regulator-suspend-microvolt = <1800000>; }; }; vcc_sdio: LDO_REG4 { regulator-always-on; regulator-boot-on; regulator-init-microvolt = <3000000>; regulator-min-microvolt = <1800000>; regulator-max-microvolt = <3300000>; regulator-name = "vcc_sdio"; regulator-state-mem { regulator-on-in-suspend; regulator-suspend-microvolt = <3000000>; }; }; vcca3v0_codec: LDO_REG5 { regulator-always-on; regulator-boot-on; regulator-min-microvolt = <3000000>; regulator-max-microvolt = <3000000>; regulator-name = "vcca3v0_codec"; regulator-state-mem { regulator-off-in-suspend; }; }; vcc_1v5: LDO_REG6 { regulator-always-on; regulator-boot-on; regulator-min-microvolt = <1500000>; regulator-max-microvolt = <1500000>; regulator-name = "vcc_1v5"; regulator-state-mem { regulator-on-in-suspend; regulator-suspend-microvolt = <1500000>; }; }; vcca1v8_codec: LDO_REG7 { regulator-always-on; regulator-boot-on; regulator-min-microvolt = <1800000>; regulator-max-microvolt = <1800000>; regulator-name = "vcca1v8_codec"; regulator-state-mem { regulator-off-in-suspend; }; }; vcc_3v0: LDO_REG8 { regulator-always-on; regulator-boot-on; regulator-min-microvolt = <3000000>; regulator-max-microvolt = <3000000>; regulator-name = "vcc_3v0"; regulator-state-mem { regulator-on-in-suspend; regulator-suspend-microvolt = <3000000>; }; }; vcc3v3_s3: SWITCH_REG1 { regulator-always-on; regulator-boot-on; regulator-name = "vcc3v3_s3"; regulator-state-mem { regulator-off-in-suspend; }; }; vcc3v3_s0: SWITCH_REG2 { regulator-always-on; regulator-boot-on; regulator-name = "vcc3v3_s0"; regulator-state-mem { regulator-off-in-suspend; }; }; }; }; };
同时移除该文件根节点下的vdd_center设备节点:
vdd_center: vdd-center { compatible = "pwm-regulator"; pwms = <&pwm3 0 25000 0>; regulator-name = "vdd_center"; regulator-min-microvolt = <800000>; regulator-max-microvolt = <1400000>; regulator-always-on; regulator-boot-on; status = "okay"; };
修改&pinctrl下pmic引脚配置节点:
pmic { cpu_b_sleep: cpu-b-sleep { rockchip,pins = <1 RK_PC1 RK_FUNC_GPIO &pcfg_pull_down>; }; gpu_sleep: gpu-sleep { rockchip,pins = <1 RK_PB6 RK_FUNC_GPIO &pcfg_pull_down>; }; pmic_int_l: pmic-int-l { rockchip,pins = <1 RK_PC5 RK_FUNC_GPIO &pcfg_pull_up>; }; };
4.5 配置内核
配置完内核之后记得保存配置:
存档:
root@zhengyang:/work/sambashare/rk3399/linux-6.3# mv rk3399_defconfig ./arch/arm64/configs/
亲爱的读者和支持者们,自动博客加入了打赏功能,陆陆续续收到了各位老铁的打赏。在此,我想由衷地感谢每一位对我们博客的支持和打赏。你们的慷慨与支持,是我们前行的动力与源泉。
日期 | 姓名 | 金额 |
---|---|---|
2023-09-06 | *源 | 19 |
2023-09-11 | *朝科 | 88 |
2023-09-21 | *号 | 5 |
2023-09-16 | *真 | 60 |
2023-10-26 | *通 | 9.9 |
2023-11-04 | *慎 | 0.66 |
2023-11-24 | *恩 | 0.01 |
2023-12-30 | I*B | 1 |
2024-01-28 | *兴 | 20 |
2024-02-01 | QYing | 20 |
2024-02-11 | *督 | 6 |
2024-02-18 | 一*x | 1 |
2024-02-20 | c*l | 18.88 |
2024-01-01 | *I | 5 |
2024-04-08 | *程 | 150 |
2024-04-18 | *超 | 20 |
2024-04-26 | .*V | 30 |
2024-05-08 | D*W | 5 |
2024-05-29 | *辉 | 20 |
2024-05-30 | *雄 | 10 |
2024-06-08 | *: | 10 |
2024-06-23 | 小狮子 | 666 |
2024-06-28 | *s | 6.66 |
2024-06-29 | *炼 | 1 |
2024-06-30 | *! | 1 |
2024-07-08 | *方 | 20 |
2024-07-18 | A*1 | 6.66 |
2024-07-31 | *北 | 12 |
2024-08-13 | *基 | 1 |
2024-08-23 | n*s | 2 |
2024-09-02 | *源 | 50 |
2024-09-04 | *J | 2 |
2024-09-06 | *强 | 8.8 |
2024-09-09 | *波 | 1 |
2024-09-10 | *口 | 1 |
2024-09-10 | *波 | 1 |
2024-09-12 | *波 | 10 |
2024-09-18 | *明 | 1.68 |
2024-09-26 | B*h | 10 |
2024-09-30 | 岁 | 10 |
2024-10-02 | M*i | 1 |
2024-10-14 | *朋 | 10 |
2024-10-22 | *海 | 10 |
2024-10-23 | *南 | 10 |
2024-10-26 | *节 | 6.66 |
2024-10-27 | *o | 5 |
2024-10-28 | W*F | 6.66 |
2024-10-29 | R*n | 6.66 |
2024-11-02 | *球 | 6 |
2024-11-021 | *鑫 | 6.66 |
2024-11-25 | *沙 | 5 |
2024-11-29 | C*n | 2.88 |

【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
2021-06-02 嵌入式Linux之uboot源码make配置编译正向分析