Commit | Line | Data |
---|---|---|
e747cbe2 | 1 | // SPDX-License-Identifier: GPL-2.0 |
e5a06dc5 BT |
2 | /* |
3 | * Copyright 2014 Bart Tanghe <bart.tanghe@thomasmore.be> | |
e5a06dc5 BT |
4 | */ |
5 | ||
6 | #include <linux/clk.h> | |
7 | #include <linux/err.h> | |
8 | #include <linux/io.h> | |
9 | #include <linux/module.h> | |
10 | #include <linux/of.h> | |
11 | #include <linux/platform_device.h> | |
12 | #include <linux/pwm.h> | |
13 | ||
14 | #define PWM_CONTROL 0x000 | |
15 | #define PWM_CONTROL_SHIFT(x) ((x) * 8) | |
16 | #define PWM_CONTROL_MASK 0xff | |
17 | #define PWM_MODE 0x80 /* set timer in PWM mode */ | |
18 | #define PWM_ENABLE (1 << 0) | |
19 | #define PWM_POLARITY (1 << 4) | |
20 | ||
21 | #define PERIOD(x) (((x) * 0x10) + 0x10) | |
22 | #define DUTY(x) (((x) * 0x10) + 0x14) | |
23 | ||
7e9713af | 24 | #define PERIOD_MIN 0x2 |
e5a06dc5 BT |
25 | |
26 | struct bcm2835_pwm { | |
27 | struct pwm_chip chip; | |
28 | struct device *dev; | |
e5a06dc5 BT |
29 | void __iomem *base; |
30 | struct clk *clk; | |
fcc76072 | 31 | unsigned long rate; |
e5a06dc5 BT |
32 | }; |
33 | ||
34 | static inline struct bcm2835_pwm *to_bcm2835_pwm(struct pwm_chip *chip) | |
35 | { | |
36 | return container_of(chip, struct bcm2835_pwm, chip); | |
37 | } | |
38 | ||
39 | static int bcm2835_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm) | |
40 | { | |
41 | struct bcm2835_pwm *pc = to_bcm2835_pwm(chip); | |
42 | u32 value; | |
43 | ||
44 | value = readl(pc->base + PWM_CONTROL); | |
45 | value &= ~(PWM_CONTROL_MASK << PWM_CONTROL_SHIFT(pwm->hwpwm)); | |
46 | value |= (PWM_MODE << PWM_CONTROL_SHIFT(pwm->hwpwm)); | |
47 | writel(value, pc->base + PWM_CONTROL); | |
48 | ||
49 | return 0; | |
50 | } | |
51 | ||
52 | static void bcm2835_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm) | |
53 | { | |
54 | struct bcm2835_pwm *pc = to_bcm2835_pwm(chip); | |
55 | u32 value; | |
56 | ||
57 | value = readl(pc->base + PWM_CONTROL); | |
58 | value &= ~(PWM_CONTROL_MASK << PWM_CONTROL_SHIFT(pwm->hwpwm)); | |
59 | writel(value, pc->base + PWM_CONTROL); | |
60 | } | |
61 | ||
2f81b51d LS |
62 | static int bcm2835_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm, |
63 | const struct pwm_state *state) | |
e5a06dc5 | 64 | { |
2f81b51d | 65 | |
e5a06dc5 | 66 | struct bcm2835_pwm *pc = to_bcm2835_pwm(chip); |
ca0d2fb7 UKK |
67 | unsigned long long period_cycles; |
68 | u64 max_period; | |
69 | ||
2f81b51d | 70 | u32 val; |
fd13c144 | 71 | |
ca0d2fb7 UKK |
72 | /* |
73 | * period_cycles must be a 32 bit value, so period * rate / NSEC_PER_SEC | |
74 | * must be <= U32_MAX. As U32_MAX * NSEC_PER_SEC < U64_MAX the | |
75 | * multiplication period * rate doesn't overflow. | |
76 | * To calculate the maximal possible period that guarantees the | |
77 | * above inequality: | |
78 | * | |
79 | * round(period * rate / NSEC_PER_SEC) <= U32_MAX | |
80 | * <=> period * rate / NSEC_PER_SEC < U32_MAX + 0.5 | |
81 | * <=> period * rate < (U32_MAX + 0.5) * NSEC_PER_SEC | |
82 | * <=> period < ((U32_MAX + 0.5) * NSEC_PER_SEC) / rate | |
83 | * <=> period < ((U32_MAX * NSEC_PER_SEC + NSEC_PER_SEC/2) / rate | |
84 | * <=> period <= ceil((U32_MAX * NSEC_PER_SEC + NSEC_PER_SEC/2) / rate) - 1 | |
85 | */ | |
fcc76072 | 86 | max_period = DIV_ROUND_UP_ULL((u64)U32_MAX * NSEC_PER_SEC + NSEC_PER_SEC / 2, pc->rate) - 1; |
ca0d2fb7 UKK |
87 | |
88 | if (state->period > max_period) | |
89 | return -EINVAL; | |
90 | ||
2f81b51d | 91 | /* set period */ |
fcc76072 | 92 | period_cycles = DIV_ROUND_CLOSEST_ULL(state->period * pc->rate, NSEC_PER_SEC); |
e5a06dc5 | 93 | |
ca0d2fb7 UKK |
94 | /* don't accept a period that is too small */ |
95 | if (period_cycles < PERIOD_MIN) | |
e5a06dc5 | 96 | return -EINVAL; |
e5a06dc5 | 97 | |
ca0d2fb7 | 98 | writel(period_cycles, pc->base + PERIOD(pwm->hwpwm)); |
e5a06dc5 | 99 | |
2f81b51d | 100 | /* set duty cycle */ |
fcc76072 | 101 | val = DIV_ROUND_CLOSEST_ULL(state->duty_cycle * pc->rate, NSEC_PER_SEC); |
2f81b51d | 102 | writel(val, pc->base + DUTY(pwm->hwpwm)); |
e5a06dc5 | 103 | |
2f81b51d LS |
104 | /* set polarity */ |
105 | val = readl(pc->base + PWM_CONTROL); | |
e5a06dc5 | 106 | |
2f81b51d LS |
107 | if (state->polarity == PWM_POLARITY_NORMAL) |
108 | val &= ~(PWM_POLARITY << PWM_CONTROL_SHIFT(pwm->hwpwm)); | |
109 | else | |
110 | val |= PWM_POLARITY << PWM_CONTROL_SHIFT(pwm->hwpwm); | |
e5a06dc5 | 111 | |
2f81b51d LS |
112 | /* enable/disable */ |
113 | if (state->enabled) | |
114 | val |= PWM_ENABLE << PWM_CONTROL_SHIFT(pwm->hwpwm); | |
e5a06dc5 | 115 | else |
2f81b51d | 116 | val &= ~(PWM_ENABLE << PWM_CONTROL_SHIFT(pwm->hwpwm)); |
e5a06dc5 | 117 | |
2f81b51d | 118 | writel(val, pc->base + PWM_CONTROL); |
e5a06dc5 BT |
119 | |
120 | return 0; | |
121 | } | |
122 | ||
123 | static const struct pwm_ops bcm2835_pwm_ops = { | |
124 | .request = bcm2835_pwm_request, | |
125 | .free = bcm2835_pwm_free, | |
2f81b51d | 126 | .apply = bcm2835_pwm_apply, |
e5a06dc5 BT |
127 | }; |
128 | ||
fcc76072 SY |
129 | static void devm_clk_rate_exclusive_put(void *data) |
130 | { | |
131 | struct clk *clk = data; | |
132 | ||
133 | clk_rate_exclusive_put(clk); | |
134 | } | |
135 | ||
e5a06dc5 BT |
136 | static int bcm2835_pwm_probe(struct platform_device *pdev) |
137 | { | |
138 | struct bcm2835_pwm *pc; | |
e5a06dc5 BT |
139 | int ret; |
140 | ||
141 | pc = devm_kzalloc(&pdev->dev, sizeof(*pc), GFP_KERNEL); | |
142 | if (!pc) | |
143 | return -ENOMEM; | |
144 | ||
145 | pc->dev = &pdev->dev; | |
146 | ||
f57e7d25 | 147 | pc->base = devm_platform_ioremap_resource(pdev, 0); |
e5a06dc5 BT |
148 | if (IS_ERR(pc->base)) |
149 | return PTR_ERR(pc->base); | |
150 | ||
2ce7b7f6 | 151 | pc->clk = devm_clk_get_enabled(&pdev->dev, NULL); |
85a5745f KK |
152 | if (IS_ERR(pc->clk)) |
153 | return dev_err_probe(&pdev->dev, PTR_ERR(pc->clk), | |
154 | "clock not found\n"); | |
e5a06dc5 | 155 | |
fcc76072 SY |
156 | ret = clk_rate_exclusive_get(pc->clk); |
157 | if (ret) | |
158 | return dev_err_probe(&pdev->dev, ret, | |
159 | "fail to get exclusive rate\n"); | |
160 | ||
161 | ret = devm_add_action_or_reset(&pdev->dev, devm_clk_rate_exclusive_put, | |
162 | pc->clk); | |
678164a5 | 163 | if (ret) |
fcc76072 | 164 | return ret; |
fcc76072 SY |
165 | |
166 | pc->rate = clk_get_rate(pc->clk); | |
167 | if (!pc->rate) | |
168 | return dev_err_probe(&pdev->dev, -EINVAL, | |
169 | "failed to get clock rate\n"); | |
170 | ||
e5a06dc5 BT |
171 | pc->chip.dev = &pdev->dev; |
172 | pc->chip.ops = &bcm2835_pwm_ops; | |
fcc76072 | 173 | pc->chip.atomic = true; |
e5a06dc5 BT |
174 | pc->chip.npwm = 2; |
175 | ||
4e7a8dbd FF |
176 | platform_set_drvdata(pdev, pc); |
177 | ||
2ce7b7f6 | 178 | ret = devm_pwmchip_add(&pdev->dev, &pc->chip); |
e5a06dc5 | 179 | if (ret < 0) |
2ce7b7f6 UKK |
180 | return dev_err_probe(&pdev->dev, ret, |
181 | "failed to add pwmchip\n"); | |
e5a06dc5 BT |
182 | |
183 | return 0; | |
e5a06dc5 BT |
184 | } |
185 | ||
119a508c FF |
186 | static int bcm2835_pwm_suspend(struct device *dev) |
187 | { | |
188 | struct bcm2835_pwm *pc = dev_get_drvdata(dev); | |
189 | ||
190 | clk_disable_unprepare(pc->clk); | |
191 | ||
192 | return 0; | |
193 | } | |
194 | ||
195 | static int bcm2835_pwm_resume(struct device *dev) | |
196 | { | |
197 | struct bcm2835_pwm *pc = dev_get_drvdata(dev); | |
198 | ||
199 | return clk_prepare_enable(pc->clk); | |
200 | } | |
201 | ||
202 | static DEFINE_SIMPLE_DEV_PM_OPS(bcm2835_pwm_pm_ops, bcm2835_pwm_suspend, | |
203 | bcm2835_pwm_resume); | |
204 | ||
e5a06dc5 BT |
205 | static const struct of_device_id bcm2835_pwm_of_match[] = { |
206 | { .compatible = "brcm,bcm2835-pwm", }, | |
207 | { /* sentinel */ } | |
208 | }; | |
209 | MODULE_DEVICE_TABLE(of, bcm2835_pwm_of_match); | |
210 | ||
211 | static struct platform_driver bcm2835_pwm_driver = { | |
212 | .driver = { | |
213 | .name = "bcm2835-pwm", | |
214 | .of_match_table = bcm2835_pwm_of_match, | |
119a508c | 215 | .pm = pm_ptr(&bcm2835_pwm_pm_ops), |
e5a06dc5 BT |
216 | }, |
217 | .probe = bcm2835_pwm_probe, | |
e5a06dc5 BT |
218 | }; |
219 | module_platform_driver(bcm2835_pwm_driver); | |
220 | ||
6ef7d1c4 | 221 | MODULE_AUTHOR("Bart Tanghe <bart.tanghe@thomasmore.be>"); |
e5a06dc5 BT |
222 | MODULE_DESCRIPTION("Broadcom BCM2835 PWM driver"); |
223 | MODULE_LICENSE("GPL v2"); |