1 // SPDX-License-Identifier: GPL-2.0
3 * Clock based PWM controller
5 * Copyright (c) 2021 Nikita Travkin <nikita@trvn.ru>
7 * This is an "adapter" driver that allows PWM consumers to use
8 * system clocks with duty cycle control as PWM outputs.
11 * - Due to the fact that exact behavior depends on the underlying
12 * clock driver, various limitations are possible.
13 * - Underlying clock may not be able to give 0% or 100% duty cycle
14 * (constant off or on), exact behavior will depend on the clock.
15 * - When the PWM is disabled, the clock will be disabled as well,
16 * line state will depend on the clock.
17 * - The clk API doesn't expose the necessary calls to implement
21 #include <linux/kernel.h>
22 #include <linux/math64.h>
23 #include <linux/err.h>
24 #include <linux/module.h>
26 #include <linux/platform_device.h>
27 #include <linux/clk.h>
28 #include <linux/pwm.h>
35 static inline struct pwm_clk_chip
*to_pwm_clk_chip(struct pwm_chip
*chip
)
37 return pwmchip_get_drvdata(chip
);
40 static int pwm_clk_apply(struct pwm_chip
*chip
, struct pwm_device
*pwm
,
41 const struct pwm_state
*state
)
43 struct pwm_clk_chip
*pcchip
= to_pwm_clk_chip(chip
);
46 u64 period
= state
->period
;
47 u64 duty_cycle
= state
->duty_cycle
;
49 if (!state
->enabled
) {
50 if (pwm
->state
.enabled
) {
51 clk_disable(pcchip
->clk
);
52 pcchip
->clk_enabled
= false;
55 } else if (!pwm
->state
.enabled
) {
56 ret
= clk_enable(pcchip
->clk
);
59 pcchip
->clk_enabled
= true;
63 * We have to enable the clk before setting the rate and duty_cycle,
64 * that however results in a window where the clk is on with a
65 * (potentially) different setting. Also setting period and duty_cycle
66 * are two separate calls, so that probably isn't atomic either.
69 rate
= DIV64_U64_ROUND_UP(NSEC_PER_SEC
, period
);
70 ret
= clk_set_rate(pcchip
->clk
, rate
);
74 if (state
->polarity
== PWM_POLARITY_INVERSED
)
75 duty_cycle
= period
- duty_cycle
;
77 return clk_set_duty_cycle(pcchip
->clk
, duty_cycle
, period
);
80 static const struct pwm_ops pwm_clk_ops
= {
81 .apply
= pwm_clk_apply
,
84 static int pwm_clk_probe(struct platform_device
*pdev
)
86 struct pwm_chip
*chip
;
87 struct pwm_clk_chip
*pcchip
;
90 chip
= devm_pwmchip_alloc(&pdev
->dev
, 1, sizeof(*pcchip
));
93 pcchip
= to_pwm_clk_chip(chip
);
95 pcchip
->clk
= devm_clk_get_prepared(&pdev
->dev
, NULL
);
96 if (IS_ERR(pcchip
->clk
))
97 return dev_err_probe(&pdev
->dev
, PTR_ERR(pcchip
->clk
),
98 "Failed to get clock\n");
100 chip
->ops
= &pwm_clk_ops
;
102 ret
= pwmchip_add(chip
);
104 return dev_err_probe(&pdev
->dev
, ret
, "Failed to add pwm chip\n");
106 platform_set_drvdata(pdev
, chip
);
110 static void pwm_clk_remove(struct platform_device
*pdev
)
112 struct pwm_chip
*chip
= platform_get_drvdata(pdev
);
113 struct pwm_clk_chip
*pcchip
= to_pwm_clk_chip(chip
);
115 pwmchip_remove(chip
);
117 if (pcchip
->clk_enabled
)
118 clk_disable(pcchip
->clk
);
121 static const struct of_device_id pwm_clk_dt_ids
[] = {
122 { .compatible
= "clk-pwm", },
125 MODULE_DEVICE_TABLE(of
, pwm_clk_dt_ids
);
127 static struct platform_driver pwm_clk_driver
= {
130 .of_match_table
= pwm_clk_dt_ids
,
132 .probe
= pwm_clk_probe
,
133 .remove
= pwm_clk_remove
,
135 module_platform_driver(pwm_clk_driver
);
137 MODULE_ALIAS("platform:pwm-clk");
138 MODULE_AUTHOR("Nikita Travkin <nikita@trvn.ru>");
139 MODULE_DESCRIPTION("Clock based PWM driver");
140 MODULE_LICENSE("GPL");