程序项目代做,有需求私信(vue、React、Java、爬虫、电路板设计、嵌入式linux等)

Rockchip RK3399 - rfkill子系统

----------------------------------------------------------------------------------------------------------------------------

开发板   :NanoPC-T4开发板
eMMC    :16GB
LPDDR3 :4GB
显示屏    :15.6英寸HDMI接口显示屏
u-boot      :uboot 2023.04
linux        :5.2.8 
----------------------------------------------------------------------------------------------------------------------------

本篇博客是以linux 5.2.8版本源码进行讲解的,与linux 6.3源码略有细微差别,但是整体上大致是一样的。

一、rfkill子系统

1.1 rfkill概述

rfkill是Linux内核提供的一个框架,用于控制无线通信硬件(如 WiFi、蓝牙、NFC 等)的开关和状态。rfkill就是RF(射频) 设备的开关,有类似一键关闭所有射频外设的功能。

rfkill的出现方便管理各种RF芯片的开关, 目前已经很多厂商的设备使用的是rfkill的驱动来管理一些无线设备的电源了,都是和RF相关的芯片,比如WiFi,蓝牙, NFC, FM,,GPS等等。

由于这个rfkill的功能是管理无线设备的电源开关,所以这和硬件的关联是直接的,一般都会涉及到硬件的power或reset引脚。

rfkill子系统有"hard"和"soft" blocked的概念,blocked的意思就是发射器关闭;

  • soft blocked ::表示软件层面对无线设备进行的阻止(比如说,通过运行rfkill激活命令)。在这种情况下,无线电器的硬件电源依旧是打开的,只是被软件控制给关掉了;
  • hard blocked:表示硬件层面对无线设备进行的阻止,通常是由于硬件按键或者物理开关进行的关闭。在这种情况下,即使在操作系统层面执行启动命令,也无法使无线电器工作;

区别在于软/硬件控制的不同。soft blocked是软件层面的禁用,硬件还是打开的,可以通过命令解除禁用;而hard blocked是硬件层面的禁用,无论从软件还是命令层面都无法解除。

当无线设备处于hard blocked状态时,意味着无法通过软件命令或驱动程序打开该设备;相反,必须在硬件层面解除阻塞才能使设备重新工作。这通常需要找到控制设备硬件的开关或按键,并在上面进行操作。

1.2 rfkill子系统框架

rfkill子系统主要三个部分组成:

  • rfkill core;为驱动程序提供了API,让驱动程序可向内核注册无线电发射器设备,以及打开和关闭内核的方法,,这样系统就知道怎么禁用设备硬件,rfkill core还向用户空间通知状态更改,并为用户空间提供查询当前状态的方法。 linux下提供了rfkill工具,用于开启和关闭无线设备功能;
  • rfkill input模块: 输入层处理程序,不推荐使用的,已由用户空间策略代码代替;
  • rfkill驱动程序:需要实现不同芯片的rkfill驱动程序;比如我们需要提供AP6356的rfkill驱动程序,实现AP6356设备的开启和关闭;

我们要做的一般是调试厂商已经做好的rfkill驱动,或者我们自己实现API的接口,这个并不难,其实最终就是操作GPIO引脚。

1.2.1 rfkill命令查看无线设备

在linux系统下可以通过rfkill工具查看无线设备的功能:

root@rk3399:/lib/modules# rfkill list
0: bt_default: Bluetooth
        Soft blocked: yes
        Hard blocked: no
1: phy0: Wireless LAN
        Soft blocked: no
        Hard blocked: no
root@rk3399:/lib/modules# rfkill
ID TYPE      DEVICE          SOFT      HARD
 0 bluetooth bt_default   blocked unblocked
 1 wlan      phy0       unblocked unblocked

这里看到有两个无线设备,一个是蓝牙的,另一个WiFi的,蓝牙设备默认是禁用的。

1.2.2 开启关闭无线设备

我们可以通过执行如下命令开启蓝牙设备;

root@rk3399:/lib/modules# rfkill unblock 0
root@rk3399:/lib/modules# rfkill list
0: bt_default: Bluetooth
        Soft blocked: no
        Hard blocked: no
1: phy0: Wireless LAN
        Soft blocked: no
        Hard blocked: no

同样我们可以通过命令关闭WiFi设备,可以看到当关闭WiFi设备后,就无法通过ifconfig看到设备了;

root@rk3399:/lib/modules# rfkill block 1
root@rk3399:/lib/modules# rfkill list
0: bt_default: Bluetooth
        Soft blocked: no
        Hard blocked: no
1: phy0: Wireless LAN
        Soft blocked: yes
        Hard blocked: no
