Blame view

drivers/clk/clk-pwm.c 3.38 KB
d2912cb15   Thomas Gleixner   treewide: Replace...
1
  // SPDX-License-Identifier: GPL-2.0-only
9a74ccdbb   Philipp Zabel   clk: Add PWM cloc...
2
3
4
  /*
   * Copyright (C) 2014 Philipp Zabel, Pengutronix
   *
9a74ccdbb   Philipp Zabel   clk: Add PWM cloc...
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
   * PWM (mis)used as clock output
   */
  #include <linux/clk-provider.h>
  #include <linux/kernel.h>
  #include <linux/module.h>
  #include <linux/of.h>
  #include <linux/platform_device.h>
  #include <linux/pwm.h>
  
  struct clk_pwm {
  	struct clk_hw hw;
  	struct pwm_device *pwm;
  	u32 fixed_rate;
  };
  
  static inline struct clk_pwm *to_clk_pwm(struct clk_hw *hw)
  {
  	return container_of(hw, struct clk_pwm, hw);
  }
  
  static int clk_pwm_prepare(struct clk_hw *hw)
  {
  	struct clk_pwm *clk_pwm = to_clk_pwm(hw);
  
  	return pwm_enable(clk_pwm->pwm);
  }
  
  static void clk_pwm_unprepare(struct clk_hw *hw)
  {
  	struct clk_pwm *clk_pwm = to_clk_pwm(hw);
  
  	pwm_disable(clk_pwm->pwm);
  }
  
  static unsigned long clk_pwm_recalc_rate(struct clk_hw *hw,
  					 unsigned long parent_rate)
  {
  	struct clk_pwm *clk_pwm = to_clk_pwm(hw);
  
  	return clk_pwm->fixed_rate;
  }
4c34282fb   Martin Blumenstingl   clk: pwm: impleme...
46
47
48
49
50
51
52
53
54
55
56
57
  static int clk_pwm_get_duty_cycle(struct clk_hw *hw, struct clk_duty *duty)
  {
  	struct clk_pwm *clk_pwm = to_clk_pwm(hw);
  	struct pwm_state state;
  
  	pwm_get_state(clk_pwm->pwm, &state);
  
  	duty->num = state.duty_cycle;
  	duty->den = state.period;
  
  	return 0;
  }
9a74ccdbb   Philipp Zabel   clk: Add PWM cloc...
58
59
60
61
  static const struct clk_ops clk_pwm_ops = {
  	.prepare = clk_pwm_prepare,
  	.unprepare = clk_pwm_unprepare,
  	.recalc_rate = clk_pwm_recalc_rate,
4c34282fb   Martin Blumenstingl   clk: pwm: impleme...
62
  	.get_duty_cycle = clk_pwm_get_duty_cycle,
9a74ccdbb   Philipp Zabel   clk: Add PWM cloc...
63
64
65
66
67
68
69
70
  };
  
  static int clk_pwm_probe(struct platform_device *pdev)
  {
  	struct device_node *node = pdev->dev.of_node;
  	struct clk_init_data init;
  	struct clk_pwm *clk_pwm;
  	struct pwm_device *pwm;
dd0b38b7c   Boris Brezillon   clk: pwm: Use pwm...
71
  	struct pwm_args pargs;
9a74ccdbb   Philipp Zabel   clk: Add PWM cloc...
72
  	const char *clk_name;
9a74ccdbb   Philipp Zabel   clk: Add PWM cloc...
73
74
75
76
77
78
79
80
81
  	int ret;
  
  	clk_pwm = devm_kzalloc(&pdev->dev, sizeof(*clk_pwm), GFP_KERNEL);
  	if (!clk_pwm)
  		return -ENOMEM;
  
  	pwm = devm_pwm_get(&pdev->dev, NULL);
  	if (IS_ERR(pwm))
  		return PTR_ERR(pwm);
dd0b38b7c   Boris Brezillon   clk: pwm: Use pwm...
82
83
  	pwm_get_args(pwm, &pargs);
  	if (!pargs.period) {
9a74ccdbb   Philipp Zabel   clk: Add PWM cloc...
84
85
86
87
88
89
  		dev_err(&pdev->dev, "invalid PWM period
  ");
  		return -EINVAL;
  	}
  
  	if (of_property_read_u32(node, "clock-frequency", &clk_pwm->fixed_rate))
dd0b38b7c   Boris Brezillon   clk: pwm: Use pwm...
90
  		clk_pwm->fixed_rate = NSEC_PER_SEC / pargs.period;
9a74ccdbb   Philipp Zabel   clk: Add PWM cloc...
91

dd0b38b7c   Boris Brezillon   clk: pwm: Use pwm...
92
93
  	if (pargs.period != NSEC_PER_SEC / clk_pwm->fixed_rate &&
  	    pargs.period != DIV_ROUND_UP(NSEC_PER_SEC, clk_pwm->fixed_rate)) {
9a74ccdbb   Philipp Zabel   clk: Add PWM cloc...
94
95
96
97
98
  		dev_err(&pdev->dev,
  			"clock-frequency does not match PWM period
  ");
  		return -EINVAL;
  	}
dd0b38b7c   Boris Brezillon   clk: pwm: Use pwm...
99
100
101
102
103
104
  	/*
  	 * FIXME: pwm_apply_args() should be removed when switching to the
  	 * atomic PWM API.
  	 */
  	pwm_apply_args(pwm);
  	ret = pwm_config(pwm, (pargs.period + 1) >> 1, pargs.period);
9a74ccdbb   Philipp Zabel   clk: Add PWM cloc...
105
106
107
108
109
110
111
112
  	if (ret < 0)
  		return ret;
  
  	clk_name = node->name;
  	of_property_read_string(node, "clock-output-names", &clk_name);
  
  	init.name = clk_name;
  	init.ops = &clk_pwm_ops;
90b6c5c73   Stephen Boyd   clk: Remove CLK_I...
113
  	init.flags = 0;
9a74ccdbb   Philipp Zabel   clk: Add PWM cloc...
114
115
116
117
  	init.num_parents = 0;
  
  	clk_pwm->pwm = pwm;
  	clk_pwm->hw.init = &init;
4cf915dfb   Stephen Boyd   clk: pwm: Migrate...
118
119
120
  	ret = devm_clk_hw_register(&pdev->dev, &clk_pwm->hw);
  	if (ret)
  		return ret;
9a74ccdbb   Philipp Zabel   clk: Add PWM cloc...
121

4cf915dfb   Stephen Boyd   clk: pwm: Migrate...
122
  	return of_clk_add_hw_provider(node, of_clk_hw_simple_get, &clk_pwm->hw);
9a74ccdbb   Philipp Zabel   clk: Add PWM cloc...
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
  }
  
  static int clk_pwm_remove(struct platform_device *pdev)
  {
  	of_clk_del_provider(pdev->dev.of_node);
  
  	return 0;
  }
  
  static const struct of_device_id clk_pwm_dt_ids[] = {
  	{ .compatible = "pwm-clock" },
  	{ }
  };
  MODULE_DEVICE_TABLE(of, clk_pwm_dt_ids);
  
  static struct platform_driver clk_pwm_driver = {
  	.probe = clk_pwm_probe,
  	.remove = clk_pwm_remove,
  	.driver = {
  		.name = "pwm-clock",
  		.of_match_table = of_match_ptr(clk_pwm_dt_ids),
  	},
  };
  
  module_platform_driver(clk_pwm_driver);
  
  MODULE_AUTHOR("Philipp Zabel <p.zabel@pengutronix.de>");
  MODULE_DESCRIPTION("PWM clock driver");
  MODULE_LICENSE("GPL");