diff options
-rw-r--r-- | Documentation/devicetree/bindings/clock/pwm-clock.txt | 26 | ||||
-rw-r--r-- | drivers/clk/Kconfig | 7 | ||||
-rw-r--r-- | drivers/clk/Makefile | 1 | ||||
-rw-r--r-- | drivers/clk/clk-pwm.c | 136 |
4 files changed, 170 insertions, 0 deletions
diff --git a/Documentation/devicetree/bindings/clock/pwm-clock.txt b/Documentation/devicetree/bindings/clock/pwm-clock.txt new file mode 100644 index 000000000000..83db876b3b90 --- /dev/null +++ b/Documentation/devicetree/bindings/clock/pwm-clock.txt | |||
@@ -0,0 +1,26 @@ | |||
1 | Binding for an external clock signal driven by a PWM pin. | ||
2 | |||
3 | This binding uses the common clock binding[1] and the common PWM binding[2]. | ||
4 | |||
5 | [1] Documentation/devicetree/bindings/clock/clock-bindings.txt | ||
6 | [2] Documentation/devicetree/bindings/pwm/pwm.txt | ||
7 | |||
8 | Required properties: | ||
9 | - compatible : shall be "pwm-clock". | ||
10 | - #clock-cells : from common clock binding; shall be set to 0. | ||
11 | - pwms : from common PWM binding; this determines the clock frequency | ||
12 | via the period given in the PWM specifier. | ||
13 | |||
14 | Optional properties: | ||
15 | - clock-output-names : From common clock binding. | ||
16 | - clock-frequency : Exact output frequency, in case the PWM period | ||
17 | is not exact but was rounded to nanoseconds. | ||
18 | |||
19 | Example: | ||
20 | clock { | ||
21 | compatible = "pwm-clock"; | ||
22 | #clock-cells = <0>; | ||
23 | clock-frequency = <25000000>; | ||
24 | clock-output-names = "mipi_mclk"; | ||
25 | pwms = <&pwm2 0 40>; /* 1 / 40 ns = 25 MHz */ | ||
26 | }; | ||
diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig index 0b474a04730f..9897f353bf1a 100644 --- a/drivers/clk/Kconfig +++ b/drivers/clk/Kconfig | |||
@@ -130,6 +130,13 @@ config COMMON_CLK_PALMAS | |||
130 | This driver supports TI Palmas devices 32KHz output KG and KG_AUDIO | 130 | This driver supports TI Palmas devices 32KHz output KG and KG_AUDIO |
131 | using common clock framework. | 131 | using common clock framework. |
132 | 132 | ||
133 | config COMMON_CLK_PWM | ||
134 | tristate "Clock driver for PWMs used as clock outputs" | ||
135 | depends on PWM | ||
136 | ---help--- | ||
137 | Adapter driver so that any PWM output can be (mis)used as clock signal | ||
138 | at 50% duty cycle. | ||
139 | |||
133 | config COMMON_CLK_PXA | 140 | config COMMON_CLK_PXA |
134 | def_bool COMMON_CLK && ARCH_PXA | 141 | def_bool COMMON_CLK && ARCH_PXA |
135 | ---help--- | 142 | ---help--- |
diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile index 1d35f3bb28e0..492bcabe7930 100644 --- a/drivers/clk/Makefile +++ b/drivers/clk/Makefile | |||
@@ -43,6 +43,7 @@ obj-$(CONFIG_ARCH_U300) += clk-u300.o | |||
43 | obj-$(CONFIG_ARCH_VT8500) += clk-vt8500.o | 43 | obj-$(CONFIG_ARCH_VT8500) += clk-vt8500.o |
44 | obj-$(CONFIG_COMMON_CLK_WM831X) += clk-wm831x.o | 44 | obj-$(CONFIG_COMMON_CLK_WM831X) += clk-wm831x.o |
45 | obj-$(CONFIG_COMMON_CLK_XGENE) += clk-xgene.o | 45 | obj-$(CONFIG_COMMON_CLK_XGENE) += clk-xgene.o |
46 | obj-$(CONFIG_COMMON_CLK_PWM) += clk-pwm.o | ||
46 | obj-$(CONFIG_COMMON_CLK_AT91) += at91/ | 47 | obj-$(CONFIG_COMMON_CLK_AT91) += at91/ |
47 | obj-$(CONFIG_ARCH_BCM_MOBILE) += bcm/ | 48 | obj-$(CONFIG_ARCH_BCM_MOBILE) += bcm/ |
48 | obj-$(CONFIG_ARCH_BERLIN) += berlin/ | 49 | obj-$(CONFIG_ARCH_BERLIN) += berlin/ |
diff --git a/drivers/clk/clk-pwm.c b/drivers/clk/clk-pwm.c new file mode 100644 index 000000000000..328fcfcefd8c --- /dev/null +++ b/drivers/clk/clk-pwm.c | |||
@@ -0,0 +1,136 @@ | |||
1 | /* | ||
2 | * Copyright (C) 2014 Philipp Zabel, Pengutronix | ||
3 | * | ||
4 | * This program is free software; you can redistribute it and/or modify | ||
5 | * it under the terms of the GNU General Public License version 2 as | ||
6 | * published by the Free Software Foundation. | ||
7 | * | ||
8 | * PWM (mis)used as clock output | ||
9 | */ | ||
10 | #include <linux/clk-provider.h> | ||
11 | #include <linux/kernel.h> | ||
12 | #include <linux/module.h> | ||
13 | #include <linux/of.h> | ||
14 | #include <linux/platform_device.h> | ||
15 | #include <linux/pwm.h> | ||
16 | |||
17 | struct clk_pwm { | ||
18 | struct clk_hw hw; | ||
19 | struct pwm_device *pwm; | ||
20 | u32 fixed_rate; | ||
21 | }; | ||
22 | |||
23 | static inline struct clk_pwm *to_clk_pwm(struct clk_hw *hw) | ||
24 | { | ||
25 | return container_of(hw, struct clk_pwm, hw); | ||
26 | } | ||
27 | |||
28 | static int clk_pwm_prepare(struct clk_hw *hw) | ||
29 | { | ||
30 | struct clk_pwm *clk_pwm = to_clk_pwm(hw); | ||
31 | |||
32 | return pwm_enable(clk_pwm->pwm); | ||
33 | } | ||
34 | |||
35 | static void clk_pwm_unprepare(struct clk_hw *hw) | ||
36 | { | ||
37 | struct clk_pwm *clk_pwm = to_clk_pwm(hw); | ||
38 | |||
39 | pwm_disable(clk_pwm->pwm); | ||
40 | } | ||
41 | |||
42 | static unsigned long clk_pwm_recalc_rate(struct clk_hw *hw, | ||
43 | unsigned long parent_rate) | ||
44 | { | ||
45 | struct clk_pwm *clk_pwm = to_clk_pwm(hw); | ||
46 | |||
47 | return clk_pwm->fixed_rate; | ||
48 | } | ||
49 | |||
50 | static const struct clk_ops clk_pwm_ops = { | ||
51 | .prepare = clk_pwm_prepare, | ||
52 | .unprepare = clk_pwm_unprepare, | ||
53 | .recalc_rate = clk_pwm_recalc_rate, | ||
54 | }; | ||
55 | |||
56 | static int clk_pwm_probe(struct platform_device *pdev) | ||
57 | { | ||
58 | struct device_node *node = pdev->dev.of_node; | ||
59 | struct clk_init_data init; | ||
60 | struct clk_pwm *clk_pwm; | ||
61 | struct pwm_device *pwm; | ||
62 | const char *clk_name; | ||
63 | struct clk *clk; | ||
64 | int ret; | ||
65 | |||
66 | clk_pwm = devm_kzalloc(&pdev->dev, sizeof(*clk_pwm), GFP_KERNEL); | ||
67 | if (!clk_pwm) | ||
68 | return -ENOMEM; | ||
69 | |||
70 | pwm = devm_pwm_get(&pdev->dev, NULL); | ||
71 | if (IS_ERR(pwm)) | ||
72 | return PTR_ERR(pwm); | ||
73 | |||
74 | if (!pwm->period) { | ||
75 | dev_err(&pdev->dev, "invalid PWM period\n"); | ||
76 | return -EINVAL; | ||
77 | } | ||
78 | |||
79 | if (of_property_read_u32(node, "clock-frequency", &clk_pwm->fixed_rate)) | ||
80 | clk_pwm->fixed_rate = NSEC_PER_SEC / pwm->period; | ||
81 | |||
82 | if (pwm->period != NSEC_PER_SEC / clk_pwm->fixed_rate && | ||
83 | pwm->period != DIV_ROUND_UP(NSEC_PER_SEC, clk_pwm->fixed_rate)) { | ||
84 | dev_err(&pdev->dev, | ||
85 | "clock-frequency does not match PWM period\n"); | ||
86 | return -EINVAL; | ||
87 | } | ||
88 | |||
89 | ret = pwm_config(pwm, (pwm->period + 1) >> 1, pwm->period); | ||
90 | if (ret < 0) | ||
91 | return ret; | ||
92 | |||
93 | clk_name = node->name; | ||
94 | of_property_read_string(node, "clock-output-names", &clk_name); | ||
95 | |||
96 | init.name = clk_name; | ||
97 | init.ops = &clk_pwm_ops; | ||
98 | init.flags = CLK_IS_BASIC | CLK_IS_ROOT; | ||
99 | init.num_parents = 0; | ||
100 | |||
101 | clk_pwm->pwm = pwm; | ||
102 | clk_pwm->hw.init = &init; | ||
103 | clk = devm_clk_register(&pdev->dev, &clk_pwm->hw); | ||
104 | if (IS_ERR(clk)) | ||
105 | return PTR_ERR(clk); | ||
106 | |||
107 | return of_clk_add_provider(node, of_clk_src_simple_get, clk); | ||
108 | } | ||
109 | |||
110 | static int clk_pwm_remove(struct platform_device *pdev) | ||
111 | { | ||
112 | of_clk_del_provider(pdev->dev.of_node); | ||
113 | |||
114 | return 0; | ||
115 | } | ||
116 | |||
117 | static const struct of_device_id clk_pwm_dt_ids[] = { | ||
118 | { .compatible = "pwm-clock" }, | ||
119 | { } | ||
120 | }; | ||
121 | MODULE_DEVICE_TABLE(of, clk_pwm_dt_ids); | ||
122 | |||
123 | static struct platform_driver clk_pwm_driver = { | ||
124 | .probe = clk_pwm_probe, | ||
125 | .remove = clk_pwm_remove, | ||
126 | .driver = { | ||
127 | .name = "pwm-clock", | ||
128 | .of_match_table = of_match_ptr(clk_pwm_dt_ids), | ||
129 | }, | ||
130 | }; | ||
131 | |||
132 | module_platform_driver(clk_pwm_driver); | ||
133 | |||
134 | MODULE_AUTHOR("Philipp Zabel <p.zabel@pengutronix.de>"); | ||
135 | MODULE_DESCRIPTION("PWM clock driver"); | ||
136 | MODULE_LICENSE("GPL"); | ||