root@rk3399:/lib/modules# ifconfig
eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 192.168.0.101  netmask 255.255.255.0  broadcast 192.168.0.255
        ether 92:a7:05:0f:19:86  txqueuelen 1000  (Ethernet)
        RX packets 719  bytes 677554 (677.5 KB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 438  bytes 36520 (36.5 KB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0
        device interrupt 24

lo: flags=73<UP,LOOPBACK,RUNNING>  mtu 65536
        inet 127.0.0.1  netmask 255.0.0.0
        loop  txqueuelen 1000  (Local Loopback)
        RX packets 195  bytes 15221 (15.2 KB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 195  bytes 15221 (15.2 KB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

此时我们通过ifconfig wlan0 up命令是无法启用WiFi的;

root@rk3399:/lib/modules# ifconfig wlan0 up
SIOCSIFFLAGS: Operation not possible due to RF-kill

1.3 目录结构

linux内核将rfkill子系统相关的代码位于内核net/rfkill目录下,rfkill驱动编程的API接口定义在include/linux/rfkill.h。

root@zhengyang:/work/sambashare/rk399/linux-5.2.8# ll net/rfkill
总用量 68
drwxr-xr-x  2 root root  4096 May 17 01:26 ./
drwxr-xr-x 70 root root  4096 May 17 01:26 ../
-rw-r--r--  1 root root 31583 May 17 01:26 core.c
-rw-r--r--  1 root root  8813 May 17 01:26 input.c
-rw-r--r--  1 root root   814 May 17 01:26 Kconfig
-rw-r--r--  1 root root   224 May 17 01:26 Makefile
-rw-r--r--  1 root root  4078 May 17 01:26 rfkill-gpio.c
-rw-r--r--  1 root root   590 May 17 01:26 rfkill.h

二、rfkill核心数据结构

学习rfkill驱动,首先要了解驱动框架涉及到的数据结构,知道每个数据结构以及成员的含义之后,再去看源码就容易了。

2.1 struct rfkill

内核使用struct rfkill数据结构来描述rfklill设备,包括rfkill设备的操作接口和状态描述等信息,定义在net/rfkill/core.c文件中:

struct rfkill{
        spinlock_t              lock;

        enum rfkill_type        type;

        unsigned long           state;

        u32                     idx;

        bool                    registered;
        bool                    persistent;
        bool                    polling_paused;
        bool                    suspended;

        const struct rfkill_ops *ops;
        void                    *data;

#ifdef CONFIG_RFKILL_LEDS
        struct led_trigger      led_trigger;
        const char              *ledtrigname;
#endif

        struct device           dev;
        struct list_head        node;

        struct delayed_work     poll_work;
        struct work_struct      uevent_work;
        struct work_struct      sync_work;
        char                    name[];
};

其中:

  • lock:自旋锁,用于保护rfkill结构体的访问。
  • type:枚举类型,表示rfkill开关类型(例如 WiFi、蓝牙、NFC 等);
  • state:无符号长整型,表示rfkill设备的状态,具体的取值与 type 相关;
  • idx:无符号整型,表示rfkill设备在系统中的索引;
  • registered:布尔类型,表示rfkill设备是否已经注册到系统中;
  • persistent:布尔类型,表示rfkill设备是否具有持久化属性;
  • polling_paused:布尔类型,表示rfkill设备是否处于轮询暂停状态;
  • suspended:布尔类型,表示rfkill设备是否处于挂起状态;
  • ops:指向一个 rfkill_ops 结构体的指针,表示rfkill设备的操作接口;
  • data:指向一个 void 类型的指针,可以用来存储任何用户自定义的数据;
  • led_trigger:一个 led_trigger 结构体,表示与rfkill设备相关联的灯;
  • ledtrigname:表示与rfkill设备相关联的灯的名称;
  • dev:一个 device 结构体,表示rfkill设备对应的device;
  • node:一个list_head结构体,用于将rfkill设备添加到全局双向链表rfkill_list中;
  • poll_work:一个delayed_work结构体,用于轮询rfkill设备状态的工作队列;
  • uevent_work:一个 work_struct 结构体,用于处理rfkill设备状态变化的uevent事件;
  • sync_work:一个work_struct结构体,用于同步rfkill设备的状态;

2.2 enum rfkill_type 

enum rfkill_type用于表示rfkill开关类型,定义在include/uapi/linux/rfkill.h:

/**
 * enum rfkill_type - type of rfkill switch.
 *
 * @RFKILL_TYPE_ALL: toggles all switches (requests only - not a switch type)
 * @RFKILL_TYPE_WLAN: switch is on a 802.11 wireless network device.
 * @RFKILL_TYPE_BLUETOOTH: switch is on a bluetooth device.
 * @RFKILL_TYPE_UWB: switch is on a ultra wideband device.
 * @RFKILL_TYPE_WIMAX: switch is on a WiMAX device.
 * @RFKILL_TYPE_WWAN: switch is on a wireless WAN device.
 * @RFKILL_TYPE_GPS: switch is on a GPS device.
 * @RFKILL_TYPE_FM: switch is on a FM radio device.
 * @RFKILL_TYPE_NFC: switch is on an NFC device.
 * @NUM_RFKILL_TYPES: number of defined rfkilltypes
 */
enum rfkill_type {
        RFKILL_TYPE_ALL = 0,
        RFKILL_TYPE_WLAN,
        RFKILL_TYPE_BLUETOOTH,
        RFKILL_TYPE_UWB,
        RFKILL_TYPE_WIMAX,
        RFKILL_TYPE_WWAN,
        RFKILL_TYPE_GPS,
        RFKILL_TYPE_FM,
        RFKILL_TYPE_NFC,
        NUM_RFKILL_TYPES,
};

其中:

  • RFKILL_TYPE_ALL:表示所有开关(仅请求,不是开关类型);
  • RFKILL_TYPE_WLAN:表示802.11无线网络设备的开关;
  • RFKILL_TYPE_BLUETOOTH:表示蓝牙设备上的开关;
  • RFKILL_TYPE_UWB:表示超宽带设备上的开关;
  • RFKILL_TYPE_WIMAX:表示 WiMAX 设备上的开关;
  • RFKILL_TYPE_WWAN:表示无线广域网设备上的开关;
  • RFKILL_TYPE_GPS:表示GPS设备上的开关;
  • RFKILL_TYPE_FM:表示FM收音机设备上的开关;
  • RFKILL_TYPE_NFC:表示NFC设备上的开关;
  • NUM_RFKILL_TYPES:枚举类型成员数量,可以用于数组的声明等;

2.3 struct rfkill_ops

struct rfkill_ops用于表示对rfkill设备的操作,这些操作一般需要与底层硬件打交道。该结构体定义在include/linux/rfkill.h:

/**
 * struct rfkill_ops - rfkill driver methods
 *
 * @poll: poll the rfkill block state(s) -- only assign this method
 *      when you need polling. When called, simply call one of the
 *      rfkill_set{,_hw,_sw}_state family of functions. If the hw
 *      is getting unblocked you need to take into account the return
 *      value of those functions to make sure the software block is
 *      properly used.
 * @query: query the rfkill block state(s) and call exactly one of the
 *      rfkill_set{,_hw,_sw}_state family of functions. Assign this
 *      method if input events can cause hardware state changes to make
 *      the rfkill core query your driver before setting a requested
 *      block.
 * @set_block: turn the transmitter on (blocked == false) or off
 *      (blocked == true) -- ignore and return 0 when hard blocked.
 *      This callback must be assigned.
 */
struct rfkill_ops {
        void    (*poll)(struct rfkill *rfkill, void *data);
        void    (*query)(struct rfkill *rfkill, void *data);
        int     (*set_block)(void *data, bool blocked);
};

其中:

  • poll: 轮询 rfkill设备开关的状态,在需要进行轮询时才分配此方法。调用该方法时,只需调用 rfkill_set{,_hw,_sw}_state 函数之一。如果硬件正在被解除阻止,则必须考虑这些函数的返回值,以确保正确使用软件阻塞;
  • query: 查询rfkill设备开关的状态,并调用 rfkill_set{,_hw,_sw}_state 函数之一。如果输入事件可能导致硬件状态更改,则应分配此方法,以使rfkill核心在设置请求块之前查询您的驱动程序;
  • set_block: 打开(blocked == false)或关闭(blocked == true)发射器。当硬件被阻止时,请忽略并返回 0,这个回调必须被分配;
通过使用这些函数,驱动程序可以在硬件和软件层面上控制rfkill设备开关的状态,并响应用户的请求或硬件事件。

三、rfkill核心API

rfkill核心提供了一些常用的API,用于管理rfkill设备的状态、注册、反注册以及灯控制等操作,这些API声明在include/linux/rfkill.h。

3.1 分配/释放rfkill设备

3.1.1 rfkill_alloc

rfkill_alloc用于动态申请一个rfkill结构体,并进行初始化;

/**
 * rfkill_alloc - Allocate rfkill structure
 * @name: name of the struct -- the string is not copied internally
 * @parent: device that has rf switch on it
 * @type: type of the switch (RFKILL_TYPE_*)
 * @ops: rfkill methods
 * @ops_data: data passed to each method
 *
 * This function should be called by the transmitter driver to allocate an
 * rfkill structure. Returns %NULL on failure.
 */
struct rfkill * __must_check rfkill_alloc(const char *name,
                                          struct device *parent,
                                          const enum rfkill_type type,
                                          const struct rfkill_ops *ops,
                                          void *ops_data);

rfkill_alloc函数实现位于net/rfkill/core.c文件中,该函数主要就是动态分配一个struct rfkill数据结构,并对ops、data、dev成员进行初始化;

struct rfkill * __must_check rfkill_alloc(const char *name,        // rfkill设备名称
                                          struct device *parent,   // 一般传入平台设备的dev成员
                                          const enum rfkill_type type,  // rfkill设备开关类型
                                          const struct rfkill_ops *ops, // rfkill设备操作集
                                          void *ops_data)  // rfkill驱动私有数据
{
        struct rfkill *rfkill;
        struct device *dev;

        if (WARN_ON(!ops))
                return NULL;

        if (WARN_ON(!ops->set_block))
                return NULL;

        if (WARN_ON(!name))
                return NULL;

        if (WARN_ON(type == RFKILL_TYPE_ALL || type >= NUM_RFKILL_TYPES))
                return NULL;

        rfkill = kzalloc(sizeof(*rfkill) + strlen(name) + 1, GFP_KERNEL);
        if (!rfkill)
                return NULL;

        spin_lock_init(&rfkill->lock);
        INIT_LIST_HEAD(&rfkill->node);
        rfkill->type = type;
        strcpy(rfkill->name, name);    // 初始化rfkill成员
        rfkill->ops = ops;
        rfkill->data = ops_data;

        dev = &rfkill->dev;
        dev->class = &rfkill_class;   // 设置其类为rfkill_class,从而当注册rfkill->dev设备后,会在/sys/class/rfkill下创建以rfkill设备名命名的文件夹
        dev->parent = parent;         // 设置父设备为platform设备的device
        device_initialize(dev);       // 初始化 

        return rfkill;
}

需要注意的是:这里并什么申请设备编号以及注册字符设备的逻辑,因此并不会注册字符设备。

这里我们看一下类rfkill_class的定义:

static struct class rfkill_class = {
        .name           = "rfkill",
        .dev_release    = rfkill_release,
        .dev_groups     = rfkill_dev_groups,
        .dev_uevent     = rfkill_dev_uevent,
        .pm             = RFKILL_PM_OPS,
};

rfkill_class是在rfkill core模块入口函数中注册的;

class_register(&rfkill_class)
3.1.2 rfkill_destroy

其对应的释放rfkill结构体的函数是rfkill_destroy:

/**
 * rfkill_destroy - Free rfkill structure
 * @rfkill: rfkill structure to be destroyed
 *
 * Destroys the rfkill structure.
 */
void rfkill_destroy(struct rfkill *rfkill);

3.2 注册/卸载rfkill设备

rfkill控制器驱动编写,实际上就是去为RF芯片分配一个rfkill数据结构,然后去根据去编写rfkill设备的操作函数。最后将其注册到内核即可。

3.2.1 rfkill_register

rfkill设备注册是通过rfkill_register函数来完成的:

/**
 * rfkill_register - Register a rfkill structure.
 * @rfkill: rfkill structure to be registered
 *
 * This function should be called by the transmitter driver to register
 * the rfkill structure. Before calling this function the driver needs
 * to be ready to service method calls from rfkill.
 *
 * If rfkill_init_sw_state() is not called before registration,
 * set_block() will be called to initialize the software blocked state
 * to a default value.
 *
 * If the hardware blocked state is not set before registration,
 * it is assumed to be unblocked.
 */
int __must_check rfkill_register(struct rfkill *rfkill);

rfkill_register函数实现位于net/rfkill/core.c文件中:

int __must_check rfkill_register(struct rfkill *rfkill)
{
        static unsigned long rfkill_no; // 静态全局变量,从0开始自增
        struct device *dev = &rfkill->dev;
        int error;

        BUG_ON(!rfkill);

        mutex_lock(&rfkill_global_mutex);  // 上锁

        if (rfkill->registered) {      // 如果已经注册,直接返回
                error = -EALREADY;
                goto unlock;
        }

        rfkill->idx = rfkill_no;                    // 编号
        dev_set_name(dev, "rfkill%lu", rfkill_no);  // 设置设备名称rfkill%d
        rfkill_no++;                                // 自增

        list_add_tail(&rfkill->node, &rfkill_list);  // 将当前rfkill设备追加到全局双向链表rfkill_list

        error = device_add(dev);   // 将dev设备注册到内核设备驱动程序模型中
        if (error)
                goto remove;

        error = rfkill_led_trigger_register(rfkill); // 注册LED触发器
        if (error)
                goto devdel;

        rfkill->registered = true;

        INIT_DELAYED_WORK(&rfkill->poll_work, rfkill_poll);   // 设置poll_work工作函数为rfkill_poll
        INIT_WORK(&rfkill->uevent_work, rfkill_uevent_work);  // 设置uevent_work工作函数为rfkill_uevent_work
        INIT_WORK(&rfkill->sync_work, rfkill_sync_work);      // 设置sync_work工作函数为rfkill_sync_work

        if (rfkill->ops->poll)                        // 如果rfkill具有轮询操作,则将轮询工作项加入workqueue中
                queue_delayed_work(system_power_efficient_wq,
                        &rfkill->poll_work,
                        round_jiffies_relative(POLL_INTERVAL));

        if (!rfkill->persistent || rfkill_epo_lock_active) {
                schedule_work(&rfkill->sync_work); // 将工作sync_work添加到工作队列system_wq
        } else {
#ifdef CONFIG_RFKILL_INPUT
                bool soft_blocked = !!(rfkill->state & RFKILL_BLOCK_SW);

                if (!atomic_read(&rfkill_input_disabled))
                        __rfkill_switch_all(rfkill->type, soft_blocked);
#endif
        }

        rfkill_global_led_trigger_event();      // 触发LED触发器时间
        rfkill_send_events(rfkill, RFKILL_OP_ADD);

        mutex_unlock(&rfkill_global_mutex);  // 解锁
        return 0;

 devdel:
        device_del(&rfkill->dev);
 remove:
        list_del_init(&rfkill->node);
 unlock:
        mutex_unlock(&rfkill_global_mutex);
        return error;
}

这里调用了device_add将设备rfkill->dev注册到linux设备驱动模型中,会在/sys/class/rfkill类文件下创建rfkill%s链接文件,但是由于未设置设备号devt(默认就是0),不会在文件系统创建设备节点/dev/rfkill%d。

3.2.2 rfkill_unregister

其对应的卸载函数是rfkill_unregister:

/**
 * rfkill_unregister - Unregister a rfkill structure.
 * @rfkill: rfkill structure to be unregistered
 *
 * This function should be called by the network driver during device
 * teardown to destroy rfkill structure. Until it returns, the driver
 * needs to be able to service method calls.
 */
void rfkill_unregister(struct rfkill *rfkill);

rfkill_unregister函数实现位于net/rfkill/core.c文件中:

void rfkill_unregister(struct rfkill *rfkill)
{
        BUG_ON(!rfkill);

        if (rfkill->ops->poll)
                cancel_delayed_work_sync(&rfkill->poll_work);

        cancel_work_sync(&rfkill->uevent_work);
        cancel_work_sync(&rfkill->sync_work);

        rfkill->registered = false;

        device_del(&rfkill->dev);

        mutex_lock(&rfkill_global_mutex);
        rfkill_send_events(rfkill, RFKILL_OP_DEL);
        list_del_init(&rfkill->node);
        rfkill_global_led_trigger_event();
        mutex_unlock(&rfkill_global_mutex);

        rfkill_led_trigger_unregister(rfkill);
}

3.3 暂停/恢复轮询操作

函数 rfkill_pause_polling(struct rfkill *rfkill) 的作用是暂停rfkill设备开关轮询操作,例如当发射器因其他原因关闭时。需要注意的是,这个函数不适用于挂起/恢复操作,因为在该情况下,核心会停止轮询操作(但也会正确处理在挂起之前暂停轮询的情况)。

/**
 * rfkill_pause_polling(struct rfkill *rfkill)
 *
 * Pause polling -- say transmitter is off for other reasons.
 * NOTE: not necessary for suspend/resume -- in that case the
 * core stops polling anyway (but will also correctly handle
 * the case of polling having been paused before suspend.)
 */
void rfkill_pause_polling(struct rfkill *rfkill);

与之相反的函数是rfkill_resume_polling,用于恢复rfkill设备开关轮询操作。需要注意的是,这个函数不适用于挂起/恢复操作,因为在该情况下,核心会停止轮询操作。

/**
 * rfkill_resume_polling(struct rfkill *rfkill)
 *
 * Resume polling
 * NOTE: not necessary for suspend/resume -- in that case the
 * core stops polling anyway
 */
void rfkill_resume_polling(struct rfkill *rfkill);

3.4 设置设备状态

3.4.1 rfkill_set_hw_state

函数 rfkill_set_hw_state 用于设置内部rfkill硬件阻塞状态;当rfkill驱动程序在硬阻塞状态发生更改时接收到事件时,应使用此函数通知rfkill核心(以及通过核心通知用户空间)当前状态。如果在恢复后状态可能已更改,则还应使用此函数。

如果分配了 poll_state,则不必(但可以)调用此函数。

任何上下文中都可以调用此函数,甚至可以从rfkill回调中调用。该函数返回组合块状态(如果发射器应被阻止则为 true),因此驱动程序无需跟踪软件块状态(可能无法跟踪)。

/**
 * rfkill_set_hw_state - Set the internal rfkill hardware block state
 * @rfkill: pointer to the rfkill class to modify.
 * @blocked: the current hardware block state to set
 *
 * rfkill drivers that get events when the hard-blocked state changes
 * use this function to notify the rfkill core (and through that also
 * userspace) of the current state.  They should also use this after
 * resume if the state could have changed.
 *
 * You need not (but may) call this function if poll_state is assigned.
 *
 * This function can be called in any context, even from within rfkill
 * callbacks.
 *
 * The function returns the combined block state (true if transmitter
 * should be blocked) so that drivers need not keep track of the soft
 * block state -- which they might not be able to.
 */
bool rfkill_set_hw_state(struct rfkill *rfkill, bool blocked);
3.4.2 rfkill_set_sw_state

函数 rfkill_set_sw_state 用于设置内部rfkill软件阻塞状态。当 rfkill驱动程序在软阻塞状态发生更改时接收到事件时,应使用此函数通知rfkill核心(以及通过核心通知用户空间)当前状态。一些平台直接对输入进行操作,但允许再次更改,因此驱动程序也需要使用此函数。

如果状态是由用户更改的,则驱动程序在恢复后还应调用此函数。这只对“持久”设备有意义。

该函数返回组合块状态(如果发射器应被阻止则为 true)

/**
 * rfkill_set_sw_state - Set the internal rfkill software block state
 * @rfkill: pointer to the rfkill class to modify.
 * @blocked: the current software block state to set
 *
 * rfkill drivers that get events when the soft-blocked state changes
 * (yes, some platforms directly act on input but allow changing again)
 * use this function to notify the rfkill core (and through that also
 * userspace) of the current state.
 *
 * Drivers should also call this function after resume if the state has
 * been changed by the user.  This only makes sense for "persistent"
 * devices (see rfkill_init_sw_state()).
 *
 * This function can be called in any context, even from within rfkill
 * callbacks.
 *
 * The function returns the combined block state (true if transmitter
 * should be blocked).
 */
bool rfkill_set_sw_state(struct rfkill *rfkill, bool blocked);
3.4.3 rfkill_set_states

函数 rfkill_set_states 用于设置内部rfkill阻塞状态。该函数可以在任何上下文中调用,甚至可以从rfkill回调中调用。

/**
 * rfkill_set_states - Set the internal rfkill block states
 * @rfkill: pointer to the rfkill class to modify.
 * @sw: the current software block state to set
 * @hw: the current hardware block state to set
 *
 * This function can be called in any context, even from within rfkill
 * callbacks.
 */
void rfkill_set_states(struct rfkill *rfkill, bool sw, bool hw);

该函数的参数包括:

  • rfkill: 指向要修改的 rfkill的指针;
  • sw: 要设置的当前软件阻塞状态;
  • hw: 要设置的当前硬件阻塞状态;

3.5 查询设备状态

函数 rfkill_blocked用于查询rfkill的阻塞状态;

/**
 * rfkill_blocked - Query rfkill block state
 *
 * @rfkill: rfkill struct to query
 */
bool rfkill_blocked(struct rfkill *rfkill);

3.6  查询设备类型

 函数 rfkill_find_type是一个辅助函数,用于通过名称查找rfkill设备的开关类型;

/**
 * rfkill_blocked - Query rfkill block state
 *
 * @rfkill: rfkill struct to query
 */
bool rfkill_blocked(struct rfkill *rfkill);

/**
 * rfkill_find_type - Helper for finding rfkill type by name
 * @name: the name of the type
 *
 * Returns enum rfkill_type that corresponds to the name.
 */
enum rfkill_type rfkill_find_type(const char *name);

3.7 获取/设备灯

函数 rfkill_get_led_trigger_name用于获取与按钮 LED相关联的 LED触发器名称。如果 LED 触发器注册失败,则此函数可能返回 NULL 指针。可以将其用作 LED 的“默认触发器”。

const char *rfkill_get_led_trigger_name(struct rfkill *rfkill);

而函数 rfkill_set_led_trigger_name用于设置LED触发器名称,如果调用,必须在调用 rfkill_register之前进行调用才能生效。

/**
 * rfkill_set_led_trigger_name - Set the LED trigger name
 * @rfkill: rfkill struct
 * @name: LED trigger name
 *
 * This function sets the LED trigger name of the radio LED
 * trigger that rfkill creates. It is optional, but if called
 * must be called before rfkill_register() to be effective.
 */
void rfkill_set_led_trigger_name(struct rfkill *rfkill, const char *name);

3.8 rfkill驱动编写步骤

rfkill驱动编写比较简单,大体上分为以下几个步骤:

  • 调用rfkill_alloc为rfkill设备申请一个struct rfkill数据结构,同时指定rfkill操作集,必须要实现set_block回调(通过控制GPIO实现RF设备的开启/关闭);
  • 调用rfkill_set_states设置内部rfkill阻塞状态;
  • 调用rfkill_register注册rfkill设备;

当rfkill驱动注册完成后,我们可以通过以下命令开启/关闭RF设备:

echo 0 >/sys/class/rfkill/rfkill0/state // 关闭 等价于rfkill block 0
echo 1 >/sys/class/rfkill/rfkill0/state // 开启 等价于rfkill unblock 0

四、rfkill WiFi设备驱动

我们以Rockchip提供的针对WiFi设备的rfkill驱动程序为例进行讲解,其代码位于net/rfkill/rfkill-wlan.c文件(需要注意是该文件在linux 5.2.8下并没有,我是从Rockchip github linux源码拷贝过来的);

/*
 * 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;
    enum of_gpio_flags 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");
View Code

从代码可以看到,rfkill WiFi驱动采用的platform设备驱动模型。既然是platform设备驱动模型,那就很好分析了。

需要注意的是:这段代码你分析下来会发现和rfkill驱动没有任何关系,所以可以先看rfkill 蓝牙驱动内容,在回过来看这个。

4.1 电路原理图

在分析源码前我们要先了解一下NanoPC-T4开发板 AP6356 WiFi部分的电路接线情况;

需要注意的是:

  • 在下图的右下角标注了VBAT电压范围为3.0~4.8V,输入电压源来自VCC3V3_SYS,这个信号是由电源输入的12V电源经过稳压管NB680GD输出得到的;
  • VDDIO输入电压为1.8V,输入电压来自rk808电源管理芯片61号引脚输出的;
  • LPO(外部低功耗时钟输入)输入引脚连接的是RTC_CLKO_WIFI,而该引脚来自RK808电源管理芯片67号输出引脚CLK32KOUT2;

这里我们需要关注一下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 外部低功耗时钟输入

4.2 入口和出口函数

咦,在net/rfkill/rfkill-wlan.c文件竟然没有找到module_init、module_exit函数,仅仅在文件最后看到了如下内容:

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);
}

这里比较奇怪,没有定义以下内容,rfkill驱动是如何注册到内核?。

module_init(rfkill_wlan_init);
module_exit(rfkill_wlan_exit);

这是因为rfkill_wlan_init、rfkill_wlan_exit在rfkill蓝牙驱动的入口函数和出口函数中调用的。

4.2.1  platform驱动注册

我们看一下rfkill_wlan_init函数,这里通过platform_driver_register函数注册了一个platform驱动。

#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),
        },
};

在plaftrom总线设备驱动模型中,我们知道当内核中有platform设备platform驱动匹配,会调用到platform_driver里的成员.probe。

4.2.2 wireless-wlan设备节点

因此我们需要在设备树中新增wireless-wlan设备节点(来自官方配置),只有wireless-wlan设备节点中的comatible与rfkill_wlan_driver中of_match_table数组中的某一项comatible相同时才会匹配,然后会执行rfkill_wlan_probe函数。

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引脚;

这里按理说应该配置WiFi功能开启和关闭所使用的的WL_REG_ON引脚(对应RK3399的GPIO0_B2),但是并没有,这是因为这个工作交由sdio_pwrseq驱动来做了;

当然还支持配置其它属性,具体可以看一下wlan_platdata_parse_dt函数代码。

4.3 rfkill_wlan_probe

rfkill_wlan_probe函数代码比较多,但是呢?其实大体流程和其他驱动相比,没多大区别;

static int rfkill_wlan_probe(struct platform_device *pdev)
{
        struct rfkill_wlan_data *rfkill;
        struct rksdmmc_gpio_wifi_moudle *pdata = pdev->dev.platform_data;  // 获取平台私有数据,这里获取到的为NULL
        int ret = -1;

        LOG("Enter %s\n", __func__);

        class_register(&rkwifi_power);   // 向内核注册了一个class,class名称为rkwifi

        if (!pdata) {                    // 走这里,尝试从设备树中解析和获取pdata结构体
#ifdef CONFIG_OF
                pdata = kzalloc(sizeof(*pdata), GFP_KERNEL);     // 动态申请rksdmmc_gpio_wifi_moudle类型数据结构
                if (!pdata)
                        return -ENOMEM;

                ret = wlan_platdata_parse_dt(&pdev->dev, pdata);   // 解析设备树,初始化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);    // 动态分配rfkill_wlan_data数据结构
        if (!rfkill)
                goto rfkill_alloc_fail;

        rfkill->pdata = pdata;               // 设置rfkill驱动私有数据
        g_rfkill = rfkill;

        LOG("%s: init gpio\n", __func__);

        if (!pdata->mregulator.power_ctrl_by_pmu) {    // 会进入,由于我们并没有配置vbat_n、reset_n引脚,因此啥也不会做
                ret = rfkill_rk_setup_gpio(&pdata->vbat_n, wlan_name, // 如果配置了WiFi模块电源控制引脚,将会向gpiolib申请GPIO,GPIO编号为pdata->vbat_n.io
                                           "wlan_vbat");
                if (ret)
                        goto fail_alloc;

                ret = rfkill_rk_setup_gpio(&pdata->reset_n, wlan_name, // 如果配置了WiFi模块复位控制引脚,将会向gpiolib申请GPIO,GPIO编号为pdata->reset_n.io
                                           "wlan_reset");
                if (ret)
                        goto fail_alloc;
        }

        wake_lock_init(&rfkill->wlan_irq_wl, WAKE_LOCK_SUSPEND,  // 初始化内核唤醒锁,wake_lock是一种特殊的锁,被用于防止系统进入低功耗状态,以确保某些关键任务能够在系统休眠时继续运行。
                       "rfkill_wlan_wake");

        rfkill_set_wifi_bt_power(1);        // 通过控制vbat_n.io引脚输出,实现WiFi芯片上电

#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;
}

主要步骤如下:

  •  注册class rkwifi_power,名称为rkwifi;
  • 动态申请rksdmmc_gpio_wifi_moudle数据结构赋值给pdata,并调用wlan_platdata_parse_dt解析设备节点属性,用来初始化pdata成员;
  • 如果配置有WiFi电源控制引脚、WiFi复位控制引脚,则设置引脚输出有效电平;

这段代码我们也看完了,有没有发现这段代码实际上与rfkill设备没有半毛钱关系,连struct rfkill数据结构都没有分配。

这段代码做的唯一有意义的事情就是,如果WiFi芯片的电源引脚、复位引脚使用的是SoC的GPIO端口的话,这里可以起到一个电源上电和复位的作用。

4.3.1 struct rfkill_wlan_data 

struct rfkill_wlan_data是Rockchip自定义的数据结构;

struct rfkill_wlan_data {
        struct rksdmmc_gpio_wifi_moudle *pdata;
        struct wake_lock wlan_irq_wl;
};

该结构体有两个成员:

  • pdata:指向平台数据(struct rksdmmc_gpio_wifi_moudle)的指针,下面单独介绍;
  • wlan_irq_wl:表示 WiFi中断锁。当 WiFi模块有中断事件需要处理时,此锁会防止系统进入睡眠状态;
4.3.2 struct rksdmmc_gpio_wifi_moudle 

struct rksdmmc_gpio_wifi_moudle是Rockchip自定义的数据结构,用于保存平台数据,描述了Rockchip平台上rfkill WiFi 设备的GPIO信息。其定义在include/linux/rfkill-wlan.h:

struct rksdmmc_gpio_wifi_moudle {
    int sdio_vol;    //sdio reference voltage
    bool vref_ctrl_enble;
    bool wifi_power_remain;
    struct rksdmmc_pmu    mregulator;
    struct rksdmmc_pmu    ioregulator;
    struct rksdmmc_gpio   vbat_n;
    struct rksdmmc_gpio   power_n;  //PMU_EN
    struct rksdmmc_gpio   reset_n;  //SYSRET_B, DAIRST
    struct rksdmmc_gpio   vddio;
    struct rksdmmc_gpio   bgf_int_b;
    struct rksdmmc_gpio   wifi_int_b;
    struct rksdmmc_gpio   gps_sync;
    struct rksdmmc_gpio   ANTSEL2;  //pin5--ANTSEL2
    struct rksdmmc_gpio   ANTSEL3;  //pin6--ANTSEL3
    struct rksdmmc_gpio   GPS_LAN;  //pin33--GPS_LAN
    struct regmap *grf;
    struct clk *ext_clk;
};

其中:

  • sdio_vol: SDIO 参考电压;
  • vref_ctrl_enble: 是否启用参考电压控制;
  • wifi_power_remain: 标志位,表示是否需要保持WiFi电源开启;
  • mregulator: 用于控制WiFi模块电源的PMIC寄存器;
  • ioregulator: 用于控制WiFi模块IO电压的PMIC寄存器;
  • vbat_n: WiFi模块电源控制引脚;
  • power_n: WiFi模块电源使能控制引脚;
  • reset_n: WiFi模块复位控制引脚;
  • vddio: WiFi模块IO电压控制引脚;
  • bgf_int_b: WiFi模块中断信号控制引脚;
  • wifi_int_b: WiFi模块中断信号控制引脚;
  • gps_sync: GPS时钟同步信号引脚;
  • ANTSEL2: 天线选择引脚2;
  • ANTSEL3: 天线选择引脚3;
  • GPS_LAN: GPS LAN 信号引脚;
  • grf: 存放了WiFi模块相关控制寄存器的regmap结构体指针;
  • ext_clk: WiFi模块使用的外部时钟源;
4.3.3 class rkwifi_power
/** Device model classes */
static struct class rkwifi_power = {
        .name        = "rkwifi",
        .class_groups = rkwifi_power_groups,
};
4.3.4 wlan_platdata_parse_dt

wlan_platdata_parse_dt函数用于解析wireless-wlan设备节点的属性,并初始化pdata;

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;
        enum of_gpio_flags 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"); // 查找rockchip,grf属性指向的系统控制器节点,然后返回该节点对应的regmap结构体指针  设备节点中配置为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);    // 获取wifi_chip_type属性,指定WiFi芯片的类型  设备节点中配置为AP6356
        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)) {    // 获取keep_wifi_power_on属性  未配置
                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)) {    // 获取power_ctrl_by_pmu属性 未配置
                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,  // 获取WiFi电源使能控制引脚 未配置
                                               &flags);
                if (gpio_is_valid(gpio)) {
                        data->power_n.io = gpio;
                        data->power_n.enable =                            // 判断引脚标志位,高电平有效?如果是设置为1,否则设置为0
                                (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, // 获取WiFi芯片VBAT电源引脚,而我们电源是来自稳压管输出  未配置
                                               &flags);
                if (gpio_is_valid(gpio)) {
                        data->vbat_n.io = gpio;
                        data->vbat_n.enable =                             // 判断引脚标志位,高电平有效?如果是设置为1,否则设置为0
                                (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,  // 获取WiFi芯片rest引脚  未配置
                                               &flags);
                if (gpio_is_valid(gpio)) {
                        data->reset_n.io = gpio;
                        data->reset_n.enable =                          // 判断引脚标志位,高电平有效?如果是设置为1,否则设置为0
                                (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,  // 配置主机唤醒中断引脚 这里配置为GPIO0_A3引脚
                                               &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");          // 获取名称为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"))  // rk3308特殊处理
                        regmap_write(data->grf, 0x0314, 0x00020002);
        }

        return 0;
}
View Code
4.3.5 rfkill_rk_setup_gpio

rfkill_rk_setup_gpio函数将会向gpiolib进行申请GPIO,参数一为GPIO的唯一编号gpio->io,参数二位GPIO的名称,否则该函数直接返回0;

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;
}
4.3.6 rfkill_set_wifi_bt_power

