xref: /openbmc/linux/drivers/pwm/pwm-clk.c (revision 1ac731c529cd4d6adbce134754b51ff7d822b145)
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