1 // SPDX-License-Identifier: GPL-2.0
3 * MediaTek Pulse Width Modulator driver
5 * Copyright (C) 2015 John Crispin <blogic@openwrt.org>
6 * Copyright (C) 2017 Zhi Mao <zhi.mao@mediatek.com>
10 #include <linux/err.h>
12 #include <linux/ioport.h>
13 #include <linux/kernel.h>
14 #include <linux/module.h>
15 #include <linux/clk.h>
17 #include <linux/of_device.h>
18 #include <linux/platform_device.h>
19 #include <linux/pwm.h>
20 #include <linux/slab.h>
21 #include <linux/types.h>
23 /* PWM registers and bits definitions */
28 #define PWMWAVENUM 0x28
29 #define PWMDWIDTH 0x2c
30 #define PWM45DWIDTH_FIXUP 0x30
32 #define PWM45THRES_FIXUP 0x34
33 #define PWM_CK_26M_SEL 0x210
35 #define PWM_CLK_DIV_MAX 7
37 struct pwm_mediatek_of_data
{
38 unsigned int num_pwms
;
44 * struct pwm_mediatek_chip - struct representing PWM chip
45 * @chip: linux PWM chip representation
46 * @regs: base address of PWM chip
47 * @clk_top: the top clock generator
48 * @clk_main: the clock used by PWM core
49 * @clk_pwms: the clock used by each PWM channel
50 * @clk_freq: the fix clock frequency of legacy MIPS SoC
51 * @soc: pointer to chip's platform data
53 struct pwm_mediatek_chip
{
58 struct clk
**clk_pwms
;
59 const struct pwm_mediatek_of_data
*soc
;
62 static const unsigned int pwm_mediatek_reg_offset
[] = {
63 0x0010, 0x0050, 0x0090, 0x00d0, 0x0110, 0x0150, 0x0190, 0x0220
66 static inline struct pwm_mediatek_chip
*
67 to_pwm_mediatek_chip(struct pwm_chip
*chip
)
69 return container_of(chip
, struct pwm_mediatek_chip
, chip
);
72 static int pwm_mediatek_clk_enable(struct pwm_chip
*chip
,
73 struct pwm_device
*pwm
)
75 struct pwm_mediatek_chip
*pc
= to_pwm_mediatek_chip(chip
);
78 ret
= clk_prepare_enable(pc
->clk_top
);
82 ret
= clk_prepare_enable(pc
->clk_main
);
86 ret
= clk_prepare_enable(pc
->clk_pwms
[pwm
->hwpwm
]);
88 goto disable_clk_main
;
93 clk_disable_unprepare(pc
->clk_main
);
95 clk_disable_unprepare(pc
->clk_top
);
100 static void pwm_mediatek_clk_disable(struct pwm_chip
*chip
,
101 struct pwm_device
*pwm
)
103 struct pwm_mediatek_chip
*pc
= to_pwm_mediatek_chip(chip
);
105 clk_disable_unprepare(pc
->clk_pwms
[pwm
->hwpwm
]);
106 clk_disable_unprepare(pc
->clk_main
);
107 clk_disable_unprepare(pc
->clk_top
);
110 static inline u32
pwm_mediatek_readl(struct pwm_mediatek_chip
*chip
,
111 unsigned int num
, unsigned int offset
)
113 return readl(chip
->regs
+ pwm_mediatek_reg_offset
[num
] + offset
);
116 static inline void pwm_mediatek_writel(struct pwm_mediatek_chip
*chip
,
117 unsigned int num
, unsigned int offset
,
120 writel(value
, chip
->regs
+ pwm_mediatek_reg_offset
[num
] + offset
);
123 static int pwm_mediatek_config(struct pwm_chip
*chip
, struct pwm_device
*pwm
,
124 int duty_ns
, int period_ns
)
126 struct pwm_mediatek_chip
*pc
= to_pwm_mediatek_chip(chip
);
127 u32 clkdiv
= 0, cnt_period
, cnt_duty
, reg_width
= PWMDWIDTH
,
128 reg_thres
= PWMTHRES
;
132 ret
= pwm_mediatek_clk_enable(chip
, pwm
);
137 /* Make sure we use the bus clock and not the 26MHz clock */
138 if (pc
->soc
->has_ck_26m_sel
)
139 writel(0, pc
->regs
+ PWM_CK_26M_SEL
);
141 /* Using resolution in picosecond gets accuracy higher */
142 resolution
= (u64
)NSEC_PER_SEC
* 1000;
143 do_div(resolution
, clk_get_rate(pc
->clk_pwms
[pwm
->hwpwm
]));
145 cnt_period
= DIV_ROUND_CLOSEST_ULL((u64
)period_ns
* 1000, resolution
);
146 while (cnt_period
> 8191) {
149 cnt_period
= DIV_ROUND_CLOSEST_ULL((u64
)period_ns
* 1000,
153 if (clkdiv
> PWM_CLK_DIV_MAX
) {
154 pwm_mediatek_clk_disable(chip
, pwm
);
155 dev_err(chip
->dev
, "period %d not supported\n", period_ns
);
159 if (pc
->soc
->pwm45_fixup
&& pwm
->hwpwm
> 2) {
161 * PWM[4,5] has distinct offset for PWMDWIDTH and PWMTHRES
162 * from the other PWMs on MT7623.
164 reg_width
= PWM45DWIDTH_FIXUP
;
165 reg_thres
= PWM45THRES_FIXUP
;
168 cnt_duty
= DIV_ROUND_CLOSEST_ULL((u64
)duty_ns
* 1000, resolution
);
169 pwm_mediatek_writel(pc
, pwm
->hwpwm
, PWMCON
, BIT(15) | clkdiv
);
170 pwm_mediatek_writel(pc
, pwm
->hwpwm
, reg_width
, cnt_period
);
171 pwm_mediatek_writel(pc
, pwm
->hwpwm
, reg_thres
, cnt_duty
);
173 pwm_mediatek_clk_disable(chip
, pwm
);
178 static int pwm_mediatek_enable(struct pwm_chip
*chip
, struct pwm_device
*pwm
)
180 struct pwm_mediatek_chip
*pc
= to_pwm_mediatek_chip(chip
);
184 ret
= pwm_mediatek_clk_enable(chip
, pwm
);
188 value
= readl(pc
->regs
);
189 value
|= BIT(pwm
->hwpwm
);
190 writel(value
, pc
->regs
);
195 static void pwm_mediatek_disable(struct pwm_chip
*chip
, struct pwm_device
*pwm
)
197 struct pwm_mediatek_chip
*pc
= to_pwm_mediatek_chip(chip
);
200 value
= readl(pc
->regs
);
201 value
&= ~BIT(pwm
->hwpwm
);
202 writel(value
, pc
->regs
);
204 pwm_mediatek_clk_disable(chip
, pwm
);
207 static const struct pwm_ops pwm_mediatek_ops
= {
208 .config
= pwm_mediatek_config
,
209 .enable
= pwm_mediatek_enable
,
210 .disable
= pwm_mediatek_disable
,
211 .owner
= THIS_MODULE
,
214 static int pwm_mediatek_probe(struct platform_device
*pdev
)
216 struct pwm_mediatek_chip
*pc
;
220 pc
= devm_kzalloc(&pdev
->dev
, sizeof(*pc
), GFP_KERNEL
);
224 pc
->soc
= of_device_get_match_data(&pdev
->dev
);
226 pc
->regs
= devm_platform_ioremap_resource(pdev
, 0);
227 if (IS_ERR(pc
->regs
))
228 return PTR_ERR(pc
->regs
);
230 pc
->clk_pwms
= devm_kcalloc(&pdev
->dev
, pc
->soc
->num_pwms
,
231 sizeof(*pc
->clk_pwms
), GFP_KERNEL
);
235 pc
->clk_top
= devm_clk_get(&pdev
->dev
, "top");
236 if (IS_ERR(pc
->clk_top
)) {
237 dev_err(&pdev
->dev
, "clock: top fail: %ld\n",
238 PTR_ERR(pc
->clk_top
));
239 return PTR_ERR(pc
->clk_top
);
242 pc
->clk_main
= devm_clk_get(&pdev
->dev
, "main");
243 if (IS_ERR(pc
->clk_main
)) {
244 dev_err(&pdev
->dev
, "clock: main fail: %ld\n",
245 PTR_ERR(pc
->clk_main
));
246 return PTR_ERR(pc
->clk_main
);
249 for (i
= 0; i
< pc
->soc
->num_pwms
; i
++) {
252 snprintf(name
, sizeof(name
), "pwm%d", i
+ 1);
254 pc
->clk_pwms
[i
] = devm_clk_get(&pdev
->dev
, name
);
255 if (IS_ERR(pc
->clk_pwms
[i
])) {
256 dev_err(&pdev
->dev
, "clock: %s fail: %ld\n",
257 name
, PTR_ERR(pc
->clk_pwms
[i
]));
258 return PTR_ERR(pc
->clk_pwms
[i
]);
262 platform_set_drvdata(pdev
, pc
);
264 pc
->chip
.dev
= &pdev
->dev
;
265 pc
->chip
.ops
= &pwm_mediatek_ops
;
267 pc
->chip
.npwm
= pc
->soc
->num_pwms
;
269 ret
= pwmchip_add(&pc
->chip
);
271 dev_err(&pdev
->dev
, "pwmchip_add() failed: %d\n", ret
);
278 static int pwm_mediatek_remove(struct platform_device
*pdev
)
280 struct pwm_mediatek_chip
*pc
= platform_get_drvdata(pdev
);
282 return pwmchip_remove(&pc
->chip
);
285 static const struct pwm_mediatek_of_data mt2712_pwm_data
= {
287 .pwm45_fixup
= false,
288 .has_ck_26m_sel
= false,
291 static const struct pwm_mediatek_of_data mt7622_pwm_data
= {
293 .pwm45_fixup
= false,
294 .has_ck_26m_sel
= false,
297 static const struct pwm_mediatek_of_data mt7623_pwm_data
= {
300 .has_ck_26m_sel
= false,
303 static const struct pwm_mediatek_of_data mt7628_pwm_data
= {
306 .has_ck_26m_sel
= false,
309 static const struct pwm_mediatek_of_data mt7629_pwm_data
= {
311 .pwm45_fixup
= false,
312 .has_ck_26m_sel
= false,
315 static const struct pwm_mediatek_of_data mt8183_pwm_data
= {
317 .pwm45_fixup
= false,
318 .has_ck_26m_sel
= true,
321 static const struct pwm_mediatek_of_data mt8516_pwm_data
= {
323 .pwm45_fixup
= false,
324 .has_ck_26m_sel
= true,
327 static const struct of_device_id pwm_mediatek_of_match
[] = {
328 { .compatible
= "mediatek,mt2712-pwm", .data
= &mt2712_pwm_data
},
329 { .compatible
= "mediatek,mt7622-pwm", .data
= &mt7622_pwm_data
},
330 { .compatible
= "mediatek,mt7623-pwm", .data
= &mt7623_pwm_data
},
331 { .compatible
= "mediatek,mt7628-pwm", .data
= &mt7628_pwm_data
},
332 { .compatible
= "mediatek,mt7629-pwm", .data
= &mt7629_pwm_data
},
333 { .compatible
= "mediatek,mt8183-pwm", .data
= &mt8183_pwm_data
},
334 { .compatible
= "mediatek,mt8516-pwm", .data
= &mt8516_pwm_data
},
337 MODULE_DEVICE_TABLE(of
, pwm_mediatek_of_match
);
339 static struct platform_driver pwm_mediatek_driver
= {
341 .name
= "pwm-mediatek",
342 .of_match_table
= pwm_mediatek_of_match
,
344 .probe
= pwm_mediatek_probe
,
345 .remove
= pwm_mediatek_remove
,
347 module_platform_driver(pwm_mediatek_driver
);
349 MODULE_AUTHOR("John Crispin <blogic@openwrt.org>");
350 MODULE_LICENSE("GPL v2");