Linux内核笔记(驱动篇)之 【pwm驱动】

博主珍藏笔记干货在这里!

Linux内核笔记汇总【持续更新】

文章目录

  • 1. PWM简介
  • 2. pwm代码路径
  • 3. 驱动加载过程
  • 4. probe函数分析
  • 5. pwm操作方法

1. PWM简介

Linux PWM(Pulse Width Modulation)子系统是用于控制和管理嵌入式系统中的脉冲宽度调制信号的框架。PWM 是一种通过控制信号的高电平时间和低电平时间的比例来模拟模拟信号的数字技术。它在许多应用中被广泛使用,如LED亮度控制、电机速度控制、音频合成等。

2. pwm代码路径

pwm驱动代码路径:kernel/drivers/pwm

以imx6ull开发板为例分析,查看编译生成文件:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PEQB0TSV-1691506160541)(https://secure2.wostatic.cn/static/p35Y3fVJDNV5BagHjE7BRX/image.png?auth_key=1691500558-az4X58xZfAZs4aWoqaseDV-0-9d2da81492e040e030ab534bd1c52420)]

可以看到编译了4个*.o文件,对应着4个.c文件。

  1. pwm-fsl-ftm: 这个驱动程序用于 Freescale Timer Module (FTM) 控制器。FTM 是一种定时器模块,可以用于产生 PWM 信号。这个驱动程序提供了对 FTM 控制器的访问和配置,使其可以用于生成 PWM 信号,例如用于 LED 控制、电机驱动等应用。
  2. pwm-imx: 这个驱动程序用于 i.MX 系列处理器的 PWM 控制器。i.MX 系列处理器通常具有多个 PWM 控制器,用于生成 PWM 信号。pwm-imx 驱动程序提供了对 i.MX 系列 PWM 控制器的访问和配置,使其可以用于各种 PWM 应用,如 LED 亮度调节、电机速度控制等。
  3. core是pwm的核心层。
  4. sysfs是应用层的接口。

3. 驱动加载过程

以pwm-imx为例,

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,
};

我们都知道linux的平台设备驱动。

驱动:可以理解为就是我们这个驱动代码。

设备:可以理解成设备树。

驱动跟设备匹配的过程简单理解就是设备树里有没有对应的定义(of_match_table 里面的信息),如果有,就会执行probe函数,这就是驱动加载的过程。

static const struct of_device_id imx_pwm_dt_ids[] = {
  { .compatible = "fsl,imx1-pwm", .data = &imx_pwm_data_v1, },
  { .compatible = "fsl,imx27-pwm", .data = &imx_pwm_data_v2, },
  { /* sentinel */ }
};

在驱动里会定义这么一个结构体,在设备树里也会有一个compatible一样字段的节点,匹配时会根据这个compatible字段判断一致,则执行probe函数。


我们可以在kernel/arch/arm/boot/dts里直接grep搜索一下这两个字段,必然能找到dts里与其对应的节点。imx6ull这款开发板,使用的dts就会包含imx6ul.dtsi。dtsi文件可以理解成C文件对应的头文件。dts可以include dtsi文件。

