0、需求:
基于高通SM8550,在GNSS定位过程中,将PM8550 GPIO9管脚拉高至1.8V(用于驱动有源天线),定位结束后拉低。
PM8550相关文档可以参考高通的“80-35348-100 SM8550 PMIC Software User Guide”。
1、debug
在实现前,下面的手段是可以实现拉高pm8550 gpio9至1.8V的。拉高后可以搜到卫星,有源天线进入工作状态。
adb root
adb shell
cd d/regmap/0-01
echo 1 > count
echo 0x9040 > address
echo 0x01 > data
echo 0x9041 > address
echo 0x01 > data
echo 0x9042 > address
echo 0x05 > data
echo 0x9044 > address
# 00 output low, 0x80 output high
echo 0x80 > data
echo 0x9046 > address
echo 0x80 > data
exit
下面从软件层面去实现。
1、配置device tree
首先按照高通文档(8.3.5#Configuration examples),配置如下的dts。
在kernel_platform/qcom/proprietary/devicetree/qcom下找到该机型的dtsi。
&soc {
// ......
gnss_ant_en {
compatible = "qcom,gpio-en";
pinctrl-names = "default";
pinctrl-0 = <&gnss_ant_default>;
ant-gpios = <&pm8550_gpios 9 GPIO_ACTIVE_LOW>;
};
};
&pm8550_gpios {
gnss_ant {
gnss_ant_default: gnss_ant_default {
pins = "gpio9"; /* GPIO 9 */
function = "normal"; /* normal output */
power-source = <1>; /* VIN1 */
output-low; /* digital output, no invert */
input-disable; /* prevent GPIO from being set to DIO */
};
};
};
配置后效果如何呢?
在/sys/firmware/devicetree/base/soc树节点下,可以查找到gnss_ant_en节点。
cat name
#gnss_ant_encat
cat pinctrl-names
#default
2、新增驱动
新增一个驱动,用于控制该gpio的高低。由于与gnss功能相关,我们将它放在kernel_platform/msm-kernel/drivers/gnss下。
新增一个文件pm8550_gpio9_out.c,用于实现驱动。
2.1 Makefile改动
修改Makefile,新增如下,模块名为:gnss-qcom-pm8550-gpio9-out,依赖pm8550_gpio9_out.o。
obj-$(CONFIG_GNSS_QCOM_PM8550_GPIO9_OUT) += gnss-qcom-pm8550-gpio9-out.o
gnss-qcom-pm8550-gpio9-out-y := pm8550_gpio9_out.o
2.2 Kconfig改动
在Kconfig中增加该config说明。
config GNSS_QCOM_PM8550_GPIO9_OUT
tristate "GNSS QCOM PM8550 GPIO9 OUT"
help
Say Y here if you need to config pm8550 gpio9 as driver-controllable
digital output HIGH or LOW.
To compile this driver as a module, choose M here: the module will
be called.
If unsure or no need, say N.
2.3 加入编译
为了能够在所有的版本中都能有该可加载模块,在kernel_platform/msm-kernel/arch/arm64/configs/vendor/xxx_GKI.config中配置该CONFIG。
CONFIG_GNSS_QCOM_PM8550_GPIO9_OUT=m
2.4 驱动实现
本方案中使用字符设备的ioctl来实现gpio的驱动。
// SPDX-License-Identifier: GPL-2.0
#include <dt-bindings/pinctrl/qcom,pmic-gpio.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/gpio.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/slab.h>
#define DEV_NAME "gnss_ant_en"
static dev_t dev_id;
static struct cdev *gnss_ant_dev;
static struct class *gnss_ant_class;
static int gnss_ant_open(struct inode *inode, struct file *file)
{
printk("%s enter\n", __func__);
// TODO
return 0;
}
static int gnss_ant_release(struct inode *inode, struct file *file)
{
printk("%s enter\n", __func__);
// TODO
return 0;
}
static void setGpio9Output(int val) {
int gpio9_out;
struct device_node *node;
printk("%s enter. val=%d\n", __func__, val);
/* 获取设备树节点的引用 */
node = of_find_node_opts_by_path("/soc/gnss_ant_en", NULL);
if (node == NULL) {
printk("%s, Failed to find device tree node\n", __func__);
return;
}
gpio9_out = of_get_named_gpio(node, "ant-gpios", 0);
gpio_request(gpio9_out, "GPIO9");
gpio_direction_output(gpio9_out, val); //1:Output HIGH;0:Output LOW
}
static long gnss_ant_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
printk("%s enter\n", __func__);
switch (cmd) {
case _IOW('k', 0, int):
setGpio9Output(0);
break;
case _IOW('k', 1, int):
setGpio9Output(1);
break;
default:
break;
}
return 0;
}
static const struct file_operations gnss_ant_fops = {
.owner = THIS_MODULE,
.open = gnss_ant_open,
.release = gnss_ant_release,
.unlocked_ioctl = gnss_ant_ioctl,
};
static int __init gnss_ant_init(void)
{
printk("%s enter\n", __func__);
/* 申请设备号 */
alloc_chrdev_region(&dev_id, 1, 1, DEV_NAME);
/* 分配字符设备 */
gnss_ant_dev = cdev_alloc();
/* 设置字符设备 */
cdev_init(gnss_ant_dev, &gnss_ant_fops);
/* 注册字符设备 */
cdev_add(gnss_ant_dev, dev_id, 1);
/* 打印申请到的主次设备号 */
printk("major:%d; minor:%d\n", MAJOR(dev_id), MINOR(dev_id));
gnss_ant_class = class_create(THIS_MODULE, DEV_NAME);
device_create(gnss_ant_class, NULL, dev_id, NULL, DEV_NAME);
return 0;
}
module_init(gnss_ant_init);
static void __exit gnss_ant_exit(void)
{
printk("gnss_ant_exit\n");
device_destroy(gnss_ant_class, dev_id);
class_destroy(gnss_ant_class);
cdev_del(gnss_ant_dev);
kfree(gnss_ant_dev);
unregister_chrdev_region(dev_id, 1);
}
module_exit(gnss_ant_exit);
MODULE_AUTHOR("阅后即奋");
MODULE_DESCRIPTION("QCOM GNSS PM8550 GPIO9 Enabler");
MODULE_LICENSE("GPL v2");
ioctl的参数cmd,魔数就选定'k',基数0表示拉低,基数1表示拉高。setGpio9Output函数也没有去润色,只是先实现个功能,可自行补充其他的check。
一开始使用of_find_node_by_name(NULL, "gnss_ant_en")函数去获取device_node,返回的是NULL,所以改成了node = of_find_node_opts_by_path("/soc/gnss_ant_en", NULL)去获取设备树节点的引用。
设备开机后,可加载模块被modprobe自动加载,字符设备创建成功。由于是动态创建的,所以主设备号可能每次开机后都不一样。
[ 8.740527] gnss_ant_init
[ 8.740559] major:488; minor:1
adb shell cat /proc/devices后,可以在看到:
Character devices:
......
488 gnss_ant_en
......
在/dev/下也能看到字符设备gnss_ant_en
crw------- 1 root root 488, 1 1970-01-02 22:59 gnss_ant_en
3、用户空间控制字符设备
编译部分就不写了。下面给出在用户空间控制字符设备"/dev/gnss_ant_en"的sample code。
#include <fcntl.h>
#include <stdio.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <log_util.h>
#ifdef LOG_NDEBUG
#undef LOG_NDEBUG
#endif
#define LOG_NDEBUG 0
#ifdef LOG_TAG
#undef LOG_TAG
#endif
#define LOG_TAG "PM8550_GPIO9_EN"
#ifdef LOGD
#undef LOGD
#endif
#define LOGI(...) ALOGI(__VA_ARGS__)
#define LOGE(...) ALOGE(__VA_ARGS__)
#define DEV_NAME "/dev/gnss_ant_en"
int pm8550_gpio9_ioctl(int cmd) {
int fd;
int ret;
fd = open(DEV_NAME, O_RDWR);
if (fd < 0) {
LOGE("open device %s failed, return %d", DEV_NAME, fd);
return -1;
}
ret = ioctl(fd, _IOW('k', cmd, int));
LOGI("ioctl %s, return %d", DEV_NAME, ret);
close(fd);
return ret;
}
然后在开始定位的函数LocApiV02::startTimeBasedTracking中调用pm8550_gpio9_ioctl(1),在结束定位的函数LocApiV02::stopTimeBasedTracking中调用pm8550_gpio9_ioctl(0)即可。
日志打印如下:
开始定位:
[ 710.940557] gnss_ant_open enter
[ 710.940576] gnss_ant_ioctl enter
[ 710.940578] setGpio9Output enter. val=1
[ 710.941423] gnss_ant_release enter
结束定位:
[ 726.021958] gnss_ant_open enter
[ 726.021980] gnss_ant_ioctl enter
[ 726.021982] setGpio9Output enter. val=0
[ 726.022581] gnss_ant_release enter
验证:
adb root
adb shell
cd /d/regmap/0-01
# 开始定位
echo 0x9044 > address
cat data
# 打印9044: 80
# 结束定位
cat data
# 打印9044: 00