1 // SPDX-License-Identifier: GPL-2.0
3 * DesignWare PWM Controller driver core
5 * Copyright (C) 2018-2020 Intel Corporation
7 * Author: Felipe Balbi (Intel)
8 * Author: Jarkko Nikula <jarkko.nikula@linux.intel.com>
9 * Author: Raymond Tan <raymond.tan@intel.com>
12 #define DEFAULT_SYMBOL_NAMESPACE dwc_pwm
14 #include <linux/bitops.h>
15 #include <linux/export.h>
16 #include <linux/kernel.h>
17 #include <linux/module.h>
18 #include <linux/pci.h>
19 #include <linux/pm_runtime.h>
20 #include <linux/pwm.h>
24 static void __dwc_pwm_set_enable(struct dwc_pwm
*dwc
, int pwm
, int enabled
)
28 reg
= dwc_pwm_readl(dwc
, DWC_TIM_CTRL(pwm
));
31 reg
|= DWC_TIM_CTRL_EN
;
33 reg
&= ~DWC_TIM_CTRL_EN
;
35 dwc_pwm_writel(dwc
, reg
, DWC_TIM_CTRL(pwm
));
38 static int __dwc_pwm_configure_timer(struct dwc_pwm
*dwc
,
39 struct pwm_device
*pwm
,
40 const struct pwm_state
*state
)
48 * Calculate width of low and high period in terms of input clock
49 * periods and check are the result within HW limits between 1 and
52 tmp
= DIV_ROUND_CLOSEST_ULL(state
->duty_cycle
, dwc
->clk_ns
);
53 if (tmp
< 1 || tmp
> (1ULL << 32))
57 tmp
= DIV_ROUND_CLOSEST_ULL(state
->period
- state
->duty_cycle
,
59 if (tmp
< 1 || tmp
> (1ULL << 32))
64 * Specification says timer usage flow is to disable timer, then
65 * program it followed by enable. It also says Load Count is loaded
66 * into timer after it is enabled - either after a disable or
67 * a reset. Based on measurements it happens also without disable
68 * whenever Load Count is updated. But follow the specification.
70 __dwc_pwm_set_enable(dwc
, pwm
->hwpwm
, false);
73 * Write Load Count and Load Count 2 registers. Former defines the
74 * width of low period and latter the width of high period in terms
75 * multiple of input clock periods:
76 * Width = ((Count + 1) * input clock period).
78 dwc_pwm_writel(dwc
, low
, DWC_TIM_LD_CNT(pwm
->hwpwm
));
79 dwc_pwm_writel(dwc
, high
, DWC_TIM_LD_CNT2(pwm
->hwpwm
));
82 * Set user-defined mode, timer reloads from Load Count registers
83 * when it counts down to 0.
84 * Set PWM mode, it makes output to toggle and width of low and high
85 * periods are set by Load Count registers.
87 ctrl
= DWC_TIM_CTRL_MODE_USER
| DWC_TIM_CTRL_PWM
;
88 dwc_pwm_writel(dwc
, ctrl
, DWC_TIM_CTRL(pwm
->hwpwm
));
91 * Enable timer. Output starts from low period.
93 __dwc_pwm_set_enable(dwc
, pwm
->hwpwm
, state
->enabled
);
98 static int dwc_pwm_apply(struct pwm_chip
*chip
, struct pwm_device
*pwm
,
99 const struct pwm_state
*state
)
101 struct dwc_pwm
*dwc
= to_dwc_pwm(chip
);
103 if (state
->polarity
!= PWM_POLARITY_INVERSED
)
106 if (state
->enabled
) {
107 if (!pwm
->state
.enabled
)
108 pm_runtime_get_sync(pwmchip_parent(chip
));
109 return __dwc_pwm_configure_timer(dwc
, pwm
, state
);
111 if (pwm
->state
.enabled
) {
112 __dwc_pwm_set_enable(dwc
, pwm
->hwpwm
, false);
113 pm_runtime_put_sync(pwmchip_parent(chip
));
120 static int dwc_pwm_get_state(struct pwm_chip
*chip
, struct pwm_device
*pwm
,
121 struct pwm_state
*state
)
123 struct dwc_pwm
*dwc
= to_dwc_pwm(chip
);
127 pm_runtime_get_sync(pwmchip_parent(chip
));
129 ctrl
= dwc_pwm_readl(dwc
, DWC_TIM_CTRL(pwm
->hwpwm
));
130 ld
= dwc_pwm_readl(dwc
, DWC_TIM_LD_CNT(pwm
->hwpwm
));
131 ld2
= dwc_pwm_readl(dwc
, DWC_TIM_LD_CNT2(pwm
->hwpwm
));
133 state
->enabled
= !!(ctrl
& DWC_TIM_CTRL_EN
);
136 * If we're not in PWM, technically the output is a 50-50
137 * based on the timer load-count only.
139 if (ctrl
& DWC_TIM_CTRL_PWM
) {
140 duty
= (ld
+ 1) * dwc
->clk_ns
;
141 period
= (ld2
+ 1) * dwc
->clk_ns
;
144 duty
= (ld
+ 1) * dwc
->clk_ns
;
148 state
->polarity
= PWM_POLARITY_INVERSED
;
149 state
->period
= period
;
150 state
->duty_cycle
= duty
;
152 pm_runtime_put_sync(pwmchip_parent(chip
));
157 static const struct pwm_ops dwc_pwm_ops
= {
158 .apply
= dwc_pwm_apply
,
159 .get_state
= dwc_pwm_get_state
,
162 struct pwm_chip
*dwc_pwm_alloc(struct device
*dev
)
164 struct pwm_chip
*chip
;
167 chip
= devm_pwmchip_alloc(dev
, DWC_TIMERS_TOTAL
, sizeof(*dwc
));
170 dwc
= to_dwc_pwm(chip
);
173 chip
->ops
= &dwc_pwm_ops
;
177 EXPORT_SYMBOL_GPL(dwc_pwm_alloc
);
179 MODULE_AUTHOR("Felipe Balbi (Intel)");
180 MODULE_AUTHOR("Jarkko Nikula <jarkko.nikula@linux.intel.com>");
181 MODULE_AUTHOR("Raymond Tan <raymond.tan@intel.com>");
182 MODULE_DESCRIPTION("DesignWare PWM Controller");
183 MODULE_LICENSE("GPL");