]>
Commit | Line | Data |
---|---|---|
901f8f54 NT |
1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* | |
3 | * Clock based PWM controller | |
4 | * | |
5 | * Copyright (c) 2021 Nikita Travkin <[email protected]> | |
6 | * | |
7 | * This is an "adapter" driver that allows PWM consumers to use | |
8 | * system clocks with duty cycle control as PWM outputs. | |
9 | * | |
10 | * Limitations: | |
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 | |
18 | * .get_state(). | |
19 | */ | |
20 | ||
21 | #include <linux/kernel.h> | |
22 | #include <linux/math64.h> | |
23 | #include <linux/err.h> | |
24 | #include <linux/module.h> | |
25 | #include <linux/of.h> | |
26 | #include <linux/platform_device.h> | |
27 | #include <linux/clk.h> | |
28 | #include <linux/pwm.h> | |
29 | ||
30 | struct pwm_clk_chip { | |
31 | struct pwm_chip chip; | |
32 | struct clk *clk; | |
33 | bool clk_enabled; | |
34 | }; | |
35 | ||
36 | #define to_pwm_clk_chip(_chip) container_of(_chip, struct pwm_clk_chip, chip) | |
37 | ||
38 | static int pwm_clk_apply(struct pwm_chip *chip, struct pwm_device *pwm, | |
39 | const struct pwm_state *state) | |
40 | { | |
41 | struct pwm_clk_chip *pcchip = to_pwm_clk_chip(chip); | |
42 | int ret; | |
43 | u32 rate; | |
44 | u64 period = state->period; | |
45 | u64 duty_cycle = state->duty_cycle; | |
46 | ||
47 | if (!state->enabled) { | |
48 | if (pwm->state.enabled) { | |
49 | clk_disable(pcchip->clk); | |
50 | pcchip->clk_enabled = false; | |
51 | } | |
52 | return 0; | |
53 | } else if (!pwm->state.enabled) { | |
54 | ret = clk_enable(pcchip->clk); | |
55 | if (ret) | |
56 | return ret; | |
57 | pcchip->clk_enabled = true; | |
58 | } | |
59 | ||
60 | /* | |
61 | * We have to enable the clk before setting the rate and duty_cycle, | |
62 | * that however results in a window where the clk is on with a | |
63 | * (potentially) different setting. Also setting period and duty_cycle | |
64 | * are two separate calls, so that probably isn't atomic either. | |
65 | */ | |
66 | ||
67 | rate = DIV64_U64_ROUND_UP(NSEC_PER_SEC, period); | |
68 | ret = clk_set_rate(pcchip->clk, rate); | |
69 | if (ret) | |
70 | return ret; | |
71 | ||
72 | if (state->polarity == PWM_POLARITY_INVERSED) | |
73 | duty_cycle = period - duty_cycle; | |
74 | ||
75 | return clk_set_duty_cycle(pcchip->clk, duty_cycle, period); | |
76 | } | |
77 | ||
78 | static const struct pwm_ops pwm_clk_ops = { | |
79 | .apply = pwm_clk_apply, | |
80 | .owner = THIS_MODULE, | |
81 | }; | |
82 | ||
83 | static int pwm_clk_probe(struct platform_device *pdev) | |
84 | { | |
85 | struct pwm_clk_chip *pcchip; | |
86 | int ret; | |
87 | ||
88 | pcchip = devm_kzalloc(&pdev->dev, sizeof(*pcchip), GFP_KERNEL); | |
89 | if (!pcchip) | |
90 | return -ENOMEM; | |
91 | ||
92 | pcchip->clk = devm_clk_get(&pdev->dev, NULL); | |
93 | if (IS_ERR(pcchip->clk)) | |
94 | return dev_err_probe(&pdev->dev, PTR_ERR(pcchip->clk), | |
95 | "Failed to get clock\n"); | |
96 | ||
97 | pcchip->chip.dev = &pdev->dev; | |
98 | pcchip->chip.ops = &pwm_clk_ops; | |
99 | pcchip->chip.npwm = 1; | |
100 | ||
101 | ret = clk_prepare(pcchip->clk); | |
102 | if (ret < 0) | |
103 | return dev_err_probe(&pdev->dev, ret, "Failed to prepare clock\n"); | |
104 | ||
105 | ret = pwmchip_add(&pcchip->chip); | |
106 | if (ret < 0) { | |
107 | clk_unprepare(pcchip->clk); | |
108 | return dev_err_probe(&pdev->dev, ret, "Failed to add pwm chip\n"); | |
109 | } | |
110 | ||
111 | platform_set_drvdata(pdev, pcchip); | |
112 | return 0; | |
113 | } | |
114 | ||
115 | static int pwm_clk_remove(struct platform_device *pdev) | |
116 | { | |
117 | struct pwm_clk_chip *pcchip = platform_get_drvdata(pdev); | |
118 | ||
119 | pwmchip_remove(&pcchip->chip); | |
120 | ||
121 | if (pcchip->clk_enabled) | |
122 | clk_disable(pcchip->clk); | |
123 | ||
124 | clk_unprepare(pcchip->clk); | |
125 | ||
126 | return 0; | |
127 | } | |
128 | ||
129 | static const struct of_device_id pwm_clk_dt_ids[] = { | |
130 | { .compatible = "clk-pwm", }, | |
131 | { /* sentinel */ } | |
132 | }; | |
133 | MODULE_DEVICE_TABLE(of, pwm_clk_dt_ids); | |
134 | ||
135 | static struct platform_driver pwm_clk_driver = { | |
136 | .driver = { | |
137 | .name = "pwm-clk", | |
138 | .of_match_table = pwm_clk_dt_ids, | |
139 | }, | |
140 | .probe = pwm_clk_probe, | |
141 | .remove = pwm_clk_remove, | |
142 | }; | |
143 | module_platform_driver(pwm_clk_driver); | |
144 | ||
145 | MODULE_ALIAS("platform:pwm-clk"); | |
146 | MODULE_AUTHOR("Nikita Travkin <[email protected]>"); | |
147 | MODULE_DESCRIPTION("Clock based PWM driver"); | |
148 | MODULE_LICENSE("GPL"); |