1901f8f54SNikita Travkin // SPDX-License-Identifier: GPL-2.0
2901f8f54SNikita Travkin /*
3901f8f54SNikita Travkin * Clock based PWM controller
4901f8f54SNikita Travkin *
5901f8f54SNikita Travkin * Copyright (c) 2021 Nikita Travkin <nikita@trvn.ru>
6901f8f54SNikita Travkin *
7901f8f54SNikita Travkin * This is an "adapter" driver that allows PWM consumers to use
8901f8f54SNikita Travkin * system clocks with duty cycle control as PWM outputs.
9901f8f54SNikita Travkin *
10901f8f54SNikita Travkin * Limitations:
11901f8f54SNikita Travkin * - Due to the fact that exact behavior depends on the underlying
12901f8f54SNikita Travkin * clock driver, various limitations are possible.
13901f8f54SNikita Travkin * - Underlying clock may not be able to give 0% or 100% duty cycle
14901f8f54SNikita Travkin * (constant off or on), exact behavior will depend on the clock.
15901f8f54SNikita Travkin * - When the PWM is disabled, the clock will be disabled as well,
16901f8f54SNikita Travkin * line state will depend on the clock.
17901f8f54SNikita Travkin * - The clk API doesn't expose the necessary calls to implement
18901f8f54SNikita Travkin * .get_state().
19901f8f54SNikita Travkin */
20901f8f54SNikita Travkin
21901f8f54SNikita Travkin #include <linux/kernel.h>
22901f8f54SNikita Travkin #include <linux/math64.h>
23901f8f54SNikita Travkin #include <linux/err.h>
24901f8f54SNikita Travkin #include <linux/module.h>
25901f8f54SNikita Travkin #include <linux/of.h>
26901f8f54SNikita Travkin #include <linux/platform_device.h>
27901f8f54SNikita Travkin #include <linux/clk.h>
28901f8f54SNikita Travkin #include <linux/pwm.h>
29901f8f54SNikita Travkin
30901f8f54SNikita Travkin struct pwm_clk_chip {
31901f8f54SNikita Travkin struct pwm_chip chip;
32901f8f54SNikita Travkin struct clk *clk;
33901f8f54SNikita Travkin bool clk_enabled;
34901f8f54SNikita Travkin };
35901f8f54SNikita Travkin
36901f8f54SNikita Travkin #define to_pwm_clk_chip(_chip) container_of(_chip, struct pwm_clk_chip, chip)
37901f8f54SNikita Travkin
pwm_clk_apply(struct pwm_chip * chip,struct pwm_device * pwm,const struct pwm_state * state)38901f8f54SNikita Travkin static int pwm_clk_apply(struct pwm_chip *chip, struct pwm_device *pwm,
39901f8f54SNikita Travkin const struct pwm_state *state)
40901f8f54SNikita Travkin {
41901f8f54SNikita Travkin struct pwm_clk_chip *pcchip = to_pwm_clk_chip(chip);
42901f8f54SNikita Travkin int ret;
43901f8f54SNikita Travkin u32 rate;
44901f8f54SNikita Travkin u64 period = state->period;
45901f8f54SNikita Travkin u64 duty_cycle = state->duty_cycle;
46901f8f54SNikita Travkin
47901f8f54SNikita Travkin if (!state->enabled) {
48901f8f54SNikita Travkin if (pwm->state.enabled) {
49901f8f54SNikita Travkin clk_disable(pcchip->clk);
50901f8f54SNikita Travkin pcchip->clk_enabled = false;
51901f8f54SNikita Travkin }
52901f8f54SNikita Travkin return 0;
53901f8f54SNikita Travkin } else if (!pwm->state.enabled) {
54901f8f54SNikita Travkin ret = clk_enable(pcchip->clk);
55901f8f54SNikita Travkin if (ret)
56901f8f54SNikita Travkin return ret;
57901f8f54SNikita Travkin pcchip->clk_enabled = true;
58901f8f54SNikita Travkin }
59901f8f54SNikita Travkin
60901f8f54SNikita Travkin /*
61901f8f54SNikita Travkin * We have to enable the clk before setting the rate and duty_cycle,
62901f8f54SNikita Travkin * that however results in a window where the clk is on with a
63901f8f54SNikita Travkin * (potentially) different setting. Also setting period and duty_cycle
64901f8f54SNikita Travkin * are two separate calls, so that probably isn't atomic either.
65901f8f54SNikita Travkin */
66901f8f54SNikita Travkin
67901f8f54SNikita Travkin rate = DIV64_U64_ROUND_UP(NSEC_PER_SEC, period);
68901f8f54SNikita Travkin ret = clk_set_rate(pcchip->clk, rate);
69901f8f54SNikita Travkin if (ret)
70901f8f54SNikita Travkin return ret;
71901f8f54SNikita Travkin
72901f8f54SNikita Travkin if (state->polarity == PWM_POLARITY_INVERSED)
73901f8f54SNikita Travkin duty_cycle = period - duty_cycle;
74901f8f54SNikita Travkin
75901f8f54SNikita Travkin return clk_set_duty_cycle(pcchip->clk, duty_cycle, period);
76901f8f54SNikita Travkin }
77901f8f54SNikita Travkin
78901f8f54SNikita Travkin static const struct pwm_ops pwm_clk_ops = {
79901f8f54SNikita Travkin .apply = pwm_clk_apply,
80901f8f54SNikita Travkin .owner = THIS_MODULE,
81901f8f54SNikita Travkin };
82901f8f54SNikita Travkin
pwm_clk_probe(struct platform_device * pdev)83901f8f54SNikita Travkin static int pwm_clk_probe(struct platform_device *pdev)
84901f8f54SNikita Travkin {
85901f8f54SNikita Travkin struct pwm_clk_chip *pcchip;
86901f8f54SNikita Travkin int ret;
87901f8f54SNikita Travkin
88901f8f54SNikita Travkin pcchip = devm_kzalloc(&pdev->dev, sizeof(*pcchip), GFP_KERNEL);
89901f8f54SNikita Travkin if (!pcchip)
90901f8f54SNikita Travkin return -ENOMEM;
91901f8f54SNikita Travkin
92901f8f54SNikita Travkin pcchip->clk = devm_clk_get_prepared(&pdev->dev, NULL);
93901f8f54SNikita Travkin if (IS_ERR(pcchip->clk))
94901f8f54SNikita Travkin return dev_err_probe(&pdev->dev, PTR_ERR(pcchip->clk),
95901f8f54SNikita Travkin "Failed to get clock\n");
96901f8f54SNikita Travkin
97901f8f54SNikita Travkin pcchip->chip.dev = &pdev->dev;
98901f8f54SNikita Travkin pcchip->chip.ops = &pwm_clk_ops;
99901f8f54SNikita Travkin pcchip->chip.npwm = 1;
100901f8f54SNikita Travkin
101901f8f54SNikita Travkin ret = pwmchip_add(&pcchip->chip);
102901f8f54SNikita Travkin if (ret < 0)
103901f8f54SNikita Travkin return dev_err_probe(&pdev->dev, ret, "Failed to add pwm chip\n");
104901f8f54SNikita Travkin
105901f8f54SNikita Travkin platform_set_drvdata(pdev, pcchip);
106901f8f54SNikita Travkin return 0;
107901f8f54SNikita Travkin }
108901f8f54SNikita Travkin
pwm_clk_remove(struct platform_device * pdev)109901f8f54SNikita Travkin static void pwm_clk_remove(struct platform_device *pdev)
110901f8f54SNikita Travkin {
111901f8f54SNikita Travkin struct pwm_clk_chip *pcchip = platform_get_drvdata(pdev);
112901f8f54SNikita Travkin
113901f8f54SNikita Travkin pwmchip_remove(&pcchip->chip);
114901f8f54SNikita Travkin
115*d5806ac6SUwe Kleine-König if (pcchip->clk_enabled)
116901f8f54SNikita Travkin clk_disable(pcchip->clk);
117901f8f54SNikita Travkin }
118901f8f54SNikita Travkin
119901f8f54SNikita Travkin static const struct of_device_id pwm_clk_dt_ids[] = {
120901f8f54SNikita Travkin { .compatible = "clk-pwm", },
121901f8f54SNikita Travkin { /* sentinel */ }
122901f8f54SNikita Travkin };
123901f8f54SNikita Travkin MODULE_DEVICE_TABLE(of, pwm_clk_dt_ids);
124901f8f54SNikita Travkin
125901f8f54SNikita Travkin static struct platform_driver pwm_clk_driver = {
126901f8f54SNikita Travkin .driver = {
127901f8f54SNikita Travkin .name = "pwm-clk",
128901f8f54SNikita Travkin .of_match_table = pwm_clk_dt_ids,
129901f8f54SNikita Travkin },
130901f8f54SNikita Travkin .probe = pwm_clk_probe,
131901f8f54SNikita Travkin .remove_new = pwm_clk_remove,
132901f8f54SNikita Travkin };
133901f8f54SNikita Travkin module_platform_driver(pwm_clk_driver);
134901f8f54SNikita Travkin
135901f8f54SNikita Travkin MODULE_ALIAS("platform:pwm-clk");
136901f8f54SNikita Travkin MODULE_AUTHOR("Nikita Travkin <nikita@trvn.ru>");
137901f8f54SNikita Travkin MODULE_DESCRIPTION("Clock based PWM driver");
138901f8f54SNikita Travkin MODULE_LICENSE("GPL");
139*d5806ac6SUwe Kleine-König