File Index
Files and directories
Sign in
FILE

pwm-rockchip.c

File preview with syntax highlighting.

linux-6.19.14/drivers/pwm/pwm-rockchip.c C
  1// SPDX-License-Identifier: GPL-2.0-only
  2/*
  3 * PWM driver for Rockchip SoCs
  4 *
  5 * Copyright (C) 2014 Beniamino Galvani <b.galvani@gmail.com>
  6 * Copyright (C) 2014 ROCKCHIP, Inc.
  7 */
  8
  9#include <linux/clk.h>
 10#include <linux/io.h>
 11#include <linux/limits.h>
 12#include <linux/math64.h>
 13#include <linux/module.h>
 14#include <linux/of.h>
 15#include <linux/platform_device.h>
 16#include <linux/property.h>
 17#include <linux/pwm.h>
 18#include <linux/time.h>
 19
 20#define PWM_CTRL_TIMER_EN	(1 << 0)
 21#define PWM_CTRL_OUTPUT_EN	(1 << 3)
 22
 23#define PWM_ENABLE		(1 << 0)
 24#define PWM_CONTINUOUS		(1 << 1)
 25#define PWM_DUTY_POSITIVE	(1 << 3)
 26#define PWM_DUTY_NEGATIVE	(0 << 3)
 27#define PWM_INACTIVE_NEGATIVE	(0 << 4)
 28#define PWM_INACTIVE_POSITIVE	(1 << 4)
 29#define PWM_POLARITY_MASK	(PWM_DUTY_POSITIVE | PWM_INACTIVE_POSITIVE)
 30#define PWM_OUTPUT_LEFT		(0 << 5)
 31#define PWM_LOCK_EN		(1 << 6)
 32#define PWM_LP_DISABLE		(0 << 8)
 33
 34struct rockchip_pwm_chip {
 35	struct clk *clk;
 36	struct clk *pclk;
 37	const struct rockchip_pwm_data *data;
 38	void __iomem *base;
 39};
 40
 41struct rockchip_pwm_regs {
 42	unsigned long duty;
 43	unsigned long period;
 44	unsigned long cntr;
 45	unsigned long ctrl;
 46};
 47
 48struct rockchip_pwm_data {
 49	struct rockchip_pwm_regs regs;
 50	unsigned int prescaler;
 51	bool supports_polarity;
 52	bool supports_lock;
 53	u32 enable_conf;
 54};
 55
 56static inline struct rockchip_pwm_chip *to_rockchip_pwm_chip(struct pwm_chip *chip)
 57{
 58	return pwmchip_get_drvdata(chip);
 59}
 60
 61static int rockchip_pwm_get_state(struct pwm_chip *chip,
 62				  struct pwm_device *pwm,
 63				  struct pwm_state *state)
 64{
 65	struct rockchip_pwm_chip *pc = to_rockchip_pwm_chip(chip);
 66	u64 prescaled_ns = (u64)pc->data->prescaler * NSEC_PER_SEC;
 67	u32 enable_conf = pc->data->enable_conf;
 68	unsigned long clk_rate;
 69	u64 tmp;
 70	u32 val;
 71	int ret;
 72
 73	ret = clk_enable(pc->pclk);
 74	if (ret)
 75		return ret;
 76
 77	ret = clk_enable(pc->clk);
 78	if (ret)
 79		return ret;
 80
 81	clk_rate = clk_get_rate(pc->clk);
 82
 83	tmp = readl_relaxed(pc->base + pc->data->regs.period);
 84	tmp *= prescaled_ns;
 85	state->period = DIV_U64_ROUND_UP(tmp, clk_rate);
 86
 87	tmp = readl_relaxed(pc->base + pc->data->regs.duty);
 88	tmp *= prescaled_ns;
 89	state->duty_cycle =  DIV_U64_ROUND_UP(tmp, clk_rate);
 90
 91	val = readl_relaxed(pc->base + pc->data->regs.ctrl);
 92	state->enabled = (val & enable_conf) == enable_conf;
 93
 94	if (pc->data->supports_polarity && !(val & PWM_DUTY_POSITIVE))
 95		state->polarity = PWM_POLARITY_INVERSED;
 96	else
 97		state->polarity = PWM_POLARITY_NORMAL;
 98
 99	clk_disable(pc->clk);
100	clk_disable(pc->pclk);
101
102	return 0;
103}
104
105static void rockchip_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
106			       const struct pwm_state *state)
107{
108	struct rockchip_pwm_chip *pc = to_rockchip_pwm_chip(chip);
109	u64 prescaled_ns = (u64)pc->data->prescaler * NSEC_PER_SEC;
110	u64 clk_rate, tmp;
111	u32 period_ticks, duty_ticks;
112	u32 ctrl;
113
114	clk_rate = clk_get_rate(pc->clk);
115
116	/*
117	 * Since period and duty cycle registers have a width of 32
118	 * bits, every possible input period can be obtained using the
119	 * default prescaler value for all practical clock rate values.
120	 */
121	tmp = mul_u64_u64_div_u64(clk_rate, state->period, prescaled_ns);
122	if (tmp > U32_MAX)
123		tmp = U32_MAX;
124	period_ticks = tmp;
125
126	tmp = mul_u64_u64_div_u64(clk_rate, state->duty_cycle, prescaled_ns);
127	if (tmp > U32_MAX)
128		tmp = U32_MAX;
129	duty_ticks = tmp;
130
131	/*
132	 * Lock the period and duty of previous configuration, then
133	 * change the duty and period, that would not be effective.
134	 */
135	ctrl = readl_relaxed(pc->base + pc->data->regs.ctrl);
136	if (pc->data->supports_lock) {
137		ctrl |= PWM_LOCK_EN;
138		writel_relaxed(ctrl, pc->base + pc->data->regs.ctrl);
139	}
140
141	writel(period_ticks, pc->base + pc->data->regs.period);
142	writel(duty_ticks, pc->base + pc->data->regs.duty);
143
144	if (pc->data->supports_polarity) {
145		ctrl &= ~PWM_POLARITY_MASK;
146		if (state->polarity == PWM_POLARITY_INVERSED)
147			ctrl |= PWM_DUTY_NEGATIVE | PWM_INACTIVE_POSITIVE;
148		else
149			ctrl |= PWM_DUTY_POSITIVE | PWM_INACTIVE_NEGATIVE;
150	}
151
152	/*
153	 * Unlock and set polarity at the same time,
154	 * the configuration of duty, period and polarity
155	 * would be effective together at next period.
156	 */
157	if (pc->data->supports_lock)
158		ctrl &= ~PWM_LOCK_EN;
159
160	writel(ctrl, pc->base + pc->data->regs.ctrl);
161}
162
163static int rockchip_pwm_enable(struct pwm_chip *chip,
164			       struct pwm_device *pwm,
165			       bool enable)
166{
167	struct rockchip_pwm_chip *pc = to_rockchip_pwm_chip(chip);
168	u32 enable_conf = pc->data->enable_conf;
169	int ret;
170	u32 val;
171
172	if (enable) {
173		ret = clk_enable(pc->clk);
174		if (ret)
175			return ret;
176	}
177
178	val = readl_relaxed(pc->base + pc->data->regs.ctrl);
179
180	if (enable)
181		val |= enable_conf;
182	else
183		val &= ~enable_conf;
184
185	writel_relaxed(val, pc->base + pc->data->regs.ctrl);
186
187	if (!enable)
188		clk_disable(pc->clk);
189
190	return 0;
191}
192
193static int rockchip_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
194			      const struct pwm_state *state)
195{
196	struct rockchip_pwm_chip *pc = to_rockchip_pwm_chip(chip);
197	struct pwm_state curstate;
198	bool enabled;
199	int ret = 0;
200
201	ret = clk_enable(pc->pclk);
202	if (ret)
203		return ret;
204
205	ret = clk_enable(pc->clk);
206	if (ret)
207		return ret;
208
209	pwm_get_state(pwm, &curstate);
210	enabled = curstate.enabled;
211
212	if (state->polarity != curstate.polarity && enabled &&
213	    !pc->data->supports_lock) {
214		ret = rockchip_pwm_enable(chip, pwm, false);
215		if (ret)
216			goto out;
217		enabled = false;
218	}
219
220	rockchip_pwm_config(chip, pwm, state);
221	if (state->enabled != enabled) {
222		ret = rockchip_pwm_enable(chip, pwm, state->enabled);
223		if (ret)
224			goto out;
225	}
226
227out:
228	clk_disable(pc->clk);
229	clk_disable(pc->pclk);
230
231	return ret;
232}
233
234static const struct pwm_ops rockchip_pwm_ops = {
235	.get_state = rockchip_pwm_get_state,
236	.apply = rockchip_pwm_apply,
237};
238
239static const struct rockchip_pwm_data pwm_data_v1 = {
240	.regs = {
241		.duty = 0x04,
242		.period = 0x08,
243		.cntr = 0x00,
244		.ctrl = 0x0c,
245	},
246	.prescaler = 2,
247	.supports_polarity = false,
248	.supports_lock = false,
249	.enable_conf = PWM_CTRL_OUTPUT_EN | PWM_CTRL_TIMER_EN,
250};
251
252static const struct rockchip_pwm_data pwm_data_v2 = {
253	.regs = {
254		.duty = 0x08,
255		.period = 0x04,
256		.cntr = 0x00,
257		.ctrl = 0x0c,
258	},
259	.prescaler = 1,
260	.supports_polarity = true,
261	.supports_lock = false,
262	.enable_conf = PWM_OUTPUT_LEFT | PWM_LP_DISABLE | PWM_ENABLE |
263		       PWM_CONTINUOUS,
264};
265
266static const struct rockchip_pwm_data pwm_data_vop = {
267	.regs = {
268		.duty = 0x08,
269		.period = 0x04,
270		.cntr = 0x0c,
271		.ctrl = 0x00,
272	},
273	.prescaler = 1,
274	.supports_polarity = true,
275	.supports_lock = false,
276	.enable_conf = PWM_OUTPUT_LEFT | PWM_LP_DISABLE | PWM_ENABLE |
277		       PWM_CONTINUOUS,
278};
279
280static const struct rockchip_pwm_data pwm_data_v3 = {
281	.regs = {
282		.duty = 0x08,
283		.period = 0x04,
284		.cntr = 0x00,
285		.ctrl = 0x0c,
286	},
287	.prescaler = 1,
288	.supports_polarity = true,
289	.supports_lock = true,
290	.enable_conf = PWM_OUTPUT_LEFT | PWM_LP_DISABLE | PWM_ENABLE |
291		       PWM_CONTINUOUS,
292};
293
294static const struct of_device_id rockchip_pwm_dt_ids[] = {
295	{ .compatible = "rockchip,rk2928-pwm", .data = &pwm_data_v1},
296	{ .compatible = "rockchip,rk3288-pwm", .data = &pwm_data_v2},
297	{ .compatible = "rockchip,vop-pwm", .data = &pwm_data_vop},
298	{ .compatible = "rockchip,rk3328-pwm", .data = &pwm_data_v3},
299	{ /* sentinel */ }
300};
301MODULE_DEVICE_TABLE(of, rockchip_pwm_dt_ids);
302
303static int rockchip_pwm_probe(struct platform_device *pdev)
304{
305	struct pwm_chip *chip;
306	struct rockchip_pwm_chip *pc;
307	u32 enable_conf, ctrl;
308	bool enabled;
309	int ret, count;
310
311	chip = devm_pwmchip_alloc(&pdev->dev, 1, sizeof(*pc));
312	if (IS_ERR(chip))
313		return PTR_ERR(chip);
314	pc = to_rockchip_pwm_chip(chip);
315
316	pc->base = devm_platform_ioremap_resource(pdev, 0);
317	if (IS_ERR(pc->base))
318		return PTR_ERR(pc->base);
319
320	pc->clk = devm_clk_get(&pdev->dev, "pwm");
321	if (IS_ERR(pc->clk)) {
322		pc->clk = devm_clk_get(&pdev->dev, NULL);
323		if (IS_ERR(pc->clk))
324			return dev_err_probe(&pdev->dev, PTR_ERR(pc->clk),
325					     "Can't get PWM clk\n");
326	}
327
328	count = of_count_phandle_with_args(pdev->dev.of_node,
329					   "clocks", "#clock-cells");
330	if (count == 2)
331		pc->pclk = devm_clk_get(&pdev->dev, "pclk");
332	else
333		pc->pclk = pc->clk;
334
335	if (IS_ERR(pc->pclk))
336		return dev_err_probe(&pdev->dev, PTR_ERR(pc->pclk), "Can't get APB clk\n");
337
338	ret = clk_prepare_enable(pc->clk);
339	if (ret)
340		return dev_err_probe(&pdev->dev, ret, "Can't prepare enable PWM clk\n");
341
342	ret = clk_prepare_enable(pc->pclk);
343	if (ret) {
344		dev_err_probe(&pdev->dev, ret, "Can't prepare enable APB clk\n");
345		goto err_clk;
346	}
347
348	platform_set_drvdata(pdev, chip);
349
350	pc->data = device_get_match_data(&pdev->dev);
351	chip->ops = &rockchip_pwm_ops;
352
353	enable_conf = pc->data->enable_conf;
354	ctrl = readl_relaxed(pc->base + pc->data->regs.ctrl);
355	enabled = (ctrl & enable_conf) == enable_conf;
356
357	ret = pwmchip_add(chip);
358	if (ret < 0) {
359		dev_err_probe(&pdev->dev, ret, "pwmchip_add() failed\n");
360		goto err_pclk;
361	}
362
363	/* Keep the PWM clk enabled if the PWM appears to be up and running. */
364	if (!enabled)
365		clk_disable(pc->clk);
366
367	clk_disable(pc->pclk);
368
369	return 0;
370
371err_pclk:
372	clk_disable_unprepare(pc->pclk);
373err_clk:
374	clk_disable_unprepare(pc->clk);
375
376	return ret;
377}
378
379static void rockchip_pwm_remove(struct platform_device *pdev)
380{
381	struct pwm_chip *chip = platform_get_drvdata(pdev);
382	struct rockchip_pwm_chip *pc = to_rockchip_pwm_chip(chip);
383
384	pwmchip_remove(chip);
385
386	clk_unprepare(pc->pclk);
387	clk_unprepare(pc->clk);
388}
389
390static struct platform_driver rockchip_pwm_driver = {
391	.driver = {
392		.name = "rockchip-pwm",
393		.of_match_table = rockchip_pwm_dt_ids,
394	},
395	.probe = rockchip_pwm_probe,
396	.remove = rockchip_pwm_remove,
397};
398module_platform_driver(rockchip_pwm_driver);
399
400MODULE_AUTHOR("Beniamino Galvani <b.galvani@gmail.com>");
401MODULE_DESCRIPTION("Rockchip SoC PWM driver");
402MODULE_LICENSE("GPL v2");
~/ Read-only file preview.