rfkill_set_wifi_bt_power函数通过控制WiFi电源使能引脚的输出,从而为WiFi芯片上电;

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))  // 未设置vbat引脚,啥也不会做;不然会设置相应引脚输出enable电平,从而电源使能
                        gpio_direction_output(vbat->io, vbat->enable);
        } else {
                if (gpio_is_valid(vbat->io))  // 未设置vbat引脚,啥也不会做
                        gpio_direction_output(vbat->io, !(vbat->enable));
        }
        wifi_bt_vbat_state = on;
        return 0;
}
4.3.7 rfkill_wlan_fb_notifier
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,
};

五、rfkill BT设备驱动

我们以Rockchip提供的针对BT设备的rfkill驱动程序为例进行讲解,其代码位于net/rfkill/rfkill-bt.c文件(需要注意是该文件在linux 5.2.8下并没有,我是从Rockchip github linux源码拷贝过来的);

/*
 * 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");
View Code

从代码可以看到,rfkill BT驱动采用的platform设备驱动模型。既然是platform设备驱动模型,那就很好分析了。

5.1 入口和出口函数

定位到模块的入口和出口函数,可以看到这里分别调用了rfkill WiFi驱动模块的入口rfkill_wlan_init和出口函数rfkill_wlan_exit;

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);
5.1.1  platform驱动注册

我们看一下rfkill_rk_init函数,这里通过platform_driver_register函数注册了一个platform驱动。

#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),
        },
};

在plaftrom总线设备驱动模型中,我们知道当内核中有platform设备platform驱动匹配,会调用到platform_driver里的成员.probe。

5.1.2 wireless-bluetooth设备节点

因此我们需要在设备树中新增wireless-bluetooth设备节点,只有wireless-bluetooth设备节点中的comatible与rfkill_rk_driver 中of_match_table数组中的某一项comatible相同时才会匹配,然后会执行rfkill_rk_probe函数。

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_gpiosuart0_gpios定义GPIO2_C3功能复用为GPIO;
  • BT,reset_gpio:配置蓝牙复位引脚BT_REG_ON为GPIO0_B1,高电平有效;BT_REG_ON用于控制蓝牙设备的开启和关闭,当为高电平时,开启蓝牙,为低电平时关闭蓝牙;
  • BT,wake_gpio:配置主机唤醒蓝牙设备引脚BT_WAKE为GPIO2_D2,高电平有效;
  • BT,wake_host_irq:配置蓝牙设备唤醒主机引脚BT_HOST_WAKE为GPIO0_A4,高电平有效;
  • status:表示该设备状态为正常运行;

需要注意的是:

  • 拉高BT_WAKE: BT不能睡眠,或者必须醒来;
  • 拉低BT_WAKE:BT可以睡眠,睡不睡BT自己决定;
  • HOST每次发数据前都要拉高BT_WAKE;

这里顺带看一下uart0_rts、uart0_gpios配置:

uart0_rts: uart0-rts {
        rockchip,pins =
                <2 RK_PC3 1 &pcfg_pull_none>;
};


// 这个也是要新增的,默认没有这个配置
wireless-bluetooth {
    uart0_gpios: uart0-gpios {
        rockchip,pins = <2 RK_PC3 RK_FUNC_GPIO &pcfg_pull_none>;
    };
};

5.2 rfkill_rk_probe

rfkill_rk_probe函数代码如下:

static int rfkill_rk_probe(struct platform_device *pdev)
{
        struct rfkill_rk_data *rfkill;
        struct rfkill_rk_platform_data *pdata = pdev->dev.platform_data;  //  获取平台私有数据,这里获取到的为NULL
        int ret = 0;
        struct proc_dir_entry *ent;

        DBG("Enter %s\n", __func__);

        if (!pdata) {           // 走这里,尝试从设备树中解析和获取pdata结构体
#ifdef CONFIG_OF
                pdata = devm_kzalloc(&pdev->dev,  // 动态申请rfkill_rk_platform_data类型数据结构
                                     sizeof(struct rfkill_rk_platform_data),
                                     GFP_KERNEL);
                if (!pdata)
                        return -ENOMEM;

                ret = bluetooth_platdata_parse_dt(&pdev->dev, pdata);    // 解析设备树,初始化pdata 
                if (ret < 0) {
#endif
                        LOG("%s: No platform data specified\n", __func__);
                        return ret;
#ifdef CONFIG_OF
                }
#endif
        }

        pdata->name = (char *)bt_name;   // 全局变量,对于AP6356未找到匹配项,默认为"bt_default"
        pdata->type = RFKILL_TYPE_BLUETOOTH;  // 设置类型为蓝牙

        rfkill = devm_kzalloc(&pdev->dev, sizeof(*rfkill), GFP_KERNEL);  // 动态分配rfkill_rk_data数据结构
        if (!rfkill)
                return -ENOMEM;

        rfkill->pdata = pdata;    // 初始化成员
        rfkill->pdev = pdev;
        g_rfkill = rfkill;

        bluetooth_dir = proc_mkdir("bluetooth", NULL);       // 在/proc文件系统创建名称为bluetooth的目录
        if (!bluetooth_dir) {
                LOG("Unable to create /proc/bluetooth directory");
                return -ENOMEM;
        }

        sleep_dir = proc_mkdir("sleep", bluetooth_dir);      // 在/proc文件系统创建名称为sleep的目录,父目录为bluetooth
        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);  // 在/proc文件系统sleep_dir目录下创建文件lpm,文件操作集设置为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);  // 在/proc文件系统sleep_dir目录下创建文件btwrite,文件操作集设置为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,  // 如果配置了蓝牙模块电源控制引脚,将会向gpiolib申请GPIO,GPIO编号为pdata->poweron_gpio.io
                                   "poweron");
        if (ret)
                goto fail_gpio;

        ret = rfkill_rk_setup_gpio(pdev, &pdata->reset_gpio, pdata->name,  // 如果配置了蓝牙模块复位控制引脚,将会向gpiolib申请GPIO,GPIO编号为pdata->reset_gpio.io
                                   "reset");
        if (ret)
                goto fail_gpio;

        ret = rfkill_rk_setup_gpio(pdev, &pdata->wake_gpio, pdata->name,  // 如果配置了主机唤醒蓝牙设备引脚,将会向gpiolib申请GPIO,GPIO编号为pdata->wake_gpio.io
                                   "wake");
        if (ret)
                goto fail_gpio;

        ret = rfkill_rk_setup_gpio(pdev, &pdata->rts_gpio, rfkill->pdata->name,   // 同上 rts引脚
                                   "rts");
        if (ret)
                goto fail_gpio;

        wake_lock_init(&rfkill->bt_irq_wl, WAKE_LOCK_SUSPEND, // 初始化内核唤醒锁,wake_lock是一种特殊的锁,被用于防止系统进入低功耗状态,以确保某些关键任务能够在系统休眠时继续运行。
                       "rfkill_rk_irq_wl");

        ret = rfkill_rk_setup_wake_irq(rfkill, 0); // 首先为中断引脚申请GPIO端口,然后申请中断,中断处理函数设置为rfkill_rk_wake_host_irq
        if (ret)
                goto fail_setup_wake_irq;

        DBG("setup rfkill\n");
        rfkill->rfkill_dev = rfkill_alloc(pdata->name, &pdev->dev, pdata->type, // 申请rfkill设备,其操作集设置为rfkill_rk_ops
                                          &rfkill_rk_ops, rfkill);
        if (!rfkill->rfkill_dev)
                goto fail_alloc;

        rfkill_set_states(rfkill->rfkill_dev, BT_BLOCKED, false);  // 软件堵塞状态设置为true,硬件堵塞状态设置为false
        ret = rfkill_register(rfkill->rfkill_dev);  // 注册rfkill设备
        if (ret < 0)
                goto fail_rfkill;

        INIT_DELAYED_WORK(&rfkill->bt_sleep_delay_work,       // 设置bt_sleep_delay_work工作函数为rfkill_rk_delay_sleep_bt
                          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;
}

主要步骤如下:

  • 动态申请rfkill_rk_platform_data数据结构赋值给pdata,并调用bluetooth_platdata_parse_dt解析设备节点属性,用来初始化pdata成员;
  • 在/proc文件系统创建目录结构/proc/bluetooth/sleep,并在sleep目录下创建lpm、btwrite文件,并设置对应的文件操作集;
  • 如果配置有蓝牙电源控制引脚、蓝牙复位控制、rts等引脚,则为其申请GPIO;
  • 初始化内核唤醒锁,wake_lock是一种特殊的锁,被用于防止系统进入低功耗状态,以确保某些关键任务能够在系统休眠时继续运行;
  • 为中断引脚申请GPIO端口,然后申请中断,中断处理函数设置为rfkill_rk_wake_host_irq;
  • 申请rfkill设备,其操作集设置为rfkill_rk_ops;
  • 注册rfkill设备;
  • 设置bt_sleep_delay_work工作函数为rfkill_rk_delay_sleep_bt;
  • 如果配置有蓝牙电源控制引脚、蓝牙复位控制引脚,则设置引脚输出为有效电平;我们开发板并没有使用的CPU的引脚来控制AP6356的电源引脚VDDIO,但是我们使用了GPIO0_B1来控制AP6356的BT_REG_ON引脚,当为高电平时,开启蓝牙,为低电平时关闭蓝牙。
5.2.1 struct rfkill_rk_data 

struct rfkill_rk_data 是Rockchip自定义的数据结构,用于描述一个rfkill BT设备的相关数据信息;

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;
};

其中:

  • pdata:指向平台数据(struct rfkill_rk_platform_data)的指针;下面单独介绍;
  • pdev:指向平台设备(struct platform_device)的指针;
  • rfkill_dev:指向rfkill设备(struct rfkill)的指针;
  • bt_irq_wl:表示用于保持唤醒锁定的wake_lock结构体;
  • bt_sleep_delay_work:表示一项延迟工作,用于在一段时间后执行蓝牙休眠操作;
  • irq_req:中断申请标志位,为1表示已经申请了中断;
5.2.2 struct rfkill_rk_platform_data

struct rfkill_rk_platform_data是Rockchip自定义的数据结构,描述了Rockchip平台上rfkill BT设备的GPIO信息。其定义在include/linux/rfkill-bt.h:

/**
 * struct rfkill_rk_platform_data - platform data for rfkill gpio device.
 * for unused gpio's, the expected value is -1.
 * @name:               name for the gpio rfkill instance
 * @reset_gpio:         GPIO which is used for reseting rfkill switch
 * @shutdown_gpio:      GPIO which is used for shutdown of rfkill switch
 */

