【msm8953】gpio口模拟pwm详细步骤

 

 

一丶简介

平台: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口:

【msm8953】gpio口模拟pwm详细步骤_第1张图片

② 进入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 

#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 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#include 
#include 

#include 
#include 
#include 
#include 

#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 
#include 
#include 
#include 
#include 
#include 
#include 
#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,示波器如下:

【msm8953】gpio口模拟pwm详细步骤_第2张图片

四丶回顾与错误记录

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驱动背光,只是板上没用,不过它是模拟的(时钟模拟)。

你可能感兴趣的:(【msm8953】gpio口模拟pwm详细步骤)