PWM驱动

一、PWM驱动介绍

PWM 原理可参考LCD背光调节实验一文的介绍,这里主要介绍 NXP 原厂提供的 Linux 内核自带的 PWM 驱动

1.1 设备树下的 PWM 控制器节点

I.MX6ULL 有8路 PWM 输出,因此对应8个 PWM 控制器,所以在设备树下有8个 PWM 控制器节点。这8路 PWM 都属于 I.MX6ULL 的 AIPS-1 域,8路 PWM 的设备树节点内容都是一样的,除了 reg 属性不同。本章实验使用 GPIO1_IO04 这个引脚来完成 PWM 实验,以 PWM3 为例,imx6ull.dtsi 文件中的 pwm3 节点信息如下:

pwm3: pwm@02088000 {compatible = "fsl,imx6ul-pwm", "fsl,imx27-pwm";reg = <0x02088000 0x4000>;interrupts = <GIC_SPI 85 IRQ_TYPE_LEVEL_HIGH>;clocks = <&clks IMX6UL_CLK_PWM3>,<&clks IMX6UL_CLK_PWM3>;clock-names = "ipg", "per";#pwm-cells = <2>;
};
1.2 PWM 子系统

内核提供了个 PWM 子系统框架,编写 PWM 驱动时一定要符合这个框架。 PWM 子系统的核心是 pwm_chip 结构体,定义在文件 include/linux/pwm.h 中

struct pwm_chip {struct device *dev;struct list_head list;const struct pwm_ops *ops;int base;unsigned int npwm;struct pwm_device *pwms;struct pwm_device * (*of_xlate)(struct pwm_chip *pc,const struct of_phandle_args *args);unsigned int of_pwm_n_cells;bool can_sleep;
};

其中 pwm_ops 结构体就是 PWM 外设的各种操作函数集合,编写驱动时需要开发人员实现, pwm_ops 结构体定义在 pwm.h 头文件中

struct pwm_ops {int (*request)(struct pwm_chip *chip, struct pwm_device *pwm);   //请求PWMvoid (*free)(struct pwm_chip *chip, struct pwm_device *pwm); //释放PWMint (*config)(struct pwm_chip *chip,                 //配置PWM周期和占空比struct pwm_device *pwm,int duty_ns, int period_ns);int (*set_polarity)(struct pwm_chip *chip,          //设置PWM极性struct pwm_device *pwm,enum pwm_polarity polarity);int (*enable)(struct pwm_chip *chip, struct pwm_device *pwm);   //使能PWMvoid (*disable)(struct pwm_chip *chip, struct pwm_device *pwm);  //关闭PWMstruct module *owner;
};

PWM 子系统驱动的核心初始化 pwm_chip 结构体,然后向内核注册初始化完成以后的pwm_chip

  • 注册 pwm_chip :
int pwmchip_add(struct pwm_chip *chip)
//chip:要向内核注册的pwm_chip
//返回值: 0 成功;负数 失败
  • 删除 pwm_chip :
int pwmchip_remove(struct pwm_chip *chip)
//chip:要移除的pwm_chip
//返回值:0 成功;负数 失败
1.3 PWM 驱动源码分析

下面简单分析一下 Linux 内核自带的 I.MX6ULL PWM 驱动,打开驱动文件 pwm-imx.c

static const struct of_device_id imx_pwm_dt_ids[] = {{ .compatible = "fsl,imx1-pwm", .data = &imx_pwm_data_v1, },//设备树PWM节点compatible属性值为“fsl,imx27-pwm”的话就会匹配此驱动{ .compatible = "fsl,imx27-pwm", .data = &imx_pwm_data_v2, },{ /* sentinel */ }
};
......
static struct platform_driver imx_pwm_driver = {.driver = {.name = "imx-pwm",.of_match_table = imx_pwm_dt_ids,},.probe = imx_pwm_probe,.remove = imx_pwm_remove,
};module_platform_driver(imx_pwm_driver);
/**************************************************/
//imx_pwm_data_v2是一个 imx_pwm_data 类型的结构体变量
static struct imx_pwm_data imx_pwm_data_v2 = {.config = imx_pwm_config_v2,.set_enable = imx_pwm_set_enable_v2,
};

当设备树节点和驱动匹配以后 imx_pwm_probe 函数就会执行