struct rfkill_rk_platform_data {
    char                    *name;
    enum rfkill_type        type;
    bool                    power_toggle;
    struct pinctrl          *pinctrl;
    struct rfkill_rk_gpio   poweron_gpio;
    struct rfkill_rk_gpio   reset_gpio;
    struct rfkill_rk_gpio   wake_gpio;      // Host wake or sleep BT
    struct rfkill_rk_irq    wake_host_irq;  // BT wakeup host
    struct rfkill_rk_gpio   rts_gpio;
    struct clk              *ext_clk;
};

其中:

  • name:表示rfkill gpio实例的名称;
  • type:表示rfkill设备开关类型,包括Bluetooth、WiFi等;
  • power_toggle:表示是否需要在初始化时使能蓝牙设备;
  • pinctrl:指向pinctrl句柄的指针,用于保存蓝牙设备的所有状态信息;
  • poweron_gpio:蓝牙设备电源使能控制引脚VDDIO;
  • reset_gpio:蓝牙设备复位控制引脚BT_REG_ON;
  • wake_gpio:主机唤醒蓝牙设备的引脚BT_WAKE;
  • wake_host_irq:表示蓝牙设备唤醒主机中断相关的配置信息,对应引脚为BT_HOST_WAKE;
  • rts_gpio:蓝牙设备串口UART_CTS_N引脚;
  • ext_clk:外部时钟源,时钟源输入引脚为LPO;

