]>
Commit | Line | Data |
---|---|---|
9c92ab61 | 1 | // SPDX-License-Identifier: GPL-2.0-only |
5182c1a5 YY |
2 | /* |
3 | * Copyright (C) Fuzhou Rockchip Electronics Co.Ltd | |
4 | * Author: Yakir Yang <ykk@rock-chips.com> | |
5182c1a5 YY |
5 | */ |
6 | ||
7 | #include <drm/drmP.h> | |
15609559 | 8 | #include <drm/drm_atomic.h> |
fcd70cd3 | 9 | #include <drm/drm_probe_helper.h> |
5182c1a5 YY |
10 | |
11 | #include "rockchip_drm_drv.h" | |
12 | #include "rockchip_drm_psr.h" | |
13 | ||
60beeccc | 14 | #define PSR_FLUSH_TIMEOUT_MS 100 |
5182c1a5 | 15 | |
5182c1a5 YY |
16 | struct psr_drv { |
17 | struct list_head list; | |
18 | struct drm_encoder *encoder; | |
19 | ||
60beeccc | 20 | struct mutex lock; |
6e6cf3e2 | 21 | int inhibit_count; |
39b138ea | 22 | bool enabled; |
5182c1a5 | 23 | |
60beeccc | 24 | struct delayed_work flush_work; |
5182c1a5 | 25 | |
2a7b44c5 | 26 | int (*set)(struct drm_encoder *encoder, bool enable); |
5182c1a5 YY |
27 | }; |
28 | ||
7f3c191b | 29 | static struct psr_drv *find_psr_by_encoder(struct drm_encoder *encoder) |
30 | { | |
31 | struct rockchip_drm_private *drm_drv = encoder->dev->dev_private; | |
32 | struct psr_drv *psr; | |
7f3c191b | 33 | |
60beeccc | 34 | mutex_lock(&drm_drv->psr_list_lock); |
7f3c191b | 35 | list_for_each_entry(psr, &drm_drv->psr_list, list) { |
36 | if (psr->encoder == encoder) | |
37 | goto out; | |
38 | } | |
39 | psr = ERR_PTR(-ENODEV); | |
40 | ||
41 | out: | |
60beeccc | 42 | mutex_unlock(&drm_drv->psr_list_lock); |
7f3c191b | 43 | return psr; |
44 | } | |
45 | ||
39b138ea | 46 | static int psr_set_state_locked(struct psr_drv *psr, bool enable) |
5182c1a5 | 47 | { |
39b138ea | 48 | int ret; |
be91a983 | 49 | |
6e6cf3e2 | 50 | if (psr->inhibit_count > 0) |
39b138ea | 51 | return -EINVAL; |
2a7b44c5 | 52 | |
39b138ea TF |
53 | if (enable == psr->enabled) |
54 | return 0; | |
5182c1a5 | 55 | |
39b138ea TF |
56 | ret = psr->set(psr->encoder, enable); |
57 | if (ret) | |
58 | return ret; | |
59 | ||
60 | psr->enabled = enable; | |
61 | return 0; | |
eec85347 SP |
62 | } |
63 | ||
60beeccc | 64 | static void psr_flush_handler(struct work_struct *work) |
5182c1a5 | 65 | { |
60beeccc SP |
66 | struct psr_drv *psr = container_of(to_delayed_work(work), |
67 | struct psr_drv, flush_work); | |
5182c1a5 | 68 | |
60beeccc | 69 | mutex_lock(&psr->lock); |
39b138ea | 70 | psr_set_state_locked(psr, true); |
60beeccc | 71 | mutex_unlock(&psr->lock); |
5182c1a5 YY |
72 | } |
73 | ||
74 | /** | |
6e6cf3e2 | 75 | * rockchip_drm_psr_inhibit_put - release PSR inhibit on given encoder |
7f3c191b | 76 | * @encoder: encoder to obtain the PSR encoder |
5182c1a5 | 77 | * |
6e6cf3e2 TF |
78 | * Decrements PSR inhibit count on given encoder. Should be called only |
79 | * for a PSR inhibit count increment done before. If PSR inhibit counter | |
80 | * reaches zero, PSR flush work is scheduled to make the hardware enter | |
81 | * PSR mode in PSR_FLUSH_TIMEOUT_MS. | |
82 | * | |
5182c1a5 YY |
83 | * Returns: |
84 | * Zero on success, negative errno on failure. | |
85 | */ | |
6e6cf3e2 | 86 | int rockchip_drm_psr_inhibit_put(struct drm_encoder *encoder) |
5182c1a5 | 87 | { |
7f3c191b | 88 | struct psr_drv *psr = find_psr_by_encoder(encoder); |
5182c1a5 YY |
89 | |
90 | if (IS_ERR(psr)) | |
91 | return PTR_ERR(psr); | |
92 | ||
60beeccc | 93 | mutex_lock(&psr->lock); |
6e6cf3e2 TF |
94 | --psr->inhibit_count; |
95 | WARN_ON(psr->inhibit_count < 0); | |
96 | if (!psr->inhibit_count) | |
97 | mod_delayed_work(system_wq, &psr->flush_work, | |
98 | PSR_FLUSH_TIMEOUT_MS); | |
60beeccc | 99 | mutex_unlock(&psr->lock); |
b883c9ba | 100 | |
5182c1a5 YY |
101 | return 0; |
102 | } | |
6e6cf3e2 | 103 | EXPORT_SYMBOL(rockchip_drm_psr_inhibit_put); |
5182c1a5 | 104 | |
15609559 EBS |
105 | void rockchip_drm_psr_inhibit_get_state(struct drm_atomic_state *state) |
106 | { | |
107 | struct drm_crtc *crtc; | |
108 | struct drm_crtc_state *crtc_state; | |
109 | struct drm_encoder *encoder; | |
110 | u32 encoder_mask = 0; | |
111 | int i; | |
112 | ||
113 | for_each_old_crtc_in_state(state, crtc, crtc_state, i) { | |
114 | encoder_mask |= crtc_state->encoder_mask; | |
115 | encoder_mask |= crtc->state->encoder_mask; | |
116 | } | |
117 | ||
118 | drm_for_each_encoder_mask(encoder, state->dev, encoder_mask) | |
119 | rockchip_drm_psr_inhibit_get(encoder); | |
120 | } | |
121 | EXPORT_SYMBOL(rockchip_drm_psr_inhibit_get_state); | |
122 | ||
123 | void rockchip_drm_psr_inhibit_put_state(struct drm_atomic_state *state) | |
124 | { | |
125 | struct drm_crtc *crtc; | |
126 | struct drm_crtc_state *crtc_state; | |
127 | struct drm_encoder *encoder; | |
128 | u32 encoder_mask = 0; | |
129 | int i; | |
130 | ||
131 | for_each_old_crtc_in_state(state, crtc, crtc_state, i) { | |
132 | encoder_mask |= crtc_state->encoder_mask; | |
133 | encoder_mask |= crtc->state->encoder_mask; | |
134 | } | |
135 | ||
136 | drm_for_each_encoder_mask(encoder, state->dev, encoder_mask) | |
137 | rockchip_drm_psr_inhibit_put(encoder); | |
138 | } | |
139 | EXPORT_SYMBOL(rockchip_drm_psr_inhibit_put_state); | |
140 | ||
5182c1a5 | 141 | /** |
6e6cf3e2 | 142 | * rockchip_drm_psr_inhibit_get - acquire PSR inhibit on given encoder |
7f3c191b | 143 | * @encoder: encoder to obtain the PSR encoder |
5182c1a5 | 144 | * |
6e6cf3e2 TF |
145 | * Increments PSR inhibit count on given encoder. This function guarantees |
146 | * that after it returns PSR is turned off on given encoder and no PSR-related | |
147 | * hardware state change occurs at least until a matching call to | |
148 | * rockchip_drm_psr_inhibit_put() is done. | |
149 | * | |
5182c1a5 YY |
150 | * Returns: |
151 | * Zero on success, negative errno on failure. | |
152 | */ | |
6e6cf3e2 | 153 | int rockchip_drm_psr_inhibit_get(struct drm_encoder *encoder) |
5182c1a5 | 154 | { |
7f3c191b | 155 | struct psr_drv *psr = find_psr_by_encoder(encoder); |
b883c9ba SP |
156 | |
157 | if (IS_ERR(psr)) | |
158 | return PTR_ERR(psr); | |
159 | ||
60beeccc | 160 | mutex_lock(&psr->lock); |
6e6cf3e2 TF |
161 | psr_set_state_locked(psr, false); |
162 | ++psr->inhibit_count; | |
60beeccc SP |
163 | mutex_unlock(&psr->lock); |
164 | cancel_delayed_work_sync(&psr->flush_work); | |
b883c9ba SP |
165 | |
166 | return 0; | |
167 | } | |
6e6cf3e2 | 168 | EXPORT_SYMBOL(rockchip_drm_psr_inhibit_get); |
b883c9ba SP |
169 | |
170 | static void rockchip_drm_do_flush(struct psr_drv *psr) | |
171 | { | |
39b138ea TF |
172 | cancel_delayed_work_sync(&psr->flush_work); |
173 | ||
174 | mutex_lock(&psr->lock); | |
175 | if (!psr_set_state_locked(psr, false)) | |
176 | mod_delayed_work(system_wq, &psr->flush_work, | |
177 | PSR_FLUSH_TIMEOUT_MS); | |
178 | mutex_unlock(&psr->lock); | |
b883c9ba | 179 | } |
5182c1a5 | 180 | |
5182c1a5 | 181 | /** |
b883c9ba | 182 | * rockchip_drm_psr_flush_all - force to flush all registered PSR encoders |
5182c1a5 YY |
183 | * @dev: drm device |
184 | * | |
185 | * Disable the PSR function for all registered encoders, and then enable the | |
186 | * PSR function back after PSR_FLUSH_TIMEOUT. If encoder PSR state have been | |
187 | * changed during flush time, then keep the state no change after flush | |
188 | * timeout. | |
189 | * | |
190 | * Returns: | |
191 | * Zero on success, negative errno on failure. | |
192 | */ | |
b883c9ba | 193 | void rockchip_drm_psr_flush_all(struct drm_device *dev) |
5182c1a5 YY |
194 | { |
195 | struct rockchip_drm_private *drm_drv = dev->dev_private; | |
196 | struct psr_drv *psr; | |
197 | ||
60beeccc | 198 | mutex_lock(&drm_drv->psr_list_lock); |
b883c9ba SP |
199 | list_for_each_entry(psr, &drm_drv->psr_list, list) |
200 | rockchip_drm_do_flush(psr); | |
60beeccc | 201 | mutex_unlock(&drm_drv->psr_list_lock); |
5182c1a5 | 202 | } |
b883c9ba | 203 | EXPORT_SYMBOL(rockchip_drm_psr_flush_all); |
5182c1a5 YY |
204 | |
205 | /** | |
206 | * rockchip_drm_psr_register - register encoder to psr driver | |
207 | * @encoder: encoder that obtain the PSR function | |
208 | * @psr_set: call back to set PSR state | |
209 | * | |
6e6cf3e2 TF |
210 | * The function returns with PSR inhibit counter initialized with one |
211 | * and the caller (typically encoder driver) needs to call | |
212 | * rockchip_drm_psr_inhibit_put() when it becomes ready to accept PSR | |
213 | * enable request. | |
214 | * | |
5182c1a5 YY |
215 | * Returns: |
216 | * Zero on success, negative errno on failure. | |
217 | */ | |
218 | int rockchip_drm_psr_register(struct drm_encoder *encoder, | |
2a7b44c5 | 219 | int (*psr_set)(struct drm_encoder *, bool enable)) |
5182c1a5 | 220 | { |
4eda776c | 221 | struct rockchip_drm_private *drm_drv; |
5182c1a5 YY |
222 | struct psr_drv *psr; |
223 | ||
224 | if (!encoder || !psr_set) | |
225 | return -EINVAL; | |
226 | ||
4eda776c EBS |
227 | drm_drv = encoder->dev->dev_private; |
228 | ||
5182c1a5 YY |
229 | psr = kzalloc(sizeof(struct psr_drv), GFP_KERNEL); |
230 | if (!psr) | |
231 | return -ENOMEM; | |
232 | ||
60beeccc SP |
233 | INIT_DELAYED_WORK(&psr->flush_work, psr_flush_handler); |
234 | mutex_init(&psr->lock); | |
5182c1a5 | 235 | |
6e6cf3e2 | 236 | psr->inhibit_count = 1; |
39b138ea | 237 | psr->enabled = false; |
5182c1a5 YY |
238 | psr->encoder = encoder; |
239 | psr->set = psr_set; | |
240 | ||
60beeccc | 241 | mutex_lock(&drm_drv->psr_list_lock); |
5182c1a5 | 242 | list_add_tail(&psr->list, &drm_drv->psr_list); |
60beeccc | 243 | mutex_unlock(&drm_drv->psr_list_lock); |
5182c1a5 YY |
244 | |
245 | return 0; | |
246 | } | |
247 | EXPORT_SYMBOL(rockchip_drm_psr_register); | |
248 | ||
249 | /** | |
250 | * rockchip_drm_psr_unregister - unregister encoder to psr driver | |
251 | * @encoder: encoder that obtain the PSR function | |
252 | * @psr_set: call back to set PSR state | |
253 | * | |
6e6cf3e2 TF |
254 | * It is expected that the PSR inhibit counter is 1 when this function is |
255 | * called, which corresponds to a state when related encoder has been | |
256 | * disconnected from any CRTCs and its driver called | |
257 | * rockchip_drm_psr_inhibit_get() to stop the PSR logic. | |
258 | * | |
5182c1a5 YY |
259 | * Returns: |
260 | * Zero on success, negative errno on failure. | |
261 | */ | |
262 | void rockchip_drm_psr_unregister(struct drm_encoder *encoder) | |
263 | { | |
264 | struct rockchip_drm_private *drm_drv = encoder->dev->dev_private; | |
265 | struct psr_drv *psr, *n; | |
266 | ||
60beeccc | 267 | mutex_lock(&drm_drv->psr_list_lock); |
5182c1a5 YY |
268 | list_for_each_entry_safe(psr, n, &drm_drv->psr_list, list) { |
269 | if (psr->encoder == encoder) { | |
6e6cf3e2 TF |
270 | /* |
271 | * Any other value would mean that the encoder | |
272 | * is still in use. | |
273 | */ | |
274 | WARN_ON(psr->inhibit_count != 1); | |
275 | ||
5182c1a5 YY |
276 | list_del(&psr->list); |
277 | kfree(psr); | |
278 | } | |
279 | } | |
60beeccc | 280 | mutex_unlock(&drm_drv->psr_list_lock); |
5182c1a5 YY |
281 | } |
282 | EXPORT_SYMBOL(rockchip_drm_psr_unregister); |