static int imx_pwm_probe(struct platform_device *pdev) {const struct of_device_id *of_id =of_match_device(imx_pwm_dt_ids, &pdev->dev);const struct imx_pwm_data *data;struct imx_chip *imx;struct resource *r;int ret = 0;if (!of_id)return -ENODEV;//为 imx_chip 类型的结构体指针变量申请内存imx = devm_kzalloc(&pdev->dev, sizeof(*imx), GFP_KERNEL);if (imx == NULL)return -ENOMEM;......//初始化imx的 chip 成员变量imx->chip.ops = &imx_pwm_ops;imx->chip.dev = &pdev->dev;imx->chip.base = -1;imx->chip.npwm = 1;imx->chip.can_sleep = true;//从设备树中获取PWM节点中PWM控制器的地址信息,//然后再进行内存映射,就可得到PWM控制器的基地址r = platform_get_resource(pdev, IORESOURCE_MEM, 0);imx->mmio_base = devm_ioremap_resource(&pdev->dev, r);if (IS_ERR(imx->mmio_base))return PTR_ERR(imx->mmio_base);data = of_id->data;imx->config = data->config;imx->set_enable = data->set_enable;ret = pwmchip_add(&imx->chip);if (ret < 0)return ret;platform_set_drvdata(pdev, imx);return 0;
}

pwm_chip 的 ops 操作集为 imx_pwm_ops, imx_pwm_ops 定义如下:

static struct pwm_ops imx_pwm_ops = {.enable = imx_pwm_enable,     //使能PWM .disable = imx_pwm_disable,        //关闭PWM .config = imx_pwm_config,      //配置PWM .owner = THIS_MODULE,
};

整个 pwm-imx.c 文件里面,最终和 PWM 寄存器打交道的是 imx_pwm_config_v2 和 imx_pwm_set_enable_v2 这两个函数

static void imx_pwm_set_enable_v2(struct pwm_chip *chip, bool enable){struct imx_chip *imx = to_imx_chip(chip);u32 val;//读取 PWMCR 寄存器的值val = readl(imx->mmio_base + MX3_PWMCR);//如果enable为真,表示使能PWM,将PWMCR寄存器的bit0置1if (enable)val |= MX3_PWMCR_EN;else //否则表示关闭PWM,将PWMCR寄存器的bit0清0 val &= ~MX3_PWMCR_EN;//将新的val值写入到PWMCR寄存器中writel(val, imx->mmio_base + MX3_PWMCR);
}
/********************************************************************/
static int imx_pwm_config_v2(struct pwm_chip *chip,struct pwm_device *pwm, int duty_ns, int period_ns) {struct imx_chip *imx = to_imx_chip(chip);struct device *dev = chip->dev;unsigned long long c;unsigned long period_cycles, duty_cycles, prescale;unsigned int period_ms;bool enable = test_bit(PWMF_ENABLED, &pwm->flags);int wait_count = 0, fifoav;u32 cr, sr;......//根据参数duty_ns和period_ns来计算出应该写入到寄存器//里面的值duty_cycles 和 period_cyclesc = clk_get_rate(imx->clk_per);c = c * period_ns;do_div(c, 1000000000);period_cycles = c;prescale = period_cycles / 0x10000 + 1;period_cycles /= prescale;c = (unsigned long long)period_cycles * duty_ns;do_div(c, period_ns);duty_cycles = c;if (period_cycles > 2)period_cycles -= 2;elseperiod_cycles = 0;//将计算得到的duty_cycles写入PWMSAR寄存器中,设置PWM的占空比writel(duty_cycles, imx->mmio_base + MX3_PWMSAR);//将计算得到的period_cycles写入PWMPR寄存器中,设置PWM的频率writel(period_cycles, imx->mmio_base + MX3_PWMPR);cr = MX3_PWMCR_PRESCALER(prescale) |MX3_PWMCR_DOZEEN | MX3_PWMCR_WAITEN |MX3_PWMCR_DBGEN | MX3_PWMCR_CLKSRC_IPG_HIGH;if (enable)cr |= MX3_PWMCR_EN;writel(cr, imx->mmio_base + MX3_PWMCR);return 0;
}

二、PWM驱动编写

2.1 修改设备树

NXP 已经写好了 PWM 驱动,在实际使用时只需要修改设备树即可,本例中 GPIO1_IO04 可以作为 PWM3 的输出引脚,因此需要在设备树里面添加 GPIO1_IO04 引脚信息以及 PWM3 控制器对应的节点信息

  • 添加 GPIO1_IO04 引脚信息:在设备树文件中 iomuxc 节点下添加引脚信息
pinctrl_pwm3: pwm3grp {fsl,pins = <MX6UL_PAD_GPIO1_IO04__PWM3_OUT 0x110b0>;
};
  • 向 pwm3 节点追加信息:在设备树文件 pwm3 节点中添加如下信息
