GPIO模拟PWM驱动风扇Linux设备驱动

前言

  • 内核版本 linux5_10;
  • GPIO产生PWM方波,控制风扇转速;
  • 本驱动可在应用层 /sys/class/pwm/下生成文件接口,通过脚本进行配置;
  • 本驱动可将该 PWM 设备树节点用于 thermal 节点中,通过与传感器关联,根据温度自动控制风扇转速;

设备树

	pwm1:gpio-pwms {
		compatible = "gpio-pwms";
		pinctrl-names = "default";
		#pwm-cells = <3>;
		status = "okay";

		pwm1 {
			label = "pwm1";
			gpios = <&gpio4 2 0>;
		};
	};

驱动源码

/**
 * pwm_gpio.c create by gp
*/
#include <linux/clk.h>
#include <linux/err.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/init.h>
#include <linux/platform_device.h>
#include <linux/pwm.h>
#include <linux/device.h>
#include <linux/gpio.h>
#include <linux/delay.h>
#include <linux/of.h>
#include <linux/of_platform.h>
#include <linux/of_gpio.h>
#include <linux/of_device.h>

#define _DEBUG_

//pwm的设备对象
struct gpio_pwm_chip
{
	// 与 PWM 架构关联 
	struct pwm_chip chip;
	// 互斥锁
	struct mutex lock;
	// 高精度定时器
    struct hrtimer mytimer;
    // 时间变量
    ktime_t kt;
    // 周期记录
	u64 period;
	// 占空比记录
	u64 duty_cycle;
	// GPIO名字字符串
    const char *desc;
    // GPIO 号
    int gpio;
    
    int active_low;
};

struct gpio_pwms_platform_data
{
	int npwms;
    struct gpio_pwm_chip pwms[0];
};

static struct gpio_pwm_chip *gloabl_pwms_dev = NULL;
static struct gpio_pwms_platform_data *pdata = NULL;
static enum hrtimer_restart    hrtimer_handler(struct hrtimer *timer);

static inline struct gpio_pwm_chip *to_gpio_chip(struct pwm_chip *chip)
{
	return container_of(chip, struct gpio_pwm_chip, chip);
}

static inline struct gpio_pwm_chip *timer_to_gpio_chip(struct hrtimer *timer)
{
	return container_of(timer, struct gpio_pwm_chip, mytimer);
}
// 应用层,导出PWM的时候会调用这个函数,一般为硬件初始化,GPIO 不做操作
static int gpio_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm)
{
	#ifdef _DEBUG_
	printk("%s %d Call\n", __func__, __LINE__);
	#endif
	return 0;
}
// 核心层回调函数,不做操作
static void gpio_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm)
{
	#ifdef _DEBUG_
	printk("%s %d Call\n", __func__, __LINE__);
	#endif
}

// 主要函数, 配置应用,应用层对PWM文件操作,enable 写 1 的时候调用
static int gpio_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
			 const struct pwm_state *newstate)
{
	struct gpio_pwm_chip *gpwm = to_gpio_chip(chip);
	struct pwm_state *oldstate = &pwm->state;
	int ret = 0;
	#ifdef _DEBUG_
	printk("%s %d Call, old->enabled %d new->enabled %d period %lld duty %lld\n", __func__, __LINE__, oldstate->enabled, newstate->enabled,newstate->period, newstate->duty_cycle);
	#endif
	// 加锁互斥
	mutex_lock(&gpwm->lock);
	
	
	if (!newstate->enabled) {
		if (oldstate->enabled) {
			// 如果状态是从打开到关闭, GPIO设置为低, 定时器关闭
			// 使能  - 》 失能 关闭产生,恢复默认
			hrtimer_cancel(&gpwm->mytimer);
			gpio_set_value(gpwm->gpio, 0);
			#ifdef _DEBUG_
			printk("%s %d cancel timer\n", __func__, __LINE__);
			#endif
		}
		goto end_mutex;
	}
	// 设置好周期,占空比
	gpwm->period = newstate->period;
	gpwm->duty_cycle = newstate->duty_cycle;
	
	
	if (!oldstate->enabled) {
		// 如果从关闭到打开
		// 失能 -》 使能
		// 初始化定时器
		hrtimer_init(&gpwm->mytimer,CLOCK_MONOTONIC,HRTIMER_MODE_REL);
		// 超时时间
        gpwm->mytimer.function = hrtimer_handler;
        // 超时时间为  gpwm->period - gpwm->duty_cycle
        gpwm->kt = ktime_set(0, gpwm->period - gpwm->duty_cycle);
        // 启动定时器
        hrtimer_start(&gpwm->mytimer,gpwm->kt,HRTIMER_MODE_REL);
		#ifdef _DEBUG_
		printk("%s %d Start timer, kt %lld\n", __func__, __LINE__, gpwm->kt);
		#endif
	}
	
end_mutex:
	mutex_unlock(&gpwm->lock);
	
	return ret;
}

