drm/sde: bridge chip error and smmu fault handling for recovery
This implementation adds the following support: 1. handle bridge chip error reported by driver to DBA. error reported to DBA will be send to recovery manager to recover the faulty bridge chip by performing a power cycle. 2. smmu fault error notification. 3. detection on errors in bridge chip driver. Change-Id: I86cc491e3cd25fdcf9b998a0d687baba0a06d836 Signed-off-by: Rahul Sharma <rahsha@codeaurora.org>
This commit is contained in:
parent
ac2c415caf
commit
faa4206fb7
7 changed files with 127 additions and 3 deletions
|
@ -16,6 +16,7 @@
|
||||||
#include "drm_edid.h"
|
#include "drm_edid.h"
|
||||||
#include "sde_kms.h"
|
#include "sde_kms.h"
|
||||||
#include "dba_bridge.h"
|
#include "dba_bridge.h"
|
||||||
|
#include "sde/sde_recovery_manager.h"
|
||||||
|
|
||||||
#undef pr_fmt
|
#undef pr_fmt
|
||||||
#define pr_fmt(fmt) "dba_bridge:[%s] " fmt, __func__
|
#define pr_fmt(fmt) "dba_bridge:[%s] " fmt, __func__
|
||||||
|
@ -36,6 +37,7 @@
|
||||||
* @num_of_input_lanes: Number of input lanes in case of DSI/LVDS
|
* @num_of_input_lanes: Number of input lanes in case of DSI/LVDS
|
||||||
* @pluggable: If it's pluggable
|
* @pluggable: If it's pluggable
|
||||||
* @panel_count: Number of panels attached to this display
|
* @panel_count: Number of panels attached to this display
|
||||||
|
* @client_info: bridge chip specific information for recovery manager
|
||||||
*/
|
*/
|
||||||
struct dba_bridge {
|
struct dba_bridge {
|
||||||
struct drm_bridge base;
|
struct drm_bridge base;
|
||||||
|
@ -52,12 +54,17 @@ struct dba_bridge {
|
||||||
bool pluggable;
|
bool pluggable;
|
||||||
u32 panel_count;
|
u32 panel_count;
|
||||||
bool cont_splash_enabled;
|
bool cont_splash_enabled;
|
||||||
|
struct recovery_client_info client_info;
|
||||||
};
|
};
|
||||||
#define to_dba_bridge(x) container_of((x), struct dba_bridge, base)
|
#define to_dba_bridge(x) container_of((x), struct dba_bridge, base)
|
||||||
|
|
||||||
|
static int _dba_bridge_recovery_callback(int err_code,
|
||||||
|
struct recovery_client_info *client_info);
|
||||||
|
|
||||||
static void _dba_bridge_cb(void *data, enum msm_dba_callback_event event)
|
static void _dba_bridge_cb(void *data, enum msm_dba_callback_event event)
|
||||||
{
|
{
|
||||||
struct dba_bridge *d_bridge = data;
|
struct dba_bridge *d_bridge = data;
|
||||||
|
int chip_err;
|
||||||
|
|
||||||
if (!d_bridge) {
|
if (!d_bridge) {
|
||||||
SDE_ERROR("Invalid data\n");
|
SDE_ERROR("Invalid data\n");
|
||||||
|
@ -73,6 +80,12 @@ static void _dba_bridge_cb(void *data, enum msm_dba_callback_event event)
|
||||||
case MSM_DBA_CB_HPD_DISCONNECT:
|
case MSM_DBA_CB_HPD_DISCONNECT:
|
||||||
DRM_DEBUG("HPD DISCONNECT\n");
|
DRM_DEBUG("HPD DISCONNECT\n");
|
||||||
break;
|
break;
|
||||||
|
case MSM_DBA_CB_DDC_I2C_ERROR:
|
||||||
|
case MSM_DBA_CB_DDC_TIMEOUT:
|
||||||
|
DRM_DEBUG("DDC FAILURE\n");
|
||||||
|
chip_err = DBA_BRIDGE_CRITICAL_ERR + d_bridge->id;
|
||||||
|
sde_recovery_set_events(chip_err);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
DRM_DEBUG("event:%d is not supported\n", event);
|
DRM_DEBUG("event:%d is not supported\n", event);
|
||||||
break;
|
break;
|
||||||
|
@ -83,6 +96,7 @@ static int _dba_bridge_attach(struct drm_bridge *bridge)
|
||||||
{
|
{
|
||||||
struct dba_bridge *d_bridge = to_dba_bridge(bridge);
|
struct dba_bridge *d_bridge = to_dba_bridge(bridge);
|
||||||
struct msm_dba_reg_info info;
|
struct msm_dba_reg_info info;
|
||||||
|
struct recovery_client_info *client_info = &d_bridge->client_info;
|
||||||
int ret = 0;
|
int ret = 0;
|
||||||
|
|
||||||
if (!bridge) {
|
if (!bridge) {
|
||||||
|
@ -115,6 +129,25 @@ static int _dba_bridge_attach(struct drm_bridge *bridge)
|
||||||
goto error;
|
goto error;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
snprintf(client_info->name, MAX_REC_NAME_LEN, "%s_%d",
|
||||||
|
d_bridge->chip_name, d_bridge->id);
|
||||||
|
|
||||||
|
client_info->recovery_cb = _dba_bridge_recovery_callback;
|
||||||
|
|
||||||
|
/* Identify individual chip by different error codes */
|
||||||
|
client_info->err_supported[0].reported_err_code =
|
||||||
|
DBA_BRIDGE_CRITICAL_ERR + d_bridge->id;
|
||||||
|
client_info->err_supported[0].pre_err_code = 0;
|
||||||
|
client_info->err_supported[0].post_err_code = 0;
|
||||||
|
client_info->no_of_err = 1;
|
||||||
|
/* bridge chip context */
|
||||||
|
client_info->pdata = d_bridge;
|
||||||
|
|
||||||
|
ret = sde_recovery_client_register(client_info);
|
||||||
|
if (ret)
|
||||||
|
SDE_ERROR("%s recovery mgr register failed %d\n",
|
||||||
|
__func__, ret);
|
||||||
|
|
||||||
DRM_INFO("client:%s bridge:[%s:%d] attached\n",
|
DRM_INFO("client:%s bridge:[%s:%d] attached\n",
|
||||||
d_bridge->client_name, d_bridge->chip_name, d_bridge->id);
|
d_bridge->client_name, d_bridge->chip_name, d_bridge->id);
|
||||||
|
|
||||||
|
@ -239,6 +272,44 @@ static void _dba_bridge_post_disable(struct drm_bridge *bridge)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int _dba_bridge_recovery_callback(int err_code,
|
||||||
|
struct recovery_client_info *client_info)
|
||||||
|
{
|
||||||
|
int rc = 0;
|
||||||
|
struct dba_bridge *d_bridge;
|
||||||
|
|
||||||
|
if (!client_info) {
|
||||||
|
SDE_ERROR("Invalid client info\n");
|
||||||
|
rc = -EINVAL;
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
d_bridge = client_info->pdata;
|
||||||
|
|
||||||
|
err_code = err_code - d_bridge->id;
|
||||||
|
|
||||||
|
switch (err_code) {
|
||||||
|
case DBA_BRIDGE_CRITICAL_ERR:
|
||||||
|
SDE_DEBUG("%s critical bridge chip error\n", __func__);
|
||||||
|
|
||||||
|
/* Power OFF */
|
||||||
|
_dba_bridge_disable(&d_bridge->base);
|
||||||
|
_dba_bridge_post_disable(&d_bridge->base);
|
||||||
|
|
||||||
|
/* settle power rails */
|
||||||
|
msleep(100);
|
||||||
|
|
||||||
|
/* Power On */
|
||||||
|
_dba_bridge_pre_enable(&d_bridge->base);
|
||||||
|
_dba_bridge_enable(&d_bridge->base);
|
||||||
|
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
SDE_ERROR("%s error %d undefined\n", __func__, err_code);
|
||||||
|
}
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
static void _dba_bridge_mode_set(struct drm_bridge *bridge,
|
static void _dba_bridge_mode_set(struct drm_bridge *bridge,
|
||||||
struct drm_display_mode *mode,
|
struct drm_display_mode *mode,
|
||||||
struct drm_display_mode *adjusted_mode)
|
struct drm_display_mode *adjusted_mode)
|
||||||
|
@ -369,6 +440,9 @@ void dba_bridge_cleanup(struct drm_bridge *bridge)
|
||||||
if (!bridge)
|
if (!bridge)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
sde_recovery_client_unregister(d_bridge->client_info.handle);
|
||||||
|
d_bridge->client_info.handle = NULL;
|
||||||
|
|
||||||
if (IS_ENABLED(CONFIG_MSM_DBA)) {
|
if (IS_ENABLED(CONFIG_MSM_DBA)) {
|
||||||
if (!IS_ERR_OR_NULL(d_bridge->dba_ctx))
|
if (!IS_ERR_OR_NULL(d_bridge->dba_ctx))
|
||||||
msm_dba_deregister_client(d_bridge->dba_ctx);
|
msm_dba_deregister_client(d_bridge->dba_ctx);
|
||||||
|
|
|
@ -93,4 +93,8 @@ static inline void msm_mmu_disable(struct msm_mmu *mmu)
|
||||||
int __init msm_smmu_driver_init(void);
|
int __init msm_smmu_driver_init(void);
|
||||||
void __exit msm_smmu_driver_cleanup(void);
|
void __exit msm_smmu_driver_cleanup(void);
|
||||||
|
|
||||||
|
/* register custom fault handler for a specific domain */
|
||||||
|
void msm_smmu_register_fault_handler(struct msm_mmu *mmu,
|
||||||
|
iommu_fault_handler_t handler);
|
||||||
|
|
||||||
#endif /* __MSM_MMU_H__ */
|
#endif /* __MSM_MMU_H__ */
|
||||||
|
|
|
@ -335,6 +335,18 @@ static struct device *msm_smmu_device_create(struct device *dev,
|
||||||
return &pdev->dev;
|
return &pdev->dev;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void msm_smmu_register_fault_handler(struct msm_mmu *mmu,
|
||||||
|
iommu_fault_handler_t handler)
|
||||||
|
{
|
||||||
|
struct msm_smmu *smmu = to_msm_smmu(mmu);
|
||||||
|
struct msm_smmu_client *client = msm_smmu_to_client(smmu);
|
||||||
|
|
||||||
|
if (client)
|
||||||
|
iommu_set_fault_handler(client->mmu_mapping->domain,
|
||||||
|
handler, client->dev);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
struct msm_mmu *msm_smmu_new(struct device *dev,
|
struct msm_mmu *msm_smmu_new(struct device *dev,
|
||||||
enum msm_mmu_domain_type domain)
|
enum msm_mmu_domain_type domain)
|
||||||
{
|
{
|
||||||
|
|
|
@ -67,7 +67,8 @@ static struct recovery_client_info info = {
|
||||||
.recovery_cb = sde_kms_recovery_callback,
|
.recovery_cb = sde_kms_recovery_callback,
|
||||||
.err_supported[0] = {SDE_UNDERRUN, 0, 0},
|
.err_supported[0] = {SDE_UNDERRUN, 0, 0},
|
||||||
.err_supported[1] = {SDE_VSYNC_MISS, 0, 0},
|
.err_supported[1] = {SDE_VSYNC_MISS, 0, 0},
|
||||||
.no_of_err = 2,
|
.err_supported[2] = {SDE_SMMU_FAULT, 0, 0},
|
||||||
|
.no_of_err = 3,
|
||||||
.handle = NULL,
|
.handle = NULL,
|
||||||
.pdata = NULL,
|
.pdata = NULL,
|
||||||
};
|
};
|
||||||
|
@ -1140,6 +1141,18 @@ static int _sde_kms_mmu_destroy(struct sde_kms *sde_kms)
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int sde_smmu_fault_handler(struct iommu_domain *iommu,
|
||||||
|
struct device *dev, unsigned long iova, int flags, void *arg)
|
||||||
|
{
|
||||||
|
|
||||||
|
dev_info(dev, "%s: iova=0x%08lx, flags=0x%x, iommu=%pK\n", __func__,
|
||||||
|
iova, flags, iommu);
|
||||||
|
|
||||||
|
sde_recovery_set_events(SDE_SMMU_FAULT);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
static int _sde_kms_mmu_init(struct sde_kms *sde_kms)
|
static int _sde_kms_mmu_init(struct sde_kms *sde_kms)
|
||||||
{
|
{
|
||||||
struct msm_mmu *mmu;
|
struct msm_mmu *mmu;
|
||||||
|
@ -1158,6 +1171,8 @@ static int _sde_kms_mmu_init(struct sde_kms *sde_kms)
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
msm_smmu_register_fault_handler(mmu, sde_smmu_fault_handler);
|
||||||
|
|
||||||
/* Attaching smmu means IOMMU HW starts to work immediately.
|
/* Attaching smmu means IOMMU HW starts to work immediately.
|
||||||
* However, display HW in LK is still accessing memory
|
* However, display HW in LK is still accessing memory
|
||||||
* while the memory map is not done yet.
|
* while the memory map is not done yet.
|
||||||
|
@ -1523,6 +1538,10 @@ static int sde_kms_recovery_callback(int err_code,
|
||||||
pr_debug("%s [SDE_VSYNC_MISS] trigger soft reset\n", __func__);
|
pr_debug("%s [SDE_VSYNC_MISS] trigger soft reset\n", __func__);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case SDE_SMMU_FAULT:
|
||||||
|
pr_debug("%s [SDE_SMMU_FAULT] trigger soft reset\n", __func__);
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
pr_err("%s error %d undefined\n", __func__, err_code);
|
pr_err("%s error %d undefined\n", __func__, err_code);
|
||||||
|
|
||||||
|
|
|
@ -29,11 +29,17 @@
|
||||||
|
|
||||||
#define MAX_REC_NAME_LEN (16)
|
#define MAX_REC_NAME_LEN (16)
|
||||||
#define MAX_REC_UEVENT_LEN (64)
|
#define MAX_REC_UEVENT_LEN (64)
|
||||||
#define MAX_REC_ERR_SUPPORT (2)
|
#define MAX_REC_ERR_SUPPORT (3)
|
||||||
|
|
||||||
/* MSM Recovery Manager Error Code */
|
/* MSM Recovery Manager Error Code */
|
||||||
|
#define SDE_SMMU_FAULT 111
|
||||||
#define SDE_UNDERRUN 222
|
#define SDE_UNDERRUN 222
|
||||||
#define SDE_VSYNC_MISS 333
|
#define SDE_VSYNC_MISS 333
|
||||||
|
/*
|
||||||
|
* instance id of bridge chip is added to make error code
|
||||||
|
* unique to individual bridge chip instance
|
||||||
|
*/
|
||||||
|
#define DBA_BRIDGE_CRITICAL_ERR 444
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* struct recovery_mgr_info - Recovery manager information
|
* struct recovery_mgr_info - Recovery manager information
|
||||||
|
|
|
@ -885,9 +885,13 @@ static void adv7533_handle_hdcp_intr(struct adv7533 *pdata, u8 hdcp_status)
|
||||||
break;
|
break;
|
||||||
case 4:
|
case 4:
|
||||||
pr_err("%s: DDC: I2C ERROR\n", __func__);
|
pr_err("%s: DDC: I2C ERROR\n", __func__);
|
||||||
|
adv7533_notify_clients(&pdata->dev_info,
|
||||||
|
MSM_DBA_CB_DDC_I2C_ERROR);
|
||||||
break;
|
break;
|
||||||
case 5:
|
case 5:
|
||||||
pr_err("%s: DDC: TIMED OUT DS DONE\n", __func__);
|
pr_err("%s: DDC: TIMED OUT DS DONE\n", __func__);
|
||||||
|
adv7533_notify_clients(&pdata->dev_info,
|
||||||
|
MSM_DBA_CB_DDC_TIMEOUT);
|
||||||
break;
|
break;
|
||||||
case 6:
|
case 6:
|
||||||
pr_err("%s: DDC: MAX CAS EXC\n", __func__);
|
pr_err("%s: DDC: MAX CAS EXC\n", __func__);
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2015, The Linux Foundation. All rights reserved.
|
* Copyright (c) 2015,2018, The Linux Foundation. All rights reserved.
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or modify
|
* This program is free software; you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License version 2 and
|
* it under the terms of the GNU General Public License version 2 and
|
||||||
|
@ -53,6 +53,9 @@
|
||||||
* @MSM_DBA_CB_POST_RESET: This callback is called after device reset is
|
* @MSM_DBA_CB_POST_RESET: This callback is called after device reset is
|
||||||
* complete and the driver has applied back all the
|
* complete and the driver has applied back all the
|
||||||
* properties.
|
* properties.
|
||||||
|
* @MSM_DBA_CB_DDC_I2C_ERROR: Detected a failure in DDC block for i2c error.
|
||||||
|
* @MSM_DBA_CB_DDC_TIMEOUT: Detected a failure in DDC block for timed out
|
||||||
|
* waiting for downstream receiver.
|
||||||
*
|
*
|
||||||
* Clients for this driver can register for receiving callbacks for specific
|
* Clients for this driver can register for receiving callbacks for specific
|
||||||
* events. This enum defines the type of events supported by the driver. An
|
* events. This enum defines the type of events supported by the driver. An
|
||||||
|
@ -71,6 +74,8 @@ enum msm_dba_callback_event {
|
||||||
MSM_DBA_CB_CEC_READ_PENDING = BIT(9),
|
MSM_DBA_CB_CEC_READ_PENDING = BIT(9),
|
||||||
MSM_DBA_CB_PRE_RESET = BIT(10),
|
MSM_DBA_CB_PRE_RESET = BIT(10),
|
||||||
MSM_DBA_CB_POST_RESET = BIT(11),
|
MSM_DBA_CB_POST_RESET = BIT(11),
|
||||||
|
MSM_DBA_CB_DDC_I2C_ERROR = BIT(12),
|
||||||
|
MSM_DBA_CB_DDC_TIMEOUT = BIT(13),
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
Loading…
Add table
Reference in a new issue