其中struct rfkill_rk_gpio、struct rfkill_rk_irq定义如下:

struct rfkill_rk_gpio {
    int     io;                                    // GPIO编号 全剧唯一 
    char    name[RFKILL_RK_GPIO_NAME_SIZE];        // GPIO名称   
    int     enable; // disable = !enable           // 有效电平  
    struct  pinctrl_state    *gpio_state;          // "rts_gpio"状态
    struct  pinctrl_state    *default_state;       // "default"状态
};

struct rfkill_rk_irq {
    char                    name[RFKILL_RK_GPIO_NAME_SIZE];  // 中断名称
    struct rfkill_rk_gpio   gpio;   // 对应的GPIO 
    int                     irq;    // IRQ编号
};
5.2.3 bluetooth_platdata_parse_dt

wlan_platdata_parse_dt函数用于解析wireless-bluetooth设备节点的属性,并初始化pdata;

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); // 获取要配置为蓝牙所使用的的uart的rts引脚 这里配置为GPIO2_C3引脚
        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);                       // 获取与设备相关联的pinctrl句柄
                if (!IS_ERR(data->pinctrl)) {
                        data->rts_gpio.default_state =
                                pinctrl_lookup_state(data->pinctrl, "default");  // 获取名字为"default"的状态 这里获取到的引脚配置节点为uart0_rts
                        data->rts_gpio.gpio_state =
                                pinctrl_lookup_state(data->pinctrl, "rts_gpio"); // 获取名字为"rts_gpio"的状态 这里获取到的引脚配置节点为uart0_gpios
                } 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);   // 配置为GPIO0_B1, 连接到AP6356 BT_REG_ON引脚,控制蓝牙功能的开启和关闭
        if (gpio_is_valid(gpio)) {
                data->reset_gpio.io = gpio;                                        // GPIO编号  
                data->reset_gpio.enable = (flags == GPIO_ACTIVE_HIGH) ? 1 : 0;     // 高电平有效 1 
                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);  // 配置为GPIO2_D2,连接到AP6356 BT_WAKE引脚,用于主机唤醒BT设备
        if (gpio_is_valid(gpio)) {
                data->wake_gpio.io = gpio;
                data->wake_gpio.enable = (flags == GPIO_ACTIVE_HIGH) ? 1 : 0;  // 高电平有效 1
                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);   // 配置为GPIO0_A4,连接到AP6356 BT_HOST_WAKE_L引脚,用于蓝牙设备唤醒主机
        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");   // 获取ext_clock时钟源,对应时钟控制器rk808 ID为1的时钟  RK808 67号输出引脚CLK32KOUT2连接着AP6356的LPO引脚
        if (IS_ERR(data->ext_clk)) {
                LOG("%s: clk_get failed!!!.\n", __func__);
        } else {
                clk_prepare_enable(data->ext_clk);       // 时钟使能
        }
        return 0;
}
View Code
5.2.4  rfkill_rk_setup_wake_irq