zrc@zrc:~/sda3/code/1.codeup/imxplore/os/kernel/arch/arm/boot/dts$ grep -nr "fsl,imx1-pwm"
imx1.dtsi:130: compatible = "fsl,imx1-pwm";
zrc@zrc:~/sda3/code/1.codeup/imxplore/os/kernel/arch/arm/boot/dts$ grep -nr "fsl,imx27-pwm"
imx6qdl.dtsi:448: compatible = "fsl,imx6q-pwm", "fsl,imx27-pwm";
imx6qdl.dtsi:459: compatible = "fsl,imx6q-pwm", "fsl,imx27-pwm";
imx6qdl.dtsi:470: compatible = "fsl,imx6q-pwm", "fsl,imx27-pwm";
imx6qdl.dtsi:481: compatible = "fsl,imx6q-pwm", "fsl,imx27-pwm";
imx6sl.dtsi:332:  compatible = "fsl,imx6sl-pwm", "fsl,imx27-pwm";
imx6sl.dtsi:342:  compatible = "fsl,imx6sl-pwm", "fsl,imx27-pwm";
imx6sl.dtsi:352:  compatible = "fsl,imx6sl-pwm", "fsl,imx27-pwm";
imx6sl.dtsi:362:  compatible = "fsl,imx6sl-pwm", "fsl,imx27-pwm";
imx6sx.dtsi:385:  compatible = "fsl,imx6sx-pwm", "fsl,imx27-pwm";
imx6sx.dtsi:395:  compatible = "fsl,imx6sx-pwm", "fsl,imx27-pwm";
imx6sx.dtsi:405:  compatible = "fsl,imx6sx-pwm", "fsl,imx27-pwm";
imx6sx.dtsi:415:  compatible = "fsl,imx6sx-pwm", "fsl,imx27-pwm";
imx6sx.dtsi:1254: compatible = "fsl,imx6sx-pwm", "fsl,imx27-pwm";
imx6sx.dtsi:1264: compatible = "fsl,imx6sx-pwm", "fsl,imx27-pwm";
imx6sx.dtsi:1274: compatible = "fsl,imx6sx-pwm", "fsl,imx27-pwm";
imx6sx.dtsi:1284: compatible = "fsl,imx6sx-pwm", "fsl,imx27-pwm";
imx27.dtsi:150:  compatible = "fsl,imx27-pwm";
imx6ul.dtsi:343: compatible = "fsl,imx6ul-pwm", "fsl,imx27-pwm";
imx6ul.dtsi:354: compatible = "fsl,imx6ul-pwm", "fsl,imx27-pwm";
imx6ul.dtsi:365: compatible = "fsl,imx6ul-pwm", "fsl,imx27-pwm";
imx6ul.dtsi:376: compatible = "fsl,imx6ul-pwm", "fsl,imx27-pwm";
imx6ul.dtsi:691: compatible = "fsl,imx6ul-pwm", "fsl,imx27-pwm";
imx6ul.dtsi:702: compatible = "fsl,imx6ul-pwm", "fsl,imx27-pwm";
imx6ul.dtsi:713: compatible = "fsl,imx6ul-pwm", "fsl,imx27-pwm";
imx6ul.dtsi:724: compatible = "fsl,imx6ul-pwm", "fsl,imx27-pwm";
imx7s.dtsi:633: compatible = "fsl,imx7d-pwm", "fsl,imx27-pwm";
imx7s.dtsi:644: compatible = "fsl,imx7d-pwm", "fsl,imx27-pwm";
imx7s.dtsi:655: compatible = "fsl,imx7d-pwm", "fsl,imx27-pwm";
imx7s.dtsi:666: compatible = "fsl,imx7d-pwm", "fsl,imx27-pwm";
imx53.dtsi:496: compatible = "fsl,imx53-pwm", "fsl,imx27-pwm";
imx53.dtsi:506: compatible = "fsl,imx53-pwm", "fsl,imx27-pwm";
imx25.dtsi:419: compatible = "fsl,imx25-pwm", "fsl,imx27-pwm";
imx25.dtsi:438: compatible = "fsl,imx25-pwm", "fsl,imx27-pwm";
imx25.dtsi:496: compatible = "fsl,imx25-pwm", "fsl,imx27-pwm";
imx25.dtsi:543: compatible = "fsl,imx25-pwm", "fsl,imx27-pwm";
imx50.dtsi:309: compatible = "fsl,imx50-pwm", "fsl,imx27-pwm";
imx50.dtsi:319: compatible = "fsl,imx50-pwm", "fsl,imx27-pwm";
imx51.dtsi:388: compatible = "fsl,imx51-pwm", "fsl,imx27-pwm";
imx51.dtsi:398: compatible = "fsl,imx51-pwm", "fsl,imx27-pwm";

随便看其一个pwm节点:

pwm7: pwm@020f8000 {
  compatible = "fsl,imx6ul-pwm", "fsl,imx27-pwm";
  reg = <0x020f8000 0x4000>;
  interrupts = ;
  clocks = <&clks IMX6UL_CLK_PWM7>,
     <&clks IMX6UL_CLK_PWM7>;
  clock-names = "ipg", "per";
  #pwm-cells = <2>;
  status = "disabled";
};

可以看到compatible有两个字段,用于设备驱动匹配。

reg用于描述寄存器的地址和长度,地址是0x020f8000 ,长度是0x4000。

interrupts用于描述中断相关的信息。GIC_SPI 是中断控制器,116是中断号,IRQ_TYPE_LEVEL_HIGH表示高电平触发中断。不一样的所有的节点都有这个。

