一丶简介
平台:msm8953 (android)
环境: ubuntu-16.04
二丶步骤
① 修改设备树,添加pwm节点:
位置:kernel/msm-3.18/arch/arm64/boot/dts/qcom/msm8953-mtp.dtsi
在soc中添加节点,如下:
gpio-demo {
compatible = "gpio-demo";
gpios = <&tlmm 33 0x0>;
status = "ok";
};
注:tlmm为gpio控制器,33为gpio号,gpio端口号可自己设置,填上可复用为通用gpio口就行。如下都可以作为pwm的gpio口:
② 进入kernel/msm-3.18/drivers/pwm,
mkdir pwm_iosim
cd pwm_iosim
③ 创建驱动文件pwm_gpio.h pwm_gpio.c
/**
* pwm_gpio.h create by SouthLj
*/
#ifndef __PWM_GPIO_H__
#define __PWM_GPIO_H__
#include <linux/ioctl.h>
#define PWM_PERIOD_SET _IOW('A', 1, unsigned long)
#define PWM_DUTY_SET _IOW('A', 2, unsigned long)
#define PWM_START _IOW('A', 3, unsigned long)
#endif /* pwm_gpio.h */
/**
* pwm_gpio.c create by SouthLj
*/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/slab.h>
#include <linux/cdev.h>
#include <linux/interrupt.h>
#include <linux/gpio.h>
#include <linux/input.h>
#include <linux/sched.h>
#include <linux/wait.h>
#include <linux/delay.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/platform_device.h>
#include <linux/of_platform.h>
#include <linux/of_gpio.h>
#include <linux/of_device.h>
#include "pwm_gpio.h"
#define PWM_DEVICE_NAME "pwm_gpio"
#define PWM_CLASS_NAME "pwm_gpio" //产生sys/class/pwm_gpio
#define PWM_DEVICE_NUM 0 //产生/dev/pwm-0
int gpio_33;
struct device_node *node;
typedef enum {
PWM_DISABLE = 0,
PWM_ENABLE,
}PWM_STATUS_t;
//pwm的设备对象
struct pwm_chip{
dev_t devno;
struct cdev *cdev;
struct class *cls;
struct device *dev;
unsigned long period;
unsigned long duty;
struct hrtimer mytimer;
ktime_t kt;
PWM_STATUS_t status;
};
struct pwm_chip *pwm_dev;
struct gpio_demo_priv{
int count;
int gpio[0];
struct mutex mtx;
int mode;
};
struct gpio_demo_priv *priv;
static void pwm_gpio_start(void); // pwm开始
static enum hrtimer_restart hrtimer_handler(struct hrtimer *timer); // 定时器处理
int pwm_drv_open (struct inode * inode, struct file *filp)
{
int minor;
int major;
minor = iminor(inode);
major = imajor(inode);
filp->private_data = &minor; //modify by SouthLj void*
return 0;
}
ssize_t pwm_drv_read (struct file *filp, char __user *userbuf, size_t count, loff_t *fpos)
{
int ret;
if(filp->f_flags & O_NONBLOCK){
return -EAGAIN;
}
ret = copy_to_user(userbuf,&pwm_dev->period,count);
if(ret > 0){
return -EFAULT;
}
return count;
}
ssize_t pwm_drv_write (struct file *filp, const char __user *userbuf, size_t count, loff_t *fpos)
{
return 0;
}
long pwm_drv_ioctl(struct file *filep, unsigned int cmd, unsigned long arg)
{
int ret = 0;
switch(cmd)
{
case PWM_PERIOD_SET :
pwm_dev->period = arg;
break;
case PWM_DUTY_SET :
pwm_dev->duty = arg;
break;
case PWM_START :
if(pwm_dev->status == PWM_DISABLE){
// start timer
pwm_dev->status = PWM_ENABLE;
pwm_gpio_start();
}else{
printk("debug:pwm_gpio aready work\n");
}
break;
default :
ret = -1;
break;
}
return 0;
}
int pwm_drv_close (struct inode *inode, struct file *filp)
{
return 0;
}
static void pwm_gpio_start(void)
{
hrtimer_init(&pwm_dev->mytimer,CLOCK_MONOTONIC,HRTIMER_MODE_REL);
pwm_dev->mytimer.function = hrtimer_handler;
pwm_dev->kt = ktime_set(0, pwm_dev->period-pwm_dev->duty);
//pwm_dev->kt = ktime_set(0, pwm_dev->duty);
hrtimer_start(&pwm_dev->mytimer,pwm_dev->kt,HRTIMER_MODE_REL);
}
static enum hrtimer_restart hrtimer_handler(struct hrtimer *timer)
{
if (gpio_get_value(gpio_33) == 1) {
// There is no need to pull down when the duty cycle is 100%
if (pwm_dev->duty != pwm_dev->period) {
gpio_set_value(gpio_33, 0);
// pwm_dev->kt = ktime_set(0, pwm_dev->period-pwm_dev->duty);
pwm_dev->kt = ktime_set(0, pwm_dev->period-pwm_dev->duty);
}
// timer overflow
hrtimer_forward_now(&pwm_dev->mytimer, pwm_dev->kt);
} else {
// There is no need to pull up when the duty cycle is 0
if (pwm_dev->duty != 0) {
gpio_set_value(gpio_33, 1);
pwm_dev->kt = ktime_set(0, pwm_dev->duty);
}
// timer overflow
hrtimer_forward_now(&pwm_dev->mytimer, pwm_dev->kt);
}
return HRTIMER_RESTART;
}
const struct file_operations pwm_fops = {
.open = pwm_drv_open,
.write = pwm_drv_write,
.read = pwm_drv_read,
.unlocked_ioctl = pwm_drv_ioctl,
.release = pwm_drv_close,
};
static int gpio_demo_probe(struct platform_device *pdev)
{
int ret;
enum of_gpio_flags flag;
struct device *dev = &pdev->dev;
node = dev->of_node;
if(! node)
return -EINVAL;
pwm_dev = kzalloc(sizeof(struct pwm_chip),GFP_KERNEL);
if(pwm_dev == NULL){
printk("kzalloc error");
return -ENOMEM;
}
ret = alloc_chrdev_region(&pwm_dev->devno,0,1,PWM_DEVICE_NAME);
if(ret != 0){
printk("debug:error alloc_chrdev_region\n");
goto err_free;
}
pwm_dev->cdev = cdev_alloc();
cdev_init(pwm_dev->cdev,&pwm_fops);
cdev_add(pwm_dev->cdev,pwm_dev->devno,1);
pwm_dev->cls = class_create(THIS_MODULE,PWM_CLASS_NAME);
if(IS_ERR(pwm_dev->cls)){
printk("debug:error class_create\n");
ret = PTR_ERR(pwm_dev->cls);
goto err_unregister;
}
pwm_dev->dev = device_create(pwm_dev->cls,NULL,pwm_dev->devno,NULL,"pwm-%d",PWM_DEVICE_NUM);
if(IS_ERR(pwm_dev->dev)){
printk("debug:error device_create\n");
ret = PTR_ERR(pwm_dev);
goto err_class_error;
}
gpio_33 = of_get_named_gpio_flags(node, "gpios", 0, &flag);
if(!gpio_is_valid(gpio_33))
printk("debug:invalid gpio,gpio=0x%x\n", gpio_33);
gpio_direction_output(gpio_33, !((flag == OF_GPIO_ACTIVE_LOW) ? 0 : 1));
pwm_dev->period = 10000000;
pwm_dev->duty = 5000000;
if(gpio_request(gpio_33, "PWM_GPIO"))
{
printk("debug:error pwm_gpio_init\n");
goto err_device_create;
}
else{
pwm_dev->status = PWM_DISABLE;
}
return 0;
err_device_create:
device_destroy(pwm_dev->cls,pwm_dev->devno);
err_class_error:
class_destroy(pwm_dev->cls);
err_unregister:
cdev_del(pwm_dev->cdev);
unregister_chrdev_region(pwm_dev->devno,1);
err_free:
kfree(pwm_dev);
return ret;
}
static void pwm_gpio_exit(void)
{
gpio_set_value(gpio_33, 1);
gpio_free(gpio_33);
}
// add by SouthLj 2019-0924
static struct of_device_id gpio_demo_of_match[] = {
{.compatible = "gpio-demo"},
{},
}
MODULE_DEVICE_TABLE(of, gpio_demo_of_match);
static struct platform_driver gpio_demo_driver = {
.probe = gpio_demo_probe,
.driver = {
.name = "gpio-demo-device",
.owner = THIS_MODULE,
.of_match_table = of_match_ptr(gpio_demo_of_match),
}
};
static int __init gpio_demo_init(void)
{
return platform_driver_register(&gpio_demo_driver);
}
static void __exit gpio_demo_exit(void)
{
pwm_gpio_exit();
device_destroy(pwm_dev->cls,pwm_dev->devno);
class_destroy(pwm_dev->cls);
cdev_del(pwm_dev->cdev);
unregister_chrdev_region(pwm_dev->devno,1);
kfree(pwm_dev);
return platform_driver_unregister(&gpio_demo_driver);
}
late_initcall(gpio_demo_init);
module_exit(gpio_demo_exit);
// add by SouthLj 2019-0924 end
MODULE_LICENSE("GPL");
MODULE_AUTHOR("SouthLj");
/* end pwm_gpio.c */
④ 更改配置,使驱动编译进内核
位置:kernel/msm-3.18/drivers/pwm/Kconfig
在if pwm 和 endif之间添加如下:
config PWM_GPIO
tristate "PWM gpio Driver"
default n
help
This is the pwm gpio driver for android system.
位置:kernel/msm-3.18/drivers/pwm/Makefile
在最后面加上,
obj-$(CONFIG_PWM_GPIO) += pwm_iosim/pwm_gpio.o
位置:kernel/msm-3.18/arch/arm64/configs/msmcortex_deconfig,添加,
CONFIG_PWM_GPIO = y
⑤ 编译与烧写
进入安卓源码根目录,执行,
source build/envsetup.sh
lunch msm8953_64-userdebug
make bootimage -j8
注:最后boot.img生成在/out/target/product/msm8953
使用adb,fastboot 烧写,
adb reboot bootloader
fastboot flash boot boot.image
fastboot reboot
重启后,ls /dev(需要root权限),就可以看见pwm-0了。同理在/sys/class下也会出现pwm_gpio。
三丶添加pwm测试程序
① 创建测试目录pwm_test
位置:kernel/msm-3.18/drivers/pwm
② 添加测试代码pwm_gpio_test.c
/**
* pwm_gpio_test.c create by SouthLj
*/
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include "pwm_gpio.h"
int main(int argc,char **argv)
{
int fd = -1;
unsigned long duty = -1;
unsigned long period = -1;
unsigned long insert = -1;
fd = open("/dev/pwm-0",O_RDWR | O_NONBLOCK);
if(fd < 0) {
exit(1);
}
duty= atol(argv[2]);
period = atol(argv[1]);
insert = atol(argv[3]);
ioctl(fd,PWM_PERIOD_SET,period); // 10 000 000ns = 10ms
ioctl(fd,PWM_DUTY_SET,duty);
ioctl(fd,PWM_START,0);
while(1) {
if (duty >= period) {
duty = 0;
}
duty+=insert;
ioctl(fd,PWM_DUTY_SET,duty);
usleep(1000);
}
close(fd);
return 0;
}
/* end pwm_gpio_test.c. */
③ 同目录下添加Android.mk和MakefileAndroid.mk:
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_SRC_FILES += pwm_gpio_test.c
LOCAL_MODULE := pwm_gpio_test
LOCAL_LDFLAGS += -L$(LOCAL_PATH)
LOCAL_LDLIBS := -llog
include $(BUILD_EXECUTABLE)
Makefile:
hostprogs-y := pwm_gpio_test
always := $(hostprogs-y)
HOSTCFLAGS_pwm_gpio_test.o += -I$(objtree)/usr/include
④回到安卓源码根目录
source build/envsetup.sh
lunch msm8953_64-userdebug
返回到kernel/msm-3.18/drivers/pwm/pwm_test,执行mm命令。
注:生成的pwm_gpio_test在out/target/product/msm8953/system/bin中
⑤ 用adb烧录到板上
adb root
adb remount
adb push pwm_gpio_test /system/bin
⑥ 测试
板上运行 pwm_gpio_test 10000000 0 1000,示波器如下:
四丶回顾与错误记录
1.最开始不是通过访问设备树节点去访问gpio的,后面发现这样写驱动,我实在找不到gpio的寄存器地址(没有寄存器手册),然后才想到通过访问设备树节点的方式来获得gpio口。
2.驱动节点已经生成,执行测试程序的时候在示波器上看不见波形。在测试中,我将手动gpio口export出来,然后设置为out,然后就在示波器上有波形了,我想应该是驱动出了问题。我之前是把设备树的解析和驱动设备分成两个模块来写的,也就是:
late_initcall(gpio_demo_init);
module_exit(gpio_demo_exit);
module_init(pwm_drv_init);
module_exit(pwm_drv_exit);
gpio_direction_output是在pwm_drv_init完成的,gpio_demo_probe仅仅是用来得到gpio口号,这样写是不对的,gpio_direction_output卸载gpio_demo_probe就好了。
这样分开写还出现了一个大错误就是有pwm波形了,就是触摸屏失效了,具体是什么原因不清楚。只要把设备驱动初始化放在gpio_demo_probe里就好了。
3.其实gpio33口是可以作为pwm输出的,看见设备树中有对它的描述,跟mpp4一样,是用来pwm驱动背光,只是板上没用,不过它是模拟的(时钟模拟)。