summaryrefslogblamecommitdiffstats
path: root/drivers/gpu/drm/rockchip/rockchip_drm_psr.c
blob: b604747fe4538eec3f6710f7e11e8b02bb66adab (plain) (tree)
1
2
3
4
5
6
7
8
9
                                        


                                                   


                     
                           
                                 



                             
                                   
 



                                         
                                     
                                              
                                        
 
                                           
 
                                                             

  



                                                                         
 
                                            






                                                            
                                              


                   
                                                                 
 
                
 
                                   
                               
 

                                   
 





                                             

 
                                                       
 

                                                                       
 
                               
                                        
                                 


   
                                                                      
                                              
  




                                                                        


                                              
                                                             
 
                                                           



                                    
                               




                                                             
                                 
 

                 
                                            
 



































                                                                       
   
                                                                      
                                              
  




                                                                              


                                              
                                                             
 
                                                           



                                    
                               

                                         

                                                   


                 
                                            


                                                      






                                                             
 
 
   
                                                                          









                                                                            
                                                       



                                                                
                                            

                                                          
                                              
 
                                          





                                                             




                                                                     



                                                          
                                                                          
 
                                             




                                 

                                            



                                                          

                                                               
 
                               
                             


                               
                                            
                                                      
                                              









                                                                 




                                                                         







                                                                         
                                            

                                                                    





                                                                      



                                             
                                              

                                           
// SPDX-License-Identifier: GPL-2.0-only
/*
 * Copyright (C) Fuzhou Rockchip Electronics Co.Ltd
 * Author: Yakir Yang <ykk@rock-chips.com>
 */

#include <drm/drmP.h>
#include <drm/drm_atomic.h>
#include <drm/drm_probe_helper.h>

#include "rockchip_drm_drv.h"
#include "rockchip_drm_psr.h"

#define PSR_FLUSH_TIMEOUT_MS	100

struct psr_drv {
	struct list_head	list;
	struct drm_encoder	*encoder;

	struct mutex		lock;
	int			inhibit_count;
	bool			enabled;

	struct delayed_work	flush_work;

	int (*set)(struct drm_encoder *encoder, bool enable);
};

static struct psr_drv *find_psr_by_encoder(struct drm_encoder *encoder)
{
	struct rockchip_drm_private *drm_drv = encoder->dev->dev_private;
	struct psr_drv *psr;

	mutex_lock(&drm_drv->psr_list_lock);
	list_for_each_entry(psr, &drm_drv->psr_list, list) {
		if (psr->encoder == encoder)
			goto out;
	}
	psr = ERR_PTR(-ENODEV);

out:
	mutex_unlock(&drm_drv->psr_list_lock);
	return psr;
}

static int psr_set_state_locked(struct psr_drv *psr, bool enable)
{
	int ret;

	if (psr->inhibit_count > 0)
		return -EINVAL;

	if (enable == psr->enabled)
		return 0;

	ret = psr->set(psr->encoder, enable);
	if (ret)
		return ret;

	psr->enabled = enable;
	return 0;
}

static void psr_flush_handler(struct work_struct *work)
{
	struct psr_drv *psr = container_of(to_delayed_work(work),
					   struct psr_drv, flush_work);

	mutex_lock(&psr->lock);
	psr_set_state_locked(psr, true);
	mutex_unlock(&psr->lock);
}

/**
 * rockchip_drm_psr_inhibit_put - release PSR inhibit on given encoder
 * @encoder: encoder to obtain the PSR encoder
 *
 * Decrements PSR inhibit count on given encoder. Should be called only
 * for a PSR inhibit count increment done before. If PSR inhibit counter
 * reaches zero, PSR flush work is scheduled to make the hardware enter
 * PSR mode in PSR_FLUSH_TIMEOUT_MS.
 *
 * Returns:
 * Zero on success, negative errno on failure.
 */
int rockchip_drm_psr_inhibit_put(struct drm_encoder *encoder)
{
	struct psr_drv *psr = find_psr_by_encoder(encoder);

	if (IS_ERR(psr))
		return PTR_ERR(psr);

	mutex_lock(&psr->lock);
	--psr->inhibit_count;
	WARN_ON(psr->inhibit_count < 0);
	if (!psr->inhibit_count)
		mod_delayed_work(system_wq, &psr->flush_work,
				 PSR_FLUSH_TIMEOUT_MS);
	mutex_unlock(&psr->lock);

	return 0;
}
EXPORT_SYMBOL(rockchip_drm_psr_inhibit_put);

void rockchip_drm_psr_inhibit_get_state(struct drm_atomic_state *state)
{
	struct drm_crtc *crtc;
	struct drm_crtc_state *crtc_state;
	struct drm_encoder *encoder;
	u32 encoder_mask = 0;
	int i;

	for_each_old_crtc_in_state(state, crtc, crtc_state, i) {
		encoder_mask |= crtc_state->encoder_mask;
		encoder_mask |= crtc->state->encoder_mask;
	}

	drm_for_each_encoder_mask(encoder, state->dev, encoder_mask)
		rockchip_drm_psr_inhibit_get(encoder);
}
EXPORT_SYMBOL(rockchip_drm_psr_inhibit_get_state);

