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

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

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

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 = <GIC_SPI 116 IRQ_TYPE_LEVEL_HIGH>;
  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控制器创建的。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5c61mosY-1691506160541)(https://secure2.wostatic.cn/static/pLSzY83VUsE38DTPVSGh8M/image.png?auth_key=1691505741-k85ZRD84oPqEVtKECt6nGR-0-6913bcb01fe8e6240acfe6f6c34cde0f)]

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

  • 2
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Linux内核中提供了PWM框架,可以通过该框架来实现PWM输入驱动PWM输入驱动是通过读取PWM信号的占空比和周期来实现的。下面是一个简单的PWM输入驱动程序的实现步骤: 1. 定义PWM输入设备结构体,包含以下成员: ``` struct pwm_input_device { struct pwm_device *pwm; struct work_struct work; u64 period; u64 duty_cycle; }; ``` 其中,pwm_device结构体用于表示PWM设备,work_struct用于异步处理PWM信号,period和duty_cycle表示PWM信号的周期和占空比。 2. 实现PWM输入设备的probe函数,在该函数中初始化PWM设备并注册PWM输入设备: ``` static int pwm_input_probe(struct platform_device *pdev) { struct pwm_input_device *dev; struct pwm_device *pwm; int ret; dev = devm_kzalloc(&pdev->dev, sizeof(*dev), GFP_KERNEL); if (!dev) return -ENOMEM; pwm = devm_pwm_get(&pdev->dev, NULL); if (IS_ERR(pwm)) return PTR_ERR(pwm); dev->pwm = pwm; ret = pwm_enable(pwm); if (ret < 0) return ret; ret = pwm_input_register(dev, &pdev->dev, "pwm_input", pwm_input_handler); if (ret < 0) pwm_disable(pwm); return ret; } ``` 3. 实现PWM输入设备的中断处理函数,在该函数中读取PWM信号的占空比和周期,并在异步处理函数中进行处理: ``` static irqreturn_t pwm_input_isr(int irq, void *data) { struct pwm_input_device *dev = data; struct pwm_device *pwm = dev->pwm; u64 period, duty_cycle; period = pwm_get_period(pwm); duty_cycle = pwm_get_duty_cycle(pwm); schedule_work(&dev->work); return IRQ_HANDLED; } ``` 4. 实现PWM输入设备的异步处理函数,在该函数中进行PWM信号的处理: ``` static void pwm_input_work(struct work_struct *work) { struct pwm_input_device *dev = container_of(work, struct pwm_input_device, work); /* 处理PWM信号 */ /* 更新周期和占空比 */ dev->period = pwm_get_period(dev->pwm); dev->duty_cycle = pwm_get_duty_cycle(dev->pwm); } ``` 通过上述步骤,就可以实现一个简单的PWM输入驱动程序。当PWM信号触发中断时,中断处理函数会读取PWM信号的占空比和周期,并调度异步处理函数进行处理。异步处理函数会更新周期和占空比,并进行PWM信号的处理。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

哆哆jarvis

众筹植发

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值