1 // SPDX-License-Identifier: GPL-2.0
3 * simple driver for PWM (Pulse Width Modulator) controller
5 * Derived from pxa PWM driver by eric miao <eric.miao@marvell.com>
8 #include <linux/bitfield.h>
9 #include <linux/bitops.h>
10 #include <linux/clk.h>
11 #include <linux/delay.h>
12 #include <linux/err.h>
14 #include <linux/kernel.h>
15 #include <linux/module.h>
17 #include <linux/of_device.h>
18 #include <linux/platform_device.h>
19 #include <linux/pwm.h>
20 #include <linux/slab.h>
22 #define MX1_PWMC 0x00 /* PWM Control Register */
23 #define MX1_PWMS 0x04 /* PWM Sample Register */
24 #define MX1_PWMP 0x08 /* PWM Period Register */
26 #define MX1_PWMC_EN BIT(4)
28 struct pwm_imx1_chip
{
31 void __iomem
*mmio_base
;
35 #define to_pwm_imx1_chip(chip) container_of(chip, struct pwm_imx1_chip, chip)
37 static int pwm_imx1_clk_prepare_enable(struct pwm_chip
*chip
)
39 struct pwm_imx1_chip
*imx
= to_pwm_imx1_chip(chip
);
42 ret
= clk_prepare_enable(imx
->clk_ipg
);
46 ret
= clk_prepare_enable(imx
->clk_per
);
48 clk_disable_unprepare(imx
->clk_ipg
);
55 static void pwm_imx1_clk_disable_unprepare(struct pwm_chip
*chip
)
57 struct pwm_imx1_chip
*imx
= to_pwm_imx1_chip(chip
);
59 clk_disable_unprepare(imx
->clk_per
);
60 clk_disable_unprepare(imx
->clk_ipg
);
63 static int pwm_imx1_config(struct pwm_chip
*chip
,
64 struct pwm_device
*pwm
, int duty_ns
, int period_ns
)
66 struct pwm_imx1_chip
*imx
= to_pwm_imx1_chip(chip
);
70 * The PWM subsystem allows for exact frequencies. However,
71 * I cannot connect a scope on my device to the PWM line and
72 * thus cannot provide the program the PWM controller
73 * exactly. Instead, I'm relying on the fact that the
74 * Bootloader (u-boot or WinCE+haret) has programmed the PWM
75 * function group already. So I'll just modify the PWM sample
76 * register to follow the ratio of duty_ns vs. period_ns
79 * This is good enough for programming the brightness of
82 * The real implementation would divide PERCLK[0] first by
83 * both the prescaler (/1 .. /128) and then by CLKSEL
86 max
= readl(imx
->mmio_base
+ MX1_PWMP
);
87 p
= max
* duty_ns
/ period_ns
;
89 writel(max
- p
, imx
->mmio_base
+ MX1_PWMS
);
94 static int pwm_imx1_enable(struct pwm_chip
*chip
, struct pwm_device
*pwm
)
96 struct pwm_imx1_chip
*imx
= to_pwm_imx1_chip(chip
);
100 ret
= pwm_imx1_clk_prepare_enable(chip
);
104 value
= readl(imx
->mmio_base
+ MX1_PWMC
);
105 value
|= MX1_PWMC_EN
;
106 writel(value
, imx
->mmio_base
+ MX1_PWMC
);
111 static void pwm_imx1_disable(struct pwm_chip
*chip
, struct pwm_device
*pwm
)
113 struct pwm_imx1_chip
*imx
= to_pwm_imx1_chip(chip
);
116 value
= readl(imx
->mmio_base
+ MX1_PWMC
);
117 value
&= ~MX1_PWMC_EN
;
118 writel(value
, imx
->mmio_base
+ MX1_PWMC
);
120 pwm_imx1_clk_disable_unprepare(chip
);
123 static const struct pwm_ops pwm_imx1_ops
= {
124 .enable
= pwm_imx1_enable
,
125 .disable
= pwm_imx1_disable
,
126 .config
= pwm_imx1_config
,
127 .owner
= THIS_MODULE
,
130 static const struct of_device_id pwm_imx1_dt_ids
[] = {
131 { .compatible
= "fsl,imx1-pwm", },
134 MODULE_DEVICE_TABLE(of
, pwm_imx1_dt_ids
);
136 static int pwm_imx1_probe(struct platform_device
*pdev
)
138 struct pwm_imx1_chip
*imx
;
141 imx
= devm_kzalloc(&pdev
->dev
, sizeof(*imx
), GFP_KERNEL
);
145 platform_set_drvdata(pdev
, imx
);
147 imx
->clk_ipg
= devm_clk_get(&pdev
->dev
, "ipg");
148 if (IS_ERR(imx
->clk_ipg
)) {
149 dev_err(&pdev
->dev
, "getting ipg clock failed with %ld\n",
150 PTR_ERR(imx
->clk_ipg
));
151 return PTR_ERR(imx
->clk_ipg
);
154 imx
->clk_per
= devm_clk_get(&pdev
->dev
, "per");
155 if (IS_ERR(imx
->clk_per
)) {
156 int ret
= PTR_ERR(imx
->clk_per
);
158 if (ret
!= -EPROBE_DEFER
)
160 "failed to get peripheral clock: %d\n",
166 imx
->chip
.ops
= &pwm_imx1_ops
;
167 imx
->chip
.dev
= &pdev
->dev
;
171 r
= platform_get_resource(pdev
, IORESOURCE_MEM
, 0);
172 imx
->mmio_base
= devm_ioremap_resource(&pdev
->dev
, r
);
173 if (IS_ERR(imx
->mmio_base
))
174 return PTR_ERR(imx
->mmio_base
);
176 return pwmchip_add(&imx
->chip
);
179 static int pwm_imx1_remove(struct platform_device
*pdev
)
181 struct pwm_imx1_chip
*imx
= platform_get_drvdata(pdev
);
183 pwm_imx1_clk_disable_unprepare(&imx
->chip
);
185 return pwmchip_remove(&imx
->chip
);
188 static struct platform_driver pwm_imx1_driver
= {
191 .of_match_table
= pwm_imx1_dt_ids
,
193 .probe
= pwm_imx1_probe
,
194 .remove
= pwm_imx1_remove
,
196 module_platform_driver(pwm_imx1_driver
);
198 MODULE_LICENSE("GPL v2");
199 MODULE_AUTHOR("Sascha Hauer <s.hauer@pengutronix.de>");