static const struct pwm_ops gpio_pwm_ops = {
	.request = gpio_pwm_request,
	.free = gpio_pwm_free,
	.apply = gpio_pwm_apply,
	.owner = THIS_MODULE,
};

// 定时器到时,回调函数
static enum hrtimer_restart hrtimer_handler(struct hrtimer *timer)
{
	struct gpio_pwm_chip *gpwm = timer_to_gpio_chip(timer);
	// printk("%s %d get gpwm 0x%lx\n", __func__, __LINE__, (unsigned long)gpwm);
	// 如果GPIO输出为低,则改为高
    if (gpio_get_value(gpwm->gpio) == 0)
    {
        // There is no need to pull down when the duty cycle is 100%
        if (gpwm->duty_cycle != 0)
        {
        	// 一个周期内,高电平时间为 gpwm->duty_cycle, 剩下的时间为低电平
            gpio_set_value(gpwm->gpio, 1);
            gpwm->kt = ktime_set(0, gpwm->duty_cycle);
        }
        // timer overflow
        // 重新设置超时时间 kt
        hrtimer_forward_now(&gpwm->mytimer, gpwm->kt);
    }
    else
    {
    	// 如果GPIO输出为高,则改为低
        // There is no need to pull up when the duty cycle is 0
        if (gpwm->duty_cycle != gpwm->period)
        {
        	// 一个周期内,高电平时间为 gpwm->duty_cycle, 剩下的时间为低电平
            gpio_set_value(gpwm->gpio, 0);
            gpwm->kt = ktime_set(0, gpwm->period - gpwm->duty_cycle);

        }
        // timer overflow
       	// 重新设置超时时间 kt
        hrtimer_forward_now(&gpwm->mytimer, gpwm->kt);
    }

    return HRTIMER_RESTART;
}


static struct gpio_pwms_platform_data * gpio_pwms_get_devtree_pdata(struct device *dev)
{
    struct device_node *node, *pp;
    struct gpio_pwms_platform_data *pdata;
    struct gpio_pwm_chip *pwm;
    int error;
    int npwms;
    int i = 0;

    node = dev->of_node;
    if (!node)
        return NULL;
	// 获取设备树中的子节点数量,此处为1
    npwms = of_get_child_count(node);
    if (npwms == 0)
        return NULL;
	// 申请结构体内存
    pdata = devm_kzalloc(dev, sizeof(pdata->npwms) + npwms * sizeof(struct gpio_pwm_chip), GFP_KERNEL);
    if (!pdata)
    {
        error = -ENOMEM;
        goto err_out;
    }
	
    pdata->npwms = npwms;

    for_each_child_of_node(node, pp)
    {
        enum of_gpio_flags flags;
		// 检查子节点是否还有 gpios 属性
        if (!of_find_property(pp, "gpios", NULL))
        {
            pdata->npwms--;
            printk( "Found pwm without gpios\n");
            continue;
        }
		
        pwm = &pdata->pwms[i++];
        // 获取子节点用到的GPIO
        pwm->gpio = of_get_gpio_flags(pp, 0, &flags);
		#ifdef _DEBUG_
        printk("pwm->gpio = %d,flags = %d",pwm->gpio,flags);
		#endif
        if (pwm->gpio < 0)
        {
            error = pwm->gpio;
            if (error != -ENOENT)
            {
                if (error != -EPROBE_DEFER)
                    dev_err(dev,
                            "Failed to get gpio flags, error: %d\n",
                            error);
                return ERR_PTR(error);
            }
        }
        else
        {
            pwm->active_low = flags ;
        }
        // 获取标签
        pwm->desc = of_get_property(pp, "label", NULL);
    }

    if (pdata->npwms == 0)
    {
        error = -EINVAL;
        goto err_out;
    }
    return pdata;

err_out:
    return ERR_PTR(error);

}