&pwm3 {pinctrl-names = "default";pinctrl-0 = <&pinctrl_pwm3>;clocks = <&clks IMX6UL_CLK_PWM3>,<&clks IMX6UL_CLK_PWM3>;status = "okay";
};
  • 屏蔽掉其他复用的 IO:检查是否有其他外设用到 GPIO1_IO04 和"gpio1 4",有的话屏蔽掉

设备树修改完成以后重新编译设备树,并使用新的设备树启动系统

2.2 使能 PWM 驱动

NXP 官方的 Linux 内核已经默认使能了 PWM 驱动

-> Device Drivers-> Pulse-Width Modulation (PWM) Support-> <*> i.MX PWM support

三、PWM驱动测试

3.1 PWM驱动测试

将开发板的 GPIO_4 (GPIO1_IO04) 引脚连接到示波器上,通过示波器来查看 PWM 波形图。可以直接在用户层来配置 PWM,进入目录 /sys/class/pwm 中

上图中 pwmchip0 ~ pwmchip7 对应 I.MX6ULL 的 PWM1 ~ PWM8,这里要用到 pwmchip2

  • 调出 pwmchip2 的 pwm0 子目录
echo 0 > /sys/class/pwm/pwmchip2/export
#执行完后会在pwmchip2目录下生成一个名为“pwm0”的子目录
  • 使能 PWM3
echo 1 > /sys/class/pwm/pwmchip2/pwm0/enable
  • 设置 PWM3 的频率:设置的是周期值,单位为 ns,比如 20KHz 频率的周期就是 50000ns
echo 50000 > /sys/class/pwm/pwmchip2/pwm0/period
  • 设置 PWM3 的占空比:设置的一个周期的 ON 时间,即高电平时间。比如 20KHz 频率下 20%占空比的 ON 时间就是 10000
echo 10000 > /sys/class/pwm/pwmchip2/pwm0/duty_cycle

设置完成使用示波器查看波形是否正确

3.2 PWM背光测试

有时需要在某个外设上添加 PWM 功能,比如:LCD 的背光控制就是 PWM 来完成的,下面以 PWM 背光控制为例,介绍如何在其他外设上添加 PWM 功能

首先是设备树描述,直接看内核里面关于 backlight(背光)的绑定文档,路径为
Documentation/devicetree/bindings/video/backlight/pwm-backlight.txt,此文档描述了如何创建 backlight 节点来使用 linux 内核自带的 pwm 背光驱动,必要的属性如下:

– compatible:为"pwm-backlight",可匹配到背光驱动 drivers/video/backlight/pwm_bl.c
– pwms: 指定背光使用哪一路PWM,及PWM相关的属性
– brightness-levels: 背光等级数组,范围 0 ~ 255,对应占空比为 0% ~ 100%
– default-brightness-level: 默认的背光等级,这里是数索引编号,不是具体的数值
– power-supply: 支持的电压,此属性可以不需要

以 ALPHA 开发板为例,看一下 PWM 背光节点是如何设置的,打开设备树文件,找到如下所示节点内容:

backlight {compatible = "pwm-backlight";  //必须为“pwm-backlight”pwms = <&pwm1 0 5000000>;        //使用pwm1、通道0、周期为 5000000ns//背光等级数组,一共8个等级,索引编号从0到7brightness-levels = <0 4 8 16 32 64 128 255>;    default-brightness-level = <7>;  //背光默认处于第7等级,即255,为100%占空比status = "okay";
};

