Linux驱动开发|PWM驱动

PWM驱动

一、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);  //请求PWM
  void (*free)(struct pwm_chip *chip, struct pwm_device *pwm);  //释放PWM
  int (*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);   //使能PWM
  void (*disable)(struct pwm_chip *chip, struct pwm_device *pwm); //关闭PWM
  struct 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置1
  if (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_cycles
  c = 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;
  else
    period_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 驱动

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

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

三、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到7
  brightness-levels = <0 4 8 16 32 64 128 255>; 
  default-brightness-level = <7>; //背光默认处于第7等级,即255,为100%占空比
  status = "okay";
};

  • 2
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值