drm/rockchip: Use a spinlock to protect psr state
[cascardo/linux.git] / drivers / gpu / drm / rockchip / rockchip_drm_psr.c
1 /*
2  * Copyright (C) Fuzhou Rockchip Electronics Co.Ltd
3  * Author: Yakir Yang <ykk@rock-chips.com>
4  *
5  * This software is licensed under the terms of the GNU General Public
6  * License version 2, as published by the Free Software Foundation, and
7  * may be copied, distributed, and modified under those terms.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  */
14
15 #include <drm/drmP.h>
16 #include <drm/drm_crtc_helper.h>
17
18 #include "rockchip_drm_drv.h"
19 #include "rockchip_drm_psr.h"
20
21 #define PSR_FLUSH_TIMEOUT       msecs_to_jiffies(3000) /* 3 seconds */
22
23 enum psr_state {
24         PSR_FLUSH,
25         PSR_ENABLE,
26         PSR_DISABLE,
27 };
28
29 struct psr_drv {
30         struct list_head        list;
31         struct drm_encoder      *encoder;
32
33         spinlock_t              lock;
34         enum psr_state          state;
35
36         struct timer_list       flush_timer;
37
38         void (*set)(struct drm_encoder *encoder, bool enable);
39 };
40
41 static struct psr_drv *find_psr_by_crtc(struct drm_crtc *crtc)
42 {
43         struct rockchip_drm_private *drm_drv = crtc->dev->dev_private;
44         struct psr_drv *psr;
45         unsigned long flags;
46
47         spin_lock_irqsave(&drm_drv->psr_list_lock, flags);
48         list_for_each_entry(psr, &drm_drv->psr_list, list) {
49                 if (psr->encoder->crtc == crtc)
50                         goto out;
51         }
52         psr = ERR_PTR(-ENODEV);
53
54 out:
55         spin_unlock_irqrestore(&drm_drv->psr_list_lock, flags);
56         return psr;
57 }
58
59 static void psr_set_state_locked(struct psr_drv *psr, enum psr_state state)
60 {
61         /*
62          * Allowed finite state machine:
63          *
64          *   PSR_ENABLE  < = = = = = >  PSR_FLUSH
65           *      | ^                        |
66           *      | |                        |
67           *      v |                        |
68          *   PSR_DISABLE < - - - - - - - - -
69          */
70
71         /* Forbid no state change */
72         if (state == psr->state)
73                 return;
74
75         /* Forbid DISABLE change to FLUSH */
76         if (state == PSR_FLUSH && psr->state == PSR_DISABLE)
77                 return;
78
79         psr->state = state;
80
81         /* Allow but no need hardware change, just need assign the state */
82         if (state == PSR_DISABLE && psr->state == PSR_FLUSH)
83                 return;
84
85         /* Refact to hardware state change */
86         switch (psr->state) {
87         case PSR_ENABLE:
88                 psr->set(psr->encoder, true);
89                 break;
90
91         case PSR_DISABLE:
92         case PSR_FLUSH:
93                 psr->set(psr->encoder, false);
94                 break;
95         }
96 }
97
98 static void psr_set_state(struct psr_drv *psr, enum psr_state state)
99 {
100         unsigned long flags;
101
102         spin_lock_irqsave(&psr->lock, flags);
103         psr_set_state_locked(psr, state);
104         spin_unlock_irqrestore(&psr->lock, flags);
105 }
106
107 static void psr_flush_handler(unsigned long data)
108 {
109         struct psr_drv *psr = (struct psr_drv *)data;
110         unsigned long flags;
111
112         if (!psr)
113                 return;
114
115         /* State changed between flush time, then keep it */
116         spin_lock_irqsave(&psr->lock, flags);
117         if (psr->state == PSR_FLUSH)
118                 psr_set_state_locked(psr, PSR_ENABLE);
119         spin_unlock_irqrestore(&psr->lock, flags);
120 }
121
122 /**
123  * rockchip_drm_psr_enable - enable the encoder PSR which bind to given CRTC
124  * @crtc: CRTC to obtain the PSR encoder
125  *
126  * Returns:
127  * Zero on success, negative errno on failure.
128  */
129 int rockchip_drm_psr_enable(struct drm_crtc *crtc)
130 {
131         struct psr_drv *psr = find_psr_by_crtc(crtc);
132
133         if (IS_ERR(psr))
134                 return PTR_ERR(psr);
135
136         psr_set_state(psr, PSR_ENABLE);
137         return 0;
138 }
139 EXPORT_SYMBOL(rockchip_drm_psr_enable);
140
141 /**
142  * rockchip_drm_psr_disable - disable the encoder PSR which bind to given CRTC
143  * @crtc: CRTC to obtain the PSR encoder
144  *
145  * Returns:
146  * Zero on success, negative errno on failure.
147  */
148 int rockchip_drm_psr_disable(struct drm_crtc *crtc)
149 {
150         struct psr_drv *psr = find_psr_by_crtc(crtc);
151
152         if (IS_ERR(psr))
153                 return PTR_ERR(psr);
154
155         psr_set_state(psr, PSR_DISABLE);
156         return 0;
157 }
158 EXPORT_SYMBOL(rockchip_drm_psr_disable);
159
160 /**
161  * rockchip_drm_psr_flush - force to flush all registered PSR encoders
162  * @dev: drm device
163  *
164  * Disable the PSR function for all registered encoders, and then enable the
165  * PSR function back after PSR_FLUSH_TIMEOUT. If encoder PSR state have been
166  * changed during flush time, then keep the state no change after flush
167  * timeout.
168  *
169  * Returns:
170  * Zero on success, negative errno on failure.
171  */
172 void rockchip_drm_psr_flush(struct drm_device *dev)
173 {
174         struct rockchip_drm_private *drm_drv = dev->dev_private;
175         struct psr_drv *psr;
176         unsigned long flags;
177
178         spin_lock_irqsave(&drm_drv->psr_list_lock, flags);
179         list_for_each_entry(psr, &drm_drv->psr_list, list) {
180                 mod_timer(&psr->flush_timer,
181                           round_jiffies_up(jiffies + PSR_FLUSH_TIMEOUT));
182
183                 psr_set_state(psr, PSR_FLUSH);
184         }
185         spin_unlock_irqrestore(&drm_drv->psr_list_lock, flags);
186 }
187 EXPORT_SYMBOL(rockchip_drm_psr_flush);
188
189 /**
190  * rockchip_drm_psr_register - register encoder to psr driver
191  * @encoder: encoder that obtain the PSR function
192  * @psr_set: call back to set PSR state
193  *
194  * Returns:
195  * Zero on success, negative errno on failure.
196  */
197 int rockchip_drm_psr_register(struct drm_encoder *encoder,
198                         void (*psr_set)(struct drm_encoder *, bool enable))
199 {
200         struct rockchip_drm_private *drm_drv = encoder->dev->dev_private;
201         struct psr_drv *psr;
202         unsigned long flags;
203
204         if (!encoder || !psr_set)
205                 return -EINVAL;
206
207         psr = kzalloc(sizeof(struct psr_drv), GFP_KERNEL);
208         if (!psr)
209                 return -ENOMEM;
210
211         setup_timer(&psr->flush_timer, psr_flush_handler, (unsigned long)psr);
212         spin_lock_init(&psr->lock);
213
214         psr->state = PSR_DISABLE;
215         psr->encoder = encoder;
216         psr->set = psr_set;
217
218         spin_lock_irqsave(&drm_drv->psr_list_lock, flags);
219         list_add_tail(&psr->list, &drm_drv->psr_list);
220         spin_unlock_irqrestore(&drm_drv->psr_list_lock, flags);
221
222         return 0;
223 }
224 EXPORT_SYMBOL(rockchip_drm_psr_register);
225
226 /**
227  * rockchip_drm_psr_unregister - unregister encoder to psr driver
228  * @encoder: encoder that obtain the PSR function
229  * @psr_set: call back to set PSR state
230  *
231  * Returns:
232  * Zero on success, negative errno on failure.
233  */
234 void rockchip_drm_psr_unregister(struct drm_encoder *encoder)
235 {
236         struct rockchip_drm_private *drm_drv = encoder->dev->dev_private;
237         struct psr_drv *psr, *n;
238         unsigned long flags;
239
240         spin_lock_irqsave(&drm_drv->psr_list_lock, flags);
241         list_for_each_entry_safe(psr, n, &drm_drv->psr_list, list) {
242                 if (psr->encoder == encoder) {
243                         del_timer(&psr->flush_timer);
244                         list_del(&psr->list);
245                         kfree(psr);
246                 }
247         }
248         spin_unlock_irqrestore(&drm_drv->psr_list_lock, flags);
249 }
250 EXPORT_SYMBOL(rockchip_drm_psr_unregister);