rfkill_rk_setup_wake_irq函数首先为中断引脚wake_host_irq申请GPIO端口,然后申请中断,中断处理函数设置为rfkill_rk_wake_host_irq;

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,  // 首先申请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);         // 映射GPIO编号到IRQ编号
                sprintf(irq->name, "%s_irq", irq->gpio.name); // 设置中断名称
                ret = request_irq(irq->irq, rfkill_rk_wake_host_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;
}
5.2.5 rfkill_rk_wake_host_irq

rfkill_rk_wake_host_irq函数防止系统在指定时间内进入休眠状态,以确保蓝牙设备的正常工作和数据传输。

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));   // 唤醒时间,单位为ms 这里配置为10*1000

        return IRQ_HANDLED;
}

这里通过调用wake_lock_timeout函数实现该功能,通过唤醒锁(wake_lock)机制,保持系统在指定时间内处于活动状态,以避免由于休眠而导致的蓝牙设备数据丢失或其他错误;

  • 第一个参数为一个指向唤醒锁实例的指针(&rfkill->bt_irq_wl);
  • 第二个参数为保持唤醒的时间(BT_IRQ_WAKELOCK_TIMEOUT,单位为毫秒)。

在函数执行过程中,首先将保持唤醒的时间转换为内核中使用的jiffies时间戳,并将该时间戳传递给wake_lock_timeout函数。 wake_lock_timeout函数会等待指定时间,直到唤醒锁被释放或者超时。如果等待期间锁被释放,则函数立即返回,否则在超时后返回并释放锁。

