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核心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");
从代码可以看到,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; }
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");
从代码可以看到,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_gpios;uart0_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; }
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; }
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
参考文章
[2] WiFi驱动(1)框架解析