aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/pwm/pwm-imx1.c
diff options
context:
space:
mode:
authorUwe Kleine-König <u.kleine-koenig@pengutronix.de>2019-01-07 14:53:52 -0500
committerThierry Reding <thierry.reding@gmail.com>2019-01-16 02:45:33 -0500
commitd80f8206905c1a8c3857d90f12bbfd6293b48a4b (patch)
treea41575aea576d5f2c02c1b979d0ebf2e29ccb872 /drivers/pwm/pwm-imx1.c
parentb9a5c60bc2f65561535dc05d0c740aa6e9e3bdf2 (diff)
pwm: imx: Split into two drivers
The two PWM implementations called v1 (for i.MX1 and i.MX21) and v2 (for i.MX27 and later) have nothing in common apart from needing two clocks named "per" and "ipg" and being integrated in a SoC named i.MX. So split the file containing the two disjunct drivers into two files and two complete separate drivers. Signed-off-by: Uwe Kleine-König <u.kleine-koenig@pengutronix.de> [thierry.reding@gmail.com: fix a modular build issue] Signed-off-by: Thierry Reding <thierry.reding@gmail.com>
Diffstat (limited to 'drivers/pwm/pwm-imx1.c')
-rw-r--r--drivers/pwm/pwm-imx1.c199
1 files changed, 199 insertions, 0 deletions
diff --git a/drivers/pwm/pwm-imx1.c b/drivers/pwm/pwm-imx1.c
new file mode 100644
index 000000000000..f8b2c2e001a7
--- /dev/null
+++ b/drivers/pwm/pwm-imx1.c
@@ -0,0 +1,199 @@
1// SPDX-License-Identifier: GPL-2.0
2/*
3 * simple driver for PWM (Pulse Width Modulator) controller
4 *
5 * Derived from pxa PWM driver by eric miao <eric.miao@marvell.com>
6 */
7
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>
13#include <linux/io.h>
14#include <linux/kernel.h>
15#include <linux/module.h>
16#include <linux/of.h>
17#include <linux/of_device.h>
18#include <linux/platform_device.h>
19#include <linux/pwm.h>
20#include <linux/slab.h>
21
22#define MX1_PWMC 0x00 /* PWM Control Register */
23#define MX1_PWMS 0x04 /* PWM Sample Register */
24#define MX1_PWMP 0x08 /* PWM Period Register */
25
26#define MX1_PWMC_EN BIT(4)
27
28struct pwm_imx1_chip {
29 struct clk *clk_ipg;
30 struct clk *clk_per;
31 void __iomem *mmio_base;
32 struct pwm_chip chip;
33};
34
35#define to_pwm_imx1_chip(chip) container_of(chip, struct pwm_imx1_chip, chip)
36
37static int pwm_imx1_clk_prepare_enable(struct pwm_chip *chip)
38{
39 struct pwm_imx1_chip *imx = to_pwm_imx1_chip(chip);
40 int ret;
41
42 ret = clk_prepare_enable(imx->clk_ipg);
43 if (ret)
44 return ret;
45
46 ret = clk_prepare_enable(imx->clk_per);
47 if (ret) {
48 clk_disable_unprepare(imx->clk_ipg);
49 return ret;
50 }
51
52 return 0;
53}
54
55static void pwm_imx1_clk_disable_unprepare(struct pwm_chip *chip)
56{
57 struct pwm_imx1_chip *imx = to_pwm_imx1_chip(chip);
58
59 clk_disable_unprepare(imx->clk_per);
60 clk_disable_unprepare(imx->clk_ipg);
61}
62
63static int pwm_imx1_config(struct pwm_chip *chip,
64 struct pwm_device *pwm, int duty_ns, int period_ns)
65{
66 struct pwm_imx1_chip *imx = to_pwm_imx1_chip(chip);
67 u32 max, p;
68
69 /*
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
77 * accordingly.
78 *
79 * This is good enough for programming the brightness of
80 * the LCD backlight.
81 *
82 * The real implementation would divide PERCLK[0] first by
83 * both the prescaler (/1 .. /128) and then by CLKSEL
84 * (/2 .. /16).
85 */
86 max = readl(imx->mmio_base + MX1_PWMP);
87 p = max * duty_ns / period_ns;
88
89 writel(max - p, imx->mmio_base + MX1_PWMS);
90
91 return 0;
92}
93
94static int pwm_imx1_enable(struct pwm_chip *chip, struct pwm_device *pwm)
95{
96 struct pwm_imx1_chip *imx = to_pwm_imx1_chip(chip);
97 u32 value;
98 int ret;
99
100 ret = pwm_imx1_clk_prepare_enable(chip);
101 if (ret < 0)
102 return ret;
103
104 value = readl(imx->mmio_base + MX1_PWMC);
105 value |= MX1_PWMC_EN;
106 writel(value, imx->mmio_base + MX1_PWMC);
107
108 return 0;
109}
110
111static void pwm_imx1_disable(struct pwm_chip *chip, struct pwm_device *pwm)
112{
113 struct pwm_imx1_chip *imx = to_pwm_imx1_chip(chip);
114 u32 value;
115
116 value = readl(imx->mmio_base + MX1_PWMC);
117 value &= ~MX1_PWMC_EN;
118 writel(value, imx->mmio_base + MX1_PWMC);
119
120 pwm_imx1_clk_disable_unprepare(chip);
121}
122
123static 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,
128};
129
130static const struct of_device_id pwm_imx1_dt_ids[] = {
131 { .compatible = "fsl,imx1-pwm", },
132 { /* sentinel */ }
133};
134MODULE_DEVICE_TABLE(of, pwm_imx1_dt_ids);
135
136static int pwm_imx1_probe(struct platform_device *pdev)
137{
138 struct pwm_imx1_chip *imx;
139 struct resource *r;
140
141 imx = devm_kzalloc(&pdev->dev, sizeof(*imx), GFP_KERNEL);
142 if (!imx)
143 return -ENOMEM;
144
145 platform_set_drvdata(pdev, imx);
146
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);
152 }
153
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);
157
158 if (ret != -EPROBE_DEFER)
159 dev_err(&pdev->dev,
160 "failed to get peripheral clock: %d\n",
161 ret);
162
163 return ret;
164 }
165
166 imx->chip.ops = &pwm_imx1_ops;
167 imx->chip.dev = &pdev->dev;
168 imx->chip.base = -1;
169 imx->chip.npwm = 1;
170
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);
175
176 return pwmchip_add(&imx->chip);
177}
178
179static int pwm_imx1_remove(struct platform_device *pdev)
180{
181 struct pwm_imx1_chip *imx = platform_get_drvdata(pdev);
182
183 pwm_imx1_clk_disable_unprepare(&imx->chip);
184
185 return pwmchip_remove(&imx->chip);
186}
187
188static struct platform_driver pwm_imx1_driver = {
189 .driver = {
190 .name = "pwm-imx1",
191 .of_match_table = pwm_imx1_dt_ids,
192 },
193 .probe = pwm_imx1_probe,
194 .remove = pwm_imx1_remove,
195};
196module_platform_driver(pwm_imx1_driver);
197
198MODULE_LICENSE("GPL v2");
199MODULE_AUTHOR("Sascha Hauer <s.hauer@pengutronix.de>");