5.2.6 rfkill_rk_ops

rfkill操作集只设置了set_block 函数,用于控制蓝牙设备的开启和关闭;

static const struct rfkill_ops rfkill_rk_ops = {
        .set_block = rfkill_rk_set_power,
};

我们来看一下rfkill_rk_set_power函数,函数也很简单,无非就是控制power、reset等引脚输出有效电平/无效电平,从而达到开启/关闭蓝牙的效果。

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)) {        // power引脚  未设置,不会进入
                        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)) {           // reset引脚  进入
                        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)) {    // uart rts引脚  进入
                        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)) {  // reset引脚  进入
                        if (gpio_get_value(reset->io) == reset->enable) {
                                gpio_direction_output(reset->io,
                                                      !reset->enable);
                                msleep(20);
                        }
                }
                if (toggle) {   // false 不会进入
                        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;
}
View Code
5.2.7 rfkill_rk_delay_sleep_bt

rfkill_rk_delay_sleep_bt函数通过拉低BT_WAKE引脚控制蓝牙设备进入睡眠状态(BT可以睡眠,睡不睡BT自己决定);

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);
        if (!sleep) {  // false时进入,控制蓝牙设备进入睡眠状态
                DBG("HOST_UART0_TX pull down 10us\n");
                if (rfkill_rk_setup_gpio(rfkill->pdev, wake,  // 为wake_gpio引脚申请GPIO
                                         rfkill->pdata->name, "wake") != 0) {
                        return;
                }

                gpio_direction_output(wake->io, wake->enable);   // 拉高AP6356 BT_WAKE引脚 使得BT不能睡眠,或者必须唤醒  
                usleep_range(10, 20);
                gpio_direction_output(wake->io, !wake->enable);  // 拉低AP6356 BT_WAKE引脚 使得BT可以睡眠,但是睡不睡眠由BT决定

                gpio_free(wake->io);   // 释放GPIO
        }
}

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);   // BT_SLLEP为true
}

但是rfkill_rk_sleep_bt_internal函数这里参数sleep传入了true,导致整个函数实际上什么也不会做。

5.3 bluesleep_lpm

/proc/bluetooth/sleep/lpm文件对应的文件操作集bluesleep_lpm定义为:

static const struct file_operations bluesleep_lpm = {
        .owner = THIS_MODULE,
        .read = bluesleep_read_proc_lpm,
        .write = bluesleep_write_proc_lpm,
};

实际上这个是用来实现蓝牙的低功耗模式,但是这里并没有支持。

5.3.1 bluesleep_read_proc_lpm

其中read函数设置为bluesleep_read_proc_lpm,这里仅仅是返回了“unsupported to read\n”;

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");
}
5.3.2 bluesleep_write_proc_lpm

而写函数bluesleep_write_proc_lpm什么也没有做;

static ssize_t bluesleep_write_proc_lpm(struct file *file,
                                        const char __user *buffer, size_t count,
                                        loff_t *data)
{
        return count;
}

