1、对象
imx6ull单片机,控制其下面的pwm3的外设。关于对象的详细介绍看裸机pwm控制屏幕亮度
- 在dtsi中的位置 /soc/aips1/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>;
};
2、pwm子系统核心
-
Linux内核提供了个PWM子系统框架,编写 PWM驱动的时候一定要符合这个框架。
-
其主要核心与其它框架一样:
1、初始化 pwm_chip 结构体2、然后向内核注册初始化完成以后的pwm_chip
/*
@ PWM子系统的核心是pwm_chip 结构体
@ 定义在文件 include/linux/pwm.h中
@ pwm_chip 结构体
*/
struct pwm_chip {
struct device *dev;
struct list_head list;
const struct pwm_ops *ops; /*PWM外设的各种操作函数集合==开发人员实现*/
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结构体
*/
struct pwm_ops {
int (*request)(struct pwm_chip *chip, //请求 PWM
struct pwm_device *pwm);
void (*free)(struct pwm_chip *chip, //释放 PWM
struct pwm_device *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, //使能 PWM
struct pwm_device *pwm);
void (*disable)(struct pwm_chip *chip, //关闭 PWM
struct pwm_device *pwm);
struct module *owner;
};
/*
@ 定义在 drivers/pwm/core.c 文件中
@ 注册驱动函数
@ chip:要向内核注册的 pwm_chip
@ 返回值:0 成功;负数 失败。
*/
int pwmchip_add(struct pwm_chip *chip)
/*
@ 卸载 PWM 驱动
@ chip:要移除的pwm_chip
@ 返回值:0 成功;负数 失败
*/
int pwmchip_remove(struct pwm_chip *chip)
3、Linux内核自带的I.MX6ULL PWM驱动简述
- 打开pwm-imx.c这个文件,这是一个标准的平台设备驱动文件。
/*
@ 用于设置PWM的频率和占空比
*/
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;
....
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;
/*
* according to imx pwm RM, the real period value should be
* PERIOD value in PWMPR plus 2.
*/
if (period_cycles > 2)
period_cycles -= 2;
else
period_cycles = 0;
writel(duty_cycles, imx->mmio_base + MX3_PWMSAR); /*将计算得到的duty_cycles写入到 PWMSAR 寄存器中,设置PWM的占空比 */
writel(period_cycles, imx->mmio_base + MX3_PWMPR); /*将计算得到的period_cycles写入到PWMPR 寄存器中,设置PWM的频率*/
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
*/
static void imx_pwm_set_enable_v2(struct pwm_chip *chip, bool enable
{
struct imx_chip *imx = to_imx_chip(chip);
u32 val;
val = readl(imx->mmio_base + MX3_PWMCR); /*读取PWMCR寄存器的值*/
if (enable) /*如果 enable 为真,表示使能 PWM*/
val |= MX3_PWMCR_EN;
else /*如果enable不为真,表示关闭PWM*/
val &= ~MX3_PWMCR_EN;
writel(val, imx->mmio_base + MX3_PWMCR); /*将新的 val值写入到PWMCR寄存器*/
}
static struct imx_pwm_data imx_pwm_data_v2 = {
.config = imx_pwm_config_v2, /*最终操作 I.MX6ULL 的 PWM 外设寄存器,进行实际配置的函数*/
.set_enable = imx_pwm_set_enable_v2, /*具体使能PWM的函数*/
};
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 */ }
};
......
/*当设备树节点和驱动匹配以后 imx_pwm_probe 函数就会执行*/
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,
};
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; /*引出核心PWM 子系统核心部件pwm_chip*/
struct resource *r;
int ret = 0;
if (!of_id)
return -ENODEV;
/*=============================开始初始化pwm_chip变量*/
/*为imx_chip 类型的结构体指针变量,申请内存*/
imx = devm_kzalloc(&pdev->dev, sizeof(*imx), GFP_KERNEL
if (imx == NULL)
return -ENOMEM;
....
imx->chip.ops = &imx_pwm_ops; /*自己实现*/
imx->chip.dev = &pdev->dev;
imx->chip.base = -1;
imx->chip.npwm = 1;
imx->chip.can_sleep = true;
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的函数*/
static struct pwm_ops imx_pwm_ops = {
.enable = imx_pwm_enable,
.disable = imx_pwm_disable,
.config = imx_pwm_config,
.owner = THIS_MODULE,
};
4、实战
-
总线驱动nxp官方已经帮我们写好了,关于设备驱动我们这也没有设备。
-
所以我们的目的就只是配置总线驱动(menuconfig),
-
以及让两者进行匹配的设置(设备树)
-
1、修改IO
添加GPIO1_IO04引脚信息==pinctrl系统
-
2、在dts里面追加节点信息
-
3、防止冲突,屏蔽其它IO
检查一下设备树中有没有其他外设用到GPIO1_IO04,如果有的话需要屏蔽掉!注意,不能只屏蔽掉GPIO1_IO04 的pinctrl配置信息,也要搜索一下“gpio1 4”,看看有没有哪里用到,用到的话也要屏蔽掉设备树修改完成以后重新编译设备树,然后使用新的设备树启动系统
-
4、配置NXP官方的Linux 内核,使能PWM驱动
如何在其他外设上添加 PWM 功能
还是修改设备树
backlight {
compatible = "pwm-backlight"; /*匹配到内核自带的 PWM背光驱动,驱动文件为drivers/video/backlight/pwm_bl.c,*/
pwms = <&pwm1 0 5000000>; /*指定背光使用哪一路 PWM,以及 PWM相关的属性*/
brightness-levels = <0 4 8 16 32 64 128 255>; /*:背光等级数组,范围0~255,对应占空比为0%~100%*/
default-brightness-level = <7>; /*:默认的背光等级,*/
status = "okay";
};