diff options
author | Antoine Ténart <antoine.tenart@free-electrons.com> | 2015-10-02 10:59:47 -0400 |
---|---|---|
committer | Thierry Reding <thierry.reding@gmail.com> | 2015-10-06 03:40:44 -0400 |
commit | 59d5c8b1537da3bc6a18a4912727bc9287e6af91 (patch) | |
tree | 867c5446d0c60f5ddc5baab0454352154570321c /drivers/pwm/pwm-berlin.c | |
parent | 67673dce9a9c765f38ba103b11e948488342f230 (diff) |
pwm: Add support for the Berlin PWM controller
Add a PWM controller driver for the Marvell Berlin SoCs. This PWM
controller has 4 channels.
Signed-off-by: Antoine Tenart <antoine.tenart@free-electrons.com>
Acked-by: Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com>
Signed-off-by: Thierry Reding <thierry.reding@gmail.com>
Diffstat (limited to 'drivers/pwm/pwm-berlin.c')
-rw-r--r-- | drivers/pwm/pwm-berlin.c | 219 |
1 files changed, 219 insertions, 0 deletions
diff --git a/drivers/pwm/pwm-berlin.c b/drivers/pwm/pwm-berlin.c new file mode 100644 index 000000000000..65108129d505 --- /dev/null +++ b/drivers/pwm/pwm-berlin.c | |||
@@ -0,0 +1,219 @@ | |||
1 | /* | ||
2 | * Marvell Berlin PWM driver | ||
3 | * | ||
4 | * Copyright (C) 2015 Marvell Technology Group Ltd. | ||
5 | * | ||
6 | * Author: Antoine Tenart <antoine.tenart@free-electrons.com> | ||
7 | * | ||
8 | * This file is licensed under the terms of the GNU General Public | ||
9 | * License version 2. This program is licensed "as is" without any | ||
10 | * warranty of any kind, whether express or implied. | ||
11 | */ | ||
12 | |||
13 | #include <linux/clk.h> | ||
14 | #include <linux/io.h> | ||
15 | #include <linux/kernel.h> | ||
16 | #include <linux/module.h> | ||
17 | #include <linux/platform_device.h> | ||
18 | #include <linux/pwm.h> | ||
19 | |||
20 | #define BERLIN_PWM_EN 0x0 | ||
21 | #define BERLIN_PWM_ENABLE BIT(0) | ||
22 | #define BERLIN_PWM_CONTROL 0x4 | ||
23 | #define BERLIN_PWM_PRESCALE_MASK 0x7 | ||
24 | #define BERLIN_PWM_PRESCALE_MAX 4096 | ||
25 | #define BERLIN_PWM_INVERT_POLARITY BIT(3) | ||
26 | #define BERLIN_PWM_DUTY 0x8 | ||
27 | #define BERLIN_PWM_TCNT 0xc | ||
28 | #define BERLIN_PWM_MAX_TCNT 65535 | ||
29 | |||
30 | struct berlin_pwm_chip { | ||
31 | struct pwm_chip chip; | ||
32 | struct clk *clk; | ||
33 | void __iomem *base; | ||
34 | }; | ||
35 | |||
36 | static inline struct berlin_pwm_chip *to_berlin_pwm_chip(struct pwm_chip *chip) | ||
37 | { | ||
38 | return container_of(chip, struct berlin_pwm_chip, chip); | ||
39 | } | ||
40 | |||
41 | static const u32 prescaler_table[] = { | ||
42 | 1, 4, 8, 16, 64, 256, 1024, 4096 | ||
43 | }; | ||
44 | |||
45 | static inline u32 berlin_pwm_readl(struct berlin_pwm_chip *chip, | ||
46 | unsigned int channel, unsigned long offset) | ||
47 | { | ||
48 | return readl_relaxed(chip->base + channel * 0x10 + offset); | ||
49 | } | ||
50 | |||
51 | static inline void berlin_pwm_writel(struct berlin_pwm_chip *chip, | ||
52 | unsigned int channel, u32 value, | ||
53 | unsigned long offset) | ||
54 | { | ||
55 | writel_relaxed(value, chip->base + channel * 0x10 + offset); | ||
56 | } | ||
57 | |||
58 | static int berlin_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm_dev, | ||
59 | int duty_ns, int period_ns) | ||
60 | { | ||
61 | struct berlin_pwm_chip *pwm = to_berlin_pwm_chip(chip); | ||
62 | unsigned int prescale; | ||
63 | u32 value, duty, period; | ||
64 | u64 cycles, tmp; | ||
65 | |||
66 | cycles = clk_get_rate(pwm->clk); | ||
67 | cycles *= period_ns; | ||
68 | do_div(cycles, NSEC_PER_SEC); | ||
69 | |||
70 | for (prescale = 0; prescale < ARRAY_SIZE(prescaler_table); prescale++) { | ||
71 | tmp = cycles; | ||
72 | do_div(tmp, prescaler_table[prescale]); | ||
73 | |||
74 | if (tmp <= BERLIN_PWM_MAX_TCNT) | ||
75 | break; | ||
76 | } | ||
77 | |||
78 | if (tmp > BERLIN_PWM_MAX_TCNT) | ||
79 | return -ERANGE; | ||
80 | |||
81 | period = tmp; | ||
82 | cycles = tmp * duty_ns; | ||
83 | do_div(cycles, period_ns); | ||
84 | duty = cycles; | ||
85 | |||
86 | value = berlin_pwm_readl(pwm, pwm_dev->hwpwm, BERLIN_PWM_CONTROL); | ||
87 | value &= ~BERLIN_PWM_PRESCALE_MASK; | ||
88 | value |= prescale; | ||
89 | berlin_pwm_writel(pwm, pwm_dev->hwpwm, value, BERLIN_PWM_CONTROL); | ||
90 | |||
91 | berlin_pwm_writel(pwm, pwm_dev->hwpwm, duty, BERLIN_PWM_DUTY); | ||
92 | berlin_pwm_writel(pwm, pwm_dev->hwpwm, period, BERLIN_PWM_TCNT); | ||
93 | |||
94 | return 0; | ||
95 | } | ||
96 | |||
97 | static int berlin_pwm_set_polarity(struct pwm_chip *chip, | ||
98 | struct pwm_device *pwm_dev, | ||
99 | enum pwm_polarity polarity) | ||
100 | { | ||
101 | struct berlin_pwm_chip *pwm = to_berlin_pwm_chip(chip); | ||
102 | u32 value; | ||
103 | |||
104 | value = berlin_pwm_readl(pwm, pwm_dev->hwpwm, BERLIN_PWM_CONTROL); | ||
105 | |||
106 | if (polarity == PWM_POLARITY_NORMAL) | ||
107 | value &= ~BERLIN_PWM_INVERT_POLARITY; | ||
108 | else | ||
109 | value |= BERLIN_PWM_INVERT_POLARITY; | ||
110 | |||
111 | berlin_pwm_writel(pwm, pwm_dev->hwpwm, value, BERLIN_PWM_CONTROL); | ||
112 | |||
113 | return 0; | ||
114 | } | ||
115 | |||
116 | static int berlin_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm_dev) | ||
117 | { | ||
118 | struct berlin_pwm_chip *pwm = to_berlin_pwm_chip(chip); | ||
119 | u32 value; | ||
120 | |||
121 | value = berlin_pwm_readl(pwm, pwm_dev->hwpwm, BERLIN_PWM_EN); | ||
122 | value |= BERLIN_PWM_ENABLE; | ||
123 | berlin_pwm_writel(pwm, pwm_dev->hwpwm, value, BERLIN_PWM_EN); | ||
124 | |||
125 | return 0; | ||
126 | } | ||
127 | |||
128 | static void berlin_pwm_disable(struct pwm_chip *chip, | ||
129 | struct pwm_device *pwm_dev) | ||
130 | { | ||
131 | struct berlin_pwm_chip *pwm = to_berlin_pwm_chip(chip); | ||
132 | u32 value; | ||
133 | |||
134 | value = berlin_pwm_readl(pwm, pwm_dev->hwpwm, BERLIN_PWM_EN); | ||
135 | value &= ~BERLIN_PWM_ENABLE; | ||
136 | berlin_pwm_writel(pwm, pwm_dev->hwpwm, value, BERLIN_PWM_EN); | ||
137 | } | ||
138 | |||
139 | static const struct pwm_ops berlin_pwm_ops = { | ||
140 | .config = berlin_pwm_config, | ||
141 | .set_polarity = berlin_pwm_set_polarity, | ||
142 | .enable = berlin_pwm_enable, | ||
143 | .disable = berlin_pwm_disable, | ||
144 | .owner = THIS_MODULE, | ||
145 | }; | ||
146 | |||
147 | static const struct of_device_id berlin_pwm_match[] = { | ||
148 | { .compatible = "marvell,berlin-pwm" }, | ||
149 | { }, | ||
150 | }; | ||
151 | MODULE_DEVICE_TABLE(of, berlin_pwm_match); | ||
152 | |||
153 | static int berlin_pwm_probe(struct platform_device *pdev) | ||
154 | { | ||
155 | struct berlin_pwm_chip *pwm; | ||
156 | struct resource *res; | ||
157 | int ret; | ||
158 | |||
159 | pwm = devm_kzalloc(&pdev->dev, sizeof(*pwm), GFP_KERNEL); | ||
160 | if (!pwm) | ||
161 | return -ENOMEM; | ||
162 | |||
163 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | ||
164 | pwm->base = devm_ioremap_resource(&pdev->dev, res); | ||
165 | if (IS_ERR(pwm->base)) | ||
166 | return PTR_ERR(pwm->base); | ||
167 | |||
168 | pwm->clk = devm_clk_get(&pdev->dev, NULL); | ||
169 | if (IS_ERR(pwm->clk)) | ||
170 | return PTR_ERR(pwm->clk); | ||
171 | |||
172 | ret = clk_prepare_enable(pwm->clk); | ||
173 | if (ret) | ||
174 | return ret; | ||
175 | |||
176 | pwm->chip.dev = &pdev->dev; | ||
177 | pwm->chip.ops = &berlin_pwm_ops; | ||
178 | pwm->chip.base = -1; | ||
179 | pwm->chip.npwm = 4; | ||
180 | pwm->chip.can_sleep = true; | ||
181 | pwm->chip.of_xlate = of_pwm_xlate_with_flags; | ||
182 | pwm->chip.of_pwm_n_cells = 3; | ||
183 | |||
184 | ret = pwmchip_add(&pwm->chip); | ||
185 | if (ret < 0) { | ||
186 | dev_err(&pdev->dev, "failed to add PWM chip: %d\n", ret); | ||
187 | clk_disable_unprepare(pwm->clk); | ||
188 | return ret; | ||
189 | } | ||
190 | |||
191 | platform_set_drvdata(pdev, pwm); | ||
192 | |||
193 | return 0; | ||
194 | } | ||
195 | |||
196 | static int berlin_pwm_remove(struct platform_device *pdev) | ||
197 | { | ||
198 | struct berlin_pwm_chip *pwm = platform_get_drvdata(pdev); | ||
199 | int ret; | ||
200 | |||
201 | ret = pwmchip_remove(&pwm->chip); | ||
202 | clk_disable_unprepare(pwm->clk); | ||
203 | |||
204 | return ret; | ||
205 | } | ||
206 | |||
207 | static struct platform_driver berlin_pwm_driver = { | ||
208 | .probe = berlin_pwm_probe, | ||
209 | .remove = berlin_pwm_remove, | ||
210 | .driver = { | ||
211 | .name = "berlin-pwm", | ||
212 | .of_match_table = berlin_pwm_match, | ||
213 | }, | ||
214 | }; | ||
215 | module_platform_driver(berlin_pwm_driver); | ||
216 | |||
217 | MODULE_AUTHOR("Antoine Tenart <antoine.tenart@free-electrons.com>"); | ||
218 | MODULE_DESCRIPTION("Marvell Berlin PWM driver"); | ||
219 | MODULE_LICENSE("GPL v2"); | ||