void rockchip_drm_psr_inhibit_put_state(struct drm_atomic_state *state)
{
	struct drm_crtc *crtc;
	struct drm_crtc_state *crtc_state;
	struct drm_encoder *encoder;
	u32 encoder_mask = 0;
	int i;

	for_each_old_crtc_in_state(state, crtc, crtc_state, i) {
		encoder_mask |= crtc_state->encoder_mask;
		encoder_mask |= crtc->state->encoder_mask;
	}

	drm_for_each_encoder_mask(encoder, state->dev, encoder_mask)
		rockchip_drm_psr_inhibit_put(encoder);
}
EXPORT_SYMBOL(rockchip_drm_psr_inhibit_put_state);

/**
 * rockchip_drm_psr_inhibit_get - acquire PSR inhibit on given encoder
 * @encoder: encoder to obtain the PSR encoder
 *
 * Increments PSR inhibit count on given encoder. This function guarantees
 * that after it returns PSR is turned off on given encoder and no PSR-related
 * hardware state change occurs at least until a matching call to
 * rockchip_drm_psr_inhibit_put() is done.
 *
 * Returns:
 * Zero on success, negative errno on failure.
 */
int rockchip_drm_psr_inhibit_get(struct drm_encoder *encoder)
{
	struct psr_drv *psr = find_psr_by_encoder(encoder);

	if (IS_ERR(psr))
		return PTR_ERR(psr);

	mutex_lock(&psr->lock);
	psr_set_state_locked(psr, false);
	++psr->inhibit_count;
	mutex_unlock(&psr->lock);
	cancel_delayed_work_sync(&psr->flush_work);

	return 0;
}
EXPORT_SYMBOL(rockchip_drm_psr_inhibit_get);

static void rockchip_drm_do_flush(struct psr_drv *psr)
{
	cancel_delayed_work_sync(&psr->flush_work);

	mutex_lock(&psr->lock);
	if (!psr_set_state_locked(psr, false))
		mod_delayed_work(system_wq, &psr->flush_work,
				 PSR_FLUSH_TIMEOUT_MS);
	mutex_unlock(&psr->lock);
}

/**
 * rockchip_drm_psr_flush_all - force to flush all registered PSR encoders
 * @dev: drm device
 *
 * Disable the PSR function for all registered encoders, and then enable the
 * PSR function back after PSR_FLUSH_TIMEOUT. If encoder PSR state have been
 * changed during flush time, then keep the state no change after flush
 * timeout.
 *
 * Returns:
 * Zero on success, negative errno on failure.
 */
void rockchip_drm_psr_flush_all(struct drm_device *dev)
{
	struct rockchip_drm_private *drm_drv = dev->dev_private;
	struct psr_drv *psr;

	mutex_lock(&drm_drv->psr_list_lock);
	list_for_each_entry(psr, &drm_drv->psr_list, list)
		rockchip_drm_do_flush(psr);
	mutex_unlock(&drm_drv->psr_list_lock);
}
EXPORT_SYMBOL(rockchip_drm_psr_flush_all);

/**
 * rockchip_drm_psr_register - register encoder to psr driver
 * @encoder: encoder that obtain the PSR function
 * @psr_set: call back to set PSR state
 *
 * The function returns with PSR inhibit counter initialized with one
 * and the caller (typically encoder driver) needs to call
 * rockchip_drm_psr_inhibit_put() when it becomes ready to accept PSR
 * enable request.
 *
 * Returns:
 * Zero on success, negative errno on failure.
 */
int rockchip_drm_psr_register(struct drm_encoder *encoder,
			int (*psr_set)(struct drm_encoder *, bool enable))
{
	struct rockchip_drm_private *drm_drv;
	struct psr_drv *psr;

	if (!encoder || !psr_set)
		return -EINVAL;

	drm_drv = encoder->dev->dev_private;

	psr = kzalloc(sizeof(struct psr_drv), GFP_KERNEL);
	if (!psr)
		return -ENOMEM;

	INIT_DELAYED_WORK(&psr->flush_work, psr_flush_handler);
	mutex_init(&psr->lock);

	psr->inhibit_count = 1;
	psr->enabled = false;
	psr->encoder = encoder;
	psr->set = psr_set;

	mutex_lock(&drm_drv->psr_list_lock);
	list_add_tail(&psr->list, &drm_drv->psr_list);
	mutex_unlock(&drm_drv->psr_list_lock);

	return 0;
}
EXPORT_SYMBOL(rockchip_drm_psr_register);

/**
 * rockchip_drm_psr_unregister - unregister encoder to psr driver
 * @encoder: encoder that obtain the PSR function
 * @psr_set: call back to set PSR state
 *
 * It is expected that the PSR inhibit counter is 1 when this function is
 * called, which corresponds to a state when related encoder has been
 * disconnected from any CRTCs and its driver called
 * rockchip_drm_psr_inhibit_get() to stop the PSR logic.
 *
 * Returns:
 * Zero on success, negative errno on failure.
 */
void rockchip_drm_psr_unregister(struct drm_encoder *encoder)
{
	struct rockchip_drm_private *drm_drv = encoder->dev->dev_private;
	struct psr_drv *psr, *n;

	mutex_lock(&drm_drv->psr_list_lock);
	list_for_each_entry_safe(psr, n, &drm_drv->psr_list, list) {
		if (psr->encoder == encoder) {
			/*
			 * Any other value would mean that the encoder
			 * is still in use.
			 */
			WARN_ON(psr->inhibit_count != 1);

			list_del(&psr->list);
			kfree(psr);
		}
	}
	mutex_unlock(&drm_drv->psr_list_lock);
}
EXPORT_SYMBOL(rockchip_drm_psr_unregister);