一、rfkill子系统
1.1 rfkill概述
rfkill是Linux内核提供的一个框架,用于控制无线通信硬件(如 WiFi、蓝牙、NFC 等)的开关和状态。rfkill就是RF(射频) 设备的开关,有类似一键关闭所有射频外设的功能。
rfkill的出现方便管理各种RF芯片的开关, 目前已经很多厂商的设备使用的是rfkill的驱动来管理一些无线设备的电源了,都是和RF相关的芯片,比如WiFi,蓝牙, NFC, FM,,GPS等等。
由于这个rfkill的功能是管理无线设备的电源开关,所以这和硬件的关联是直接的,一般都会涉及到硬件的power或reset引脚。
rfkill子系统有"hard"和"soft" block的概念,block的意思就是发射器关闭,hard block和soft block的差异在于:
- hard block : 软件无法管控到的只读类型的无线模块;
- soft block: 系统软件可设置的无线模块(可不需要读权限);
1.2 rfkill子系统框架
rfkill子系统主要三个部分组成:
- rfkill core;为驱动程序提供了API,让驱动程序可向内核注册无线电发射器设备,以及打开和关闭内核的方法,,这样系统就知道怎么禁用设备硬件.,rfkill core还向用户空间通知状态更改,并为用户空间提供查询当前状态的方法。 linux下提供了rfkill工具,用于开启和关闭无线设备功能。
- rfkill input模块: 输入层处理程序,不推荐使用的,已由用户空间策略代码代替;
- rfkill驱动程序:需要实现不同芯片的rfill驱动程序;比如我们需要提供AP6356的rfkill驱动程序,实现AP6356设备的开启和关闭;
我们要做的一般是调试厂商已经做好的rfkill驱动,或者我们自己实现API的接口,这个并不难,其实最终就是操作GPIO引脚。
在linux系统下可以通过rfkill工具查看无线设备的功能:
root@zhengyang:~# rfkill ID TYPE DEVICE SOFT HARD 0 bluetooth hci0 unblocked unblocked root@zhengyang:~# rfkill list 0: hci0: Bluetooth Soft blocked: no Hard blocked: no
这里看到有个无线设备,是蓝牙的,操作开关就不演示了,按照命令帮助操作就能操作到蓝牙的开关了。
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文件中:
structrfkill{ 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.1.1 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.1.2 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设备
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结构体的函数是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 注册r/卸载fkill设备
rfkill控制器驱动编写,实际上就是去为RF芯片分配一个rfkill数据结构,然后去根据去编写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_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);
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);
四、rfkill设备驱动
编写rfkill设备驱动一般包含以下几个步骤:
我们以Rockchip提供的针对WiFi设备的rfkill驱动程序为例进行讲解:
/* * 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");
参考文章