5.4 bluesleep_btwrite

文件操作集bluesleep_btwrite定义为:

static const struct file_operations bluesleep_btwrite = {
        .owner = THIS_MODULE,
        .read = bluesleep_read_proc_btwrite,
        .write = bluesleep_write_proc_btwrite,
};
5.4.1 bluesleep_read_proc_btwrite

其中read函数设置为bluesleep_read_proc_btwrite,这里仅仅是返回了“unsupported to read\n”;

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");
}
5.4.2 bluesleep_write_proc_btwrite

而写函数bluesleep_write_proc_btwrite根据用户传入的写入是0还是非0来控制蓝牙设备处于唤醒/休眠状态;

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))  // 将用户空间输入第一个字符拷贝到b
                return -EFAULT;

        DBG("btwrite %c\n", b);
        /* HCI_DEV_WRITE */
        if (b != '0')        // 如果不是0
                rfkill_rk_sleep_bt(BT_WAKEUP);  //  传入false, 蓝牙设备进入休眠(BT可以睡眠,睡不睡BT自己决定) 
        else
                rfkill_rk_sleep_bt(BT_SLEEP);    // 传入true, 啥也不会做

        return count;
}

rfkill_rk_sleep_bt函数这里调用rfkill_rk_sleep_bt_internal函数拉低BT_WAKE引脚,控制蓝牙设备进入睡眠状态(BT可以睡眠,睡不睡BT自己决定);

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); // 如果sleep传入false时会生效,控制蓝牙设备进入休眠状态;传入true什么也不会做

#ifdef CONFIG_BT_AUTOSLEEP  // 如果配置自动睡眠,会将工作bt_sleep_delay_work添加到内核工作队列中,并在延时时间达到时执行bt_sleep_delay_work工作函数rfkill_rk_delay_sleep_bt(实际上这个函数啥也没做)
        if (sleep == BT_WAKEUP) {
                schedule_delayed_work(&rfkill->bt_sleep_delay_work,
                                      msecs_to_jiffies(BT_WAKEUP_TIMEOUT));  // 延时时间10s
        }
#endif
}

所以通过分析我们可以猜测当执行命令echo 1 > /proc/bluetooth/sleep/btwrite,会将AP6536的BT_WAKE引脚拉低。

六、测试

我们在上一篇文章已经移植了AP6356驱动,其中包括rfkill WiFi驱动、rfkill 蓝牙驱动;我们给开发板上电,进入ubuntu系统直接测试即可。

6.1 目录结构

我们在/dev目录下可以看到一个rfkill字符设备文件;

root@rk3399:/lib/modules# ll /dev/rfkill
crw-rw-r-- 1 root netdev 10, 63 Jun  4 19:07 /dev/rfkill

在/sys/class/rfkill目录下存放class名称为rfkill的设备,在这里可以看到rfkill0、以及rfkill1文件夹,分别指向了蓝牙设备和WiFi设备;

root@rk3399:/lib/modules# ll /sys/class/rfkill/
total 0
drwxr-xr-x  2 root root 0 Mar 15 23:04 ./
drwxr-xr-x 58 root root 0 Mar 15 23:04 ../
lrwxrwxrwx  1 root root 0 Mar 15 23:04 rfkill0 -> ../../devices/platform/wireless-bluetooth/rfkill/rfkill0/
lrwxrwxrwx  1 root root 0 Jun  4 20:28 rfkill1 -> ../../devices/platform/fe310000.dwmmc/mmc_host/mmc0/mmc0:0001/mmc0:0001:1/ieee80211/phy0/rfkill1/

从rfkill0这个指向的路径,很明显可以看出是源码net/rfkill/rfkill-bt.c实现的的平台设备驱动。

而rfkill1这个指向的路径,和我们这一节介绍的net/rfkill/rfkill-wlan.c文件没有任何关系。这个驱动等我们分析SDIO驱动时再介绍。

我们查看平台设备wireless-bluetooth的信息;

root@rk3399:/# ll /sys/bus/platform/devices/wireless-bluetooth
lrwxrwxrwx 1 root root 0 Mar 15 23:04 /sys/bus/platform/devices/wireless-bluetooth -> ../../../devices/platform/wireless-bluetooth/

可以看到它指向了/sys/devices/platform/wireless-bluetooth/;

root@rk3399:/# ll /sys/devices/platform/wireless-bluetooth/
total 0
drwxr-xr-x  4 root root    0 Mar 15 23:04 ./
drwxr-xr-x 79 root root    0 Mar 15 23:04 ../
lrwxrwxrwx  1 root root    0 Jun  4 19:07 driver -> ../../../bus/platform/drivers/rfkill_bt/
-rw-r--r--  1 root root 4096 Jun  4 20:50 driver_override
-r--r--r--  1 root root 4096 Jun  4 20:50 modalias
lrwxrwxrwx  1 root root    0 Jun  4 20:50 of_node -> ../../../firmware/devicetree/base/wireless-bluetooth/
drwxr-xr-x  2 root root    0 Jun  4 20:50 power/
drwxr-xr-x  3 root root    0 Mar 15 23:04 rfkill/
lrwxrwxrwx  1 root root    0 Mar 15 23:04 subsystem -> ../../../bus/platform/
-rw-r--r--  1 root root 4096 Mar 15 23:04 uevent

这里的文件及目录的管理方式起本质是数据结构的管理思想,目录可以视为结构体,文件是数据结构成员,而链接文件是数据指针。因此可以看到:

  • 设备节点指向了/sys/firmware/devicetree/base/wireless-bluetooth/文件夹,该文件夹保存的就是设备树中/wireless-bluetooth设备节点的属性;
  • subsytem指向了/sys/bus/platform,表示它归属的类型,wireless-bluetooth设备这里是挂在platform总线下的(这个很好理解,因为我们采用的platform总线驱动模型);
  • drivers指向了平台设备wireless-bluetooth的驱动,由于注册的驱动名称为rfkill_bt,因此指向了/sys/bus/platform/drivers/rfkill_bt;
  • 由于在wireless-bluetooth设备驱动中调用了rfkill_register函数,该函数会注册名为rfkill%d的device,其父设置为当前平台设备,因此会在平台设备wireless-bluetooth目录下创建rfkill0目录;不过这里实际上又套了个rfkill文件夹,有点没太明白为啥;

6.2 开启/关闭蓝牙设备

当rfkill 蓝牙驱动注册完成后,我们可以通过以下命令关闭蓝牙设备:

root@rk3399:/# echo 0 >/sys/class/rfkill/rfkill0/state
root@rk3399:/# rfkill list 0
0: bt_default: Bluetooth
        Soft blocked: yes
        Hard blocked: no

接着我们开启蓝牙设备:

root@rk3399:/# echo 1 >/sys/class/rfkill/rfkill0/state
root@rk3399:/# rfkill list 0
0: bt_default: Bluetooth
        Soft blocked: no
        Hard blocked: no

6.3 proc文件系统测试

我们在注册rfkill蓝牙驱动的时候,在proc文件系统创建了目录结构/proc/bluetooth/sleep/,如下所示;

root@rk3399:/# ll /proc/bluetooth/sleep/
total 0
dr-xr-xr-x 2 root root 0 Jun  4 20:54 ./
dr-xr-xr-x 3 root root 0 Jun  4 20:54 ../
-r--r--r-- 1 root root 0 Jun  4 20:54 btwrite
-r--r--r-- 1 root root 0 Jun  4 20:54 lpm

当我们执行命令echo 1 > /proc/bluetooth/sleep/btwrite,会将AP6536的BT_WAKE引脚拉低,我们可以通过万用表测量GPIO2_D2引脚电压;

root@rk3399:/# echo 1 > /proc/bluetooth/sleep/btwrite

当我们执行读取命令时,将会提示unsupported to read;

root@rk3399:/# cat  /proc/bluetooth/sleep/lpm
unsupported to read
root@rk3399:/# cat  /proc/bluetooth/sleep/btwrite
unsupported to read

参考文章

[1]  Android 蓝牙笔记-底层RFKILL驱动

[2] WiFi驱动(1)框架解析

[3] Rockchip 4.19 rfkill-bt.c

[4] Rockchip 4.19 rfkill-wlan.c

posted @ 2023-06-04 00:34  大奥特曼打小怪兽  阅读(1680)  评论(1编辑  收藏  举报
如果有任何技术小问题,欢迎大家交流沟通,共同进步