1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Copyright (C) 2014 Philipp Zabel, Pengutronix |
4 | * |
5 | * PWM (mis)used as clock output |
6 | */ |
7 | #include <linux/clk-provider.h> |
8 | #include <linux/kernel.h> |
9 | #include <linux/module.h> |
10 | #include <linux/of.h> |
11 | #include <linux/platform_device.h> |
12 | #include <linux/pwm.h> |
13 | |
14 | struct clk_pwm { |
15 | struct clk_hw hw; |
16 | struct pwm_device *pwm; |
17 | u32 fixed_rate; |
18 | }; |
19 | |
20 | static inline struct clk_pwm *to_clk_pwm(struct clk_hw *hw) |
21 | { |
22 | return container_of(hw, struct clk_pwm, hw); |
23 | } |
24 | |
25 | static int clk_pwm_prepare(struct clk_hw *hw) |
26 | { |
27 | struct clk_pwm *clk_pwm = to_clk_pwm(hw); |
28 | |
29 | return pwm_enable(pwm: clk_pwm->pwm); |
30 | } |
31 | |
32 | static void clk_pwm_unprepare(struct clk_hw *hw) |
33 | { |
34 | struct clk_pwm *clk_pwm = to_clk_pwm(hw); |
35 | |
36 | pwm_disable(pwm: clk_pwm->pwm); |
37 | } |
38 | |
39 | static unsigned long clk_pwm_recalc_rate(struct clk_hw *hw, |
40 | unsigned long parent_rate) |
41 | { |
42 | struct clk_pwm *clk_pwm = to_clk_pwm(hw); |
43 | |
44 | return clk_pwm->fixed_rate; |
45 | } |
46 | |
47 | static int clk_pwm_get_duty_cycle(struct clk_hw *hw, struct clk_duty *duty) |
48 | { |
49 | struct clk_pwm *clk_pwm = to_clk_pwm(hw); |
50 | struct pwm_state state; |
51 | |
52 | pwm_get_state(pwm: clk_pwm->pwm, state: &state); |
53 | |
54 | duty->num = state.duty_cycle; |
55 | duty->den = state.period; |
56 | |
57 | return 0; |
58 | } |
59 | |
60 | static const struct clk_ops clk_pwm_ops = { |
61 | .prepare = clk_pwm_prepare, |
62 | .unprepare = clk_pwm_unprepare, |
63 | .recalc_rate = clk_pwm_recalc_rate, |
64 | .get_duty_cycle = clk_pwm_get_duty_cycle, |
65 | }; |
66 | |
67 | static int clk_pwm_probe(struct platform_device *pdev) |
68 | { |
69 | struct device_node *node = pdev->dev.of_node; |
70 | struct clk_init_data init; |
71 | struct clk_pwm *clk_pwm; |
72 | struct pwm_device *pwm; |
73 | struct pwm_args pargs; |
74 | const char *clk_name; |
75 | int ret; |
76 | |
77 | clk_pwm = devm_kzalloc(dev: &pdev->dev, size: sizeof(*clk_pwm), GFP_KERNEL); |
78 | if (!clk_pwm) |
79 | return -ENOMEM; |
80 | |
81 | pwm = devm_pwm_get(dev: &pdev->dev, NULL); |
82 | if (IS_ERR(ptr: pwm)) |
83 | return PTR_ERR(ptr: pwm); |
84 | |
85 | pwm_get_args(pwm, args: &pargs); |
86 | if (!pargs.period) { |
87 | dev_err(&pdev->dev, "invalid PWM period\n" ); |
88 | return -EINVAL; |
89 | } |
90 | |
91 | if (of_property_read_u32(np: node, propname: "clock-frequency" , out_value: &clk_pwm->fixed_rate)) |
92 | clk_pwm->fixed_rate = div64_u64(NSEC_PER_SEC, divisor: pargs.period); |
93 | |
94 | if (!clk_pwm->fixed_rate) { |
95 | dev_err(&pdev->dev, "fixed_rate cannot be zero\n" ); |
96 | return -EINVAL; |
97 | } |
98 | |
99 | if (pargs.period != NSEC_PER_SEC / clk_pwm->fixed_rate && |
100 | pargs.period != DIV_ROUND_UP(NSEC_PER_SEC, clk_pwm->fixed_rate)) { |
101 | dev_err(&pdev->dev, |
102 | "clock-frequency does not match PWM period\n" ); |
103 | return -EINVAL; |
104 | } |
105 | |
106 | /* |
107 | * FIXME: pwm_apply_args() should be removed when switching to the |
108 | * atomic PWM API. |
109 | */ |
110 | pwm_apply_args(pwm); |
111 | ret = pwm_config(pwm, duty_ns: (pargs.period + 1) >> 1, period_ns: pargs.period); |
112 | if (ret < 0) |
113 | return ret; |
114 | |
115 | clk_name = node->name; |
116 | of_property_read_string(np: node, propname: "clock-output-names" , out_string: &clk_name); |
117 | |
118 | init.name = clk_name; |
119 | init.ops = &clk_pwm_ops; |
120 | init.flags = 0; |
121 | init.num_parents = 0; |
122 | |
123 | clk_pwm->pwm = pwm; |
124 | clk_pwm->hw.init = &init; |
125 | ret = devm_clk_hw_register(dev: &pdev->dev, hw: &clk_pwm->hw); |
126 | if (ret) |
127 | return ret; |
128 | |
129 | return of_clk_add_hw_provider(np: node, get: of_clk_hw_simple_get, data: &clk_pwm->hw); |
130 | } |
131 | |
132 | static void clk_pwm_remove(struct platform_device *pdev) |
133 | { |
134 | of_clk_del_provider(np: pdev->dev.of_node); |
135 | } |
136 | |
137 | static const struct of_device_id clk_pwm_dt_ids[] = { |
138 | { .compatible = "pwm-clock" }, |
139 | { } |
140 | }; |
141 | MODULE_DEVICE_TABLE(of, clk_pwm_dt_ids); |
142 | |
143 | static struct platform_driver clk_pwm_driver = { |
144 | .probe = clk_pwm_probe, |
145 | .remove_new = clk_pwm_remove, |
146 | .driver = { |
147 | .name = "pwm-clock" , |
148 | .of_match_table = clk_pwm_dt_ids, |
149 | }, |
150 | }; |
151 | |
152 | module_platform_driver(clk_pwm_driver); |
153 | |
154 | MODULE_AUTHOR("Philipp Zabel <p.zabel@pengutronix.de>" ); |
155 | MODULE_DESCRIPTION("PWM clock driver" ); |
156 | MODULE_LICENSE("GPL" ); |
157 | |