clocks描述的是时钟相关信息。

status比较重要,它相当于使能。所有的节点必须有这个,设备树里要定义成okay,驱动才会执行probe函数。

4. probe函数分析

设备驱动匹配后,就会执行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;

  data = of_id->data;

  // 分配空间给struct imx_chip
  imx = devm_kzalloc(&pdev->dev, sizeof(*imx), GFP_KERNEL);
  if (imx == NULL)
    return -ENOMEM;
  // 获取时钟信息
  imx->clk_per = devm_clk_get(&pdev->dev, "per");
  if (IS_ERR(imx->clk_per)) {
    dev_err(&pdev->dev, "getting per clock failed with %ld\n",
        PTR_ERR(imx->clk_per));
    return PTR_ERR(imx->clk_per);
  }

  imx->chip.ops = data->ops;
  imx->chip.dev = &pdev->dev;
  imx->chip.base = -1; // number of first PWM controlled by this chip
  imx->chip.npwm = 1; // number of PWMs controlled by this chip

  if (data->polarity_supported) {
    dev_dbg(&pdev->dev, "PWM supports output inversion\n");
    imx->chip.of_xlate = of_pwm_xlate_with_flags;
    imx->chip.of_pwm_n_cells = 3;
  }

  r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
  // 将dts里的reg映射成虚拟地址
  imx->mmio_base = devm_ioremap_resource(&pdev->dev, r);
  if (IS_ERR(imx->mmio_base))
    return PTR_ERR(imx->mmio_base);

  // 注册pwm驱动
  ret = pwmchip_add(&imx->chip);
  if (ret < 0)
    return ret;

  platform_set_drvdata(pdev, imx);
  return 0;
}

从linux的驱动分类来说,pwm属于字符设备驱动,字符设备驱动想读来说还是比较简单的,来看下它的注册过程。

首先我们要知道这么一个概念,基本上所有的驱动,都会定义一个与芯片控制器相关的结构体,因为linux也是面向对象编程的思想,所以会对其控制器抽象出一个结构体,这里就是struct imx_chip,同系列的芯片但PWM控制器可能有有点差异,所以抽象出struct imx_pwm_data这个结构体,但其实这个并不是所有的驱动都会定义。

我们重点关注的还是struct imx_chip

struct imx_chip {
  struct clk  *clk_per;

  void __iomem  *mmio_base;

  struct pwm_chip  chip;
};

该这结构体只定义了3个成员,其中clk是跟芯片有关的,mmio_base就是用来存放pwm寄存器基地址映射出来的虚拟地址,基本上所有的驱动都是这个套路。struct pwm_chip是pwm核心层抽象出来的数据结构,对所有厂家的PWM控制器都通用的,因此,pwm_chip这个结构体也是非常重要的。

/**
 * struct pwm_chip - abstract a PWM controller
 * @dev: device providing the PWMs
 * @list: list node for internal use
 * @ops: callbacks for this PWM controller
 * @base: number of first PWM controlled by this chip
 * @npwm: number of PWMs controlled by this chip
 * @pwms: array of PWM devices allocated by the framework
 * @of_xlate: request a PWM device given a device tree PWM specifier
 * @of_pwm_n_cells: number of cells expected in the device tree PWM specifier
 */
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;
};

对于pwm_chip ,重点是npwm、pwms、base和pwm_ops。

static const struct pwm_ops imx_pwm_ops_v1 = {
  .enable = imx_pwm_enable_v1,
  .disable = imx_pwm_disable_v1,
  .config = imx_pwm_config_v1,
  .owner = THIS_MODULE,
};

pwm_ops是提供给pwm核心层操作的接口。

enable就是用来设置寄存器,用来打开pwm输出的。

disable也是用来设置寄存器,用来关闭pwm输出的。

.config就是用来根据核心层传进来的周期和占空比,计算写进寄存器的值,然后再写进寄存器。当然,pwm核心层,就是应用层传进来的,通常应用层会操作sysfs里的接口来操作pwm。

5. pwm操作方法

进入到/sys/class/pwm,可以看到pwmchip0,这个就是根据你的pwm控制器创建的。

Linux内核笔记(驱动篇)之 【pwm驱动】_第1张图片

进入pwmchip0目录,然后执行echo X > export就行,X表示pwm通道。

你可能感兴趣的:(linux,嵌入式,驱动开发)