前言
- 内核版本 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>;
};
};
驱动源码
#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_
struct gpio_pwm_chip
{
struct pwm_chip chip;
struct mutex lock;
struct hrtimer mytimer;
ktime_t kt;
u64 period;
u64 duty_cycle;
const char *desc;
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);
}
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
}
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) {
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->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);
if (gpio_get_value(gpwm->gpio) == 0)
{
if (gpwm->duty_cycle != 0)
{
gpio_set_value(gpwm->gpio, 1);
gpwm->kt = ktime_set(0, gpwm->duty_cycle);
}
hrtimer_forward_now(&gpwm->mytimer, gpwm->kt);
}
else
{
if (gpwm->duty_cycle != gpwm->period)
{
gpio_set_value(gpwm->gpio, 0);
gpwm->kt = ktime_set(0, gpwm->period - gpwm->duty_cycle);
}
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;
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;
if (!of_find_property(pp, "gpios", NULL))
{
pdata->npwms--;
printk( "Found pwm without gpios\n");
continue;
}
pwm = &pdata->pwms[i++];
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);
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;
}
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
gpwm->chip.ops = &gpio_pwm_ops;
gpwm->chip.of_xlate = of_pwm_xlate_with_flags;
gpwm->chip.of_pwm_n_cells = 3;
gpwm->chip.base = -1;
gpwm->chip.npwm = 1;
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;
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");