static int gpio_pwm_probe(struct platform_device *pdev)
{
    struct device *dev = &pdev->dev;
    int error = 0, i;
    unsigned int gpio;
    struct gpio_pwm_chip *gpwm = NULL;
	
    pdata = pdev->dev.platform_data;

    if (!pdata)
    {
        pdata = gpio_pwms_get_devtree_pdata(dev);
        if (IS_ERR(pdata))
            return PTR_ERR(pdata);
        if (!pdata)
        {
            printk( "missing platform data\n");
            return -EINVAL;
        }
    }
	// 申请内存
    gloabl_pwms_dev = devm_kzalloc(dev, pdata->npwms * sizeof(struct gpio_pwm_chip), GFP_KERNEL);
    if (!gloabl_pwms_dev)
    {
        printk("no memory for gloabl_pwms_dev data\n");
        return -ENOMEM;
    }
	// 设置全局变量
    memcpy(gloabl_pwms_dev, pdata->pwms, pdata->npwms * sizeof(struct gpio_pwm_chip));
	

    for (i = 0; i < pdata->npwms; i++)
    {
		gpwm = &gloabl_pwms_dev[i];
		mutex_init(&gpwm->lock);
		gpwm->chip.dev = dev;
       
        gpio = gpwm->gpio;

        if(!gpio_is_valid(gpio))
            printk("debug:invalid gpio,gpio=0x%x\n", gpio);
		// 设置gpio 为输出,并根据active_low 设置高低电平,此处没什么用处,因为上面已经写死了
        error = gpio_direction_output(gpio, !((gpwm->active_low == OF_GPIO_ACTIVE_LOW) ? 0 : 1));
        if (error)
        {
            printk(
                "unable to set direction on gpio %u, err=%d\n",
                gpio, error);
            return error;
        }
		// 向系统申请goio
        error = devm_gpio_request(dev, gpio,gpwm->desc);
        if (error)
        {
            printk( "unable to request gpio %u, err=%d\n",
                    gpio, error);
			return error;
        }
		#ifdef _DEBUG_
        else
        {
            printk("successed to request gpio\n");
        }
		#endif
		// 填充 pwm_chip 结构体
		gpwm->chip.ops = &gpio_pwm_ops;
		gpwm->chip.of_xlate = of_pwm_xlate_with_flags;
		gpwm->chip.of_pwm_n_cells = 3;
		// base = -1 由系统分配base
		gpwm->chip.base = -1;
		gpwm->chip.npwm = 1;
		// 添加,成功后会在 /sys/class/pwm 下生成 gpiochip 目录
		error = pwmchip_add(&gpwm->chip);
		if (error < 0) {
			dev_err(&pdev->dev, "failed to add PWM chip: %d\n", error);
			return error;
		}
		#ifdef _DEBUG_
		printk("%s %d New gpwm 0x%lx, gpio %d\n", __func__, __LINE__, (unsigned long)gpwm, gpio);
		#endif
    }
	
	platform_set_drvdata(pdev, gpwm);
	
    return error;
}

static int gpio_pwm_remove(struct platform_device *pdev)
{
	struct gpio_pwm_chip *gpwm = NULL;
	int i;
	// 移除 pwmchip
    for (i = 0; i < pdata->npwms; i++)
    {
		gpwm = &gloabl_pwms_dev[i];
		pwmchip_remove(&gpwm->chip);
		#ifdef _DEBUG_
		printk("%s %d Remove gpwm 0x%lx\n", __func__, __LINE__, (unsigned long)gpwm);
		#endif
	}
	
	return 0;
}

static struct of_device_id gpio_pwm_of_match[] =
{
	// 与设备树匹配名字
    {.compatible = "gpio-pwms"},
    {},
};

MODULE_DEVICE_TABLE(of, gpio_pwm_of_match);

static struct platform_driver gpio_pwm_driver =
{
    .probe = gpio_pwm_probe,
	.remove = gpio_pwm_remove,
    .driver = {
        .name = "gpio-pwms",
        .owner = THIS_MODULE,
        .of_match_table = of_match_ptr(gpio_pwm_of_match),
    }
};

module_platform_driver(gpio_pwm_driver);

MODULE_DESCRIPTION("Module For PWM GPIO");
MODULE_AUTHOR("GUO PENG, <engguopeng@buaa.edu.cn>");
MODULE_LICENSE("GPL");
/* end pwm_gpio.c */
  • 3
    点赞
  • 34
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Call Me Gavyn

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值