Linux驱动开发|PWM驱动相关推荐

  1. Linux驱动开发 -- touch驱动注册

    Linux i2c驱动开发 – touch 驱动 文章目录 Linux i2c驱动开发 -- touch 驱动 前言 一.i2c 驱动框架 二.Linux的MODULE声明 1. MODULE相关声明 ...

  2. 解析Linux内核源码中数据同步问题丨C++后端开发丨Linux服务器开发丨Linux内核开发丨驱动开发丨嵌入式开发丨内核操作系统

    剖析Linux内核源码数据同步 1.pdflush机制原理 2.超级块同步/inode同步 3.拥塞及强制回写技术 视频讲解如下,点击观看: 解析Linux内核源码中数据同步问题丨C++后端开发丨Li ...

  3. Linux驱动开发——串口设备驱动

    Linux驱动开发--串口设备驱动 一.串口简介 串口全称叫做串行接口,通常也叫做 COM 接口,串行接口指的是数据一个一个的顺序传输,通信线路简单.使用两条线即可实现双向通信,一条用于发送,一条用于 ...

  4. Linux嵌入式驱动开发02——驱动编译到内核

    文章目录 全系列传送门 make menuconfig图形化配置界面 1. 怎么进入到make menuconfig图形化界面? 2. make menuconfig图形化界面的操作 3. 退出 4. ...

  5. STM32MP157驱动开发——SPI驱动

    STM32MP157驱动开发--SPI驱动 一.简介 1.SPI介绍 2.STM32MP1 SPI介绍 3. ICM-20608 简介 4.Linux下的SPI框架 二.驱动开发 1)IO 的 pin ...

  6. STM32F427库函数配置DMA驱动TIM5 PWM驱动WS2812B单总线全彩RGB

    STM32F427库函数配置DMA驱动TIM5 PWM驱动WS2812B单总线全彩RGB 系列文章传送门: STM32F4多路PWM DMA控制千颗WS2812/SK6812配置过程全解析 STM32 ...

  7. linux驱动开发音频设备驱动,linux驱动开发—基于Device tree机制的驱动编写

    摘要:媒介 Device Tree是一种用去描绘硬件的数据布局,类似板级描绘说话,发源于OpenFirmware(OF).正在现在遍及应用的kernel 2.6.x版本中,对分歧仄台.分歧硬件,往] ...

  8. Linux 设备驱动开发思想 —— 驱动分层与驱动分离

    前面我们学习I2C.USB.SD驱动时,有没有发现一个共性,就是在驱动开发时,每个驱动都分层三部分,由上到下分别是: 1.XXX 设备驱动 2.XXX 核心层 3.XXX 主机控制器驱动 而需要我们编 ...

  9. linux摄像头内核驱动开发,怎么在Linux下开发摄像头驱动

    无根之木不活,无基之楼不立,无论是学习哪个领域知识,基础是重中之重. 针对学习linux驱动,我们来仔细谈谈: 个人认为C语言和数据结构就是重中之重!Linux系统最优秀的地方就在于内核.无论是进程调 ...

  10. I.MX6ULL ARM驱动开发---网络设备驱动框架

    引言   网络驱动是 linux 里面驱动三巨头之一,linux 下的网络功能非常强大,嵌入式 linux 中也常常用到网络功能.前面我们已经讲过了字符设备驱动和块设备驱动,本章我们就来学习一下 li ...

最新文章

  1. Fourinone2.0对分布式文件的简化操作
  2. python简单编程-编程中最简单的语言Python,这样学或许更容易
  3. 【收藏备用】服务器基本故障及排查方法
  4. pytorch的梯度计算以及backward方法
  5. 定义一个集合类Set,(考察动态数组的建立)
  6. 13.小结Action
  7. python 查找文件名包含指定字符串
  8. nodejs获得服务器响应,轻松创建nodejs服务器(6):作出响应
  9. 【转】矩阵分解 - SVD
  10. 关于QtCharts中的映射器与模型的使用
  11. ios沙盒查找图片展示
  12. 表情识别(六)--局部特征学习和Handcrafted特征结合
  13. phalapi做登录检测_1.4 PhalApi 2.x 接口响应与在线调试
  14. c语言产品信息管理课程设计,商品信息管理系统(C语言课程设计).doc
  15. linux退出热键_linux用户退出登录的命令介绍
  16. c语言程序输出s是什么,C语言中printf格式化输出函数
  17. 镜像 网站 linux 程序,腾讯开源镜像网站(腾讯云软件源)地址,附使用说明
  18. 如何用Mindmanager画思维导图
  19. ubuntu20.04关闭内核自动更新
  20. oracle linux7.9安装 Oracle Enterprise Manager Cloud Control13.5

热门文章

  1. win7调整屏幕亮度_win7系统设置护眼色后,不生效怎么办?
  2. 计算机ps基础考试题,2014计算机一级考试PS及基础模拟试题
  3. 判断一个时间点是否在一个时间段的方法
  4. 如何将YouTube视频插入PowerPoint演示文稿
  5. 2013.06.25《流行音乐的分类》
  6. 数据库查询三个以上名字重复的数据
  7. mysql多表查询去重复数据_SQL重复记录查询 查询多个字段、多表查询、删除重复记录的方法...
  8. h桥控制电机刹车_基于H桥控制直流电机驱动电路设计
  9. 微信信用分-服务商模式(免密代扣-免确认订单模式——智能零售-称重柜)
  10. 戴尔r410服务器raid装系统,Dell R410 Raid磁盘阵列驱动