一、前言
在开发中,有时候已经定版了,但是想做多一路pwm来调节某种功能,这个时候需要用到GPIO模拟pwm。
二、dts编写
blacklight {
compatible = "gpio-backlight";
pinctrl-names = "default";
label = "BLACKLIGHT1";
gpio-sim = <&pio PB 4 GPIO_ACTIVE_HIGH>;
status = "Okay";
};
三、驱动编写
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/platform_device.h>
#include <linux/err.h>
#include <linux/pwm.h>
#include <linux/slab.h>
#include <linux/clk.h>
// #include <linux/adc.h>
#include <linux/iio/iio.h>
#include <linux/iio/machine.h>
#include <linux/iio/driver.h>
#include <linux/iio/consumer.h>
#include <linux/delay.h>
#include <linux/time.h>
#include <linux/timer.h>
#include <linux/input.h>
#include <linux/interrupt.h>
// #include <linux/io.h>
#include <linux/of_platform.h>
#include <linux/of_device.h>
#include <linux/types.h>
#include <linux/ioport.h>
#include <asm/irq.h>
#include <linux/of_irq.h>
#include <linux/of_address.h>
#include <linux/pm_wakeirq.h>
#include <linux/reset.h>
struct gpio_blacklight_dev {
int value;
unsigned gpio;
struct gpio_desc * gpiod;
struct mutex mtx;
const char* name;
int light_level;
struct device *dev;
int high_time;
int low_time;
unsigned long flags;
unsigned active_low;
struct timer_list pwm_timer;
void __iomem *reg_base;
};
static int gpio_sim = 0;
static int tcount = 0, flag = 0;
static int high_flag;
static int low_flag;
static int frequency = 1000; // 初始频率,单位:Hz
static int high_cnt = 50;//调节该值就能操作占空比
static int period_cnt = 100;//100个定时周期认为是一个pwm周期,所以定时器的时间应该是1s/frequency/period_cnt
//static int timer_cnt = 0;
static struct hrtimer pwm_timer1;
static ktime_t pwm_period;
static ssize_t light_level_show(struct device *dev, struct device_attribute *attr, char *buf)
{
struct gpio_blacklight_dev *g_dev = (struct gpio_blacklight_dev *)dev_get_drvdata(dev);
return sprintf(buf, "%d\n", high_cnt);
}
static ssize_t light_level_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
{
long light_level;
struct gpio_blacklight_dev *g_dev = (struct gpio_blacklight_dev *)dev_get_drvdata(dev);
kstrtoul(buf, 10, &light_level);
high_cnt = light_level;
return count;
}
static struct device_attribute light_level = {
.attr = {
.name = "light_level",
.mode = S_IRUGO | S_IWUGO | S_IXUGO ,
},
.show = light_level_show,
.store = light_level_store,
};
static const struct of_device_id of_gpio_blacklights_match[] = {
{ .compatible = "gpio-backlight" },
{},
};
MODULE_DEVICE_TABLE(of, of_gpio_blacklights_match);
/*************************************************************************/
static enum hrtimer_restart pwm_timer_callback(struct hrtimer *timer) {
static int timer_cnt = 0;
if(++timer_cnt == high_cnt){
gpio_set_value(gpio_sim, 0);
}
if(timer_cnt == period_cnt){
gpio_set_value(gpio_sim, 1);
timer_cnt = 0;
}
hrtimer_forward(timer, timer->_softexpires, pwm_period);
//printk("==============================pwm_timer_callback\n");
return HRTIMER_RESTART;
}
static int io_pwm_init(void) {
// 计算 PWM 周期为pwm_period*period_cnt=pwm_period*100=1*10^(-6)
pwm_period = ktime_set(0, 1000000000 / frequency/ period_cnt);
// 初始化定时器
hrtimer_init(&pwm_timer1, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
pwm_timer1.function = pwm_timer_callback;//回调函数操作GPIO
// 启动高精度定时器
hrtimer_start(&pwm_timer1, pwm_period, HRTIMER_MODE_REL);
printk(KERN_INFO "PWM Timer Module Loaded\n");
printk("==============================io_pwm_init\n");
return 0;
}
/*************************************************************************/
static int gpio_blacklight_probe(struct platform_device *pdev)
{
int count = 0, ret = 0;
struct gpio_blacklight_platform_data *pdata = dev_get_platdata(&pdev->dev);
struct device *dev = &pdev->dev;
struct gpio_blacklight_dev *gblacklight_dev;
printk("enter gpio_blacklight_probe init\n");
gblacklight_dev = devm_kzalloc(dev, sizeof(struct gpio_blacklight_dev), GFP_KERNEL);
memset(gblacklight_dev, 0, sizeof(*gblacklight_dev));
if (!gblacklight_dev)
{
printk("devm_kzalloc gblacklight_dev fail \n");
return -1;
}
gblacklight_dev->reg_base = of_iomap(pdev->dev.of_node, 0);
if (gblacklight_dev->reg_base == 0)
{
printk("%s:Failed to ioremap() io memory region.\n", __func__);
ret = -EBUSY;
}
else
printk("xxkey base: %p !\n", gblacklight_dev->reg_base);
gpio_sim = of_get_named_gpio_flags(pdev->dev.of_node, "gpio-sim", 0, NULL);
if (gpio_sim < 0)
{
printk("%s() Can not read property gpio_sim\n", __FUNCTION__);
gpio_sim = 0;
}
ret = gpio_request(gpio_sim, "gpio-sim");
if (ret < 0)
{
printk("gpio request gpio_sim error!\n");
}
else
{
printk("==============================gpio-sim\n");
gpio_direction_output(gpio_sim, 1);
}
//放在这里初始化
io_pwm_init();
if(device_create_file(&pdev->dev, &light_level))
printk("zwhtest test device create light_level file fail because of erorr\n");
platform_set_drvdata(pdev, gblacklight_dev);
err:
return ERR_PTR(ret);
}
static int gpio_blacklight_remove(struct platform_device *pdev)
{
struct gpio_blacklight_dev *gblacklight_dev = (struct gpio_blacklight_dev *)dev_get_drvdata(&pdev->dev);
del_timer(&gblacklight_dev->pwm_timer);
device_remove_file(gblacklight_dev->dev, &light_level);
dev_set_drvdata(gblacklight_dev->dev, NULL);
hrtimer_cancel(&pwm_timer1);
return 0;
}
static struct platform_driver gpio_blacklight_driver = {
.probe = gpio_blacklight_probe,
.remove = gpio_blacklight_remove,
.driver = {
.name = "blacklight-gpio",
.of_match_table = of_match_ptr(of_gpio_blacklights_match),
.owner = THIS_MODULE,
},
};
module_platform_driver(gpio_blacklight_driver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("zwh");
这个代码直接拷下来就能用,修改frequency这个值就能调节频率。 用GPIO模拟的PWM频率做不了太高,high_cnt调节该值可以进行GPIO模拟,主要就是占空比的模拟。这两个值可以抽到dts进行解析,这样每次修改就只需要改dts。
四、总结
这篇笔记有GPIO、PWM、定时器的知识,代码逻辑比较简单,需要好好巩固。