1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * PWM framework driver for Cirrus Logic EP93xx |
4 | * |
5 | * Copyright (c) 2009 Matthieu Crapet <mcrapet@gmail.com> |
6 | * Copyright (c) 2009, 2013 H Hartley Sweeten <hsweeten@visionengravers.com> |
7 | * |
8 | * EP9301/02 have only one channel: |
9 | * platform device ep93xx-pwm.1 - PWMOUT1 (EGPIO14) |
10 | * |
11 | * EP9307 has only one channel: |
12 | * platform device ep93xx-pwm.0 - PWMOUT |
13 | * |
14 | * EP9312/15 have two channels: |
15 | * platform device ep93xx-pwm.0 - PWMOUT |
16 | * platform device ep93xx-pwm.1 - PWMOUT1 (EGPIO14) |
17 | */ |
18 | |
19 | #include <linux/module.h> |
20 | #include <linux/platform_device.h> |
21 | #include <linux/slab.h> |
22 | #include <linux/clk.h> |
23 | #include <linux/err.h> |
24 | #include <linux/io.h> |
25 | #include <linux/pwm.h> |
26 | |
27 | #include <asm/div64.h> |
28 | |
29 | #include <linux/soc/cirrus/ep93xx.h> /* for ep93xx_pwm_{acquire,release}_gpio() */ |
30 | |
31 | #define EP93XX_PWMx_TERM_COUNT 0x00 |
32 | #define EP93XX_PWMx_DUTY_CYCLE 0x04 |
33 | #define EP93XX_PWMx_ENABLE 0x08 |
34 | #define EP93XX_PWMx_INVERT 0x0c |
35 | |
36 | struct ep93xx_pwm { |
37 | void __iomem *base; |
38 | struct clk *clk; |
39 | }; |
40 | |
41 | static inline struct ep93xx_pwm *to_ep93xx_pwm(struct pwm_chip *chip) |
42 | { |
43 | return pwmchip_get_drvdata(chip); |
44 | } |
45 | |
46 | static int ep93xx_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm) |
47 | { |
48 | struct platform_device *pdev = to_platform_device(pwmchip_parent(chip)); |
49 | |
50 | return ep93xx_pwm_acquire_gpio(pdev); |
51 | } |
52 | |
53 | static void ep93xx_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm) |
54 | { |
55 | struct platform_device *pdev = to_platform_device(pwmchip_parent(chip)); |
56 | |
57 | ep93xx_pwm_release_gpio(pdev); |
58 | } |
59 | |
60 | static int ep93xx_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm, |
61 | const struct pwm_state *state) |
62 | { |
63 | int ret; |
64 | struct ep93xx_pwm *ep93xx_pwm = to_ep93xx_pwm(chip); |
65 | bool enabled = state->enabled; |
66 | void __iomem *base = ep93xx_pwm->base; |
67 | unsigned long long c; |
68 | unsigned long period_cycles; |
69 | unsigned long duty_cycles; |
70 | unsigned long term; |
71 | |
72 | if (state->polarity != pwm->state.polarity) { |
73 | if (enabled) { |
74 | writew(val: 0x0, addr: ep93xx_pwm->base + EP93XX_PWMx_ENABLE); |
75 | clk_disable_unprepare(clk: ep93xx_pwm->clk); |
76 | enabled = false; |
77 | } |
78 | |
79 | /* |
80 | * The clock needs to be enabled to access the PWM registers. |
81 | * Polarity can only be changed when the PWM is disabled. |
82 | */ |
83 | ret = clk_prepare_enable(clk: ep93xx_pwm->clk); |
84 | if (ret) |
85 | return ret; |
86 | |
87 | if (state->polarity == PWM_POLARITY_INVERSED) |
88 | writew(val: 0x1, addr: ep93xx_pwm->base + EP93XX_PWMx_INVERT); |
89 | else |
90 | writew(val: 0x0, addr: ep93xx_pwm->base + EP93XX_PWMx_INVERT); |
91 | |
92 | clk_disable_unprepare(clk: ep93xx_pwm->clk); |
93 | } |
94 | |
95 | if (!state->enabled) { |
96 | if (enabled) { |
97 | writew(val: 0x0, addr: ep93xx_pwm->base + EP93XX_PWMx_ENABLE); |
98 | clk_disable_unprepare(clk: ep93xx_pwm->clk); |
99 | } |
100 | |
101 | return 0; |
102 | } |
103 | |
104 | /* |
105 | * The clock needs to be enabled to access the PWM registers. |
106 | * Configuration can be changed at any time. |
107 | */ |
108 | if (!pwm_is_enabled(pwm)) { |
109 | ret = clk_prepare_enable(clk: ep93xx_pwm->clk); |
110 | if (ret) |
111 | return ret; |
112 | } |
113 | |
114 | c = clk_get_rate(clk: ep93xx_pwm->clk); |
115 | c *= state->period; |
116 | do_div(c, 1000000000); |
117 | period_cycles = c; |
118 | |
119 | c = period_cycles; |
120 | c *= state->duty_cycle; |
121 | do_div(c, state->period); |
122 | duty_cycles = c; |
123 | |
124 | if (period_cycles < 0x10000 && duty_cycles < 0x10000) { |
125 | term = readw(addr: base + EP93XX_PWMx_TERM_COUNT); |
126 | |
127 | /* Order is important if PWM is running */ |
128 | if (period_cycles > term) { |
129 | writew(val: period_cycles, addr: base + EP93XX_PWMx_TERM_COUNT); |
130 | writew(val: duty_cycles, addr: base + EP93XX_PWMx_DUTY_CYCLE); |
131 | } else { |
132 | writew(val: duty_cycles, addr: base + EP93XX_PWMx_DUTY_CYCLE); |
133 | writew(val: period_cycles, addr: base + EP93XX_PWMx_TERM_COUNT); |
134 | } |
135 | ret = 0; |
136 | } else { |
137 | ret = -EINVAL; |
138 | } |
139 | |
140 | if (!pwm_is_enabled(pwm)) |
141 | clk_disable_unprepare(clk: ep93xx_pwm->clk); |
142 | |
143 | if (ret) |
144 | return ret; |
145 | |
146 | if (!enabled) { |
147 | ret = clk_prepare_enable(clk: ep93xx_pwm->clk); |
148 | if (ret) |
149 | return ret; |
150 | |
151 | writew(val: 0x1, addr: ep93xx_pwm->base + EP93XX_PWMx_ENABLE); |
152 | } |
153 | |
154 | return 0; |
155 | } |
156 | |
157 | static const struct pwm_ops ep93xx_pwm_ops = { |
158 | .request = ep93xx_pwm_request, |
159 | .free = ep93xx_pwm_free, |
160 | .apply = ep93xx_pwm_apply, |
161 | }; |
162 | |
163 | static int ep93xx_pwm_probe(struct platform_device *pdev) |
164 | { |
165 | struct pwm_chip *chip; |
166 | struct ep93xx_pwm *ep93xx_pwm; |
167 | int ret; |
168 | |
169 | chip = devm_pwmchip_alloc(parent: &pdev->dev, npwm: 1, sizeof_priv: sizeof(*ep93xx_pwm)); |
170 | if (IS_ERR(ptr: chip)) |
171 | return PTR_ERR(ptr: chip); |
172 | ep93xx_pwm = to_ep93xx_pwm(chip); |
173 | |
174 | ep93xx_pwm->base = devm_platform_ioremap_resource(pdev, index: 0); |
175 | if (IS_ERR(ptr: ep93xx_pwm->base)) |
176 | return PTR_ERR(ptr: ep93xx_pwm->base); |
177 | |
178 | ep93xx_pwm->clk = devm_clk_get(dev: &pdev->dev, id: "pwm_clk" ); |
179 | if (IS_ERR(ptr: ep93xx_pwm->clk)) |
180 | return PTR_ERR(ptr: ep93xx_pwm->clk); |
181 | |
182 | chip->ops = &ep93xx_pwm_ops; |
183 | |
184 | ret = devm_pwmchip_add(&pdev->dev, chip); |
185 | if (ret < 0) |
186 | return ret; |
187 | |
188 | return 0; |
189 | } |
190 | |
191 | static struct platform_driver ep93xx_pwm_driver = { |
192 | .driver = { |
193 | .name = "ep93xx-pwm" , |
194 | }, |
195 | .probe = ep93xx_pwm_probe, |
196 | }; |
197 | module_platform_driver(ep93xx_pwm_driver); |
198 | |
199 | MODULE_DESCRIPTION("Cirrus Logic EP93xx PWM driver" ); |
200 | MODULE_AUTHOR("Matthieu Crapet <mcrapet@gmail.com>" ); |
201 | MODULE_AUTHOR("H Hartley Sweeten <hsweeten@visionengravers.com>" ); |
202 | MODULE_ALIAS("platform:ep93xx-pwm" ); |
203 | MODULE_LICENSE("GPL" ); |
204 | |